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
+21 -32
View File
@@ -1,45 +1,34 @@
import QtQuick
import QtQuick.Controls
import Sloposk
// Spike UI: proves text commits, keysyms and dragging. Replaced by the real
// keyboard in milestone 2.
Window {
id: root
required property var backend
property var placement: null
required property LayoutModel layoutModel
required property KeyboardController controller
property var placement: null // FloatingWindow, set from C++
width: 720
height: 120
width: 1000
height: 340
visible: false
color: "#d1d3d9"
color: "transparent"
Rectangle {
id: handle
anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: 6 }
width: 120; height: 6; radius: 3
color: "#8e8e93"
anchors.fill: parent
radius: Theme.windowRadius
color: Theme.background
DragHandler {
target: null
margin: 12
property point last
onActiveChanged: last = centroid.scenePressPosition
onCentroidChanged: {
if (!active || !root.placement)
return
const p = centroid.scenePosition
root.placement.moveBy(p.x - last.x, p.y - last.y)
}
Keyboard {
anchors { left: parent.left; right: parent.right; top: parent.top; bottom: handle.top }
anchors.margins: 2
model: root.layoutModel
controller: root.controller
}
DragHandle {
id: handle
anchors { left: parent.left; right: parent.right; bottom: parent.bottom }
onMoved: (dx, dy) => root.placement?.moveBy(dx, dy)
}
}
Row {
anchors.centerIn: parent
spacing: 8
Button { text: "héllo👍"; focusPolicy: Qt.NoFocus; onClicked: root.backend.commitText(text) }
Button { text: "⌫"; focusPolicy: Qt.NoFocus; onClicked: root.backend.sendKeysym(0xff08) }
Button { text: "⏎"; focusPolicy: Qt.NoFocus; onClicked: root.backend.sendKeysym(0xff0d) }
Label { anchors.verticalCenter: parent.verticalCenter; text: root.backend.active ? "active" : "inactive" }
}
}