From 028c5be4182bf0f316f5953dfb42631bf97a661a Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Mon, 27 Apr 2015 16:04:41 -0600 Subject: [PATCH 1/2] + ToggleMenuView * Start of hotkeys for menus * General menu cleanup & rework --- core/client.js | 10 ++- core/edit_text_view.js | 7 +- core/mci_view_factory.js | 9 +++ core/menu_view.js | 12 ++++ core/toggle_menu_view.js | 109 +++++++++++++++++++++++++++++++ core/vertical_menu_view.js | 18 ++++- core/view.js | 3 + core/view_controller.js | 5 ++ mods/art/demo_edit_text_view.ans | Bin 588 -> 655 bytes mods/menu.json | 8 ++- 10 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 core/toggle_menu_view.js diff --git a/core/client.js b/core/client.js index df0ed384..bbe768d6 100644 --- a/core/client.js +++ b/core/client.js @@ -17,12 +17,18 @@ exports.Client = Client; //var ANSI_CONTROL_REGEX = /(?:(?:\u001b\[)|\u009b)(?:(?:[0-9]{1,3})?(?:(?:;[0-9]{0,3})*)?[A-M|f-m])|\u001b[A-M]/g; // :TODO: Move all of the key stuff to it's own module + +// +// Resources & Standards: +// * http://www.ansi-bbs.org/ansi-bbs-core-server.html +// var ANSI_KEY_NAME_MAP = { - 0x08 : 'backspace', - 0x09 : 'tab', + 0x08 : 'backspace', // BS + 0x09 : 'tab', // 0x7f : 'del', 0x1b : 'esc', 0x0d : 'enter', + 0x19 : 'end of medium', // EM / CTRL-Y }; var ANSI_KEY_CSI_NAME_MAP = { diff --git a/core/edit_text_view.js b/core/edit_text_view.js index 5a26976f..67ad59e6 100644 --- a/core/edit_text_view.js +++ b/core/edit_text_view.js @@ -34,7 +34,6 @@ EditTextView.prototype.onKeyPress = function(key, isSpecial) { assert(1 === key.length); - // :TODO: how to handle justify left/center? if(this.text.length < this.maxLength) { key = strUtil.stylizeString(key, this.textStyle); @@ -58,8 +57,6 @@ EditTextView.prototype.onKeyPress = function(key, isSpecial) { }; EditTextView.prototype.onSpecialKeyPress = function(keyName) { - // :TODO: handle 'enter' & others for multiLine - if(this.isSpecialKeyMapped('backspace', keyName)) { if(this.text.length > 0) { this.text = this.text.substr(0, this.text.length - 1); @@ -73,6 +70,10 @@ EditTextView.prototype.onSpecialKeyPress = function(keyName) { } } } + } else if(this.isSpecialKeyMapped('clearLine', keyName)) { + this.text = ''; + this.cursorPos.x = 0; + this.setFocus(true); // resetting focus will redraw & adjust cursor } diff --git a/core/mci_view_factory.js b/core/mci_view_factory.js index ba761165..aac8c93f 100644 --- a/core/mci_view_factory.js +++ b/core/mci_view_factory.js @@ -6,6 +6,7 @@ var EditTextView = require('./edit_text_view.js').EditTextView; var ButtonView = require('./button_view.js').ButtonView; var VerticalMenuView = require('./vertical_menu_view.js').VerticalMenuView; var SpinnerMenuView = require('./spinner_menu_view.js').SpinnerMenuView; +var ToggleMenuView = require('./toggle_menu_view.js').ToggleMenuView; var Config = require('./config.js').config; var packageJson = require('../package.json'); @@ -177,6 +178,14 @@ MCIViewFactory.prototype.createFromMCI = function(mci) { view = new SpinnerMenuView(options); break; + case 'TM' : + setOption(0, 'textStyle'); + + setFocusOption(0, 'focusTextStyle') + + view = new ToggleMenuView(options); + break; + default : options.text = this.getPredefinedViewLabel(mci.code); if(options.text) { diff --git a/core/menu_view.js b/core/menu_view.js index aab10b87..3f66d675 100644 --- a/core/menu_view.js +++ b/core/menu_view.js @@ -4,8 +4,10 @@ var View = require('./view.js').View; var ansi = require('./ansi_term.js'); var miscUtil = require('./misc_util.js'); + var util = require('util'); var assert = require('assert'); +var _ = require('lodash'); exports.MenuView = MenuView; @@ -23,6 +25,8 @@ function MenuView(options) { this.items = []; } + this.setHotKeys(options.hotkeys); + this.focusedItemIndex = options.focusedItemIndex || 0; this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0; @@ -35,6 +39,7 @@ function MenuView(options) { this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1); this.justify = options.justify || 'none'; + /* this.moveSelection = function(fromIndex, toIndex) { assert(!self.positionCacheExpired); assert(fromIndex >= 0 && fromIndex <= self.items.length); @@ -47,6 +52,7 @@ function MenuView(options) { self.focusedItemIndex = toIndex; self.drawItem(toIndex); }; + */ /* this.cachePositions = function() { @@ -97,3 +103,9 @@ MenuView.prototype.setItems = function(items) { } }; +MenuView.prototype.setHotKeys = function(hotkeys) { + if(_.isObject(hotkeys)) { + this.hotkeys = hotkeys; + } +} + diff --git a/core/toggle_menu_view.js b/core/toggle_menu_view.js new file mode 100644 index 00000000..f519b8c6 --- /dev/null +++ b/core/toggle_menu_view.js @@ -0,0 +1,109 @@ +/* jslint node: true */ +'use strict'; + +var MenuView = require('./menu_view.js').MenuView; +var ansi = require('./ansi_term.js'); +var strUtil = require('./string_util.js'); + +var util = require('util'); +var assert = require('assert'); +var _ = require('lodash'); + +exports.ToggleMenuView = ToggleMenuView; + +function ToggleMenuView (options) { + options.cursor = options.cursor || 'hide'; + + MenuView.call(this, options); + + var self = this; + + this.cachePositions = function() { + self.positionCacheExpired = false; + }; + + this.updateSelection = function() { + assert(!self.positionCacheExpired); + assert(this.focusedItemIndex >= 0 && this.focusedItemIndex <= self.items.length); + + self.redraw(); + }; +} + +util.inherits(ToggleMenuView, MenuView); + +ToggleMenuView.prototype.redraw = function() { + ToggleMenuView.super_.prototype.redraw.call(this); + + this.cachePositions(); + + this.client.term.write(this.getANSIColor(this.hasFocus ? this.getFocusColor() : this.getColor())); + + assert(this.items.length === 2); + for(var i = 0; i < 2; i++) { + var item = this.items[i]; + var text = strUtil.stylizeString(item.text, i === this.focusedItemIndex ? this.focusTextStyle : this.textStyle); + + if(1 === i) { + this.client.term.write(this.getANSIColor(this.getColor()) + ' / '); // :TODO: We need a color for this!!! + } + + this.client.term.write(this.getANSIColor(i === this.focusedItemIndex ? this.getFocusColor() : this.getColor())); + this.client.term.write(text); + } +} + +ToggleMenuView.prototype.setFocus = function(focused) { + ToggleMenuView.super_.prototype.setFocus.call(this, focused); + + this.redraw(); +}; + +ToggleMenuView.prototype.onKeyPress = function(key, isSpecial) { + if(isSpecial || !this.hotkeys) { + return; + } + + assert(1 === key.length); + + var keyIndex = this.hotkeys[key]; + if(!_.isUndefined(keyIndex)) { + this.focusedItemIndex = keyIndex; + this.updateSelection(); + } + + ToggleMenuView.super_.prototype.onKeyPress.call(this, key, isSpecial); +} + +ToggleMenuView.prototype.onSpecialKeyPress = function(keyName) { + + if(this.isSpecialKeyMapped('right', keyName) || this.isSpecialKeyMapped('down', keyName)) { + if(this.items.length - 1 === this.focusedItemIndex) { + this.focusedItemIndex = 0; + } else { + this.focusedItemIndex++; + } + } else if(this.isSpecialKeyMapped('left', keyName) || this.isSpecialKeyMapped('up', keyName)) { + if(0 === this.focusedItemIndex) { + this.focusedItemIndex = this.items.length - 1; + } else { + this.focusedItemIndex--; + } + } + + this.updateSelection(); + + ToggleMenuView.super_.prototype.onSpecialKeyPress.call(this, keyName); +} + +ToggleMenuView.prototype.getData = function() { + return this.focusedItemIndex; +}; + +ToggleMenuView.prototype.setItems = function(items) { + ToggleMenuView.super_.prototype.setItems.call(this, items); + + this.items = this.items.splice(0, 2); // switch/toggle only works with two elements + + this.dimens.width = this.items.join(' / ').length; // :TODO: allow configurable seperator +} diff --git a/core/vertical_menu_view.js b/core/vertical_menu_view.js index d58a632b..1a87995f 100644 --- a/core/vertical_menu_view.js +++ b/core/vertical_menu_view.js @@ -16,7 +16,7 @@ function VerticalMenuView(options) { var self = this; - this.itemSpacing = 3; + this.itemSpacing = 3; // :TODO: bring from options/configurable this.calculateDimens = function() { if(!self.dimens || !self.dimens.width) { @@ -54,6 +54,19 @@ function VerticalMenuView(options) { } }; + this.changeSelection = function(fromIndex, toIndex) { + assert(!self.positionCacheExpired); + assert(fromIndex >= 0 && fromIndex <= self.items.length); + assert(toIndex >= 0 && toIndex <= self.items.length); + + self.items[fromIndex].focused = false; + self.drawItem(fromIndex); + + self.items[toIndex].focused = true; + self.focusedItemIndex = toIndex; + self.drawItem(toIndex); + }; + this.drawItem = function(index) { assert(!this.positionCacheExpired); @@ -91,6 +104,7 @@ VerticalMenuView.prototype.setFocus = function(focused) { this.redraw(); }; + VerticalMenuView.prototype.onSpecialKeyPress = function(keyName) { var prevFocusedItemIndex = this.focusedItemIndex; @@ -110,7 +124,7 @@ VerticalMenuView.prototype.onSpecialKeyPress = function(keyName) { } if(prevFocusedItemIndex !== this.focusedItemIndex) { - this.moveSelection(prevFocusedItemIndex, this.focusedItemIndex); + this.changeSelection(prevFocusedItemIndex, this.focusedItemIndex); } VerticalMenuView.super_.prototype.onSpecialKeyPress.call(this, keyName); diff --git a/core/view.js b/core/view.js index 0db3c4f0..b03c91f4 100644 --- a/core/view.js +++ b/core/view.js @@ -18,6 +18,9 @@ var VIEW_SPECIAL_KEY_MAP_DEFAULT = { next : [ 'tab' ], up : [ 'up arrow' ], down : [ 'down arrow' ], + left : [ 'left arrow' ], + right : [ 'right arrow' ], + clearLine : [ 'end of medium' ], }; function View(options) { diff --git a/core/view_controller.js b/core/view_controller.js index 5c7d08c0..9a1d7385 100644 --- a/core/view_controller.js +++ b/core/view_controller.js @@ -203,6 +203,11 @@ function ViewController(options) { view.textMaskChar = self.client.currentThemeInfo.getPasswordChar(); } + value = getViewProp('hotkeys'); + if(_.isObject(value)) { + view.setHotKeys(value); + } + value = getViewProp('submit'); if(_.isBoolean(value)) { diff --git a/mods/art/demo_edit_text_view.ans b/mods/art/demo_edit_text_view.ans index c0b234cf65cee00d9009da6bc82829c976df39f6..e6f7a2401b4f1e2d3af8bdccfea559a1ff8b9f40 100644 GIT binary patch delta 118 zcmX@Z($Bh~jB#=WW3at~f^@W@wXtb#h;+08h|W(>&q?)7%_|MdOf6S{N*m-_0o4Iz tRYQEuq@zttt&PpW?8!$N6V>>b7#J8C0~pvDz(CZ~$yXuF-4VirlK@eQ89e|1 delta 54 ucmeBYJ;So0jB#=aW9Vc)rf9X(j0_Bni~$Vn3}7Ja>Ex>r=I#jL!ASr Date: Mon, 27 Apr 2015 17:27:23 -0600 Subject: [PATCH 2/2] * Allow hot keys to be case insensitive * Start of better demo --- core/menu_view.js | 17 +++++++++---- core/toggle_menu_view.js | 4 ++-- mods/art/demo_edit_text_view1.ans | Bin 0 -> 422 bytes mods/menu.json | 38 +++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 mods/art/demo_edit_text_view1.ans diff --git a/core/menu_view.js b/core/menu_view.js index 3f66d675..21a47dbe 100644 --- a/core/menu_view.js +++ b/core/menu_view.js @@ -25,7 +25,9 @@ function MenuView(options) { this.items = []; } - this.setHotKeys(options.hotkeys); + this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true); + + this.setHotKeys(options.hotKeys); this.focusedItemIndex = options.focusedItemIndex || 0; this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0; @@ -103,9 +105,16 @@ MenuView.prototype.setItems = function(items) { } }; -MenuView.prototype.setHotKeys = function(hotkeys) { - if(_.isObject(hotkeys)) { - this.hotkeys = hotkeys; +MenuView.prototype.setHotKeys = function(hotKeys) { + if(_.isObject(hotKeys)) { + if(this.caseInsensitiveHotKeys) { + this.hotKeys = {}; + for(var key in hotKeys) { + this.hotKeys[key.toLowerCase()] = hotKeys[key]; + } + } else { + this.hotKeys = hotKeys; + } } } diff --git a/core/toggle_menu_view.js b/core/toggle_menu_view.js index f519b8c6..a5e7d21a 100644 --- a/core/toggle_menu_view.js +++ b/core/toggle_menu_view.js @@ -60,13 +60,13 @@ ToggleMenuView.prototype.setFocus = function(focused) { }; ToggleMenuView.prototype.onKeyPress = function(key, isSpecial) { - if(isSpecial || !this.hotkeys) { + if(isSpecial || !this.hotKeys) { return; } assert(1 === key.length); - var keyIndex = this.hotkeys[key]; + var keyIndex = this.hotKeys[this.caseInsensitiveHotKeys ? key.toLowerCase() : key]; if(!_.isUndefined(keyIndex)) { this.focusedItemIndex = keyIndex; this.updateSelection(); diff --git a/mods/art/demo_edit_text_view1.ans b/mods/art/demo_edit_text_view1.ans new file mode 100644 index 0000000000000000000000000000000000000000..6307d29ea11b9173b060c57566bb5a074ddac0fc GIT binary patch literal 422 zcmb`CJqv>{5QcRtf>XD;WN|;g5bC62KZZh~)()YAsMJ7GK>{uP^}VD;>fSpzcrWig zcX22rAtXU8Y>NTg;<_p*jBYO>ZS#xq~#({Hozo`>6MX%C(aMGV~P|Y|S#yQFR zdaN7SjSh{cQ)ZB020^7DDyJX0SsDJAv{I(kDLv-}fHM9meDER&{n#}P!!(b^!qBnY MX9rSk(~p|!8`1DtdjJ3c literal 0 HcmV?d00001 diff --git a/mods/menu.json b/mods/menu.json index d803cc26..83969d24 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -199,6 +199,41 @@ } } }, + "demoEditTextView" : { + "art" : "demo_edit_text_view1.ans", + "options" : { "cls" : true }, + "form" : { + "0" : { + "BT4ET1ET2ET3" : { + "mci" : { + "ET1" : { + "maxLength" : 20 + }, + "ET2" : { + "maxLength" : 20 + }, + "ET3" : { + "fillChar" : " ", + "maxLength" : 20 + }, + "BT4" : { + "text" : "Back", + "submit" : true + } + }, + "submit" : { + "*" : [ + { + "value" : 4, + "action" : "@menu:demoMain" + } + ] + } + } + } + } + } + /* "demoEditTextView" : { "art" : "demo_edit_text_view.ans", "options" : { "cls" : true }, @@ -219,11 +254,12 @@ }, "TM6" : { "items" : [ "Yes", "No" ], - "hotkeys" : { "y" : 0, "n" : 1 } + "hotkeys" : { "Y" : 0, "n" : 1 } } } } } } } + */ } \ No newline at end of file