mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
refactor: redesign on-screen keyboard (#1644)
# Refactor: Redesign On-Screen Keyboard ## Summary Complete redesign of the on-screen keyboard (used for WiFi password, KOReader, Calibre URLs) with improved layout, navigation, visual style, and new input features: **cursor mode** for text navigation, **password mode** with visibility toggle, and **URL mode** with pre-defined snippets. ## Screenshots ### Base Theme |**master** | **PR #1644** | |----------|-------------| | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/49125857-12d0-4020-b872-05d0ddbf1d94" /> | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/ad16656b-d66e-43dd-8697-2b85f709d7f8" /> | ### Lyra Theme | **master** | **PR #1644** | |----------|-------------| | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/d9901251-9154-48d2-83b4-376d3223d132" /> | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/84b45949-ed61-4924-af1b-d570c4c61e13" /> | ### Keyboard States | ABC Mode | Symbol Mode | URL Mode | |----------|-------------|----------| | <img width="480" height="280" alt="image" src="https://github.com/user-attachments/assets/6397a82e-50b8-4d03-92e0-f707ed7c9054" /> | <img width="480" height="280" alt="image" src="https://github.com/user-attachments/assets/205d34fe-0413-49e9-9db2-30569c297ba0" /> | <img width="480" height="280" alt="image" src="https://github.com/user-attachments/assets/801ddeab-e082-4a22-a9df-c66adb1afc16" /> | | Cursor Mode | Password Toggle | |-------------|-----------------| | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/841773e1-7a3c-45e5-aa89-e5787255608c" /> | <img width="480" height="800" alt="image" src="https://github.com/user-attachments/assets/9487a0b3-3f47-41ed-9cd3-b24e56e342a8" /> | ## Changes ### Layout (10-column uniform grid) - Reduced from 13/11/10 columns per row to **10 uniform columns** across all rows - Keyboard now uses **90% of screen width** (was ~66%) - Row 0: Numbers `1-9, 0` with secondary symbols (`!@#$%^&*()`) - Rows 1-3: Standard QWERTY letters - Bottom row: `shift` | `#@!` | `___` | `←` | `OK` ### New Symbol Mode (#@!) - New mode toggle key `#@!` / `abc` switches between letter and symbol layouts - Symbol layout: 4 rows (numbers, inverted symbols, paired symbols, loose symbols) - Covers all 95 printable ASCII characters - **No secondary hints, no long-press** in symbol mode (simple and direct) - SHIFT key remains visible but **disabled** in symbol mode ### URL Mode - In `InputType::Url`, the Space key becomes a **URL toggle** button - Activating URL mode replaces the 4 content rows with a **3×3 grid of URL snippets**: - Col 0 (protocols): `https://`, `http://`, `/opds` - Col 1 (hosts/ports): `www.`, `192.168.`, `:8080` - Col 2 (domains): `.com`, `.org`, `.net` - Snippets are inserted as full strings at the cursor position - URL mode **persists** after inserting a snippet (does not auto-deactivate) - Column alignment: col 0 over ABC, col 1 over URL, col 2 over Del - Up/Down navigation maps `bottomCol - 1` / `urlCol + 1` - SHIFT disabled in URL mode - SpecMode (`abc`) exits URL mode back to ABC - SpecSpace (`URL`) toggles URL mode on/off; selection always stays on the URL button - Button styled with `KeyboardKeyType::Mode` for consistent outline ### Cursor Mode - **Enter**: Long-press Up (500ms) while in keyboard mode - **Exit**: Short-press Down while in cursor mode (resets `passwordVisible`, clears toggle position) - **Navigate**: Left/Right move cursor position within text (one position per press, no continuous repeat) - **Visual**: - Keyboard mode: underline cursor (2px line + serifs) - Cursor mode: inverted block cursor (black fill + white character) - Block width adapts to the actual character width under cursor (minimum 6px for narrow chars like space) - Block position includes inter-character kerning offset for correct alignment (calculated via string-difference: `getTextWidth(before+char) - getTextWidth(before) - getTextWidth(char)`) - End-of-text: thin 6px block - Password hidden: 3-part drawing (Part 1 + block + Part 3) to prevent block overflow onto `*` characters - Toggle position: caret ("I") cursor at saved position, `[abc]`/`[***]` label with inverted selection - **Inactive key styling**: - BaseTheme: 2px outline rectangle - LyraTheme: gray filled rounded rectangle (`Color::LightGray`) - **Password toggle position**: in cursor mode (Password only), Hold Right (500ms) enters toggle — caret cursor shows saved position, `[abc]`/`[***]` label becomes selected. Press Confirm to toggle `passwordVisible`. Press Left to restore cursor to saved position. Right from toggle is a no-op. Down from toggle exits to keyboard. - `cursorPos` persists between keyboard and cursor modes ### Password Mode - `InputType::Password` enum replaces `bool isPassword` parameter - Text is masked with `*` except for one revealed character: - Keyboard mode: reveals character at `cursorPos - 1` - Cursor mode: no reveal in display text (block cursor draws actual char directly) - **Toggle `[abc]`/`[***]`**: accessible via cursor mode — Hold Right (500ms) enters toggle position, Confirm toggles visibility, Left exits back to cursor. Caret ("I") shown at saved position while in toggle. - `passwordVisible` resets to `false` when exiting cursor mode - **Long-press Del (1.5s)**: clears all text and resets cursor to 0 ### InputType Enum - Replaced `bool isPassword` constructor parameter with `enum class InputType { Text, Password, Url }` - Callers updated: `WifiSelectionActivity`, `KOReaderSettingsActivity`, `CalibreSettingsActivity` ### Contextual Tips - `"Tips:"` header followed by context-sensitive hints, centered between text field underline and keyboard as a block - ABC mode: `"Hold SELECT for UPPERCASE or secondary char"` (shift ON: `"lowercase"` variant) + `"Hold DEL to clear all text"` (only if text not empty) - ABC + `InputType::Url`: same + `"Press URL for snippets"` - Symbol mode: `"Hold DEL to clear all text"` (only if text not empty) - URL mode: `"Press ABC to exit URL mode"` + `"Hold DEL to clear all text"` (only if text not empty) - Cursor mode: `"Press DOWN to return to keyboard"` ### Hint Phases (cursor mode, Password only) - **Phase 1**: `"Hold UP to edit entry"` — shown after 2× DEL press, auto-hides after 4s, positioned below underline - **Phase 2**: `"Press < or > to move cursor"` + dynamic password toggle hint — shown when entering cursor mode, positioned below underline, visible until exit - When `!passwordVisible`: `"Hold > then press [abc] to show password"` - When `passwordVisible`: `"Hold > then press [***] to hide password"` - When in toggle position: `"Press < to return to cursor position"` ### Long-Press Alternative Character - Holding Confirm (>500ms) inserts the **alternative character** instead of the primary - Letters: long-press inserts opposite case (e.g., `a`→`A`, `A`→`a`) - Numbers/symbols (row 0): long-press inserts secondary (e.g., `0`→`)`, `)`→`0`) - Only active in ABC mode; disabled in Symbol mode and URL mode - **`InputType::Url`**: Hold SELECT on ABC rows 1+ (letters) returns primary character only (same as short press). Row 0 (symbols) still returns secondary character on Hold SELECT. ### Shift (2 sticky states) - Reduced from 3 states (shift/SHIFT/LOCK) to **2 sticky states** (shift/SHIFT) - Shift stays active after typing until manually toggled off - Label: `shift` (off) / `SHIFT` (on) ### SpecialKeyType Enum - `enum class SpecialKeyType { Shift, Mode, Space, Del, Ok }` replaces plain `enum` (`SpecShift`, `SpecMode`, etc.) for type safety - All switch cases updated to `SpecialKeyType::*` with `static_cast<int>()` for array indexing - `onExit()` reverted to simple `Activity::onExit()` call (half-refresh removed) - **Bottom row column mapping**: navigating up/down between content rows and bottom row uses `col/2` and `col*2` formulas for consistent positioning (10 cols ↔ 5 cols) - **URL mode column mapping**: `bottomCol - 1` / `urlCol + 1` (3 cols ↔ 5 cols) - **Wrap-around**: row 0 → up → bottom row and bottom row → down → row 0 both apply correct column mapping ### Visual Improvements (both Base and Lyra themes) - **Space key**: underscore-style horizontal line (60% of key width, 3px thick) - **Delete key**: arrow `←` drawn with lines (3px thick) instead of "DEL" text - **Secondary label** (ABC row 0): small hint in top-right corner with separation from primary number - **BaseTheme**: selection uses **inverted fill** (black rect + white text) instead of `[bracket]` markers - **BaseTheme**: text field brackets drawn as **stretchable lines** that adapt to multi-line input (1px normal, 3px cursor mode) - **LyraTheme**: text field uses **fixed-width underline** (16px margins, 8px each side) instead of stretchable line (2px normal, 3px cursor mode) - **Both themes**: special keys (shift, mode, space, del, OK) have bordered/bordered-rounded rectangles - **Font size**: keyboard uses `UI_12_FONT_ID` in both themes (was `UI_10` in Base) - **Key height**: 40px in all themes for better proportions - **Layout unification**: text and password toggle are left-aligned in all themes (`keyboardCenteredText = false` for Lyra/Lyra3Covers) - **`primaryOffset` removed**: dead code eliminated from BaseTheme and LyraTheme `drawKeyboardKey` ### New Theme Metrics - `keyboardVerticalOffset`: per-theme vertical adjustment of keyboard position - Base: `-13`, Lyra: `-7` - `keyboardBottomKeySpacing`: independent spacing for bottom row keys - Base: `5`, Lyra: `5` - Bottom-aligned keyboard in both themes for consistent vertical positioning - Bottom row total width calculated to match content rows width (10-col based, consistent across modes) - 4px extra gap between content rows and bottom row when `bkSpacing > 0` - `keyboardCenteredText`: `false` for all themes (unified left-aligned text) ### Defensive Improvements - **State reset on re-entry**: `onEnter()` resets all mutable state (`symMode`, `urlMode`, `cursorMode`, `togglePos`, `passwordVisible`, `shiftState`, `selectedRow`, `selectedCol`, `rightHeld`, `rightLongHandled`, `savedCursorPos`, `rightStartCursorPos`, `delPressCount`, `hintVisible`, `hintShowTime`) — prevents stale state when re-entering the keyboard - **Bounds checking**: `insertChar`/`insertString` clamp `cursorPos` to `text.length()` before inserting - **Empty string guard**: `insertString` returns early on empty string - **`std::string::npos`**: used instead of `SIZE_MAX` for size_t sentinel (proper C++ idiom) - **`<algorithm>` header**: included for `std::max` ## Files Modified | File | Changes | |------|---------| | `src/activities/util/KeyboardEntryActivity.h` | `InputType` enum, `KeyDef` struct, 10-col layouts, cursor/password/URL/toggle state, hints (`delPressCount`, `hintVisible`, `hintShowTime`), held vars (`rightHeld`, `rightLongHandled`, `savedCursorPos`, `rightStartCursorPos`), `mapColContentBottom` helper | | `src/activities/util/KeyboardEntryActivity.cpp` | Complete rewrite: layout rendering, symbol/cursor/password/URL modes, toggle position, long-press, contextual tips, hint phases, block cursor kerning alignment, defensive bounds checks, state reset | | `src/components/themes/BaseTheme.h` | `KeyboardKeyType` enum, new `drawTextField`/`drawKeyboardKey` signatures, `keyboardVerticalOffset`, `keyboardBottomKeySpacing` metrics | | `src/components/themes/BaseTheme.cpp` | Redesigned `drawTextField` (stretchable brackets), `drawKeyboardKey` (inverted selection, space/delete graphics, secondary label, inactive selection), removed `primaryOffset` dead code | | `src/components/themes/lyra/LyraTheme.h` | Override signatures, `keyboardVerticalOffset`, `keyboardBottomKeySpacing`, `keyboardKeyHeight` adjustments | | `src/components/themes/lyra/LyraTheme.cpp` | `drawTextField` (fixed underline), `drawKeyboardKey` (rounded rects for special keys, space/delete graphics, secondary label, inactive selection), removed `primaryOffset` dead code | | `src/components/themes/lyra/Lyra3CoversTheme.h` | `keyboardCenteredText = false`, `keyboardVerticalOffset = -7`, inherits Lyra overrides | | `src/activities/network/WifiSelectionActivity.cpp` | `bool isPassword` → `InputType::Password` | | `src/activities/settings/KOReaderSettingsActivity.cpp` | `bool isPassword` → `InputType::Text`/`InputType::Password`/`InputType::Url` | | `src/activities/settings/CalibreSettingsActivity.cpp` | `bool isPassword` → `InputType::Text`/`InputType::Password`/`InputType::Url` | ## Backward Compatibility - **API change**: Constructor parameter changed from `bool isPassword` to `InputType inputType` (default `InputType::Text`) - **All callers updated**: WiFi, KOReader, and Calibre integrations migrated to new `InputType` enum ## Testing ### Input & Text Handling - [x] Empty input → press OK (submit empty string) - [x] Back button → cancel (no text returned) - [x] Pre-filled initial text (e.g., editing existing WiFi password) - [x] Password mode: text masked with `*` characters, one character revealed - [x] Delete on empty text (no crash) - [x] Very long text near maxLength limit - [x] URL with path and port (~60 chars) - [x] Multi-line text wrapping in input field - [x] Space insert in middle of text (cursor mode) - [x] Delete last character repeatedly - [ ] Type all 95 printable ASCII characters ### Mode Switching - [x] ABC → #@! preserves typed text and cursor position - [x] #@! → ABC preserves typed text and cursor position - [x] Shift state preserved when switching modes - [x] Switch modes multiple times rapidly ### Shift Behavior - [x] Shift OFF → type letter → inserts lowercase, shift stays OFF - [x] Shift ON → type letter → inserts uppercase, shift stays ON - [x] Shift ON → type number → inserts symbol, shift stays ON - [x] Shift ON → navigate rows → shift stays ON - [x] Shift ON → switch to #@! → shift shows "shift" (disabled) - [x] Shift ON → switch to ABC → shift state preserved - [x] Shift ON → switch to URL → shift shows "shift" (disabled) - [x] Shift disabled in URL mode: pressing shift does nothing ### Long-Press - [x] Long-press letter with shift OFF → inserts uppercase - [x] Long-press letter with shift ON → inserts lowercase - [x] Long-press number → inserts secondary symbol - [x] Long-press symbol (row 0) → inserts opposite (number) - [x] Long-press key without secondary (e.g., `-`, `=` in rows 2-3) → inserts primary character on release - [x] Long-press on special keys (shift, mode, space, del, ok) → no alternative inserted - [x] Long-press in #@! mode → no effect (disabled) - [x] Long-press in URL mode → no effect (disabled) - [x] Long-press number in row 0 with InputType::Url → inserts secondary symbol (same as non-URL) - [x] Short press after cancelled long-press → normal behavior - [x] Long-press at maxLength → no character inserted - [x] Long-press Del (1.5s) → clears all text ### Cursor Mode - [x] Long-press Up → enters cursor mode - [x] Short-press Down → exits cursor mode (resets passwordVisible) - [x] Left/Right navigate within text - [x] Left at position 0 → no movement - [x] Right at end of text → no movement in Text mode, enters toggle in Password mode (Hold Right) - [x] Block cursor visual: correct width for character, thin block at end - [x] Underline cursor visual (keyboard mode): correct position with serifs - [x] Inactive key styling: outline (Base) or gray fill (Lyra) on selected key - [x] Typing with cursor mid-text → inserts at cursor position - [x] Deleting with cursor mid-text → deletes character before cursor - [x] Exit cursor mode → type at cursor position (inserts mid-text, not at end) - [x] Exit cursor mode from toggle → cursor at saved position (not end of text) ### Password Mode - [x] Masked text with one revealed character at `cursorPos - 1` - [x] Cursor mode: block shows actual character, display text all `*` - [x] Toggle `[abc]`/`[***]`: Hold Right (500ms) in cursor mode enters toggle, Confirm toggles visibility, Left exits back to cursor - [x] Exiting cursor mode resets `passwordVisible` to false - [x] Long-press Del clears all text ### URL Mode - [x] URL toggle activates/deactivates URL mode - [x] URL button stays selected after toggle (both on and off) - [x] Deactivating URL mode returns to ABC (not SYM) - [x] 3×3 snippet grid displays correctly - [x] Column alignment: col 0 over ABC, col 1 over URL, col 2 over Del - [x] Snippet insertion: inserts full string at cursor position - [x] URL mode persists after snippet insertion - [x] Shift disabled in InputType::Url - [x] SpecMode (`abc`) exits URL mode to ABC - [x] Up/Down navigation between URL grid and bottom row ### Re-entry State Reset - [x] Enter keyboard → activate URL mode → exit → re-enter → URL mode OFF - [x] Enter keyboard → switch to SYM → exit → re-enter → ABC mode - [x] Enter keyboard → enter cursor mode → exit → re-enter → keyboard mode - [x] Enter keyboard → enter toggle pos → exit → re-enter → togglePos OFF - [x] Enter keyboard → activate shift → exit → re-enter → shift OFF - [x] Enter password keyboard → toggle password visible → exit → re-enter → password hidden ### Navigation - [x] Left/right wrap-around within content rows - [x] Left/right wrap-around within bottom row - [x] Up from row 0 → bottom row (correct column mapping) - [x] Down from bottom row → row 0 (correct column mapping) - [x] Up from bottom row → last content row (correct column) - [x] Down from last content row → bottom row (correct column) - [x] Navigate horizontally in bottom row, then up → correct content column - [x] Navigate horizontally in bottom row, then down (wrap) → correct content column ### Visual (both themes) - [x] Secondary hints only on ABC row 0 - [x] No secondary hints in #@! mode or URL mode - [x] No secondary hints on letter rows (1-3) - [x] Space bar: horizontal line centered, not touching edges - [x] Delete: arrow `←` drawn correctly - [x] Selected key: inverted colors (black fill, white text) - [x] All special keys have border rectangles - [x] Fixed underline in text field (Both themes) - [x] Mode key label: `#@!` in ABC mode, `abc` in symbol mode, `abc` in URL mode - [x] URL key label: `URL` (only in InputType::Url), styled same as other bottom keys - [x] Shift label: `shift` when OFF, `SHIFT` when ON, `shift` when disabled (SYM/URL) - [x] Both themes: bottom row total width matches content rows width - [x] URL snippet grid centered over ABC/URL/Del buttons ### Device & Theme Coverage - [ ] Base Theme on X3 - [x] Base Theme on X4 - [ ] Lyra Theme on X3 - [x] Lyra Theme on X4 - [ ] Lyra Extended Theme on X3 - [x] Lyra Extended Theme on X4 ### Toggle Position - [x] Hold Right > 500ms in cursor mode (Password) → enters toggle, caret visible at saved position - [x] Short-press Right in cursor mode (Password) → advances cursor 1 position, does not jump to toggle - [x] Short-press Left in cursor mode (Password) → moves cursor left 1 position, from toggle returns to saved position - [x] Confirm in toggle → toggles `passwordVisible` - [x] Left from toggle → returns to saved position, caret disappears, block cursor appears - [x] Right from toggle → no-op - [x] Down from toggle → exits to keyboard, cursor at saved position - [x] Hold Right in cursor mode (InputType::Text) → no effect - [x] Hold Right in cursor mode (InputType::Url) → no effect - [x] Hold Right < 500ms released in cursor mode (Password) → short press, advances cursor 1 - [x] No continuous repeat when holding Left or Right in cursor mode ### Caret Visual in Toggle - [x] In toggle: caret "I" visible at saved cursor position - [x] Character under cursor visible (no gap) in password not-visible mode - [x] Character under cursor visible in password visible mode - [x] `[abc]`/`[***]` label with inverted selection in toggle ### Contextual Tips - [x] `"Tips:"` header centered above contextual hints - [x] Single tip → `"Tips:"` + one line - [x] Multiple tips → `"Tips:"` + multiple lines, all centered as block - [x] No tips shown when not applicable (e.g., ABC with empty text and non-URL) - [x] `"UPPERCASE"` shown when shift OFF - [x] `"lowercase"` shown when shift ON - [x] `"secondary char"` shown for InputType::Url ### Hint Phases - [x] 2× DEL → Phase 1 appears ("Hold UP to edit entry") - [x] Phase 1 auto-hides after 4s - [x] Phase 2 appears when entering cursor mode ("Press < or > to move cursor") - [x] Phase 2 shows "Hold > then press [abc] to show password" when `!passwordVisible` - [x] Phase 2 shows "Hold > then press [***] to hide password" when `passwordVisible` - [x] Phase 2 shows "Press < to return to cursor position" when in toggle - [x] Phase 2 disappears when exiting cursor mode ### Long-Press `InputType::Url` Behavior - [x] Hold SELECT on letter rows (rows 1+) with InputType::Url → same character as short press - [x] Hold SELECT on row 0 with InputType::Url → secondary character works normally ### Number Row Reorder - [x] Number row order: 1-9, 0 left to right - [x] `(` and `)` are adjacent (positions 8 and 9) via secondary labels - [x] Long-press on row 0 returns correct secondary symbols in new order - [x] SYM row 1: `(` and `)` also adjacent (positions 8 and 9) ### Block Cursor Alignment - [x] Block cursor correctly positioned for consecutive spaces (kerning offset applied) - [x] Block cursor correctly positioned for mixed characters (letters, numbers, symbols) - [x] Block width minimum 6px for narrow characters (space) — visible as block, not thin line - [x] Password hidden: 3-part drawing prevents block overflow onto `*` characters - [x] Password visible: block post-loop draws correctly on continuous text (no 3-part needed) - [x] End-of-text block: thin 6px block at correct position ### Integration - [x] WiFi password entry (connect to network) - [ ] KOReader username, password, and sync server URL - [ ] Calibre OPDS URL, username, and password - [ ] Calibre OPDS URL: empty → opens with "https://" prefilled - [ ] Calibre OPDS URL: type "http://" or "https://" only → saved as empty - [ ] Calibre OPDS URL: type full URL → saved correctly - [ ] Calibre OPDS URL: existing URL → opens with existing URL (not "https://" prefill)
This commit is contained in:
@@ -190,20 +190,18 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
||||
// Show password entry
|
||||
state = WifiSelectionState::PASSWORD_ENTRY;
|
||||
// Don't allow screen updates while changing activity
|
||||
startActivityForResult(
|
||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
||||
"", // No initial text
|
||||
64, // Max password length
|
||||
false // Show password by default (hard keyboard to use)
|
||||
),
|
||||
[this](const ActivityResult& result) {
|
||||
if (result.isCancelled) {
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
} else {
|
||||
enteredPassword = std::get<KeyboardResult>(result.data).text;
|
||||
// state will be updated in next loop iteration
|
||||
}
|
||||
});
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_ENTER_WIFI_PASSWORD),
|
||||
"", // No initial text
|
||||
64, // Max password length
|
||||
InputType::Password),
|
||||
[this](const ActivityResult& result) {
|
||||
if (result.isCancelled) {
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
} else {
|
||||
enteredPassword = std::get<KeyboardResult>(result.data).text;
|
||||
// state will be updated in next loop iteration
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Connect directly for open networks
|
||||
attemptConnection();
|
||||
|
||||
@@ -50,13 +50,17 @@ void CalibreSettingsActivity::loop() {
|
||||
|
||||
void CalibreSettingsActivity::handleSelection() {
|
||||
if (selectedIndex == 0) {
|
||||
// OPDS Server URL
|
||||
// OPDS Server URL - prefill with https:// if empty to save typing
|
||||
const std::string currentUrl = SETTINGS.opdsServerUrl;
|
||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_CALIBRE_WEB_URL),
|
||||
SETTINGS.opdsServerUrl, 127, false),
|
||||
prefillUrl, 127, InputType::Url),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
strncpy(SETTINGS.opdsServerUrl, kb.text.c_str(), sizeof(SETTINGS.opdsServerUrl) - 1);
|
||||
const std::string urlToSave =
|
||||
(kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
||||
strncpy(SETTINGS.opdsServerUrl, urlToSave.c_str(), sizeof(SETTINGS.opdsServerUrl) - 1);
|
||||
SETTINGS.opdsServerUrl[sizeof(SETTINGS.opdsServerUrl) - 1] = '\0';
|
||||
SETTINGS.saveToFile();
|
||||
}
|
||||
@@ -64,7 +68,7 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
} else if (selectedIndex == 1) {
|
||||
// Username
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_USERNAME),
|
||||
SETTINGS.opdsUsername, 63, false),
|
||||
SETTINGS.opdsUsername, 63, InputType::Text),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
@@ -76,7 +80,7 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
} else if (selectedIndex == 2) {
|
||||
// Password
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_PASSWORD),
|
||||
SETTINGS.opdsPassword, 63, false),
|
||||
SETTINGS.opdsPassword, 63, InputType::Password),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
|
||||
@@ -54,9 +54,7 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
if (selectedIndex == 0) {
|
||||
// Username
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_USERNAME),
|
||||
KOREADER_STORE.getUsername(),
|
||||
64, // maxLength
|
||||
false), // not password
|
||||
KOREADER_STORE.getUsername(), 64, InputType::Text),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
@@ -66,33 +64,31 @@ void KOReaderSettingsActivity::handleSelection() {
|
||||
});
|
||||
} else if (selectedIndex == 1) {
|
||||
// Password
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_PASSWORD),
|
||||
KOREADER_STORE.getPassword(),
|
||||
64, // maxLength
|
||||
false), // show characters
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), kb.text);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
startActivityForResult(
|
||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_KOREADER_PASSWORD),
|
||||
KOREADER_STORE.getPassword(), 64, InputType::Password),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), kb.text);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 2) {
|
||||
// Sync Server URL - prefill with https:// if empty to save typing
|
||||
const std::string currentUrl = KOREADER_STORE.getServerUrl();
|
||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||
startActivityForResult(
|
||||
std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_SYNC_SERVER_URL), prefillUrl,
|
||||
128, // maxLength - URLs can be long
|
||||
false), // not password
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
const std::string urlToSave = (kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
||||
KOREADER_STORE.setServerUrl(urlToSave);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
startActivityForResult(std::make_unique<KeyboardEntryActivity>(renderer, mappedInput, tr(STR_SYNC_SERVER_URL),
|
||||
prefillUrl, 128, InputType::Url),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& kb = std::get<KeyboardResult>(result.data);
|
||||
const std::string urlToSave =
|
||||
(kb.text == "https://" || kb.text == "http://") ? "" : kb.text;
|
||||
KOREADER_STORE.setServerUrl(urlToSave);
|
||||
KOREADER_STORE.saveToFile();
|
||||
}
|
||||
});
|
||||
} else if (selectedIndex == 3) {
|
||||
// Document Matching - toggle between Filename and Binary
|
||||
const auto current = KOREADER_STORE.getMatchMethod();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -8,31 +9,26 @@
|
||||
#include "../Activity.h"
|
||||
#include "util/ButtonNavigator.h"
|
||||
|
||||
/**
|
||||
* Reusable keyboard entry activity for text input.
|
||||
* Can be started from any activity that needs text entry via startActivityForResult()
|
||||
*/
|
||||
struct KeyDef {
|
||||
char primary;
|
||||
char secondary;
|
||||
};
|
||||
|
||||
enum class SpecialKeyType { Shift, Mode, Space, Del, Ok };
|
||||
|
||||
enum class InputType { Text, Password, Url };
|
||||
|
||||
class KeyboardEntryActivity : public Activity {
|
||||
public:
|
||||
/**
|
||||
* Constructor
|
||||
* @param renderer Reference to the GfxRenderer for drawing
|
||||
* @param mappedInput Reference to MappedInputManager for handling input
|
||||
* @param title Title to display above the keyboard
|
||||
* @param initialText Initial text to show in the input field
|
||||
* @param maxLength Maximum length of input text (0 for unlimited)
|
||||
* @param isPassword If true, display asterisks instead of actual characters
|
||||
*/
|
||||
explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
std::string title = "Enter Text", std::string initialText = "",
|
||||
const size_t maxLength = 0, const bool isPassword = false)
|
||||
const size_t maxLength = 0, InputType inputType = InputType::Text)
|
||||
: Activity("KeyboardEntry", renderer, mappedInput),
|
||||
title(std::move(title)),
|
||||
text(std::move(initialText)),
|
||||
maxLength(maxLength),
|
||||
isPassword(isPassword) {}
|
||||
inputType(inputType) {}
|
||||
|
||||
// Activity overrides
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
@@ -42,34 +38,146 @@ class KeyboardEntryActivity : public Activity {
|
||||
std::string title;
|
||||
std::string text;
|
||||
size_t maxLength;
|
||||
bool isPassword;
|
||||
InputType inputType;
|
||||
bool passwordVisible = false;
|
||||
|
||||
ButtonNavigator buttonNavigator;
|
||||
|
||||
// Keyboard state
|
||||
int selectedRow = 0;
|
||||
int selectedCol = 0;
|
||||
int shiftState = 0; // 0 = lower case, 1 = upper case, 2 = shift lock)
|
||||
int shiftState = 0;
|
||||
bool symMode = false;
|
||||
bool confirmHeld = false;
|
||||
bool confirmLongHandled = false;
|
||||
|
||||
bool cursorMode = false;
|
||||
bool togglePos = false;
|
||||
size_t cursorPos = 0;
|
||||
bool upHeld = false;
|
||||
bool upLongHandled = false;
|
||||
bool downHeld = false;
|
||||
bool downLongHandled = false;
|
||||
bool rightHeld = false;
|
||||
bool rightLongHandled = false;
|
||||
size_t savedCursorPos = 0;
|
||||
size_t rightStartCursorPos = 0;
|
||||
|
||||
bool urlMode = false;
|
||||
static constexpr int URL_SNIPPET_COUNT = 9;
|
||||
static constexpr const char* const urlSnippets[URL_SNIPPET_COUNT] = {
|
||||
"https://", "www.", ".com", "http://", "192.168.", ".org", "/opds", ":8080", ".net"};
|
||||
|
||||
int delPressCount = 0;
|
||||
bool hintVisible = false;
|
||||
unsigned long hintShowTime = 0;
|
||||
|
||||
// Handlers
|
||||
void onComplete(std::string text);
|
||||
void onCancel();
|
||||
|
||||
// Keyboard layout
|
||||
static constexpr int NUM_ROWS = 5;
|
||||
static constexpr int KEYS_PER_ROW = 13; // Max keys per row (rows 0 and 1 have 13 keys)
|
||||
static const char* const keyboard[NUM_ROWS];
|
||||
static const char* const keyboardShift[NUM_ROWS];
|
||||
static const char* const shiftString[3];
|
||||
static constexpr uint16_t LONG_PRESS_MS = 500;
|
||||
static constexpr uint16_t DEL_LONG_PRESS_MS = 1500;
|
||||
|
||||
// Special key positions (bottom row)
|
||||
static constexpr int SPECIAL_ROW = 4;
|
||||
static constexpr int SHIFT_COL = 0;
|
||||
static constexpr int SPACE_COL = 2;
|
||||
static constexpr int BACKSPACE_COL = 7;
|
||||
static constexpr int DONE_COL = 9;
|
||||
static constexpr int COLS = 10;
|
||||
static constexpr int ABC_ROWS = 4;
|
||||
static constexpr int SYM_ROWS = 4;
|
||||
static constexpr int BOTTOM_KEY_COUNT = 5;
|
||||
|
||||
static constexpr KeyDef abcLayout[ABC_ROWS][COLS] = {
|
||||
{{'1', '!'},
|
||||
{'2', '@'},
|
||||
{'3', '#'},
|
||||
{'4', '$'},
|
||||
{'5', '%'},
|
||||
{'6', '^'},
|
||||
{'7', '&'},
|
||||
{'8', '*'},
|
||||
{'9', '('},
|
||||
{'0', ')'}},
|
||||
{{'q', 'Q'},
|
||||
{'w', 'W'},
|
||||
{'e', 'E'},
|
||||
{'r', 'R'},
|
||||
{'t', 'T'},
|
||||
{'y', 'Y'},
|
||||
{'u', 'U'},
|
||||
{'i', 'I'},
|
||||
{'o', 'O'},
|
||||
{'p', 'P'}},
|
||||
{{'a', 'A'},
|
||||
{'s', 'S'},
|
||||
{'d', 'D'},
|
||||
{'f', 'F'},
|
||||
{'g', 'G'},
|
||||
{'h', 'H'},
|
||||
{'j', 'J'},
|
||||
{'k', 'K'},
|
||||
{'l', 'L'},
|
||||
{'-', '_'}},
|
||||
{{'z', 'Z'},
|
||||
{'x', 'X'},
|
||||
{'c', 'C'},
|
||||
{'v', 'V'},
|
||||
{'b', 'B'},
|
||||
{'n', 'N'},
|
||||
{'m', 'M'},
|
||||
{'=', '+'},
|
||||
{'.', '>'},
|
||||
{',', '<'}},
|
||||
};
|
||||
|
||||
static constexpr KeyDef symLayout[SYM_ROWS][COLS] = {
|
||||
{{'1', '\0'},
|
||||
{'2', '\0'},
|
||||
{'3', '\0'},
|
||||
{'4', '\0'},
|
||||
{'5', '\0'},
|
||||
{'6', '\0'},
|
||||
{'7', '\0'},
|
||||
{'8', '\0'},
|
||||
{'9', '\0'},
|
||||
{'0', '\0'}},
|
||||
{{'!', '\0'},
|
||||
{'@', '\0'},
|
||||
{'#', '\0'},
|
||||
{'$', '\0'},
|
||||
{'%', '\0'},
|
||||
{'^', '\0'},
|
||||
{'&', '\0'},
|
||||
{'*', '\0'},
|
||||
{'(', '\0'},
|
||||
{')', '\0'}},
|
||||
{{'-', '\0'},
|
||||
{'_', '\0'},
|
||||
{'=', '\0'},
|
||||
{'+', '\0'},
|
||||
{'[', '\0'},
|
||||
{']', '\0'},
|
||||
{'{', '\0'},
|
||||
{'}', '\0'},
|
||||
{';', '\0'},
|
||||
{':', '\0'}},
|
||||
{{'\'', '\0'},
|
||||
{'"', '\0'},
|
||||
{'/', '\0'},
|
||||
{'\\', '\0'},
|
||||
{'|', '\0'},
|
||||
{'?', '\0'},
|
||||
{'.', '\0'},
|
||||
{',', '\0'},
|
||||
{'~', '\0'},
|
||||
{'`', '\0'}},
|
||||
};
|
||||
|
||||
static const char* const shiftString[2];
|
||||
|
||||
int getContentRowCount() const;
|
||||
int getContentColCount() const;
|
||||
int getTotalRowCount() const;
|
||||
bool isBottomRow(int row) const;
|
||||
char getSelectedChar() const;
|
||||
bool handleKeyPress(); // false if onComplete was triggered
|
||||
int getRowLength(int row) const;
|
||||
char getAlternativeChar() const;
|
||||
bool handleKeyPress();
|
||||
bool insertChar(char c);
|
||||
void insertString(const std::string& str);
|
||||
void mapColContentBottom(int& col, bool goingUp) const;
|
||||
};
|
||||
|
||||
@@ -781,18 +781,70 @@ void BaseTheme::drawHelpText(const GfxRenderer& renderer, Rect rect, const char*
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, rect.y, truncatedLabel.c_str());
|
||||
}
|
||||
|
||||
void BaseTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const {
|
||||
renderer.drawText(UI_12_FONT_ID, rect.x + 10, rect.y, "[");
|
||||
renderer.drawText(UI_12_FONT_ID, rect.x + rect.width - 15, rect.y + rect.height, "]");
|
||||
void BaseTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode,
|
||||
int contentStartX, int contentWidth) const {
|
||||
const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID);
|
||||
const int lineY = rect.y + rect.height + lineHeight + BaseMetrics::values.verticalSpacing;
|
||||
const int thickness = cursorMode ? 3 : 1;
|
||||
if (contentWidth > 0) {
|
||||
renderer.drawLine(rect.x + contentStartX, lineY, rect.x + contentStartX + contentWidth, lineY, thickness, true);
|
||||
} else {
|
||||
const int hPadding = 4;
|
||||
const int lineW = textWidth + hPadding * 2;
|
||||
renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, thickness,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label,
|
||||
const bool isSelected) const {
|
||||
const int itemWidth = renderer.getTextWidth(UI_10_FONT_ID, label);
|
||||
const int textX = rect.x + (rect.width - itemWidth) / 2;
|
||||
void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||
const char* secondaryLabel, const KeyboardKeyType keyType,
|
||||
const bool inactiveSelection) const {
|
||||
if (isSelected) {
|
||||
renderer.drawText(UI_10_FONT_ID, textX - 6, rect.y, "[");
|
||||
renderer.drawText(UI_10_FONT_ID, textX + itemWidth, rect.y, "]");
|
||||
if (inactiveSelection) {
|
||||
renderer.drawRect(rect.x, rect.y, rect.width, rect.height, 2, true);
|
||||
} else if (keyType == KeyboardKeyType::Disabled) {
|
||||
renderer.fillRectDither(rect.x, rect.y, rect.width, rect.height, Color::LightGray);
|
||||
} else {
|
||||
renderer.fillRect(rect.x, rect.y, rect.width, rect.height, true);
|
||||
}
|
||||
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del ||
|
||||
keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok ||
|
||||
keyType == KeyboardKeyType::Disabled) {
|
||||
renderer.drawRect(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, textX, rect.y, label);
|
||||
|
||||
const bool invert = isSelected && !inactiveSelection;
|
||||
|
||||
if (keyType == KeyboardKeyType::Space) {
|
||||
const int lineHalfWidth = rect.width * 3 / 10;
|
||||
const int centerX = rect.x + rect.width / 2;
|
||||
const int lineY = rect.y + rect.height / 2 + 3;
|
||||
renderer.drawLine(centerX - lineHalfWidth, lineY, centerX + lineHalfWidth, lineY, 3, !invert);
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyType == KeyboardKeyType::Del) {
|
||||
const int centerX = rect.x + rect.width / 2;
|
||||
const int centerY = rect.y + rect.height / 2;
|
||||
const int arrowLen = rect.width / 4;
|
||||
const int arrowHead = arrowLen / 2;
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX + arrowLen / 2, centerY, 3, !invert);
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY - arrowHead, 3,
|
||||
!invert);
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY + arrowHead, 3,
|
||||
!invert);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool hasSecondary = secondaryLabel != nullptr && secondaryLabel[0] != '\0';
|
||||
const int itemWidth = renderer.getTextWidth(UI_12_FONT_ID, label);
|
||||
const int textX = rect.x + (rect.width - itemWidth) / 2;
|
||||
const int textY = rect.y + (rect.height - renderer.getLineHeight(UI_12_FONT_ID)) / 2;
|
||||
|
||||
if (hasSecondary) {
|
||||
const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel);
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert);
|
||||
}
|
||||
|
||||
renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert);
|
||||
}
|
||||
|
||||
@@ -60,12 +60,19 @@ struct ThemeMetrics {
|
||||
int keyboardKeyWidth;
|
||||
int keyboardKeyHeight;
|
||||
int keyboardKeySpacing;
|
||||
int keyboardBottomKeyHeight;
|
||||
int keyboardBottomKeySpacing;
|
||||
bool keyboardBottomAligned;
|
||||
bool keyboardCenteredText;
|
||||
int keyboardVerticalOffset;
|
||||
int keyboardTextFieldWidthPercent;
|
||||
int keyboardWidthPercent;
|
||||
};
|
||||
|
||||
enum UIIcon { Folder, Text, Image, Book, File, Recent, Settings, Transfer, Library, Wifi, Hotspot };
|
||||
|
||||
enum class KeyboardKeyType { Normal, Shift, Mode, Space, Del, Ok, Disabled };
|
||||
|
||||
// Default theme implementation (Classic Theme)
|
||||
// Additional themes can inherit from this and override methods as needed
|
||||
|
||||
@@ -96,10 +103,15 @@ constexpr ThemeMetrics values = {.batteryWidth = 15,
|
||||
.statusBarHorizontalMargin = 5,
|
||||
.statusBarVerticalMargin = 19,
|
||||
.keyboardKeyWidth = 22,
|
||||
.keyboardKeyHeight = 30,
|
||||
.keyboardKeySpacing = 10,
|
||||
.keyboardBottomAligned = false,
|
||||
.keyboardCenteredText = false};
|
||||
.keyboardKeyHeight = 40,
|
||||
.keyboardKeySpacing = 0,
|
||||
.keyboardBottomKeyHeight = 35,
|
||||
.keyboardBottomKeySpacing = 5,
|
||||
.keyboardBottomAligned = true,
|
||||
.keyboardCenteredText = false,
|
||||
.keyboardVerticalOffset = -13,
|
||||
.keyboardTextFieldWidthPercent = 85,
|
||||
.keyboardWidthPercent = 90};
|
||||
}
|
||||
|
||||
class BaseTheme {
|
||||
@@ -139,8 +151,11 @@ class BaseTheme {
|
||||
const int pageCount, std::string title, const int paddingBottom = 0,
|
||||
const int textYOffset = 0) const;
|
||||
virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const;
|
||||
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const;
|
||||
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const;
|
||||
virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false,
|
||||
int contentStartX = 0, int contentWidth = 0) const;
|
||||
virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||
const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal,
|
||||
bool inactiveSelection = false) const;
|
||||
virtual bool showsFileIcons() const { return false; }
|
||||
|
||||
// Shared constants and helpers for battery drawing (used by all themes)
|
||||
|
||||
@@ -34,10 +34,15 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
|
||||
.statusBarHorizontalMargin = 5,
|
||||
.statusBarVerticalMargin = 19,
|
||||
.keyboardKeyWidth = 31,
|
||||
.keyboardKeyHeight = 50,
|
||||
.keyboardKeyHeight = 40,
|
||||
.keyboardKeySpacing = 0,
|
||||
.keyboardBottomKeyHeight = 35,
|
||||
.keyboardBottomKeySpacing = 5,
|
||||
.keyboardBottomAligned = true,
|
||||
.keyboardCenteredText = true};
|
||||
.keyboardCenteredText = false,
|
||||
.keyboardVerticalOffset = -7,
|
||||
.keyboardTextFieldWidthPercent = 85,
|
||||
.keyboardWidthPercent = 90};
|
||||
}
|
||||
|
||||
class Lyra3CoversTheme : public LyraTheme {
|
||||
|
||||
@@ -578,20 +578,67 @@ void LyraTheme::fillPopupProgress(const GfxRenderer& renderer, const Rect& layou
|
||||
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
|
||||
}
|
||||
|
||||
void LyraTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const {
|
||||
void LyraTheme::drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode,
|
||||
int contentStartX, int contentWidth) const {
|
||||
int lineY = rect.y + rect.height + renderer.getLineHeight(UI_12_FONT_ID) + LyraMetrics::values.verticalSpacing;
|
||||
int lineW = textWidth + hPaddingInSelection * 2;
|
||||
renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, 3);
|
||||
const int thickness = cursorMode ? 3 : 1;
|
||||
if (contentWidth > 0) {
|
||||
renderer.drawLine(rect.x + contentStartX, lineY, rect.x + contentStartX + contentWidth, lineY, thickness, true);
|
||||
} else {
|
||||
int lineW = textWidth + hPaddingInSelection * 2;
|
||||
renderer.drawLine(rect.x + (rect.width - lineW) / 2, lineY, rect.x + (rect.width + lineW) / 2, lineY, thickness,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void LyraTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label,
|
||||
const bool isSelected) const {
|
||||
void LyraTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||
const char* secondaryLabel, const KeyboardKeyType keyType,
|
||||
const bool inactiveSelection) const {
|
||||
if (isSelected) {
|
||||
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black);
|
||||
if (inactiveSelection) {
|
||||
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray);
|
||||
} else if (keyType == KeyboardKeyType::Disabled) {
|
||||
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::LightGray);
|
||||
} else {
|
||||
renderer.fillRoundedRect(rect.x, rect.y, rect.width, rect.height, cornerRadius, Color::Black);
|
||||
}
|
||||
} else if (keyType == KeyboardKeyType::Shift || keyType == KeyboardKeyType::Mode || keyType == KeyboardKeyType::Del ||
|
||||
keyType == KeyboardKeyType::Space || keyType == KeyboardKeyType::Ok ||
|
||||
keyType == KeyboardKeyType::Disabled) {
|
||||
renderer.drawRoundedRect(rect.x, rect.y, rect.width, rect.height, 1, cornerRadius, true);
|
||||
}
|
||||
|
||||
const bool invert = isSelected && !inactiveSelection;
|
||||
|
||||
if (keyType == KeyboardKeyType::Space) {
|
||||
const int lineHalfWidth = rect.width * 3 / 10;
|
||||
const int centerX = rect.x + rect.width / 2;
|
||||
const int lineY = rect.y + rect.height / 2 + 3;
|
||||
renderer.drawLine(centerX - lineHalfWidth, lineY, centerX + lineHalfWidth, lineY, 3, !invert);
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyType == KeyboardKeyType::Del) {
|
||||
const int centerX = rect.x + rect.width / 2;
|
||||
const int centerY = rect.y + rect.height / 2;
|
||||
const int arrowLen = rect.width / 4;
|
||||
const int arrowHead = arrowLen / 2;
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX + arrowLen / 2, centerY, 3, !invert);
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY - arrowHead, 3,
|
||||
!invert);
|
||||
renderer.drawLine(centerX - arrowLen / 2, centerY, centerX - arrowLen / 2 + arrowHead, centerY + arrowHead, 3,
|
||||
!invert);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool hasSecondary = secondaryLabel != nullptr && secondaryLabel[0] != '\0';
|
||||
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, label);
|
||||
const int textX = rect.x + (rect.width - textWidth) / 2;
|
||||
const int textY = rect.y + (rect.height - renderer.getLineHeight(UI_12_FONT_ID)) / 2;
|
||||
renderer.drawText(UI_12_FONT_ID, textX, textY, label, !isSelected);
|
||||
renderer.drawText(UI_12_FONT_ID, textX, textY, label, !invert);
|
||||
|
||||
if (hasSecondary) {
|
||||
const int secWidth = renderer.getTextWidth(SMALL_FONT_ID, secondaryLabel);
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + rect.width - secWidth - 1, rect.y, secondaryLabel, !invert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,15 @@ constexpr ThemeMetrics values = {.batteryWidth = 16,
|
||||
.statusBarHorizontalMargin = 5,
|
||||
.statusBarVerticalMargin = 19,
|
||||
.keyboardKeyWidth = 31,
|
||||
.keyboardKeyHeight = 50,
|
||||
.keyboardKeyHeight = 40,
|
||||
.keyboardKeySpacing = 0,
|
||||
.keyboardBottomKeyHeight = 35,
|
||||
.keyboardBottomKeySpacing = 5,
|
||||
.keyboardBottomAligned = true,
|
||||
.keyboardCenteredText = true};
|
||||
.keyboardCenteredText = false,
|
||||
.keyboardVerticalOffset = -7,
|
||||
.keyboardTextFieldWidthPercent = 85,
|
||||
.keyboardWidthPercent = 90};
|
||||
}
|
||||
|
||||
class LyraTheme : public BaseTheme {
|
||||
@@ -66,7 +71,10 @@ class LyraTheme : public BaseTheme {
|
||||
void drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) const;
|
||||
Rect drawPopup(const GfxRenderer& renderer, const char* message) const override;
|
||||
void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const override;
|
||||
void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const override;
|
||||
void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const override;
|
||||
void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false,
|
||||
int contentStartX = 0, int contentWidth = 0) const override;
|
||||
void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected,
|
||||
const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal,
|
||||
bool inactiveSelection = false) const override;
|
||||
bool showsFileIcons() const override { return true; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user