kbmorse: new keyboard, for the Bangle.js 1

pull/1809/head
Richard de Boer 2022-05-09 20:55:31 +02:00
parent 0e2dccdbda
commit 3990c0ef32
No known key found for this signature in database
GPG Key ID: 8721727971871937
7 changed files with 288 additions and 0 deletions

1
apps/kbmorse/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New Keyboard!

25
apps/kbmorse/README.md Normal file
View File

@ -0,0 +1,25 @@
# Morse Keyboard
A library that provides the ability to input text by entering morse code.
![demo](demo.gif)
## Usage
* Press `BTN1` to input a dot, `BTN3` to input a dash, and `BTN2` to accept the
character for your current input.
* Long-press `BTN1` to toggle UPPERCASE for your next character.
* Long-press `BTN2` to finish editing.
* Tap the left side of the screen for backspace.
* Swipe left/right to move the cursor.
* Input three spaces in a row for a newline.
The top/bottom of the screen show which characters start with your current input,
so basically you just look which side includes the letter you want to type, and
press that button to narrow your selection, until it appears next to `BTN2`.
## For Developers
See the README for `kbswipe`/`kbtouch` for instructions on how to use this in your app.

BIN
apps/kbmorse/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
apps/kbmorse/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 KiB

247
apps/kbmorse/lib.js Normal file
View File

