diff --git a/src/const/index.js b/src/const/index.js index 6ad88ae1..0439125b 100644 --- a/src/const/index.js +++ b/src/const/index.js @@ -72,6 +72,22 @@ const C = { // Keymap. KEYMAP_MAX_LAYERS: 16, + // KLE legend position indices (see https://github.com/ijprest/keyboard-layout-editor/wiki/Serialized-Data-Format). + POSITION_TO_INDEX: { + 'top left': 0, + 'bottom left': 1, + 'top right': 2, + 'bottom right': 3, + 'front left': 4, + 'front right': 5, + 'center left': 6, + 'center right': 7, + 'top center': 8, + 'center': 9, + 'bottom center': 10, + 'front center': 11 + }, + // Macros. MACRO_NONE: 0, MACRO_INTERVAL: 1, diff --git a/src/state/keyboard/index.js b/src/state/keyboard/index.js index 6bda1a6a..05f4031f 100644 --- a/src/state/keyboard/index.js +++ b/src/state/keyboard/index.js @@ -40,7 +40,9 @@ class Keyboard { name: '', bootloaderSize: C.BOOTLOADER_4096, rgbNum: 0, - backlightLevels: 3 + backlightLevels: 3, + positionIndexToLayerMap: [], + strictLayers: false }; this.valid = false; @@ -65,6 +67,10 @@ class Keyboard { this.deselect = this.deselect.bind(this); + this.setLayer = this.setLayer.bind(this); + this.getLayer = this.getLayer.bind(this); + this.updateLayers = this.updateLayers.bind(this); + // Import KLE if it exists. if (json) this.importKLE(json); @@ -409,6 +415,50 @@ class Keyboard { this.state.update(); } + /** + * Gets the layer the supplied position is currently mapped to. + * @param {String} position The position for which the layer should be retrieved. + * @return {*} The layer the position is mapped to or the empty string if the position is not mapped. + */ + getLayer(position) { + const positionIndex = C.POSITION_TO_INDEX[position]; + const layer = this.settings.positionIndexToLayerMap[positionIndex]; + if (layer != undefined) { + return layer; + } else { + return ''; + } + } + + /** + * Maps the supplied legend position to the supplied layer. + * + * @param {String} position The legend position to be mapped. + * @param {Number} layer The layer the position should be mapped to. + */ + setLayer(position, layer) { + const positionIndex = C.POSITION_TO_INDEX[position]; + if (isNaN(layer)) { + this.settings.positionIndexToLayerMap[positionIndex] = undefined; + } else { + this.settings.positionIndexToLayerMap[positionIndex] = layer; + } + + this.state.update(); + } + + /** + * Regenerates all keycodes according to key legends and verifies this keyboard. + */ + updateLayers() { + for (const key of this.keys) { + key.guessKeycodes(); + } + this.verify(); + + this.state.update(); + } + /* * Deselect all keys. */ diff --git a/src/state/keyboard/key.js b/src/state/keyboard/key.js index af1924fe..5bd9f54e 100644 --- a/src/state/keyboard/key.js +++ b/src/state/keyboard/key.js @@ -22,10 +22,10 @@ class Key { this.selected = 0; - this.keycodes = Array(C.KEYMAP_MAX_LAYERS).fill(new Keycode('KC_TRNS', [])); + this.keycodes = new Array(C.KEYMAP_MAX_LAYERS); // Bind functions. - this.guessLegend = this.guessLegend.bind(this); + this.guessKeycodes = this.guessKeycodes.bind(this); this.select = this.select.bind(this); this.serialize = this.serialize.bind(this); @@ -105,7 +105,7 @@ class Key { this._col = 0; // Guess the legend. - this.guessLegend(); + this.guessKeycodes(); } /* @@ -127,20 +127,83 @@ class Key { } /* - * Guess the legend of the key. + * Guess the keycode of the key using the key's legends and the current legend-to-layer map. */ - guessLegend() { - // Get the last legend. + guessKeycodes() { + /* + No legends = SPACE on l0 and TRNS on others + Only one legend = assign l0, TRNS on others + No base layer legend but multiple higher layer legends = SPACE on l0, TRNS where unspecified + Others = TRNS where unspecified + + Alternative strict mode: + If legend unspecified = NO on base layer, TRNS elsewhere + + This allows for keys that are inactive on the base layer but SPACE needs to be specified explicitly. + */ + + this.keycodes.fill(new Keycode('KC_TRNS', [])); + + const strictMode = this.keyboard.settings.strictLayers; + const legends = this.legend.split('\n'); - const legend = legends[legends.length - 1]; - // Look for an alias. + const indexToLayer = this.keyboard.settings.positionIndexToLayerMap; + const isLayerMapDefined = indexToLayer.some(v => { return v != undefined }); + + // Simple case with zero or one legends or default behavior with no layer map. + if (!isLayerMapDefined || Utils.countTruthy(legends) <= 1) { + const legend = legends[legends.length - 1]; + this.keycodes[0] = Key.legendToKeycode(legend, 'KC_NO'); + return; + } + + // layers has the same elements as legends but in such an order that the index of a legend is equal to the + // layer it is to be assigned to. + const layers = []; + for (let i = 0; i < legends.length; i++) { + const layer = indexToLayer[i]; + if (layer != undefined) { + layers[layer] = legends[i]; + } + } + + // Base layer, using 'KC_NO' as a fallback. + const legend = layers[0]; + if (legend) { + this.keycodes[0] = Key.legendToKeycode(legend, 'KC_NO'); + } else { + if (strictMode) { + this.keycodes[0] = new Keycode('KC_NO', []); + } else { + // Assign SPACE if blank. + this.keycodes[0] = new Keycode('KC_SPC', []); + } + } + + // Higher layers, using 'KC_TRNS' as a fallback. + for (let i = 1; i < layers.length; i++) { + const legend = layers[i]; + if (legend) { + this.keycodes[i] = Key.legendToKeycode(legend, 'KC_TRNS'); + } + } + } + + /** + * Returns a new keycode object corresponding to legend, or if no such keycode can be found, one corresponsing to + * fallback. + * + * @param {String} legend The legend to which a keycode should be found. + * @param {String} fallback A string specifing the fallback keycode (e.g. 'KC_NO' or 'KC_TRNS'). + * @return {Keycode} The newly constructed keycode. + */ + static legendToKeycode(legend, fallback) { const keycode = C.KEYCODE_ALIASES[legend.toUpperCase()]; if (keycode) { - this.keycodes[0] = new Keycode(keycode.template.raw[0], []); + return new Keycode(keycode.template.raw[0], []); } else { - // Default to KC_NO. - this.keycodes[0] = new Keycode('KC_NO', []); + return new Keycode(fallback, []); } } diff --git a/src/ui/elements/layerbox/index.js b/src/ui/elements/layerbox/index.js new file mode 100644 index 00000000..5cc3cc72 --- /dev/null +++ b/src/ui/elements/layerbox/index.js @@ -0,0 +1,51 @@ +const React = require('react'); + +class LayerBox extends React.Component { + + constructor(props) { + super(props); + + // Bind functions. + this.onChange = this.onChange.bind(this); + this.changeValue = this.changeValue.bind(this); + } + + /* + * Called when the value of the input changes. + * + * @param {Event} e The event triggered. + */ + onChange(e) { + const target = e.target; + let value = target.value.trim(); + + // Change the value. + this.changeValue(parseInt(value)); + } + + /* + * Called to change the value to a certain value. + * + * @param {Number} value The value to change to. + */ + changeValue(value) { + // Make sure there is a function we can call. + if (!this.props.onChange) return; + + // Change the value. + this.props.onChange(value); + } + + render() { + return
+ +
; + } + +} + +module.exports = LayerBox; diff --git a/src/ui/panes/settings/index.js b/src/ui/panes/settings/index.js index fb7ea481..3429407d 100644 --- a/src/ui/panes/settings/index.js +++ b/src/ui/panes/settings/index.js @@ -1,6 +1,7 @@ const React = require('react'); const NumberBox = require('ui/elements/numberbox'); +const LayerBox = require('ui/elements/layerbox'); const Help = require('ui/elements/help'); const C = require('const'); @@ -130,6 +131,119 @@ class Settings extends React.Component { The number of backlight levels.
+

+ Legend-to-layer mapping +

+ + Select which layer the legends in each position should be mapped to. Only affects keys with multiple + legends. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 leftcenterright
top + keyboard.setLayer('top left', v) }/> + + keyboard.setLayer('top center', v) }/> + + keyboard.setLayer('top right', v) }/> +
center + keyboard.setLayer('center left', v) }/> + + keyboard.setLayer('center', v) }/> + + keyboard.setLayer('center right', v) }/> +
bottom + keyboard.setLayer('bottom left', v) }/> + + keyboard.setLayer('bottom center', v) }/> + + keyboard.setLayer('bottom right', v) }/> +
front + keyboard.setLayer('front left', v) }/> + + keyboard.setLayer('front center', v) }/> + + keyboard.setLayer('front right', v) }/> +
+
+
+ + +
+ + In strict mode, 'KC_NO' is assigned to keys with no legend for the base layer. In strict mode, the space + bar needs to be specified explicitly by adding the legend 'Space'. + +
+ Apply the above mapping
+ (resets manual changes to keymap) +
+ +
Save your layout.