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;