Files
sloposk/qml/Key.qml
T
ericek111andClaude Opus 5.5 e532f5c072 Smooth flick animation: scale the symbol instead of its font size
font.pixelSize is an integer, so the old animation stepped in whole
pixels and re-laid out the text each step: width and height jumped,
centering snapped x sideways and y even moved backwards. Draw at the
final size and animate scale and position, which are continuous.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:30:15 +02:00

89 lines
3.0 KiB
QML

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
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
// 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
}
// Flicking slides the secondary symbol down into the main position.
Text {
id: flick
// Drawn at its final size and scaled down at rest. Animating
// font.pixelSize instead steps in whole pixels and re-lays out the
// text each step, so the symbol jitters.
readonly property real restScale: 0.6
readonly property real restCenterY: key.height * 0.1 + height * restScale / 2
readonly property real centerY: restCenterY + (key.height / 2 - restCenterY) * key.flickProgress
visible: text !== "" && !key.blank
text: key.flickLabel
anchors.horizontalCenter: parent.horizontalCenter
y: centerY - height / 2
scale: restScale + (1 - restScale) * key.flickProgress
font.pixelSize: key.height * 0.4
color: key.flickProgress >= 1 ? Theme.text : 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
visible: !key.blank
opacity: 1 - key.flickProgress
}
}