Public Access
Add widget kit, input layer with Compose Key, Launcher and demo apps
Steps 5 and 6 of M0. - lib/ui (host-tested): ListModel, LineEditor (UTF-8, byte-limited), wrapText, ToastQueue, DialogModel - lib/input (host-tested): KeyMapper turns keyboard state into logical keys (Fn arrows, ` Back, Fn+` Home) and composes accents with opt - src/ui: off-screen 16-bit frame pushed in one transfer (fixes the flicker), Status Bar, list, text, line editor, dialog, Toast widgets, Notifier (Toast + beep + LED), vendored public-domain Latin-1 fonts - Apps: minimal Launcher, Widget demo, Diagnostics (SD erase now behind a confirmation dialog) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
#include "key_mapper.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace roro {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kArmed = '*';
|
||||
|
||||
bool isAccent(char c) { return c == '\'' || c == '`' || c == '^' || c == '"' || c == ',' || c == '~'; }
|
||||
|
||||
// Latin-1 code point for accent + base letter, or 0.
|
||||
uint32_t composeLatin1(char accent, char base) {
|
||||
struct Entry {
|
||||
char accent;
|
||||
const char* bases;
|
||||
const uint32_t* codes;
|
||||
};
|
||||
static const uint32_t acute[] = {0xE1, 0xE9, 0xED, 0xF3, 0xFA, 0xFD, 0xC1, 0xC9, 0xCD, 0xD3, 0xDA, 0xDD};
|
||||
static const uint32_t grave[] = {0xE0, 0xE8, 0xEC, 0xF2, 0xF9, 0xC0, 0xC8, 0xCC, 0xD2, 0xD9};
|
||||
static const uint32_t circ[] = {0xE2, 0xEA, 0xEE, 0xF4, 0xFB, 0xC2, 0xCA, 0xCE, 0xD4, 0xDB};
|
||||
static const uint32_t diaer[] = {0xE4, 0xEB, 0xEF, 0xF6, 0xFC, 0xFF, 0xC4, 0xCB, 0xCF, 0xD6, 0xDC};
|
||||
static const uint32_t cedil[] = {0xE7, 0xC7};
|
||||
static const uint32_t tilde[] = {0xE3, 0xF1, 0xF5, 0xC3, 0xD1, 0xD5};
|
||||
static const Entry table[] = {
|
||||
{'\'', "aeiouyAEIOUY", acute}, {'`', "aeiouAEIOU", grave}, {'^', "aeiouAEIOU", circ},
|
||||
{'"', "aeiouyAEIOU", diaer}, {',', "cC", cedil}, {'~', "anoANO", tilde},
|
||||
};
|
||||
for (auto& e : table) {
|
||||
if (e.accent != accent) continue;
|
||||
for (int i = 0; e.bases[i]; i++)
|
||||
if (e.bases[i] == base) return e.codes[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
KeyEvent charEvent(uint32_t cp, const RawKeys& keys) {
|
||||
KeyEvent e = KeyEvent::character(cp);
|
||||
e.shift = keys.shift;
|
||||
e.ctrl = keys.ctrl;
|
||||
e.alt = keys.alt;
|
||||
return e;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<KeyEvent> KeyMapper::update(const RawKeys& keys) {
|
||||
std::vector<KeyEvent> out;
|
||||
|
||||
if (keys.opt && !previous_.opt && keys.chars.empty()) {
|
||||
compose_ = compose_ ? 0 : kArmed; // a second opt cancels
|
||||
}
|
||||
if (keys.enter && !previous_.enter) out.push_back(KeyEvent::of(Key::Select));
|
||||
if (keys.del && !previous_.del) out.push_back(KeyEvent::of(Key::Delete));
|
||||
if (keys.tab && !previous_.tab) out.push_back(KeyEvent::of(Key::Tab));
|
||||
|
||||
for (char c : keys.chars) {
|
||||
bool wasHeld = std::find(previous_.chars.begin(), previous_.chars.end(), c) != previous_.chars.end();
|
||||
if (!wasHeld) onChar(c, keys, out);
|
||||
}
|
||||
|
||||
previous_ = keys;
|
||||
return out;
|
||||
}
|
||||
|
||||
void KeyMapper::onChar(char c, const RawKeys& keys, std::vector<KeyEvent>& out) {
|
||||
// Holding opt while typing the accent is the same as pressing opt first.
|
||||
if (keys.opt && !compose_) compose_ = kArmed;
|
||||
|
||||
if (compose_ == kArmed) {
|
||||
if (isAccent(c)) {
|
||||
compose_ = c;
|
||||
return;
|
||||
}
|
||||
compose_ = 0; // not an accent: type it normally below
|
||||
} else if (compose_) {
|
||||
char accent = compose_;
|
||||
compose_ = 0;
|
||||
uint32_t composed = composeLatin1(accent, c);
|
||||
if (composed) {
|
||||
out.push_back(charEvent(composed, keys));
|
||||
} else {
|
||||
out.push_back(charEvent(static_cast<unsigned char>(accent), keys));
|
||||
out.push_back(charEvent(static_cast<unsigned char>(c), keys));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (keys.fn) {
|
||||
switch (c) {
|
||||
case ';': out.push_back(KeyEvent::of(Key::Up)); return;
|
||||
case '.': out.push_back(KeyEvent::of(Key::Down)); return;
|
||||
case ',': out.push_back(KeyEvent::of(Key::Left)); return;
|
||||
case '/': out.push_back(KeyEvent::of(Key::Right)); return;
|
||||
case '`': out.push_back(KeyEvent::of(Key::Home)); return;
|
||||
default: return; // other Fn combos are unassigned
|
||||
}
|
||||
}
|
||||
if (c == '`') {
|
||||
out.push_back(KeyEvent::of(Key::Back));
|
||||
return;
|
||||
}
|
||||
out.push_back(charEvent(static_cast<unsigned char>(c), keys));
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
Reference in New Issue
Block a user