mirror of https://github.com/espruino/BangleApps
kbmorse: new keyboard, for the Bangle.js 1
parent
0e2dccdbda
commit
3990c0ef32
|
@ -0,0 +1 @@
|
|||
0.01: New Keyboard!
|
|
@ -0,0 +1,25 @@
|
|||
# Morse Keyboard
|
||||
|
||||
A library that provides the ability to input text by entering morse code.
|
||||
|
||||

|
||||
|
||||
## 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.
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 693 KiB |
|
@ -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);
|
||||
});
|
||||
};
|
|
@ -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"}
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
Loading…
Reference in New Issue