diff --git a/Modules/Settings/Common/DialogRowButton.qml b/Modules/Settings/Common/DialogRowButton.qml index 96ada26..805682c 100644 --- a/Modules/Settings/Common/DialogRowButton.qml +++ b/Modules/Settings/Common/DialogRowButton.qml @@ -15,14 +15,17 @@ Item { required property Component content required property string header property int horizontalContentMargin - required property string icon + property string icon required property string label + property alias iconLabel: openButton.iconLabel + property alias rowButton: openButton property string subtext: "" property bool open property real openHeight: Math.min(rootParent.height * 0.8, 600) property real openWidth: Math.min(rootParent.width * 0.8, 400) required property Item rootParent property bool separateContent + required property string settingAnchor property bool first: false property bool last: false @@ -57,6 +60,7 @@ Item { anchors.fill: parent enabled: false hoverEnabled: enabled + preventStealing: true parent: root.open ? root.rootParent : root onClicked: root.open = false @@ -77,7 +81,8 @@ Item { backdrop.enabled: true dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased - dialogBg.radius: Tokens.rounding.largeIncreased + dialogBg.topRightRadius: Tokens.rounding.largeIncreased + dialogBg.topLeftRadius: Tokens.rounding.largeIncreased dialogContent.opacity: 1 dialogWrapper.height: root.openHeight dialogWrapper.width: root.openWidth @@ -105,7 +110,7 @@ Item { } Anim { - properties: "opacity,radius,bottomLeftRadius,bottomRightRadius" + properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius" type: Anim.DefaultEffects } @@ -172,6 +177,7 @@ Item { sourceComponent: MouseArea { onWheel: event => event.accepted = true + preventStealing: true ColumnLayout { anchors.bottomMargin: Tokens.padding.largeIncreased @@ -224,6 +230,7 @@ Item { text: qsTr("Cancel") type: TextButton.Text verticalPadding: Tokens.padding.small + stateLayer.preventStealing: true onClicked: { root.cancelled(); @@ -237,6 +244,7 @@ Item { isRound: true text: root.acceptLabel type: TextButton.Text + stateLayer.preventStealing: true verticalPadding: Tokens.padding.small onClicked: { diff --git a/Modules/Settings/Common/DialogSelectButton.qml b/Modules/Settings/Common/DialogSelectButton.qml index 87c93bd..d8cb270 100644 --- a/Modules/Settings/Common/DialogSelectButton.qml +++ b/Modules/Settings/Common/DialogSelectButton.qml @@ -93,10 +93,13 @@ DialogRowButton { onOpenChanged: { if (open) { + root.rootParent.interactive = false; if (!initialSelect) selectedItem = null; else selectedItem = initialSelect; + } else { + root.rootParent.interactive = true; } } } diff --git a/Modules/Settings/Common/RowButton.qml b/Modules/Settings/Common/RowButton.qml index 5d2953d..50d1c3d 100644 --- a/Modules/Settings/Common/RowButton.qml +++ b/Modules/Settings/Common/RowButton.qml @@ -17,6 +17,7 @@ ConnectedRect { property alias subtext: subLabel.text property alias text: label.text property string trailingIcon + property string activeItem signal clicked(event: MouseEvent) @@ -40,15 +41,16 @@ ConnectedRect { spacing: Tokens.spacing.medium Behavior on opacity { - Anim { - } + Anim {} } MaterialIcon { id: iconLabel color: Colors.palette.m3onSurfaceVariant + visible: root.icon !== "" fill: 1 + text: root.icon font.pointSize: Tokens.font.size.normal } @@ -79,6 +81,26 @@ ConnectedRect { } } + Loader { + active: root.activeItem && root.subtext + asynchronous: true + visible: active + Layout.fillHeight: true + + sourceComponent: CustomRect { + color: Colors.palette.m3secondaryContainer + radius: Tokens.rounding.full + implicitWidth: text.implicitWidth + Tokens.padding.medium * 2 + + CustomText { + id: text + anchors.centerIn: parent + color: Colors.palette.m3onSecondaryContainer + text: root.activeItem + } + } + } + Loader { active: root.trailingIcon asynchronous: true diff --git a/Modules/Settings/Common/SearchPopup.qml b/Modules/Settings/Common/SearchPopup.qml new file mode 100644 index 0000000..193fd1a --- /dev/null +++ b/Modules/Settings/Common/SearchPopup.qml @@ -0,0 +1,128 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import QtQuick +import QtQuick.Layouts +import ZShell.Config +import qs.Components +import qs.Services + +DialogRowButton { + id: root + + required property var model + property var selectedItem + property var initialSelect + + acceptAllowed: !!selectedItem + horizontalContentMargin: -Tokens.padding.extraSmall + separateContent: true + + content: Component { + ColumnLayout { + VerticalFadeListView { + bottomMargin: Tokens.padding.large + Layout.fillHeight: true + Layout.fillWidth: true + model: ScriptModel { + values: { + const s = searchField.text; + var regex = new RegExp(s, "i"); + + return root.model.filter(n => regex.test(n)); + } + } + spacing: 0 + topMargin: Tokens.padding.large + + delegate: CustomRect { + id: item + + required property var modelData + readonly property bool selected: root.selectedItem === modelData + + anchors.left: ListView.view.contentItem.left + anchors.margins: 1 // Gets cut off for some reason without this + anchors.right: ListView.view.contentItem.right + color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0) + implicitHeight: label.implicitHeight + Tokens.padding.small * 2 + radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium + + Behavior on radius { + Anim { + type: Anim.SlowEffects + } + } + + StateLayer { + id: stateLayer + + onClicked: root.selectedItem = item.modelData + } + + CustomText { + id: label + + anchors.left: parent.left + anchors.margins: Tokens.padding.large + anchors.right: item.selected ? checkIcon.left : parent.right + anchors.rightMargin: item.selected ? Tokens.spacing.medium : anchors.margins + anchors.verticalCenter: parent.verticalCenter + color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface + elide: Text.ElideRight + font.pointSize: Tokens.font.size.smaller + text: item.modelData + } + + MaterialIcon { + id: checkIcon + + anchors.margins: Tokens.padding.large + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + color: Colors.palette.m3onTertiaryContainer + font.pointSize: Tokens.font.size.normal + opacity: item.selected ? 1 : 0 + text: "check" + + Behavior on opacity { + Anim { + type: Anim.SlowEffects + } + } + } + } + } + + SearchBar { + id: searchField + + Layout.fillWidth: true + bg.border.color: Colors.palette.m3outlineVariant + bg.color: Colors.tPalette.m3surfaceContainerLowest + clearIcon.font.pointSize: Tokens.font.size.large + clearIcon.padding: Tokens.padding.extraSmall + font.pointSize: Tokens.font.size.small + placeholderText: qsTr("Search...") + searchIcon.anchors.leftMargin: Tokens.padding.large + searchIcon.font.pointSize: Tokens.font.size.large + + Behavior on bg.border.color { + CAnim {} + } + } + } + } + + onOpenChanged: { + if (open) { + root.rootParent.interactive = false; + if (!initialSelect) + selectedItem = null; + else + selectedItem = initialSelect; + } else { + root.rootParent.interactive = true; + } + } +} diff --git a/Modules/Settings/Common/TimeDialogRow.qml b/Modules/Settings/Common/TimeDialogRow.qml new file mode 100644 index 0000000..25bc7cb --- /dev/null +++ b/Modules/Settings/Common/TimeDialogRow.qml @@ -0,0 +1,249 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import ZShell.Blobs +import qs.Components +import ZShell.Config +import qs.Services + +Item { + id: root + + property bool acceptAllowed: true + required property string acceptLabel + required property Component content + required property string header + property int horizontalContentMargin + property string icon + required property string label + property alias iconLabel: openButton.iconText + property alias rowButton: openButton + property alias contentItem: dialogContent.item + property string subtext: "" + property bool open + property real openHeight: Math.min(rootParent.height * 0.8, 400) + property real openWidth: Math.min(rootParent.width * 0.8, 600) + required property Item rootParent + property bool first: false + property bool last: false + required property string settingAnchor + property color openColor: Colors.palette.m3surfaceContainerHighest + + signal accepted + signal cancelled + + function reparentWrapper(): void { + const newParent = open ? rootParent : root; + const pos = dialogWrapper.mapToItem(newParent, 0, 0); + dialogWrapper.parent = newParent; + dialogWrapper.x = pos.x; + dialogWrapper.y = pos.y; + } + + Layout.fillWidth: true + implicitHeight: openButton.implicitHeight + z: open || dialogTransition.running ? 2 : 0 + + BlobGroup { + id: blobGroup + + color: root.open ? root.openColor : Colors.tPalette.m3surfaceContainer + + Behavior on color { + CAnim {} + } + } + + MouseArea { + id: backdrop + + anchors.fill: parent + enabled: false + hoverEnabled: enabled + preventStealing: true + parent: root.open ? root.rootParent : root + + onClicked: root.open = false + } + + Item { + id: dialogWrapper + + height: openButton.implicitHeight + width: root.width + z: 1 + + states: State { + name: "open" + when: root.open + + PropertyChanges { + backdrop.enabled: true + dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased + dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased + dialogBg.topRightRadius: Tokens.rounding.largeIncreased + dialogBg.topLeftRadius: Tokens.rounding.largeIncreased + dialogContent.opacity: 1 + dialogWrapper.height: root.openHeight + dialogWrapper.width: root.openWidth + dialogWrapper.x: (root.rootParent.width - root.openWidth) / 2 + dialogWrapper.y: (root.rootParent.height - root.openHeight) / 2 + elevation.opacity: 1 + openButton.opacity: 0 + } + } + transitions: Transition { + id: dialogTransition + + SequentialAnimation { + ScriptAction { + script: root.reparentWrapper() + } + + Anim { + properties: "x,y" + } + } + + PropertyAction { + property: "enabled" + } + + Anim { + properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius" + type: Anim.DefaultEffects + } + + Anim { + properties: "width,height" + } + } + + Elevation { + id: elevation + + anchors.fill: parent + bottomLeftRadius: dialogBg.bottomLeftRadius + bottomRightRadius: dialogBg.bottomRightRadius + level: 4 + opacity: 0 + radius: dialogBg.radius + + transform: Matrix4x4 { + matrix: dialogBg.deformMatrix + } + } + + BlobRect { + id: dialogBg + + anchors.fill: parent + bottomLeftRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall + bottomRightRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall + topRightRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall + topLeftRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall + deformScale: 0 + group: blobGroup + opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5) + } + + TimeDialogToggle { + id: openButton + + anchors.left: parent.left + anchors.right: parent.right + color: "transparent" + height: Math.min(implicitHeight, parent.height) + subtext: root.subtext + last: root.last + first: root.first + text: root.label + + transform: Matrix4x4 { + matrix: dialogBg.deformMatrix + } + + onClicked: root.open = true + } + + Loader { + id: dialogContent + + active: opacity > 0 + anchors.fill: parent + asynchronous: false + opacity: 0 + + sourceComponent: MouseArea { + onWheel: event => event.accepted = true + preventStealing: true + acceptedButtons: Qt.NoButton + cursorShape: undefined + enabled: dialogContent.opacity > 0 + clip: true + implicitWidth: innerLayout.implicitWidth + innerLayout.anchors.margins * 2 + implicitHeight: innerLayout.implicitHeight + innerLayout.anchors.margins + innerLayout.anchors.bottomMargin + + ColumnLayout { + id: innerLayout + anchors.bottomMargin: Tokens.padding.largeIncreased + anchors.fill: parent + anchors.margins: Tokens.padding.extraLarge + spacing: 0 + + CustomText { + font.pointSize: Tokens.font.size.large + text: root.header + } + + Loader { + Layout.fillWidth: true + Layout.leftMargin: root.horizontalContentMargin + Layout.topMargin: Tokens.spacing.medium + Layout.bottomMargin: Tokens.spacing.medium + Layout.rightMargin: root.horizontalContentMargin + sourceComponent: root.content + } + + RowLayout { + Layout.alignment: Qt.AlignRight + spacing: Tokens.spacing.extraSmall + + TextButton { + horizontalPadding: Tokens.padding.largeIncreased + isRound: true + text: qsTr("Cancel") + type: TextButton.Text + verticalPadding: Tokens.padding.small + stateLayer.preventStealing: true + + onClicked: { + root.cancelled(); + root.open = false; + } + } + + TextButton { + enabled: root.acceptAllowed + horizontalPadding: Tokens.padding.largeIncreased + isRound: true + text: root.acceptLabel + type: TextButton.Text + stateLayer.preventStealing: true + verticalPadding: Tokens.padding.small + + onClicked: { + root.accepted(); + root.open = false; + } + } + } + } + } + transform: Matrix4x4 { + matrix: dialogBg.deformMatrix + } + } + } +} diff --git a/Modules/Settings/Common/TimeDialogSelect.qml b/Modules/Settings/Common/TimeDialogSelect.qml new file mode 100644 index 0000000..33967dc --- /dev/null +++ b/Modules/Settings/Common/TimeDialogSelect.qml @@ -0,0 +1,50 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import ZShell.Config +import qs.Helpers +import qs.Components +import qs.Services + +TimeDialogRow { + id: root + + required property int start + required property int end + property int endCurrent + property int startCurrent + openColor: Colors.palette.m3surfaceContainer + openHeight: Math.min(rootParent.height * 0.8, contentItem?.implicitHeight ?? 0) + openWidth: Math.min(rootParent.width * 0.8, contentItem?.implicitWidth ?? 0) + + function keyFor(item: var): string { + return item.id; + } + + function labelFor(item: var): string { + return item.label; + } + + horizontalContentMargin: -Tokens.padding.extraSmall + + content: Component { + TimeInput { + id: timeContent + start: root.start + end: root.end + + onEndEdit: value => root.endCurrent = value + onStartEdit: value => root.startCurrent = value + } + } + + onOpenChanged: { + if (open) { + root.rootParent.interactive = false; + root.z = 5; + } else { + root.rootParent.interactive = true; + root.z = 1; + } + } +} diff --git a/Modules/Settings/Common/OverlayRow.qml b/Modules/Settings/Common/TimeDialogToggle.qml similarity index 86% rename from Modules/Settings/Common/OverlayRow.qml rename to Modules/Settings/Common/TimeDialogToggle.qml index f7a6dae..0b540fa 100644 --- a/Modules/Settings/Common/OverlayRow.qml +++ b/Modules/Settings/Common/TimeDialogToggle.qml @@ -10,12 +10,14 @@ ConnectedRect { property alias checked: switchButton.checked property int horizontalPadding: Tokens.padding.largeIncreased - required property Component popup property alias subtext: subtext.text property alias text: text.text + property alias icon: icon + property string iconText: "open_in_new" property int verticalPadding: Tokens.padding.small - signal clicked(checked: bool) + signal clicked + signal toggled(checked: bool) Layout.fillWidth: true implicitHeight: layout.implicitHeight + verticalPadding * 2 @@ -50,11 +52,11 @@ ConnectedRect { anchors.right: switchButton.left anchors.rightMargin: Tokens.spacing.medium anchors.verticalCenter: parent.verticalCenter - text: "open_in_new" + text: root.iconText } StateLayer { - onClicked: PopupManager.requestOpen(root.popup) + onClicked: root.clicked() } CustomSwitch { @@ -64,6 +66,6 @@ ConnectedRect { anchors.rightMargin: root.horizontalPadding anchors.verticalCenter: parent.verticalCenter - onClicked: root.clicked(checked) + onClicked: root.toggled(checked) } } diff --git a/Modules/Settings/Common/TimeInput.qml b/Modules/Settings/Common/TimeInput.qml index 05e8052..c8bfd2b 100644 --- a/Modules/Settings/Common/TimeInput.qml +++ b/Modules/Settings/Common/TimeInput.qml @@ -6,15 +6,13 @@ import qs.Components import qs.Helpers import qs.Services -CustomClippingRect { +ColumnLayout { id: root - required property var object - required property list settings - property bool shouldBeActive: true - - signal applySettings(startTime: int, endTime: int) - signal close + required property int start + required property int end + signal endEdit(time: int) + signal startEdit(time: int) function convertHour(timeValue: int): int { return Math.floor(timeValue / 60); @@ -28,555 +26,459 @@ CustomClippingRect { return hour * 60 + minute; } - color: Colors.palette.m3surfaceContainer - implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin - implicitWidth: column.implicitWidth + column.anchors.margins * 2 - radius: Tokens.rounding.largeIncreased + spacing: Tokens.spacing.medium - ColumnLayout { - id: column + RowLayout { + CustomRect { + id: startHourRect - anchors.left: parent.left - anchors.margins: Tokens.padding.largeIncreased - anchors.right: parent.right - anchors.top: parent.top - spacing: Tokens.spacing.medium + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Tokens.rounding.small + + CustomRect { + anchors.fill: parent + border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest + border.width: startHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim {} + } + } + + TextFieldBase { + id: startHourField + + function setConfigText(): string { + var val = root.convertHour(root.start); + if (val === 0) { + return "00"; + } + return String(val); + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + clip: true + color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface + font.family: "Roboto" + font.letterSpacing: -0.25 + font.pixelSize: 56 + font.weight: 400 + horizontalAlignment: TextInput.AlignHCenter + text: setConfigText() + verticalAlignment: TextInput.AlignVCenter + + Keys.onPressed: event => { + if (event.key === Qt.Key_Backspace) { + event.accepted = true; + if (startHourField.text.length >= 2) { + startHourField.text = "0" + startHourField.text[0]; + } else if (startHourField.text.length === 1) { + startHourField.text = "0"; + } + return; + } else if (event.key === Qt.Key_Escape) { + event.accepted = true; + startHourField.text = setConfigText(); + startHourField.focus = false; + } else if (event.key === Qt.Key_Return) { + startHourField.focus = false; + return; + } else if (event.key === Qt.Key_Tab) { + startMinuteField.focus = true; + } else if (event.key === Qt.Key_Backtab) { + endMinuteField.focus = true; + } + + if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { + event.accepted = true; + var digit = event.text; + var textLen = startHourField.text.length; + + if (textLen >= 2 && startHourField.text[0] !== '0') { + return; + } + + var val = 0; + if (textLen === 0) { + val = parseInt(digit); + } else if (textLen === 1) { + val = parseInt(startHourField.text + digit); + } else { + val = parseInt(startHourField.text[1] + digit); + } + + val = Math.max(0, Math.min(23, val)); + + if (textLen >= 2 && val < 10) { + startHourField.text = "0" + val; + } else { + startHourField.text = val.toString(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onTextChanged: { + var mins = parseInt(startMinuteField.text); + const hours = root.convertToMinutes(parseInt(startHourField.text)); + root.startEdit(hours + mins); + } + } + } CustomText { - text: qsTr("Select time") + id: startSeparator + + font.pointSize: Tokens.font.size.extraLarge + text: ":" } - RowLayout { - CustomRect { - id: startHourRect + CustomRect { + id: startMinuteRect - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Tokens.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest - border.width: startHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - TextFieldBase { - id: startHourField - - function setConfigText(setting: string): string { - var val = root.convertHour(root.object[setting]); - if (val === 0) { - return "00"; - } - return String(val); - } - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - clip: true - color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface - font.family: "Roboto" - font.letterSpacing: -0.25 - font.pixelSize: 56 - font.weight: 400 - horizontalAlignment: TextInput.AlignHCenter - text: setConfigText(root.settings[1]) - verticalAlignment: TextInput.AlignVCenter - - Keys.onPressed: event => { - if (event.key === Qt.Key_Backspace) { - event.accepted = true; - if (startHourField.text.length >= 2) { - startHourField.text = "0" + startHourField.text[0]; - } else if (startHourField.text.length === 1) { - startHourField.text = "0"; - } - return; - } else if (event.key === Qt.Key_Escape) { - event.accepted = true; - startHourField.text = setConfigText(root.settings[1]); - startHourField.focus = false; - } else if (event.key === Qt.Key_Return) { - startHourField.focus = false; - return; - } else if (event.key === Qt.Key_Tab) { - startMinuteField.focus = true; - } else if (event.key === Qt.Key_Backtab) { - endMinuteField.focus = true; - } - - if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { - event.accepted = true; - var digit = event.text; - var textLen = startHourField.text.length; - - if (textLen >= 2 && startHourField.text[0] !== '0') { - return; - } - - var val = 0; - if (textLen === 0) { - val = parseInt(digit); - } else if (textLen === 1) { - val = parseInt(startHourField.text + digit); - } else { - val = parseInt(startHourField.text[1] + digit); - } - - val = Math.max(0, Math.min(23, val)); - - if (textLen >= 2 && val < 10) { - startHourField.text = "0" + val; - } else { - startHourField.text = val.toString(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onTextEdited: { - if (startHourField.text === "") - return; - var val = parseInt(startHourField.text); - if (isNaN(val)) - return; - val = Math.max(0, Math.min(23, val)); - var newText = val.toString(); - if (newText !== startHourField.text) - startHourField.text = newText; - } - } - } - - CustomText { - id: startSeparator - - font.pointSize: Tokens.font.size.extraLarge - text: ":" - } + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Tokens.rounding.small CustomRect { - id: startMinuteRect + anchors.fill: parent + border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest + border.width: startMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Tokens.rounding.small + Behavior on border.width { + Anim {} + } + } - CustomRect { - anchors.fill: parent - border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest - border.width: startMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width + TextFieldBase { + id: startMinuteField - Behavior on border.width { - Anim { - } + function setConfigText(): string { + var val = root.convertMinute(root.start); + if (val === 0) { + return "00"; } + return String(val); } - TextFieldBase { - id: startMinuteField - - function setConfigText(setting: string): string { - var val = root.convertMinute(root.object[setting]); - if (val === 0) { - return "00"; - } - return String(val); - } - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - clip: true - color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface - font.family: "Roboto" - font.letterSpacing: -0.25 - font.pixelSize: 56 - font.weight: 400 - horizontalAlignment: TextInput.AlignHCenter - text: setConfigText(root.settings[1]) - verticalAlignment: TextInput.AlignVCenter - - Keys.onPressed: event => { - if (event.key === Qt.Key_Backspace) { - event.accepted = true; - if (startMinuteField.text.length >= 2) { - startMinuteField.text = "0" + startMinuteField.text[0]; - } else if (startMinuteField.text.length === 1) { - startMinuteField.text = "0"; - } - return; - } else if (event.key === Qt.Key_Escape) { - event.accepted = true; - startMinuteField.text = setConfigText(root.settings[1]); - startMinuteField.focus = false; - } else if (event.key === Qt.Key_Return) { - startMinuteField.focus = false; - return; - } else if (event.key === Qt.Key_Tab) { - endHourField.focus = true; - } else if (event.key === Qt.Key_Backtab) { - startHourField.focus = true; - } - - if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { - event.accepted = true; - var digit = event.text; - var textLen = startMinuteField.text.length; - - if (textLen >= 2 && startMinuteField.text[0] !== '0') { - return; - } - - var val = 0; - if (textLen === 0) { - val = parseInt(digit); - } else if (textLen === 1) { - val = parseInt(startMinuteField.text + digit); - } else { - val = parseInt(startMinuteField.text[1] + digit); - } - - val = Math.max(0, Math.min(59, val)); - - if (textLen >= 2 && val < 10) { - startMinuteField.text = "0" + val; - } else { - startMinuteField.text = val.toString(); - } - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + clip: true + color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface + font.family: "Roboto" + font.letterSpacing: -0.25 + font.pixelSize: 56 + font.weight: 400 + horizontalAlignment: TextInput.AlignHCenter + text: setConfigText() + verticalAlignment: TextInput.AlignVCenter + Keys.onPressed: event => { + if (event.key === Qt.Key_Backspace) { event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onTextEdited: { - if (startMinuteField.text === "") - return; - var val = parseInt(startMinuteField.text); - if (isNaN(val)) - return; - val = Math.max(0, Math.min(23, val)); - var newText = val.toString(); - if (newText !== startMinuteField.text) - startMinuteField.text = newText; - } - } - } - - Item { - id: spacer - - Layout.preferredWidth: Tokens.spacing.largeIncreased - } - - CustomRect { - id: endHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Tokens.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest - border.width: endHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { + if (startMinuteField.text.length >= 2) { + startMinuteField.text = "0" + startMinuteField.text[0]; + } else if (startMinuteField.text.length === 1) { + startMinuteField.text = "0"; } - } - } - - TextFieldBase { - id: endHourField - - function setConfigText(setting: string): string { - var val = root.convertHour(root.object[setting]); - if (val === 0) { - return "00"; - } - return String(val); - } - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - clip: true - color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface - font.family: "Roboto" - font.letterSpacing: -0.25 - font.pixelSize: 56 - font.weight: 400 - horizontalAlignment: TextInput.AlignHCenter - text: setConfigText(root.settings[2]) - verticalAlignment: TextInput.AlignVCenter - - Keys.onPressed: event => { - if (event.key === Qt.Key_Backspace) { - event.accepted = true; - if (endHourField.text.length >= 2) { - endHourField.text = "0" + endHourField.text[0]; - } else if (endHourField.text.length === 1) { - endHourField.text = "0"; - } - return; - } else if (event.key === Qt.Key_Escape) { - event.accepted = true; - endHourField.text = setConfigText(root.settings[2]); - endHourField.focus = false; - } else if (event.key === Qt.Key_Return) { - endHourField.focus = false; - return; - } else if (event.key === Qt.Key_Tab) { - endMinuteField.focus = true; - } else if (event.key === Qt.Key_Backtab) { - startMinuteField.focus = true; - } - - if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { - event.accepted = true; - var digit = event.text; - var textLen = endHourField.text.length; - - if (textLen >= 2 && endHourField.text[0] !== '0') { - return; - } - - var val = 0; - if (textLen === 0) { - val = parseInt(digit); - } else if (textLen === 1) { - val = parseInt(endHourField.text + digit); - } else { - val = parseInt(endHourField.text[1] + digit); - } - - val = Math.max(0, Math.min(23, val)); - - if (textLen >= 2 && val < 10) { - endHourField.text = "0" + val; - } else { - endHourField.text = val.toString(); - } - } - + return; + } else if (event.key === Qt.Key_Escape) { event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onTextEdited: { - if (endHourField.text === "") - return; - var val = parseInt(endHourField.text); - if (isNaN(val)) - return; - val = Math.max(0, Math.min(23, val)); - var newText = val.toString(); - if (newText !== endHourField.text) - endHourField.text = newText; - } - } - } - - CustomText { - id: endSeparator - - font.pointSize: Tokens.font.size.extraLarge - text: ":" - } - - CustomRect { - id: endMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Tokens.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest - border.width: endMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - TextFieldBase { - id: endMinuteField - - function setConfigText(setting: string): string { - var val = root.convertMinute(root.object[setting]); - if (val === 0) { - return "00"; - } - return String(val); + startMinuteField.text = setConfigText(); + startMinuteField.focus = false; + } else if (event.key === Qt.Key_Return) { + startMinuteField.focus = false; + return; + } else if (event.key === Qt.Key_Tab) { + endHourField.focus = true; + } else if (event.key === Qt.Key_Backtab) { + startHourField.focus = true; } - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - clip: true - color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface - font.family: "Roboto" - font.letterSpacing: -0.25 - font.pixelSize: 56 - font.weight: 400 - horizontalAlignment: TextInput.AlignHCenter - text: setConfigText(root.settings[2]) - verticalAlignment: TextInput.AlignVCenter - - Keys.onPressed: event => { - if (event.key === Qt.Key_Backspace) { - event.accepted = true; - if (endMinuteField.text.length >= 2) { - endMinuteField.text = "0" + endMinuteField.text[0]; - } else if (endMinuteField.text.length === 1) { - endMinuteField.text = "0"; - } - return; - } else if (event.key === Qt.Key_Escape) { - event.accepted = true; - endMinuteField.text = setConfigText(root.settings[2]); - endMinuteField.focus = false; - } else if (event.key === Qt.Key_Return) { - endMinuteField.focus = false; - return; - } else if (event.key === Qt.Key_Tab) { - startHourField.focus = true; - } else if (event.key === Qt.Key_Backtab) { - endHourField.focus = true; - } - - if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { - event.accepted = true; - var digit = event.text; - var textLen = endMinuteField.text.length; - - if (textLen >= 2 && endMinuteField.text[0] !== '0') { - return; - } - - var val = 0; - if (textLen === 0) { - val = parseInt(digit); - } else if (textLen === 1) { - val = parseInt(endMinuteField.text + digit); - } else { - val = parseInt(endMinuteField.text[1] + digit); - } - - val = Math.max(0, Math.min(59, val)); - - if (textLen >= 2 && val < 10) { - endMinuteField.text = "0" + val; - } else { - endMinuteField.text = val.toString(); - } - } - + if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onTextEdited: { - if (endMinuteField.text === "") + var digit = event.text; + var textLen = startMinuteField.text.length; + + if (textLen >= 2 && startMinuteField.text[0] !== '0') { return; - var val = parseInt(endMinuteField.text); - if (isNaN(val)) - return; - val = Math.max(0, Math.min(23, val)); - var newText = val.toString(); - if (newText !== endMinuteField.text) - endMinuteField.text = newText; + } + + var val = 0; + if (textLen === 0) { + val = parseInt(digit); + } else if (textLen === 1) { + val = parseInt(startMinuteField.text + digit); + } else { + val = parseInt(startMinuteField.text[1] + digit); + } + + val = Math.max(0, Math.min(59, val)); + + if (textLen >= 2 && val < 10) { + startMinuteField.text = "0" + val; + } else { + startMinuteField.text = val.toString(); + } } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onTextChanged: { + var mins = parseInt(startMinuteField.text); + const hours = root.convertToMinutes(parseInt(startHourField.text)); + root.startEdit(hours + mins); } } } - RowLayout { - CustomText { - Layout.preferredWidth: spacer.x + spacer.width - text: qsTr("Start") + Item { + id: spacer + + Layout.preferredWidth: Tokens.spacing.largeIncreased + } + + CustomRect { + id: endHourRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Tokens.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest + border.width: endHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim {} + } } - CustomText { - Layout.preferredWidth: endMinuteRect.width + endHourRect.width - text: qsTr("End") + TextFieldBase { + id: endHourField + + function setConfigText(): string { + var val = root.convertHour(root.end); + if (val === 0) { + return "00"; + } + return String(val); + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + clip: true + color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface + font.family: "Roboto" + font.letterSpacing: -0.25 + font.pixelSize: 56 + font.weight: 400 + horizontalAlignment: TextInput.AlignHCenter + text: setConfigText() + verticalAlignment: TextInput.AlignVCenter + + Keys.onPressed: event => { + if (event.key === Qt.Key_Backspace) { + event.accepted = true; + if (endHourField.text.length >= 2) { + endHourField.text = "0" + endHourField.text[0]; + } else if (endHourField.text.length === 1) { + endHourField.text = "0"; + } + return; + } else if (event.key === Qt.Key_Escape) { + event.accepted = true; + endHourField.text = setConfigText(); + endHourField.focus = false; + } else if (event.key === Qt.Key_Return) { + endHourField.focus = false; + return; + } else if (event.key === Qt.Key_Tab) { + endMinuteField.focus = true; + } else if (event.key === Qt.Key_Backtab) { + startMinuteField.focus = true; + } + + if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { + event.accepted = true; + var digit = event.text; + var textLen = endHourField.text.length; + + if (textLen >= 2 && endHourField.text[0] !== '0') { + return; + } + + var val = 0; + if (textLen === 0) { + val = parseInt(digit); + } else if (textLen === 1) { + val = parseInt(endHourField.text + digit); + } else { + val = parseInt(endHourField.text[1] + digit); + } + + val = Math.max(0, Math.min(23, val)); + + if (textLen >= 2 && val < 10) { + endHourField.text = "0" + val; + } else { + endHourField.text = val.toString(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onTextChanged: { + var mins = parseInt(endMinuteField.text); + const hours = root.convertToMinutes(parseInt(endHourField.text)); + root.endEdit(hours + mins); + } + } + } + + CustomText { + id: endSeparator + + font.pointSize: Tokens.font.size.extraLarge + text: ":" + } + + CustomRect { + id: endMinuteRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Tokens.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest + border.width: endMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim {} + } + } + + TextFieldBase { + id: endMinuteField + + function setConfigText(): string { + var val = root.convertMinute(root.end); + if (val === 0) { + return "00"; + } + return String(val); + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + clip: true + color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface + font.family: "Roboto" + font.letterSpacing: -0.25 + font.pixelSize: 56 + font.weight: 400 + horizontalAlignment: TextInput.AlignHCenter + text: setConfigText() + verticalAlignment: TextInput.AlignVCenter + + Keys.onPressed: event => { + if (event.key === Qt.Key_Backspace) { + event.accepted = true; + if (endMinuteField.text.length >= 2) { + endMinuteField.text = "0" + endMinuteField.text[0]; + } else if (endMinuteField.text.length === 1) { + endMinuteField.text = "0"; + } + return; + } else if (event.key === Qt.Key_Escape) { + event.accepted = true; + endMinuteField.text = setConfigText(); + endMinuteField.focus = false; + } else if (event.key === Qt.Key_Return) { + endMinuteField.focus = false; + return; + } else if (event.key === Qt.Key_Tab) { + startHourField.focus = true; + } else if (event.key === Qt.Key_Backtab) { + endHourField.focus = true; + } + + if (event.text.length === 1 && event.text >= "0" && event.text <= "9") { + event.accepted = true; + var digit = event.text; + var textLen = endMinuteField.text.length; + + if (textLen >= 2 && endMinuteField.text[0] !== '0') { + return; + } + + var val = 0; + if (textLen === 0) { + val = parseInt(digit); + } else if (textLen === 1) { + val = parseInt(endMinuteField.text + digit); + } else { + val = parseInt(endMinuteField.text[1] + digit); + } + + val = Math.max(0, Math.min(59, val)); + + if (textLen >= 2 && val < 10) { + endMinuteField.text = "0" + val; + } else { + endMinuteField.text = val.toString(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onTextChanged: { + var mins = parseInt(endMinuteField.text); + const hours = root.convertToMinutes(parseInt(endHourField.text)); + root.endEdit(hours + mins); + } } } } RowLayout { - id: buttonRow - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: Tokens.padding.largeIncreased - anchors.right: parent.right - anchors.top: column.bottom - anchors.topMargin: Tokens.spacing.medium - - Item { - id: buttonSpacer - - Layout.fillWidth: true + CustomText { + Layout.preferredWidth: spacer.x + spacer.width + text: qsTr("Start") } - ButtonRow { - spacing: Tokens.spacing.medium - - IconTextButton { - font.pointSize: Tokens.font.size.normal - icon: "close" - inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2) - inactiveOnColor: Colors.palette.m3onSurfaceVariant - isRound: true - isToggle: false - shapeMorph: true - shapeMorphExpansion: pressed ? 12 : 0 - text: "Cancel" - - onClicked: root.close() - } - - IconTextButton { - font.pointSize: Tokens.font.size.normal - icon: "check" - isRound: true - isToggle: false - shapeMorph: true - shapeMorphExpansion: pressed ? 12 : 0 - text: "Apply" - - onClicked: { - const start = root.convertToMinutes(parseInt(startHourField.text)) + parseInt(startMinuteField.text); - const end = root.convertToMinutes(parseInt(endHourField.text)) + parseInt(endMinuteField.text); - root.applySettings(start, end); - } - } + CustomText { + Layout.preferredWidth: endMinuteRect.width + endHourRect.width + text: qsTr("End") } } } diff --git a/Modules/Settings/Common/ToggleRow.qml b/Modules/Settings/Common/ToggleRow.qml index 09f22f5..36fb583 100644 --- a/Modules/Settings/Common/ToggleRow.qml +++ b/Modules/Settings/Common/ToggleRow.qml @@ -12,6 +12,7 @@ CustomSwitch { property alias last: bg.last property string settingAnchor property string subtext + property string iconText function flashHighlight(): void { bg.flashHighlight(); @@ -63,8 +64,7 @@ CustomSwitch { text: root.text Behavior on opacity { - Anim { - } + Anim {} } } @@ -81,10 +81,16 @@ CustomSwitch { visible: root.subtext Behavior on opacity { - Anim { - } + Anim {} } } + + MaterialIcon { + id: icon + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: root.iconText ?? "" + } } } diff --git a/Modules/Settings/Content.qml b/Modules/Settings/Content.qml index 66b616a..75a05c2 100644 --- a/Modules/Settings/Content.qml +++ b/Modules/Settings/Content.qml @@ -24,8 +24,7 @@ CustomClippingRect { radius: Tokens.rounding.largeIncreased + Tokens.padding.small Behavior on blobColor { - CAnim { - } + CAnim {} } TapHandler { @@ -70,8 +69,4 @@ CustomClippingRect { anchors.top: parent.top sState: root.sState } - - PopupOverlay { - anchors.fill: parent - } } diff --git a/Modules/Settings/PageCompRegistry.qml b/Modules/Settings/PageCompRegistry.qml index 8fea9b4..ee45aba 100644 --- a/Modules/Settings/PageCompRegistry.qml +++ b/Modules/Settings/PageCompRegistry.qml @@ -30,6 +30,10 @@ QtObject { Component { WallpaperSelect {} } + + Component { + ColorsFonts {} + } } }, Component { diff --git a/Modules/Settings/Pages.qml b/Modules/Settings/Pages.qml index f853699..ad148b1 100644 --- a/Modules/Settings/Pages.qml +++ b/Modules/Settings/Pages.qml @@ -1,6 +1,7 @@ import QtQuick import qs.Components import ZShell.Config +import qs.Services Item { id: root @@ -41,6 +42,18 @@ Item { objectName: "PageContainer" Component.onCompleted: root.loadPage(root.sState.currentPageIdx) + + CustomRect { + anchors.fill: parent + color: Qt.alpha(Colors.palette.m3shadow, 0.3) + opacity: root.sState.dimmed ? 1 : 0 + visible: opacity > 0 + z: 0 + + Behavior on opacity { + Anim {} + } + } } Connections { diff --git a/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml index 991c1b2..8985fb0 100644 --- a/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml +++ b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml @@ -55,6 +55,10 @@ PageBase { enabled: Object.keys(model).length > 0 header: qsTr("Add new entry") icon: "add" + settingAnchor: "panels-bar-status-icons-add-item" + onOpenChanged: { + root.sState.dimmed = open; + } last: true label: qsTr("Add entry") model: { diff --git a/Modules/Settings/Pages/Panels/Sidebar/SidebarLlm.qml b/Modules/Settings/Pages/Panels/Sidebar/SidebarLlm.qml index ebb3445..ce750e5 100644 --- a/Modules/Settings/Pages/Panels/Sidebar/SidebarLlm.qml +++ b/Modules/Settings/Pages/Panels/Sidebar/SidebarLlm.qml @@ -75,10 +75,18 @@ PageBase { acceptLabel: qsTr("Confirm") enabled: Object.keys(model).length > 0 + settingAnchor: "panels-sidebar-select-scheme" + separateContent: false header: qsTr("Select scheme") first: true last: true - icon: "add" + icon: "format_paint" + iconLabel.font.pointSize: Tokens.font.size.large + rowButton.trailingIcon: "open_in_new" + onOpenChanged: { + root.sState.dimmed = open; + } + rowButton.activeItem: root.schemes.find(s => s.id === Config.llm.appearance.scheme).label label: qsTr("Color scheme") subtext: qsTr("Select the color scheme used for code blocks") model: root.schemes diff --git a/Modules/Settings/Pages/Panels/SidebarPanel.qml b/Modules/Settings/Pages/Panels/SidebarPanel.qml index 9aef799..6c8abd3 100644 --- a/Modules/Settings/Pages/Panels/SidebarPanel.qml +++ b/Modules/Settings/Pages/Panels/SidebarPanel.qml @@ -88,6 +88,10 @@ PageBase { enabled: Object.keys(model).length > 0 header: qsTr("Add new entry") icon: "add" + onOpenChanged: { + root.sState.dimmed = open; + } + settingAnchor: "panels-sidebar-quick-toggles-add-item" label: qsTr("Add entry") last: true model: { diff --git a/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml b/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml new file mode 100644 index 0000000..44a490e --- /dev/null +++ b/Modules/Settings/Pages/Wallpaper/ColorsFonts.qml @@ -0,0 +1,327 @@ +pragma ComponentBehavior: Bound + +import Quickshell +import Quickshell.Io +import QtQuick +import QtQuick.Layouts +import ZShell.Config +import qs.Paths +import qs.Services +import qs.Modules.Settings.Common + +PageBase { + id: root + + readonly property list fonts: Qt.fontFamilies() + readonly property var schemes: [ + { + id: "onedark", + label: qsTr("One Dark") + }, + { + id: "nord", + label: qsTr("Nord") + }, + { + id: "dracula", + label: qsTr("Dracula") + }, + { + id: "darkgreen", + label: qsTr("Darkgreen") + }, + { + id: "solarized", + label: qsTr("Solarized") + }, + { + id: "rosepine", + label: qsTr("Rosepine") + }, + { + id: "gruvbox", + label: qsTr("Gruvbox") + }, + { + id: "catppuccin", + label: qsTr("Catppuccin") + }, + { + id: "tokyonight", + label: qsTr("Tokyo Night") + }, + { + id: "shadotheme", + label: qsTr("Shadotheme") + } + ] + + isSubPage: true + title: qsTr("Colors & font") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Tokens.spacing.extraSmall / 2 + width: root.cappedWidth + + SectionHeader { + first: true + text: qsTr("Colors") + } + + ToggleRow { + text: qsTr("Dynamic colors") + first: true + subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.general.color.schemeGeneration ? "enabled" : "disabled") + checked: Config.general.color.schemeGeneration + settingAnchor: "colors-fonts-dynamic-colors" + + onToggled: { + Config.general.color.schemeGeneration = checked; + + if (checked) + Colors.setMode(Config.general.color.mode); + } + } + + DialogSelectButton { + settingAnchor: "colors-fonts-preset-scheme" + model: root.schemes + acceptLabel: qsTr("Confirm") + enabled: !Config.general.color.schemeGeneration + header: qsTr("Select scheme") + rowButton.activeItem: root.schemes.find(s => { + var regex = new RegExp(Config.colors.presets.name, "i"); + return regex.test(s.id); + }).label ?? "" + label: qsTr("Preset scheme") + subtext: qsTr("Select preset color scheme") + rootParent: root.flickable + + FileView { + id: view + path: `${Paths.state}/presets.json` + + watchChanges: true + onFileChanged: reload() + + JsonAdapter { + id: adapter + + property var presets + } + } + + initialSelect: Config.colors.presets.name.toLowerCase() + + onAccepted: { + if (!selectedItem) + return; + + const s = selectedItem.toLowerCase(); + let v = ""; + let a = ""; + const existingVariant = Config.colors.presets.variant; + const existingAccent = Config.colors.presets.accent; + const variantKeys = Object.keys(adapter.presets[s].variants); + + if (existingVariant !== "" && variantKeys.some(v => v === existingVariant)) + v = existingVariant; + + if (v !== "" && existingAccent !== "" && adapter.presets[s].variants[v]?.accents.some(a => a === existingAccent)) + a = existingAccent; + + if (a !== "") + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]); + else if (v !== "") + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`]); + else + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${variantKeys[0]}`]); + } + } + + DialogSelectButton { + settingAnchor: "colors-fonts-preset-variant" + // qmlformat off + model: { + const v = adapter.presets[Config.colors.presets.name].variants; + const map = Object.keys(v).map(v => ({ + id: v, + label: qsTr("%1%2") + .arg(v.charAt(0).toUpperCase()) + .arg(v.slice(1).toLowerCase()) + })); + + return map; + } + // qmlformat on + acceptLabel: qsTr("Confirm") + enabled: !Config.general.color.schemeGeneration && model.length > 1 + header: qsTr("Select variant") + rowButton.activeItem: { + const v = Config.colors.presets.variant ?? ""; + if (v === "") + return ""; + return qsTr("%1%2").arg(v.charAt(0).toUpperCase()).arg(v.slice(1).toLowerCase()); + } + label: qsTr("Variant") + subtext: qsTr("Select preset variant") + rootParent: root.flickable + + initialSelect: Config.colors.presets.variant + + onAccepted: { + if (!selectedItem) + return; + + const v = selectedItem.toLowerCase(); + const s = Config.colors.presets.name; + const existingAccent = Config.colors.presets.accent; + let a = ""; + + if (existingAccent !== "" && adapter.presets[s].variants[v]?.accents.some(a => a === existingAccent)) + a = existingAccent; + + if (a !== "") + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]); + else + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`]); + } + } + + DialogSelectButton { + settingAnchor: "colors-fonts-preset-accent" + // qmlformat off + model: { + const a = adapter.presets[Config.colors.presets.name].variants[Config.colors.presets.variant]?.accents ?? []; + + if (a.length === 0) + return a; + + const map = a.map(a => ({ + id: a, + label: qsTr("%1%2") + .arg(a.charAt(0).toUpperCase()) + .arg(a.slice(1).toLowerCase()) + })); + + return map; + } + // qmlformat on + acceptLabel: qsTr("Confirm") + enabled: !Config.general.color.schemeGeneration && model.length > 1 + header: qsTr("Select accent") + rowButton.activeItem: { + const a = Config.colors.presets.accent ?? ""; + if (a === "") + return ""; + return qsTr("%1%2").arg(a.charAt(0).toUpperCase()).arg(a.slice(1).toLowerCase()); + } + label: qsTr("Accent") + last: true + subtext: qsTr("Select preset accent") + rootParent: root.flickable + + initialSelect: Config.colors.presets.accent + + onAccepted: { + if (!selectedItem) + return; + + const a = selectedItem.toLowerCase(); + const v = Config.colors.presets.variant; + const s = Config.colors.presets.name; + + Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${s}:${v}`, "-a", `${a}`]); + } + } + + SectionHeader { + text: qsTr("Fonts") + } + + SearchPopup { + id: selectSansFont + + settingAnchor: "colors-fonts-sans-font" + model: root.fonts + acceptLabel: qsTr("Confirm") + enabled: Object.keys(model).length > 0 + separateContent: false + header: qsTr("Select font") + first: true + rowButton.activeItem: root.fonts.find(s => { + var regex = new RegExp(Config.appearance.font.family.sans, "i"); + return regex.test(s); + }) ?? "" + label: qsTr("Sans font") + subtext: qsTr("Select desired sans font") + rootParent: root.flickable + + initialSelect: Config.appearance.font.family.sans + + onAccepted: { + if (!selectedItem) + return; + + Config.appearance.font.family.sans = selectedItem; + } + } + + SearchPopup { + id: selectMonoFont + + acceptLabel: qsTr("Confirm") + enabled: Object.keys(model).length > 0 + settingAnchor: "colors-fonts-mono-font" + separateContent: false + header: qsTr("Select font") + rowButton.activeItem: root.fonts.find(s => { + var regex = new RegExp(Config.appearance.font.family.mono, "i"); + return regex.test(s); + }) ?? "" + label: qsTr("Mono font") + subtext: qsTr("Select desired mono font") + model: root.fonts + rootParent: root.flickable + + initialSelect: Config.appearance.font.family.mono + + onAccepted: { + if (!selectedItem) + return; + + Config.appearance.font.family.mono = selectedItem; + } + } + + SearchPopup { + id: selectClockFont + + acceptLabel: qsTr("Confirm") + enabled: Object.keys(model).length > 0 + separateContent: false + settingAnchor: "colors-fonts-clock-font" + header: qsTr("Select font") + last: true + rowButton.activeItem: root.fonts.find(s => { + var regex = new RegExp(Config.appearance.font.family.clock, "i"); + return regex.test(s); + }) ?? "" + label: qsTr("Clock font") + subtext: qsTr("Select desired clock font") + model: root.fonts + rootParent: root.flickable + + initialSelect: Config.appearance.font.family.clock + + onAccepted: { + if (!selectedItem) + return; + + Config.appearance.font.family.clock = selectedItem; + } + } + } +} diff --git a/Modules/Settings/Pages/WallpaperPage.qml b/Modules/Settings/Pages/WallpaperPage.qml index 40274ad..5e7035c 100644 --- a/Modules/Settings/Pages/WallpaperPage.qml +++ b/Modules/Settings/Pages/WallpaperPage.qml @@ -33,18 +33,35 @@ PageBase { } } - IconTextButton { + ButtonRow { Layout.alignment: Qt.AlignHCenter - enabled: Config.background.enabled - horizontalPadding: Tokens.padding.extraLarge - icon: "wallpaper" - isRound: true - shapeMorph: true - text: qsTr("Wallpapers") - type: IconTextButton.Tonal - verticalPadding: Tokens.padding.small + spacing: Tokens.spacing.small - onClicked: root.sState.openSubPage(1) // Wallpaper page + IconTextButton { + enabled: Config.background.enabled + horizontalPadding: Tokens.padding.extraLarge + icon: "wallpaper" + isRound: true + shapeMorph: true + text: qsTr("Wallpapers") + type: IconTextButton.Tonal + verticalPadding: Tokens.padding.small + + onClicked: root.sState.openSubPage(1) // Wallpaper page + } + + IconTextButton { + enabled: Config.background.enabled + horizontalPadding: Tokens.padding.extraLarge + icon: "palette" + isRound: true + shapeMorph: true + text: qsTr("Colors & font") + type: IconTextButton.Tonal + verticalPadding: Tokens.padding.small + + onClicked: root.sState.openSubPage(2) // Colors & font page + } } ToggleRow { @@ -76,9 +93,7 @@ PageBase { onToggled: Colors.setMode(checked ? "dark" : "light") } - OverlayRow { - id: darkMode - + TimeDialogSelect { readonly property string endTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); d.setMinutes(Config.general.color.scheduleDarkEnd); @@ -90,35 +105,30 @@ PageBase { return Qt.formatTime(d, "hh:mm AP"); } - checked: Config.general.color.scheduleDark + rowButton.checked: Config.general.color.scheduleDark + rowButton.onToggled: Config.general.color.scheduleDark = rowButton.checked + start: Config.general.color.scheduleDarkStart + end: Config.general.color.scheduleDarkEnd first: true settingAnchor: "style-schedule-dark-mode" + acceptLabel: qsTr("Apply") + rootParent: root.flickable + header: qsTr("Select time") subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) - text: qsTr("Schedule dark mode") + label: qsTr("Schedule dark mode") - popup: Component { - TimeInput { - object: Config.general.color - settings: ["scheduleDark", "scheduleDarkStart", "scheduleDarkEnd"] - - onApplySettings: (start, end) => { - Config.general.color.scheduleDarkStart = start; - Config.general.color.scheduleDarkEnd = end; - ModeScheduler.checkStartup(); - PopupManager.requestClose(); - } - onClose: PopupManager.requestClose() - } + onOpenChanged: { + root.sState.dimmed = open; } - onClicked: value => { - Config.general.color.scheduleDark = value; + onAccepted: { + Config.general.color.scheduleDarkStart = startCurrent; + Config.general.color.scheduleDarkEnd = endCurrent; + ModeScheduler.checkStartup(); } } - OverlayRow { - id: hyprsunset - + TimeDialogSelect { readonly property string endTime: { var d = new Date(0, 0, 0, 0, 0, 0, 0); d.setMinutes(Config.general.color.scheduleHyprsunsetEnd); @@ -131,29 +141,26 @@ PageBase { } Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing - checked: Config.general.color.scheduleHyprsunset + rowButton.checked: Config.general.color.scheduleHyprsunset + rowButton.onToggled: Config.general.color.scheduleHyprsunset = rowButton.checked + start: Config.general.color.scheduleHyprsunsetStart + end: Config.general.color.scheduleHyprsunsetEnd last: true settingAnchor: "style-schedule-hyprsunset" + acceptLabel: qsTr("Apply") + rootParent: root.flickable + header: qsTr("Select time") subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) - text: qsTr("Schedule hyprsunset") + label: qsTr("Schedule hyprsunset") - popup: Component { - TimeInput { - object: Config.general.color - settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd"] - - onApplySettings: (start, end) => { - Config.general.color.scheduleHyprsunsetStart = start; - Config.general.color.scheduleHyprsunsetEnd = end; - Hyprsunset.checkStartup(); - PopupManager.requestClose(); - } - onClose: PopupManager.requestClose() - } + onOpenChanged: { + root.sState.dimmed = open; } - onClicked: value => { - Config.general.color.scheduleHyprsunset = value; + onAccepted: { + Config.general.color.scheduleHyprsunsetStart = startCurrent; + Config.general.color.scheduleHyprsunsetEnd = endCurrent; + Hyprsunset.checkStartup(); } } } diff --git a/Modules/Settings/PopupManager.qml b/Modules/Settings/PopupManager.qml deleted file mode 100644 index 4b37c02..0000000 --- a/Modules/Settings/PopupManager.qml +++ /dev/null @@ -1,20 +0,0 @@ -pragma Singleton - -import Quickshell -import QtQuick - -Singleton { - id: root - - property bool closed: true - property Component currentPopup: null - - function requestClose(): void { - closed = true; - } - - function requestOpen(component: Component): void { - currentPopup = component; - closed = false; - } -} diff --git a/Modules/Settings/PopupOverlay.qml b/Modules/Settings/PopupOverlay.qml deleted file mode 100644 index d18440e..0000000 --- a/Modules/Settings/PopupOverlay.qml +++ /dev/null @@ -1,47 +0,0 @@ -import QtQuick -import ZShell.Config -import qs.Components -import qs.Services - -CustomRect { - id: root - - readonly property bool closing: PopupManager.closed - readonly property var currentPopup: PopupManager.currentPopup - property bool shouldBeVisible: loader.status === Loader.Ready - - color: Qt.alpha(Colors.palette.m3shadow, 0.3) - opacity: shouldBeVisible && !closing ? 1 : 0 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - - onVisibleChanged: if (!visible) - PopupManager.currentPopup = null - - CustomMouseArea { - anchors.fill: parent - hoverEnabled: true - preventStealing: true - propagateComposedEvents: false - - onClicked: { - const insideItemWidth = mouseX < loader.x + loader.item.width && mouseX > loader.x; - const insideItemHeight = mouseY < loader.y + loader.item.height && mouseY > loader.y; - if (insideItemHeight && insideItemWidth) - return; - - PopupManager.requestClose(); - } - } - - Loader { - id: loader - - anchors.centerIn: parent - sourceComponent: root.currentPopup - } -} diff --git a/Modules/Settings/SettingsState.qml b/Modules/Settings/SettingsState.qml index 79e80d8..11933e1 100644 --- a/Modules/Settings/SettingsState.qml +++ b/Modules/Settings/SettingsState.qml @@ -16,6 +16,7 @@ QtObject { property string searchText property DesktopEntry selectedApp property BluetoothDevice selectedBtDevice + property bool dimmed property string selectedWallpaperCategory property list subPageIdxStack diff --git a/cli/src/zshell/subcommands/scheme.py b/cli/src/zshell/subcommands/scheme.py index ed60e8b..2a65612 100644 --- a/cli/src/zshell/subcommands/scheme.py +++ b/cli/src/zshell/subcommands/scheme.py @@ -94,9 +94,8 @@ def list_presets( if v.accents: entry["accents"] = sorted(v.accents) entry["default_accent"] = sorted(v.accents)[0] - variants[v.id] = entry - out[meta.name] = { - "id": sid, + variants[v.id.lower()] = entry + out[sid.lower()] = { "variants": variants, } print(json.dumps({"presets": out}, indent=2)) @@ -670,7 +669,7 @@ def generate( name = palette_obj.scheme flavor = palette_obj.variant - display_name = schemes[p_scheme].name + display_name = schemes[p_scheme].id config["colors"]["presets"] = { "name": display_name, "variant": p_variant,