diff --git a/html/css/main.css b/html/css/main.css index 49dc47d..ce6a2c0 100644 --- a/html/css/main.css +++ b/html/css/main.css @@ -269,13 +269,13 @@ a { .button--sacnite:not(.disabled):hover { color: #37474f; } -.button--sacnite.button--inverted:not(.disabled):hover { +.button--sacnite.button--inverted:not(.disabled):hover i, .button--sacnite.button--inverted:not(.disabled):focus i { color: #fff; } .button--sacnite.button--inverted.active { color: #fff; } -.button--sacnite:not(.active):not(.disabled):hover::before { +.button--sacnite:not(.active):not(.disabled):hover::before, .button--sacnite:not(.active):not(.disabled):focus::before { box-shadow: inset 0 0 0 2px #37474f; -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); diff --git a/html/css/svg-styles.css b/html/css/svg-styles.css index c60ac9b..a5dde9a 100644 --- a/html/css/svg-styles.css +++ b/html/css/svg-styles.css @@ -1,6 +1,10 @@ .node:hover { fill: #808080; } + +.node.hover { + fill: #808080; +} .node.selected { fill: #14A520; } diff --git a/html/index.html b/html/index.html index 0df2f02..1640f32 100644 --- a/html/index.html +++ b/html/index.html @@ -39,11 +39,11 @@
  • @@ -88,11 +88,13 @@

    Settings

    + + diff --git a/html/js/guiutils/KeyTraversal.js b/html/js/guiutils/KeyTraversal.js new file mode 100644 index 0000000..3e9497b --- /dev/null +++ b/html/js/guiutils/KeyTraversal.js @@ -0,0 +1,224 @@ +GTE.UI = (function (parentModule) { + + /** + * Creates a new KeyTraversal object. Currently implements + * "postorder" tree traversal. + * @class + */ + function KeyTraversal() { + this.keyMode = GTE.KEYMODES.NODE_TRAVERSAL; + this.activeNode = null; + this.activeMulti = null; + this.enabled = null; + } + + /** + * Function that switches the current key traversal mode + * Valid modes are declared in GTE.KEYMODES + * @param {Number} newMode Key mode to be switched to. + */ + KeyTraversal.prototype.switchMode = function(newMode) { + switch(newMode) { + case GTE.KEYMODES.DISABLED: + break; + case GTE.KEYMODES.NODE_TRAVERSAL: + case GTE.KEYMODES.MULTI_TRAVERSAL: + var buttonsToBlur = document.getElementsByTagName('button'); + for (var i = 0; i < buttonsToBlur.length; i++) + buttonsToBlur[i].blur(); + break; + case GTE.KEYMODES.NO_TRAVERSAL: + this.resetCanvasFocuses(); + break; + } + this.keyMode = newMode; + }; + + /** + * Function that adds listeners for KeyTraversal functionality + */ + KeyTraversal.prototype.addListeners = function() { + this.enabled = true; + document.addEventListener("keydown", this.keyHandler.bind(this)); + document.addEventListener("click", this.resetCanvasFocuses.bind(this)); + }; + + /** + * Function that removes listeners for KeyTraversal functionality + */ + KeyTraversal.prototype.removeListeners = function() { + this.enabled = false; + document.removeEventListener("keydown", this.keyHandler.bind(this)); + document.removeEventListener("click", this.resetCanvasFocuses.bind(this)); + } + + /** + * Function that resets the currently any currently focused canvas objects + */ + KeyTraversal.prototype.resetCanvasFocuses = function() { + if (this.activeNode != null){ + this.activeNode.classList.remove("hover"); + } + if (this.activeMulti != null) { + this.activeMulti.onmouseout(); + } + this.activeNode = null; + this.activeMulti = null; + } + + /** + * Function that traverses canvas objects via keypresses + * @param {Number} e KeyCode for key pressed + */ + KeyTraversal.prototype.keyHandler = function(e) { + //console.log(e.which); + if (this.keyMode == GTE.KEYMODES.DISABLED) + return; + /** + * Function that traverses canvas tree until it finds an object of a + * specified type, else returns a default canvas object. + * + * @param {String} name The tag name of the target canvas object. + * @param {SVGAnimatedString} startNode Start object to traverse from + * @param {SVGAnimatedString} loopValue Value to hover on after end traversal + * @param {Boolean} isReverse Determines if traversing backwards + * @return {SVGAnimatedString} Returns the next object after traversal. + */ + function findElement(name, startNode, loopValue, isReverse) { + var nextElement = startNode; + var check; + + while (true) { + + if (isReverse) + check = nextElement.previousElementSibling; + else + check = nextElement.nextElementSibling; + + if (check == null) { + nextElement = loopValue; + break; + } else { + nextElement = check; + if (nextElement.tagName == name) + break; + } + } + + return nextElement; + }; + + /** + * Function that changes the current mode, and delegates what the + * current hovered svg object should change to. + * + * @param {String} newMode The mode corresponding to the hovered object. + * @param {String} name The tag name of the SVG object. + * @param {SVGAnimatedString} currentValue The current hovered object. + * @param {SVGAnimatedString} rootValue The default object to hover on. + * @param {SVGAnimatedString} loopValue Value to hover on after end traversal + * @param {Boolean} isReverse Whether to traverse backwards if needed. + * @param {KeyTraversal} self Access to outer scope of this function. + * @return {SVGAnimatedString} Returns the new object to hover on. + */ + function assignActive(newMode, name, currentValue, rootValue, loopValue, isReverse, self) { + // If coming from a different mode, reset and change mode + if (self.keyMode != newMode) { + self.resetCanvasFocuses(); + self.switchMode(newMode); + } + + // If currently null, then hover on root + if (currentValue == null) + currentValue = rootValue; + else + currentValue = findElement(name, currentValue, loopValue, isReverse); + + return currentValue; + }; + + // Will hold the default loop-around value + var loopValue; + // Will hold the root value to hover on initially + var rootValue; + + switch(e.which) { + case KeyEvent.DOM_VK_UP: + + // Remove hover on current node + if (this.activeNode != null) + this.activeNode.classList.remove("hover"); + + rootValue = document.querySelector(".node.root"); + loopValue = document.querySelector(".node"); + + this.activeNode = assignActive(GTE.KEYMODES.NODE_TRAVERSAL, "ellipse", + this.activeNode, rootValue, loopValue, false, this); + + // Add hover to new current node + this.activeNode.classList.add("hover"); + break; + + case KeyEvent.DOM_VK_DOWN: + + // Remove hover on current node + if (this.activeNode != null) + this.activeNode.classList.remove("hover"); + + rootValue = document.querySelector(".node.root"); + loopValue = document.querySelector(".node.root"); + + this.activeNode = assignActive(GTE.KEYMODES.NODE_TRAVERSAL, "ellipse", + this.activeNode, rootValue, loopValue, true, this); + + if (!this.activeNode) + return; + // Add hover to new current node + this.activeNode.classList.add("hover"); + break; + + case KeyEvent.DOM_VK_X: + + // Remove hover on current multi + if (this.activeMulti != null) + this.activeMulti.onmouseout(); + + rootValue = document.querySelector(".multiaction-rect.root"); + loopValue = document.querySelector(".multiaction-rect.root"); + + do { + this.activeMulti = assignActive(GTE.KEYMODES.MULTI_TRAVERSAL, "rect", + this.activeMulti, rootValue, loopValue, false, this); + } + while(this.activeMulti != null && this.activeMulti.classList.contains('show')); + + if (!this.activeMulti) + return; + // Add hover to current multi + this.activeMulti.onmouseover(); + break; + + case KeyEvent.DOM_VK_SPACE: + + // Simulate click on object depending on mode + if (this.keyMode == GTE.KEYMODES.NODE_TRAVERSAL) { + if (this.activeNode != null) + this.activeNode.onclick(); + } else if (this.keyMode == GTE.KEYMODES.MULTI_TRAVERSAL) { + if (this.activeMulti != null) + this.activeMulti.onclick(); + } + break; + + case KeyEvent.DOM_VK_TAB: + + // Disable key traversal when tabbing; tabbing is for natural DOM ordering + this.switchMode(GTE.KEYMODES.NO_TRAVERSAL); + break; + } + }; + + parentModule.KeyTraversal = KeyTraversal; + return parentModule; + +}(GTE.UI)); diff --git a/html/js/guiutils/MultiAction.js b/html/js/guiutils/MultiAction.js index 8639ecc..e756c41 100644 --- a/html/js/guiutils/MultiAction.js +++ b/html/js/guiutils/MultiAction.js @@ -31,6 +31,8 @@ GTE.TREE = (function(parentModule) { color: '#9d9d9d' }) .addClass('multiaction-rect'); + if (this.nodesInLine[0].parent.parent == null) + this.shape.addClass('root'); this.shape.translate(this.x1, this.y - GTE.CONSTANTS.CIRCLE_SIZE / 2); var thisMultiAction = this; diff --git a/html/js/main.js b/html/js/main.js index a3d5464..253c001 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -4,6 +4,8 @@ // GTE is initialized by the library GTE.canvas = SVG('canvas').size("100%", "100%").attr({'style': 'background: #fff'}); GTE.tools = new GTE.UI.Tools(); + GTE.keyTraversal = new GTE.UI.KeyTraversal(); + // Initialize settings var setSettingsToDefaults = function() { GTE.STORAGE.settingsCircleSize = GTE.CONSTANTS.CIRCLE_SIZE; @@ -73,6 +75,8 @@ // Always start with root and two children GTE.tools.newTree(); + // Initiate key listening for canvas traversal + GTE.keyTraversal.addListeners(); document.getElementById("button-new").addEventListener("click", function(){ GTE.tools.newTree(); @@ -161,6 +165,7 @@ return false; }); + document.getElementById("button-settings-reset").addEventListener("click", function() { // Clear localStorage and reset settings localStorage.clear(); diff --git a/html/js/structure.js b/html/js/structure.js index 5c8f4e3..c7387a6 100644 --- a/html/js/structure.js +++ b/html/js/structure.js @@ -44,6 +44,13 @@ var GTE = (function () { MERGE: 3, DISSOLVE: 4 }; + GTE.KEYMODES = { + DISABLED: 0, + NO_TRAVERSAL: 1, + NODE_TRAVERSAL: 2, + MULTI_TRAVERSAL: 3 + }; + if (Object.freeze) { Object.freeze(GTE.MODES); } diff --git a/html/js/thirdparty/KeyEvent.js b/html/js/thirdparty/KeyEvent.js new file mode 100644 index 0000000..6dc2d31 --- /dev/null +++ b/html/js/thirdparty/KeyEvent.js @@ -0,0 +1,120 @@ +// This file allows access to readable keycodes for key presses +if (typeof KeyEvent == "undefined") { + var KeyEvent = { + DOM_VK_CANCEL: 3, + DOM_VK_HELP: 6, + DOM_VK_BACK_SPACE: 8, + DOM_VK_TAB: 9, + DOM_VK_CLEAR: 12, + DOM_VK_RETURN: 13, + DOM_VK_ENTER: 14, + DOM_VK_SHIFT: 16, + DOM_VK_CONTROL: 17, + DOM_VK_ALT: 18, + DOM_VK_PAUSE: 19, + DOM_VK_CAPS_LOCK: 20, + DOM_VK_ESCAPE: 27, + DOM_VK_SPACE: 32, + DOM_VK_PAGE_UP: 33, + DOM_VK_PAGE_DOWN: 34, + DOM_VK_END: 35, + DOM_VK_HOME: 36, + DOM_VK_LEFT: 37, + DOM_VK_UP: 38, + DOM_VK_RIGHT: 39, + DOM_VK_DOWN: 40, + DOM_VK_PRINTSCREEN: 44, + DOM_VK_INSERT: 45, + DOM_VK_DELETE: 46, + DOM_VK_0: 48, + DOM_VK_1: 49, + DOM_VK_2: 50, + DOM_VK_3: 51, + DOM_VK_4: 52, + DOM_VK_5: 53, + DOM_VK_6: 54, + DOM_VK_7: 55, + DOM_VK_8: 56, + DOM_VK_9: 57, + DOM_VK_SEMICOLON: 59, + DOM_VK_EQUALS: 61, + DOM_VK_A: 65, + DOM_VK_B: 66, + DOM_VK_C: 67, + DOM_VK_D: 68, + DOM_VK_E: 69, + DOM_VK_F: 70, + DOM_VK_G: 71, + DOM_VK_H: 72, + DOM_VK_I: 73, + DOM_VK_J: 74, + DOM_VK_K: 75, + DOM_VK_L: 76, + DOM_VK_M: 77, + DOM_VK_N: 78, + DOM_VK_O: 79, + DOM_VK_P: 80, + DOM_VK_Q: 81, + DOM_VK_R: 82, + DOM_VK_S: 83, + DOM_VK_T: 84, + DOM_VK_U: 85, + DOM_VK_V: 86, + DOM_VK_W: 87, + DOM_VK_X: 88, + DOM_VK_Y: 89, + DOM_VK_Z: 90, + DOM_VK_CONTEXT_MENU: 93, + DOM_VK_NUMPAD0: 96, + DOM_VK_NUMPAD1: 97, + DOM_VK_NUMPAD2: 98, + DOM_VK_NUMPAD3: 99, + DOM_VK_NUMPAD4: 100, + DOM_VK_NUMPAD5: 101, + DOM_VK_NUMPAD6: 102, + DOM_VK_NUMPAD7: 103, + DOM_VK_NUMPAD8: 104, + DOM_VK_NUMPAD9: 105, + DOM_VK_MULTIPLY: 106, + DOM_VK_ADD: 107, + DOM_VK_SEPARATOR: 108, + DOM_VK_SUBTRACT: 109, + DOM_VK_DECIMAL: 110, + DOM_VK_DIVIDE: 111, + DOM_VK_F1: 112, + DOM_VK_F2: 113, + DOM_VK_F3: 114, + DOM_VK_F4: 115, + DOM_VK_F5: 116, + DOM_VK_F6: 117, + DOM_VK_F7: 118, + DOM_VK_F8: 119, + DOM_VK_F9: 120, + DOM_VK_F10: 121, + DOM_VK_F11: 122, + DOM_VK_F12: 123, + DOM_VK_F13: 124, + DOM_VK_F14: 125, + DOM_VK_F15: 126, + DOM_VK_F16: 127, + DOM_VK_F17: 128, + DOM_VK_F18: 129, + DOM_VK_F19: 130, + DOM_VK_F20: 131, + DOM_VK_F21: 132, + DOM_VK_F22: 133, + DOM_VK_F23: 134, + DOM_VK_F24: 135, + DOM_VK_NUM_LOCK: 144, + DOM_VK_SCROLL_LOCK: 145, + DOM_VK_COMMA: 188, + DOM_VK_PERIOD: 190, + DOM_VK_SLASH: 191, + DOM_VK_BACK_QUOTE: 192, + DOM_VK_OPEN_BRACKET: 219, + DOM_VK_BACK_SLASH: 220, + DOM_VK_CLOSE_BRACKET: 221, + DOM_VK_QUOTE: 222, + DOM_VK_META: 224 + }; +} diff --git a/html/js/thirdparty/KeyEvent.min.js b/html/js/thirdparty/KeyEvent.min.js new file mode 100644 index 0000000..c841d41 --- /dev/null +++ b/html/js/thirdparty/KeyEvent.min.js @@ -0,0 +1 @@ +if("undefined"==typeof KeyEvent)var KeyEvent={DOM_VK_CANCEL:3,DOM_VK_HELP:6,DOM_VK_BACK_SPACE:8,DOM_VK_TAB:9,DOM_VK_CLEAR:12,DOM_VK_RETURN:13,DOM_VK_ENTER:14,DOM_VK_SHIFT:16,DOM_VK_CONTROL:17,DOM_VK_ALT:18,DOM_VK_PAUSE:19,DOM_VK_CAPS_LOCK:20,DOM_VK_ESCAPE:27,DOM_VK_SPACE:32,DOM_VK_PAGE_UP:33,DOM_VK_PAGE_DOWN:34,DOM_VK_END:35,DOM_VK_HOME:36,DOM_VK_LEFT:37,DOM_VK_UP:38,DOM_VK_RIGHT:39,DOM_VK_DOWN:40,DOM_VK_PRINTSCREEN:44,DOM_VK_INSERT:45,DOM_VK_DELETE:46,DOM_VK_0:48,DOM_VK_1:49,DOM_VK_2:50,DOM_VK_3:51,DOM_VK_4:52,DOM_VK_5:53,DOM_VK_6:54,DOM_VK_7:55,DOM_VK_8:56,DOM_VK_9:57,DOM_VK_SEMICOLON:59,DOM_VK_EQUALS:61,DOM_VK_A:65,DOM_VK_B:66,DOM_VK_C:67,DOM_VK_D:68,DOM_VK_E:69,DOM_VK_F:70,DOM_VK_G:71,DOM_VK_H:72,DOM_VK_I:73,DOM_VK_J:74,DOM_VK_K:75,DOM_VK_L:76,DOM_VK_M:77,DOM_VK_N:78,DOM_VK_O:79,DOM_VK_P:80,DOM_VK_Q:81,DOM_VK_R:82,DOM_VK_S:83,DOM_VK_T:84,DOM_VK_U:85,DOM_VK_V:86,DOM_VK_W:87,DOM_VK_X:88,DOM_VK_Y:89,DOM_VK_Z:90,DOM_VK_CONTEXT_MENU:93,DOM_VK_NUMPAD0:96,DOM_VK_NUMPAD1:97,DOM_VK_NUMPAD2:98,DOM_VK_NUMPAD3:99,DOM_VK_NUMPAD4:100,DOM_VK_NUMPAD5:101,DOM_VK_NUMPAD6:102,DOM_VK_NUMPAD7:103,DOM_VK_NUMPAD8:104,DOM_VK_NUMPAD9:105,DOM_VK_MULTIPLY:106,DOM_VK_ADD:107,DOM_VK_SEPARATOR:108,DOM_VK_SUBTRACT:109,DOM_VK_DECIMAL:110,DOM_VK_DIVIDE:111,DOM_VK_F1:112,DOM_VK_F2:113,DOM_VK_F3:114,DOM_VK_F4:115,DOM_VK_F5:116,DOM_VK_F6:117,DOM_VK_F7:118,DOM_VK_F8:119,DOM_VK_F9:120,DOM_VK_F10:121,DOM_VK_F11:122,DOM_VK_F12:123,DOM_VK_F13:124,DOM_VK_F14:125,DOM_VK_F15:126,DOM_VK_F16:127,DOM_VK_F17:128,DOM_VK_F18:129,DOM_VK_F19:130,DOM_VK_F20:131,DOM_VK_F21:132,DOM_VK_F22:133,DOM_VK_F23:134,DOM_VK_F24:135,DOM_VK_NUM_LOCK:144,DOM_VK_SCROLL_LOCK:145,DOM_VK_COMMA:188,DOM_VK_PERIOD:190,DOM_VK_SLASH:191,DOM_VK_BACK_QUOTE:192,DOM_VK_OPEN_BRACKET:219,DOM_VK_BACK_SLASH:220,DOM_VK_CLOSE_BRACKET:221,DOM_VK_QUOTE:222,DOM_VK_META:224}; \ No newline at end of file diff --git a/html/js/tree/Node.js b/html/js/tree/Node.js index e34bb34..44e4a2b 100644 --- a/html/js/tree/Node.js +++ b/html/js/tree/Node.js @@ -71,6 +71,8 @@ GTE.TREE = (function (parentModule) { .click(function() { thisNode.onClick(); }); + if (!this.parent) + this.shape.addClass('root'); if (this.player) { this.shape.fill(this.player.colour); } else { @@ -133,6 +135,7 @@ GTE.TREE = (function (parentModule) { * Function that defines the behaviour of the node on click */ Node.prototype.onClick = function () { + switch (GTE.MODE) { case GTE.MODES.ADD: // As talked in email "the phases of creating a game tree" diff --git a/html/js/tree/Tree.js b/html/js/tree/Tree.js index 024c596..d758de9 100644 --- a/html/js/tree/Tree.js +++ b/html/js/tree/Tree.js @@ -570,6 +570,7 @@ GTE.TREE = (function (parentModule) { * @return {Node} newNode Node that has been added */ Tree.prototype.addChildNodeTo = function (parentNode) { + var newNode = new GTE.TREE.Node(parentNode); this.positionsUpdated = false; return newNode;