|
- import {EventCaller} from "/app/js/common/EventCaller.js";
- import Utils from "/app/js/common/Utils.js";
-
- // Keycode list based on...
- // https://keycode.info/
- var KEYBYCODE = {
- 3:"break",
- 8:"backspace",
- 9:"tab",
- 13:"enter",
- 16:"shift",
- 17:"ctrl",
- 18:"alt",
- 19:"pause",
- 20:"capslock",
- 27:"esc",
- 32:"space",
- 33:"pageup",
- 34:"pagedown",
- 35:"end",
- 36:"home",
- 37:"left",
- 38:"up",
- 39:"right",
- 40:"down",
- 41:"select",
- 42:"print",
- 43:"execute",
- 44:"printscreen",
- 45:"insert",
- 46:"delete",
- 47:"help",
- 48:"0",
- 49:"1",
- 50:"2",
- 51:"3",
- 52:"4",
- 53:"5",
- 54:"6",
- 55:"7",
- 56:"8",
- 57:"9",
- 65:"a",
- 66:"b",
- 67:"c",
- 68:"d",
- 69:"e",
- 70:"f",
- 71:"g",
- 72:"h",
- 73:"i",
- 74:"j",
- 75:"k",
- 76:"l",
- 77:"m",
- 78:"n",
- 79:"o",
- 80:"p",
- 81:"q",
- 82:"r",
- 83:"s",
- 84:"t",
- 85:"u",
- 86:"v",
- 87:"w",
- 88:"x",
- 89:"y",
- 90:"z",
- 91:"leftmod", // Window key (left)
- 92:"rightwin",// Window key (right)
- 93:"rightmod",// Window key (right)
- 96:"num0",
- 97:"num1",
- 98:"num2",
- 99:"num3",
- 100:"num4",
- 101:"num5",
- 102:"num6",
- 103:"num7",
- 104:"num8",
- 105:"num9",
- 112:"f1",
- 113:"f2",
- 114:"f3",
- 115:"f4",
- 116:"f5",
- 117:"f6",
- 118:"f7",
- 119:"f8",
- 120:"f9",
- 121:"f10",
- 122:"f11",
- 123:"f12",
- 144:"numlock",
- 145:"scrolllock",
- };
-
- var KEYBYNAME = (function(){
- var keys = Object.keys(KEYBYCODE);
- var o = {};
- for (var i=0; i < keys.length; i++){
- if (KEYBYCODE.hasOwnProperty(keys[i])){
- o[KEYBYCODE[keys[i]]] = keys[i];
- }
- }
- return o;
- })();
-
- var KEYTYPE = {
- "number":[48,49,50,51,52,53,54,55,56,57,96,97,98,99,100,101,102,103,104,105],
- "letter":[65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90],
- "mod":[16,17,18],
- "arrow":[37,38,39,40],
- "wasd":[87,65,83,68],
- "fn":[112,113,114,115,116,117,118,119,120,121,122,123],
- "n1":[48,96],
- "n2":[49,97],
- "n3":[50,98],
- "n4":[51,99],
- "n5":[52,100],
- "n6":[53,101],
- "n7":[54,102],
- "n8":[55,103],
- "n9":[56,104],
- "n0":[57,105]
- };
-
-
- var KEYMAP = {
- "lastcode":null,
- "lastaction":"",
- "currentcodes":[]
- };
-
-
- var MOUSEBYCODE = {
- 0: "mbl", // (M)ouse (B)utton (L)eft
- 1: "mbm", // (M)ouse (B)utton (M)iddle
- 2: "mbr" // (M)ouse (B)utton (R)ight
- };
-
- var MOUSEBYNAME = {
- "mbl": 0,
- "mbm": 1,
- "mbr": 2
- };
-
- // TODO: Reeval this idea.
- const KEYPRESS_DELAY = 350; // Time in milliseconds. NOTE: May make this a variable in future.
- const MOUSECLICK_DELAY = 350; // Time in milliseconds.
-
- function AssignCodeName(code, name){
- name = name.toLowerCase();
- var remove = (code in Object.keys(KEYBYCODE));
-
- if (name in Object.keys(KEYBYNAME)){
- if (remove && KEYBYCODE[code] === name){
- return; // We're being asked to replace the exact same thing. SKIP!
- }
- throw new ValueError("Key name '" + name + "' already assigned. Cannot use duplicate key names.");
- }
- if (remove){
- delete KEYBYNAME[KEYBYCODE[code]];
- }
- KEYBYCODE[code] = name;
- KEYBYNAME[name] = code;
- }
-
-
- function KeymapContains(code){
- return KEYMAP["currentcodes"].findIndex(c=>c[0] == code) >= 0;
- }
-
-
- function AddToKeymap(code, action){
- KEYMAP["lastcode"] = code;
- KEYMAP["lastaction"] = action;
- if (KeymapContains(code) == false){
- KEYMAP["currentcodes"].push([code, Math.floor(Date.now())]);
- if (KEYMAP["currentcodes"].length > 1){
- KEYMAP["currentcodes"].sort(function(a, b){return a[0] - b[0];});
- }
- return true;
- }
- return false;
- }
-
- function RemoveFromKeymap(code, action){
- KEYMAP["lastcode"] = code;
- KEYMAP["lastaction"] = action;
- var ctime = Math.floor(Date.now());
- for (var i=0; i < KEYMAP["currentcodes"].length; i++){
- if (KEYMAP["currentcodes"][i][0] === code){
- var timediff = ctime - KEYMAP["currentcodes"][i][1];
- KEYMAP["currentcodes"].splice(i, 1);
- return timediff;
- }
- }
- return -1;
- }
-
- function KeyNameToCode(key){
- return (key in Object.keys(KEYBYNAME)) ? KEYBYNAME[key] : -1;
- }
-
- function CodeToKeyName(code){
- return (code in Object.keys(KEYBYCODE)) ? KEYBYCODE[code] : "" + code;
- }
-
- function CodesToEventName(codes, mouse){
- var ename = "";
- mouse = (mouse === true);
- for (var i=0; i < codes.length; i++){
- if (mouse){
- switch(codes[i]){
- case 0:
- ename += ((ename !== "") ? "+" : "") + "mouseleft";
- break;
- case 1:
- ename += ((ename !== "") ? "+" : "") + "mouseright";
- break;
- case 2:
- ename += ((ename !== "") ? "+" : "") + "mousemiddle";
- break;
- case 8000:
- ename += ((ename !== "") ? "+" : "") + "mousemove";
- break;
- case 8001:
- ename += ((ename !== "") ? "+" : "") + "wheel";
- default:
- ename += ((ename !== "") ? "+" : "") + "mousebtn" + codes[i].toString();
- }
- } else {
- ename += ((ename !== "") ? "+" : "") + CodeToKeyName(codes[i]);
- }
- }
- return ename;
- }
-
- function KeymapEventName(){
- return CodesToEventName(KEYMAP["currentcodes"].map(e=>e[0]));
- }
-
-
- function ReorderEventName(ename){
- // This function takes a keyboard and mouse event name and reorders it into key-code order.
- // This way users can write the event any way they want, but should still result in proper
- // event being called.
- var elist = ename.split("+");
- // TODO: Need to test for duplicate event names for both keyboard and mouse event names.
- var ecodes = [];
- var mcodes = [];
- for (var i=0; i < elist.length; i++){
- var key = elist[i].trim().toLowerCase();
-
- // Check for mouse events first. These are hardcoded for now.
- if (key === "mouseleft"){
- mcodes.push(0);
- } else if (key === "mouseright"){
- mcodes.push(1);
- } else if (key === "mousemiddle"){
- mcodes.push(2);
- } else if (key.startsWith("mousebtn")){
- var sub = key.substring(8);
- if (!isNaN(sub)){
- mcodes.push(parseInt(sub));
- } else {
- return ""; // This event name does not include valid mouse button code.
- }
- } else if (key === "mousemove"){
- mcodes.push(8000);
- } else if (key === "wheel"){
- mcodes.push(8001);
- }
-
- // Now handle keyboard event names.
- else if (!(key in Object.keys(KEYBYNAME))){
- if (!isNaN(key))
- ecodes.push(parseInt(key));
- else
- return ""; // This event name does not include valid key name!
- } else {
- ecodes.push(KEYBYNAME[key]);
- }
- }
- if (ecodes.length > 0 || mcodes.length > 0){
- var rename = "";
- if (ecodes.length > 0){
- ecodes.sort(function(a, b){return a-b;});
- rename = CodesToEventName(ecodes);
- }
- if (mcodes.length > 0){
- mcodes.sort(function(a, b){return a-b;});
- rename += ((rename !== "") ? "+" : "") + CodesToEventName(mcodes, true);
- }
- return rename;
- }
- return "";
- }
-
-
- export default class Input{
- constructor(){
- this.__emitter = new EventCaller();
- this.__preventDefaults = false;
- // Internet Explorer... that fudged up p.o.s. has to make mouse button detection difficult
- // with different button values from ALL other browsers. So... if Input has this value set to
- // true, then mouseup and mousedown events will base it's detection of IE button values, instead
- // of the real values.
- this.__ieMouseMode = false;
- // If set, this is the element that the mouse will focus on and adjust it's position against.
- this.__mouseTarget = null;
- this.__mousePosition = null;
- this.__mouseLastButton = -1;
- this.__mouseLastAction = "";
- this.__mouseButtons = [];
- this.__mouseInBounds = false;
-
- this.__keyboardEnabled = false;
- this.__mouseEnabled = false;
-
-
- var buttonID = (function(e){
- var btn = e.button;
- if ((this.__ieMouseMode && btn === 1) || (!this.__ieMouseMode && btn === 0)){
- btn = 0;
- } else if ((this.__ieMouseMode && e.button === 4) || (!this.__ieMouseMode && e.button === 1)){
- btn = 1;
- }
- return btn;
- }).bind(this);
-
- var addMouseButton = (function(btn){
- if (this.__mouseButtons.findIndex(b=>b[0]===btn) < 0){
- this.__mouseButtons.push([btn, Math.floor(Date.now())]);
- return true;
- }
- return false;
- }).bind(this);
-
- var removeMouseButton = (function(btn){
- var idx = this.__mouseButtons.findIndex(b=>b[0]===btn);
- var diff = -1;
- if (idx >= 0){
- diff = Math.floor(Date.now()) - this.__mouseButtons[idx][1];
- this.__mouseButtons.splice(idx, 1);
- }
- return diff;
- }).bind(this);
-
- // ---------------------------------------------------------------------
- // Handling keyboard events.
-
- this.enableKeyboardInput = (function(){
- var handle_keydown = (function(e){
- if (AddToKeymap(e.keyCode, "keydown")){
- var ename = KeymapEventName();
- var edata = {
- source: this,
- iscombo: (ename.indexOf("+") >= 0),
- keys: ename,
- keycode:e.keyCode,
- keyname:CodeToKeyName(e.keyCode),
- action:"keydown"
- }
- this.__emitter.emit(ename, edata);
- this.__emitter.emit("keydown", edata);
- }
- }).bind(this);
-
- var handle_keyup = (function(e){
- var timediff = RemoveFromKeymap(e.keyCode, "keyup");
- if (timediff < 0){
- console.log("WARNING: Failed to find keycode '" + e.keyCode + "' in the Key Map.");
- } else {
- var ename = KeymapEventName();
- var edata = {
- source: this,
- iscombo: (ename.indexOf("+") >= 0),
- keys: ename,
- keycode: e.keyCode,
- keyname: CodeToKeyName(e.keyCode),
- action:"keyup"
- }
- if (timediff <= KEYPRESS_DELAY && KEYMAP["currentcodes"].length <= 0){
- this.__emitter.emit("keypress", edata);
- }
- this.__emitter.emit("keyup", edata);
- }
- }).bind(this);
-
- return (function(enable){
- enable = (enable !== false);
- // NOTE: There shouldn't be any harm if the user repeatedly enables or disables keyboard.
- if (enable){
- this.__keyboardEnabled = true;
- window.addEventListener("keydown", handle_keydown, false);
- window.addEventListener("keyup", handle_keyup, false);
- } else {
- this.__keyboardEnabled = false;
- window.removeEventListener("keydown", handle_keydown);
- window.removeEventListener("keyup", handle_keyup);
- }
- }).bind(this);
- }).apply(this);
-
-
- // ---------------------------------------------------------------------------------------
- // Handling mouse events.
-
- var MouseButtonsEventName = (function(){
- var e = "";
- for (var i=0; i < this.__mouseButtons.length; i++){
- e += (e !== "") ? "+" : "";
- switch (this.__mouseButtons[i][0]){
- case 0:
- e += "mouseleft";
- break;
- case 1:
- e += "mouseright";
- break;
- case 2:
- e += "mousemiddle";
- break;
- default:
- e += "mousebtn" + this.__mouseButtons[i][0].toString();
- }
- }
- return e;
- }).bind(this);
-
-
- // This function will only return an event name if keyboard
- var MouseEventName = (function(addon){
- var ename = KeymapEventName();
- var mname = MouseButtonsEventName();
- if (mname !== "")
- ename += ((ename !== "") ? "+" : "") + mname;
- if (typeof(addon) === "string")
- ename += ((ename !== "") ? "+" : "") + addon;
- return ename;
- }).bind(this);
-
- this.enableMouseInput = (function(){
- var mousePosition = (function(e){
- var pos = {
- lastX: (this.__mousePosition !== null) ? this.__mousePosition.x : null,
- lastY: (this.__mousePosition !== null) ? this.__mousePosition.y : null,
- x: e.clientX,
- y: e.clientY,
- inbounds: true
- }
- if (this.__mouseTarget !== null){
- var rect = this.__mouseTarget.getBoundingClientRect();
- pos.x -= rect.left;
- pos.y -= rect.top;
- pos.inbounds = (pos.x >= 0 && pos.x < rect.width && pos.y >= 0 && pos.y < rect.height);
- }
- pos.x = Math.floor(pos.x);
- pos.y = Math.floor(pos.y);
- this.__mouseInBounds = pos.inbounds;
- return pos;
- }).bind(this);
-
- var handle_mousemove = (function(e){
- var pos = mousePosition(e);
- if (pos.inbounds){
- if (this.__preventDefaults){
- e.preventDefault();
- if (e.stopPropagation)
- e.stopPropagation();
- e.cancelBubble = true;
- }
- this.__mousePosition = pos;
- this.__mouseLastAction = "mousemove";
- var ename = MouseEventName("mousemove");
- var data = {
- source: this,
- isCombo: (ename.indexOf("+") >= 0),
- lastX: pos.lastX,
- lastY: pos.lastY,
- x: pos.x,
- y: pos.y,
- button: this.__mouseLastButton,
- delta: 0,
- action: "mousemove"
- };
- if (ename !== "")
- this.__emitter.emit(ename, data);
- }
- return false;
- }).bind(this);
-
- var handle_mousedown = (function(e){
- var button = buttonID(e);
- var pos = mousePosition(e);
- if (pos.inbounds){
- if (this.__preventDefaults){
- e.preventDefault();
- if (e.stopPropagation)
- e.stopPropagation();
- e.cancelBubble = true;
- }
- if (addMouseButton(button)){
- var ename = MouseEventName();
- var data = {
- source: this,
- isCombo: (ename.indexOf("+") >= 0),
- lastX: pos.lastX,
- lastY: pos.lastY,
- x: pos.x,
- y: pos.y,
- button: button,
- delta: 0,
- action: "mousedown"
- };
- this.__mousePosition = pos;
- this.__mouseLastButton = button;
- this.__mouseLastAction = "mousedown";
- if (ename !== "")
- this.__emitter.emit(ename, data);
- this.__emitter.emit("mousedown", data);
- }
- }
- return false;
- }).bind(this);
-
- var handle_mouseup = (function(e){
- var button = buttonID(e);
- var pos = mousePosition(e);
- // NOTE: I still want to check for button removal, even before testing if an event should
- // fire, so that I don't have any phantom buttons listed as "pressed" in the mouseButtons list.
- var diff = removeMouseButton(button);
- if (pos.inbounds){
- if (this.__preventDefaults){
- e.preventDefault();
- if (e.stopPropagation)
- e.stopPropagation();
- e.cancelBubble = true;
- }
- if (diff >= 0){
- this.__mousePosition = pos;
- this.__mouseLastButton = button;
- this.__mouseLastAction = "mouseup";
- var data = {
- source: this,
- isCombo: false,
- lastX: pos.lastX,
- lastY: pos.lastY,
- x: pos.x,
- y: pos.y,
- button: button,
- delta: 0,
- action: "mouseup"
- }
- this.__emitter.emit("mouseup", data);
- if (diff <= MOUSECLICK_DELAY && this.__mouseButtons.length <= 0){
- this.__emitter.emit("mouseclick", data);
- }
- }
- }
- return false;
- }).bind(this);
-
- var handle_mousewheel = (function(e){
- var pos = mousePosition(e);
- if (pos.inbounds === true){
- if (this.__preventDefaults)
- e.preventDefault();
- var ename = MouseEventName("wheel");
- var data = {
- source: this,
- isCombo: (ename.indexOf("+") >= 0),
- lastX: pos.lastX,
- lastY: pos.lastY,
- x: pos.x,
- y: pos.y,
- button: this.__mouseLastButton,
- delta: Math.sign(e.deltaY),
- action: "wheel"
- };
- if (ename !== "wheel")
- this.__emitter.emit(ename, data);
- if (data.delta < 0)
- this.__emitter.emit("wheeldown", data);
- if (data.delta > 0)
- this.__emitter.emit("wheelup", data);
- this.__emitter.emit("wheel", data);
- }
- }).bind(this);
-
- // This event is purely for preventing Default behaviors on mouse events we're not using.
- var handle_mouseprevdef = (function(e){
- var pos = mousePosition(e);;
- if (this.__preventDefaults && (pos === null || pos.inbounds)){
- e.preventDefault();
- if (e.stopPropagation)
- e.stopPropagation();
- e.cancelBubble = true;
- }
- return false;
- }).bind(this);
-
- return (function(enable){
- enable = (enable !== false);
- // NOTE: There shouldn't be any harm if the user repeatedly enables or disables mouse.
- if (enable){
- this.__mouseEnabled = true;
- window.addEventListener("mousemove", handle_mousemove);
- window.addEventListener("mousedown", handle_mousedown);
- window.addEventListener("mouseup", handle_mouseup);
- window.addEventListener("mousewheel", handle_mousewheel); // For older browsers?
- window.addEventListener("wheel", handle_mousewheel);
- window.addEventListener("click", handle_mouseprevdef);
- window.addEventListener("dblclick", handle_mouseprevdef);
- window.addEventListener("contextmenu", handle_mouseprevdef);
- } else {
- this.__mouseEnabled = false;
- window.removeEventListener("mousemove", handle_mousemove);
- window.removeEventListener("mousedown", handle_mousedown);
- window.removeEventListener("mouseup", handle_mouseup);
- window.removeEventListener("mousewheel", handle_mousewheel); // For older browsers?
- window.removeEventListener("wheel", handle_mousewheel);
- window.removeEventListener("click", handle_mouseprevdef);
- window.removeEventListener("dblclick", handle_mouseprevdef);
- window.removeEventListener("contextmenu", handle_mouseprevdef);
- }
- }).bind(this);
- }).apply(this);
-
- this.enableKeyboardInput();
- this.enableMouseInput();
- }
-
- get mouseInputEnabled(){return this.__mouseEnabled;}
- get keyboardInputEnabled(){return this.__keyboardEnabled;}
-
- get lastkey(){
- if (KEYMAP["lastcode"] !== null){
- if (KEYMAP["lastcode"] in Object.keys(KEYBYCODE)){
- return KEYBYCODE[KEYMAP["lastcode"]];
- }
- return "" + KEYMAP["lastcode"];
- }
- return "0";
- }
-
- get lastkeyaction(){
- return KEYMAP["lastaction"];
- }
-
- get currentKeys(){
- return KeymapEventName();
- }
-
- get currentKeyCodes(){
- return KEYMAP["currentcodes"].map(e=>e[0]);
- }
-
- get lastMouseAction(){
- return this.__mouseLastAction;
- }
-
- get lastMouseButton(){
- return this.__mouseLastButton;
- }
-
- get lastMousePosition(){
- if (this.__mousePosition === null || this.__mousePosition.lastX === null)
- return null;
- return {
- x: this.__mousePosition.lastX,
- y: this.__mousePosition.lastY
- };
- }
-
- get currentMousePosition(){
- if (this.__mousePosition === null)
- return null;
- return {
- x: this.__mousePosition.x,
- y: this.__mousePosition.y
- };
- }
-
- get preventDefaults(){return this.__preventDefaults;}
- set preventDefaults(p){
- this.__preventDefaults = (p === true);
- }
-
- get ieMouseMode(){return this.__ieMouseMode;}
- set ieMouseMode(m){this.__ieMouseMode = (m === true);}
-
- get mouseTargetElement(){return this.__mouseTarget;}
- set mouseTargetElement(el){
- if (el === null || Utils.isElement(el)){
- this.__mouseTarget = el;
- } else {
- throw new TypeError("Expected Mouse Target Element to be null or an HTMLElement object.");
- }
- }
-
- isKeyDown(key){
- if (typeof(key) === 'string'){
- key = KeyNameToCode(key);
- }
- for (var i=0; i < KEYMAP["currentcodes"].length; i++){
- if (KEYMAP["currentcodes"][i][0] === key){
- return true;
- }
- }
- return false;
- }
-
- isMouseInBounds(){return this.__mouseInBounds;}
- lastMousePosition(){
- return [this.__mousePosition.x, this.__mousePosition.y];
- }
-
- listen(ename, func, owner=null, once=false){
- if (([
- "keyup",
- "keydown",
- "keypress",
- "mousemove",
- "mousedown",
- "mouseup",
- "mouseclick",
- "wheel",
- "wheelup",
- "wheeldown"
- ]).indexOf(ename) >= 0){
- this.__emitter.listen(ename, func, owner, once);
- } else {
- ename = ReorderEventName(ename);
- if (ename === ""){
- throw new ValueError("Failed to parse key or key combination.");
- }
- this.__emitter.listen(ename, func, owner, once);
- }
- return this;
- }
-
- unlisten(ename, func, owner=null){
- if (([
- "keyup",
- "keydown",
- "keypress",
- "mousemove",
- "mousedown",
- "mouseup",
- "mouseclick",
- "wheel",
- "wheelup",
- "wheeldown"
- ]).indexOf(ename) >= 0){
- this.__emitter.unlisten(ename, func, owner);
- } else {
- ename = ReorderEventName(ename);
- if (ename !== "")
- this.__emitter.unlisten(ename, func, owner);
- }
- return this;
- }
- }
-
-
|