Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map legend position to layers #6

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/const/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
52 changes: 51 additions & 1 deletion src/state/keyboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class Keyboard {
name: '',
bootloaderSize: C.BOOTLOADER_4096,
rgbNum: 0,
backlightLevels: 3
backlightLevels: 3,
positionIndexToLayerMap: [],
strictLayers: false
};

this.valid = false;
Expand All @@ -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);

Expand Down Expand Up @@ -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.
*/
Expand Down
85 changes: 74 additions & 11 deletions src/state/keyboard/key.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -105,7 +105,7 @@ class Key {
this._col = 0;

// Guess the legend.
this.guessLegend();
this.guessKeycodes();
}

/*
Expand All @@ -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, []);
}
}

Expand Down
51 changes: 51 additions & 0 deletions src/ui/elements/layerbox/index.js
Original file line number Diff line number Diff line change
@@ -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 <div className='layerbox'>
<input
style={{ width: '2rem', textAlign: 'center' }}
type='text'
value={ this.props.value }
onChange={ this.onChange }/>
</div>;
}

}

module.exports = LayerBox;
114 changes: 114 additions & 0 deletions src/ui/panes/settings/index.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -130,6 +131,119 @@ class Settings extends React.Component {
The number of backlight levels.
</Help>
<div style={{ height: '1.5rem' }}/>
<h2 style={{ width: '16.8rem', textAlign: 'center' }}>
Legend-to-layer mapping
</h2>
<Help>
Select which layer the legends in each position should be mapped to. Only affects keys with multiple
legends.
</Help>
<div style={{ height: '0.5rem' }}/>
<table style={{ display: 'inline-block' }}>
<tbody>
<tr>
<th>&nbsp;</th>
<th>left</th>
<th>center</th>
<th>right</th>
</tr>
<tr>
<th>top</th>
<td>
<LayerBox
value={ keyboard.getLayer('top left') }
onChange={ v => keyboard.setLayer('top left', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('top center') }
onChange={ v => keyboard.setLayer('top center', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('top right') }
onChange={ v => keyboard.setLayer('top right', v) }/>
</td>
</tr>
<tr>
<th>center</th>
<td>
<LayerBox
value={ keyboard.getLayer('center left') }
onChange={ v => keyboard.setLayer('center left', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('center') }
onChange={ v => keyboard.setLayer('center', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('center right') }
onChange={ v => keyboard.setLayer('center right', v) }/>
</td>
</tr>
<tr>
<th>bottom</th>
<td>
<LayerBox
value={ keyboard.getLayer('bottom left') }
onChange={ v => keyboard.setLayer('bottom left', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('bottom center') }
onChange={ v => keyboard.setLayer('bottom center', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('bottom right') }
onChange={ v => keyboard.setLayer('bottom right', v) }/>
</td>
</tr>
<tr>
<th>front</th>
<td>
<LayerBox
value={ keyboard.getLayer('front left') }
onChange={ v => keyboard.setLayer('front left', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('front center') }
onChange={ v => keyboard.setLayer('front center', v) }/>
</td>
<td>
<LayerBox
value={ keyboard.getLayer('front right') }
onChange={ v => keyboard.setLayer('front right', v) }/>
</td>
</tr>
</tbody>
</table>
<div style={{ height: '0.5rem' }}/>
<div style={{ width: '16.8rem', textAlign: 'center', display: 'inline-block' }}>
<input
type='checkbox'
checked={ keyboard.settings.strictLayers }
/>
<label
onClick={ () => { keyboard.settings.strictLayers = !keyboard.settings.strictLayers; state.update(); } }>
Use strict mode.
</label>
</div>
<Help>
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'.
</Help>
<div style={{ height: '0.5rem' }}/>
Apply the above mapping <br/>
(resets manual changes to keymap)
<div style={{ height: '0.5rem' }}/>
<button onClick={ keyboard.updateLayers }>
Regenerate keymap
</button>
<div style={{ height: '1.5rem' }}/>
Save your layout.
<div style={{ height: '0.5rem' }}/>
<button onClick={ this.save }>
Expand Down
Loading