Gestures: flick, long-press alternates, trackpad, layer slide, rollover
- PointerTracker: per-finger gesture state machine (pure, unit-tested) - AlternatesPicker + AlternatesPopup.qml: long-press choices, finger selects - Space long-press trackpad moves the cursor with arrow keysyms - Slide from .?123 onto a symbol types it and switches back - New finger commits pending taps (iOS rollover) - Flick animation: secondary symbol slides down as the finger moves - Transparent popup margin with an input region that passes clicks through - tst_keyboardsurface: end-to-end gesture tests on the real QML Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import QtQuick
|
||||
import Sloposk
|
||||
|
||||
// Long-press choices above a key; the one under the finger is highlighted.
|
||||
Item {
|
||||
id: popup
|
||||
|
||||
required property AlternatesPicker picker
|
||||
|
||||
visible: picker.open
|
||||
x: picker.rect.x
|
||||
y: picker.rect.y
|
||||
width: picker.rect.width
|
||||
height: picker.rect.height
|
||||
|
||||
Rectangle {
|
||||
anchors { fill: background; topMargin: 1; bottomMargin: -2 }
|
||||
radius: background.radius
|
||||
color: Theme.keyShadow
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
radius: Theme.keyRadius + 2
|
||||
color: Theme.characterKey
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
|
||||
Repeater {
|
||||
model: popup.picker.choices
|
||||
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
required property int index
|
||||
readonly property bool selected: index === popup.picker.selected
|
||||
|
||||
width: popup.width / popup.picker.choices.length
|
||||
height: popup.height
|
||||
radius: Theme.keyRadius
|
||||
color: selected ? Theme.accent : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.modelData
|
||||
font.pixelSize: parent.height * 0.4
|
||||
color: parent.selected ? Theme.accentText : Theme.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -13,7 +13,9 @@ Item {
|
||||
required property real keyWidth
|
||||
required property real keyHeight
|
||||
required property bool pressed
|
||||
required property real flickProgress
|
||||
property int shiftMode: KeyboardController.Off
|
||||
property bool blank: false // labels hidden, e.g. in trackpad mode
|
||||
|
||||
readonly property bool isCharacter: keyType === "char" || keyType === "space"
|
||||
readonly property bool shiftActive: keyType === "shift" && shiftMode !== KeyboardController.Off
|
||||
@@ -52,13 +54,18 @@ Item {
|
||||
color: key.lit ? Theme.characterKey : Theme.functionKey
|
||||
}
|
||||
|
||||
// Flicking slides the secondary symbol down into the main position.
|
||||
Text {
|
||||
id: flick
|
||||
visible: text !== ""
|
||||
readonly property real restY: key.height * 0.1
|
||||
readonly property real centerY: (key.height - height) / 2
|
||||
|
||||
visible: text !== "" && !key.blank
|
||||
text: key.flickLabel
|
||||
anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: key.height * 0.1 }
|
||||
font.pixelSize: key.height * 0.24
|
||||
color: Theme.secondaryText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: restY + (centerY - restY) * key.flickProgress
|
||||
font.pixelSize: key.height * (0.24 + 0.16 * key.flickProgress)
|
||||
color: key.flickProgress >= 1 ? Theme.text : Theme.secondaryText
|
||||
}
|
||||
|
||||
Text {
|
||||
@@ -70,5 +77,7 @@ Item {
|
||||
}
|
||||
font.pixelSize: key.height * (key.isWord ? 0.24 : flick.visible ? 0.34 : 0.4)
|
||||
color: Theme.text
|
||||
visible: !key.blank
|
||||
opacity: 1 - key.flickProgress
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ KeyboardSurface {
|
||||
model: surface.model
|
||||
delegate: Key {
|
||||
shiftMode: surface.controller ? surface.controller.shiftMode : KeyboardController.Off
|
||||
blank: surface.trackpadActive
|
||||
}
|
||||
}
|
||||
|
||||
AlternatesPopup {
|
||||
picker: surface.alternates
|
||||
z: 1
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -8,17 +8,29 @@ Window {
|
||||
required property KeyboardController controller
|
||||
property var placement: null // FloatingWindow, set from C++
|
||||
|
||||
// Transparent room above the keyboard for long-press popups on the top row.
|
||||
readonly property real popupMargin: 90
|
||||
// Outside this, input passes through to whatever is below the window.
|
||||
readonly property rect inputRegion: keyboard.alternates.open
|
||||
? Qt.rect(0, 0, width, height)
|
||||
: Qt.rect(panel.x, panel.y, panel.width, panel.height)
|
||||
|
||||
width: 1000
|
||||
height: 340
|
||||
height: 340 + popupMargin
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
onInputRegionChanged: placement?.setInputRegion(inputRegion)
|
||||
onPlacementChanged: placement?.setInputRegion(inputRegion)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
id: panel
|
||||
anchors { fill: parent; topMargin: root.popupMargin }
|
||||
radius: Theme.windowRadius
|
||||
color: Theme.background
|
||||
|
||||
Keyboard {
|
||||
id: keyboard
|
||||
anchors { left: parent.left; right: parent.right; top: parent.top; bottom: handle.top }
|
||||
anchors.margins: 2
|
||||
model: root.layoutModel
|
||||
|
||||
@@ -13,6 +13,8 @@ QtObject {
|
||||
readonly property color text: dark ? "#ffffff" : "#000000"
|
||||
readonly property color secondaryText: dark ? "#a5a5aa" : "#8a8a8e"
|
||||
readonly property color handle: dark ? "#8e8e93" : "#7c7c80"
|
||||
readonly property color accent: dark ? "#0a84ff" : "#007aff"
|
||||
readonly property color accentText: "#ffffff"
|
||||
|
||||
readonly property real windowRadius: 14
|
||||
readonly property real keyRadius: 6
|
||||
|
||||
Reference in New Issue
Block a user