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 required property string modifier property int shiftMode: KeyboardController.Off property var modifierModes: ({}) property bool blank: false // labels hidden, e.g. in trackpad mode readonly property bool isCharacter: keyType === "char" || keyType === "space" // Off / Once / Locked for shift, caps lock and ctrl/alt/super. readonly property int latch: { if (keyType === "shift") return shiftMode if (keyType === "capslock") return shiftMode === KeyboardController.Locked ? KeyboardController.Locked : KeyboardController.Off if (keyType === "modifier") return modifierModes[modifier] ?? KeyboardController.Off return KeyboardController.Off } // Accent color for locked modifiers. Shift is excluded: its glyph shows the lock. readonly property bool locked: latch === KeyboardController.Locked && keyType !== "shift" // Light background: character keys at rest, function keys while pressed or latched (as on iPad). readonly property bool lit: (isCharacter !== pressed) || latch !== KeyboardController.Off readonly property var defaultGlyphs: ({ backspace: "⌫", globe: "🌐", dismiss: "⌨", enter: "return", }) readonly property string displayLabel: { if (keyType === "shift") return ["⇧", "⬆", "⇪"][shiftMode] return label !== "" ? label : (defaultGlyphs[keyType] ?? "") } // Multi-character label (e.g. "return"). Counts code points, not UTF-16 units, so an emoji counts as one. 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.locked ? Theme.accent : 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: key.locked ? Theme.accentText : Theme.text visible: !key.blank opacity: 1 - key.flickProgress } }