diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml index c96c5f0c..bfd747de 100644 --- a/modules/lock/Center.qml +++ b/modules/lock/Center.qml @@ -41,6 +41,7 @@ ColumnLayout { PasswordInput { Layout.alignment: Qt.AlignHCenter + centerScale: Math.max(0.8, root.centerScale) centerWidth: root.centerWidth lock: root.lock } diff --git a/modules/lock/center/InputField.qml b/modules/lock/center/InputField.qml index 78ba7bb6..90b0c7de 100644 --- a/modules/lock/center/InputField.qml +++ b/modules/lock/center/InputField.qml @@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell +import M3Shapes import Caelestia.Config import qs.components import qs.services @@ -10,9 +11,19 @@ import qs.modules.lock Item { id: root + required property real centerScale required property Pam pam readonly property alias placeholder: placeholder + readonly property alias placeholderWidth: nonAnimPlaceholder.width property string buffer + readonly property list shapeQueue: { + const shapes = [MaterialShape.Slanted, MaterialShape.Arch, MaterialShape.Fan, MaterialShape.Arrow, MaterialShape.SemiCircle, MaterialShape.Triangle, MaterialShape.Diamond, MaterialShape.ClamShell, MaterialShape.Pentagon, MaterialShape.Gem, MaterialShape.Sunny, MaterialShape.VerySunny, MaterialShape.Cookie4Sided, MaterialShape.Ghostish, MaterialShape.SoftBurst]; + for (let i = shapes.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shapes[i], shapes[j]] = [shapes[j], shapes[i]]; + } + return shapes; + } clip: true @@ -31,22 +42,30 @@ Item { target: root.pam } - StyledText { - id: placeholder - - anchors.centerIn: parent + TextMetrics { + id: nonAnimPlaceholder text: { if (root.pam.passwd.active) return qsTr("Loading..."); if (root.pam.state === "max") - return qsTr("You have reached the maximum number of tries"); + return qsTr("Max tries reached"); return qsTr("Enter your password"); } + font: placeholder.font + } + + StyledText { + id: placeholder + + anchors.centerIn: parent + anchors.verticalCenterOffset: 1 + + text: nonAnimPlaceholder.text animate: true color: root.pam.passwd.active ? Colours.palette.m3secondary : Colours.palette.m3outline - font: Tokens.font.mono.medium + font: Tokens.font.body.builders.medium.scale(root.centerScale).width(110).build() opacity: root.buffer ? 0 : 1 @@ -60,7 +79,12 @@ Item { ListView { id: charList - readonly property int fullWidth: count * (implicitHeight + spacing) - spacing + readonly property int fullWidth: { + let w = (count - 1) * spacing; + for (let i = 0; i < count; i++) + w += ((itemAtIndex(i) as CharItem)?.nonAnimWidthScale ?? 1) * implicitHeight; + return w + implicitHeight; // Extra padding at ends + } function bindImWidth(): void { imWidthBehavior.enabled = false; @@ -82,63 +106,7 @@ Item { values: root.buffer.split("") } - delegate: StyledRect { - id: ch - - implicitWidth: implicitHeight - implicitHeight: charList.implicitHeight - - color: Colours.palette.m3onSurface - radius: Tokens.rounding.medium / 2 - - opacity: 0 - scale: 0 - Component.onCompleted: { - opacity = 1; - scale = 1; - } - ListView.onRemove: removeAnim.start() - - SequentialAnimation { - id: removeAnim - - PropertyAction { - target: ch - property: "ListView.delayRemove" - value: true - } - ParallelAnimation { - Anim { - type: Anim.DefaultEffects - target: ch - property: "opacity" - to: 0 - } - Anim { - target: ch - property: "scale" - to: 0.5 - } - } - PropertyAction { - target: ch - property: "ListView.delayRemove" - value: false - } - } - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } - } - - Behavior on scale { - Anim { - type: Anim.FastSpatial - } - } - } + delegate: CharItem {} Behavior on implicitWidth { id: imWidthBehavior @@ -146,4 +114,121 @@ Item { Anim {} } } + + component CharItem: Item { + id: char + + required property int index + property real nonAnimWidthScale: 1 + + implicitHeight: charList.implicitHeight + + ListView.onRemove: { + initAnim.stop(); + removeAnim.start(); + } + + MaterialShape { + id: charShape + + anchors.centerIn: parent + implicitSize: charList.implicitHeight * 1.5 + shape: root.shapeQueue[char.index % root.shapeQueue.length] ?? MaterialShape.Circle + color: Colours.palette.m3onSurface + + Behavior on color { + CAnim {} + } + + SequentialAnimation { + id: initAnim + + running: true + + ParallelAnimation { + Anim { + target: charShape + property: "opacity" + from: 0 + to: 1 + type: Anim.DefaultEffects + } + Anim { + target: charShape + property: "scale" + from: 0 + to: 1 + type: Anim.FastSpatial + } + Anim { + target: char + property: "implicitWidth" + from: charList.implicitHeight + to: charList.implicitHeight * 1.3 + type: Anim.DefaultEffects + } + PropertyAction { + target: char + property: "nonAnimWidthScale" + value: 1.5 + } + } + PauseAnimation { + duration: 180 * Tokens.anim.durations.scale + } + PropertyAction { + target: charShape + property: "shape" + value: MaterialShape.Circle + } + ParallelAnimation { + Anim { + target: charShape + property: "scale" + to: 2 / 3 + type: Anim.FastSpatial + } + Anim { + target: char + property: "implicitWidth" + to: charList.implicitHeight + type: Anim.DefaultEffects + } + PropertyAction { + target: char + property: "nonAnimWidthScale" + value: 1 + } + } + } + + SequentialAnimation { + id: removeAnim + + PropertyAction { + target: char + property: "ListView.delayRemove" + value: true + } + ParallelAnimation { + Anim { + type: Anim.DefaultEffects + target: charShape + property: "opacity" + to: 0 + } + Anim { + target: charShape + property: "scale" + to: 0.5 + } + } + PropertyAction { + target: char + property: "ListView.delayRemove" + value: false + } + } + } + } } diff --git a/modules/lock/center/PasswordInput.qml b/modules/lock/center/PasswordInput.qml index fb01d53c..e93884a2 100644 --- a/modules/lock/center/PasswordInput.qml +++ b/modules/lock/center/PasswordInput.qml @@ -1,5 +1,8 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Layouts +import M3Shapes import Caelestia.Config import qs.components import qs.components.controls @@ -8,10 +11,14 @@ import qs.services StyledRect { id: root + required property real centerScale required property int centerWidth required property var lock - implicitWidth: centerWidth * 0.8 + 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 @@ -33,6 +40,10 @@ StyledRect { root.lock.pam.handleKey(event); } + Behavior on implicitWidth { + Anim {} + } + StateLayer { hoverEnabled: false cursorShape: Qt.IBeamCursor @@ -47,44 +58,39 @@ StyledRect { spacing: Tokens.spacing.medium Item { + id: iconWrapper + Layout.fillHeight: true implicitWidth: height - MaterialIcon { - id: fprintIcon - + AnimLoader { anchors.centerIn: parent - 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.m3onSurface - opacity: root.lock.pam.passwd.active ? 0 : 1 + anchors.verticalCenterOffset: sourceComponent === iconComp ? 1 : 0 + sourceComp: root.lock.pam.passwd.active ? loadingComp : iconComp + } - Behavior on opacity { - Anim { - type: Anim.DefaultEffects + 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() } } - Loader { - anchors.fill: parent - anchors.margins: Tokens.padding.small + Component { + id: loadingComp - active: opacity > 0 - opacity: root.lock.pam.passwd.active ? 1 : 0 - - sourceComponent: LoadingIndicator {} - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } + LoadingIndicator { + implicitSize: iconWrapper.height - Tokens.padding.small * 2 } } } @@ -95,19 +101,45 @@ StyledRect { Layout.fillWidth: true Layout.fillHeight: true + centerScale: root.centerScale pam: root.lock.pam } - StyledRect { + Item { + id: enterButton + implicitWidth: implicitHeight - implicitHeight: enterIcon.implicitHeight + Tokens.padding.small + implicitHeight: { + const h = enterIcon.implicitHeight + Tokens.padding.extraSmall * 2; + return h % 2 === 0 ? h : h + 1; + } - color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) - radius: Tokens.rounding.full + MaterialShape { + anchors.fill: parent - StateLayer { - color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface - onClicked: root.lock.pam.passwd.start() + 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 { @@ -115,8 +147,15 @@ StyledRect { anchors.centerIn: parent text: "arrow_forward" - color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface - fontStyle: Tokens.font.icon.size(Tokens.font.icon.large.pointSize).weight(Font.Medium).build() + 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 + } + } } } } diff --git a/modules/lock/center/StateMessage.qml b/modules/lock/center/StateMessage.qml index 40b09643..d6a37a35 100644 --- a/modules/lock/center/StateMessage.qml +++ b/modules/lock/center/StateMessage.qml @@ -119,7 +119,7 @@ Item { opacity: root.stateMsgShouldBeVisible && !root.msg ? 1 : 0 color: Colours.palette.m3onSurfaceVariant - font: Tokens.font.mono.small + font: Tokens.font.body.small horizontalAlignment: Qt.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere lineHeight: 1.2 @@ -145,7 +145,7 @@ Item { opacity: 0 color: Colours.palette.m3error - font: Tokens.font.mono.small + font: Tokens.font.body.small horizontalAlignment: Qt.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere