From ea4d5376f6a011eb34dc7ea28662df5fb9c094a9 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 16:22:56 +0200 Subject: [PATCH 01/17] initial commit for settings revamp --- Components/CustomSpinBox.qml | 20 +- Components/FadeImage.qml | 79 +++ Components/IconTextButton.qml | 1 + Components/VerticalFadeFlickable.qml | 71 ++ Drawers/Interactions.qml | 2 + Drawers/Panels.qml | 8 +- Drawers/Windows.qml | 10 +- Effects/Mask.qml | 7 + Helpers/Wallpapers.qml | 7 + Modules/SettingsNew/Common/ConnectedRect.qml | 14 + Modules/SettingsNew/Common/HyprTimeInput.qml | 659 ++++++++++++++++++ Modules/SettingsNew/Common/PageBase.qml | 69 ++ Modules/SettingsNew/Common/PopupRow.qml | 68 ++ Modules/SettingsNew/Common/SelectRow.qml | 64 ++ .../SettingsNew/Common/SettingHyprSpinner.qml | 214 ++++++ Modules/SettingsNew/Common/SpinRow.qml | 63 ++ Modules/SettingsNew/Common/StackPage.qml | 125 ++++ Modules/SettingsNew/Common/TimeInput.qml | 595 ++++++++++++++++ Modules/SettingsNew/Common/ToggleRow.qml | 82 +++ Modules/SettingsNew/Common/WallItem.qml | 62 ++ .../SettingsNew/Common/WallpaperCropper.qml | 314 +++++++++ Modules/SettingsNew/Content.qml | 72 ++ Modules/SettingsNew/NavPane.qml | 25 + Modules/SettingsNew/NavPane/NavLocations.qml | 125 ++++ Modules/SettingsNew/NavPane/SearchBar.qml | 75 ++ Modules/SettingsNew/PageCompRegistry.qml | 73 ++ Modules/SettingsNew/PageRegistry.qml | 64 ++ Modules/SettingsNew/Pages.qml | 100 +++ Modules/SettingsNew/Pages/Screenshot.qml | 137 ++++ Modules/SettingsNew/Pages/Wallpaper.qml | 220 ++++++ .../Pages/Wallpaper/WallpaperSelect.qml | 135 ++++ Modules/SettingsNew/PopupManager.qml | 20 + Modules/SettingsNew/PopupOverlay.qml | 46 ++ Modules/SettingsNew/SettingsState.qml | 33 + Modules/SettingsNew/Wrapper.qml | 47 ++ Paths/Paths.qml | 2 +- 36 files changed, 3693 insertions(+), 15 deletions(-) create mode 100644 Components/FadeImage.qml create mode 100644 Components/VerticalFadeFlickable.qml create mode 100644 Effects/Mask.qml create mode 100644 Modules/SettingsNew/Common/ConnectedRect.qml create mode 100644 Modules/SettingsNew/Common/HyprTimeInput.qml create mode 100644 Modules/SettingsNew/Common/PageBase.qml create mode 100644 Modules/SettingsNew/Common/PopupRow.qml create mode 100644 Modules/SettingsNew/Common/SelectRow.qml create mode 100644 Modules/SettingsNew/Common/SettingHyprSpinner.qml create mode 100644 Modules/SettingsNew/Common/SpinRow.qml create mode 100644 Modules/SettingsNew/Common/StackPage.qml create mode 100644 Modules/SettingsNew/Common/TimeInput.qml create mode 100644 Modules/SettingsNew/Common/ToggleRow.qml create mode 100644 Modules/SettingsNew/Common/WallItem.qml create mode 100644 Modules/SettingsNew/Common/WallpaperCropper.qml create mode 100644 Modules/SettingsNew/Content.qml create mode 100644 Modules/SettingsNew/NavPane.qml create mode 100644 Modules/SettingsNew/NavPane/NavLocations.qml create mode 100644 Modules/SettingsNew/NavPane/SearchBar.qml create mode 100644 Modules/SettingsNew/PageCompRegistry.qml create mode 100644 Modules/SettingsNew/PageRegistry.qml create mode 100644 Modules/SettingsNew/Pages.qml create mode 100644 Modules/SettingsNew/Pages/Screenshot.qml create mode 100644 Modules/SettingsNew/Pages/Wallpaper.qml create mode 100644 Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml create mode 100644 Modules/SettingsNew/PopupManager.qml create mode 100644 Modules/SettingsNew/PopupOverlay.qml create mode 100644 Modules/SettingsNew/SettingsState.qml create mode 100644 Modules/SettingsNew/Wrapper.qml diff --git a/Components/CustomSpinBox.qml b/Components/CustomSpinBox.qml index 7526242..55b4189 100644 --- a/Components/CustomSpinBox.qml +++ b/Components/CustomSpinBox.qml @@ -87,9 +87,15 @@ RowLayout { id: upButton color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) - implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 + implicitHeight: upIcon.implicitHeight + Appearance.padding.extraSmall * 2 implicitWidth: implicitHeight - radius: Appearance.rounding.full + radius: upState.pressed ? Appearance.rounding.small : Appearance.rounding.normal + + Behavior on radius { + Anim { + type: Anim.FastEffects + } + } StateLayer { id: upState @@ -120,9 +126,15 @@ RowLayout { CustomRect { color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) - implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2 + implicitHeight: downIcon.implicitHeight + Appearance.padding.extraSmall * 2 implicitWidth: implicitHeight - radius: Appearance.rounding.full + radius: downState.pressed ? Appearance.rounding.small : Appearance.rounding.normal + + Behavior on radius { + Anim { + type: Anim.FastEffects + } + } StateLayer { id: downState diff --git a/Components/FadeImage.qml b/Components/FadeImage.qml new file mode 100644 index 0000000..b72a203 --- /dev/null +++ b/Components/FadeImage.qml @@ -0,0 +1,79 @@ +import QtQuick +import Quickshell +import qs.Config + +Image { + id: root + + property int fadeInAnim: Anim.DefaultEffects + property int fadeInLargeAnim: Anim.StandardLarge + property int fadeOutAnim: Anim.FastEffects + property bool fadingOut + property bool hadPrevious + property bool preventInit + + function maybeStartInAnim(): void { + if (!preventInit && !opacityInAnim.running && status === Image.Ready) { + opacityInAnim.type = hadPrevious ? fadeInAnim : fadeInLargeAnim; + opacityInAnim.start(); + } + } + + asynchronous: true + fillMode: Image.PreserveAspectCrop + opacity: 0 + retainWhileLoading: true + sourceSize: { + const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1; + return Qt.size(width * dpr, height * dpr); + } + + Anim on opacity { + id: opacityInAnim + + running: false + to: 1 + } + Behavior on source { + SequentialAnimation { + ScriptAction { + script: opacityInAnim.stop() + } + + PropertyAction { + property: "fadingOut" + target: root + value: true + } + + Anim { + property: "opacity" + target: root + to: 0 + type: root.fadeOutAnim + } + + PropertyAction { + property: "fadingOut" + target: root + value: false + } + + PropertyAction { + property: "hadPrevious" + target: root + value: root.source + } + + PropertyAction { + } + + ScriptAction { + script: root.maybeStartInAnim() + } + } + } + + onPreventInitChanged: maybeStartInAnim() + onStatusChanged: maybeStartInAnim() +} diff --git a/Components/IconTextButton.qml b/Components/IconTextButton.qml index dc20535..0aa93bf 100644 --- a/Components/IconTextButton.qml +++ b/Components/IconTextButton.qml @@ -63,6 +63,7 @@ ButtonBase { id: label Layout.alignment: Qt.AlignVCenter + Layout.topMargin: -1 color: root.onColor font: root.font } diff --git a/Components/VerticalFadeFlickable.qml b/Components/VerticalFadeFlickable.qml new file mode 100644 index 0000000..edd63df --- /dev/null +++ b/Components/VerticalFadeFlickable.qml @@ -0,0 +1,71 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Effects + +CustomFlickable { + id: root + + property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 + property real fadeAmount: 0.2 + property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 + + function fadeShouldBeActive(isStart: bool): bool { + // When content is smaller than flickable size, hide fade when rebound starts + if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0))) + return false; + + if (isStart) + return visibleArea.yPosition > 0; + return visibleArea.yPosition + visibleArea.heightRatio < 1; + } + + flickableDirection: Flickable.VerticalFlick + layer.enabled: true + + Behavior on bottomFadeOpacity { + Anim { + type: Anim.SlowEffects + } + } + layer.effect: Mask { + maskSource: mask + + Rectangle { + id: mask + + anchors.fill: parent + layer.enabled: true + visible: false + + gradient: Gradient { + orientation: Gradient.Vertical + + GradientStop { + color: Qt.rgba(0, 0, 0, root.topFadeOpacity) + position: 0 + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 1) + position: root.fadeAmount + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 1) + position: 1 - root.fadeAmount + } + + GradientStop { + color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity) + position: 1 + } + } + } + } + Behavior on topFadeOpacity { + Anim { + type: Anim.SlowEffects + } + } +} diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 6051d5a..a70df18 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -298,6 +298,8 @@ Item { root.visibilities.sidebar = false; root.panels.popouts.hasCurrent = false; root.visibilities.launcher = false; + } else { + Config.save(); } } diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index d76e533..d2c4ab4 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -10,7 +10,7 @@ import qs.Modules.Osd as Osd import qs.Components.Toast as Toasts import qs.Modules.Launcher as Launcher import qs.Modules.Resources as Resources -import qs.Modules.Settings as Settings +import qs.Modules.SettingsNew as Settings import qs.Modules.Drawing as Drawing import qs.Modules.Dock as Dock import qs.Modules.Clipboard as Clipboard @@ -184,11 +184,11 @@ Item { Item { id: settingsWrapper - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top clip: true - implicitHeight: settings.implicitHeight * (1 - settings.offsetScale) + implicitHeight: settings.implicitHeight implicitWidth: settings.implicitWidth + x: (root.width - settings.implicitWidth) / 2 + y: (settings.implicitHeight + (root.height - root.bar.implicitHeight - settings.implicitHeight) / 2) * (1 - settings.offsetScale) - settings.implicitHeight - 5 Settings.Wrapper { id: settings diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index bfcb512..cb58a55 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -323,15 +323,13 @@ CustomWindow { PanelBg { id: settingsBg - property real extraHeight: 0.2 + property real extraHeight: 0 deformAmount: 0.03 implicitHeight: panels.settings.height * (1 + extraHeight) implicitWidth: panels.settings.width - panel: panels.settings - radius: Appearance.rounding.large - topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller - topRightRadius: Appearance.rounding.large + Appearance.padding.smaller + panel: panels.settingsWrapper + radius: Appearance.rounding.large + Appearance.padding.normal x: panels.settingsWrapper.x + panels.settings.x + root.borderThickness y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight } @@ -441,7 +439,7 @@ CustomWindow { resources.transform: Matrix4x4 { matrix: resourcesBg.deformMatrix } - settings.transform: Matrix4x4 { + settingsWrapper.transform: Matrix4x4 { matrix: settingsBg.deformMatrix } sidebar.transform: Matrix4x4 { diff --git a/Effects/Mask.qml b/Effects/Mask.qml new file mode 100644 index 0000000..e8f889d --- /dev/null +++ b/Effects/Mask.qml @@ -0,0 +1,7 @@ +import QtQuick.Effects + +MultiEffect { + maskEnabled: true + maskSpreadAtMin: 1 + maskThresholdMin: 0.5 +} diff --git a/Helpers/Wallpapers.qml b/Helpers/Wallpapers.qml index e31ba1d..1e12dcc 100644 --- a/Helpers/Wallpapers.qml +++ b/Helpers/Wallpapers.qml @@ -21,6 +21,13 @@ Searcher { property bool recentlyChanged property bool showPreview: false + function getCategoryFor(w: FileSystemEntry): string { + let category = w.parentDir.slice(Paths.wallsdir.length + 1); + if (category.includes("/")) + category = category.slice(0, category.indexOf("/")); + return category; + } + function getCrop(screen: string): var { return root.crops[screen]; } diff --git a/Modules/SettingsNew/Common/ConnectedRect.qml b/Modules/SettingsNew/Common/ConnectedRect.qml new file mode 100644 index 0000000..e273ae4 --- /dev/null +++ b/Modules/SettingsNew/Common/ConnectedRect.qml @@ -0,0 +1,14 @@ +import QtQuick +import qs.Components +import qs.Config + +CustomRect { + property bool first + property bool last + + bottomLeftRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall + bottomRightRadius: last ? Appearance.rounding.large : Appearance.rounding.extraSmall + color: DynamicColors.tPalette.m3surfaceContainer + topLeftRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall + topRightRadius: first ? Appearance.rounding.large : Appearance.rounding.extraSmall +} diff --git a/Modules/SettingsNew/Common/HyprTimeInput.qml b/Modules/SettingsNew/Common/HyprTimeInput.qml new file mode 100644 index 0000000..b170d78 --- /dev/null +++ b/Modules/SettingsNew/Common/HyprTimeInput.qml @@ -0,0 +1,659 @@ +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Helpers + +Item { + id: root + + readonly property string endTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(object[settings[2]]); + return Qt.formatTime(d, "hh:mm AP"); + } + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name + required property string name + required property var object + required property list settings + property bool shouldBeActive: true + readonly property string startTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(object[settings[1]]); + return Qt.formatTime(d, "hh:mm AP"); + } + + function commitChoice(choice: int, setting: string): void { + root.object[setting] = choice; + Config.save(); + Hyprsunset.checkStartup(); + } + + function convertHour(timeValue: int): int { + return Math.floor(timeValue / 60); + } + + function convertMinute(timeValue: int): int { + return timeValue % 60; + } + + function convertToMinutes(hour: int, minute: int): int { + return hour * 60 + minute; + } + + Layout.fillWidth: true + implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0.8 + visible: opacity > 0 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + Behavior on y { + Anim { + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomText { + id: text + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.name + } + + CustomText { + Layout.alignment: Qt.AlignLeft + Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal) + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.normal + text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime) + wrapMode: Text.WordWrap + } + } + + ColumnLayout { + id: optionLayout + + Layout.fillHeight: true + Layout.fillWidth: true + + RowLayout { + CustomText { + Layout.preferredWidth: spacer.x + spacer.width + text: qsTr("Start") + } + + CustomText { + Layout.preferredWidth: endMinuteRect.width + endHourRect.width + text: qsTr("End") + } + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + text: qsTr("Enabled: ") + } + + CustomSwitch { + id: enabledSwitch + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + checked: root.object[root.settings[0]] + + onToggled: { + root.object[root.settings[0]] = checked; + Config.save(); + } + } + } + + RowLayout { + Layout.fillHeight: true + + CustomRect { + id: startHourRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: startHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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 + onEditingFinished: { + root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); + } + 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: Appearance.font.size.extraLarge + text: ":" + } + + CustomRect { + id: startMinuteRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: startMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onEditingFinished: { + root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); + } + 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: Appearance.spacing.large + } + + CustomRect { + id: endHourRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: endHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onEditingFinished: { + root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[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: Appearance.font.size.extraLarge + text: ":" + } + + CustomRect { + id: endMinuteRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: endMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + id: endMinuteField + + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onEditingFinished: { + root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]); + } + onTextEdited: { + if (endMinuteField.text === "") + 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; + } + } + } + } + + RowLayout { + Layout.fillWidth: true + + CustomText { + id: startHour + + Layout.preferredWidth: startSeparator.x + startSeparator.width + text: qsTr("Hour") + } + + CustomText { + id: startMinute + + Layout.preferredWidth: spacer.x + spacer.width - x + text: qsTr("Minute") + } + + CustomText { + Layout.preferredWidth: endSeparator.x + endSeparator.width - x + text: qsTr("Hour") + } + + CustomText { + text: qsTr("Minute") + } + } + } + } +} diff --git a/Modules/SettingsNew/Common/PageBase.qml b/Modules/SettingsNew/Common/PageBase.qml new file mode 100644 index 0000000..64a5b74 --- /dev/null +++ b/Modules/SettingsNew/Common/PageBase.qml @@ -0,0 +1,69 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Modules.SettingsNew +import qs.Components +import qs.Config + +ColumnLayout { + id: root + + readonly property int cappedWidth: Math.min(800, width) + default property Item contentChild + readonly property alias flickable: flickable + property bool isSubPage + required property SettingsState sState + required property string title + + spacing: Appearance.spacing.large + + MouseArea { // Prevent clicks from reaching flickable + Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin + + implicitHeight: header.implicitHeight - Layout.bottomMargin + implicitWidth: header.implicitWidth + z: 1 + + RowLayout { + id: header + + spacing: Appearance.spacing.large + + Loader { + active: root.isSubPage + asynchronous: true + visible: active + + sourceComponent: IconButton { + icon: "arrow_back" + inactiveColor: DynamicColors.tPalette.m3surfaceContainerHigh + inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant + isRound: true + type: IconButton.Tonal + + onClicked: root.sState.closeSubPage() + } + } + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.large + text: root.title + } + } + } + + VerticalFadeFlickable { + id: flickable + + Layout.fillHeight: true + Layout.fillWidth: true + Layout.topMargin: -topMargin + bottomMargin: Appearance.padding.extraLarge + contentHeight: root.contentChild?.implicitHeight ?? 0 + contentItem.children: [root.contentChild] + topMargin: Appearance.padding.large + } +} diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/SettingsNew/Common/PopupRow.qml new file mode 100644 index 0000000..34c2f8d --- /dev/null +++ b/Modules/SettingsNew/Common/PopupRow.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew + +ConnectedRect { + id: root + + property alias checked: switchButton.checked + property int horizontalPadding: Appearance.padding.largeIncreased + required property Component popup + property alias subtext: subtext.text + property alias text: text.text + property int verticalPadding: Appearance.padding.normal + + signal clicked(checked: bool) + + Layout.fillWidth: true + implicitHeight: layout.implicitHeight + verticalPadding * 2 + + Column { + id: layout + + anchors.left: parent.left + anchors.leftMargin: root.horizontalPadding + anchors.right: icon.left + anchors.rightMargin: Appearance.padding.normal + anchors.verticalCenter: parent.verticalCenter + + CustomText { + id: text + + font.pointSize: Appearance.font.size.smaller + } + + CustomText { + id: subtext + + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.small + wrapMode: Text.WordWrap + } + } + + MaterialIcon { + id: icon + + anchors.right: switchButton.left + anchors.rightMargin: Appearance.spacing.normal + anchors.verticalCenter: parent.verticalCenter + text: "open_in_new" + } + + StateLayer { + onClicked: PopupManager.requestOpen(root.popup) + } + + CustomSwitch { + id: switchButton + + anchors.right: parent.right + anchors.rightMargin: root.horizontalPadding + anchors.verticalCenter: parent.verticalCenter + + onClicked: root.clicked(checked) + } +} diff --git a/Modules/SettingsNew/Common/SelectRow.qml b/Modules/SettingsNew/Common/SelectRow.qml new file mode 100644 index 0000000..184a02b --- /dev/null +++ b/Modules/SettingsNew/Common/SelectRow.qml @@ -0,0 +1,64 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property alias active: splitButton.active + property alias fallbackIcon: splitButton.fallbackIcon + property alias fallbackText: splitButton.fallbackText + property alias menuItems: splitButton.menuItems + property alias menuOnTop: splitButton.menuOnTop + property string subtext + property alias text: label.text + + signal selected(item: MenuItem) + + Layout.fillWidth: true + clip: false + implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2 + z: splitButton.expanded ? 1 : 0 + + RowLayout { + id: rowLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.smaller + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: root.subtext + visible: root.subtext + } + } + + CustomSplitButton { + id: splitButton + + type: CustomSplitButton.Tonal + + menu.onItemSelected: item => root.selected(item) + stateLayer.onClicked: splitButton.expanded = !splitButton.expanded + } + } +} diff --git a/Modules/SettingsNew/Common/SettingHyprSpinner.qml b/Modules/SettingsNew/Common/SettingHyprSpinner.qml new file mode 100644 index 0000000..e18016f --- /dev/null +++ b/Modules/SettingsNew/Common/SettingHyprSpinner.qml @@ -0,0 +1,214 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Helpers + +Item { + id: root + + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name + required property string name + required property var object + required property list settings + property bool shouldBeActive: true + + function commitChoice(choice: int, setting: string): void { + root.object[setting] = choice; + Config.save(); + } + + function formattedValue(setting: string): string { + const value = root.object[setting]; + + if (value === null || value === undefined) + return ""; + + return String(value); + } + + function hourToAmPm(hour) { + var h = Number(hour) % 24; + var d = new Date(2000, 0, 1, h, 0, 0); + return Qt.formatTime(d, "h AP"); + } + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0.8 + visible: opacity > 0 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + Behavior on y { + Anim { + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + + RowLayout { + id: row + + anchors.left: parent.left + anchors.margins: Appearance.padding.small + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + CustomText { + id: text + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + font.pointSize: Appearance.font.size.larger + text: root.name + } + + CustomText { + Layout.alignment: Qt.AlignLeft + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.normal + text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]])) + } + } + + ColumnLayout { + id: optionLayout + + Layout.fillHeight: true + Layout.preferredWidth: 100 + + RowLayout { + Layout.preferredWidth: optionLayout.width + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Enabled: ") + } + + CustomSwitch { + id: enabledSwitch + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + checked: root.object[root.settings[2]] + + onToggled: { + root.object[root.settings[2]] = checked; + Config.save(); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: setting2.expanded ? -1 : 1 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Start: ") + } + + SpinnerButton { + id: setting1 + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 + Layout.preferredWidth: height * 2 + currentIndex: root.object[root.settings[0]] + enabled: enabledSwitch.checked + text: root.formattedValue(root.settings[0]) + + menu.onItemSelected: item => { + root.commitChoice(item, root.settings[0]); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: setting1.expanded ? -1 : 1 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("End: ") + } + + SpinnerButton { + id: setting2 + + Layout.alignment: Qt.AlignRight | Qt.AlignHCenter + Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 + Layout.preferredWidth: height * 2 + currentIndex: root.object[root.settings[1]] + enabled: enabledSwitch.checked + text: root.formattedValue(root.settings[1]) + + menu.onItemSelected: item => { + root.commitChoice(item, root.settings[1]); + } + } + } + + RowLayout { + Layout.preferredWidth: optionLayout.width + z: -2 + + CustomText { + Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter + Layout.fillWidth: true + text: qsTr("Temp: ") + } + + CustomRect { + id: rect + + Layout.preferredHeight: 33 + Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 200), 50) + color: DynamicColors.tPalette.m3surfaceContainerHigh + radius: Appearance.rounding.full + + CustomTextField { + id: textField + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 200) + text: root.formattedValue(root.settings[3]) + + onEditingFinished: { + root.object[root.settings[3]] = textField.text; + Config.save(); + } + } + } + } + } + } +} diff --git a/Modules/SettingsNew/Common/SpinRow.qml b/Modules/SettingsNew/Common/SpinRow.qml new file mode 100644 index 0000000..91b831d --- /dev/null +++ b/Modules/SettingsNew/Common/SpinRow.qml @@ -0,0 +1,63 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property real from: 0 + property real stepSize: 1 + property string subtext + property alias text: label.text + property real to: 99 + property real value + + signal moved(value: real) + + Layout.fillWidth: true + implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2 + + RowLayout { + id: rowLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.smaller + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: root.subtext + visible: root.subtext + } + } + + CustomSpinBox { + max: root.to + min: root.from + step: root.stepSize + value: root.value + + onValueModified: v => root.moved(v) + } + } +} diff --git a/Modules/SettingsNew/Common/StackPage.qml b/Modules/SettingsNew/Common/StackPage.qml new file mode 100644 index 0000000..867802a --- /dev/null +++ b/Modules/SettingsNew/Common/StackPage.qml @@ -0,0 +1,125 @@ +import QtQuick +import QtQuick.Controls +import qs.Components +import qs.Modules.SettingsNew +import qs.Config + +StackView { + id: root + + readonly property int animMovement: Appearance.padding.extraLarge * 2 + default property list pages + required property SettingsState sState + + function openSubPage(idx: int, immediate: bool): void { + const page = pages[idx]; + if (page) { + push(page, { + sState + }, immediate ? StackView.Immediate : StackView.PushTransition); + } else { + console.warn(logCat, "Attempted to open invalid sub-page index", idx); + sState.closeSubPage(); + } + } + + clip: busy + + popEnter: Transition { + SequentialAnimation { + PropertyAction { + property: "opacity" + value: 0 + } + + PauseAnimation { + duration: Appearance.anim.durations.expressiveEffects + } + + ParallelAnimation { + Anim { + property: "opacity" + to: 1 + type: Anim.SlowEffects + } + + Anim { + from: -root.animMovement + property: "x" + to: 0 + type: Anim.SlowEffects + } + } + } + } + popExit: Transition { + Anim { + property: "opacity" + to: 0 + type: Anim.DefaultEffects + } + } + pushEnter: Transition { + SequentialAnimation { + PropertyAction { + property: "opacity" + value: 0 + } + + PauseAnimation { + duration: Appearance.anim.durations.expressiveEffects + } + + ParallelAnimation { + Anim { + property: "opacity" + to: 1 + type: Anim.SlowEffects + } + + Anim { + from: root.animMovement + property: "x" + to: 0 + type: Anim.SlowEffects + } + } + } + } + pushExit: Transition { + Anim { + property: "opacity" + to: 0 + type: Anim.DefaultEffects + } + } + + Component.onCompleted: { + openSubPage(0, true); + for (const page of sState.subPageIdxStack) + openSubPage(page, true); + } + + LoggingCategory { + id: logCat + + defaultLogLevel: LoggingCategory.Info + name: "caelestia.nexus" + } + + Connections { + function onSubPageClosed(): void { + if (root.depth < root.sState.subPageIdxStack.length) { + console.log(logCat, "Attempted to close page while depth < stack depth. Ignoring."); + return; + } + root.pop(); + } + + function onSubPageOpened(idx: int): void { + root.openSubPage(idx, false); + } + + target: root.sState + } +} diff --git a/Modules/SettingsNew/Common/TimeInput.qml b/Modules/SettingsNew/Common/TimeInput.qml new file mode 100644 index 0000000..f922230 --- /dev/null +++ b/Modules/SettingsNew/Common/TimeInput.qml @@ -0,0 +1,595 @@ +import QtQuick +import QtQuick.Layouts +import ZShell.Components +import qs.Config +import qs.Components +import qs.Helpers + +CustomClippingRect { + id: root + + readonly property string endTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(object[settings[2]]); + return Qt.formatTime(d, "hh:mm AP"); + } + required property var object + required property list settings + property bool shouldBeActive: true + readonly property string startTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(object[settings[1]]); + return Qt.formatTime(d, "hh:mm AP"); + } + + signal applySettings(startTime: int, endTime: int) + signal close + + function convertHour(timeValue: int): int { + return Math.floor(timeValue / 60); + } + + function convertMinute(timeValue: int): int { + return timeValue % 60; + } + + function convertToMinutes(hour: int, minute: int): int { + return hour * 60 + minute; + } + + color: DynamicColors.palette.m3surfaceContainer + implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin + implicitWidth: column.implicitWidth + column.anchors.margins * 2 + radius: Appearance.rounding.large + + ColumnLayout { + id: column + + anchors.left: parent.left + anchors.margins: Appearance.padding.largeIncreased + anchors.right: parent.right + anchors.top: parent.top + spacing: Appearance.spacing.normal + + CustomText { + text: qsTr("Select time") + } + + RowLayout { + CustomRect { + id: startHourRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: startHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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: Appearance.font.size.extraLarge + text: ":" + } + + CustomRect { + id: startMinuteRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: startMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + 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: Appearance.spacing.large + } + + CustomRect { + id: endHourRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: endHourField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + 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: Appearance.font.size.extraLarge + text: ":" + } + + CustomRect { + id: endMinuteRect + + Layout.preferredHeight: 72 + Layout.preferredWidth: 96 + color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest + implicitHeight: 72 + implicitWidth: 96 + radius: Appearance.rounding.small + + CustomRect { + anchors.fill: parent + border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest + border.width: endMinuteField.focus ? 2 : 0 + radius: parent.radius - border.width + + Behavior on border.width { + Anim { + } + } + } + + CustomTextField { + id: endMinuteField + + 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface + cursorHeight: height - Appearance.padding.normal * 2 + 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(); + } + } + + event.accepted = true; + } + onCursorPositionChanged: cursorPosition = 2 + onTextEdited: { + if (endMinuteField.text === "") + 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; + } + } + } + } + + RowLayout { + CustomText { + Layout.preferredWidth: spacer.x + spacer.width + text: qsTr("Start") + } + + CustomText { + Layout.preferredWidth: endMinuteRect.width + endHourRect.width + text: qsTr("End") + } + } + } + + RowLayout { + id: buttonRow + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: Appearance.padding.largeIncreased + anchors.right: parent.right + anchors.top: column.bottom + anchors.topMargin: Appearance.spacing.normal + + Item { + id: buttonSpacer + + Layout.fillWidth: true + } + + ButtonRow { + spacing: Appearance.spacing.normal + + IconTextButton { + font.pointSize: Appearance.font.size.normal + icon: "close" + inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) + inactiveOnColor: DynamicColors.palette.m3onSurfaceVariant + isRound: true + isToggle: false + shapeMorph: true + shapeMorphExpansion: pressed ? 12 : 0 + text: "Cancel" + + onClicked: root.close() + } + + IconTextButton { + font.pointSize: Appearance.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); + } + } + } + } +} diff --git a/Modules/SettingsNew/Common/ToggleRow.qml b/Modules/SettingsNew/Common/ToggleRow.qml new file mode 100644 index 0000000..25e4168 --- /dev/null +++ b/Modules/SettingsNew/Common/ToggleRow.qml @@ -0,0 +1,82 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +CustomSwitch { + id: root + + readonly property alias bg: bg + property alias first: bg.first + property alias last: bg.last + property string subtext + + Layout.fillWidth: true + cLayer: 2 + horizontalPadding: Appearance.padding.largeIncreased + implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2 + implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2 + indicator.anchors.right: right + indicator.anchors.rightMargin: root.horizontalPadding + indicator.anchors.verticalCenter: verticalCenter + verticalPadding: Appearance.padding.normal + + background: ConnectedRect { + id: bg + + StateLayer { + id: stateLayer + + manualPressOverride: root.pressed + } + } + contentItem: Item { + anchors.left: parent.left + anchors.leftMargin: root.horizontalPadding + anchors.right: root.indicator.left + anchors.rightMargin: Appearance.spacing.normal + implicitHeight: column.implicitHeight + implicitWidth: column.implicitWidth + + Column { + id: column + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 0 + + CustomText { + id: label + + anchors.left: parent.left + anchors.right: parent.right + elide: Text.ElideRight + font: { + const f = Qt.font(label.font); + f.pointSize = Appearance.font.size.smaller; + return f; + } + text: root.text + } + + CustomText { + id: subtext + + anchors.left: parent.left + anchors.right: parent.right + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font: { + const f = Qt.font(subtext.font); + f.pointSize = Appearance.font.size.small; + return f; + } + text: root.subtext + visible: root.subtext + } + } + } + + onPressed: stateLayer.press(stateLayer.mouseX, stateLayer.mouseY) +} diff --git a/Modules/SettingsNew/Common/WallItem.qml b/Modules/SettingsNew/Common/WallItem.qml new file mode 100644 index 0000000..e681b47 --- /dev/null +++ b/Modules/SettingsNew/Common/WallItem.qml @@ -0,0 +1,62 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.Components +import qs.Config + +Item { + id: root + + property alias imgHeight: imgWrapper.implicitHeight + property alias radius: imgWrapper.radius + property alias source: img.source + + signal clicked + + Layout.fillWidth: true + implicitHeight: layout.implicitHeight + + ColumnLayout { + id: layout + + anchors.fill: parent + spacing: Appearance.spacing.small + + CustomClippingRect { + id: imgWrapper + + Layout.fillWidth: true + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: width + radius: Appearance.rounding.large + + Image { + id: img + + anchors.fill: parent + asynchronous: true + fillMode: Image.PreserveAspectCrop + opacity: status === Image.Ready ? 1 : 0 + retainWhileLoading: true + sourceSize: { + const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1; + return Qt.size(width * dpr, height * dpr); + } + + Behavior on opacity { + Anim { + type: Anim.SlowEffects + } + } + } + } + } + + StateLayer { + anchors.bottomMargin: layout.implicitHeight - imgWrapper.implicitHeight + + onClicked: root.clicked() + } +} diff --git a/Modules/SettingsNew/Common/WallpaperCropper.qml b/Modules/SettingsNew/Common/WallpaperCropper.qml new file mode 100644 index 0000000..b0888b1 --- /dev/null +++ b/Modules/SettingsNew/Common/WallpaperCropper.qml @@ -0,0 +1,314 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import ZShell.Internal +import qs.Config +import qs.Components +import qs.Helpers + +Item { + id: wrapper + + property bool changesMade: false + property bool shouldBeActive: true + + signal requestCrop + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: shouldBeActive ? 400 : 0 + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0.8 + visible: opacity > 0 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + Behavior on y { + Anim { + } + } + + IconButton { + anchors.margins: Appearance.padding.normal + anchors.right: parent.right + anchors.top: parent.top + icon: "check" + opacity: wrapper.changesMade ? 1 : 0 + scale: wrapper.changesMade ? 1 : 0 + z: 2 + + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } + + onClicked: { + wrapper.requestCrop(); + wrapper.changesMade = false; + } + } + + RowLayout { + id: root + + anchors.fill: parent + spacing: Appearance.spacing.normal + + Repeater { + model: ScriptModel { + values: [...Quickshell.screens].sort((a, b) => { + return a.x - b.x; + }) + } + + Item { + id: delegate + + required property ShellScreen modelData + + function applyCrop(): void { + if (!cropRectLoader.item) + return; + const cropRect = cropRectLoader.item; + + // We need to calculate the exact percentage coordinates that map perfectly + // to our C++ backend, regardless of current display scaling + const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; + const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; + const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; + const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; + + const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent); + + // We just pass the percentages directly to the backend + Wallpapers.setCrop(delegate.modelData.name, finalRect, cropRect.zoom); + } + + function zoomClipRect(zoom: real): void { + if (!cropRectLoader.item) + return; + const cropRect = cropRectLoader.item; + + let oldCenterX = cropRect.x + cropRect.width * 0.5; + let oldCenterY = cropRect.y + cropRect.height * 0.5; + + cropRect.zoom = zoom; + + cropRect.x = oldCenterX - cropRect.width * 0.5; + cropRect.y = oldCenterY - cropRect.height * 0.5; + + cropRect.clampToBounds(); + } + + Layout.fillHeight: true + Layout.fillWidth: true + + Connections { + function onRequestCrop(): void { + delegate.applyCrop(); + } + + target: wrapper + } + + RowLayout { + id: sliderLayout + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: 30 + + CustomSlider { + id: zoomSlider + + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.normal + Layout.preferredHeight: Appearance.padding.larger * 3 + Layout.rightMargin: Appearance.padding.normal + from: 1.0 + implicitHeight: Appearance.padding.larger * 3 + insetIcon: "crop" + to: 5.0 + value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 + + onInteraction: value => { + delegate.zoomClipRect(1 + (value * 4)); + wrapper.changesMade = true; + } + } + } + + CachingImage { + id: scaledImg + + property var displayData + property real monitorScale: 1.0 + + anchors.bottom: sliderLayout.top + anchors.bottomMargin: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + asynchronous: true + fillMode: Image.PreserveAspectFit + retainWhileLoading: true + source: Wallpapers.current + sourceSize.height: parent.height + sourceSize.width: parent.width + + onPaintedWidthChanged: { + if (paintedWidth > 0 && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onSourceChanged: { + if (cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onStatusChanged: { + if (scaledImg.status == Image.Ready && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + + CustomText { + id: monitorId + + anchors.centerIn: parent + color: Qt.alpha(DynamicColors.palette.m3surface, 0.85) + font.pointSize: Appearance.font.size.large * 4 + style: Text.Outline + styleColor: DynamicColors.palette.m3onSurface + text: delegate.modelData.name + } + + Loader { + id: cropRectLoader + + active: scaledImg.paintedWidth > 0 + + sourceComponent: Component { + CustomRect { + id: cropRect + + property real aspectRatio: delegate.modelData.width / delegate.modelData.height + readonly property real baseHeight: baseWidth / aspectRatio + readonly property real baseWidth: { + let fittedHeight = scaledImg.paintedHeight; + let fittedWidth = fittedHeight * aspectRatio; + + if (fittedWidth > scaledImg.paintedWidth) { + fittedWidth = scaledImg.paintedWidth; + fittedHeight = fittedWidth / aspectRatio; + } + + return fittedWidth; + } + readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 + readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 + property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight + property real zoom: 1.0 + + function centerInImage() { + x = imageX + (scaledImg.paintedWidth - width) / 2; + y = imageY + (scaledImg.paintedHeight - height) / 2; + } + + function clampToBounds() { + x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); + + y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); + } + + function restoreFromData() { + let data = Wallpapers.getCrop(delegate.modelData.name); + + if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { + zoom = data.zoom > 0 ? data.zoom : 1.0; + x = imageX + (data.x * scaledImg.paintedWidth); + y = imageY + (data.y * scaledImg.paintedHeight); + + clampToBounds(); + } else { + zoom = 1.0; + centerInImage(); + } + } + + border.color: DynamicColors.palette.m3primary + border.width: 2 + height: baseHeight / zoom + opacity: 1 + width: baseWidth / zoom + + Behavior on opacity { + Anim { + } + } + + Component.onCompleted: { + restoreFromData(); + } + onHeightChanged: clampToBounds() + onWidthChanged: clampToBounds() + } + } + } + + MouseArea { + id: mouse + + function updateCrop(mouseX, mouseY) { + if (!cropRectLoader.item) + return; + const cropRect = cropRectLoader.item; + + let nx = mouseX - cropRect.width * 0.5; + let ny = mouseY - cropRect.height * 0.5; + + nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width)); + + ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height)); + + cropRect.x = nx; + cropRect.y = ny; + } + + anchors.fill: parent + hoverEnabled: true + preventStealing: true + + onPositionChanged: mouse => { + if (pressed) { + updateCrop(mouse.x, mouse.y); + wrapper.changesMade = true; + } + } + onPressed: mouse => { + updateCrop(mouse.x, mouse.y); + wrapper.changesMade = true; + } + onReleased: { + wrapper.changesMade = true; + } + } + } + } + } + } +} diff --git a/Modules/SettingsNew/Content.qml b/Modules/SettingsNew/Content.qml new file mode 100644 index 0000000..026c2f6 --- /dev/null +++ b/Modules/SettingsNew/Content.qml @@ -0,0 +1,72 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import ZShell.Blobs +import qs.Components +import qs.Config + +CustomClippingRect { + id: root + + property color blobColor: DynamicColors.tPalette.m3surfaceContainerLow + readonly property real ratio: 16.0 / 9.0 + readonly property SettingsState sState: SettingsState { + id: sState + + onClose: root.close() + } + + signal close + + implicitHeight: sState.screen.height * 0.7 + implicitWidth: implicitHeight * ratio + radius: Appearance.rounding.large + Appearance.padding.normal + + Behavior on blobColor { + CAnim { + } + } + + BlobGroup { + id: blobGroup + + color: root.blobColor + smoothing: Appearance.rounding.normal + } + + BlobInvertedRect { + anchors.fill: parent + borderBottom: Appearance.padding.normal + borderLeft: navPane.width + navPane.anchors.margins * 2 + borderRight: Appearance.padding.normal + borderTop: Appearance.padding.normal + group: blobGroup + opacity: root.blobColor.a + radius: Appearance.rounding.large + } + + NavPane { + id: navPane + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: Appearance.padding.large + anchors.top: parent.top + sState: root.sState + width: Math.min(600, Math.round(root.width / 3)) + } + + Pages { + anchors.bottom: parent.bottom + anchors.left: navPane.right + anchors.leftMargin: navPane.anchors.margins + anchors.margins + anchors.margins: Appearance.padding.extraLarge + anchors.right: parent.right + anchors.top: parent.top + sState: root.sState + } + + PopupOverlay { + anchors.fill: parent + } +} diff --git a/Modules/SettingsNew/NavPane.qml b/Modules/SettingsNew/NavPane.qml new file mode 100644 index 0000000..a2ef504 --- /dev/null +++ b/Modules/SettingsNew/NavPane.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Layouts +import qs.Modules.SettingsNew.NavPane +import qs.Config + +ColumnLayout { + id: root + + required property SettingsState sState + + spacing: Appearance.spacing.large + + SearchBar { + Layout.fillWidth: true + sState: root.sState + } + + NavLocations { + Layout.bottomMargin: -bottomMargin + Layout.fillHeight: true + Layout.fillWidth: true + Layout.topMargin: -topMargin + sState: root.sState + } +} diff --git a/Modules/SettingsNew/NavPane/NavLocations.qml b/Modules/SettingsNew/NavPane/NavLocations.qml new file mode 100644 index 0000000..0b8b41a --- /dev/null +++ b/Modules/SettingsNew/NavPane/NavLocations.qml @@ -0,0 +1,125 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Modules.SettingsNew + +Flickable { + id: root + + required property SettingsState sState + + bottomMargin: Appearance.padding.large + contentHeight: content.implicitHeight + topMargin: Appearance.padding.large + + ColumnLayout { + id: content + + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.spacing.extraSmall + + Repeater { + id: list + + model: PageRegistry.pages + + CustomRect { + id: item + + required property int index + readonly property bool isCategoryEnd: index === list.model.length - 1 || PageRegistry.pages[index + 1].category !== modelData.category + readonly property bool isCategoryStart: index === 0 || PageRegistry.pages[index - 1].category !== modelData.category + readonly property bool isCurrentPage: index === root.sState.currentPageIdx + required property var modelData + + Layout.fillWidth: true + Layout.topMargin: index !== 0 && isCategoryStart ? Appearance.spacing.small : 0 + bottomLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall + bottomRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryEnd ? Appearance.rounding.normal : Appearance.rounding.extraSmall + color: isCurrentPage ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2) + implicitHeight: { + const h = layout.implicitHeight + layout.anchors.margins * 2; + return h % 2 === 0 ? h : h + 1; + } + topLeftRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall + topRightRadius: stateLayer.pressed ? Appearance.rounding.medium : isCurrentPage ? Appearance.rounding.large : isCategoryStart ? Appearance.rounding.normal : Appearance.rounding.extraSmall + + RadiusBehavior on bottomLeftRadius { + } + RadiusBehavior on bottomRightRadius { + } + RadiusBehavior on topLeftRadius { + } + RadiusBehavior on topRightRadius { + } + + StateLayer { + id: stateLayer + + anchors.fill: parent + bottomLeftRadius: parent.bottomLeftRadius + bottomRightRadius: parent.bottomRightRadius + topLeftRadius: parent.topLeftRadius + topRightRadius: parent.topRightRadius + + onClicked: root.sState.currentPageIdx = item.index + } + + RowLayout { + id: layout + + anchors.fill: parent + anchors.margins: Appearance.padding.large + spacing: Appearance.spacing.small + + CustomRect { + Layout.bottomMargin: -1 + Layout.fillHeight: true + Layout.topMargin: -1 + color: item.isCurrentPage ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer + implicitWidth: height + radius: Appearance.rounding.full + + MaterialIcon { + anchors.centerIn: parent + anchors.verticalCenterOffset: 1 + color: item.isCurrentPage ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer + fill: item.modelData.noFill ? 0 : 1 + font.pointSize: Appearance.font.size.large + grade: 25 + text: item.modelData.icon + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + text: item.modelData.name + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + text: item.modelData.description + } + } + } + } + } + } + + component RadiusBehavior: Behavior { + Anim { + type: Anim.DefaultEffects + } + } +} diff --git a/Modules/SettingsNew/NavPane/SearchBar.qml b/Modules/SettingsNew/NavPane/SearchBar.qml new file mode 100644 index 0000000..0dd0844 --- /dev/null +++ b/Modules/SettingsNew/NavPane/SearchBar.qml @@ -0,0 +1,75 @@ +import QtQuick +import QtQuick.Layouts +import qs.Modules.SettingsNew +import qs.Components +import qs.Config + +CustomRect { + id: root + + required property SettingsState sState + + border.color: DynamicColors.palette.m3outlineVariant + color: DynamicColors.tPalette.m3surfaceContainerLowest + implicitHeight: searchLayout.implicitHeight + Appearance.padding.normal * 2 + radius: Appearance.rounding.full + + Behavior on border.color { + CAnim { + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.IBeamCursor + + onClicked: searchField.focus = true + } + + RowLayout { + id: searchLayout + + anchors.left: parent.left + anchors.margins: Appearance.padding.large + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Appearance.spacing.small + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + text: "search" + } + + CustomTextField { + id: searchField + + Layout.fillHeight: true + Layout.fillWidth: true + color: DynamicColors.palette.m3onSurfaceVariant + placeholderText: qsTr("Search settings") + placeholderTextColor: DynamicColors.palette.m3onSurfaceVariant + + Binding { + property: "searchOpen" + target: root.sState + value: searchField.text.length > 0 + } + } + + IconButton { + icon: "close" + isRound: true + opacity: searchField.text.length > 0 ? 1 : 0 + padding: Appearance.padding.extraSmall + type: IconButton.Text + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + + onClicked: searchField.clear() + } + } +} diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml new file mode 100644 index 0000000..0ab4db1 --- /dev/null +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -0,0 +1,73 @@ +pragma Singleton + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config +import qs.Modules.SettingsNew.Common +import qs.Modules.SettingsNew.Pages +import qs.Modules.SettingsNew.Pages.Wallpaper + +QtObject { + id: root + + readonly property list pageComps: [ + // Appearance + Component { + // Wallpaper & style + StackPage { + Component { + Wallpaper { + } + } + + Component { + WallpaperSelect { + } + } + } + }, + + // Screenshot + Component { + StackPage { + Component { + Screenshot { + } + } + } + } + ] + readonly property Component placeholderComp: Component { + PlaceholderComp { + } + } + + component PlaceholderComp: Item { + property SettingsState sState // To avoid the warning from non-existent property + + ColumnLayout { + anchors.centerIn: parent + spacing: Appearance.padding.extraSmall + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outlineVariant + font.pointSize: Appearance.font.size.extraLarge + text: "handyman" + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outlineVariant + text: qsTr("Page under construction") + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outlineVariant + text: qsTr("This page will be available in a future update.") + } + } + } +} diff --git a/Modules/SettingsNew/PageRegistry.qml b/Modules/SettingsNew/PageRegistry.qml new file mode 100644 index 0000000..e116e6a --- /dev/null +++ b/Modules/SettingsNew/PageRegistry.qml @@ -0,0 +1,64 @@ +pragma Singleton + +import QtQuick +import Quickshell + +Singleton { + id: root + + readonly property list pages: [ + // Appearance + { + name: qsTr("Appearance"), + icon: "colors", + description: qsTr("Colors, animations, font, wallpaper"), + category: "appearance" + }, + { + name: qsTr("Screenshot"), + icon: "screenshot_region", + description: qsTr("Set screenshot effects"), + category: "appearance" + }, + + // Connectivity + { + name: qsTr("Network"), + icon: "wifi", + description: qsTr("Wi-Fi, ethernet"), + category: "connectivity" + }, + { + name: qsTr("Connected devices"), + icon: "devices_other", + description: qsTr("Bluetooth, paiting"), + category: "connectivity" + }, + { + name: qsTr("Audio"), + icon: "volume_up", + description: qsTr("App volumes, sound devices"), + category: "connectivity" + }, + + // Shell + { + name: qsTr("Panels"), + icon: "dock_to_bottom", + description: qsTr("Bar, dashboard, launcher, sidebar"), + category: "shell" + }, + { + name: qsTr("Apps"), + icon: "apps", + description: qsTr("Default apps, favorites, hidden apps"), + category: "shell" + }, + { + name: qsTr("Services"), + icon: "build", + description: qsTr("Poll intervals, audio and brightness increments"), + category: "shell" + }, + ] +} diff --git a/Modules/SettingsNew/Pages.qml b/Modules/SettingsNew/Pages.qml new file mode 100644 index 0000000..e85acd0 --- /dev/null +++ b/Modules/SettingsNew/Pages.qml @@ -0,0 +1,100 @@ +import QtQuick +import qs.Components +import qs.Config + +Item { + id: root + + property int animOff + property Item currentItem + property int lastPageIdx + required property SettingsState sState + + function loadPage(idx: int): void { + if (currentItem) + currentItem.destroy(); + + const comp = PageCompRegistry.pageComps[idx] ?? PageCompRegistry.placeholderComp; + const incubator = comp.incubateObject(container, { + sState + }); + + const attach = () => { + incubator.object.anchors.fill = container; + currentItem = incubator.object; + }; + + if (incubator.status === Component.Ready) + attach(); + else + incubator.onStatusChanged = status => { + if (status === Component.Ready) + attach(); + }; + } + + Item { + id: container + + anchors.fill: parent + layer.enabled: opacity < 1 + objectName: "PageContainer" + + Component.onCompleted: root.loadPage(root.sState.currentPageIdx) + } + + Connections { + function onCurrentPageIdxChanged(): void { + switchAnim.complete(); + root.animOff = Appearance.padding.normal * (root.sState.currentPageIdx > root.lastPageIdx ? 1 : -1); + switchAnim.start(); + root.lastPageIdx = root.sState.currentPageIdx; + } + + target: root.sState + } + + SequentialAnimation { + id: switchAnim + + Anim { + property: "opacity" + target: container + to: 0 + type: Anim.DefaultEffects + } + + ScriptAction { + script: root.loadPage(root.sState.currentPageIdx) + } + + PropertyAction { + property: "topMargin" + target: container.anchors + value: root.animOff + } + + PropertyAction { + property: "bottomMargin" + target: container.anchors + value: -root.animOff + } + + ParallelAnimation { + Anim { + from: 0 + property: "opacity" + target: container + to: 1 + type: Anim.SlowEffects + } + + Anim { + properties: "topMargin,bottomMargin" + target: container.anchors + to: 0 + type: Anim.SlowEffects + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Screenshot.qml b/Modules/SettingsNew/Pages/Screenshot.qml new file mode 100644 index 0000000..a4c3cb3 --- /dev/null +++ b/Modules/SettingsNew/Pages/Screenshot.qml @@ -0,0 +1,137 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import ZShell.Components +import qs.Helpers +import qs.Components +import qs.Config +import qs.Modules.SettingsNew +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + title: qsTr("Screenshot effects") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.large + width: root.cappedWidth + + ToggleRow { + checked: Config.screenshot.enable_pp + first: true + text: qsTr("Enable effects") + + onToggled: Config.screenshot.enable_pp = checked + } + + SelectRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] + enabled: Config.screenshot.enable_pp + last: true + subtext: qsTr("Automatic or manual effect values") + text: qsTr("Effects mode") + + menuItems: [ + MenuItem { + icon: "build" + text: qsTr("Manual") + value: "manual" + }, + MenuItem { + icon: "rotate_auto" + text: qsTr("Auto") + value: "auto" + } + ] + + onSelected: item => { + Config.screenshot.mode = item.value; + Config.save(); + } + } + + ToggleRow { + checked: Config.screenshot.rounding + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" + first: true + text: qsTr("Enable rounded corners") + + onToggled: Config.screenshot.rounding = checked + } + + SpinRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding + from: 0 + stepSize: 1 + text: qsTr("Corner radius") + to: 50 + value: Config.screenshot.radius + + onMoved: value => { + const newVal = Math.floor(value); + Config.screenshot.radius = newVal; + } + } + + ToggleRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + checked: Config.screenshot.shadow + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" + text: qsTr("Enable shadow") + + onToggled: Config.screenshot.shadow = checked + } + + SpinRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow + from: 0 + stepSize: 1 + text: qsTr("Shadow blur amount") + to: 100 + value: Config.screenshot.shadow_blur + + onMoved: value => { + const newVal = Math.floor(value); + Config.screenshot.shadow_blur = newVal; + } + } + + SpinRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow + from: -100 + stepSize: 10 + text: qsTr("Shadow horizontal offset") + to: 100 + value: Config.screenshot.shadow_offset_x + + onMoved: value => { + const newVal = Math.floor(value); + Config.screenshot.shadow_offset_x = newVal; + } + } + + SpinRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow + from: -100 + last: true + stepSize: 10 + text: qsTr("Shadow vertical offset") + to: 100 + value: Config.screenshot.shadow_offset_y + + onMoved: value => { + const newVal = Math.floor(value); + Config.screenshot.shadow_offset_y = newVal; + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Wallpaper.qml b/Modules/SettingsNew/Pages/Wallpaper.qml new file mode 100644 index 0000000..4edf637 --- /dev/null +++ b/Modules/SettingsNew/Pages/Wallpaper.qml @@ -0,0 +1,220 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import ZShell.Components +import qs.Helpers +import qs.Components +import qs.Config +import qs.Modules.SettingsNew +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + title: qsTr("Wallpaper & style") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.large + width: root.cappedWidth + + CustomClippingRect { + id: wallWrapper + + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: { + const screen = root.sState.screen; + const cWidth = root.cappedWidth; + return Math.min(Math.round(cWidth * 0.4), cWidth / screen.width * screen.height); + } + implicitWidth: { + const screen = root.sState.screen; + return implicitHeight / screen.height * screen.width; + } + radius: Appearance.rounding.large + + Loader { + active: opacity > 0 + anchors.centerIn: parent + opacity: Config.background.enabled ? 0 : 1 + + Behavior on opacity { + Anim { + type: Anim.SlowEffects + } + } + sourceComponent: ColumnLayout { + spacing: Appearance.spacing.extraSmall + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3onSurfaceVariant + text: "hide_image" + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3onSurfaceVariant + text: qsTr("Wallpaper disabled") + } + } + } + + Item { + anchors.fill: parent + opacity: Config.background.enabled ? 1 : 0 + + Behavior on opacity { + Anim { + type: Anim.SlowEffects + } + } + + Loader { + id: wallIndicatorLoader + + active: opacity > 0 + anchors.fill: parent + opacity: 0 + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + sourceComponent: CustomRect { + color: DynamicColors.palette.m3primaryContainer + radius: Appearance.rounding.normal + } + } + + Timer { + id: wallLoadDebounceTimer + + interval: 100 + + onTriggered: { + if (wallImg.status !== Image.Ready) + wallIndicatorLoader.opacity = 1; + } + } + + FadeImage { + id: wallImg + + anchors.fill: parent + fadeInAnim: Anim.SlowEffects + fadeOutAnim: Anim.DefaultEffects + preventInit: wallIndicatorLoader.opacity > 0 + source: Wallpapers.current + + onSourceChanged: wallLoadDebounceTimer.restart() + onStatusChanged: { + if (status === Image.Ready) { + wallLoadDebounceTimer.stop(); + wallIndicatorLoader.opacity = 0; + } + } + } + } + } + + IconTextButton { + Layout.alignment: Qt.AlignHCenter + enabled: Config.background.enabled + horizontalPadding: Appearance.padding.extraLarge + icon: "wallpaper" + isRound: true + shapeMorph: true + text: qsTr("Wallpapers") + type: IconTextButton.Tonal + verticalPadding: Appearance.padding.normal + + onClicked: root.sState.openSubPage(1) // Wallpaper page + } + + ToggleRow { + checked: Config.background.enabled + first: true + text: qsTr("Display wallpaper") + + onToggled: Config.background.enabled = checked + } + + ToggleRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + checked: DynamicColors.transparency.enabled + subtext: qsTr("Base %1, layers %2").arg(DynamicColors.transparency.base).arg(DynamicColors.transparency.layers) + text: qsTr("Transparency") + + onToggled: Config.appearance.transparency.enabled = checked + } + + ToggleRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + checked: !DynamicColors.light + last: true + text: qsTr("Dark theme") + + onToggled: DynamicColors.setMode(checked ? "dark" : "light") + } + + PopupRow { + checked: Config.general.color.scheduleDark + first: true + subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleDarkStart).arg(Config.general.color.scheduleDarkEnd) + text: 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; + Config.save(); + ModeScheduler.checkStartup(); + PopupManager.requestClose(); + } + onClose: PopupManager.requestClose() + } + } + + onClicked: value => { + Config.general.color.scheduleDark = value; + } + } + + PopupRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + checked: Config.general.color.scheduleHyprsunset + last: true + subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleHyprsunsetStart).arg(Config.general.color.scheduleHyprsunsetEnd) + text: 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; + Config.save(); + Hyprsunset.checkStartup(); + PopupManager.requestClose(); + } + onClose: PopupManager.requestClose() + } + } + + onClicked: value => { + Config.general.color.scheduleHyprsunset = value; + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml b/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml new file mode 100644 index 0000000..2578303 --- /dev/null +++ b/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml @@ -0,0 +1,135 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import ZShell.Models +import qs.Paths +import qs.Helpers +import qs.Components +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Wallpapers") + + Item { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + implicitHeight: childrenRect.height + + WallpaperCropper { + id: cropper + } + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.margins: Appearance.spacing.normal + anchors.top: cropper.bottom + spacing: Appearance.spacing.small + width: root.cappedWidth + + CustomText { + Layout.topMargin: Appearance.spacing.large + font.pointSize: Appearance.font.size.large + text: qsTr("Wallpapers") + } + + GridLayout { + Layout.fillWidth: true + columnSpacing: Appearance.spacing.extraSmall + columns: 4 + rowSpacing: Appearance.spacing.extraSmall + visible: localWalls.count > 0 + + Repeater { + id: localWalls + + model: { + const walls = Wallpapers.list; + var baseDir = Paths.wallsdir; + const categories = {}; + const list = []; + for (const w of walls) { + var parentDir = w.parentDir; + if (!parentDir.endsWith("/")) + parentDir = parentDir + "/"; + + if (!baseDir.endsWith("/")) + baseDir = baseDir + "/"; + + if (parentDir !== baseDir) { + const category = Wallpapers.getCategoryFor(w); + if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0)) + categories[category] = w; + } else { + list.push(w); + } + } + list.push(...Object.values(categories)); + list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name)); + while (list.length < 4) + list.push(null); + + return list; + } + + WallItem { + required property FileSystemEntry modelData + + enabled: modelData + + // Empty placeholders for sizing + opacity: modelData ? 1 : 0 + source: String(modelData?.path ?? "") + + onClicked: { + if (modelData.parentDir !== Paths.wallsdir) { + root.sState.selectedWallpaperCategory = Wallpapers.getCategoryFor(modelData); + root.sState.openSubPage(2); // Category page + } else { + Wallpapers.setWallpaper(modelData.path); + root.sState.closeSubPage(); + } + } + } + } + } + + Loader { + Layout.fillWidth: true + active: localWalls.count === 0 + asynchronous: true + visible: active + + sourceComponent: CustomRect { + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3 + radius: Appearance.rounding.large + + ColumnLayout { + id: noWallsLayout + + anchors.centerIn: parent + spacing: Appearance.spacing.extraSmall + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outline + text: "hide_image" + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outline + text: qsTr("No local wallpapers found") + } + } + } + } + } + } +} diff --git a/Modules/SettingsNew/PopupManager.qml b/Modules/SettingsNew/PopupManager.qml new file mode 100644 index 0000000..4b37c02 --- /dev/null +++ b/Modules/SettingsNew/PopupManager.qml @@ -0,0 +1,20 @@ +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/SettingsNew/PopupOverlay.qml b/Modules/SettingsNew/PopupOverlay.qml new file mode 100644 index 0000000..e3f0aca --- /dev/null +++ b/Modules/SettingsNew/PopupOverlay.qml @@ -0,0 +1,46 @@ +import QtQuick +import qs.Config +import qs.Components + +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(DynamicColors.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/SettingsNew/SettingsState.qml b/Modules/SettingsNew/SettingsState.qml new file mode 100644 index 0000000..06a753d --- /dev/null +++ b/Modules/SettingsNew/SettingsState.qml @@ -0,0 +1,33 @@ +import QtQuick +import Quickshell +import Quickshell.Bluetooth + +QtObject { + id: root + + property bool animatingContainer + property int currentPageIdx + property bool isWindow + property ShellScreen screen + property bool searchOpen + property DesktopEntry selectedApp + property BluetoothDevice selectedBtDevice + property string selectedWallpaperCategory + property list subPageIdxStack + + signal close + signal subPageClosed + signal subPageOpened(idx: int) + + function closeSubPage(): void { + subPageClosed(); + subPageIdxStack.pop(); + } + + function openSubPage(idx: int): void { + subPageIdxStack.push(idx); + subPageOpened(idx); + } + + onCurrentPageIdxChanged: subPageIdxStack.length = 0 +} diff --git a/Modules/SettingsNew/Wrapper.qml b/Modules/SettingsNew/Wrapper.qml new file mode 100644 index 0000000..1895ae7 --- /dev/null +++ b/Modules/SettingsNew/Wrapper.qml @@ -0,0 +1,47 @@ +import Quickshell +import QtQuick +import qs.Components +import qs.Config +import qs.Helpers + +Item { + id: root + + property real offsetScale: shouldBeActive ? 0 : 1 + required property var panels + required property ShellScreen screen + readonly property bool shouldBeActive: visibilities.settings + required property PersistentProperties visibilities + + implicitHeight: content.implicitHeight + implicitWidth: content.implicitWidth + opacity: 1 - offsetScale + visible: offsetScale < 1 + + Behavior on offsetScale { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + + CustomClippingRect { + anchors.fill: parent + + Loader { + id: content + + active: root.shouldBeActive || root.visible + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + + sourceComponent: Content { + sState.animatingContainer: content.opacity < 1 + sState.currentPageIdx: ["wallpaper"][0] + sState.screen: root.screen + + onClose: console.log("shouldclose") + } + } + } +} diff --git a/Paths/Paths.qml b/Paths/Paths.qml index 27131f1..4847062 100644 --- a/Paths/Paths.qml +++ b/Paths/Paths.qml @@ -19,7 +19,7 @@ Singleton { readonly property string recsdir: Quickshell.env("ZSHELL_RECORDINGS_DIR") || `${videos}/Recordings` readonly property string state: `${Quickshell.env("XDG_STATE_HOME") || `${home}/.local/state`}/zshell` readonly property string videos: Quickshell.env("XDG_VIDEOS_DIR") || `${home}/Videos` - readonly property string wallsdir: Quickshell.env("ZSHELL_WALLPAPERS_DIR") || absolutePath(Config.wallpaperPath) + readonly property string wallsdir: Quickshell.env("ZSHELL_WALLPAPERS_DIR") || absolutePath(Config.general.wallpaperPath) function absolutePath(path: string): string { return toLocalFile(path.replace("~", home)); -- 2.47.3 From 6a80961b2b7c4a079018d14c2557e6807d8dadb4 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 22:32:56 +0200 Subject: [PATCH 02/17] added audio and apps page in settings --- Components/StateLayer.qml | 3 +- Components/VerticalFadeListView.qml | 72 +++ Config/Config.qml | 2 + Config/Launcher.qml | 2 + Helpers/Strings.qml | 26 + Modules/Launcher/Items/AppItem.qml | 15 + Modules/Launcher/Services/Apps.qml | 3 +- .../SettingsNew/Common/AudioDeviceList.qml | 96 ++++ Modules/SettingsNew/Common/BlobPopup.qml | 127 +++++ Modules/SettingsNew/Common/ItemList.qml | 116 ++++ Modules/SettingsNew/Common/NavRow.qml | 70 +++ Modules/SettingsNew/Common/OverlayRow.qml | 68 +++ Modules/SettingsNew/Common/PageBase.qml | 1 + Modules/SettingsNew/Common/PopupRow.qml | 155 ++++-- Modules/SettingsNew/Common/SectionHeader.qml | 16 + Modules/SettingsNew/Common/SliderRow.qml | 81 +++ Modules/SettingsNew/Common/StackPage.qml | 2 +- .../SettingsNew/Common/WallpaperCropper.qml | 511 +++++++++--------- Modules/SettingsNew/PageCompRegistry.qml | 53 +- Modules/SettingsNew/Pages/Apps/AllApps.qml | 100 ++++ Modules/SettingsNew/Pages/Apps/AppInfo.qml | 172 ++++++ Modules/SettingsNew/Pages/AppsPage.qml | 177 ++++++ .../SettingsNew/Pages/Audio/AppVolumes.qml | 69 +++ Modules/SettingsNew/Pages/AudioPage.qml | 136 +++++ Modules/SettingsNew/Pages/Screenshot.qml | 3 +- Modules/SettingsNew/Pages/Wallpaper.qml | 99 +--- .../Pages/Wallpaper/WallpaperSelect.qml | 158 +++--- Plugins/ZShell/appdb.cpp | 377 +++++++------ Plugins/ZShell/appdb.hpp | 10 + 29 files changed, 2080 insertions(+), 640 deletions(-) create mode 100644 Components/VerticalFadeListView.qml create mode 100644 Helpers/Strings.qml create mode 100644 Modules/SettingsNew/Common/AudioDeviceList.qml create mode 100644 Modules/SettingsNew/Common/BlobPopup.qml create mode 100644 Modules/SettingsNew/Common/ItemList.qml create mode 100644 Modules/SettingsNew/Common/NavRow.qml create mode 100644 Modules/SettingsNew/Common/OverlayRow.qml create mode 100644 Modules/SettingsNew/Common/SectionHeader.qml create mode 100644 Modules/SettingsNew/Common/SliderRow.qml create mode 100644 Modules/SettingsNew/Pages/Apps/AllApps.qml create mode 100644 Modules/SettingsNew/Pages/Apps/AppInfo.qml create mode 100644 Modules/SettingsNew/Pages/AppsPage.qml create mode 100644 Modules/SettingsNew/Pages/Audio/AppVolumes.qml create mode 100644 Modules/SettingsNew/Pages/AudioPage.qml diff --git a/Components/StateLayer.qml b/Components/StateLayer.qml index 613e810..d3eab5b 100644 --- a/Components/StateLayer.qml +++ b/Components/StateLayer.qml @@ -20,6 +20,7 @@ MouseArea { return (Math.sqrt(Math.max(d1, d2, d3, d4)) + (shapeMorph ? 24 : 0)) * 1.3; } property real endRadiusAtPress + property bool manualHoverOverride property bool manualPressOverride property real pressX: width / 2 property real pressY: height / 2 @@ -27,7 +28,7 @@ MouseArea { readonly property alias rect: base property bool shapeMorph property bool showHoverBackground: true - property real stateOpacity: containsMouse ? 0.08 : 0 + property real stateOpacity: containsMouse || manualHoverOverride ? 0.08 : 0 property alias topLeftRadius: base.topLeftRadius property alias topRightRadius: base.topRightRadius diff --git a/Components/VerticalFadeListView.qml b/Components/VerticalFadeListView.qml new file mode 100644 index 0000000..0c35590 --- /dev/null +++ b/Components/VerticalFadeListView.qml @@ -0,0 +1,72 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Effects + +CustomListView { + id: root + + property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 + property real fadeAmount: 0.2 + property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 + + function fadeShouldBeActive(isStart: bool): bool { + // When content is smaller than flickable size, hide fade when rebound starts + if (contentHeight + topMargin + bottomMargin < height && rebound.running && ((isStart ? verticalOvershoot > 0 : verticalOvershoot < 0))) + return false; + + if (isStart) + return visibleArea.yPosition > 0; + return visibleArea.yPosition + visibleArea.heightRatio < 1; + } + + flickableDirection: Flickable.VerticalFlick + layer.enabled: true + orientation: ListView.Vertical + + Behavior on bottomFadeOpacity { + Anim { + type: Anim.SlowEffects + } + } + layer.effect: Mask { + maskSource: mask + + Rectangle { + id: mask + + anchors.fill: parent + layer.enabled: true + visible: false + + gradient: Gradient { + orientation: Gradient.Vertical + + GradientStop { + color: Qt.rgba(0, 0, 0, root.topFadeOpacity) + position: 0 + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 1) + position: root.fadeAmount + } + + GradientStop { + color: Qt.rgba(0, 0, 0, 1) + position: 1 - root.fadeAmount + } + + GradientStop { + color: Qt.rgba(0, 0, 0, root.bottomFadeOpacity) + position: 1 + } + } + } + } + Behavior on topFadeOpacity { + Anim { + type: Anim.SlowEffects + } + } +} diff --git a/Config/Config.qml b/Config/Config.qml index 44d07da..422d81b 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -245,6 +245,8 @@ Singleton { return { maxAppsShown: launcher.maxAppsShown, maxWallpapers: launcher.maxWallpapers, + hiddenApps: launcher.hiddenApps, + favoriteApps: launcher.favoriteApps, uwsm: launcher.uwsm, actionPrefix: launcher.actionPrefix, specialPrefix: launcher.specialPrefix, diff --git a/Config/Launcher.qml b/Config/Launcher.qml index 5ab58cc..07070e3 100644 --- a/Config/Launcher.qml +++ b/Config/Launcher.qml @@ -84,6 +84,8 @@ JsonObject { dangerous: false }, ] + property list favoriteApps: [] + property list hiddenApps: [] property int maxAppsShown: 10 property int maxWallpapers: 7 property Sizes sizes: Sizes { diff --git a/Helpers/Strings.qml b/Helpers/Strings.qml new file mode 100644 index 0000000..f1980b5 --- /dev/null +++ b/Helpers/Strings.qml @@ -0,0 +1,26 @@ +pragma Singleton + +import Quickshell + +Singleton { + property var _regexCache: ({}) + + function testRegexList(filterList: list, target: string): bool { + const regexChecker = /^\^.*\$$/; + for (const filter of filterList) { + if (regexChecker.test(filter)) { + let re = _regexCache[filter]; + if (!re) { + re = new RegExp(filter); + _regexCache[filter] = re; + } + if (re.test(target)) + return true; + } else { + if (filter === target) + return true; + } + } + return false; + } +} diff --git a/Modules/Launcher/Items/AppItem.qml b/Modules/Launcher/Items/AppItem.qml index b553649..0123c13 100644 --- a/Modules/Launcher/Items/AppItem.qml +++ b/Modules/Launcher/Items/AppItem.qml @@ -65,5 +65,20 @@ Item { width: root.width - icon.width - Appearance.rounding.normal * 2 } } + + Loader { + id: favoriteIcon + + active: root.modelData && Strings.testRegexList(Config.launcher.favoriteApps, root.modelData.id) + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + asynchronous: true + + sourceComponent: MaterialIcon { + color: DynamicColors.palette.m3primary + fill: 1 + text: "favorite" + } + } } } diff --git a/Modules/Launcher/Services/Apps.qml b/Modules/Launcher/Services/Apps.qml index dbd10fd..d137223 100644 --- a/Modules/Launcher/Services/Apps.qml +++ b/Modules/Launcher/Services/Apps.qml @@ -74,7 +74,8 @@ Searcher { AppDb { id: appDb - entries: DesktopEntries.applications.values + entries: DesktopEntries.applications.values.filter(a => !Strings.testRegexList(Config.launcher.hiddenApps, a.id)) + favoriteApps: Config.launcher.favoriteApps path: `${Paths.state}/apps.sqlite` } } diff --git a/Modules/SettingsNew/Common/AudioDeviceList.qml b/Modules/SettingsNew/Common/AudioDeviceList.qml new file mode 100644 index 0000000..0c38960 --- /dev/null +++ b/Modules/SettingsNew/Common/AudioDeviceList.qml @@ -0,0 +1,96 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import qs.Components +import qs.Config + +ItemList { + id: root + + property int currentId: -1 + property string iconName: "speaker" + property var nodes: [] + + signal selected(node: PwNode) + + last: true + showList: true + + delegate: Item { + id: device + + readonly property bool active: device.modelData?.id === root.currentId + required property int index + required property PwNode modelData + + anchors.left: root.list.contentItem.left + anchors.right: root.list.contentItem.right + implicitHeight: deviceLayout.implicitHeight + deviceLayout.anchors.margins * 2 + + StateLayer { + bottomLeftRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius + bottomRightRadius: device.index === root?.list.count - 1 ? Appearance.rounding.large : radius + radius: Appearance.rounding.extraSmall + + onClicked: root.selected(device.modelData) + } + + RowLayout { + id: deviceLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.large + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.normal + + CustomRect { + color: device.active ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondaryContainer + implicitHeight: devIcon.implicitHeight + Appearance.padding.normal * 2 + implicitWidth: implicitHeight + radius: Appearance.rounding.full + + MaterialIcon { + id: devIcon + + anchors.centerIn: parent + color: device.active ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSecondaryContainer + fill: device.active ? 1 : 0 + font.pointSize: Appearance.font.size.large + text: root.iconName + + Behavior on fill { + Anim { + } + } + } + } + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.smaller + text: device.modelData?.description || device.modelData?.name || qsTr("Unknown") + } + + MaterialIcon { + color: DynamicColors.palette.m3primary + font.pointSize: Appearance.font.size.large + opacity: device.active ? 1 : 0 + text: "check" + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + } + } + } + model: ScriptModel { + values: [...root.nodes].sort((a, b) => (a.description || a.name || "").localeCompare(b.description || b.name || "")) + } +} diff --git a/Modules/SettingsNew/Common/BlobPopup.qml b/Modules/SettingsNew/Common/BlobPopup.qml new file mode 100644 index 0000000..c4226b4 --- /dev/null +++ b/Modules/SettingsNew/Common/BlobPopup.qml @@ -0,0 +1,127 @@ +import QtQuick +import ZShell.Blobs +import qs.Components +import qs.Config + +Item { + id: root + + property real animDriver + property alias color: blobGroup.color + default required property Item content + property bool hoverOverride + readonly property alias hovered: btn.containsMouse + property alias icon: icon.text + property bool open + property int padding + property bool pressOverride + property int topMovement: Appearance.padding.large + + implicitHeight: btn.implicitHeight * 0.9 + implicitWidth: btn.implicitWidth * 0.9 + + Binding { + property: "opacity" + target: root.content + value: root.animDriver + } + + BlobGroup { + id: blobGroup + + color: DynamicColors.palette.m3surfaceContainerHighest + smoothing: Appearance.rounding.medium + + Behavior on color { + CAnim { + } + } + } + + BlobRect { + id: btnRect + + anchors.fill: parent + anchors.margins: (!(btn.pressed || root.pressOverride) && (btn.containsMouse || root.hoverOverride) ? -Appearance.padding.extraSmall : 0) + (root.open ? -Appearance.padding.extraSmall : 0) + group: blobGroup + radius: root.open ? Appearance.rounding.large : Appearance.rounding.medium + + Behavior on anchors.margins { + Anim { + } + } + Behavior on radius { + Anim { + type: Anim.DefaultEffects + } + } + } + + BlobRect { + id: rect + + anchors.right: parent.right + anchors.top: parent.top + deformScale: 0.00001 + group: blobGroup + implicitHeight: parent.height + implicitWidth: parent.width + radius: Appearance.rounding.large + + states: State { + name: "open" + when: root.open + + PropertyChanges { + rect.anchors.rightMargin: root.width - Appearance.spacing.small + rect.anchors.topMargin: -root.topMovement + rect.implicitHeight: root.content.implicitHeight + root.padding * 2 + rect.implicitWidth: root.content.implicitWidth + root.padding * 2 + root.animDriver: 1 + } + } + transitions: Transition { + Anim { + properties: "rightMargin,implicitWidth" + } + + Anim { + duration: Appearance.anim.durations.expressiveFastSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + properties: "topMargin,implicitHeight" + } + + Anim { + property: "animDriver" + type: Anim.DefaultEffects + } + } + + MouseArea { // MouseArea to catch inputs + anchors.fill: parent + children: [root.content] + clip: true + } + } + + MouseArea { + id: btn + + anchors.centerIn: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + implicitHeight: icon.implicitHeight + Appearance.padding.extraSmall * 2 + implicitWidth: implicitHeight + + onClicked: root.open = !root.open + + MaterialIcon { + id: icon + + anchors.centerIn: parent + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + text: "view_apps" + } + } +} diff --git a/Modules/SettingsNew/Common/ItemList.qml b/Modules/SettingsNew/Common/ItemList.qml new file mode 100644 index 0000000..850f3aa --- /dev/null +++ b/Modules/SettingsNew/Common/ItemList.qml @@ -0,0 +1,116 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property alias delegate: list.delegate + property int extraHeight + readonly property alias list: list + property alias model: list.model + property string placeholderIcon + property string placeholderText + property bool showList + + Layout.fillWidth: true + clip: true + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Appearance.padding.extraLarge * 2) + extraHeight + + Behavior on implicitHeight { + Anim { + } + } + + Loader { + id: placeholder + + active: opacity > 0 + anchors.centerIn: parent + opacity: root.showList && list.count > 0 ? 0 : 1 + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + sourceComponent: ColumnLayout { + spacing: Appearance.spacing.extraSmall + + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + animate: true + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.large + text: root.placeholderIcon + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + animate: true + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.large + text: root.placeholderText + } + } + } + + ListView { + id: list + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + interactive: false + opacity: root.showList ? 1 : 0 + spacing: 0 + + add: Transition { + Anim { + from: 0 + property: "opacity" + to: 1 + type: Anim.DefaultEffects + } + } + displaced: Transition { + Anim { + property: "opacity" + to: 1 + type: Anim.DefaultEffects + } + + Anim { + property: "y" + } + } + move: Transition { + Anim { + property: "opacity" + to: 1 + type: Anim.DefaultEffects + } + + Anim { + property: "y" + } + } + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + remove: Transition { + Anim { + property: "opacity" + to: 0 + type: Anim.DefaultEffects + } + } + } +} diff --git a/Modules/SettingsNew/Common/NavRow.qml b/Modules/SettingsNew/Common/NavRow.qml new file mode 100644 index 0000000..e7a9cec --- /dev/null +++ b/Modules/SettingsNew/Common/NavRow.qml @@ -0,0 +1,70 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property alias icon: icon.text + property alias label: label.text + property alias status: status.text + + signal clicked + + Layout.fillWidth: true + implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2 + + StateLayer { + onClicked: root.clicked() + } + + RowLayout { + id: navLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + MaterialIcon { + id: icon + + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + } + + CustomText { + id: status + + Layout.fillWidth: true + animate: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + visible: text + } + } + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + text: "chevron_right" + } + } +} diff --git a/Modules/SettingsNew/Common/OverlayRow.qml b/Modules/SettingsNew/Common/OverlayRow.qml new file mode 100644 index 0000000..34c2f8d --- /dev/null +++ b/Modules/SettingsNew/Common/OverlayRow.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew + +ConnectedRect { + id: root + + property alias checked: switchButton.checked + property int horizontalPadding: Appearance.padding.largeIncreased + required property Component popup + property alias subtext: subtext.text + property alias text: text.text + property int verticalPadding: Appearance.padding.normal + + signal clicked(checked: bool) + + Layout.fillWidth: true + implicitHeight: layout.implicitHeight + verticalPadding * 2 + + Column { + id: layout + + anchors.left: parent.left + anchors.leftMargin: root.horizontalPadding + anchors.right: icon.left + anchors.rightMargin: Appearance.padding.normal + anchors.verticalCenter: parent.verticalCenter + + CustomText { + id: text + + font.pointSize: Appearance.font.size.smaller + } + + CustomText { + id: subtext + + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.small + wrapMode: Text.WordWrap + } + } + + MaterialIcon { + id: icon + + anchors.right: switchButton.left + anchors.rightMargin: Appearance.spacing.normal + anchors.verticalCenter: parent.verticalCenter + text: "open_in_new" + } + + StateLayer { + onClicked: PopupManager.requestOpen(root.popup) + } + + CustomSwitch { + id: switchButton + + anchors.right: parent.right + anchors.rightMargin: root.horizontalPadding + anchors.verticalCenter: parent.verticalCenter + + onClicked: root.clicked(checked) + } +} diff --git a/Modules/SettingsNew/Common/PageBase.qml b/Modules/SettingsNew/Common/PageBase.qml index 64a5b74..be5ec06 100644 --- a/Modules/SettingsNew/Common/PageBase.qml +++ b/Modules/SettingsNew/Common/PageBase.qml @@ -64,6 +64,7 @@ ColumnLayout { bottomMargin: Appearance.padding.extraLarge contentHeight: root.contentChild?.implicitHeight ?? 0 contentItem.children: [root.contentChild] + fadeAmount: 0.1 topMargin: Appearance.padding.large } } diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/SettingsNew/Common/PopupRow.qml index 34c2f8d..542a5bb 100644 --- a/Modules/SettingsNew/Common/PopupRow.qml +++ b/Modules/SettingsNew/Common/PopupRow.qml @@ -1,68 +1,121 @@ import QtQuick import QtQuick.Layouts -import qs.Config +import Quickshell import qs.Components -import qs.Modules.SettingsNew +import qs.Config +import qs.Drawers ConnectedRect { id: root - property alias checked: switchButton.checked - property int horizontalPadding: Appearance.padding.largeIncreased - required property Component popup - property alias subtext: subtext.text - property alias text: text.text - property int verticalPadding: Appearance.padding.normal - - signal clicked(checked: bool) + default required property Item content + property alias icon: icon.text + property bool keepPopupAsChild + property alias label: label.text + readonly property alias popup: popup + property alias status: status.text Layout.fillWidth: true - implicitHeight: layout.implicitHeight + verticalPadding * 2 - - Column { - id: layout - - anchors.left: parent.left - anchors.leftMargin: root.horizontalPadding - anchors.right: icon.left - anchors.rightMargin: Appearance.padding.normal - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - font.pointSize: Appearance.font.size.smaller - } - - CustomText { - id: subtext - - color: DynamicColors.palette.m3outline - font.pointSize: Appearance.font.size.small - wrapMode: Text.WordWrap - } - } - - MaterialIcon { - id: icon - - anchors.right: switchButton.left - anchors.rightMargin: Appearance.spacing.normal - anchors.verticalCenter: parent.verticalCenter - text: "open_in_new" - } + implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2 StateLayer { - onClicked: PopupManager.requestOpen(root.popup) + id: stateLayer + + manualHoverOverride: popup.hovered && !popup.open + + onClicked: popup.open = true } - CustomSwitch { - id: switchButton + RowLayout { + id: navLayout - anchors.right: parent.right - anchors.rightMargin: root.horizontalPadding - anchors.verticalCenter: parent.verticalCenter + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small - onClicked: root.clicked(checked) + MaterialIcon { + id: icon + + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + } + + CustomText { + id: status + + Layout.fillWidth: true + animate: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + visible: text + } + } + + Item { + id: triggerArea + + implicitHeight: popup.implicitHeight + implicitWidth: popup.implicitWidth + + TransformWatcher { + id: tWatcher + + a: area.parent + b: triggerArea + } + + MouseArea { + id: area + + anchors.fill: parent + cursorShape: undefined + enabled: popup.open + hoverEnabled: true + parent: { + if (root.keepPopupAsChild) + return triggerArea; + + const win = QsWindow.window; + const contentWin = win as Windows; // If inside the drawer content window, put it inside the interaction wrapper so hover works + return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem; + } + z: popup.animDriver > 0 ? 1 : 0 + + onClicked: popup.open = false + + BlobPopup { + id: popup + + color: open || hovered || stateLayer.containsMouse ? DynamicColors.palette.m3secondaryContainer : DynamicColors.palette.m3surfaceContainerHighest + content: root.content + hoverOverride: stateLayer.containsMouse + padding: Appearance.padding.small + pressOverride: stateLayer.pressed + x: { + tWatcher.transform; + return triggerArea.mapToItem(area.parent, 0, 0).x; + } + y: { + tWatcher.transform; + return triggerArea.mapToItem(area.parent, 0, 0).y; + } + } + } + } } } diff --git a/Modules/SettingsNew/Common/SectionHeader.qml b/Modules/SettingsNew/Common/SectionHeader.qml new file mode 100644 index 0000000..ffaeb06 --- /dev/null +++ b/Modules/SettingsNew/Common/SectionHeader.qml @@ -0,0 +1,16 @@ +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +CustomText { + property bool first + + Layout.bottomMargin: Appearance.spacing.extraSmall + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.small + Layout.topMargin: first ? 0 : Appearance.spacing.large - ((parent as ColumnLayout).spacing ?? 0) + color: DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + font.pointSize: Appearance.font.size.medium +} diff --git a/Modules/SettingsNew/Common/SliderRow.qml b/Modules/SettingsNew/Common/SliderRow.qml new file mode 100644 index 0000000..0ba872b --- /dev/null +++ b/Modules/SettingsNew/Common/SliderRow.qml @@ -0,0 +1,81 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property alias icon: slider.insetIcon + property alias label: label.text + property real value + property alias valueLabel: valueLabel.text + + signal moved(value: real) + + Layout.fillWidth: true + implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins + rowLayout.anchors.topMargin + + RowLayout { + id: rowLayout + + anchors.fill: parent + anchors.margins: Appearance.padding.largeIncreased + anchors.topMargin: Appearance.padding.large + spacing: Appearance.spacing.small + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.small + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + } + + CustomText { + id: valueLabel + + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.small + } + } + + CustomMouseArea { + function onWheel(event: WheelEvent): void { + const step = Config.services.audioIncrement; + if (event.angleDelta.y > 0) + root.moved(Math.min(1, root.value + step)); + else if (event.angleDelta.y < 0) + root.moved(Math.max(0, root.value - step)); + } + + Layout.fillWidth: true + implicitHeight: Appearance.padding.larger * 3 + + CustomSlider { + id: slider + + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + enabled: root.enabled + implicitHeight: parent.implicitHeight + radius: Appearance.rounding.small + value: root.value + + onInteraction: v => root.moved(v) + } + } + } + } +} diff --git a/Modules/SettingsNew/Common/StackPage.qml b/Modules/SettingsNew/Common/StackPage.qml index 867802a..b1e6c8c 100644 --- a/Modules/SettingsNew/Common/StackPage.qml +++ b/Modules/SettingsNew/Common/StackPage.qml @@ -104,7 +104,7 @@ StackView { id: logCat defaultLogLevel: LoggingCategory.Info - name: "caelestia.nexus" + name: "ZShell.settings" } Connections { diff --git a/Modules/SettingsNew/Common/WallpaperCropper.qml b/Modules/SettingsNew/Common/WallpaperCropper.qml index b0888b1..f9703e4 100644 --- a/Modules/SettingsNew/Common/WallpaperCropper.qml +++ b/Modules/SettingsNew/Common/WallpaperCropper.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Hyprland -import ZShell.Internal +import ZShell.Components import qs.Config import qs.Components import qs.Helpers @@ -13,13 +13,71 @@ Item { id: wrapper property bool changesMade: false + readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null + property var screens: [] + property int selectedScreenIndex: 0 property bool shouldBeActive: true - signal requestCrop + function applyCrop(): void { + if (!cropRectLoader.item || !currentScreen) + return; + + const cropRect = cropRectLoader.item; + + const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; + const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; + const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; + const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; + + Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom); + } + + function refreshScreens(): void { + screens = [...Quickshell.screens].sort((a, b) => a.x - b.x); + + if (screens.length === 0) { + selectedScreenIndex = 0; + return; + } + + selectedScreenIndex = Math.max(0, Math.min(selectedScreenIndex, screens.length - 1)); + } + + function selectScreen(index: int): void { + if (index < 0 || index >= screens.length || index === selectedScreenIndex) + return; + + selectedScreenIndex = index; + + if (cropRectLoader.item) + Qt.callLater(syncCropToScreen); + } + + function syncCropToScreen(): void { + if (cropRectLoader.item) + cropRectLoader.item.restoreFromData(); + } + + function zoomClipRect(zoom: real): void { + if (!cropRectLoader.item) + return; + + const cropRect = cropRectLoader.item; + + const centerX = cropRect.x + cropRect.width * 0.5; + const centerY = cropRect.y + cropRect.height * 0.5; + + cropRect.zoom = zoom; + + cropRect.x = centerX - cropRect.width * 0.5; + cropRect.y = centerY - cropRect.height * 0.5; + + cropRect.clampToBounds(); + } anchors.left: parent.left anchors.right: parent.right - implicitHeight: shouldBeActive ? 400 : 0 + implicitHeight: shouldBeActive ? 430 : 0 opacity: shouldBeActive ? 1 : 0 scale: shouldBeActive ? 1 : 0.8 visible: opacity > 0 @@ -37,6 +95,14 @@ Item { } } + Component.onCompleted: { + refreshScreens(); + Qt.callLater(syncCropToScreen); + } + onSelectedScreenIndexChanged: { + Qt.callLater(syncCropToScreen); + } + IconButton { anchors.margins: Appearance.padding.normal anchors.right: parent.right @@ -56,259 +122,216 @@ Item { } onClicked: { - wrapper.requestCrop(); + wrapper.applyCrop(); wrapper.changesMade = false; } } - RowLayout { - id: root + ButtonRow { + id: screenSelector - anchors.fill: parent - spacing: Appearance.spacing.normal + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.extraLarge * 2 + anchors.right: parent.right + anchors.rightMargin: Appearance.padding.extraLarge * 2 + anchors.top: parent.top + implicitHeight: 34 + spacing: Appearance.spacing.small Repeater { - model: ScriptModel { - values: [...Quickshell.screens].sort((a, b) => { - return a.x - b.x; - }) + model: wrapper.screens + + delegate: TextButton { + required property int index + readonly property bool isCurrent: wrapper.selectedScreenIndex === index + required property var modelData + + fillWidth: true + inactiveColor: isCurrent ? DynamicColors.palette.m3primary : Qt.alpha(DynamicColors.palette.m3surfaceContainer, 0.7) + inactiveOnColor: isCurrent ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.7) + isRound: true + shapeMorph: true + text: modelData.name + + onClicked: wrapper.selectScreen(index) + } + } + } + + RowLayout { + id: sliderLayout + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: 30 + + CustomSlider { + id: zoomSlider + + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.normal + Layout.preferredHeight: Appearance.padding.larger * 3 + Layout.rightMargin: Appearance.padding.normal + from: 1.0 + implicitHeight: Appearance.padding.larger * 3 + insetIcon: "crop" + to: 5.0 + value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 + + onInteraction: value => { + wrapper.zoomClipRect(1 + (value * 4)); + wrapper.changesMade = true; + } + } + } + + Image { + id: scaledImg + + property var displayData + property real monitorScale: 1.0 + + anchors.bottom: sliderLayout.top + anchors.bottomMargin: Appearance.spacing.normal + anchors.left: parent.left + anchors.right: parent.right + anchors.top: screenSelector.bottom + anchors.topMargin: Appearance.spacing.normal + asynchronous: true + fillMode: Image.PreserveAspectFit + retainWhileLoading: true + source: Wallpapers.current + sourceSize.height: parent.height + sourceSize.width: parent.width + + onPaintedWidthChanged: { + if (paintedWidth > 0 && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onSourceChanged: { + if (cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + onStatusChanged: { + if (scaledImg.status == Image.Ready && cropRectLoader.item) { + cropRectLoader.item.restoreFromData(); + } + } + + Loader { + id: cropRectLoader + + active: scaledImg.paintedWidth > 0 && wrapper.currentScreen + + sourceComponent: Component { + CustomRect { + id: cropRect + + property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1 + readonly property real baseHeight: baseWidth / aspectRatio + readonly property real baseWidth: { + let fittedHeight = scaledImg.paintedHeight; + let fittedWidth = fittedHeight * aspectRatio; + + if (fittedWidth > scaledImg.paintedWidth) { + fittedWidth = scaledImg.paintedWidth; + fittedHeight = fittedWidth / aspectRatio; + } + + return fittedWidth; + } + readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 + readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 + property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight + property real zoom: 1.0 + + function centerInImage() { + x = imageX + (scaledImg.paintedWidth - width) / 2; + y = imageY + (scaledImg.paintedHeight - height) / 2; + } + + function clampToBounds() { + x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); + y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); + } + + function restoreFromData() { + if (!wrapper.currentScreen) + return; + + let data = Wallpapers.getCrop(wrapper.currentScreen.name); + + if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { + zoom = data.zoom > 0 ? data.zoom : 1.0; + x = imageX + (data.x * scaledImg.paintedWidth); + y = imageY + (data.y * scaledImg.paintedHeight); + + clampToBounds(); + } else { + zoom = 1.0; + centerInImage(); + } + } + + border.color: DynamicColors.palette.m3primary + border.width: 2 + height: baseHeight / zoom + opacity: 1 + width: baseWidth / zoom + + Behavior on opacity { + Anim { + } + } + + Component.onCompleted: { + restoreFromData(); + } + onHeightChanged: clampToBounds() + onWidthChanged: clampToBounds() + } + } + } + + MouseArea { + id: mouse + + function updateCrop(mouseX, mouseY) { + if (!cropRectLoader.item) + return; + + const cropRect = cropRectLoader.item; + + let nx = mouseX - cropRect.width * 0.5; + let ny = mouseY - cropRect.height * 0.5; + + nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width)); + ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height)); + + cropRect.x = nx; + cropRect.y = ny; } - Item { - id: delegate + anchors.fill: parent + hoverEnabled: true + preventStealing: true - required property ShellScreen modelData - - function applyCrop(): void { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - // We need to calculate the exact percentage coordinates that map perfectly - // to our C++ backend, regardless of current display scaling - const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; - const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; - const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; - const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; - - const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent); - - // We just pass the percentages directly to the backend - Wallpapers.setCrop(delegate.modelData.name, finalRect, cropRect.zoom); - } - - function zoomClipRect(zoom: real): void { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - let oldCenterX = cropRect.x + cropRect.width * 0.5; - let oldCenterY = cropRect.y + cropRect.height * 0.5; - - cropRect.zoom = zoom; - - cropRect.x = oldCenterX - cropRect.width * 0.5; - cropRect.y = oldCenterY - cropRect.height * 0.5; - - cropRect.clampToBounds(); - } - - Layout.fillHeight: true - Layout.fillWidth: true - - Connections { - function onRequestCrop(): void { - delegate.applyCrop(); - } - - target: wrapper - } - - RowLayout { - id: sliderLayout - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: 30 - - CustomSlider { - id: zoomSlider - - Layout.fillWidth: true - Layout.leftMargin: Appearance.padding.normal - Layout.preferredHeight: Appearance.padding.larger * 3 - Layout.rightMargin: Appearance.padding.normal - from: 1.0 - implicitHeight: Appearance.padding.larger * 3 - insetIcon: "crop" - to: 5.0 - value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 - - onInteraction: value => { - delegate.zoomClipRect(1 + (value * 4)); - wrapper.changesMade = true; - } - } - } - - CachingImage { - id: scaledImg - - property var displayData - property real monitorScale: 1.0 - - anchors.bottom: sliderLayout.top - anchors.bottomMargin: Appearance.spacing.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - asynchronous: true - fillMode: Image.PreserveAspectFit - retainWhileLoading: true - source: Wallpapers.current - sourceSize.height: parent.height - sourceSize.width: parent.width - - onPaintedWidthChanged: { - if (paintedWidth > 0 && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - onSourceChanged: { - if (cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - onStatusChanged: { - if (scaledImg.status == Image.Ready && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - - CustomText { - id: monitorId - - anchors.centerIn: parent - color: Qt.alpha(DynamicColors.palette.m3surface, 0.85) - font.pointSize: Appearance.font.size.large * 4 - style: Text.Outline - styleColor: DynamicColors.palette.m3onSurface - text: delegate.modelData.name - } - - Loader { - id: cropRectLoader - - active: scaledImg.paintedWidth > 0 - - sourceComponent: Component { - CustomRect { - id: cropRect - - property real aspectRatio: delegate.modelData.width / delegate.modelData.height - readonly property real baseHeight: baseWidth / aspectRatio - readonly property real baseWidth: { - let fittedHeight = scaledImg.paintedHeight; - let fittedWidth = fittedHeight * aspectRatio; - - if (fittedWidth > scaledImg.paintedWidth) { - fittedWidth = scaledImg.paintedWidth; - fittedHeight = fittedWidth / aspectRatio; - } - - return fittedWidth; - } - readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 - readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 - property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight - property real zoom: 1.0 - - function centerInImage() { - x = imageX + (scaledImg.paintedWidth - width) / 2; - y = imageY + (scaledImg.paintedHeight - height) / 2; - } - - function clampToBounds() { - x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); - - y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); - } - - function restoreFromData() { - let data = Wallpapers.getCrop(delegate.modelData.name); - - if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { - zoom = data.zoom > 0 ? data.zoom : 1.0; - x = imageX + (data.x * scaledImg.paintedWidth); - y = imageY + (data.y * scaledImg.paintedHeight); - - clampToBounds(); - } else { - zoom = 1.0; - centerInImage(); - } - } - - border.color: DynamicColors.palette.m3primary - border.width: 2 - height: baseHeight / zoom - opacity: 1 - width: baseWidth / zoom - - Behavior on opacity { - Anim { - } - } - - Component.onCompleted: { - restoreFromData(); - } - onHeightChanged: clampToBounds() - onWidthChanged: clampToBounds() - } - } - } - - MouseArea { - id: mouse - - function updateCrop(mouseX, mouseY) { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - let nx = mouseX - cropRect.width * 0.5; - let ny = mouseY - cropRect.height * 0.5; - - nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width)); - - ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height)); - - cropRect.x = nx; - cropRect.y = ny; - } - - anchors.fill: parent - hoverEnabled: true - preventStealing: true - - onPositionChanged: mouse => { - if (pressed) { - updateCrop(mouse.x, mouse.y); - wrapper.changesMade = true; - } - } - onPressed: mouse => { - updateCrop(mouse.x, mouse.y); - wrapper.changesMade = true; - } - onReleased: { - wrapper.changesMade = true; - } - } + onPositionChanged: mouse => { + if (pressed) { + updateCrop(mouse.x, mouse.y); + wrapper.changesMade = true; } } + onPressed: mouse => { + updateCrop(mouse.x, mouse.y); + wrapper.changesMade = true; + } + onReleased: { + wrapper.changesMade = true; + } } } } diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml index 0ab4db1..327870d 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -7,6 +7,8 @@ import qs.Config import qs.Modules.SettingsNew.Common import qs.Modules.SettingsNew.Pages import qs.Modules.SettingsNew.Pages.Wallpaper +import qs.Modules.SettingsNew.Pages.Audio +import qs.Modules.SettingsNew.Pages.Apps QtObject { id: root @@ -27,15 +29,62 @@ QtObject { } } }, - - // Screenshot Component { + // Screenshot StackPage { Component { Screenshot { } } } + }, + + // Connectivity + Component { + PlaceholderComp { + } + }, + Component { + PlaceholderComp { + } + }, + Component { + // Audio + StackPage { + Component { + AudioPage { + } + } + + Component { + AppVolumes { + } + } + } + }, + + // Shell + Component { + PlaceholderComp { + } + }, + Component { + StackPage { + Component { + AppsPage { + } + } + + Component { + AllApps { + } + } + + Component { + AppInfo { + } + } + } } ] readonly property Component placeholderComp: Component { diff --git a/Modules/SettingsNew/Pages/Apps/AllApps.qml b/Modules/SettingsNew/Pages/Apps/AllApps.qml new file mode 100644 index 0000000..4f1a9ed --- /dev/null +++ b/Modules/SettingsNew/Pages/Apps/AllApps.qml @@ -0,0 +1,100 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs.Components +import qs.Config +import qs.Helpers +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("All apps") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + Repeater { + id: list + + model: [...DesktopEntries.applications.values].sort((a, b) => a.name.localeCompare(b.name)) + + ConnectedRect { + id: appItem + + required property int index + required property DesktopEntry modelData + + Layout.fillWidth: true + first: index === 0 + implicitHeight: appRow.implicitHeight + appRow.anchors.margins * 2 + last: index === list.count - 1 + + StateLayer { + onClicked: { + root.sState.selectedApp = appItem.modelData; + root.sState.openSubPage(2); + } + } + + RowLayout { + id: appRow + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + IconImage { + asynchronous: true + implicitSize: Math.round(Appearance.font.size.large * 1.8) + source: Quickshell.iconPath(appItem.modelData.icon, "image-missing") + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: appItem.modelData.name + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: (appItem.modelData.comment || appItem.modelData.genericName) ?? "" + visible: text + } + } + + MaterialIcon { + color: DynamicColors.palette.m3primary + fill: 1 + font.pointSize: Appearance.font.size.small + text: "favorite" + visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id) + } + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + text: "chevron_right" + } + } + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Apps/AppInfo.qml b/Modules/SettingsNew/Pages/Apps/AppInfo.qml new file mode 100644 index 0000000..ba44059 --- /dev/null +++ b/Modules/SettingsNew/Pages/Apps/AppInfo.qml @@ -0,0 +1,172 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import qs.Components +import qs.Config +import qs.Helpers +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + readonly property DesktopEntry app: sState.selectedApp + readonly property bool favoriteByRegex: app && matchedByRegex(Config.launcher.favoriteApps, app.id) + readonly property bool hiddenByRegex: app && matchedByRegex(Config.launcher.hiddenApps, app.id) + + function isRegexEntry(s: string): bool { + return /^\^.*\$$/.test(s); + } + + function matchedByRegex(filterList: list, id: string): bool { + return filterList.some(f => isRegexEntry(f) && new RegExp(f).test(id)); + } + + isSubPage: true + title: qsTr("App info") + + onAppChanged: { + // Auto close when app lost + if (!app) + sState.closeSubPage(); + } + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Header + RowLayout { + Layout.bottomMargin: Appearance.spacing.large + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.small + spacing: Appearance.spacing.large + + IconImage { + asynchronous: true + implicitSize: Math.round(Appearance.font.size.large * 3) + source: Quickshell.iconPath(root.app?.icon, "image-missing") + } + + ColumnLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.extraSmall / 2 + + CustomText { + Layout.fillWidth: true + font.pointSize: Appearance.font.size.medium + text: root.app?.name ?? "" + wrapMode: Text.WordWrap + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.small + text: (root.app?.comment || root.app?.genericName) ?? "" + visible: text + wrapMode: Text.WordWrap + } + } + } + + // Launcher + SectionHeader { + first: true + text: qsTr("Launcher") + } + + ToggleRow { + checked: root.app && Strings.testRegexList(Config.launcher.favoriteApps, root.app.id) + enabled: !root.favoriteByRegex + first: true + subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the top of the launcher") + text: qsTr("Favorite") + + onToggled: { + const apps = Config.launcher.favoriteApps; + Config.launcher.favoriteApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id); + } + } + + ToggleRow { + checked: root.app && Strings.testRegexList(Config.launcher.hiddenApps, root.app.id) + enabled: !root.hiddenByRegex + last: true + subtext: root.hiddenByRegex ? qsTr("Matched by a regex in hiddenApps — edit the config file to change") : qsTr("Hide from the launcher") + text: qsTr("Hidden") + + onToggled: { + const apps = Config.launcher.hiddenApps; + Config.launcher.hiddenApps = checked ? [...apps, root.app.id] : apps.filter(a => a !== root.app.id); + } + } + + // Details + SectionHeader { + text: qsTr("Details") + } + + WrapInfoRow { + id: appId + + first: true + label: qsTr("App ID") + labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, command.labelComp.implicitWidth) + value: root.app?.id ?? "" + } + + WrapInfoRow { + id: command + + label: qsTr("Command") + labelComp.Layout.preferredWidth: Math.max(labelComp.implicitWidth, appId.labelComp.implicitWidth) + last: true + value: (root.app?.command ?? []).join(" ") + } + } + + component WrapInfoRow: ConnectedRect { + id: row + + property alias label: label.text + readonly property alias labelComp: label + property alias value: value.text + + Layout.fillWidth: true + implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2 + + RowLayout { + id: rowLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + CustomText { + id: label + + Layout.alignment: Qt.AlignTop + font.pointSize: Appearance.font.size.small + } + + Item { + Layout.fillWidth: true + } + + CustomText { + id: value + + Layout.fillWidth: true + Layout.maximumWidth: implicitWidth + 1 // Whyyyyyyyyy + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + } + } + } +} diff --git a/Modules/SettingsNew/Pages/AppsPage.qml b/Modules/SettingsNew/Pages/AppsPage.qml new file mode 100644 index 0000000..d33fc1a --- /dev/null +++ b/Modules/SettingsNew/Pages/AppsPage.qml @@ -0,0 +1,177 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import ZShell +import qs.Helpers +import qs.Components +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + title: qsTr("Apps") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Default applications + SectionHeader { + first: true + text: qsTr("Default applications") + } + + DefaultRow { + first: true + icon: "terminal" + label: qsTr("Terminal") + status: Config.general.apps.terminal.join(" ") + + onSelected: app => Config.general.apps.terminal = app.command + } + + DefaultRow { + icon: "volume_up" + label: qsTr("Audio") + status: Config.general.apps.audio.join(" ") + + onSelected: app => Config.general.apps.audio = app.command + } + + DefaultRow { + icon: "play_circle" + label: qsTr("Media playback") + status: Config.general.apps.playback.join(" ") + + onSelected: app => Config.general.apps.playback = app.command + } + + DefaultRow { + icon: "folder" + label: qsTr("File manager") + last: true + status: Config.general.apps.explorer.join(" ") + + onSelected: app => Config.general.apps.explorer = app.command + } + + // Library + SectionHeader { + text: qsTr("Library") + } + + NavRow { + first: true + icon: "apps" + label: qsTr("All apps") + last: true + status: qsTr("Browse installed apps, set favorites and hidden") + + onClicked: root.sState.openSubPage(1) + } + } + + component DefaultRow: PopupRow { + id: row + + readonly property int popupHeight: root.flickable.height - y + root.flickable.contentY - Appearance.padding.large - Appearance.padding.extraLarge + + signal selected(app: DesktopEntry) + + keepPopupAsChild: { + if (root.sState.animatingContainer || root.opacity < 1) + return true; + + let p = root.parent; + while (p && p.objectName !== "PageContainer") + p = p.parent; + return p?.opacity < 1; + } + popup.topMovement: Math.max(0 - popupHeight, Appearance.padding.large) + + Loader { + active: row.popup.animDriver > 0 + anchors.centerIn: parent + + sourceComponent: VerticalFadeListView { + id: list + + fadeAmount: 0.05 + implicitHeight: ZUtils.clamp(row.popupHeight, 200, 800) + implicitWidth: 300 + model: { + const apps = [...DesktopEntries.applications.values]; + const favorited = new Set(apps.filter(a => Strings.testRegexList(Config.launcher.favoriteApps, a.id))); + return apps.sort((a, b) => (favorited.has(b) - favorited.has(a)) || a.name.localeCompare(b.name)); + } + + delegate: StateLayer { + id: appItem + + required property int index + required property DesktopEntry modelData + + anchors.fill: undefined + anchors.left: list.contentItem.left + anchors.right: list.contentItem.right + implicitHeight: itemLayout.implicitHeight + itemLayout.anchors.margins * 2 + radius: Appearance.rounding.small + + onClicked: { + row.popup.open = false; + row.selected(modelData); + } + + RowLayout { + id: itemLayout + + anchors.fill: parent + anchors.margins: Appearance.padding.normal + spacing: Appearance.spacing.small + + IconImage { + asynchronous: true + implicitSize: Math.round(Appearance.font.size.large * 1.8) + source: Quickshell.iconPath(appItem.modelData.icon, "image-missing") + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: appItem.modelData.name + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: (appItem.modelData.comment || appItem.modelData.genericName) ?? "" + visible: text + } + } + + MaterialIcon { + color: DynamicColors.palette.m3primary + fill: 1 + font.pointSize: Appearance.font.size.small + text: "favorite" + visible: Strings.testRegexList(Config.launcher.favoriteApps, appItem.modelData.id) + } + } + } + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Audio/AppVolumes.qml b/Modules/SettingsNew/Pages/Audio/AppVolumes.qml new file mode 100644 index 0000000..ab19def --- /dev/null +++ b/Modules/SettingsNew/Pages/Audio/AppVolumes.qml @@ -0,0 +1,69 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Services.Pipewire +import qs.Modules.SettingsNew.Common +import qs.Helpers +import qs.Components +import qs.Config +import qs.Daemons + +PageBase { + id: root + + isSubPage: true + title: qsTr("App volumes") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + CustomText { + Layout.bottomMargin: Appearance.spacing.small + Layout.fillWidth: true + Layout.leftMargin: Appearance.padding.small + color: DynamicColors.palette.m3outline + font.pointSize: Appearance.font.size.small + text: qsTr("Adjust the volume of individual apps currently playing audio.") + wrapMode: Text.WordWrap + } + + ItemList { + id: streamList + + color: list.count === 0 ? DynamicColors.tPalette.m3surfaceContainer : "transparent" + first: true + last: true + list.spacing: Appearance.spacing.extraSmall / 2 + placeholderIcon: "music_off" + placeholderText: qsTr("No apps playing audio") + showList: true + + delegate: SliderRow { + id: stream + + required property int index + required property PwNode modelData + + anchors.left: streamList.list.contentItem.left + anchors.right: streamList.list.contentItem.right + enabled: !stream.modelData?.audio?.muted + first: index === 0 + icon: Icons.getVolumeIcon(stream.modelData?.audio?.volume ?? 0, stream.modelData?.audio?.muted ?? false) + label: Audio.getStreamName(stream.modelData) + last: index === streamList.list.count - 1 + value: stream.modelData?.audio?.volume ?? 0 + valueLabel: Math.round(value * 100) + "%" + + onMoved: v => Audio.setStreamVolume(stream.modelData, v) + } + model: ScriptModel { + values: [...Audio.streams] + } + } + } +} diff --git a/Modules/SettingsNew/Pages/AudioPage.qml b/Modules/SettingsNew/Pages/AudioPage.qml new file mode 100644 index 0000000..43cb71c --- /dev/null +++ b/Modules/SettingsNew/Pages/AudioPage.qml @@ -0,0 +1,136 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Daemons +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common +import qs.Helpers + +PageBase { + id: root + + title: qsTr("Audio") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Output + SliderRow { + enabled: !Audio.muted + first: true + icon: Icons.getVolumeIcon(Audio.volume, Audio.muted) + label: qsTr("Output") + value: Audio.volume + valueLabel: Math.round(value * 100) + "%" + + onMoved: v => Audio.setVolume(v) + } + + ToggleRow { + checked: Audio.muted + text: qsTr("Muted") + + onToggled: Audio.setStreamMuted(Audio.sink, checked) + } + + AudioDeviceList { + currentId: Audio.sink?.id ?? -1 + iconName: "speaker" + nodes: Audio.sinks + placeholderIcon: "speaker" + placeholderText: qsTr("No output devices") + + onSelected: node => Audio.setAudioSink(node) + } + + // Input + SliderRow { + Layout.topMargin: Appearance.spacing.large - parent.spacing + enabled: !Audio.sourceMuted + first: true + icon: Icons.getMicVolumeIcon(Audio.sourceVolume, Audio.sourceMuted) + label: qsTr("Input") + value: Audio.sourceVolume + valueLabel: Math.round(value * 100) + "%" + + onMoved: v => Audio.setSourceVolume(v) + } + + ToggleRow { + checked: Audio.sourceMuted + text: qsTr("Muted") + + onToggled: Audio.setStreamMuted(Audio.source, checked) + } + + AudioDeviceList { + currentId: Audio.source?.id ?? -1 + iconName: "mic" + nodes: Audio.sources + placeholderIcon: "mic_off" + placeholderText: qsTr("No input devices") + + onSelected: node => Audio.setAudioSource(node) + } + + // Per-app volumes + ConnectedRect { + Layout.fillWidth: true + Layout.topMargin: Appearance.spacing.large - parent.spacing + first: true + implicitHeight: appLayout.implicitHeight + appLayout.anchors.margins * 2 + last: true + + StateLayer { + onClicked: root.sState.openSubPage(1) + } + + RowLayout { + id: appLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + MaterialIcon { + font.pointSize: Appearance.font.size.medium + text: "tune" + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: qsTr("App volumes") + } + + CustomText { + Layout.fillWidth: true + animate: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: Audio.streams.length === 0 ? qsTr("No apps playing audio") : Audio.streams.length === 1 ? qsTr("1 app playing audio") : qsTr("%1 apps playing audio").arg(Audio.streams.length) + } + } + + MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.medium + text: "chevron_right" + } + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Screenshot.qml b/Modules/SettingsNew/Pages/Screenshot.qml index a4c3cb3..cbc9f42 100644 --- a/Modules/SettingsNew/Pages/Screenshot.qml +++ b/Modules/SettingsNew/Pages/Screenshot.qml @@ -68,6 +68,7 @@ PageBase { Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding from: 0 + last: true stepSize: 1 text: qsTr("Corner radius") to: 50 @@ -80,9 +81,9 @@ PageBase { } ToggleRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing checked: Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" + first: true text: qsTr("Enable shadow") onToggled: Config.screenshot.shadow = checked diff --git a/Modules/SettingsNew/Pages/Wallpaper.qml b/Modules/SettingsNew/Pages/Wallpaper.qml index 4edf637..f576a85 100644 --- a/Modules/SettingsNew/Pages/Wallpaper.qml +++ b/Modules/SettingsNew/Pages/Wallpaper.qml @@ -20,105 +20,18 @@ PageBase { spacing: Appearance.spacing.large width: root.cappedWidth - CustomClippingRect { + Item { id: wallWrapper Layout.alignment: Qt.AlignHCenter - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: { - const screen = root.sState.screen; - const cWidth = root.cappedWidth; - return Math.min(Math.round(cWidth * 0.4), cWidth / screen.width * screen.height); - } + implicitHeight: cropper.height implicitWidth: { const screen = root.sState.screen; return implicitHeight / screen.height * screen.width; } - radius: Appearance.rounding.large - Loader { - active: opacity > 0 - anchors.centerIn: parent - opacity: Config.background.enabled ? 0 : 1 - - Behavior on opacity { - Anim { - type: Anim.SlowEffects - } - } - sourceComponent: ColumnLayout { - spacing: Appearance.spacing.extraSmall - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - color: DynamicColors.palette.m3onSurfaceVariant - text: "hide_image" - } - - CustomText { - Layout.alignment: Qt.AlignHCenter - color: DynamicColors.palette.m3onSurfaceVariant - text: qsTr("Wallpaper disabled") - } - } - } - - Item { - anchors.fill: parent - opacity: Config.background.enabled ? 1 : 0 - - Behavior on opacity { - Anim { - type: Anim.SlowEffects - } - } - - Loader { - id: wallIndicatorLoader - - active: opacity > 0 - anchors.fill: parent - opacity: 0 - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } - } - sourceComponent: CustomRect { - color: DynamicColors.palette.m3primaryContainer - radius: Appearance.rounding.normal - } - } - - Timer { - id: wallLoadDebounceTimer - - interval: 100 - - onTriggered: { - if (wallImg.status !== Image.Ready) - wallIndicatorLoader.opacity = 1; - } - } - - FadeImage { - id: wallImg - - anchors.fill: parent - fadeInAnim: Anim.SlowEffects - fadeOutAnim: Anim.DefaultEffects - preventInit: wallIndicatorLoader.opacity > 0 - source: Wallpapers.current - - onSourceChanged: wallLoadDebounceTimer.restart() - onStatusChanged: { - if (status === Image.Ready) { - wallLoadDebounceTimer.stop(); - wallIndicatorLoader.opacity = 0; - } - } - } + WallpaperCropper { + id: cropper } } @@ -162,7 +75,7 @@ PageBase { onToggled: DynamicColors.setMode(checked ? "dark" : "light") } - PopupRow { + OverlayRow { checked: Config.general.color.scheduleDark first: true subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleDarkStart).arg(Config.general.color.scheduleDarkEnd) @@ -189,7 +102,7 @@ PageBase { } } - PopupRow { + OverlayRow { Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing checked: Config.general.color.scheduleHyprsunset last: true diff --git a/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml b/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml index 2578303..53470a4 100644 --- a/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml +++ b/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml @@ -15,118 +15,102 @@ PageBase { isSubPage: true title: qsTr("Wallpapers") - Item { + ColumnLayout { anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top - implicitHeight: childrenRect.height + spacing: Appearance.spacing.small + width: root.cappedWidth - WallpaperCropper { - id: cropper + CustomText { + Layout.topMargin: Appearance.spacing.large + font.pointSize: Appearance.font.size.large + text: qsTr("Wallpapers") } - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - anchors.margins: Appearance.spacing.normal - anchors.top: cropper.bottom - spacing: Appearance.spacing.small - width: root.cappedWidth + GridLayout { + Layout.fillWidth: true + columnSpacing: Appearance.spacing.extraSmall + columns: 4 + rowSpacing: Appearance.spacing.extraSmall + visible: localWalls.count > 0 - CustomText { - Layout.topMargin: Appearance.spacing.large - font.pointSize: Appearance.font.size.large - text: qsTr("Wallpapers") - } + Repeater { + id: localWalls - GridLayout { - Layout.fillWidth: true - columnSpacing: Appearance.spacing.extraSmall - columns: 4 - rowSpacing: Appearance.spacing.extraSmall - visible: localWalls.count > 0 + model: { + const walls = Wallpapers.list; + var baseDir = Paths.wallsdir; + const categories = {}; + const list = []; + for (const w of walls) { + var parentDir = w.parentDir; + if (!parentDir.endsWith("/")) + parentDir = parentDir + "/"; - Repeater { - id: localWalls + if (!baseDir.endsWith("/")) + baseDir = baseDir + "/"; - model: { - const walls = Wallpapers.list; - var baseDir = Paths.wallsdir; - const categories = {}; - const list = []; - for (const w of walls) { - var parentDir = w.parentDir; - if (!parentDir.endsWith("/")) - parentDir = parentDir + "/"; - - if (!baseDir.endsWith("/")) - baseDir = baseDir + "/"; - - if (parentDir !== baseDir) { - const category = Wallpapers.getCategoryFor(w); - if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0)) - categories[category] = w; - } else { - list.push(w); - } + if (parentDir !== baseDir) { + const category = Wallpapers.getCategoryFor(w); + if (category && (!(category in categories) || categories[category].name.localeCompare(w.name) > 0)) + categories[category] = w; + } else { + list.push(w); } - list.push(...Object.values(categories)); - list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name)); - while (list.length < 4) - list.push(null); - - return list; } + list.push(...Object.values(categories)); + list.sort((a, b) => ((a.parentDir === baseDir) - (b.parentDir === baseDir)) || a.name.localeCompare(b.name)); + while (list.length < 4) + list.push(null); - WallItem { - required property FileSystemEntry modelData + return list; + } - enabled: modelData + WallItem { + required property FileSystemEntry modelData - // Empty placeholders for sizing - opacity: modelData ? 1 : 0 - source: String(modelData?.path ?? "") + enabled: modelData - onClicked: { - if (modelData.parentDir !== Paths.wallsdir) { - root.sState.selectedWallpaperCategory = Wallpapers.getCategoryFor(modelData); - root.sState.openSubPage(2); // Category page - } else { - Wallpapers.setWallpaper(modelData.path); - root.sState.closeSubPage(); - } - } + // Empty placeholders for sizing + opacity: modelData ? 1 : 0 + source: String(modelData?.path ?? "") + + onClicked: { + Wallpapers.setWallpaper(modelData.path); + root.sState.closeSubPage(); } } } + } - Loader { - Layout.fillWidth: true - active: localWalls.count === 0 - asynchronous: true - visible: active + Loader { + Layout.fillWidth: true + active: localWalls.count === 0 + asynchronous: true + visible: active - sourceComponent: CustomRect { - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3 - radius: Appearance.rounding.large + sourceComponent: CustomRect { + color: DynamicColors.tPalette.m3surfaceContainer + implicitHeight: noWallsLayout.implicitHeight + Appearance.padding.extraLarge * 3 + radius: Appearance.rounding.large - ColumnLayout { - id: noWallsLayout + ColumnLayout { + id: noWallsLayout - anchors.centerIn: parent - spacing: Appearance.spacing.extraSmall + anchors.centerIn: parent + spacing: Appearance.spacing.extraSmall - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - color: DynamicColors.palette.m3outline - text: "hide_image" - } + MaterialIcon { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outline + text: "hide_image" + } - CustomText { - Layout.alignment: Qt.AlignHCenter - color: DynamicColors.palette.m3outline - text: qsTr("No local wallpapers found") - } + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3outline + text: qsTr("No local wallpapers found") } } } diff --git a/Plugins/ZShell/appdb.cpp b/Plugins/ZShell/appdb.cpp index 8614df3..c66ac59 100644 --- a/Plugins/ZShell/appdb.cpp +++ b/Plugins/ZShell/appdb.cpp @@ -1,265 +1,324 @@ #include "appdb.hpp" +#include #include #include #include +Q_LOGGING_CATEGORY(lcAppDb, "ZShell.appdb", QtInfoMsg) + namespace ZShell { AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent) - : QObject(parent) - , m_entry(entry) - , m_frequency(frequency) { - const auto mo = m_entry->metaObject(); - const auto tmo = metaObject(); + : QObject(parent) + , m_entry(entry) + , m_frequency(frequency) { + const auto mo = m_entry->metaObject(); + const auto tmo = &AppEntry::staticMetaObject; - for (const auto& prop : - { "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) { - const auto metaProp = mo->property(mo->indexOfProperty(prop)); - const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop)); - QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal()); - } + for (const auto& prop : + { "name", "comment", "execString", "startupClass", "genericName", "categories", "keywords" }) { + const auto metaProp = mo->property(mo->indexOfProperty(prop)); + const auto thisMetaProp = tmo->property(tmo->indexOfProperty(prop)); + QObject::connect(m_entry, metaProp.notifySignal(), this, thisMetaProp.notifySignal()); + } - QObject::connect(m_entry, &QObject::destroyed, this, [this]() { - m_entry = nullptr; - deleteLater(); - }); + QObject::connect(m_entry, &QObject::destroyed, this, [this]() { + m_entry = nullptr; + deleteLater(); + }); } QObject* AppEntry::entry() const { - return m_entry; + return m_entry; } quint32 AppEntry::frequency() const { - return m_frequency; + return m_frequency; } void AppEntry::setFrequency(unsigned int frequency) { - if (m_frequency != frequency) { - m_frequency = frequency; - emit frequencyChanged(); - } + if (m_frequency != frequency) { + m_frequency = frequency; + emit frequencyChanged(); + } } void AppEntry::incrementFrequency() { - m_frequency++; - emit frequencyChanged(); + m_frequency++; + emit frequencyChanged(); } QString AppEntry::id() const { - if (!m_entry) { - return ""; - } - return m_entry->property("id").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("id").toString(); } QString AppEntry::name() const { - if (!m_entry) { - return ""; - } - return m_entry->property("name").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("name").toString(); } QString AppEntry::comment() const { - if (!m_entry) { - return ""; - } - return m_entry->property("comment").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("comment").toString(); } QString AppEntry::execString() const { - if (!m_entry) { - return ""; - } - return m_entry->property("execString").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("execString").toString(); } QString AppEntry::startupClass() const { - if (!m_entry) { - return ""; - } - return m_entry->property("startupClass").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("startupClass").toString(); } QString AppEntry::genericName() const { - if (!m_entry) { - return ""; - } - return m_entry->property("genericName").toString(); + if (!m_entry) { + return ""; + } + return m_entry->property("genericName").toString(); } QString AppEntry::categories() const { - if (!m_entry) { - return ""; - } - return m_entry->property("categories").toStringList().join(" "); + if (!m_entry) { + return ""; + } + return m_entry->property("categories").toStringList().join(" "); } QString AppEntry::keywords() const { - if (!m_entry) { - return ""; - } - return m_entry->property("keywords").toStringList().join(" "); + if (!m_entry) { + return ""; + } + return m_entry->property("keywords").toStringList().join(" "); } AppDb::AppDb(QObject* parent) - : QObject(parent) - , m_timer(new QTimer(this)) - , m_uuid(QUuid::createUuid().toString()) { - m_timer->setSingleShot(true); - m_timer->setInterval(300); - QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps); + : QObject(parent) + , m_timer(new QTimer(this)) + , m_uuid(QUuid::createUuid().toString()) { + m_timer->setSingleShot(true); + m_timer->setInterval(300); + QObject::connect(m_timer, &QTimer::timeout, this, &AppDb::updateApps); - auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid); - db.setDatabaseName(":memory:"); - db.open(); + auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid); + db.setDatabaseName(":memory:"); + db.open(); - QSqlQuery query(db); - query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); + QSqlQuery query(db); + query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); } QString AppDb::uuid() const { - return m_uuid; + return m_uuid; } QString AppDb::path() const { - return m_path; + return m_path; } void AppDb::setPath(const QString& path) { - auto newPath = path.isEmpty() ? ":memory:" : path; + auto newPath = path.isEmpty() ? ":memory:" : path; - if (m_path == newPath) { - return; - } + if (m_path == newPath) { + return; + } - m_path = newPath; - emit pathChanged(); + m_path = newPath; + emit pathChanged(); - auto db = QSqlDatabase::database(m_uuid, false); - db.close(); - db.setDatabaseName(newPath); - db.open(); + auto db = QSqlDatabase::database(m_uuid, false); + db.close(); + db.setDatabaseName(newPath); + db.open(); - QSqlQuery query(db); - query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); + QSqlQuery query(db); + query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); - updateAppFrequencies(); + updateAppFrequencies(); } QObjectList AppDb::entries() const { - return m_entries; + return m_entries; } void AppDb::setEntries(const QObjectList& entries) { - if (m_entries == entries) { - return; - } + if (m_entries == entries) { + return; + } - m_entries = entries; - emit entriesChanged(); + m_entries = entries; + emit entriesChanged(); - m_timer->start(); + m_timer->start(); +} + +QStringList AppDb::favoriteApps() const { + return m_favoriteApps; +} + +void AppDb::setFavoriteApps(const QStringList& favApps) { + if (m_favoriteApps == favApps) { + return; + } + + m_favoriteApps = favApps; + emit favoriteAppsChanged(); + m_favoriteAppsRegex.clear(); + m_favoriteAppsRegex.reserve(m_favoriteApps.size()); + for (const QString& item : std::as_const(m_favoriteApps)) { + const QRegularExpression re(regexifyString(item)); + if (re.isValid()) { + m_favoriteAppsRegex << re; + } else { + qCWarning(lcAppDb) << "setFavoriteApps: regular expression is not valid:" << re.pattern(); + } + } + + emit appsChanged(); +} + +QString AppDb::regexifyString(const QString& original) const { + if (original.startsWith('^') && original.endsWith('$')) + return original; + + const QString escaped = QRegularExpression::escape(original); + return QStringLiteral("^%1$").arg(escaped); } QQmlListProperty AppDb::apps() { - return QQmlListProperty(this, &getSortedApps()); + return QQmlListProperty(this, &getSortedApps()); } void AppDb::incrementFrequency(const QString& id) { - auto db = QSqlDatabase::database(m_uuid); - QSqlQuery query(db); + auto db = QSqlDatabase::database(m_uuid); + QSqlQuery query(db); - query.prepare("INSERT INTO frequencies (id, frequency) " - "VALUES (:id, 1) " - "ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1"); - query.bindValue(":id", id); - query.exec(); + query.prepare("INSERT INTO frequencies (id, frequency) " + "VALUES (:id, 1) " + "ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1"); + query.bindValue(":id", id); + query.exec(); - auto* app = m_apps.value(id); - if (app) { - const auto before = getSortedApps(); - - app->incrementFrequency(); - - if (before != getSortedApps()) { - emit appsChanged(); - } - } else { - qWarning() << "AppDb::incrementFrequency: could not find app with id" << id; - } + auto* app = m_apps.value(id); + if (app) { + const auto before = getSortedApps(); + app->incrementFrequency(); + getSortedApps(); + if (before != m_sortedApps) { + emit appsChanged(); + } + } else { + qCWarning(lcAppDb) << "incrementFrequency: could not find app with id" << id; + } } QList& AppDb::getSortedApps() const { - m_sortedApps = m_apps.values(); - std::sort(m_sortedApps.begin(), m_sortedApps.end(), [](AppEntry* a, AppEntry* b) { - if (a->frequency() != b->frequency()) { - return a->frequency() > b->frequency(); - } - return a->name().localeAwareCompare(b->name()) < 0; - }); - return m_sortedApps; + m_sortedApps = m_apps.values(); + + // Pre-compute favorite status to avoid repeated regex matching during sort + QSet favSet; + favSet.reserve(m_sortedApps.size()); + for (const auto* app : std::as_const(m_sortedApps)) { + if (isFavorite(app)) + favSet.insert(app->id()); + } + + std::sort(m_sortedApps.begin(), m_sortedApps.end(), [&favSet](AppEntry* a, AppEntry* b) { + const bool aIsFav = favSet.contains(a->id()); + const bool bIsFav = favSet.contains(b->id()); + if (aIsFav != bIsFav) + return aIsFav; + if (a->frequency() != b->frequency()) + return a->frequency() > b->frequency(); + return a->name().localeAwareCompare(b->name()) < 0; + }); + return m_sortedApps; +} + +bool AppDb::isFavorite(const AppEntry* app) const { + for (const QRegularExpression& re : m_favoriteAppsRegex) { + if (re.match(app->id()).hasMatch()) { + return true; + } + } + return false; } quint32 AppDb::getFrequency(const QString& id) const { - auto db = QSqlDatabase::database(m_uuid); - QSqlQuery query(db); + auto db = QSqlDatabase::database(m_uuid); + QSqlQuery query(db); - query.prepare("SELECT frequency FROM frequencies WHERE id = :id"); - query.bindValue(":id", id); + query.prepare("SELECT frequency FROM frequencies WHERE id = :id"); + query.bindValue(":id", id); - if (query.exec() && query.next()) { - return query.value(0).toUInt(); - } + if (query.exec() && query.next()) { + return query.value(0).toUInt(); + } - return 0; + return 0; } void AppDb::updateAppFrequencies() { - const auto before = getSortedApps(); + const auto before = getSortedApps(); - for (auto* app : std::as_const(m_apps)) { - app->setFrequency(getFrequency(app->id())); - } + for (auto* app : std::as_const(m_apps)) { + app->setFrequency(getFrequency(app->id())); + } - if (before != getSortedApps()) { - emit appsChanged(); - } + getSortedApps(); + if (before != m_sortedApps) { + emit appsChanged(); + } } void AppDb::updateApps() { - bool dirty = false; + bool dirty = false; - for (const auto& entry : std::as_const(m_entries)) { - const auto id = entry->property("id").toString(); - if (!m_apps.contains(id)) { - dirty = true; - auto* const newEntry = new AppEntry(entry, getFrequency(id), this); - QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() { - if (m_apps.remove(id)) { - emit appsChanged(); - } - }); - m_apps.insert(id, newEntry); - } - } + for (const auto& entry : std::as_const(m_entries)) { + const auto id = entry->property("id").toString(); + if (!m_apps.contains(id)) { + dirty = true; + auto* const newEntry = new AppEntry(entry, getFrequency(id), this); + QObject::connect(newEntry, &QObject::destroyed, this, [id, this]() { + if (m_apps.remove(id)) { + emit appsChanged(); + } + }); + m_apps.insert(id, newEntry); + } + } - QSet newIds; - for (const auto& entry : std::as_const(m_entries)) { - newIds.insert(entry->property("id").toString()); - } + QSet newIds; + for (const auto& entry : std::as_const(m_entries)) { + newIds.insert(entry->property("id").toString()); + } - for (auto it = m_apps.keyBegin(); it != m_apps.keyEnd(); ++it) { - const auto& id = *it; - if (!newIds.contains(id)) { - dirty = true; - m_apps.take(id)->deleteLater(); - } - } + for (auto it = m_apps.begin(); it != m_apps.end();) { + if (!newIds.contains(it.key())) { + dirty = true; + it.value()->deleteLater(); + it = m_apps.erase(it); + } else { + ++it; + } + } - if (dirty) { - emit appsChanged(); - } + if (dirty) { + emit appsChanged(); + } } } // namespace ZShell diff --git a/Plugins/ZShell/appdb.hpp b/Plugins/ZShell/appdb.hpp index 48c7187..2a5f1fb 100644 --- a/Plugins/ZShell/appdb.hpp +++ b/Plugins/ZShell/appdb.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace ZShell { @@ -66,6 +67,7 @@ QML_ELEMENT Q_PROPERTY(QString uuid READ uuid CONSTANT) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED) Q_PROPERTY(QObjectList entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED) +Q_PROPERTY(QStringList favoriteApps READ favoriteApps WRITE setFavoriteApps NOTIFY favoriteAppsChanged REQUIRED) Q_PROPERTY(QQmlListProperty apps READ apps NOTIFY appsChanged) public: @@ -79,6 +81,9 @@ void setPath(const QString& path); [[nodiscard]] QObjectList entries() const; void setEntries(const QObjectList& entries); +[[nodiscard]] QStringList favoriteApps() const; +void setFavoriteApps(const QStringList& favApps); + [[nodiscard]] QQmlListProperty apps(); Q_INVOKABLE void incrementFrequency(const QString& id); @@ -86,6 +91,7 @@ Q_INVOKABLE void incrementFrequency(const QString& id); signals: void pathChanged(); void entriesChanged(); +void favoriteAppsChanged(); void appsChanged(); private: @@ -94,10 +100,14 @@ QTimer* m_timer; const QString m_uuid; QString m_path; QObjectList m_entries; +QStringList m_favoriteApps; +QList m_favoriteAppsRegex; QHash m_apps; mutable QList m_sortedApps; +QString regexifyString(const QString& original) const; QList& getSortedApps() const; +bool isFavorite(const AppEntry* app) const; quint32 getFrequency(const QString& id) const; void updateAppFrequencies(); void updateApps(); -- 2.47.3 From 5c6882d2a8641c216744fab9d3bc79dfa0e12506 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 23:06:30 +0200 Subject: [PATCH 03/17] added panels page --- Config/Config.qml | 5 +- Config/DashboardConfig.qml | 1 - Config/Launcher.qml | 3 + Config/SidebarConfig.qml | 1 + Drawers/Interactions.qml | 4 +- Modules/Launcher/Services/Actions.qml | 2 +- Modules/SettingsNew/PageCompRegistry.qml | 27 +++- Modules/SettingsNew/PageRegistry.qml | 2 +- Modules/SettingsNew/Pages/Apps/AppInfo.qml | 2 +- Modules/SettingsNew/Pages/Panels/BarPanel.qml | 82 +++++++++++ .../Pages/Panels/DashboardPanel.qml | 85 ++++++++++++ .../Pages/Panels/LauncherPanel.qml | 131 ++++++++++++++++++ .../SettingsNew/Pages/Panels/SidebarPanel.qml | 46 ++++++ Modules/SettingsNew/Pages/PanelsPage.qml | 50 +++++++ 14 files changed, 433 insertions(+), 8 deletions(-) create mode 100644 Modules/SettingsNew/Pages/Panels/BarPanel.qml create mode 100644 Modules/SettingsNew/Pages/Panels/DashboardPanel.qml create mode 100644 Modules/SettingsNew/Pages/Panels/LauncherPanel.qml create mode 100644 Modules/SettingsNew/Pages/Panels/SidebarPanel.qml create mode 100644 Modules/SettingsNew/Pages/PanelsPage.qml diff --git a/Config/Config.qml b/Config/Config.qml index 422d81b..e59116c 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -167,7 +167,6 @@ Singleton { enabled: dashboard.enabled, mediaUpdateInterval: dashboard.mediaUpdateInterval, resourceUpdateInterval: dashboard.resourceUpdateInterval, - dragThreshold: dashboard.dragThreshold, performance: { showBattery: dashboard.performance.showBattery, showGpu: dashboard.performance.showGpu, @@ -245,6 +244,9 @@ Singleton { return { maxAppsShown: launcher.maxAppsShown, maxWallpapers: launcher.maxWallpapers, + dragThreshold: launcher.dragThreshold, + enableDangerousActions: launcher.enableDangerousActions, + enabled: launcher.enabled, hiddenApps: launcher.hiddenApps, favoriteApps: launcher.favoriteApps, uwsm: launcher.uwsm, @@ -349,6 +351,7 @@ Singleton { function serializeSidebar(): var { return { enabled: sidebar.enabled, + dragThreshold: sidebar.dragThreshold, sizes: { width: sidebar.sizes.width } diff --git a/Config/DashboardConfig.qml b/Config/DashboardConfig.qml index e0286bb..6ae2d55 100644 --- a/Config/DashboardConfig.qml +++ b/Config/DashboardConfig.qml @@ -1,7 +1,6 @@ import Quickshell.Io JsonObject { - property int dragThreshold: 50 property bool enabled: true property int mediaUpdateInterval: 500 property Performance performance: Performance { diff --git a/Config/Launcher.qml b/Config/Launcher.qml index 07070e3..d746bbd 100644 --- a/Config/Launcher.qml +++ b/Config/Launcher.qml @@ -84,6 +84,9 @@ JsonObject { dangerous: false }, ] + property int dragThreshold: 50 + property bool enableDangerousActions: false + property bool enabled: true property list favoriteApps: [] property list hiddenApps: [] property int maxAppsShown: 10 diff --git a/Config/SidebarConfig.qml b/Config/SidebarConfig.qml index 80800ce..e064090 100644 --- a/Config/SidebarConfig.qml +++ b/Config/SidebarConfig.qml @@ -1,6 +1,7 @@ import Quickshell.Io JsonObject { + property int dragThreshold: 30 property bool enabled: true property Sizes sizes: Sizes { } diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index a70df18..7148e46 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -75,7 +75,7 @@ Item { const dragX = x - centroid.pressPosition.x; const dragY = y - centroid.pressPosition.y; - if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -200) + if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold) root.visibilities.launcher = true; if (root.singleGestureTriggered) @@ -100,7 +100,7 @@ Item { root.singleGestureTriggered = true; } - if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -20) { + if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) { root.visibilities.sidebar = true; root.singleGestureTriggered = true; } diff --git a/Modules/Launcher/Services/Actions.qml b/Modules/Launcher/Services/Actions.qml index ae4f6fa..25a1b26 100644 --- a/Modules/Launcher/Services/Actions.qml +++ b/Modules/Launcher/Services/Actions.qml @@ -19,7 +19,7 @@ Searcher { Variants { id: variants - model: Config.launcher.actions.filter(a => (a.enabled ?? true)) + model: Config.launcher.actions.filter(a => (a.enabled ?? true) && (Config.launcher.enableDangerousActions || !(a.dangerous ?? false))) Action { } diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml index 327870d..e2c7c83 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -9,6 +9,7 @@ import qs.Modules.SettingsNew.Pages import qs.Modules.SettingsNew.Pages.Wallpaper import qs.Modules.SettingsNew.Pages.Audio import qs.Modules.SettingsNew.Pages.Apps +import qs.Modules.SettingsNew.Pages.Panels QtObject { id: root @@ -65,7 +66,31 @@ QtObject { // Shell Component { - PlaceholderComp { + StackPage { + Component { + PanelsPage { + } + } + + Component { + DashboardPanel { + } + } + + Component { + BarPanel { + } + } + + Component { + LauncherPanel { + } + } + + Component { + SidebarPanel { + } + } } }, Component { diff --git a/Modules/SettingsNew/PageRegistry.qml b/Modules/SettingsNew/PageRegistry.qml index e116e6a..84aef7f 100644 --- a/Modules/SettingsNew/PageRegistry.qml +++ b/Modules/SettingsNew/PageRegistry.qml @@ -31,7 +31,7 @@ Singleton { { name: qsTr("Connected devices"), icon: "devices_other", - description: qsTr("Bluetooth, paiting"), + description: qsTr("Bluetooth, pairing"), category: "connectivity" }, { diff --git a/Modules/SettingsNew/Pages/Apps/AppInfo.qml b/Modules/SettingsNew/Pages/Apps/AppInfo.qml index ba44059..65f0a7e 100644 --- a/Modules/SettingsNew/Pages/Apps/AppInfo.qml +++ b/Modules/SettingsNew/Pages/Apps/AppInfo.qml @@ -82,7 +82,7 @@ PageBase { checked: root.app && Strings.testRegexList(Config.launcher.favoriteApps, root.app.id) enabled: !root.favoriteByRegex first: true - subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the top of the launcher") + subtext: root.favoriteByRegex ? qsTr("Matched by a regex in favoriteApps — edit the config file to change") : qsTr("Pin to the bottom of the launcher") text: qsTr("Favorite") onToggled: { diff --git a/Modules/SettingsNew/Pages/Panels/BarPanel.qml b/Modules/SettingsNew/Pages/Panels/BarPanel.qml new file mode 100644 index 0000000..28269a2 --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/BarPanel.qml @@ -0,0 +1,82 @@ +pragma ComponentBehavior: Bound + +import QtQuick.Layouts +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Taskbar") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Behavior + SectionHeader { + first: true + text: qsTr("Behavior") + } + + ToggleRow { + checked: Config.barConfig.autoHide + first: true + last: true + subtext: qsTr("Hide the bar, reveal on hover") + text: qsTr("Auto hide") + + onToggled: Config.barConfig.autoHide = checked + } + + // Components + SectionHeader { + text: qsTr("Components") + } + + NavRow { + first: true + icon: "workspaces" + label: qsTr("Workspaces") + status: qsTr("Indicators, window icons") + + onClicked: root.sState.openSubPage(5) + } + + NavRow { + icon: "web_asset" + label: qsTr("Active window") + status: qsTr("Title display, popout") + + onClicked: root.sState.openSubPage(6) + } + + NavRow { + icon: "widgets" + label: qsTr("Tray") + status: qsTr("System tray icons") + + onClicked: root.sState.openSubPage(7) + } + + NavRow { + icon: "signal_cellular_alt" + label: qsTr("Status icons") + status: qsTr("Visible indicators") + + onClicked: root.sState.openSubPage(8) + } + + NavRow { + icon: "schedule" + label: qsTr("Clock") + last: true + status: qsTr("Date, icon, background") + + onClicked: root.sState.openSubPage(9) + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml b/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml new file mode 100644 index 0000000..bb4ebad --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml @@ -0,0 +1,85 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Dashboard") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // General + SectionHeader { + first: true + text: qsTr("General") + } + + ToggleRow { + checked: Config.dashboard.enabled + first: true + last: true + text: qsTr("Enabled") + + onToggled: Config.dashboard.enabled = checked + } + + // Performance widgets + SectionHeader { + text: qsTr("Performance widgets") + } + + ToggleRow { + checked: Config.dashboard.performance.showBattery + first: true + text: qsTr("Battery") + + onToggled: Config.dashboard.performance.showBattery = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showGpu + text: qsTr("GPU") + + onToggled: Config.dashboard.performance.showGpu = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showCpu + text: qsTr("CPU") + + onToggled: Config.dashboard.performance.showCpu = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showMemory + text: qsTr("Memory") + + onToggled: Config.dashboard.performance.showMemory = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showStorage + text: qsTr("Storage") + + onToggled: Config.dashboard.performance.showStorage = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showNetwork + last: true + text: qsTr("Network") + + onToggled: Config.dashboard.performance.showNetwork = checked + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/LauncherPanel.qml b/Modules/SettingsNew/Pages/Panels/LauncherPanel.qml new file mode 100644 index 0000000..471b49c --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/LauncherPanel.qml @@ -0,0 +1,131 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Launcher") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // General + SectionHeader { + first: true + text: qsTr("General") + } + + ToggleRow { + checked: Config.launcher.enabled + first: true + last: true + text: qsTr("Enabled") + + onToggled: Config.launcher.enabled = checked + } + + // Display + SectionHeader { + text: qsTr("Display") + } + + SpinRow { + first: true + from: 1 + stepSize: 1 + text: qsTr("Max items shown") + to: 20 + value: Config.launcher.maxAppsShown + + onMoved: v => Config.launcher.maxShown = v + } + + SpinRow { + from: 1 + stepSize: 1 + text: qsTr("Max wallpapers") + to: 30 + value: Config.launcher.maxWallpapers + + onMoved: v => Config.launcher.maxWallpapers = v + } + + SpinRow { + from: 0 + last: true + stepSize: 5 + subtext: qsTr("Pixels dragged before the launcher opens") + text: qsTr("Drag threshold") + to: 200 + value: Config.launcher.dragThreshold + + onMoved: v => Config.launcher.dragThreshold = v + } + + // Behavior + SectionHeader { + text: qsTr("Behavior") + } + + ToggleRow { + checked: Config.launcher.enableDangerousActions + first: true + last: true + subtext: qsTr("Allow actions that shut down or log out") + text: qsTr("Enable dangerous actions") + + onToggled: Config.launcher.enableDangerousActions = checked + } + + // Fuzzy search + SectionHeader { + text: qsTr("Fuzzy search") + } + + ToggleRow { + checked: Config.launcher.useFuzzy.apps + first: true + text: qsTr("Apps") + + onToggled: Config.launcher.useFuzzy.apps = checked + } + + ToggleRow { + checked: Config.launcher.useFuzzy.actions + text: qsTr("Actions") + + onToggled: Config.launcher.useFuzzy.actions = checked + } + + ToggleRow { + checked: Config.launcher.useFuzzy.schemes + text: qsTr("Schemes") + + onToggled: Config.launcher.useFuzzy.schemes = checked + } + + ToggleRow { + checked: Config.launcher.useFuzzy.variants + text: qsTr("Variants") + + onToggled: Config.launcher.useFuzzy.variants = checked + } + + ToggleRow { + checked: Config.launcher.useFuzzy.wallpapers + last: true + text: qsTr("Wallpapers") + + onToggled: Config.launcher.useFuzzy.wallpapers = checked + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/SidebarPanel.qml b/Modules/SettingsNew/Pages/Panels/SidebarPanel.qml new file mode 100644 index 0000000..57f1159 --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/SidebarPanel.qml @@ -0,0 +1,46 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Sidebar") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + SectionHeader { + first: true + text: qsTr("General") + } + + ToggleRow { + checked: Config.sidebar.enabled + first: true + text: qsTr("Enabled") + + onToggled: Config.sidebar.enabled = checked + } + + SpinRow { + from: 0 + last: true + stepSize: 5 + subtext: qsTr("Pixels dragged before the sidebar opens") + text: qsTr("Drag threshold") + to: 200 + value: Config.sidebar.dragThreshold + + onMoved: v => Config.sidebar.dragThreshold = v + } + } +} diff --git a/Modules/SettingsNew/Pages/PanelsPage.qml b/Modules/SettingsNew/Pages/PanelsPage.qml new file mode 100644 index 0000000..9cb238c --- /dev/null +++ b/Modules/SettingsNew/Pages/PanelsPage.qml @@ -0,0 +1,50 @@ +import QtQuick.Layouts +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + title: qsTr("Panels") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + NavRow { + first: true + icon: "dashboard" + label: qsTr("Dashboard") + status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled") + + onClicked: root.sState.openSubPage(1) + } + + NavRow { + icon: "dock_to_bottom" + label: qsTr("Taskbar") + status: !Config.barConfig.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover") + + onClicked: root.sState.openSubPage(2) + } + + NavRow { + icon: "apps" + label: qsTr("Launcher") + status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled") + + onClicked: root.sState.openSubPage(3) + } + + NavRow { + icon: "dock_to_right" + label: qsTr("Sidebar") + last: true + status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled") + + onClicked: root.sState.openSubPage(4) + } + } +} -- 2.47.3 From 4df31d7564ea2da376c9eafe7539fc39183aa768 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 15:30:33 +0200 Subject: [PATCH 04/17] added services page + rename barConfig json property to bar, migration helper function --- Components/CustomFlickable.qml | 18 + Config/BarConfig.qml | 6 + Config/Config.qml | 412 ++---------------- Config/DashboardConfig.qml | 1 + Config/NotifConfig.qml | 1 + Daemons/NotifServer.qml | 7 +- Drawers/Exclusions.qml | 2 +- Drawers/Interactions.qml | 20 +- Drawers/Windows.qml | 14 +- Helpers/HasNotifications.qml | 16 - Modules/Bar/Bar.qml | 10 +- Modules/Bar/BarLoader.qml | 12 +- Modules/Bar/Border.qml | 8 +- Modules/Clock.qml | 2 +- Modules/HyprsunsetWidget.qml | 2 +- Modules/Launcher/WallpaperList.qml | 2 +- Modules/MediaWidget.qml | 2 +- Modules/NotifBell.qml | 7 +- Modules/Notifications/Content.qml | 17 +- .../Sidebar/Utils/Background.qml | 2 +- Modules/Resources.qml | 2 +- Modules/Settings/Categories/Bar.qml | 28 +- Modules/SettingsNew/Common/NavRow.qml | 6 +- Modules/SettingsNew/Common/PopupRow.qml | 6 +- Modules/SettingsNew/PageCompRegistry.qml | 42 +- Modules/SettingsNew/Pages/AppsPage.qml | 10 +- .../SettingsNew/Pages/Panels/Bar/BarClock.qml | 61 +++ .../Pages/Panels/Bar/BarStatusIcons.qml | 93 ++++ .../SettingsNew/Pages/Panels/Bar/BarTray.qml | 31 ++ Modules/SettingsNew/Pages/Panels/BarPanel.qml | 40 +- .../Pages/Panels/DashboardPanel.qml | 49 --- .../Pages/Panels/ResourcesPanel.qml | 85 ++++ Modules/SettingsNew/Pages/PanelsPage.qml | 34 +- .../Pages/Services/NotificationsPage.qml | 162 +++++++ Modules/SettingsNew/Pages/ServicesPage.qml | 214 +++++++++ Modules/SysTray/TrayItem.qml | 4 +- Modules/SysTray/TrayWidget.qml | 25 +- Modules/SysTray/Widgets/AudioWidget.qml | 43 ++ Modules/SysTray/Widgets/UPowerWidget.qml | 24 + Modules/Updates/UpdatesWidget.qml | 2 +- Modules/Workspaces.qml | 2 +- 41 files changed, 963 insertions(+), 561 deletions(-) delete mode 100644 Helpers/HasNotifications.qml create mode 100644 Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml create mode 100644 Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml create mode 100644 Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml create mode 100644 Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml create mode 100644 Modules/SettingsNew/Pages/Services/NotificationsPage.qml create mode 100644 Modules/SettingsNew/Pages/ServicesPage.qml diff --git a/Components/CustomFlickable.qml b/Components/CustomFlickable.qml index 41b7ba8..249f093 100644 --- a/Components/CustomFlickable.qml +++ b/Components/CustomFlickable.qml @@ -4,12 +4,30 @@ import qs.Helpers Flickable { id: root + property bool doneFakeFlick + interactive: !Visibilities.getForActive().isDrawing maximumFlickVelocity: 3000 rebound: Transition { + onRunningChanged: { + if (!running && !root.doneFakeFlick) { + root.doneFakeFlick = true; + root.flick(1, 1); + root.flick(-1, -1); + Qt.callLater(() => root.cancelFlick()); + } + } + Anim { properties: "x,y" } } + + Timer { + interval: 10 + running: root.doneFakeFlick + + onTriggered: root.doneFakeFlick = false + } } diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index 0f489ef..55a3d35 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -72,6 +72,12 @@ JsonObject { property bool upower: true } component Tray: JsonObject { + property bool showAudio: true + property bool showBluetooth: false + property bool showMicrophone: true + property bool showNetwork: false + property bool showPower: true + property bool showWifi: false property int trayIconSize: 24 } } diff --git a/Config/Config.qml b/Config/Config.qml index e59116c..6d22849 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -12,7 +12,7 @@ Singleton { property alias appearance: adapter.appearance property alias background: adapter.background - property alias barConfig: adapter.barConfig + property alias bar: adapter.bar property alias clipboard: adapter.clipboard property alias colors: adapter.colors property alias dashboard: adapter.dashboard @@ -20,6 +20,7 @@ Singleton { property alias general: adapter.general property alias launcher: adapter.launcher property alias lock: adapter.lock + property bool migratingLegacyBar: false property alias notifs: adapter.notifs property alias osd: adapter.osd property alias overview: adapter.overview @@ -29,6 +30,41 @@ Singleton { property alias sidebar: adapter.sidebar property alias utilities: adapter.utilities + function applyLegacyBarConfig(legacy): void { + const tray = legacy.tray ?? {}; + const popouts = legacy.popouts ?? {}; + + bar.autoHide = pick(legacy.autoHide, bar.autoHide); + bar.hideWhenNotif = pick(legacy.hideWhenNotif, bar.hideWhenNotif); + bar.rounding = pick(legacy.rounding, bar.rounding); + bar.border = pick(legacy.border, bar.border); + bar.smoothing = pick(legacy.smoothing, bar.smoothing); + bar.height = pick(legacy.height, bar.height); + bar.tray.trayIconSize = pick(tray.trayIconSize, bar.tray.trayIconSize); + bar.popouts.tray = pick(popouts.tray, bar.popouts.tray); + bar.popouts.audio = pick(popouts.audio, bar.popouts.audio); + bar.popouts.activeWindow = pick(popouts.activeWindow, bar.popouts.activeWindow); + bar.popouts.resources = pick(popouts.resources, bar.popouts.resources); + bar.popouts.clock = pick(popouts.clock, bar.popouts.clock); + bar.popouts.network = pick(popouts.network, bar.popouts.network); + bar.popouts.upower = pick(popouts.upower, bar.popouts.upower); + bar.entries = pick(legacy.entries, bar.entries); + } + + function migrateLegacyBarConfig(raw): bool { + if (raw.bar !== undefined || raw.barConfig === undefined) + return false; + + migratingLegacyBar = true; + applyLegacyBarConfig(raw.barConfig); + migratingLegacyBar = false; + return true; + } + + function pick(value, fallback) { + return value === undefined ? fallback : value; + } + function save(): void { saveTimer.restart(); recentlySaved = true; @@ -39,353 +75,6 @@ Singleton { saveTimer.restart(); } - function serializeAppearance(): var { - return { - rounding: { - scale: appearance.rounding.scale - }, - spacing: { - scale: appearance.spacing.scale - }, - padding: { - scale: appearance.padding.scale - }, - deform: { - scale: appearance.deform.scale - }, - font: { - family: { - sans: appearance.font.family.sans, - mono: appearance.font.family.mono, - material: appearance.font.family.material, - clock: appearance.font.family.clock - }, - size: { - scale: appearance.font.size.scale - } - }, - anim: { - mediaGifSpeedAdjustment: appearance.anim.mediaGifSpeedAdjustment, - sessionGifSpeed: appearance.anim.sessionGifSpeed, - durations: { - scale: appearance.anim.durations.scale - } - }, - transparency: { - enabled: appearance.transparency.enabled, - base: appearance.transparency.base, - layers: appearance.transparency.layers - } - }; - } - - function serializeBackground(): var { - return { - wallFadeDuration: background.wallFadeDuration, - enabled: background.enabled, - alignX: background.alignX, - sourceClipX: background.sourceClipX, - sourceClipY: background.sourceClipY, - sourceClipW: background.sourceClipW, - sourceClipH: background.sourceClipH, - alignY: background.alignY, - zoom: background.zoom - }; - } - - function serializeBar(): var { - return { - autoHide: barConfig.autoHide, - hideWhenNotif: barConfig.hideWhenNotif, - rounding: barConfig.rounding, - border: barConfig.border, - smoothing: barConfig.smoothing, - height: barConfig.height, - tray: { - trayIconSize: barConfig.tray.trayIconSize - }, - popouts: { - tray: barConfig.popouts.tray, - audio: barConfig.popouts.audio, - activeWindow: barConfig.popouts.activeWindow, - resources: barConfig.popouts.resources, - clock: barConfig.popouts.clock, - network: barConfig.popouts.network, - upower: barConfig.popouts.upower - }, - entries: barConfig.entries - }; - } - - function serializeClipboard(): var { - return { - enabled: clipboard.enabled, - maxEntriesShown: clipboard.maxEntriesShown, - sizes: { - width: clipboard.sizes.width, - previewWidth: clipboard.sizes.previewWidth, - minPreviewWidth: clipboard.sizes.minPreviewWidth, - itemHeight: clipboard.sizes.itemHeight - } - }; - } - - function serializeColors(): var { - return { - schemeType: colors.schemeType, - presets: { - name: colors.presets.name, - variant: colors.presets.variant, - accent: colors.presets.accent - } - }; - } - - function serializeConfig(): var { - return { - barConfig: serializeBar(), - lock: serializeLock(), - general: serializeGeneral(), - services: serializeServices(), - notifs: serializeNotifs(), - sidebar: serializeSidebar(), - utilities: serializeUtilities(), - dashboard: serializeDashboard(), - appearance: serializeAppearance(), - osd: serializeOsd(), - background: serializeBackground(), - launcher: serializeLauncher(), - colors: serializeColors(), - dock: serializeDock(), - screenshot: serializeScreenshot(), - clipboard: serializeClipboard() - }; - } - - function serializeDashboard(): var { - return { - enabled: dashboard.enabled, - mediaUpdateInterval: dashboard.mediaUpdateInterval, - resourceUpdateInterval: dashboard.resourceUpdateInterval, - performance: { - showBattery: dashboard.performance.showBattery, - showGpu: dashboard.performance.showGpu, - showCpu: dashboard.performance.showCpu, - showMemory: dashboard.performance.showMemory, - showStorage: dashboard.performance.showStorage, - showNetwork: dashboard.performance.showNetwork - }, - sizes: { - tabIndicatorHeight: dashboard.sizes.tabIndicatorHeight, - tabIndicatorSpacing: dashboard.sizes.tabIndicatorSpacing, - infoWidth: dashboard.sizes.infoWidth, - infoIconSize: dashboard.sizes.infoIconSize, - dateTimeWidth: dashboard.sizes.dateTimeWidth, - mediaWidth: dashboard.sizes.mediaWidth, - mediaProgressSweep: dashboard.sizes.mediaProgressSweep, - mediaProgressThickness: dashboard.sizes.mediaProgressThickness, - resourceProgessThickness: dashboard.sizes.resourceProgessThickness, - weatherWidth: dashboard.sizes.weatherWidth, - mediaCoverArtSize: dashboard.sizes.mediaCoverArtSize, - mediaVisualizerSize: dashboard.sizes.mediaVisualizerSize, - resourceSize: dashboard.sizes.resourceSize - } - }; - } - - function serializeDock(): var { - return { - enable: dock.enable, - height: dock.height, - hoverToReveal: dock.hoverToReveal, - pinnedApps: dock.pinnedApps, - pinnedOnStartup: dock.pinnedOnStartup, - ignoredAppRegexes: dock.ignoredAppRegexes - }; - } - - function serializeGeneral(): var { - return { - logo: general.logo, - wallpaperPath: general.wallpaperPath, - showOverFullscreen: general.showOverFullscreen, - desktopIcons: general.desktopIcons, - dateFormat: general.dateFormat, - color: { - mode: general.color.mode, - smart: general.color.smart, - scheduleDark: general.color.scheduleDark, - scheduleHyprsunset: general.color.scheduleHyprsunset, - scheduleHyprsunsetStart: general.color.scheduleHyprsunsetStart, - hyprsunsetTemp: general.color.hyprsunsetTemp, - scheduleHyprsunsetEnd: general.color.scheduleHyprsunsetEnd, - schemeGeneration: general.color.schemeGeneration, - scheduleDarkStart: general.color.scheduleDarkStart, - scheduleDarkEnd: general.color.scheduleDarkEnd, - neovimColors: general.color.neovimColors - }, - apps: { - terminal: general.apps.terminal, - audio: general.apps.audio, - playback: general.apps.playback, - explorer: general.apps.explorer - }, - idle: { - timeouts: general.idle.timeouts - }, - battery: { - popupThresholds: general.battery.popupThresholds, - critPerc: general.battery.critPerc - } - }; - } - - function serializeLauncher(): var { - return { - maxAppsShown: launcher.maxAppsShown, - maxWallpapers: launcher.maxWallpapers, - dragThreshold: launcher.dragThreshold, - enableDangerousActions: launcher.enableDangerousActions, - enabled: launcher.enabled, - hiddenApps: launcher.hiddenApps, - favoriteApps: launcher.favoriteApps, - uwsm: launcher.uwsm, - actionPrefix: launcher.actionPrefix, - specialPrefix: launcher.specialPrefix, - useFuzzy: { - apps: launcher.useFuzzy.apps, - actions: launcher.useFuzzy.actions, - schemes: launcher.useFuzzy.schemes, - variants: launcher.useFuzzy.variants, - wallpapers: launcher.useFuzzy.wallpapers - }, - sizes: { - itemWidth: launcher.sizes.itemWidth, - itemHeight: launcher.sizes.itemHeight, - wallpaperWidth: launcher.sizes.wallpaperWidth, - wallpaperHeight: launcher.sizes.wallpaperHeight - }, - actions: launcher.actions - }; - } - - function serializeLock(): var { - return { - recolorLogo: lock.recolorLogo, - enableFprint: lock.enableFprint, - showNotifContent: lock.showNotifContent, - showNotifIcon: lock.showNotifIcon, - maxFprintTries: lock.maxFprintTries, - blurAmount: lock.blurAmount, - sizes: { - heightMult: lock.sizes.heightMult, - ratio: lock.sizes.ratio, - centerWidth: lock.sizes.centerWidth - } - }; - } - - function serializeNotifs(): var { - return { - expire: notifs.expire, - defaultExpireTimeout: notifs.defaultExpireTimeout, - appNotifCooldown: notifs.appNotifCooldown, - clearThreshold: notifs.clearThreshold, - expandThreshold: notifs.expandThreshold, - actionOnClick: notifs.actionOnClick, - groupPreviewNum: notifs.groupPreviewNum, - sizes: { - width: notifs.sizes.width, - image: notifs.sizes.image, - badge: notifs.sizes.badge - } - }; - } - - function serializeOsd(): var { - return { - enabled: osd.enabled, - hideDelay: osd.hideDelay, - enableBrightness: osd.enableBrightness, - enableMicrophone: osd.enableMicrophone, - allMonBrightness: osd.allMonBrightness, - sizes: { - sliderWidth: osd.sizes.sliderWidth, - sliderHeight: osd.sizes.sliderHeight - } - }; - } - - function serializeScreenshot(): var { - return { - enable_pp: screenshot.enable_pp, - mode: screenshot.mode, - radius: screenshot.radius, - shadow: screenshot.shadow, - rounding: screenshot.rounding, - shadow_blur: screenshot.shadow_blur, - shadow_color: screenshot.shadow_color, - shadow_offset_x: screenshot.shadow_offset_x, - shadow_offset_y: screenshot.shadow_offset_y - }; - } - - function serializeServices(): var { - return { - weatherLocation: services.weatherLocation, - updates: services.updates, - useFahrenheit: services.useFahrenheit, - minBrightness: services.minBrightness, - ddcutilService: services.ddcutilService, - useTwelveHourClock: services.useTwelveHourClock, - gpuType: services.gpuType, - audioIncrement: services.audioIncrement, - brightnessIncrement: services.brightnessIncrement, - maxVolume: services.maxVolume, - defaultPlayer: services.defaultPlayer, - playerAliases: services.playerAliases, - visualizerBars: services.visualizerBars - }; - } - - function serializeSidebar(): var { - return { - enabled: sidebar.enabled, - dragThreshold: sidebar.dragThreshold, - sizes: { - width: sidebar.sizes.width - } - }; - } - - function serializeUtilities(): var { - return { - enabled: utilities.enabled, - maxToasts: utilities.maxToasts, - sizes: { - width: utilities.sizes.width, - toastWidth: utilities.sizes.toastWidth - }, - toasts: { - configLoaded: utilities.toasts.configLoaded, - chargingChanged: utilities.toasts.chargingChanged, - gameModeChanged: utilities.toasts.gameModeChanged, - dndChanged: utilities.toasts.dndChanged, - audioOutputChanged: utilities.toasts.audioOutputChanged, - audioInputChanged: utilities.toasts.audioInputChanged, - capsLockChanged: utilities.toasts.capsLockChanged, - numLockChanged: utilities.toasts.numLockChanged, - kbLayoutChanged: utilities.toasts.kbLayoutChanged, - vpnChanged: utilities.toasts.vpnChanged, - nowPlaying: utilities.toasts.nowPlaying - }, - vpn: { - enabled: utilities.vpn.enabled, - provider: utilities.vpn.provider - } - }; - } - ElapsedTimer { id: timer } @@ -397,20 +86,7 @@ Singleton { onTriggered: { timer.restart(); - try { - let config = {}; - try { - config = JSON.parse(fileView.text()); - } catch (e) { - config = {}; - } - - config = root.serializeConfig(); - - fileView.setText(JSON.stringify(config, null, 4)); - } catch (e) { - Toaster.toast(qsTr("Failed to serialize config"), e.message, "settings_alert", Toast.Error); - } + fileView.writeAdapter(); } } @@ -430,6 +106,10 @@ Singleton { path: `${Paths.config}/config.json` watchChanges: true + onAdapterUpdated: { + if (!root.migratingLegacyBar) + root.save(); + } onFileChanged: { if (!root.recentlySaved) { timer.restart(); @@ -446,7 +126,11 @@ Singleton { ModeScheduler.checkStartup(); Hyprsunset.checkStartup(); try { - JSON.parse(text()); + const raw = JSON.parse(text()); + const migrated = root.migrateLegacyBarConfig(raw); + if (migrated) + root.save(); + const elapsed = timer.elapsedMs(); if (adapter.utilities.toasts.configLoaded && !root.recentlySaved) { @@ -467,7 +151,7 @@ Singleton { } property BackgroundConfig background: BackgroundConfig { } - property BarConfig barConfig: BarConfig { + property BarConfig bar: BarConfig { } property ClipboardConfig clipboard: ClipboardConfig { } diff --git a/Config/DashboardConfig.qml b/Config/DashboardConfig.qml index 6ae2d55..6f2e38e 100644 --- a/Config/DashboardConfig.qml +++ b/Config/DashboardConfig.qml @@ -10,6 +10,7 @@ JsonObject { } component Performance: JsonObject { + property bool enabled: true property bool showBattery: true property bool showCpu: true property bool showGpu: true diff --git a/Config/NotifConfig.qml b/Config/NotifConfig.qml index 0a11d46..01e8b69 100644 --- a/Config/NotifConfig.qml +++ b/Config/NotifConfig.qml @@ -9,6 +9,7 @@ JsonObject { property bool expire: true property int groupPreviewNum: 3 property bool openExpanded: false + property bool showInFullscreen: true property Sizes sizes: Sizes { } diff --git a/Daemons/NotifServer.qml b/Daemons/NotifServer.qml index 34b81ec..66b6206 100644 --- a/Daemons/NotifServer.qml +++ b/Daemons/NotifServer.qml @@ -25,7 +25,7 @@ Singleton { property alias server: server function shouldThrottle(appName: string): bool { - if (props.dnd) + if (props.dnd || [...Visibilities.screens.values()].some(v => v.sidebar)) return false; const key = (appName || "unknown").trim().toLowerCase(); @@ -49,11 +49,6 @@ Singleton { if (loaded) { saveTimer.restart(); } - if (root.list.length > 0) { - HasNotifications.hasNotifications = true; - } else { - HasNotifications.hasNotifications = false; - } } Timer { diff --git a/Drawers/Exclusions.qml b/Drawers/Exclusions.qml index 5cded67..3373e34 100644 --- a/Drawers/Exclusions.qml +++ b/Drawers/Exclusions.qml @@ -44,7 +44,7 @@ Scope { } component ExclusionZone: CustomWindow { - exclusiveZone: Config.barConfig.border + exclusiveZone: Config.bar.border implicitHeight: 1 implicitWidth: 1 name: "Bar-Exclusion" diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index 7148e46..363ab4e 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -21,15 +21,15 @@ Item { required property PersistentProperties visibilities function inBottomPanel(panel: Item, x: real, y: real): bool { - return y > root.height - panel.height - Config.barConfig.border && withinPanelWidth(panel, x, y); + return y > root.height - panel.height - Config.bar.border && withinPanelWidth(panel, x, y); } function inLeftPanel(panel: Item, x: real, y: real): bool { - return x < panel.x + panel.width + Config.barConfig.border && withinPanelHeight(panel, x, y); + return x < panel.x + panel.width + Config.bar.border && withinPanelHeight(panel, x, y); } function inRightPanel(panel: Item, x: real, y: real): bool { - return x > panel.x - Config.barConfig.border && withinPanelHeight(panel, x, y); + return x > panel.x - Config.bar.border && withinPanelHeight(panel, x, y); } function inTopPanel(panel: Item, x: real, y: real): bool { @@ -75,7 +75,7 @@ Item { const dragX = x - centroid.pressPosition.x; const dragY = y - centroid.pressPosition.y; - if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold) + if (centroid.pressPosition.y >= root.screen.height - Config.bar.border && centroid.pressPosition.x > root.screen.width / 5 && dragY < -Config.launcher.dragThreshold) root.visibilities.launcher = true; if (root.singleGestureTriggered) @@ -91,7 +91,7 @@ Item { } } - if (centroid.pressPosition.y > root.screen.height - Config.barConfig.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) + if (centroid.pressPosition.y > root.screen.height - Config.bar.border && centroid.pressPosition.x < root.screen.width / 5 && dragY < -50) root.visibilities.clipboard = true; if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight && centroid.pressPosition.x > root.screen.width / 5 && !root.visibilities.launcher) @@ -100,17 +100,17 @@ Item { root.singleGestureTriggered = true; } - if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) { + if (centroid.pressPosition.x > root.screen.width - Config.bar.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -Config.sidebar.dragThreshold) { root.visibilities.sidebar = true; root.singleGestureTriggered = true; } - if (centroid.pressPosition.x >= root.screen.width - Config.barConfig.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { + if (centroid.pressPosition.x >= root.screen.width - Config.bar.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`); root.singleGestureTriggered = true; } - if (centroid.pressPosition.x <= Config.barConfig.border && dragX > 20) { + if (centroid.pressPosition.x <= Config.bar.border && dragX > 20) { Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`); root.singleGestureTriggered = true; } @@ -174,7 +174,7 @@ Item { root.popouts.hasCurrent = false; } - if (Config.barConfig.autoHide) + if (Config.bar.autoHide) root.bar.isHovered = false; } } @@ -193,7 +193,7 @@ Item { return; } - if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight) + if (!root.visibilities.bar && Config.bar.autoHide && y < root.bar.implicitHeight) root.bar.isHovered = true; if (root.panels.sidebar.width === 0) { diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index cb58a55..cf5c486 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -18,9 +18,9 @@ CustomWindow { id: root readonly property alias bar: bar - readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.barConfig.border - readonly property real borderRounding: Config.barConfig.rounding * (1 - fsTransitionProg) - readonly property real borderThickness: Config.barConfig.border * (1 - fsTransitionProg) + readonly property real borderLayoutThickness: hasFullscreen ? 0 : Config.bar.border + readonly property real borderRounding: Config.bar.rounding * (1 - fsTransitionProg) + readonly property real borderThickness: Config.bar.border * (1 - fsTransitionProg) readonly property int dragMaskPadding: { if (focusGrab.active) return 0; @@ -69,7 +69,7 @@ CustomWindow { } contentItem.Keys.onEscapePressed: { - if (Config.barConfig.autoHide) + if (Config.bar.autoHide) visibilities.bar = false; visibilities.launcher = false; visibilities.sidebar = false; @@ -190,8 +190,8 @@ CustomWindow { Binding { property: "bar" target: visibilities - value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.barConfig.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered - when: Config.barConfig.autoHide + value: visibilities.sidebar || visibilities.dashboard || visibilities.osd || (!Config.bar.hideWhenNotif && visibilities.notif) || visibilities.resources || visibilities.settings || bar.isHovered + when: Config.bar.autoHide } Item { @@ -211,7 +211,7 @@ CustomWindow { id: blobGroup color: root.surfaceColor - smoothing: Config.barConfig.smoothing + smoothing: Config.bar.smoothing } BlobInvertedRect { diff --git a/Helpers/HasNotifications.qml b/Helpers/HasNotifications.qml deleted file mode 100644 index 235dac8..0000000 --- a/Helpers/HasNotifications.qml +++ /dev/null @@ -1,16 +0,0 @@ -pragma Singleton - -import Quickshell -import Quickshell.Io - -Singleton { - id: root - - property alias hasNotifications: adapter.hasNotifications - - JsonObject { - id: adapter - - property bool hasNotifications: false - } -} diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index a2dd682..6c3550b 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -57,15 +57,15 @@ RowLayout { const item = ch.item; const itemWidth = item.implicitWidth; - if (id === "audio" && Config.barConfig.popouts.audio) { + if (id === "audio" && Config.bar.popouts.audio) { popouts.currentName = "audio"; popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.hasCurrent = true; - } else if (id === "network" && Config.barConfig.popouts.network) { + } else if (id === "network" && Config.bar.popouts.network) { popouts.currentName = "network"; popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.hasCurrent = true; - } else if (id === "upower" && Config.barConfig.popouts.upower) { + } else if (id === "upower" && Config.bar.popouts.upower) { popouts.currentName = "upower"; popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x); popouts.hasCurrent = true; @@ -81,8 +81,8 @@ RowLayout { Repeater { id: repeater - // model: Config.barConfig.entries.filted(n => n.index > 50).sort(n => n.index) - model: Config.barConfig.entries + // model: Config.bar.entries.filted(n => n.index > 50).sort(n => n.index) + model: Config.bar.entries DelegateChooser { role: "id" diff --git a/Modules/Bar/BarLoader.qml b/Modules/Bar/BarLoader.qml index 3fd00e1..f5baba8 100644 --- a/Modules/Bar/BarLoader.qml +++ b/Modules/Bar/BarLoader.qml @@ -15,15 +15,15 @@ import qs.Modules.Network Item { id: root - readonly property int contentHeight: Config.barConfig.height + padding * 2 - readonly property int exclusiveZone: Config.barConfig.autoHide ? Config.barConfig.border : contentHeight + readonly property int contentHeight: Config.bar.height + padding * 2 + readonly property int exclusiveZone: Config.bar.autoHide ? Config.bar.border : contentHeight required property bool fullscreen property bool isHovered - readonly property int padding: Math.max(Appearance.padding.smaller, Config.barConfig.border) + readonly property int padding: Math.max(Appearance.padding.smaller, Config.bar.border) required property Wrapper popouts required property ClipWrapper popoutsWrapper required property ShellScreen screen - readonly property bool shouldBeVisible: !fullscreen && (!Config.barConfig.autoHide || visibilities.bar || isHovered) + readonly property bool shouldBeVisible: !fullscreen && (!Config.bar.autoHide || visibilities.bar || isHovered) readonly property int vPadding: 6 required property PersistentProperties visibilities @@ -31,8 +31,8 @@ Item { content.item?.checkPopout(x); } - implicitHeight: fullscreen ? 0 : Config.barConfig.border - visible: height > Config.barConfig.border + implicitHeight: fullscreen ? 0 : Config.bar.border + visible: height > Config.bar.border states: State { name: "visible" diff --git a/Modules/Bar/Border.qml b/Modules/Bar/Border.qml index ff9e476..5257d31 100644 --- a/Modules/Bar/Border.qml +++ b/Modules/Bar/Border.qml @@ -38,11 +38,11 @@ Item { Rectangle { anchors.fill: parent - anchors.margins: Config.barConfig.border + 1 + anchors.margins: Config.bar.border + 1 anchors.topMargin: root.bar.implicitHeight + 1 - radius: Config.barConfig.border > 0 ? Config.barConfig.rounding : 0 - topLeftRadius: Config.barConfig.rounding - topRightRadius: Config.barConfig.rounding + radius: Config.bar.border > 0 ? Config.bar.rounding : 0 + topLeftRadius: Config.bar.rounding + topRightRadius: Config.bar.rounding } } } diff --git a/Modules/Clock.qml b/Modules/Clock.qml index ccbd8cd..fb668c0 100644 --- a/Modules/Clock.qml +++ b/Modules/Clock.qml @@ -14,7 +14,7 @@ CustomRect { required property PersistentProperties visibilities color: visibilities.dashboard ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: timeText.contentWidth + Appearance.padding.normal * 2 radius: Appearance.rounding.full diff --git a/Modules/HyprsunsetWidget.qml b/Modules/HyprsunsetWidget.qml index eef2218..4a2ad27 100644 --- a/Modules/HyprsunsetWidget.qml +++ b/Modules/HyprsunsetWidget.qml @@ -9,7 +9,7 @@ CustomRect { property bool tempEnabled: Hyprsunset.enabled color: root.tempEnabled ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: implicitHeight radius: Appearance.rounding.full diff --git a/Modules/Launcher/WallpaperList.qml b/Modules/Launcher/WallpaperList.qml index 94d6ab1..e1afd76 100644 --- a/Modules/Launcher/WallpaperList.qml +++ b/Modules/Launcher/WallpaperList.qml @@ -22,7 +22,7 @@ PathView { let outerMargins = 0; if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins) outerMargins = panels.utilities.implicitWidth; - const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2; + const maxWidth = screen.width - Config.bar.rounding * 4 - (barMargins + outerMargins) * 2; if (maxWidth <= 0) return 0; diff --git a/Modules/MediaWidget.qml b/Modules/MediaWidget.qml index 1b7c821..644446a 100644 --- a/Modules/MediaWidget.qml +++ b/Modules/MediaWidget.qml @@ -12,7 +12,7 @@ CustomRect { readonly property int textWidth: Math.min(metrics.width, 200) color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: layout.implicitWidth + Appearance.padding.normal * 2 radius: Appearance.rounding.full diff --git a/Modules/NotifBell.qml b/Modules/NotifBell.qml index 4dd85f2..ba15f26 100644 --- a/Modules/NotifBell.qml +++ b/Modules/NotifBell.qml @@ -1,8 +1,7 @@ import Quickshell -import Quickshell.Hyprland import QtQuick import qs.Config -import qs.Helpers +import qs.Daemons import qs.Components CustomRect { @@ -12,7 +11,7 @@ CustomRect { required property PersistentProperties visibilities color: visibilities.sidebar ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: implicitHeight radius: Appearance.rounding.full @@ -24,7 +23,7 @@ CustomRect { fill: root.visibilities.sidebar ? 1 : 0 font.family: "Material Symbols Rounded" font.pointSize: Appearance.font.size.larger - text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4" + text: NotifServer.list.length ? "\uf4fe" : "\ue7f4" Behavior on color { CAnim { diff --git a/Modules/Notifications/Content.qml b/Modules/Notifications/Content.qml index 71ddb2d..568f81e 100644 --- a/Modules/Notifications/Content.qml +++ b/Modules/Notifications/Content.qml @@ -1,11 +1,12 @@ pragma ComponentBehavior: Bound -import qs.Components -import qs.Config -import qs.Daemons import Quickshell import Quickshell.Widgets import QtQuick +import qs.Components +import qs.Config +import qs.Daemons +import qs.Helpers Item { id: root @@ -72,7 +73,15 @@ Item { } } model: ScriptModel { - values: NotifServer.popups.filter(n => !n.closed) + values: { + const popups = NotifServer.popups.filter(n => !n.closed); + + if (!Config.notifs.showInFullscreen) { + if (Hypr.monitorFor(root.panels.screen).activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false) + return []; + } + return popups; + } } move: Transition { Anim { diff --git a/Modules/Notifications/Sidebar/Utils/Background.qml b/Modules/Notifications/Sidebar/Utils/Background.qml index 359e19f..df12728 100644 --- a/Modules/Notifications/Sidebar/Utils/Background.qml +++ b/Modules/Notifications/Sidebar/Utils/Background.qml @@ -7,7 +7,7 @@ ShapePath { id: root readonly property bool flatten: wrapper.height < rounding * 2 - readonly property real rounding: Config.barConfig.rounding + readonly property real rounding: Config.bar.rounding readonly property real roundingY: flatten ? wrapper.height / 2 : rounding required property var sidebar required property Wrapper wrapper diff --git a/Modules/Resources.qml b/Modules/Resources.qml index 94805d8..cf3ef24 100644 --- a/Modules/Resources.qml +++ b/Modules/Resources.qml @@ -16,7 +16,7 @@ CustomRect { clip: true color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: rowLayout.implicitWidth + Appearance.padding.larger * 2 radius: Appearance.rounding.full diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml index f139ef8..de65d23 100644 --- a/Modules/Settings/Categories/Bar.qml +++ b/Modules/Settings/Categories/Bar.qml @@ -11,7 +11,7 @@ SettingsPage { SettingSwitch { name: "Auto hide" - object: Config.barConfig + object: Config.bar setting: "autoHide" } @@ -21,7 +21,7 @@ SettingsPage { SettingSpinBox { min: 1 name: "Height" - object: Config.barConfig + object: Config.bar setting: "height" } @@ -31,7 +31,7 @@ SettingsPage { SettingSpinBox { min: 0 name: "Rounding" - object: Config.barConfig + object: Config.bar setting: "rounding" } @@ -41,7 +41,7 @@ SettingsPage { SettingSpinBox { min: 0 name: "Border" - object: Config.barConfig + object: Config.bar setting: "border" } @@ -51,7 +51,7 @@ SettingsPage { SettingSpinBox { min: 0 name: "Smoothing" - object: Config.barConfig + object: Config.bar setting: "smoothing" } } @@ -66,7 +66,7 @@ SettingsPage { SettingSpinBox { min: 16 name: "Tray icon size" - object: Config.barConfig.tray + object: Config.bar.tray setting: "trayIconSize" } } @@ -80,7 +80,7 @@ SettingsPage { SettingSwitch { name: "Tray" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "tray" } @@ -89,7 +89,7 @@ SettingsPage { SettingSwitch { name: "Audio" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "audio" } @@ -98,7 +98,7 @@ SettingsPage { SettingSwitch { name: "Active window" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "activeWindow" } @@ -107,7 +107,7 @@ SettingsPage { SettingSwitch { name: "Resources" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "resources" } @@ -116,7 +116,7 @@ SettingsPage { SettingSwitch { name: "Clock" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "clock" } @@ -125,7 +125,7 @@ SettingsPage { SettingSwitch { name: "Network" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "network" } @@ -134,7 +134,7 @@ SettingsPage { SettingSwitch { name: "Power" - object: Config.barConfig.popouts + object: Config.bar.popouts setting: "upower" } } @@ -148,7 +148,7 @@ SettingsPage { SettingBarEntryList { name: "Bar entries" - object: Config.barConfig + object: Config.bar setting: "entries" } } diff --git a/Modules/SettingsNew/Common/NavRow.qml b/Modules/SettingsNew/Common/NavRow.qml index e7a9cec..107d11a 100644 --- a/Modules/SettingsNew/Common/NavRow.qml +++ b/Modules/SettingsNew/Common/NavRow.qml @@ -9,8 +9,8 @@ ConnectedRect { id: root property alias icon: icon.text - property alias label: label.text property alias status: status.text + property alias text: label.text signal clicked @@ -28,13 +28,13 @@ ConnectedRect { anchors.leftMargin: Appearance.padding.largeIncreased anchors.margins: Appearance.padding.normal anchors.rightMargin: Appearance.padding.largeIncreased - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal MaterialIcon { id: icon color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.medium + font.pointSize: Appearance.font.size.large } ColumnLayout { diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/SettingsNew/Common/PopupRow.qml index 542a5bb..8778a90 100644 --- a/Modules/SettingsNew/Common/PopupRow.qml +++ b/Modules/SettingsNew/Common/PopupRow.qml @@ -11,9 +11,9 @@ ConnectedRect { default required property Item content property alias icon: icon.text property bool keepPopupAsChild - property alias label: label.text readonly property alias popup: popup property alias status: status.text + property alias text: label.text Layout.fillWidth: true implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2 @@ -33,13 +33,13 @@ ConnectedRect { anchors.leftMargin: Appearance.padding.largeIncreased anchors.margins: Appearance.padding.normal anchors.rightMargin: Appearance.padding.largeIncreased - spacing: Appearance.spacing.small + spacing: Appearance.spacing.normal MaterialIcon { id: icon color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.medium + font.pointSize: Appearance.font.size.larger } ColumnLayout { diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml index e2c7c83..b40238f 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -10,6 +10,8 @@ import qs.Modules.SettingsNew.Pages.Wallpaper import qs.Modules.SettingsNew.Pages.Audio import qs.Modules.SettingsNew.Pages.Apps import qs.Modules.SettingsNew.Pages.Panels +import qs.Modules.SettingsNew.Pages.Panels.Bar +import qs.Modules.SettingsNew.Pages.Services QtObject { id: root @@ -72,13 +74,18 @@ QtObject { } } + Component { + BarPanel { + } + } + Component { DashboardPanel { } } Component { - BarPanel { + ResourcesPanel { } } @@ -91,8 +98,26 @@ QtObject { SidebarPanel { } } + + // Bar sub pages + Component { + BarTray { + } + } + + Component { + BarStatusIcons { + } + } + + Component { + BarClock { + } + } } }, + + // Apps Component { StackPage { Component { @@ -110,6 +135,21 @@ QtObject { } } } + }, + + // Services + Component { + StackPage { + Component { + ServicesPage { + } + } + + Component { + NotificationsPage { + } + } + } } ] readonly property Component placeholderComp: Component { diff --git a/Modules/SettingsNew/Pages/AppsPage.qml b/Modules/SettingsNew/Pages/AppsPage.qml index d33fc1a..0cb1376 100644 --- a/Modules/SettingsNew/Pages/AppsPage.qml +++ b/Modules/SettingsNew/Pages/AppsPage.qml @@ -30,33 +30,33 @@ PageBase { DefaultRow { first: true icon: "terminal" - label: qsTr("Terminal") status: Config.general.apps.terminal.join(" ") + text: qsTr("Terminal") onSelected: app => Config.general.apps.terminal = app.command } DefaultRow { icon: "volume_up" - label: qsTr("Audio") status: Config.general.apps.audio.join(" ") + text: qsTr("Audio") onSelected: app => Config.general.apps.audio = app.command } DefaultRow { icon: "play_circle" - label: qsTr("Media playback") status: Config.general.apps.playback.join(" ") + text: qsTr("Media playback") onSelected: app => Config.general.apps.playback = app.command } DefaultRow { icon: "folder" - label: qsTr("File manager") last: true status: Config.general.apps.explorer.join(" ") + text: qsTr("File manager") onSelected: app => Config.general.apps.explorer = app.command } @@ -69,9 +69,9 @@ PageBase { NavRow { first: true icon: "apps" - label: qsTr("All apps") last: true status: qsTr("Browse installed apps, set favorites and hidden") + text: qsTr("All apps") onClicked: root.sState.openSubPage(1) } diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml b/Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml new file mode 100644 index 0000000..5d2d2bb --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml @@ -0,0 +1,61 @@ +pragma ComponentBehavior: Bound + +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + readonly property list clockFormats: [ + MenuItem { + text: qsTr("Long format") + value: "dddd d MMM - hh:mm" + }, + MenuItem { + text: qsTr("Short format") + value: "ddd d MMM - hh:mm" + }, + MenuItem { + text: qsTr("Time only") + value: "hh:mm" + }, + MenuItem { + text: qsTr("Long format seconds") + value: "dddd d MMM - hh:mm:ss" + }, + MenuItem { + text: qsTr("Short format seconds") + value: "ddd d MMM - hh:mm:ss" + }, + MenuItem { + text: qsTr("Time only seconds") + value: "hh:mm:ss" + } + ] + + isSubPage: true + title: qsTr("Clock") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + SelectRow { + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing + active: root.clockFormats.find(item => item.value === Config.general.dateFormat) + first: true + last: true + menuItems: root.clockFormats + subtext: qsTr("Change how time is displayed in the widget") + text: qsTr("Time format") + + onSelected: item => { + Config.general.dateFormat = item.value; + } + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml b/Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml new file mode 100644 index 0000000..b305f18 --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml @@ -0,0 +1,93 @@ +pragma ComponentBehavior: Bound + +import QtQuick.Layouts +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("System icons") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Visible icons + SectionHeader { + first: true + text: qsTr("Visible icons") + } + + ToggleRow { + checked: Config.bar.tray.showAudio + first: true + text: qsTr("Speakers") + + onToggled: Config.bar.tray.showAudio = checked + } + + ToggleRow { + checked: Config.bar.tray.showMicrophone + text: qsTr("Microphone") + + onToggled: Config.bar.tray.showMicrophone = checked + } + + //////// FOR LATER: + // ToggleRow { + // checked: Config.bar.tray.showNetwork + // text: qsTr("Network") + // + // onToggled: Config.bar.tray.showNetwork = checked + // } + // + // ToggleRow { + // checked: Config.bar.tray.showWifi + // text: qsTr("Wi-Fi") + // + // onToggled: Config.bar.tray.showWifi = checked + // } + // + // ToggleRow { + // checked: Config.bar.tray.showBluetooth + // text: qsTr("Bluetooth") + // + // onToggled: Config.bar.tray.showBluetooth = checked + // } + + ToggleRow { + checked: Config.bar.tray.showPower + last: true + text: qsTr("Battery") + + onToggled: Config.bar.tray.showPower = checked + } + + // Behaviour + SectionHeader { + text: qsTr("Behavior") + } + + ToggleRow { + checked: Config.bar.popouts.audio + first: true + subtext: qsTr("Show a details popout when hovering the audio icons") + text: qsTr("Audio popout on hover") + + onToggled: Config.bar.popouts.audio = checked + } + + ToggleRow { + checked: Config.bar.popouts.upower + last: true + subtext: qsTr("Show a details popout when hovering the power icon") + text: qsTr("Power popout on hover") + + onToggled: Config.bar.popouts.upower = checked + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml b/Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml new file mode 100644 index 0000000..b40d9ba --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml @@ -0,0 +1,31 @@ +pragma ComponentBehavior: Bound + +import QtQuick.Layouts +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Tray") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + SpinRow { + first: true + from: 12 + last: true + stepSize: 1 + text: qsTr("Icon size") + to: 24 + value: Config.bar.tray.trayIconSize + + onMoved: value => Config.bar.tray.trayIconSize = value + } + } +} diff --git a/Modules/SettingsNew/Pages/Panels/BarPanel.qml b/Modules/SettingsNew/Pages/Panels/BarPanel.qml index 28269a2..2cafa71 100644 --- a/Modules/SettingsNew/Pages/Panels/BarPanel.qml +++ b/Modules/SettingsNew/Pages/Panels/BarPanel.qml @@ -23,13 +23,13 @@ PageBase { } ToggleRow { - checked: Config.barConfig.autoHide + checked: Config.bar.autoHide first: true last: true subtext: qsTr("Hide the bar, reveal on hover") text: qsTr("Auto hide") - onToggled: Config.barConfig.autoHide = checked + onToggled: Config.bar.autoHide = checked } // Components @@ -39,44 +39,28 @@ PageBase { NavRow { first: true - icon: "workspaces" - label: qsTr("Workspaces") - status: qsTr("Indicators, window icons") + icon: "widgets" + status: qsTr("System tray icons") + text: qsTr("Tray") onClicked: root.sState.openSubPage(5) } NavRow { - icon: "web_asset" - label: qsTr("Active window") - status: qsTr("Title display, popout") + icon: "signal_cellular_alt" + status: qsTr("Visible indicators") + text: qsTr("Status icons") onClicked: root.sState.openSubPage(6) } NavRow { - icon: "widgets" - label: qsTr("Tray") - status: qsTr("System tray icons") + icon: "schedule" + last: true + status: qsTr("Date, icon, background") + text: qsTr("Clock") onClicked: root.sState.openSubPage(7) } - - NavRow { - icon: "signal_cellular_alt" - label: qsTr("Status icons") - status: qsTr("Visible indicators") - - onClicked: root.sState.openSubPage(8) - } - - NavRow { - icon: "schedule" - label: qsTr("Clock") - last: true - status: qsTr("Date, icon, background") - - onClicked: root.sState.openSubPage(9) - } } } diff --git a/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml b/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml index bb4ebad..4d14f36 100644 --- a/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml +++ b/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml @@ -32,54 +32,5 @@ PageBase { onToggled: Config.dashboard.enabled = checked } - - // Performance widgets - SectionHeader { - text: qsTr("Performance widgets") - } - - ToggleRow { - checked: Config.dashboard.performance.showBattery - first: true - text: qsTr("Battery") - - onToggled: Config.dashboard.performance.showBattery = checked - } - - ToggleRow { - checked: Config.dashboard.performance.showGpu - text: qsTr("GPU") - - onToggled: Config.dashboard.performance.showGpu = checked - } - - ToggleRow { - checked: Config.dashboard.performance.showCpu - text: qsTr("CPU") - - onToggled: Config.dashboard.performance.showCpu = checked - } - - ToggleRow { - checked: Config.dashboard.performance.showMemory - text: qsTr("Memory") - - onToggled: Config.dashboard.performance.showMemory = checked - } - - ToggleRow { - checked: Config.dashboard.performance.showStorage - text: qsTr("Storage") - - onToggled: Config.dashboard.performance.showStorage = checked - } - - ToggleRow { - checked: Config.dashboard.performance.showNetwork - last: true - text: qsTr("Network") - - onToggled: Config.dashboard.performance.showNetwork = checked - } } } diff --git a/Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml b/Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml new file mode 100644 index 0000000..ace7608 --- /dev/null +++ b/Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml @@ -0,0 +1,85 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Resources") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // General + SectionHeader { + first: true + text: qsTr("General") + } + + ToggleRow { + checked: Config.dashboard.performance.enabled + first: true + last: true + text: qsTr("Enabled") + + onToggled: Config.dashboard.performance.enabled = checked + } + + // Performance widgets + SectionHeader { + text: qsTr("Performance widgets") + } + + ToggleRow { + checked: Config.dashboard.performance.showBattery + first: true + text: qsTr("Battery") + + onToggled: Config.dashboard.performance.showBattery = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showGpu + text: qsTr("GPU") + + onToggled: Config.dashboard.performance.showGpu = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showCpu + text: qsTr("CPU") + + onToggled: Config.dashboard.performance.showCpu = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showMemory + text: qsTr("Memory") + + onToggled: Config.dashboard.performance.showMemory = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showStorage + text: qsTr("Storage") + + onToggled: Config.dashboard.performance.showStorage = checked + } + + ToggleRow { + checked: Config.dashboard.performance.showNetwork + last: true + text: qsTr("Network") + + onToggled: Config.dashboard.performance.showNetwork = checked + } + } +} diff --git a/Modules/SettingsNew/Pages/PanelsPage.qml b/Modules/SettingsNew/Pages/PanelsPage.qml index 9cb238c..ca785d7 100644 --- a/Modules/SettingsNew/Pages/PanelsPage.qml +++ b/Modules/SettingsNew/Pages/PanelsPage.qml @@ -15,36 +15,44 @@ PageBase { NavRow { first: true - icon: "dashboard" - label: qsTr("Dashboard") - status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled") + icon: "dock_to_bottom" + status: !Config.bar.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover") + text: qsTr("Bar") onClicked: root.sState.openSubPage(1) } NavRow { - icon: "dock_to_bottom" - label: qsTr("Taskbar") - status: !Config.barConfig.autoHide ? qsTr("Always visible") : qsTr("Reveal on hover") + icon: "dashboard" + status: Config.dashboard.enabled ? qsTr("Enabled") : qsTr("Disabled") + text: qsTr("Dashboard") onClicked: root.sState.openSubPage(2) } NavRow { - icon: "apps" - label: qsTr("Launcher") - status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled") + icon: "insert_chart" + status: Config.dashboard.performance.enabled ? qsTr("Enabled") : qsTr("Disabled") + text: qsTr("Resources") onClicked: root.sState.openSubPage(3) } NavRow { - icon: "dock_to_right" - label: qsTr("Sidebar") - last: true - status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled") + icon: "apps" + status: Config.launcher.enabled ? qsTr("Enabled") : qsTr("Disabled") + text: qsTr("Launcher") onClicked: root.sState.openSubPage(4) } + + NavRow { + icon: "dock_to_right" + last: true + status: Config.sidebar.enabled ? qsTr("Enabled") : qsTr("Disabled") + text: qsTr("Sidebar") + + onClicked: root.sState.openSubPage(5) + } } } diff --git a/Modules/SettingsNew/Pages/Services/NotificationsPage.qml b/Modules/SettingsNew/Pages/Services/NotificationsPage.qml new file mode 100644 index 0000000..764e44d --- /dev/null +++ b/Modules/SettingsNew/Pages/Services/NotificationsPage.qml @@ -0,0 +1,162 @@ +import QtQuick +import QtQuick.Layouts +import qs.Config +import qs.Components +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + isSubPage: true + title: qsTr("Notifications") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Notifications + SectionHeader { + first: true + text: qsTr("Notifications") + } + + ToggleRow { + checked: Config.notifs.showInFullscreen + first: true + subtext: qsTr("Whether notifications appear over fullscreen apps") + text: qsTr("Show in fullscreen") + + onToggled: Config.notifs.showInFullscreen = checked + } + + ToggleRow { + checked: Config.notifs.expire + subtext: qsTr("Dismiss notifications after their timeout") + text: qsTr("Expire automatically") + + onToggled: Config.notifs.expire = checked + } + + ToggleRow { + checked: Config.notifs.openExpanded + subtext: qsTr("Show notifications expanded by default") + text: qsTr("Open expanded") + + onToggled: Config.notifs.openExpanded = checked + } + + SpinRow { + from: 1000 + stepSize: 500 + subtext: qsTr("Time before a notification dismisses (ms)") + text: qsTr("Default timeout") + to: 60000 + value: Config.notifs.defaultExpireTimeout + + onMoved: v => Config.notifs.defaultExpireTimeout = Math.round(v) + } + + SpinRow { + from: 1 + last: true + stepSize: 1 + subtext: qsTr("Notifications shown per group before collapsing") + text: qsTr("Group preview count") + to: 10 + value: Config.notifs.groupPreviewNum + + onMoved: v => Config.notifs.groupPreviewNum = Math.round(v) + } + + // Toasts + SectionHeader { + text: qsTr("Toasts") + } + + SpinRow { + first: true + from: 1 + stepSize: 1 + subtext: qsTr("Maximum number of toasts shown at once") + text: qsTr("Visible toasts") + to: 10 + value: Config.utilities.maxToasts + + onMoved: v => Config.utilities.maxToasts = Math.round(v) + } + + ToggleRow { + checked: Config.utilities.toasts.chargingChanged + text: qsTr("Charging changes") + + onToggled: Config.utilities.toasts.chargingChanged = checked + } + + ToggleRow { + checked: Config.utilities.toasts.gameModeChanged + text: qsTr("Game mode changes") + + onToggled: Config.utilities.toasts.gameModeChanged = checked + } + + // ToggleRow { + // checked: Config.utilities.toasts.dndChanged + // text: qsTr("Do not disturb changes") + // + // onToggled: Config.utilities.toasts.dndChanged = checked + // } + + ToggleRow { + checked: Config.utilities.toasts.audioOutputChanged + text: qsTr("Audio output changes") + + onToggled: Config.utilities.toasts.audioOutputChanged = checked + } + + ToggleRow { + checked: Config.utilities.toasts.audioInputChanged + last: true + text: qsTr("Audio input changes") + + onToggled: Config.utilities.toasts.audioInputChanged = checked + } + + // ToggleRow { + // checked: Config.utilities.toasts.capsLockChanged + // text: qsTr("Caps lock changes") + // + // onToggled: Config.utilities.toasts.capsLockChanged = checked + // } + // + // ToggleRow { + // checked: Config.utilities.toasts.numLockChanged + // text: qsTr("Num lock changes") + // + // onToggled: Config.utilities.toasts.numLockChanged = checked + // } + // + // ToggleRow { + // checked: Config.utilities.toasts.kbLayoutChanged + // text: qsTr("Keyboard layout changes") + // + // onToggled: Config.utilities.toasts.kbLayoutChanged = checked + // } + // + // ToggleRow { + // checked: Config.utilities.toasts.vpnChanged + // text: qsTr("VPN changes") + // + // onToggled: Config.utilities.toasts.vpnChanged = checked + // } + // + // ToggleRow { + // checked: Config.utilities.toasts.nowPlaying + // last: true + // text: qsTr("Now playing") + // + // onToggled: Config.utilities.toasts.nowPlaying = checked + // } + } +} diff --git a/Modules/SettingsNew/Pages/ServicesPage.qml b/Modules/SettingsNew/Pages/ServicesPage.qml new file mode 100644 index 0000000..d6751b5 --- /dev/null +++ b/Modules/SettingsNew/Pages/ServicesPage.qml @@ -0,0 +1,214 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import ZShell.Services +import qs.Components +import qs.Helpers +import qs.Config +import qs.Modules.SettingsNew.Common + +PageBase { + id: root + + // GPU options + the config string each maps to (see Gpu::parseType) + readonly property list gpuItems: [ + MenuItem { + text: qsTr("Auto") + }, + MenuItem { + text: "NVIDIA" + }, + MenuItem { + text: qsTr("Generic") + }, + MenuItem { + text: qsTr("None") + } + ] + readonly property list gpuValues: ["", "NVIDIA", "GENERIC", "None"] + + function gpuKeyToIndex(key: string): int { + const u = (key ?? "").trim().toUpperCase(); + if (u === "") + return 0; // Auto + if (u === "NVIDIA") + return 1; + if (u === "GENERIC") + return 2; + return 3; // None + } + + title: qsTr("Services") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + // Detected running players, used as default-player options + Variants { + id: playerVariants + + model: [...new Set(Players.list.map(p => Players.getIdentity(p)).filter(id => id))] + + MenuItem { + required property string modelData + + activeIcon: "music_note" + icon: modelData === Config.services.defaultPlayer ? "check" : "" + text: modelData + } + } + + // Notifications + SectionHeader { + first: true + text: qsTr("Notifications") + } + + NavRow { + first: true + icon: "notifications" + last: true + status: qsTr("Notifications, toasts, timeouts") + text: qsTr("Notifications") + + onClicked: root.sState.openSubPage(1) + } + + // Polling + SectionHeader { + text: qsTr("Polling") + } + + SpinRow { + first: true + from: 100 + stepSize: 50 + subtext: qsTr("How often the media position updates (ms)") + text: qsTr("Media refresh") + to: 2000 + value: Config.dashboard.mediaUpdateInterval + + onMoved: v => Config.dashboard.mediaUpdateInterval = v + } + + SpinRow { + from: 0.5 + last: true + stepSize: 0.5 + subtext: qsTr("CPU, memory and GPU update interval (seconds)") + text: qsTr("System stats refresh") + to: 10 + value: Config.dashboard.resourceUpdateInterval / 1000 + + onMoved: v => Config.dashboard.resourceUpdateInterval = Math.round(v * 1000) + } + + // Media & lyrics + SectionHeader { + text: qsTr("Media") + } + + SelectRow { + active: menuItems.find(i => i.text === Config.services.defaultPlayer) ?? null + fallbackIcon: "music_note" + fallbackText: Config.services.defaultPlayer || qsTr("Auto") + first: true + last: true + menuItems: playerVariants.instances + subtext: qsTr("Preferred media player when several are open") + text: qsTr("Default player") + + onSelected: item => Config.services.defaultPlayer = item.text + } + + // Input increments + SectionHeader { + text: qsTr("Input increments") + } + + SpinRow { + first: true + from: 1 + stepSize: 1 + subtext: qsTr("Amount the volume changes per input (%)") + text: qsTr("Volume step") + to: 50 + value: Math.round(Config.services.audioIncrement * 100) + + onMoved: v => Config.services.audioIncrement = v / 100 + } + + SpinRow { + from: 1 + stepSize: 1 + subtext: qsTr("Amount the brightness changes per input (%)") + text: qsTr("Brightness step") + to: 50 + value: Math.round(Config.services.brightnessIncrement * 100) + + onMoved: v => Config.services.brightnessIncrement = v / 100 + } + + SpinRow { + from: 1 + stepSize: 1 + subtext: qsTr("Lowest allowed brightness (%)") + text: qsTr("Brightness minimum") + to: 20 + value: Math.round(Config.services.minBrightness * 100) + + onMoved: v => Config.services.minBrightness = v / 100 + } + + SpinRow { + from: 50 + last: true + stepSize: 5 + subtext: qsTr("Upper limit for output volume (%)") + text: qsTr("Max volume") + to: 200 + value: Math.round(Config.services.maxVolume * 100) + + onMoved: v => Config.services.maxVolume = v / 100 + } + + // Service tuning + SectionHeader { + text: qsTr("Service tuning") + } + + SpinRow { + first: true + from: 10 + stepSize: 2 + subtext: qsTr("Resolution of the audio visualizer") + text: qsTr("Visualizer resolution") + to: 120 + value: Config.services.visualizerBars + + onMoved: v => Config.services.visualiserBars = v + } + + ToggleRow { + checked: Config.general.color.smart + subtext: qsTr("Derive theme mode from the wallpaper") + text: qsTr("Smart color scheme") + + onToggled: Config.general.color.smart = checked + } + + SelectRow { + active: root.gpuItems[root.gpuKeyToIndex(Config.services.gpuType)] + last: true + menuItems: root.gpuItems + menuOnTop: true + subtext: Gpu.name ? qsTr("Monitoring: %1").arg(Gpu.name) : qsTr("Override for GPU type") + text: qsTr("GPU") + + onSelected: item => Config.services.gpuType = root.gpuValues[root.gpuItems.indexOf(item)] + } + } +} diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index fbbd1fc..9f9e388 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -57,7 +57,7 @@ Item { if (mouse.button === Qt.LeftButton) { root.item.activate(); console.log(icon.source + "\n" + root.item.id); - } else if (mouse.button === Qt.RightButton && Config.barConfig.popouts.tray) { + } else if (mouse.button === Qt.RightButton && Config.bar.popouts.tray) { root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.hasCurrent = true; @@ -77,7 +77,7 @@ Item { anchors.centerIn: parent antialiasing: true color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - implicitSize: Config.barConfig.tray.trayIconSize * root.dpr + implicitSize: Config.bar.tray.trayIconSize * root.dpr layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark scale: 1 / root.dpr source: root.resolveIcon(root.item.id, root.item.icon) diff --git a/Modules/SysTray/TrayWidget.qml b/Modules/SysTray/TrayWidget.qml index b3864c5..0ea8130 100644 --- a/Modules/SysTray/TrayWidget.qml +++ b/Modules/SysTray/TrayWidget.qml @@ -47,12 +47,12 @@ RowLayout { let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y); let child = closestRowChild(sysModRow, modRowPos.x); if (child) { - if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio) + if (child.objectName === "audioWidget" && Config.bar.popouts.audio) return { id: "audio", item: child }; - if (child.objectName === "upowerWidget" && Config.barConfig.popouts.upower) + if (child.objectName === "upowerWidget" && Config.bar.popouts.upower) return { id: "upower", item: child @@ -75,7 +75,7 @@ RowLayout { return null; } - height: Config.barConfig.height + Appearance.padding.smallest * 2 + height: Config.bar.height + Appearance.padding.smallest * 2 spacing: Appearance.padding.small width: sysTray.implicitWidth + sysTrayMod.implicitWidth + Appearance.padding.small @@ -123,22 +123,31 @@ RowLayout { Layout.fillHeight: true bottomLeftRadius: Appearance.rounding.smallest / 2 color: DynamicColors.tPalette.m3surfaceContainer - implicitWidth: sysModRow.width + Appearance.padding.smaller * 2 + implicitWidth: sysModRow.implicitWidth + Appearance.padding.smaller + Appearance.padding.normal radius: Appearance.rounding.full topLeftRadius: Appearance.rounding.smallest / 2 - Row { + RowLayout { id: sysModRow - anchors.centerIn: parent - spacing: Appearance.padding.small + anchors.fill: parent + anchors.leftMargin: Appearance.padding.smaller + anchors.rightMargin: Appearance.padding.normal AudioWidget { + Layout.fillHeight: true + Layout.rightMargin: hasContent ? Appearance.spacing.extraSmall : 0 objectName: "audioWidget" + + Behavior on Layout.rightMargin { + Anim { + type: Anim.FastEffects + } + } } UPowerWidget { - height: parent.height + Layout.fillHeight: true objectName: "upowerWidget" } } diff --git a/Modules/SysTray/Widgets/AudioWidget.qml b/Modules/SysTray/Widgets/AudioWidget.qml index 202812b..3ecac22 100644 --- a/Modules/SysTray/Widgets/AudioWidget.qml +++ b/Modules/SysTray/Widgets/AudioWidget.qml @@ -11,25 +11,68 @@ RowLayout { id: root property color barColor: DynamicColors.palette.m3primary + readonly property bool hasContent: mic.visible || speaker.visible property color textColor: DynamicColors.palette.m3onSurface + width: hasContent ? implicitWidth : 0 + MaterialIcon { id: mic + readonly property bool shouldBeVisible: Config.bar.tray.showMicrophone + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: shouldBeVisible ? implicitWidth : 0 animate: true color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor fill: 1 font.pointSize: Appearance.font.size.larger + opacity: shouldBeVisible ? 1 : 0 + scale: shouldBeVisible ? 1 : 0 text: Audio.sourceMuted ? "mic_off" : "mic" + visible: opacity > 0 + + Behavior on Layout.maximumWidth { + Anim { + } + } + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } } MaterialIcon { + id: speaker + + readonly property bool shouldBeVisible: Config.bar.tray.showAudio + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: shouldBeVisible ? implicitWidth : 0 animate: true color: Audio.muted ? DynamicColors.palette.m3error : root.textColor fill: 1 font.pointSize: Appearance.font.size.larger + opacity: shouldBeVisible ? 1 : 0 + scale: shouldBeVisible ? 1 : 0 text: Audio.muted ? "volume_off" : "volume_up" + visible: opacity > 0 + + Behavior on Layout.maximumWidth { + Anim { + } + } + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } } } diff --git a/Modules/SysTray/Widgets/UPowerWidget.qml b/Modules/SysTray/Widgets/UPowerWidget.qml index b3d4f7e..0c4b4c8 100644 --- a/Modules/SysTray/Widgets/UPowerWidget.qml +++ b/Modules/SysTray/Widgets/UPowerWidget.qml @@ -8,8 +8,32 @@ import qs.Helpers Item { id: root + readonly property bool shouldBeActive: Config.bar.tray.showPower + + Layout.preferredHeight: shouldBeActive ? implicitHeight : 0 + Layout.preferredWidth: shouldBeActive ? implicitWidth : 0 implicitHeight: Battery.isLaptop ? batteryIconLoader.item.implicitHeight : upowerIconLoader.item.implicitHeight implicitWidth: Battery.isLaptop ? batteryIconLoader.item.implicitWidth : upowerIconLoader.item.implicitWidth + opacity: shouldBeActive ? 1 : 0 + scale: shouldBeActive ? 1 : 0 + visible: opacity > 0 + + Behavior on Layout.preferredHeight { + Anim { + } + } + Behavior on Layout.preferredWidth { + Anim { + } + } + Behavior on opacity { + Anim { + } + } + Behavior on scale { + Anim { + } + } Loader { id: batteryIconLoader diff --git a/Modules/Updates/UpdatesWidget.qml b/Modules/Updates/UpdatesWidget.qml index bcc1063..14be0cb 100644 --- a/Modules/Updates/UpdatesWidget.qml +++ b/Modules/Updates/UpdatesWidget.qml @@ -12,7 +12,7 @@ CustomRect { property color textColor: DynamicColors.palette.m3onSurface color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 + implicitHeight: Config.bar.height + Appearance.padding.smallest * 2 implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2 radius: Appearance.rounding.full diff --git a/Modules/Workspaces.qml b/Modules/Workspaces.qml index 30ee987..91420db 100644 --- a/Modules/Workspaces.qml +++ b/Modules/Workspaces.qml @@ -21,7 +21,7 @@ Item { readonly property int workspacesShown: workspaces.length height: implicitHeight - implicitHeight: Config.barConfig.height + Appearance.padding.smaller * 2 + implicitHeight: Config.bar.height + Appearance.padding.smaller * 2 implicitWidth: (root.workspaceButtonWidth * root.workspacesShown) + root.activeWorkspaceMargin * 2 Behavior on implicitWidth { -- 2.47.3 From d08b2b7ac6698c6af8a22d086c5fb4885696d06f Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 15:42:50 +0200 Subject: [PATCH 05/17] correct index for subpages --- Modules/SettingsNew/Pages/Panels/BarPanel.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/SettingsNew/Pages/Panels/BarPanel.qml b/Modules/SettingsNew/Pages/Panels/BarPanel.qml index 2cafa71..5995239 100644 --- a/Modules/SettingsNew/Pages/Panels/BarPanel.qml +++ b/Modules/SettingsNew/Pages/Panels/BarPanel.qml @@ -43,7 +43,7 @@ PageBase { status: qsTr("System tray icons") text: qsTr("Tray") - onClicked: root.sState.openSubPage(5) + onClicked: root.sState.openSubPage(6) } NavRow { @@ -51,7 +51,7 @@ PageBase { status: qsTr("Visible indicators") text: qsTr("Status icons") - onClicked: root.sState.openSubPage(6) + onClicked: root.sState.openSubPage(7) } NavRow { @@ -60,7 +60,7 @@ PageBase { status: qsTr("Date, icon, background") text: qsTr("Clock") - onClicked: root.sState.openSubPage(7) + onClicked: root.sState.openSubPage(8) } } } -- 2.47.3 From ecfa1115cd53e5578444cf5f1073ae824475ab00 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 20:21:36 +0200 Subject: [PATCH 06/17] wonky fix for cropping rect desync --- .../SettingsNew/Common/SettingHyprSpinner.qml | 214 ------------------ Modules/SettingsNew/Common/TimeInput.qml | 10 - .../SettingsNew/Common/WallpaperCropper.qml | 39 ++-- Modules/SettingsNew/Pages/Wallpaper.qml | 35 ++- 4 files changed, 51 insertions(+), 247 deletions(-) delete mode 100644 Modules/SettingsNew/Common/SettingHyprSpinner.qml diff --git a/Modules/SettingsNew/Common/SettingHyprSpinner.qml b/Modules/SettingsNew/Common/SettingHyprSpinner.qml deleted file mode 100644 index e18016f..0000000 --- a/Modules/SettingsNew/Common/SettingHyprSpinner.qml +++ /dev/null @@ -1,214 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - } - - function formattedValue(setting: string): string { - const value = root.object[setting]; - - if (value === null || value === undefined) - return ""; - - return String(value); - } - - function hourToAmPm(hour) { - var h = Number(hour) % 24; - var d = new Date(2000, 0, 1, h, 0, 0); - return Qt.formatTime(d, "h AP"); - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]])) - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.preferredWidth: 100 - - RowLayout { - Layout.preferredWidth: optionLayout.width - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[2]] - - onToggled: { - root.object[root.settings[2]] = checked; - Config.save(); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting2.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Start: ") - } - - SpinnerButton { - id: setting1 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[0]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[0]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[0]); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting1.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("End: ") - } - - SpinnerButton { - id: setting2 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[1]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[1]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[1]); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: -2 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Temp: ") - } - - CustomRect { - id: rect - - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 200), 50) - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - id: textField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 200) - text: root.formattedValue(root.settings[3]) - - onEditingFinished: { - root.object[root.settings[3]] = textField.text; - Config.save(); - } - } - } - } - } - } -} diff --git a/Modules/SettingsNew/Common/TimeInput.qml b/Modules/SettingsNew/Common/TimeInput.qml index f922230..0b88fb1 100644 --- a/Modules/SettingsNew/Common/TimeInput.qml +++ b/Modules/SettingsNew/Common/TimeInput.qml @@ -8,19 +8,9 @@ import qs.Helpers CustomClippingRect { id: root - readonly property string endTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[2]]); - return Qt.formatTime(d, "hh:mm AP"); - } required property var object required property list settings property bool shouldBeActive: true - readonly property string startTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[1]]); - return Qt.formatTime(d, "hh:mm AP"); - } signal applySettings(startTime: int, endTime: int) signal close diff --git a/Modules/SettingsNew/Common/WallpaperCropper.qml b/Modules/SettingsNew/Common/WallpaperCropper.qml index f9703e4..21c3b69 100644 --- a/Modules/SettingsNew/Common/WallpaperCropper.qml +++ b/Modules/SettingsNew/Common/WallpaperCropper.qml @@ -12,6 +12,7 @@ import qs.Helpers Item { id: wrapper + property int buttonRowHeight: Appearance.padding.large * 3 property bool changesMade: false readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null property var screens: [] @@ -34,13 +35,7 @@ Item { function refreshScreens(): void { screens = [...Quickshell.screens].sort((a, b) => a.x - b.x); - - if (screens.length === 0) { - selectedScreenIndex = 0; - return; - } - - selectedScreenIndex = Math.max(0, Math.min(selectedScreenIndex, screens.length - 1)); + selectedScreenIndex = 0; } function selectScreen(index: int): void { @@ -130,12 +125,13 @@ Item { ButtonRow { id: screenSelector + anchors.bottom: scaledImg.top + anchors.bottomMargin: Appearance.spacing.normal anchors.left: parent.left anchors.leftMargin: Appearance.padding.extraLarge * 2 anchors.right: parent.right anchors.rightMargin: Appearance.padding.extraLarge * 2 anchors.top: parent.top - implicitHeight: 34 spacing: Appearance.spacing.small Repeater { @@ -196,8 +192,9 @@ Item { anchors.bottomMargin: Appearance.spacing.normal anchors.left: parent.left anchors.right: parent.right - anchors.top: screenSelector.bottom - anchors.topMargin: Appearance.spacing.normal + anchors.top: parent.top + anchors.topMargin: wrapper.buttonRowHeight + Appearance.spacing.normal + // anchors.top: screenSelector.bottom asynchronous: true fillMode: Image.PreserveAspectFit retainWhileLoading: true @@ -207,24 +204,33 @@ Item { onPaintedWidthChanged: { if (paintedWidth > 0 && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); + if (!wrapper.screens.length) + wrapper.refreshScreens(); + + wrapper.syncCropToScreen(); } } onSourceChanged: { if (cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); + if (!wrapper.screens.length) + wrapper.refreshScreens(); + + wrapper.syncCropToScreen(); } } onStatusChanged: { if (scaledImg.status == Image.Ready && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); + if (!wrapper.screens.length) + wrapper.refreshScreens(); + + wrapper.syncCropToScreen(); } } Loader { id: cropRectLoader - active: scaledImg.paintedWidth > 0 && wrapper.currentScreen + active: scaledImg.status === Image.Ready sourceComponent: Component { CustomRect { @@ -259,11 +265,10 @@ Item { } function restoreFromData() { - if (!wrapper.currentScreen) - return; - let data = Wallpapers.getCrop(wrapper.currentScreen.name); + console.log(data.x, data.y); + if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { zoom = data.zoom > 0 ? data.zoom : 1.0; x = imageX + (data.x * scaledImg.paintedWidth); diff --git a/Modules/SettingsNew/Pages/Wallpaper.qml b/Modules/SettingsNew/Pages/Wallpaper.qml index f576a85..a45aae7 100644 --- a/Modules/SettingsNew/Pages/Wallpaper.qml +++ b/Modules/SettingsNew/Pages/Wallpaper.qml @@ -25,10 +25,7 @@ PageBase { Layout.alignment: Qt.AlignHCenter implicitHeight: cropper.height - implicitWidth: { - const screen = root.sState.screen; - return implicitHeight / screen.height * screen.width; - } + implicitWidth: root.cappedWidth WallpaperCropper { id: cropper @@ -76,9 +73,22 @@ PageBase { } OverlayRow { + id: darkMode + + readonly property string endTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(Config.general.color.scheduleDarkEnd); + return Qt.formatTime(d, "hh:mm AP"); + } + readonly property string startTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(Config.general.color.scheduleDarkStart); + return Qt.formatTime(d, "hh:mm AP"); + } + checked: Config.general.color.scheduleDark first: true - subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleDarkStart).arg(Config.general.color.scheduleDarkEnd) + subtext: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) text: qsTr("Schedule dark mode") popup: Component { @@ -103,10 +113,23 @@ PageBase { } OverlayRow { + id: hyprsunset + + readonly property string endTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(Config.general.color.scheduleHyprsunsetEnd); + return Qt.formatTime(d, "hh:mm AP"); + } + readonly property string startTime: { + var d = new Date(0, 0, 0, 0, 0, 0, 0); + d.setMinutes(Config.general.color.scheduleHyprsunsetStart); + return Qt.formatTime(d, "hh:mm AP"); + } + Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing checked: Config.general.color.scheduleHyprsunset last: true - subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(Config.general.color.scheduleHyprsunsetStart).arg(Config.general.color.scheduleHyprsunsetEnd) + subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) text: qsTr("Schedule hyprsunset") popup: Component { -- 2.47.3 From 01c63ebeadc83eb52486bbee4002aec417eec858 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 23:06:29 +0200 Subject: [PATCH 07/17] blobpopup positioning and radius changes --- Components/VerticalFadeFlickable.qml | 2 +- Components/VerticalFadeListView.qml | 2 +- Modules/SettingsNew/Common/BlobPopup.qml | 6 +++--- Modules/SettingsNew/Common/PopupRow.qml | 4 +++- Modules/SettingsNew/NavPane/NavLocations.qml | 3 ++- Modules/SettingsNew/Pages/AppsPage.qml | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/Components/VerticalFadeFlickable.qml b/Components/VerticalFadeFlickable.qml index edd63df..8cc61d7 100644 --- a/Components/VerticalFadeFlickable.qml +++ b/Components/VerticalFadeFlickable.qml @@ -7,7 +7,7 @@ CustomFlickable { id: root property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 - property real fadeAmount: 0.2 + property real fadeAmount: 0.1 property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 function fadeShouldBeActive(isStart: bool): bool { diff --git a/Components/VerticalFadeListView.qml b/Components/VerticalFadeListView.qml index 0c35590..5392d95 100644 --- a/Components/VerticalFadeListView.qml +++ b/Components/VerticalFadeListView.qml @@ -7,7 +7,7 @@ CustomListView { id: root property real bottomFadeOpacity: fadeShouldBeActive(false) ? 0 : 1 - property real fadeAmount: 0.2 + property real fadeAmount: 0.1 property real topFadeOpacity: fadeShouldBeActive(true) ? 0 : 1 function fadeShouldBeActive(isStart: bool): bool { diff --git a/Modules/SettingsNew/Common/BlobPopup.qml b/Modules/SettingsNew/Common/BlobPopup.qml index c4226b4..eeccdda 100644 --- a/Modules/SettingsNew/Common/BlobPopup.qml +++ b/Modules/SettingsNew/Common/BlobPopup.qml @@ -66,7 +66,7 @@ Item { group: blobGroup implicitHeight: parent.height implicitWidth: parent.width - radius: Appearance.rounding.large + radius: Appearance.rounding.small + Appearance.padding.small states: State { name: "open" @@ -86,8 +86,8 @@ Item { } Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial + duration: Appearance.anim.durations.expressiveFastEffects + easing.bezierCurve: Appearance.anim.curves.expressiveFastEffects properties: "topMargin,implicitHeight" } diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/SettingsNew/Common/PopupRow.qml index 8778a90..3a49180 100644 --- a/Modules/SettingsNew/Common/PopupRow.qml +++ b/Modules/SettingsNew/Common/PopupRow.qml @@ -83,7 +83,7 @@ ConnectedRect { id: area anchors.fill: parent - cursorShape: undefined + cursorShape: popup.open ? Qt.ArrowCursor : undefined enabled: popup.open hoverEnabled: true parent: { @@ -94,6 +94,8 @@ ConnectedRect { const contentWin = win as Windows; // If inside the drawer content window, put it inside the interaction wrapper so hover works return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem; } + preventStealing: true + propagateComposedEvents: false z: popup.animDriver > 0 ? 1 : 0 onClicked: popup.open = false diff --git a/Modules/SettingsNew/NavPane/NavLocations.qml b/Modules/SettingsNew/NavPane/NavLocations.qml index 0b8b41a..6d5278a 100644 --- a/Modules/SettingsNew/NavPane/NavLocations.qml +++ b/Modules/SettingsNew/NavPane/NavLocations.qml @@ -6,13 +6,14 @@ import qs.Components import qs.Config import qs.Modules.SettingsNew -Flickable { +VerticalFadeFlickable { id: root required property SettingsState sState bottomMargin: Appearance.padding.large contentHeight: content.implicitHeight + fadeAmount: 0.1 topMargin: Appearance.padding.large ColumnLayout { diff --git a/Modules/SettingsNew/Pages/AppsPage.qml b/Modules/SettingsNew/Pages/AppsPage.qml index 0cb1376..2ba835d 100644 --- a/Modules/SettingsNew/Pages/AppsPage.qml +++ b/Modules/SettingsNew/Pages/AppsPage.qml @@ -93,7 +93,7 @@ PageBase { p = p.parent; return p?.opacity < 1; } - popup.topMovement: Math.max(0 - popupHeight, Appearance.padding.large) + popup.topMovement: Appearance.padding.large Loader { active: row.popup.animDriver > 0 -- 2.47.3 From 6a15be7b1548ad1f634b6d76357adac00b8d7244 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 25 Jun 2026 12:57:11 +0200 Subject: [PATCH 08/17] sys tray spacing --- Components/M3TextField.qml | 309 +++++++++++++++++++++++++++++++++ Modules/SysTray/TrayWidget.qml | 2 +- 2 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 Components/M3TextField.qml diff --git a/Components/M3TextField.qml b/Components/M3TextField.qml new file mode 100644 index 0000000..8f23325 --- /dev/null +++ b/Components/M3TextField.qml @@ -0,0 +1,309 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Config + +Item { + id: root + + enum Variant { + Outlined, + Filled + } + + readonly property color accent: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline)) + readonly property real disabledOpacity: 0.38 + readonly property string effectiveTrailingIcon: root.trailingIcon.length > 0 ? root.trailingIcon : (root.password ? (field.echoMode === TextInput.Password ? "visibility" : "visibility_off") : (root.isError ? "error" : "")) + property string errorText: "" + readonly property alias field: field + readonly property bool floating: focused || hasContent || prefixText.length > 0 + readonly property bool focused: field.activeFocus + readonly property bool hasContent: field.text.length > 0 + readonly property color iconColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant)) + property int inputMethodHints: Qt.ImhNone + property bool isError: false + property string label: "" + readonly property color labelColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : (focused ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant)) + property string leadingIcon: "" + property int maxLength: 0 + property color notchColor: DynamicColors.palette.m3surface + readonly property bool outlined: variant === M3TextField.Variant.Outlined + property bool password: false + property string placeholder: "" + property string prefixText: "" + property string suffixText: "" + readonly property color supportColor: !root.enabled ? DynamicColors.palette.m3onSurface : (isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant) + property string supportingText: "" + property alias text: field.text + property string trailingIcon: "" + readonly property bool trailingInteractive: root.password && root.trailingIcon.length === 0 + property var validator: null + property int variant: M3TextField.Variant.Outlined + + signal accepted + + function forceFieldFocus(): void { + field.forceActiveFocus(); + } + + implicitHeight: 56 + (supportRow.visible ? supportRow.implicitHeight + Appearance.spacing.extraSmall : 0) + implicitWidth: 240 + + CustomRect { + id: filledBg + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + color: root.enabled ? (root.focused ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) : DynamicColors.palette.m3surfaceContainerHigh) : DynamicColors.palette.m3onSurface + implicitHeight: 56 + opacity: root.enabled ? 1 : 0.04 // M3 filled disabled container @ 4% + + topLeftRadius: Appearance.rounding.small + topRightRadius: Appearance.rounding.small + visible: !root.outlined + + Behavior on color { + CAnim { + } + } + + StateLayer { + anchors.fill: parent + color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface + enabled: root.enabled + topLeftRadius: Appearance.rounding.small + topRightRadius: Appearance.rounding.small + + onClicked: field.forceActiveFocus() + } + + CustomRect { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: root.accent + implicitHeight: root.focused || root.isError ? 2 : 1 + opacity: root.enabled ? 1 : root.disabledOpacity + + Behavior on color { + CAnim { + } + } + Behavior on implicitHeight { + Anim { + type: Anim.StandardSmall + } + } + } + } + + CustomRect { + id: outline + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + border.color: root.accent + border.width: root.focused || root.isError ? 2 : 1 + color: "transparent" + implicitHeight: 56 + opacity: root.enabled ? 1 : root.disabledOpacity + radius: Appearance.rounding.small + visible: root.outlined + + Behavior on border.color { + CAnim { + } + } + Behavior on border.width { + Anim { + type: Anim.StandardSmall + } + } + + StateLayer { + anchors.fill: parent + anchors.margins: outline.border.width + color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface + enabled: root.enabled + radius: Appearance.rounding.small + + onClicked: field.forceActiveFocus() + } + } + + Rectangle { + color: root.notchColor + height: outline.border.width + opacity: root.enabled ? 1 : root.disabledOpacity + visible: root.outlined && root.floating && root.label.length > 0 + width: labelText.width + Appearance.spacing.extraSmall + x: labelText.x - Appearance.spacing.extraSmall / 2 + y: 0 + } + + CustomText { + id: labelText + + color: root.labelColor + font.pointSize: root.floating ? Appearance.font.size.small : Appearance.font.size.medium + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.label + visible: root.label.length > 0 + x: root.floating ? (Appearance.padding.larger + Appearance.spacing.extraSmall / 2) : (leading.visible ? leading.x + leading.width + Appearance.spacing.small : Appearance.padding.larger) + y: root.floating ? (root.outlined ? -height / 2 : Appearance.padding.extraSmall) : (56 - height) / 2 + + Behavior on color { + CAnim { + } + } + Behavior on x { + Anim { + type: Anim.StandardSmall + } + } + Behavior on y { + Anim { + type: Anim.StandardSmall + } + } + } + + MaterialIcon { + id: leading + + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.larger + anchors.top: parent.top + anchors.topMargin: (56 - height) / 2 + color: root.iconColor + font.pointSize: Appearance.font.size.medium + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.leadingIcon + visible: root.leadingIcon.length > 0 + + Behavior on color { + CAnim { + } + } + } + + MaterialIcon { + id: trailing + + anchors.right: parent.right + anchors.rightMargin: Appearance.padding.larger + anchors.top: parent.top + anchors.topMargin: (56 - height) / 2 + color: root.isError ? DynamicColors.palette.m3error : (root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface) + font.pointSize: Appearance.font.size.medium + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.effectiveTrailingIcon + visible: root.effectiveTrailingIcon.length > 0 + + MouseArea { + anchors.fill: parent + anchors.margins: -Appearance.padding.small + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + enabled: root.trailingInteractive && root.enabled + + onClicked: field.echoMode = field.echoMode === TextInput.Password ? TextInput.Normal : TextInput.Password + } + } + + CustomText { + id: prefix + + anchors.left: leading.visible ? leading.right : parent.left + anchors.leftMargin: leading.visible ? Appearance.spacing.small : Appearance.padding.larger + anchors.top: parent.top + anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0 + color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface + font.pointSize: Appearance.font.size.medium + height: 56 + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.prefixText + verticalAlignment: Text.AlignVCenter + visible: root.prefixText.length > 0 && root.floating + } + + CustomText { + id: suffix + + anchors.right: trailing.visible ? trailing.left : parent.right + anchors.rightMargin: trailing.visible ? Appearance.spacing.small : Appearance.padding.larger + anchors.top: parent.top + anchors.topMargin: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0 + color: root.enabled ? DynamicColors.palette.m3onSurfaceVariant : DynamicColors.palette.m3onSurface + font.pointSize: Appearance.font.size.medium + height: 56 + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.suffixText + verticalAlignment: Text.AlignVCenter + visible: root.suffixText.length > 0 && root.floating + } + + CustomTextField { + id: field + + anchors.left: prefix.visible ? prefix.right : (leading.visible ? leading.right : parent.left) + anchors.leftMargin: prefix.visible ? Appearance.spacing.extraSmall : (leading.visible ? Appearance.spacing.small : Appearance.padding.larger) + anchors.right: suffix.visible ? suffix.left : (trailing.visible ? trailing.left : parent.right) + anchors.rightMargin: suffix.visible ? Appearance.spacing.extraSmall : (trailing.visible ? Appearance.spacing.small : Appearance.padding.larger) + anchors.top: parent.top + color: DynamicColors.palette.m3onSurface + echoMode: root.password ? TextInput.Password : TextInput.Normal + enabled: root.enabled + font.pointSize: Appearance.font.size.medium + height: 56 + inputMethodHints: root.inputMethodHints + maximumLength: root.maxLength > 0 ? root.maxLength : 32767 + opacity: root.enabled ? 1 : root.disabledOpacity + placeholderText: root.focused ? root.placeholder : "" + topPadding: root.floating && !root.outlined ? Appearance.font.size.small + Appearance.padding.extraSmall : 0 + validator: root.validator + verticalAlignment: TextInput.AlignVCenter + + onAccepted: root.accepted() + onTextEdited: if (root.isError) + root.isError = false + } + + Item { + id: supportRow + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.topMargin: 56 + Appearance.spacing.extraSmall + implicitHeight: Math.max(supportText.implicitHeight, counter.implicitHeight) + visible: (root.isError && root.errorText.length > 0) || root.supportingText.length > 0 || root.maxLength > 0 + + CustomText { + id: supportText + + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.larger + anchors.right: counter.visible ? counter.left : parent.right + anchors.rightMargin: Appearance.spacing.small + color: root.supportColor + font.pointSize: Appearance.font.size.small + opacity: root.enabled ? 1 : root.disabledOpacity + text: root.isError && root.errorText.length > 0 ? root.errorText : root.supportingText + wrapMode: Text.WordWrap + } + + CustomText { + id: counter + + anchors.right: parent.right + anchors.rightMargin: Appearance.padding.larger + color: root.supportColor + font.pointSize: Appearance.font.size.small + opacity: root.enabled ? 1 : root.disabledOpacity + text: `${root.text.length}/${root.maxLength}` + visible: root.maxLength > 0 + } + } +} diff --git a/Modules/SysTray/TrayWidget.qml b/Modules/SysTray/TrayWidget.qml index 0ea8130..1fdf5dc 100644 --- a/Modules/SysTray/TrayWidget.qml +++ b/Modules/SysTray/TrayWidget.qml @@ -136,7 +136,7 @@ RowLayout { AudioWidget { Layout.fillHeight: true - Layout.rightMargin: hasContent ? Appearance.spacing.extraSmall : 0 + Layout.rightMargin: hasContent ? Appearance.spacing.extraSmall / 2 : 0 objectName: "audioWidget" Behavior on Layout.rightMargin { -- 2.47.3 From 0b92cbb3b46291afac02000988c6f0803f3aaa19 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 25 Jun 2026 18:58:21 +0200 Subject: [PATCH 09/17] add corner fill --- Modules/SettingsNew/Common/BlobPopup.qml | 1 + Plugins/ZShell/Blobs/blobgroup.cpp | 8 +++ Plugins/ZShell/Blobs/blobgroup.hpp | 9 +++ Plugins/ZShell/Blobs/blobrect.cpp | 91 +++++++++++++++++------- Plugins/ZShell/Blobs/blobrect.hpp | 12 ++++ Plugins/ZShell/Blobs/blobshape.cpp | 41 ++++++----- Plugins/ZShell/Blobs/blobshape.hpp | 9 ++- 7 files changed, 124 insertions(+), 47 deletions(-) diff --git a/Modules/SettingsNew/Common/BlobPopup.qml b/Modules/SettingsNew/Common/BlobPopup.qml index eeccdda..4cfd011 100644 --- a/Modules/SettingsNew/Common/BlobPopup.qml +++ b/Modules/SettingsNew/Common/BlobPopup.qml @@ -30,6 +30,7 @@ Item { id: blobGroup color: DynamicColors.palette.m3surfaceContainerHighest + cornerFill: false smoothing: Appearance.rounding.medium Behavior on color { diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index e77cf05..339aae2 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -29,6 +29,14 @@ void BlobGroup::setColor(const QColor& c) { markDirty(); } +void BlobGroup::setCornerFill(bool e) { + if (m_cornerFill == e) + return; + m_cornerFill = e; + emit cornerFillChanged(); + markDirty(); +} + void BlobGroup::addShape(BlobShape* shape) { if (!shape || m_shapes.contains(shape)) return; diff --git a/Plugins/ZShell/Blobs/blobgroup.hpp b/Plugins/ZShell/Blobs/blobgroup.hpp index e917804..c9244f4 100644 --- a/Plugins/ZShell/Blobs/blobgroup.hpp +++ b/Plugins/ZShell/Blobs/blobgroup.hpp @@ -13,6 +13,7 @@ Q_OBJECT QML_ELEMENT Q_PROPERTY(qreal smoothing READ smoothing WRITE setSmoothing NOTIFY smoothingChanged) Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) +Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFillChanged) public: explicit BlobGroup(QObject* parent = nullptr); @@ -30,6 +31,12 @@ void setSmoothing(qreal s); void setColor(const QColor& c); +[[nodiscard]] bool cornerFill() const { + return m_cornerFill; +} + +void setCornerFill(bool e); + void addShape(BlobShape* shape); void removeShape(BlobShape* shape); @@ -51,10 +58,12 @@ void ensurePhysicsUpdated(); signals: void smoothingChanged(); void colorChanged(); +void cornerFillChanged(); private: qreal m_smoothing = 32.0; QColor m_color{ 0x44, 0x88, 0xff }; +bool m_cornerFill = true; QList m_shapes; BlobInvertedRect* m_invertedRect = nullptr; bool m_physicsUpdated = false; diff --git a/Plugins/ZShell/Blobs/blobrect.cpp b/Plugins/ZShell/Blobs/blobrect.cpp index 5847fa4..9da0a49 100644 --- a/Plugins/ZShell/Blobs/blobrect.cpp +++ b/Plugins/ZShell/Blobs/blobrect.cpp @@ -27,16 +27,6 @@ void BlobRect::updatePolish() { emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; - - if (m_group) { - QMetaObject::invokeMethod( - this, - [this]() { - if (m_group) - m_group->markDirty(); - }, - Qt::QueuedConnection); - } } else { QMetaObject::invokeMethod( this, @@ -105,17 +95,15 @@ void BlobRect::updatePhysics() { const float kStiffness = static_cast(m_stiffness); const float kDamping = static_cast(m_damping); + const float invDamp = 1.0f / (1.0f + kDamping * dt); - const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; - m_dmVel00 += accel00 * dt; + m_dmVel00 = (m_dmVel00 - kStiffness * (m_dm00 - target00) * dt) * invDamp; m_dm00 += m_dmVel00 * dt; - const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; - m_dmVel01 += accel01 * dt; + m_dmVel01 = (m_dmVel01 - kStiffness * (m_dm01 - target01) * dt) * invDamp; m_dm01 += m_dmVel01 * dt; - const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; - m_dmVel11 += accel11 * dt; + m_dmVel11 = (m_dmVel11 - kStiffness * (m_dm11 - target11) * dt) * invDamp; m_dm11 += m_dmVel11 * dt; m_deformMatrix = QMatrix4x4(m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); @@ -178,11 +166,24 @@ bool BlobRect::isExcluded(const BlobShape* other) const { return false; } +bool BlobRect::isCornerExcluded(const BlobShape* other) const { + for (const auto& ptr : m_excludeCorners) { + if (ptr == other) + return true; + } + return false; +} + QQmlListProperty BlobRect::exclude() { return QQmlListProperty( this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast); } +QQmlListProperty BlobRect::excludeCorners() { + return QQmlListProperty(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt, + &excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast); +} + void BlobRect::excludeAppend(QQmlListProperty* prop, BlobRect* rect) { auto* self = static_cast(prop->object); self->m_exclude.append(rect); @@ -229,6 +230,52 @@ void BlobRect::excludeRemoveLast(QQmlListProperty* prop) { emit self->excludeChanged(); } +void BlobRect::excludeCornersAppend(QQmlListProperty* prop, BlobRect* rect) { + auto* self = static_cast(prop->object); + self->m_excludeCorners.append(rect); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeCornersChanged(); +} + +qsizetype BlobRect::excludeCornersCount(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + return self->m_excludeCorners.size(); +} + +BlobRect* BlobRect::excludeCornersAt(QQmlListProperty* prop, qsizetype index) { + auto* self = static_cast(prop->object); + return self->m_excludeCorners.at(index); +} + +void BlobRect::excludeCornersClear(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + if (self->m_excludeCorners.isEmpty()) + return; + self->m_excludeCorners.clear(); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeCornersChanged(); +} + +void BlobRect::excludeCornersReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect) { + auto* self = static_cast(prop->object); + self->m_excludeCorners[index] = rect; + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeCornersChanged(); +} + +void BlobRect::excludeCornersRemoveLast(QQmlListProperty* prop) { + auto* self = static_cast(prop->object); + if (self->m_excludeCorners.isEmpty()) + return; + self->m_excludeCorners.removeLast(); + if (self->m_group) + self->m_group->markDirty(); + emit self->excludeCornersChanged(); +} + void BlobRect::checkAtRest(float speed) { constexpr float kEpsilon = 0.002f; const bool atRest = std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon && @@ -242,19 +289,9 @@ void BlobRect::checkAtRest(float speed) { m_dmVel00 = 0.0f; m_dmVel01 = 0.0f; m_dmVel11 = 0.0f; - m_deformMatrix = QMatrix4x4(); + m_deformMatrix = QMatrix4x4(); // identity emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; - - if (m_group) { - QMetaObject::invokeMethod( - this, - [this]() { - if (m_group) - m_group->markDirty(); - }, - Qt::QueuedConnection); - } } } diff --git a/Plugins/ZShell/Blobs/blobrect.hpp b/Plugins/ZShell/Blobs/blobrect.hpp index 11d9910..06d851b 100644 --- a/Plugins/ZShell/Blobs/blobrect.hpp +++ b/Plugins/ZShell/Blobs/blobrect.hpp @@ -14,6 +14,7 @@ Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessCha Q_PROPERTY(qreal damping READ damping WRITE setDamping NOTIFY dampingChanged) Q_PROPERTY(qreal deformScale READ deformScale WRITE setDeformScale NOTIFY deformScaleChanged) Q_PROPERTY(QQmlListProperty exclude READ exclude NOTIFY excludeChanged) +Q_PROPERTY(QQmlListProperty excludeCorners READ excludeCorners NOTIFY excludeCornersChanged) Q_PROPERTY(qreal topLeftRadius READ topLeftRadius WRITE setTopLeftRadius NOTIFY topLeftRadiusChanged) Q_PROPERTY(qreal topRightRadius READ topRightRadius WRITE setTopRightRadius NOTIFY topRightRadiusChanged) Q_PROPERTY(qreal bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius NOTIFY bottomLeftRadiusChanged) @@ -58,8 +59,10 @@ void setDeformScale(qreal s) { } QQmlListProperty exclude(); +QQmlListProperty excludeCorners(); bool isExcluded(const BlobShape* other) const override; +bool isCornerExcluded(const BlobShape* other) const override; void cornerRadii(float out[4]) const override; [[nodiscard]] qreal topLeftRadius() const { @@ -91,6 +94,7 @@ void stiffnessChanged(); void dampingChanged(); void deformScaleChanged(); void excludeChanged(); +void excludeCornersChanged(); void topLeftRadiusChanged(); void topRightRadiusChanged(); void bottomLeftRadiusChanged(); @@ -130,6 +134,7 @@ qreal m_bottomLeftRadius = -1; qreal m_bottomRightRadius = -1; QList > m_exclude; +QList > m_excludeCorners; static void excludeAppend(QQmlListProperty* prop, BlobRect* rect); static qsizetype excludeCount(QQmlListProperty* prop); @@ -137,4 +142,11 @@ static BlobRect* excludeAt(QQmlListProperty* prop, qsizetype index); static void excludeClear(QQmlListProperty* prop); static void excludeReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect); static void excludeRemoveLast(QQmlListProperty* prop); + +static void excludeCornersAppend(QQmlListProperty* prop, BlobRect* rect); +static qsizetype excludeCornersCount(QQmlListProperty* prop); +static BlobRect* excludeCornersAt(QQmlListProperty* prop, qsizetype index); +static void excludeCornersClear(QQmlListProperty* prop); +static void excludeCornersReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect); +static void excludeCornersRemoveLast(QQmlListProperty* prop); }; diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 68ff191..e1626ca 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -31,6 +31,12 @@ static float cpuSmoothstep(float edge0, float edge1, float x) { return t * t * (3.0f - 2.0f * t); } +static float cornerFillFactor(float sd, float smoothFactor) { + const float outside = cpuSmoothstep(0.0f, smoothFactor, sd); + const float inside = cpuSmoothstep(0.0f, -smoothFactor, sd); + return std::max(outside, inside); +} + BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents); @@ -70,19 +76,11 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (m_group) { m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); - m_pendingDw += static_cast(newGeometry.width() - oldGeometry.width()); - m_pendingDh += static_cast(newGeometry.height() - oldGeometry.height()); - - const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) + - std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f); - const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f; - - if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold || - std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) { + const auto dw = std::abs(newGeometry.width() - oldGeometry.width()); + const auto dh = std::abs(newGeometry.height() - oldGeometry.height()); + if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { m_pendingDx = 0; m_pendingDy = 0; - m_pendingDw = 0; - m_pendingDh = 0; m_group->markShapeDirty(this); } } @@ -281,10 +279,12 @@ void BlobShape::updatePolish() { const float smoothFactor = pad; constexpr float minR = 2.0f; + const bool cornerFill = m_group->cornerFill(); const auto rectCount = m_cachedRects.size(); for (qsizetype i = 0; i < rectCount; ++i) { auto& ri = m_cachedRects[i]; const int riExcludeMask = ri.excludeMask; + BlobShape* const si = rectShapes[i]; float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f; const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh; @@ -292,19 +292,26 @@ void BlobShape::updatePolish() { const float cBlX = ri.cx - ri.hw, cBlY = ri.cy + ri.hh; const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh; - for (qsizetype j = 0; j < rectCount; ++j) { + for (qsizetype j = 0; cornerFill && j < rectCount; ++j) { if (j == i) continue; if (riExcludeMask & (1 << j)) continue; + BlobShape* const sj = rectShapes[j]; + if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) + continue; const auto& rj = m_cachedRects[j]; - fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh))); - fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh))); - fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh))); - fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh))); + const float sdTr = cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh); + const float sdBr = cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh); + const float sdBl = cpuSdBox(cBlX, cBlY, rj.cx, rj.cy, rj.hw, rj.hh); + const float sdTl = cpuSdBox(cTlX, cTlY, rj.cx, rj.cy, rj.hw, rj.hh); + fTr = std::min(fTr, cornerFillFactor(sdTr, smoothFactor)); + fBr = std::min(fBr, cornerFillFactor(sdBr, smoothFactor)); + fBl = std::min(fBl, cornerFillFactor(sdBl, smoothFactor)); + fTl = std::min(fTl, cornerFillFactor(sdTl, smoothFactor)); } - if (m_cachedHasInverted) { + if (cornerFill && m_cachedHasInverted) { const float icx = m_cachedInvertedInner[0]; const float icy = m_cachedInvertedInner[1]; const float ihw = m_cachedInvertedInner[2]; diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index 12c8747..e9542b7 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -61,6 +61,10 @@ virtual bool isExcluded(const BlobShape* /*other*/) const { return false; } +virtual bool isCornerExcluded(const BlobShape* /*other*/) const { + return false; +} + virtual void cornerRadii(float out[4]) const; virtual void updatePhysics() { @@ -72,9 +76,10 @@ void updateCenteredDeformMatrix(); BlobGroup* m_group = nullptr; qreal m_radius = 0; -QMatrix4x4 m_deformMatrix; +QMatrix4x4 m_deformMatrix; // identity by default QMatrix4x4 m_centeredDeformMatrix; +// Cached data from updatePolish float m_cachedPaddedX = 0; float m_cachedPaddedY = 0; float m_cachedPaddedW = 0; @@ -84,8 +89,6 @@ QVector m_cachedRects; int m_cachedMyIndex = -2; float m_pendingDx = 0; float m_pendingDy = 0; -float m_pendingDw = 0; -float m_pendingDh = 0; bool m_cachedHasInverted = false; float m_cachedInvertedRadius = 0; float m_cachedInvertedOuter[4] = {}; -- 2.47.3 From ce6e314fde5a8eff5cdfc8536c578712b81057dd Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 26 Jun 2026 17:23:56 +0200 Subject: [PATCH 10/17] chore: regions now manually set, moved to separate file --- Drawers/Regions.qml | 89 ++++++++++ Drawers/Windows.qml | 46 ++---- Modules/SysTray/TrayItem.qml | 4 +- network-dev/wifi-sim.sh | 310 +++++++++++++++++++++++++++++++++++ 4 files changed, 415 insertions(+), 34 deletions(-) create mode 100644 Drawers/Regions.qml create mode 100755 network-dev/wifi-sim.sh diff --git a/Drawers/Regions.qml b/Drawers/Regions.qml new file mode 100644 index 0000000..5d74faf --- /dev/null +++ b/Drawers/Regions.qml @@ -0,0 +1,89 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import qs.Modules.Bar as Bar + +Region { + id: root + + required property Bar.BarLoader bar + readonly property real borderThickness: win.borderThickness + readonly property alias menuPopoutRegion: menuPopoutRegion + required property Panels panels + required property var win + + height: win.height - bar.implicitHeight - win.borderThickness - win.dragMaskPadding * 2 + intersection: Intersection.Xor + width: win.width - win.borderThickness * 2 - win.dragMaskPadding * 2 + x: win.borderThickness + win.dragMaskPadding + y: bar.implicitHeight + win.dragMaskPadding + + R { + panel: root.panels.dashboardWrapper + } + + R { + panel: root.panels.launcher + } + + R { + id: sidebarRegion + + panel: root.panels.sidebar + width: panel.width * (1 - root.panels.sidebar.offsetScale) + root.borderThickness + x: root.win.width - width + } + + R { + panel: root.panels.osdWrapper + width: panel.width * (1 - root.panels.osd.offsetScale) + root.borderThickness + x: root.win.width - width + } + + R { + panel: root.panels.notifications + } + + R { + height: panel.height * (1 - root.panels.utilities.offsetScale) + root.borderThickness + panel: root.panels.utilities + y: root.win.height - height + } + + R { + panel: root.panels.popoutsWrapper + } + + R { + panel: root.panels.resourcesWrapper + } + + R { + panel: root.panels.settingsWrapper + } + + R { + panel: root.panels.dock + } + + R { + panel: root.panels.clipboard + } + + Region { + id: menuPopoutRegion + + intersection: Intersection.Subtract + } + + component R: Region { + required property Item panel + + height: panel.height + intersection: Intersection.Subtract + width: panel.width + x: panel.x + root.borderThickness + y: panel.y + root.bar.implicitHeight + } +} diff --git a/Drawers/Windows.qml b/Drawers/Windows.qml index cf5c486..6d6842e 100644 --- a/Drawers/Windows.qml +++ b/Drawers/Windows.qml @@ -44,7 +44,7 @@ CustomWindow { readonly property bool hasFullscreenOnNormalWs: monitor?.activeWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen > 1) ?? false readonly property bool hasSpecialWorkspace: (monitor?.lastIpcObject.specialWorkspace?.name.length ?? 0) > 0 readonly property alias interactionWrapper: interactions - readonly property alias menuRegion: menuPopoutRegion + readonly property alias menuRegion: regions.menuPopoutRegion readonly property HyprlandMonitor monitor: Hypr.monitorFor(screen) property var root: Quickshell.shellDir readonly property real sdfBorderOffset: 2 * fsTransitionProg @@ -56,7 +56,7 @@ CustomWindow { WlrLayershell.layer: (fsTransitionProg > 0 && Config.general.showOverFullscreen) || (hasSpecialWorkspace && hasFullscreenOnNormalWs) ? WlrLayer.Overlay : WlrLayer.Top color: "transparent" contentItem.focus: true - mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : region) + mask: visibilities.isDrawing ? null : (hasFullscreen ? emptyRegion : regions) name: "Bar" Behavior on fsTransitionProg { @@ -93,12 +93,6 @@ CustomWindow { panels.popouts.hasCurrent = false; } - Region { - id: menuPopoutRegion - - intersection: Intersection.Subtract - } - Region { id: emptyRegion @@ -106,17 +100,21 @@ CustomWindow { width: panels.notifications.width x: panels.notifications.x + root.borderThickness y: panels.notifications.y + bar.implicitHeight + + Region { + height: panels.osd.height + width: panels.osdWrapper.width * (1 - panels.osd.offsetScale) + root.borderThickness + x: root.width - width + y: panels.osdWrapper.y + bar.implicitHeight + } } - Region { - id: region + Regions { + id: regions - height: root.height - bar.implicitHeight - root.borderThickness - root.dragMaskPadding * 2 - intersection: Intersection.Xor - regions: [...popoutRegions.instances, menuPopoutRegion] - width: root.width - root.borderThickness * 2 - root.dragMaskPadding * 2 - x: root.borderThickness + root.dragMaskPadding - y: bar.implicitHeight + root.dragMaskPadding + bar: bar + panels: panels + win: root } anchors { @@ -126,22 +124,6 @@ CustomWindow { top: true } - Variants { - id: popoutRegions - - model: panels.children - - Region { - required property Item modelData - - height: modelData.height - intersection: Intersection.Subtract - width: modelData.width - x: modelData.x + root.borderThickness - y: modelData.y + bar.implicitHeight - } - } - HyprlandFocusGrab { id: focusGrab diff --git a/Modules/SysTray/TrayItem.qml b/Modules/SysTray/TrayItem.qml index 9f9e388..c0f80bc 100644 --- a/Modules/SysTray/TrayItem.qml +++ b/Modules/SysTray/TrayItem.qml @@ -45,13 +45,13 @@ Item { CustomRect { anchors.fill: parent anchors.margins: 3 - color: Config.general.color.scheduleDark && root.current ? DynamicColors.palette.m3primary : "transparent" + color: icon.layer.enabled && root.current ? DynamicColors.palette.m3primary : "transparent" radius: Appearance.rounding.full StateLayer { acceptedButtons: Qt.LeftButton | Qt.RightButton anchors.fill: parent - color: Config.general.color.scheduleDark && root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface + color: icon.layer.enabled && root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface onClicked: mouse => { if (mouse.button === Qt.LeftButton) { diff --git a/network-dev/wifi-sim.sh b/network-dev/wifi-sim.sh new file mode 100755 index 0000000..f3b2cea --- /dev/null +++ b/network-dev/wifi-sim.sh @@ -0,0 +1,310 @@ +#!/bin/bash +# ============================================================================= +# wifi-sim.sh — Virtual WiFi environment for development & testing +# +# Creates 3 dummy networks using mac80211_hwsim + hostapd: +# - FreeWifi (open, strong signal, ch 1) +# - OfficeNetwork (open, medium signal, ch 6) +# - HomeNetwork (WPA2, weak signal, ch 11) +# +# Usage: +# sudo ./wifi-sim.sh start +# sudo ./wifi-sim.sh stop +# sudo ./wifi-sim.sh status +# ============================================================================= + +set -euo pipefail + +# --- User configuration ------------------------------------------------------ + +SSID_STRONG="FreeWifi" # Open network, strong signal +SSID_MEDIUM="OfficeNetwork" # Open network, medium signal +SSID_SECURE="HomeNetwork" # WPA2 network, weak signal +WIFI_PASSWORD="testpassword123" + +# TX power levels in mBm (millibel-milliwatts). Higher = stronger signal. +# These affect the RSSI reported to scanning clients. +TXPOWER_STRONG=3000 # 30 dBm +TXPOWER_MEDIUM=1500 # 15 dBm +TXPOWER_WEAK=100 # 1 dBm + +RUN_DIR="/tmp/wifi-sim" + +# --- Helpers ----------------------------------------------------------------- + +die() { + echo "ERROR: $*" >&2 + exit 1 +} +info() { echo "[*] $*"; } +ok() { echo "[+] $*"; } + +require_cmds() { + local missing=() + for cmd in "$@"; do + command -v "$cmd" &>/dev/null || missing+=("$cmd") + done + if [[ ${#missing[@]} -gt 0 ]]; then + die "Missing required tools: ${missing[*]} + Install with: sudo apt install hostapd wpasupplicant iw" + fi +} + +get_phy_for_iface() { + iw dev "$1" info | awk '/wiphy/{print "phy"$2}' +} + +# --- Start ------------------------------------------------------------------- + +cmd_start() { + [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 start" + require_cmds hostapd iw ip + + if [[ -d "$RUN_DIR" && -f "$RUN_DIR/interfaces" ]]; then + die "wifi-sim appears to already be running. Run 'sudo $0 stop' first." + fi + mkdir -p "$RUN_DIR" + + # Snapshot existing wlan interfaces so we can identify the new ones + info "Snapshotting existing interfaces..." + BEFORE=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim' || true)) + + # Load the kernel module with 4 radios (3 APs + 1 client) + info "Loading mac80211_hwsim (4 radios)..." + if lsmod | grep -q mac80211_hwsim; then + die "mac80211_hwsim is already loaded. Run 'sudo $0 stop' or 'sudo rmmod mac80211_hwsim' first." + fi + modprobe mac80211_hwsim radios=4 + sleep 1 # Give the kernel a moment to register all interfaces + + # Find the interfaces created by our modprobe + AFTER=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim')) + NEW_IFACES=() + for iface in "${AFTER[@]}"; do + [[ ! " ${BEFORE[*]:-} " =~ " $iface " ]] && NEW_IFACES+=("$iface") + done + + if [[ ${#NEW_IFACES[@]} -lt 4 ]]; then + rmmod mac80211_hwsim 2>/dev/null || true + die "Expected 4 new wlan interfaces, found ${#NEW_IFACES[@]}. Check 'dmesg' for errors." + fi + + AP1="${NEW_IFACES[0]}" # Will host SSID_STRONG + AP2="${NEW_IFACES[1]}" # Will host SSID_MEDIUM + AP3="${NEW_IFACES[2]}" # Will host SSID_SECURE + CLIENT="${NEW_IFACES[3]}" # Client-side interface for your code to use + + info "Interfaces assigned: AP1=$AP1 AP2=$AP2 AP3=$AP3 Client=$CLIENT" + echo "$AP1 $AP2 $AP3 $CLIENT" >"$RUN_DIR/interfaces" + + # Prevent NetworkManager from fighting us over these interfaces + if command -v nmcli &>/dev/null && nmcli general status &>/dev/null 2>&1; then + info "Marking interfaces as unmanaged in NetworkManager..." + for iface in "$AP1" "$AP2" "$AP3"; do + nmcli device set "$iface" managed no 2>/dev/null || true + done + + nmcli device set "$CLIENT" managed yes + fi + + # Set TX power on each physical radio to simulate signal strength differences. + # mac80211_hwsim factors TX power into the RSSI reported to scanning clients. + # Note: for precise RSSI control, consider 'wmediumd' (a wireless medium daemon). + info "Setting TX power levels for signal strength simulation..." + PHY1=$(get_phy_for_iface "$AP1") + PHY2=$(get_phy_for_iface "$AP2") + PHY3=$(get_phy_for_iface "$AP3") + + iw phy "$PHY1" set txpower fixed $TXPOWER_STRONG + iw phy "$PHY2" set txpower fixed $TXPOWER_MEDIUM + iw phy "$PHY3" set txpower fixed $TXPOWER_WEAK + + # --- hostapd config: AP1 (open, strong, channel 1) ----------------------- + cat >"$RUN_DIR/ap1.conf" <"$RUN_DIR/ap2.conf" <"$RUN_DIR/ap3.conf" <"$RUN_DIR/ap1.log" 2>&1 || + { + cat "$RUN_DIR/ap1.log" + die "hostapd failed for AP1. See log above." + } + + hostapd -B -P "$RUN_DIR/ap2.pid" "$RUN_DIR/ap2.conf" \ + >"$RUN_DIR/ap2.log" 2>&1 || + { + cat "$RUN_DIR/ap2.log" + die "hostapd failed for AP2. See log above." + } + + hostapd -B -P "$RUN_DIR/ap3.pid" "$RUN_DIR/ap3.conf" \ + >"$RUN_DIR/ap3.log" 2>&1 || + { + cat "$RUN_DIR/ap3.log" + die "hostapd failed for AP3. See log above." + } + + # Bring up the client interface so it's ready to scan/connect + ip link set "$CLIENT" up + + # --- Summary ------------------------------------------------------------- + ok "Virtual WiFi environment is up!" + echo "" + echo " Networks:" + echo " ┌──────────────────┬──────────┬────────┬─────────────┐" + echo " │ SSID │ Security │ Signal │ Interface │" + echo " ├──────────────────┼──────────┼────────┼─────────────┤" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_STRONG" "Open" "Strong" "$AP1" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_MEDIUM" "Open" "Medium" "$AP2" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_SECURE" "WPA2" "Weak" "$AP3" + echo " └──────────────────┴──────────┴────────┴─────────────┘" + echo "" + echo " WPA2 password : $WIFI_PASSWORD" + echo " Client iface : $CLIENT (use this in your code)" + echo "" + echo " Useful commands:" + echo " Scan for networks : sudo iw dev $CLIENT scan | grep -E 'SSID|signal'" + echo " Connect (open) : sudo iw dev $CLIENT connect \"$SSID_STRONG\"" + echo " Connect (WPA2) : sudo wpa_supplicant -Dnl80211 -i$CLIENT \\" + echo " -c <(wpa_passphrase \"$SSID_SECURE\" \"$WIFI_PASSWORD\")" + echo " Stop simulation : sudo $0 stop" + echo "" + echo " Logs: $RUN_DIR/ap{1,2,3}.log" +} + +# --- Stop -------------------------------------------------------------------- + +cmd_stop() { + [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 stop" + + info "Stopping virtual WiFi environment..." + local had_something=false + + # Kill hostapd processes via PID files + for ap in ap1 ap2 ap3; do + PID_FILE="$RUN_DIR/$ap.pid" + if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if kill "$PID" 2>/dev/null; then + info "Stopped hostapd for $ap (PID $PID)" + fi + rm -f "$PID_FILE" + had_something=true + fi + done + + # Re-enable NetworkManager management for these interfaces + if [[ -f "$RUN_DIR/interfaces" ]]; then + read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" + if command -v nmcli &>/dev/null; then + for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do + nmcli device set "$iface" managed yes 2>/dev/null || true + done + fi + fi + + # Unload the kernel module (also destroys all virtual interfaces) + if lsmod | grep -q mac80211_hwsim; then + info "Unloading mac80211_hwsim..." + rmmod mac80211_hwsim && had_something=true + fi + + # Clean up runtime directory + rm -rf "$RUN_DIR" + + if $had_something; then + ok "Virtual WiFi environment stopped." + else + echo "Nothing was running." + fi +} + +# --- Status ------------------------------------------------------------------ + +cmd_status() { + if [[ ! -f "$RUN_DIR/interfaces" ]]; then + echo "wifi-sim is not running." + return + fi + + read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" + echo "wifi-sim is running." + echo "" + echo " Interfaces:" + for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do + STATE=$(ip link show "$iface" 2>/dev/null | awk '/state/{print $9}' || echo "unknown") + CHANNEL=$(iw dev "$iface" info 2>/dev/null | awk '/channel/{print $2}' || echo "?") + printf " %-10s state=%-4s channel=%s\n" "$iface" "$STATE" "$CHANNEL" + done + echo "" + echo " Processes:" + for ap in ap1 ap2 ap3; do + PID_FILE="$RUN_DIR/$ap.pid" + if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo " hostapd ($ap) PID=$PID running" + else + echo " hostapd ($ap) PID=$PID DEAD (check $RUN_DIR/$ap.log)" + fi + fi + done +} + +# --- Entry point ------------------------------------------------------------- + +case "${1:-help}" in +start) cmd_start ;; +stop) cmd_stop ;; +status) cmd_status ;; +*) + echo "Usage: sudo $0 {start|stop|status}" + echo "" + echo " start Load mac80211_hwsim and bring up 3 virtual APs" + echo " stop Tear down all virtual interfaces and unload module" + echo " status Show current state of interfaces and processes" + echo "" + echo "Networks created on start:" + printf " %-16s open, strong signal\n" "$SSID_STRONG" + printf " %-16s open, medium signal\n" "$SSID_MEDIUM" + printf " %-16s WPA2, weak signal (password: $WIFI_PASSWORD)\n" "$SSID_SECURE" + ;; +esac -- 2.47.3 From 82a660229b48f65cc24db4819b50369071477e51 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 26 Jun 2026 17:25:25 +0200 Subject: [PATCH 11/17] remove network-dev folder --- network-dev/wifi-sim.sh | 310 ---------------------------------------- 1 file changed, 310 deletions(-) delete mode 100755 network-dev/wifi-sim.sh diff --git a/network-dev/wifi-sim.sh b/network-dev/wifi-sim.sh deleted file mode 100755 index f3b2cea..0000000 --- a/network-dev/wifi-sim.sh +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/bash -# ============================================================================= -# wifi-sim.sh — Virtual WiFi environment for development & testing -# -# Creates 3 dummy networks using mac80211_hwsim + hostapd: -# - FreeWifi (open, strong signal, ch 1) -# - OfficeNetwork (open, medium signal, ch 6) -# - HomeNetwork (WPA2, weak signal, ch 11) -# -# Usage: -# sudo ./wifi-sim.sh start -# sudo ./wifi-sim.sh stop -# sudo ./wifi-sim.sh status -# ============================================================================= - -set -euo pipefail - -# --- User configuration ------------------------------------------------------ - -SSID_STRONG="FreeWifi" # Open network, strong signal -SSID_MEDIUM="OfficeNetwork" # Open network, medium signal -SSID_SECURE="HomeNetwork" # WPA2 network, weak signal -WIFI_PASSWORD="testpassword123" - -# TX power levels in mBm (millibel-milliwatts). Higher = stronger signal. -# These affect the RSSI reported to scanning clients. -TXPOWER_STRONG=3000 # 30 dBm -TXPOWER_MEDIUM=1500 # 15 dBm -TXPOWER_WEAK=100 # 1 dBm - -RUN_DIR="/tmp/wifi-sim" - -# --- Helpers ----------------------------------------------------------------- - -die() { - echo "ERROR: $*" >&2 - exit 1 -} -info() { echo "[*] $*"; } -ok() { echo "[+] $*"; } - -require_cmds() { - local missing=() - for cmd in "$@"; do - command -v "$cmd" &>/dev/null || missing+=("$cmd") - done - if [[ ${#missing[@]} -gt 0 ]]; then - die "Missing required tools: ${missing[*]} - Install with: sudo apt install hostapd wpasupplicant iw" - fi -} - -get_phy_for_iface() { - iw dev "$1" info | awk '/wiphy/{print "phy"$2}' -} - -# --- Start ------------------------------------------------------------------- - -cmd_start() { - [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 start" - require_cmds hostapd iw ip - - if [[ -d "$RUN_DIR" && -f "$RUN_DIR/interfaces" ]]; then - die "wifi-sim appears to already be running. Run 'sudo $0 stop' first." - fi - mkdir -p "$RUN_DIR" - - # Snapshot existing wlan interfaces so we can identify the new ones - info "Snapshotting existing interfaces..." - BEFORE=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim' || true)) - - # Load the kernel module with 4 radios (3 APs + 1 client) - info "Loading mac80211_hwsim (4 radios)..." - if lsmod | grep -q mac80211_hwsim; then - die "mac80211_hwsim is already loaded. Run 'sudo $0 stop' or 'sudo rmmod mac80211_hwsim' first." - fi - modprobe mac80211_hwsim radios=4 - sleep 1 # Give the kernel a moment to register all interfaces - - # Find the interfaces created by our modprobe - AFTER=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim')) - NEW_IFACES=() - for iface in "${AFTER[@]}"; do - [[ ! " ${BEFORE[*]:-} " =~ " $iface " ]] && NEW_IFACES+=("$iface") - done - - if [[ ${#NEW_IFACES[@]} -lt 4 ]]; then - rmmod mac80211_hwsim 2>/dev/null || true - die "Expected 4 new wlan interfaces, found ${#NEW_IFACES[@]}. Check 'dmesg' for errors." - fi - - AP1="${NEW_IFACES[0]}" # Will host SSID_STRONG - AP2="${NEW_IFACES[1]}" # Will host SSID_MEDIUM - AP3="${NEW_IFACES[2]}" # Will host SSID_SECURE - CLIENT="${NEW_IFACES[3]}" # Client-side interface for your code to use - - info "Interfaces assigned: AP1=$AP1 AP2=$AP2 AP3=$AP3 Client=$CLIENT" - echo "$AP1 $AP2 $AP3 $CLIENT" >"$RUN_DIR/interfaces" - - # Prevent NetworkManager from fighting us over these interfaces - if command -v nmcli &>/dev/null && nmcli general status &>/dev/null 2>&1; then - info "Marking interfaces as unmanaged in NetworkManager..." - for iface in "$AP1" "$AP2" "$AP3"; do - nmcli device set "$iface" managed no 2>/dev/null || true - done - - nmcli device set "$CLIENT" managed yes - fi - - # Set TX power on each physical radio to simulate signal strength differences. - # mac80211_hwsim factors TX power into the RSSI reported to scanning clients. - # Note: for precise RSSI control, consider 'wmediumd' (a wireless medium daemon). - info "Setting TX power levels for signal strength simulation..." - PHY1=$(get_phy_for_iface "$AP1") - PHY2=$(get_phy_for_iface "$AP2") - PHY3=$(get_phy_for_iface "$AP3") - - iw phy "$PHY1" set txpower fixed $TXPOWER_STRONG - iw phy "$PHY2" set txpower fixed $TXPOWER_MEDIUM - iw phy "$PHY3" set txpower fixed $TXPOWER_WEAK - - # --- hostapd config: AP1 (open, strong, channel 1) ----------------------- - cat >"$RUN_DIR/ap1.conf" <"$RUN_DIR/ap2.conf" <"$RUN_DIR/ap3.conf" <"$RUN_DIR/ap1.log" 2>&1 || - { - cat "$RUN_DIR/ap1.log" - die "hostapd failed for AP1. See log above." - } - - hostapd -B -P "$RUN_DIR/ap2.pid" "$RUN_DIR/ap2.conf" \ - >"$RUN_DIR/ap2.log" 2>&1 || - { - cat "$RUN_DIR/ap2.log" - die "hostapd failed for AP2. See log above." - } - - hostapd -B -P "$RUN_DIR/ap3.pid" "$RUN_DIR/ap3.conf" \ - >"$RUN_DIR/ap3.log" 2>&1 || - { - cat "$RUN_DIR/ap3.log" - die "hostapd failed for AP3. See log above." - } - - # Bring up the client interface so it's ready to scan/connect - ip link set "$CLIENT" up - - # --- Summary ------------------------------------------------------------- - ok "Virtual WiFi environment is up!" - echo "" - echo " Networks:" - echo " ┌──────────────────┬──────────┬────────┬─────────────┐" - echo " │ SSID │ Security │ Signal │ Interface │" - echo " ├──────────────────┼──────────┼────────┼─────────────┤" - printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_STRONG" "Open" "Strong" "$AP1" - printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_MEDIUM" "Open" "Medium" "$AP2" - printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_SECURE" "WPA2" "Weak" "$AP3" - echo " └──────────────────┴──────────┴────────┴─────────────┘" - echo "" - echo " WPA2 password : $WIFI_PASSWORD" - echo " Client iface : $CLIENT (use this in your code)" - echo "" - echo " Useful commands:" - echo " Scan for networks : sudo iw dev $CLIENT scan | grep -E 'SSID|signal'" - echo " Connect (open) : sudo iw dev $CLIENT connect \"$SSID_STRONG\"" - echo " Connect (WPA2) : sudo wpa_supplicant -Dnl80211 -i$CLIENT \\" - echo " -c <(wpa_passphrase \"$SSID_SECURE\" \"$WIFI_PASSWORD\")" - echo " Stop simulation : sudo $0 stop" - echo "" - echo " Logs: $RUN_DIR/ap{1,2,3}.log" -} - -# --- Stop -------------------------------------------------------------------- - -cmd_stop() { - [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 stop" - - info "Stopping virtual WiFi environment..." - local had_something=false - - # Kill hostapd processes via PID files - for ap in ap1 ap2 ap3; do - PID_FILE="$RUN_DIR/$ap.pid" - if [[ -f "$PID_FILE" ]]; then - PID=$(cat "$PID_FILE") - if kill "$PID" 2>/dev/null; then - info "Stopped hostapd for $ap (PID $PID)" - fi - rm -f "$PID_FILE" - had_something=true - fi - done - - # Re-enable NetworkManager management for these interfaces - if [[ -f "$RUN_DIR/interfaces" ]]; then - read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" - if command -v nmcli &>/dev/null; then - for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do - nmcli device set "$iface" managed yes 2>/dev/null || true - done - fi - fi - - # Unload the kernel module (also destroys all virtual interfaces) - if lsmod | grep -q mac80211_hwsim; then - info "Unloading mac80211_hwsim..." - rmmod mac80211_hwsim && had_something=true - fi - - # Clean up runtime directory - rm -rf "$RUN_DIR" - - if $had_something; then - ok "Virtual WiFi environment stopped." - else - echo "Nothing was running." - fi -} - -# --- Status ------------------------------------------------------------------ - -cmd_status() { - if [[ ! -f "$RUN_DIR/interfaces" ]]; then - echo "wifi-sim is not running." - return - fi - - read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" - echo "wifi-sim is running." - echo "" - echo " Interfaces:" - for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do - STATE=$(ip link show "$iface" 2>/dev/null | awk '/state/{print $9}' || echo "unknown") - CHANNEL=$(iw dev "$iface" info 2>/dev/null | awk '/channel/{print $2}' || echo "?") - printf " %-10s state=%-4s channel=%s\n" "$iface" "$STATE" "$CHANNEL" - done - echo "" - echo " Processes:" - for ap in ap1 ap2 ap3; do - PID_FILE="$RUN_DIR/$ap.pid" - if [[ -f "$PID_FILE" ]]; then - PID=$(cat "$PID_FILE") - if kill -0 "$PID" 2>/dev/null; then - echo " hostapd ($ap) PID=$PID running" - else - echo " hostapd ($ap) PID=$PID DEAD (check $RUN_DIR/$ap.log)" - fi - fi - done -} - -# --- Entry point ------------------------------------------------------------- - -case "${1:-help}" in -start) cmd_start ;; -stop) cmd_stop ;; -status) cmd_status ;; -*) - echo "Usage: sudo $0 {start|stop|status}" - echo "" - echo " start Load mac80211_hwsim and bring up 3 virtual APs" - echo " stop Tear down all virtual interfaces and unload module" - echo " status Show current state of interfaces and processes" - echo "" - echo "Networks created on start:" - printf " %-16s open, strong signal\n" "$SSID_STRONG" - printf " %-16s open, medium signal\n" "$SSID_MEDIUM" - printf " %-16s WPA2, weak signal (password: $WIFI_PASSWORD)\n" "$SSID_SECURE" - ;; -esac -- 2.47.3 From 04481ae7e1ac3c8022ecb0a22cbf8e47956253d2 Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 27 Jun 2026 00:11:18 +0200 Subject: [PATCH 12/17] sidebar menu on top --- .../Sidebar/Utils/Cards/Record.qml | 1 + network-dev/wifi-sim.sh | 310 ++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100755 network-dev/wifi-sim.sh diff --git a/Modules/Notifications/Sidebar/Utils/Cards/Record.qml b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml index 16d1f3c..1d12b0a 100644 --- a/Modules/Notifications/Sidebar/Utils/Cards/Record.qml +++ b/Modules/Notifications/Sidebar/Utils/Cards/Record.qml @@ -73,6 +73,7 @@ CustomRect { CustomSplitButton { active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0] enabled: !Recorder.running + menuOnTop: true menuItems: [ MenuItem { diff --git a/network-dev/wifi-sim.sh b/network-dev/wifi-sim.sh new file mode 100755 index 0000000..f3b2cea --- /dev/null +++ b/network-dev/wifi-sim.sh @@ -0,0 +1,310 @@ +#!/bin/bash +# ============================================================================= +# wifi-sim.sh — Virtual WiFi environment for development & testing +# +# Creates 3 dummy networks using mac80211_hwsim + hostapd: +# - FreeWifi (open, strong signal, ch 1) +# - OfficeNetwork (open, medium signal, ch 6) +# - HomeNetwork (WPA2, weak signal, ch 11) +# +# Usage: +# sudo ./wifi-sim.sh start +# sudo ./wifi-sim.sh stop +# sudo ./wifi-sim.sh status +# ============================================================================= + +set -euo pipefail + +# --- User configuration ------------------------------------------------------ + +SSID_STRONG="FreeWifi" # Open network, strong signal +SSID_MEDIUM="OfficeNetwork" # Open network, medium signal +SSID_SECURE="HomeNetwork" # WPA2 network, weak signal +WIFI_PASSWORD="testpassword123" + +# TX power levels in mBm (millibel-milliwatts). Higher = stronger signal. +# These affect the RSSI reported to scanning clients. +TXPOWER_STRONG=3000 # 30 dBm +TXPOWER_MEDIUM=1500 # 15 dBm +TXPOWER_WEAK=100 # 1 dBm + +RUN_DIR="/tmp/wifi-sim" + +# --- Helpers ----------------------------------------------------------------- + +die() { + echo "ERROR: $*" >&2 + exit 1 +} +info() { echo "[*] $*"; } +ok() { echo "[+] $*"; } + +require_cmds() { + local missing=() + for cmd in "$@"; do + command -v "$cmd" &>/dev/null || missing+=("$cmd") + done + if [[ ${#missing[@]} -gt 0 ]]; then + die "Missing required tools: ${missing[*]} + Install with: sudo apt install hostapd wpasupplicant iw" + fi +} + +get_phy_for_iface() { + iw dev "$1" info | awk '/wiphy/{print "phy"$2}' +} + +# --- Start ------------------------------------------------------------------- + +cmd_start() { + [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 start" + require_cmds hostapd iw ip + + if [[ -d "$RUN_DIR" && -f "$RUN_DIR/interfaces" ]]; then + die "wifi-sim appears to already be running. Run 'sudo $0 stop' first." + fi + mkdir -p "$RUN_DIR" + + # Snapshot existing wlan interfaces so we can identify the new ones + info "Snapshotting existing interfaces..." + BEFORE=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim' || true)) + + # Load the kernel module with 4 radios (3 APs + 1 client) + info "Loading mac80211_hwsim (4 radios)..." + if lsmod | grep -q mac80211_hwsim; then + die "mac80211_hwsim is already loaded. Run 'sudo $0 stop' or 'sudo rmmod mac80211_hwsim' first." + fi + modprobe mac80211_hwsim radios=4 + sleep 1 # Give the kernel a moment to register all interfaces + + # Find the interfaces created by our modprobe + AFTER=($(iw dev 2>/dev/null | awk '/Interface/{print $2}' | grep -v '^hwsim')) + NEW_IFACES=() + for iface in "${AFTER[@]}"; do + [[ ! " ${BEFORE[*]:-} " =~ " $iface " ]] && NEW_IFACES+=("$iface") + done + + if [[ ${#NEW_IFACES[@]} -lt 4 ]]; then + rmmod mac80211_hwsim 2>/dev/null || true + die "Expected 4 new wlan interfaces, found ${#NEW_IFACES[@]}. Check 'dmesg' for errors." + fi + + AP1="${NEW_IFACES[0]}" # Will host SSID_STRONG + AP2="${NEW_IFACES[1]}" # Will host SSID_MEDIUM + AP3="${NEW_IFACES[2]}" # Will host SSID_SECURE + CLIENT="${NEW_IFACES[3]}" # Client-side interface for your code to use + + info "Interfaces assigned: AP1=$AP1 AP2=$AP2 AP3=$AP3 Client=$CLIENT" + echo "$AP1 $AP2 $AP3 $CLIENT" >"$RUN_DIR/interfaces" + + # Prevent NetworkManager from fighting us over these interfaces + if command -v nmcli &>/dev/null && nmcli general status &>/dev/null 2>&1; then + info "Marking interfaces as unmanaged in NetworkManager..." + for iface in "$AP1" "$AP2" "$AP3"; do + nmcli device set "$iface" managed no 2>/dev/null || true + done + + nmcli device set "$CLIENT" managed yes + fi + + # Set TX power on each physical radio to simulate signal strength differences. + # mac80211_hwsim factors TX power into the RSSI reported to scanning clients. + # Note: for precise RSSI control, consider 'wmediumd' (a wireless medium daemon). + info "Setting TX power levels for signal strength simulation..." + PHY1=$(get_phy_for_iface "$AP1") + PHY2=$(get_phy_for_iface "$AP2") + PHY3=$(get_phy_for_iface "$AP3") + + iw phy "$PHY1" set txpower fixed $TXPOWER_STRONG + iw phy "$PHY2" set txpower fixed $TXPOWER_MEDIUM + iw phy "$PHY3" set txpower fixed $TXPOWER_WEAK + + # --- hostapd config: AP1 (open, strong, channel 1) ----------------------- + cat >"$RUN_DIR/ap1.conf" <"$RUN_DIR/ap2.conf" <"$RUN_DIR/ap3.conf" <"$RUN_DIR/ap1.log" 2>&1 || + { + cat "$RUN_DIR/ap1.log" + die "hostapd failed for AP1. See log above." + } + + hostapd -B -P "$RUN_DIR/ap2.pid" "$RUN_DIR/ap2.conf" \ + >"$RUN_DIR/ap2.log" 2>&1 || + { + cat "$RUN_DIR/ap2.log" + die "hostapd failed for AP2. See log above." + } + + hostapd -B -P "$RUN_DIR/ap3.pid" "$RUN_DIR/ap3.conf" \ + >"$RUN_DIR/ap3.log" 2>&1 || + { + cat "$RUN_DIR/ap3.log" + die "hostapd failed for AP3. See log above." + } + + # Bring up the client interface so it's ready to scan/connect + ip link set "$CLIENT" up + + # --- Summary ------------------------------------------------------------- + ok "Virtual WiFi environment is up!" + echo "" + echo " Networks:" + echo " ┌──────────────────┬──────────┬────────┬─────────────┐" + echo " │ SSID │ Security │ Signal │ Interface │" + echo " ├──────────────────┼──────────┼────────┼─────────────┤" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_STRONG" "Open" "Strong" "$AP1" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_MEDIUM" "Open" "Medium" "$AP2" + printf " │ %-16s │ %-8s │ %-6s │ %-11s │\n" "$SSID_SECURE" "WPA2" "Weak" "$AP3" + echo " └──────────────────┴──────────┴────────┴─────────────┘" + echo "" + echo " WPA2 password : $WIFI_PASSWORD" + echo " Client iface : $CLIENT (use this in your code)" + echo "" + echo " Useful commands:" + echo " Scan for networks : sudo iw dev $CLIENT scan | grep -E 'SSID|signal'" + echo " Connect (open) : sudo iw dev $CLIENT connect \"$SSID_STRONG\"" + echo " Connect (WPA2) : sudo wpa_supplicant -Dnl80211 -i$CLIENT \\" + echo " -c <(wpa_passphrase \"$SSID_SECURE\" \"$WIFI_PASSWORD\")" + echo " Stop simulation : sudo $0 stop" + echo "" + echo " Logs: $RUN_DIR/ap{1,2,3}.log" +} + +# --- Stop -------------------------------------------------------------------- + +cmd_stop() { + [[ $EUID -ne 0 ]] && die "Please run as root: sudo $0 stop" + + info "Stopping virtual WiFi environment..." + local had_something=false + + # Kill hostapd processes via PID files + for ap in ap1 ap2 ap3; do + PID_FILE="$RUN_DIR/$ap.pid" + if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if kill "$PID" 2>/dev/null; then + info "Stopped hostapd for $ap (PID $PID)" + fi + rm -f "$PID_FILE" + had_something=true + fi + done + + # Re-enable NetworkManager management for these interfaces + if [[ -f "$RUN_DIR/interfaces" ]]; then + read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" + if command -v nmcli &>/dev/null; then + for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do + nmcli device set "$iface" managed yes 2>/dev/null || true + done + fi + fi + + # Unload the kernel module (also destroys all virtual interfaces) + if lsmod | grep -q mac80211_hwsim; then + info "Unloading mac80211_hwsim..." + rmmod mac80211_hwsim && had_something=true + fi + + # Clean up runtime directory + rm -rf "$RUN_DIR" + + if $had_something; then + ok "Virtual WiFi environment stopped." + else + echo "Nothing was running." + fi +} + +# --- Status ------------------------------------------------------------------ + +cmd_status() { + if [[ ! -f "$RUN_DIR/interfaces" ]]; then + echo "wifi-sim is not running." + return + fi + + read -r AP1 AP2 AP3 CLIENT <"$RUN_DIR/interfaces" + echo "wifi-sim is running." + echo "" + echo " Interfaces:" + for iface in "$AP1" "$AP2" "$AP3" "$CLIENT"; do + STATE=$(ip link show "$iface" 2>/dev/null | awk '/state/{print $9}' || echo "unknown") + CHANNEL=$(iw dev "$iface" info 2>/dev/null | awk '/channel/{print $2}' || echo "?") + printf " %-10s state=%-4s channel=%s\n" "$iface" "$STATE" "$CHANNEL" + done + echo "" + echo " Processes:" + for ap in ap1 ap2 ap3; do + PID_FILE="$RUN_DIR/$ap.pid" + if [[ -f "$PID_FILE" ]]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + echo " hostapd ($ap) PID=$PID running" + else + echo " hostapd ($ap) PID=$PID DEAD (check $RUN_DIR/$ap.log)" + fi + fi + done +} + +# --- Entry point ------------------------------------------------------------- + +case "${1:-help}" in +start) cmd_start ;; +stop) cmd_stop ;; +status) cmd_status ;; +*) + echo "Usage: sudo $0 {start|stop|status}" + echo "" + echo " start Load mac80211_hwsim and bring up 3 virtual APs" + echo " stop Tear down all virtual interfaces and unload module" + echo " status Show current state of interfaces and processes" + echo "" + echo "Networks created on start:" + printf " %-16s open, strong signal\n" "$SSID_STRONG" + printf " %-16s open, medium signal\n" "$SSID_MEDIUM" + printf " %-16s WPA2, weak signal (password: $WIFI_PASSWORD)\n" "$SSID_SECURE" + ;; +esac -- 2.47.3 From e685500698c9ed0f2cf33feff1af4bae01b88f12 Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 28 Jun 2026 22:48:46 +0200 Subject: [PATCH 13/17] revert certain changes for blobs --- Plugins/ZShell/Blobs/blobrect.cpp | 25 ++++++++++++++++++++++++- Plugins/ZShell/Blobs/blobshape.cpp | 12 +++++++++--- Plugins/ZShell/Blobs/blobshape.hpp | 2 ++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobrect.cpp b/Plugins/ZShell/Blobs/blobrect.cpp index 9da0a49..578ed58 100644 --- a/Plugins/ZShell/Blobs/blobrect.cpp +++ b/Plugins/ZShell/Blobs/blobrect.cpp @@ -3,6 +3,7 @@ #include #include +#include BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) { @@ -27,6 +28,17 @@ void BlobRect::updatePolish() { emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; + + if (m_group) { + QMetaObject::invokeMethod( + this, + [this]() { + if (m_group) + m_group->markDirty(); + }, + Qt::QueuedConnection + ); + } } else { QMetaObject::invokeMethod( this, @@ -289,9 +301,20 @@ void BlobRect::checkAtRest(float speed) { m_dmVel00 = 0.0f; m_dmVel01 = 0.0f; m_dmVel11 = 0.0f; - m_deformMatrix = QMatrix4x4(); // identity + m_deformMatrix = QMatrix4x4(); emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); m_physicsActive = false; + + if (m_group) { + QMetaObject::invokeMethod( + this, + [this]() { + if (m_group) + m_group->markDirty(); + }, + Qt::QueuedConnection + ); + } } } diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index e1626ca..128d4b1 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -76,9 +76,15 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome if (m_group) { m_pendingDx += static_cast(newGeometry.x() - oldGeometry.x()); m_pendingDy += static_cast(newGeometry.y() - oldGeometry.y()); - const auto dw = std::abs(newGeometry.width() - oldGeometry.width()); - const auto dh = std::abs(newGeometry.height() - oldGeometry.height()); - if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) { + m_pendingDw += static_cast(newGeometry.width() - oldGeometry.width()); + m_pendingDh += static_cast(newGeometry.height() - oldGeometry.height()); + + const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) + + std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f); + const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f; + + if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold || + std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) { m_pendingDx = 0; m_pendingDy = 0; m_group->markShapeDirty(this); diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index e9542b7..517a477 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -84,6 +84,8 @@ float m_cachedPaddedX = 0; float m_cachedPaddedY = 0; float m_cachedPaddedW = 0; float m_cachedPaddedH = 0; +float m_pendingDw = 0; +float m_pendingDh = 0; QRectF m_localPaddedRect; QVector m_cachedRects; int m_cachedMyIndex = -2; -- 2.47.3 From 437f935f73adaf22ce2d9dbbd626f27518753a8d Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 29 Jun 2026 23:54:54 +0200 Subject: [PATCH 14/17] add about page in settings --- Helpers/SystemInfo.qml | 70 ++++++++++++-- Modules/SettingsNew/Common/InfoRow.qml | 78 +++++++++++++++ Modules/SettingsNew/PageCompRegistry.qml | 10 ++ Modules/SettingsNew/PageRegistry.qml | 6 ++ Modules/SettingsNew/Pages/AboutPage.qml | 117 +++++++++++++++++++++++ Plugins/ZShell/zutils.hpp | 3 + 6 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 Modules/SettingsNew/Common/InfoRow.qml create mode 100644 Modules/SettingsNew/Pages/AboutPage.qml diff --git a/Helpers/SystemInfo.qml b/Helpers/SystemInfo.qml index c4e3565..d0af2a9 100644 --- a/Helpers/SystemInfo.qml +++ b/Helpers/SystemInfo.qml @@ -1,17 +1,30 @@ pragma Singleton -import qs.Config +import QtQuick import Quickshell import Quickshell.Io -import QtQuick +import qs.Config +import qs.Paths Singleton { id: root + property string boardName + property string boardVendor + readonly property string device: { + if (!boardName) + return boardVendor; + if (!boardVendor || boardName.toLowerCase().startsWith(boardVendor.toLowerCase())) + return boardName; + return `${boardVendor} ${boardName}`; + } + property string firmware + property string hostname property bool isDefaultLogo: true + property string kernel property string osId property list osIdLike - property string osLogo + property string osLogo: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`) property string osName property string osPrettyName readonly property string shell: Quickshell.env("SHELL").split("/").pop() @@ -19,6 +32,12 @@ Singleton { readonly property string user: Quickshell.env("USER") readonly property string wm: Quickshell.env("XDG_CURRENT_DESKTOP") || Quickshell.env("XDG_SESSION_DESKTOP") + function sanitiseDmi(s: string): string { + const t = s.trim(); + const junk = ["to be filled by o.e.m.", "system product name", "system manufacturer", "system version", "default string", "o.e.m.", "not specified", "not applicable", "unknown", "none", ""]; + return junk.includes(t.toLowerCase()) ? "" : t; + } + FileView { id: osRelease @@ -35,7 +54,10 @@ Singleton { root.osIdLike = fd("ID_LIKE").split(" "); const logo = Quickshell.iconPath(fd("LOGO"), true); - if (Config.general.logo) { + if (Config.general.logo === "zshell") { + root.osLogo = Qt.resolvedUrl(`${Quickshell.shellDir}/assets/logo.svg`); + root.isDefaultLogo = true; + } else if (Config.general.logo) { root.osLogo = Quickshell.iconPath(Config.general.logo, true) || "file://" + Paths.absolutePath(Config.general.logo); root.isDefaultLogo = false; } else if (logo) { @@ -53,6 +75,39 @@ Singleton { target: Config.general } + FileView { + path: "/proc/sys/kernel/osrelease" + + onLoaded: root.kernel = text().trim() + } + + FileView { + path: "/proc/sys/kernel/hostname" + + onLoaded: root.hostname = text().trim() + } + + FileView { + path: "/sys/class/dmi/id/sys_vendor" + printErrors: false + + onLoaded: root.boardVendor = root.sanitiseDmi(text()) + } + + FileView { + path: "/sys/class/dmi/id/product_name" + printErrors: false + + onLoaded: root.boardName = root.sanitiseDmi(text()) + } + + FileView { + path: "/sys/class/dmi/id/bios_version" + printErrors: false + + onLoaded: root.firmware = root.sanitiseDmi(text()) + } + Timer { interval: 15000 repeat: true @@ -69,12 +124,15 @@ Singleton { onLoaded: { const up = parseInt(text().split(" ")[0] ?? 0); - const hours = Math.floor(up / 3600); + const days = Math.floor(up / 86400); + const hours = Math.floor((up % 86400) / 3600); const minutes = Math.floor((up % 3600) / 60); let str = ""; + if (days > 0) + str += `${days} day${days === 1 ? "" : "s"}`; if (hours > 0) - str += `${hours} hour${hours === 1 ? "" : "s"}`; + str += `${str ? ", " : ""}${hours} hour${hours === 1 ? "" : "s"}`; if (minutes > 0 || !str) str += `${str ? ", " : ""}${minutes} minute${minutes === 1 ? "" : "s"}`; root.uptime = str; diff --git a/Modules/SettingsNew/Common/InfoRow.qml b/Modules/SettingsNew/Common/InfoRow.qml new file mode 100644 index 0000000..1a715ff --- /dev/null +++ b/Modules/SettingsNew/Common/InfoRow.qml @@ -0,0 +1,78 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import qs.Components +import qs.Config + +ConnectedRect { + id: root + + property string icon + property color iconColor: DynamicColors.palette.m3onSurfaceVariant + property alias label: label.text + property Component leadingComponent: icon ? iconComp : null + property string subtext + property alias value: value.text + + Layout.fillWidth: true + implicitHeight: rowLayout.implicitHeight + rowLayout.anchors.margins * 2 + + Component { + id: iconComp + + MaterialIcon { + color: root.iconColor + font.pointSize: Appearance.font.size.small + text: root.icon + } + } + + RowLayout { + id: rowLayout + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.largeIncreased + anchors.margins: Appearance.padding.larger + anchors.rightMargin: Appearance.padding.largeIncreased + spacing: Appearance.spacing.small + + Loader { + active: root.leadingComponent + sourceComponent: root.leadingComponent + visible: root.leadingComponent + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: label + + Layout.fillWidth: true + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + } + + CustomText { + Layout.fillWidth: true + color: DynamicColors.palette.m3outline + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + text: root.subtext + visible: root.subtext + } + } + + CustomText { + id: value + + Layout.maximumWidth: root.width / 2 + color: DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + horizontalAlignment: Text.AlignRight + } + } +} diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml index b40238f..a74e153 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -150,6 +150,16 @@ QtObject { } } } + }, + + // About + Component { + StackPage { + Component { + AboutPage { + } + } + } } ] readonly property Component placeholderComp: Component { diff --git a/Modules/SettingsNew/PageRegistry.qml b/Modules/SettingsNew/PageRegistry.qml index 84aef7f..3971d36 100644 --- a/Modules/SettingsNew/PageRegistry.qml +++ b/Modules/SettingsNew/PageRegistry.qml @@ -60,5 +60,11 @@ Singleton { description: qsTr("Poll intervals, audio and brightness increments"), category: "shell" }, + { + name: qsTr("About"), + icon: "info", + description: qsTr("System information"), + category: "about" + }, ] } diff --git a/Modules/SettingsNew/Pages/AboutPage.qml b/Modules/SettingsNew/Pages/AboutPage.qml new file mode 100644 index 0000000..cec1e3d --- /dev/null +++ b/Modules/SettingsNew/Pages/AboutPage.qml @@ -0,0 +1,117 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import ZShell +import qs.Modules.SettingsNew.Common +import qs.Config +import qs.Helpers +import qs.Components + +PageBase { + id: root + + property string cliVersion + readonly property int pluginCount: 0 + property string quickshellVersion + + title: qsTr("About") + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + spacing: Appearance.spacing.extraSmall / 2 + width: root.cappedWidth + + Process { + command: ["quickshell", "--version"] + running: true + + stdout: StdioCollector { + onStreamFinished: root.quickshellVersion = text.trim().split(" ")[1] ?? "" + } + } + + ConnectedRect { + Layout.fillWidth: true + first: true + implicitHeight: hero.implicitHeight + Appearance.padding.extraLarge * 2 + last: true + + ColumnLayout { + id: hero + + anchors.centerIn: parent + spacing: Appearance.spacing.small + width: parent.width - Appearance.padding.largeIncreased * 2 + + CustomText { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: Appearance.spacing.small + font.pointSize: Appearance.font.size.large + text: "ZShell" + } + + CustomText { + Layout.alignment: Qt.AlignHCenter + color: DynamicColors.palette.m3onSurfaceVariant + font: Appearance.font.body.medium + text: ZUtils.version ? `v${ZUtils.version}` : "…" + } + } + } + + // System + SectionHeader { + text: qsTr("System") + } + + InfoRow { + first: true + label: qsTr("Hostname") + value: SystemInfo.hostname + } + + InfoRow { + label: qsTr("Device") + value: SystemInfo.device + } + + InfoRow { + label: qsTr("Distro") + value: SystemInfo.osPrettyName || SystemInfo.osName + } + + InfoRow { + label: qsTr("Kernel") + value: SystemInfo.kernel + } + + InfoRow { + label: qsTr("Firmware") + last: true + value: SystemInfo.firmware + } + + // Software + SectionHeader { + text: qsTr("Software") + } + + InfoRow { + first: true + label: qsTr("Shell") + value: ZUtils.version || "…" + } + + InfoRow { + label: qsTr("Quickshell") + value: root.quickshellVersion || "…" + } + + InfoRow { + label: qsTr("Qt") + last: true + value: ZUtils.qtVersion || "…" + } + } +} diff --git a/Plugins/ZShell/zutils.hpp b/Plugins/ZShell/zutils.hpp index 54cfb5f..dfbd42f 100644 --- a/Plugins/ZShell/zutils.hpp +++ b/Plugins/ZShell/zutils.hpp @@ -12,6 +12,9 @@ Q_OBJECT QML_ELEMENT QML_SINGLETON +Q_PROPERTY(QString version READ version CONSTANT) +Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT) + public: // clang-format off Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); -- 2.47.3 From 157bdd8fd9f613bc943276298b8d8e2382c54459 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:25:12 +0200 Subject: [PATCH 15/17] new spin box, textfield and searchbar --- Components/CustomSpinBox.qml | 236 +++---- Components/CustomTextField.qml | 316 +++++++-- Components/Menu.qml | 3 +- Components/SearchBar.qml | 94 +++ Components/TextFieldBase.qml | 88 +++ Modules/Launcher/AppList.qml | 2 +- Modules/Launcher/Content.qml | 156 ++--- Modules/Launcher/ContentList.qml | 2 +- Modules/Launcher/WallpaperList.qml | 2 +- Modules/SettingsNew/Common/HyprTimeInput.qml | 659 ------------------- Modules/SettingsNew/Common/PageBase.qml | 6 + Modules/SettingsNew/Common/PopupRow.qml | 4 +- Modules/SettingsNew/Common/SpinRow.qml | 8 +- Modules/SettingsNew/Common/TimeInput.qml | 12 +- Modules/SettingsNew/Content.qml | 4 + Modules/SettingsNew/NavPane.qml | 23 +- Modules/SettingsNew/NavPane/NavLocations.qml | 4 + Modules/SettingsNew/NavPane/SearchBar.qml | 75 --- Modules/SettingsNew/Pages/AboutPage.qml | 2 +- 19 files changed, 641 insertions(+), 1055 deletions(-) create mode 100644 Components/SearchBar.qml create mode 100644 Components/TextFieldBase.qml delete mode 100644 Modules/SettingsNew/Common/HyprTimeInput.qml delete mode 100644 Modules/SettingsNew/NavPane/SearchBar.qml diff --git a/Components/CustomSpinBox.qml b/Components/CustomSpinBox.qml index 55b4189..0b8f9bd 100644 --- a/Components/CustomSpinBox.qml +++ b/Components/CustomSpinBox.qml @@ -1,180 +1,132 @@ -pragma ComponentBehavior: Bound - import QtQuick -import QtQuick.Layouts +import QtQuick.Templates import qs.Config -RowLayout { +DoubleSpinBox { id: root - property string displayText: root.value.toString() - property bool isEditing: false - property real max: Infinity - property real min: -Infinity - property alias repeatRate: timer.interval - property real step: 1 - property real value + property int cLayer: 1 + property int repeatDecay: 50 + property int repeatRate: 400 - signal valueModified(value: real) + function decrease(): void { + let newValue = Math.max(from, value - stepSize); + const decimals = stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0; + newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals); + value = newValue; + valueModified(); + } + function increase(): void { + let newValue = Math.min(to, value + stepSize); + const decimals = stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0; + newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals); + value = newValue; + valueModified(); + } + + decimals: stepSize < 1 ? Math.max(1, Math.ceil(-Math.log10(stepSize))) : 0 + editable: true + implicitHeight: Math.max(up.indicator.implicitHeight, down.indicator.implicitHeight, contentItem.implicitHeight) + topPadding + bottomPadding + implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding + leftPadding: up.indicator.implicitWidth + Appearance.spacing.extraSmall / 2 + rightPadding: down.indicator.implicitWidth + Appearance.spacing.extraSmall / 2 spacing: Appearance.spacing.small - onValueChanged: { - if (!root.isEditing) { - root.displayText = root.value.toString(); - } - } - - CustomTextField { - id: textField - - color: root.enabled ? DynamicColors.palette.m3onSurface : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) - implicitHeight: upButton.implicitHeight + contentItem: TextFieldBase { + horizontalAlignment: TextField.AlignHCenter + implicitWidth: 65 inputMethodHints: Qt.ImhFormattedNumbersOnly - leftPadding: Appearance.padding.normal - padding: Appearance.padding.small - rightPadding: Appearance.padding.normal - text: root.isEditing ? text : root.displayText + leftPadding: Appearance.padding.larger + readOnly: !root.editable + rightPadding: Appearance.padding.larger + text: root.textFromValue(root.value, root.locale) + validator: root.validator background: CustomRect { - color: root.enabled ? DynamicColors.tPalette.m3surfaceContainerHigh : DynamicColors.tPalette.m3surfaceContainerLow - implicitWidth: 100 - radius: Appearance.rounding.full - } - validator: DoubleValidator { - bottom: root.min - decimals: root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0 - top: root.max - } - - onAccepted: { - const numValue = parseFloat(text); - if (!isNaN(numValue)) { - const clampedValue = Math.max(root.min, Math.min(root.max, numValue)); - root.value = clampedValue; - root.displayText = clampedValue.toString(); - root.valueModified(clampedValue); - } else { - text = root.displayText; - } - root.isEditing = false; - } - onActiveFocusChanged: { - if (activeFocus) { - root.isEditing = true; - } else { - root.isEditing = false; - root.displayText = root.value.toString(); - } - } - onEditingFinished: { - if (text !== root.displayText) { - const numValue = parseFloat(text); - if (!isNaN(numValue)) { - const clampedValue = Math.max(root.min, Math.min(root.max, numValue)); - root.value = clampedValue; - root.displayText = clampedValue.toString(); - root.valueModified(clampedValue); - } else { - text = root.displayText; - } - } - root.isEditing = false; + color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) + radius: Appearance.rounding.extraSmall } } + down.indicator: IconButton { + id: downButton - CustomRect { + bottomRightRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall + color: enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) : disabledColor + disabledColor: Qt.alpha(DynamicColors.palette.m3surfaceContainerHighest, 0.4) + icon: "remove" + isRound: true + label.anchors.horizontalCenterOffset: pressed ? 0 : 2 + padding: Appearance.padding.extraSmall + topRightRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall + type: IconButton.Text + + Behavior on bottomRightRadius { + Anim { + type: Anim.DefaultEffects + } + } + Behavior on label.anchors.horizontalCenterOffset { + Anim { + type: Anim.DefaultEffects + } + } + Behavior on topRightRadius { + Anim { + type: Anim.DefaultEffects + } + } + } + up.indicator: IconButton { id: upButton - color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) - implicitHeight: upIcon.implicitHeight + Appearance.padding.extraSmall * 2 - implicitWidth: implicitHeight - radius: upState.pressed ? Appearance.rounding.small : Appearance.rounding.normal + anchors.right: parent.right + bottomLeftRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall + color: enabled ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) : disabledColor + disabledColor: Qt.alpha(DynamicColors.palette.m3surfaceContainerHighest, 0.4) + icon: "add" + isRound: true + label.anchors.horizontalCenterOffset: pressed ? 0 : -2 + padding: Appearance.padding.extraSmall + topLeftRadius: pressed ? Appearance.rounding.small : Appearance.rounding.extraSmall + type: IconButton.Text - Behavior on radius { + Behavior on bottomLeftRadius { Anim { - type: Anim.FastEffects + type: Anim.DefaultEffects } } - - StateLayer { - id: upState - - color: DynamicColors.palette.m3onPrimary - - onClicked: { - let newValue = Math.min(root.max, root.value + root.step); - // Round to avoid floating point precision errors - const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; - newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals); - root.value = newValue; - root.displayText = newValue.toString(); - root.valueModified(newValue); - } - onPressAndHold: timer.start() - onReleased: timer.stop() - } - - MaterialIcon { - id: upIcon - - anchors.centerIn: parent - color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) - text: "keyboard_arrow_up" - } - } - - CustomRect { - color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) - implicitHeight: downIcon.implicitHeight + Appearance.padding.extraSmall * 2 - implicitWidth: implicitHeight - radius: downState.pressed ? Appearance.rounding.small : Appearance.rounding.normal - - Behavior on radius { + Behavior on label.anchors.horizontalCenterOffset { Anim { - type: Anim.FastEffects + type: Anim.DefaultEffects } } - - StateLayer { - id: downState - - color: DynamicColors.palette.m3onPrimary - - onClicked: { - let newValue = Math.max(root.min, root.value - root.step); - // Round to avoid floating point precision errors - const decimals = root.step < 1 ? Math.max(1, Math.ceil(-Math.log10(root.step))) : 0; - newValue = Math.round(newValue * Math.pow(10, decimals)) / Math.pow(10, decimals); - root.value = newValue; - root.displayText = newValue.toString(); - root.valueModified(newValue); + Behavior on topLeftRadius { + Anim { + type: Anim.DefaultEffects } - onPressAndHold: timer.start() - onReleased: timer.stop() - } - - MaterialIcon { - id: downIcon - - anchors.centerIn: parent - color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) - text: "keyboard_arrow_down" } } Timer { id: timer - interval: 100 + interval: root.repeatRate repeat: true + running: upButton.pressed || downButton.pressed triggeredOnStart: true + onRunningChanged: { + if (!running) + interval = root.repeatRate; + } onTriggered: { - if (upState.pressed) - upState.onClicked(); - else if (downState.pressed) - downState.onClicked(); + if (upButton.pressed) + root.increase(); + else if (downButton.pressed) + root.decrease(); + if (interval > root.repeatDecay) + interval -= root.repeatDecay; } } } diff --git a/Components/CustomTextField.qml b/Components/CustomTextField.qml index ae7ee94..999557d 100644 --- a/Components/CustomTextField.qml +++ b/Components/CustomTextField.qml @@ -1,77 +1,295 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Controls +import QtQuick.Shapes import qs.Config -TextField { +TextFieldBase { id: root - property int cursorHeight: root.height + enum TextFieldType { + Outlined, + Filled + } - background: null - color: DynamicColors.palette.m3onSurface - cursorVisible: !readOnly - font.family: Appearance.font.family.sans - font.pointSize: Appearance.font.size.smaller - placeholderTextColor: DynamicColors.palette.m3outline - renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering + readonly property int clampedRadius: Math.min(horizontalPadding, Math.min(width, height) / 2, radius) + readonly property string effectiveSupportingText: isError && errorText ? errorText : supportingText + property bool emptyIsValid: true + property string errorText + readonly property int filledOffset: type === CustomTextField.Filled ? Appearance.spacing.small : 0 + readonly property int horizontalPadding: Appearance.padding.large + property bool isError + property string leadingIcon + readonly property int leadingOffset: leadingIcon ? leadingIconLoader.width + leadingIconLoader.anchors.leftMargin : 0 + readonly property real outlineGap: placeholder.width * root.smallFontScale + Appearance.spacing.extraSmall * 2 + property real outlineGapScale: activeFocus || text ? 1 : 0 + property int radius: Appearance.rounding.small + readonly property real smallFontScale: smallFontSize / font.pointSize + property int smallFontSize: Appearance.font.size.small + property string supportingText + readonly property int supportingTextOffset: effectiveSupportingText ? supportingTextLoader.height + Appearance.spacing.extraSmall : 0 + property string trailingIcon + readonly property int trailingOffset: trailingIcon ? trailingIconLoader.width + trailingIconLoader.anchors.rightMargin : 0 + property int type: CustomTextField.Outlined + readonly property bool valid: !validate || (!text && emptyIsValid) || (validate instanceof RegExp ? validate.test(text) : !!validate(text)) + property var validate // Regex or function - Behavior on color { - CAnim { + bottomPadding: Appearance.padding.large + supportingTextOffset - filledOffset + leftPadding: horizontalPadding + leadingOffset + rightPadding: horizontalPadding + trailingOffset + topPadding: Appearance.padding.large + filledOffset + + background: Loader { + anchors.bottomMargin: root.supportingTextOffset + anchors.fill: parent + sourceComponent: root.type === CustomTextField.Filled ? filledComp : outlineComp + + StateLayer { + id: stateLayer + + cursorShape: Qt.IBeamCursor + enabled: !root.activeFocus + manualPressOverride: tapHandler.pressed + radius: root.type === CustomTextField.Outlined ? root.clampedRadius : 0 + topLeftRadius: root.clampedRadius + topRightRadius: root.clampedRadius + + onClicked: root.focus = true } } - cursorDelegate: CustomRect { - id: cursor - - property bool disableBlink - - color: DynamicColors.palette.m3primary - implicitWidth: 2 - radius: Appearance.rounding.normal - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.small - } + Behavior on outlineGapScale { + Anim { + type: Anim.DefaultEffects } + } - Connections { - function onCursorPositionChanged(): void { - if (root.activeFocus && root.cursorVisible) { - cursor.opacity = 1; - cursor.disableBlink = true; - enableBlink.restart(); + onEditingFinished: { + if (!valid) + isError = true; + } + onPressed: { + if (stateLayer.enabled) + stateLayer.press(stateLayer.mouseX, stateLayer.mouseY); + } + onTextEdited: { + if (isError) + isError = false; + } + + Item { + id: contentWrapper + + anchors.bottomMargin: root.supportingTextOffset + anchors.fill: parent + + CustomText { + id: placeholder + + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + anchors.topMargin: Appearance.padding.extraSmall + anchors.verticalCenter: parent.verticalCenter + color: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : root.text ? DynamicColors.palette.m3outline : root.placeholderTextColor) + font.family: root.font.family + font.pointSize: root.font.pointSize + font.variableAxes: root.font.variableAxes + font.weight: root.font.weight + renderType: Text.QtRendering + text: root.placeholderText + + states: [ + State { + name: "smallOutlined" + when: root.type === CustomTextField.Outlined && (root.activeFocus || root.text) + + PropertyChanges { + placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + -Appearance.spacing.extraSmall + placeholder.scale: root.smallFontScale + } + + AnchorChanges { + anchors.verticalCenter: contentWrapper.top + target: placeholder + } + }, + State { + name: "smallFilled" + when: root.type === CustomTextField.Filled && (root.activeFocus || root.text) + + PropertyChanges { + placeholder.anchors.leftMargin: -(1 - root.smallFontScale) * placeholder.width / 2 + root.horizontalPadding + root.leadingOffset + placeholder.scale: root.smallFontScale + } + + AnchorChanges { + anchors.top: contentWrapper.top + anchors.verticalCenter: undefined + target: placeholder + } + } + ] + transitions: Transition { + Anim { + properties: "scale,leftMargin" + type: Anim.DefaultEffects + } + + AnchorAnim { + duration: Appearance.anim.durations.expressiveEffects + easing: Appearance.anim.curves.expressiveDefaultEffects } } - - target: root } - Timer { - id: enableBlink + Loader { + id: leadingIconLoader - interval: 100 + active: root.leadingIcon + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.larger + anchors.verticalCenter: parent.verticalCenter - onTriggered: cursor.disableBlink = false + sourceComponent: MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + text: root.leadingIcon + } } - Timer { - interval: 500 - repeat: true - running: root.activeFocus && root.cursorVisible && !cursor.disableBlink - triggeredOnStart: true + Loader { + id: trailingIconLoader - onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1 - } + active: root.trailingIcon + anchors.right: parent.right + anchors.rightMargin: Appearance.padding.larger + anchors.verticalCenter: parent.verticalCenter - Binding { - cursor.opacity: 0 - when: !root.activeFocus || !root.cursorVisible + sourceComponent: MaterialIcon { + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + text: root.trailingIcon + } } } - Behavior on placeholderTextColor { - CAnim { + + Loader { + id: supportingTextLoader + + active: root.effectiveSupportingText + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: root.horizontalPadding + + sourceComponent: CustomText { + color: root.isError ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.small + text: root.effectiveSupportingText + } + } + + TapHandler { + id: tapHandler + } + + Component { + id: outlineComp + + Shape { + id: bg + + asynchronous: true + preferredRendererType: Shape.CurveRenderer + + ShapePath { + capStyle: ShapePath.RoundCap + fillColor: "transparent" + startX: root.horizontalPadding - root.clampedRadius + root.outlineGap * (1 - root.outlineGapScale) / 2 + root.outlineGap * root.outlineGapScale + strokeColor: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline) + strokeWidth: root.activeFocus ? 2 : 1 + + Behavior on strokeColor { + CAnim { + } + } + Behavior on strokeWidth { + Anim { + } + } + + PathLine { + x: bg.width - root.clampedRadius + } + + PathArc { + radiusX: root.clampedRadius + radiusY: root.clampedRadius + x: bg.width + y: root.clampedRadius + } + + PathLine { + x: bg.width + y: bg.height - root.clampedRadius + } + + PathArc { + radiusX: root.clampedRadius + radiusY: root.clampedRadius + x: bg.width - root.clampedRadius + y: bg.height + } + + PathLine { + x: root.clampedRadius + y: bg.height + } + + PathArc { + radiusX: root.clampedRadius + radiusY: root.clampedRadius + x: 0 + y: bg.height - root.clampedRadius + } + + PathLine { + x: 0 + y: root.clampedRadius + } + + PathArc { + radiusX: root.clampedRadius + radiusY: root.clampedRadius + x: root.clampedRadius + y: 0 + } + + PathLine { + x: root.horizontalPadding - root.clampedRadius + root.outlineGap * (1 - root.outlineGapScale) / 2 + } + } + } + } + + Component { + id: filledComp + + CustomRect { + color: root.activeFocus ? DynamicColors.tPalette.m3surfaceContainerHighest : DynamicColors.tPalette.m3surfaceContainerHigh + topLeftRadius: root.clampedRadius + topRightRadius: root.clampedRadius + + CustomRect { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: root.isError ? DynamicColors.palette.m3error : (root.activeFocus ? DynamicColors.palette.m3primary : DynamicColors.palette.m3outline) + implicitHeight: root.activeFocus ? 2 : 1 + + Behavior on implicitHeight { + Anim { + } + } + } } } } diff --git a/Components/Menu.qml b/Components/Menu.qml index bf19a84..2d24a0d 100644 --- a/Components/Menu.qml +++ b/Components/Menu.qml @@ -30,8 +30,9 @@ MouseArea { signal itemSelected(item: MenuItem) anchors.fill: parent - cursorShape: undefined + cursorShape: expanded ? Qt.ArrowCursor : undefined enabled: expanded + hoverEnabled: expanded layer.enabled: opacity < 1 opacity: expanded ? 1 : 0 parent: { diff --git a/Components/SearchBar.qml b/Components/SearchBar.qml new file mode 100644 index 0000000..c8727fa --- /dev/null +++ b/Components/SearchBar.qml @@ -0,0 +1,94 @@ +import QtQuick +import qs.Config + +TextFieldBase { + id: root + + readonly property alias bg: bg + readonly property alias clearIcon: clearIcon + readonly property alias searchIcon: searchIcon + + bottomPadding: Appearance.padding.large + leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + Appearance.spacing.small + rightPadding: clearIcon.width + clearIcon.anchors.rightMargin + Appearance.spacing.small + topPadding: Appearance.padding.large + + background: CustomRect { + id: bg + + anchors.fill: parent + color: DynamicColors.tPalette.m3surfaceContainer + radius: Appearance.rounding.full + + StateLayer { + id: stateLayer + + cursorShape: Qt.IBeamCursor + enabled: !root.activeFocus + manualPressOverride: tapHandler.pressed + + onClicked: root.focus = true + } + } + + onPressed: { + if (stateLayer.enabled) + stateLayer.press(stateLayer.mouseX, stateLayer.mouseY); + } + + CustomText { + id: placeholder + + anchors.left: parent.left + anchors.leftMargin: root.leftPadding + anchors.verticalCenter: parent.verticalCenter + color: root.placeholderTextColor + font: root.font + opacity: root.text ? 0 : 1 + text: root.placeholderText + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + } + + MaterialIcon { + id: searchIcon + + anchors.left: parent.left + anchors.leftMargin: Appearance.padding.large + anchors.verticalCenter: parent.verticalCenter + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.large + text: "search" + } + + IconButton { + id: clearIcon + + anchors.right: parent.right + anchors.rightMargin: Appearance.padding.larger + anchors.verticalCenter: parent.verticalCenter + enabled: root.text + icon: "clear" + opacity: root.text ? 1 : 0 + radius: Appearance.rounding.full + radiusMorph: false + stateLayer.hoverEnabled: enabled + type: IconButton.Text + + Behavior on opacity { + Anim { + type: Anim.DefaultEffects + } + } + + onClicked: root.clear() + } + + TapHandler { + id: tapHandler + } +} diff --git a/Components/TextFieldBase.qml b/Components/TextFieldBase.qml new file mode 100644 index 0000000..8854c42 --- /dev/null +++ b/Components/TextFieldBase.qml @@ -0,0 +1,88 @@ +import QtQuick +import QtQuick.Templates +import qs.Config + +TextField { + id: root + + color: DynamicColors.palette.m3onSurface + cursorVisible: !readOnly + font.pointSize: Appearance.font.size.small + implicitHeight: contentHeight + topPadding + bottomPadding + implicitWidth: contentWidth + leftPadding + rightPadding + placeholderTextColor: DynamicColors.palette.m3onSurfaceVariant // No anim cause placeholder is custom + renderType: echoMode === TextField.Password ? TextField.QtRendering : TextField.NativeRendering + selectedTextColor: color + selectionColor: Qt.alpha(DynamicColors.palette.m3primary, 0.4) + verticalAlignment: TextInput.AlignVCenter + + Behavior on color { + CAnim { + } + } + cursorDelegate: Item { + } + Behavior on selectionColor { + CAnim { + } + } + + CustomRect { + id: cursor + + property bool disableBlink + + color: DynamicColors.palette.m3primary + implicitHeight: root.cursorRectangle.height + implicitWidth: 1.5 + radius: Appearance.rounding.large + x: root.cursorRectangle.x + y: root.cursorRectangle.y + + Behavior on opacity { + Anim { + type: Anim.StandardSmall + } + } + Behavior on x { + Anim { + duration: Appearance.anim.durations.expressiveFastEffects + easing.bezierCurve: [0.2, 1, 0.21, 1, 1, 1] + } + } + + Connections { + function onCursorPositionChanged(): void { + if (root.activeFocus && root.cursorVisible) { + cursor.opacity = 1; + cursor.disableBlink = true; + enableBlink.restart(); + } + } + + target: root + } + + Timer { + id: enableBlink + + interval: 500 + + onTriggered: cursor.disableBlink = false + } + + Timer { + interval: 500 + repeat: true + running: root.activeFocus && root.cursorVisible && !cursor.disableBlink + triggeredOnStart: true + + onTriggered: parent.opacity = parent.opacity === 1 ? 0 : 1 + } + + Binding { + cursor.opacity: 0 + when: !root.activeFocus || !root.cursorVisible + } + } +} diff --git a/Modules/Launcher/AppList.qml b/Modules/Launcher/AppList.qml index ea2e854..fc98b53 100644 --- a/Modules/Launcher/AppList.qml +++ b/Modules/Launcher/AppList.qml @@ -10,7 +10,7 @@ import qs.Config CustomListView { id: root - required property CustomTextField search + required property SearchBar search required property PersistentProperties visibilities highlightFollowsCurrentItem: false diff --git a/Modules/Launcher/Content.qml b/Modules/Launcher/Content.qml index 3837ae4..0bc84c4 100644 --- a/Modules/Launcher/Content.qml +++ b/Modules/Launcher/Content.qml @@ -16,13 +16,13 @@ Item { readonly property int rounding: Appearance.rounding.large required property PersistentProperties visibilities - implicitHeight: searchWrapper.height + listWrapper.height + padding * 2 + implicitHeight: search.height + listWrapper.height + padding * 2 implicitWidth: listWrapper.width + padding * 2 Item { id: listWrapper - anchors.bottom: searchWrapper.top + anchors.bottom: search.top anchors.bottomMargin: root.padding anchors.horizontalCenter: parent.horizontalCenter implicitHeight: list.height + root.padding @@ -32,7 +32,7 @@ Item { id: list content: root - maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3 + maxHeight: root.maxHeight - search.implicitHeight - root.padding * 3 padding: root.padding panels: root.panels rounding: root.rounding @@ -41,131 +41,69 @@ Item { } } - CustomRect { - id: searchWrapper + SearchBar { + id: search anchors.bottom: parent.bottom anchors.left: parent.left anchors.margins: root.padding anchors.right: parent.right - color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2) - implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight) - radius: Appearance.rounding.smallest + bottomPadding: Appearance.padding.larger + placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix) + topPadding: Appearance.padding.larger - MaterialIcon { - id: searchIcon - - anchors.left: parent.left - anchors.leftMargin: root.padding + 10 - anchors.verticalCenter: parent.verticalCenter - color: DynamicColors.palette.m3onSurfaceVariant - text: "search" + Component.onCompleted: { + console.log(search.color); + console.log(search.placeholderTextColor); + forceActiveFocus(); } - - CustomTextField { - id: search - - anchors.left: searchIcon.right - anchors.leftMargin: Appearance.spacing.small - anchors.right: clearIcon.left - anchors.rightMargin: Appearance.spacing.small - bottomPadding: Appearance.padding.larger - placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix) - topPadding: Appearance.padding.larger - - Component.onCompleted: forceActiveFocus() - Keys.onDownPressed: list.currentList?.decrementCurrentIndex() - Keys.onEscapePressed: root.visibilities.launcher = false - Keys.onPressed: event => { - if (!Config.launcher.vimKeybinds) - return; - - if (event.modifiers & Qt.ControlModifier) { - if (event.key === Qt.Key_J) { - list.currentList?.incrementCurrentIndex(); - event.accepted = true; - } else if (event.key === Qt.Key_K) { - list.currentList?.decrementCurrentIndex(); - event.accepted = true; - } - } else if (event.key === Qt.Key_Tab) { + Keys.onDownPressed: list.currentList?.decrementCurrentIndex() + Keys.onEscapePressed: root.visibilities.launcher = false + Keys.onPressed: event => { + if (event.modifiers & Qt.ControlModifier) { + if (event.key === Qt.Key_J) { list.currentList?.incrementCurrentIndex(); event.accepted = true; - } else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { + } else if (event.key === Qt.Key_K) { list.currentList?.decrementCurrentIndex(); event.accepted = true; } + } else if (event.key === Qt.Key_Tab) { + list.currentList?.incrementCurrentIndex(); + event.accepted = true; + } else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { + list.currentList?.decrementCurrentIndex(); + event.accepted = true; } - Keys.onUpPressed: list.currentList?.incrementCurrentIndex() - onAccepted: { - const currentItem = list.currentList?.currentItem; - if (currentItem) { - if (list.showWallpapers) { - if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent) - Wallpapers.previewColourLock = true; - Wallpapers.setWallpaper(currentItem.modelData.path); - root.visibilities.launcher = false; - } else if (text.startsWith(Config.launcher.actionPrefix)) { - if (text.startsWith(`${Config.launcher.actionPrefix}calc `)) - currentItem.onClicked(); - else - currentItem.modelData.onClicked(list.currentList); - } else { - Apps.launch(currentItem.modelData); - root.visibilities.launcher = false; - } + } + Keys.onUpPressed: list.currentList?.incrementCurrentIndex() + onAccepted: { + const currentItem = list.currentList?.currentItem; + if (currentItem) { + if (list.showWallpapers) { + if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent) + Wallpapers.previewColourLock = true; + Wallpapers.setWallpaper(currentItem.modelData.path); + root.visibilities.launcher = false; + } else if (text.startsWith(Config.launcher.actionPrefix)) { + if (text.startsWith(`${Config.launcher.actionPrefix}calc `)) + currentItem.onClicked(); + else + currentItem.modelData.onClicked(list.currentList); + } else { + Apps.launch(currentItem.modelData); + root.visibilities.launcher = false; } } - - Connections { - function onLauncherChanged(): void { - if (!root.visibilities.launcher) - search.text = ""; - } - - target: root.visibilities - } } - MaterialIcon { - id: clearIcon - - anchors.right: parent.right - anchors.rightMargin: root.padding + 10 - anchors.verticalCenter: parent.verticalCenter - color: DynamicColors.palette.m3onSurfaceVariant - opacity: { - if (!search.text) - return 0; - if (mouse.pressed) - return 0.7; - if (mouse.containsMouse) - return 0.8; - return 1; - } - text: "close" - width: search.text ? implicitWidth : implicitWidth / 2 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.small - } - } - Behavior on width { - Anim { - duration: Appearance.anim.durations.small - } + Connections { + function onLauncherChanged(): void { + if (!root.visibilities.launcher) + search.text = ""; } - MouseArea { - id: mouse - - anchors.fill: parent - cursorShape: search.text ? Qt.PointingHandCursor : undefined - hoverEnabled: true - - onClicked: search.text = "" - } + target: root.visibilities } } } diff --git a/Modules/Launcher/ContentList.qml b/Modules/Launcher/ContentList.qml index 40fec4c..6b62f2e 100644 --- a/Modules/Launcher/ContentList.qml +++ b/Modules/Launcher/ContentList.qml @@ -17,7 +17,7 @@ Item { required property int padding required property var panels required property int rounding - required property CustomTextField search + required property SearchBar search readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `) required property PersistentProperties visibilities diff --git a/Modules/Launcher/WallpaperList.qml b/Modules/Launcher/WallpaperList.qml index e1afd76..735191f 100644 --- a/Modules/Launcher/WallpaperList.qml +++ b/Modules/Launcher/WallpaperList.qml @@ -37,7 +37,7 @@ PathView { return visible; } required property var panels - required property CustomTextField search + required property SearchBar search required property var visibilities cacheItemCount: 4 diff --git a/Modules/SettingsNew/Common/HyprTimeInput.qml b/Modules/SettingsNew/Common/HyprTimeInput.qml deleted file mode 100644 index b170d78..0000000 --- a/Modules/SettingsNew/Common/HyprTimeInput.qml +++ /dev/null @@ -1,659 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Config -import qs.Components -import qs.Helpers - -Item { - id: root - - readonly property string endTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[2]]); - return Qt.formatTime(d, "hh:mm AP"); - } - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - readonly property string startTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[1]]); - return Qt.formatTime(d, "hh:mm AP"); - } - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - Hyprsunset.checkStartup(); - } - - function convertHour(timeValue: int): int { - return Math.floor(timeValue / 60); - } - - function convertMinute(timeValue: int): int { - return timeValue % 60; - } - - function convertToMinutes(hour: int, minute: int): int { - return hour * 60 + minute; - } - - Layout.fillWidth: true - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal) - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime) - wrapMode: Text.WordWrap - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.fillWidth: true - - RowLayout { - CustomText { - Layout.preferredWidth: spacer.x + spacer.width - text: qsTr("Start") - } - - CustomText { - Layout.preferredWidth: endMinuteRect.width + endHourRect.width - text: qsTr("End") - } - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[0]] - - onToggled: { - root.object[root.settings[0]] = checked; - Config.save(); - } - } - } - - RowLayout { - Layout.fillHeight: true - - CustomRect { - id: startHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: startMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.spacing.large - } - - CustomRect { - id: endHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: endMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - id: endMinuteField - - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]); - } - onTextEdited: { - if (endMinuteField.text === "") - 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; - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - id: startHour - - Layout.preferredWidth: startSeparator.x + startSeparator.width - text: qsTr("Hour") - } - - CustomText { - id: startMinute - - Layout.preferredWidth: spacer.x + spacer.width - x - text: qsTr("Minute") - } - - CustomText { - Layout.preferredWidth: endSeparator.x + endSeparator.width - x - text: qsTr("Hour") - } - - CustomText { - text: qsTr("Minute") - } - } - } - } -} diff --git a/Modules/SettingsNew/Common/PageBase.qml b/Modules/SettingsNew/Common/PageBase.qml index be5ec06..f3198c5 100644 --- a/Modules/SettingsNew/Common/PageBase.qml +++ b/Modules/SettingsNew/Common/PageBase.qml @@ -25,6 +25,8 @@ ColumnLayout { implicitWidth: header.implicitWidth z: 1 + onClicked: focus = true + RowLayout { id: header @@ -66,5 +68,9 @@ ColumnLayout { contentItem.children: [root.contentChild] fadeAmount: 0.1 topMargin: Appearance.padding.large + + TapHandler { + onTapped: flickable.focus = true + } } } diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/SettingsNew/Common/PopupRow.qml index 3a49180..e9dd53e 100644 --- a/Modules/SettingsNew/Common/PopupRow.qml +++ b/Modules/SettingsNew/Common/PopupRow.qml @@ -85,7 +85,7 @@ ConnectedRect { anchors.fill: parent cursorShape: popup.open ? Qt.ArrowCursor : undefined enabled: popup.open - hoverEnabled: true + hoverEnabled: popup.open parent: { if (root.keepPopupAsChild) return triggerArea; @@ -94,8 +94,6 @@ ConnectedRect { const contentWin = win as Windows; // If inside the drawer content window, put it inside the interaction wrapper so hover works return contentWin ? contentWin.interactionWrapper : (win as QsWindow).contentItem; } - preventStealing: true - propagateComposedEvents: false z: popup.animDriver > 0 ? 1 : 0 onClicked: popup.open = false diff --git a/Modules/SettingsNew/Common/SpinRow.qml b/Modules/SettingsNew/Common/SpinRow.qml index 91b831d..d104a3f 100644 --- a/Modules/SettingsNew/Common/SpinRow.qml +++ b/Modules/SettingsNew/Common/SpinRow.qml @@ -52,12 +52,12 @@ ConnectedRect { } CustomSpinBox { - max: root.to - min: root.from - step: root.stepSize + from: root.from + stepSize: root.stepSize + to: root.to value: root.value - onValueModified: v => root.moved(v) + onValueModified: root.moved(value) } } } diff --git a/Modules/SettingsNew/Common/TimeInput.qml b/Modules/SettingsNew/Common/TimeInput.qml index 0b88fb1..b806721 100644 --- a/Modules/SettingsNew/Common/TimeInput.qml +++ b/Modules/SettingsNew/Common/TimeInput.qml @@ -68,7 +68,7 @@ CustomClippingRect { } } - CustomTextField { + TextFieldBase { id: startHourField function setConfigText(setting: string): string { @@ -84,7 +84,6 @@ CustomClippingRect { anchors.verticalCenter: parent.verticalCenter clip: true color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 font.family: "Roboto" font.letterSpacing: -0.25 font.pixelSize: 56 @@ -188,7 +187,7 @@ CustomClippingRect { } } - CustomTextField { + TextFieldBase { id: startMinuteField function setConfigText(setting: string): string { @@ -204,7 +203,6 @@ CustomClippingRect { anchors.verticalCenter: parent.verticalCenter clip: true color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 font.family: "Roboto" font.letterSpacing: -0.25 font.pixelSize: 56 @@ -307,7 +305,7 @@ CustomClippingRect { } } - CustomTextField { + TextFieldBase { id: endHourField function setConfigText(setting: string): string { @@ -323,7 +321,6 @@ CustomClippingRect { anchors.verticalCenter: parent.verticalCenter clip: true color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 font.family: "Roboto" font.letterSpacing: -0.25 font.pixelSize: 56 @@ -427,7 +424,7 @@ CustomClippingRect { } } - CustomTextField { + TextFieldBase { id: endMinuteField function setConfigText(setting: string): string { @@ -443,7 +440,6 @@ CustomClippingRect { anchors.verticalCenter: parent.verticalCenter clip: true color: focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 font.family: "Roboto" font.letterSpacing: -0.25 font.pixelSize: 56 diff --git a/Modules/SettingsNew/Content.qml b/Modules/SettingsNew/Content.qml index 026c2f6..6230d21 100644 --- a/Modules/SettingsNew/Content.qml +++ b/Modules/SettingsNew/Content.qml @@ -27,6 +27,10 @@ CustomClippingRect { } } + TapHandler { + onTapped: root.focus = true + } + BlobGroup { id: blobGroup diff --git a/Modules/SettingsNew/NavPane.qml b/Modules/SettingsNew/NavPane.qml index a2ef504..7360ed6 100644 --- a/Modules/SettingsNew/NavPane.qml +++ b/Modules/SettingsNew/NavPane.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Layouts +import qs.Components import qs.Modules.SettingsNew.NavPane import qs.Config @@ -11,8 +12,28 @@ ColumnLayout { spacing: Appearance.spacing.large SearchBar { + id: searchField + Layout.fillWidth: true - sState: root.sState + bg.border.color: DynamicColors.palette.m3outlineVariant + bg.color: DynamicColors.tPalette.m3surfaceContainerLowest + clearIcon.font.pointSize: Appearance.font.size.large + clearIcon.padding: Appearance.padding.extraSmall + font.pointSize: Appearance.font.size.normal + placeholderText: qsTr("Search settings") + searchIcon.anchors.leftMargin: Appearance.padding.largeIncreased + searchIcon.font.pointSize: Appearance.font.size.large + + Behavior on bg.border.color { + CAnim { + } + } + + Binding { + property: "searchOpen" + target: root.sState + value: searchField.text.length > 0 + } } NavLocations { diff --git a/Modules/SettingsNew/NavPane/NavLocations.qml b/Modules/SettingsNew/NavPane/NavLocations.qml index 6d5278a..a5f1706 100644 --- a/Modules/SettingsNew/NavPane/NavLocations.qml +++ b/Modules/SettingsNew/NavPane/NavLocations.qml @@ -16,6 +16,10 @@ VerticalFadeFlickable { fadeAmount: 0.1 topMargin: Appearance.padding.large + TapHandler { + onTapped: root.focus = true + } + ColumnLayout { id: content diff --git a/Modules/SettingsNew/NavPane/SearchBar.qml b/Modules/SettingsNew/NavPane/SearchBar.qml deleted file mode 100644 index 0dd0844..0000000 --- a/Modules/SettingsNew/NavPane/SearchBar.qml +++ /dev/null @@ -1,75 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Modules.SettingsNew -import qs.Components -import qs.Config - -CustomRect { - id: root - - required property SettingsState sState - - border.color: DynamicColors.palette.m3outlineVariant - color: DynamicColors.tPalette.m3surfaceContainerLowest - implicitHeight: searchLayout.implicitHeight + Appearance.padding.normal * 2 - radius: Appearance.rounding.full - - Behavior on border.color { - CAnim { - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.IBeamCursor - - onClicked: searchField.focus = true - } - - RowLayout { - id: searchLayout - - anchors.left: parent.left - anchors.margins: Appearance.padding.large - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.small - - MaterialIcon { - color: DynamicColors.palette.m3onSurfaceVariant - text: "search" - } - - CustomTextField { - id: searchField - - Layout.fillHeight: true - Layout.fillWidth: true - color: DynamicColors.palette.m3onSurfaceVariant - placeholderText: qsTr("Search settings") - placeholderTextColor: DynamicColors.palette.m3onSurfaceVariant - - Binding { - property: "searchOpen" - target: root.sState - value: searchField.text.length > 0 - } - } - - IconButton { - icon: "close" - isRound: true - opacity: searchField.text.length > 0 ? 1 : 0 - padding: Appearance.padding.extraSmall - type: IconButton.Text - - Behavior on opacity { - Anim { - type: Anim.DefaultEffects - } - } - - onClicked: searchField.clear() - } - } -} diff --git a/Modules/SettingsNew/Pages/AboutPage.qml b/Modules/SettingsNew/Pages/AboutPage.qml index cec1e3d..9d55c24 100644 --- a/Modules/SettingsNew/Pages/AboutPage.qml +++ b/Modules/SettingsNew/Pages/AboutPage.qml @@ -54,7 +54,7 @@ PageBase { CustomText { Layout.alignment: Qt.AlignHCenter color: DynamicColors.palette.m3onSurfaceVariant - font: Appearance.font.body.medium + font.pointSize: Appearance.font.size.medium text: ZUtils.version ? `v${ZUtils.version}` : "…" } } -- 2.47.3 From deaeac4dbdcdf64c8f15c37ca08199edd0a764ba Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:31:23 +0200 Subject: [PATCH 16/17] section headers for screenshot page + rename --- Modules/SettingsNew/PageCompRegistry.qml | 4 ++-- .../{Screenshot.qml => ScreenshotPage.qml} | 20 +++++++++++++------ .../{Wallpaper.qml => WallpaperPage.qml} | 0 3 files changed, 16 insertions(+), 8 deletions(-) rename Modules/SettingsNew/Pages/{Screenshot.qml => ScreenshotPage.qml} (88%) rename Modules/SettingsNew/Pages/{Wallpaper.qml => WallpaperPage.qml} (100%) diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/SettingsNew/PageCompRegistry.qml index a74e153..c91229d 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/SettingsNew/PageCompRegistry.qml @@ -22,7 +22,7 @@ QtObject { // Wallpaper & style StackPage { Component { - Wallpaper { + WallpaperPage { } } @@ -36,7 +36,7 @@ QtObject { // Screenshot StackPage { Component { - Screenshot { + ScreenshotPage { } } } diff --git a/Modules/SettingsNew/Pages/Screenshot.qml b/Modules/SettingsNew/Pages/ScreenshotPage.qml similarity index 88% rename from Modules/SettingsNew/Pages/Screenshot.qml rename to Modules/SettingsNew/Pages/ScreenshotPage.qml index cbc9f42..74aa368 100644 --- a/Modules/SettingsNew/Pages/Screenshot.qml +++ b/Modules/SettingsNew/Pages/ScreenshotPage.qml @@ -17,9 +17,14 @@ PageBase { ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top - spacing: Appearance.spacing.large + spacing: Appearance.spacing.extraSmall / 2 width: root.cappedWidth + SectionHeader { + first: true + text: qsTr("Effects") + } + ToggleRow { checked: Config.screenshot.enable_pp first: true @@ -29,7 +34,6 @@ PageBase { } SelectRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] enabled: Config.screenshot.enable_pp last: true @@ -55,6 +59,10 @@ PageBase { } } + SectionHeader { + text: qsTr("Rounded corners") + } + ToggleRow { checked: Config.screenshot.rounding enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" @@ -65,7 +73,6 @@ PageBase { } SpinRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.rounding from: 0 last: true @@ -80,6 +87,10 @@ PageBase { } } + SectionHeader { + text: qsTr("Shadow") + } + ToggleRow { checked: Config.screenshot.shadow enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" @@ -90,7 +101,6 @@ PageBase { } SpinRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow from: 0 stepSize: 1 @@ -105,7 +115,6 @@ PageBase { } SpinRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow from: -100 stepSize: 10 @@ -120,7 +129,6 @@ PageBase { } SpinRow { - Layout.topMargin: Appearance.spacing.extraSmall / 2 - parent.spacing enabled: Config.screenshot.enable_pp && Config.screenshot.mode === "manual" && Config.screenshot.shadow from: -100 last: true diff --git a/Modules/SettingsNew/Pages/Wallpaper.qml b/Modules/SettingsNew/Pages/WallpaperPage.qml similarity index 100% rename from Modules/SettingsNew/Pages/Wallpaper.qml rename to Modules/SettingsNew/Pages/WallpaperPage.qml -- 2.47.3 From 38b9362515514ca89f68528bc706d971ebd4289d Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:43:34 +0200 Subject: [PATCH 17/17] remove old Settings, rename new --- Drawers/Panels.qml | 2 +- Modules/Settings/Categories.qml | 370 ---------- Modules/Settings/Categories/Appearance.qml | 162 ----- Modules/Settings/Categories/Background.qml | 48 -- Modules/Settings/Categories/Bar.qml | 217 ------ Modules/Settings/Categories/Dashboard.qml | 218 ------ Modules/Settings/Categories/General.qml | 308 -------- Modules/Settings/Categories/Launcher.qml | 165 ----- Modules/Settings/Categories/Lockscreen.qml | 125 ---- .../Settings/Categories/Lockscreen/Idle.qml | 115 --- Modules/Settings/Categories/Notifications.qml | 116 --- Modules/Settings/Categories/Osd.qml | 81 --- Modules/Settings/Categories/Screenshot.qml | 150 ---- Modules/Settings/Categories/Services.qml | 145 ---- Modules/Settings/Categories/Sidebar.qml | 28 - Modules/Settings/Categories/SystemUpdates.qml | 197 ------ Modules/Settings/Categories/Utilities.qml | 245 ------- .../Common/AudioDeviceList.qml | 0 .../Common/BlobPopup.qml | 0 .../Common/ConnectedRect.qml | 0 .../Common/InfoRow.qml | 0 .../Common/ItemList.qml | 0 .../Common/NavRow.qml | 0 .../Common/OverlayRow.qml | 2 +- .../Common/PageBase.qml | 2 +- .../Common/PopupRow.qml | 0 .../Common/SectionHeader.qml | 0 .../Common/SelectRow.qml | 0 .../Common/SliderRow.qml | 0 .../Common/SpinRow.qml | 0 .../Common/StackPage.qml | 2 +- .../Common/TimeInput.qml | 0 .../Common/ToggleRow.qml | 0 .../Common/WallItem.qml | 0 .../Common/WallpaperCropper.qml | 0 Modules/Settings/Content.qml | 416 ++--------- Modules/Settings/Controls/HyprTimeInput.qml | 660 ----------------- Modules/Settings/Controls/SchemesListView.qml | 161 ----- Modules/Settings/Controls/Separator.qml | 31 - .../Settings/Controls/SettingActionList.qml | 282 -------- .../Settings/Controls/SettingAliasList.qml | 203 ------ .../Settings/Controls/SettingBarEntryList.qml | 241 ------- .../Settings/Controls/SettingHyprSpinner.qml | 214 ------ Modules/Settings/Controls/SettingInput.qml | 99 --- Modules/Settings/Controls/SettingList.qml | 297 -------- Modules/Settings/Controls/SettingListView.qml | 163 ----- Modules/Settings/Controls/SettingReadOnly.qml | 70 -- Modules/Settings/Controls/SettingSpinBox.qml | 81 --- Modules/Settings/Controls/SettingSpinner.qml | 180 ----- .../Settings/Controls/SettingStringList.qml | 78 --- Modules/Settings/Controls/SettingSwitch.qml | 79 --- Modules/Settings/Controls/SettingsHeader.qml | 25 - .../Settings/Controls/SettingsIconButton.qml | 83 --- Modules/Settings/Controls/SettingsPage.qml | 76 -- Modules/Settings/Controls/SettingsSection.qml | 48 -- Modules/Settings/Controls/SpinnerButton.qml | 63 -- .../Settings/Controls/StringListEditor.qml | 126 ---- Modules/Settings/Controls/TimeInput.qml | 661 ------------------ Modules/Settings/Controls/VSeparator.qml | 12 - .../Settings/Controls/WallpaperCropper.qml | 314 --------- Modules/Settings/Controls/WallpaperGrid.qml | 158 ----- Modules/{SettingsNew => Settings}/NavPane.qml | 2 +- .../NavPane/NavLocations.qml | 2 +- .../PageCompRegistry.qml | 16 +- .../PageRegistry.qml | 0 Modules/{SettingsNew => Settings}/Pages.qml | 0 .../Pages/AboutPage.qml | 2 +- .../Pages/Apps/AllApps.qml | 2 +- .../Pages/Apps/AppInfo.qml | 2 +- .../Pages/AppsPage.qml | 2 +- .../Pages/Audio/AppVolumes.qml | 2 +- .../Pages/AudioPage.qml | 2 +- .../Pages/Panels/Bar/BarClock.qml | 2 +- .../Pages/Panels/Bar/BarStatusIcons.qml | 2 +- .../Pages/Panels/Bar/BarTray.qml | 2 +- .../Pages/Panels/BarPanel.qml | 2 +- .../Pages/Panels/DashboardPanel.qml | 2 +- .../Pages/Panels/LauncherPanel.qml | 2 +- .../Pages/Panels/ResourcesPanel.qml | 2 +- .../Pages/Panels/SidebarPanel.qml | 2 +- .../Pages/PanelsPage.qml | 2 +- .../Pages/ScreenshotPage.qml | 4 +- .../Pages/Services/NotificationsPage.qml | 2 +- .../Pages/ServicesPage.qml | 2 +- .../Pages/Wallpaper/WallpaperSelect.qml | 2 +- .../Pages/WallpaperPage.qml | 4 +- .../PopupManager.qml | 0 .../PopupOverlay.qml | 0 Modules/Settings/SettingsSearch.qml | 317 --------- .../SettingsState.qml | 0 Modules/Settings/Wrapper.qml | 8 +- Modules/SettingsNew/Content.qml | 76 -- Modules/SettingsNew/Wrapper.qml | 47 -- 93 files changed, 88 insertions(+), 7943 deletions(-) delete mode 100644 Modules/Settings/Categories.qml delete mode 100644 Modules/Settings/Categories/Appearance.qml delete mode 100644 Modules/Settings/Categories/Background.qml delete mode 100644 Modules/Settings/Categories/Bar.qml delete mode 100644 Modules/Settings/Categories/Dashboard.qml delete mode 100644 Modules/Settings/Categories/General.qml delete mode 100644 Modules/Settings/Categories/Launcher.qml delete mode 100644 Modules/Settings/Categories/Lockscreen.qml delete mode 100644 Modules/Settings/Categories/Lockscreen/Idle.qml delete mode 100644 Modules/Settings/Categories/Notifications.qml delete mode 100644 Modules/Settings/Categories/Osd.qml delete mode 100644 Modules/Settings/Categories/Screenshot.qml delete mode 100644 Modules/Settings/Categories/Services.qml delete mode 100644 Modules/Settings/Categories/Sidebar.qml delete mode 100644 Modules/Settings/Categories/SystemUpdates.qml delete mode 100644 Modules/Settings/Categories/Utilities.qml rename Modules/{SettingsNew => Settings}/Common/AudioDeviceList.qml (100%) rename Modules/{SettingsNew => Settings}/Common/BlobPopup.qml (100%) rename Modules/{SettingsNew => Settings}/Common/ConnectedRect.qml (100%) rename Modules/{SettingsNew => Settings}/Common/InfoRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/ItemList.qml (100%) rename Modules/{SettingsNew => Settings}/Common/NavRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/OverlayRow.qml (97%) rename Modules/{SettingsNew => Settings}/Common/PageBase.qml (98%) rename Modules/{SettingsNew => Settings}/Common/PopupRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/SectionHeader.qml (100%) rename Modules/{SettingsNew => Settings}/Common/SelectRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/SliderRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/SpinRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/StackPage.qml (98%) rename Modules/{SettingsNew => Settings}/Common/TimeInput.qml (100%) rename Modules/{SettingsNew => Settings}/Common/ToggleRow.qml (100%) rename Modules/{SettingsNew => Settings}/Common/WallItem.qml (100%) rename Modules/{SettingsNew => Settings}/Common/WallpaperCropper.qml (100%) delete mode 100644 Modules/Settings/Controls/HyprTimeInput.qml delete mode 100644 Modules/Settings/Controls/SchemesListView.qml delete mode 100644 Modules/Settings/Controls/Separator.qml delete mode 100644 Modules/Settings/Controls/SettingActionList.qml delete mode 100644 Modules/Settings/Controls/SettingAliasList.qml delete mode 100644 Modules/Settings/Controls/SettingBarEntryList.qml delete mode 100644 Modules/Settings/Controls/SettingHyprSpinner.qml delete mode 100644 Modules/Settings/Controls/SettingInput.qml delete mode 100644 Modules/Settings/Controls/SettingList.qml delete mode 100644 Modules/Settings/Controls/SettingListView.qml delete mode 100644 Modules/Settings/Controls/SettingReadOnly.qml delete mode 100644 Modules/Settings/Controls/SettingSpinBox.qml delete mode 100644 Modules/Settings/Controls/SettingSpinner.qml delete mode 100644 Modules/Settings/Controls/SettingStringList.qml delete mode 100644 Modules/Settings/Controls/SettingSwitch.qml delete mode 100644 Modules/Settings/Controls/SettingsHeader.qml delete mode 100644 Modules/Settings/Controls/SettingsIconButton.qml delete mode 100644 Modules/Settings/Controls/SettingsPage.qml delete mode 100644 Modules/Settings/Controls/SettingsSection.qml delete mode 100644 Modules/Settings/Controls/SpinnerButton.qml delete mode 100644 Modules/Settings/Controls/StringListEditor.qml delete mode 100644 Modules/Settings/Controls/TimeInput.qml delete mode 100644 Modules/Settings/Controls/VSeparator.qml delete mode 100644 Modules/Settings/Controls/WallpaperCropper.qml delete mode 100644 Modules/Settings/Controls/WallpaperGrid.qml rename Modules/{SettingsNew => Settings}/NavPane.qml (96%) rename Modules/{SettingsNew => Settings}/NavPane/NavLocations.qml (99%) rename Modules/{SettingsNew => Settings}/PageCompRegistry.qml (88%) rename Modules/{SettingsNew => Settings}/PageRegistry.qml (100%) rename Modules/{SettingsNew => Settings}/Pages.qml (100%) rename Modules/{SettingsNew => Settings}/Pages/AboutPage.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/Apps/AllApps.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/Apps/AppInfo.qml (99%) rename Modules/{SettingsNew => Settings}/Pages/AppsPage.qml (99%) rename Modules/{SettingsNew => Settings}/Pages/Audio/AppVolumes.qml (97%) rename Modules/{SettingsNew => Settings}/Pages/AudioPage.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/Panels/Bar/BarClock.qml (97%) rename Modules/{SettingsNew => Settings}/Pages/Panels/Bar/BarStatusIcons.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/Panels/Bar/BarTray.qml (93%) rename Modules/{SettingsNew => Settings}/Pages/Panels/BarPanel.qml (96%) rename Modules/{SettingsNew => Settings}/Pages/Panels/DashboardPanel.qml (94%) rename Modules/{SettingsNew => Settings}/Pages/Panels/LauncherPanel.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/Panels/ResourcesPanel.qml (97%) rename Modules/{SettingsNew => Settings}/Pages/Panels/SidebarPanel.qml (95%) rename Modules/{SettingsNew => Settings}/Pages/PanelsPage.qml (97%) rename Modules/{SettingsNew => Settings}/Pages/ScreenshotPage.qml (97%) rename Modules/{SettingsNew => Settings}/Pages/Services/NotificationsPage.qml (99%) rename Modules/{SettingsNew => Settings}/Pages/ServicesPage.qml (99%) rename Modules/{SettingsNew => Settings}/Pages/Wallpaper/WallpaperSelect.qml (98%) rename Modules/{SettingsNew => Settings}/Pages/WallpaperPage.qml (98%) rename Modules/{SettingsNew => Settings}/PopupManager.qml (100%) rename Modules/{SettingsNew => Settings}/PopupOverlay.qml (100%) delete mode 100644 Modules/Settings/SettingsSearch.qml rename Modules/{SettingsNew => Settings}/SettingsState.qml (100%) delete mode 100644 Modules/SettingsNew/Content.qml delete mode 100644 Modules/SettingsNew/Wrapper.qml diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index d2c4ab4..9f2f67a 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -10,7 +10,7 @@ import qs.Modules.Osd as Osd import qs.Components.Toast as Toasts import qs.Modules.Launcher as Launcher import qs.Modules.Resources as Resources -import qs.Modules.SettingsNew as Settings +import qs.Modules.Settings as Settings import qs.Modules.Drawing as Drawing import qs.Modules.Dock as Dock import qs.Modules.Clipboard as Clipboard diff --git a/Modules/Settings/Categories.qml b/Modules/Settings/Categories.qml deleted file mode 100644 index 87454c5..0000000 --- a/Modules/Settings/Categories.qml +++ /dev/null @@ -1,370 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import Quickshell.Widgets -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Modules as Modules -import qs.Config -import qs.Helpers - -Item { - id: root - - required property Item content - - signal settingSelected(string category, string section, string settingName) - - // Function to select category by key - function selectCategory(categoryKey: string) { - for (let i = 0; i < appearanceCats.count; i++) { - const item = appearanceCats.get(i); - if (item.key === categoryKey) { - appearanceView.currentIndex = i; - root.content.setCategory(item.key, item._index); - return; - } - } - for (let i = 0; i < systemCats.count; i++) { - const item = systemCats.get(i); - if (item.key === categoryKey) { - sysView.currentIndex = i; - root.content.setCategory(item.key, item._index); - return; - } - } - for (let i = 0; i < panelCats.count; i++) { - const item = panelCats.get(i); - if (item.key === categoryKey) { - panelsView.currentIndex = i; - root.content.setCategory(item.key, item._index); - return; - } - } - } - - implicitHeight: searchBar.implicitHeight + Appearance.spacing.smaller + clayout.contentHeight + clayout.anchors.margins * 2 - implicitWidth: clayout.contentWidth + clayout.anchors.margins * 2 - - ListModel { - id: systemCats - - ListElement { - _index: 0 - icon: "settings" - key: "general" - name: "General" - } - - ListElement { - _index: 1 - icon: "build_circle" - key: "services" - name: "Services" - } - - ListElement { - _index: 2 - icon: "notifications" - key: "notifications" - name: "Notifications" - } - - ListElement { - _index: 3 - icon: "handyman" - key: "utilities" - name: "Utilities" - } - - ListElement { - _index: 4 - icon: "cached" - key: "updates" - name: "Updates" - } - } - - ListModel { - id: panelCats - - ListElement { - _index: 5 - icon: "settop_component" - key: "bar" - name: "Bar" - } - - ListElement { - _index: 6 - icon: "lock" - key: "lockscreen" - name: "Lockscreen" - } - - ListElement { - _index: 7 - icon: "view_sidebar" - key: "sidebar" - name: "Sidebar" - } - - ListElement { - _index: 8 - icon: "dashboard" - key: "dashboard" - name: "Dashboard" - } - - ListElement { - _index: 9 - icon: "display_settings" - key: "osd" - name: "On screen display" - } - - ListElement { - _index: 10 - icon: "rocket_launch" - key: "launcher" - name: "Launcher" - } - } - - ListModel { - id: appearanceCats - - ListElement { - _index: 11 - icon: "wallpaper" - key: "wallpaper" - name: "Wallpaper" - } - - ListElement { - _index: 12 - icon: "screenshot_region" - key: "screenshot" - name: "Screenshot" - } - - ListElement { - _index: 13 - icon: "colors" - key: "appearance" - name: "Appearance" - } - } - - CustomClippingRect { - anchors.fill: parent - color: DynamicColors.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - - CustomFlickable { - id: clayout - - anchors.fill: parent - anchors.margins: Appearance.padding.extraSmall - contentHeight: contentItem.childrenRect.height - contentWidth: contentItem.childrenRect.width - flickableDirection: Flickable.VerticalFlick - - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - spacing: Appearance.spacing.normal - - CustomListView { - id: sysView - - Layout.fillWidth: true - Layout.preferredHeight: contentItem.childrenRect.height - Layout.preferredWidth: contentItem.childrenRect.width - boundsBehavior: Flickable.StopAtBounds - highlightFollowsCurrentItem: false - interactive: false - model: systemCats - spacing: 2 - - delegate: Category { - view: sysView - } - // highlight: CustomRect { - // color: DynamicColors.palette.m3primary - // implicitHeight: sysView.currentItem?.implicitHeight ?? 0 - // implicitWidth: sysView.width - // radius: Appearance.rounding.normal - Appearance.padding.smaller - // y: sysView.currentItem?.y ?? 0 - // - // Behavior on y { - // Anim { - // duration: Appearance.anim.durations.small - // easing.bezierCurve: Appearance.anim.curves.expressiveEffects - // } - // } - // } - } - - CustomListView { - id: panelsView - - Layout.fillWidth: true - Layout.preferredHeight: contentItem.childrenRect.height - Layout.preferredWidth: contentItem.childrenRect.width - boundsBehavior: Flickable.StopAtBounds - contentWidth: contentItem.childrenRect.width - highlightFollowsCurrentItem: false - interactive: false - model: panelCats - spacing: 2 - - delegate: Category { - view: panelsView - } - // highlight: CustomRect { - // color: DynamicColors.palette.m3primary - // implicitHeight: panelsView.currentItem?.implicitHeight ?? 0 - // implicitWidth: panelsView.width - // radius: Appearance.rounding.normal - Appearance.padding.smaller - // y: panelsView.currentItem?.y ?? 0 - // - // Behavior on y { - // Anim { - // duration: Appearance.anim.durations.small - // easing.bezierCurve: Appearance.anim.curves.expressiveEffects - // } - // } - // } - } - - CustomListView { - id: appearanceView - - Layout.fillWidth: true - Layout.preferredHeight: contentItem.childrenRect.height - Layout.preferredWidth: contentItem.childrenRect.width - boundsBehavior: Flickable.StopAtBounds - contentWidth: contentItem.childrenRect.width - highlightFollowsCurrentItem: false - interactive: false - model: appearanceCats - spacing: 2 - - delegate: Category { - view: appearanceView - } - // highlight: CustomRect { - // color: DynamicColors.palette.m3primary - // implicitHeight: appearanceView.currentItem?.implicitHeight ?? 0 - // implicitWidth: appearanceView.width - // radius: Appearance.rounding.normal - Appearance.padding.smaller - // y: appearanceView.currentItem?.y ?? 0 - // - // Behavior on y { - // Anim { - // duration: Appearance.anim.durations.small - // easing.bezierCurve: Appearance.anim.curves.expressiveEffects - // } - // } - // } - } - } - } - } - - component Category: CustomRect { - id: categoryItem - - required property int _index - readonly property bool first: index === 0 - required property string icon - required property int index - required property string key - readonly property bool last: index === view.model.count - 1 - required property string name - readonly property bool selected: key === root.content.currentCategory - required property ListView view - - bottomLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall - bottomRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || last ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall - color: selected ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer - implicitHeight: 50 - implicitWidth: 256 - topLeftRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall - topRightRadius: layer.pressed ? Appearance.rounding.smallest : selected || first ? Appearance.rounding.normal - clayout.anchors.margins : Appearance.rounding.extraSmall - - Behavior on bottomLeftRadius { - Anim { - type: Anim.FastEffects - } - } - Behavior on bottomRightRadius { - Anim { - type: Anim.FastEffects - } - } - Behavior on topLeftRadius { - Anim { - type: Anim.FastEffects - } - } - Behavior on topRightRadius { - Anim { - type: Anim.FastEffects - } - } - - RowLayout { - id: layout - - anchors.left: parent.left - anchors.margins: Appearance.padding.smaller - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - MaterialIcon { - id: icon - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillHeight: true - Layout.leftMargin: Appearance.padding.small - Layout.preferredWidth: icon.contentWidth - color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - fill: categoryItem.key === root.content.currentCategory ? 1 : 0 - font.pointSize: Appearance.font.size.small * 2 - text: categoryItem.icon - verticalAlignment: Text.AlignVCenter - - Behavior on fill { - Anim { - } - } - } - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillHeight: true - Layout.fillWidth: true - Layout.leftMargin: Appearance.spacing.normal - color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - text: categoryItem.name - verticalAlignment: Text.AlignVCenter - } - } - - StateLayer { - id: layer - - color: categoryItem.key === root.content.currentCategory ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - - onClicked: { - root.content.setCategory(categoryItem.key, categoryItem._index); - categoryItem.view.currentIndex = categoryItem.index; - } - } - } -} diff --git a/Modules/Settings/Categories/Appearance.qml b/Modules/Settings/Categories/Appearance.qml deleted file mode 100644 index 5b46dec..0000000 --- a/Modules/Settings/Categories/Appearance.qml +++ /dev/null @@ -1,162 +0,0 @@ -import QtQuick -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - id: root - - SettingsSection { - sectionId: "Scale" - - SettingsHeader { - name: "Scale" - } - - SettingSpinBox { - name: "Rounding scale" - object: Config.appearance.rounding - setting: "scale" - step: 0.1 - } - - Separator { - } - - SettingSpinBox { - name: "Spacing scale" - object: Config.appearance.spacing - setting: "scale" - step: 0.1 - } - - Separator { - } - - SettingSpinBox { - name: "Padding scale" - object: Config.appearance.padding - setting: "scale" - step: 0.1 - } - - Separator { - } - - SettingSpinBox { - name: "Font size scale" - object: Config.appearance.font.size - setting: "scale" - step: 0.1 - } - - Separator { - } - - SettingSpinBox { - name: "Animation duration scale" - object: Config.appearance.anim.durations - setting: "scale" - step: 0.1 - } - - Separator { - } - - SettingSpinBox { - name: "Deform animation scale" - object: Config.appearance.deform - setting: "scale" - step: 0.1 - } - } - - SettingsSection { - sectionId: "Fonts" - - SettingsHeader { - name: "Fonts" - } - - SettingListView { - name: "Sans family" - object: Config.appearance.font.family - setting: "sans" - stringList: Qt.fontFamilies() - } - - Separator { - } - - SettingListView { - name: "Monospace family" - object: Config.appearance.font.family - setting: "mono" - stringList: Qt.fontFamilies() - } - } - - SettingsSection { - sectionId: "Animation" - - SettingsHeader { - name: "Animation" - } - - SettingSpinBox { - name: "Media GIF speed adjustment" - object: Config.appearance.anim - setting: "mediaGifSpeedAdjustment" - step: 10 - } - - Separator { - } - - SettingSpinBox { - max: 5 - min: 0 - name: "Session GIF speed" - object: Config.appearance.anim - setting: "sessionGifSpeed" - step: 0.1 - } - } - - SettingsSection { - sectionId: "Transparency" - - SettingsHeader { - name: "Transparency" - } - - SettingSwitch { - name: "Enable transparency" - object: Config.appearance.transparency - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - max: 1 - min: 0 - name: "Base opacity" - object: Config.appearance.transparency - setting: "base" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - max: 1 - min: 0 - name: "Layer opacity" - object: Config.appearance.transparency - setting: "layers" - step: 0.05 - } - } -} diff --git a/Modules/Settings/Categories/Background.qml b/Modules/Settings/Categories/Background.qml deleted file mode 100644 index 1b88388..0000000 --- a/Modules/Settings/Categories/Background.qml +++ /dev/null @@ -1,48 +0,0 @@ -import Quickshell -import QtQuick.Layouts -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - id: root - - required property ShellScreen screen - - SettingsSection { - sectionId: "Wallpaper" - - SettingsHeader { - name: "Wallpaper" - } - - SettingSwitch { - name: "Enable wallpaper rendering" - object: Config.background - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Fade duration" - object: Config.background - setting: "wallFadeDuration" - step: 50 - } - - Separator { - } - - WallpaperCropper { - } - } - - SettingsSection { - sectionId: "Wallpapers" - - WallpaperGrid { - } - } -} diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml deleted file mode 100644 index de65d23..0000000 --- a/Modules/Settings/Categories/Bar.qml +++ /dev/null @@ -1,217 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Bar" - - SettingsHeader { - name: "Bar" - } - - SettingSwitch { - name: "Auto hide" - object: Config.bar - setting: "autoHide" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Height" - object: Config.bar - setting: "height" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Rounding" - object: Config.bar - setting: "rounding" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Border" - object: Config.bar - setting: "border" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Smoothing" - object: Config.bar - setting: "smoothing" - } - } - - SettingsSection { - sectionId: "Tray" - - SettingsHeader { - name: "System tray" - } - - SettingSpinBox { - min: 16 - name: "Tray icon size" - object: Config.bar.tray - setting: "trayIconSize" - } - } - - SettingsSection { - sectionId: "Popouts" - - SettingsHeader { - name: "Popouts" - } - - SettingSwitch { - name: "Tray" - object: Config.bar.popouts - setting: "tray" - } - - Separator { - } - - SettingSwitch { - name: "Audio" - object: Config.bar.popouts - setting: "audio" - } - - Separator { - } - - SettingSwitch { - name: "Active window" - object: Config.bar.popouts - setting: "activeWindow" - } - - Separator { - } - - SettingSwitch { - name: "Resources" - object: Config.bar.popouts - setting: "resources" - } - - Separator { - } - - SettingSwitch { - name: "Clock" - object: Config.bar.popouts - setting: "clock" - } - - Separator { - } - - SettingSwitch { - name: "Network" - object: Config.bar.popouts - setting: "network" - } - - Separator { - } - - SettingSwitch { - name: "Power" - object: Config.bar.popouts - setting: "upower" - } - } - - SettingsSection { - sectionId: "Entries" - - SettingsHeader { - name: "Entries" - } - - SettingBarEntryList { - name: "Bar entries" - object: Config.bar - setting: "entries" - } - } - - SettingsSection { - sectionId: "Dock" - - SettingsHeader { - name: "Dock" - } - - SettingSwitch { - name: "Enable dock" - object: Config.dock - setting: "enable" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Dock height" - object: Config.dock - setting: "height" - } - - Separator { - } - - SettingSwitch { - name: "Hover to reveal" - object: Config.dock - setting: "hoverToReveal" - } - - Separator { - } - - SettingSwitch { - name: "Pin on startup" - object: Config.dock - setting: "pinnedOnStartup" - } - - Separator { - } - - SettingStringList { - addLabel: qsTr("Add pinned app") - name: "Pinned apps" - object: Config.dock - setting: "pinnedApps" - } - - Separator { - } - - SettingStringList { - addLabel: qsTr("Add ignored regex") - name: "Ignored app regexes" - object: Config.dock - setting: "ignoredAppRegexes" - } - } -} diff --git a/Modules/Settings/Categories/Dashboard.qml b/Modules/Settings/Categories/Dashboard.qml deleted file mode 100644 index 1e9ad84..0000000 --- a/Modules/Settings/Categories/Dashboard.qml +++ /dev/null @@ -1,218 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Dashboard" - - SettingsHeader { - name: "Dashboard" - } - - SettingSwitch { - name: "Enable dashboard" - object: Config.dashboard - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Media update interval" - object: Config.dashboard - setting: "mediaUpdateInterval" - step: 50 - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Resource update interval" - object: Config.dashboard - setting: "resourceUpdateInterval" - step: 50 - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Drag threshold" - object: Config.dashboard - setting: "dragThreshold" - } - } - - SettingsSection { - sectionId: "Performance" - - SettingsHeader { - name: "Performance" - } - - SettingSwitch { - name: "Show battery" - object: Config.dashboard.performance - setting: "showBattery" - } - - Separator { - } - - SettingSwitch { - name: "Show GPU" - object: Config.dashboard.performance - setting: "showGpu" - } - - Separator { - } - - SettingSwitch { - name: "Show CPU" - object: Config.dashboard.performance - setting: "showCpu" - } - - Separator { - } - - SettingSwitch { - name: "Show memory" - object: Config.dashboard.performance - setting: "showMemory" - } - - Separator { - } - - SettingSwitch { - name: "Show storage" - object: Config.dashboard.performance - setting: "showStorage" - } - - Separator { - } - - SettingSwitch { - name: "Show network" - object: Config.dashboard.performance - setting: "showNetwork" - } - } - - // SettingsSection { - // sectionId: "Layout Sizes" - // - // SettingsHeader { - // name: "Layout Sizes" - // } - // - // SettingReadOnly { - // name: "Tab indicator height" - // value: String(Config.dashboard.sizes.tabIndicatorHeight) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Tab indicator spacing" - // value: String(Config.dashboard.sizes.tabIndicatorSpacing) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Info width" - // value: String(Config.dashboard.sizes.infoWidth) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Info icon size" - // value: String(Config.dashboard.sizes.infoIconSize) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Date time width" - // value: String(Config.dashboard.sizes.dateTimeWidth) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Media width" - // value: String(Config.dashboard.sizes.mediaWidth) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Media progress sweep" - // value: String(Config.dashboard.sizes.mediaProgressSweep) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Media progress thickness" - // value: String(Config.dashboard.sizes.mediaProgressThickness) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Resource progress thickness" - // value: String(Config.dashboard.sizes.resourceProgessThickness) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Weather width" - // value: String(Config.dashboard.sizes.weatherWidth) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Media cover art size" - // value: String(Config.dashboard.sizes.mediaCoverArtSize) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Media visualiser size" - // value: String(Config.dashboard.sizes.mediaVisualiserSize) - // } - // - // Separator { - // } - // - // SettingReadOnly { - // name: "Resource size" - // value: String(Config.dashboard.sizes.resourceSize) - // } - // } -} diff --git a/Modules/Settings/Categories/General.qml b/Modules/Settings/Categories/General.qml deleted file mode 100644 index 89d9bc7..0000000 --- a/Modules/Settings/Categories/General.qml +++ /dev/null @@ -1,308 +0,0 @@ -import Quickshell -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import qs.Modules.Settings.Controls -import qs.Config -import qs.Components -import qs.Helpers - -SettingsPage { - id: root - - function schemeTypeItem(items, value) { - for (let i = 0; i < items.length; i++) { - const item = items[i]; - if (item.value === value) - return item; - } - - return items[0] ?? null; - } - - SettingsSection { - sectionId: "General" - - SettingsHeader { - name: "General" - } - - SettingInput { - name: "Logo" - object: Config.general - setting: "logo" - } - - Separator { - } - - SettingInput { - name: "Wallpaper path" - object: Config.general - setting: "wallpaperPath" - } - - Separator { - } - - SettingSwitch { - name: "Desktop icons" - object: Config.general - setting: "desktopIcons" - } - - Separator { - } - - SettingInput { - name: "Date format" - object: Config.general - setting: "dateFormat" - } - } - - SettingsSection { - sectionId: "Color" - z: 1 - - SettingsHeader { - name: "Color" - } - - CustomSplitButtonRow { - active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1] - enabled: Config.general.color.schemeGeneration - label: qsTr("Scheme mode") - - menuItems: [ - MenuItem { - icon: "light_mode" - text: qsTr("Light") - value: "light" - }, - MenuItem { - icon: "dark_mode" - text: qsTr("Dark") - value: "dark" - } - ] - - onSelected: item => { - Config.general.color.mode = item.value; - Config.save(); - - if (item.value === "light") - ModeScheduler.applyLightMode(); - else if (item.value === "dark") - ModeScheduler.applyDarkMode(); - } - } - - Separator { - } - - CustomSplitButtonRow { - id: schemeType - - active: root.schemeTypeItem(menuItems, Config.colors.schemeType) - enabled: Config.general.color.schemeGeneration - label: qsTr("Scheme type") - - menuItems: [ - MenuItem { - icon: "palette" - text: qsTr("Vibrant") - value: "vibrant" - }, - MenuItem { - icon: "gesture" - text: qsTr("Expressive") - value: "expressive" - }, - MenuItem { - icon: "contrast" - text: qsTr("Monochrome") - value: "monochrome" - }, - MenuItem { - icon: "tonality" - text: qsTr("Neutral") - value: "neutral" - }, - MenuItem { - icon: "gradient" - text: qsTr("Tonal spot") - value: "tonal-spot" - }, - MenuItem { - icon: "target" - text: qsTr("Fidelity") - value: "fidelity" - }, - MenuItem { - icon: "article" - text: qsTr("Content") - value: "content" - }, - MenuItem { - icon: "colors" - text: qsTr("Rainbow") - value: "rainbow" - }, - MenuItem { - icon: "nutrition" - text: qsTr("Fruit salad") - value: "fruit-salad" - } - ] - - onSelected: item => { - Config.colors.schemeType = item.value; - Config.save(); - - Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--scheme", item.value]); - } - } - - Separator { - } - - SettingSwitch { - name: "Automatic color scheme" - object: Config.general.color - setting: "schemeGeneration" - } - - Separator { - shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1 - } - - SchemesListView { - name: "Color scheme presets" - object: Config.colors.presets - setting: "name" - shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1 - stringList: FetchPresets.presetNames() - } - - Separator { - shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration - } - - SchemesListView { - name: "Preset variant" - object: Config.colors.presets - setting: "variant" - shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration - stringList: FetchPresets.variantNames(Config.colors.presets.name) - - onOptionSet: item => { - Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${item}`]); - } - } - - Separator { - shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration - } - - SchemesListView { - name: "Preset accent" - object: Config.colors.presets - setting: "accent" - shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration - stringList: FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant) - - onOptionSet: item => { - Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${Config.colors.presets.variant}`, "--accent", `${item}`]); - } - } - - Separator { - shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0 - } - - SettingSwitch { - name: "Smart color scheme" - object: Config.general.color - setting: "smart" - shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0 - } - - Separator { - shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0 - } - - TimeInput { - name: "Schedule dark mode" - object: Config.general.color - settings: ["scheduleDark", "scheduleDarkStart", "scheduleDarkEnd"] - shouldBeActive: Config.general.color.schemeGeneration - } - - Separator { - } - - HyprTimeInput { - name: "Schedule Hyprsunset" - object: Config.general.color - settings: ["scheduleHyprsunset", "scheduleHyprsunsetStart", "scheduleHyprsunsetEnd", "hyprsunsetTemp"] - } - - Separator { - } - - SettingSpinBox { - max: 20000 - min: 1000 - name: "Hyprsunset temperature" - object: Config.general.color - setting: "hyprsunsetTemp" - step: 200 - } - } - - SettingsSection { - sectionId: "Default Apps" - - SettingsHeader { - name: "Default Apps" - } - - SettingStringList { - addLabel: qsTr("Add terminal command") - name: "Terminal" - object: Config.general.apps - setting: "terminal" - } - - Separator { - } - - SettingStringList { - addLabel: qsTr("Add audio command") - name: "Audio" - object: Config.general.apps - setting: "audio" - } - - Separator { - } - - SettingStringList { - addLabel: qsTr("Add playback command") - name: "Playback" - object: Config.general.apps - setting: "playback" - } - - Separator { - } - - SettingStringList { - addLabel: qsTr("Add explorer command") - name: "Explorer" - object: Config.general.apps - setting: "explorer" - } - } -} diff --git a/Modules/Settings/Categories/Launcher.qml b/Modules/Settings/Categories/Launcher.qml deleted file mode 100644 index 15832a8..0000000 --- a/Modules/Settings/Categories/Launcher.qml +++ /dev/null @@ -1,165 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Launcher" - - SettingsHeader { - name: "Launcher" - } - - SettingSpinBox { - min: 1 - name: "Max apps shown" - object: Config.launcher - setting: "maxAppsShown" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Max wallpapers shown" - object: Config.launcher - setting: "maxWallpapers" - } - - Separator { - } - - SettingInput { - name: "Action prefix" - object: Config.launcher - setting: "actionPrefix" - } - - Separator { - } - - SettingInput { - name: "Special prefix" - object: Config.launcher - setting: "specialPrefix" - } - - Separator { - } - - SettingSwitch { - name: "Use UWSM launch command" - object: Config.launcher - setting: "uwsm" - } - } - - SettingsSection { - sectionId: "Fuzzy Search" - - SettingsHeader { - name: "Fuzzy Search" - } - - SettingSwitch { - name: "Apps" - object: Config.launcher.useFuzzy - setting: "apps" - } - - Separator { - } - - SettingSwitch { - name: "Actions" - object: Config.launcher.useFuzzy - setting: "actions" - } - - Separator { - } - - SettingSwitch { - name: "Schemes" - object: Config.launcher.useFuzzy - setting: "schemes" - } - - Separator { - } - - SettingSwitch { - name: "Variants" - object: Config.launcher.useFuzzy - setting: "variants" - } - - Separator { - } - - SettingSwitch { - name: "Wallpapers" - object: Config.launcher.useFuzzy - setting: "wallpapers" - } - } - - SettingsSection { - sectionId: "Sizes" - - SettingsHeader { - name: "Sizes" - } - - SettingSpinBox { - min: 1 - name: "Item width" - object: Config.launcher.sizes - setting: "itemWidth" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Item height" - object: Config.launcher.sizes - setting: "itemHeight" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Wallpaper width" - object: Config.launcher.sizes - setting: "wallpaperWidth" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Wallpaper height" - object: Config.launcher.sizes - setting: "wallpaperHeight" - } - } - - SettingsSection { - sectionId: "Actions" - - SettingsHeader { - name: "Actions" - } - - SettingActionList { - name: "Launcher actions" - object: Config.launcher - setting: "actions" - } - } -} diff --git a/Modules/Settings/Categories/Lockscreen.qml b/Modules/Settings/Categories/Lockscreen.qml deleted file mode 100644 index adfa74c..0000000 --- a/Modules/Settings/Categories/Lockscreen.qml +++ /dev/null @@ -1,125 +0,0 @@ -import qs.Modules.Settings.Categories.Lockscreen -import qs.Modules.Settings.Controls -import qs.Helpers -import qs.Config - -SettingsPage { - id: root - - SettingsSection { - sectionId: "Lockscreen" - - SettingsHeader { - name: "Lockscreen" - } - - SettingSwitch { - name: "Recolor logo" - object: Config.lock - setting: "recolorLogo" - } - - Separator { - } - - SettingSwitch { - name: "Enable fingerprint" - object: Config.lock - setting: "enableFprint" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Max fingerprint tries" - object: Config.lock - setting: "maxFprintTries" - step: 1 - } - - Separator { - } - - SettingSwitch { - name: "Show notification details" - object: Config.lock - setting: "showNotifContent" - } - - Separator { - } - - SettingSwitch { - name: "Show notification icon" - object: Config.lock - setting: "showNotifIcon" - } - - Separator { - } - - SettingSpinBox { - min: 0 - name: "Blur amount" - object: Config.lock - setting: "blurAmount" - step: 1 - } - - Separator { - } - - SettingSpinBox { - max: 2 - min: 0.1 - name: "Height multiplier" - object: Config.lock.sizes - setting: "heightMult" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - max: 4 - min: 0.5 - name: "Aspect ratio" - object: Config.lock.sizes - setting: "ratio" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - min: 100 - name: "Center width" - object: Config.lock.sizes - setting: "centerWidth" - step: 10 - } - } - - SettingsSection { - sectionId: "Greeter" - - SettingsHeader { - name: "Greeter" - } - - SettingsIconButton { - name: "Install wallpaper and color scheme to greeter" - } - } - - SettingsSection { - sectionId: "Idle" - - Idle { - } - } -} diff --git a/Modules/Settings/Categories/Lockscreen/Idle.qml b/Modules/Settings/Categories/Lockscreen/Idle.qml deleted file mode 100644 index 43ea412..0000000 --- a/Modules/Settings/Categories/Lockscreen/Idle.qml +++ /dev/null @@ -1,115 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Modules.Settings.Controls - -ColumnLayout { - id: root - - property bool shouldBeActive: true - - function addTimeoutEntry() { - let list = [...Config.general.idle.timeouts]; - - list.push({ - name: "New Entry", - timeout: 600, - idleAction: "lock" - }); - - Config.general.idle.timeouts = list; - Config.save(); - } - - function deleteTimeoutEntry(index) { - let list = [...Config.general.idle.timeouts]; - list.splice(index, 1); - Config.general.idle.timeouts = list; - Config.save(); - } - - function updateTimeoutEntry(i, key, value) { - const list = [...Config.general.idle.timeouts]; - let entry = list[i]; - - entry[key] = value; - list[i] = entry; - - Config.general.idle.timeouts = list; - Config.save(); - } - - anchors.left: parent.left - anchors.right: parent.right - height: shouldBeActive ? implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - spacing: Appearance.spacing.smaller - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Settings { - name: "Idle Monitors" - } - - Repeater { - model: [...Config.general.idle.timeouts] - - SettingList { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - - onAddActiveActionRequested: { - root.updateTimeoutEntry(index, "activeAction", ""); - } - onDeleteRequested: function (index) { - root.deleteTimeoutEntry(index); - } - onFieldEdited: function (key, value) { - root.updateTimeoutEntry(index, key, value); - } - } - } - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addTimeoutEntry() - } - - component Settings: CustomRect { - id: settingsItem - - required property string name - - Layout.preferredHeight: 60 - Layout.preferredWidth: 200 - - CustomText { - id: text - - anchors.fill: parent - font.bold: true - font.pointSize: Appearance.font.size.large * 2 - text: settingsItem.name - verticalAlignment: Text.AlignVCenter - } - } -} diff --git a/Modules/Settings/Categories/Notifications.qml b/Modules/Settings/Categories/Notifications.qml deleted file mode 100644 index a338724..0000000 --- a/Modules/Settings/Categories/Notifications.qml +++ /dev/null @@ -1,116 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Notifications" - - SettingsHeader { - name: "Notifications" - } - - SettingSwitch { - name: "Expire notifications" - object: Config.notifs - setting: "expire" - } - - Separator { - } - - SettingSpinBox { - name: "Default expire timeout" - min: 0 - object: Config.notifs - setting: "defaultExpireTimeout" - step: 100 - } - - Separator { - } - - SettingSpinBox { - name: "App notification cooldown" - min: 0 - object: Config.notifs - setting: "appNotifCooldown" - step: 100 - } - - Separator { - } - - SettingSpinBox { - name: "Clear threshold" - max: 1 - min: 0 - object: Config.notifs - setting: "clearThreshold" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - name: "Expand threshold" - min: 0 - object: Config.notifs - setting: "expandThreshold" - } - - Separator { - } - - SettingSwitch { - name: "Action on click" - object: Config.notifs - setting: "actionOnClick" - } - - Separator { - } - - SettingSpinBox { - name: "Group preview count" - min: 1 - object: Config.notifs - setting: "groupPreviewNum" - } - } - - SettingsSection { - sectionId: "Sizes" - - SettingsHeader { - name: "Sizes" - } - - SettingSpinBox { - name: "Width" - min: 1 - object: Config.notifs.sizes - setting: "width" - } - - Separator { - } - - SettingSpinBox { - name: "Image size" - min: 1 - object: Config.notifs.sizes - setting: "image" - } - - Separator { - } - - SettingSpinBox { - name: "Badge size" - min: 1 - object: Config.notifs.sizes - setting: "badge" - } - } -} diff --git a/Modules/Settings/Categories/Osd.qml b/Modules/Settings/Categories/Osd.qml deleted file mode 100644 index e40b147..0000000 --- a/Modules/Settings/Categories/Osd.qml +++ /dev/null @@ -1,81 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "On Screen Display" - - SettingsHeader { - name: "On Screen Display" - } - - SettingSwitch { - name: "Enable OSD" - object: Config.osd - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - name: "Hide delay" - min: 0 - object: Config.osd - setting: "hideDelay" - step: 100 - } - - Separator { - } - - SettingSwitch { - name: "Enable brightness OSD" - object: Config.osd - setting: "enableBrightness" - } - - Separator { - } - - SettingSwitch { - name: "Enable microphone OSD" - object: Config.osd - setting: "enableMicrophone" - } - - Separator { - } - - SettingSwitch { - name: "Brightness on all monitors" - object: Config.osd - setting: "allMonBrightness" - } - } - - SettingsSection { - sectionId: "Sizes" - - SettingsHeader { - name: "Sizes" - } - - SettingSpinBox { - name: "Slider width" - min: 1 - object: Config.osd.sizes - setting: "sliderWidth" - } - - Separator { - } - - SettingSpinBox { - name: "Slider height" - min: 1 - object: Config.osd.sizes - setting: "sliderHeight" - } - } -} diff --git a/Modules/Settings/Categories/Screenshot.qml b/Modules/Settings/Categories/Screenshot.qml deleted file mode 100644 index 10528a5..0000000 --- a/Modules/Settings/Categories/Screenshot.qml +++ /dev/null @@ -1,150 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config -import qs.Components - -SettingsPage { - SettingsSection { - sectionId: "Screenshot" - - SettingsHeader { - name: "Screenshot" - } - - SettingSwitch { - name: "Enable effects" - object: Config.screenshot - setting: "enable_pp" - } - - Separator { - } - - CustomSplitButtonRow { - active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] - enabled: Config.screenshot.enable_pp - label: qsTr("Effects mode") - - menuItems: [ - MenuItem { - icon: "build" - text: qsTr("Manual") - value: "manual" - }, - MenuItem { - icon: "rotate_auto" - text: qsTr("Auto") - value: "auto" - } - ] - - onSelected: item => { - Config.screenshot.mode = item.value; - Config.save(); - } - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" - } - - SettingSwitch { - enabled: Config.screenshot.enable_pp - name: "Enable rounded corners" - object: Config.screenshot - setting: "rounding" - shouldBeActive: Config.screenshot.mode === "manual" - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding - } - - SettingSpinBox { - enabled: Config.screenshot.enable_pp - min: 0 - name: "Corner radius" - object: Config.screenshot - setting: "radius" - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding - step: 1 - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" - } - - SettingSwitch { - enabled: Config.screenshot.enable_pp - name: "Enable shadow" - object: Config.screenshot - setting: "shadow" - shouldBeActive: Config.screenshot.mode === "manual" - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - } - - SettingSpinBox { - enabled: Config.screenshot.enable_pp - min: 0 - name: "Shadow blur amount" - object: Config.screenshot - setting: "shadow_blur" - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - step: 1 - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - } - - // SettingSwitch { - // name: "Shadow color broken atm" - // object: Config.Screenshot - // setting: "shadow_color" - // shouldBeActive: Config.screenshot.mode === "manual" - // } - // - // Separator { - // shouldBeActive: Config.screenshot.mode === "manual" - // } - - // SettingSpinBox { - // min: 1 - // name: "Shadow passes" - // object: Config.screenshot - // setting: "shadow_blur_passes" - // shouldBeActive: Config.screenshot.mode === "manual" - // step: 1 - // } - // - // Separator { - // shouldBeActive: Config.screenshot.mode === "manual" - // } - - SettingSpinBox { - enabled: Config.screenshot.enable_pp - min: 0 - name: "Shadow offset X" - object: Config.screenshot - setting: "shadow_offset_x" - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - step: 1 - } - - Separator { - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - } - - SettingSpinBox { - enabled: Config.screenshot.enable_pp - min: 0 - name: "Shadow offset Y" - object: Config.screenshot - setting: "shadow_offset_y" - shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow - step: 1 - } - } -} diff --git a/Modules/Settings/Categories/Services.qml b/Modules/Settings/Categories/Services.qml deleted file mode 100644 index ef7bb7a..0000000 --- a/Modules/Settings/Categories/Services.qml +++ /dev/null @@ -1,145 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Services" - - SettingsHeader { - name: "Services" - } - - SettingInput { - name: "Weather location" - object: Config.services - setting: "weatherLocation" - } - - Separator { - } - - SettingSwitch { - name: "Check for updates" - object: Config.services - setting: "updates" - } - - Separator { - } - - SettingSwitch { - name: "Use Fahrenheit" - object: Config.services - setting: "useFahrenheit" - } - - Separator { - } - - SettingSwitch { - name: "Use twelve hour clock" - object: Config.services - setting: "useTwelveHourClock" - } - - Separator { - } - - SettingSwitch { - name: "Enable ddcutil service" - object: Config.services - setting: "ddcutilService" - } - - Separator { - } - - SettingInput { - name: "GPU type" - object: Config.services - setting: "gpuType" - } - } - - SettingsSection { - sectionId: "Media" - - SettingsHeader { - name: "Media" - } - - SettingSpinBox { - max: 1 - min: 0 - name: "Audio increment" - object: Config.services - setting: "audioIncrement" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - max: 1 - min: 0 - name: "Brightness increment" - object: Config.services - setting: "brightnessIncrement" - step: 0.05 - } - - Separator { - } - - SettingSpinBox { - max: 1.0 - min: 0 - name: "Minimum brightness" - object: Config.services - setting: "minBrightness" - step: 0.01 - } - - Separator { - } - - SettingSpinBox { - max: 5 - min: 0 - name: "Max volume" - object: Config.services - setting: "maxVolume" - step: 0.05 - } - - Separator { - } - - SettingInput { - name: "Default player" - object: Config.services - setting: "defaultPlayer" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Visualizer resolution" - object: Config.services - setting: "visualizerBars" - step: 1 - } - - Separator { - } - - SettingAliasList { - name: "Player aliases" - object: Config.services - setting: "playerAliases" - } - } -} diff --git a/Modules/Settings/Categories/Sidebar.qml b/Modules/Settings/Categories/Sidebar.qml deleted file mode 100644 index 8e07ae6..0000000 --- a/Modules/Settings/Categories/Sidebar.qml +++ /dev/null @@ -1,28 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Sidebar" - - SettingsHeader { - name: "Sidebar" - } - - SettingSwitch { - name: "Enable sidebar" - object: Config.sidebar - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - name: "Width" - min: 1 - object: Config.sidebar.sizes - setting: "width" - } - } -} diff --git a/Modules/Settings/Categories/SystemUpdates.qml b/Modules/Settings/Categories/SystemUpdates.qml deleted file mode 100644 index dd01d70..0000000 --- a/Modules/Settings/Categories/SystemUpdates.qml +++ /dev/null @@ -1,197 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick.Layouts -import QtQuick -import qs.Config -import qs.Helpers -import qs.Components -import qs.Modules.Settings.Controls - -CustomClippingRect { - id: root - - radius: Appearance.rounding.normal - Appearance.padding.smaller - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.margins: Appearance.padding.large - spacing: Appearance.spacing.large - - MaterialIcon { - font.pointSize: Appearance.font.size.larger * 4 - text: "update" - } - - ColumnLayout { - CustomText { - font.pointSize: Appearance.font.size.large * 2 - text: "System updates" - } - - RowLayout { - id: row - - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: `${Updates.availableUpdates} available updates` - } - - CustomRect { - Layout.preferredHeight: 40 - Layout.preferredWidth: 150 - color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary - radius: Appearance.rounding.full - - RowLayout { - anchors.centerIn: parent - - MaterialIcon { - animate: true - color: DynamicColors.palette.m3onPrimary - font.pointSize: Appearance.font.size.large - text: Updates.updating ? "update" : "download" - } - - CustomText { - color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary - text: "Update all" - } - } - - StateLayer { - color: DynamicColors.palette.m3onPrimary - enabled: !Updates.updating - - onClicked: Updates.performSystemUpdate() - } - } - } - } - } - - CustomListView { - id: view - - readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 - - Layout.fillHeight: true - Layout.fillWidth: true - clip: true - contentHeight: height - spacing: Appearance.spacing.normal - - delegate: CustomRect { - id: update - - required property var modelData - readonly property list sections: modelData.update.split(" ") - - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: view.itemHeight - implicitWidth: parent.width - radius: Appearance.rounding.small - Appearance.padding.small - - RowLayout { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.smaller - anchors.rightMargin: Appearance.padding.smaller - - MaterialIcon { - font.pointSize: Appearance.font.size.large * 2 - text: "package_2" - } - - ColumnLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - Layout.preferredHeight: 25 - elide: Text.ElideRight - font.pointSize: Appearance.font.size.large - text: update.sections[0] - } - - CustomText { - Layout.fillWidth: true - color: DynamicColors.palette.m3onSurfaceVariant - text: Updates.formatUpdateTime(update.modelData.timestamp) - } - } - - RowLayout { - Layout.fillHeight: true - Layout.preferredWidth: 500 - - MarqueeText { - id: versionFrom - - Layout.fillHeight: true - Layout.preferredWidth: 225 - animate: true - color: DynamicColors.palette.m3tertiary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[1] - width: 225 - } - - MaterialIcon { - Layout.fillHeight: true - color: DynamicColors.palette.m3secondary - font.pointSize: Appearance.font.size.extraLarge - horizontalAlignment: Text.AlignHCenter - text: "arrow_right_alt" - verticalAlignment: Text.AlignVCenter - } - - MarqueeText { - id: versionTo - - Layout.fillHeight: true - Layout.preferredWidth: 225 - animate: true - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignHCenter - marqueeEnabled: true - pauseMs: 4000 - text: update.sections[3] - width: 225 - } - } - - IconButton { - Layout.preferredHeight: width - icon: "download" - - onClicked: { - Updates.performPackageUpdate(update.sections[0]); - } - } - } - } - model: ScriptModel { - id: script - - objectProp: "update" - values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ - update, - timestamp - })) - } - } - } -} diff --git a/Modules/Settings/Categories/Utilities.qml b/Modules/Settings/Categories/Utilities.qml deleted file mode 100644 index 2e5fee7..0000000 --- a/Modules/Settings/Categories/Utilities.qml +++ /dev/null @@ -1,245 +0,0 @@ -import qs.Modules.Settings.Controls -import qs.Config - -SettingsPage { - SettingsSection { - sectionId: "Utilities" - - SettingsHeader { - name: "Utilities" - } - - SettingSwitch { - name: "Enable utilities" - object: Config.utilities - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Max toasts" - object: Config.utilities - setting: "maxToasts" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Panel width" - object: Config.utilities.sizes - setting: "width" - } - - Separator { - } - - SettingSpinBox { - min: 1 - name: "Toast width" - object: Config.utilities.sizes - setting: "toastWidth" - } - } - - SettingsSection { - sectionId: "Clipboard" - - SettingsHeader { - name: "Clipboard" - } - - SettingSwitch { - name: "Enable clipboard history viewer" - object: Config.clipboard - setting: "enabled" - } - - Separator { - } - - SettingSpinBox { - max: 20 - min: 1 - name: "Max entries visible" - object: Config.clipboard - setting: "maxEntriesShown" - } - - Separator { - } - - SettingSpinBox { - max: 80 - min: 30 - name: "Entry height" - object: Config.clipboard.sizes - setting: "itemHeight" - } - - Separator { - } - - SettingSpinBox { - max: 700 - min: 300 - name: "Entry width" - object: Config.clipboard.sizes - setting: "width" - } - - Separator { - } - - SettingSpinBox { - max: 1800 - min: 50 - name: "Minimum preview width" - object: Config.clipboard.sizes - setting: "minPreviewWidth" - } - - Separator { - } - - SettingSpinBox { - max: 1800 - min: 50 - name: "Maximum preview width" - object: Config.clipboard.sizes - setting: "previewWidth" - } - } - - SettingsSection { - sectionId: "Toasts" - - SettingsHeader { - name: "Toasts" - } - - SettingSwitch { - name: "Config loaded" - object: Config.utilities.toasts - setting: "configLoaded" - } - - Separator { - } - - SettingSwitch { - name: "Charging changed" - object: Config.utilities.toasts - setting: "chargingChanged" - } - - Separator { - } - - SettingSwitch { - name: "Game mode changed" - object: Config.utilities.toasts - setting: "gameModeChanged" - } - - // Separator { - // } - // - // SettingSwitch { - // name: "Do not disturb changed" - // object: Config.utilities.toasts - // setting: "dndChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Audio output changed" - // object: Config.utilities.toasts - // setting: "audioOutputChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Audio input changed" - // object: Config.utilities.toasts - // setting: "audioInputChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Caps lock changed" - // object: Config.utilities.toasts - // setting: "capsLockChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Num lock changed" - // object: Config.utilities.toasts - // setting: "numLockChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Keyboard layout changed" - // object: Config.utilities.toasts - // setting: "kbLayoutChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "VPN changed" - // object: Config.utilities.toasts - // setting: "vpnChanged" - // } - // - // Separator { - // } - // - // SettingSwitch { - // name: "Now playing" - // object: Config.utilities.toasts - // setting: "nowPlaying" - // } - } - - // SettingsSection { - // sectionId: "VPN" - // - // SettingsHeader { - // name: "VPN" - // } - // - // SettingSwitch { - // name: "Enable VPN integration" - // object: Config.utilities.vpn - // setting: "enabled" - // } - // - // Separator { - // } - // - // SettingStringList { - // name: "Provider" - // addLabel: qsTr("Add VPN provider") - // object: Config.utilities.vpn - // setting: "provider" - // } - // } -} diff --git a/Modules/SettingsNew/Common/AudioDeviceList.qml b/Modules/Settings/Common/AudioDeviceList.qml similarity index 100% rename from Modules/SettingsNew/Common/AudioDeviceList.qml rename to Modules/Settings/Common/AudioDeviceList.qml diff --git a/Modules/SettingsNew/Common/BlobPopup.qml b/Modules/Settings/Common/BlobPopup.qml similarity index 100% rename from Modules/SettingsNew/Common/BlobPopup.qml rename to Modules/Settings/Common/BlobPopup.qml diff --git a/Modules/SettingsNew/Common/ConnectedRect.qml b/Modules/Settings/Common/ConnectedRect.qml similarity index 100% rename from Modules/SettingsNew/Common/ConnectedRect.qml rename to Modules/Settings/Common/ConnectedRect.qml diff --git a/Modules/SettingsNew/Common/InfoRow.qml b/Modules/Settings/Common/InfoRow.qml similarity index 100% rename from Modules/SettingsNew/Common/InfoRow.qml rename to Modules/Settings/Common/InfoRow.qml diff --git a/Modules/SettingsNew/Common/ItemList.qml b/Modules/Settings/Common/ItemList.qml similarity index 100% rename from Modules/SettingsNew/Common/ItemList.qml rename to Modules/Settings/Common/ItemList.qml diff --git a/Modules/SettingsNew/Common/NavRow.qml b/Modules/Settings/Common/NavRow.qml similarity index 100% rename from Modules/SettingsNew/Common/NavRow.qml rename to Modules/Settings/Common/NavRow.qml diff --git a/Modules/SettingsNew/Common/OverlayRow.qml b/Modules/Settings/Common/OverlayRow.qml similarity index 97% rename from Modules/SettingsNew/Common/OverlayRow.qml rename to Modules/Settings/Common/OverlayRow.qml index 34c2f8d..94d36c6 100644 --- a/Modules/SettingsNew/Common/OverlayRow.qml +++ b/Modules/Settings/Common/OverlayRow.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew +import qs.Modules.Settings ConnectedRect { id: root diff --git a/Modules/SettingsNew/Common/PageBase.qml b/Modules/Settings/Common/PageBase.qml similarity index 98% rename from Modules/SettingsNew/Common/PageBase.qml rename to Modules/Settings/Common/PageBase.qml index f3198c5..60d9088 100644 --- a/Modules/SettingsNew/Common/PageBase.qml +++ b/Modules/Settings/Common/PageBase.qml @@ -2,7 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts -import qs.Modules.SettingsNew +import qs.Modules.Settings import qs.Components import qs.Config diff --git a/Modules/SettingsNew/Common/PopupRow.qml b/Modules/Settings/Common/PopupRow.qml similarity index 100% rename from Modules/SettingsNew/Common/PopupRow.qml rename to Modules/Settings/Common/PopupRow.qml diff --git a/Modules/SettingsNew/Common/SectionHeader.qml b/Modules/Settings/Common/SectionHeader.qml similarity index 100% rename from Modules/SettingsNew/Common/SectionHeader.qml rename to Modules/Settings/Common/SectionHeader.qml diff --git a/Modules/SettingsNew/Common/SelectRow.qml b/Modules/Settings/Common/SelectRow.qml similarity index 100% rename from Modules/SettingsNew/Common/SelectRow.qml rename to Modules/Settings/Common/SelectRow.qml diff --git a/Modules/SettingsNew/Common/SliderRow.qml b/Modules/Settings/Common/SliderRow.qml similarity index 100% rename from Modules/SettingsNew/Common/SliderRow.qml rename to Modules/Settings/Common/SliderRow.qml diff --git a/Modules/SettingsNew/Common/SpinRow.qml b/Modules/Settings/Common/SpinRow.qml similarity index 100% rename from Modules/SettingsNew/Common/SpinRow.qml rename to Modules/Settings/Common/SpinRow.qml diff --git a/Modules/SettingsNew/Common/StackPage.qml b/Modules/Settings/Common/StackPage.qml similarity index 98% rename from Modules/SettingsNew/Common/StackPage.qml rename to Modules/Settings/Common/StackPage.qml index b1e6c8c..3bead7f 100644 --- a/Modules/SettingsNew/Common/StackPage.qml +++ b/Modules/Settings/Common/StackPage.qml @@ -1,7 +1,7 @@ import QtQuick import QtQuick.Controls import qs.Components -import qs.Modules.SettingsNew +import qs.Modules.Settings import qs.Config StackView { diff --git a/Modules/SettingsNew/Common/TimeInput.qml b/Modules/Settings/Common/TimeInput.qml similarity index 100% rename from Modules/SettingsNew/Common/TimeInput.qml rename to Modules/Settings/Common/TimeInput.qml diff --git a/Modules/SettingsNew/Common/ToggleRow.qml b/Modules/Settings/Common/ToggleRow.qml similarity index 100% rename from Modules/SettingsNew/Common/ToggleRow.qml rename to Modules/Settings/Common/ToggleRow.qml diff --git a/Modules/SettingsNew/Common/WallItem.qml b/Modules/Settings/Common/WallItem.qml similarity index 100% rename from Modules/SettingsNew/Common/WallItem.qml rename to Modules/Settings/Common/WallItem.qml diff --git a/Modules/SettingsNew/Common/WallpaperCropper.qml b/Modules/Settings/Common/WallpaperCropper.qml similarity index 100% rename from Modules/SettingsNew/Common/WallpaperCropper.qml rename to Modules/Settings/Common/WallpaperCropper.qml diff --git a/Modules/Settings/Content.qml b/Modules/Settings/Content.qml index dcca906..6230d21 100644 --- a/Modules/Settings/Content.qml +++ b/Modules/Settings/Content.qml @@ -1,398 +1,76 @@ pragma ComponentBehavior: Bound -import Quickshell -import Quickshell.Widgets import QtQuick -import QtQuick.Controls +import ZShell.Blobs import qs.Components -import qs.Modules as Modules -import qs.Modules.Settings.Categories as Cat import qs.Config -import qs.Helpers -Item { +CustomClippingRect { id: root - property string currentCategory: "general" - property int currentIndex: 0 - property int lastIndex: 0 - readonly property real nonAnimHeight: Math.floor(screen.height / 1.5) + viewWrapper.anchors.margins * 2 - readonly property real nonAnimWidth: view.implicitWidth + Math.floor(screen.width / 2) + viewWrapper.anchors.margins * 2 - property string pendingSection: "" - property string pendingSetting: "" - required property ShellScreen screen - required property PersistentProperties visibilities + property color blobColor: DynamicColors.tPalette.m3surfaceContainerLow + readonly property real ratio: 16.0 / 9.0 + readonly property SettingsState sState: SettingsState { + id: sState - function scrollToSetting(section: string, settingName: string) { - root.pendingSection = section; - root.pendingSetting = settingName; - scrollTimer.restart(); + onClose: root.close() } - function setCategory(category: string, index: int): void { - currentIndex = index; - currentCategory = category; - } + signal close - implicitHeight: nonAnimHeight - implicitWidth: nonAnimWidth + implicitHeight: sState.screen.height * 0.7 + implicitWidth: implicitHeight * ratio + radius: Appearance.rounding.large + Appearance.padding.normal - Timer { - id: scrollTimer - - interval: 50 - - onTriggered: { - if (root.pendingSection && stack.currentItem) { - stack.currentItem.scrollToSectionAndHighlight(root.pendingSection, root.pendingSetting); - root.pendingSection = ""; - root.pendingSetting = ""; - } + Behavior on blobColor { + CAnim { } } - Connections { - function onCurrentCategoryChanged() { - if (root.currentCategory === "general") { - stack.replaceCurrentItem(general, [], StackView.PopTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "wallpaper") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(background, [], StackView.PopTransition); - else - stack.replaceCurrentItem(background, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "bar") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(bar, [], StackView.PopTransition); - else - stack.replaceCurrentItem(bar, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "appearance") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(appearance, [], StackView.PopTransition); - else - stack.replaceCurrentItem(appearance, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "lockscreen") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(lockscreen, [], StackView.PopTransition); - else - stack.replaceCurrentItem(lockscreen, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "services") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(services, [], StackView.PopTransition); - else - stack.replaceCurrentItem(services, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "notifications") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(notifications, [], StackView.PopTransition); - else - stack.replaceCurrentItem(notifications, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "sidebar") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(sidebar, [], StackView.PopTransition); - else - stack.replaceCurrentItem(sidebar, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "utilities") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(utilities, [], StackView.PopTransition); - else - stack.replaceCurrentItem(utilities, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "dashboard") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(dashboard, [], StackView.PopTransition); - else - stack.replaceCurrentItem(dashboard, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "osd") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(osd, [], StackView.PopTransition); - else - stack.replaceCurrentItem(osd, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "launcher") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(launcher, [], StackView.PopTransition); - else - stack.replaceCurrentItem(launcher, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "screenshot") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(screenshot, [], StackView.PopTransition); - else - stack.replaceCurrentItem(screenshot, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } else if (root.currentCategory === "updates") { - if (root.currentIndex < root.lastIndex) - stack.replaceCurrentItem(updates, [], StackView.PopTransition); - else - stack.replaceCurrentItem(updates, [], StackView.PushTransition); - root.lastIndex = root.currentIndex; - } - } - - target: root + TapHandler { + onTapped: root.focus = true } - CustomClippingRect { - id: viewWrapper + BlobGroup { + id: blobGroup + color: root.blobColor + smoothing: Appearance.rounding.normal + } + + BlobInvertedRect { anchors.fill: parent - anchors.margins: Appearance.padding.smaller - radius: Appearance.rounding.large - Appearance.padding.smaller - - SettingsSearch { - id: searchBar - - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - - onSettingSelected: (category, section, settingName) => { - layout.selectCategory(category); - root.scrollToSetting(section, settingName); - } - } - - Item { - id: view - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.top: searchBar.bottom - anchors.topMargin: Appearance.spacing.smaller - implicitWidth: layout.implicitWidth - - Categories { - id: layout - - anchors.fill: parent - content: root - - onSettingSelected: (category, section, settingName) => { - root.scrollToSetting(section, settingName); - } - } - } - - CustomClippingRect { - id: categoryContent - - anchors.bottom: parent.bottom - anchors.left: view.right - anchors.leftMargin: Appearance.spacing.smaller - anchors.right: parent.right - anchors.top: searchBar.bottom - anchors.topMargin: Appearance.spacing.smaller - color: DynamicColors.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - - StackView { - id: stack - - anchors.fill: parent - initialItem: general - - popEnter: Transition { - SequentialAnimation { - Anim { - duration: 0 - from: 0 - property: "opacity" - to: 0 - } - - PauseAnimation { - duration: Appearance.anim.durations.expressiveEffects - } - - ParallelAnimation { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 0 - property: "opacity" - to: 1 - } - - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: -50 - property: "y" - to: 0 - } - } - } - } - popExit: Transition { - ParallelAnimation { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 1 - property: "opacity" - to: 0 - } - - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 0 - property: "y" - to: 50 - } - } - } - pushEnter: Transition { - SequentialAnimation { - Anim { - duration: 0 - from: 0 - property: "opacity" - to: 0 - } - - PauseAnimation { - duration: Appearance.anim.durations.expressiveEffects - } - - ParallelAnimation { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 0 - property: "opacity" - to: 1 - } - - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 50 - property: "y" - to: 0 - } - } - } - } - pushExit: Transition { - ParallelAnimation { - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 1 - property: "opacity" - to: 0 - } - - Anim { - duration: Appearance.anim.durations.expressiveFastSpatial - from: 0 - property: "y" - to: -50 - } - } - } - } - } + borderBottom: Appearance.padding.normal + borderLeft: navPane.width + navPane.anchors.margins * 2 + borderRight: Appearance.padding.normal + borderTop: Appearance.padding.normal + group: blobGroup + opacity: root.blobColor.a + radius: Appearance.rounding.large } - Component { - id: general + NavPane { + id: navPane - Cat.General { - } + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.margins: Appearance.padding.large + anchors.top: parent.top + sState: root.sState + width: Math.min(600, Math.round(root.width / 3)) } - Component { - id: background - - Cat.Background { - screen: root.screen - } + Pages { + anchors.bottom: parent.bottom + anchors.left: navPane.right + anchors.leftMargin: navPane.anchors.margins + anchors.margins + anchors.margins: Appearance.padding.extraLarge + anchors.right: parent.right + anchors.top: parent.top + sState: root.sState } - Component { - id: appearance - - Cat.Appearance { - } - } - - Component { - id: bar - - Cat.Bar { - } - } - - Component { - id: lockscreen - - Cat.Lockscreen { - } - } - - Component { - id: services - - Cat.Services { - } - } - - Component { - id: notifications - - Cat.Notifications { - } - } - - Component { - id: sidebar - - Cat.Sidebar { - } - } - - Component { - id: utilities - - Cat.Utilities { - } - } - - Component { - id: dashboard - - Cat.Dashboard { - } - } - - Component { - id: osd - - Cat.Osd { - } - } - - Component { - id: launcher - - Cat.Launcher { - } - } - - Component { - id: screenshot - - Cat.Screenshot { - } - } - - Component { - id: updates - - Cat.SystemUpdates { - } + PopupOverlay { + anchors.fill: parent } } diff --git a/Modules/Settings/Controls/HyprTimeInput.qml b/Modules/Settings/Controls/HyprTimeInput.qml deleted file mode 100644 index 661fda9..0000000 --- a/Modules/Settings/Controls/HyprTimeInput.qml +++ /dev/null @@ -1,660 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Config -import qs.Components -import qs.Helpers - -Item { - id: root - - readonly property string endTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[2]]); - return Qt.formatTime(d, "hh:mm AP"); - } - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - readonly property string startTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[1]]); - return Qt.formatTime(d, "hh:mm AP"); - } - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - Hyprsunset.checkStartup(); - } - - function convertHour(timeValue: int): int { - return Math.floor(timeValue / 60); - } - - function convertMinute(timeValue: int): int { - return timeValue % 60; - } - - function convertToMinutes(hour: int, minute: int): int { - return hour * 60 + minute; - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal) - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime) - wrapMode: Text.WordWrap - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.fillWidth: true - - RowLayout { - CustomText { - Layout.preferredWidth: spacer.x + spacer.width - text: qsTr("Start") - } - - CustomText { - Layout.preferredWidth: endMinuteRect.width + endHourRect.width - text: qsTr("End") - } - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[0]] - - onToggled: { - root.object[root.settings[0]] = checked; - Config.save(); - } - } - } - - RowLayout { - Layout.fillHeight: true - - CustomRect { - id: startHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: startMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.spacing.large - } - - CustomRect { - id: endHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: endMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - id: endMinuteField - - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]); - } - onTextEdited: { - if (endMinuteField.text === "") - 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; - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - id: startHour - - Layout.preferredWidth: startSeparator.x + startSeparator.width - text: qsTr("Hour") - } - - CustomText { - id: startMinute - - Layout.preferredWidth: spacer.x + spacer.width - x - text: qsTr("Minute") - } - - CustomText { - Layout.preferredWidth: endSeparator.x + endSeparator.width - x - text: qsTr("Hour") - } - - CustomText { - text: qsTr("Minute") - } - } - } - } -} diff --git a/Modules/Settings/Controls/SchemesListView.qml b/Modules/Settings/Controls/SchemesListView.qml deleted file mode 100644 index ae4f558..0000000 --- a/Modules/Settings/Controls/SchemesListView.qml +++ /dev/null @@ -1,161 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick -import QtQuick.Layouts -import qs.Config -import qs.Components - -Item { - id: root - - required property string name - required property var object - property alias row: row - required property string setting - property bool shouldBeActive: true - required property list stringList - - signal optionSet(option: string) - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.height : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomClippingRect { - Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5 - Layout.preferredWidth: 500 - color: DynamicColors.tPalette.m3surfaceContainer - radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale - - CustomRect { - id: searchBox - - anchors.left: parent.left - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.top: parent.top - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: 42 - radius: Appearance.rounding.full - - MaterialIcon { - id: searchIcon - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.leftMargin: Appearance.padding.large - anchors.top: parent.top - font.pointSize: Appearance.font.size.large - text: "search" - verticalAlignment: Text.AlignVCenter - } - - CustomTextField { - id: textSearch - - anchors.left: searchIcon.right - anchors.leftMargin: Appearance.spacing.small - anchors.right: parent.right - anchors.rightMargin: Appearance.spacing.normal - anchors.verticalCenter: parent.verticalCenter - placeholderText: "Search..." - } - } - - CustomClippingRect { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.top: searchBox.bottom - bottomLeftRadius: 21 - bottomRightRadius: 21 - - CustomListView { - anchors.fill: parent - clip: true - spacing: Appearance.spacing.small - - delegate: CustomRect { - id: delegate - - required property string modelData - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: 42 - radius: Appearance.rounding.smallest - - CustomText { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - text: modelData - verticalAlignment: Text.AlignVCenter - } - - MaterialIcon { - anchors.fill: parent - anchors.rightMargin: Appearance.padding.normal - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignRight - text: "check_circle" - verticalAlignment: Text.AlignVCenter - visible: root.object[root.setting] === delegate.modelData - } - - StateLayer { - onClicked: { - root.object[root.setting] = delegate.modelData; - root.optionSet(delegate.modelData); - Config.save(); - } - } - } - model: ScriptModel { - values: { - const values = root.stringList; - const search = textSearch.text; - var regex = new RegExp(search, "i"); - - return values.filter(n => regex.test(n)); - } - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/Separator.qml b/Modules/Settings/Controls/Separator.qml deleted file mode 100644 index f699e20..0000000 --- a/Modules/Settings/Controls/Separator.qml +++ /dev/null @@ -1,31 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -CustomRect { - id: root - - property bool shouldBeActive: true - - anchors.left: parent.left - anchors.right: parent.right - color: DynamicColors.tPalette.m3outlineVariant - implicitHeight: shouldBeActive ? 1 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } -} diff --git a/Modules/Settings/Controls/SettingActionList.qml b/Modules/Settings/Controls/SettingActionList.qml deleted file mode 100644 index 373e021..0000000 --- a/Modules/Settings/Controls/SettingActionList.qml +++ /dev/null @@ -1,282 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -ColumnLayout { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - - function addAction() { - const list = [...root.object[root.setting]]; - list.push({ - name: "New Action", - icon: "bolt", - description: "", - command: [], - enabled: true, - dangerous: false - }); - root.object[root.setting] = list; - Config.save(); - } - - function removeAction(index) { - const list = [...root.object[root.setting]]; - list.splice(index, 1); - root.object[root.setting] = list; - Config.save(); - } - - function updateAction(index, key, value) { - const list = [...root.object[root.setting]]; - const entry = list[index]; - entry[key] = value; - list[index] = entry; - root.object[root.setting] = list; - Config.save(); - } - - anchors.left: parent.left - anchors.right: parent.right - height: shouldBeActive ? implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - spacing: Appearance.spacing.smaller - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - z: -1 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - Repeater { - model: [...root.object[root.setting]] - - CustomRect { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: layout.implicitHeight + Appearance.padding.normal * 2 - color: DynamicColors.tPalette.m3surfaceContainer - radius: Appearance.rounding.normal - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: modelData.name ?? qsTr("Action") - } - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "delete" - type: IconButton.Tonal - - onClicked: root.removeAction(index) - } - } - - Separator { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Name") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: 350 - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: modelData.name ?? "" - - onEditingFinished: root.updateAction(index, "name", text) - } - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Icon") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: 350 - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: modelData.icon ?? "" - - onEditingFinished: root.updateAction(index, "icon", text) - } - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Description") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: 350 - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: modelData.description ?? "" - - onEditingFinished: root.updateAction(index, "description", text) - } - } - } - - StringListEditor { - Layout.fillWidth: true - addLabel: qsTr("Add command argument") - anchors.left: undefined - anchors.right: undefined - values: [...(modelData.command ?? [])] - - onListEdited: function (values) { - root.updateAction(index, "command", values); - } - } - - Separator { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Enabled") - } - - CustomSwitch { - checked: modelData.enabled ?? true - - onToggled: root.updateAction(index, "enabled", checked) - } - } - - Separator { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Dangerous") - } - - CustomSwitch { - checked: modelData.dangerous ?? false - - onToggled: root.updateAction(index, "dangerous", checked) - } - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addAction() - } - - CustomText { - Layout.fillWidth: true - text: qsTr("Add action") - } - } -} diff --git a/Modules/Settings/Controls/SettingAliasList.qml b/Modules/Settings/Controls/SettingAliasList.qml deleted file mode 100644 index 908dc29..0000000 --- a/Modules/Settings/Controls/SettingAliasList.qml +++ /dev/null @@ -1,203 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -CustomRect { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - - function addAlias() { - const list = [...root.object[root.setting]]; - list.push({ - from: "", - to: "" - }); - root.object[root.setting] = list; - Config.save(); - } - - function removeAlias(index) { - const list = [...root.object[root.setting]]; - list.splice(index, 1); - root.object[root.setting] = list; - Config.save(); - } - - function updateAlias(index, key, value) { - const list = [...root.object[root.setting]]; - const entry = [...list[index]]; - entry[key] = value; - list[index] = entry; - root.object[root.setting] = list; - Config.save(); - } - - anchors.left: parent.left - anchors.right: parent.right - height: shouldBeActive ? layout.implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - z: -1 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.smaller - - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - Repeater { - model: [...root.object[root.setting]] - - Item { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2 - - CustomRect { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: -(Appearance.spacing.smaller / 2) - color: DynamicColors.tPalette.m3outlineVariant - implicitHeight: 1 - visible: index !== 0 - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.small - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("From") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50) - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - id: fromTextField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550) - text: modelData.from ?? "" - - onEditingFinished: root.updateAlias(index, "from", text) - } - } - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "delete" - type: IconButton.Tonal - - onClicked: root.removeAlias(index) - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("To") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50) - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - id: toTextField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550) - text: modelData.to ?? "" - - onEditingFinished: root.updateAlias(index, "to", text) - } - } - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addAlias() - } - - CustomText { - Layout.fillWidth: true - text: qsTr("Add alias") - } - } - } -} diff --git a/Modules/Settings/Controls/SettingBarEntryList.qml b/Modules/Settings/Controls/SettingBarEntryList.qml deleted file mode 100644 index 6f29eea..0000000 --- a/Modules/Settings/Controls/SettingBarEntryList.qml +++ /dev/null @@ -1,241 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick -import QtQuick.Layouts -import QtQml.Models -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - property bool dragActive: false - property real dragHeight: 0 - property real dragStartX: 0 - property real dragStartY: 0 - property real dragX: 0 - property real dragY: 0 - property var draggedModelData: null - property string draggedUid: "" - property bool dropAnimating: false - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - property var pendingCommitEntries: [] - required property string setting - property bool shouldBeActive: true - property int uidCounter: 0 - property var visualEntries: [] - - function ensureVisualEntries() { - if (!root.dragActive && !root.dropAnimating) - root.rebuildVisualEntries(); - } - - function iconForId(id) { - switch (id) { - case "workspaces": - return "dashboard"; - case "audio": - return "volume_up"; - case "media": - return "play_arrow"; - case "resources": - return "monitoring"; - case "updates": - return "system_update"; - case "dash": - return "space_dashboard"; - case "spacer": - return "horizontal_rule"; - case "activeWindow": - return "web_asset"; - case "tray": - return "widgets"; - case "upower": - return "battery_full"; - case "network": - return "wifi"; - case "clock": - return "schedule"; - case "notifBell": - return "notifications"; - default: - return "drag_indicator"; - } - } - - function indexForUid(uid) { - for (let i = 0; i < root.visualEntries.length; i++) { - if (root.visualEntries[i].uid === uid) - return i; - } - - return -1; - } - - function labelForId(id) { - switch (id) { - case "workspaces": - return qsTr("Workspaces"); - case "audio": - return qsTr("Audio"); - case "media": - return qsTr("Media"); - case "resources": - return qsTr("Resources"); - case "updates": - return qsTr("Updates"); - case "dash": - return qsTr("Dash"); - case "spacer": - return qsTr("Spacer"); - case "activeWindow": - return qsTr("Title"); - case "tray": - return qsTr("Tray"); - case "upower": - return qsTr("Power"); - case "network": - return qsTr("Network"); - case "clock": - return qsTr("Clock"); - case "notifBell": - return qsTr("Notifs"); - case "hyprsunset": - return qsTr("Night light"); - default: - return id; - } - } - - function rebuildVisualEntries() { - const entries = root.object[root.setting] ?? []; - const next = []; - - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - let existing = null; - - for (let j = 0; j < root.visualEntries.length; j++) { - if (root.visualEntries[j].entry === entry) { - existing = root.visualEntries[j]; - break; - } - } - - if (existing) - next.push(existing); - else - next.push({ - uid: `entry-${root.uidCounter++}`, - entry - }); - } - - root.visualEntries = next; - } - - function updateEntry(index, value) { - const list = [...root.object[root.setting]]; - const entry = list[index]; - entry.enabled = value; - list[index] = entry; - root.object[root.setting] = list; - Config.save(); - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? layout.implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Component.onCompleted: root.rebuildVisualEntries() - - CustomRect { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - z: -1 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: layout - - anchors.fill: parent - - // spacing: Appearance.spacing.smaller - - DelegateModel { - id: visualModel - - delegate: entryDelegate - - model: ScriptModel { - objectProp: "uid" - values: root.visualEntries - } - } - - Repeater { - delegate: entryDelegate - model: visualModel - } - } - - Component { - id: entryDelegate - - IconButton { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0) - checked: modelData.entry.enabled ?? true - font: Appearance.font.family.sans - // icon: root.iconForId(modelData.entry.id) - icon: root.labelForId(modelData.entry.id) - inactiveColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) - isToggle: true - radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8 - radiusAnim.duration: MaterialEasing.expressiveEffectsTime - radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects - visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix)) - - Behavior on Layout.preferredWidth { - Anim { - duration: MaterialEasing.expressiveEffectsTime - easing.bezierCurve: MaterialEasing.expressiveEffects - } - } - - onClicked: root.updateEntry(index, internalChecked) - } - } -} diff --git a/Modules/Settings/Controls/SettingHyprSpinner.qml b/Modules/Settings/Controls/SettingHyprSpinner.qml deleted file mode 100644 index e18016f..0000000 --- a/Modules/Settings/Controls/SettingHyprSpinner.qml +++ /dev/null @@ -1,214 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - } - - function formattedValue(setting: string): string { - const value = root.object[setting]; - - if (value === null || value === undefined) - return ""; - - return String(value); - } - - function hourToAmPm(hour) { - var h = Number(hour) % 24; - var d = new Date(2000, 0, 1, h, 0, 0); - return Qt.formatTime(d, "h AP"); - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]])) - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.preferredWidth: 100 - - RowLayout { - Layout.preferredWidth: optionLayout.width - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[2]] - - onToggled: { - root.object[root.settings[2]] = checked; - Config.save(); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting2.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Start: ") - } - - SpinnerButton { - id: setting1 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[0]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[0]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[0]); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting1.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("End: ") - } - - SpinnerButton { - id: setting2 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[1]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[1]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[1]); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: -2 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Temp: ") - } - - CustomRect { - id: rect - - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.normal * 2, 200), 50) - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - id: textField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 200) - text: root.formattedValue(root.settings[3]) - - onEditingFinished: { - root.object[root.settings[3]] = textField.text; - Config.save(); - } - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/SettingInput.qml b/Modules/Settings/Controls/SettingInput.qml deleted file mode 100644 index 9ca5032..0000000 --- a/Modules/Settings/Controls/SettingInput.qml +++ /dev/null @@ -1,99 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - - function formattedValue(): string { - const value = root.object[root.setting]; - - if (value === null || value === undefined) - return ""; - - return String(value); - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomRect { - id: rect - - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(textField.contentWidth + Appearance.padding.large * 2, 550), 50) - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - id: textField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550) - text: root.formattedValue() - - onEditingFinished: { - root.object[root.setting] = textField.text; - Config.save(); - } - } - } - } -} diff --git a/Modules/Settings/Controls/SettingList.qml b/Modules/Settings/Controls/SettingList.qml deleted file mode 100644 index fad061f..0000000 --- a/Modules/Settings/Controls/SettingList.qml +++ /dev/null @@ -1,297 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -Item { - id: root - - required property int index - required property var modelData - property bool shouldBeActive: true - - signal addActiveActionRequested - signal deleteRequested(int index) - signal fieldEdited(string key, var value) - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - CustomRect { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: -(Appearance.spacing.smaller / 2) - color: DynamicColors.tPalette.m3outlineVariant - implicitHeight: 1 - visible: root.index !== 0 - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.large - - Item { - id: nameCell - - property string draftName: "" - property bool editing: false - - function beginEdit() { - draftName = root.modelData.name ?? ""; - editing = true; - nameEditor.forceActiveFocus(); - } - - function cancelEdit() { - draftName = root.modelData.name ?? ""; - editing = false; - } - - function commitEdit() { - editing = false; - - if (draftName !== (root.modelData.name ?? "")) { - root.fieldEdited("name", draftName); - } - } - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillHeight: true - Layout.preferredWidth: root.width / 2 - - HoverHandler { - id: nameHover - } - - HoverIconButton { - id: editButton - - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - font.pointSize: Appearance.font.size.large - icon: "edit" - shouldBeVisible: nameHover.hovered && !nameCell.editing - - onClicked: nameCell.beginEdit() - } - - CustomText { - anchors.left: parent.left - anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0 - anchors.right: deleteButton.left - anchors.rightMargin: Appearance.spacing.small - anchors.verticalCenter: parent.verticalCenter - elide: Text.ElideRight // enable if CustomText supports it - font.pointSize: Appearance.font.size.larger - text: root.modelData.name - visible: !nameCell.editing - - Behavior on anchors.leftMargin { - Anim { - } - } - } - - HoverIconButton { - id: deleteButton - - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - font.pointSize: Appearance.font.size.large - icon: "delete" - shouldBeVisible: nameHover.hovered && !nameCell.editing - - onClicked: root.deleteRequested(root.index) - } - - CustomRect { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - color: DynamicColors.tPalette.m3surface - implicitHeight: nameEditor.implicitHeight + (Appearance.padding.normal * 2) - implicitWidth: Math.min(nameEditor.contentWidth + (Appearance.padding.normal * 2), parent.width - Appearance.padding.normal) - radius: Appearance.rounding.small - visible: nameCell.editing - - CustomTextField { - id: nameEditor - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal) - text: nameCell.draftName - - Keys.onEscapePressed: { - nameCell.cancelEdit(); - } - onEditingFinished: { - nameCell.commitEdit(); - } - onTextEdited: { - nameCell.draftName = nameEditor.text; - } - } - } - } - - VSeparator { - } - - ColumnLayout { - id: cLayout - - Layout.fillWidth: true - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Timeout") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(timeField.contentWidth + Appearance.padding.normal * 3, 200), 50) - color: DynamicColors.tPalette.m3surface - radius: Appearance.rounding.small - - CustomTextField { - id: timeField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: String(root.modelData.timeout ?? "") - - onEditingFinished: { - root.fieldEdited("timeout", Number(text)); - } - } - } - } - - Separator { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - Layout.fillWidth: true - text: qsTr("Idle Action") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(idleField.contentWidth + Appearance.padding.normal * 3, 200), 50) - color: DynamicColors.tPalette.m3surface - radius: Appearance.rounding.small - - CustomTextField { - id: idleField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: root.modelData.idleAction ?? "" - - onEditingFinished: { - root.fieldEdited("idleAction", text); - } - } - } - } - - Separator { - Layout.fillWidth: true - anchors.left: undefined - anchors.right: undefined - } - - Item { - Layout.fillWidth: true - implicitHeight: activeActionRow.visible ? activeActionRow.implicitHeight : addButtonRow.implicitHeight - - RowLayout { - id: activeActionRow - - anchors.left: parent.left - anchors.right: parent.right - visible: root.modelData.activeAction !== undefined - - CustomText { - Layout.fillWidth: true - text: qsTr("Active Action") - } - - CustomRect { - Layout.preferredHeight: 33 - Layout.preferredWidth: Math.max(Math.min(actionField.contentWidth + Appearance.padding.normal * 3, 200), 50) - color: DynamicColors.tPalette.m3surface - radius: Appearance.rounding.small - - CustomTextField { - id: actionField - - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: root.modelData.activeAction ?? "" - - onEditingFinished: { - root.fieldEdited("activeAction", text); - } - } - } - } - - RowLayout { - id: addButtonRow - - anchors.left: parent.left - anchors.right: parent.right - visible: root.modelData.activeAction === undefined - - IconButton { - id: button - - Layout.alignment: Qt.AlignLeft - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addActiveActionRequested() - } - - CustomText { - Layout.alignment: Qt.AlignLeft - text: qsTr("Add active action") - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/SettingListView.qml b/Modules/Settings/Controls/SettingListView.qml deleted file mode 100644 index 213b0af..0000000 --- a/Modules/Settings/Controls/SettingListView.qml +++ /dev/null @@ -1,163 +0,0 @@ -pragma ComponentBehavior: Bound - -import Quickshell -import QtQuick -import QtQuick.Layouts -import qs.Config -import qs.Components - -Item { - id: root - - required property string name - required property var object - property alias row: row - required property string setting - property bool shouldBeActive: true - required property list stringList - - signal optionSet - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.height : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomClippingRect { - id: fontArea - - Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5 - Layout.preferredWidth: 500 - color: DynamicColors.tPalette.m3surfaceContainer - radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale - - CustomRect { - id: searchBox - - anchors.left: parent.left - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.top: parent.top - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: 42 - radius: Appearance.rounding.full - - MaterialIcon { - id: searchIcon - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.leftMargin: Appearance.padding.large - anchors.top: parent.top - font.pointSize: Appearance.font.size.large - text: "search" - verticalAlignment: Text.AlignVCenter - } - - CustomTextField { - id: fontSearch - - anchors.left: searchIcon.right - anchors.leftMargin: Appearance.spacing.small - anchors.right: parent.right - anchors.rightMargin: Appearance.spacing.normal - anchors.verticalCenter: parent.verticalCenter - placeholderText: "Search..." - } - } - - CustomClippingRect { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.top: searchBox.bottom - bottomLeftRadius: 21 - bottomRightRadius: 21 - - CustomListView { - anchors.fill: parent - clip: true - spacing: Appearance.spacing.small - - delegate: CustomRect { - id: fontDelegate - - required property string modelData - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: 42 - radius: Appearance.rounding.smallest - - CustomText { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - text: modelData - verticalAlignment: Text.AlignVCenter - } - - MaterialIcon { - anchors.fill: parent - anchors.rightMargin: Appearance.padding.normal - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - horizontalAlignment: Text.AlignRight - text: "check_circle" - verticalAlignment: Text.AlignVCenter - visible: root.object[root.setting] === fontDelegate.modelData - } - - StateLayer { - onClicked: { - root.object[root.setting] = fontDelegate.modelData; - root.optionSet(); - Config.save(); - } - } - } - model: ScriptModel { - values: { - const values = root.stringList; - const search = fontSearch.text; - var regex = new RegExp(search, "i"); - - return values.filter(n => regex.test(n)); - } - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/SettingReadOnly.qml b/Modules/Settings/Controls/SettingReadOnly.qml deleted file mode 100644 index 992cc45..0000000 --- a/Modules/Settings/Controls/SettingReadOnly.qml +++ /dev/null @@ -1,70 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - property bool shouldBeActive: true - required property string value - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - color: DynamicColors.palette.m3onSurfaceVariant - font.family: Appearance.font.family.mono - font.pointSize: Appearance.font.size.normal - text: root.value - } - } -} diff --git a/Modules/Settings/Controls/SettingSpinBox.qml b/Modules/Settings/Controls/SettingSpinBox.qml deleted file mode 100644 index 4ce4c76..0000000 --- a/Modules/Settings/Controls/SettingSpinBox.qml +++ /dev/null @@ -1,81 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - property real max: Infinity - property real min: -Infinity - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - property real step: 1 - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomSpinBox { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - max: root.max - min: root.min - step: root.step - value: Number(root.object[root.setting] ?? 0) - - onValueModified: function (value) { - root.object[root.setting] = value; - Config.save(); - } - } - } -} diff --git a/Modules/Settings/Controls/SettingSpinner.qml b/Modules/Settings/Controls/SettingSpinner.qml deleted file mode 100644 index cff00a4..0000000 --- a/Modules/Settings/Controls/SettingSpinner.qml +++ /dev/null @@ -1,180 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - } - - function formattedValue(setting: string): string { - const value = root.object[setting]; - - if (value === null || value === undefined) - return ""; - - return String(value); - } - - function hourToAmPm(hour) { - var h = Number(hour) % 24; - var d = new Date(2000, 0, 1, h, 0, 0); - return Qt.formatTime(d, "h AP"); - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(root.hourToAmPm(root.object[root.settings[0]])).arg(root.hourToAmPm(root.object[root.settings[1]])) - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.preferredWidth: 100 - - RowLayout { - Layout.preferredWidth: optionLayout.width - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[2]] - - onToggled: { - root.object[root.settings[2]] = checked; - Config.save(); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting2.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("Start: ") - } - - SpinnerButton { - id: setting1 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[0]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[0]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[0]); - } - } - } - - RowLayout { - Layout.preferredWidth: optionLayout.width - z: setting1.expanded ? -1 : 1 - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - Layout.fillWidth: true - text: qsTr("End: ") - } - - SpinnerButton { - id: setting2 - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - Layout.preferredHeight: Appearance.font.size.large + Appearance.padding.smaller * 2 - Layout.preferredWidth: height * 2 - currentIndex: root.object[root.settings[1]] - enabled: enabledSwitch.checked - text: root.formattedValue(root.settings[1]) - - menu.onItemSelected: item => { - root.commitChoice(item, root.settings[1]); - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/SettingStringList.qml b/Modules/Settings/Controls/SettingStringList.qml deleted file mode 100644 index 7081d2e..0000000 --- a/Modules/Settings/Controls/SettingStringList.qml +++ /dev/null @@ -1,78 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - property string addLabel: qsTr("Add entry") - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? layout.implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - ColumnLayout { - id: layout - - anchors.left: parent.left - anchors.right: parent.right - spacing: Appearance.spacing.small - - CustomText { - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - StringListEditor { - Layout.fillWidth: true - addLabel: root.addLabel - anchors.left: undefined - anchors.right: undefined - anchors.verticalCenter: undefined - values: [...(root.object[root.setting] ?? [])] - - onListEdited: function (values) { - root.object[root.setting] = values; - Config.save(); - } - } - } -} diff --git a/Modules/Settings/Controls/SettingSwitch.qml b/Modules/Settings/Controls/SettingSwitch.qml deleted file mode 100644 index 09d4f92..0000000 --- a/Modules/Settings/Controls/SettingSwitch.qml +++ /dev/null @@ -1,79 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property string setting - property bool shouldBeActive: true - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomSwitch { - id: cswitch - - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - checked: root.object[root.setting] - - onToggled: { - root.object[root.setting] = checked; - Config.save(); - } - } - } -} diff --git a/Modules/Settings/Controls/SettingsHeader.qml b/Modules/Settings/Controls/SettingsHeader.qml deleted file mode 100644 index 42d3d95..0000000 --- a/Modules/Settings/Controls/SettingsHeader.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -CustomRect { - id: root - - required property string name - property bool shouldBeActive: true - - implicitHeight: 60 - implicitWidth: 200 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - CustomText { - anchors.fill: parent - font.bold: true - font.pointSize: Appearance.font.size.large * 2 - text: root.name - verticalAlignment: Text.AlignVCenter - } -} diff --git a/Modules/Settings/Controls/SettingsIconButton.qml b/Modules/Settings/Controls/SettingsIconButton.qml deleted file mode 100644 index 5f6673d..0000000 --- a/Modules/Settings/Controls/SettingsIconButton.qml +++ /dev/null @@ -1,83 +0,0 @@ -import Quickshell -import QtQuick -import QtQuick.Layouts -import qs.Paths -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - property alias button: iButton - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - property bool shouldBeActive: true - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - IconButton { - id: iButton - - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - icon: "download" - - onClicked: { - const lockBg = `${Paths.state}/lockscreen_bg.png`; - const scheme = `${Paths.state}/scheme.json`; - const face = `${Paths.home}/.face`; - const destination = "/etc/zshell-greeter/images"; - Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]); - } - } - } -} diff --git a/Modules/Settings/Controls/SettingsPage.qml b/Modules/Settings/Controls/SettingsPage.qml deleted file mode 100644 index e80cb54..0000000 --- a/Modules/Settings/Controls/SettingsPage.qml +++ /dev/null @@ -1,76 +0,0 @@ -import QtQuick -import QtQuick.Controls -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - default property alias contentData: clayout.data - - function scrollToSectionAndHighlight(sectionId: string, settingName: string): bool { - for (let i = 0; i < clayout.children.length; i++) { - const section = clayout.children[i]; - if (section.sectionId === sectionId) { - const targetY = section.y - Appearance.padding.normal; - flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height)); - - SettingsHighlight.highlight(settingName); - return true; - } - } - return false; - } - - CustomFlickable { - id: flickable - - anchors.fill: parent - // for future: - // anchors.leftMargin: Appearance.padding.extraLarge - // anchors.rightMargin: Appearance.padding.extraLarge - clip: true - contentHeight: clayout.implicitHeight + clayout.anchors.margins * 2 - - CustomScrollBar.vertical: CustomScrollBar { - flickable: flickable - } - - TapHandler { - acceptedButtons: Qt.LeftButton - - onTapped: function (eventPoint) { - const menu = SettingsDropdowns.activeMenu; - if (!menu) - return; - - const p = eventPoint.scenePosition; - - if (SettingsDropdowns.hit(SettingsDropdowns.activeTrigger, p)) - return; - - if (SettingsDropdowns.hit(menu, p)) - return; - - SettingsDropdowns.closeActive(); - } - } - - Column { - id: clayout - - anchors.left: parent.left - anchors.margins: Appearance.padding.extraSmall - anchors.right: parent.right - anchors.top: parent.top - spacing: Appearance.spacing.small - - // move: Transition { - // Anim { - // properties: "y" - // } - // } - } - } -} diff --git a/Modules/Settings/Controls/SettingsSection.qml b/Modules/Settings/Controls/SettingsSection.qml deleted file mode 100644 index 5ed5d81..0000000 --- a/Modules/Settings/Controls/SettingsSection.qml +++ /dev/null @@ -1,48 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -CustomRect { - id: root - - default property alias contentData: layout.data - property real contentPadding: Appearance.padding.large - property string sectionId: "" - - anchors.left: parent.left - anchors.right: parent.right - color: DynamicColors.tPalette.m3surfaceContainer - implicitHeight: layout.height + contentPadding * 2 - radius: Appearance.rounding.normal - Appearance.padding.extraSmall - - Behavior on implicitHeight { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Column { - id: layout - - anchors.left: parent.left - anchors.margins: root.contentPadding - anchors.right: parent.right - anchors.top: parent.top - // anchors.verticalCenter: parent.verticalCenter - spacing: Appearance.spacing.normal - - Behavior on height { - Anim { - } - } - move: Transition { - Anim { - properties: "y" - } - } - } -} diff --git a/Modules/Settings/Controls/SpinnerButton.qml b/Modules/Settings/Controls/SpinnerButton.qml deleted file mode 100644 index 7aedaa1..0000000 --- a/Modules/Settings/Controls/SpinnerButton.qml +++ /dev/null @@ -1,63 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Helpers -import qs.Config - -CustomRect { - id: root - - property alias currentIndex: menu.currentIndex - property bool enabled - property alias expanded: menu.expanded - property alias label: label - property alias menu: menu - property bool shouldBeActive: true - property alias text: label.text - - color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2) - opacity: shouldBeActive ? 1 : 0 - radius: Appearance.rounding.full - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - z: expanded ? 100 : 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - CustomText { - id: label - - anchors.centerIn: parent - color: root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3onSurface, 2) - font.pointSize: Appearance.font.size.large - } - - StateLayer { - visible: root.enabled - - onClicked: { - SettingsDropdowns.toggle(menu, root); - } - } - - PathViewMenu { - id: menu - - anchors.centerIn: parent - from: 1 - implicitWidth: root.width - itemHeight: root.height - to: 24 - } -} diff --git a/Modules/Settings/Controls/StringListEditor.qml b/Modules/Settings/Controls/StringListEditor.qml deleted file mode 100644 index 1598cac..0000000 --- a/Modules/Settings/Controls/StringListEditor.qml +++ /dev/null @@ -1,126 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -ColumnLayout { - id: root - - property string addLabel: qsTr("Add entry") - property bool shouldBeActive: true - property var values: [] - - signal listEdited(var values) - - function addValue() { - const list = [...root.values]; - list.push(""); - root.listEdited(list); - } - - function removeValue(index) { - const list = [...root.values]; - list.splice(index, 1); - root.listEdited(list); - } - - function updateValue(index, value) { - const list = [...root.values]; - list[index] = value; - root.listEdited(list); - } - - anchors.left: parent.left - anchors.right: parent.right - height: shouldBeActive ? implicitHeight : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - spacing: Appearance.spacing.smaller - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Repeater { - model: [...root.values] - - Item { - required property int index - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 - - CustomRect { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: -(Appearance.spacing.smaller / 2) - color: DynamicColors.tPalette.m3outlineVariant - implicitHeight: 1 - visible: index !== 0 - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - CustomRect { - Layout.fillWidth: true - Layout.preferredHeight: 33 - color: DynamicColors.tPalette.m3surfaceContainerHigh - radius: Appearance.rounding.full - - CustomTextField { - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.normal - text: String(modelData ?? "") - - onEditingFinished: root.updateValue(index, text) - } - } - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "delete" - type: IconButton.Tonal - - onClicked: root.removeValue(index) - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - IconButton { - font.pointSize: Appearance.font.size.large - icon: "add" - - onClicked: root.addValue() - } - - CustomText { - Layout.fillWidth: true - text: root.addLabel - } - } -} diff --git a/Modules/Settings/Controls/TimeInput.qml b/Modules/Settings/Controls/TimeInput.qml deleted file mode 100644 index 10c937d..0000000 --- a/Modules/Settings/Controls/TimeInput.qml +++ /dev/null @@ -1,661 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Config -import qs.Components -import qs.Helpers - -Item { - id: root - - readonly property string endTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[2]]); - return Qt.formatTime(d, "hh:mm AP"); - } - readonly property bool highlighted: SettingsHighlight.highlightedSetting === name - required property string name - required property var object - required property list settings - property bool shouldBeActive: true - readonly property string startTime: { - var d = new Date(0, 0, 0, 0, 0, 0, 0); - d.setMinutes(object[settings[1]]); - return Qt.formatTime(d, "hh:mm AP"); - } - - function commitChoice(choice: int, setting: string): void { - root.object[setting] = choice; - Config.save(); - ModeScheduler.checkStartup(); - } - - function convertHour(timeValue: int): int { - return Math.floor(timeValue / 60); - } - - function convertMinute(timeValue: int): int { - return timeValue % 60; - } - - function convertToMinutes(hour: int, minute: int): int { - return hour * 60 + minute; - } - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: -Appearance.padding.smaller - color: DynamicColors.palette.m3primaryContainer - opacity: root.highlighted ? 0.5 : 0 - radius: Appearance.rounding.small - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.normal - } - } - } - - RowLayout { - id: row - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - - CustomText { - id: text - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - font.pointSize: Appearance.font.size.larger - text: root.name - } - - CustomText { - Layout.alignment: Qt.AlignLeft - Layout.preferredWidth: Math.min(contentWidth, optionLayout.x - Appearance.spacing.normal) - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.normal - text: qsTr("Dark mode will turn on at %1, and turn off at %2.").arg(root.startTime).arg(root.endTime) - wrapMode: Text.WordWrap - } - } - - ColumnLayout { - id: optionLayout - - Layout.fillHeight: true - Layout.fillWidth: true - - RowLayout { - CustomText { - Layout.preferredWidth: spacer.x + spacer.width - text: qsTr("Start") - } - - CustomText { - Layout.preferredWidth: endMinuteRect.width + endHourRect.width - text: qsTr("End") - } - - CustomText { - Layout.alignment: Qt.AlignLeft | Qt.AlignHCenter - text: qsTr("Enabled: ") - } - - CustomSwitch { - id: enabledSwitch - - Layout.alignment: Qt.AlignRight | Qt.AlignHCenter - checked: root.object[root.settings[0]] - - onToggled: { - root.object[root.settings[0]] = checked; - Config.save(); - ModeScheduler.checkStartup(); - } - } - } - - RowLayout { - Layout.fillHeight: true - - CustomRect { - id: startHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: startMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: startMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: startMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: startMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(startHourField.text), parseInt(startMinuteField.text)), root.settings[1]); - } - 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: Appearance.spacing.large - } - - CustomRect { - id: endHourRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endHourField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endHourField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endHourField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[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: Appearance.font.size.extraLarge - text: ":" - } - - CustomRect { - id: endMinuteRect - - Layout.preferredHeight: 72 - Layout.preferredWidth: 96 - color: endMinuteField.focus ? DynamicColors.palette.m3onPrimaryContainer : DynamicColors.palette.m3surfaceContainerHighest - implicitHeight: 72 - implicitWidth: 96 - radius: Appearance.rounding.small - - CustomRect { - anchors.fill: parent - border.color: endMinuteField.focus ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3surfaceContainerHighest - border.width: endMinuteField.focus ? 2 : 0 - radius: parent.radius - border.width - - Behavior on border.width { - Anim { - } - } - } - - CustomTextField { - id: endMinuteField - - 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 ? DynamicColors.palette.m3primaryContainer : DynamicColors.palette.m3onSurface - cursorHeight: height - Appearance.padding.normal * 2 - 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(); - } - } - - event.accepted = true; - } - onCursorPositionChanged: cursorPosition = 2 - onEditingFinished: { - root.commitChoice(root.convertToMinutes(parseInt(endHourField.text), parseInt(endMinuteField.text)), root.settings[2]); - } - onTextEdited: { - if (endMinuteField.text === "") - 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; - } - } - } - } - - RowLayout { - Layout.fillWidth: true - - CustomText { - id: startHour - - Layout.preferredWidth: startSeparator.x + startSeparator.width - text: qsTr("Hour") - } - - CustomText { - id: startMinute - - Layout.preferredWidth: spacer.x + spacer.width - x - text: qsTr("Minute") - } - - CustomText { - Layout.preferredWidth: endSeparator.x + endSeparator.width - x - text: qsTr("Hour") - } - - CustomText { - text: qsTr("Minute") - } - } - } - } -} diff --git a/Modules/Settings/Controls/VSeparator.qml b/Modules/Settings/Controls/VSeparator.qml deleted file mode 100644 index c7d507e..0000000 --- a/Modules/Settings/Controls/VSeparator.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.Components -import qs.Config - -CustomRect { - id: root - - Layout.fillHeight: true - Layout.preferredWidth: 1 - color: DynamicColors.tPalette.m3outlineVariant -} diff --git a/Modules/Settings/Controls/WallpaperCropper.qml b/Modules/Settings/Controls/WallpaperCropper.qml deleted file mode 100644 index b0888b1..0000000 --- a/Modules/Settings/Controls/WallpaperCropper.qml +++ /dev/null @@ -1,314 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import Quickshell -import Quickshell.Hyprland -import ZShell.Internal -import qs.Config -import qs.Components -import qs.Helpers - -Item { - id: wrapper - - property bool changesMade: false - property bool shouldBeActive: true - - signal requestCrop - - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: shouldBeActive ? 400 : 0 - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } - - IconButton { - anchors.margins: Appearance.padding.normal - anchors.right: parent.right - anchors.top: parent.top - icon: "check" - opacity: wrapper.changesMade ? 1 : 0 - scale: wrapper.changesMade ? 1 : 0 - z: 2 - - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - - onClicked: { - wrapper.requestCrop(); - wrapper.changesMade = false; - } - } - - RowLayout { - id: root - - anchors.fill: parent - spacing: Appearance.spacing.normal - - Repeater { - model: ScriptModel { - values: [...Quickshell.screens].sort((a, b) => { - return a.x - b.x; - }) - } - - Item { - id: delegate - - required property ShellScreen modelData - - function applyCrop(): void { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - // We need to calculate the exact percentage coordinates that map perfectly - // to our C++ backend, regardless of current display scaling - const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; - const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; - const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; - const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; - - const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent); - - // We just pass the percentages directly to the backend - Wallpapers.setCrop(delegate.modelData.name, finalRect, cropRect.zoom); - } - - function zoomClipRect(zoom: real): void { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - let oldCenterX = cropRect.x + cropRect.width * 0.5; - let oldCenterY = cropRect.y + cropRect.height * 0.5; - - cropRect.zoom = zoom; - - cropRect.x = oldCenterX - cropRect.width * 0.5; - cropRect.y = oldCenterY - cropRect.height * 0.5; - - cropRect.clampToBounds(); - } - - Layout.fillHeight: true - Layout.fillWidth: true - - Connections { - function onRequestCrop(): void { - delegate.applyCrop(); - } - - target: wrapper - } - - RowLayout { - id: sliderLayout - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: 30 - - CustomSlider { - id: zoomSlider - - Layout.fillWidth: true - Layout.leftMargin: Appearance.padding.normal - Layout.preferredHeight: Appearance.padding.larger * 3 - Layout.rightMargin: Appearance.padding.normal - from: 1.0 - implicitHeight: Appearance.padding.larger * 3 - insetIcon: "crop" - to: 5.0 - value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 - - onInteraction: value => { - delegate.zoomClipRect(1 + (value * 4)); - wrapper.changesMade = true; - } - } - } - - CachingImage { - id: scaledImg - - property var displayData - property real monitorScale: 1.0 - - anchors.bottom: sliderLayout.top - anchors.bottomMargin: Appearance.spacing.normal - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - asynchronous: true - fillMode: Image.PreserveAspectFit - retainWhileLoading: true - source: Wallpapers.current - sourceSize.height: parent.height - sourceSize.width: parent.width - - onPaintedWidthChanged: { - if (paintedWidth > 0 && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - onSourceChanged: { - if (cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - onStatusChanged: { - if (scaledImg.status == Image.Ready && cropRectLoader.item) { - cropRectLoader.item.restoreFromData(); - } - } - - CustomText { - id: monitorId - - anchors.centerIn: parent - color: Qt.alpha(DynamicColors.palette.m3surface, 0.85) - font.pointSize: Appearance.font.size.large * 4 - style: Text.Outline - styleColor: DynamicColors.palette.m3onSurface - text: delegate.modelData.name - } - - Loader { - id: cropRectLoader - - active: scaledImg.paintedWidth > 0 - - sourceComponent: Component { - CustomRect { - id: cropRect - - property real aspectRatio: delegate.modelData.width / delegate.modelData.height - readonly property real baseHeight: baseWidth / aspectRatio - readonly property real baseWidth: { - let fittedHeight = scaledImg.paintedHeight; - let fittedWidth = fittedHeight * aspectRatio; - - if (fittedWidth > scaledImg.paintedWidth) { - fittedWidth = scaledImg.paintedWidth; - fittedHeight = fittedWidth / aspectRatio; - } - - return fittedWidth; - } - readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2 - readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2 - property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight - property real zoom: 1.0 - - function centerInImage() { - x = imageX + (scaledImg.paintedWidth - width) / 2; - y = imageY + (scaledImg.paintedHeight - height) / 2; - } - - function clampToBounds() { - x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); - - y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); - } - - function restoreFromData() { - let data = Wallpapers.getCrop(delegate.modelData.name); - - if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) { - zoom = data.zoom > 0 ? data.zoom : 1.0; - x = imageX + (data.x * scaledImg.paintedWidth); - y = imageY + (data.y * scaledImg.paintedHeight); - - clampToBounds(); - } else { - zoom = 1.0; - centerInImage(); - } - } - - border.color: DynamicColors.palette.m3primary - border.width: 2 - height: baseHeight / zoom - opacity: 1 - width: baseWidth / zoom - - Behavior on opacity { - Anim { - } - } - - Component.onCompleted: { - restoreFromData(); - } - onHeightChanged: clampToBounds() - onWidthChanged: clampToBounds() - } - } - } - - MouseArea { - id: mouse - - function updateCrop(mouseX, mouseY) { - if (!cropRectLoader.item) - return; - const cropRect = cropRectLoader.item; - - let nx = mouseX - cropRect.width * 0.5; - let ny = mouseY - cropRect.height * 0.5; - - nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width)); - - ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height)); - - cropRect.x = nx; - cropRect.y = ny; - } - - anchors.fill: parent - hoverEnabled: true - preventStealing: true - - onPositionChanged: mouse => { - if (pressed) { - updateCrop(mouse.x, mouse.y); - wrapper.changesMade = true; - } - } - onPressed: mouse => { - updateCrop(mouse.x, mouse.y); - wrapper.changesMade = true; - } - onReleased: { - wrapper.changesMade = true; - } - } - } - } - } - } -} diff --git a/Modules/Settings/Controls/WallpaperGrid.qml b/Modules/Settings/Controls/WallpaperGrid.qml deleted file mode 100644 index 9c91fad..0000000 --- a/Modules/Settings/Controls/WallpaperGrid.qml +++ /dev/null @@ -1,158 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import ZShell.Models -import qs.Components -import qs.Helpers -import qs.Config - -GridView { - id: root - - readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth)) - readonly property int minCellWidth: 200 + Appearance.spacing.normal - property bool shouldBeActive: true - - anchors.left: parent.left - anchors.right: parent.right - cellHeight: 140 + Appearance.spacing.normal - cellWidth: width / columnsCount - clip: true - implicitHeight: shouldBeActive ? contentHeight : 0 - interactive: false - model: Wallpapers.list - opacity: shouldBeActive ? 1 : 0 - scale: shouldBeActive ? 1 : 0.8 - visible: opacity > 0 - - delegate: Item { - required property int index - readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent - readonly property real itemMargin: Appearance.spacing.normal - readonly property real itemRadius: Appearance.rounding.small - required property var modelData - - height: root.cellHeight - width: root.cellWidth - - CustomClippingRect { - id: image - - anchors.bottomMargin: itemMargin - anchors.fill: parent - anchors.leftMargin: itemMargin - anchors.rightMargin: itemMargin - anchors.topMargin: itemMargin - antialiasing: true - color: DynamicColors.tPalette.m3surfaceContainer - radius: itemRadius - - CachingImage { - id: cachingImage - - anchors.fill: parent - antialiasing: true - cache: true - fillMode: Image.PreserveAspectCrop - opacity: status === Image.Ready ? 1 : 0 - path: modelData.path - smooth: true - sourceSize: Qt.size(width, height) - visible: opacity > 0 - - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutQuad - } - } - } - - Image { - id: fallbackImage - - anchors.fill: parent - antialiasing: true - asynchronous: true - cache: true - fillMode: Image.PreserveAspectCrop - opacity: status === Image.Ready && cachingImage.status !== Image.Ready ? 1 : 0 - smooth: true - source: fallbackTimer.triggered && cachingImage.status !== Image.Ready ? modelData.path : "" - sourceSize: Qt.size(width, height) - visible: opacity > 0 - - Behavior on opacity { - NumberAnimation { - duration: 1000 - easing.type: Easing.OutQuad - } - } - } - - Rectangle { - anchors.fill: parent - antialiasing: true - border.color: DynamicColors.palette.m3primary - border.width: isCurrent ? 2 : 0 - color: "transparent" - radius: itemRadius + 2 - smooth: true - - Behavior on border.width { - NumberAnimation { - duration: 150 - easing.type: Easing.OutQuad - } - } - - MaterialIcon { - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.top: parent.top - color: DynamicColors.palette.m3primary - font.pointSize: Appearance.font.size.large - text: "check_circle" - visible: isCurrent - } - } - - Timer { - id: fallbackTimer - - property bool triggered: false - - interval: 800 - running: cachingImage.status === Image.Loading || cachingImage.status === Image.Null - - onTriggered: triggered = true - } - } - - StateLayer { - anchors.bottomMargin: itemMargin - anchors.fill: parent - anchors.leftMargin: itemMargin - anchors.rightMargin: itemMargin - anchors.topMargin: itemMargin - radius: itemRadius - - onClicked: { - Wallpapers.setWallpaper(modelData.path); - } - } - } - Behavior on opacity { - Anim { - } - } - Behavior on scale { - Anim { - } - } - Behavior on y { - Anim { - } - } -} diff --git a/Modules/SettingsNew/NavPane.qml b/Modules/Settings/NavPane.qml similarity index 96% rename from Modules/SettingsNew/NavPane.qml rename to Modules/Settings/NavPane.qml index 7360ed6..b87a45c 100644 --- a/Modules/SettingsNew/NavPane.qml +++ b/Modules/Settings/NavPane.qml @@ -1,7 +1,7 @@ import QtQuick import QtQuick.Layouts import qs.Components -import qs.Modules.SettingsNew.NavPane +import qs.Modules.Settings.NavPane import qs.Config ColumnLayout { diff --git a/Modules/SettingsNew/NavPane/NavLocations.qml b/Modules/Settings/NavPane/NavLocations.qml similarity index 99% rename from Modules/SettingsNew/NavPane/NavLocations.qml rename to Modules/Settings/NavPane/NavLocations.qml index a5f1706..678c82b 100644 --- a/Modules/SettingsNew/NavPane/NavLocations.qml +++ b/Modules/Settings/NavPane/NavLocations.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config -import qs.Modules.SettingsNew +import qs.Modules.Settings VerticalFadeFlickable { id: root diff --git a/Modules/SettingsNew/PageCompRegistry.qml b/Modules/Settings/PageCompRegistry.qml similarity index 88% rename from Modules/SettingsNew/PageCompRegistry.qml rename to Modules/Settings/PageCompRegistry.qml index c91229d..39da282 100644 --- a/Modules/SettingsNew/PageCompRegistry.qml +++ b/Modules/Settings/PageCompRegistry.qml @@ -4,14 +4,14 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config -import qs.Modules.SettingsNew.Common -import qs.Modules.SettingsNew.Pages -import qs.Modules.SettingsNew.Pages.Wallpaper -import qs.Modules.SettingsNew.Pages.Audio -import qs.Modules.SettingsNew.Pages.Apps -import qs.Modules.SettingsNew.Pages.Panels -import qs.Modules.SettingsNew.Pages.Panels.Bar -import qs.Modules.SettingsNew.Pages.Services +import qs.Modules.Settings.Common +import qs.Modules.Settings.Pages +import qs.Modules.Settings.Pages.Wallpaper +import qs.Modules.Settings.Pages.Audio +import qs.Modules.Settings.Pages.Apps +import qs.Modules.Settings.Pages.Panels +import qs.Modules.Settings.Pages.Panels.Bar +import qs.Modules.Settings.Pages.Services QtObject { id: root diff --git a/Modules/SettingsNew/PageRegistry.qml b/Modules/Settings/PageRegistry.qml similarity index 100% rename from Modules/SettingsNew/PageRegistry.qml rename to Modules/Settings/PageRegistry.qml diff --git a/Modules/SettingsNew/Pages.qml b/Modules/Settings/Pages.qml similarity index 100% rename from Modules/SettingsNew/Pages.qml rename to Modules/Settings/Pages.qml diff --git a/Modules/SettingsNew/Pages/AboutPage.qml b/Modules/Settings/Pages/AboutPage.qml similarity index 98% rename from Modules/SettingsNew/Pages/AboutPage.qml rename to Modules/Settings/Pages/AboutPage.qml index 9d55c24..19044ba 100644 --- a/Modules/SettingsNew/Pages/AboutPage.qml +++ b/Modules/Settings/Pages/AboutPage.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import Quickshell.Io import ZShell -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common import qs.Config import qs.Helpers import qs.Components diff --git a/Modules/SettingsNew/Pages/Apps/AllApps.qml b/Modules/Settings/Pages/Apps/AllApps.qml similarity index 98% rename from Modules/SettingsNew/Pages/Apps/AllApps.qml rename to Modules/Settings/Pages/Apps/AllApps.qml index 4f1a9ed..505ee57 100644 --- a/Modules/SettingsNew/Pages/Apps/AllApps.qml +++ b/Modules/Settings/Pages/Apps/AllApps.qml @@ -7,7 +7,7 @@ import Quickshell.Widgets import qs.Components import qs.Config import qs.Helpers -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Apps/AppInfo.qml b/Modules/Settings/Pages/Apps/AppInfo.qml similarity index 99% rename from Modules/SettingsNew/Pages/Apps/AppInfo.qml rename to Modules/Settings/Pages/Apps/AppInfo.qml index 65f0a7e..8d3b903 100644 --- a/Modules/SettingsNew/Pages/Apps/AppInfo.qml +++ b/Modules/Settings/Pages/Apps/AppInfo.qml @@ -5,7 +5,7 @@ import Quickshell.Widgets import qs.Components import qs.Config import qs.Helpers -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/AppsPage.qml b/Modules/Settings/Pages/AppsPage.qml similarity index 99% rename from Modules/SettingsNew/Pages/AppsPage.qml rename to Modules/Settings/Pages/AppsPage.qml index 2ba835d..cd53b40 100644 --- a/Modules/SettingsNew/Pages/AppsPage.qml +++ b/Modules/Settings/Pages/AppsPage.qml @@ -8,7 +8,7 @@ import ZShell import qs.Helpers import qs.Components import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Audio/AppVolumes.qml b/Modules/Settings/Pages/Audio/AppVolumes.qml similarity index 97% rename from Modules/SettingsNew/Pages/Audio/AppVolumes.qml rename to Modules/Settings/Pages/Audio/AppVolumes.qml index ab19def..b84fba8 100644 --- a/Modules/SettingsNew/Pages/Audio/AppVolumes.qml +++ b/Modules/Settings/Pages/Audio/AppVolumes.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Services.Pipewire -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common import qs.Helpers import qs.Components import qs.Config diff --git a/Modules/SettingsNew/Pages/AudioPage.qml b/Modules/Settings/Pages/AudioPage.qml similarity index 98% rename from Modules/SettingsNew/Pages/AudioPage.qml rename to Modules/Settings/Pages/AudioPage.qml index 43cb71c..f0e1882 100644 --- a/Modules/SettingsNew/Pages/AudioPage.qml +++ b/Modules/Settings/Pages/AudioPage.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import qs.Daemons import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common import qs.Helpers PageBase { diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml b/Modules/Settings/Pages/Panels/Bar/BarClock.qml similarity index 97% rename from Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml rename to Modules/Settings/Pages/Panels/Bar/BarClock.qml index 5d2d2bb..109cf36 100644 --- a/Modules/SettingsNew/Pages/Panels/Bar/BarClock.qml +++ b/Modules/Settings/Pages/Panels/Bar/BarClock.qml @@ -3,7 +3,7 @@ pragma ComponentBehavior: Bound import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml similarity index 98% rename from Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml rename to Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml index b305f18..3c9561a 100644 --- a/Modules/SettingsNew/Pages/Panels/Bar/BarStatusIcons.qml +++ b/Modules/Settings/Pages/Panels/Bar/BarStatusIcons.qml @@ -2,7 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick.Layouts import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml b/Modules/Settings/Pages/Panels/Bar/BarTray.qml similarity index 93% rename from Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml rename to Modules/Settings/Pages/Panels/Bar/BarTray.qml index b40d9ba..9f8d096 100644 --- a/Modules/SettingsNew/Pages/Panels/Bar/BarTray.qml +++ b/Modules/Settings/Pages/Panels/Bar/BarTray.qml @@ -2,7 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick.Layouts import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/BarPanel.qml b/Modules/Settings/Pages/Panels/BarPanel.qml similarity index 96% rename from Modules/SettingsNew/Pages/Panels/BarPanel.qml rename to Modules/Settings/Pages/Panels/BarPanel.qml index 5995239..4bfc80d 100644 --- a/Modules/SettingsNew/Pages/Panels/BarPanel.qml +++ b/Modules/Settings/Pages/Panels/BarPanel.qml @@ -2,7 +2,7 @@ pragma ComponentBehavior: Bound import QtQuick.Layouts import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml b/Modules/Settings/Pages/Panels/DashboardPanel.qml similarity index 94% rename from Modules/SettingsNew/Pages/Panels/DashboardPanel.qml rename to Modules/Settings/Pages/Panels/DashboardPanel.qml index 4d14f36..a8e0cef 100644 --- a/Modules/SettingsNew/Pages/Panels/DashboardPanel.qml +++ b/Modules/Settings/Pages/Panels/DashboardPanel.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/LauncherPanel.qml b/Modules/Settings/Pages/Panels/LauncherPanel.qml similarity index 98% rename from Modules/SettingsNew/Pages/Panels/LauncherPanel.qml rename to Modules/Settings/Pages/Panels/LauncherPanel.qml index 471b49c..630896e 100644 --- a/Modules/SettingsNew/Pages/Panels/LauncherPanel.qml +++ b/Modules/Settings/Pages/Panels/LauncherPanel.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml b/Modules/Settings/Pages/Panels/ResourcesPanel.qml similarity index 97% rename from Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml rename to Modules/Settings/Pages/Panels/ResourcesPanel.qml index ace7608..3c5cde4 100644 --- a/Modules/SettingsNew/Pages/Panels/ResourcesPanel.qml +++ b/Modules/Settings/Pages/Panels/ResourcesPanel.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Panels/SidebarPanel.qml b/Modules/Settings/Pages/Panels/SidebarPanel.qml similarity index 95% rename from Modules/SettingsNew/Pages/Panels/SidebarPanel.qml rename to Modules/Settings/Pages/Panels/SidebarPanel.qml index 57f1159..e1f02bb 100644 --- a/Modules/SettingsNew/Pages/Panels/SidebarPanel.qml +++ b/Modules/Settings/Pages/Panels/SidebarPanel.qml @@ -4,7 +4,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/PanelsPage.qml b/Modules/Settings/Pages/PanelsPage.qml similarity index 97% rename from Modules/SettingsNew/Pages/PanelsPage.qml rename to Modules/Settings/Pages/PanelsPage.qml index ca785d7..2200b96 100644 --- a/Modules/SettingsNew/Pages/PanelsPage.qml +++ b/Modules/Settings/Pages/PanelsPage.qml @@ -1,6 +1,6 @@ import QtQuick.Layouts import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/ScreenshotPage.qml b/Modules/Settings/Pages/ScreenshotPage.qml similarity index 97% rename from Modules/SettingsNew/Pages/ScreenshotPage.qml rename to Modules/Settings/Pages/ScreenshotPage.qml index 74aa368..83b75a1 100644 --- a/Modules/SettingsNew/Pages/ScreenshotPage.qml +++ b/Modules/Settings/Pages/ScreenshotPage.qml @@ -6,8 +6,8 @@ import ZShell.Components import qs.Helpers import qs.Components import qs.Config -import qs.Modules.SettingsNew -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Services/NotificationsPage.qml b/Modules/Settings/Pages/Services/NotificationsPage.qml similarity index 99% rename from Modules/SettingsNew/Pages/Services/NotificationsPage.qml rename to Modules/Settings/Pages/Services/NotificationsPage.qml index 764e44d..2b0a90c 100644 --- a/Modules/SettingsNew/Pages/Services/NotificationsPage.qml +++ b/Modules/Settings/Pages/Services/NotificationsPage.qml @@ -2,7 +2,7 @@ import QtQuick import QtQuick.Layouts import qs.Config import qs.Components -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/ServicesPage.qml b/Modules/Settings/Pages/ServicesPage.qml similarity index 99% rename from Modules/SettingsNew/Pages/ServicesPage.qml rename to Modules/Settings/Pages/ServicesPage.qml index d6751b5..7241194 100644 --- a/Modules/SettingsNew/Pages/ServicesPage.qml +++ b/Modules/Settings/Pages/ServicesPage.qml @@ -5,7 +5,7 @@ import ZShell.Services import qs.Components import qs.Helpers import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml b/Modules/Settings/Pages/Wallpaper/WallpaperSelect.qml similarity index 98% rename from Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml rename to Modules/Settings/Pages/Wallpaper/WallpaperSelect.qml index 53470a4..72c98a0 100644 --- a/Modules/SettingsNew/Pages/Wallpaper/WallpaperSelect.qml +++ b/Modules/Settings/Pages/Wallpaper/WallpaperSelect.qml @@ -7,7 +7,7 @@ import qs.Paths import qs.Helpers import qs.Components import qs.Config -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/Pages/WallpaperPage.qml b/Modules/Settings/Pages/WallpaperPage.qml similarity index 98% rename from Modules/SettingsNew/Pages/WallpaperPage.qml rename to Modules/Settings/Pages/WallpaperPage.qml index a45aae7..a07c68d 100644 --- a/Modules/SettingsNew/Pages/WallpaperPage.qml +++ b/Modules/Settings/Pages/WallpaperPage.qml @@ -6,8 +6,8 @@ import ZShell.Components import qs.Helpers import qs.Components import qs.Config -import qs.Modules.SettingsNew -import qs.Modules.SettingsNew.Common +import qs.Modules.Settings +import qs.Modules.Settings.Common PageBase { id: root diff --git a/Modules/SettingsNew/PopupManager.qml b/Modules/Settings/PopupManager.qml similarity index 100% rename from Modules/SettingsNew/PopupManager.qml rename to Modules/Settings/PopupManager.qml diff --git a/Modules/SettingsNew/PopupOverlay.qml b/Modules/Settings/PopupOverlay.qml similarity index 100% rename from Modules/SettingsNew/PopupOverlay.qml rename to Modules/Settings/PopupOverlay.qml diff --git a/Modules/Settings/SettingsSearch.qml b/Modules/Settings/SettingsSearch.qml deleted file mode 100644 index ac7906e..0000000 --- a/Modules/Settings/SettingsSearch.qml +++ /dev/null @@ -1,317 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import qs.Components -import qs.Config -import "../../scripts/fuzzysort.js" as Fuzzy -import "../../scripts/SettingsIndex.mjs" as SettingsIndex - -Item { - id: root - - property alias text: searchField.text - - signal settingSelected(string category, string section, string settingName) - - function close() { - searchField.text = ""; - searchField.focus = false; - popup.close(); - } - - function search(query) { - resultsModel.clear(); - - if (!query || query.trim() === "") { - popup.close(); - return; - } - - const results = SettingsIndex.searchSettings(query, Fuzzy); - - for (const result of results.slice(0, 10)) { - resultsModel.append({ - name: result.name, - category: result.category, - categoryName: result.categoryName, - section: result.section, - matchType: result.matchType - }); - } - - if (resultsModel.count > 0) { - popup.open(); - } else { - popup.close(); - } - } - - implicitHeight: searchContainer.implicitHeight - implicitWidth: 200 - - Shortcut { - sequence: "/" - - onActivated: searchField.forceActiveFocus() - } - - ListModel { - id: resultsModel - } - - CustomRect { - id: searchContainer - - anchors.fill: parent - color: DynamicColors.tPalette.m3surfaceContainerHigh - implicitHeight: searchRow.implicitHeight + Appearance.padding.small * 2 - radius: Appearance.rounding.full - - RowLayout { - id: searchRow - - anchors.fill: parent - anchors.leftMargin: Appearance.padding.normal - anchors.rightMargin: Appearance.padding.small - spacing: Appearance.spacing.small - - MaterialIcon { - Layout.alignment: Qt.AlignVCenter - color: DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.larger - text: "search" - } - - CustomTextField { - id: searchField - - Layout.alignment: Qt.AlignVCenter - Layout.fillWidth: true - font.pointSize: Appearance.font.size.small - placeholderText: qsTr("Search settings...") - - Keys.onDownPressed: { - if (popup.visible && resultsList.count > 0) { - resultsList.currentIndex = Math.min(resultsList.currentIndex + 1, resultsList.count - 1); - } - } - Keys.onEscapePressed: { - root.close(); - } - Keys.onReturnPressed: { - if (popup.visible && resultsList.currentIndex >= 0) { - const item = resultsModel.get(resultsList.currentIndex); - root.settingSelected(item.category, item.section, item.name); - root.close(); - } - } - Keys.onUpPressed: { - if (popup.visible && resultsList.count > 0) { - resultsList.currentIndex = Math.max(resultsList.currentIndex - 1, 0); - } - } - onTextChanged: { - searchTimer.restart(); - } - } - - // Clear button - IconButton { - Layout.alignment: Qt.AlignVCenter - font.pointSize: Appearance.font.size.larger - icon: "close" - opacity: searchField.text.length > 0 ? 1 : 0 - - Behavior on opacity { - Anim { - duration: Appearance.anim.durations.small - } - } - - onClicked: { - root.close(); - } - } - } - } - - // Debounce timer for search - Timer { - id: searchTimer - - interval: 150 - - onTriggered: root.search(searchField.text) - } - - // Results dropdown - Popup { - id: popup - - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside - implicitHeight: Math.min(resultsList.contentHeight + Appearance.padding.small * 2 + 10, (55 + Appearance.spacing.smaller) * 5 - Appearance.spacing.smaller + 10 + Appearance.padding.small * 2) - implicitWidth: root.width + Appearance.padding.small * 2 - modal: false - padding: 0 - x: root.width / 2 - popup.width / 2 - y: searchContainer.height + Appearance.spacing.small - 5 - - background: Item { - } - contentItem: Item { - property alias view: resultsList - - Elevation { - id: popupShadow - - anchors.centerIn: parent - height: popup.implicitHeight - 10 - level: 2 - radius: Appearance.rounding.normal - width: popup.implicitWidth - 10 - - CustomRect { - anchors.fill: parent - color: DynamicColors.palette.m3surfaceContainer - radius: Appearance.rounding.normal - - CustomListView { - id: resultsList - - anchors.fill: parent - anchors.margins: Appearance.padding.small - clip: true - currentIndex: 0 - highlightFollowsCurrentItem: false - highlightRangeMode: ListView.ApplyRange - model: resultsModel - preferredHighlightBegin: 0 - preferredHighlightEnd: height - spacing: Appearance.spacing.smaller - - delegate: SearchResultItem { - required property string category - required property string categoryName - required property int index - required property string matchType - required property string name - required property string section - - highlighted: index === resultsList.currentIndex - width: resultsList.width - - onClicked: { - root.settingSelected(category, section, name); - root.close(); - } - } - highlight: CustomRect { - color: DynamicColors.palette.m3primary - implicitHeight: resultsList.currentItem?.implicitHeight ?? 0 - implicitWidth: resultsList.width - radius: Appearance.rounding.normal - Appearance.padding.smaller - y: resultsList.currentItem?.y ?? 0 - - Behavior on y { - Anim { - duration: Appearance.anim.durations.small - easing.bezierCurve: Appearance.anim.curves.expressiveEffects - } - } - } - } - } - } - } - enter: Transition { - Anim { - duration: Appearance.anim.durations.small - from: 0 - property: "opacity" - to: 1 - } - - Anim { - duration: Appearance.anim.durations.small - from: 0.95 - property: "scale" - to: 1.0 - } - } - exit: Transition { - Anim { - duration: Appearance.anim.durations.smaller - from: 1 - property: "opacity" - to: 0 - } - } - } - - // Search result item component - component SearchResultItem: CustomRect { - id: resultItem - - property bool highlighted: false - - signal clicked - - implicitHeight: 45 + Appearance.padding.small * 2 - radius: Appearance.rounding.small - - ColumnLayout { - id: resultLayout - - anchors.left: parent.left - anchors.margins: Appearance.padding.small - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - spacing: 2 - - // Setting name - CustomText { - Layout.fillWidth: true - color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface - elide: Text.ElideRight - font.pointSize: Appearance.font.size.small - font.weight: Font.Medium - text: resultItem.name - } - - // Category and section path - RowLayout { - Layout.fillWidth: true - spacing: Appearance.spacing.smaller - - CustomText { - color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.smaller - opacity: 0.8 - text: resultItem.categoryName - } - - CustomText { - color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant - font.pointSize: Appearance.font.size.smaller - opacity: 0.6 - text: "›" - } - - CustomText { - Layout.fillWidth: true - color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant - elide: Text.ElideRight - font.pointSize: Appearance.font.size.smaller - opacity: 0.8 - text: resultItem.section - } - } - } - - StateLayer { - onClicked: resultItem.clicked() - } - } -} diff --git a/Modules/SettingsNew/SettingsState.qml b/Modules/Settings/SettingsState.qml similarity index 100% rename from Modules/SettingsNew/SettingsState.qml rename to Modules/Settings/SettingsState.qml diff --git a/Modules/Settings/Wrapper.qml b/Modules/Settings/Wrapper.qml index a734fd4..1895ae7 100644 --- a/Modules/Settings/Wrapper.qml +++ b/Modules/Settings/Wrapper.qml @@ -13,7 +13,6 @@ Item { readonly property bool shouldBeActive: visibilities.settings required property PersistentProperties visibilities - anchors.topMargin: (-implicitHeight - 5) * offsetScale implicitHeight: content.implicitHeight implicitWidth: content.implicitWidth opacity: 1 - offsetScale @@ -37,8 +36,11 @@ Item { anchors.horizontalCenter: parent.horizontalCenter sourceComponent: Content { - screen: root.screen - visibilities: root.visibilities + sState.animatingContainer: content.opacity < 1 + sState.currentPageIdx: ["wallpaper"][0] + sState.screen: root.screen + + onClose: console.log("shouldclose") } } } diff --git a/Modules/SettingsNew/Content.qml b/Modules/SettingsNew/Content.qml deleted file mode 100644 index 6230d21..0000000 --- a/Modules/SettingsNew/Content.qml +++ /dev/null @@ -1,76 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import ZShell.Blobs -import qs.Components -import qs.Config - -CustomClippingRect { - id: root - - property color blobColor: DynamicColors.tPalette.m3surfaceContainerLow - readonly property real ratio: 16.0 / 9.0 - readonly property SettingsState sState: SettingsState { - id: sState - - onClose: root.close() - } - - signal close - - implicitHeight: sState.screen.height * 0.7 - implicitWidth: implicitHeight * ratio - radius: Appearance.rounding.large + Appearance.padding.normal - - Behavior on blobColor { - CAnim { - } - } - - TapHandler { - onTapped: root.focus = true - } - - BlobGroup { - id: blobGroup - - color: root.blobColor - smoothing: Appearance.rounding.normal - } - - BlobInvertedRect { - anchors.fill: parent - borderBottom: Appearance.padding.normal - borderLeft: navPane.width + navPane.anchors.margins * 2 - borderRight: Appearance.padding.normal - borderTop: Appearance.padding.normal - group: blobGroup - opacity: root.blobColor.a - radius: Appearance.rounding.large - } - - NavPane { - id: navPane - - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.margins: Appearance.padding.large - anchors.top: parent.top - sState: root.sState - width: Math.min(600, Math.round(root.width / 3)) - } - - Pages { - anchors.bottom: parent.bottom - anchors.left: navPane.right - anchors.leftMargin: navPane.anchors.margins + anchors.margins - anchors.margins: Appearance.padding.extraLarge - anchors.right: parent.right - anchors.top: parent.top - sState: root.sState - } - - PopupOverlay { - anchors.fill: parent - } -} diff --git a/Modules/SettingsNew/Wrapper.qml b/Modules/SettingsNew/Wrapper.qml deleted file mode 100644 index 1895ae7..0000000 --- a/Modules/SettingsNew/Wrapper.qml +++ /dev/null @@ -1,47 +0,0 @@ -import Quickshell -import QtQuick -import qs.Components -import qs.Config -import qs.Helpers - -Item { - id: root - - property real offsetScale: shouldBeActive ? 0 : 1 - required property var panels - required property ShellScreen screen - readonly property bool shouldBeActive: visibilities.settings - required property PersistentProperties visibilities - - implicitHeight: content.implicitHeight - implicitWidth: content.implicitWidth - opacity: 1 - offsetScale - visible: offsetScale < 1 - - Behavior on offsetScale { - Anim { - duration: Appearance.anim.durations.expressiveDefaultSpatial - easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial - } - } - - CustomClippingRect { - anchors.fill: parent - - Loader { - id: content - - active: root.shouldBeActive || root.visible - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - - sourceComponent: Content { - sState.animatingContainer: content.opacity < 1 - sState.currentPageIdx: ["wallpaper"][0] - sState.screen: root.screen - - onClose: console.log("shouldclose") - } - } - } -} -- 2.47.3