diff --git a/dank-qml-common b/dank-qml-common index b5b763969..b690ebcf7 160000 --- a/dank-qml-common +++ b/dank-qml-common @@ -1 +1 @@ -Subproject commit b5b7639693b2c83f0f97ddd2af3d60d559174846 +Subproject commit b690ebcf7eddffae846fb1aa1a9acaf89a384777 diff --git a/flake.lock b/flake.lock index 191a394d2..38e16f49c 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "dank-qml-common": { "flake": false, "locked": { - "lastModified": 1791237894, - "narHash": "sha256-O2Gsf++QDSD7GHXGitFHvHKW+5jaYkTwKZb40U7EB2U=", + "lastModified": 1791252506, + "narHash": "sha256-AWmajKfU9u791pk4z3qr9WJT2Q11AJWlbdvNGzu1TL4=", "owner": "AvengeMedia", "repo": "dank-qml-common", - "rev": "b5b7639693b2c83f0f97ddd2af3d60d559174846", + "rev": "b690ebcf7eddffae846fb1aa1a9acaf89a384777", "type": "github" }, "original": { diff --git a/quickshell/Modals/PolkitAuthContent.qml b/quickshell/Modals/PolkitAuthContent.qml index a6544dc7c..afadef578 100644 --- a/quickshell/Modals/PolkitAuthContent.qml +++ b/quickshell/Modals/PolkitAuthContent.qml @@ -95,6 +95,7 @@ DDialog { function onAuthenticationFailed() { root.isLoading = false; + passwordField.shake(); } function onAuthenticationRequestCancelled() { @@ -150,18 +151,16 @@ DDialog { id: passwordField width: parent.width - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true + morph: true + busy: root.isLoading labelText: root.inputPromptLabel || I18n.tr("Password") isError: PolkitService.authFailed supportingText: isError ? I18n.tr("Authentication failed - try again") : "" leftIconName: root.polkitPamHasFprint ? "fingerprint" : "lock" - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText text: root.passwordInput showPasswordToggle: !(root.currentFlow?.responseVisible ?? false) echoMode: (root.currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password - placeholderText: "" enabled: !root.isLoading onTextEdited: root.passwordInput = text onAccepted: root.submitAuth() @@ -181,7 +180,6 @@ DDialog { wrapText: true text: I18n.tr("Authenticate", "verb, polkit password dialog submit button") enabled: !root.isLoading - busy: root.isLoading onClicked: root.submitAuth() } ] diff --git a/quickshell/Modals/WifiPasswordModal.qml b/quickshell/Modals/WifiPasswordModal.qml index a02b2fbbb..cf1f60615 100644 --- a/quickshell/Modals/WifiPasswordModal.qml +++ b/quickshell/Modals/WifiPasswordModal.qml @@ -165,9 +165,8 @@ DModal { open(); Qt.callLater(() => { - if (reason === "wrong-password" && fieldsInfo.length === 0) { - passwordInput.text = ""; - } + if (reason === "wrong-password") + shakeSecretFields(); focusFirstField(); }); } @@ -176,6 +175,19 @@ DModal { close(); } + function shakeSecretFields() { + if (fieldsInfo.length === 0) { + passwordInput.text = ""; + passwordInput.shake(); + return; + } + for (var i = 0; i < dynamicFieldsRepeater.count; i++) { + const item = dynamicFieldsRepeater.itemAt(i); + if (item?.morph) + item.shake(); + } + } + function getFieldLabel(fieldName) { switch (fieldName) { case "username": @@ -369,13 +381,10 @@ DModal { DTextField { id: ssidInput visible: isHiddenNetwork - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true leftIconName: "wifi" width: parent.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText labelText: I18n.tr("Network Name (SSID)") enabled: root.shouldBeVisible @@ -390,12 +399,10 @@ DModal { id: fieldInput required property var modelData required property int index - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true + morph: modelData.isSecret leftIconName: modelData.isSecret ? "lock" : "person" width: contentFocusScope.contentItem.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText showPasswordToggle: modelData.isSecret isError: modelData.isSecret && isPromptMode && promptReason === "wrong-password" && text.length === 0 supportingText: isError ? I18n.tr("Incorrect password") : "" @@ -476,13 +483,10 @@ DModal { DTextField { id: usernameInput visible: showUsernameField - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true leftIconName: "person" width: parent.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText text: wifiUsernameInput labelText: I18n.tr("Username", "text field label for network, vpn and account forms") enabled: root.shouldBeVisible @@ -494,13 +498,11 @@ DModal { DTextField { id: passwordInput visible: showPasswordField - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true + morph: true leftIconName: "lock" width: parent.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText text: wifiPasswordInput showPasswordToggle: true isError: isPromptMode && promptReason === "wrong-password" && text.length === 0 @@ -522,13 +524,10 @@ DModal { DTextField { id: anonInput visible: showAnonField - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true leftIconName: "person_off" width: parent.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText text: wifiAnonymousIdentityInput labelText: I18n.tr("Anonymous Identity (optional)") enabled: root.shouldBeVisible @@ -540,13 +539,10 @@ DModal { DTextField { id: domainMatchInput visible: showDomainField - outlined: true - controlHeight: Theme.fieldHeightLarge + expressive: true leftIconName: "domain" width: parent.width - font.pixelSize: Theme.fontSizeMedium - textColor: Theme.surfaceText text: wifiDomainInput labelText: I18n.tr("Domain (optional)") enabled: root.shouldBeVisible diff --git a/quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml b/quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml index 9e8a6e7a9..f1c31ab46 100644 --- a/quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml +++ b/quickshell/Modules/ControlCenter/Details/VpnProfileDelegate.qml @@ -174,6 +174,8 @@ CcListRow { DTextField { id: usernameField width: parent.width + expressive: true + leftIconName: "person" placeholderText: I18n.tr("Username") text: (root.configData && (root.configData.username || (root.configData.data && root.configData.data.username))) || "" } @@ -181,10 +183,12 @@ CcListRow { DTextField { id: passwordField width: parent.width + expressive: true + leftIconName: "lock" placeholderText: I18n.tr("Password") echoMode: TextInput.Password showPasswordToggle: true - normalBorderColor: root.hasError ? Theme.error : Theme.outlineMedium + isError: root.hasError } DButton { diff --git a/quickshell/Modules/Greetd/GreeterContent.qml b/quickshell/Modules/Greetd/GreeterContent.qml index f5ebec661..77b652797 100644 --- a/quickshell/Modules/Greetd/GreeterContent.qml +++ b/quickshell/Modules/Greetd/GreeterContent.qml @@ -353,7 +353,6 @@ Item { GreeterState.username = ""; GreeterState.usernameInput = ""; GreeterState.selectedUserIndex = -1; - inputField.text = ""; root.applyPickerPreviewTheme(); Qt.callLater(() => inputField.forceActiveFocus()); } @@ -365,7 +364,6 @@ Item { root.userListOpen = true; GreeterState.username = ""; GreeterState.usernameInput = ""; - inputField.text = ""; root.applyPickerPreviewTheme(); } @@ -387,7 +385,7 @@ Item { Greetd.cancelSession(); const previousUser = GreeterState.username; GreeterState.reset(); - inputField.text = ""; + inputField.passwordVisible = false; PortalService.profileImage = ""; if (previousUser) root.pickerThemeUsername = previousUser; @@ -438,7 +436,6 @@ Item { // Some PAM stacks expect an explicit empty response to advance U2F/fprint or fail normally. Greetd.respond(GreeterState.passwordBuffer || ""); GreeterState.passwordBuffer = ""; - inputField.text = ""; return true; } @@ -911,16 +908,14 @@ Item { } Rectangle { - property bool showPassword: false - Layout.fillWidth: true - Layout.preferredHeight: root.showUserPicker && root.userListOpen ? Math.max(60, userPicker.implicitHeight + Theme.spacingM * 2) : 60 - + Layout.preferredHeight: root.userListOpen ? Math.max(Theme.buttonHeightM, userPicker.implicitHeight + Theme.spacingM * 2) : Theme.buttonHeightM + visible: root.showUserPicker clip: true - radius: Theme.cornerRadius - color: Theme.withAlpha(Theme.cardSurface, 0.9) - border.color: inputField.activeFocus ? Theme.primary : Theme.outlineMedium - border.width: inputField.activeFocus ? Theme.outlineWidthFocused : Theme.layerOutlineWidth + radius: Theme.cornerRadiusXL + color: Theme.surfaceContainerHigh + border.color: Theme.outlineMedium + border.width: Theme.layerOutlineWidth GreeterUserPicker { id: userPicker @@ -931,7 +926,6 @@ Item { anchors.top: root.userListOpen ? parent.top : undefined anchors.margins: Theme.spacingM maxExpandedHeight: root.userPickerMaxHeight - visible: root.showUserPicker && !GreeterState.showPasswordInput expanded: root.userListOpen autoLoginVisible: root.autoLoginAvailable autoLoginChecked: root.autoLoginOnSuccess @@ -942,248 +936,95 @@ Item { onManualEntryRequested: root.enterManualUsernameEntry() } - DIcon { - id: lockIcon - - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - name: GreeterState.showPasswordInput ? "lock" : "person" - size: 20 - color: inputField.activeFocus ? Theme.primary : Theme.surfaceVariantText - visible: !root.showUserPicker + Behavior on Layout.preferredHeight { + NumberAnimation { + duration: Theme.mediumDuration + easing.type: Theme.standardEasing + } } + } - TextInput { - id: inputField + DTextField { + id: inputField - property bool syncingFromState: false + readonly property bool inputIdle: (Greetd.state === GreetdState.Inactive || root.awaitingExternalAuth || root.pendingPasswordResponse) && !GreeterState.unlocking - anchors.fill: parent - anchors.leftMargin: lockIcon.width + Theme.spacingM * 2 - anchors.rightMargin: { - let margin = Theme.spacingM; - if (GreeterState.showPasswordInput && revealButton.visible) { - margin += revealButton.width; - } - if (externalAuthButton.visible) { - margin += externalAuthButton.width; - } - if (virtualKeyboardButton.visible) { - margin += virtualKeyboardButton.width; - } - if (enterButton.visible) { - margin += enterButton.width + 2; - } - return margin; - } - enabled: !root.showUserPicker || GreeterState.showPasswordInput - opacity: 0 - focus: !root.showUserPicker || GreeterState.showPasswordInput - echoMode: GreeterState.showPasswordInput ? (parent.showPassword ? TextInput.Normal : TextInput.Password) : TextInput.Normal - onTextChanged: { - if (syncingFromState) - return; - if (GreeterState.showPasswordInput) { - GreeterState.passwordBuffer = text; - if (!text || text.length === 0) - root.passwordSubmitRequested = false; - } else { - GreeterState.usernameInput = text; - } - } - onAccepted: { - if (GreeterState.showPasswordInput) { - root.startAuthSession(true); - } else { - if (text.trim()) { - root.submitUsername(text); - syncingFromState = true; - text = ""; - syncingFromState = false; - } - } + Layout.fillWidth: true + visible: !root.showUserPicker + focus: visible + expressive: true + morph: GreeterState.showPasswordInput + busy: !inputIdle + isError: GreeterState.pamState !== "" + leftIconName: GreeterState.showPasswordInput ? "lock" : "person" + labelText: { + if (GreeterState.unlocking) + return I18n.tr("Logging in..."); + if (!inputIdle) + return I18n.tr("Authenticating..."); + if (GreeterState.showPasswordInput) + return I18n.tr("Password..."); + return I18n.tr("Username..."); + } + placeholderColor: inputIdle ? Theme.outline : Theme.primary + showPasswordToggle: GreeterState.showPasswordInput && inputIdle + echoMode: GreeterState.showPasswordInput ? TextInput.Password : TextInput.Normal + showAcceptButton: inputIdle + acceptButtonName: I18n.tr("Login") + text: GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput + trailingContent: Row { + DActionButton { + iconName: root.greeterPamHasFprint ? "fingerprint" : "key" + tooltipText: root.greeterPamHasFprint ? I18n.tr("Fingerprint") : I18n.tr("Security key") + buttonSize: inputField.accessorySize + visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && inputField.inputIdle + onClicked: root.startAuthSession(false) } - Component.onCompleted: { - syncingFromState = true; - text = GreeterState.showPasswordInput ? GreeterState.passwordBuffer : GreeterState.usernameInput; - syncingFromState = false; - if (isPrimaryScreen && !powerMenu.isVisible) - forceActiveFocus(); + DActionButton { + iconName: "keyboard" + Accessible.name: I18n.tr("Keyboard") + buttonSize: inputField.accessorySize + visible: inputField.inputIdle + onClicked: { + if (keyboard_controller.isKeyboardActive) + keyboard_controller.hide(); + else + keyboard_controller.show(); + } } - onVisibleChanged: { - if (visible && isPrimaryScreen && !powerMenu.isVisible) - forceActiveFocus(); + } + onTextEdited: { + if (!GreeterState.showPasswordInput) { + GreeterState.usernameInput = text; + return; } + GreeterState.passwordBuffer = text; + if (text.length === 0) + root.passwordSubmitRequested = false; + } + onAccepted: { + if (GreeterState.showPasswordInput) { + root.startAuthSession(true); + return; + } + if (text.trim()) + root.submitUsername(text); + } + Component.onCompleted: { + if (isPrimaryScreen && !powerMenu.isVisible) + forceActiveFocus(); + } + onVisibleChanged: { + if (visible && isPrimaryScreen && !powerMenu.isVisible) + forceActiveFocus(); } KeyboardController { id: keyboard_controller target: inputField rootObject: root - } - - StyledText { - id: placeholder - - anchors.left: lockIcon.right - anchors.leftMargin: Theme.spacingM - anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))) - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - text: { - if (GreeterState.unlocking) { - return I18n.tr("Logging in..."); - } - if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) { - return I18n.tr("Authenticating..."); - } - if (GreeterState.showPasswordInput) { - return I18n.tr("Password..."); - } - if (root.showUserPicker) { - return ""; - } - return I18n.tr("Username..."); - } - color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline - font.pixelSize: Theme.fontSizeMedium - opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length === 0 : (root.showUserPicker ? false : GreeterState.usernameInput.length === 0)) ? 1 : 0 - - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.standardEasing - } - } - - Behavior on color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - } - - StyledText { - anchors.left: lockIcon.right - anchors.leftMargin: Theme.spacingM - anchors.right: (GreeterState.showPasswordInput && revealButton.visible ? revealButton.left : (externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)))) - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - text: { - if (GreeterState.showPasswordInput) { - if (parent.showPassword) { - return GreeterState.passwordBuffer; - } - return "•".repeat(GreeterState.passwordBuffer.length); - } - return GreeterState.usernameInput; - } - color: Theme.surfaceText - font.pixelSize: (GreeterState.showPasswordInput && !parent.showPassword) ? Theme.fontSizeLarge : Theme.fontSizeMedium - opacity: (GreeterState.showPasswordInput ? GreeterState.passwordBuffer.length > 0 : (root.showUserPicker ? false : GreeterState.usernameInput.length > 0)) ? 1 : 0 - clip: true - elide: Text.ElideNone - horizontalAlignment: implicitWidth > width ? Text.AlignRight : Text.AlignLeft - - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.standardEasing - } - } - } - - DActionButton { - id: revealButton - - anchors.right: externalAuthButton.visible ? externalAuthButton.left : (virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right)) - anchors.rightMargin: 0 - anchors.verticalCenter: parent.verticalCenter - iconName: parent.showPassword ? "visibility_off" : "visibility" - Accessible.name: parent.showPassword ? I18n.tr("Hide password") : I18n.tr("Show password") - buttonSize: 32 - visible: GreeterState.showPasswordInput && GreeterState.passwordBuffer.length > 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking - enabled: visible - onClicked: parent.showPassword = !parent.showPassword - } - DActionButton { - id: externalAuthButton - - anchors.right: virtualKeyboardButton.visible ? virtualKeyboardButton.left : (enterButton.visible ? enterButton.left : parent.right) - anchors.rightMargin: 0 - anchors.verticalCenter: parent.verticalCenter - iconName: root.greeterPamHasFprint ? "fingerprint" : "key" - tooltipText: root.greeterPamHasFprint ? I18n.tr("Fingerprint") : I18n.tr("Security key") - buttonSize: 32 - visible: GreeterState.showPasswordInput && root.greeterExternalAuthAvailable && GreeterState.passwordBuffer.length === 0 && (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking - enabled: visible - onClicked: root.startAuthSession(false) - } - DActionButton { - id: virtualKeyboardButton - - anchors.right: enterButton.visible ? enterButton.left : parent.right - anchors.rightMargin: enterButton.visible ? 0 : Theme.spacingS - anchors.verticalCenter: parent.verticalCenter - iconName: "keyboard" - Accessible.name: I18n.tr("Keyboard") - buttonSize: 32 - visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking && (!root.showUserPicker || GreeterState.showPasswordInput) - enabled: visible - onClicked: { - if (keyboard_controller.isKeyboardActive) { - keyboard_controller.hide(); - } else { - keyboard_controller.show(); - } - } - } - - DActionButton { - id: enterButton - - anchors.right: parent.right - anchors.rightMargin: 2 - anchors.verticalCenter: parent.verticalCenter - iconName: "keyboard_return" - Accessible.name: I18n.tr("Login") - buttonSize: 36 - visible: (Greetd.state === GreetdState.Inactive || awaitingExternalAuth || pendingPasswordResponse) && !GreeterState.unlocking && (!root.showUserPicker || GreeterState.showPasswordInput) - enabled: true - onClicked: { - if (GreeterState.showPasswordInput) { - root.startAuthSession(true); - } else { - if (inputField.text.trim()) { - root.submitUsername(inputField.text); - inputField.text = ""; - } - } - } - - Behavior on opacity { - NumberAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - } - - Behavior on border.color { - ColorAnimation { - duration: Theme.shortDuration - easing.type: Theme.standardEasing - } - } - - Behavior on Layout.preferredHeight { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.standardEasing - } + expressive: true } } } @@ -1569,8 +1410,8 @@ Item { passwordFailureCount = passwordFailureCount + 1; } authFeedbackMessage = currentAuthMessage(); + inputField.shake(); GreeterState.passwordBuffer = ""; - inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } @@ -1585,8 +1426,8 @@ Item { GreeterState.unlocking = false; GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); + inputField.shake(); GreeterState.passwordBuffer = ""; - inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } @@ -1622,7 +1463,6 @@ Item { GreeterState.pamState = "error"; authFeedbackMessage = currentAuthMessage(); GreeterState.passwordBuffer = ""; - inputField.text = ""; placeholderDelay.restart(); Greetd.cancelSession(); } diff --git a/quickshell/Modules/Settings/CreateUserTab.qml b/quickshell/Modules/Settings/CreateUserTab.qml index 7ba5f339a..edcbd922d 100644 --- a/quickshell/Modules/Settings/CreateUserTab.qml +++ b/quickshell/Modules/Settings/CreateUserTab.qml @@ -72,7 +72,7 @@ Item { DTextField { id: usernameField - outlined: true + expressive: true leftIconName: "person" labelText: I18n.tr("Username") width: parent.width @@ -113,7 +113,7 @@ Item { DTextField { id: passwordField - outlined: true + expressive: true leftIconName: "lock" labelText: I18n.tr("Password") width: parent.width @@ -131,7 +131,7 @@ Item { DTextField { id: confirmField - outlined: true + expressive: true leftIconName: "lock" labelText: I18n.tr("Confirm password") width: parent.width diff --git a/quickshell/Modules/Settings/NetworkWifiTab.qml b/quickshell/Modules/Settings/NetworkWifiTab.qml index 5656573e0..80f8729db 100644 --- a/quickshell/Modules/Settings/NetworkWifiTab.qml +++ b/quickshell/Modules/Settings/NetworkWifiTab.qml @@ -852,7 +852,7 @@ Item { SettingsRow { visible: hotspotCard.showForm body: DTextField { - outlined: true + expressive: true width: parent.width labelText: I18n.tr("Hotspot name", "hotspot SSID field label") placeholderText: I18n.tr("SSID", "hotspot network name placeholder") @@ -867,7 +867,7 @@ Item { SettingsRow { visible: hotspotCard.showForm body: DTextField { - outlined: true + expressive: true width: parent.width labelText: I18n.tr("Password", "hotspot password field label") placeholderText: I18n.tr("Optional; leave blank for open hotspot", "hotspot password field placeholder")