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:
+74
@@ -0,0 +1,74 @@
|
||||
import QtQuick
|
||||
import Sloposk
|
||||
|
||||
// One key, drawn from the LayoutModel roles. Purely visual.
|
||||
Item {
|
||||
id: key
|
||||
|
||||
required property string label
|
||||
required property string flickLabel
|
||||
required property string keyType
|
||||
required property real keyX
|
||||
required property real keyY
|
||||
required property real keyWidth
|
||||
required property real keyHeight
|
||||
required property bool pressed
|
||||
property int shiftMode: KeyboardController.Off
|
||||
|
||||
readonly property bool isCharacter: keyType === "char" || keyType === "space"
|
||||
readonly property bool shiftActive: keyType === "shift" && shiftMode !== KeyboardController.Off
|
||||
// Character keys darken when pressed; function keys lighten (as on iPad).
|
||||
readonly property bool lit: (isCharacter !== pressed) || shiftActive
|
||||
|
||||
readonly property var defaultGlyphs: ({
|
||||
backspace: "⌫",
|
||||
globe: "🌐",
|
||||
dismiss: "⌨",
|
||||
enter: "return",
|
||||
})
|
||||
readonly property string displayLabel: {
|
||||
if (keyType === "shift")
|
||||
return ["⇧", "⬆", "⇪"][shiftMode]
|
||||
return label !== "" ? label : (defaultGlyphs[keyType] ?? "")
|
||||
}
|
||||
// Count code points, not UTF-16 units, so emoji glyphs aren't treated as words.
|
||||
readonly property bool isWord: [...displayLabel].length > 1 && !isCharacter
|
||||
|
||||
x: keyX
|
||||
y: keyY
|
||||
width: keyWidth
|
||||
height: keyHeight
|
||||
|
||||
Rectangle {
|
||||
anchors { fill: face; topMargin: 1; bottomMargin: -1 }
|
||||
radius: Theme.keyRadius
|
||||
color: Theme.keyShadow
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: face
|
||||
anchors.fill: parent
|
||||
radius: Theme.keyRadius
|
||||
color: key.lit ? Theme.characterKey : Theme.functionKey
|
||||
}
|
||||
|
||||
Text {
|
||||
id: flick
|
||||
visible: text !== ""
|
||||
text: key.flickLabel
|
||||
anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: key.height * 0.1 }
|
||||
font.pixelSize: key.height * 0.24
|
||||
color: Theme.secondaryText
|
||||
}
|
||||
|
||||
Text {
|
||||
text: key.displayLabel
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
verticalCenterOffset: flick.visible ? key.height * 0.14 : 0
|
||||
}
|
||||
font.pixelSize: key.height * (key.isWord ? 0.24 : flick.visible ? 0.34 : 0.4)
|
||||
color: Theme.text
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user