Implement Matryoshka Keyboard
|
@ -0,0 +1,7 @@
|
|||
1.00: New keyboard
|
||||
1.01: Change swipe interface to taps, speed up responses (efficiency tweaks).
|
||||
1.02: Generalize drawing and letter scaling. Allow custom and auto-generated character sets. Improve documentation.
|
||||
1.03: Attempt to improve keyboard load time.
|
||||
1.04: Make code asynchronous and improve load time.
|
||||
1.05: Fix layout issue and rename library
|
||||
1.06: Touch up readme, prep for IPO, add screenshots
|
|
@ -0,0 +1,119 @@
|
|||
# Matryoshka Keyboard
|
||||
|
||||

|
||||
|
||||
 
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
Nested key input utility.
|
||||
|
||||
## How to type
|
||||
|
||||
Press your finger down on the letter group that contains the character you would like to type, then tap the letter you
|
||||
want to enter. Once you are touching the letter you want, release your
|
||||
finger.
|
||||
|
||||

|
||||
|
||||
Press "shft" or "caps" to access alternative characters, including upper case letters, punctuation, and special
|
||||
characters.
|
||||
Pressing "shft" also reveals a cancel button if you would like to terminate input without saving.
|
||||
|
||||
Press "ok" to finish typing and send your text to whatever app called this keyboard.
|
||||
|
||||
Press "del" to delete the leftmost character.
|
||||
|
||||
## Themes and Colors
|
||||
|
||||
This keyboard will attempt to use whatever theme or colorscheme is being used by your Bangle device.
|
||||
|
||||
## How to use in a program
|
||||
|
||||
This was developed to match the interface implemented for kbtouch, kbswipe, etc.
|
||||
|
||||
In your app's metadata, add:
|
||||
|
||||
```json
|
||||
"dependencies": {"textinput": "type"}
|
||||
```
|
||||
|
||||
From inside your app, call:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
textInput.input({text: ""})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, if you want to improve the load time of the keyboard, you can pre-generate the data the keyboard needs
|
||||
to function and render like so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const defaultKeyboard = textInput.generateKeyboard(textInput.defaultCharSet);
|
||||
const defaultShiftKeyboard = textInput.generateKeyboard(textInput.defaultCharSetShift);
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: defaultKeyboard, keyboardShift: defaultShiftKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And it was faster!
|
||||
});
|
||||
```
|
||||
|
||||
This isn't required, but if you are using a large character set, and the user is interacting with the keyboard a lot,
|
||||
it can really smooth the experience.
|
||||
|
||||
The default keyboard has a full set of alphanumeric characters as well as special characters and buttons in a
|
||||
pre-defined layout. If your application needs something different, or you want to have a custom layout, you can do so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const customKeyboard = textInput.generateKeyboard([
|
||||
["1", "2", "3", "4"], ["5", "6", "7", "8"], ["9", "0", ".", "-"], "ok", "del", "cncl"
|
||||
]);
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: customKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And they could only enter numbers, periods, and dashes!
|
||||
});
|
||||
```
|
||||
|
||||
This will give you a keyboard with six buttons. The first three buttons will open up a 2x2 keyboard. The second three
|
||||
buttons are special keys for submitting, deleting, and cancelling respectively.
|
||||
|
||||
Finally if you are like, super lazy, or have a dynamic set of keys you want to be using at any given time, you can
|
||||
generate keysets from strings like so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const customKeyboard = textInput.generateKeyboard(createCharSet("ABCDEFGHIJKLMNOP", ["ok", "shft", "cncl"]));
|
||||
const customShiftKeyboard = textInput.generateKeyboard(createCharSet("abcdefghijklmnop", ["ok", "shft", "cncl"]));
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: customKeyboard, keyboardShift: customShiftKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And the keyboard was automatically generated to include "ABCDEFGHIJKLMNOP" plus an OK button, a shift button, and a cancel button!
|
||||
});
|
||||
```
|
||||
|
||||
The promise resolves when the user hits "ok" on the input or if they cancel. If the user cancels, undefined is
|
||||
returned, although the user can hit "OK" with an empty string as well. If you define a custom character set and
|
||||
do not include the "ok" button your user will be soft-locked by the keyboard. Fair warning!
|
||||
|
||||
At some point I may add swipe-for-space and swipe-for-delete as well as swipe-for-submit and swipe-for-cancel
|
||||
however I want to have a good strategy for the touch screen
|
||||
[affordance](https://careerfoundry.com/en/blog/ux-design/affordances-ux-design/).
|
||||
|
||||
## Secret features
|
||||
|
||||
If you long press a key with characters on it, that will enable "Shift" mode.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcBkmSpICVz//ABARGCBIRByA/Dk+AAgUH8AECgP4kmRCwX4n+PAoXH8YEC+IRC4HguE4/+P/EfCIXwgARHn4RG+P/j4RDJwgRBGQIRIEYNxCIRECGpV/CIXAgY1P4/8v41JOgeOn4RDGo4jER5Y1FCJWQg4RDYpeSNIQAMkmTCBwRBz4IG9YRIyA8COgJHBhMgI4+QyVJAYJrC9Mkw5rHwFAkEQCImSCJvAhIRBpazFGo3HEYVJkIjGCIIUCAQu/CKGSGo4jPLIhHMNayPLYo6zBYozpH9MvdI+TfaGSv4KHCI+Qg4GDI4IABg5HGyIYENYIAB45rGyPACKIIDx/4gF/CIPx/8fCIY1F4H8CJPA8BtCa4I1DCJFxCIYXBCILXBGpXHGplwn5HPuE4NaH4n6PLyC6CgEnYpeSpICDdJYRFz4RQARQ"))
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 852 B |
|
@ -0,0 +1,489 @@
|
|||
/**
|
||||
* Attempt to lay out a set of characters in a logical way to optimize the number of buttons with the number
|
||||
* of characters per button. Useful if you need to dynamically (or frequently) change your character set
|
||||
* and don't want to create a layout for ever possible combination.
|
||||
* @param text The text you want to parse into a character set.
|
||||
* @param specials Any special buttons you want to add to the keyboard (must match hardcoded special string values)
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function createCharSet(text, specials) {
|
||||
specials = specials || [];
|
||||
const mandatoryExtraKeys = specials.length;
|
||||
const preferredNumChars = [1, 2, 4, 6, 9, 12];
|
||||
const preferredNumKeys = [4, 6, 9, 12].map(num => num - mandatoryExtraKeys);
|
||||
let keyIndex = 0, charIndex = 0;
|
||||
let keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex];
|
||||
while (keySpace < text.length) {
|
||||
const numKeys = preferredNumKeys[keyIndex];
|
||||
const numChars = preferredNumChars[charIndex];
|
||||
const nextNumKeys = preferredNumKeys[keyIndex];
|
||||
const nextNumChars = preferredNumChars[charIndex];
|
||||
if (numChars <= numKeys) {
|
||||
charIndex++;
|
||||
} else if ((text.length / nextNumChars) < nextNumKeys) {
|
||||
charIndex++;
|
||||
} else {
|
||||
keyIndex++;
|
||||
}
|
||||
keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex];
|
||||
}
|
||||
const charsPerKey = preferredNumChars[charIndex];
|
||||
let charSet = [];
|
||||
for (let i = 0; i < text.length; i += charsPerKey) {
|
||||
charSet.push(text.slice(i, i + charsPerKey)
|
||||
.split(""));
|
||||
}
|
||||
charSet = charSet.concat(specials);
|
||||
return charSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the width, height, padding (between chars) and number of characters that need to fit horizontally /
|
||||
* vertically, this function attempts to select the largest font it can that will still fit within the bounds when
|
||||
* drawing a grid of characters. Does not handle multi-letter entries well, assumes we are laying out a grid of
|
||||
* single characters.
|
||||
* @param width The total width available for letters (px)
|
||||
* @param height The total height available for letters (px)
|
||||
* @param padding The amount of space required between characters (px)
|
||||
* @param gridWidth The number of characters wide the rendering is going to be
|
||||
* @param gridHeight The number of characters high the rendering is going to be
|
||||
* @returns {{w: number, h: number, font: string}}
|
||||
*/
|
||||
function getBestFont(width, height, padding, gridWidth, gridHeight) {
|
||||
let font = "4x6";
|
||||
let w = 4;
|
||||
let h = 6;
|
||||
const charMaxWidth = width / gridWidth - padding * gridWidth;
|
||||
const charMaxHeight = height / gridHeight - padding * gridHeight;
|
||||
if (charMaxWidth >= 6 && charMaxHeight >= 8) {
|
||||
w = 6;
|
||||
h = 8;
|
||||
font = "6x8";
|
||||
}
|
||||
if (charMaxWidth >= 12 && charMaxHeight >= 16) {
|
||||
w = 12;
|
||||
h = 16;
|
||||
font = "6x8:2";
|
||||
}
|
||||
if (charMaxWidth >= 12 && charMaxHeight >= 20) {
|
||||
w = 12;
|
||||
h = 20;
|
||||
font = "12x20";
|
||||
}
|
||||
if (charMaxWidth >= 20 && charMaxHeight >= 20) {
|
||||
font = "Vector" + Math.floor(Math.min(charMaxWidth, charMaxHeight));
|
||||
const dims = g.setFont(font)
|
||||
.stringMetrics("W");
|
||||
w = dims.width
|
||||
h = dims.height;
|
||||
}
|
||||
return {w, h, font};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a set of key objects given an array of arrays of characters to make available for typing.
|
||||
* @param characterArrays
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function getKeys(characterArrays) {
|
||||
if (Array.isArray(characterArrays)) {
|
||||
return Promise.all(characterArrays.map((chars, i) => generateKeyFromChars(characterArrays, i)));
|
||||
} else {
|
||||
return generateKeyFromChars(characterArrays, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function generateKeyFromChars(chars, i) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let special;
|
||||
if (!Array.isArray(chars[i]) && chars[i].length > 1) {
|
||||
// If it's not an array we assume it's a string. Fingers crossed I guess, lol. Be nice to my functions!
|
||||
special = chars[i];
|
||||
}
|
||||
const key = getKeyByIndex(chars, i, special);
|
||||
if (!special) {
|
||||
key.chars = chars[i];
|
||||
}
|
||||
if (key.chars.length > 1) {
|
||||
key.pendingSubKeys = true;
|
||||
key.getSubKeys = () => getKeys(key.chars);
|
||||
resolve(key)
|
||||
} else {
|
||||
resolve(key);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a set of characters (or sets of characters) get the position and dimensions of the i'th key in that set.
|
||||
* @param charSet An array where each element represents a key on the hypothetical keyboard.
|
||||
* @param i The index of the key in the set you want to get dimensions for.
|
||||
* @param special The special property of the key - for example "del" for a key used for deleting characters.
|
||||
* @returns {{special, bord: number, pad: number, w: number, x: number, h: number, y: number, chars: *[]}}
|
||||
*/
|
||||
function getKeyByIndex(charSet, i, special) {
|
||||
// Key dimensions
|
||||
const keyboardOffsetY = 40;
|
||||
const margin = 3;
|
||||
const padding = 4;
|
||||
const border = 2;
|
||||
const gridWidth = Math.ceil(Math.sqrt(charSet.length));
|
||||
const gridHeight = Math.ceil(charSet.length / gridWidth);
|
||||
const keyWidth = Math.floor((g.getWidth()) / gridWidth) - margin;
|
||||
const keyHeight = Math.floor((g.getHeight() - keyboardOffsetY) / gridHeight) - margin;
|
||||
const gridx = i % gridWidth;
|
||||
const gridy = Math.floor(i / gridWidth) % gridWidth;
|
||||
const x = gridx * (keyWidth + margin);
|
||||
const y = gridy * (keyHeight + margin) + keyboardOffsetY;
|
||||
const w = keyWidth;
|
||||
const h = keyHeight;
|
||||
// internal Character spacing
|
||||
const numChars = charSet[i].length;
|
||||
const subGridWidth = Math.ceil(Math.sqrt(numChars));
|
||||
const subGridHeight = Math.ceil(numChars / subGridWidth);
|
||||
const bestFont = getBestFont(w - padding, h - padding, 0, subGridWidth, subGridHeight);
|
||||
const letterWidth = bestFont.w;
|
||||
const letterHeight = bestFont.h;
|
||||
const totalWidth = (subGridWidth - 1) * (w / subGridWidth) + padding + letterWidth + 1;
|
||||
const totalHeight = (subGridHeight - 1) * (h / subGridHeight) + padding + letterHeight + 1;
|
||||
const extraPadH = (w - totalWidth) / 2;
|
||||
const extraPadV = (h - totalHeight) / 2;
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
pad : padding,
|
||||
bord : border,
|
||||
chars: [],
|
||||
special,
|
||||
subGridWidth,
|
||||
subGridHeight,
|
||||
extraPadH,
|
||||
extraPadV,
|
||||
font : bestFont.font
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is probably the most intense part of this keyboard library. If you don't do it ahead of time, it will happen
|
||||
* when you call the keyboard, and it can take up to 1.5 seconds for a full keyboard. Not a super great user experience
|
||||
* SO if you have a tiny keyset, don't worry about it so much, but if you want to maximize performance, generate
|
||||
* a keyboard ahead of time and pass it into the "keyboard" argument of the object in the "input" function.
|
||||
* @param charSets
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function generateKeyboard(charSets) {
|
||||
if (!Array.isArray(charSets)) {
|
||||
// User passed a string. We will divvy it up into a real set of subdivided characters.
|
||||
charSets = createCharSet(charSets, ["ok", "del", "shft"]);
|
||||
}
|
||||
return getKeys(charSets);
|
||||
}
|
||||
|
||||
const defaultCharSet = [
|
||||
["a", "b", "c", "d", "e", "f", "g", "h", "i"],
|
||||
["j", "k", "l", "m", "n", "o", "p", "q", "r"],
|
||||
["s", "t", "u", "v", "w", "x", "y", "z", "0"],
|
||||
["1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||
[" ", "`", "-", "=", "[", "]", "\\", ";", "'"],
|
||||
[",", ".", "/"],
|
||||
"ok",
|
||||
"shft",
|
||||
"del"
|
||||
];
|
||||
|
||||
const defaultCharSetShift = [
|
||||
["A", "B", "C", "D", "E", "F", "G", "H", "I"],
|
||||
["J", "K", "L", "M", "N", "O", "P", "Q", "R"],
|
||||
["S", "T", "U", "V", "W", "X", "Y", "Z", ")"],
|
||||
["!", "@", "#", "$", "%", "^", "&", "*", "("],
|
||||
["~", "_", "+", "{", "}", "|", ":", "\"", "<"],
|
||||
[">", "?"],
|
||||
"ok",
|
||||
"shft",
|
||||
"del"
|
||||
];
|
||||
|
||||
/**
|
||||
* Given initial options, allow the user to type a set of characters and return their entry in a promise. If you do not
|
||||
* submit your own character set, a default alphanumeric keyboard will display.
|
||||
* @param options The object containing initial options for the keyboard.
|
||||
* @param {string} options.text The initial text to display / edit in the keyboard
|
||||
* @param {array[]|string[]} [options.keyboardMain] The primary keyboard generated with generateKeyboard()
|
||||
* @param {array[]|string[]} [options.keyboardShift] Like keyboardMain, but displayed when shift / capslock is pressed.
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function input(options) {
|
||||
options = options || {};
|
||||
let typed = options.text || "";
|
||||
let resolveFunction = () => {};
|
||||
let shift = false;
|
||||
let caps = false;
|
||||
let activeKeySet;
|
||||
|
||||
const offsetX = 0;
|
||||
const offsetY = 40;
|
||||
|
||||
E.showMessage("Loading...");
|
||||
let keyboardPromise;
|
||||
if (options.keyboardMain) {
|
||||
keyboardPromise = Promise.all([options.keyboardMain, options.keyboardShift || Promise.resolve([])]);
|
||||
} else {
|
||||
keyboardPromise = Promise.all([generateKeyboard(defaultCharSet), generateKeyboard(defaultCharSetShift)])
|
||||
}
|
||||
|
||||
let mainKeys;
|
||||
let mainKeysShift;
|
||||
|
||||
/**
|
||||
* Draw an individual keyboard key - handles special formatting and the rectangle pad, followed by the character
|
||||
* rendering. Returns a promise so that you can do many of these in a loop without blocking the thread.
|
||||
* @param key
|
||||
*/
|
||||
function drawKey(key) {
|
||||
let bgColor = g.theme.bg;
|
||||
if (key.special) {
|
||||
if (key.special === "ok") bgColor = "#0F0";
|
||||
if (key.special === "cncl") bgColor = "#F00";
|
||||
if (key.special === "del") bgColor = g.theme.bg2;
|
||||
if (key.special === "spc") bgColor = g.theme.bg2;
|
||||
if (key.special === "shft") {
|
||||
bgColor = shift ? g.theme.bgH : g.theme.bg2;
|
||||
}
|
||||
if (key.special === "caps") {
|
||||
bgColor = caps ? g.theme.bgH : g.theme.bg2;
|
||||
}
|
||||
g.setColor(bgColor)
|
||||
.fillRect({x: key.x, y: key.y, w: key.w, h: key.h});
|
||||
}
|
||||
g.setColor(g.theme.fg)
|
||||
.drawRect({x: key.x, y: key.y, w: key.w, h: key.h});
|
||||
drawChars(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the characters for a given key - this handles the layout of all characters needed for the key, whether the
|
||||
* key has 12 characters, 1, or if it represents a special key.
|
||||
* @param key
|
||||
*/
|
||||
function drawChars(key) {
|
||||
const numChars = key.chars.length;
|
||||
if (key.special) {
|
||||
g.setColor(g.theme.fg)
|
||||
.setFont("12x20")
|
||||
.setFontAlign(-1, -1)
|
||||
.drawString(key.special, key.x + key.w / 2 - g.stringWidth(key.special) / 2, key.y + key.h / 2 - 10, false);
|
||||
} else {
|
||||
g.setColor(g.theme.fg)
|
||||
.setFont(key.font)
|
||||
.setFontAlign(-1, -1);
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
const gridX = i % key.subGridWidth;
|
||||
const gridY = Math.floor(i / key.subGridWidth) % key.subGridWidth;
|
||||
const charOffsetX = gridX * (key.w / key.subGridWidth);
|
||||
const charOffsetY = gridY * (key.h / key.subGridHeight);
|
||||
const posX = key.x + key.pad + charOffsetX + key.extraPadH;
|
||||
const posY = key.y + key.pad + charOffsetY + key.extraPadV;
|
||||
g.drawString(key.chars[i], posX, posY, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key set corresponding to the indicated shift state. Allows easy switching between capital letters and
|
||||
* lower case by just switching the boolean passed here.
|
||||
* @param shift
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function getMainKeySet(shift) {
|
||||
return shift ? mainKeysShift : mainKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw all the given keys on the screen.
|
||||
* @param keys
|
||||
*/
|
||||
function drawKeys(keys) {
|
||||
keys.forEach(key => {
|
||||
drawKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the text that the user has typed so far, includes a cursor and automatic truncation when the string is too
|
||||
* long.
|
||||
* @param text
|
||||
* @param cursorChar
|
||||
*/
|
||||
function drawTyped(text, cursorChar) {
|
||||
let visibleText = text;
|
||||
let ellipsis = false;
|
||||
const maxWidth = 176 - 40;
|
||||
while (g.setFont("12x20")
|
||||
.stringWidth(visibleText) > maxWidth) {
|
||||
ellipsis = true;
|
||||
visibleText = visibleText.slice(1);
|
||||
}
|
||||
if (ellipsis) {
|
||||
visibleText = "..." + visibleText;
|
||||
}
|
||||
g.setColor(g.theme.bg2)
|
||||
.fillRect(5, 5, 171, 30);
|
||||
g.setColor(g.theme.fg2)
|
||||
.setFont("12x20")
|
||||
.drawString(visibleText + cursorChar, 15, 10, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the space on the screen that the keyboard occupies (not the text the user has written).
|
||||
*/
|
||||
function clearKeySpace() {
|
||||
g.setColor(g.theme.bg)
|
||||
.fillRect(offsetX, offsetY, 176, 176);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on a touch even, determine which key was pressed by the user.
|
||||
* @param touchEvent
|
||||
* @param keys
|
||||
* @returns {*}
|
||||
*/
|
||||
function getTouchedKey(touchEvent, keys) {
|
||||
return keys.find((key) => {
|
||||
let relX = touchEvent.x - key.x;
|
||||
let relY = touchEvent.y - key.y;
|
||||
return relX > 0 && relX < key.w && relY > 0 && relY < key.h;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On a touch event, determine whether a key is touched and take appropriate action if it is.
|
||||
* @param button
|
||||
* @param touchEvent
|
||||
*/
|
||||
function keyTouch(button, touchEvent) {
|
||||
const pressedKey = getTouchedKey(touchEvent, activeKeySet);
|
||||
if (pressedKey == null) {
|
||||
// User tapped empty space.
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
return;
|
||||
}
|
||||
if (pressedKey.pendingSubKeys) {
|
||||
// We have to generate the subkeys for this key still, but we decided to wait until we needed it!
|
||||
pressedKey.pendingSubKeys = false;
|
||||
pressedKey.getSubKeys()
|
||||
.then(subkeys => {
|
||||
pressedKey.subKeys = subkeys;
|
||||
keyTouch(undefined, touchEvent);
|
||||
})
|
||||
return;
|
||||
}
|
||||
// Haptic feedback
|
||||
Bangle.buzz(25, 1);
|
||||
if (pressedKey.subKeys) {
|
||||
// Hold press for "shift!"
|
||||
if (touchEvent.type > 1) {
|
||||
shift = !shift;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
} else {
|
||||
swapKeySet(pressedKey.subKeys);
|
||||
}
|
||||
} else {
|
||||
if (pressedKey.special) {
|
||||
evaluateSpecialFunctions(pressedKey);
|
||||
} else {
|
||||
typed = typed + pressedKey.chars;
|
||||
shift = false;
|
||||
drawTyped(typed, "");
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage setting, generating, and rendering new keys when a key set is changed.
|
||||
* @param newKeys
|
||||
*/
|
||||
function swapKeySet(newKeys) {
|
||||
activeKeySet = newKeys;
|
||||
clearKeySpace();
|
||||
drawKeys(activeKeySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the key contains any of the special strings that have their own special behaviour when pressed.
|
||||
* @param key
|
||||
*/
|
||||
function evaluateSpecialFunctions(key) {
|
||||
switch (key.special) {
|
||||
case "ok":
|
||||
setTimeout(() => resolveFunction(typed), 50);
|
||||
break;
|
||||
case "del":
|
||||
typed = typed.slice(0, -1);
|
||||
drawTyped(typed, "");
|
||||
break;
|
||||
case "shft":
|
||||
shift = !shift;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
break;
|
||||
case "caps":
|
||||
caps = !caps;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
break;
|
||||
case "cncl":
|
||||
setTimeout(() => resolveFunction(), 50);
|
||||
break;
|
||||
case "spc":
|
||||
typed = typed + " ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let isCursorVisible = true;
|
||||
|
||||
const blinkInterval = setInterval(() => {
|
||||
if (!activeKeySet) return;
|
||||
isCursorVisible = !isCursorVisible;
|
||||
if (isCursorVisible) {
|
||||
drawTyped(typed, "_");
|
||||
} else {
|
||||
drawTyped(typed, "");
|
||||
}
|
||||
}, 200);
|
||||
|
||||
|
||||
/**
|
||||
* We return a promise but the resolve function is assigned to a variable in the higher function scope. That allows
|
||||
* us to return the promise and resolve it after we are done typing without having to return the entire scope of the
|
||||
* application within the promise.
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
g.clear(true);
|
||||
resolveFunction = resolve;
|
||||
keyboardPromise.then((result) => {
|
||||
mainKeys = result[0];
|
||||
mainKeysShift = result[1];
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
Bangle.setUI({
|
||||
mode: "custom", touch: keyTouch
|
||||
});
|
||||
Bangle.setLocked(false);
|
||||
})
|
||||
}).then((result) => {
|
||||
g.clearRect(Bangle.appRect);
|
||||
clearInterval(blinkInterval);
|
||||
Bangle.setUI();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
exports.input = input;
|
||||
exports.generateKeyboard = generateKeyboard;
|
||||
exports.defaultCharSet = defaultCharSet;
|
||||
exports.defaultCharSetShift = defaultCharSetShift;
|
||||
exports.createCharSet = createCharSet;
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "kbmatry",
|
||||
"name": "Matryoshka Keyboard",
|
||||
"version":"1.06",
|
||||
"description": "A library for text input via onscreen keyboard. Easily enter characters with nested keyboards.",
|
||||
"icon": "icon.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot6.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"},{"url":"screenshot5.png"},{"url": "help.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |