UNPKG

alloyfinger

Version:

super tiny size multi-touch gestures library for the web.

1,161 lines (1,125 loc) 171 kB
/* Alloy Game Engine * By AlloyTeam http://www.alloyteam.com/ * Github: https://github.com/AlloyTeam/AlloyGameEngine * MIT Licensed. */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof exports === 'object') { module.exports = factory(); } else { root.AlloyPaper = factory(); } }(this, function () { 'use strict'; // The base Class implementation (does nothing) var Class = function () { }; // Create a new Class that inherits from this class Class.extend = function (prop) { var _super = this.prototype; var prototype = Object.create(_super); // Copy the properties over onto the new prototype for (var name in prop) { if (name != "statics") { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" ? (function (temp_name, fn) { return function () { var tmp = this._super; // Add a new ._super() method that is the same method // but on the super-class this._super = _super[temp_name]; // The method only need to be bound temporarily, so we // remove it when we're done executing var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } } // The dummy class constructor function _Class() { // All construction is actually done in the init method this.ctor.apply(this, arguments); } //继承父类的静态属性 for (var key in this) { if (this.hasOwnProperty(key) && key != "extend") _Class[key] = this[key]; } // Populate our constructed prototype object _Class.prototype = prototype; _Class.prototype._super = Object.create(_super); //静态属性和方法 if (prop.statics) { for (var key in prop.statics) { if (prop.statics.hasOwnProperty(key)) { _Class[key] = prop.statics[key]; if (key == "ctor") { //提前执行静态构造函数 _Class[key](); } } } } // Enforce the constructor to be what we expect _Class.prototype.constructor = _Class; // And make this class extendable _Class.extend = Class.extend; return _Class; }; window.Class = Class; //AlloyPaper var AlloyPaper={}; AlloyPaper.DefaultCursor = "default"; AlloyPaper.Cache = {}; AlloyPaper.TWEEN = Class.extend({ "statics": { "ctor": function() { if (Date.now === undefined) { Date.now = function() { return new Date().valueOf(); }; } this._tweens = []; }, "REVISION": "14", "getAll": function() { return this._tweens; }, "removeAll": function() { this._tweens = []; }, "add": function(tween) { this._tweens.push(tween); }, "remove": function(tween) { var i = this._tweens.indexOf(tween); if (i !== -1) { this._tweens.splice(i, 1); } }, "update": function(time) { if (this._tweens.length === 0) return false; var i = 0; time = time !== undefined ? time : typeof window !== "undefined" && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now(); while (i < this._tweens.length) { if (this._tweens[i].update(time)) { i++; } else { this._tweens.splice(i, 1); } } return true; }, "Tween": function(object) { var _object = object; var _valuesStart = {}; var _valuesEnd = {}; var _valuesStartRepeat = {}; var _duration = 1e3; var _repeat = 0; var _yoyo = false; var _isPlaying = false; var _reversed = false; var _delayTime = 0; var _startTime = null; var _easingFunction = AlloyPaper.TWEEN.Easing.Linear.None; var _interpolationFunction = AlloyPaper.TWEEN.Interpolation.Linear; var _chainedTweens = []; var _onStartCallback = null; var _onStartCallbackFired = false; var _onUpdateCallback = null; var _onCompleteCallback = null; var _onStopCallback = null; var _paused = false, _passTime = null; for (var field in object) { _valuesStart[field] = parseFloat(object[field], 10); } this.toggle = function() { if (_paused) { this.play(); } else { this.pause(); } }; this.pause = function() { _paused = true; var pauseTime = typeof window !== "undefined" && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now(); _passTime = pauseTime - _startTime; }; this.play = function() { _paused = false; var nowTime = typeof window !== "undefined" && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now(); _startTime = nowTime - _passTime; }; this.to = function(properties, duration) { if (duration !== undefined) { _duration = duration; } _valuesEnd = properties; return this; }; this.start = function(time) { AlloyPaper.TWEEN.add(this); _isPlaying = true; _onStartCallbackFired = false; _startTime = time !== undefined ? time : typeof window !== "undefined" && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now(); _startTime += _delayTime; for (var property in _valuesEnd) { if (_valuesEnd[property] instanceof Array) { if (_valuesEnd[property].length === 0) { continue; } _valuesEnd[property] = [_object[property]].concat(_valuesEnd[property]); } _valuesStart[property] = _object[property]; if (_valuesStart[property] instanceof Array === false) { _valuesStart[property] *= 1; } _valuesStartRepeat[property] = _valuesStart[property] || 0; } return this; }; this.stop = function() { if (!_isPlaying) { return this; } AlloyPaper.TWEEN.remove(this); _isPlaying = false; if (_onStopCallback !== null) { _onStopCallback.call(_object); } this.stopChainedTweens(); return this; }; this.stopChainedTweens = function() { for (var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++) { _chainedTweens[i].stop(); } }; this.delay = function(amount) { _delayTime = amount; return this; }; this.repeat = function(times) { _repeat = times; return this; }; this.yoyo = function(yoyo) { _yoyo = yoyo; return this; }; this.easing = function(easing) { _easingFunction = easing; return this; }; this.interpolation = function(interpolation) { _interpolationFunction = interpolation; return this; }; this.chain = function() { _chainedTweens = arguments; return this; }; this.onStart = function(callback) { _onStartCallback = callback; return this; }; this.onUpdate = function(callback) { _onUpdateCallback = callback; return this; }; this.onComplete = function(callback) { _onCompleteCallback = callback; return this; }; this.onStop = function(callback) { _onStopCallback = callback; return this; }; this.update = function(time) { if (_paused) return true; var property; if (time < _startTime) { return true; } if (_onStartCallbackFired === false) { if (_onStartCallback !== null) { _onStartCallback.call(_object); } _onStartCallbackFired = true; } var elapsed = (time - _startTime) / _duration; elapsed = elapsed > 1 ? 1 : elapsed; var value = _easingFunction(elapsed); for (property in _valuesEnd) { var start = _valuesStart[property] || 0; var end = _valuesEnd[property]; if (end instanceof Array) { _object[property] = _interpolationFunction(end, value); } else { if (typeof end === "string") { end = start + parseFloat(end, 10); } if (typeof end === "number") { _object[property] = start + (end - start) * value; } } } if (_onUpdateCallback !== null) { _onUpdateCallback.call(_object, value); } if (elapsed == 1) { if (_repeat > 0) { if (isFinite(_repeat)) { _repeat--; } for (property in _valuesStartRepeat) { if (typeof _valuesEnd[property] === "string") { _valuesStartRepeat[property] = _valuesStartRepeat[property] + parseFloat(_valuesEnd[property], 10); } if (_yoyo) { var tmp = _valuesStartRepeat[property]; _valuesStartRepeat[property] = _valuesEnd[property]; _valuesEnd[property] = tmp; } _valuesStart[property] = _valuesStartRepeat[property]; } if (_yoyo) { _reversed = !_reversed; } _startTime = time + _delayTime; return true; } else { if (_onCompleteCallback !== null) { _onCompleteCallback.call(_object); } for (var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++) { _chainedTweens[i].start(time); } return false; } } return true; }; }, "Easing": { "Linear": { "None": function(k) { return k; } }, "Quadratic": { "In": function(k) { return k * k; }, "Out": function(k) { return k * (2 - k); }, "InOut": function(k) { if ((k *= 2) < 1) return.5 * k * k; return -.5 * (--k * (k - 2) - 1); } }, "Cubic": { "In": function(k) { return k * k * k; }, "Out": function(k) { return --k * k * k + 1; }, "InOut": function(k) { if ((k *= 2) < 1) return.5 * k * k * k; return.5 * ((k -= 2) * k * k + 2); } }, "Quartic": { "In": function(k) { return k * k * k * k; }, "Out": function(k) { return 1 - --k * k * k * k; }, "InOut": function(k) { if ((k *= 2) < 1) return.5 * k * k * k * k; return -.5 * ((k -= 2) * k * k * k - 2); } }, "Quintic": { "In": function(k) { return k * k * k * k * k; }, "Out": function(k) { return --k * k * k * k * k + 1; }, "InOut": function(k) { if ((k *= 2) < 1) return.5 * k * k * k * k * k; return.5 * ((k -= 2) * k * k * k * k + 2); } }, "Sinusoidal": { "In": function(k) { return 1 - Math.cos(k * Math.PI / 2); }, "Out": function(k) { return Math.sin(k * Math.PI / 2); }, "InOut": function(k) { return.5 * (1 - Math.cos(Math.PI * k)); } }, "Exponential": { "In": function(k) { return k === 0 ? 0 : Math.pow(1024, k - 1); }, "Out": function(k) { return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); }, "InOut": function(k) { if (k === 0) return 0; if (k === 1) return 1; if ((k *= 2) < 1) return.5 * Math.pow(1024, k - 1); return.5 * (-Math.pow(2, -10 * (k - 1)) + 2); } }, "Circular": { "In": function(k) { return 1 - Math.sqrt(1 - k * k); }, "Out": function(k) { return Math.sqrt(1 - --k * k); }, "InOut": function(k) { if ((k *= 2) < 1) return -.5 * (Math.sqrt(1 - k * k) - 1); return.5 * (Math.sqrt(1 - (k -= 2) * k) + 1); } }, "Elastic": { "In": function(k) { var s, a = .1, p = .4; if (k === 0) return 0; if (k === 1) return 1; if (!a || a < 1) { a = 1; s = p / 4; } else s = p * Math.asin(1 / a) / (2 * Math.PI); return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p)); }, "Out": function(k) { var s, a = .1, p = .4; if (k === 0) return 0; if (k === 1) return 1; if (!a || a < 1) { a = 1; s = p / 4; } else s = p * Math.asin(1 / a) / (2 * Math.PI); return a * Math.pow(2, -10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1; }, "InOut": function(k) { var s, a = .1, p = .4; if (k === 0) return 0; if (k === 1) return 1; if (!a || a < 1) { a = 1; s = p / 4; } else s = p * Math.asin(1 / a) / (2 * Math.PI); if ((k *= 2) < 1) return -.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p)); return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * .5 + 1; } }, "Back": { "In": function(k) { var s = 1.70158; return k * k * ((s + 1) * k - s); }, "Out": function(k) { var s = 1.70158; return --k * k * ((s + 1) * k + s) + 1; }, "InOut": function(k) { var s = 1.70158 * 1.525; if ((k *= 2) < 1) return.5 * (k * k * ((s + 1) * k - s)); return.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2); } }, "Bounce": { "In": function(k) { return 1 - AlloyPaper.TWEEN.Easing.Bounce.Out(1 - k); }, "Out": function(k) { if (k < 1 / 2.75) { return 7.5625 * k * k; } else if (k < 2 / 2.75) { return 7.5625 * (k -= 1.5 / 2.75) * k + .75; } else if (k < 2.5 / 2.75) { return 7.5625 * (k -= 2.25 / 2.75) * k + .9375; } else { return 7.5625 * (k -= 2.625 / 2.75) * k + .984375; } }, "InOut": function(k) { if (k < .5) return AlloyPaper.TWEEN.Easing.Bounce.In(k * 2) * .5; return AlloyPaper.TWEEN.Easing.Bounce.Out(k * 2 - 1) * .5 + .5; } } }, "Interpolation": { "Linear": function(v, k) { var m = v.length - 1, f = m * k, i = Math.floor(f), fn = AlloyPaper.TWEEN.Interpolation.Utils.Linear; if (k < 0) return fn(v[0], v[1], f); if (k > 1) return fn(v[m], v[m - 1], m - f); return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); }, "Bezier": function(v, k) { var b = 0, n = v.length - 1, pw = Math.pow, bn = AlloyPaper.TWEEN.Interpolation.Utils.Bernstein, i; for (i = 0; i <= n; i++) { b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); } return b; }, "CatmullRom": function(v, k) { var m = v.length - 1, f = m * k, i = Math.floor(f), fn = AlloyPaper.TWEEN.Interpolation.Utils.CatmullRom; if (v[0] === v[m]) { if (k < 0) i = Math.floor(f = m * (1 + k)); return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); } else { if (k < 0) return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); if (k > 1) return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); } }, "Utils": { "Linear": function(p0, p1, t) { return (p1 - p0) * t + p0; }, "Bernstein": function(n, i) { var fc = AlloyPaper.TWEEN.Interpolation.Utils.getFactorial(); return fc(n) / fc(i) / fc(n - i); }, "getFactorial": function() { return function() { var a = [1]; return function(n) { var s = 1, i; if (a[n]) return a[n]; for (i = n; i > 1; i--) s *= i; return a[n] = s; }; }(); }, "CatmullRom": function(p0, p1, p2, p3, t) { var v0 = (p2 - p0) * .5, v1 = (p3 - p1) * .5, t2 = t * t, t3 = t * t2; return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; } } } } }); //begin-------------------AlloyPaper.Dom---------------------begin AlloyPaper.Dom = Class.extend({ "statics": { "get": function(selector) { this.element = document.querySelector(selector); return this; }, "on": function(type, fn) { this.element.addEventListener(type, fn, false); return this; } } }); //end-------------------AlloyPaper.Dom---------------------end //begin-------------------AlloyPaper.FPS---------------------begin AlloyPaper.FPS = Class.extend({ "statics": { "get": function() { if (!this.instance) this.instance = new this(); this.instance._computeFPS(); return this.instance; } }, "ctor": function() { this.last = new Date(); this.current = null; this.lastMeasured=new Date(); this.fpsList = []; this.totalValue = 0; this.value = 60; }, "_computeFPS": function() { this.current = new Date(); if (this.current - this.last > 0) { var fps = Math.ceil(1e3 / (this.current - this.last)); this.fpsList.push(fps); this.totalValue += fps; this.last = this.current; } if (this.current - this.lastMeasured > 1000) { this.value =Math.ceil( this.totalValue / this.fpsList.length); this.totalValue = 0; this.fpsList.length = 0; this.lastMeasured = this.current; } } }); //end-------------------AlloyPaper.FPS---------------------end AlloyPaper.Keyboard = Class.extend({ "statics": { "ctor": function() { var KeyboardJS = {}, locales = {}, locale, map, macros, activeKeys = [], bindings = [], activeBindings = [], activeMacros = [], aI, usLocale; usLocale = { map: { "3": ["cancel"], "8": ["backspace"], "9": ["tab"], "12": ["clear"], "13": ["enter"], "16": ["shift"], "17": ["ctrl"], "18": ["alt", "menu"], "19": ["pause", "break"], "20": ["capslock"], "27": ["escape", "esc"], "32": ["space", "spacebar"], "33": ["pageup"], "34": ["pagedown"], "35": ["end"], "36": ["home"], "37": ["left"], "38": ["up"], "39": ["right"], "40": ["down"], "41": ["select"], "42": ["printscreen"], "43": ["execute"], "44": ["snapshot"], "45": ["insert", "ins"], "46": ["delete", "del"], "47": ["help"], "91": ["command", "windows", "win", "super", "leftcommand", "leftwindows", "leftwin", "leftsuper"], "92": ["command", "windows", "win", "super", "rightcommand", "rightwindows", "rightwin", "rightsuper"], "145": ["scrolllock", "scroll"], "186": ["semicolon", ";"], "187": ["equal", "equalsign", "="], "188": ["comma", ","], "189": ["dash", "-"], "190": ["period", "."], "191": ["slash", "forwardslash", "/"], "192": ["graveaccent", "`"], "219": ["openbracket", "["], "220": ["backslash", "\\"], "221": ["closebracket", "]"], "222": ["apostrophe", "'"], "48": ["zero", "0"], "49": ["one", "1"], "50": ["two", "2"], "51": ["three", "3"], "52": ["four", "4"], "53": ["five", "5"], "54": ["six", "6"], "55": ["seven", "7"], "56": ["eight", "8"], "57": ["nine", "9"], "96": ["numzero", "num0"], "97": ["numone", "num1"], "98": ["numtwo", "num2"], "99": ["numthree", "num3"], "100": ["numfour", "num4"], "101": ["numfive", "num5"], "102": ["numsix", "num6"], "103": ["numseven", "num7"], "104": ["numeight", "num8"], "105": ["numnine", "num9"], "106": ["nummultiply", "num*"], "107": ["numadd", "num+"], "108": ["numenter"], "109": ["numsubtract", "num-"], "110": ["numdecimal", "num."], "111": ["numdivide", "num/"], "144": ["numlock", "num"], "112": ["f1"], "113": ["f2"], "114": ["f3"], "115": ["f4"], "116": ["f5"], "117": ["f6"], "118": ["f7"], "119": ["f8"], "120": ["f9"], "121": ["f10"], "122": ["f11"], "123": ["f12"] }, macros: [["shift + `", ["tilde", "~"]], ["shift + 1", ["exclamation", "exclamationpoint", "!"]], ["shift + 2", ["at", "@"]], ["shift + 3", ["number", "#"]], ["shift + 4", ["dollar", "dollars", "dollarsign", "$"]], ["shift + 5", ["percent", "%"]], ["shift + 6", ["caret", "^"]], ["shift + 7", ["ampersand", "and", "&"]], ["shift + 8", ["asterisk", "*"]], ["shift + 9", ["openparen", "("]], ["shift + 0", ["closeparen", ")"]], ["shift + -", ["underscore", "_"]], ["shift + =", ["plus", "+"]], ["shift + (", ["opencurlybrace", "opencurlybracket", "{"]], ["shift + )", ["closecurlybrace", "closecurlybracket", "}"]], ["shift + \\", ["verticalbar", "|"]], ["shift + ;", ["colon", ":"]], ["shift + '", ["quotationmark", '"']], ["shift + !,", ["openanglebracket", "<"]], ["shift + .", ["closeanglebracket", ">"]], ["shift + /", ["questionmark", "?"]]] }; for (aI = 65; aI <= 90; aI += 1) { usLocale.map[aI] = String.fromCharCode(aI + 32); usLocale.macros.push(["shift + " + String.fromCharCode(aI + 32) + ", capslock + " + String.fromCharCode(aI + 32), [String.fromCharCode(aI)]]); } registerLocale("us", usLocale); getSetLocale("us"); enable(); KeyboardJS.enable = enable; KeyboardJS.disable = disable; KeyboardJS.activeKeys = getActiveKeys; KeyboardJS.releaseKey = removeActiveKey; KeyboardJS.pressKey = addActiveKey; KeyboardJS.on = createBinding; KeyboardJS.clear = removeBindingByKeyCombo; KeyboardJS.clear.key = removeBindingByKeyName; KeyboardJS.locale = getSetLocale; KeyboardJS.locale.register = registerLocale; KeyboardJS.macro = createMacro; KeyboardJS.macro.remove = removeMacro; KeyboardJS.key = {}; KeyboardJS.key.name = getKeyName; KeyboardJS.key.code = getKeyCode; KeyboardJS.combo = {}; KeyboardJS.combo.active = isSatisfiedCombo; KeyboardJS.combo.parse = parseKeyCombo; KeyboardJS.combo.stringify = stringifyKeyCombo; function enable() { if (window.addEventListener) { window.document.addEventListener("keydown", keydown, false); window.document.addEventListener("keyup", keyup, false); window.addEventListener("blur", reset, false); window.addEventListener("webkitfullscreenchange", reset, false); window.addEventListener("mozfullscreenchange", reset, false); } else if (window.attachEvent) { window.document.attachEvent("onkeydown", keydown); window.document.attachEvent("onkeyup", keyup); window.attachEvent("onblur", reset); } } function disable() { reset(); if (window.removeEventListener) { window.document.removeEventListener("keydown", keydown, false); window.document.removeEventListener("keyup", keyup, false); window.removeEventListener("blur", reset, false); window.removeEventListener("webkitfullscreenchange", reset, false); window.removeEventListener("mozfullscreenchange", reset, false); } else if (window.detachEvent) { window.document.detachEvent("onkeydown", keydown); window.document.detachEvent("onkeyup", keyup); window.detachEvent("onblur", reset); } } function reset(event) { activeKeys = []; pruneMacros(); pruneBindings(event); } function keydown(event) { var keyNames, keyName, kI; keyNames = getKeyName(event.keyCode); if (keyNames.length < 1) { return; } event.isRepeat = false; for (kI = 0; kI < keyNames.length; kI += 1) { keyName = keyNames[kI]; if (getActiveKeys().indexOf(keyName) != -1) event.isRepeat = true; addActiveKey(keyName); } executeMacros(); executeBindings(event); } function keyup(event) { var keyNames, kI; keyNames = getKeyName(event.keyCode); if (keyNames.length < 1) { return; } for (kI = 0; kI < keyNames.length; kI += 1) { removeActiveKey(keyNames[kI]); } pruneMacros(); pruneBindings(event); } function getKeyName(keyCode) { return map[keyCode] || []; } function getKeyCode(keyName) { var keyCode; for (keyCode in map) { if (!map.hasOwnProperty(keyCode)) { continue; } if (map[keyCode].indexOf(keyName) > -1) { return keyCode; } } return false; } function createMacro(combo, injectedKeys) { if (typeof combo !== "string" && (typeof combo !== "object" || typeof combo.push !== "function")) { throw new Error("Cannot create macro. The combo must be a string or array."); } if (typeof injectedKeys !== "object" || typeof injectedKeys.push !== "function") { throw new Error("Cannot create macro. The injectedKeys must be an array."); } macros.push([combo, injectedKeys]); } function removeMacro(combo) { var macro, mI; if (typeof combo !== "string" && (typeof combo !== "object" || typeof combo.push !== "function")) { throw new Error("Cannot remove macro. The combo must be a string or array."); } for (mI = 0; mI < macros.length; mI += 1) { macro = macros[mI]; if (compareCombos(combo, macro[0])) { removeActiveKey(macro[1]); macros.splice(mI, 1); break; } } } function executeMacros() { var mI, combo, kI; for (mI = 0; mI < macros.length; mI += 1) { combo = parseKeyCombo(macros[mI][0]); if (activeMacros.indexOf(macros[mI]) === -1 && isSatisfiedCombo(combo)) { activeMacros.push(macros[mI]); for (kI = 0; kI < macros[mI][1].length; kI += 1) { addActiveKey(macros[mI][1][kI]); } } } } function pruneMacros() { var mI, combo, kI; for (mI = 0; mI < activeMacros.length; mI += 1) { combo = parseKeyCombo(activeMacros[mI][0]); if (isSatisfiedCombo(combo) === false) { for (kI = 0; kI < activeMacros[mI][1].length; kI += 1) { removeActiveKey(activeMacros[mI][1][kI]); } activeMacros.splice(mI, 1); mI -= 1; } } } function createBinding(keyCombo, keyDownCallback, keyUpCallback) { var api = {}, binding, subBindings = [], bindingApi = {}, kI, subCombo; if (typeof keyCombo === "string") { keyCombo = parseKeyCombo(keyCombo); } for (kI = 0; kI < keyCombo.length; kI += 1) { binding = {}; subCombo = stringifyKeyCombo([keyCombo[kI]]); if (typeof subCombo !== "string") { throw new Error("Failed to bind key combo. The key combo must be string."); } binding.keyCombo = subCombo; binding.keyDownCallback = []; binding.keyUpCallback = []; if (keyDownCallback) { binding.keyDownCallback.push(keyDownCallback); } if (keyUpCallback) { binding.keyUpCallback.push(keyUpCallback); } bindings.push(binding); subBindings.push(binding); } api.clear = clear; api.on = on; return api; function clear() { var bI; for (bI = 0; bI < subBindings.length; bI += 1) { bindings.splice(bindings.indexOf(subBindings[bI]), 1); } } function on(eventName) { var api = {}, callbacks, cI, bI; if (typeof eventName !== "string") { throw new Error("Cannot bind callback. The event name must be a string."); } if (eventName !== "keyup" && eventName !== "keydown") { throw new Error('Cannot bind callback. The event name must be a "keyup" or "keydown".'); } callbacks = Array.prototype.slice.apply(arguments, [1]); for (cI = 0; cI < callbacks.length; cI += 1) { if (typeof callbacks[cI] === "function") { if (eventName === "keyup") { for (bI = 0; bI < subBindings.length; bI += 1) { subBindings[bI].keyUpCallback.push(callbacks[cI]); } } else if (eventName === "keydown") { for (bI = 0; bI < subBindings.length; bI += 1) { subBindings[bI].keyDownCallback.push(callbacks[cI]); } } } } api.clear = clear; return api; function clear() { var cI, bI; for (cI = 0; cI < callbacks.length; cI += 1) { if (typeof callbacks[cI] === "function") { if (eventName === "keyup") { for (bI = 0; bI < subBindings.length; bI += 1) { subBindings[bI].keyUpCallback.splice(subBindings[bI].keyUpCallback.indexOf(callbacks[cI]), 1); } } else { for (bI = 0; bI < subBindings.length; bI += 1) { subBindings[bI].keyDownCallback.splice(subBindings[bI].keyDownCallback.indexOf(callbacks[cI]), 1); } } } } } } } function removeBindingByKeyCombo(keyCombo) { var bI, binding, keyName; for (bI = 0; bI < bindings.length; bI += 1) { binding = bindings[bI]; if (compareCombos(keyCombo, binding.keyCombo)) { bindings.splice(bI, 1); bI -= 1; } } } function removeBindingByKeyName(keyName) { var bI, kI, binding; if (keyName) { for (bI = 0; bI < bindings.length; bI += 1) { binding = bindings[bI]; for (kI = 0; kI < binding.keyCombo.length; kI += 1) { if (binding.keyCombo[kI].indexOf(keyName) > -1) { bindings.splice(bI, 1); bI -= 1; break; } } } } else { bindings = []; } } function executeBindings(event) { var bI, sBI, binding, bindingKeys, remainingKeys, cI, killEventBubble, kI, bindingKeysSatisfied, index, sortedBindings = [], bindingWeight; remainingKeys = [].concat(activeKeys); for (bI = 0; bI < bindings.length; bI += 1) { bindingWeight = extractComboKeys(bindings[bI].keyCombo).length; if (!sortedBindings[bindingWeight]) { sortedBindings[bindingWeight] = []; } sortedBindings[bindingWeight].push(bindings[bI]); } for (sBI = sortedBindings.length - 1; sBI >= 0; sBI -= 1) { if (!sortedBindings[sBI]) { continue; } for (bI = 0; bI < sortedBindings[sBI].length; bI += 1) { binding = sortedBindings[sBI][bI]; bindingKeys = extractComboKeys(binding.keyCombo); bindingKeysSatisfied = true; for (kI = 0; kI < bindingKeys.length; kI += 1) { if (remainingKeys.indexOf(bindingKeys[kI]) === -1) { bindingKeysSatisfied = false; break; } } if (bindingKeysSatisfied && isSatisfiedCombo(binding.keyCombo)) { activeBindings.push(binding); for (kI = 0; kI < bindingKeys.length; kI += 1) { index = remainingKeys.indexOf(bindingKeys[kI]); if (index > -1) { remainingKeys.splice(index, 1); kI -= 1; } } for (cI = 0; cI < binding.keyDownCallback.length; cI += 1) { if (binding.keyDownCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) { killEventBubble = true; } } if (killEventBubble === true) { event.preventDefault(); event.stopPropagation(); } } } } } function pruneBindings(event) { var bI, cI, binding, killEventBubble; for (bI = 0; bI < activeBindings.length; bI += 1) { binding = activeBindings[bI]; if (isSatisfiedCombo(binding.keyCombo) === false) { for (cI = 0; cI < binding.keyUpCallback.length; cI += 1) { if (binding.keyUpCallback[cI](event, getActiveKeys(), binding.keyCombo) === false) { killEventBubble = true; } } if (killEventBubble === true) { event.preventDefault(); event.stopPropagation(); } activeBindings.splice(bI, 1); bI -= 1; } } } function compareCombos(keyComboArrayA, keyComboArrayB) { var cI, sI, kI; keyComboArrayA = parseKeyCombo(keyComboArrayA); keyComboArrayB = parseKeyCombo(keyComboArrayB); if (keyComboArrayA.length !== keyComboArrayB.length) { return false; } for (cI = 0; cI < keyComboArrayA.length; cI += 1) { if (keyComboArrayA[cI].length !== keyComboArrayB[cI].length) { return false; } for (sI = 0; sI < keyComboArrayA[cI].length; sI += 1) { if (keyComboArrayA[cI][sI].length !== keyComboArrayB[cI][sI].length) { return false; } for (kI = 0; kI < keyComboArrayA[cI][sI].length; kI += 1) { if (keyComboArrayB[cI][sI].indexOf(keyComboArrayA[cI][sI][kI]) === -1) { return false; } } } } return true; } function isSatisfiedCombo(keyCombo) { var cI, sI, stage, kI, stageOffset = 0, index, comboMatches; keyCombo = parseKeyCombo(keyCombo); for (cI = 0; cI < keyCombo.length; cI += 1) { comboMatches = true; stageOffset = 0; for (sI = 0; sI < keyCombo[cI].length; sI += 1) { stage = [].concat(keyCombo[cI][sI]); for (kI = stageOffset; kI < activeKeys.length; kI += 1) { index = stage.indexOf(activeKeys[kI]); if (index > -1) { stage.splice(index, 1); stageOffset = kI; } } if (stage.length !== 0) { comboMatches = false; break; } } if (comboMatches) { return true; } } return false; } function extractComboKeys(keyCombo) { var cI, sI, kI, keys = []; keyCombo = parseKeyCombo(keyCombo); for (cI = 0; cI < keyCombo.length; cI += 1) { for (sI = 0; sI < keyCombo[cI].length; sI += 1) { keys = keys.concat(keyCombo[cI][sI]); } } return keys; } function parseKeyCombo(keyCombo) { var s = keyCombo, i = 0, op = 0, ws = false, nc = false, combos = [], combo = [], stage = [], key = ""; if (typeof keyCombo === "object" && typeof keyCombo.push === "function") { return keyCombo; } if (typeof keyCombo !== "string") { throw new Error('Cannot parse "keyCombo" because its type is "' + typeof keyCombo + '". It must be a "string".'); } while (s.charAt(i) === " ") { i += 1; } while (true) { if (s.charAt(i) === " ") { while (s.charAt(i) === " ") { i += 1; } ws = true; } else if (s.charAt(i) === ",") { if (op || nc) { throw new Error("Failed to parse key combo. Unexpected , at character index " + i + "."); } nc = true; i += 1; } else if (s.charAt(i) === "+") { if (key.length) { stage.push(key); key = ""; } if (op || nc) { throw new Error("Failed to parse key combo. Unexpected + at character index " + i + "."); } op = true; i += 1; } else if (s.charAt(i) === ">") { if (key.length) { stage.push(key); key = ""; } if (stage.length) { combo.push(stage); stage = []; } if (op || nc) { throw new Error("Failed to parse key combo. Unexpected > at character index " + i + "."); } op = true; i += 1; } else if (i < s.length - 1 && s.charAt(i) === "!" && (s.charAt(i + 1) === ">" || s.charAt(i + 1) === "," || s.charAt(i + 1) === "+")) { key += s.charAt(i + 1); op = false; ws = false; nc = false; i += 2; } else if (i < s.length && s.charAt(i) !== "+" && s.charAt(i) !==