Layouts, iPad-style rendering, taps, shift and layers

- JSON layouts (en-US, sv-SE inheriting number/symbol layers via base)
- LayoutModel + KeyGeometry: proportional rows, nearest-key hit testing
- KeyboardSurface: multi-pointer touch/mouse routing to the controller
- KeyboardController + ShiftState: iOS shift/caps-lock, layers, globe, dismiss
- QML view with light/dark iPad palette and drag handle
- DevHooks: SLOPOSK_AUTOTYPE, SLOPOSK_SCREENSHOT for headless checks
- Unit tests for shift, loader, geometry and controller

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 14:50:21 +02:00
co-authored by Claude Opus 5.5
parent cdef9b4ad0
commit be14bbcc62
35 changed files with 1970 additions and 65 deletions
+117
View File
@@ -0,0 +1,117 @@
{
"id": "en-US",
"name": "English (US)",
"locale": "en_US",
"layers": [
{
"name": "letters",
"rows": [
[
{"label": "q", "flick": "1"},
{"label": "w", "flick": "2"},
{"label": "e", "flick": "3", "alternates": ["é", "è", "ê", "ë", "ē", "ė", "ę"]},
{"label": "r", "flick": "4"},
{"label": "t", "flick": "5", "alternates": ["þ"]},
{"label": "y", "flick": "6", "alternates": ["ý", "ÿ"]},
{"label": "u", "flick": "7", "alternates": ["ú", "ù", "û", "ü", "ū"]},
{"label": "i", "flick": "8", "alternates": ["í", "ì", "î", "ï", "ī", "į"]},
{"label": "o", "flick": "9", "alternates": ["ó", "ò", "ô", "ö", "õ", "ø", "œ", "ō"]},
{"label": "p", "flick": "0"},
{"type": "backspace", "width": 1.5}
],
[
{"type": "spacer", "width": 0.5},
{"label": "a", "flick": "@", "alternates": ["á", "à", "â", "ä", "æ", "ã", "å", "ā"]},
{"label": "s", "flick": "#", "alternates": ["ß", "ś", "š"]},
{"label": "d", "flick": "$", "alternates": ["ð"]},
{"label": "f", "flick": "&"},
{"label": "g", "flick": "*"},
{"label": "h", "flick": "("},
{"label": "j", "flick": ")"},
{"label": "k", "flick": "'"},
{"label": "l", "flick": "\"", "alternates": ["ł"]},
{"type": "enter", "width": 2}
],
[
{"type": "shift"},
{"label": "z", "flick": "%", "alternates": ["ž", "ź", "ż"]},
{"label": "x", "flick": "-"},
{"label": "c", "flick": "+", "alternates": ["ç", "ć", "č"]},
{"label": "v", "flick": "="},
{"label": "b", "flick": "/"},
{"label": "n", "flick": ";", "alternates": ["ñ", "ń"]},
{"label": "m", "flick": ":"},
{"label": ",", "flick": "!", "shifted": ","},
{"label": ".", "flick": "?", "shifted": ".", "alternates": ["…"]},
{"type": "shift", "width": 1.5}
],
[
{"type": "layer", "label": ".?123", "target": "numbers", "width": 1.5},
{"type": "globe"},
{"type": "space", "width": 6.5},
{"type": "layer", "label": ".?123", "target": "numbers", "width": 1.5},
{"type": "dismiss"}
]
]
},
{
"name": "numbers",
"rows": [
["1", "2", "3", "4", "5", "6", "7", "8", "9",
{"label": "0", "alternates": ["°"]},
{"type": "backspace", "width": 1.5}],
[
{"type": "spacer", "width": 0.5},
"@", "#",
{"label": "$", "alternates": ["€", "£", "¥", "₩", "₽", "¢"]},
{"label": "&", "alternates": ["§"]},
"*", "(", ")",
{"label": "'", "alternates": ["‘", "’", "`"]},
{"label": "\"", "alternates": ["“", "”", "„", "«", "»"]},
{"type": "enter", "width": 2}
],
[
{"type": "layer", "label": "#+=", "target": "symbols"},
"%",
{"label": "-", "alternates": ["–", "—", "•"]},
"+", "=",
{"label": "/", "alternates": ["\\"]},
";", ":", ",",
{"label": ".", "alternates": ["…"]},
{"type": "layer", "label": "#+=", "target": "symbols", "width": 1.5}
],
[
{"type": "layer", "label": "ABC", "target": "letters", "width": 1.5},
{"type": "globe"},
{"type": "space", "width": 6.5},
{"type": "layer", "label": "ABC", "target": "letters", "width": 1.5},
{"type": "dismiss"}
]
]
},
{
"name": "symbols",
"rows": [
["[", "]", "{", "}", "#", "%", "^", "*", "+", "=",
{"type": "backspace", "width": 1.5}],
[
{"type": "spacer", "width": 0.5},
"_", "\\", "|", "~", "<", ">", "€", "£", "¥",
{"type": "enter", "width": 2}
],
[
{"type": "layer", "label": "123", "target": "numbers"},
"•", "…", ".", ",", "?", "!", "'", "\"", "`",
{"type": "layer", "label": "123", "target": "numbers", "width": 1.5}
],
[
{"type": "layer", "label": "ABC", "target": "letters", "width": 1.5},
{"type": "globe"},
{"type": "space", "width": 6.5},
{"type": "layer", "label": "ABC", "target": "letters", "width": 1.5},
{"type": "dismiss"}
]
]
}
]
}