@ -0,0 +1,247 @@
exports.input = function(options) {
options = options || {};
let text = options.text;
if ("string"!= typeof text) text = "";
let code = "",
cur = text.length, // cursor position
uc = !text.length, // uppercase
spc = 0; // consecutive spaces entered
const codes = {
// letters
"a": ".-",
"b": "-...",
"c": "-.-.",
"d": "-..",
"e": ".",
// no é
"f": "..-.",
"g": "--.",
"h": "....",
"i": "..",
"j": ".---",
"k": "-.-",
"l": ".-..",
"m": "--",
"n": "-.",
"o": "---",
"p": ".--.",
"q": "--.-",
"r": ".-.",
"s": "...",
"t": "-",
"u": "..-",
"v": "...-",
"w": ".--",
"x": "-..-",
"y": "-.--",
"z": "--..",
//digits
"1": ".----",
"2": "..---",
"3": "...--",
"4": "....-",
"5": ".....",
"6": "-....",
"7": "--...",
"8": "---..",
"9": "----.",
"0": "-----",
// punctuation
".": ".-.-.-",
",": "--..--",
":": "---...",
"?": "..--..",
"!": "-.-.--",
"'": ".----.",
"-": "-....-",
"_": "..--.-",
"/": "-..-.",
"(": "-.--.",
")": "-.--.-",
"\"": ".-..-.",
"=": "-...-",
"+": ".-.-.",
"*": "-..-",
"@": ".--.-.",
"$": "...-..-",
"&": ".-...",
}, chars = Object.keys(codes);
function choices(start) {
return chars.filter(char => codes[char].startsWith(start));
}
function char(code) {
if (code==="") return " ";
for(const char in codes) {
if (codes[char]===code) return char;
}
const c = choices(code);
if (c.length===1) return c[0]; // "-.-.-" is nothing, and only "-.-.--"(!) starts with it
return null;
}
return new Promise((resolve, reject) => {
function update() {
let dots = [], dashes = [];
layout.pick.label = (code==="" ? " " : "");
choices(code).forEach(char => {
const c = codes[char];
if (c===code) {
layout.pick.label = char;
}
const next = c.substring(code.length, code.length+1);
if (next===".") dots.push(char);
else if (next==="-") dashes.push(char);
});
if (!code && spc>1) layout.pick.label = atob("ABIYAQAAAAAAAAAABwABwABwABwABwABwOBwOBwOBxwBxwBxwB/////////xwABwABwAAOAAOAAOAA==");
g.setFont("6x8:2");
const wrap = t => g.wrapString(t, Bangle.appRect.w-60).join("\n");
layout.del.label = cur ? atob("AAwIAQ/hAiKkEiKhAg/gAA==") : " ";
layout.code.label = code;
layout.dots.label = wrap(dots.join(" "));
layout.dashes.label = wrap(dashes.join(" "));
if (uc) {
layout.pick.label = layout.pick.label.toUpperCase();
layout.dots.label = layout.dots.label.toUpperCase();
layout.dashes.label = layout.dashes.label.toUpperCase();
}
let label = text.slice(0, cur)+"|"+text.slice(cur);
layout.text.label = g.wrapString(label, Bangle.appRect.w-80).join("\n")
.replace("|", atob("AAwQAfPPPAwAwAwAwAwAwAwAwAwAwAwAwPPPPA=="));
layout.update();
layout.render();
}
function add(d) {
code += d;
const l = choices(code).length;
if (l===1) done();
else if (l<1) {
Bangle.buzz(20);
code = code.slice(0, -1);
} else update();
}
function del() {
if (code.length) code = code.slice(0, -1); // delete last dot/dash
else if (cur) { // delete char at cursor
text = text.slice(0, cur-1)+text.slice(cur);
cur--;
} else Bangle.buzz(20); // (already) at start of text
spc = 0;
uc = false;
update();
}
function done() {
let c = char(code);
if (c!==null) {
if (uc) c = c.toUpperCase();
uc = false;
text = text.slice(0, cur)+c+text.slice(cur);
cur++;
code = "";
if (c===" ") spc++;
else spc = 0;
if (spc>=3) {
text = text.slice(0, cur-3)+"\n"+text.slice(cur);
cur -= 2;
uc = true;
spc = 0;
}
update();
} else {
console.log(`No char for ${code}!`);
Bangle.buzz(20);
}
}
const Layout = require("Layout");
let layout = new Layout({
type: "h", c: [
{
type: "v", width: Bangle.appRect.w-8, bgCol: g.theme.bg, c: [
{id: "dots", type: "txt", font: "6x8:2", label: "", fillx: 1, bgCol: g.theme.bg},
{filly: 1, bgCol: g.theme.bg},
{
type: "h", fillx: 1, c: [
{id: "del", type: "txt", font: "6x8", label: "<X"},
{width: 5, bgCol: g.theme.bg},
{id: "text", type: "txt", font: "6x8:2", col: g.theme.fg2, bgCol: g.theme.bg2},
{fillx: 1, bgCol: g.theme.bg},
{id: "code", type: "txt", font: "6x8", label: "", bgCol: g.theme.bg},
{width: 5, bgCol: g.theme.bg},
{id: "pick", type: "txt", font: "6x8:3", label: "", col: g.theme.fgH, bgCol: g.theme.bgH},
],
},
{filly: 1, bgCol: g.theme.bg},
{id: "dashes", type: "txt", font: "6x8:2", label: "", fillx: 1, bgCol: g.theme.bg},
]
},
// button labels (rotated 90 degrees)
{
type: "v", pad: 1, filly: 1, c: ["<.", "^", "|"].map(l =>
({type: "txt", font: "6x8", height: Math.floor(Bangle.appRect.h/3), r: 1, label: l})
)
}
]
});
g.reset().clear();
update();
if (Bangle.btnWatches) Bangle.btnWatches.forEach(clearWatch);
Bangle.btnWatches = [];
// BTN1: press for dot, long-press to toggle uppercase
let ucTimeout;
const UC_TIME = 500;
Bangle.btnWatches.push(setWatch(e => {
if (ucTimeout) clearTimeout(ucTimeout);
ucTimeout = null;
if (e.state) {
// pressed: start UpperCase toggle timer
ucTimeout = setTimeout(() => {
ucTimeout = null;
uc = !uc;
update();
}, UC_TIME);
} else if (e.time-e.lastTime<UC_TIME/1000) add(".");
}, BTN1, {repeat: true, edge: "both"}));
// BTN2: press to pick current character, long press to enter text
let enterTimeout;
const ENTER_TIME = 1000;
Bangle.btnWatches.push(setWatch(e => {
if (enterTimeout) clearTimeout(enterTimeout);
enterTimeout = null;
if (e.state) {
// pressed: start UpperCase toggle timer
enterTimeout = setTimeout(() => {
enterTimeout = null;
resolve(text);
}, ENTER_TIME);
} else if (e.time-e.lastTime<ENTER_TIME/1000) done();
}, BTN2, {repeat: true, edge: "both"}));
// BTN3: press for dash (long-press is hardcoded to device reboot)
Bangle.btnWatches.push(setWatch(() => {
add("-");
}, BTN3, {repeat: true, edge: "falling"}));
// Left-hand side: backspace
if (Bangle.touchHandler) Bangle.removeListener("touch", Bangle.touchHandler);
Bangle.touchHandler = side => {
if (side===1) del();
};
Bangle.on("touch", Bangle.touchHandler);
// swipe: move cursor
if (Bangle.swipeHandler) Bangle.removeListener("swipe", Bangle.swipeHandler);
Bangle.swipeHandler = dir => {
cur = Math.max(0, Math.min(text.length, cur+dir));
update();
};
Bangle.on("swipe", Bangle.swipeHandler);
});
};

View File

@ -0,0 +1,15 @@
{
"id": "kbmorse",
"name": "Morse keyboard",
"version": "0.01",
"description": "A library for text input as morse code",
"icon": "app.png",
"type": "textinput",
"tags": "keyboard",
"supports" : ["BANGLEJS"],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"}
]
}

BIN
apps/kbmorse/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB