diff --git a/modules/lock/Center.qml b/modules/lock/Center.qml index d7f83052..64ef10ce 100644 --- a/modules/lock/Center.qml +++ b/modules/lock/Center.qml @@ -1,13 +1,9 @@ -pragma ComponentBehavior: Bound - +import "center" import QtQuick import QtQuick.Layouts import Caelestia.Config import qs.components -import qs.components.controls -import qs.components.images import qs.services -import qs.utils ColumnLayout { id: root @@ -36,336 +32,20 @@ ColumnLayout { font: Tokens.font.title.builders.medium.weight(Font.DemiBold).build() } - StyledClippingRect { + ProfilePic { Layout.alignment: Qt.AlignHCenter - - implicitWidth: root.centerWidth / 2 - implicitHeight: root.centerWidth / 2 - - color: Colours.tPalette.m3surfaceContainer - radius: Tokens.rounding.full - - MaterialIcon { - anchors.centerIn: parent - - text: "person" - color: Colours.palette.m3onSurfaceVariant - fontStyle: Tokens.font.icon.size(Math.floor(root.centerWidth / 4)).build() - visible: pfp.status !== Image.Ready - } - - CachingImage { - id: pfp - - anchors.fill: parent - path: `${Paths.home}/.face` - } + centerWidth: root.centerWidth } - StyledRect { + PasswordInput { Layout.alignment: Qt.AlignHCenter - - implicitWidth: root.centerWidth * 0.8 - 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); - } - - StateLayer { - onClicked: { - parent.forceActiveFocus(); - } - - hoverEnabled: false - cursorShape: Qt.IBeamCursor - } - - RowLayout { - id: input - - anchors.fill: parent - anchors.margins: Tokens.padding.extraSmall - spacing: Tokens.spacing.medium - - Item { - implicitWidth: implicitHeight - implicitHeight: fprintIcon.implicitHeight + Tokens.padding.small - - MaterialIcon { - id: fprintIcon - - 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 - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } - } - } - - CircularIndicator { - anchors.fill: parent - running: root.lock.pam.passwd.active - } - } - - InputField { - id: inputField - - pam: root.lock.pam - } - - StyledRect { - implicitWidth: implicitHeight - implicitHeight: enterIcon.implicitHeight + Tokens.padding.small - - color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) - radius: Tokens.rounding.full - - StateLayer { - color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface - onClicked: root.lock.pam.passwd.start() - } - - MaterialIcon { - id: enterIcon - - 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() - } - } - } + centerWidth: root.centerWidth + lock: root.lock } - Item { + StateMessage { Layout.fillWidth: true Layout.topMargin: -Tokens.spacing.largeIncreased - - implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight) - - Behavior on implicitHeight { - Anim {} - } - - StyledText { - id: stateMessage - - readonly property string msg: { - if (Hypr.kbLayout !== Hypr.defaultKbLayout) { - if (Hypr.capsLock && Hypr.numLock) - return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull); - if (Hypr.capsLock) - return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull); - if (Hypr.numLock) - return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull); - return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull); - } - - if (Hypr.capsLock && Hypr.numLock) - return qsTr("Caps lock and Num lock are ON."); - if (Hypr.capsLock) - return qsTr("Caps lock is ON."); - if (Hypr.numLock) - return qsTr("Num lock is ON."); - - return ""; - } - - property bool shouldBeVisible - - onMsgChanged: { - if (msg) { - if (opacity > 0) { - animate = true; - text = msg; - animate = false; - } else { - text = msg; - } - shouldBeVisible = true; - } else { - shouldBeVisible = false; - } - } - - anchors.left: parent.left - anchors.right: parent.right - - scale: shouldBeVisible && !message.msg ? 1 : 0.7 - opacity: shouldBeVisible && !message.msg ? 1 : 0 - color: Colours.palette.m3onSurfaceVariant - - font: Tokens.font.mono.small - horizontalAlignment: Qt.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - lineHeight: 1.2 - - Behavior on scale { - Anim {} - } - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } - } - } - - StyledText { - id: message - - readonly property Pam pam: root.lock.pam - readonly property string msg: { - if (pam.fprintState === "error") - return qsTr("FP ERROR: %1").arg(pam.fprint.message); - if (pam.state === "error") - return qsTr("PW ERROR: %1").arg(pam.passwd.message); - - if (pam.lockMessage) - return pam.lockMessage; - - if (pam.state === "max" && pam.fprintState === "max") - return qsTr("Maximum password and fingerprint attempts reached."); - if (pam.state === "max") { - if (pam.fprint.available) - return qsTr("Maximum password attempts reached. Please use fingerprint."); - return qsTr("Maximum password attempts reached."); - } - if (pam.fprintState === "max") - return qsTr("Maximum fingerprint attempts reached. Please use password."); - - if (pam.state === "fail") { - if (pam.fprint.available) - return qsTr("Incorrect password. Please try again or use fingerprint."); - return qsTr("Incorrect password. Please try again."); - } - if (pam.fprintState === "fail") - return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries); - - return ""; - } - - anchors.left: parent.left - anchors.right: parent.right - - scale: 0.7 - opacity: 0 - color: Colours.palette.m3error - - font: Tokens.font.mono.small - horizontalAlignment: Qt.AlignHCenter - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - - onMsgChanged: { - if (msg) { - if (opacity > 0) { - animate = true; - text = msg; - animate = false; - - exitAnim.stop(); - if (scale < 1) - appearAnim.restart(); - else - flashAnim.restart(); - } else { - text = msg; - exitAnim.stop(); - appearAnim.restart(); - } - } else { - appearAnim.stop(); - flashAnim.stop(); - exitAnim.start(); - } - } - - Connections { - function onFlashMsg(): void { - exitAnim.stop(); - if (message.scale < 1) - appearAnim.restart(); - else - flashAnim.restart(); - } - - target: root.lock.pam - } - - Anim { - id: appearAnim - - type: Anim.DefaultEffects - target: message - properties: "scale,opacity" - to: 1 - onFinished: flashAnim.restart() - } - - SequentialAnimation { - id: flashAnim - - loops: 2 - - FlashAnim { - to: 0.3 - } - FlashAnim { - to: 1 - } - } - - ParallelAnimation { - id: exitAnim - - Anim { - target: message - property: "scale" - to: 0.7 - type: Anim.StandardLarge - } - Anim { - target: message - property: "opacity" - to: 0 - type: Anim.StandardLarge - } - } - } - } - - component FlashAnim: NumberAnimation { - target: message - property: "opacity" - duration: Tokens.anim.durations.small - easing.type: Easing.Linear + pam: root.lock.pam } } diff --git a/modules/lock/Clock.qml b/modules/lock/center/Clock.qml similarity index 100% rename from modules/lock/Clock.qml rename to modules/lock/center/Clock.qml diff --git a/modules/lock/InputField.qml b/modules/lock/center/InputField.qml similarity index 97% rename from modules/lock/InputField.qml rename to modules/lock/center/InputField.qml index 4ed153c5..78ba7bb6 100644 --- a/modules/lock/InputField.qml +++ b/modules/lock/center/InputField.qml @@ -1,11 +1,11 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Layouts import Quickshell import Caelestia.Config import qs.components import qs.services +import qs.modules.lock Item { id: root @@ -14,9 +14,6 @@ Item { readonly property alias placeholder: placeholder property string buffer - Layout.fillWidth: true - Layout.fillHeight: true - clip: true Connections { diff --git a/modules/lock/center/PasswordInput.qml b/modules/lock/center/PasswordInput.qml new file mode 100644 index 00000000..5518ea84 --- /dev/null +++ b/modules/lock/center/PasswordInput.qml @@ -0,0 +1,112 @@ +import QtQuick +import QtQuick.Layouts +import Caelestia.Config +import qs.components +import qs.components.controls +import qs.services + +StyledRect { + id: root + + required property int centerWidth + required property var lock + + implicitWidth: centerWidth * 0.8 + 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); + } + + 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 { + implicitWidth: implicitHeight + implicitHeight: fprintIcon.implicitHeight + Tokens.padding.small + + MaterialIcon { + id: fprintIcon + + 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 + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + } + + CircularIndicator { + anchors.fill: parent + running: root.lock.pam.passwd.active + } + } + + InputField { + id: inputField + + Layout.fillWidth: true + Layout.fillHeight: true + + pam: root.lock.pam + } + + StyledRect { + implicitWidth: implicitHeight + implicitHeight: enterIcon.implicitHeight + Tokens.padding.small + + color: root.lock.pam.buffer ? Colours.palette.m3primary : Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) + radius: Tokens.rounding.full + + StateLayer { + color: root.lock.pam.buffer ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface + onClicked: root.lock.pam.passwd.start() + } + + MaterialIcon { + id: enterIcon + + 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() + } + } + } +} diff --git a/modules/lock/center/ProfilePic.qml b/modules/lock/center/ProfilePic.qml new file mode 100644 index 00000000..b19b1452 --- /dev/null +++ b/modules/lock/center/ProfilePic.qml @@ -0,0 +1,34 @@ +import QtQuick +import Caelestia.Config +import qs.components +import qs.components.images +import qs.services +import qs.utils + +StyledClippingRect { + id: root + + required property int centerWidth + + implicitWidth: centerWidth / 2 + implicitHeight: centerWidth / 2 + + color: Colours.tPalette.m3surfaceContainer + radius: Tokens.rounding.full + + MaterialIcon { + anchors.centerIn: parent + + text: "person" + color: Colours.palette.m3onSurfaceVariant + fontStyle: Tokens.font.icon.size(root.centerWidth / 4).build() + visible: pfp.status !== Image.Ready + } + + CachingImage { + id: pfp + + anchors.fill: parent + path: `${Paths.home}/.face` + } +} diff --git a/modules/lock/center/StateMessage.qml b/modules/lock/center/StateMessage.qml new file mode 100644 index 00000000..40b09643 --- /dev/null +++ b/modules/lock/center/StateMessage.qml @@ -0,0 +1,211 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Caelestia.Config +import qs.components +import qs.services +import qs.modules.lock + +Item { + id: root + + required property Pam pam + + readonly property string msg: { + if (pam.fprintState === "error") + return qsTr("FP ERROR: %1").arg(pam.fprint.message); + if (pam.state === "error") + return qsTr("PW ERROR: %1").arg(pam.passwd.message); + + if (pam.lockMessage) + return pam.lockMessage; + + if (pam.state === "max" && pam.fprintState === "max") + return qsTr("Maximum password and fingerprint attempts reached."); + if (pam.state === "max") { + if (pam.fprint.available) + return qsTr("Maximum password attempts reached. Please use fingerprint."); + return qsTr("Maximum password attempts reached."); + } + if (pam.fprintState === "max") + return qsTr("Maximum fingerprint attempts reached. Please use password."); + + if (pam.state === "fail") { + if (pam.fprint.available) + return qsTr("Incorrect password. Please try again or use fingerprint."); + return qsTr("Incorrect password. Please try again."); + } + if (pam.fprintState === "fail") + return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries); + + return ""; + } + + readonly property string stateMsg: { + if (Hypr.kbLayout !== Hypr.defaultKbLayout) { + if (Hypr.capsLock && Hypr.numLock) + return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull); + if (Hypr.capsLock) + return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull); + if (Hypr.numLock) + return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull); + return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull); + } + + if (Hypr.capsLock && Hypr.numLock) + return qsTr("Caps lock and Num lock are ON."); + if (Hypr.capsLock) + return qsTr("Caps lock is ON."); + if (Hypr.numLock) + return qsTr("Num lock is ON."); + + return ""; + } + + property bool stateMsgShouldBeVisible + + onMsgChanged: { + if (msg) { + if (message.opacity > 0) { + message.animate = true; + message.text = msg; + message.animate = false; + + exitAnim.stop(); + if (message.scale < 1) + appearAnim.restart(); + else + flashAnim.restart(); + } else { + message.text = msg; + exitAnim.stop(); + appearAnim.restart(); + } + } else { + appearAnim.stop(); + flashAnim.stop(); + exitAnim.start(); + } + } + + onStateMsgChanged: { + if (stateMsg) { + if (stateMessage.opacity > 0) { + stateMessage.animate = true; + stateMessage.text = stateMsg; + stateMessage.animate = false; + } else { + stateMessage.text = stateMsg; + } + stateMsgShouldBeVisible = true; + } else { + stateMsgShouldBeVisible = false; + } + } + + implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight) + + Behavior on implicitHeight { + Anim {} + } + + StyledText { + id: stateMessage + + anchors.left: parent.left + anchors.right: parent.right + + scale: root.stateMsgShouldBeVisible && !root.msg ? 1 : 0.7 + opacity: root.stateMsgShouldBeVisible && !root.msg ? 1 : 0 + color: Colours.palette.m3onSurfaceVariant + + font: Tokens.font.mono.small + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + lineHeight: 1.2 + + Behavior on scale { + Anim {} + } + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + } + + StyledText { + id: message + + anchors.left: parent.left + anchors.right: parent.right + + scale: 0.7 + opacity: 0 + color: Colours.palette.m3error + + font: Tokens.font.mono.small + horizontalAlignment: Qt.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + + Connections { + function onFlashMsg(): void { + exitAnim.stop(); + if (message.scale < 1) + appearAnim.restart(); + else + flashAnim.restart(); + } + + target: root.pam + } + + Anim { + id: appearAnim + + type: Anim.DefaultEffects + target: message + properties: "scale,opacity" + to: 1 + onFinished: flashAnim.restart() + } + + SequentialAnimation { + id: flashAnim + + loops: 2 + + FlashAnim { + to: 0.3 + } + FlashAnim { + to: 1 + } + } + + ParallelAnimation { + id: exitAnim + + Anim { + target: message + property: "scale" + to: 0.7 + type: Anim.StandardLarge + } + Anim { + target: message + property: "opacity" + to: 0 + type: Anim.StandardLarge + } + } + } + + component FlashAnim: NumberAnimation { + target: message + property: "opacity" + duration: Tokens.anim.durations.small + easing.type: Easing.Linear + } +}