nosignal-shell/modules/lock/center/PasswordInput.qml
2 * r + 2 * t a1124c8228
feat: expressive lock input (#1536)
* feat: shapes in lock input

* fix: better lock input placeholder

* feat: scale lock center properly + style improvements

* fix: use non anim placeholder width

* fix: not mono lock state text

* fix: use shape queue instead of random

* fix: use scale for enter button instead of state layer

* fix: no slant for lock state message

* fix: enter button pressable when no buffer
2026-06-09 19:07:18 +10:00

162 lines
4.6 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import M3Shapes
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.services
StyledRect {
id: root
required property real centerScale
required property int centerWidth
required property var lock
implicitWidth: {
const w = centerWidth * 0.8;
return lock.pam.buffer ? w : Math.min(w, inputField.placeholderWidth + iconWrapper.implicitWidth + enterButton.implicitWidth + input.spacing * 2 + Tokens.padding.medium * 2);
}
implicitHeight: input.implicitHeight + Tokens.padding.small
color: Colours.tPalette.m3surfaceContainer
radius: Tokens.rounding.full
focus: true
onActiveFocusChanged: {
if (!activeFocus)
forceActiveFocus();
}
Keys.onPressed: event => {
if (root.lock.unlocking)
return;
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
inputField.placeholder.animate = false;
root.lock.pam.handleKey(event);
}
Behavior on implicitWidth {
Anim {}
}
StateLayer {
hoverEnabled: false
cursorShape: Qt.IBeamCursor
onClicked: parent.forceActiveFocus()
}
RowLayout {
id: input
anchors.fill: parent
anchors.margins: Tokens.padding.extraSmall
spacing: Tokens.spacing.medium
Item {
id: iconWrapper
Layout.fillHeight: true
implicitWidth: height
AnimLoader {
anchors.centerIn: parent
anchors.verticalCenterOffset: sourceComponent === iconComp ? 1 : 0
sourceComp: root.lock.pam.passwd.active ? loadingComp : iconComp
}
Component {
id: iconComp
MaterialIcon {
animate: true
text: {
if (root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries)
return "fingerprint_off";
if (root.lock.pam.fprint.active)
return "fingerprint";
return "lock";
}
color: root.lock.pam.fprint.tries >= GlobalConfig.lock.maxFprintTries ? Colours.palette.m3error : Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.medium.scale(root.centerScale).build()
}
}
Component {
id: loadingComp
LoadingIndicator {
implicitSize: iconWrapper.height - Tokens.padding.small * 2
}
}
}
InputField {
id: inputField
Layout.fillWidth: true
Layout.fillHeight: true
centerScale: root.centerScale
pam: root.lock.pam
}
Item {
id: enterButton
implicitWidth: implicitHeight
implicitHeight: {
const h = enterIcon.implicitHeight + Tokens.padding.extraSmall * 2;
return h % 2 === 0 ? h : h + 1;
}
MaterialShape {
anchors.fill: parent
color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2)
shape: root.lock.pam.buffer ? MaterialShape.Arrow : MaterialShape.Circle
scale: !root.lock.pam.buffer ? 1 : mouse.pressed ? 0.6 : mouse.containsMouse ? 0.8 : 0.7
rotation: 90
Behavior on scale {
Anim {
type: Anim.FastSpatial
}
}
Behavior on color {
CAnim {}
}
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: root.lock.pam.buffer ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: root.lock.pam.buffer && root.lock.pam.passwd.start()
}
}
MaterialIcon {
id: enterIcon
anchors.centerIn: parent
text: "arrow_forward"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.builders.medium.scale(root.centerScale * 1.2).build()
opacity: root.lock.pam.buffer ? 0 : 1
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
}
}
}