mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(keyboard): hold-Enter accent popup; relocate cursor-mode trigger
Replaces the 8 separate Latin-diacritic alt-alphabet pages (Polish, Czech, Slovak, German, French, Spanish, Portuguese, Nordic) with one popup: Hold Enter on a Latin letter that has accented variants (a c d e i l n o r s t u y z) opens a floating horizontal strip of that letter's accents, anchored over its own row so the grid stays visible underneath (LEFT/ RIGHT picks, Enter inserts via a new shared insertGlyph() helper, Cancel dismisses). Holding a letter with no variants is a no-op. Cyrillic/Greek remain full alt-alphabet pages; NodePrefs::keyboard_alt_alphabet shrinks from 11 to 3 values accordingly -- an old saved Polish..Nordic value just clamps to Latin via DataStore.cpp's existing range check, no migration code needed. Freeing Hold-Enter on letter cells required moving cursor-mode's own trigger: UP from the top letter row now enters it instead of wrapping to the special row. To keep that wrap reachable, cursor mode's UP/DOWN (Home/End) continue the wrap once already at that boundary -- UP again lands on the special row, DOWN again back on the letter grid -- reusing the same proportional column mapping the old direct wrap used. Diagnostics' font-coverage sample swaps its 8 per-language lines for one line sampling the new accent table. Docs (message_screen, settings_screen, solo_ui_framework) and release-notes updated to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -332,28 +332,19 @@ struct NodePrefs { // persisted to file
|
||||
// and the misc-fixed font (src/helpers/ui/MiscFixedFont.h) for on-screen
|
||||
// rendering — every alphabet here must be in its U+0020-04FF range.
|
||||
//
|
||||
// The Latin-diacritic entries (Polish..Nordic) replace what used to be a
|
||||
// single combined "Ext.Latin" curated subset — each is now its own full,
|
||||
// linguistically-correct set of that language's non-ASCII letters instead
|
||||
// of a shared partial one. Not a schema change: keyboard_alt_alphabet is
|
||||
// still just an index into keyboardAlphabetLabel()/KeyboardWidget's tables,
|
||||
// so widening KB_ALPHABET_COUNT needs no persisted-data migration — only
|
||||
// note that an index of 3 now means Polish, not the old combined Ext.Latin.
|
||||
// Latin-diacritic letters (Polish/Czech/Slovak/German/French/Spanish/
|
||||
// Portuguese/Nordic) used to each be their own full alt-alphabet page here;
|
||||
// they're now reached instead by holding Enter on a Latin letter that has
|
||||
// accented variants (see KeyboardWidget.h's KB_ACCENT_VARIANTS), so this
|
||||
// enum only covers actual non-Latin scripts. Not a schema change: an old
|
||||
// saved value of 3+ (one of the removed languages) just clamps to 0 (Latin)
|
||||
// via DataStore.cpp's existing `>= KB_ALPHABET_COUNT` range check.
|
||||
static const uint8_t KB_ALPHABET_LATIN_ONLY = 0;
|
||||
static const uint8_t KB_ALPHABET_CYRILLIC = 1;
|
||||
static const uint8_t KB_ALPHABET_GREEK = 2;
|
||||
static const uint8_t KB_ALPHABET_POLISH = 3; // ą ć ę ł ń ó ś ź ż
|
||||
static const uint8_t KB_ALPHABET_CZECH = 4; // á č ď é ě í ň ó ř š ť ú ů ý ž
|
||||
static const uint8_t KB_ALPHABET_SLOVAK = 5; // á ä č ď é í ĺ ľ ň ó ô ŕ š ť ú ý ž
|
||||
static const uint8_t KB_ALPHABET_GERMAN = 6; // ä ö ü ß
|
||||
static const uint8_t KB_ALPHABET_FRENCH = 7; // à â ç é è ê ë î ï ô ù û ü ÿ œ
|
||||
static const uint8_t KB_ALPHABET_SPANISH = 8; // á é í ñ ó ú ü
|
||||
static const uint8_t KB_ALPHABET_PORTUGUESE = 9; // á à â ã ç é ê í ó ô õ ú
|
||||
static const uint8_t KB_ALPHABET_NORDIC = 10; // å ä æ ö ø (Danish/Norwegian/Swedish share this set)
|
||||
static const uint8_t KB_ALPHABET_COUNT = 11;
|
||||
static const uint8_t KB_ALPHABET_COUNT = 3;
|
||||
static const char* keyboardAlphabetLabel(uint8_t idx) {
|
||||
static const char* L[KB_ALPHABET_COUNT] = { "Latin", "Cyrillic", "Greek", "Polish", "Czech",
|
||||
"Slovak", "German", "French", "Spanish", "Portuguese", "Nordic" };
|
||||
static const char* L[KB_ALPHABET_COUNT] = { "Latin", "Cyrillic", "Greek" };
|
||||
return L[idx < KB_ALPHABET_COUNT ? idx : 0];
|
||||
}
|
||||
|
||||
|
||||
@@ -175,20 +175,14 @@ class DiagnosticsScreen : public UIScreen {
|
||||
// One sample line per script/keyboard alphabet the UI font claims to cover
|
||||
// (all inside the U+0020-04FF glyph range the OLED misc-fixed / e-ink Lemon
|
||||
// fonts carry), so the font's coverage can be checked by eye on real
|
||||
// hardware. Per-language lines use the same 2-letter codes as
|
||||
// KeyboardWidget::altAlphabetHint and each shows that language's own
|
||||
// non-ASCII letters (see KeyboardWidget.h's KB_*_CHARS tables).
|
||||
// hardware. "Acc" samples one variant from each KeyboardWidget.h
|
||||
// KB_ACCENT_VARIANTS group (the Hold-Enter accent popup that replaced the
|
||||
// old per-language alt-alphabet pages) so every accented letter actually
|
||||
// reachable in the UI still gets an eyeball check.
|
||||
void buildFontLines() {
|
||||
_line_count = 0;
|
||||
addLine("Latin ABCabc xyz");
|
||||
addLine("PL ąćęłńóśźż");
|
||||
addLine("CZ áčďéěíňóřš");
|
||||
addLine("SK áäčďíĺľňôŕ");
|
||||
addLine("DE äöüß");
|
||||
addLine("FR àâçéèêëîïœ");
|
||||
addLine("ES áéíñóúü");
|
||||
addLine("PT áàâãçéêíóôõú");
|
||||
addLine("ND åäæöø");
|
||||
addLine("Acc áçďéíłñóřśťúýź");
|
||||
addLine("Grk ΑΒΓ αβγξω"); // ΑΒΓ αβγξω
|
||||
addLine("Cyr АБВ абвжя"); // АБВ абвжя
|
||||
addLine("Num 0123456789");
|
||||
|
||||
@@ -93,107 +93,6 @@ static const char* const KB_T9_GROUPS_GREEK[9] = {
|
||||
".,!?'-", "αβγ", "δεζ", "ηθι", "κλμ", "νξο", "πρσς", "τυφ", "χψω"
|
||||
};
|
||||
|
||||
// Per-language Latin-diacritic ABC grids. Each is that language's own full
|
||||
// set of non-ASCII letters (not a shared curated subset — see NodePrefs.h's
|
||||
// KB_ALPHABET_POLISH..KB_ALPHABET_NORDIC doc comment for why these replaced a
|
||||
// single combined "Ext.Latin" page). Every grid is filled to all 40 cells the
|
||||
// same way: letters first, then punctuation to round out the last letter
|
||||
// row, then a full digit row, then any further rows filled with more
|
||||
// punctuation/symbols — consistent shape regardless of how few letters a
|
||||
// given language actually needs (German's 4, at the small end, still gets a
|
||||
// full page rather than mostly-blank cells).
|
||||
static const char* const KB_POLISH_CHARS[4][10] = {
|
||||
{ "ą", "ć", "ę", "ł", "ń", "ó", "ś", "ź", "ż", "." },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ ",", "!", "?", "'", "-", "\"", ":", ";", "(", ")" },
|
||||
{ "@", "#", "&", "*", "_", "+", "=", "/", "\\", "~" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_POLISH[9] = {
|
||||
".,!?'-", "ąć", "ęł", "ńó", "śź", "ż", ";()", "@#&*", "_+=/"
|
||||
};
|
||||
|
||||
static const char* const KB_CZECH_CHARS[4][10] = {
|
||||
{ "á", "č", "ď", "é", "ě", "í", "ň", "ó", "ř", "š" },
|
||||
{ "ť", "ú", "ů", "ý", "ž", ".", ",", "!", "?", "'" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "-", "\"", ":", ";", "(", ")", "@", "#", "&", "*" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_CZECH[9] = {
|
||||
".,!?'-", "áč", "ďé", "ěí", "ňó", "řš", "ťú", "ůý", "ž"
|
||||
};
|
||||
|
||||
// Slovak overlaps Czech heavily (both descend from a shared diacritic
|
||||
// tradition) but adds ä/ĺ/ľ/ô/ŕ and drops none — kept as its own page rather
|
||||
// than merged, per the "each language separate" request.
|
||||
static const char* const KB_SLOVAK_CHARS[4][10] = {
|
||||
{ "á", "ä", "č", "ď", "é", "í", "ĺ", "ľ", "ň", "ó" },
|
||||
{ "ô", "ŕ", "š", "ť", "ú", "ý", "ž", ".", ",", "!" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "?", "'", "-", "\"", ":", ";", "(", ")", "@", "#" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_SLOVAK[9] = {
|
||||
".,!?'-", "áäč", "ďé", "íĺ", "ľň", "óô", "ŕš", "ťú", "ýž"
|
||||
};
|
||||
|
||||
// German: only 4 non-ASCII letters (ä ö ü ß) — the smallest of these
|
||||
// keyboards, still filled to a full page rather than left sparse.
|
||||
static const char* const KB_GERMAN_CHARS[4][10] = {
|
||||
{ "ä", "ö", "ü", "ß", ".", ",", "!", "?", "'", "-" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "\"", ":", ";", "(", ")", "@", "#", "&", "*", "_" },
|
||||
{ "+", "=", "/", "\\", "~", "<", ">", "[", "]", "{" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_GERMAN[9] = {
|
||||
".,!?'-", "äö", "üß", ";()", "@#&*", "_+=/", "\\~<>", "[]{}", "|^$%"
|
||||
};
|
||||
|
||||
// French: à â ç é è ê ë î ï ô ù û ü ÿ œ. ÿ's uppercase (Ÿ, U+0178) is the one
|
||||
// case-shift exception outside the Latin-1 flat -0x20 rule — see
|
||||
// kbApplyCapsUtf8. œ is Latin Extended-A (adjacent-pair rule, verified).
|
||||
static const char* const KB_FRENCH_CHARS[4][10] = {
|
||||
{ "à", "â", "ç", "é", "è", "ê", "ë", "î", "ï", "ô" },
|
||||
{ "ù", "û", "ü", "ÿ", "œ", ".", ",", "!", "?", "'" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "-", "\"", ":", ";", "(", ")", "@", "#", "&", "*" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_FRENCH[9] = {
|
||||
".,!?'-", "àâ", "çé", "èê", "ëî", "ïô", "ùû", "üÿ", "œ"
|
||||
};
|
||||
|
||||
// Spanish: the 5 accented vowels, ñ, and ü (only appears in güe/güi).
|
||||
static const char* const KB_SPANISH_CHARS[4][10] = {
|
||||
{ "á", "é", "í", "ñ", "ó", "ú", "ü", ".", ",", "!" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "?", "'", "-", "\"", ":", ";", "(", ")", "@", "#" },
|
||||
{ "&", "*", "_", "+", "=", "/", "\\", "~", "<", ">" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_SPANISH[9] = {
|
||||
".,!?'-", "áé", "íñ", "óú", "ü", ";()", "@#&*", "_+=/", "\\~<>"
|
||||
};
|
||||
|
||||
// Portuguese: á à â ã ç é ê í ó ô õ ú (ü dropped from modern orthography).
|
||||
static const char* const KB_PORTUGUESE_CHARS[4][10] = {
|
||||
{ "á", "à", "â", "ã", "ç", "é", "ê", "í", "ó", "ô" },
|
||||
{ "õ", "ú", ".", ",", "!", "?", "'", "-", "\"", ":" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ ";", "(", ")", "@", "#", "&", "*", "_", "+", "=" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_PORTUGUESE[9] = {
|
||||
".,!?'-", "áà", "âã", "çé", "êí", "óô", "õú", ";()", "@#&*"
|
||||
};
|
||||
|
||||
// Nordic: å ä æ ö ø — shared across Danish/Norwegian/Swedish (their
|
||||
// alphabets differ only in which of these 5 each uses), so kept as one page
|
||||
// rather than three near-identical ones.
|
||||
static const char* const KB_NORDIC_CHARS[4][10] = {
|
||||
{ "å", "ä", "æ", "ö", "ø", ".", ",", "!", "?", "'" },
|
||||
{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" },
|
||||
{ "-", "\"", ":", ";", "(", ")", "@", "#", "&", "*" },
|
||||
{ "_", "+", "=", "/", "\\", "~", "<", ">", "[", "]" },
|
||||
};
|
||||
static const char* const KB_T9_GROUPS_NORDIC[9] = {
|
||||
".,!?'-", "åä", "æö", "ø", ";()", "@#&*", "_+=/", "\\~<>", "[]{}"
|
||||
};
|
||||
// Buffer cap for typed text, in bytes. Matches MeshCore's MAX_TEXT_LEN
|
||||
// (10*CIPHER_BLOCK_SIZE = 160) so a full-length message can be composed; each
|
||||
// field passes its own smaller max to begin() where its store is smaller.
|
||||
@@ -312,6 +211,41 @@ static int kbUtf8CharBytesAt(const char* buf, int pos, int len) {
|
||||
return n;
|
||||
}
|
||||
|
||||
// Accented variants of a Latin base letter, reachable by Hold-Enter on that
|
||||
// letter's cell on the plain Latin ABC page (see handleInput's KEY_CONTEXT_MENU
|
||||
// block and the accent_active state machine). Replaces the old per-language
|
||||
// full alt-alphabet pages (Polish/Czech/Slovak/German/French/Spanish/
|
||||
// Portuguese/Nordic) with one popup covering the union of all their accented
|
||||
// letters -- closer to a phone keyboard's long-press accent picker than a
|
||||
// second full page per language. Each entry is one UTF-8 string of
|
||||
// concatenated variants (like KB_T9_GROUPS_* group strings), read with the
|
||||
// same kbUtf8Len()/kbUtf8CharAt() helpers used there.
|
||||
// Ligature/non-diacritic letters are filed under their conventional key, same
|
||||
// as a phone keyboard's long-press: ß (German) -> s, œ (French) -> o. ĺ/ŕ
|
||||
// (Slovak) are l/r with an acute, not i/e variants, so they're filed there.
|
||||
static const char KB_ACCENT_BASES[] = "acdeilnorstuyz";
|
||||
static const char* const KB_ACCENT_VARIANTS[] = {
|
||||
"áàâãäåą", // a
|
||||
"çćč", // c
|
||||
"ď", // d
|
||||
"éèêëěę", // e
|
||||
"íîï", // i
|
||||
"łĺľ", // l
|
||||
"ñńň", // n
|
||||
"óòôõöøœ", // o
|
||||
"řŕ", // r
|
||||
"śšß", // s
|
||||
"ť", // t
|
||||
"úùûüů", // u
|
||||
"ýÿ", // y
|
||||
"źżž", // z
|
||||
};
|
||||
static const int KB_ACCENT_COUNT = sizeof(KB_ACCENT_BASES) - 1; // exclude the trailing NUL
|
||||
static int findAccentGroup(char base) {
|
||||
for (int i = 0; i < KB_ACCENT_COUNT; i++) if (KB_ACCENT_BASES[i] == base) return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static const int KB_PH_MAX = 20; // max placeholders in list (PopupMenu::PM_MAX_ITEMS=24 is the hard ceiling)
|
||||
static const int KB_PH_LEN = 30; // max placeholder string length incl. null -- sized for the longest
|
||||
// CLI-command candidate (AdminScreen), not just the short {x} tokens
|
||||
@@ -332,7 +266,10 @@ struct KeyboardWidget {
|
||||
int len;
|
||||
int max_len;
|
||||
int cursor_pos; // insertion point into buf, in bytes; defaults to len (append)
|
||||
bool cursor_mode; // true while Hold-Enter has parked the grid to reposition cursor_pos
|
||||
bool cursor_mode; // true while UP-from-row-0 has parked the grid to reposition cursor_pos
|
||||
bool accent_active = false; // true while the Hold-Enter accent popup is open
|
||||
int accent_group = -1; // index into KB_ACCENT_VARIANTS for the held cell's base letter
|
||||
int accent_sel = 0; // selected variant within that group
|
||||
int row, col;
|
||||
int page; // see totalPages()/pageIsAltAlphabet()/pageIsSymbols() below
|
||||
bool caps;
|
||||
@@ -392,14 +329,6 @@ struct KeyboardWidget {
|
||||
switch (prefs->keyboard_alt_alphabet) {
|
||||
case NodePrefs::KB_ALPHABET_CYRILLIC: return KB_CYRILLIC_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_GREEK: return KB_GREEK_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_POLISH: return KB_POLISH_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_CZECH: return KB_CZECH_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_SLOVAK: return KB_SLOVAK_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_GERMAN: return KB_GERMAN_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_FRENCH: return KB_FRENCH_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_SPANISH: return KB_SPANISH_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_PORTUGUESE: return KB_PORTUGUESE_CHARS[r][c];
|
||||
case NodePrefs::KB_ALPHABET_NORDIC: return KB_NORDIC_CHARS[r][c];
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -415,14 +344,6 @@ struct KeyboardWidget {
|
||||
switch (prefs->keyboard_alt_alphabet) {
|
||||
case NodePrefs::KB_ALPHABET_CYRILLIC: return KB_T9_GROUPS_CYRILLIC[cell];
|
||||
case NodePrefs::KB_ALPHABET_GREEK: return KB_T9_GROUPS_GREEK[cell];
|
||||
case NodePrefs::KB_ALPHABET_POLISH: return KB_T9_GROUPS_POLISH[cell];
|
||||
case NodePrefs::KB_ALPHABET_CZECH: return KB_T9_GROUPS_CZECH[cell];
|
||||
case NodePrefs::KB_ALPHABET_SLOVAK: return KB_T9_GROUPS_SLOVAK[cell];
|
||||
case NodePrefs::KB_ALPHABET_GERMAN: return KB_T9_GROUPS_GERMAN[cell];
|
||||
case NodePrefs::KB_ALPHABET_FRENCH: return KB_T9_GROUPS_FRENCH[cell];
|
||||
case NodePrefs::KB_ALPHABET_SPANISH: return KB_T9_GROUPS_SPANISH[cell];
|
||||
case NodePrefs::KB_ALPHABET_PORTUGUESE: return KB_T9_GROUPS_PORTUGUESE[cell];
|
||||
case NodePrefs::KB_ALPHABET_NORDIC: return KB_T9_GROUPS_NORDIC[cell];
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -438,14 +359,6 @@ struct KeyboardWidget {
|
||||
switch (alt) {
|
||||
case NodePrefs::KB_ALPHABET_CYRILLIC: return "CY";
|
||||
case NodePrefs::KB_ALPHABET_GREEK: return "GR";
|
||||
case NodePrefs::KB_ALPHABET_POLISH: return "PL";
|
||||
case NodePrefs::KB_ALPHABET_CZECH: return "CZ";
|
||||
case NodePrefs::KB_ALPHABET_SLOVAK: return "SK";
|
||||
case NodePrefs::KB_ALPHABET_GERMAN: return "DE";
|
||||
case NodePrefs::KB_ALPHABET_FRENCH: return "FR";
|
||||
case NodePrefs::KB_ALPHABET_SPANISH: return "ES";
|
||||
case NodePrefs::KB_ALPHABET_PORTUGUESE: return "PT";
|
||||
case NodePrefs::KB_ALPHABET_NORDIC: return "ND";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
@@ -459,6 +372,9 @@ struct KeyboardWidget {
|
||||
len = strlen(buf);
|
||||
cursor_pos = len;
|
||||
cursor_mode = false;
|
||||
accent_active = false;
|
||||
accent_group = -1;
|
||||
accent_sel = 0;
|
||||
row = col = 0;
|
||||
page = 0;
|
||||
caps = false;
|
||||
@@ -475,6 +391,24 @@ struct KeyboardWidget {
|
||||
addPlaceholder("{time}");
|
||||
}
|
||||
|
||||
// Insert one UTF-8 codepoint (a grid cell's own glyph, or a picked accent
|
||||
// variant) at cursor_pos, applying Shift/caps-lock the same way every cell
|
||||
// commit does. Shared by the plain-Latin-cell commit below and the accent
|
||||
// popup's Enter commit, so the two paths can't drift apart.
|
||||
void insertGlyph(const char* one, bool use_caps) {
|
||||
char shown[5];
|
||||
kbApplyCapsUtf8(one, use_caps, shown, sizeof(shown));
|
||||
int n = (int)strlen(shown);
|
||||
int tail_len = len - cursor_pos;
|
||||
if (len + n <= max_len) {
|
||||
memmove(buf + cursor_pos + n, buf + cursor_pos, tail_len);
|
||||
memcpy(buf + cursor_pos, shown, n);
|
||||
len += n;
|
||||
cursor_pos += n;
|
||||
buf[len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void clearPlaceholders() { _ph_count = 0; }
|
||||
|
||||
void addPlaceholder(const char* ph) {
|
||||
@@ -643,6 +577,43 @@ struct KeyboardWidget {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
|
||||
// Accent popup: floats over the still-visible grid (same idea as the
|
||||
// placeholder overlay just below), anchored on the held letter's own row
|
||||
// so it reads as "popping out of" that key instead of taking over the
|
||||
// whole keyboard. LEFT/RIGHT picks, Enter commits, Cancel dismisses.
|
||||
if (accent_active) {
|
||||
const char* group = KB_ACCENT_VARIANTS[accent_group];
|
||||
int n = kbUtf8Len(group);
|
||||
const int pad = 3;
|
||||
int seg_w = cw * 2 + pad;
|
||||
int bw = n * seg_w;
|
||||
int max_bw = display.width() - 4;
|
||||
if (bw > max_bw) { bw = max_bw; seg_w = bw / n; }
|
||||
int bx = (display.width() - bw) / 2;
|
||||
int by = chars_y + row * cell_h - 1;
|
||||
int bh = cell_h + 1;
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
display.fillRect(bx, by, bw, bh);
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(bx, by, bw, bh);
|
||||
for (int i = 0; i < n; i++) {
|
||||
char one[5]; kbUtf8CharAt(group, i, one);
|
||||
char shown[5]; kbApplyCapsUtf8(one, caps, shown, sizeof(shown));
|
||||
int x = bx + i * seg_w;
|
||||
if (i == accent_sel) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.fillRect(x + 1, by + 1, seg_w - 1, bh - 2);
|
||||
display.setColor(DisplayDriver::DARK);
|
||||
} else {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
int tw = display.getTextWidth(shown);
|
||||
display.setCursor(x + (seg_w - tw) / 2, by + 1);
|
||||
display.print(shown);
|
||||
}
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
|
||||
// placeholder picker overlay (drawn on top of keyboard)
|
||||
if (_ph_menu.active) _ph_menu.render(display);
|
||||
return 50;
|
||||
@@ -677,20 +648,51 @@ struct KeyboardWidget {
|
||||
return NONE;
|
||||
}
|
||||
|
||||
// Cursor-positioning sub-mode (see the Hold-Enter block below): the grid
|
||||
// Cursor-positioning sub-mode (see the KEY_UP block below): the grid
|
||||
// selection is parked while LEFT/RIGHT walk cursor_pos one codepoint at a
|
||||
// time and UP/DOWN jump to the very start/end -- Home/End, in effect.
|
||||
// Enter/Cancel just leave the mode; the actual edit (insert/backspace)
|
||||
// happens back in normal typing, now targeting the repositioned cursor.
|
||||
// time. UP/DOWN jump to the very start/end -- Home/End, in effect -- and,
|
||||
// once already at that boundary, continue the wrap the entry trigger
|
||||
// interrupted: UP again lands on the special row, DOWN again back on the
|
||||
// letter grid's row 0, same destinations the plain grid wrap used to reach
|
||||
// directly (see the entry/exit comment below). Enter/Cancel just leave the
|
||||
// mode from anywhere; the actual edit (insert/backspace) happens back in
|
||||
// normal typing, now targeting the repositioned cursor.
|
||||
if (cursor_mode) {
|
||||
if (c == KEY_LEFT) { if (cursor_pos > 0) cursor_pos -= kbUtf8LastCharBytes(buf, cursor_pos); return NONE; }
|
||||
if (c == KEY_RIGHT) { if (cursor_pos < len) cursor_pos += kbUtf8CharBytesAt(buf, cursor_pos, len); return NONE; }
|
||||
if (c == KEY_UP) { cursor_pos = 0; return NONE; }
|
||||
if (c == KEY_DOWN) { cursor_pos = len; return NONE; }
|
||||
if (c == KEY_UP) {
|
||||
if (cursor_pos > 0) { cursor_pos = 0; return NONE; }
|
||||
cursor_mode = false;
|
||||
row = gridRows();
|
||||
col = col * KB_SPECIAL / gridCols();
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_DOWN) {
|
||||
if (cursor_pos < len) { cursor_pos = len; return NONE; }
|
||||
cursor_mode = false;
|
||||
row = 0;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_ENTER || c == KEY_CANCEL) { cursor_mode = false; return NONE; }
|
||||
return NONE;
|
||||
}
|
||||
|
||||
if (accent_active) {
|
||||
const char* group = KB_ACCENT_VARIANTS[accent_group];
|
||||
int n = kbUtf8Len(group);
|
||||
if (keyIsPrev(c)) { accent_sel = (accent_sel > 0) ? accent_sel - 1 : n - 1; return NONE; }
|
||||
if (keyIsNext(c)) { accent_sel = (accent_sel < n - 1) ? accent_sel + 1 : 0; return NONE; }
|
||||
if (c == KEY_ENTER) {
|
||||
char one[5]; kbUtf8CharAt(group, accent_sel, one);
|
||||
insertGlyph(one, caps);
|
||||
if (caps && !caps_lock) caps = false;
|
||||
accent_active = false;
|
||||
return NONE;
|
||||
}
|
||||
if (c == KEY_CANCEL) { accent_active = false; return NONE; }
|
||||
return NONE;
|
||||
}
|
||||
|
||||
if (c == KEY_CANCEL) return CANCELLED;
|
||||
|
||||
const int rows = gridRows();
|
||||
@@ -699,9 +701,14 @@ struct KeyboardWidget {
|
||||
// Hold-Enter is normally "cancel", but three places give it a more useful
|
||||
// meaning instead: Shift -> toggle a persistent caps-lock (a plain tap is
|
||||
// one-shot -- see the commit sites below); Backspace -> clear the whole
|
||||
// field in one action instead of holding it down; anywhere on the letter/
|
||||
// symbol grid -> enter cursor-positioning mode (see above). Every other
|
||||
// special-row cell keeps hold-to-cancel.
|
||||
// field in one action instead of holding it down; a Latin-page letter cell
|
||||
// with accented variants -> open the accent popup (see accent_active
|
||||
// above). Every other special-row cell keeps hold-to-cancel; any other
|
||||
// letter/symbol cell (a plain letter with no accents, or any T9/alt-
|
||||
// alphabet/symbols cell) is a silent no-op instead, so it can't
|
||||
// accidentally close the keyboard. Cursor mode itself moved off Hold-Enter
|
||||
// entirely -- see the KEY_UP block below, where UP from row 0 now enters
|
||||
// it instead.
|
||||
if (c == KEY_CONTEXT_MENU) {
|
||||
if (row == rows && col == 0) { // Shift
|
||||
caps_lock = !caps_lock;
|
||||
@@ -714,10 +721,12 @@ struct KeyboardWidget {
|
||||
t9_cell = -1;
|
||||
return NONE;
|
||||
}
|
||||
if (row < rows) { // any letter/symbol cell
|
||||
cursor_mode = true;
|
||||
t9_cell = -1;
|
||||
return NONE;
|
||||
if (row < rows) {
|
||||
if (!isT9() && page == 0) {
|
||||
int gi = findAccentGroup(cellStr(row, col)[0]);
|
||||
if (gi >= 0) { accent_active = true; accent_group = gi; accent_sel = 0; t9_cell = -1; return NONE; }
|
||||
}
|
||||
return NONE; // no variants for this cell, or T9/alt-alphabet/symbols page
|
||||
}
|
||||
return CANCELLED;
|
||||
}
|
||||
@@ -728,8 +737,12 @@ struct KeyboardWidget {
|
||||
if (row == rows - 1) // leaving special row upward
|
||||
col = col * cols / KB_SPECIAL;
|
||||
} else {
|
||||
row = rows; // wrap up onto the special row
|
||||
col = col * KB_SPECIAL / cols;
|
||||
// row 0: enter cursor mode instead of wrapping to the special row --
|
||||
// row/col are deliberately left as-is so the cursor-mode UP/DOWN
|
||||
// continuation above can still reach the special row proportionally.
|
||||
cursor_mode = true;
|
||||
t9_cell = -1;
|
||||
return NONE;
|
||||
}
|
||||
t9_cell = -1; // navigating away finalizes any pending multi-tap cycle
|
||||
return NONE;
|
||||
@@ -806,18 +819,8 @@ struct KeyboardWidget {
|
||||
}
|
||||
t9_last_ms = millis();
|
||||
} else if (row < rows) {
|
||||
char shown[3];
|
||||
kbApplyCapsUtf8(cellStr(row, col), caps, shown, sizeof(shown));
|
||||
int n = (int)strlen(shown);
|
||||
int tail_len = len - cursor_pos;
|
||||
if (len + n <= max_len) {
|
||||
memmove(buf + cursor_pos + n, buf + cursor_pos, tail_len);
|
||||
memcpy(buf + cursor_pos, shown, n);
|
||||
len += n;
|
||||
cursor_pos += n;
|
||||
buf[len] = '\0';
|
||||
if (caps && !caps_lock) caps = false; // one-shot: revert after the letter it capitalised
|
||||
}
|
||||
insertGlyph(cellStr(row, col), caps);
|
||||
if (caps && !caps_lock) caps = false; // one-shot: revert after the letter it capitalised
|
||||
} else {
|
||||
t9_cell = -1; // any special-row action finalizes a pending multi-tap cycle
|
||||
switch (col) {
|
||||
|
||||
Reference in New Issue
Block a user