From ea4d5376f6a011eb34dc7ea28662df5fb9c094a9 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 16:22:56 +0200 Subject: [PATCH 01/44] 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)); From 6a80961b2b7c4a079018d14c2557e6807d8dadb4 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 22:32:56 +0200 Subject: [PATCH 02/44] 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(); From 5c6882d2a8641c216744fab9d3bc79dfa0e12506 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 23 Jun 2026 23:06:30 +0200 Subject: [PATCH 03/44] 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) + } + } +} From 4df31d7564ea2da376c9eafe7539fc39183aa768 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 15:30:33 +0200 Subject: [PATCH 04/44] 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 { From d08b2b7ac6698c6af8a22d086c5fb4885696d06f Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 15:42:50 +0200 Subject: [PATCH 05/44] 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) } } } From ecfa1115cd53e5578444cf5f1073ae824475ab00 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 20:21:36 +0200 Subject: [PATCH 06/44] 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 { From 01c63ebeadc83eb52486bbee4002aec417eec858 Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 24 Jun 2026 23:06:29 +0200 Subject: [PATCH 07/44] 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 From 6a15be7b1548ad1f634b6d76357adac00b8d7244 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 25 Jun 2026 12:57:11 +0200 Subject: [PATCH 08/44] 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 { From 0b92cbb3b46291afac02000988c6f0803f3aaa19 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 25 Jun 2026 18:58:21 +0200 Subject: [PATCH 09/44] 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] = {}; From ce6e314fde5a8eff5cdfc8536c578712b81057dd Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 26 Jun 2026 17:23:56 +0200 Subject: [PATCH 10/44] 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 From 82a660229b48f65cc24db4819b50369071477e51 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 26 Jun 2026 17:25:25 +0200 Subject: [PATCH 11/44] 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 From 04481ae7e1ac3c8022ecb0a22cbf8e47956253d2 Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 27 Jun 2026 00:11:18 +0200 Subject: [PATCH 12/44] 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 From e685500698c9ed0f2cf33feff1af4bae01b88f12 Mon Sep 17 00:00:00 2001 From: zach Date: Sun, 28 Jun 2026 22:48:46 +0200 Subject: [PATCH 13/44] 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; From 644a1e0c785a081b172b42c675051e16d0f4218a Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 29 Jun 2026 22:40:28 +0200 Subject: [PATCH 14/44] workflow update; node and docker deps added --- .gitea/workflows/ci-image.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitea/workflows/ci-image.yml b/.gitea/workflows/ci-image.yml index cf2f52c..cded8b0 100644 --- a/.gitea/workflows/ci-image.yml +++ b/.gitea/workflows/ci-image.yml @@ -8,6 +8,8 @@ on: jobs: build: runs-on: alpine + container: + image: node:26-alpine env: IMAGE: git.aramjonghu.nl/aramjonghu/zshell-ci:latest @@ -15,6 +17,9 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Install Docker CLI + run: apk add --no-cache docker-cli + - name: Build image run: docker build -t "$IMAGE" -f ci/Dockerfile . From b939224e9d3804c009df6606378e7a36267bd8ee Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Mon, 29 Jun 2026 23:01:54 +0200 Subject: [PATCH 15/44] action secret added so we have push permissions for docker --- .gitea/workflows/ci-image.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/ci-image.yml b/.gitea/workflows/ci-image.yml index cded8b0..a2d5a7c 100644 --- a/.gitea/workflows/ci-image.yml +++ b/.gitea/workflows/ci-image.yml @@ -20,6 +20,9 @@ jobs: - name: Install Docker CLI run: apk add --no-cache docker-cli + - name: Login to registry + run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.aramjonghu.nl --username aramjonghu --password-stdin + - name: Build image run: docker build -t "$IMAGE" -f ci/Dockerfile . From 437f935f73adaf22ce2d9dbbd626f27518753a8d Mon Sep 17 00:00:00 2001 From: zach Date: Mon, 29 Jun 2026 23:54:54 +0200 Subject: [PATCH 16/44] 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); From 3eecbc16bc2bff7a293ad5d1336d23d253e7262b Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 00:23:23 +0200 Subject: [PATCH 17/44] perf(lazylistview): collect pending indices and hoist layoutSize cast in updatePolishdbus workaround for session --- Plugins/ZShell/Components/lazylistview.cpp | 466 ++++++++++++--------- 1 file changed, 268 insertions(+), 198 deletions(-) diff --git a/Plugins/ZShell/Components/lazylistview.cpp b/Plugins/ZShell/Components/lazylistview.cpp index 4638404..0fc9438 100644 --- a/Plugins/ZShell/Components/lazylistview.cpp +++ b/Plugins/ZShell/Components/lazylistview.cpp @@ -1,6 +1,7 @@ #include "lazylistview.hpp" #include +#include #include #include @@ -13,11 +14,7 @@ constexpr int ASYNC_BATCH_DESTROY = 4; namespace ZShell::components { -// --- LazyListViewAttached --- - -LazyListViewAttached::LazyListViewAttached(QObject* parent) - : QObject(parent) { -} +LazyListViewAttached::LazyListViewAttached(QObject* parent) : QObject(parent) {} qreal LazyListViewAttached::preferredHeight() const { return m_preferredHeight; @@ -85,10 +82,7 @@ void LazyListViewAttached::setTrackViewport(bool track) { emit trackViewportChanged(); } -// --- LazyListView --- - -LazyListView::LazyListView(QQuickItem* parent) - : QQuickItem(parent) { +LazyListView::LazyListView(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents, false); } @@ -103,8 +97,6 @@ LazyListView::~LazyListView() { destroyDelegate(entry); } -// --- Model & Delegate --- - QAbstractItemModel* LazyListView::model() const { return m_model; } @@ -138,8 +130,6 @@ void LazyListView::setDelegate(QQmlComponent* delegate) { emit delegateChanged(); } -// --- Layout --- - qreal LazyListView::spacing() const { return m_spacing; } @@ -172,8 +162,6 @@ void LazyListView::setContentY(qreal contentY) { polish(); } -// --- Viewport --- - QRectF LazyListView::viewport() const { return m_viewport; } @@ -211,8 +199,6 @@ void LazyListView::setCacheBuffer(qreal buffer) { polish(); } -// --- Sizing --- - qreal LazyListView::estimatedHeight() const { return m_estimatedHeight; } @@ -258,7 +244,8 @@ qreal LazyListView::delegateHeight(QQuickItem* item) { if (!item) return 0; - auto* attached = qobject_cast(qmlAttachedPropertiesObject(item, false)); + auto* attached = qobject_cast( + qmlAttachedPropertiesObject(item, false)); if (attached && attached->preferredHeight() >= 0) return attached->preferredHeight(); @@ -269,7 +256,8 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) { if (!item) return 0; - auto* attached = qobject_cast(qmlAttachedPropertiesObject(item, false)); + auto* attached = qobject_cast( + qmlAttachedPropertiesObject(item, false)); if (attached) { if (attached->visibleHeight() >= 0) return attached->visibleHeight(); @@ -283,12 +271,11 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) { bool LazyListView::isDelegateReady(QQuickItem* item) { if (!item) return false; - auto* att = qobject_cast(qmlAttachedPropertiesObject(item, false)); + auto* att = qobject_cast( + qmlAttachedPropertiesObject(item, false)); return !att || att->ready(); } -// --- Animation Durations --- - int LazyListView::removeDuration() const { return m_removeDuration; } @@ -311,21 +298,18 @@ void LazyListView::setReadyDelay(int delay) { emit readyDelayChanged(); } -// --- State --- - int LazyListView::count() const { return m_model ? m_model->rowCount() : 0; } -// --- QQuickItem Overrides --- - void LazyListView::componentComplete() { QQuickItem::componentComplete(); m_componentComplete = true; resetContent(); } -void LazyListView::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { +void LazyListView::geometryChange(const QRectF& newGeometry, + const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); if (!m_componentComplete) @@ -348,71 +332,84 @@ void LazyListView::updatePolish() { // Flush pending inserts — make items visible and clear the adding flag // so enter animations begin. When readyDelay > 0 the entire insert is // deferred so delegates have time to lay out before appearing. - for (auto& entry : m_delegates) { - if (!entry.pendingInsert || !entry.item) - continue; + + // Collect pending indices first — avoids scanning the entire hash each frame + QList pendingIndices; + for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) { + if (it->pendingInsert && it->item) + pendingIndices.append(it.key()); + } + + for (int pendingIdx : pendingIndices) { + auto& entry = m_delegates[pendingIdx]; if (m_readyDelay > 0) { if (!entry.readyDelayStarted) { entry.readyDelayStarted = true; auto* item = entry.item; QTimer::singleShot(m_readyDelay, this, [this, item] { - auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; - const int idx = indexIt.value(); - auto it = m_delegates.find(idx); - if (it == m_delegates.end() || it->item != item || !it->pendingInsert) - return; + auto indexIt = m_itemToIndex.find(item); + if (indexIt == m_itemToIndex.end()) + return; + const int idx = indexIt.value(); + auto it = m_delegates.find(idx); + if (it == m_delegates.end() || it->item != item || + !it->pendingInsert) + return; - it->pendingInsert = false; - it->readyDelayStarted = false; + it->pendingInsert = false; + it->readyDelayStarted = false; - // Set initial y to visual position (based on current visible heights) - if (idx >= 0 && idx < static_cast(m_layout.size())) { - qreal visualY = 0; - bool hasVisItem = false; - for (int i = 0; i < static_cast(m_layout.size()); ++i) { - qreal h; - auto dit = m_delegates.find(i); - if (dit != m_delegates.end() && dit->item) - h = delegateVisibleHeight(dit->item); - else - h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); - if (h > 0) { - if (hasVisItem) - visualY += m_spacing; - hasVisItem = true; - } - if (i == idx) - break; - if (h > 0) - visualY += h; + // Set initial y to visual position (based on current visible heights) + if (idx >= 0 && idx < static_cast(m_layout.size())) { + qreal visualY = 0; + bool hasVisItem = false; + for (int i = 0; i < static_cast(m_layout.size()); + ++i) { + qreal h = NAN; + auto dit = m_delegates.find(i); + if (dit != m_delegates.end() && dit->item) + h = delegateVisibleHeight(dit->item); + else + h = m_layout[i].heightKnown + ? m_layout[i].height + : effectiveEstimatedHeight(); + if (h > 0) { + if (hasVisItem) + visualY += m_spacing; + hasVisItem = true; } - item->setY(visualY - m_contentY); + if (i == idx) + break; + if (h > 0) + visualY += h; } + item->setY(visualY - m_contentY); + } - item->setVisible(true); - auto* att = - qobject_cast(qmlAttachedPropertiesObject(item, false)); - if (att) { - att->setAdding(false); - att->setReady(true); - } + item->setVisible(true); + auto* att = qobject_cast( + qmlAttachedPropertiesObject(item, false)); + if (att) { + att->setAdding(false); + att->setReady(true); + } - // Animate from visual position to layout position - if (idx >= 0 && idx < static_cast(m_layout.size())) - item->setProperty("y", m_layout[idx].targetY - m_contentY); + // Animate from visual position to layout position + if (idx >= 0 && idx < static_cast(m_layout.size())) + item->setProperty("y", + m_layout[idx].targetY - m_contentY); - polish(); - }); + polish(); + }); } continue; } entry.pendingInsert = false; entry.item->setVisible(true); - auto* att = qobject_cast(qmlAttachedPropertiesObject(entry.item, false)); + auto* att = qobject_cast( + qmlAttachedPropertiesObject(entry.item, false)); if (att) { att->setAdding(false); att->setReady(true); @@ -429,12 +426,13 @@ void LazyListView::updatePolish() { record.isNew = false; // Position delegates — QML Behavior on y handles the animation + const int layoutSize = static_cast(m_layout.size()); for (auto& entry : m_delegates) { if (!entry.item || entry.pendingRemoval || entry.pendingInsert) continue; const int idx = entry.modelIndex; - if (idx < 0 || idx >= static_cast(m_layout.size())) + if (idx < 0 || idx >= layoutSize) continue; if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height)) @@ -454,7 +452,8 @@ void LazyListView::relayout() { qreal y = 0; bool hasLayoutItem = false; for (auto& record : m_layout) { - const qreal layoutH = record.heightKnown ? record.height : effectiveEstimatedHeight(); + const qreal layoutH = record.heightKnown ? record.height + : effectiveEstimatedHeight(); if (layoutH > 0) { if (hasLayoutItem) y += m_spacing; @@ -476,12 +475,13 @@ void LazyListView::relayout() { qreal visY = 0; bool hasVisItem = false; for (int i = 0; i < static_cast(m_layout.size()); ++i) { - qreal h; + qreal h = NAN; auto dit = m_delegates.find(i); if (dit != m_delegates.end() && dit->item) h = delegateVisibleHeight(dit->item); else - h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); + h = m_layout[i].heightKnown ? m_layout[i].height + : effectiveEstimatedHeight(); if (h > 0) { if (hasVisItem) visY += m_spacing; @@ -542,11 +542,11 @@ QRectF LazyListView::effectiveViewport() const { std::pair LazyListView::computeVisibleRange() const { if (m_layout.isEmpty()) - return { -1, -1 }; + return {-1, -1}; const auto vp = effectiveViewport(); if (vp.isEmpty()) - return { -1, -1 }; + return {-1, -1}; const qreal vpTop = vp.y(); const qreal vpBottom = vp.y() + vp.height(); @@ -559,7 +559,9 @@ std::pair LazyListView::computeVisibleRange() const { while (lo <= hi) { const int mid = lo + (hi - lo) / 2; const auto& record = m_layout[mid]; - const qreal itemBottom = record.targetY + (record.heightKnown ? record.height : effectiveEstimatedHeight()); + const qreal itemBottom = + record.targetY + + (record.heightKnown ? record.height : effectiveEstimatedHeight()); if (itemBottom >= vpTop) { first = mid; @@ -570,7 +572,7 @@ std::pair LazyListView::computeVisibleRange() const { } if (first >= static_cast(m_layout.size())) - return { -1, -1 }; + return {-1, -1}; // Linear scan for last visible item int last = first; @@ -580,7 +582,7 @@ std::pair LazyListView::computeVisibleRange() const { last = i; } - return { first, last }; + return {first, last}; } // --- Delegate Lifecycle --- @@ -612,9 +614,12 @@ void LazyListView::syncDelegates() { } // Batch destroy - const int destroyBudget = m_asynchronous ? ASYNC_BATCH_DESTROY : static_cast(toRemove.size()); + const int destroyBudget = m_asynchronous + ? ASYNC_BATCH_DESTROY + : static_cast(toRemove.size()); QVector removedEntries; - removedEntries.reserve(std::min(destroyBudget, static_cast(toRemove.size()))); + removedEntries.reserve( + std::min(destroyBudget, static_cast(toRemove.size()))); int destroyed = 0; for (int idx : toRemove) { if (destroyed >= destroyBudget) @@ -638,7 +643,8 @@ void LazyListView::syncDelegates() { } // Batch create - const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE : static_cast(toCreate.size()); + const int createBudget = m_asynchronous ? ASYNC_BATCH_CREATE + : static_cast(toCreate.size()); int created = 0; for (int i : toCreate) { if (created >= createBudget) @@ -658,8 +664,9 @@ void LazyListView::syncDelegates() { // Pending inserts need to become visible on the next frame, and // async mode may have remaining create/destroy work. - if (created > 0 || (m_asynchronous && (destroyed < static_cast(toRemove.size()) || - created < static_cast(toCreate.size())))) + if (created > 0 || + (m_asynchronous && (destroyed < static_cast(toRemove.size()) || + created < static_cast(toCreate.size())))) polish(); } @@ -704,8 +711,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { initialProps.insert(QStringLiteral("index"), modelIndex); if (!hasModelData) { - const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); - initialProps.insert(QStringLiteral("modelData"), m_model->data(index, role)); + const auto role = roleNames.isEmpty() ? Qt::DisplayRole + : roleNames.constBegin().key(); + initialProps.insert(QStringLiteral("modelData"), + m_model->data(index, role)); } m_delegate->setInitialProperties(entry.item, initialProps); @@ -715,9 +724,10 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { // Only set adding = true for genuinely new model items (not viewport entries). // Cleared on the next frame in updatePolish when the item becomes visible. - if (modelIndex < static_cast(m_layout.size()) && m_layout[modelIndex].isNew) { - auto* addingAttached = - qobject_cast(qmlAttachedPropertiesObject(entry.item, true)); + if (modelIndex < static_cast(m_layout.size()) && + m_layout[modelIndex].isNew) { + auto* addingAttached = qobject_cast( + qmlAttachedPropertiesObject(entry.item, true)); if (addingAttached) addingAttached->setAdding(true); } @@ -730,84 +740,103 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { // Height-change handler — uses m_itemToIndex for O(1) lookup. // Ignored while the delegate is not yet ready. auto onHeightChanged = [this, item = entry.item] { - if (!isDelegateReady(item)) - return; - auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; - const int idx = indexIt.value(); - auto delegateIt = m_delegates.find(idx); - if (delegateIt == m_delegates.end() || delegateIt->item != item) - return; - const qreal h = delegateHeight(item); - if (idx < static_cast(m_layout.size()) && !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) { - const qreal oldH = m_layout[idx].height; - const bool wasKnown = m_layout[idx].heightKnown; - m_layout[idx].height = h; - m_layout[idx].heightKnown = true; - if (wasKnown) - untrackHeight(oldH); - trackHeight(h); + if (!isDelegateReady(item)) + return; + auto indexIt = m_itemToIndex.find(item); + if (indexIt == m_itemToIndex.end()) + return; + const int idx = indexIt.value(); + auto delegateIt = m_delegates.find(idx); + if (delegateIt == m_delegates.end() || delegateIt->item != item) + return; + const qreal h = delegateHeight(item); + if (idx < static_cast(m_layout.size()) && + !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) { + const qreal oldH = m_layout[idx].height; + const bool wasKnown = m_layout[idx].heightKnown; + m_layout[idx].height = h; + m_layout[idx].heightKnown = true; + if (wasKnown) + untrackHeight(oldH); + trackHeight(h); - // If this tracked item is above the viewport, emit a - // compensation delta so the consumer can adjust scroll. - if (wasKnown) { - auto* att = qobject_cast(qmlAttachedPropertiesObject(item, false)); - if (att && att->trackViewport()) { - const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; - if (m_layout[idx].targetY < vpTop) - emit viewportAdjustNeeded(h - oldH); - } - } + // If this tracked item is above the viewport, emit a + // compensation delta so the consumer can adjust scroll. + if (wasKnown) { + auto* att = qobject_cast( + qmlAttachedPropertiesObject(item, false)); + if (att && att->trackViewport()) { + const qreal vpTop = m_useCustomViewport ? m_viewport.y() + : m_contentY; + if (m_layout[idx].targetY < vpTop) + emit viewportAdjustNeeded(h - oldH); + } + } - if (!m_relayoutPending) { - m_relayoutPending = true; - QTimer::singleShot(0, this, [this] { - m_relayoutPending = false; - relayout(); - polish(); - }); - } - } - }; + if (!m_relayoutPending) { + m_relayoutPending = true; + QTimer::singleShot(0, this, [this] { + m_relayoutPending = false; + relayout(); + polish(); + }); + } + } + }; // Watch implicitHeight as fallback - connect(entry.item, &QQuickItem::implicitHeightChanged, this, onHeightChanged); + connect(entry.item, + &QQuickItem::implicitHeightChanged, + this, + onHeightChanged); // Watch attached properties if the delegate uses them - auto* attached = qobject_cast(qmlAttachedPropertiesObject(entry.item, false)); + auto* attached = qobject_cast( + qmlAttachedPropertiesObject(entry.item, false)); if (attached) { - connect(attached, &LazyListViewAttached::preferredHeightChanged, this, onHeightChanged); - connect(attached, &LazyListViewAttached::visibleHeightChanged, this, [this] { - polish(); - }); - connect(attached, &LazyListViewAttached::readyChanged, this, [this, item = entry.item] { - auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; - const int idx = indexIt.value(); - if (idx >= static_cast(m_layout.size())) - return; - auto* att = qobject_cast(qmlAttachedPropertiesObject(item, false)); - if (!att || !att->ready()) - return; + connect(attached, + &LazyListViewAttached::preferredHeightChanged, + this, + onHeightChanged); + connect(attached, + &LazyListViewAttached::visibleHeightChanged, + this, + [this] { polish(); }); + connect(attached, + &LazyListViewAttached::readyChanged, + this, + [this, item = entry.item] { + auto indexIt = m_itemToIndex.find(item); + if (indexIt == m_itemToIndex.end()) + return; + const int idx = indexIt.value(); + if (idx >= static_cast(m_layout.size())) + return; + auto* att = qobject_cast( + qmlAttachedPropertiesObject(item, false)); + if (!att || !att->ready()) + return; - const qreal h = delegateHeight(item); - const qreal oldLayoutH = m_layout[idx].heightKnown ? m_layout[idx].height : effectiveEstimatedHeight(); - if (m_layout[idx].heightKnown) - untrackHeight(m_layout[idx].height); - m_layout[idx].height = h; - m_layout[idx].heightKnown = true; - trackHeight(h); + const qreal h = delegateHeight(item); + const qreal oldLayoutH = m_layout[idx].heightKnown + ? m_layout[idx].height + : effectiveEstimatedHeight(); + if (m_layout[idx].heightKnown) + untrackHeight(m_layout[idx].height); + m_layout[idx].height = h; + m_layout[idx].heightKnown = true; + trackHeight(h); - if (att->trackViewport() && !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) { - const qreal vpTop = m_useCustomViewport ? m_viewport.y() : m_contentY; - if (m_layout[idx].targetY < vpTop) - emit viewportAdjustNeeded(h - oldLayoutH); - } + if (att->trackViewport() && + !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) { + const qreal vpTop = m_useCustomViewport ? m_viewport.y() + : m_contentY; + if (m_layout[idx].targetY < vpTop) + emit viewportAdjustNeeded(h - oldLayoutH); + } - polish(); - }); + polish(); + }); } return entry; @@ -832,7 +861,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) { for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) { const auto name = QString::fromUtf8(it.value()); - entry.item->setProperty(name.toUtf8().constData(), m_model->data(index, it.key())); + entry.item->setProperty(name.toUtf8().constData(), + m_model->data(index, it.key())); if (name == QStringLiteral("modelData")) hasModelData = true; } @@ -840,7 +870,8 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) { entry.item->setProperty("index", entry.modelIndex); if (!hasModelData) { - const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); + const auto role = roleNames.isEmpty() ? Qt::DisplayRole + : roleNames.constBegin().key(); entry.item->setProperty("modelData", m_model->data(index, role)); } } @@ -852,24 +883,46 @@ void LazyListView::connectModel() { return; m_modelConnections = { - connect(m_model, &QAbstractItemModel::rowsInserted, this, &LazyListView::onRowsInserted), - connect(m_model, &QAbstractItemModel::rowsAboutToBeRemoved, this, &LazyListView::onRowsAboutToBeRemoved), - connect(m_model, &QAbstractItemModel::rowsRemoved, this, &LazyListView::onRowsRemoved), - connect(m_model, &QAbstractItemModel::rowsMoved, this, &LazyListView::onRowsMoved), - connect(m_model, &QAbstractItemModel::dataChanged, this, &LazyListView::onDataChanged), - connect(m_model, &QAbstractItemModel::modelReset, this, &LazyListView::onModelReset), - connect(m_model, &QAbstractItemModel::layoutChanged, this, - [this] { - for (auto& entry : m_delegates) - updateDelegateData(entry); - polish(); - }), - connect(m_model, &QObject::destroyed, this, - [this] { - m_model = nullptr; - resetContent(); - emit modelChanged(); - }), + connect(m_model, + &QAbstractItemModel::rowsInserted, + this, + &LazyListView::onRowsInserted), + connect(m_model, + &QAbstractItemModel::rowsAboutToBeRemoved, + this, + &LazyListView::onRowsAboutToBeRemoved), + connect(m_model, + &QAbstractItemModel::rowsRemoved, + this, + &LazyListView::onRowsRemoved), + connect(m_model, + &QAbstractItemModel::rowsMoved, + this, + &LazyListView::onRowsMoved), + connect(m_model, + &QAbstractItemModel::dataChanged, + this, + &LazyListView::onDataChanged), + connect(m_model, + &QAbstractItemModel::modelReset, + this, + &LazyListView::onModelReset), + connect(m_model, + &QAbstractItemModel::layoutChanged, + this, + [this] { + for (auto& entry : m_delegates) + updateDelegateData(entry); + polish(); + }), + connect(m_model, + &QObject::destroyed, + this, + [this] { + m_model = nullptr; + resetContent(); + emit modelChanged(); + }), }; } @@ -909,13 +962,15 @@ void LazyListView::resetContent() { polish(); } -void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last) { +void LazyListView::onRowsInserted(const QModelIndex& parent, + int first, + int last) { if (parent.isValid()) return; const int insertCount = last - first + 1; // Insert new layout records - m_layout.insert(first, insertCount, ItemRecord{ 0, 0, false, true }); + m_layout.insert(first, insertCount, ItemRecord{0, 0, false, true}); // Shift existing delegate indices QHash shifted; @@ -935,7 +990,9 @@ void LazyListView::onRowsInserted(const QModelIndex& parent, int first, int last polish(); } -void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { +void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, + int first, + int last) { if (parent.isValid()) return; @@ -955,22 +1012,24 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first, } if (m_removeDuration > 0 && entry.item) { - auto* attached = - qobject_cast(qmlAttachedPropertiesObject(entry.item, false)); + auto* attached = qobject_cast( + qmlAttachedPropertiesObject(entry.item, false)); if (attached) attached->setRemoving(true); // Schedule destruction after the remove animation duration auto* item = entry.item; QTimer::singleShot(m_removeDuration, this, [this, item] { - for (auto it = m_dyingDelegates.begin(); it != m_dyingDelegates.end(); ++it) { - if (it->item == item) { - destroyDelegate(*it); - m_dyingDelegates.erase(it); - return; - } + for (auto it = m_dyingDelegates.begin(); + it != m_dyingDelegates.end(); + ++it) { + if (it->item == item) { + destroyDelegate(*it); + m_dyingDelegates.erase(it); + return; } - }); + } + }); m_dyingDelegates.append(std::move(entry)); } else { destroyDelegate(entry); @@ -978,7 +1037,9 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, int first, } } -void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last) { +void LazyListView::onRowsRemoved(const QModelIndex& parent, + int first, + int last) { if (parent.isValid()) return; @@ -1011,7 +1072,11 @@ void LazyListView::onRowsRemoved(const QModelIndex& parent, int first, int last) polish(); } -void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row) { +void LazyListView::onRowsMoved(const QModelIndex& parent, + int start, + int end, + const QModelIndex& destination, + int row) { if (parent.isValid() || destination.isValid()) return; @@ -1055,7 +1120,9 @@ void LazyListView::onRowsMoved(const QModelIndex& parent, int start, int end, co polish(); } -void LazyListView::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles) { +void LazyListView::onDataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QList& roles) { Q_UNUSED(roles) if (topLeft.parent().isValid()) @@ -1079,15 +1146,18 @@ void LazyListView::onModelReset() { // Check if the model data actually changed if (newRows == oldRows) { const auto roleNames = m_model->roleNames(); - const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); + const auto role = roleNames.isEmpty() ? Qt::DisplayRole + : roleNames.constBegin().key(); bool changed = false; - for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) { + for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); + ++it) { if (!it->item || it.key() >= newRows) { changed = true; break; } - const auto newData = m_model->data(m_model->index(it.key(), 0), role); + const auto newData = + m_model->data(m_model->index(it.key(), 0), role); const auto oldData = it->item->property("modelData"); if (newData != oldData) { changed = true; From 4a18edfc78d38daf5a6d52d31ca77c521a579604 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 01:18:07 +0200 Subject: [PATCH 18/44] fix(strokecanvasitem): use qreal instead of float to suppress -Wdouble-promotion --- Plugins/ZShell/Internal/stroke.hpp | 4 +- Plugins/ZShell/Internal/strokecanvasitem.cpp | 84 +++++---- Plugins/ZShell/Internal/strokecanvasitem.hpp | 95 +++++------ .../ZShell/Internal/strokecanvasrenderer.cpp | 161 +++++++++--------- .../ZShell/Internal/strokecanvasrenderer.hpp | 28 ++- 5 files changed, 184 insertions(+), 188 deletions(-) diff --git a/Plugins/ZShell/Internal/stroke.hpp b/Plugins/ZShell/Internal/stroke.hpp index 8f9bd1d..aba955b 100644 --- a/Plugins/ZShell/Internal/stroke.hpp +++ b/Plugins/ZShell/Internal/stroke.hpp @@ -11,9 +11,9 @@ struct Stroke { QVector points; QCanvasPath path; QColor color; - float width; + qreal width; int groupId = -1; bool isSinglePoint = false; }; -}; +}; // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/strokecanvasitem.cpp b/Plugins/ZShell/Internal/strokecanvasitem.cpp index 41295c8..4f8d615 100644 --- a/Plugins/ZShell/Internal/strokecanvasitem.cpp +++ b/Plugins/ZShell/Internal/strokecanvasitem.cpp @@ -6,55 +6,52 @@ namespace ZShell::internal { -StrokeCanvasItem::StrokeCanvasItem(QQuickItem *parent) : QCanvasPainterItem(parent) { +StrokeCanvasItem::StrokeCanvasItem(QQuickItem* parent) + : QCanvasPainterItem(parent) { setFillColor(Qt::transparent); setAlphaBlending(true); } -QCanvasPainterItemRenderer *StrokeCanvasItem::createItemRenderer() const { +QCanvasPainterItemRenderer* StrokeCanvasItem::createItemRenderer() const { return new StrokeCanvasRenderer; } static bool shouldAddPoint( - const QVector &points, - const QPointF &p, - qreal minDistance) -{ - if (points.isEmpty()) - return true; + const QVector& points, const QPointF& p, qreal minDistance) { + if (points.isEmpty()) return true; const QPointF delta = p - points.last(); - return QPointF::dotProduct(delta, delta) - >= minDistance * minDistance; + return QPointF::dotProduct(delta, delta) >= minDistance * minDistance; } -static QCanvasPath buildStrokePath(const QVector &points, float width) { +static QCanvasPath buildStrokePath(const QVector& points, qreal width) { QCanvasPath path; if (points.size() == 1) { - path.circle(points[0], width * 0.5f); + path.circle(points[0], width * 0.5); return path; } - auto catmullToBezier = []( - const QPointF &p0, const QPointF &p1, - const QPointF &p2, const QPointF &p3, - float tension, - QPointF &cp1, QPointF &cp2) - { - cp1 = p1 + (p2 - p0) * tension / 3.0f; - cp2 = p2 - (p3 - p1) * tension / 3.0f; - }; + auto catmullToBezier = [](const QPointF& p0, + const QPointF& p1, + const QPointF& p2, + const QPointF& p3, + qreal tension, + QPointF& cp1, + QPointF& cp2) { + cp1 = p1 + (p2 - p0) * tension / 3.0; + cp2 = p2 - (p3 - p1) * tension / 3.0; + }; - const float tension = 0.5f; + const qreal tension = 0.5; path.moveTo(points[0]); for (int i = 0; i < points.size() - 1; ++i) { - const QPointF &p0 = points[qMax(i - 1, 0)]; - const QPointF &p1 = points[i]; - const QPointF &p2 = points[i + 1]; - const QPointF &p3 = points[qMin(i + 2, points.size() - 1)]; + const QPointF& p0 = points[qMax(i - 1, 0)]; + const QPointF& p1 = points[i]; + const QPointF& p2 = points[i + 1]; + const QPointF& p3 = points[qMin(i + 2, points.size() - 1)]; QPointF cp1, cp2; catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2); @@ -64,9 +61,8 @@ static QCanvasPath buildStrokePath(const QVector &points, float width) return path; } -void StrokeCanvasItem::setPenColor(const QColor &color) { - if (m_penColor == color) - return; +void StrokeCanvasItem::setPenColor(const QColor& color) { + if (m_penColor == color) return; m_penColor = color; update(); @@ -75,8 +71,7 @@ void StrokeCanvasItem::setPenColor(const QColor &color) { } void StrokeCanvasItem::setHoverVisible(bool visible) { - if (m_hoverVisible == visible) - return; + if (m_hoverVisible == visible) return; m_hoverVisible = visible; update(); @@ -84,9 +79,8 @@ void StrokeCanvasItem::setHoverVisible(bool visible) { emit hoverVisibleChanged(); } -void StrokeCanvasItem::setHoverPoint(const QPointF &point) { - if (m_hoverPoint == point) - return; +void StrokeCanvasItem::setHoverPoint(const QPointF& point) { + if (m_hoverPoint == point) return; m_hoverPoint = point; update(); @@ -110,8 +104,7 @@ void StrokeCanvasItem::showHover(qreal x, qreal y) { } void StrokeCanvasItem::hideHover() { - if (!m_hoverVisible) - return; + if (!m_hoverVisible) return; m_hoverVisible = false; update(); @@ -119,9 +112,8 @@ void StrokeCanvasItem::hideHover() { emit hoverVisibleChanged(); } -void StrokeCanvasItem::setPenWidth(float width) { - if (qFuzzyCompare(m_penWidth, width)) - return; +void StrokeCanvasItem::setPenWidth(qreal width) { + if (qFuzzyCompare(m_penWidth, width)) return; m_penWidth = width; update(); @@ -143,8 +135,7 @@ void StrokeCanvasItem::beginStroke(qreal x, qreal y) { void StrokeCanvasItem::appendPoint(qreal x, qreal y) { const QPointF incoming{x, y}; - if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0)) - return; + if (!shouldAddPoint(m_currentStroke.points, incoming, 2.0)) return; QPointF smoothed; if (m_currentStroke.points.isEmpty()) { @@ -159,7 +150,8 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) { constexpr qreal minAlpha = 0.1; constexpr qreal maxAlpha = 0.3; - const qreal t = std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0); + const qreal t = + std::clamp((dist - minDist) / (maxDist - minDist), 0.0, 1.0); const qreal alpha = minAlpha + t * (maxAlpha - minAlpha); smoothed = last * (1.0 - alpha) + incoming * alpha; @@ -171,11 +163,11 @@ void StrokeCanvasItem::appendPoint(qreal x, qreal y) { void StrokeCanvasItem::endStroke() { m_isDrawing = false; - if (m_currentStroke.points.isEmpty()) - return; + if (m_currentStroke.points.isEmpty()) return; m_currentStroke.isSinglePoint = (m_currentStroke.points.size() == 1); - m_currentStroke.path = buildStrokePath(m_currentStroke.points, m_currentStroke.width); + m_currentStroke.path = + buildStrokePath(m_currentStroke.points, m_currentStroke.width); m_currentStroke.groupId = m_nextGroupId++; m_currentStroke.points.clear(); m_strokes.append(m_currentStroke); @@ -190,4 +182,4 @@ void StrokeCanvasItem::clear() { update(); } -}; +}; // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/strokecanvasitem.hpp b/Plugins/ZShell/Internal/strokecanvasitem.hpp index cf40da7..67a75fd 100644 --- a/Plugins/ZShell/Internal/strokecanvasitem.hpp +++ b/Plugins/ZShell/Internal/strokecanvasitem.hpp @@ -13,68 +13,67 @@ namespace ZShell::internal { class StrokeCanvasRenderer; class StrokeCanvasItem : public QCanvasPainterItem { -Q_OBJECT + Q_OBJECT -QML_NAMED_ELEMENT(StrokeCanvas) + QML_NAMED_ELEMENT(StrokeCanvas) -Q_PROPERTY(QColor penColor READ penColor WRITE setPenColor NOTIFY penColorChanged) -Q_PROPERTY(bool hoverVisible READ hoverVisible WRITE setHoverVisible NOTIFY hoverVisibleChanged) -Q_PROPERTY(QPointF hoverPoint READ hoverPoint WRITE setHoverPoint NOTIFY hoverPointChanged) -Q_PROPERTY(qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged) + Q_PROPERTY( + QColor penColor READ penColor WRITE setPenColor NOTIFY penColorChanged) + Q_PROPERTY( + bool hoverVisible READ hoverVisible WRITE setHoverVisible NOTIFY + hoverVisibleChanged) + Q_PROPERTY( + QPointF hoverPoint READ hoverPoint WRITE setHoverPoint NOTIFY + hoverPointChanged) + Q_PROPERTY( + qreal penWidth READ penWidth WRITE setPenWidth NOTIFY penWidthChanged) -public: -explicit StrokeCanvasItem(QQuickItem *parent = nullptr); + public: + explicit StrokeCanvasItem(QQuickItem* parent = nullptr); -[[nodiscard]] bool hoverVisible() const { - return m_hoverVisible; -} -[[nodiscard]] QPointF hoverPoint() const { - return m_hoverPoint; -} + [[nodiscard]] bool hoverVisible() const { return m_hoverVisible; } + [[nodiscard]] QPointF hoverPoint() const { return m_hoverPoint; } -void setHoverVisible(bool visible); -void setHoverPoint(const QPointF &point); + void setHoverVisible(bool visible); + void setHoverPoint(const QPointF& point); -Q_INVOKABLE void showHover(qreal x, qreal y); -Q_INVOKABLE void hideHover(); + Q_INVOKABLE void showHover(qreal x, qreal y); + Q_INVOKABLE void hideHover(); -[[nodiscard]] QColor penColor() const { - return m_penColor; -} -[[nodiscard]] float penWidth() const { - return m_penWidth; -} + [[nodiscard]] QColor penColor() const { return m_penColor; } + [[nodiscard]] qreal penWidth() const { return m_penWidth; } -void setPenColor(const QColor &color); -void setPenWidth(float width); + void setPenColor(const QColor& color); + void setPenWidth(qreal width); -Q_INVOKABLE void clear(); + Q_INVOKABLE void clear(); -Q_INVOKABLE void beginStroke(qreal x, qreal y); -Q_INVOKABLE void appendPoint(qreal x, qreal y); -Q_INVOKABLE void endStroke(); + Q_INVOKABLE void beginStroke(qreal x, qreal y); + Q_INVOKABLE void appendPoint(qreal x, qreal y); + Q_INVOKABLE void endStroke(); -signals: -void penColorChanged(); -void penWidthChanged(); -void hoverVisibleChanged(); -void hoverPointChanged(); + signals: + void penColorChanged(); + void penWidthChanged(); + void hoverVisibleChanged(); + void hoverPointChanged(); -protected: -[[nodiscard]] QCanvasPainterItemRenderer *createItemRenderer() const override; + protected: + [[nodiscard]] QCanvasPainterItemRenderer* createItemRenderer() + const override; -private: -friend class StrokeCanvasRenderer; + private: + friend class StrokeCanvasRenderer; -bool m_hoverVisible = false; -QPointF m_hoverPoint; -QColor m_penColor = Qt::white; -float m_penWidth = 4.f; -bool m_isDrawing = false; + bool m_hoverVisible = false; + QPointF m_hoverPoint; + QColor m_penColor = Qt::white; + qreal m_penWidth = 4.0; + bool m_isDrawing = false; -int m_nextGroupId = 0; -QVector m_strokes; -Stroke m_currentStroke; + int m_nextGroupId = 0; + QVector m_strokes; + Stroke m_currentStroke; }; -}; +}; // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp index a648f9a..2e4c5fe 100644 --- a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp @@ -5,13 +5,11 @@ namespace ZShell::internal { static void drawStroke( - QCanvasPainter *painter, - const QVector &points, - const QColor &color, - float width) { - - if (points.isEmpty()) - return; + QCanvasPainter* painter, + const QVector& points, + const QColor& color, + qreal width) { + if (points.isEmpty()) return; painter->setStrokeStyle(color); painter->setFillStyle(color); @@ -26,26 +24,27 @@ static void drawStroke( return; } - auto catmullToBezier = []( - const QPointF &p0, const QPointF &p1, - const QPointF &p2, const QPointF &p3, - float tension, - QPointF &cp1, QPointF &cp2) - { - cp1 = p1 + (p2 - p0) * tension / 3.0f; - cp2 = p2 - (p3 - p1) * tension / 3.0f; - }; + auto catmullToBezier = [](const QPointF& p0, + const QPointF& p1, + const QPointF& p2, + const QPointF& p3, + qreal tension, + QPointF& cp1, + QPointF& cp2) { + cp1 = p1 + (p2 - p0) * tension / 3.0; + cp2 = p2 - (p3 - p1) * tension / 3.0; + }; - const float tension = 0.5f; // increase toward 1.0 for tighter curves + const qreal tension = 0.5; // increase toward 1.0 for tighter curves painter->beginPath(); painter->moveTo(points[0]); for (int i = 0; i < points.size() - 1; ++i) { - const QPointF &p0 = points[qMax(i - 1, 0)]; - const QPointF &p1 = points[i]; - const QPointF &p2 = points[i + 1]; - const QPointF &p3 = points[qMin(i + 2, points.size() - 1)]; + const QPointF& p0 = points[qMax(i - 1, 0)]; + const QPointF& p1 = points[i]; + const QPointF& p2 = points[i + 1]; + const QPointF& p3 = points[qMin(i + 2, points.size() - 1)]; QPointF cp1, cp2; catmullToBezier(p0, p1, p2, p3, tension, cp1, cp2); @@ -56,13 +55,12 @@ static void drawStroke( } static void drawHoverCursor( - QCanvasPainter *painter, - const QPointF &point, - float penWidth, + QCanvasPainter* painter, + const QPointF& point, + qreal penWidth, QColor penColor, - bool isDrawing) -{ - const float radius = penWidth * 0.5f; + bool isDrawing) { + const qreal radius = penWidth * 0.5; if (isDrawing) { painter->setFillStyle(penColor); @@ -73,55 +71,59 @@ static void drawHoverCursor( const float lineWidth = 1.5f; const float crosshairSize = 6.0f; - const bool useDashes = penWidth > 10.0f; + const bool useDashes = penWidth > 10.0; - auto drawOutline = [&](const QColor &color, float width) { - painter->setStrokeStyle(color); - painter->setLineWidth(width); - painter->setLineCap(QCanvasPainter::LineCap::Round); + auto drawOutline = [&](const QColor& color, qreal width) { + painter->setStrokeStyle(color); + painter->setLineWidth(width); + painter->setLineCap(QCanvasPainter::LineCap::Round); - if (useDashes) { - const int dashCount = 12; - const float fullAngle = 2.0f * M_PI; - const float dashAngle = fullAngle / dashCount * 0.5f; - const float gapAngle = fullAngle / dashCount * 0.5f; + if (useDashes) { + const int dashCount = 12; + const float fullAngle = 2.0f * M_PI; + const float dashAngle = fullAngle / dashCount * 0.5f; + const float gapAngle = fullAngle / dashCount * 0.5f; - float angle = 0.0f; - for (int i = 0; i < dashCount; ++i) { - painter->beginPath(); - painter->arc(point, radius, angle, angle + dashAngle, - QCanvasPainter::PathWinding::ClockWise, - QCanvasPainter::PathConnection::NotConnected); - painter->stroke(); - angle += dashAngle + gapAngle; - } - } else { - painter->beginPath(); - painter->circle(point, radius); - painter->stroke(); - } - }; + float angle = 0.0f; + for (int i = 0; i < dashCount; ++i) { + painter->beginPath(); + painter->arc( + point, + radius, + angle, + angle + dashAngle, + QCanvasPainter::PathWinding::ClockWise, + QCanvasPainter::PathConnection::NotConnected); + painter->stroke(); + angle += dashAngle + gapAngle; + } + } else { + painter->beginPath(); + painter->circle(point, radius); + painter->stroke(); + } + }; - auto drawCrosshair = [&](const QColor &color, float width) { - painter->setStrokeStyle(color); - painter->setLineWidth(width); - painter->setLineCap(QCanvasPainter::LineCap::Round); + auto drawCrosshair = [&](const QColor& color, qreal width) { + painter->setStrokeStyle(color); + painter->setLineWidth(width); + painter->setLineCap(QCanvasPainter::LineCap::Round); - const float inner = radius + 3.0f; - const float outer = radius + 3.0f + crosshairSize; + const float inner = radius + 3.0f; + const float outer = radius + 3.0f + crosshairSize; - painter->beginPath(); - painter->moveTo(point + QPointF(0, -outer)); - painter->lineTo(point + QPointF(0, -inner)); - painter->moveTo(point + QPointF(0, outer)); - painter->lineTo(point + QPointF(0, inner)); - painter->moveTo(point + QPointF(-outer, 0)); - painter->lineTo(point + QPointF(-inner, 0)); - painter->moveTo(point + QPointF( outer, 0)); - painter->lineTo(point + QPointF( inner, 0)); - painter->stroke(); - }; + painter->beginPath(); + painter->moveTo(point + QPointF(0, -outer)); + painter->lineTo(point + QPointF(0, -inner)); + painter->moveTo(point + QPointF(0, outer)); + painter->lineTo(point + QPointF(0, inner)); + painter->moveTo(point + QPointF(-outer, 0)); + painter->lineTo(point + QPointF(-inner, 0)); + painter->moveTo(point + QPointF(outer, 0)); + painter->lineTo(point + QPointF(inner, 0)); + painter->stroke(); + }; drawOutline(QColor(0, 0, 0, 160), lineWidth + 1.0f); drawCrosshair(QColor(0, 0, 0, 160), lineWidth + 1.0f); @@ -130,8 +132,8 @@ static void drawHoverCursor( drawCrosshair(QColor(255, 255, 255, 220), lineWidth); } -void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { - auto *canvas = static_cast(item); +void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem* item) { + auto* canvas = static_cast(item); m_penColor = canvas->m_penColor; m_penWidth = canvas->m_penWidth; @@ -140,7 +142,7 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { m_strokes.append(canvas->m_strokes[m_strokes.size()]); if (canvas->m_strokes.isEmpty() && !m_strokes.isEmpty()) { - for (const auto &stroke : m_strokes) + for (const auto& stroke : m_strokes) if (stroke.groupId >= 0) m_pendingGroupRemovals.append(stroke.groupId); m_strokes.clear(); @@ -153,14 +155,14 @@ void StrokeCanvasRenderer::synchronizeData(QCanvasPainterItem *item) { m_isDrawing = canvas->m_isDrawing; } -void StrokeCanvasRenderer::paint(QCanvasPainter *painter) { +void StrokeCanvasRenderer::paint(QCanvasPainter* painter) { for (int id : m_pendingGroupRemovals) painter->removePathGroup(id); m_pendingGroupRemovals.clear(); painter->clearRect(0, 0, width(), height()); - for (const auto &stroke : m_strokes) { + for (const auto& stroke : m_strokes) { painter->setStrokeStyle(stroke.color); painter->setFillStyle(stroke.color); painter->setLineWidth(stroke.width); @@ -174,10 +176,15 @@ void StrokeCanvasRenderer::paint(QCanvasPainter *painter) { } } - drawStroke(painter, m_currentStroke.points, m_currentStroke.color, m_currentStroke.width); + drawStroke( + painter, + m_currentStroke.points, + m_currentStroke.color, + m_currentStroke.width); if (m_hoverVisible) - drawHoverCursor(painter, m_hoverPoint, m_penWidth, m_penColor, m_isDrawing); + drawHoverCursor( + painter, m_hoverPoint, m_penWidth, m_penColor, m_isDrawing); } -}; +}; // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.hpp b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp index 74ff1f6..2877b6d 100644 --- a/Plugins/ZShell/Internal/strokecanvasrenderer.hpp +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.hpp @@ -7,22 +7,20 @@ namespace ZShell::internal { class StrokeCanvasRenderer final : public QCanvasPainterItemRenderer { + public: + void synchronizeData(QCanvasPainterItem* item) override; + void paint(QCanvasPainter* painter) override; -public: -void synchronizeData(QCanvasPainterItem *item) override; -void paint(QCanvasPainter *painter) override; - -private: -QColor m_penColor; -float m_penWidth = 4.f; -bool m_hoverVisible = false; -QPointF m_hoverPoint; -bool m_isDrawing = false; - -QVector m_strokes; -Stroke m_currentStroke; -QVector m_pendingGroupRemovals; + private: + QColor m_penColor; + qreal m_penWidth = 4.0; + bool m_hoverVisible = false; + QPointF m_hoverPoint; + bool m_isDrawing = false; + QVector m_strokes; + Stroke m_currentStroke; + QVector m_pendingGroupRemovals; }; -}; +}; // namespace ZShell::internal From f439456ff4458e17cb17f8b11df95f90703e6196 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 01:26:19 +0200 Subject: [PATCH 19/44] fix(strokecanvasrenderer): use qreal instead of float in drawHoverCursor --- .../ZShell/Internal/strokecanvasrenderer.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp index 2e4c5fe..704fcd4 100644 --- a/Plugins/ZShell/Internal/strokecanvasrenderer.cpp +++ b/Plugins/ZShell/Internal/strokecanvasrenderer.cpp @@ -19,7 +19,7 @@ static void drawStroke( if (points.size() == 1) { painter->beginPath(); - painter->circle(points.front(), width * 0.5f); + painter->circle(points.front(), width * 0.5); painter->fill(); return; } @@ -69,8 +69,8 @@ static void drawHoverCursor( painter->fill(); } - const float lineWidth = 1.5f; - const float crosshairSize = 6.0f; + const qreal lineWidth = 1.5; + const qreal crosshairSize = 6.0; const bool useDashes = penWidth > 10.0; auto drawOutline = [&](const QColor& color, qreal width) { @@ -81,11 +81,11 @@ static void drawHoverCursor( if (useDashes) { const int dashCount = 12; - const float fullAngle = 2.0f * M_PI; - const float dashAngle = fullAngle / dashCount * 0.5f; - const float gapAngle = fullAngle / dashCount * 0.5f; + const qreal fullAngle = 2.0 * M_PI; + const qreal dashAngle = fullAngle / dashCount * 0.5; + const qreal gapAngle = fullAngle / dashCount * 0.5; - float angle = 0.0f; + qreal angle = 0.0; for (int i = 0; i < dashCount; ++i) { painter->beginPath(); painter->arc( @@ -110,8 +110,8 @@ static void drawHoverCursor( painter->setLineWidth(width); painter->setLineCap(QCanvasPainter::LineCap::Round); - const float inner = radius + 3.0f; - const float outer = radius + 3.0f + crosshairSize; + const qreal inner = radius + 3.0; + const qreal outer = radius + 3.0 + crosshairSize; painter->beginPath(); painter->moveTo(point + QPointF(0, -outer)); @@ -125,8 +125,8 @@ static void drawHoverCursor( painter->stroke(); }; - drawOutline(QColor(0, 0, 0, 160), lineWidth + 1.0f); - drawCrosshair(QColor(0, 0, 0, 160), lineWidth + 1.0f); + drawOutline(QColor(0, 0, 0, 160), lineWidth + 1.0); + drawCrosshair(QColor(0, 0, 0, 160), lineWidth + 1.0); drawOutline(QColor(255, 255, 255, 220), lineWidth); drawCrosshair(QColor(255, 255, 255, 220), lineWidth); From 396825d7857434420282fca87d1c6575ad647ddb Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 01:36:14 +0200 Subject: [PATCH 20/44] fix(desktopmodel): cast qsizetype to int to suppress -Wshorten-64-to-32 --- Plugins/ZShell/Services/desktopmodel.cpp | 98 +++++++++++++++--------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/Plugins/ZShell/Services/desktopmodel.cpp b/Plugins/ZShell/Services/desktopmodel.cpp index 2fa55c7..fb361e4 100644 --- a/Plugins/ZShell/Services/desktopmodel.cpp +++ b/Plugins/ZShell/Services/desktopmodel.cpp @@ -6,25 +6,30 @@ namespace ZShell::services { -DesktopModel::DesktopModel(QObject *parent) : QAbstractListModel(parent) { -} +DesktopModel::DesktopModel(QObject* parent) : QAbstractListModel(parent) {} -int DesktopModel::rowCount(const QModelIndex &parent) const { +int DesktopModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; - return m_items.count(); + return static_cast(m_items.size()); } -QVariant DesktopModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() >= m_items.size()) return QVariant(); +QVariant DesktopModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || index.row() >= static_cast(m_items.size())) return QVariant(); - const DesktopItem &item = m_items[index.row()]; + const DesktopItem& item = m_items[index.row()]; switch (role) { - case FileNameRole: return item.fileName; - case FilePathRole: return item.filePath; - case IsDirRole: return item.isDir; - case GridXRole: return item.gridX; - case GridYRole: return item.gridY; - default: return QVariant(); + case FileNameRole: + return item.fileName; + case FilePathRole: + return item.filePath; + case IsDirRole: + return item.isDir; + case GridXRole: + return item.gridX; + case GridYRole: + return item.gridY; + default: + return QVariant(); } } @@ -38,8 +43,8 @@ QHash DesktopModel::roleNames() const { return roles; } -QPoint DesktopModel::getEmptySpot(const QSet &occupied) const { - for (int x = 0; ; ++x) { +QPoint DesktopModel::getEmptySpot(const QSet& occupied) const { + for (int x = 0;; ++x) { for (int y = 0; y < m_rows; ++y) { QString key = QString::number(x) + "," + QString::number(y); if (!occupied.contains(key)) { @@ -49,7 +54,7 @@ QPoint DesktopModel::getEmptySpot(const QSet &occupied) const { } } -void DesktopModel::loadDirectory(const QString &path) { +void DesktopModel::loadDirectory(const QString& path) { m_watchedPath = path; if (!m_watcher.directories().isEmpty()) @@ -57,9 +62,12 @@ void DesktopModel::loadDirectory(const QString &path) { m_watcher.addPath(path); - connect(&m_watcher, &QFileSystemWatcher::directoryChanged, - this, &DesktopModel::onDirectoryChanged, - Qt::UniqueConnection); + connect( + &m_watcher, + &QFileSystemWatcher::directoryChanged, + this, + &DesktopModel::onDirectoryChanged, + Qt::UniqueConnection); beginResetModel(); m_items.clear(); @@ -72,14 +80,16 @@ void DesktopModel::loadDirectory(const QString &path) { QVariantMap savedLayout = sm.getLayout(); QSet occupied; - for (const QFileInfo &fileInfo : list) { + for (const QFileInfo& fileInfo : list) { if (savedLayout.contains(fileInfo.fileName())) { QVariantMap pos = savedLayout[fileInfo.fileName()].toMap(); - occupied.insert(QString::number(pos["x"].toInt()) + "," + QString::number(pos["y"].toInt())); + occupied.insert( + QString::number(pos["x"].toInt()) + "," + + QString::number(pos["y"].toInt())); } } - for (const QFileInfo &fileInfo : list) { + for (const QFileInfo& fileInfo : list) { DesktopItem item; item.fileName = fileInfo.fileName(); item.filePath = fileInfo.absoluteFilePath(); @@ -93,7 +103,9 @@ void DesktopModel::loadDirectory(const QString &path) { QPoint spot = getEmptySpot(occupied); item.gridX = spot.x(); item.gridY = spot.y(); - occupied.insert(QString::number(item.gridX) + "," + QString::number(item.gridY)); + occupied.insert( + QString::number(item.gridX) + "," + + QString::number(item.gridY)); } m_items.append(item); } @@ -105,7 +117,7 @@ void DesktopModel::onDirectoryChanged() { } void DesktopModel::moveIcon(int index, int newX, int newY) { - if (index < 0 || index >= m_items.size()) return; + if (index < 0 || index >= static_cast(m_items.size())) return; m_items[index].gridX = newX; m_items[index].gridY = newY; @@ -129,7 +141,13 @@ void DesktopModel::saveCurrentLayout() { sm.saveLayout(layout); } -void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString& leaderPath, int targetX, int targetY, int maxCol, int maxRow) { +void DesktopModel::massMove( + const QVariantList& selectedPathsList, + const QString& leaderPath, + int targetX, + int targetY, + int maxCol, + int maxRow) { QStringList selectedPaths; for (const QVariant& v : selectedPathsList) { selectedPaths << v.toString(); @@ -151,12 +169,13 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString if (deltaX == 0 && deltaY == 0) return; - if (selectedPaths.size() == 1 && targetX >= 0 && targetX <= maxCol && targetY >= 0 && targetY <= maxRow) { + if (selectedPaths.size() == 1 && targetX >= 0 && targetX <= maxCol && + targetY >= 0 && targetY <= maxRow) { QString movingPath = selectedPaths.first(); int movingIndex = -1; int targetIndex = -1; - for (int i = 0; i < m_items.size(); ++i) { + for (int i = 0; i < static_cast(m_items.size()); ++i) { if (m_items[i].filePath == movingPath) { movingIndex = i; } else if (m_items[i].gridX == targetX && m_items[i].gridY == targetY) { @@ -170,7 +189,10 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString m_items[movingIndex].gridX = targetX; m_items[movingIndex].gridY = targetY; - emit dataChanged(index(0, 0), index(m_items.size() - 1, 0), {GridXRole, GridYRole}); + emit dataChanged( + index(0, 0), + index(static_cast(m_items.size()) - 1, 0), + {GridXRole, GridYRole}); saveCurrentLayout(); return; } @@ -179,11 +201,13 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString QList movingItems; QSet occupied; - for (int i = 0; i < m_items.size(); ++i) { + for (int i = 0; i < static_cast(m_items.size()); ++i) { if (selectedPaths.contains(m_items[i].filePath)) { movingItems.append(&m_items[i]); } else { - occupied.insert(QString::number(m_items[i].gridX) + "," + QString::number(m_items[i].gridY)); + occupied.insert( + QString::number(m_items[i].gridX) + "," + + QString::number(m_items[i].gridY)); } } @@ -191,8 +215,10 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString int newX = item->gridX + deltaX; int newY = item->gridY + deltaY; - bool outOfBounds = newX < 0 || newX > maxCol || newY < 0 || newY > maxRow; - bool collision = occupied.contains(QString::number(newX) + "," + QString::number(newY)); + bool outOfBounds = newX < 0 || newX > maxCol || newY < 0 || + newY > maxRow; + bool collision = occupied.contains( + QString::number(newX) + "," + QString::number(newY)); if (outOfBounds || collision) { bool found = false; @@ -208,15 +234,17 @@ void DesktopModel::massMove(const QVariantList& selectedPathsList, const QString } } } else { - occupied.insert(QString::number(newX) + "," + QString::number(newY)); + occupied.insert( + QString::number(newX) + "," + QString::number(newY)); } item->gridX = newX; item->gridY = newY; } - emit dataChanged(index(0, 0), index(m_items.size() - 1, 0), {GridXRole, GridYRole}); + emit dataChanged( + index(0, 0), index(static_cast(m_items.size()) - 1, 0), {GridXRole, GridYRole}); saveCurrentLayout(); } -}; +}; // namespace ZShell::services From b2cddea636d8f70b75aad74db8c21af01aa7c865 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 01:36:40 +0200 Subject: [PATCH 21/44] fix(gpu): use epsilon comparison instead of == for floating-point guards --- Plugins/ZShell/Services/gpu.cpp | 195 +++++++++++++++++++------------- 1 file changed, 118 insertions(+), 77 deletions(-) diff --git a/Plugins/ZShell/Services/gpu.cpp b/Plugins/ZShell/Services/gpu.cpp index 9a236cd..f1bcf94 100644 --- a/Plugins/ZShell/Services/gpu.cpp +++ b/Plugins/ZShell/Services/gpu.cpp @@ -17,26 +17,31 @@ namespace ZShell::services { namespace { constexpr const char* kTypeDetectScript = - "if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null 2>&1; then echo NVIDIA;" - " elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC;" + "if command -v nvidia-smi >/dev/null 2>&1 && nvidia-smi -L >/dev/null " + "2>&1; then echo NVIDIA;" + " elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep " + "-q .; then echo GENERIC;" " else echo NONE; fi"; -constexpr const char* kNameDetectScript = "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null" - " || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' -f1" - " || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1"; +constexpr const char* kNameDetectScript = + "nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null" + " || glxinfo -B 2>/dev/null | grep 'Device:' | cut -d':' -f2 | cut -d'(' " + "-f1" + " || lspci 2>/dev/null | grep -i 'vga\\|3d controller\\|display' | head -1"; } // namespace -Gpu::Gpu(QObject* parent) - : TickingService(parent) { - QString configPath = QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); +Gpu::Gpu(QObject* parent) : TickingService(parent) { + QString configPath = + QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); auto reloadConfig = [this, configPath]() { QFile file(configPath); if (file.open(QIODevice::ReadOnly)) { QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); if (!doc.isNull()) { - QJsonObject services = doc.object().value("services").toObject(); + QJsonObject services = + doc.object().value("services").toObject(); setUserType(parseType(services.value("gpuType").toString())); } } @@ -46,18 +51,25 @@ Gpu::Gpu(QObject* parent) static QFileSystemWatcher* watcher = new QFileSystemWatcher(); if (!watcher->files().contains(configPath)) { - QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, [this, configPath]() { - QTimer::singleShot(100, this, [this, configPath]() { - QFile file(configPath); - if (file.exists()) { - QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); - if (!doc.isNull()) { - QJsonObject services = doc.object().value("services").toObject(); - setUserType(parseType(services.value("gpuType").toString())); + QObject::connect( + watcher, + &QFileSystemWatcher::fileChanged, + this, + [this, configPath]() { + QTimer::singleShot(100, this, [this, configPath]() { + QFile file(configPath); + if (file.exists()) { + QJsonDocument doc = + QJsonDocument::fromJson(file.readAll()); + if (!doc.isNull()) { + QJsonObject services = + doc.object().value("services").toObject(); + setUserType(parseType( + services.value("gpuType").toString())); + } } - } + }); }); - }); watcher->addPath(configPath); } @@ -134,13 +146,13 @@ void Gpu::setName(QString value) { } void Gpu::setMemoryUsed(qreal value) { - if (m_memoryUsed == value) return; + if (std::abs(m_memoryUsed - value) < 0.001) return; m_memoryUsed = value; Q_EMIT memoryUsedChanged(); } void Gpu::setMemoryTotal(qreal value) { - if (m_memoryTotal == value) return; + if (std::abs(m_memoryTotal - value) < 0.001) return; m_memoryTotal = value; Q_EMIT memoryTotalChanged(); } @@ -169,15 +181,22 @@ void Gpu::detectTypeOnce() { return; } m_typeProc = new QProcess(this); - QObject::connect(m_typeProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { - const QByteArray out = m_typeProc->readAllStandardOutput().trimmed(); + QObject::connect( + m_typeProc, + &QProcess::finished, + this, + [this](int, QProcess::ExitStatus) { + const QByteArray out = + m_typeProc->readAllStandardOutput().trimmed(); if (!out.isEmpty()) { setAutoType(parseType(QString::fromLatin1(out))); } m_typeProc->deleteLater(); m_typeProc = nullptr; }); - m_typeProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript) }); + m_typeProc->start( + QStringLiteral("sh"), + {QStringLiteral("-c"), QString::fromLatin1(kTypeDetectScript)}); } void Gpu::detectNameOnce() { @@ -185,21 +204,30 @@ void Gpu::detectNameOnce() { return; } m_nameProc = new QProcess(this); - QObject::connect(m_nameProc, &QProcess::finished, this, [this](int, QProcess::ExitStatus) { - const QString output = QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed(); + QObject::connect( + m_nameProc, + &QProcess::finished, + this, + [this](int, QProcess::ExitStatus) { + const QString output = + QString::fromUtf8(m_nameProc->readAllStandardOutput()).trimmed(); if (!output.isEmpty()) { const QString lower = output.toLower(); - if (lower.contains(QStringLiteral("nvidia")) || lower.contains(QStringLiteral("geforce")) || - lower.contains(QStringLiteral("rtx")) || lower.contains(QStringLiteral("gtx")) || - lower.contains(QStringLiteral("rx"))) { + if (lower.contains(QStringLiteral("nvidia")) || + lower.contains(QStringLiteral("geforce")) || + lower.contains(QStringLiteral("rtx")) || + lower.contains(QStringLiteral("gtx")) || + lower.contains(QStringLiteral("rx"))) { setName(cleanName(output)); } else { - static const QRegularExpression bracketRe(QStringLiteral("\\[([^\\]]+)\\][^\\[]*$")); + static const QRegularExpression bracketRe( + QStringLiteral("\\[([^\\]]+)\\][^\\[]*$")); const auto bracket = bracketRe.match(output); if (bracket.hasMatch()) { setName(cleanName(bracket.captured(1))); } else { - static const QRegularExpression colonRe(QStringLiteral(":\\s*(.+)")); + static const QRegularExpression colonRe( + QStringLiteral(":\\s*(.+)")); const auto colon = colonRe.match(output); if (colon.hasMatch()) { setName(cleanName(colon.captured(1))); @@ -210,17 +238,21 @@ void Gpu::detectNameOnce() { m_nameProc->deleteLater(); m_nameProc = nullptr; }); - m_nameProc->start(QStringLiteral("sh"), { QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript) }); + m_nameProc->start( + QStringLiteral("sh"), + {QStringLiteral("-c"), QString::fromLatin1(kNameDetectScript)}); } void Gpu::readGenericUsage() { - const QStringList paths = - QDir(QStringLiteral("/sys/class/drm")) - .entryList(QStringList() << QStringLiteral("card*"), QDir::Dirs | QDir::NoDotAndDotDot); + const QStringList paths = QDir(QStringLiteral("/sys/class/drm")) + .entryList( + QStringList() << QStringLiteral("card*"), + QDir::Dirs | QDir::NoDotAndDotDot); qreal sum = 0.0; int count = 0; for (const QString& card : paths) { - QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent").arg(card)); + QFile f(QStringLiteral("/sys/class/drm/%1/device/gpu_busy_percent") + .arg(card)); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { continue; } @@ -244,57 +276,65 @@ void Gpu::startNvidiaUsage() { return; } m_nvidiaProc = new QProcess(this); - QObject::connect(m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() { - while (m_nvidiaProc->canReadLine()) { - const QByteArray out = m_nvidiaProc->readLine(); - const QString output = QString::fromUtf8(out).trimmed(); - if (output.isEmpty()) - continue; + QObject::connect( + m_nvidiaProc, &QProcess::readyReadStandardOutput, this, [this]() { + while (m_nvidiaProc->canReadLine()) { + const QByteArray out = m_nvidiaProc->readLine(); + const QString output = QString::fromUtf8(out).trimmed(); + if (output.isEmpty()) continue; - const QList parts = output.split(','); - if (parts.size() < 4) - return; + const QList parts = output.split(','); + if (parts.size() < 4) return; - bool ok1 = false; - bool ok2 = false; - bool ok3 = false; - bool ok4 = false; - const qreal usage = parts.at(0).trimmed().toDouble(&ok1) / 100.0; - const qreal temp = parts.at(1).trimmed().toDouble(&ok2); - const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3); - const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4); + bool ok1 = false; + bool ok2 = false; + bool ok3 = false; + bool ok4 = false; + const qreal usage = + parts.at(0).trimmed().toDouble(&ok1) / 100.0; + const qreal temp = parts.at(1).trimmed().toDouble(&ok2); + const qreal memUsed = parts.at(2).trimmed().toDouble(&ok3); + const qreal memTotal = parts.at(3).trimmed().toDouble(&ok4); - if (ok1 && std::abs(usage - m_percentage) > 0.0001) { - m_percentage = usage; - Q_EMIT percentageChanged(); + if (ok1 && std::abs(usage - m_percentage) > 0.0001) { + m_percentage = usage; + Q_EMIT percentageChanged(); + } + if (ok2 && std::abs(temp - m_temperature) > 0.05) { + m_temperature = temp; + Q_EMIT temperatureChanged(); + } + if (ok3) { + setMemoryUsed(memUsed); + } + if (ok4) { + setMemoryTotal(memTotal); + } } - if (ok2 && std::abs(temp - m_temperature) > 0.05) { - m_temperature = temp; - Q_EMIT temperatureChanged(); - } - if (ok3) { - setMemoryUsed(memUsed); - } - if (ok4) { - setMemoryTotal(memTotal); - } - } - }); + }); QObject::connect(m_nvidiaProc, &QProcess::finished, this, [this]() { m_nvidiaProc->deleteLater(); m_nvidiaProc = nullptr; }); - QObject::connect(m_nvidiaProc, &QProcess::errorOccurred, this, [this](QProcess::ProcessError) { - m_nvidiaProc->deleteLater(); - m_nvidiaProc = nullptr; - }); + QObject::connect( + m_nvidiaProc, + &QProcess::errorOccurred, + this, + [this](QProcess::ProcessError) { + m_nvidiaProc->deleteLater(); + m_nvidiaProc = nullptr; + }); - m_nvidiaProc->start(QStringLiteral("nvidia-smi"), { QStringLiteral("--query-gpu=utilization.gpu,temperature.gpu,memory.used,memory.total"), - QStringLiteral("--format=csv,noheader,nounits"), - QStringLiteral("-lms"), - QString::number(updateInterval()) }); + m_nvidiaProc->start( + QStringLiteral("nvidia-smi"), + {QStringLiteral( + "--query-gpu=utilization.gpu,temperature.gpu," + "memory.used,memory.total"), + QStringLiteral("--format=csv,noheader,nounits"), + QStringLiteral("-lms"), + QString::number(updateInterval())}); } void Gpu::readGpuTemperature() { @@ -322,7 +362,8 @@ Gpu::Type Gpu::parseType(const QString& s) { QString Gpu::cleanName(QString s) { static const QRegularExpression noise( - QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"), QRegularExpression::CaseInsensitiveOption); + QStringLiteral("\\(R\\)|\\(TM\\)|Graphics"), + QRegularExpression::CaseInsensitiveOption); static const QRegularExpression spaces(QStringLiteral("\\s+")); s.replace(noise, QString()); s.replace(spaces, QStringLiteral(" ")); From fed9372b35a1b5817b664d61b0d7dc5638d90249 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 01:54:22 +0200 Subject: [PATCH 22/44] refactor(tickingservice): flatten nested if blocks with early returns --- Plugins/ZShell/Services/tickingservice.cpp | 61 +++++++++++++--------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/Plugins/ZShell/Services/tickingservice.cpp b/Plugins/ZShell/Services/tickingservice.cpp index 4485d86..5c51cdc 100644 --- a/Plugins/ZShell/Services/tickingservice.cpp +++ b/Plugins/ZShell/Services/tickingservice.cpp @@ -10,43 +10,52 @@ namespace ZShell::services { TickingService::TickingService(QObject* parent) - : Service(parent) - , m_timer(new QTimer(this)) { + : Service(parent), m_timer(new QTimer(this)) { m_timer->setSingleShot(false); - QObject::connect(m_timer, &QTimer::timeout, this, [this] { - tick(); - }); + QObject::connect(m_timer, &QTimer::timeout, this, [this] { tick(); }); - QString configPath = QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); + QString configPath = + QDir::homePath() + QStringLiteral("/.config/zshell/config.json"); auto reloadConfig = [this, configPath]() { - QFile file(configPath); - if (file.open(QIODevice::ReadOnly)) { - QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); - if (!doc.isNull()) { - QJsonObject dashboard = doc.object().value("dashboard").toObject(); - if (dashboard.contains("resourceUpdateInterval")) { - applyInterval(dashboard.value("resourceUpdateInterval").toInt(1000)); - } - } - } - }; + QFile file(configPath); + if (!file.open(QIODevice::ReadOnly)) { + return; + } + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (doc.isNull()) { + return; + } + QJsonObject dashboard = doc.object().value("dashboard").toObject(); + if (dashboard.contains("resourceUpdateInterval")) { + applyInterval( + dashboard.value("resourceUpdateInterval").toInt(1000)); + } + }; reloadConfig(); static auto* watcher = new QFileSystemWatcher(); if (!watcher->files().contains(configPath)) { - QObject::connect(watcher, &QFileSystemWatcher::fileChanged, this, [this, configPath]() { + QObject::connect( + watcher, + &QFileSystemWatcher::fileChanged, + this, + [this, configPath]() { QTimer::singleShot(100, this, [this, configPath]() { QFile file(configPath); - if (file.exists()) { - QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); - if (!doc.isNull()) { - QJsonObject dashboard = doc.object().value("dashboard").toObject(); - if (dashboard.contains("resourceUpdateInterval")) { - applyInterval(dashboard.value("resourceUpdateInterval").toInt(1000)); - } - } + if (!file.exists()) { + return; + } + QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + if (doc.isNull()) { + return; + } + QJsonObject dashboard = + doc.object().value("dashboard").toObject(); + if (dashboard.contains("resourceUpdateInterval")) { + applyInterval(dashboard.value("resourceUpdateInterval") + .toInt(1000)); } }); }); From 467e44bb9fecb893220490df0e0e58b94ffe076d Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 02:04:24 +0200 Subject: [PATCH 23/44] tidy&format(all): all files formatted and relevant warnings resolved --- .clang-format | 6 +- Plugins/ZShell/Blobs/blobgroup.cpp | 40 +- Plugins/ZShell/Blobs/blobgroup.hpp | 70 +- Plugins/ZShell/Blobs/blobinvertedrect.cpp | 61 +- Plugins/ZShell/Blobs/blobinvertedrect.hpp | 82 ++- Plugins/ZShell/Blobs/blobmaterial.cpp | 35 +- Plugins/ZShell/Blobs/blobmaterial.hpp | 48 +- Plugins/ZShell/Blobs/blobrect.cpp | 128 ++-- Plugins/ZShell/Blobs/blobrect.hpp | 200 +++--- Plugins/ZShell/Blobs/blobshape.cpp | 206 ++++-- Plugins/ZShell/Blobs/blobshape.hpp | 121 ++-- Plugins/ZShell/Components/buttonrow.cpp | 62 +- Plugins/ZShell/Components/buttonrow.hpp | 39 +- Plugins/ZShell/Components/lazylistview.cpp | 454 +++++------- Plugins/ZShell/Components/lazylistview.hpp | 387 ++++++----- Plugins/ZShell/Components/wavyline.cpp | 8 +- Plugins/ZShell/Components/wavyline.hpp | 168 +++-- Plugins/ZShell/Internal/arcgauge.cpp | 24 +- Plugins/ZShell/Internal/arcgauge.hpp | 88 ++- .../ZShell/Internal/cachingimagemanager.cpp | 292 ++++---- .../ZShell/Internal/cachingimagemanager.hpp | 81 ++- Plugins/ZShell/Internal/circularbuffer.cpp | 25 +- Plugins/ZShell/Internal/circularbuffer.hpp | 51 +- .../Internal/circularindicatormanager.cpp | 57 +- .../Internal/circularindicatormanager.hpp | 102 +-- Plugins/ZShell/Internal/hyprdevices.cpp | 163 ++--- Plugins/ZShell/Internal/hyprdevices.hpp | 99 +-- Plugins/ZShell/Internal/hyprextras.cpp | 166 +++-- Plugins/ZShell/Internal/hyprextras.hpp | 71 +- Plugins/ZShell/Internal/lidwatcher.cpp | 58 +- Plugins/ZShell/Internal/lidwatcher.hpp | 26 +- .../Internal/linearindicatormanager.cpp | 53 +- .../Internal/linearindicatormanager.hpp | 100 +-- Plugins/ZShell/Internal/sparklineitem.cpp | 98 ++- Plugins/ZShell/Internal/sparklineitem.hpp | 139 ++-- Plugins/ZShell/Internal/visualizerbars.cpp | 35 +- Plugins/ZShell/Internal/visualizerbars.hpp | 96 +-- Plugins/ZShell/Internal/wallpaperimage.cpp | 75 +- Plugins/ZShell/Internal/wallpaperimage.hpp | 180 +++-- Plugins/ZShell/Models/filesystemmodel.cpp | 656 +++++++++--------- Plugins/ZShell/Models/filesystemmodel.hpp | 218 +++--- Plugins/ZShell/Services/audiocollector.cpp | 98 ++- Plugins/ZShell/Services/audiocollector.hpp | 70 +- Plugins/ZShell/Services/audioprovider.cpp | 30 +- Plugins/ZShell/Services/audioprovider.hpp | 44 +- Plugins/ZShell/Services/beattracker.cpp | 13 +- Plugins/ZShell/Services/beattracker.hpp | 48 +- Plugins/ZShell/Services/cavaprovider.cpp | 30 +- Plugins/ZShell/Services/cavaprovider.hpp | 68 +- Plugins/ZShell/Services/cpu.cpp | 19 +- Plugins/ZShell/Services/cpu.hpp | 56 +- Plugins/ZShell/Services/desktopmodel.hpp | 94 +-- .../ZShell/Services/desktopstatemanager.cpp | 10 +- .../ZShell/Services/desktopstatemanager.hpp | 18 +- Plugins/ZShell/Services/diskinfo.cpp | 17 +- Plugins/ZShell/Services/diskinfo.hpp | 63 +- Plugins/ZShell/Services/gpu.hpp | 128 ++-- Plugins/ZShell/Services/hyprsunsetmanager.cpp | 45 +- Plugins/ZShell/Services/hyprsunsetmanager.hpp | 97 +-- Plugins/ZShell/Services/memory.cpp | 10 +- Plugins/ZShell/Services/memory.hpp | 40 +- Plugins/ZShell/Services/sensorslib.cpp | 51 +- Plugins/ZShell/Services/service.cpp | 4 +- Plugins/ZShell/Services/service.hpp | 18 +- Plugins/ZShell/Services/serviceref.cpp | 3 +- Plugins/ZShell/Services/serviceref.hpp | 24 +- Plugins/ZShell/Services/storage.cpp | 80 ++- Plugins/ZShell/Services/storage.hpp | 67 +- Plugins/ZShell/Services/tickingservice.hpp | 33 +- Plugins/ZShell/Services/usagefmt.cpp | 10 +- Plugins/ZShell/Services/usagefmt.hpp | 13 +- Plugins/ZShell/appdb.cpp | 326 ++++----- Plugins/ZShell/appdb.hpp | 144 ++-- Plugins/ZShell/imageanalyser.cpp | 340 +++++---- Plugins/ZShell/imageanalyser.hpp | 74 +- Plugins/ZShell/qalculator.cpp | 9 +- Plugins/ZShell/qalculator.hpp | 13 +- Plugins/ZShell/requests.cpp | 14 +- Plugins/ZShell/requests.hpp | 19 +- Plugins/ZShell/toaster.cpp | 29 +- Plugins/ZShell/toaster.hpp | 109 +-- Plugins/ZShell/writefile.cpp | 262 ++++--- Plugins/ZShell/writefile.hpp | 42 +- Plugins/ZShell/zutils.cpp | 90 ++- Plugins/ZShell/zutils.hpp | 37 +- 85 files changed, 4139 insertions(+), 3738 deletions(-) diff --git a/.clang-format b/.clang-format index a2cde00..a931072 100644 --- a/.clang-format +++ b/.clang-format @@ -1,15 +1,15 @@ --- BasedOnStyle: LLVM AccessModifierOffset: 0 -AlignAfterOpenBracket: Align +AlignAfterOpenBracket: AlwaysBreak AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignOperands: Align AllowAllParametersOfDeclarationOnNextLine: true AllowShortFunctionsOnASingleLine: Inline -AllowShortIfStatementsOnASingleLine: Never +AllowShortIfStatementsOnASingleLine: WithoutElse AlwaysBreakBeforeMultilineStrings: false -AlwaysBreakTemplateDeclarations: Yes +AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: false BinPackParameters: false BreakBeforeBraces: Attach diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index e77cf05..86ef2ff 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -2,9 +2,7 @@ #include "blobinvertedrect.hpp" #include "blobshape.hpp" -BlobGroup::BlobGroup(QObject* parent) - : QObject(parent) { -} +BlobGroup::BlobGroup(QObject* parent) : QObject(parent) {} BlobGroup::~BlobGroup() { for (auto* shape : std::as_const(m_shapes)) @@ -14,24 +12,21 @@ BlobGroup::~BlobGroup() { } void BlobGroup::setSmoothing(qreal s) { - if (qFuzzyCompare(m_smoothing, s)) - return; + if (qFuzzyCompare(m_smoothing, s)) return; m_smoothing = s; emit smoothingChanged(); markDirty(); } void BlobGroup::setColor(const QColor& c) { - if (m_color == c) - return; + if (m_color == c) return; m_color = c; emit colorChanged(); markDirty(); } void BlobGroup::addShape(BlobShape* shape) { - if (!shape || m_shapes.contains(shape)) - return; + if (!shape || m_shapes.contains(shape)) return; m_shapes.append(shape); markDirty(); } @@ -42,15 +37,13 @@ void BlobGroup::removeShape(BlobShape* shape) { } void BlobGroup::setInvertedRect(BlobInvertedRect* rect) { - if (m_invertedRect == rect) - return; + if (m_invertedRect == rect) return; m_invertedRect = rect; markDirty(); } void BlobGroup::clearInvertedRect(BlobInvertedRect* rect) { - if (m_invertedRect != rect) - return; + if (m_invertedRect != rect) return; m_invertedRect = nullptr; markDirty(); } @@ -75,15 +68,19 @@ void BlobGroup::markShapeDirty(BlobShape* source) { // Use cached padded rects to find spatial neighbors const float pad = static_cast(m_smoothing) * 2.0f; - const QRectF srcRect(static_cast(source->m_cachedPaddedX - pad), - static_cast(source->m_cachedPaddedY - pad), static_cast(source->m_cachedPaddedW + pad * 2.0f), - static_cast(source->m_cachedPaddedH + pad * 2.0f)); + const QRectF srcRect( + static_cast(source->m_cachedPaddedX - pad), + static_cast(source->m_cachedPaddedY - pad), + static_cast(source->m_cachedPaddedW + pad * 2.0f), + static_cast(source->m_cachedPaddedH + pad * 2.0f)); for (auto* shape : std::as_const(m_shapes)) { - if (shape == source) - continue; - const QRectF otherRect(static_cast(shape->m_cachedPaddedX), static_cast(shape->m_cachedPaddedY), - static_cast(shape->m_cachedPaddedW), static_cast(shape->m_cachedPaddedH)); + if (shape == source) continue; + const QRectF otherRect( + static_cast(shape->m_cachedPaddedX), + static_cast(shape->m_cachedPaddedY), + static_cast(shape->m_cachedPaddedW), + static_cast(shape->m_cachedPaddedH)); if (srcRect.intersects(otherRect)) { shape->polish(); shape->update(); @@ -97,8 +94,7 @@ void BlobGroup::markShapeDirty(BlobShape* source) { } void BlobGroup::ensurePhysicsUpdated() { - if (m_physicsUpdated) - return; + if (m_physicsUpdated) return; m_physicsUpdated = true; for (auto* shape : std::as_const(m_shapes)) shape->updatePhysics(); diff --git a/Plugins/ZShell/Blobs/blobgroup.hpp b/Plugins/ZShell/Blobs/blobgroup.hpp index e917804..c90ed7e 100644 --- a/Plugins/ZShell/Blobs/blobgroup.hpp +++ b/Plugins/ZShell/Blobs/blobgroup.hpp @@ -9,53 +9,49 @@ class BlobShape; class BlobInvertedRect; class BlobGroup : public QObject { -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_OBJECT + QML_ELEMENT + Q_PROPERTY( + qreal smoothing READ smoothing WRITE setSmoothing NOTIFY + smoothingChanged) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) -public: -explicit BlobGroup(QObject* parent = nullptr); -~BlobGroup() override; + public: + explicit BlobGroup(QObject* parent = nullptr); + ~BlobGroup() override; -[[nodiscard]] qreal smoothing() const { - return m_smoothing; -} + [[nodiscard]] qreal smoothing() const { return m_smoothing; } -void setSmoothing(qreal s); + void setSmoothing(qreal s); -[[nodiscard]] QColor color() const { - return m_color; -} + [[nodiscard]] QColor color() const { return m_color; } -void setColor(const QColor& c); + void setColor(const QColor& c); -void addShape(BlobShape* shape); -void removeShape(BlobShape* shape); + void addShape(BlobShape* shape); + void removeShape(BlobShape* shape); -void setInvertedRect(BlobInvertedRect* rect); -void clearInvertedRect(BlobInvertedRect* rect); + void setInvertedRect(BlobInvertedRect* rect); + void clearInvertedRect(BlobInvertedRect* rect); -[[nodiscard]] const QList& shapes() const { - return m_shapes; -} + [[nodiscard]] const QList& shapes() const { return m_shapes; } -[[nodiscard]] BlobInvertedRect* invertedRect() const { - return m_invertedRect; -} + [[nodiscard]] BlobInvertedRect* invertedRect() const { + return m_invertedRect; + } -void markDirty(); -void markShapeDirty(BlobShape* source); -void ensurePhysicsUpdated(); + void markDirty(); + void markShapeDirty(BlobShape* source); + void ensurePhysicsUpdated(); -signals: -void smoothingChanged(); -void colorChanged(); + signals: + void smoothingChanged(); + void colorChanged(); -private: -qreal m_smoothing = 32.0; -QColor m_color{ 0x44, 0x88, 0xff }; -QList m_shapes; -BlobInvertedRect* m_invertedRect = nullptr; -bool m_physicsUpdated = false; + private: + qreal m_smoothing = 32.0; + QColor m_color{0x44, 0x88, 0xff}; + QList m_shapes; + BlobInvertedRect* m_invertedRect = nullptr; + bool m_physicsUpdated = false; }; diff --git a/Plugins/ZShell/Blobs/blobinvertedrect.cpp b/Plugins/ZShell/Blobs/blobinvertedrect.cpp index a8b85f4..328cbf3 100644 --- a/Plugins/ZShell/Blobs/blobinvertedrect.cpp +++ b/Plugins/ZShell/Blobs/blobinvertedrect.cpp @@ -5,12 +5,9 @@ #include #include -#include #include -BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) - : BlobShape(parent) { -} +BlobInvertedRect::BlobInvertedRect(QQuickItem* parent) : BlobShape(parent) {} static void setFrameIndices(quint16* idx) { // Top strip: 0-1-4, 1-5-4 @@ -43,7 +40,8 @@ static void setFrameIndices(quint16* idx) { idx[23] = 7; } -QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { +QSGNode* BlobInvertedRect::updatePaintNode( + QSGNode* oldNode, UpdatePaintNodeData*) { if (!m_group) { delete oldNode; return nullptr; @@ -71,8 +69,11 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData delete oldNode; node = new QSGGeometryNode; - auto* geometry = - new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 8, 24, QSGGeometry::UnsignedShortType); + auto* geometry = new QSGGeometry( + QSGGeometry::defaultAttributes_TexturedPoint2D(), + 8, + 24, + QSGGeometry::UnsignedShortType); geometry->setDrawingMode(QSGGeometry::DrawTriangles); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); @@ -120,10 +121,17 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData material->m_color = m_group->color(); material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_invertedRadius = m_cachedInvertedRadius; - memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); - memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); + memcpy( + material->m_invertedOuter, + m_cachedInvertedOuter, + sizeof(m_cachedInvertedOuter)); + memcpy( + material->m_invertedInner, + m_cachedInvertedInner, + sizeof(m_cachedInvertedInner)); - const int count = static_cast(qMin(m_cachedRects.size(), qsizetype(16))); + const int count = + static_cast(qMin(m_cachedRects.size(), qsizetype(16))); material->m_rectCount = count; for (int i = 0; i < count; ++i) material->m_rects[i] = m_cachedRects[i]; @@ -134,52 +142,41 @@ QSGNode* BlobInvertedRect::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData } BlobInvertedRect::~BlobInvertedRect() { - if (m_group) - m_group->clearInvertedRect(this); + if (m_group) m_group->clearInvertedRect(this); } void BlobInvertedRect::setBorderLeft(qreal v) { - if (qFuzzyCompare(m_borderLeft, v)) - return; + if (qFuzzyCompare(m_borderLeft, v)) return; m_borderLeft = v; emit borderLeftChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobInvertedRect::setBorderRight(qreal v) { - if (qFuzzyCompare(m_borderRight, v)) - return; + if (qFuzzyCompare(m_borderRight, v)) return; m_borderRight = v; emit borderRightChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobInvertedRect::setBorderTop(qreal v) { - if (qFuzzyCompare(m_borderTop, v)) - return; + if (qFuzzyCompare(m_borderTop, v)) return; m_borderTop = v; emit borderTopChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobInvertedRect::setBorderBottom(qreal v) { - if (qFuzzyCompare(m_borderBottom, v)) - return; + if (qFuzzyCompare(m_borderBottom, v)) return; m_borderBottom = v; emit borderBottomChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobInvertedRect::registerWithGroup() { - if (m_group) - m_group->setInvertedRect(this); + if (m_group) m_group->setInvertedRect(this); } void BlobInvertedRect::unregisterFromGroup() { - if (m_group) - m_group->clearInvertedRect(this); + if (m_group) m_group->clearInvertedRect(this); } diff --git a/Plugins/ZShell/Blobs/blobinvertedrect.hpp b/Plugins/ZShell/Blobs/blobinvertedrect.hpp index 0a36ef8..0fe0695 100644 --- a/Plugins/ZShell/Blobs/blobinvertedrect.hpp +++ b/Plugins/ZShell/Blobs/blobinvertedrect.hpp @@ -5,60 +5,58 @@ #include class BlobInvertedRect : public BlobShape { -Q_OBJECT -QML_ELEMENT -Q_PROPERTY(qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY borderLeftChanged) -Q_PROPERTY(qreal borderRight READ borderRight WRITE setBorderRight NOTIFY borderRightChanged) -Q_PROPERTY(qreal borderTop READ borderTop WRITE setBorderTop NOTIFY borderTopChanged) -Q_PROPERTY(qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY borderBottomChanged) + Q_OBJECT + QML_ELEMENT + Q_PROPERTY( + qreal borderLeft READ borderLeft WRITE setBorderLeft NOTIFY + borderLeftChanged) + Q_PROPERTY( + qreal borderRight READ borderRight WRITE setBorderRight NOTIFY + borderRightChanged) + Q_PROPERTY( + qreal borderTop READ borderTop WRITE setBorderTop NOTIFY + borderTopChanged) + Q_PROPERTY( + qreal borderBottom READ borderBottom WRITE setBorderBottom NOTIFY + borderBottomChanged) -public: -explicit BlobInvertedRect(QQuickItem* parent = nullptr); -~BlobInvertedRect() override; + public: + explicit BlobInvertedRect(QQuickItem* parent = nullptr); + ~BlobInvertedRect() override; -[[nodiscard]] qreal borderLeft() const { - return m_borderLeft; -} + [[nodiscard]] qreal borderLeft() const { return m_borderLeft; } -void setBorderLeft(qreal v); + void setBorderLeft(qreal v); -[[nodiscard]] qreal borderRight() const { - return m_borderRight; -} + [[nodiscard]] qreal borderRight() const { return m_borderRight; } -void setBorderRight(qreal v); + void setBorderRight(qreal v); -[[nodiscard]] qreal borderTop() const { - return m_borderTop; -} + [[nodiscard]] qreal borderTop() const { return m_borderTop; } -void setBorderTop(qreal v); + void setBorderTop(qreal v); -[[nodiscard]] qreal borderBottom() const { - return m_borderBottom; -} + [[nodiscard]] qreal borderBottom() const { return m_borderBottom; } -void setBorderBottom(qreal v); + void setBorderBottom(qreal v); -signals: -void borderLeftChanged(); -void borderRightChanged(); -void borderTopChanged(); -void borderBottomChanged(); + signals: + void borderLeftChanged(); + void borderRightChanged(); + void borderTopChanged(); + void borderBottomChanged(); -protected: -[[nodiscard]] bool isInvertedRect() const override { - return true; -} + protected: + [[nodiscard]] bool isInvertedRect() const override { return true; } -QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; + QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; -void registerWithGroup() override; -void unregisterFromGroup() override; + void registerWithGroup() override; + void unregisterFromGroup() override; -private: -qreal m_borderLeft = 0; -qreal m_borderRight = 0; -qreal m_borderTop = 0; -qreal m_borderBottom = 0; + private: + qreal m_borderLeft = 0; + qreal m_borderRight = 0; + qreal m_borderTop = 0; + qreal m_borderBottom = 0; }; diff --git a/Plugins/ZShell/Blobs/blobmaterial.cpp b/Plugins/ZShell/Blobs/blobmaterial.cpp index 8bb85b3..e32b25e 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.cpp +++ b/Plugins/ZShell/Blobs/blobmaterial.cpp @@ -1,24 +1,26 @@ #include "blobmaterial.hpp" +#include + #include -static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float), - "BlobMaterial packs excludeMask into a float slot via memcpy"); +static_assert( + sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float), + "BlobMaterial packs excludeMask into a float slot via memcpy"); QSGMaterialType* BlobMaterial::type() const { static QSGMaterialType s_type; return &s_type; } -QSGMaterialShader* BlobMaterial::createShader(QSGRendererInterface::RenderMode) const { +QSGMaterialShader* BlobMaterial::createShader( + QSGRendererInterface::RenderMode) const { return new BlobMaterialShader; } int BlobMaterial::compare(const QSGMaterial* other) const { - if (this < other) - return -1; - if (this > other) - return 1; + if (this < other) return -1; + if (this > other) return 1; return 0; } @@ -27,7 +29,8 @@ BlobMaterialShader::BlobMaterialShader() { setShaderFileName(FragmentStage, QStringLiteral(":/shaders/blob.frag.qsb")); } -bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) { +bool BlobMaterialShader::updateUniformData( + RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) { Q_UNUSED(oldMaterial); auto* mat = static_cast(newMaterial); QByteArray* buf = state.uniformData(); @@ -59,10 +62,10 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM // Color as vec4 (offset 96, 16 bytes) const float color[4] = { - static_cast(mat->m_color.redF()), - static_cast(mat->m_color.greenF()), - static_cast(mat->m_color.blueF()), - static_cast(mat->m_color.alphaF()), + mat->m_color.redF(), + mat->m_color.greenF(), + mat->m_color.blueF(), + mat->m_color.alphaF(), }; memcpy(buf->data() + 96, color, 16); @@ -86,11 +89,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM const auto& r = mat->m_rects[i]; const int base = 160 + i * 80; // Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt) - float maskAsFloat; + float maskAsFloat = NAN; memcpy(&maskAsFloat, &r.excludeMask, sizeof(float)); - const float d0[4] = { r.cx, r.cy, r.hw, r.hh }; - const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig }; - const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f }; + const float d0[4] = {r.cx, r.cy, r.hw, r.hh}; + const float d1[4] = {maskAsFloat, r.offsetX, r.offsetY, r.minEig}; + const float d3[4] = {r.screenHalfX, r.screenHalfY, 0.0f, 0.0f}; memcpy(buf->data() + base, d0, 16); memcpy(buf->data() + base + 16, d1, 16); memcpy(buf->data() + base + 32, r.invDeform, 16); diff --git a/Plugins/ZShell/Blobs/blobmaterial.hpp b/Plugins/ZShell/Blobs/blobmaterial.hpp index 0d49d9f..4a30448 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.hpp +++ b/Plugins/ZShell/Blobs/blobmaterial.hpp @@ -9,39 +9,43 @@ struct BlobRectData { float offsetX = 0, offsetY = 0; float minEig = 1.0f; // Inverse of 2x2 deformation matrix, column-major for GLSL - float invDeform[4] = { 1, 0, 0, 1 }; + float invDeform[4] = {1, 0, 0, 1}; // Screen-space AABB half-extents of the deformed rect float screenHalfX = 0, screenHalfY = 0; // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU - float radius[4] = { 0, 0, 0, 0 }; + float radius[4] = {0, 0, 0, 0}; // Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect. // Used by the shader to skip smin between excluded pairs. int excludeMask = 0; }; class BlobMaterial : public QSGMaterial { -public: -[[nodiscard]] QSGMaterialType* type() const override; -[[nodiscard]] QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; -int compare(const QSGMaterial* other) const override; + public: + [[nodiscard]] QSGMaterialType* type() const override; + [[nodiscard]] QSGMaterialShader* createShader( + QSGRendererInterface::RenderMode) const override; + int compare(const QSGMaterial* other) const override; -float m_paddedX = 0; -float m_paddedY = 0; -float m_paddedW = 0; -float m_paddedH = 0; -float m_smoothFactor = 32.0f; -int m_rectCount = 0; -int m_myIndex = -2; -QColor m_color{ 0x44, 0x88, 0xff }; -int m_hasInverted = 0; -float m_invertedRadius = 0; -float m_invertedOuter[4] = {}; -float m_invertedInner[4] = {}; -BlobRectData m_rects[16] = {}; + float m_paddedX = 0; + float m_paddedY = 0; + float m_paddedW = 0; + float m_paddedH = 0; + float m_smoothFactor = 32.0f; + int m_rectCount = 0; + int m_myIndex = -2; + QColor m_color{0x44, 0x88, 0xff}; + int m_hasInverted = 0; + float m_invertedRadius = 0; + float m_invertedOuter[4] = {}; + float m_invertedInner[4] = {}; + BlobRectData m_rects[16] = {}; }; class BlobMaterialShader : public QSGMaterialShader { -public: -BlobMaterialShader(); -bool updateUniformData(RenderState& state, QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override; + public: + BlobMaterialShader(); + bool updateUniformData( + RenderState& state, + QSGMaterial* newMaterial, + QSGMaterial* oldMaterial) override; }; diff --git a/Plugins/ZShell/Blobs/blobrect.cpp b/Plugins/ZShell/Blobs/blobrect.cpp index 5847fa4..c141afc 100644 --- a/Plugins/ZShell/Blobs/blobrect.cpp +++ b/Plugins/ZShell/Blobs/blobrect.cpp @@ -4,19 +4,18 @@ #include #include -BlobRect::BlobRect(QQuickItem* parent) - : BlobShape(parent) { -} +BlobRect::BlobRect(QQuickItem* parent) : BlobShape(parent) {} BlobRect::~BlobRect() { - if (m_group) - m_group->removeShape(this); + if (m_group) m_group->removeShape(this); } void BlobRect::updatePolish() { if (m_physicsActive) { - float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f); - float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); + float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + + std::abs(m_dm11 - 1.0f); + float totalVel = + std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); if (totalDelta < 0.004f && totalVel < 0.05f) { m_dm00 = 1.0f; @@ -32,18 +31,16 @@ void BlobRect::updatePolish() { QMetaObject::invokeMethod( this, [this]() { - if (m_group) - m_group->markDirty(); - }, + if (m_group) m_group->markDirty(); + }, Qt::QueuedConnection); } } else { QMetaObject::invokeMethod( this, [this]() { - if (m_physicsActive && m_group) - m_group->markDirty(); - }, + if (m_physicsActive && m_group) m_group->markDirty(); + }, Qt::QueuedConnection); } } @@ -64,20 +61,20 @@ void BlobRect::updatePhysics() { const float dt = static_cast(m_elapsed.restart()) / 1000.0f; if (dt > 0.1f || dt < 0.001f) { m_prevScenePos = scenePos; - if (m_physicsActive) - checkAtRest(0.0f); + if (m_physicsActive) checkAtRest(0.0f); return; } - const float velX = static_cast(scenePos.x() - m_prevScenePos.x()) / dt; - const float velY = static_cast(scenePos.y() - m_prevScenePos.y()) / dt; + const float velX = + static_cast(scenePos.x() - m_prevScenePos.x()) / dt; + const float velY = + static_cast(scenePos.y() - m_prevScenePos.y()) / dt; m_prevScenePos = scenePos; const float speed = std::sqrt(velX * velX + velY * velY); if (!m_physicsActive) { - if (speed < 5.0f) - return; + if (speed < 5.0f) return; m_physicsActive = true; } @@ -89,7 +86,8 @@ void BlobRect::updatePhysics() { float target11 = 1.0f; if (speed > 5.0f) { - const float targetStretch = 1.0f + std::min(speed * kStretchFactor, kMaxStretch); + const float targetStretch = + 1.0f + std::min(speed * kStretchFactor, kMaxStretch); const float targetCompress = 1.0f / targetStretch; const float cosA = velX / speed; @@ -106,19 +104,23 @@ void BlobRect::updatePhysics() { const float kStiffness = static_cast(m_stiffness); const float kDamping = static_cast(m_damping); - const float accel00 = -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; + const float accel00 = + -kStiffness * (m_dm00 - target00) - kDamping * m_dmVel00; m_dmVel00 += accel00 * dt; m_dm00 += m_dmVel00 * dt; - const float accel01 = -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; + const float accel01 = + -kStiffness * (m_dm01 - target01) - kDamping * m_dmVel01; m_dmVel01 += accel01 * dt; m_dm01 += m_dmVel01 * dt; - const float accel11 = -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; + const float accel11 = + -kStiffness * (m_dm11 - target11) - kDamping * m_dmVel11; m_dmVel11 += accel11 * dt; 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); + m_deformMatrix = QMatrix4x4( + m_dm00, m_dm01, 0, 0, m_dm01, m_dm11, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); emit rawDeformMatrixChanged(); updateCenteredDeformMatrix(); @@ -129,8 +131,7 @@ void BlobRect::setTopLeftRadius(qreal r) { if (!qFuzzyCompare(m_topLeftRadius, r)) { m_topLeftRadius = r; emit topLeftRadiusChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } } @@ -138,8 +139,7 @@ void BlobRect::setTopRightRadius(qreal r) { if (!qFuzzyCompare(m_topRightRadius, r)) { m_topRightRadius = r; emit topRightRadiusChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } } @@ -147,8 +147,7 @@ void BlobRect::setBottomLeftRadius(qreal r) { if (!qFuzzyCompare(m_bottomLeftRadius, r)) { m_bottomLeftRadius = r; emit bottomLeftRadiusChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } } @@ -156,38 +155,51 @@ void BlobRect::setBottomRightRadius(qreal r) { if (!qFuzzyCompare(m_bottomRightRadius, r)) { m_bottomRightRadius = r; emit bottomRightRadiusChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } } void BlobRect::cornerRadii(float out[4]) const { const auto maxR = static_cast(std::min(width(), height())) * 0.5f; const auto base = std::min(static_cast(m_radius), maxR); - out[0] = std::min(m_topRightRadius >= 0 ? static_cast(m_topRightRadius) : base, maxR); - out[1] = std::min(m_bottomRightRadius >= 0 ? static_cast(m_bottomRightRadius) : base, maxR); - out[2] = std::min(m_bottomLeftRadius >= 0 ? static_cast(m_bottomLeftRadius) : base, maxR); - out[3] = std::min(m_topLeftRadius >= 0 ? static_cast(m_topLeftRadius) : base, maxR); + out[0] = std::min( + m_topRightRadius >= 0 ? static_cast(m_topRightRadius) : base, + maxR); + out[1] = std::min( + m_bottomRightRadius >= 0 ? static_cast(m_bottomRightRadius) + : base, + maxR); + out[2] = std::min( + m_bottomLeftRadius >= 0 ? static_cast(m_bottomLeftRadius) : base, + maxR); + out[3] = std::min( + m_topLeftRadius >= 0 ? static_cast(m_topLeftRadius) : base, + maxR); } bool BlobRect::isExcluded(const BlobShape* other) const { for (const auto& ptr : m_exclude) { - if (ptr == other) - return true; + if (ptr == other) return true; } return false; } QQmlListProperty BlobRect::exclude() { return QQmlListProperty( - this, nullptr, &excludeAppend, &excludeCount, &excludeAt, &excludeClear, &excludeReplace, &excludeRemoveLast); + this, + nullptr, + &excludeAppend, + &excludeCount, + &excludeAt, + &excludeClear, + &excludeReplace, + &excludeRemoveLast); } void BlobRect::excludeAppend(QQmlListProperty* prop, BlobRect* rect) { auto* self = static_cast(prop->object); self->m_exclude.append(rect); - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeChanged(); } @@ -196,44 +208,43 @@ qsizetype BlobRect::excludeCount(QQmlListProperty* prop) { return self->m_exclude.size(); } -BlobRect* BlobRect::excludeAt(QQmlListProperty* prop, qsizetype index) { +BlobRect* BlobRect::excludeAt( + QQmlListProperty* prop, qsizetype index) { auto* self = static_cast(prop->object); return self->m_exclude.at(index); } void BlobRect::excludeClear(QQmlListProperty* prop) { auto* self = static_cast(prop->object); - if (self->m_exclude.isEmpty()) - return; + if (self->m_exclude.isEmpty()) return; self->m_exclude.clear(); - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeChanged(); } -void BlobRect::excludeReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect) { +void BlobRect::excludeReplace( + QQmlListProperty* prop, qsizetype index, BlobRect* rect) { auto* self = static_cast(prop->object); self->m_exclude[index] = rect; - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeChanged(); } void BlobRect::excludeRemoveLast(QQmlListProperty* prop) { auto* self = static_cast(prop->object); - if (self->m_exclude.isEmpty()) - return; + if (self->m_exclude.isEmpty()) return; self->m_exclude.removeLast(); - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeChanged(); } 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 && - std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon && - std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && speed < 5.0f; + const bool atRest = + std::abs(m_dm00 - 1.0f) < kEpsilon && std::abs(m_dm01) < kEpsilon && + std::abs(m_dm11 - 1.0f) < kEpsilon && std::abs(m_dmVel00) < kEpsilon && + std::abs(m_dmVel01) < kEpsilon && std::abs(m_dmVel11) < kEpsilon && + speed < 5.0f; if (atRest) { m_dm00 = 1.0f; @@ -251,9 +262,8 @@ void BlobRect::checkAtRest(float speed) { QMetaObject::invokeMethod( this, [this]() { - if (m_group) - m_group->markDirty(); - }, + if (m_group) m_group->markDirty(); + }, Qt::QueuedConnection); } } diff --git a/Plugins/ZShell/Blobs/blobrect.hpp b/Plugins/ZShell/Blobs/blobrect.hpp index 11d9910..a8d9d40 100644 --- a/Plugins/ZShell/Blobs/blobrect.hpp +++ b/Plugins/ZShell/Blobs/blobrect.hpp @@ -8,133 +8,135 @@ #include class BlobRect : public BlobShape { -Q_OBJECT -QML_ELEMENT -Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) -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(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) -Q_PROPERTY( - qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) + Q_OBJECT + QML_ELEMENT + Q_PROPERTY( + qreal stiffness READ stiffness WRITE setStiffness NOTIFY + stiffnessChanged) + 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( + 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) + Q_PROPERTY( + qreal bottomRightRadius READ bottomRightRadius WRITE + setBottomRightRadius NOTIFY bottomRightRadiusChanged) -public: -explicit BlobRect(QQuickItem* parent = nullptr); -~BlobRect() override; + public: + explicit BlobRect(QQuickItem* parent = nullptr); + ~BlobRect() override; -[[nodiscard]] qreal stiffness() const { - return m_stiffness; -} + [[nodiscard]] qreal stiffness() const { return m_stiffness; } -void setStiffness(qreal s) { - if (!qFuzzyCompare(m_stiffness, s)) { - m_stiffness = s; - emit stiffnessChanged(); + void setStiffness(qreal s) { + if (!qFuzzyCompare(m_stiffness, s)) { + m_stiffness = s; + emit stiffnessChanged(); + } } -} -[[nodiscard]] qreal damping() const { - return m_damping; -} + [[nodiscard]] qreal damping() const { return m_damping; } -void setDamping(qreal d) { - if (!qFuzzyCompare(m_damping, d)) { - m_damping = d; - emit dampingChanged(); + void setDamping(qreal d) { + if (!qFuzzyCompare(m_damping, d)) { + m_damping = d; + emit dampingChanged(); + } } -} -[[nodiscard]] qreal deformScale() const { - return m_deformScale; -} + [[nodiscard]] qreal deformScale() const { return m_deformScale; } -void setDeformScale(qreal s) { - if (!qFuzzyCompare(m_deformScale, s)) { - m_deformScale = s; - emit deformScaleChanged(); + void setDeformScale(qreal s) { + if (!qFuzzyCompare(m_deformScale, s)) { + m_deformScale = s; + emit deformScaleChanged(); + } } -} -QQmlListProperty exclude(); + QQmlListProperty exclude(); -bool isExcluded(const BlobShape* other) const override; -void cornerRadii(float out[4]) const override; + bool isExcluded(const BlobShape* other) const override; + void cornerRadii(float out[4]) const override; -[[nodiscard]] qreal topLeftRadius() const { - return m_topLeftRadius; -} + [[nodiscard]] qreal topLeftRadius() const { return m_topLeftRadius; } -void setTopLeftRadius(qreal r); + void setTopLeftRadius(qreal r); -[[nodiscard]] qreal topRightRadius() const { - return m_topRightRadius; -} + [[nodiscard]] qreal topRightRadius() const { return m_topRightRadius; } -void setTopRightRadius(qreal r); + void setTopRightRadius(qreal r); -[[nodiscard]] qreal bottomLeftRadius() const { - return m_bottomLeftRadius; -} + [[nodiscard]] qreal bottomLeftRadius() const { return m_bottomLeftRadius; } -void setBottomLeftRadius(qreal r); + void setBottomLeftRadius(qreal r); -[[nodiscard]] qreal bottomRightRadius() const { - return m_bottomRightRadius; -} + [[nodiscard]] qreal bottomRightRadius() const { + return m_bottomRightRadius; + } -void setBottomRightRadius(qreal r); + void setBottomRightRadius(qreal r); -signals: -void stiffnessChanged(); -void dampingChanged(); -void deformScaleChanged(); -void excludeChanged(); -void topLeftRadiusChanged(); -void topRightRadiusChanged(); -void bottomLeftRadiusChanged(); -void bottomRightRadiusChanged(); + signals: + void stiffnessChanged(); + void dampingChanged(); + void deformScaleChanged(); + void excludeChanged(); + void topLeftRadiusChanged(); + void topRightRadiusChanged(); + void bottomLeftRadiusChanged(); + void bottomRightRadiusChanged(); -protected: -void updatePolish() override; -void updatePhysics() override; + protected: + void updatePolish() override; + void updatePhysics() override; -private: -void checkAtRest(float speed); + private: + void checkAtRest(float speed); -// Physics state -QPointF m_prevScenePos; -QElapsedTimer m_elapsed; -bool m_physicsActive = false; -bool m_hasPrevPos = false; + // Physics state + QPointF m_prevScenePos; + QElapsedTimer m_elapsed; + bool m_physicsActive = false; + bool m_hasPrevPos = false; -// Symmetric 2x2 deformation matrix components (3 independent: m00, m01, -// m11) Rest state is identity: m00=1, m01=0, m11=1 -float m_dm00 = 1.0f; -float m_dm01 = 0.0f; -float m_dm11 = 1.0f; + // Symmetric 2x2 deformation matrix components (3 independent: m00, m01, + // m11) Rest state is identity: m00=1, m01=0, m11=1 + float m_dm00 = 1.0f; + float m_dm01 = 0.0f; + float m_dm11 = 1.0f; -// Spring velocities for each component -float m_dmVel00 = 0.0f; -float m_dmVel01 = 0.0f; -float m_dmVel11 = 0.0f; + // Spring velocities for each component + float m_dmVel00 = 0.0f; + float m_dmVel01 = 0.0f; + float m_dmVel11 = 0.0f; -qreal m_stiffness = 200.0; -qreal m_damping = 16.0; -qreal m_deformScale = 0.0005; + qreal m_stiffness = 200.0; + qreal m_damping = 16.0; + qreal m_deformScale = 0.0005; -qreal m_topLeftRadius = -1; -qreal m_topRightRadius = -1; -qreal m_bottomLeftRadius = -1; -qreal m_bottomRightRadius = -1; + qreal m_topLeftRadius = -1; + qreal m_topRightRadius = -1; + qreal m_bottomLeftRadius = -1; + qreal m_bottomRightRadius = -1; -QList > m_exclude; + QList> m_exclude; -static void excludeAppend(QQmlListProperty* prop, BlobRect* rect); -static qsizetype excludeCount(QQmlListProperty* prop); -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 excludeAppend(QQmlListProperty* prop, BlobRect* rect); + static qsizetype excludeCount(QQmlListProperty* prop); + 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); }; diff --git a/Plugins/ZShell/Blobs/blobshape.cpp b/Plugins/ZShell/Blobs/blobshape.cpp index 68ff191..bd69822 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -18,7 +18,8 @@ static float deformPadding(const QMatrix4x4& dm, float hw, float hh) { return std::max(extraX, extraY); } -static float cpuSdBox(float px, float py, float cx, float cy, float hw, float hh) { +static float cpuSdBox( + float px, float py, float cx, float cy, float hw, float hh) { const float dx = std::abs(px - cx) - hw; const float dy = std::abs(py - cy) - hh; const float mdx = std::max(dx, 0.0f); @@ -31,54 +32,53 @@ static float cpuSmoothstep(float edge0, float edge1, float x) { return t * t * (3.0f - 2.0f * t); } -BlobShape::BlobShape(QQuickItem* parent) - : QQuickItem(parent) { +BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents); } void BlobShape::setGroup(BlobGroup* g) { - if (m_group == g) - return; - if (m_group && isComponentComplete()) - unregisterFromGroup(); + if (m_group == g) return; + if (m_group && isComponentComplete()) unregisterFromGroup(); m_group = g; - if (m_group && isComponentComplete()) - registerWithGroup(); + if (m_group && isComponentComplete()) registerWithGroup(); emit groupChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobShape::setRadius(qreal r) { - if (qFuzzyCompare(m_radius, r)) - return; + if (qFuzzyCompare(m_radius, r)) return; m_radius = r; emit radiusChanged(); - if (m_group) - m_group->markDirty(); + if (m_group) m_group->markDirty(); } void BlobShape::componentComplete() { QQuickItem::componentComplete(); - if (m_group) - registerWithGroup(); + if (m_group) registerWithGroup(); } -void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) { +void BlobShape::geometryChange( + const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); updateCenteredDeformMatrix(); 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()); + 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 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) { + 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_pendingDw = 0; @@ -111,18 +111,15 @@ void BlobShape::cornerRadii(float out[4]) const { } void BlobShape::registerWithGroup() { - if (m_group) - m_group->addShape(this); + if (m_group) m_group->addShape(this); } void BlobShape::unregisterFromGroup() { - if (m_group) - m_group->removeShape(this); + if (m_group) m_group->removeShape(this); } void BlobShape::updatePolish() { - if (!m_group) - return; + if (!m_group) return; m_group->ensurePhysicsUpdated(); @@ -144,27 +141,30 @@ void BlobShape::updatePolish() { m_cachedPaddedY = static_cast(scenePos.y()) - totalPad; m_cachedPaddedW = static_cast(width()) + 2.0f * totalPad; m_cachedPaddedH = static_cast(height()) + 2.0f * totalPad; - m_localPaddedRect = QRectF(static_cast(-totalPad), static_cast(-totalPad), - width() + 2.0 * static_cast(totalPad), height() + 2.0 * static_cast(totalPad)); + m_localPaddedRect = QRectF( + static_cast(-totalPad), + static_cast(-totalPad), + width() + 2.0 * static_cast(totalPad), + height() + 2.0 * static_cast(totalPad)); } m_cachedRects.clear(); m_cachedMyIndex = -2; - const QRectF myPadded(static_cast(m_cachedPaddedX), static_cast(m_cachedPaddedY), - static_cast(m_cachedPaddedW), static_cast(m_cachedPaddedH)); + const QRectF myPadded( + static_cast(m_cachedPaddedX), + static_cast(m_cachedPaddedY), + static_cast(m_cachedPaddedW), + static_cast(m_cachedPaddedH)); QVector rectShapes; rectShapes.reserve(m_group->shapes().size()); for (BlobShape* other : m_group->shapes()) { - if (other->isInvertedRect()) - continue; + if (other->isInvertedRect()) continue; - if (other->width() <= 0 || other->height() <= 0) - continue; + if (other->width() <= 0 || other->height() <= 0) continue; - if (isExcluded(other)) - continue; + if (isExcluded(other)) continue; const QPointF otherScene = other->mapToScene(QPointF(0, 0)); @@ -174,10 +174,13 @@ void BlobShape::updatePolish() { } else { const float otherHW = static_cast(other->width()) * 0.5f; const float otherHH = static_cast(other->height()) * 0.5f; - const float otherPad = pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); - const QRectF otherPadded(otherScene.x() - static_cast(otherPad), - otherScene.y() - static_cast(otherPad), other->width() + 2.0 * static_cast(otherPad), - other->height() + 2.0 * static_cast(otherPad)); + const float otherPad = + pad + deformPadding(other->m_deformMatrix, otherHW, otherHH); + const QRectF otherPadded( + otherScene.x() - static_cast(otherPad), + otherScene.y() - static_cast(otherPad), + other->width() + 2.0 * static_cast(otherPad), + other->height() + 2.0 * static_cast(otherPad)); include = myPadded.intersects(otherPadded); } @@ -217,19 +220,16 @@ void BlobShape::updatePolish() { } } - if (isInvertedRect()) - m_cachedMyIndex = -1; + if (isInvertedRect()) m_cachedMyIndex = -1; const auto cachedCount = m_cachedRects.size(); for (qsizetype i = 0; i < cachedCount; ++i) { int mask = 0; BlobShape* si = rectShapes[i]; for (qsizetype j = 0; j < cachedCount; ++j) { - if (j == i) - continue; + if (j == i) continue; BlobShape* sj = rectShapes[j]; - if (si->isExcluded(sj) || sj->isExcluded(si)) - mask |= (1 << j); + if (si->isExcluded(sj) || sj->isExcluded(si)) mask |= (1 << j); } m_cachedRects[i].excludeMask = mask; } @@ -242,15 +242,25 @@ void BlobShape::updatePolish() { auto* inv = m_group->invertedRect(); if (inv) { const QPointF invScene = inv->mapToScene(QPointF(0, 0)); - const float outerCX = static_cast(invScene.x() + inv->width() / 2.0); - const float outerCY = static_cast(invScene.y() + inv->height() / 2.0); + const float outerCX = + static_cast(invScene.x() + inv->width() / 2.0); + const float outerCY = + static_cast(invScene.y() + inv->height() / 2.0); const float outerHW = static_cast(inv->width() / 2.0); const float outerHH = static_cast(inv->height() / 2.0); - const float innerCX = outerCX + static_cast((inv->borderLeft() - inv->borderRight()) / 2.0); - const float innerCY = outerCY + static_cast((inv->borderTop() - inv->borderBottom()) / 2.0); - const float innerHW = outerHW - static_cast((inv->borderLeft() + inv->borderRight()) / 2.0); - const float innerHH = outerHH - static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); + const float innerCX = + outerCX + + static_cast((inv->borderLeft() - inv->borderRight()) / 2.0); + const float innerCY = + outerCY + + static_cast((inv->borderTop() - inv->borderBottom()) / 2.0); + const float innerHW = + outerHW - + static_cast((inv->borderLeft() + inv->borderRight()) / 2.0); + const float innerHH = + outerHH - + static_cast((inv->borderTop() + inv->borderBottom()) / 2.0); bool nearBorder = isInvertedRect(); if (!nearBorder) { @@ -259,8 +269,10 @@ void BlobShape::updatePolish() { const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f; const float myHW = m_cachedPaddedW * 0.5f; const float myHH = m_cachedPaddedH * 0.5f; - nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) || - (myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin); + nearBorder = (myCX - myHW < innerCX - innerHW + margin) || + (myCX + myHW > innerCX + innerHW - margin) || + (myCY - myHH < innerCY - innerHH + margin) || + (myCY + myHH > innerCY + innerHH - margin); } if (nearBorder) { @@ -293,15 +305,33 @@ void BlobShape::updatePolish() { const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh; for (qsizetype j = 0; j < rectCount; ++j) { - if (j == i) - continue; - if (riExcludeMask & (1 << j)) - continue; + if (j == i) continue; + if (riExcludeMask & (1 << j)) 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))); + 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))); } if (m_cachedHasInverted) { @@ -309,10 +339,30 @@ void BlobShape::updatePolish() { const float icy = m_cachedInvertedInner[1]; const float ihw = m_cachedInvertedInner[2]; const float ihh = m_cachedInvertedInner[3]; - fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh))); - fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh))); - fBl = std::min(fBl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh))); - fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); + fTr = std::min( + fTr, + cpuSmoothstep( + 0.0f, + smoothFactor, + -cpuSdBox(cTrX, cTrY, icx, icy, ihw, ihh))); + fBr = std::min( + fBr, + cpuSmoothstep( + 0.0f, + smoothFactor, + -cpuSdBox(cBrX, cBrY, icx, icy, ihw, ihh))); + fBl = std::min( + fBl, + cpuSmoothstep( + 0.0f, + smoothFactor, + -cpuSdBox(cBlX, cBlY, icx, icy, ihw, ihh))); + fTl = std::min( + fTl, + cpuSmoothstep( + 0.0f, + smoothFactor, + -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); } ri.radius[0] = std::max(ri.radius[0] * fTr, minR); @@ -332,7 +382,8 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { if (!node) { node = new QSGGeometryNode; - auto* geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); + auto* geometry = + new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4); geometry->setDrawingMode(QSGGeometry::DrawTriangleStrip); node->setGeometry(geometry); node->setFlag(QSGNode::OwnsGeometry); @@ -368,10 +419,17 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) { material->m_color = m_group->color(); material->m_hasInverted = m_cachedHasInverted ? 1 : 0; material->m_invertedRadius = m_cachedInvertedRadius; - memcpy(material->m_invertedOuter, m_cachedInvertedOuter, sizeof(m_cachedInvertedOuter)); - memcpy(material->m_invertedInner, m_cachedInvertedInner, sizeof(m_cachedInvertedInner)); + memcpy( + material->m_invertedOuter, + m_cachedInvertedOuter, + sizeof(m_cachedInvertedOuter)); + memcpy( + material->m_invertedInner, + m_cachedInvertedInner, + sizeof(m_cachedInvertedInner)); - const int count = static_cast(qMin(m_cachedRects.size(), qsizetype(16))); + const int count = + static_cast(qMin(m_cachedRects.size(), qsizetype(16))); material->m_rectCount = count; for (int i = 0; i < count; ++i) material->m_rects[i] = m_cachedRects[i]; diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index 12c8747..55fb592 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -9,85 +9,78 @@ class BlobGroup; class BlobShape : public QQuickItem { -Q_OBJECT -Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged) -Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) -Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) -Q_PROPERTY(QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY rawDeformMatrixChanged) + Q_OBJECT + Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged) + Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) + Q_PROPERTY( + QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) + Q_PROPERTY( + QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY + rawDeformMatrixChanged) -friend class BlobGroup; + friend class BlobGroup; -public: -explicit BlobShape(QQuickItem* parent = nullptr); -~BlobShape() override = default; + public: + explicit BlobShape(QQuickItem* parent = nullptr); + ~BlobShape() override = default; -[[nodiscard]] BlobGroup* group() const { - return m_group; -} + [[nodiscard]] BlobGroup* group() const { return m_group; } -void setGroup(BlobGroup* g); + void setGroup(BlobGroup* g); -[[nodiscard]] qreal radius() const { - return m_radius; -} + [[nodiscard]] qreal radius() const { return m_radius; } -void setRadius(qreal r); + void setRadius(qreal r); -[[nodiscard]] QMatrix4x4 deformMatrix() const { - return m_centeredDeformMatrix; -} + [[nodiscard]] QMatrix4x4 deformMatrix() const { + return m_centeredDeformMatrix; + } -[[nodiscard]] QMatrix4x4 rawDeformMatrix() const { - return m_deformMatrix; -} + [[nodiscard]] QMatrix4x4 rawDeformMatrix() const { return m_deformMatrix; } -signals: -void groupChanged(); -void radiusChanged(); -void deformMatrixChanged(); -void rawDeformMatrixChanged(); + signals: + void groupChanged(); + void radiusChanged(); + void deformMatrixChanged(); + void rawDeformMatrixChanged(); -protected: -void componentComplete() override; -void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; -void updatePolish() override; -QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; + protected: + void componentComplete() override; + void geometryChange( + const QRectF& newGeometry, const QRectF& oldGeometry) override; + void updatePolish() override; + QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; -[[nodiscard]] virtual bool isInvertedRect() const { - return false; -} + [[nodiscard]] virtual bool isInvertedRect() const { return false; } -virtual bool isExcluded(const BlobShape* /*other*/) const { - return false; -} + virtual bool isExcluded(const BlobShape*) const { return false; } -virtual void cornerRadii(float out[4]) const; + virtual void cornerRadii(float out[4]) const; -virtual void updatePhysics() { -} + virtual void updatePhysics() {} -virtual void registerWithGroup(); -virtual void unregisterFromGroup(); -void updateCenteredDeformMatrix(); + virtual void registerWithGroup(); + virtual void unregisterFromGroup(); + void updateCenteredDeformMatrix(); -BlobGroup* m_group = nullptr; -qreal m_radius = 0; -QMatrix4x4 m_deformMatrix; -QMatrix4x4 m_centeredDeformMatrix; + BlobGroup* m_group = nullptr; + qreal m_radius = 0; + QMatrix4x4 m_deformMatrix; + QMatrix4x4 m_centeredDeformMatrix; -float m_cachedPaddedX = 0; -float m_cachedPaddedY = 0; -float m_cachedPaddedW = 0; -float m_cachedPaddedH = 0; -QRectF m_localPaddedRect; -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] = {}; -float m_cachedInvertedInner[4] = {}; + float m_cachedPaddedX = 0; + float m_cachedPaddedY = 0; + float m_cachedPaddedW = 0; + float m_cachedPaddedH = 0; + QRectF m_localPaddedRect; + 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] = {}; + float m_cachedInvertedInner[4] = {}; }; diff --git a/Plugins/ZShell/Components/buttonrow.cpp b/Plugins/ZShell/Components/buttonrow.cpp index a56ad33..7da35c9 100644 --- a/Plugins/ZShell/Components/buttonrow.cpp +++ b/Plugins/ZShell/Components/buttonrow.cpp @@ -3,11 +3,10 @@ namespace ZShell::components { ButtonRow::ButtonRow(QQuickItem* parent) - : QQuickItem(parent) - , m_dirty(false) - , m_spacing(0.0) { + : QQuickItem(parent), m_dirty(false), m_spacing(0.0) { setFlag(QQuickItem::ItemHasContents, true); - QObject::connect(this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate); + QObject::connect( + this, &ButtonRow::widthChanged, this, &ButtonRow::invalidate); } qreal ButtonRow::spacing() const { @@ -15,8 +14,7 @@ qreal ButtonRow::spacing() const { } void ButtonRow::setSpacing(qreal spacing) { - if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) - return; + if (qFuzzyCompare(m_spacing + 1.0, spacing + 1.0)) return; m_spacing = spacing; emit spacingChanged(); @@ -24,18 +22,33 @@ void ButtonRow::setSpacing(qreal spacing) { invalidate(); } -void ButtonRow::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) { +void ButtonRow::itemChange( + QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) { if (change == QQuickItem::ItemChildAddedChange) { auto* const child = data.item; - QObject::connect(child, &QQuickItem::implicitWidthChanged, this, &ButtonRow::invalidate); - QObject::connect(child, &QQuickItem::implicitHeightChanged, this, &ButtonRow::invalidate); - QObject::connect(child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate); + QObject::connect( + child, + &QQuickItem::implicitWidthChanged, + this, + &ButtonRow::invalidate); + QObject::connect( + child, + &QQuickItem::implicitHeightChanged, + this, + &ButtonRow::invalidate); + QObject::connect( + child, &QQuickItem::visibleChanged, this, &ButtonRow::invalidate); const auto* childMeta = child->metaObject(); - const auto morphSignalIdx = childMeta->indexOfSignal("shapeMorphExpansionChanged()"); + const auto morphSignalIdx = + childMeta->indexOfSignal("shapeMorphExpansionChanged()"); if (morphSignalIdx != -1) - QObject::connect(child, childMeta->method(morphSignalIdx), this, - metaObject()->method(metaObject()->indexOfSlot("invalidate()"))); + QObject::connect( + child, + childMeta->method(morphSignalIdx), + this, + metaObject()->method( + metaObject()->indexOfSlot("invalidate()"))); invalidate(); } else if (change == QQuickItem::ItemChildRemovedChange) { @@ -77,8 +90,7 @@ void ButtonRow::relayout() { maxHeight = qMax(maxHeight, child->implicitHeight()); const auto prop = child->property("fillWidth"); - if (!prop.isValid()) - continue; + if (!prop.isValid()) continue; if (prop.toBool()) { fillWidthCount++; @@ -88,15 +100,17 @@ void ButtonRow::relayout() { } } - if (fillWidthCount == 0) - fillWidthCount = 1; // Avoid divide by 0 + if (fillWidthCount == 0) fillWidthCount = 1; // Avoid divide by 0 - const auto widthPerItem = (width() - totalSpacing - reservedWidth) / static_cast(fillWidthCount); + const auto widthPerItem = (width() - totalSpacing - reservedWidth) / + static_cast(fillWidthCount); QList baseWidths; baseWidths.reserve(nChildren); for (auto* const child : validChildren) - baseWidths.append(child->property("fillWidth").toBool() ? widthPerItem : child->implicitWidth()); + baseWidths.append( + child->property("fillWidth").toBool() ? widthPerItem + : child->implicitWidth()); qreal accX = 0; for (int i = 0; i < nChildren; ++i) { @@ -108,12 +122,12 @@ void ButtonRow::relayout() { // clang-format on // Items at edges push by full amount, items in middle push by half - if (i > 1) - prevExtraWidth /= 2; - if (i < nChildren - 2) - nextExtraWidth /= 2; + if (i > 1) prevExtraWidth /= 2; + if (i < nChildren - 2) nextExtraWidth /= 2; - child->setWidth(baseWidths[i] + getMorphExpansion(child) - prevExtraWidth - nextExtraWidth); + child->setWidth( + baseWidths[i] + getMorphExpansion(child) - prevExtraWidth - + nextExtraWidth); child->setHeight(maxHeight); child->setX(accX); diff --git a/Plugins/ZShell/Components/buttonrow.hpp b/Plugins/ZShell/Components/buttonrow.hpp index 4c5f13d..e2417e6 100644 --- a/Plugins/ZShell/Components/buttonrow.hpp +++ b/Plugins/ZShell/Components/buttonrow.hpp @@ -5,33 +5,34 @@ namespace ZShell::components { class ButtonRow : public QQuickItem { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) -public: -explicit ButtonRow(QQuickItem* parent = nullptr); + public: + explicit ButtonRow(QQuickItem* parent = nullptr); -[[nodiscard]] qreal spacing() const; -void setSpacing(qreal spacing); + [[nodiscard]] qreal spacing() const; + void setSpacing(qreal spacing); -signals: -void spacingChanged(); + signals: + void spacingChanged(); -protected: -void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData& data) override; -void updatePolish() override; + protected: + void itemChange( + QQuickItem::ItemChange change, + const QQuickItem::ItemChangeData& data) override; + void updatePolish() override; -private slots: -void invalidate(); + private slots: + void invalidate(); -private: -void relayout(); -static qreal getMorphExpansion(const QQuickItem* item); + void relayout(); + static qreal getMorphExpansion(const QQuickItem* item); -bool m_dirty; -qreal m_spacing; + bool m_dirty; + qreal m_spacing; }; } // namespace ZShell::components diff --git a/Plugins/ZShell/Components/lazylistview.cpp b/Plugins/ZShell/Components/lazylistview.cpp index 0fc9438..ede9d62 100644 --- a/Plugins/ZShell/Components/lazylistview.cpp +++ b/Plugins/ZShell/Components/lazylistview.cpp @@ -21,8 +21,7 @@ qreal LazyListViewAttached::preferredHeight() const { } void LazyListViewAttached::setPreferredHeight(qreal height) { - if (qFuzzyCompare(m_preferredHeight + 1.0, height + 1.0)) - return; + if (qFuzzyCompare(m_preferredHeight + 1.0, height + 1.0)) return; m_preferredHeight = height; emit preferredHeightChanged(); } @@ -32,8 +31,7 @@ qreal LazyListViewAttached::visibleHeight() const { } void LazyListViewAttached::setVisibleHeight(qreal height) { - if (qFuzzyCompare(m_visibleHeight + 1.0, height + 1.0)) - return; + if (qFuzzyCompare(m_visibleHeight + 1.0, height + 1.0)) return; m_visibleHeight = height; emit visibleHeightChanged(); } @@ -43,8 +41,7 @@ bool LazyListViewAttached::ready() const { } void LazyListViewAttached::setReady(bool ready) { - if (m_ready == ready) - return; + if (m_ready == ready) return; m_ready = ready; emit readyChanged(); } @@ -54,8 +51,7 @@ bool LazyListViewAttached::adding() const { } void LazyListViewAttached::setAdding(bool adding) { - if (m_adding == adding) - return; + if (m_adding == adding) return; m_adding = adding; emit addingChanged(); } @@ -65,8 +61,7 @@ bool LazyListViewAttached::removing() const { } void LazyListViewAttached::setRemoving(bool removing) { - if (m_removing == removing) - return; + if (m_removing == removing) return; m_removing = removing; emit removingChanged(); } @@ -76,8 +71,7 @@ bool LazyListViewAttached::trackViewport() const { } void LazyListViewAttached::setTrackViewport(bool track) { - if (m_trackViewport == track) - return; + if (m_trackViewport == track) return; m_trackViewport = track; emit trackViewportChanged(); } @@ -102,16 +96,13 @@ QAbstractItemModel* LazyListView::model() const { } void LazyListView::setModel(QAbstractItemModel* model) { - if (m_model == model) - return; + if (m_model == model) return; - if (m_model) - disconnectModel(); + if (m_model) disconnectModel(); m_model = model; - if (m_model) - connectModel(); + if (m_model) connectModel(); resetContent(); emit modelChanged(); @@ -122,8 +113,7 @@ QQmlComponent* LazyListView::delegate() const { } void LazyListView::setDelegate(QQmlComponent* delegate) { - if (m_delegate == delegate) - return; + if (m_delegate == delegate) return; m_delegate = delegate; resetContent(); @@ -135,8 +125,7 @@ qreal LazyListView::spacing() const { } void LazyListView::setSpacing(qreal spacing) { - if (qFuzzyCompare(m_spacing, spacing)) - return; + if (qFuzzyCompare(m_spacing, spacing)) return; m_spacing = spacing; emit spacingChanged(); polish(); @@ -155,8 +144,7 @@ qreal LazyListView::contentY() const { } void LazyListView::setContentY(qreal contentY) { - if (qFuzzyCompare(m_contentY, contentY)) - return; + if (qFuzzyCompare(m_contentY, contentY)) return; m_contentY = contentY; emit contentYChanged(); polish(); @@ -167,12 +155,10 @@ QRectF LazyListView::viewport() const { } void LazyListView::setViewport(const QRectF& viewport) { - if (m_viewport == viewport) - return; + if (m_viewport == viewport) return; m_viewport = viewport; emit viewportChanged(); - if (m_useCustomViewport) - polish(); + if (m_useCustomViewport) polish(); } bool LazyListView::useCustomViewport() const { @@ -180,8 +166,7 @@ bool LazyListView::useCustomViewport() const { } void LazyListView::setUseCustomViewport(bool use) { - if (m_useCustomViewport == use) - return; + if (m_useCustomViewport == use) return; m_useCustomViewport = use; emit useCustomViewportChanged(); polish(); @@ -192,8 +177,7 @@ qreal LazyListView::cacheBuffer() const { } void LazyListView::setCacheBuffer(qreal buffer) { - if (qFuzzyCompare(m_cacheBuffer, buffer)) - return; + if (qFuzzyCompare(m_cacheBuffer, buffer)) return; m_cacheBuffer = buffer; emit cacheBufferChanged(); polish(); @@ -204,8 +188,7 @@ qreal LazyListView::estimatedHeight() const { } void LazyListView::setEstimatedHeight(qreal height) { - if (qFuzzyCompare(m_estimatedHeight, height)) - return; + if (qFuzzyCompare(m_estimatedHeight, height)) return; m_estimatedHeight = height; emit estimatedHeightChanged(); polish(); @@ -216,17 +199,14 @@ bool LazyListView::asynchronous() const { } void LazyListView::setAsynchronous(bool async) { - if (m_asynchronous == async) - return; + if (m_asynchronous == async) return; m_asynchronous = async; emit asynchronousChanged(); } qreal LazyListView::effectiveEstimatedHeight() const { - if (m_estimatedHeight >= 0) - return m_estimatedHeight; - if (m_knownHeightCount > 0) - return m_knownHeightSum / m_knownHeightCount; + if (m_estimatedHeight >= 0) return m_estimatedHeight; + if (m_knownHeightCount > 0) return m_knownHeightSum / m_knownHeightCount; return 40; } @@ -241,8 +221,7 @@ void LazyListView::untrackHeight(qreal height) { } qreal LazyListView::delegateHeight(QQuickItem* item) { - if (!item) - return 0; + if (!item) return 0; auto* attached = qobject_cast( qmlAttachedPropertiesObject(item, false)); @@ -253,14 +232,12 @@ qreal LazyListView::delegateHeight(QQuickItem* item) { } qreal LazyListView::delegateVisibleHeight(QQuickItem* item) { - if (!item) - return 0; + if (!item) return 0; auto* attached = qobject_cast( qmlAttachedPropertiesObject(item, false)); if (attached) { - if (attached->visibleHeight() >= 0) - return attached->visibleHeight(); + if (attached->visibleHeight() >= 0) return attached->visibleHeight(); if (attached->preferredHeight() >= 0) return attached->preferredHeight(); } @@ -269,8 +246,7 @@ qreal LazyListView::delegateVisibleHeight(QQuickItem* item) { } bool LazyListView::isDelegateReady(QQuickItem* item) { - if (!item) - return false; + if (!item) return false; auto* att = qobject_cast( qmlAttachedPropertiesObject(item, false)); return !att || att->ready(); @@ -281,8 +257,7 @@ int LazyListView::removeDuration() const { } void LazyListView::setRemoveDuration(int duration) { - if (m_removeDuration == duration) - return; + if (m_removeDuration == duration) return; m_removeDuration = duration; emit removeDurationChanged(); } @@ -292,8 +267,7 @@ int LazyListView::readyDelay() const { } void LazyListView::setReadyDelay(int delay) { - if (m_readyDelay == delay) - return; + if (m_readyDelay == delay) return; m_readyDelay = delay; emit readyDelayChanged(); } @@ -308,17 +282,15 @@ void LazyListView::componentComplete() { resetContent(); } -void LazyListView::geometryChange(const QRectF& newGeometry, - const QRectF& oldGeometry) { +void LazyListView::geometryChange( + const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChange(newGeometry, oldGeometry); - if (!m_componentComplete) - return; + if (!m_componentComplete) return; if (!qFuzzyCompare(newGeometry.width(), oldGeometry.width())) { for (auto& entry : m_delegates) { - if (entry.item) - entry.item->setWidth(newGeometry.width()); + if (entry.item) entry.item->setWidth(newGeometry.width()); } } @@ -326,8 +298,7 @@ void LazyListView::geometryChange(const QRectF& newGeometry, } void LazyListView::updatePolish() { - if (!m_componentComplete || !m_model || !m_delegate) - return; + if (!m_componentComplete || !m_model || !m_delegate) return; // Flush pending inserts — make items visible and clear the adding flag // so enter animations begin. When readyDelay > 0 the entire insert is @@ -335,9 +306,9 @@ void LazyListView::updatePolish() { // Collect pending indices first — avoids scanning the entire hash each frame QList pendingIndices; - for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); ++it) { - if (it->pendingInsert && it->item) - pendingIndices.append(it.key()); + for (auto it = m_delegates.constBegin(); it != m_delegates.constEnd(); + ++it) { + if (it->pendingInsert && it->item) pendingIndices.append(it.key()); } for (int pendingIdx : pendingIndices) { @@ -349,8 +320,7 @@ void LazyListView::updatePolish() { auto* item = entry.item; QTimer::singleShot(m_readyDelay, this, [this, item] { auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; + if (indexIt == m_itemToIndex.end()) return; const int idx = indexIt.value(); auto it = m_delegates.find(idx); if (it == m_delegates.end() || it->item != item || @@ -375,14 +345,11 @@ void LazyListView::updatePolish() { ? m_layout[i].height : effectiveEstimatedHeight(); if (h > 0) { - if (hasVisItem) - visualY += m_spacing; + if (hasVisItem) visualY += m_spacing; hasVisItem = true; } - if (i == idx) - break; - if (h > 0) - visualY += h; + if (i == idx) break; + if (h > 0) visualY += h; } item->setY(visualY - m_contentY); } @@ -397,8 +364,8 @@ void LazyListView::updatePolish() { // Animate from visual position to layout position if (idx >= 0 && idx < static_cast(m_layout.size())) - item->setProperty("y", - m_layout[idx].targetY - m_contentY); + item->setProperty( + "y", m_layout[idx].targetY - m_contentY); polish(); }); @@ -432,8 +399,7 @@ void LazyListView::updatePolish() { continue; const int idx = entry.modelIndex; - if (idx < 0 || idx >= layoutSize) - continue; + if (idx < 0 || idx >= layoutSize) continue; if (m_layout[idx].heightKnown && qFuzzyIsNull(m_layout[idx].height)) continue; @@ -444,8 +410,6 @@ void LazyListView::updatePolish() { } } -// --- Layout Engine --- - void LazyListView::relayout() { // Layout positioning uses preferredHeight (final/non-animated). // Only add spacing between items with non-zero height. @@ -455,8 +419,7 @@ void LazyListView::relayout() { const qreal layoutH = record.heightKnown ? record.height : effectiveEstimatedHeight(); if (layoutH > 0) { - if (hasLayoutItem) - y += m_spacing; + if (hasLayoutItem) y += m_spacing; hasLayoutItem = true; record.targetY = y; y += layoutH; @@ -483,8 +446,7 @@ void LazyListView::relayout() { h = m_layout[i].heightKnown ? m_layout[i].height : effectiveEstimatedHeight(); if (h > 0) { - if (hasVisItem) - visY += m_spacing; + if (hasVisItem) visY += m_spacing; hasVisItem = true; visY += h; } @@ -492,11 +454,9 @@ void LazyListView::relayout() { // Account for dying delegates still visually present for (const auto& dying : std::as_const(m_dyingDelegates)) { - if (!dying.item) - continue; + if (!dying.item) continue; const qreal dyingH = delegateVisibleHeight(dying.item); - if (dyingH > 0) - visY = std::max(visY, dying.item->y() + dyingH); + if (dyingH > 0) visY = std::max(visY, dying.item->y() + dyingH); } if (!qFuzzyCompare(m_contentHeight + 1.0, visY + 1.0)) { @@ -519,8 +479,7 @@ QRectF LazyListView::effectiveViewport() const { if (!m_useCustomViewport && m_layoutHeight > 0) { const qreal top = std::min(vp.y(), m_layoutHeight); const qreal bottom = std::max(vp.y() + vp.height(), 0.0); - if (bottom > top) - vp = QRectF(vp.x(), top, vp.width(), bottom - top); + if (bottom > top) vp = QRectF(vp.x(), top, vp.width(), bottom - top); } vp.adjust(0, -m_cacheBuffer, 0, m_cacheBuffer); @@ -541,12 +500,10 @@ QRectF LazyListView::effectiveViewport() const { } std::pair LazyListView::computeVisibleRange() const { - if (m_layout.isEmpty()) - return {-1, -1}; + if (m_layout.isEmpty()) return {-1, -1}; const auto vp = effectiveViewport(); - if (vp.isEmpty()) - return {-1, -1}; + if (vp.isEmpty()) return {-1, -1}; const qreal vpTop = vp.y(); const qreal vpBottom = vp.y() + vp.height(); @@ -571,22 +528,18 @@ std::pair LazyListView::computeVisibleRange() const { } } - if (first >= static_cast(m_layout.size())) - return {-1, -1}; + if (first >= static_cast(m_layout.size())) return {-1, -1}; // Linear scan for last visible item int last = first; for (int i = first; i < static_cast(m_layout.size()); ++i) { - if (m_layout[i].targetY > vpBottom) - break; + if (m_layout[i].targetY > vpBottom) break; last = i; } return {first, last}; } -// --- Delegate Lifecycle --- - void LazyListView::syncDelegates() { const auto [first, last] = computeVisibleRange(); @@ -601,8 +554,7 @@ void LazyListView::syncDelegates() { const auto vp = effectiveViewport(); QList toRemove; for (auto it = m_delegates.begin(); it != m_delegates.end(); ++it) { - if (visibleIndices.contains(it.key())) - continue; + if (visibleIndices.contains(it.key())) continue; if (!it->item || vp.isEmpty()) { toRemove.append(it.key()); continue; @@ -622,11 +574,9 @@ void LazyListView::syncDelegates() { std::min(destroyBudget, static_cast(toRemove.size()))); int destroyed = 0; for (int idx : toRemove) { - if (destroyed >= destroyBudget) - break; + if (destroyed >= destroyBudget) break; auto entry = m_delegates.take(idx); - if (entry.item) - m_itemToIndex.remove(entry.item); + if (entry.item) m_itemToIndex.remove(entry.item); removedEntries.append(std::move(entry)); ++destroyed; } @@ -637,8 +587,7 @@ void LazyListView::syncDelegates() { QList toCreate; if (first >= 0) { for (int i = first; i <= last; ++i) { - if (!m_delegates.contains(i)) - toCreate.append(i); + if (!m_delegates.contains(i)) toCreate.append(i); } } @@ -647,8 +596,7 @@ void LazyListView::syncDelegates() { : static_cast(toCreate.size()); int created = 0; for (int i : toCreate) { - if (created >= createBudget) - break; + if (created >= createBudget) break; auto entry = createDelegate(i); if (entry.item) { @@ -674,25 +622,21 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { DelegateEntry entry; entry.modelIndex = modelIndex; - if (!m_delegate || !m_model) - return entry; + if (!m_delegate || !m_model) return entry; const auto roleNames = m_model->roleNames(); // Use the delegate component's creation context for beginCreate // so bound components (pragma ComponentBehavior: Bound) are accepted. auto* compContext = m_delegate->creationContext(); - if (!compContext) - compContext = qmlContext(this); - if (!compContext) - return entry; + if (!compContext) compContext = qmlContext(this); + if (!compContext) return entry; auto* obj = m_delegate->beginCreate(compContext); entry.item = qobject_cast(obj); if (!entry.item) { - if (obj) - m_delegate->completeCreate(); + if (obj) m_delegate->completeCreate(); delete obj; return entry; } @@ -705,16 +649,15 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) { const auto name = QString::fromUtf8(it.value()); initialProps.insert(name, m_model->data(index, it.key())); - if (name == QStringLiteral("modelData")) - hasModelData = true; + if (name == QStringLiteral("modelData")) hasModelData = true; } initialProps.insert(QStringLiteral("index"), modelIndex); if (!hasModelData) { const auto role = roleNames.isEmpty() ? Qt::DisplayRole : roleNames.constBegin().key(); - initialProps.insert(QStringLiteral("modelData"), - m_model->data(index, role)); + initialProps.insert( + QStringLiteral("modelData"), m_model->data(index, role)); } m_delegate->setInitialProperties(entry.item, initialProps); @@ -728,8 +671,7 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { m_layout[modelIndex].isNew) { auto* addingAttached = qobject_cast( qmlAttachedPropertiesObject(entry.item, true)); - if (addingAttached) - addingAttached->setAdding(true); + if (addingAttached) addingAttached->setAdding(true); } m_delegate->completeCreate(); @@ -740,15 +682,12 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { // Height-change handler — uses m_itemToIndex for O(1) lookup. // Ignored while the delegate is not yet ready. auto onHeightChanged = [this, item = entry.item] { - if (!isDelegateReady(item)) - return; + if (!isDelegateReady(item)) return; auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; + if (indexIt == m_itemToIndex.end()) return; const int idx = indexIt.value(); auto delegateIt = m_delegates.find(idx); - if (delegateIt == m_delegates.end() || delegateIt->item != item) - return; + if (delegateIt == m_delegates.end() || delegateIt->item != item) return; const qreal h = delegateHeight(item); if (idx < static_cast(m_layout.size()) && !qFuzzyCompare(m_layout[idx].height + 1.0, h + 1.0)) { @@ -756,8 +695,7 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { const bool wasKnown = m_layout[idx].heightKnown; m_layout[idx].height = h; m_layout[idx].heightKnown = true; - if (wasKnown) - untrackHeight(oldH); + if (wasKnown) untrackHeight(oldH); trackHeight(h); // If this tracked item is above the viewport, emit a @@ -785,58 +723,56 @@ LazyListView::DelegateEntry LazyListView::createDelegate(int modelIndex) { }; // Watch implicitHeight as fallback - connect(entry.item, - &QQuickItem::implicitHeightChanged, - this, - onHeightChanged); + connect( + entry.item, &QQuickItem::implicitHeightChanged, this, onHeightChanged); // Watch attached properties if the delegate uses them auto* attached = qobject_cast( qmlAttachedPropertiesObject(entry.item, false)); if (attached) { - connect(attached, - &LazyListViewAttached::preferredHeightChanged, - this, - onHeightChanged); - connect(attached, - &LazyListViewAttached::visibleHeightChanged, - this, - [this] { polish(); }); - connect(attached, - &LazyListViewAttached::readyChanged, - this, - [this, item = entry.item] { - auto indexIt = m_itemToIndex.find(item); - if (indexIt == m_itemToIndex.end()) - return; - const int idx = indexIt.value(); - if (idx >= static_cast(m_layout.size())) - return; - auto* att = qobject_cast( - qmlAttachedPropertiesObject(item, false)); - if (!att || !att->ready()) - return; + connect( + attached, + &LazyListViewAttached::preferredHeightChanged, + this, + onHeightChanged); + connect( + attached, + &LazyListViewAttached::visibleHeightChanged, + this, + [this] { polish(); }); + connect( + attached, + &LazyListViewAttached::readyChanged, + this, + [this, item = entry.item] { + auto indexIt = m_itemToIndex.find(item); + if (indexIt == m_itemToIndex.end()) return; + const int idx = indexIt.value(); + if (idx >= static_cast(m_layout.size())) return; + auto* att = qobject_cast( + qmlAttachedPropertiesObject(item, false)); + if (!att || !att->ready()) return; - const qreal h = delegateHeight(item); - const qreal oldLayoutH = m_layout[idx].heightKnown - ? m_layout[idx].height - : effectiveEstimatedHeight(); - if (m_layout[idx].heightKnown) - untrackHeight(m_layout[idx].height); - m_layout[idx].height = h; - m_layout[idx].heightKnown = true; - trackHeight(h); + const qreal h = delegateHeight(item); + const qreal oldLayoutH = m_layout[idx].heightKnown + ? m_layout[idx].height + : effectiveEstimatedHeight(); + if (m_layout[idx].heightKnown) + untrackHeight(m_layout[idx].height); + m_layout[idx].height = h; + m_layout[idx].heightKnown = true; + trackHeight(h); - if (att->trackViewport() && - !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) { - const qreal vpTop = m_useCustomViewport ? m_viewport.y() - : m_contentY; - if (m_layout[idx].targetY < vpTop) - emit viewportAdjustNeeded(h - oldLayoutH); - } + if (att->trackViewport() && + !qFuzzyCompare(h + 1.0, oldLayoutH + 1.0)) { + const qreal vpTop = m_useCustomViewport ? m_viewport.y() + : m_contentY; + if (m_layout[idx].targetY < vpTop) + emit viewportAdjustNeeded(h - oldLayoutH); + } - polish(); - }); + polish(); + }); } return entry; @@ -852,8 +788,7 @@ void LazyListView::destroyDelegate(DelegateEntry& entry) { } void LazyListView::updateDelegateData(DelegateEntry& entry) { - if (!m_model || !entry.item) - return; + if (!m_model || !entry.item) return; const auto roleNames = m_model->roleNames(); const auto index = m_model->index(entry.modelIndex, 0); @@ -861,10 +796,9 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) { for (auto it = roleNames.constBegin(); it != roleNames.constEnd(); ++it) { const auto name = QString::fromUtf8(it.value()); - entry.item->setProperty(name.toUtf8().constData(), - m_model->data(index, it.key())); - if (name == QStringLiteral("modelData")) - hasModelData = true; + entry.item->setProperty( + name.toUtf8().constData(), m_model->data(index, it.key())); + if (name == QStringLiteral("modelData")) hasModelData = true; } entry.item->setProperty("index", entry.modelIndex); @@ -876,53 +810,58 @@ void LazyListView::updateDelegateData(DelegateEntry& entry) { } } -// --- Model Connection --- - void LazyListView::connectModel() { - if (!m_model) - return; + if (!m_model) return; m_modelConnections = { - connect(m_model, - &QAbstractItemModel::rowsInserted, - this, - &LazyListView::onRowsInserted), - connect(m_model, - &QAbstractItemModel::rowsAboutToBeRemoved, - this, - &LazyListView::onRowsAboutToBeRemoved), - connect(m_model, - &QAbstractItemModel::rowsRemoved, - this, - &LazyListView::onRowsRemoved), - connect(m_model, - &QAbstractItemModel::rowsMoved, - this, - &LazyListView::onRowsMoved), - connect(m_model, - &QAbstractItemModel::dataChanged, - this, - &LazyListView::onDataChanged), - connect(m_model, - &QAbstractItemModel::modelReset, - this, - &LazyListView::onModelReset), - connect(m_model, - &QAbstractItemModel::layoutChanged, - this, - [this] { - for (auto& entry : m_delegates) - updateDelegateData(entry); - polish(); - }), - connect(m_model, - &QObject::destroyed, - this, - [this] { - m_model = nullptr; - resetContent(); - emit modelChanged(); - }), + connect( + m_model, + &QAbstractItemModel::rowsInserted, + this, + &LazyListView::onRowsInserted), + connect( + m_model, + &QAbstractItemModel::rowsAboutToBeRemoved, + this, + &LazyListView::onRowsAboutToBeRemoved), + connect( + m_model, + &QAbstractItemModel::rowsRemoved, + this, + &LazyListView::onRowsRemoved), + connect( + m_model, + &QAbstractItemModel::rowsMoved, + this, + &LazyListView::onRowsMoved), + connect( + m_model, + &QAbstractItemModel::dataChanged, + this, + &LazyListView::onDataChanged), + connect( + m_model, + &QAbstractItemModel::modelReset, + this, + &LazyListView::onModelReset), + connect( + m_model, + &QAbstractItemModel::layoutChanged, + this, + [this] { + for (auto& entry : m_delegates) + updateDelegateData(entry); + polish(); + }), + connect( + m_model, + &QObject::destroyed, + this, + [this] { + m_model = nullptr; + resetContent(); + emit modelChanged(); + }), }; } @@ -962,11 +901,9 @@ void LazyListView::resetContent() { polish(); } -void LazyListView::onRowsInserted(const QModelIndex& parent, - int first, - int last) { - if (parent.isValid()) - return; +void LazyListView::onRowsInserted( + const QModelIndex& parent, int first, int last) { + if (parent.isValid()) return; const int insertCount = last - first + 1; // Insert new layout records @@ -990,19 +927,15 @@ void LazyListView::onRowsInserted(const QModelIndex& parent, polish(); } -void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, - int first, - int last) { - if (parent.isValid()) - return; +void LazyListView::onRowsAboutToBeRemoved( + const QModelIndex& parent, int first, int last) { + if (parent.isValid()) return; for (int i = first; i <= last; ++i) { - if (!m_delegates.contains(i)) - continue; + if (!m_delegates.contains(i)) continue; auto entry = m_delegates.take(i); - if (entry.item) - m_itemToIndex.remove(entry.item); + if (entry.item) m_itemToIndex.remove(entry.item); entry.pendingRemoval = true; // Never made visible — skip remove animation @@ -1014,8 +947,7 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, if (m_removeDuration > 0 && entry.item) { auto* attached = qobject_cast( qmlAttachedPropertiesObject(entry.item, false)); - if (attached) - attached->setRemoving(true); + if (attached) attached->setRemoving(true); // Schedule destruction after the remove animation duration auto* item = entry.item; @@ -1037,18 +969,15 @@ void LazyListView::onRowsAboutToBeRemoved(const QModelIndex& parent, } } -void LazyListView::onRowsRemoved(const QModelIndex& parent, - int first, - int last) { - if (parent.isValid()) - return; +void LazyListView::onRowsRemoved( + const QModelIndex& parent, int first, int last) { + if (parent.isValid()) return; const int removeCount = last - first + 1; // Untrack known heights being removed for (int i = first; i <= last; ++i) { - if (m_layout[i].heightKnown) - untrackHeight(m_layout[i].height); + if (m_layout[i].heightKnown) untrackHeight(m_layout[i].height); } // Remove layout records @@ -1072,13 +1001,13 @@ void LazyListView::onRowsRemoved(const QModelIndex& parent, polish(); } -void LazyListView::onRowsMoved(const QModelIndex& parent, - int start, - int end, - const QModelIndex& destination, - int row) { - if (parent.isValid() || destination.isValid()) - return; +void LazyListView::onRowsMoved( + const QModelIndex& parent, + int start, + int end, + const QModelIndex& destination, + int row) { + if (parent.isValid() || destination.isValid()) return; const int count = end - start + 1; const int dest = row > start ? row - count : row; @@ -1101,10 +1030,8 @@ void LazyListView::onRowsMoved(const QModelIndex& parent, if (oldIdx >= start && oldIdx <= end) { newIdx = dest + (oldIdx - start); } else { - if (oldIdx > end) - newIdx -= count; - if (newIdx >= dest) - newIdx += count; + if (oldIdx > end) newIdx -= count; + if (newIdx >= dest) newIdx += count; } auto entry = std::move(it.value()); @@ -1120,17 +1047,16 @@ void LazyListView::onRowsMoved(const QModelIndex& parent, polish(); } -void LazyListView::onDataChanged(const QModelIndex& topLeft, - const QModelIndex& bottomRight, - const QList& roles) { +void LazyListView::onDataChanged( + const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QList& roles) { Q_UNUSED(roles) - if (topLeft.parent().isValid()) - return; + if (topLeft.parent().isValid()) return; for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - if (m_delegates.contains(i)) - updateDelegateData(m_delegates[i]); + if (m_delegates.contains(i)) updateDelegateData(m_delegates[i]); } } diff --git a/Plugins/ZShell/Components/lazylistview.hpp b/Plugins/ZShell/Components/lazylistview.hpp index c1bc987..e0077c4 100644 --- a/Plugins/ZShell/Components/lazylistview.hpp +++ b/Plugins/ZShell/Components/lazylistview.hpp @@ -12,232 +12,265 @@ namespace ZShell::components { class LazyListViewAttached : public QObject { -Q_OBJECT + Q_OBJECT -Q_PROPERTY(qreal preferredHeight READ preferredHeight WRITE setPreferredHeight NOTIFY preferredHeightChanged) -Q_PROPERTY(qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY visibleHeightChanged) -Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) -Q_PROPERTY(bool adding READ adding NOTIFY addingChanged) -Q_PROPERTY(bool removing READ removing NOTIFY removingChanged) -Q_PROPERTY(bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY trackViewportChanged) + Q_PROPERTY( + qreal preferredHeight READ preferredHeight WRITE setPreferredHeight + NOTIFY preferredHeightChanged) + Q_PROPERTY( + qreal visibleHeight READ visibleHeight WRITE setVisibleHeight NOTIFY + visibleHeightChanged) + Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) + Q_PROPERTY(bool adding READ adding NOTIFY addingChanged) + Q_PROPERTY(bool removing READ removing NOTIFY removingChanged) + Q_PROPERTY( + bool trackViewport READ trackViewport WRITE setTrackViewport NOTIFY + trackViewportChanged) -public: -explicit LazyListViewAttached(QObject* parent = nullptr); + public: + explicit LazyListViewAttached(QObject* parent = nullptr); -[[nodiscard]] qreal preferredHeight() const; -void setPreferredHeight(qreal height); + [[nodiscard]] qreal preferredHeight() const; + void setPreferredHeight(qreal height); -[[nodiscard]] qreal visibleHeight() const; -void setVisibleHeight(qreal height); + [[nodiscard]] qreal visibleHeight() const; + void setVisibleHeight(qreal height); -[[nodiscard]] bool ready() const; -void setReady(bool ready); + [[nodiscard]] bool ready() const; + void setReady(bool ready); -[[nodiscard]] bool adding() const; -void setAdding(bool adding); + [[nodiscard]] bool adding() const; + void setAdding(bool adding); -[[nodiscard]] bool removing() const; -void setRemoving(bool removing); + [[nodiscard]] bool removing() const; + void setRemoving(bool removing); -[[nodiscard]] bool trackViewport() const; -void setTrackViewport(bool track); + [[nodiscard]] bool trackViewport() const; + void setTrackViewport(bool track); -signals: -void preferredHeightChanged(); -void visibleHeightChanged(); -void readyChanged(); -void addingChanged(); -void removingChanged(); -void trackViewportChanged(); + signals: + void preferredHeightChanged(); + void visibleHeightChanged(); + void readyChanged(); + void addingChanged(); + void removingChanged(); + void trackViewportChanged(); -private: -qreal m_preferredHeight = -1; -qreal m_visibleHeight = -1; -bool m_ready = false; -bool m_adding = false; -bool m_removing = false; -bool m_trackViewport = false; + private: + qreal m_preferredHeight = -1; + qreal m_visibleHeight = -1; + bool m_ready = false; + bool m_adding = false; + bool m_removing = false; + bool m_trackViewport = false; }; class LazyListView : public QQuickItem { -Q_OBJECT -QML_ELEMENT -QML_ATTACHED(LazyListViewAttached) + Q_OBJECT + QML_ELEMENT + QML_ATTACHED(LazyListViewAttached) -// Model & Delegate -Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged) -Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + // Model & Delegate + Q_PROPERTY( + QAbstractItemModel* model READ model WRITE setModel NOTIFY modelChanged) + Q_PROPERTY( + QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY + delegateChanged) -// Layout -Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) -Q_PROPERTY(qreal contentHeight READ contentHeight NOTIFY contentHeightChanged) -Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged) -Q_PROPERTY(qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged) + // Layout + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY( + qreal contentHeight READ contentHeight NOTIFY contentHeightChanged) + Q_PROPERTY(qreal layoutHeight READ layoutHeight NOTIFY layoutHeightChanged) + Q_PROPERTY( + qreal contentY READ contentY WRITE setContentY NOTIFY contentYChanged) -// Viewport & Lazy Loading -Q_PROPERTY(QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged) -Q_PROPERTY(bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport NOTIFY useCustomViewportChanged) -Q_PROPERTY(qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY cacheBufferChanged) + // Viewport & Lazy Loading + Q_PROPERTY( + QRectF viewport READ viewport WRITE setViewport NOTIFY viewportChanged) + Q_PROPERTY( + bool useCustomViewport READ useCustomViewport WRITE setUseCustomViewport + NOTIFY useCustomViewportChanged) + Q_PROPERTY( + qreal cacheBuffer READ cacheBuffer WRITE setCacheBuffer NOTIFY + cacheBufferChanged) -// Sizing -Q_PROPERTY(qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight NOTIFY estimatedHeightChanged) + // Sizing + Q_PROPERTY( + qreal estimatedHeight READ estimatedHeight WRITE setEstimatedHeight + NOTIFY estimatedHeightChanged) -// Async -Q_PROPERTY(bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY asynchronousChanged) + // Async + Q_PROPERTY( + bool asynchronous READ asynchronous WRITE setAsynchronous NOTIFY + asynchronousChanged) -// Animation Durations -Q_PROPERTY(int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY removeDurationChanged) -Q_PROPERTY(int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY readyDelayChanged) + // Animation Durations + Q_PROPERTY( + int removeDuration READ removeDuration WRITE setRemoveDuration NOTIFY + removeDurationChanged) + Q_PROPERTY( + int readyDelay READ readyDelay WRITE setReadyDelay NOTIFY + readyDelayChanged) -// State -Q_PROPERTY(int count READ count NOTIFY countChanged) + // State + Q_PROPERTY(int count READ count NOTIFY countChanged) -public: -explicit LazyListView(QQuickItem* parent = nullptr); -~LazyListView() override; + public: + explicit LazyListView(QQuickItem* parent = nullptr); + ~LazyListView() override; -static LazyListViewAttached* qmlAttachedProperties(QObject* object); + static LazyListViewAttached* qmlAttachedProperties(QObject* object); -// Model & Delegate -[[nodiscard]] QAbstractItemModel* model() const; -void setModel(QAbstractItemModel* model); + // Model & Delegate + [[nodiscard]] QAbstractItemModel* model() const; + void setModel(QAbstractItemModel* model); -[[nodiscard]] QQmlComponent* delegate() const; -void setDelegate(QQmlComponent* delegate); + [[nodiscard]] QQmlComponent* delegate() const; + void setDelegate(QQmlComponent* delegate); -// Layout -[[nodiscard]] qreal spacing() const; -void setSpacing(qreal spacing); + // Layout + [[nodiscard]] qreal spacing() const; + void setSpacing(qreal spacing); -[[nodiscard]] qreal contentHeight() const; -[[nodiscard]] qreal layoutHeight() const; + [[nodiscard]] qreal contentHeight() const; + [[nodiscard]] qreal layoutHeight() const; -[[nodiscard]] qreal contentY() const; -void setContentY(qreal contentY); + [[nodiscard]] qreal contentY() const; + void setContentY(qreal contentY); -// Viewport -[[nodiscard]] QRectF viewport() const; -void setViewport(const QRectF& viewport); + // Viewport + [[nodiscard]] QRectF viewport() const; + void setViewport(const QRectF& viewport); -[[nodiscard]] bool useCustomViewport() const; -void setUseCustomViewport(bool use); + [[nodiscard]] bool useCustomViewport() const; + void setUseCustomViewport(bool use); -[[nodiscard]] qreal cacheBuffer() const; -void setCacheBuffer(qreal buffer); + [[nodiscard]] qreal cacheBuffer() const; + void setCacheBuffer(qreal buffer); -// Sizing -[[nodiscard]] qreal estimatedHeight() const; -void setEstimatedHeight(qreal height); + // Sizing + [[nodiscard]] qreal estimatedHeight() const; + void setEstimatedHeight(qreal height); -// Async -[[nodiscard]] bool asynchronous() const; -void setAsynchronous(bool async); + // Async + [[nodiscard]] bool asynchronous() const; + void setAsynchronous(bool async); -// Animation Durations -[[nodiscard]] int removeDuration() const; -void setRemoveDuration(int duration); + // Animation Durations + [[nodiscard]] int removeDuration() const; + void setRemoveDuration(int duration); -[[nodiscard]] int readyDelay() const; -void setReadyDelay(int delay); + [[nodiscard]] int readyDelay() const; + void setReadyDelay(int delay); -// State -[[nodiscard]] int count() const; -signals: -void modelChanged(); -void delegateChanged(); -void spacingChanged(); -void contentHeightChanged(); -void layoutHeightChanged(); -void contentYChanged(); -void viewportChanged(); -void useCustomViewportChanged(); -void cacheBufferChanged(); -void estimatedHeightChanged(); -void asynchronousChanged(); -void removeDurationChanged(); -void readyDelayChanged(); -void countChanged(); -void viewportAdjustNeeded(qreal delta); + // State + [[nodiscard]] int count() const; + signals: + void modelChanged(); + void delegateChanged(); + void spacingChanged(); + void contentHeightChanged(); + void layoutHeightChanged(); + void contentYChanged(); + void viewportChanged(); + void useCustomViewportChanged(); + void cacheBufferChanged(); + void estimatedHeightChanged(); + void asynchronousChanged(); + void removeDurationChanged(); + void readyDelayChanged(); + void countChanged(); + void viewportAdjustNeeded(qreal delta); -protected: -void componentComplete() override; -void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) override; -void updatePolish() override; + protected: + void componentComplete() override; + void geometryChange( + const QRectF& newGeometry, const QRectF& oldGeometry) override; + void updatePolish() override; -private: -struct ItemRecord { - qreal targetY = 0; - qreal height = 0; - bool heightKnown = false; - bool isNew = false; -}; + private: + struct ItemRecord { + qreal targetY = 0; + qreal height = 0; + bool heightKnown = false; + bool isNew = false; + }; -struct DelegateEntry { - int modelIndex = -1; - QQuickItem* item = nullptr; - bool pendingRemoval = false; - bool pendingInsert = false; - bool readyDelayStarted = false; -}; + struct DelegateEntry { + int modelIndex = -1; + QQuickItem* item = nullptr; + bool pendingRemoval = false; + bool pendingInsert = false; + bool readyDelayStarted = false; + }; -// Layout -void relayout(); -[[nodiscard]] std::pair computeVisibleRange() const; -[[nodiscard]] QRectF effectiveViewport() const; -[[nodiscard]] qreal effectiveEstimatedHeight() const; -[[nodiscard]] static qreal delegateHeight(QQuickItem* item); -[[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item); -[[nodiscard]] static bool isDelegateReady(QQuickItem* item); -void trackHeight(qreal height); -void untrackHeight(qreal height); + // Layout + void relayout(); + [[nodiscard]] std::pair computeVisibleRange() const; + [[nodiscard]] QRectF effectiveViewport() const; + [[nodiscard]] qreal effectiveEstimatedHeight() const; + [[nodiscard]] static qreal delegateHeight(QQuickItem* item); + [[nodiscard]] static qreal delegateVisibleHeight(QQuickItem* item); + [[nodiscard]] static bool isDelegateReady(QQuickItem* item); + void trackHeight(qreal height); + void untrackHeight(qreal height); -// Delegate lifecycle -void syncDelegates(); -DelegateEntry createDelegate(int modelIndex); -void destroyDelegate(DelegateEntry& entry); -void updateDelegateData(DelegateEntry& entry); + // Delegate lifecycle + void syncDelegates(); + DelegateEntry createDelegate(int modelIndex); + void destroyDelegate(DelegateEntry& entry); + void updateDelegateData(DelegateEntry& entry); -// Model connection -void connectModel(); -void disconnectModel(); -void resetContent(); -void onRowsInserted(const QModelIndex& parent, int first, int last); -void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last); -void onRowsRemoved(const QModelIndex& parent, int first, int last); -void onRowsMoved(const QModelIndex& parent, int start, int end, const QModelIndex& destination, int row); -void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QList& roles); -void onModelReset(); + // Model connection + void connectModel(); + void disconnectModel(); + void resetContent(); + void onRowsInserted(const QModelIndex& parent, int first, int last); + void onRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last); + void onRowsRemoved(const QModelIndex& parent, int first, int last); + void onRowsMoved( + const QModelIndex& parent, + int start, + int end, + const QModelIndex& destination, + int row); + void onDataChanged( + const QModelIndex& topLeft, + const QModelIndex& bottomRight, + const QList& roles); + void onModelReset(); -// Members -QAbstractItemModel* m_model = nullptr; -QQmlComponent* m_delegate = nullptr; + // Members + QAbstractItemModel* m_model = nullptr; + QQmlComponent* m_delegate = nullptr; -qreal m_spacing = 0; -qreal m_contentHeight = 0; -qreal m_layoutHeight = 0; -qreal m_contentY = 0; + qreal m_spacing = 0; + qreal m_contentHeight = 0; + qreal m_layoutHeight = 0; + qreal m_contentY = 0; -QRectF m_viewport; -bool m_useCustomViewport = false; -qreal m_cacheBuffer = 0; + QRectF m_viewport; + bool m_useCustomViewport = false; + qreal m_cacheBuffer = 0; -qreal m_estimatedHeight = -1; -qreal m_knownHeightSum = 0; -int m_knownHeightCount = 0; -bool m_asynchronous = false; + qreal m_estimatedHeight = -1; + qreal m_knownHeightSum = 0; + int m_knownHeightCount = 0; + bool m_asynchronous = false; -int m_removeDuration = 300; -int m_readyDelay = 0; + int m_removeDuration = 300; + int m_readyDelay = 0; -QVector m_layout; -QHash m_delegates; -QHash m_itemToIndex; -QVector m_dyingDelegates; + QVector m_layout; + QHash m_delegates; + QHash m_itemToIndex; + QVector m_dyingDelegates; -bool m_componentComplete = false; -bool m_relayoutPending = false; + bool m_componentComplete = false; + bool m_relayoutPending = false; -QList m_modelConnections; + QList m_modelConnections; }; } // namespace ZShell::components diff --git a/Plugins/ZShell/Components/wavyline.cpp b/Plugins/ZShell/Components/wavyline.cpp index 757b7a2..8619f05 100644 --- a/Plugins/ZShell/Components/wavyline.cpp +++ b/Plugins/ZShell/Components/wavyline.cpp @@ -194,7 +194,8 @@ void WavyLine::paintLinear(QPainter* painter) { bool first = true; for (int x = m_lineWidth / 2; x <= drawEnd; ++x) { - const auto theta = m_frequency * 2 * M_PI * (x + m_startX) / len + phase; + const auto theta = + m_frequency * 2 * M_PI * (x + m_startX) / len + phase; const auto waveY = centerY + amplitude * qSin(theta); if (first) { path.moveTo(x, waveY); @@ -215,7 +216,10 @@ void WavyLine::paintArc(QPainter* painter) { const auto amplitude = m_lineWidth * m_amplitudeMultiplier; const auto cx = width() / 2.0; const auto cy = height() / 2.0; - const auto radius = m_radius > 0 ? m_radius : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0; + const auto radius = + m_radius > 0 + ? m_radius + : (qMin(width(), height()) - m_lineWidth - 2 * amplitude) / 2.0; if (radius <= 0) { return; diff --git a/Plugins/ZShell/Components/wavyline.hpp b/Plugins/ZShell/Components/wavyline.hpp index e9fab48..8f25589 100644 --- a/Plugins/ZShell/Components/wavyline.hpp +++ b/Plugins/ZShell/Components/wavyline.hpp @@ -6,102 +6,116 @@ namespace ZShell::controls { class WavyLine : public QQuickPaintedItem { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged FINAL) -Q_PROPERTY(qreal amplitudeMultiplier READ amplitudeMultiplier WRITE setAmplitudeMultiplier NOTIFY - amplitudeMultiplierChanged FINAL) -Q_PROPERTY(int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged FINAL) -Q_PROPERTY(qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL) -Q_PROPERTY(qreal fullLength READ fullLength WRITE setFullLength NOTIFY fullLengthChanged FINAL) -Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) -Q_PROPERTY(qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY waveProgressChanged FINAL) -Q_PROPERTY(PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged FINAL) -Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged FINAL) -Q_PROPERTY(qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY fullAngleChanged FINAL) -Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) -Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) + Q_PROPERTY( + int lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged + FINAL) + Q_PROPERTY( + qreal amplitudeMultiplier READ amplitudeMultiplier WRITE + setAmplitudeMultiplier NOTIFY amplitudeMultiplierChanged FINAL) + Q_PROPERTY( + int frequency READ frequency WRITE setFrequency NOTIFY frequencyChanged + FINAL) + Q_PROPERTY( + qreal startX READ startX WRITE setStartX NOTIFY startXChanged FINAL) + Q_PROPERTY( + qreal fullLength READ fullLength WRITE setFullLength NOTIFY + fullLengthChanged FINAL) + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged FINAL) + Q_PROPERTY( + qreal waveProgress READ waveProgress WRITE setWaveProgress NOTIFY + waveProgressChanged FINAL) + Q_PROPERTY( + PathType pathType READ pathType WRITE setPathType NOTIFY pathTypeChanged + FINAL) + Q_PROPERTY( + qreal startAngle READ startAngle WRITE setStartAngle NOTIFY + startAngleChanged FINAL) + Q_PROPERTY( + qreal fullAngle READ fullAngle WRITE setFullAngle NOTIFY + fullAngleChanged FINAL) + Q_PROPERTY( + qreal radius READ radius WRITE setRadius NOTIFY radiusChanged FINAL) + Q_PROPERTY(qreal value READ value WRITE setValue NOTIFY valueChanged FINAL) -public: -enum PathType { - Linear, - Arc -}; -Q_ENUM(PathType) + public: + enum PathType { Linear, Arc }; + Q_ENUM(PathType) -explicit WavyLine(QQuickItem* parent = nullptr); + explicit WavyLine(QQuickItem* parent = nullptr); -[[nodiscard]] int lineWidth() const; -void setLineWidth(int lineWidth); + [[nodiscard]] int lineWidth() const; + void setLineWidth(int lineWidth); -[[nodiscard]] qreal amplitudeMultiplier() const; -void setAmplitudeMultiplier(qreal amplitudeMultiplier); + [[nodiscard]] qreal amplitudeMultiplier() const; + void setAmplitudeMultiplier(qreal amplitudeMultiplier); -[[nodiscard]] int frequency() const; -void setFrequency(int frequency); + [[nodiscard]] int frequency() const; + void setFrequency(int frequency); -[[nodiscard]] qreal startX() const; -void setStartX(qreal startX); + [[nodiscard]] qreal startX() const; + void setStartX(qreal startX); -[[nodiscard]] qreal fullLength() const; -void setFullLength(qreal fullLength); + [[nodiscard]] qreal fullLength() const; + void setFullLength(qreal fullLength); -[[nodiscard]] QColor color() const; -void setColor(const QColor& color); + [[nodiscard]] QColor color() const; + void setColor(const QColor& color); -[[nodiscard]] qreal waveProgress() const; -void setWaveProgress(qreal progress); + [[nodiscard]] qreal waveProgress() const; + void setWaveProgress(qreal progress); -[[nodiscard]] PathType pathType() const; -void setPathType(PathType pathType); + [[nodiscard]] PathType pathType() const; + void setPathType(PathType pathType); -[[nodiscard]] qreal startAngle() const; -void setStartAngle(qreal startAngle); + [[nodiscard]] qreal startAngle() const; + void setStartAngle(qreal startAngle); -[[nodiscard]] qreal fullAngle() const; -void setFullAngle(qreal fullAngle); + [[nodiscard]] qreal fullAngle() const; + void setFullAngle(qreal fullAngle); -[[nodiscard]] qreal radius() const; -void setRadius(qreal radius); + [[nodiscard]] qreal radius() const; + void setRadius(qreal radius); -[[nodiscard]] qreal value() const; -void setValue(qreal value); + [[nodiscard]] qreal value() const; + void setValue(qreal value); -void paint(QPainter* painter) override; + void paint(QPainter* painter) override; -signals: -void lineWidthChanged(); -void amplitudeMultiplierChanged(); -void frequencyChanged(); -void startXChanged(); -void fullLengthChanged(); -void colorChanged(); -void waveProgressChanged(); -void pathTypeChanged(); -void startAngleChanged(); -void fullAngleChanged(); -void radiusChanged(); -void valueChanged(); + signals: + void lineWidthChanged(); + void amplitudeMultiplierChanged(); + void frequencyChanged(); + void startXChanged(); + void fullLengthChanged(); + void colorChanged(); + void waveProgressChanged(); + void pathTypeChanged(); + void startAngleChanged(); + void fullAngleChanged(); + void radiusChanged(); + void valueChanged(); -private: -void paintLinear(QPainter* painter); -void paintArc(QPainter* painter); + private: + void paintLinear(QPainter* painter); + void paintArc(QPainter* painter); -int m_lineWidth; -qreal m_amplitudeMultiplier; -int m_frequency; -qreal m_startX; -qreal m_fullLength; -QColor m_color; -qreal m_waveProgress; -PathType m_pathType; -qreal m_startAngle; -qreal m_fullAngle; -qreal m_radius; -qreal m_value; -qreal m_startAngleRad; -qreal m_fullAngleRad; + int m_lineWidth; + qreal m_amplitudeMultiplier; + int m_frequency; + qreal m_startX; + qreal m_fullLength; + QColor m_color; + qreal m_waveProgress; + PathType m_pathType; + qreal m_startAngle; + qreal m_fullAngle; + qreal m_radius; + qreal m_value; + qreal m_startAngleRad; + qreal m_fullAngleRad; }; } // namespace ZShell::controls diff --git a/Plugins/ZShell/Internal/arcgauge.cpp b/Plugins/ZShell/Internal/arcgauge.cpp index 4f77b50..571f3b7 100644 --- a/Plugins/ZShell/Internal/arcgauge.cpp +++ b/Plugins/ZShell/Internal/arcgauge.cpp @@ -6,8 +6,7 @@ namespace ZShell::internal { -ArcGauge::ArcGauge(QQuickItem* parent) - : QQuickPaintedItem(parent) { +ArcGauge::ArcGauge(QQuickItem* parent) : QQuickPaintedItem(parent) { setAntialiasing(true); } @@ -36,7 +35,8 @@ void ArcGauge::paint(QPainter* painter) { // Draw value arc if (m_percentage > 0.0) { - const int valueSweep16 = qRound(static_cast(sweepAngle16) * m_percentage); + const int valueSweep16 = + qRound(static_cast(sweepAngle16) * m_percentage); QPen valuePen(m_accentColor, m_lineWidth); valuePen.setCapStyle(Qt::RoundCap); painter->setPen(valuePen); @@ -49,8 +49,7 @@ qreal ArcGauge::percentage() const { } void ArcGauge::setPercentage(qreal percentage) { - if (qFuzzyCompare(m_percentage, percentage)) - return; + if (qFuzzyCompare(m_percentage, percentage)) return; m_percentage = percentage; emit percentageChanged(); update(); @@ -61,8 +60,7 @@ QColor ArcGauge::accentColor() const { } void ArcGauge::setAccentColor(const QColor& color) { - if (m_accentColor == color) - return; + if (m_accentColor == color) return; m_accentColor = color; emit accentColorChanged(); update(); @@ -73,8 +71,7 @@ QColor ArcGauge::trackColor() const { } void ArcGauge::setTrackColor(const QColor& color) { - if (m_trackColor == color) - return; + if (m_trackColor == color) return; m_trackColor = color; emit trackColorChanged(); update(); @@ -85,8 +82,7 @@ qreal ArcGauge::startAngle() const { } void ArcGauge::setStartAngle(qreal angle) { - if (qFuzzyCompare(m_startAngle, angle)) - return; + if (qFuzzyCompare(m_startAngle, angle)) return; m_startAngle = angle; emit startAngleChanged(); update(); @@ -97,8 +93,7 @@ qreal ArcGauge::sweepAngle() const { } void ArcGauge::setSweepAngle(qreal angle) { - if (qFuzzyCompare(m_sweepAngle, angle)) - return; + if (qFuzzyCompare(m_sweepAngle, angle)) return; m_sweepAngle = angle; emit sweepAngleChanged(); update(); @@ -109,8 +104,7 @@ qreal ArcGauge::lineWidth() const { } void ArcGauge::setLineWidth(qreal width) { - if (qFuzzyCompare(m_lineWidth, width)) - return; + if (qFuzzyCompare(m_lineWidth, width)) return; m_lineWidth = width; emit lineWidthChanged(); update(); diff --git a/Plugins/ZShell/Internal/arcgauge.hpp b/Plugins/ZShell/Internal/arcgauge.hpp index f10f47f..697f5c4 100644 --- a/Plugins/ZShell/Internal/arcgauge.hpp +++ b/Plugins/ZShell/Internal/arcgauge.hpp @@ -8,54 +8,66 @@ namespace ZShell::internal { class ArcGauge : public QQuickPaintedItem { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(qreal percentage READ percentage WRITE setPercentage NOTIFY percentageChanged) -Q_PROPERTY(QColor accentColor READ accentColor WRITE setAccentColor NOTIFY accentColorChanged) -Q_PROPERTY(QColor trackColor READ trackColor WRITE setTrackColor NOTIFY trackColorChanged) -Q_PROPERTY(qreal startAngle READ startAngle WRITE setStartAngle NOTIFY startAngleChanged) -Q_PROPERTY(qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY sweepAngleChanged) -Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged) + Q_PROPERTY( + qreal percentage READ percentage WRITE setPercentage NOTIFY + percentageChanged) + Q_PROPERTY( + QColor accentColor READ accentColor WRITE setAccentColor NOTIFY + accentColorChanged) + Q_PROPERTY( + QColor trackColor READ trackColor WRITE setTrackColor NOTIFY + trackColorChanged) + Q_PROPERTY( + qreal startAngle READ startAngle WRITE setStartAngle NOTIFY + startAngleChanged) + Q_PROPERTY( + qreal sweepAngle READ sweepAngle WRITE setSweepAngle NOTIFY + sweepAngleChanged) + Q_PROPERTY( + qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY + lineWidthChanged) -public: -explicit ArcGauge(QQuickItem* parent = nullptr); + public: + explicit ArcGauge(QQuickItem* parent = nullptr); -void paint(QPainter* painter) override; + void paint(QPainter* painter) override; -[[nodiscard]] qreal percentage() const; -void setPercentage(qreal percentage); + [[nodiscard]] qreal percentage() const; + void setPercentage(qreal percentage); -[[nodiscard]] QColor accentColor() const; -void setAccentColor(const QColor& color); + [[nodiscard]] QColor accentColor() const; + void setAccentColor(const QColor& color); -[[nodiscard]] QColor trackColor() const; -void setTrackColor(const QColor& color); + [[nodiscard]] QColor trackColor() const; + void setTrackColor(const QColor& color); -[[nodiscard]] qreal startAngle() const; -void setStartAngle(qreal angle); + [[nodiscard]] qreal startAngle() const; + void setStartAngle(qreal angle); -[[nodiscard]] qreal sweepAngle() const; -void setSweepAngle(qreal angle); + [[nodiscard]] qreal sweepAngle() const; + void setSweepAngle(qreal angle); -[[nodiscard]] qreal lineWidth() const; -void setLineWidth(qreal width); + [[nodiscard]] qreal lineWidth() const; + void setLineWidth(qreal width); -signals: -void percentageChanged(); -void accentColorChanged(); -void trackColorChanged(); -void startAngleChanged(); -void sweepAngleChanged(); -void lineWidthChanged(); + signals: + void percentageChanged(); + void accentColorChanged(); + void trackColorChanged(); + void startAngleChanged(); + void sweepAngleChanged(); + void lineWidthChanged(); -private: -qreal m_percentage = 0.0; -QColor m_accentColor; -QColor m_trackColor; -qreal m_startAngle = 0.75 * M_PI; -qreal m_sweepAngle = 1.5 * M_PI; -qreal m_lineWidth = 10.0; + private: + qreal m_percentage = 0.0; + QColor m_accentColor; + QColor m_trackColor; + qreal m_startAngle = 0.75 * M_PI; + qreal m_sweepAngle = 1.5 * M_PI; + qreal m_lineWidth = 10.0; }; -} // namespace ZShell::Internal +} // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/cachingimagemanager.cpp b/Plugins/ZShell/Internal/cachingimagemanager.cpp index 493b405..e53bf42 100644 --- a/Plugins/ZShell/Internal/cachingimagemanager.cpp +++ b/Plugins/ZShell/Internal/cachingimagemanager.cpp @@ -12,212 +12,232 @@ namespace ZShell::internal { qreal CachingImageManager::effectiveScale() const { - if (m_item && m_item->window()) { - return m_item->window()->devicePixelRatio(); - } + if (m_item && m_item->window()) { + return m_item->window()->devicePixelRatio(); + } - return 1.0; + return 1.0; } QSize CachingImageManager::effectiveSize() const { - if (!m_item) { - return QSize(); - } + if (!m_item) { + return QSize(); + } - const qreal scale = effectiveScale(); - const QSize size = QSizeF(m_item->width() * scale, m_item->height() * scale).toSize(); - m_item->setProperty("sourceSize", size); - return size; + const qreal scale = effectiveScale(); + const QSize size = + QSizeF(m_item->width() * scale, m_item->height() * scale).toSize(); + m_item->setProperty("sourceSize", size); + return size; } QQuickItem* CachingImageManager::item() const { - return m_item; + return m_item; } void CachingImageManager::setItem(QQuickItem* item) { - if (m_item == item) { - return; - } + if (m_item == item) { + return; + } - if (m_widthConn) { - disconnect(m_widthConn); - } - if (m_heightConn) { - disconnect(m_heightConn); - } + if (m_widthConn) { + disconnect(m_widthConn); + } + if (m_heightConn) { + disconnect(m_heightConn); + } - m_item = item; - emit itemChanged(); + m_item = item; + emit itemChanged(); - if (item) { - m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { - updateSource(); - }); - m_heightConn = connect(item, &QQuickItem::heightChanged, this, [this]() { - updateSource(); - }); - updateSource(); - } + if (item) { + m_widthConn = connect(item, &QQuickItem::widthChanged, this, [this]() { + updateSource(); + }); + m_heightConn = + connect(item, &QQuickItem::heightChanged, this, [this]() { + updateSource(); + }); + updateSource(); + } } QUrl CachingImageManager::cacheDir() const { - return m_cacheDir; + return m_cacheDir; } void CachingImageManager::setCacheDir(const QUrl& cacheDir) { - if (m_cacheDir == cacheDir) { - return; - } + if (m_cacheDir == cacheDir) { + return; + } - m_cacheDir = cacheDir; - if (!m_cacheDir.path().endsWith("/")) { - m_cacheDir.setPath(m_cacheDir.path() + "/"); - } - emit cacheDirChanged(); + m_cacheDir = cacheDir; + if (!m_cacheDir.path().endsWith("/")) { + m_cacheDir.setPath(m_cacheDir.path() + "/"); + } + emit cacheDirChanged(); } QString CachingImageManager::path() const { - return m_path; + return m_path; } void CachingImageManager::setPath(const QString& path) { - if (m_path == path) { - return; - } + if (m_path == path) { + return; + } - m_path = path; - emit pathChanged(); + m_path = path; + emit pathChanged(); - if (!path.isEmpty()) { - updateSource(path); - } + if (!path.isEmpty()) { + updateSource(path); + } } void CachingImageManager::updateSource() { - updateSource(m_path); + updateSource(m_path); } void CachingImageManager::updateSource(const QString& path) { - if (path.isEmpty() || path == m_shaPath) { - // Path is empty or already calculating sha for path - return; - } + if (path.isEmpty() || path == m_shaPath) { + // Path is empty or already calculating sha for path + return; + } - m_shaPath = path; + m_shaPath = path; - const auto future = QtConcurrent::run(&CachingImageManager::sha256sum, path); + const auto future = + QtConcurrent::run(&CachingImageManager::sha256sum, path); - const auto watcher = new QFutureWatcher(this); + const auto watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, this, [watcher, path, this]() { - if (m_path != path) { - // Object is destroyed or path has changed, ignore - watcher->deleteLater(); - return; - } + connect( + watcher, + &QFutureWatcher::finished, + this, + [watcher, path, this]() { + if (m_path != path) { + // Object is destroyed or path has changed, ignore + watcher->deleteLater(); + return; + } - const QSize size = effectiveSize(); + const QSize size = effectiveSize(); - if (!m_item || !size.width() || !size.height()) { - watcher->deleteLater(); - return; - } + if (!m_item || !size.width() || !size.height()) { + watcher->deleteLater(); + return; + } - const QString fillMode = m_item->property("fillMode").toString(); - // clang-format off + const QString fillMode = m_item->property("fillMode").toString(); + // clang-format off const QString filename = QString("%1@%2x%3-%4.png") .arg(watcher->result()).arg(size.width()).arg(size.height()) .arg(fillMode == "PreserveAspectCrop" ? "crop" : fillMode == "PreserveAspectFit" ? "fit" : "stretch"); - // clang-format on + // clang-format on - const QUrl cache = m_cacheDir.resolved(QUrl(filename)); - if (m_cachePath == cache) { - watcher->deleteLater(); - return; - } + const QUrl cache = m_cacheDir.resolved(QUrl(filename)); + if (m_cachePath == cache) { + watcher->deleteLater(); + return; + } - m_cachePath = cache; - emit cachePathChanged(); + m_cachePath = cache; + emit cachePathChanged(); - if (!cache.isLocalFile()) { - qWarning() << "CachingImageManager::updateSource: cachePath" << cache << "is not a local file"; - watcher->deleteLater(); - return; - } + if (!cache.isLocalFile()) { + qWarning() << "CachingImageManager::updateSource: cachePath" + << cache << "is not a local file"; + watcher->deleteLater(); + return; + } - const QImageReader reader(cache.toLocalFile()); - if (reader.canRead()) { - m_item->setProperty("source", cache); - } else { - m_item->setProperty("source", QUrl::fromLocalFile(path)); - createCache(path, cache.toLocalFile(), fillMode, size); - } + const QImageReader reader(cache.toLocalFile()); + if (reader.canRead()) { + m_item->setProperty("source", cache); + } else { + m_item->setProperty("source", QUrl::fromLocalFile(path)); + createCache(path, cache.toLocalFile(), fillMode, size); + } - // Clear current running sha if same - if (m_shaPath == path) { - m_shaPath = QString(); - } + // Clear current running sha if same + if (m_shaPath == path) { + m_shaPath = QString(); + } - watcher->deleteLater(); - }); + watcher->deleteLater(); + }); - watcher->setFuture(future); + watcher->setFuture(future); } QUrl CachingImageManager::cachePath() const { - return m_cachePath; + return m_cachePath; } void CachingImageManager::createCache( - const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const { - QThreadPool::globalInstance()->start([path, cache, fillMode, size] { - QImage image(path); + const QString& path, + const QString& cache, + const QString& fillMode, + const QSize& size) const { + QThreadPool::globalInstance()->start([path, cache, fillMode, size] { + QImage image(path); - if (image.isNull()) { - qWarning() << "CachingImageManager::createCache: failed to read" << path; - return; - } + if (image.isNull()) { + qWarning() << "CachingImageManager::createCache: failed to read" + << path; + return; + } - image.convertTo(QImage::Format_ARGB32); + image.convertTo(QImage::Format_ARGB32); - if (fillMode == "PreserveAspectCrop") { - image = image.scaled(size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - } else if (fillMode == "PreserveAspectFit") { - image = image.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - } else { - image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - } + if (fillMode == "PreserveAspectCrop") { + image = image.scaled( + size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + } else if (fillMode == "PreserveAspectFit") { + image = image.scaled( + size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } else { + image = image.scaled( + size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } - if (fillMode == "PreserveAspectCrop" || fillMode == "PreserveAspectFit") { - QImage canvas(size, QImage::Format_ARGB32); - canvas.fill(Qt::transparent); + if (fillMode == "PreserveAspectCrop" || + fillMode == "PreserveAspectFit") { + QImage canvas(size, QImage::Format_ARGB32); + canvas.fill(Qt::transparent); - QPainter painter(&canvas); - painter.drawImage((size.width() - image.width()) / 2, (size.height() - image.height()) / 2, image); - painter.end(); + QPainter painter(&canvas); + painter.drawImage( + (size.width() - image.width()) / 2, + (size.height() - image.height()) / 2, + image); + painter.end(); - image = canvas; - } + image = canvas; + } - const QString parent = QFileInfo(cache).absolutePath(); - if (!QDir().mkpath(parent) || !image.save(cache)) { - qWarning() << "CachingImageManager::createCache: failed to save to" << cache; - } - }); + const QString parent = QFileInfo(cache).absolutePath(); + if (!QDir().mkpath(parent) || !image.save(cache)) { + qWarning() << "CachingImageManager::createCache: failed to save to" + << cache; + } + }); } QString CachingImageManager::sha256sum(const QString& path) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "CachingImageManager::sha256sum: failed to open" << path; - return ""; - } + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "CachingImageManager::sha256sum: failed to open" << path; + return ""; + } - QCryptographicHash hash(QCryptographicHash::Sha256); - hash.addData(&file); - file.close(); + QCryptographicHash hash(QCryptographicHash::Sha256); + hash.addData(&file); + file.close(); - return hash.result().toHex(); + return hash.result().toHex(); } } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/cachingimagemanager.hpp b/Plugins/ZShell/Internal/cachingimagemanager.hpp index a4f0b93..587d9a6 100644 --- a/Plugins/ZShell/Internal/cachingimagemanager.hpp +++ b/Plugins/ZShell/Internal/cachingimagemanager.hpp @@ -7,60 +7,65 @@ namespace ZShell::internal { class CachingImageManager : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) -Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) + Q_PROPERTY( + QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) + Q_PROPERTY( + QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged + REQUIRED) -Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) -Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) -public: -explicit CachingImageManager(QObject* parent = nullptr) - : QObject(parent) - , m_item(nullptr) { -} + public: + explicit CachingImageManager(QObject* parent = nullptr) + : QObject(parent), m_item(nullptr) {} -[[nodiscard]] QQuickItem* item() const; -void setItem(QQuickItem* item); + [[nodiscard]] QQuickItem* item() const; + void setItem(QQuickItem* item); -[[nodiscard]] QUrl cacheDir() const; -void setCacheDir(const QUrl& cacheDir); + [[nodiscard]] QUrl cacheDir() const; + void setCacheDir(const QUrl& cacheDir); -[[nodiscard]] QString path() const; -void setPath(const QString& path); + [[nodiscard]] QString path() const; + void setPath(const QString& path); -[[nodiscard]] QUrl cachePath() const; + [[nodiscard]] QUrl cachePath() const; -Q_INVOKABLE void updateSource(); -Q_INVOKABLE void updateSource(const QString& path); + Q_INVOKABLE void updateSource(); + Q_INVOKABLE void updateSource(const QString& path); -signals: -void itemChanged(); -void cacheDirChanged(); + signals: + void itemChanged(); + void cacheDirChanged(); -void pathChanged(); -void cachePathChanged(); -void usingCacheChanged(); + void pathChanged(); + void cachePathChanged(); + void usingCacheChanged(); -private: -QString m_shaPath; + private: + QString m_shaPath; -QQuickItem* m_item; -QUrl m_cacheDir; + QQuickItem* m_item; + QUrl m_cacheDir; -QString m_path; -QUrl m_cachePath; + QString m_path; + QUrl m_cachePath; -QMetaObject::Connection m_widthConn; -QMetaObject::Connection m_heightConn; + QMetaObject::Connection m_widthConn; + QMetaObject::Connection m_heightConn; -[[nodiscard]] qreal effectiveScale() const; -[[nodiscard]] QSize effectiveSize() const; + [[nodiscard]] qreal effectiveScale() const; + [[nodiscard]] QSize effectiveSize() const; -void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; -[[nodiscard]] static QString sha256sum(const QString& path); + void createCache( + const QString& path, + const QString& cache, + const QString& fillMode, + const QSize& size) const; + [[nodiscard]] static QString sha256sum(const QString& path); }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/circularbuffer.cpp b/Plugins/ZShell/Internal/circularbuffer.cpp index 0703f2c..b1c12bc 100644 --- a/Plugins/ZShell/Internal/circularbuffer.cpp +++ b/Plugins/ZShell/Internal/circularbuffer.cpp @@ -4,19 +4,15 @@ namespace ZShell::internal { -CircularBuffer::CircularBuffer(QObject* parent) - : QObject(parent) { -} +CircularBuffer::CircularBuffer(QObject* parent) : QObject(parent) {} int CircularBuffer::capacity() const { return m_capacity; } void CircularBuffer::setCapacity(int capacity) { - if (capacity < 0) - capacity = 0; - if (m_capacity == capacity) - return; + if (capacity < 0) capacity = 0; + if (m_capacity == capacity) return; const auto old = values(); @@ -52,8 +48,7 @@ QList CircularBuffer::values() const { } qreal CircularBuffer::maximum() const { - if (m_count == 0) - return 0.0; + if (m_count == 0) return 0.0; qreal maxVal = at(0); for (int i = 1; i < m_count; ++i) @@ -62,8 +57,7 @@ qreal CircularBuffer::maximum() const { } void CircularBuffer::push(qreal value) { - if (m_capacity <= 0) - return; + if (m_capacity <= 0) return; m_data[m_head] = value; m_head = (m_head + 1) % m_capacity; @@ -75,8 +69,7 @@ void CircularBuffer::push(qreal value) { } void CircularBuffer::clear() { - if (m_count == 0) - return; + if (m_count == 0) return; m_head = 0; m_count = 0; @@ -85,10 +78,10 @@ void CircularBuffer::clear() { } qreal CircularBuffer::at(int index) const { - if (index < 0 || index >= m_count) - return 0.0; + if (index < 0 || index >= m_count) return 0.0; - const int actualIndex = (m_head - m_count + index + m_capacity) % m_capacity; + const int actualIndex = + (m_head - m_count + index + m_capacity) % m_capacity; return m_data[actualIndex]; } diff --git a/Plugins/ZShell/Internal/circularbuffer.hpp b/Plugins/ZShell/Internal/circularbuffer.hpp index 763500e..a2496f5 100644 --- a/Plugins/ZShell/Internal/circularbuffer.hpp +++ b/Plugins/ZShell/Internal/circularbuffer.hpp @@ -7,38 +7,39 @@ namespace ZShell::internal { class CircularBuffer : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged) -Q_PROPERTY(int count READ count NOTIFY countChanged) -Q_PROPERTY(QList values READ values NOTIFY valuesChanged) -Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged) + Q_PROPERTY( + int capacity READ capacity WRITE setCapacity NOTIFY capacityChanged) + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QList values READ values NOTIFY valuesChanged) + Q_PROPERTY(qreal maximum READ maximum NOTIFY valuesChanged) -public: -explicit CircularBuffer(QObject* parent = nullptr); + public: + explicit CircularBuffer(QObject* parent = nullptr); -[[nodiscard]] int capacity() const; -void setCapacity(int capacity); + [[nodiscard]] int capacity() const; + void setCapacity(int capacity); -[[nodiscard]] int count() const; -[[nodiscard]] QList values() const; -[[nodiscard]] qreal maximum() const; + [[nodiscard]] int count() const; + [[nodiscard]] QList values() const; + [[nodiscard]] qreal maximum() const; -Q_INVOKABLE void push(qreal value); -Q_INVOKABLE void clear(); -Q_INVOKABLE [[nodiscard]] qreal at(int index) const; + Q_INVOKABLE void push(qreal value); + Q_INVOKABLE void clear(); + Q_INVOKABLE [[nodiscard]] qreal at(int index) const; -signals: -void capacityChanged(); -void countChanged(); -void valuesChanged(); + signals: + void capacityChanged(); + void countChanged(); + void valuesChanged(); -private: -QVector m_data; -int m_head = 0; -int m_count = 0; -int m_capacity = 0; + private: + QVector m_data; + int m_head = 0; + int m_count = 0; + int m_capacity = 0; }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/circularindicatormanager.cpp b/Plugins/ZShell/Internal/circularindicatormanager.cpp index da09c7c..15ce0f2 100644 --- a/Plugins/ZShell/Internal/circularindicatormanager.cpp +++ b/Plugins/ZShell/Internal/circularindicatormanager.cpp @@ -15,8 +15,10 @@ constexpr qint32 TAIL_DEGREES_OFFSET = -20; constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250; constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520; -constexpr std::array DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 }; -constexpr std::array DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 }; +constexpr std::array DELAY_TO_EXPAND_IN_MS = + {0, 1350, 2700, 4050}; +constexpr std::array DELAY_TO_COLLAPSE_IN_MS = { + 667, 2017, 3367, 4717}; } // namespace advance @@ -26,7 +28,7 @@ constexpr qint32 TOTAL_DURATION_IN_MS = 6000; constexpr qint32 DURATION_SPIN_IN_MS = 500; constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000; constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000; -constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 }; +constexpr std::array DELAY_SPINS_IN_MS = {0, 1500, 3000, 4500}; constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0; constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000; constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500; @@ -38,7 +40,7 @@ constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080; // Despite of the constant rotation, there are also 5 extra rotations the entire animation. The // total degrees that each extra rotation goes by. constexpr qint32 SPIN_ROTATION_DEGREES = 90; -constexpr std::array END_FRACTION_RANGE = { 0.10, 0.87 }; +constexpr std::array END_FRACTION_RANGE = {0.10, 0.87}; } // namespace retreat @@ -61,7 +63,7 @@ CircularIndicatorManager::CircularIndicatorManager(QObject* parent) , m_rotation(0) , m_completeEndProgress(0) { // Fast out slow in - m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 }); + m_curve.addCubicBezierSegment({0.4, 0.0}, {0.2, 1.0}, {1.0, 1.0}); } qreal CircularIndicatorManager::startFraction() const { @@ -100,11 +102,13 @@ qreal CircularIndicatorManager::completeEndDuration() const { } } -CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const { +CircularIndicatorManager::IndeterminateAnimationType +CircularIndicatorManager::indeterminateAnimationType() const { return m_type; } -void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) { +void CircularIndicatorManager::setIndeterminateAnimationType( + IndeterminateAnimationType t) { if (m_type != t) { m_type = t; emit indeterminateAnimationTypeChanged(); @@ -150,24 +154,27 @@ void CircularIndicatorManager::updateRetreat(qreal progress) { // Extra rotation for the faster spinning. qreal spinRotation = 0; for (const int spinDelay : DELAY_SPINS_IN_MS) { - spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) * - SPIN_ROTATION_DEGREES; + spinRotation += + m_curve.valueForProgress( + getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) * + SPIN_ROTATION_DEGREES; } m_rotation = constantRotation + spinRotation; emit rotationChanged(); // Grow active indicator. - qreal fraction = - m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS)); - fraction -= - m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS)); + qreal fraction = m_curve.valueForProgress(getFractionInRange( + playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS)); + fraction -= m_curve.valueForProgress(getFractionInRange( + playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS)); if (!qFuzzyIsNull(m_startFraction)) { m_startFraction = 0.0; emit startFractionChanged(); } const auto oldEndFrac = m_endFraction; - m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction); + m_endFraction = + std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction); // Completing animation. if (m_completeEndProgress > 0) { @@ -184,22 +191,32 @@ void CircularIndicatorManager::updateAdvance(qreal progress) { const auto playtime = progress * TOTAL_DURATION_IN_MS; // Adds constant rotation to segment positions. - m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET; + m_startFraction = + CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET; m_endFraction = CONSTANT_ROTATION_DEGREES * progress; // Adds cycle specific rotation to segment positions. for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) { // While expanding. - qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS); - m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; + qreal fraction = getFractionInRange( + playtime, + DELAY_TO_EXPAND_IN_MS[cycleIndex], + DURATION_TO_EXPAND_IN_MS); + m_endFraction += + m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; // While collapsing. - fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS); - m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; + fraction = getFractionInRange( + playtime, + DELAY_TO_COLLAPSE_IN_MS[cycleIndex], + DURATION_TO_COLLAPSE_IN_MS); + m_startFraction += + m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE; } // Closes the gap between head and tail for complete end. - m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress; + m_startFraction += + (m_endFraction - m_startFraction) * m_completeEndProgress; m_startFraction /= 360.0; m_endFraction /= 360.0; diff --git a/Plugins/ZShell/Internal/circularindicatormanager.hpp b/Plugins/ZShell/Internal/circularindicatormanager.hpp index 0411ca1..d8c78c5 100644 --- a/Plugins/ZShell/Internal/circularindicatormanager.hpp +++ b/Plugins/ZShell/Internal/circularindicatormanager.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -8,66 +7,71 @@ namespace ZShell::internal { class CircularIndicatorManager : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged) -Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged) -Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged) -Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged) -Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY - completeEndProgressChanged) -Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged) -Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged) -Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE - setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged) + Q_PROPERTY( + qreal startFraction READ startFraction NOTIFY startFractionChanged) + Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged) + Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged) + Q_PROPERTY( + qreal progress READ progress WRITE setProgress NOTIFY progressChanged) + Q_PROPERTY( + qreal completeEndProgress READ completeEndProgress WRITE + setCompleteEndProgress NOTIFY completeEndProgressChanged) + Q_PROPERTY( + qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged) + Q_PROPERTY( + qreal completeEndDuration READ completeEndDuration NOTIFY + indeterminateAnimationTypeChanged) + Q_PROPERTY( + IndeterminateAnimationType indeterminateAnimationType READ + indeterminateAnimationType WRITE setIndeterminateAnimationType + NOTIFY indeterminateAnimationTypeChanged) -public: -explicit CircularIndicatorManager(QObject* parent = nullptr); + public: + explicit CircularIndicatorManager(QObject* parent = nullptr); -enum IndeterminateAnimationType { - Advance = 0, - Retreat -}; -Q_ENUM(IndeterminateAnimationType) + enum IndeterminateAnimationType { Advance = 0, Retreat }; + Q_ENUM(IndeterminateAnimationType) -[[nodiscard]] qreal startFraction() const; -[[nodiscard]] qreal endFraction() const; -[[nodiscard]] qreal rotation() const; + [[nodiscard]] qreal startFraction() const; + [[nodiscard]] qreal endFraction() const; + [[nodiscard]] qreal rotation() const; -[[nodiscard]] qreal progress() const; -void setProgress(qreal progress); + [[nodiscard]] qreal progress() const; + void setProgress(qreal progress); -[[nodiscard]] qreal completeEndProgress() const; -void setCompleteEndProgress(qreal progress); + [[nodiscard]] qreal completeEndProgress() const; + void setCompleteEndProgress(qreal progress); -[[nodiscard]] qreal duration() const; -[[nodiscard]] qreal completeEndDuration() const; + [[nodiscard]] qreal duration() const; + [[nodiscard]] qreal completeEndDuration() const; -[[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const; -void setIndeterminateAnimationType(IndeterminateAnimationType t); + [[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const; + void setIndeterminateAnimationType(IndeterminateAnimationType t); -signals: -void startFractionChanged(); -void endFractionChanged(); -void rotationChanged(); -void progressChanged(); -void completeEndProgressChanged(); -void indeterminateAnimationTypeChanged(); + signals: + void startFractionChanged(); + void endFractionChanged(); + void rotationChanged(); + void progressChanged(); + void completeEndProgressChanged(); + void indeterminateAnimationTypeChanged(); -private: -IndeterminateAnimationType m_type; -QEasingCurve m_curve; + private: + IndeterminateAnimationType m_type; + QEasingCurve m_curve; -qreal m_progress; -qreal m_startFraction; -qreal m_endFraction; -qreal m_rotation; -qreal m_completeEndProgress; + qreal m_progress; + qreal m_startFraction; + qreal m_endFraction; + qreal m_rotation; + qreal m_completeEndProgress; -void update(qreal progress); -void updateAdvance(qreal progress); -void updateRetreat(qreal progress); + void update(qreal progress); + void updateAdvance(qreal progress); + void updateRetreat(qreal progress); }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/hyprdevices.cpp b/Plugins/ZShell/Internal/hyprdevices.cpp index 7b877dc..487ce8e 100644 --- a/Plugins/ZShell/Internal/hyprdevices.cpp +++ b/Plugins/ZShell/Internal/hyprdevices.cpp @@ -5,130 +5,131 @@ namespace ZShell::internal::hypr { HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent) - : QObject(parent) - , m_lastIpcObject(ipcObject) {} + : QObject(parent), m_lastIpcObject(ipcObject) {} QVariantHash HyprKeyboard::lastIpcObject() const { - return m_lastIpcObject.toVariantHash(); + return m_lastIpcObject.toVariantHash(); } QString HyprKeyboard::address() const { - return m_lastIpcObject.value("address").toString(); + return m_lastIpcObject.value("address").toString(); } QString HyprKeyboard::name() const { - return m_lastIpcObject.value("name").toString(); + return m_lastIpcObject.value("name").toString(); } QString HyprKeyboard::layout() const { - return m_lastIpcObject.value("layout").toString(); + return m_lastIpcObject.value("layout").toString(); } QString HyprKeyboard::activeKeymap() const { - return m_lastIpcObject.value("active_keymap").toString(); + return m_lastIpcObject.value("active_keymap").toString(); } bool HyprKeyboard::capsLock() const { - return m_lastIpcObject.value("capsLock").toBool(); + return m_lastIpcObject.value("capsLock").toBool(); } bool HyprKeyboard::numLock() const { - return m_lastIpcObject.value("numLock").toBool(); + return m_lastIpcObject.value("numLock").toBool(); } bool HyprKeyboard::main() const { - return m_lastIpcObject.value("main").toBool(); + return m_lastIpcObject.value("main").toBool(); } bool HyprKeyboard::updateLastIpcObject(QJsonObject object) { - if (m_lastIpcObject == object) { - return false; - } + if (m_lastIpcObject == object) { + return false; + } - const auto last = m_lastIpcObject; + const auto last = m_lastIpcObject; - m_lastIpcObject = object; - emit lastIpcObjectChanged(); + m_lastIpcObject = object; + emit lastIpcObjectChanged(); - bool dirty = false; - if (last.value("address") != object.value("address")) { - dirty = true; - emit addressChanged(); - } - if (last.value("name") != object.value("name")) { - dirty = true; - emit nameChanged(); - } - if (last.value("layout") != object.value("layout")) { - dirty = true; - emit layoutChanged(); - } - if (last.value("active_keymap") != object.value("active_keymap")) { - dirty = true; - emit activeKeymapChanged(); - } - if (last.value("capsLock") != object.value("capsLock")) { - dirty = true; - emit capsLockChanged(); - } - if (last.value("numLock") != object.value("numLock")) { - dirty = true; - emit numLockChanged(); - } - if (last.value("main") != object.value("main")) { - dirty = true; - emit mainChanged(); - } - return dirty; + bool dirty = false; + if (last.value("address") != object.value("address")) { + dirty = true; + emit addressChanged(); + } + if (last.value("name") != object.value("name")) { + dirty = true; + emit nameChanged(); + } + if (last.value("layout") != object.value("layout")) { + dirty = true; + emit layoutChanged(); + } + if (last.value("active_keymap") != object.value("active_keymap")) { + dirty = true; + emit activeKeymapChanged(); + } + if (last.value("capsLock") != object.value("capsLock")) { + dirty = true; + emit capsLockChanged(); + } + if (last.value("numLock") != object.value("numLock")) { + dirty = true; + emit numLockChanged(); + } + if (last.value("main") != object.value("main")) { + dirty = true; + emit mainChanged(); + } + return dirty; } -HyprDevices::HyprDevices(QObject* parent) - : QObject(parent) {} +HyprDevices::HyprDevices(QObject* parent) : QObject(parent) {} QQmlListProperty HyprDevices::keyboards() { - return QQmlListProperty(this, &m_keyboards); + return QQmlListProperty(this, &m_keyboards); } bool HyprDevices::updateLastIpcObject(QJsonObject object) { - const auto val = object.value("keyboards").toArray(); - bool dirty = false; + const auto val = object.value("keyboards").toArray(); + bool dirty = false; - for (auto it = m_keyboards.begin(); it != m_keyboards.end();) { - auto* const keyboard = *it; - const auto inNewValues = std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) { - return o.toObject().value("address").toString() == keyboard->address(); - }); + for (auto it = m_keyboards.begin(); it != m_keyboards.end();) { + auto* const keyboard = *it; + const auto inNewValues = + std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) { + return o.toObject().value("address").toString() == + keyboard->address(); + }); - if (!inNewValues) { - dirty = true; - it = m_keyboards.erase(it); - keyboard->deleteLater(); - } else { - ++it; - } - } + if (!inNewValues) { + dirty = true; + it = m_keyboards.erase(it); + keyboard->deleteLater(); + } else { + ++it; + } + } - for (const auto& o : val) { - const auto obj = o.toObject(); - const auto addr = obj.value("address").toString(); + for (const auto& o : val) { + const auto obj = o.toObject(); + const auto addr = obj.value("address").toString(); - auto it = std::find_if(m_keyboards.begin(), m_keyboards.end(), [addr](const HyprKeyboard* kb) { - return kb->address() == addr; - }); + auto it = std::find_if( + m_keyboards.begin(), + m_keyboards.end(), + [addr](const HyprKeyboard* kb) { return kb->address() == addr; }); - if (it != m_keyboards.end()) { - dirty |= (*it)->updateLastIpcObject(obj); - } else { - dirty = true; - m_keyboards << new HyprKeyboard(obj, this); - } - } + if (it != m_keyboards.end()) { + dirty |= (*it)->updateLastIpcObject(obj); + } else { + dirty = true; + m_keyboards << new HyprKeyboard(obj, this); + } + } - if (dirty) { - emit keyboardsChanged(); - } + if (dirty) { + emit keyboardsChanged(); + } - return dirty; + return dirty; } } // namespace ZShell::internal::hypr diff --git a/Plugins/ZShell/Internal/hyprdevices.hpp b/Plugins/ZShell/Internal/hyprdevices.hpp index 285823d..b554240 100644 --- a/Plugins/ZShell/Internal/hyprdevices.hpp +++ b/Plugins/ZShell/Internal/hyprdevices.hpp @@ -8,67 +8,72 @@ namespace ZShell::internal::hypr { class HyprKeyboard : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE( + "HyprKeyboard instances can only be retrieved from a HyprDevices") -Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged) -Q_PROPERTY(QString address READ address NOTIFY addressChanged) -Q_PROPERTY(QString name READ name NOTIFY nameChanged) -Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged) -Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged) -Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged) -Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged) -Q_PROPERTY(bool main READ main NOTIFY mainChanged) + Q_PROPERTY( + QVariantHash lastIpcObject READ lastIpcObject NOTIFY + lastIpcObjectChanged) + Q_PROPERTY(QString address READ address NOTIFY addressChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged) + Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged) + Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged) + Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged) + Q_PROPERTY(bool main READ main NOTIFY mainChanged) -public: -explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr); + public: + explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr); -[[nodiscard]] QVariantHash lastIpcObject() const; -[[nodiscard]] QString address() const; -[[nodiscard]] QString name() const; -[[nodiscard]] QString layout() const; -[[nodiscard]] QString activeKeymap() const; -[[nodiscard]] bool capsLock() const; -[[nodiscard]] bool numLock() const; -[[nodiscard]] bool main() const; + [[nodiscard]] QVariantHash lastIpcObject() const; + [[nodiscard]] QString address() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString layout() const; + [[nodiscard]] QString activeKeymap() const; + [[nodiscard]] bool capsLock() const; + [[nodiscard]] bool numLock() const; + [[nodiscard]] bool main() const; -bool updateLastIpcObject(QJsonObject object); + bool updateLastIpcObject(QJsonObject object); -signals: -void lastIpcObjectChanged(); -void addressChanged(); -void nameChanged(); -void layoutChanged(); -void activeKeymapChanged(); -void capsLockChanged(); -void numLockChanged(); -void mainChanged(); + signals: + void lastIpcObjectChanged(); + void addressChanged(); + void nameChanged(); + void layoutChanged(); + void activeKeymapChanged(); + void capsLockChanged(); + void numLockChanged(); + void mainChanged(); -private: -QJsonObject m_lastIpcObject; + private: + QJsonObject m_lastIpcObject; }; class HyprDevices : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE( + "HyprDevices instances can only be retrieved from a HyprExtras") -Q_PROPERTY( - QQmlListProperty keyboards READ keyboards NOTIFY keyboardsChanged) + Q_PROPERTY( + QQmlListProperty keyboards READ + keyboards NOTIFY keyboardsChanged) -public: -explicit HyprDevices(QObject* parent = nullptr); + public: + explicit HyprDevices(QObject* parent = nullptr); -[[nodiscard]] QQmlListProperty keyboards(); + [[nodiscard]] QQmlListProperty keyboards(); -bool updateLastIpcObject(QJsonObject object); + bool updateLastIpcObject(QJsonObject object); -signals: -void keyboardsChanged(); + signals: + void keyboardsChanged(); -private: -QList m_keyboards; + private: + QList m_keyboards; }; -} // namespace ZShell::internal::hypr +} diff --git a/Plugins/ZShell/Internal/hyprextras.cpp b/Plugins/ZShell/Internal/hyprextras.cpp index 60805d4..7adea83 100644 --- a/Plugins/ZShell/Internal/hyprextras.cpp +++ b/Plugins/ZShell/Internal/hyprextras.cpp @@ -64,7 +64,8 @@ static QString luaArray(const QVariantList& list) { parts << luaValue(item); } - return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); + return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + + QLatin1String(" }"); } static QString luaArray(const QStringList& list) { @@ -75,7 +76,8 @@ static QString luaArray(const QStringList& list) { parts << luaEscapeString(item); } - return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); + return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + + QLatin1String(" }"); } static QString luaMapFromHash(const QVariantHash& hash) { @@ -83,10 +85,12 @@ static QString luaMapFromHash(const QVariantHash& hash) { parts.reserve(hash.size()); for (auto it = hash.cbegin(); it != hash.cend(); ++it) { - parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value()); + parts << luaEscapeString(it.key()) + QLatin1String(" = ") + + luaValue(it.value()); } - return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); + return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + + QLatin1String(" }"); } static QString luaMap(const QVariantMap& map) { @@ -94,10 +98,12 @@ static QString luaMap(const QVariantMap& map) { parts.reserve(map.size()); for (auto it = map.cbegin(); it != map.cend(); ++it) { - parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value()); + parts << luaEscapeString(it.key()) + QLatin1String(" = ") + + luaValue(it.value()); } - return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }"); + return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + + QLatin1String(" }"); } static QString luaValue(const QVariant& v) { @@ -143,7 +149,8 @@ static QString normalizeOptionPath(QString key) { } static QString buildHlConfigCall(const QString& key, const QVariant& value) { - const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts); + const auto parts = + normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts); if (parts.isEmpty()) { return {}; } @@ -189,7 +196,8 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) { const auto option = obj.value(QStringLiteral("option")).toString(); - if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) { + if (option.contains(QStringLiteral("color")) || + option.contains(QStringLiteral("col."))) { return colorFromInt(static_cast(value)); } @@ -234,7 +242,8 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) { return {}; } -static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) { +static void insertNestedValue( + QVariantMap& root, const QStringList& path, const QVariant& value) { if (path.isEmpty()) { return; } @@ -261,16 +270,19 @@ HyprExtras::HyprExtras(QObject* parent) , m_devices(new HyprDevices(this)) { const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE"); if (his.isEmpty()) { - qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket."; + qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to " + "connect to Hyprland socket."; return; } - auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); + auto hyprDir = + QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); if (!QDir(hyprDir).exists()) { hyprDir = QStringLiteral("/tmp/hypr/") + his; if (!QDir(hyprDir).exists()) { - qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket."; + qCWarning(lcHypr) << "Hyprland socket directory does not exist. " + "Unable to connect to Hyprland socket."; return; } } @@ -283,9 +295,15 @@ HyprExtras::HyprExtras(QObject* parent) m_socket = new QLocalSocket(this); - QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError); - QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged); - QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent); + QObject::connect( + m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError); + QObject::connect( + m_socket, + &QLocalSocket::stateChanged, + this, + &HyprExtras::socketStateChanged); + QObject::connect( + m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); } @@ -304,10 +322,11 @@ void HyprExtras::message(const QString& message) { } makeRequest(message, [](bool success, const QByteArray& res) { - if (!success) { - qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res); - } - }); + if (!success) { + qCWarning(lcHypr) + << "message: request error:" << QString::fromUtf8(res); + } + }); } void HyprExtras::batchMessage(const QStringList& messages) { @@ -315,10 +334,12 @@ void HyprExtras::batchMessage(const QStringList& messages) { return; } - makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')), - [](bool success, const QByteArray& res) { + makeRequest( + QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')), + [](bool success, const QByteArray& res) { if (!success) { - qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res); + qCWarning(lcHypr) + << "batchMessage: request error:" << QString::fromUtf8(res); } }); } @@ -342,11 +363,14 @@ void HyprExtras::applyOptions(const QVariantHash& options) { return; } - makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) { + makeRequest( + QStringLiteral("eval ") + calls.join(QLatin1String("; ")), + [this](bool success, const QByteArray& res) { if (success) { refreshOptions(); } else { - qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res); + qCWarning(lcHypr) + << "applyOptions: request error" << QString::fromUtf8(res); } }); } @@ -372,26 +396,26 @@ void HyprExtras::refreshOptions() { auto nextOptions = std::make_shared(); - auto step = std::make_shared >(); + auto step = std::make_shared>(); *step = [this, generation, nextOptions, step](int index) { - if (generation != m_optionsRefreshGeneration) { - return; + if (generation != m_optionsRefreshGeneration) { + return; + } + + if (index >= optionKeys.size()) { + if (m_options != *nextOptions) { + m_options = *nextOptions; + emit optionsChanged(); } + return; + } - if (index >= optionKeys.size()) { - if (m_options != *nextOptions) { - m_options = *nextOptions; - emit optionsChanged(); - } - return; - } + const QString key = optionKeys.at(index); - const QString key = optionKeys.at(index); - - m_optionsRefresh = makeRequestJson( - QStringLiteral("getoption ") + key, - [this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response) - { + m_optionsRefresh = makeRequestJson( + QStringLiteral("getoption ") + key, + [this, generation, nextOptions, step, index, key]( + bool success, const QJsonDocument& response) { m_optionsRefresh.reset(); if (generation != m_optionsRefreshGeneration) { @@ -399,19 +423,26 @@ void HyprExtras::refreshOptions() { } if (success && response.isObject()) { - const QVariant value = parseGetOptionValue(response.object()); + const QVariant value = + parseGetOptionValue(response.object()); if (value.isValid()) { - insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value); + insertNestedValue( + *nextOptions, + key.split(QLatin1Char(':'), Qt::SkipEmptyParts), + value); } else { - qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key; + qCWarning(lcHypr) << "refreshOptions: getoption " + "returned no usable value for" + << key; } } else if (!success) { - qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key; + qCWarning(lcHypr) + << "refreshOptions: getoption request error for" << key; } (*step)(index + 1); }); - }; + }; (*step)(0); } @@ -421,7 +452,9 @@ void HyprExtras::refreshDevices() { m_devicesRefresh->close(); } - m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) { + m_devicesRefresh = makeRequestJson( + QStringLiteral("devices"), + [this](bool success, const QJsonDocument& response) { m_devicesRefresh.reset(); if (success) { m_devices->updateLastIpcObject(response.object()); @@ -431,15 +464,19 @@ void HyprExtras::refreshDevices() { void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const { if (!m_socketValid) { - qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error; + qCWarning(lcHypr) + << "socketError: unable to connect to Hyprland event socket:" + << error; } else { - qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error; + qCWarning(lcHypr) << "socketError: Hyprland event socket error:" + << error; } } void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) { if (state == QLocalSocket::UnconnectedState && m_socketValid) { - qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected."; + qCWarning(lcHypr) + << "socketStateChanged: Hyprland event socket disconnected."; } m_socketValid = state == QLocalSocket::ConnectedState; @@ -452,7 +489,8 @@ void HyprExtras::readEvent() { break; } rawEvent.truncate(rawEvent.length() - 1); - const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); + const auto event = + QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); handleEvent(QString::fromUtf8(event)); } } @@ -466,14 +504,18 @@ void HyprExtras::handleEvent(const QString& event) { } HyprExtras::SocketPtr HyprExtras::makeRequestJson( - const QString& request, const std::function& callback) { - return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) { + const QString& request, + const std::function& callback) { + return makeRequest( + QStringLiteral("j/") + request, + [callback](bool success, const QByteArray& response) { callback(success, QJsonDocument::fromJson(response)); }); } HyprExtras::SocketPtr HyprExtras::makeRequest( - const QString& request, const std::function& callback) { + const QString& request, + const std::function& callback) { if (m_requestSocket.isEmpty()) { return SocketPtr(); } @@ -481,18 +523,24 @@ HyprExtras::SocketPtr HyprExtras::makeRequest( auto socket = SocketPtr::create(this); QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() { - QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() { + QObject::connect( + socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() { const auto response = socket->readAll(); callback(true, std::move(response)); socket->close(); }); - socket->write(request.toUtf8()); - socket->flush(); - }); + socket->write(request.toUtf8()); + socket->flush(); + }); - QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) { - qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request; + QObject::connect( + socket.data(), + &QLocalSocket::errorOccurred, + this, + [=](QLocalSocket::LocalSocketError err) { + qCWarning(lcHypr) << "makeRequest: error making request:" << err + << "| request:" << request; callback(false, {}); socket->close(); }); diff --git a/Plugins/ZShell/Internal/hyprextras.hpp b/Plugins/ZShell/Internal/hyprextras.hpp index afcac8d..2fc9aac 100644 --- a/Plugins/ZShell/Internal/hyprextras.hpp +++ b/Plugins/ZShell/Internal/hyprextras.hpp @@ -15,51 +15,56 @@ namespace ZShell::internal::hypr { class HyprDevices; class HyprExtras : public QObject { -Q_OBJECT -QML_ELEMENT - Q_MOC_INCLUDE("hyprdevices.hpp") + Q_OBJECT + QML_ELEMENT + Q_MOC_INCLUDE("hyprdevices.hpp") -Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged) -Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) + Q_PROPERTY(QVariantMap options READ options NOTIFY optionsChanged) + Q_PROPERTY( + ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) -public: -explicit HyprExtras(QObject* parent = nullptr); + public: + explicit HyprExtras(QObject* parent = nullptr); -[[nodiscard]] QVariantMap options() const; -[[nodiscard]] HyprDevices* devices() const; + [[nodiscard]] QVariantMap options() const; + [[nodiscard]] HyprDevices* devices() const; -Q_INVOKABLE void message(const QString& message); -Q_INVOKABLE void batchMessage(const QStringList& messages); -Q_INVOKABLE void applyOptions(const QVariantHash& options); + Q_INVOKABLE void message(const QString& message); + Q_INVOKABLE void batchMessage(const QStringList& messages); + Q_INVOKABLE void applyOptions(const QVariantHash& options); -Q_INVOKABLE void refreshOptions(); -Q_INVOKABLE void refreshDevices(); + Q_INVOKABLE void refreshOptions(); + Q_INVOKABLE void refreshDevices(); -signals: -void optionsChanged(); + signals: + void optionsChanged(); -private: -using SocketPtr = QSharedPointer; + private: + using SocketPtr = QSharedPointer; -QString m_requestSocket; -QString m_eventSocket; -QLocalSocket* m_socket; -bool m_socketValid; + QString m_requestSocket; + QString m_eventSocket; + QLocalSocket* m_socket; + bool m_socketValid; -QVariantMap m_options; -HyprDevices* const m_devices; + QVariantMap m_options; + HyprDevices* const m_devices; -SocketPtr m_optionsRefresh; -SocketPtr m_devicesRefresh; -quint64 m_optionsRefreshGeneration = 0; + SocketPtr m_optionsRefresh; + SocketPtr m_devicesRefresh; + quint64 m_optionsRefreshGeneration = 0; -void socketError(QLocalSocket::LocalSocketError error) const; -void socketStateChanged(QLocalSocket::LocalSocketState state); -void readEvent(); -void handleEvent(const QString& event); + void socketError(QLocalSocket::LocalSocketError error) const; + void socketStateChanged(QLocalSocket::LocalSocketState state); + void readEvent(); + void handleEvent(const QString& event); -SocketPtr makeRequestJson(const QString& request, const std::function& callback); -SocketPtr makeRequest(const QString& request, const std::function& callback); + SocketPtr makeRequestJson( + const QString& request, + const std::function& callback); + SocketPtr makeRequest( + const QString& request, + const std::function& callback); }; } // namespace ZShell::internal::hypr diff --git a/Plugins/ZShell/Internal/lidwatcher.cpp b/Plugins/ZShell/Internal/lidwatcher.cpp index 108da0b..8f3a689 100644 --- a/Plugins/ZShell/Internal/lidwatcher.cpp +++ b/Plugins/ZShell/Internal/lidwatcher.cpp @@ -14,27 +14,29 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) { auto bus = QDBusConnection::systemBus(); if (!bus.isConnected()) { qCWarning(lcLidWatcher) - << "Failed to connect to system bus:" << bus.lastError().message(); + << "Failed to connect to system bus:" << bus.lastError().message(); return; } - bool ok = bus.connect("org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "PrepareForSleep", - this, - SLOT(handlePrepareForSleep(bool))); + bool ok = bus.connect( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "PrepareForSleep", + this, + SLOT(handlePrepareForSleep(bool))); if (!ok) { qCWarning(lcLidWatcher) - << "Failed to connect to PrepareForSleep signal:" - << bus.lastError().message(); + << "Failed to connect to PrepareForSleep signal:" + << bus.lastError().message(); } - QDBusInterface login1("org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - bus); + QDBusInterface login1( + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + bus); const QDBusReply reply = login1.call("GetSession", "auto"); if (!reply.isValid()) { qCWarning(lcLidWatcher) << "Failed to get session path"; @@ -42,28 +44,30 @@ LidWatcher::LidWatcher(QObject* parent) : QObject(parent) { } const auto sessionPath = reply.value().path(); - ok = bus.connect("org.freedesktop.login1", - sessionPath, - "org.freedesktop.login1.Session", - "Lock", - this, - SLOT(handleLockRequested())); + ok = bus.connect( + "org.freedesktop.login1", + sessionPath, + "org.freedesktop.login1.Session", + "Lock", + this, + SLOT(handleLockRequested())); if (!ok) { qCWarning(lcLidWatcher) - << "Failed to connect to Lock signal:" << bus.lastError().message(); + << "Failed to connect to Lock signal:" << bus.lastError().message(); } - ok = bus.connect("org.freedesktop.login1", - sessionPath, - "org.freedesktop.login1.Session", - "Unlock", - this, - SLOT(handleUnlockRequested())); + ok = bus.connect( + "org.freedesktop.login1", + sessionPath, + "org.freedesktop.login1.Session", + "Unlock", + this, + SLOT(handleUnlockRequested())); if (!ok) { qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:" - << bus.lastError().message(); + << bus.lastError().message(); } } diff --git a/Plugins/ZShell/Internal/lidwatcher.hpp b/Plugins/ZShell/Internal/lidwatcher.hpp index 4016f16..6a6f4dc 100644 --- a/Plugins/ZShell/Internal/lidwatcher.hpp +++ b/Plugins/ZShell/Internal/lidwatcher.hpp @@ -6,22 +6,22 @@ namespace ZShell::internal { class LidWatcher : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -public: -explicit LidWatcher(QObject* parent = nullptr); + public: + explicit LidWatcher(QObject* parent = nullptr); -signals: -void aboutToSleep(); -void resumed(); -void lockRequested(); -void unlockRequested(); + signals: + void aboutToSleep(); + void resumed(); + void lockRequested(); + void unlockRequested(); -private slots: -void handlePrepareForSleep(bool sleep); -void handleLockRequested(); -void handleUnlockRequested(); + private slots: + void handlePrepareForSleep(bool sleep); + void handleLockRequested(); + void handleUnlockRequested(); }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/linearindicatormanager.cpp b/Plugins/ZShell/Internal/linearindicatormanager.cpp index 655ed80..0a3ee38 100644 --- a/Plugins/ZShell/Internal/linearindicatormanager.cpp +++ b/Plugins/ZShell/Internal/linearindicatormanager.cpp @@ -5,12 +5,12 @@ namespace { constexpr int TOTAL_DURATION_IN_MS = 1800; -constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = { 533, 567, 850, 750 }; -constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = { 1267, 1000, 333, 0 }; +constexpr std::array DURATION_TO_MOVE_SEGMENT_ENDS = {533, 567, 850, 750}; +constexpr std::array DELAY_TO_MOVE_SEGMENT_ENDS = {1267, 1000, 333, 0}; QEasingCurve curve(const QPointF& c1, const QPointF& c2) { QEasingCurve curve(QEasingCurve::BezierSpline); - curve.addCubicBezierSegment(c1, c2, { 1.0, 1.0 }); + curve.addCubicBezierSegment(c1, c2, {1.0, 1.0}); return curve; } @@ -24,11 +24,7 @@ qreal getFractionInRange(qreal playtime, int start, int duration) { namespace ZShell::controls { LinearIndicatorSegment::LinearIndicatorSegment(int gap, QObject* parent) - : QObject(parent) - , m_startFraction(0) - , m_endFraction(0) - , m_gapSize(gap) { -} + : QObject(parent), m_startFraction(0), m_endFraction(0), m_gapSize(gap) {} qreal LinearIndicatorSegment::startFraction() const { return m_startFraction; @@ -45,24 +41,28 @@ int LinearIndicatorSegment::gapSize() const { LinearIndicatorManager::LinearIndicatorManager(QObject* parent) : QObject(parent) , m_interpolators({ - curve({ 0.2, 0.0 }, { 0.8, 1.0 }), - curve({ 0.4, 0.0 }, { 1.0, 1.0 }), - curve({ 0.0, 0.0 }, { 0.65, 1.0 }), - curve({ 0.1, 0.0 }, { 0.45, 1.0 }), - }) + curve({0.2, 0.0}, {0.8, 1.0}), + curve({0.4, 0.0}, {1.0, 1.0}), + curve({0.0, 0.0}, {0.65, 1.0}), + curve({0.1, 0.0}, {0.45, 1.0}), + }) , m_progress(0) , m_completeEndProgress(0) , m_gap(4) , m_activeIndicators({ - new LinearIndicatorSegment(m_gap, this), - new LinearIndicatorSegment(m_gap, this), - }) { + new LinearIndicatorSegment(m_gap, this), + new LinearIndicatorSegment(m_gap, this), + }) { for (auto el : m_activeIndicators) - QObject::connect(this, &LinearIndicatorManager::updated, el, &LinearIndicatorSegment::updated); + QObject::connect( + this, + &LinearIndicatorManager::updated, + el, + &LinearIndicatorSegment::updated); } QList LinearIndicatorManager::activeIndicators() const { - return { m_activeIndicators.cbegin(), m_activeIndicators.cend() }; + return {m_activeIndicators.cbegin(), m_activeIndicators.cend()}; } qreal LinearIndicatorManager::progress() const { @@ -98,12 +98,19 @@ void LinearIndicatorManager::update(qreal progress) { const auto di = i * 2; auto* const indicator = m_activeIndicators[i]; - auto fraction = getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di], DURATION_TO_MOVE_SEGMENT_ENDS[di]); - indicator->m_startFraction = std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0); + auto fraction = getFractionInRange( + playtime, + DELAY_TO_MOVE_SEGMENT_ENDS[di], + DURATION_TO_MOVE_SEGMENT_ENDS[di]); + indicator->m_startFraction = + std::clamp(m_interpolators[di].valueForProgress(fraction), 0.0, 1.0); - fraction = - getFractionInRange(playtime, DELAY_TO_MOVE_SEGMENT_ENDS[di + 1], DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]); - indicator->m_endFraction = std::clamp(m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0); + fraction = getFractionInRange( + playtime, + DELAY_TO_MOVE_SEGMENT_ENDS[di + 1], + DURATION_TO_MOVE_SEGMENT_ENDS[di + 1]); + indicator->m_endFraction = std::clamp( + m_interpolators[di + 1].valueForProgress(fraction), 0.0, 1.0); } m_progress = progress; diff --git a/Plugins/ZShell/Internal/linearindicatormanager.hpp b/Plugins/ZShell/Internal/linearindicatormanager.hpp index dd2f48c..0eea258 100644 --- a/Plugins/ZShell/Internal/linearindicatormanager.hpp +++ b/Plugins/ZShell/Internal/linearindicatormanager.hpp @@ -11,76 +11,80 @@ namespace ZShell::controls { class LinearIndicatorManager; class LinearIndicatorSegment : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("LinearIndicatorSegments can only be retrieved from a " - "LinearIndicatorManager.") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE( + "LinearIndicatorSegments can only be retrieved from a " + "LinearIndicatorManager.") -Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL) -Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL) -Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL) + Q_PROPERTY(qreal startFraction READ startFraction NOTIFY updated FINAL) + Q_PROPERTY(qreal endFraction READ endFraction NOTIFY updated FINAL) + Q_PROPERTY(int gapSize READ gapSize NOTIFY updated FINAL) -public: -explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr); + public: + explicit LinearIndicatorSegment(int gap, QObject* parent = nullptr); -qreal startFraction() const; -qreal endFraction() const; -int gapSize() const; + qreal startFraction() const; + qreal endFraction() const; + int gapSize() const; -signals: -void updated(); + signals: + void updated(); -private: -qreal m_startFraction; -qreal m_endFraction; -int m_gapSize; + private: + qreal m_startFraction; + qreal m_endFraction; + int m_gapSize; -friend LinearIndicatorManager; + friend LinearIndicatorManager; }; class LinearIndicatorManager : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY( - QList activeIndicators READ activeIndicators CONSTANT FINAL) + Q_PROPERTY( + QList activeIndicators READ + activeIndicators CONSTANT FINAL) -Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL) -Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE updateCompleteEndProgress NOTIFY updated FINAL) -Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL) + Q_PROPERTY(qreal progress READ progress WRITE update NOTIFY updated FINAL) + Q_PROPERTY( + qreal completeEndProgress READ completeEndProgress WRITE + updateCompleteEndProgress NOTIFY updated FINAL) + Q_PROPERTY(int gap READ gap WRITE setGap NOTIFY updated FINAL) -Q_PROPERTY(qreal duration READ duration CONSTANT FINAL) -Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL) + Q_PROPERTY(qreal duration READ duration CONSTANT FINAL) + Q_PROPERTY(qreal completeEndDuration READ completeEndDuration CONSTANT FINAL) -public: -explicit LinearIndicatorManager(QObject* parent = nullptr); + public: + explicit LinearIndicatorManager(QObject* parent = nullptr); -QList activeIndicators() const; + QList activeIndicators() const; -qreal progress() const; -qreal completeEndProgress() const; + qreal progress() const; + qreal completeEndProgress() const; -int gap() const; -void setGap(int gap); + int gap() const; + void setGap(int gap); -int duration() const; -int completeEndDuration() const; + int duration() const; + int completeEndDuration() const; -void update(qreal progress); -void updateCompleteEndProgress(qreal progress); + void update(qreal progress); + void updateCompleteEndProgress(qreal progress); -signals: -void updated(); + signals: + void updated(); -private: -static constexpr int SEGMENTS = 2; + private: + static constexpr int SEGMENTS = 2; -std::array m_interpolators; -qreal m_progress; -qreal m_completeEndProgress; -int m_gap; + std::array m_interpolators; + qreal m_progress; + qreal m_completeEndProgress; + int m_gap; -std::array m_activeIndicators; + std::array m_activeIndicators; }; } // namespace ZShell::controls diff --git a/Plugins/ZShell/Internal/sparklineitem.cpp b/Plugins/ZShell/Internal/sparklineitem.cpp index 830926f..c4f2314 100644 --- a/Plugins/ZShell/Internal/sparklineitem.cpp +++ b/Plugins/ZShell/Internal/sparklineitem.cpp @@ -7,47 +7,46 @@ namespace ZShell::internal { -SparklineItem::SparklineItem(QQuickItem* parent) - : QQuickPaintedItem(parent) { +SparklineItem::SparklineItem(QQuickItem* parent) : QQuickPaintedItem(parent) { setAntialiasing(true); } void SparklineItem::paint(QPainter* painter) { const bool has1 = m_line1 && m_line1->count() >= 2; const bool has2 = m_line2 && m_line2->count() >= 2; - if (!has1 && !has2) - return; + if (!has1 && !has2) return; painter->setRenderHint(QPainter::Antialiasing, true); // Draw line1 first (behind), then line2 (in front) - if (has1) - drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha); - if (has2) - drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha); + if (has1) drawLine(painter, m_line1, m_line1Color, m_line1FillAlpha); + if (has2) drawLine(painter, m_line2, m_line2Color, m_line2FillAlpha); } -void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha) { - if (m_historyLength < 2) - return; +void SparklineItem::drawLine( + QPainter* painter, + CircularBuffer* buffer, + const QColor& color, + qreal fillAlpha) { + if (m_historyLength < 2) return; const qreal w = width(); const qreal h = height(); const int len = buffer->count(); - if (len < 2 || m_maxValue <= 0.0) - return; + if (len < 2 || m_maxValue <= 0.0) return; const qreal stepX = w / static_cast(m_historyLength - 1); - const qreal startX = w - (len - 1) * stepX - stepX * m_slideProgress + stepX; + const qreal startX = + w - (len - 1) * stepX - stepX * m_slideProgress + stepX; const qreal strokePad = qCeil(m_lineWidth / 2); - const qreal curvePad = 3.0; - const qreal topPad = strokePad + curvePad; + const qreal curvePad = 3.0; + const qreal topPad = strokePad + curvePad; const qreal bottomPad = strokePad; - const qreal plotTop = topPad; + const qreal plotTop = topPad; const qreal plotBottom = h - bottomPad; const qreal fillBottom = h; - const qreal plotH = qMax(1.0, plotBottom - plotTop); + const qreal plotH = qMax(1.0, plotBottom - plotTop); QVector points; points.reserve(len); @@ -93,23 +92,22 @@ void SparklineItem::drawLine(QPainter* painter, CircularBuffer* buffer, const QC } void SparklineItem::connectBuffer(CircularBuffer* buffer) { - if (!buffer) - return; + if (!buffer) return; connect(buffer, &CircularBuffer::valuesChanged, this, [this]() { - update(); - }); + update(); + }); connect(buffer, &QObject::destroyed, this, [this, buffer]() { - if (m_line1 == buffer) { - m_line1 = nullptr; - emit line1Changed(); - } - if (m_line2 == buffer) { - m_line2 = nullptr; - emit line2Changed(); - } - update(); - }); + if (m_line1 == buffer) { + m_line1 = nullptr; + emit line1Changed(); + } + if (m_line2 == buffer) { + m_line2 = nullptr; + emit line2Changed(); + } + update(); + }); } CircularBuffer* SparklineItem::line1() const { @@ -117,10 +115,8 @@ CircularBuffer* SparklineItem::line1() const { } void SparklineItem::setLine1(CircularBuffer* buffer) { - if (m_line1 == buffer) - return; - if (m_line1) - disconnect(m_line1, nullptr, this, nullptr); + if (m_line1 == buffer) return; + if (m_line1) disconnect(m_line1, nullptr, this, nullptr); m_line1 = buffer; connectBuffer(buffer); emit line1Changed(); @@ -132,10 +128,8 @@ CircularBuffer* SparklineItem::line2() const { } void SparklineItem::setLine2(CircularBuffer* buffer) { - if (m_line2 == buffer) - return; - if (m_line2) - disconnect(m_line2, nullptr, this, nullptr); + if (m_line2 == buffer) return; + if (m_line2) disconnect(m_line2, nullptr, this, nullptr); m_line2 = buffer; connectBuffer(buffer); emit line2Changed(); @@ -147,8 +141,7 @@ QColor SparklineItem::line1Color() const { } void SparklineItem::setLine1Color(const QColor& color) { - if (m_line1Color == color) - return; + if (m_line1Color == color) return; m_line1Color = color; emit line1ColorChanged(); update(); @@ -159,8 +152,7 @@ QColor SparklineItem::line2Color() const { } void SparklineItem::setLine2Color(const QColor& color) { - if (m_line2Color == color) - return; + if (m_line2Color == color) return; m_line2Color = color; emit line2ColorChanged(); update(); @@ -171,8 +163,7 @@ qreal SparklineItem::line1FillAlpha() const { } void SparklineItem::setLine1FillAlpha(qreal alpha) { - if (qFuzzyCompare(m_line1FillAlpha, alpha)) - return; + if (qFuzzyCompare(m_line1FillAlpha, alpha)) return; m_line1FillAlpha = alpha; emit line1FillAlphaChanged(); update(); @@ -183,8 +174,7 @@ qreal SparklineItem::line2FillAlpha() const { } void SparklineItem::setLine2FillAlpha(qreal alpha) { - if (qFuzzyCompare(m_line2FillAlpha, alpha)) - return; + if (qFuzzyCompare(m_line2FillAlpha, alpha)) return; m_line2FillAlpha = alpha; emit line2FillAlphaChanged(); update(); @@ -195,8 +185,7 @@ qreal SparklineItem::maxValue() const { } void SparklineItem::setMaxValue(qreal value) { - if (qFuzzyCompare(m_maxValue, value)) - return; + if (qFuzzyCompare(m_maxValue, value)) return; m_maxValue = value; emit maxValueChanged(); update(); @@ -207,8 +196,7 @@ qreal SparklineItem::slideProgress() const { } void SparklineItem::setSlideProgress(qreal progress) { - if (qFuzzyCompare(m_slideProgress, progress)) - return; + if (qFuzzyCompare(m_slideProgress, progress)) return; m_slideProgress = progress; emit slideProgressChanged(); update(); @@ -219,8 +207,7 @@ int SparklineItem::historyLength() const { } void SparklineItem::setHistoryLength(int length) { - if (m_historyLength == length) - return; + if (m_historyLength == length) return; m_historyLength = length; emit historyLengthChanged(); update(); @@ -231,8 +218,7 @@ qreal SparklineItem::lineWidth() const { } void SparklineItem::setLineWidth(qreal width) { - if (qFuzzyCompare(m_lineWidth, width)) - return; + if (qFuzzyCompare(m_lineWidth, width)) return; m_lineWidth = width; emit lineWidthChanged(); update(); diff --git a/Plugins/ZShell/Internal/sparklineitem.hpp b/Plugins/ZShell/Internal/sparklineitem.hpp index 0583da8..3cea1ed 100644 --- a/Plugins/ZShell/Internal/sparklineitem.hpp +++ b/Plugins/ZShell/Internal/sparklineitem.hpp @@ -10,81 +10,102 @@ namespace ZShell::internal { class SparklineItem : public QQuickPaintedItem { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed) -Q_PROPERTY(CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed) -Q_PROPERTY(QColor line1Color READ line1Color WRITE setLine1Color NOTIFY line1ColorChanged) -Q_PROPERTY(QColor line2Color READ line2Color WRITE setLine2Color NOTIFY line2ColorChanged) -Q_PROPERTY(qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY line1FillAlphaChanged) -Q_PROPERTY(qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY line2FillAlphaChanged) -Q_PROPERTY(qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged) -Q_PROPERTY(qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY slideProgressChanged) -Q_PROPERTY(int historyLength READ historyLength WRITE setHistoryLength NOTIFY historyLengthChanged) -Q_PROPERTY(qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY lineWidthChanged) + Q_PROPERTY( + CircularBuffer* line1 READ line1 WRITE setLine1 NOTIFY line1Changed) + Q_PROPERTY( + CircularBuffer* line2 READ line2 WRITE setLine2 NOTIFY line2Changed) + Q_PROPERTY( + QColor line1Color READ line1Color WRITE setLine1Color NOTIFY + line1ColorChanged) + Q_PROPERTY( + QColor line2Color READ line2Color WRITE setLine2Color NOTIFY + line2ColorChanged) + Q_PROPERTY( + qreal line1FillAlpha READ line1FillAlpha WRITE setLine1FillAlpha NOTIFY + line1FillAlphaChanged) + Q_PROPERTY( + qreal line2FillAlpha READ line2FillAlpha WRITE setLine2FillAlpha NOTIFY + line2FillAlphaChanged) + Q_PROPERTY( + qreal maxValue READ maxValue WRITE setMaxValue NOTIFY maxValueChanged) + Q_PROPERTY( + qreal slideProgress READ slideProgress WRITE setSlideProgress NOTIFY + slideProgressChanged) + Q_PROPERTY( + int historyLength READ historyLength WRITE setHistoryLength NOTIFY + historyLengthChanged) + Q_PROPERTY( + qreal lineWidth READ lineWidth WRITE setLineWidth NOTIFY + lineWidthChanged) -public: -explicit SparklineItem(QQuickItem* parent = nullptr); + public: + explicit SparklineItem(QQuickItem* parent = nullptr); -void paint(QPainter* painter) override; + void paint(QPainter* painter) override; -[[nodiscard]] CircularBuffer* line1() const; -void setLine1(CircularBuffer* buffer); + [[nodiscard]] CircularBuffer* line1() const; + void setLine1(CircularBuffer* buffer); -[[nodiscard]] CircularBuffer* line2() const; -void setLine2(CircularBuffer* buffer); + [[nodiscard]] CircularBuffer* line2() const; + void setLine2(CircularBuffer* buffer); -[[nodiscard]] QColor line1Color() const; -void setLine1Color(const QColor& color); + [[nodiscard]] QColor line1Color() const; + void setLine1Color(const QColor& color); -[[nodiscard]] QColor line2Color() const; -void setLine2Color(const QColor& color); + [[nodiscard]] QColor line2Color() const; + void setLine2Color(const QColor& color); -[[nodiscard]] qreal line1FillAlpha() const; -void setLine1FillAlpha(qreal alpha); + [[nodiscard]] qreal line1FillAlpha() const; + void setLine1FillAlpha(qreal alpha); -[[nodiscard]] qreal line2FillAlpha() const; -void setLine2FillAlpha(qreal alpha); + [[nodiscard]] qreal line2FillAlpha() const; + void setLine2FillAlpha(qreal alpha); -[[nodiscard]] qreal maxValue() const; -void setMaxValue(qreal value); + [[nodiscard]] qreal maxValue() const; + void setMaxValue(qreal value); -[[nodiscard]] qreal slideProgress() const; -void setSlideProgress(qreal progress); + [[nodiscard]] qreal slideProgress() const; + void setSlideProgress(qreal progress); -[[nodiscard]] int historyLength() const; -void setHistoryLength(int length); + [[nodiscard]] int historyLength() const; + void setHistoryLength(int length); -[[nodiscard]] qreal lineWidth() const; -void setLineWidth(qreal width); + [[nodiscard]] qreal lineWidth() const; + void setLineWidth(qreal width); -signals: -void line1Changed(); -void line2Changed(); -void line1ColorChanged(); -void line2ColorChanged(); -void line1FillAlphaChanged(); -void line2FillAlphaChanged(); -void maxValueChanged(); -void slideProgressChanged(); -void historyLengthChanged(); -void lineWidthChanged(); + signals: + void line1Changed(); + void line2Changed(); + void line1ColorChanged(); + void line2ColorChanged(); + void line1FillAlphaChanged(); + void line2FillAlphaChanged(); + void maxValueChanged(); + void slideProgressChanged(); + void historyLengthChanged(); + void lineWidthChanged(); -private: -void drawLine(QPainter* painter, CircularBuffer* buffer, const QColor& color, qreal fillAlpha); -void connectBuffer(CircularBuffer* buffer); + private: + void drawLine( + QPainter* painter, + CircularBuffer* buffer, + const QColor& color, + qreal fillAlpha); + void connectBuffer(CircularBuffer* buffer); -CircularBuffer* m_line1 = nullptr; -CircularBuffer* m_line2 = nullptr; -QColor m_line1Color; -QColor m_line2Color; -qreal m_line1FillAlpha = 0.15; -qreal m_line2FillAlpha = 0.2; -qreal m_maxValue = 1024.0; -qreal m_slideProgress = 0.0; -int m_historyLength = 30; -qreal m_lineWidth = 2.0; + CircularBuffer* m_line1 = nullptr; + CircularBuffer* m_line2 = nullptr; + QColor m_line1Color; + QColor m_line2Color; + qreal m_line1FillAlpha = 0.15; + qreal m_line2FillAlpha = 0.2; + qreal m_maxValue = 1024.0; + qreal m_slideProgress = 0.0; + int m_historyLength = 30; + qreal m_lineWidth = 2.0; }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/visualizerbars.cpp b/Plugins/ZShell/Internal/visualizerbars.cpp index fb77d70..25d0d8d 100644 --- a/Plugins/ZShell/Internal/visualizerbars.cpp +++ b/Plugins/ZShell/Internal/visualizerbars.cpp @@ -9,14 +9,12 @@ namespace ZShell::internal { -VisualizerBars::VisualizerBars(QQuickItem* parent) - : QQuickPaintedItem(parent) { +VisualizerBars::VisualizerBars(QQuickItem* parent) : QQuickPaintedItem(parent) { setAntialiasing(true); } void VisualizerBars::advance(qreal dt) { - if (m_displayValues.isEmpty() || m_settled) - return; + if (m_displayValues.isEmpty() || m_settled) return; // dt is in seconds (from FrameAnimation.frameTime), convert to ms const qreal dtMs = dt * 1000.0; @@ -45,8 +43,7 @@ void VisualizerBars::advance(qreal dt) { } void VisualizerBars::paint(QPainter* painter) { - if (m_displayValues.isEmpty()) - return; + if (m_displayValues.isEmpty()) return; painter->setRenderHint(QPainter::Antialiasing, true); painter->setPen(Qt::NoPen); @@ -68,15 +65,13 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) { const qreal h = height(); const auto count = m_displayValues.size(); - if (count == 0) - return; + if (count == 0) return; const qreal sideWidth = w * 0.4; const qreal slotWidth = sideWidth / static_cast(count); const qreal barWidth = slotWidth - m_spacing; - if (barWidth <= 0) - return; + if (barWidth <= 0) return; const qreal sideOffset = rightSide ? w * 0.6 : 0; const qreal maxBarHeight = h * 0.4; @@ -86,12 +81,11 @@ void VisualizerBars::drawSide(QPainter* painter, bool rightSide) { const qreal value = std::clamp(m_displayValues[valueIndex], 0.0, 1.0); const qreal barHeight = value * maxBarHeight; - if (barHeight <= 0) - continue; + if (barHeight <= 0) continue; const qreal x = static_cast(i) * slotWidth + sideOffset; const qreal y = h - barHeight; - const qreal r = std::min({ m_rounding, barWidth / 2.0, barHeight }); + const qreal r = std::min({m_rounding, barWidth / 2.0, barHeight}); QPainterPath path; path.moveTo(x, h); @@ -141,8 +135,7 @@ QColor VisualizerBars::primaryColor() const { } void VisualizerBars::setPrimaryColor(const QColor& color) { - if (m_primaryColor == color) - return; + if (m_primaryColor == color) return; m_primaryColor = color; emit primaryColorChanged(); update(); @@ -153,8 +146,7 @@ QColor VisualizerBars::secondaryColor() const { } void VisualizerBars::setSecondaryColor(const QColor& color) { - if (m_secondaryColor == color) - return; + if (m_secondaryColor == color) return; m_secondaryColor = color; emit secondaryColorChanged(); update(); @@ -165,8 +157,7 @@ qreal VisualizerBars::rounding() const { } void VisualizerBars::setRounding(qreal rounding) { - if (qFuzzyCompare(m_rounding, rounding)) - return; + if (qFuzzyCompare(m_rounding, rounding)) return; m_rounding = rounding; emit roundingChanged(); update(); @@ -177,8 +168,7 @@ qreal VisualizerBars::spacing() const { } void VisualizerBars::setSpacing(qreal spacing) { - if (qFuzzyCompare(m_spacing, spacing)) - return; + if (qFuzzyCompare(m_spacing, spacing)) return; m_spacing = spacing; emit spacingChanged(); update(); @@ -189,8 +179,7 @@ int VisualizerBars::animationDuration() const { } void VisualizerBars::setAnimationDuration(int duration) { - if (m_animationDuration == duration) - return; + if (m_animationDuration == duration) return; m_animationDuration = duration; emit animationDurationChanged(); } diff --git a/Plugins/ZShell/Internal/visualizerbars.hpp b/Plugins/ZShell/Internal/visualizerbars.hpp index 64651cd..fafdb4b 100644 --- a/Plugins/ZShell/Internal/visualizerbars.hpp +++ b/Plugins/ZShell/Internal/visualizerbars.hpp @@ -9,64 +9,72 @@ namespace ZShell::internal { class VisualizerBars : public QQuickPaintedItem { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(QVector values READ values WRITE setValues NOTIFY valuesChanged) -Q_PROPERTY(QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY primaryColorChanged) -Q_PROPERTY(QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY secondaryColorChanged) -Q_PROPERTY(qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged) -Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) -Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged) -Q_PROPERTY(bool settled READ settled NOTIFY settledChanged) + Q_PROPERTY( + QVector values READ values WRITE setValues NOTIFY valuesChanged) + Q_PROPERTY( + QColor primaryColor READ primaryColor WRITE setPrimaryColor NOTIFY + primaryColorChanged) + Q_PROPERTY( + QColor secondaryColor READ secondaryColor WRITE setSecondaryColor NOTIFY + secondaryColorChanged) + Q_PROPERTY( + qreal rounding READ rounding WRITE setRounding NOTIFY roundingChanged) + Q_PROPERTY(qreal spacing READ spacing WRITE setSpacing NOTIFY spacingChanged) + Q_PROPERTY( + int animationDuration READ animationDuration WRITE setAnimationDuration + NOTIFY animationDurationChanged) + Q_PROPERTY(bool settled READ settled NOTIFY settledChanged) -public: -explicit VisualizerBars(QQuickItem* parent = nullptr); + public: + explicit VisualizerBars(QQuickItem* parent = nullptr); -void paint(QPainter* painter) override; + void paint(QPainter* painter) override; -Q_INVOKABLE void advance(qreal dt); + Q_INVOKABLE void advance(qreal dt); -[[nodiscard]] QVector values() const; -void setValues(const QVector& values); + [[nodiscard]] QVector values() const; + void setValues(const QVector& values); -[[nodiscard]] QColor primaryColor() const; -void setPrimaryColor(const QColor& color); + [[nodiscard]] QColor primaryColor() const; + void setPrimaryColor(const QColor& color); -[[nodiscard]] QColor secondaryColor() const; -void setSecondaryColor(const QColor& color); + [[nodiscard]] QColor secondaryColor() const; + void setSecondaryColor(const QColor& color); -[[nodiscard]] qreal rounding() const; -void setRounding(qreal rounding); + [[nodiscard]] qreal rounding() const; + void setRounding(qreal rounding); -[[nodiscard]] qreal spacing() const; -void setSpacing(qreal spacing); + [[nodiscard]] qreal spacing() const; + void setSpacing(qreal spacing); -[[nodiscard]] int animationDuration() const; -void setAnimationDuration(int duration); + [[nodiscard]] int animationDuration() const; + void setAnimationDuration(int duration); -[[nodiscard]] bool settled() const; + [[nodiscard]] bool settled() const; -signals: -void valuesChanged(); -void primaryColorChanged(); -void secondaryColorChanged(); -void roundingChanged(); -void spacingChanged(); -void animationDurationChanged(); -void settledChanged(); + signals: + void valuesChanged(); + void primaryColorChanged(); + void secondaryColorChanged(); + void roundingChanged(); + void spacingChanged(); + void animationDurationChanged(); + void settledChanged(); -private: -void drawSide(QPainter* painter, bool rightSide); + private: + void drawSide(QPainter* painter, bool rightSide); -QVector m_targetValues; -QVector m_displayValues; -QColor m_primaryColor; -QColor m_secondaryColor; -qreal m_rounding = 0.0; -qreal m_spacing = 0.0; -int m_animationDuration = 200; -bool m_settled = true; + QVector m_targetValues; + QVector m_displayValues; + QColor m_primaryColor; + QColor m_secondaryColor; + qreal m_rounding = 0.0; + qreal m_spacing = 0.0; + int m_animationDuration = 200; + bool m_settled = true; }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Internal/wallpaperimage.cpp b/Plugins/ZShell/Internal/wallpaperimage.cpp index c48aab4..e0e6da9 100644 --- a/Plugins/ZShell/Internal/wallpaperimage.cpp +++ b/Plugins/ZShell/Internal/wallpaperimage.cpp @@ -7,15 +7,16 @@ #include #include #include -#include namespace ZShell::internal { -WallpaperImage::WallpaperImage(QQuickItem *parent) - : QQuickItem(parent) -{ +WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents, true); - connect(&m_imageWatcher, &QFutureWatcher::finished, this, &WallpaperImage::handleImageLoaded); + connect( + &m_imageWatcher, + &QFutureWatcher::finished, + this, + &WallpaperImage::handleImageLoaded); } WallpaperImage::~WallpaperImage() { @@ -23,14 +24,13 @@ WallpaperImage::~WallpaperImage() { } void WallpaperImage::setStatus(const Status s) { - if (m_status == s) - return; + if (m_status == s) return; m_status = s; emit statusChanged(); } -void WallpaperImage::setSource(const QUrl &source) { +void WallpaperImage::setSource(const QUrl& source) { if (m_source == source) return; m_source = source; emit sourceChanged(); @@ -39,7 +39,7 @@ void WallpaperImage::setSource(const QUrl &source) { loadImage(); } -void WallpaperImage::setScreenResolution(const QSize &screenResolution) { +void WallpaperImage::setScreenResolution(const QSize& screenResolution) { if (m_screenResolution == screenResolution) return; m_screenResolution = screenResolution; emit screenResolutionChanged(); @@ -86,11 +86,16 @@ void WallpaperImage::setCropHeight(qreal h) { QString WallpaperImage::getCacheFilePath() const { if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString(); - QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache"; + QString cachePath = + QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + + "/zshell/imagecache"; QDir().mkpath(cachePath); - QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height()); - QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); + QString id = m_source.toString() + "_" + + QString::number(m_screenResolution.width()) + "x" + + QString::number(m_screenResolution.height()); + QByteArray hash = + QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex(); return cachePath + "/" + hash + ".png"; } @@ -102,7 +107,8 @@ void WallpaperImage::loadImage() { } QString cacheFile = getCacheFilePath(); - QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString(); + QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() + : m_source.toString(); if (sourceFile.startsWith("qrc:/")) { sourceFile = sourceFile.mid(3); @@ -110,8 +116,10 @@ void WallpaperImage::loadImage() { QSize targetRes = m_screenResolution; - QFuture future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage { - if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) { + QFuture future = + QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage { + if (!targetRes.isEmpty() && !cacheFile.isEmpty() && + QFileInfo::exists(cacheFile)) { QImage cached(cacheFile); if (!cached.isNull()) return cached; } @@ -123,8 +131,12 @@ void WallpaperImage::loadImage() { return original; } - if (original.width() > targetRes.width() || original.height() > targetRes.height()) { - QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + if (original.width() > targetRes.width() || + original.height() > targetRes.height()) { + QImage scaled = original.scaled( + targetRes, + Qt::KeepAspectRatioByExpanding, + Qt::SmoothTransformation); if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG"); return scaled; } @@ -145,8 +157,9 @@ void WallpaperImage::handleImageLoaded() { update(); } -QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) { - auto *node = static_cast(oldNode); +QSGNode* WallpaperImage::updatePaintNode( + QSGNode* oldNode, UpdatePaintNodeData*) { + auto* node = static_cast(oldNode); if (m_image.isNull()) { delete node; @@ -159,7 +172,8 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * if (m_textureDirty) { if (m_texture) delete m_texture; - m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel); + m_texture = window()->createTextureFromImage( + m_image, QQuickWindow::TextureHasAlphaChannel); m_textureDirty = false; } @@ -175,8 +189,7 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * m_cropX * m_texture->textureSize().width(), m_cropY * m_texture->textureSize().height(), cW * m_texture->textureSize().width(), - cH * m_texture->textureSize().height() - ); + cH * m_texture->textureSize().height()); QRectF bounds = boundingRect(); if (bounds.isEmpty() || reqRect.isEmpty()) return node; @@ -202,25 +215,23 @@ QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData * sourceRect.x() / m_texture->textureSize().width(), sourceRect.y() / m_texture->textureSize().height(), sourceRect.width() / m_texture->textureSize().width(), - sourceRect.height() / m_texture->textureSize().height() - ); + sourceRect.height() / m_texture->textureSize().height()); bool changed = false; - auto updateIfChanged = [&](qreal &dst, qreal value) { - if (!qFuzzyCompare(dst, value)) { - dst = value; - changed = true; - } - }; + auto updateIfChanged = [&](qreal& dst, qreal value) { + if (!qFuzzyCompare(dst, value)) { + dst = value; + changed = true; + } + }; updateIfChanged(m_actualCropX, normalizedActual.x()); updateIfChanged(m_actualCropY, normalizedActual.y()); updateIfChanged(m_actualCropWidth, normalizedActual.width()); updateIfChanged(m_actualCropHeight, normalizedActual.height()); - if (changed) - emit actualCropChanged(); + if (changed) emit actualCropChanged(); node->setSourceRect(sourceRect); } diff --git a/Plugins/ZShell/Internal/wallpaperimage.hpp b/Plugins/ZShell/Internal/wallpaperimage.hpp index 3c00fe4..9c0d2a9 100644 --- a/Plugins/ZShell/Internal/wallpaperimage.hpp +++ b/Plugins/ZShell/Internal/wallpaperimage.hpp @@ -11,129 +11,109 @@ namespace ZShell::internal { class WallpaperImage : public QQuickItem { -Q_OBJECT -QML_NAMED_ELEMENT(WallpaperImage) -Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) -Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged) -Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) -Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_OBJECT + QML_NAMED_ELEMENT(WallpaperImage) + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY( + QSize screenResolution READ screenResolution WRITE setScreenResolution + NOTIFY screenResolutionChanged) + Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) -Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged) -Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged) -Q_PROPERTY(qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged) -Q_PROPERTY(qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged) + Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged) + Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged) + Q_PROPERTY( + qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged) + Q_PROPERTY( + qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged) -Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged) -Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged) -Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged) -Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged) + Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged) + Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged) + Q_PROPERTY( + qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY + cropWidthChanged) + Q_PROPERTY( + qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY + cropHeightChanged) -public: -explicit WallpaperImage(QQuickItem *parent = nullptr); -~WallpaperImage() override; + public: + explicit WallpaperImage(QQuickItem* parent = nullptr); + ~WallpaperImage() override; -enum Status { - Null, - Ready, - Loading, - Error -}; -Q_ENUM(Status) + enum Status { Null, Ready, Loading, Error }; + Q_ENUM(Status) -Status status() const { - return m_status; -} + Status status() const { return m_status; } -QUrl source() const { - return m_source; -} -void setSource(const QUrl &source); + QUrl source() const { return m_source; } + void setSource(const QUrl& source); -QSize screenResolution() const { - return m_screenResolution; -} -void setScreenResolution(const QSize &screenResolution); + QSize screenResolution() const { return m_screenResolution; } + void setScreenResolution(const QSize& screenResolution); -qreal zoom() const { - return m_zoom; -} -void setZoom(qreal zoom); + qreal zoom() const { return m_zoom; } + void setZoom(qreal zoom); -qreal actualCropX() const { - return m_actualCropX; -} + qreal actualCropX() const { return m_actualCropX; } -qreal actualCropY() const { - return m_actualCropY; -} + qreal actualCropY() const { return m_actualCropY; } -qreal actualCropWidth() const { - return m_actualCropWidth; -} + qreal actualCropWidth() const { return m_actualCropWidth; } -qreal actualCropHeight() const { - return m_actualCropHeight; -} + qreal actualCropHeight() const { return m_actualCropHeight; } -qreal cropX() const { - return m_cropX; -} -void setCropX(qreal x); + qreal cropX() const { return m_cropX; } + void setCropX(qreal x); -qreal cropY() const { - return m_cropY; -} -void setCropY(qreal y); + qreal cropY() const { return m_cropY; } + void setCropY(qreal y); -qreal cropWidth() const { - return m_cropWidth; -} -void setCropWidth(qreal w); + qreal cropWidth() const { return m_cropWidth; } + void setCropWidth(qreal w); -qreal cropHeight() const { - return m_cropHeight; -} -void setCropHeight(qreal h); + qreal cropHeight() const { return m_cropHeight; } + void setCropHeight(qreal h); -protected: -QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override; + protected: + QSGNode* updatePaintNode( + QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override; -signals: -void sourceChanged(); -void screenResolutionChanged(); -void zoomChanged(); -void actualCropChanged(); -void cropXChanged(); -void cropYChanged(); -void cropWidthChanged(); -void cropHeightChanged(); -void statusChanged(); + signals: + void sourceChanged(); + void screenResolutionChanged(); + void zoomChanged(); + void actualCropChanged(); + void cropXChanged(); + void cropYChanged(); + void cropWidthChanged(); + void cropHeightChanged(); + void statusChanged(); -private: -void loadImage(); -void handleImageLoaded(); -QString getCacheFilePath() const; -void setStatus(const Status s); + private: + void loadImage(); + void handleImageLoaded(); + QString getCacheFilePath() const; + void setStatus(const Status s); -Status m_status = Null; -QUrl m_source; -QSize m_screenResolution; -qreal m_zoom = 1.0; + Status m_status = Null; + QUrl m_source; + QSize m_screenResolution; + qreal m_zoom = 1.0; -qreal m_actualCropX = 0.0; -qreal m_actualCropY = 0.0; -qreal m_actualCropWidth = 1.0; -qreal m_actualCropHeight = 1.0; + qreal m_actualCropX = 0.0; + qreal m_actualCropY = 0.0; + qreal m_actualCropWidth = 1.0; + qreal m_actualCropHeight = 1.0; -qreal m_cropX = 0.0; -qreal m_cropY = 0.0; -qreal m_cropWidth = 1.0; -qreal m_cropHeight = 1.0; + qreal m_cropX = 0.0; + qreal m_cropY = 0.0; + qreal m_cropWidth = 1.0; + qreal m_cropHeight = 1.0; -QImage m_image; -QSGTexture *m_texture = nullptr; -bool m_textureDirty = false; -QFutureWatcher m_imageWatcher; + QImage m_image; + QSGTexture* m_texture = nullptr; + bool m_textureDirty = false; + QFutureWatcher m_imageWatcher; }; } // namespace ZShell::internal diff --git a/Plugins/ZShell/Models/filesystemmodel.cpp b/Plugins/ZShell/Models/filesystemmodel.cpp index 790cefa..d0769d5 100644 --- a/Plugins/ZShell/Models/filesystemmodel.cpp +++ b/Plugins/ZShell/Models/filesystemmodel.cpp @@ -6,474 +6,512 @@ namespace ZShell::models { -FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent) - : QObject(parent) - , m_fileInfo(path) - , m_path(path) - , m_relativePath(relativePath) - , m_isImageInitialised(false) - , m_mimeTypeInitialised(false) {} +FileSystemEntry::FileSystemEntry( + const QString& path, const QString& relativePath, QObject* parent) + : QObject(parent) + , m_fileInfo(path) + , m_path(path) + , m_relativePath(relativePath) + , m_isImageInitialised(false) + , m_mimeTypeInitialised(false) {} QString FileSystemEntry::path() const { - return m_path; + return m_path; }; QString FileSystemEntry::relativePath() const { - return m_relativePath; + return m_relativePath; }; QString FileSystemEntry::name() const { - return m_fileInfo.fileName(); + return m_fileInfo.fileName(); }; QString FileSystemEntry::baseName() const { - return m_fileInfo.baseName(); + return m_fileInfo.baseName(); }; QString FileSystemEntry::parentDir() const { - return m_fileInfo.absolutePath(); + return m_fileInfo.absolutePath(); }; QString FileSystemEntry::suffix() const { - return m_fileInfo.completeSuffix(); + return m_fileInfo.completeSuffix(); }; qint64 FileSystemEntry::size() const { - return m_fileInfo.size(); + return m_fileInfo.size(); }; bool FileSystemEntry::isDir() const { - return m_fileInfo.isDir(); + return m_fileInfo.isDir(); }; bool FileSystemEntry::isImage() const { - if (!m_isImageInitialised) { - QImageReader reader(m_path); - m_isImage = reader.canRead(); - m_isImageInitialised = true; - } - return m_isImage; + if (!m_isImageInitialised) { + QImageReader reader(m_path); + m_isImage = reader.canRead(); + m_isImageInitialised = true; + } + return m_isImage; } QString FileSystemEntry::mimeType() const { - if (!m_mimeTypeInitialised) { - const QMimeDatabase db; - m_mimeType = db.mimeTypeForFile(m_path).name(); - m_mimeTypeInitialised = true; - } - return m_mimeType; + if (!m_mimeTypeInitialised) { + const QMimeDatabase db; + m_mimeType = db.mimeTypeForFile(m_path).name(); + m_mimeTypeInitialised = true; + } + return m_mimeType; } void FileSystemEntry::updateRelativePath(const QDir& dir) { - const auto relPath = dir.relativeFilePath(m_path); - if (m_relativePath != relPath) { - m_relativePath = relPath; - emit relativePathChanged(); - } + const auto relPath = dir.relativeFilePath(m_path); + if (m_relativePath != relPath) { + m_relativePath = relPath; + emit relativePathChanged(); + } } FileSystemModel::FileSystemModel(QObject* parent) - : QAbstractListModel(parent) - , m_recursive(false) - , m_watchChanges(true) - , m_showHidden(false) - , m_filter(NoFilter) { - connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive); - connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir); + : QAbstractListModel(parent) + , m_recursive(false) + , m_watchChanges(true) + , m_showHidden(false) + , m_filter(NoFilter) { + connect( + &m_watcher, + &QFileSystemWatcher::directoryChanged, + this, + &FileSystemModel::watchDirIfRecursive); + connect( + &m_watcher, + &QFileSystemWatcher::directoryChanged, + this, + &FileSystemModel::updateEntriesForDir); } int FileSystemModel::rowCount(const QModelIndex& parent) const { - if (parent != QModelIndex()) { - return 0; - } - return static_cast(m_entries.size()); + if (parent != QModelIndex()) { + return 0; + } + return static_cast(m_entries.size()); } QVariant FileSystemModel::data(const QModelIndex& index, int role) const { - if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) { - return QVariant(); - } - return QVariant::fromValue(m_entries.at(index.row())); + if (role != Qt::UserRole || !index.isValid() || + index.row() >= m_entries.size()) { + return QVariant(); + } + return QVariant::fromValue(m_entries.at(index.row())); } QHash FileSystemModel::roleNames() const { - return { { Qt::UserRole, "modelData" } }; + return {{Qt::UserRole, "modelData"}}; } QString FileSystemModel::path() const { - return m_path; + return m_path; } void FileSystemModel::setPath(const QString& path) { - if (m_path == path) { - return; - } + if (m_path == path) { + return; + } - m_path = path; - emit pathChanged(); + m_path = path; + emit pathChanged(); - m_dir.setPath(m_path); + m_dir.setPath(m_path); - for (const auto& entry : std::as_const(m_entries)) { - entry->updateRelativePath(m_dir); - } + for (const auto& entry : std::as_const(m_entries)) { + entry->updateRelativePath(m_dir); + } - update(); + update(); } bool FileSystemModel::recursive() const { - return m_recursive; + return m_recursive; } void FileSystemModel::setRecursive(bool recursive) { - if (m_recursive == recursive) { - return; - } + if (m_recursive == recursive) { + return; + } - m_recursive = recursive; - emit recursiveChanged(); + m_recursive = recursive; + emit recursiveChanged(); - update(); + update(); } bool FileSystemModel::watchChanges() const { - return m_watchChanges; + return m_watchChanges; } void FileSystemModel::setWatchChanges(bool watchChanges) { - if (m_watchChanges == watchChanges) { - return; - } + if (m_watchChanges == watchChanges) { + return; + } - m_watchChanges = watchChanges; - emit watchChangesChanged(); + m_watchChanges = watchChanges; + emit watchChangesChanged(); - update(); + update(); } bool FileSystemModel::showHidden() const { - return m_showHidden; + return m_showHidden; } void FileSystemModel::setShowHidden(bool showHidden) { - if (m_showHidden == showHidden) { - return; - } + if (m_showHidden == showHidden) { + return; + } - m_showHidden = showHidden; - emit showHiddenChanged(); + m_showHidden = showHidden; + emit showHiddenChanged(); - update(); + update(); } bool FileSystemModel::sortReverse() const { - return m_sortReverse; + return m_sortReverse; } void FileSystemModel::setSortReverse(bool sortReverse) { - if (m_sortReverse == sortReverse) { - return; - } + if (m_sortReverse == sortReverse) { + return; + } - m_sortReverse = sortReverse; - emit sortReverseChanged(); + m_sortReverse = sortReverse; + emit sortReverseChanged(); - update(); + update(); } FileSystemModel::Filter FileSystemModel::filter() const { - return m_filter; + return m_filter; } void FileSystemModel::setFilter(Filter filter) { - if (m_filter == filter) { - return; - } + if (m_filter == filter) { + return; + } - m_filter = filter; - emit filterChanged(); + m_filter = filter; + emit filterChanged(); - update(); + update(); } QStringList FileSystemModel::nameFilters() const { - return m_nameFilters; + return m_nameFilters; } void FileSystemModel::setNameFilters(const QStringList& nameFilters) { - if (m_nameFilters == nameFilters) { - return; - } + if (m_nameFilters == nameFilters) { + return; + } - m_nameFilters = nameFilters; - emit nameFiltersChanged(); + m_nameFilters = nameFilters; + emit nameFiltersChanged(); - update(); + update(); } QQmlListProperty FileSystemModel::entries() { - return QQmlListProperty(this, &m_entries); + return QQmlListProperty(this, &m_entries); } void FileSystemModel::watchDirIfRecursive(const QString& path) { - if (m_recursive && m_watchChanges) { - const auto currentDir = m_dir; - const bool showHidden = m_showHidden; - const auto future = QtConcurrent::run([showHidden, path]() { - QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot; - if (showHidden) { - filters |= QDir::Hidden; - } + if (m_recursive && m_watchChanges) { + const auto currentDir = m_dir; + const bool showHidden = m_showHidden; + const auto future = QtConcurrent::run([showHidden, path]() { + QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot; + if (showHidden) { + filters |= QDir::Hidden; + } - QDirIterator iter(path, filters, QDirIterator::Subdirectories); - QStringList dirs; - while (iter.hasNext()) { - dirs << iter.next(); - } - return dirs; - }); - const auto watcher = new QFutureWatcher(this); - connect(watcher, &QFutureWatcher::finished, this, [currentDir, showHidden, watcher, this]() { - const auto paths = watcher->result(); - if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) { - // Ignore if dir or showHidden has changed - m_watcher.addPaths(paths); - } - watcher->deleteLater(); - }); - watcher->setFuture(future); - } + QDirIterator iter(path, filters, QDirIterator::Subdirectories); + QStringList dirs; + while (iter.hasNext()) { + dirs << iter.next(); + } + return dirs; + }); + const auto watcher = new QFutureWatcher(this); + connect( + watcher, + &QFutureWatcher::finished, + this, + [currentDir, showHidden, watcher, this]() { + const auto paths = watcher->result(); + if (currentDir == m_dir && showHidden == m_showHidden && + !paths.isEmpty()) { + // Ignore if dir or showHidden has changed + m_watcher.addPaths(paths); + } + watcher->deleteLater(); + }); + watcher->setFuture(future); + } } void FileSystemModel::update() { - updateWatcher(); - updateEntries(); + updateWatcher(); + updateEntries(); } void FileSystemModel::updateWatcher() { - if (!m_watcher.directories().isEmpty()) { - m_watcher.removePaths(m_watcher.directories()); - } + if (!m_watcher.directories().isEmpty()) { + m_watcher.removePaths(m_watcher.directories()); + } - if (!m_watchChanges || m_path.isEmpty()) { - return; - } + if (!m_watchChanges || m_path.isEmpty()) { + return; + } - m_watcher.addPath(m_path); - watchDirIfRecursive(m_path); + m_watcher.addPath(m_path); + watchDirIfRecursive(m_path); } void FileSystemModel::updateEntries() { - if (m_path.isEmpty()) { - if (!m_entries.isEmpty()) { - beginResetModel(); - qDeleteAll(m_entries); - m_entries.clear(); - endResetModel(); - emit entriesChanged(); - } + if (m_path.isEmpty()) { + if (!m_entries.isEmpty()) { + beginResetModel(); + qDeleteAll(m_entries); + m_entries.clear(); + endResetModel(); + emit entriesChanged(); + } - return; - } + return; + } - for (auto& future : m_futures) { - future.cancel(); - } - m_futures.clear(); + for (auto& future : m_futures) { + future.cancel(); + } + m_futures.clear(); - updateEntriesForDir(m_path); + updateEntriesForDir(m_path); } void FileSystemModel::updateEntriesForDir(const QString& dir) { - const auto recursive = m_recursive; - const auto showHidden = m_showHidden; - const auto filter = m_filter; - const auto nameFilters = m_nameFilters; + const auto recursive = m_recursive; + const auto showHidden = m_showHidden; + const auto filter = m_filter; + const auto nameFilters = m_nameFilters; - QSet oldPaths; - for (const auto& entry : std::as_const(m_entries)) { - oldPaths << entry->path(); - } + QSet oldPaths; + for (const auto& entry : std::as_const(m_entries)) { + oldPaths << entry->path(); + } - const auto future = QtConcurrent::run([=](QPromise, QSet>>& promise) { - const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; + const auto future = QtConcurrent::run( + [=](QPromise, QSet>>& promise) { + const auto flags = recursive ? QDirIterator::Subdirectories + : QDirIterator::NoIteratorFlags; - std::optional iter; + std::optional iter; - if (filter == Images) { - QStringList extraNameFilters = nameFilters; - const auto formats = QImageReader::supportedImageFormats(); - for (const auto& format : formats) { - extraNameFilters << "*." + format; - } + if (filter == Images) { + QStringList extraNameFilters = nameFilters; + const auto formats = QImageReader::supportedImageFormats(); + for (const auto& format : formats) { + extraNameFilters << "*." + format; + } - QDir::Filters filters = QDir::Files; - if (showHidden) { - filters |= QDir::Hidden; - } + QDir::Filters filters = QDir::Files; + if (showHidden) { + filters |= QDir::Hidden; + } - iter.emplace(dir, extraNameFilters, filters, flags); - } else { - QDir::Filters filters; + iter.emplace(dir, extraNameFilters, filters, flags); + } else { + QDir::Filters filters; - if (filter == Files) { - filters = QDir::Files; - } else if (filter == Dirs) { - filters = QDir::Dirs | QDir::NoDotAndDotDot; - } else { - filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot; - } + if (filter == Files) { + filters = QDir::Files; + } else if (filter == Dirs) { + filters = QDir::Dirs | QDir::NoDotAndDotDot; + } else { + filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot; + } - if (showHidden) { - filters |= QDir::Hidden; - } + if (showHidden) { + filters |= QDir::Hidden; + } - if (nameFilters.isEmpty()) { - iter.emplace(dir, filters, flags); - } else { - iter.emplace(dir, nameFilters, filters, flags); - } - } + if (nameFilters.isEmpty()) { + iter.emplace(dir, filters, flags); + } else { + iter.emplace(dir, nameFilters, filters, flags); + } + } - QSet newPaths; - while (iter->hasNext()) { - if (promise.isCanceled()) { - return; - } + QSet newPaths; + while (iter->hasNext()) { + if (promise.isCanceled()) { + return; + } - QString path = iter->next(); + QString path = iter->next(); - if (filter == Images) { - QImageReader reader(path); - if (!reader.canRead()) { - continue; - } - } + if (filter == Images) { + QImageReader reader(path); + if (!reader.canRead()) { + continue; + } + } - newPaths.insert(path); - } + newPaths.insert(path); + } - if (promise.isCanceled() || newPaths == oldPaths) { - return; - } + if (promise.isCanceled() || newPaths == oldPaths) { + return; + } - promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths)); - }); + promise.addResult( + qMakePair(oldPaths - newPaths, newPaths - oldPaths)); + }); - if (m_futures.contains(dir)) { - m_futures[dir].cancel(); - } - m_futures.insert(dir, future); + if (m_futures.contains(dir)) { + m_futures[dir].cancel(); + } + m_futures.insert(dir, future); - const auto watcher = new QFutureWatcher, QSet>>(this); + const auto watcher = + new QFutureWatcher, QSet>>(this); - connect(watcher, &QFutureWatcher, QSet>>::finished, this, [dir, watcher, this]() { - m_futures.remove(dir); + connect( + watcher, + &QFutureWatcher, QSet>>::finished, + this, + [dir, watcher, this]() { + m_futures.remove(dir); - if (!watcher->future().isResultReadyAt(0)) { - watcher->deleteLater(); - return; - } + if (!watcher->future().isResultReadyAt(0)) { + watcher->deleteLater(); + return; + } - const auto result = watcher->result(); - applyChanges(result.first, result.second); + const auto result = watcher->result(); + applyChanges(result.first, result.second); - watcher->deleteLater(); - }); + watcher->deleteLater(); + }); - watcher->setFuture(future); + watcher->setFuture(future); } -void FileSystemModel::applyChanges(const QSet& removedPaths, const QSet& addedPaths) { - QList removedIndices; - for (int i = 0; i < m_entries.size(); ++i) { - if (removedPaths.contains(m_entries[i]->path())) { - removedIndices << i; - } - } - std::sort(removedIndices.begin(), removedIndices.end(), std::greater()); +void FileSystemModel::applyChanges( + const QSet& removedPaths, const QSet& addedPaths) { + QList removedIndices; + for (int i = 0; i < m_entries.size(); ++i) { + if (removedPaths.contains(m_entries[i]->path())) { + removedIndices << i; + } + } + std::sort(removedIndices.begin(), removedIndices.end(), std::greater()); - // Batch remove old entries - int start = -1; - int end = -1; - for (int idx : std::as_const(removedIndices)) { - if (start == -1) { - start = idx; - end = idx; - } else if (idx == end - 1) { - end = idx; - } else { - beginRemoveRows(QModelIndex(), end, start); - for (int i = start; i >= end; --i) { - m_entries.takeAt(i)->deleteLater(); - } - endRemoveRows(); + // Batch remove old entries + int start = -1; + int end = -1; + for (int idx : std::as_const(removedIndices)) { + if (start == -1) { + start = idx; + end = idx; + } else if (idx == end - 1) { + end = idx; + } else { + beginRemoveRows(QModelIndex(), end, start); + for (int i = start; i >= end; --i) { + m_entries.takeAt(i)->deleteLater(); + } + endRemoveRows(); - start = idx; - end = idx; - } - } - if (start != -1) { - beginRemoveRows(QModelIndex(), end, start); - for (int i = start; i >= end; --i) { - m_entries.takeAt(i)->deleteLater(); - } - endRemoveRows(); - } + start = idx; + end = idx; + } + } + if (start != -1) { + beginRemoveRows(QModelIndex(), end, start); + for (int i = start; i >= end; --i) { + m_entries.takeAt(i)->deleteLater(); + } + endRemoveRows(); + } - // Create new entries - QList newEntries; - for (const auto& path : addedPaths) { - newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this); - } - std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) { - return compareEntries(a, b); - }); + // Create new entries + QList newEntries; + for (const auto& path : addedPaths) { + newEntries << new FileSystemEntry( + path, m_dir.relativeFilePath(path), this); + } + std::sort( + newEntries.begin(), + newEntries.end(), + [this](const FileSystemEntry* a, const FileSystemEntry* b) { + return compareEntries(a, b); + }); - // Batch insert new entries - int insertStart = -1; - QList batchItems; - for (const auto& entry : std::as_const(newEntries)) { - const auto it = std::lower_bound( - m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) { - return compareEntries(a, b); - }); - const auto row = static_cast(it - m_entries.begin()); + // Batch insert new entries + int insertStart = -1; + QList batchItems; + for (const auto& entry : std::as_const(newEntries)) { + const auto it = std::lower_bound( + m_entries.begin(), + m_entries.end(), + entry, + [this](const FileSystemEntry* a, const FileSystemEntry* b) { + return compareEntries(a, b); + }); + const auto row = static_cast(it - m_entries.begin()); - if (insertStart == -1) { - insertStart = row; - batchItems << entry; - } else if (row == insertStart + batchItems.size()) { - batchItems << entry; - } else { - beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast(batchItems.size()) - 1); - for (int i = 0; i < batchItems.size(); ++i) { - m_entries.insert(insertStart + i, batchItems[i]); - } - endInsertRows(); + if (insertStart == -1) { + insertStart = row; + batchItems << entry; + } else if (row == insertStart + batchItems.size()) { + batchItems << entry; + } else { + beginInsertRows( + QModelIndex(), + insertStart, + insertStart + static_cast(batchItems.size()) - 1); + for (int i = 0; i < batchItems.size(); ++i) { + m_entries.insert(insertStart + i, batchItems[i]); + } + endInsertRows(); - insertStart = row; - batchItems.clear(); - batchItems << entry; - } - } - if (!batchItems.isEmpty()) { - beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast(batchItems.size()) - 1); - for (int i = 0; i < batchItems.size(); ++i) { - m_entries.insert(insertStart + i, batchItems[i]); - } - endInsertRows(); - } + insertStart = row; + batchItems.clear(); + batchItems << entry; + } + } + if (!batchItems.isEmpty()) { + beginInsertRows( + QModelIndex(), + insertStart, + insertStart + static_cast(batchItems.size()) - 1); + for (int i = 0; i < batchItems.size(); ++i) { + m_entries.insert(insertStart + i, batchItems[i]); + } + endInsertRows(); + } - emit entriesChanged(); + emit entriesChanged(); } -bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const { - if (a->isDir() != b->isDir()) { - return m_sortReverse ^ a->isDir(); - } - const auto cmp = a->relativePath().localeAwareCompare(b->relativePath()); - return m_sortReverse ? cmp > 0 : cmp < 0; +bool FileSystemModel::compareEntries( + const FileSystemEntry* a, const FileSystemEntry* b) const { + if (a->isDir() != b->isDir()) { + return m_sortReverse ^ a->isDir(); + } + const auto cmp = a->relativePath().localeAwareCompare(b->relativePath()); + return m_sortReverse ? cmp > 0 : cmp < 0; } } // namespace ZShell::models diff --git a/Plugins/ZShell/Models/filesystemmodel.hpp b/Plugins/ZShell/Models/filesystemmodel.hpp index f860c2a..94bb29a 100644 --- a/Plugins/ZShell/Models/filesystemmodel.hpp +++ b/Plugins/ZShell/Models/filesystemmodel.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -14,136 +13,151 @@ namespace ZShell::models { class FileSystemEntry : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE( + "FileSystemEntry instances can only be retrieved from a " + "FileSystemModel") -Q_PROPERTY(QString path READ path CONSTANT) -Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged) -Q_PROPERTY(QString name READ name CONSTANT) -Q_PROPERTY(QString baseName READ baseName CONSTANT) -Q_PROPERTY(QString parentDir READ parentDir CONSTANT) -Q_PROPERTY(QString suffix READ suffix CONSTANT) -Q_PROPERTY(qint64 size READ size CONSTANT) -Q_PROPERTY(bool isDir READ isDir CONSTANT) -Q_PROPERTY(bool isImage READ isImage CONSTANT) -Q_PROPERTY(QString mimeType READ mimeType CONSTANT) + Q_PROPERTY(QString path READ path CONSTANT) + Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QString baseName READ baseName CONSTANT) + Q_PROPERTY(QString parentDir READ parentDir CONSTANT) + Q_PROPERTY(QString suffix READ suffix CONSTANT) + Q_PROPERTY(qint64 size READ size CONSTANT) + Q_PROPERTY(bool isDir READ isDir CONSTANT) + Q_PROPERTY(bool isImage READ isImage CONSTANT) + Q_PROPERTY(QString mimeType READ mimeType CONSTANT) -public: -explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr); + public: + explicit FileSystemEntry( + const QString& path, + const QString& relativePath, + QObject* parent = nullptr); -[[nodiscard]] QString path() const; -[[nodiscard]] QString relativePath() const; -[[nodiscard]] QString name() const; -[[nodiscard]] QString baseName() const; -[[nodiscard]] QString parentDir() const; -[[nodiscard]] QString suffix() const; -[[nodiscard]] qint64 size() const; -[[nodiscard]] bool isDir() const; -[[nodiscard]] bool isImage() const; -[[nodiscard]] QString mimeType() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString relativePath() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString baseName() const; + [[nodiscard]] QString parentDir() const; + [[nodiscard]] QString suffix() const; + [[nodiscard]] qint64 size() const; + [[nodiscard]] bool isDir() const; + [[nodiscard]] bool isImage() const; + [[nodiscard]] QString mimeType() const; -void updateRelativePath(const QDir& dir); + void updateRelativePath(const QDir& dir); -signals: -void relativePathChanged(); + signals: + void relativePathChanged(); -private: -const QFileInfo m_fileInfo; + private: + const QFileInfo m_fileInfo; -const QString m_path; -QString m_relativePath; + const QString m_path; + QString m_relativePath; -mutable bool m_isImage; -mutable bool m_isImageInitialised; + mutable bool m_isImage; + mutable bool m_isImageInitialised; -mutable QString m_mimeType; -mutable bool m_mimeTypeInitialised; + mutable QString m_mimeType; + mutable bool m_mimeTypeInitialised; }; class FileSystemModel : public QAbstractListModel { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) -Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) -Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged) -Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged) -Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged) -Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged) -Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged) + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + Q_PROPERTY( + bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) + Q_PROPERTY( + bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY + watchChangesChanged) + Q_PROPERTY( + bool showHidden READ showHidden WRITE setShowHidden NOTIFY + showHiddenChanged) + Q_PROPERTY( + bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY + sortReverseChanged) + Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged) + Q_PROPERTY( + QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY + nameFiltersChanged) -Q_PROPERTY(QQmlListProperty entries READ entries NOTIFY entriesChanged) + Q_PROPERTY( + QQmlListProperty entries READ entries + NOTIFY entriesChanged) -public: -enum Filter { - NoFilter, - Images, - Files, - Dirs -}; -Q_ENUM(Filter) + public: + enum Filter { NoFilter, Images, Files, Dirs }; + Q_ENUM(Filter) -explicit FileSystemModel(QObject* parent = nullptr); + explicit FileSystemModel(QObject* parent = nullptr); -[[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const override; -[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; -[[nodiscard]] QHash roleNames() const override; + [[nodiscard]] int rowCount( + const QModelIndex& parent = QModelIndex()) const override; + [[nodiscard]] QVariant data( + const QModelIndex& index, int role = Qt::DisplayRole) const override; + [[nodiscard]] QHash roleNames() const override; -[[nodiscard]] QString path() const; -void setPath(const QString& path); + [[nodiscard]] QString path() const; + void setPath(const QString& path); -[[nodiscard]] bool recursive() const; -void setRecursive(bool recursive); + [[nodiscard]] bool recursive() const; + void setRecursive(bool recursive); -[[nodiscard]] bool watchChanges() const; -void setWatchChanges(bool watchChanges); + [[nodiscard]] bool watchChanges() const; + void setWatchChanges(bool watchChanges); -[[nodiscard]] bool showHidden() const; -void setShowHidden(bool showHidden); + [[nodiscard]] bool showHidden() const; + void setShowHidden(bool showHidden); -[[nodiscard]] bool sortReverse() const; -void setSortReverse(bool sortReverse); + [[nodiscard]] bool sortReverse() const; + void setSortReverse(bool sortReverse); -[[nodiscard]] Filter filter() const; -void setFilter(Filter filter); + [[nodiscard]] Filter filter() const; + void setFilter(Filter filter); -[[nodiscard]] QStringList nameFilters() const; -void setNameFilters(const QStringList& nameFilters); + [[nodiscard]] QStringList nameFilters() const; + void setNameFilters(const QStringList& nameFilters); -[[nodiscard]] QQmlListProperty entries(); + [[nodiscard]] QQmlListProperty entries(); -signals: -void pathChanged(); -void recursiveChanged(); -void watchChangesChanged(); -void showHiddenChanged(); -void sortReverseChanged(); -void filterChanged(); -void nameFiltersChanged(); -void entriesChanged(); + signals: + void pathChanged(); + void recursiveChanged(); + void watchChangesChanged(); + void showHiddenChanged(); + void sortReverseChanged(); + void filterChanged(); + void nameFiltersChanged(); + void entriesChanged(); -private: -QDir m_dir; -QFileSystemWatcher m_watcher; -QList m_entries; -QHash, QSet > > > m_futures; + private: + QDir m_dir; + QFileSystemWatcher m_watcher; + QList m_entries; + QHash, QSet>>> m_futures; -QString m_path; -bool m_recursive; -bool m_watchChanges; -bool m_showHidden; -bool m_sortReverse; -Filter m_filter; -QStringList m_nameFilters; + QString m_path; + bool m_recursive; + bool m_watchChanges; + bool m_showHidden; + bool m_sortReverse; + Filter m_filter; + QStringList m_nameFilters; -void watchDirIfRecursive(const QString& path); -void update(); -void updateWatcher(); -void updateEntries(); -void updateEntriesForDir(const QString& dir); -void applyChanges(const QSet& removedPaths, const QSet& addedPaths); -[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const; + void watchDirIfRecursive(const QString& path); + void update(); + void updateWatcher(); + void updateEntries(); + void updateEntriesForDir(const QString& dir); + void applyChanges( + const QSet& removedPaths, const QSet& addedPaths); + [[nodiscard]] bool compareEntries( + const FileSystemEntry* a, const FileSystemEntry* b) const; }; } // namespace ZShell::models diff --git a/Plugins/ZShell/Services/audiocollector.cpp b/Plugins/ZShell/Services/audiocollector.cpp index 770c499..a9382e3 100644 --- a/Plugins/ZShell/Services/audiocollector.cpp +++ b/Plugins/ZShell/Services/audiocollector.cpp @@ -23,20 +23,33 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector) m_loop = pw_main_loop_new(nullptr); if (!m_loop) { - qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop"; + qWarning() + << "PipeWireWorker::init: failed to create PipeWire main loop"; pw_deinit(); return; } - timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC }; - m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this); - pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); + timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC}; + m_timer = + pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this); + pw_loop_update_timer( + pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); auto props = pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr); + PW_KEY_MEDIA_TYPE, + "Audio", + PW_KEY_MEDIA_CATEGORY, + "Capture", + PW_KEY_MEDIA_ROLE, + "Music", + nullptr); pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); pw_properties_setf( - props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), ac::SAMPLE_RATE); + props, + PW_KEY_NODE_LATENCY, + "%u/%u", + nextPowerOf2(512 * ac::SAMPLE_RATE / 48000), + ac::SAMPLE_RATE); pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true"); pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false"); @@ -55,21 +68,28 @@ PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector) params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info); pw_stream_events events{}; - events.state_changed = [](void* data, pw_stream_state, pw_stream_state state, const char*) { - auto* self = static_cast(data); - self->streamStateChanged(state); - }; + events.state_changed = + [](void* data, pw_stream_state, pw_stream_state state, const char*) { + auto* self = static_cast(data); + self->streamStateChanged(state); + }; events.process = [](void* data) { - auto* self = static_cast(data); - self->processStream(); - }; + auto* self = static_cast(data); + self->processStream(); + }; - m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this); + m_stream = pw_stream_new_simple( + pw_main_loop_get_loop(m_loop), "ZShell-shell", props, &events, this); - const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, PW_ID_ANY, - static_cast( - PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS), - params, 1); + const int success = pw_stream_connect( + m_stream, + PW_DIRECTION_INPUT, + PW_ID_ANY, + static_cast( + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS), + params, + 1); if (success < 0) { qWarning() << "PipeWireWorker::init: failed to connect stream"; pw_stream_destroy(m_stream); @@ -98,8 +118,13 @@ void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) { self->m_collector->clearBuffer(); } else { self->m_idle = true; - timespec timeout = { 0, 500 * SPA_NSEC_PER_MSEC }; - pw_loop_update_timer(pw_main_loop_get_loop(self->m_loop), self->m_timer, &timeout, &timeout, false); + timespec timeout = {0, 500 * SPA_NSEC_PER_MSEC}; + pw_loop_update_timer( + pw_main_loop_get_loop(self->m_loop), + self->m_timer, + &timeout, + &timeout, + false); } } } @@ -108,12 +133,14 @@ void PipeWireWorker::streamStateChanged(pw_stream_state state) { m_idle = false; switch (state) { case PW_STREAM_STATE_PAUSED: { - timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC }; - pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); + timespec timeout = {0, 10 * SPA_NSEC_PER_MSEC}; + pw_loop_update_timer( + pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false); break; } case PW_STREAM_STATE_STREAMING: - pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false); + pw_loop_update_timer( + pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false); break; case PW_STREAM_STATE_ERROR: pw_main_loop_quit(m_loop); @@ -171,7 +198,8 @@ void AudioCollector::clearBuffer() { auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed); std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f); - auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); + auto* oldRead = + m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); m_writeBuffer.store(oldRead, std::memory_order_release); } @@ -181,11 +209,13 @@ void AudioCollector::loadChunk(const qint16* samples, quint32 count) { } auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed); - std::transform(samples, samples + count, writeBuffer->begin(), [](qint16 sample) { + std::transform( + samples, samples + count, writeBuffer->begin(), [](qint16 sample) { return sample / 32768.0f; }); - auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); + auto* oldRead = + m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel); m_writeBuffer.store(oldRead, std::memory_order_release); } @@ -206,9 +236,11 @@ quint32 AudioCollector::readChunk(double* out, quint32 count) { } auto* readBuffer = m_readBuffer.load(std::memory_order_acquire); - std::transform(readBuffer->begin(), readBuffer->begin() + count, out, [](float sample) { - return static_cast(sample); - }); + std::transform( + readBuffer->begin(), + readBuffer->begin() + count, + out, + [](float sample) { return static_cast(sample); }); return count; } @@ -218,8 +250,7 @@ AudioCollector::AudioCollector(QObject* parent) , m_buffer1(ac::CHUNK_SIZE) , m_buffer2(ac::CHUNK_SIZE) , m_readBuffer(&m_buffer1) - , m_writeBuffer(&m_buffer2) { -} + , m_writeBuffer(&m_buffer2) {} AudioCollector::~AudioCollector() { stop(); @@ -232,9 +263,8 @@ void AudioCollector::start() { clearBuffer(); - m_thread = std::jthread([this](std::stop_token token) { - PipeWireWorker worker(token, this); - }); + m_thread = std::jthread( + [this](std::stop_token token) { PipeWireWorker worker(token, this); }); } void AudioCollector::stop() { diff --git a/Plugins/ZShell/Services/audiocollector.hpp b/Plugins/ZShell/Services/audiocollector.hpp index e0fe2f6..d235796 100644 --- a/Plugins/ZShell/Services/audiocollector.hpp +++ b/Plugins/ZShell/Services/audiocollector.hpp @@ -22,55 +22,55 @@ constexpr quint32 CHUNK_SIZE = 512; class AudioCollector; class PipeWireWorker { -public: -explicit PipeWireWorker(std::stop_token token, AudioCollector* collector); + public: + explicit PipeWireWorker(std::stop_token token, AudioCollector* collector); -void run(); + void run(); -private: -pw_main_loop* m_loop; -pw_stream* m_stream; -spa_source* m_timer; -bool m_idle; + private: + pw_main_loop* m_loop; + pw_stream* m_stream; + spa_source* m_timer; + bool m_idle; -std::stop_token m_token; -AudioCollector* m_collector; + std::stop_token m_token; + AudioCollector* m_collector; -static void handleTimeout(void* data, uint64_t expirations); -void streamStateChanged(pw_stream_state state); -void processStream(); + static void handleTimeout(void* data, uint64_t expirations); + void streamStateChanged(pw_stream_state state); + void processStream(); -[[nodiscard]] unsigned int nextPowerOf2(unsigned int n); + [[nodiscard]] unsigned int nextPowerOf2(unsigned int n); }; class AudioCollector : public Service { -Q_OBJECT + Q_OBJECT -public: -AudioCollector(const AudioCollector&) = delete; -AudioCollector& operator=(const AudioCollector&) = delete; + public: + AudioCollector(const AudioCollector&) = delete; + AudioCollector& operator=(const AudioCollector&) = delete; -static AudioCollector& instance(); + static AudioCollector& instance(); -void clearBuffer(); -void loadChunk(const qint16* samples, quint32 count); -quint32 readChunk(float* out, quint32 count = 0); -quint32 readChunk(double* out, quint32 count = 0); + void clearBuffer(); + void loadChunk(const qint16* samples, quint32 count); + quint32 readChunk(float* out, quint32 count = 0); + quint32 readChunk(double* out, quint32 count = 0); -private: -explicit AudioCollector(QObject* parent = nullptr); -~AudioCollector() override; + private: + explicit AudioCollector(QObject* parent = nullptr); + ~AudioCollector() override; -std::jthread m_thread; -std::vector m_buffer1; -std::vector m_buffer2; -std::atomic*> m_readBuffer; -std::atomic*> m_writeBuffer; -quint32 m_sampleCount; + std::jthread m_thread; + std::vector m_buffer1; + std::vector m_buffer2; + std::atomic*> m_readBuffer; + std::atomic*> m_writeBuffer; + quint32 m_sampleCount; -void reload(); -void start() override; -void stop() override; + void reload(); + void start() override; + void stop() override; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/audioprovider.cpp b/Plugins/ZShell/Services/audioprovider.cpp index dd99bf1..3957d3f 100644 --- a/Plugins/ZShell/Services/audioprovider.cpp +++ b/Plugins/ZShell/Services/audioprovider.cpp @@ -7,9 +7,7 @@ namespace ZShell::services { -AudioProcessor::AudioProcessor(QObject* parent) - : QObject(parent) { -} +AudioProcessor::AudioProcessor(QObject* parent) : QObject(parent) {} AudioProcessor::~AudioProcessor() { stop(); @@ -17,12 +15,17 @@ AudioProcessor::~AudioProcessor() { void AudioProcessor::init() { m_timer = new QTimer(this); - m_timer->setInterval(static_cast(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE)); + m_timer->setInterval( + static_cast(ac::CHUNK_SIZE * 1000.0 / ac::SAMPLE_RATE)); connect(m_timer, &QTimer::timeout, this, &AudioProcessor::process); } void AudioProcessor::start() { - QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::ref, Qt::QueuedConnection, this); + QMetaObject::invokeMethod( + &AudioCollector::instance(), + &AudioCollector::ref, + Qt::QueuedConnection, + this); if (m_timer) { m_timer->start(); } @@ -32,14 +35,15 @@ void AudioProcessor::stop() { if (m_timer) { m_timer->stop(); } - QMetaObject::invokeMethod(&AudioCollector::instance(), &AudioCollector::unref, Qt::QueuedConnection, this); + QMetaObject::invokeMethod( + &AudioCollector::instance(), + &AudioCollector::unref, + Qt::QueuedConnection, + this); } AudioProvider::AudioProvider(QObject* parent) - : Service(parent) - , m_processor(nullptr) - , m_thread(nullptr) { -} + : Service(parent), m_processor(nullptr), m_thread(nullptr) {} AudioProvider::~AudioProvider() { if (m_thread) { @@ -50,7 +54,8 @@ AudioProvider::~AudioProvider() { void AudioProvider::init() { if (!m_processor) { - qWarning() << "AudioProvider::init: attempted to init with no processor set"; + qWarning() + << "AudioProvider::init: attempted to init with no processor set"; return; } @@ -58,7 +63,8 @@ void AudioProvider::init() { m_processor->moveToThread(m_thread); connect(m_thread, &QThread::started, m_processor, &AudioProcessor::init); - connect(m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater); + connect( + m_thread, &QThread::finished, m_processor, &AudioProcessor::deleteLater); connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater); m_thread->start(); diff --git a/Plugins/ZShell/Services/audioprovider.hpp b/Plugins/ZShell/Services/audioprovider.hpp index 76d4614..4de6e04 100644 --- a/Plugins/ZShell/Services/audioprovider.hpp +++ b/Plugins/ZShell/Services/audioprovider.hpp @@ -7,41 +7,41 @@ namespace ZShell::services { class AudioProcessor : public QObject { -Q_OBJECT + Q_OBJECT -public: -explicit AudioProcessor(QObject* parent = nullptr); -~AudioProcessor() override; + public: + explicit AudioProcessor(QObject* parent = nullptr); + ~AudioProcessor() override; -void init(); + void init(); -void start(); -void stop(); + void start(); + void stop(); -protected: -virtual void process() = 0; + protected: + virtual void process() = 0; -private: -QTimer* m_timer; + private: + QTimer* m_timer; }; class AudioProvider : public Service { -Q_OBJECT + Q_OBJECT -public: -explicit AudioProvider(QObject* parent = nullptr); -~AudioProvider() override; + public: + explicit AudioProvider(QObject* parent = nullptr); + ~AudioProvider() override; -protected: -AudioProcessor* m_processor; + protected: + AudioProcessor* m_processor; -void init(); + void init(); -private: -QThread* m_thread; + private: + QThread* m_thread; -void start() override; -void stop() override; + void start() override; + void stop() override; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/beattracker.cpp b/Plugins/ZShell/Services/beattracker.cpp index 5e83770..498b5d0 100644 --- a/Plugins/ZShell/Services/beattracker.cpp +++ b/Plugins/ZShell/Services/beattracker.cpp @@ -10,8 +10,7 @@ BeatProcessor::BeatProcessor(QObject* parent) : AudioProcessor(parent) , m_tempo(new_aubio_tempo("default", 1024, ac::CHUNK_SIZE, ac::SAMPLE_RATE)) , m_in(new_fvec(ac::CHUNK_SIZE)) - , m_out(new_fvec(2)) { -}; + , m_out(new_fvec(2)) {}; BeatProcessor::~BeatProcessor() { if (m_tempo) { @@ -36,13 +35,15 @@ void BeatProcessor::process() { } } -BeatTracker::BeatTracker(QObject* parent) - : AudioProvider(parent) - , m_bpm(120) { +BeatTracker::BeatTracker(QObject* parent) : AudioProvider(parent), m_bpm(120) { m_processor = new BeatProcessor(); init(); - connect(static_cast(m_processor), &BeatProcessor::beat, this, &BeatTracker::updateBpm); + connect( + static_cast(m_processor), + &BeatProcessor::beat, + this, + &BeatTracker::updateBpm); } smpl_t BeatTracker::bpm() const { diff --git a/Plugins/ZShell/Services/beattracker.hpp b/Plugins/ZShell/Services/beattracker.hpp index 4982572..b04ba05 100644 --- a/Plugins/ZShell/Services/beattracker.hpp +++ b/Plugins/ZShell/Services/beattracker.hpp @@ -7,43 +7,43 @@ namespace ZShell::services { class BeatProcessor : public AudioProcessor { -Q_OBJECT + Q_OBJECT -public: -explicit BeatProcessor(QObject* parent = nullptr); -~BeatProcessor() override; + public: + explicit BeatProcessor(QObject* parent = nullptr); + ~BeatProcessor() override; -signals: -void beat(smpl_t bpm); + signals: + void beat(smpl_t bpm); -protected: -void process() override; + protected: + void process() override; -private: -aubio_tempo_t* m_tempo; -fvec_t* m_in; -fvec_t* m_out; + private: + aubio_tempo_t* m_tempo; + fvec_t* m_in; + fvec_t* m_out; }; class BeatTracker : public AudioProvider { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged) + Q_PROPERTY(smpl_t bpm READ bpm NOTIFY bpmChanged) -public: -explicit BeatTracker(QObject* parent = nullptr); + public: + explicit BeatTracker(QObject* parent = nullptr); -[[nodiscard]] smpl_t bpm() const; + [[nodiscard]] smpl_t bpm() const; -signals: -void bpmChanged(); -void beat(smpl_t bpm); + signals: + void bpmChanged(); + void beat(smpl_t bpm); -private: -smpl_t m_bpm; + private: + smpl_t m_bpm; -void updateBpm(smpl_t bpm); + void updateBpm(smpl_t bpm); }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/cavaprovider.cpp b/Plugins/ZShell/Services/cavaprovider.cpp index ec64db2..21fb7bc 100644 --- a/Plugins/ZShell/Services/cavaprovider.cpp +++ b/Plugins/ZShell/Services/cavaprovider.cpp @@ -2,7 +2,6 @@ #include "audiocollector.hpp" #include "audioprovider.hpp" -#include #include #include #include @@ -14,8 +13,7 @@ CavaProcessor::CavaProcessor(QObject* parent) , m_plan(nullptr) , m_in(new double[ac::CHUNK_SIZE]) , m_out(nullptr) - , m_bars(0) { -}; + , m_bars(0) {}; CavaProcessor::~CavaProcessor() { cleanup(); @@ -27,7 +25,8 @@ void CavaProcessor::process() { return; } - const int count = static_cast(AudioCollector::instance().readChunk(m_in)); + const int count = + static_cast(AudioCollector::instance().readChunk(m_in)); // Process in data via cava cava_execute(m_in, count, m_out, m_plan); @@ -35,7 +34,7 @@ void CavaProcessor::process() { // Apply monstercat filter QVector values(m_bars); - for(int i = 0; i < m_bars; ++i) { + for (int i = 0; i < m_bars; ++i) { values[i] = m_out[i]; } @@ -63,7 +62,8 @@ void CavaProcessor::process() { void CavaProcessor::setBars(int bars) { if (bars < 0) { - qWarning() << "CavaProcessor::setBars: bars must be greater than 0. Setting to 0."; + qWarning() << "CavaProcessor::setBars: bars must be greater than 0. " + "Setting to 0."; bars = 0; } @@ -100,13 +100,15 @@ void CavaProcessor::initCava() { } CavaProvider::CavaProvider(QObject* parent) - : AudioProvider(parent) - , m_bars(0) - , m_values(m_bars, 0.0) { + : AudioProvider(parent), m_bars(0), m_values(m_bars, 0.0) { m_processor = new CavaProcessor(); init(); - connect(static_cast(m_processor), &CavaProcessor::valuesChanged, this, &CavaProvider::updateValues); + connect( + static_cast(m_processor), + &CavaProcessor::valuesChanged, + this, + &CavaProvider::updateValues); } int CavaProvider::bars() const { @@ -115,7 +117,8 @@ int CavaProvider::bars() const { void CavaProvider::setBars(int bars) { if (bars < 0) { - qWarning() << "CavaProvider::setBars: bars must be greater than 0. Setting to 0."; + qWarning() << "CavaProvider::setBars: bars must be greater than 0. " + "Setting to 0."; bars = 0; } @@ -129,7 +132,10 @@ void CavaProvider::setBars(int bars) { emit valuesChanged(); QMetaObject::invokeMethod( - static_cast(m_processor), &CavaProcessor::setBars, Qt::QueuedConnection, bars); + static_cast(m_processor), + &CavaProcessor::setBars, + Qt::QueuedConnection, + bars); } QVector CavaProvider::values() const { diff --git a/Plugins/ZShell/Services/cavaprovider.hpp b/Plugins/ZShell/Services/cavaprovider.hpp index 5bfd8e9..2d2aebc 100644 --- a/Plugins/ZShell/Services/cavaprovider.hpp +++ b/Plugins/ZShell/Services/cavaprovider.hpp @@ -7,58 +7,58 @@ namespace ZShell::services { class CavaProcessor : public AudioProcessor { -Q_OBJECT + Q_OBJECT -public: -explicit CavaProcessor(QObject* parent = nullptr); -~CavaProcessor() override; + public: + explicit CavaProcessor(QObject* parent = nullptr); + ~CavaProcessor() override; -void setBars(int bars); + void setBars(int bars); -signals: -void valuesChanged(QVector values); + signals: + void valuesChanged(QVector values); -protected: -void process() override; + protected: + void process() override; -private: -struct cava_plan* m_plan; -double* m_in; -double* m_out; + private: + struct cava_plan* m_plan; + double* m_in; + double* m_out; -int m_bars; -QVector m_values; + int m_bars; + QVector m_values; -void reload(); -void initCava(); -void cleanup(); + void reload(); + void initCava(); + void cleanup(); }; class CavaProvider : public AudioProvider { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged) + Q_PROPERTY(int bars READ bars WRITE setBars NOTIFY barsChanged) -Q_PROPERTY(QVector values READ values NOTIFY valuesChanged) + Q_PROPERTY(QVector values READ values NOTIFY valuesChanged) -public: -explicit CavaProvider(QObject* parent = nullptr); + public: + explicit CavaProvider(QObject* parent = nullptr); -[[nodiscard]] int bars() const; -void setBars(int bars); + [[nodiscard]] int bars() const; + void setBars(int bars); -[[nodiscard]] QVector values() const; + [[nodiscard]] QVector values() const; -signals: -void barsChanged(); -void valuesChanged(); + signals: + void barsChanged(); + void valuesChanged(); -private: -int m_bars; -QVector m_values; + private: + int m_bars; + QVector m_values; -void updateValues(QVector values); + void updateValues(QVector values); }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/cpu.cpp b/Plugins/ZShell/Services/cpu.cpp index c04f356..d183da9 100644 --- a/Plugins/ZShell/Services/cpu.cpp +++ b/Plugins/ZShell/Services/cpu.cpp @@ -8,8 +8,7 @@ namespace ZShell::services { -Cpu::Cpu(QObject* parent) - : TickingService(parent) { +Cpu::Cpu(QObject* parent) : TickingService(parent) { readNameOnce(); } @@ -41,7 +40,8 @@ void Cpu::readNameOnce() { const QByteArray data = f.readAll(); f.close(); - static const QRegularExpression re(QStringLiteral("model name\\s*:\\s*(.+)")); + static const QRegularExpression re( + QStringLiteral("model name\\s*:\\s*(.+)")); const auto match = re.match(QString::fromLatin1(data)); if (!match.hasMatch()) { return; @@ -64,8 +64,9 @@ void Cpu::refreshPercentage() { const QByteArray data = f.readAll(); f.close(); - static const QRegularExpression re( - QStringLiteral("^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)")); + static const QRegularExpression re(QStringLiteral( + "^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)" + "\\s+(\\d+)\\s+(\\d+)")); const auto match = re.match(QString::fromLatin1(data)); if (!match.hasMatch()) { return; @@ -83,7 +84,10 @@ void Cpu::refreshPercentage() { const quint64 totalDiff = total > m_lastTotal ? total - m_lastTotal : 0; const quint64 idleDiff = idle > m_lastIdle ? idle - m_lastIdle : 0; - const qreal newPerc = totalDiff > 0 ? 1.0 - static_cast(idleDiff) / static_cast(totalDiff) : 0.0; + const qreal newPerc = + totalDiff > 0 + ? 1.0 - static_cast(idleDiff) / static_cast(totalDiff) + : 0.0; m_lastTotal = total; m_lastIdle = idle; @@ -105,7 +109,8 @@ void Cpu::refreshTemperature() { QString Cpu::cleanName(QString s) { static const QRegularExpression noise( - QStringLiteral("\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"), + QStringLiteral( + "\\(R\\)|\\(TM\\)|CPU|\\d+(?:th|nd|rd|st) Gen |Core |Processor"), QRegularExpression::CaseInsensitiveOption); static const QRegularExpression spaces(QStringLiteral("\\s+")); diff --git a/Plugins/ZShell/Services/cpu.hpp b/Plugins/ZShell/Services/cpu.hpp index eb3909c..3fd7332 100644 --- a/Plugins/ZShell/Services/cpu.hpp +++ b/Plugins/ZShell/Services/cpu.hpp @@ -7,42 +7,42 @@ namespace ZShell::services { class Cpu : public TickingService { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -Q_PROPERTY(QString name READ name NOTIFY nameChanged) -Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) -Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) + Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) -public: -explicit Cpu(QObject* parent = nullptr); + public: + explicit Cpu(QObject* parent = nullptr); -[[nodiscard]] QString name() const; -[[nodiscard]] qreal percentage() const; -[[nodiscard]] qreal temperature() const; + [[nodiscard]] QString name() const; + [[nodiscard]] qreal percentage() const; + [[nodiscard]] qreal temperature() const; -signals: -void nameChanged(); -void percentageChanged(); -void temperatureChanged(); + signals: + void nameChanged(); + void percentageChanged(); + void temperatureChanged(); -protected: -void tick() override; + protected: + void tick() override; -private: -void readNameOnce(); -void refreshPercentage(); -void refreshTemperature(); + private: + void readNameOnce(); + void refreshPercentage(); + void refreshTemperature(); -[[nodiscard]] static QString cleanName(QString s); + [[nodiscard]] static QString cleanName(QString s); -QString m_name; -qreal m_percentage = 0.0; -qreal m_temperature = 0.0; -quint64 m_lastIdle = 0; -quint64 m_lastTotal = 0; -bool m_nameLoaded = false; + QString m_name; + qreal m_percentage = 0.0; + qreal m_temperature = 0.0; + quint64 m_lastIdle = 0; + quint64 m_lastTotal = 0; + bool m_nameLoaded = false; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/desktopmodel.hpp b/Plugins/ZShell/Services/desktopmodel.hpp index 286b40a..4f47a00 100644 --- a/Plugins/ZShell/Services/desktopmodel.hpp +++ b/Plugins/ZShell/Services/desktopmodel.hpp @@ -17,49 +17,57 @@ struct DesktopItem { }; class DesktopModel : public QAbstractListModel { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -public: -enum DesktopRoles { - FileNameRole = Qt::UserRole + 1, - FilePathRole, - IsDirRole, - GridXRole, - GridYRole + public: + enum DesktopRoles { + FileNameRole = Qt::UserRole + 1, + FilePathRole, + IsDirRole, + GridXRole, + GridYRole + }; + + explicit DesktopModel(QObject* parent = nullptr); + + [[nodiscard]] int rowCount( + const QModelIndex& parent = QModelIndex()) const override; + [[nodiscard]] QVariant data( + const QModelIndex& index, int role = Qt::DisplayRole) const override; + [[nodiscard]] QHash roleNames() const override; + + Q_INVOKABLE void loadDirectory(const QString& path); + Q_INVOKABLE void moveIcon(int index, int newX, int newY); + Q_INVOKABLE void massMove( + const QVariantList& selectedPathsList, + const QString& leaderPath, + int targetX, + int targetY, + int maxCol, + int maxRow); + + Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) + + [[nodiscard]] int rows() const { return m_rows; } + void setRows(int r) { + if (m_rows != r) { + m_rows = r; + emit rowsChanged(); + } + } + + signals: + void rowsChanged(); + + private: + int m_rows = 1; + QList m_items; + QString m_watchedPath; + QFileSystemWatcher m_watcher; + void saveCurrentLayout(); + [[nodiscard]] QPoint getEmptySpot(const QSet& occupied) const; + void onDirectoryChanged(); }; -explicit DesktopModel(QObject *parent = nullptr); - -[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; -[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; -[[nodiscard]] QHash roleNames() const override; - -Q_INVOKABLE void loadDirectory(const QString &path); -Q_INVOKABLE void moveIcon(int index, int newX, int newY); -Q_INVOKABLE void massMove(const QVariantList &selectedPathsList, const QString &leaderPath, int targetX, int targetY, int maxCol, int maxRow); - -Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged) - -public: -[[nodiscard]] int rows() const { - return m_rows; -} -void setRows(int r) { - if (m_rows != r) { m_rows = r; emit rowsChanged(); } -} - -signals: -void rowsChanged(); - -private: -int m_rows = 1; -QList m_items; -QString m_watchedPath; -QFileSystemWatcher m_watcher; -void saveCurrentLayout(); -[[nodiscard]] QPoint getEmptySpot(const QSet &occupied) const; -void onDirectoryChanged(); -}; - -}; +}; // namespace ZShell::services diff --git a/Plugins/ZShell/Services/desktopstatemanager.cpp b/Plugins/ZShell/Services/desktopstatemanager.cpp index 820a1e6..84b0df0 100644 --- a/Plugins/ZShell/Services/desktopstatemanager.cpp +++ b/Plugins/ZShell/Services/desktopstatemanager.cpp @@ -9,11 +9,12 @@ namespace ZShell::services { -DesktopStateManager::DesktopStateManager(QObject *parent) : QObject(parent) { -} +DesktopStateManager::DesktopStateManager(QObject* parent) : QObject(parent) {} QString DesktopStateManager::getConfigFilePath() const { - QString configDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + "/zshell"; + QString configDir = + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + + "/zshell"; QDir dir(configDir); if (!dir.exists()) { dir.mkpath("."); @@ -30,7 +31,8 @@ void DesktopStateManager::saveLayout(const QVariantMap& layout) { file.write(doc.toJson(QJsonDocument::Indented)); file.close(); } else { - qWarning() << "zshell: Cannot save desktop layout to" << getConfigFilePath(); + qWarning() << "zshell: Cannot save desktop layout to" + << getConfigFilePath(); } } diff --git a/Plugins/ZShell/Services/desktopstatemanager.hpp b/Plugins/ZShell/Services/desktopstatemanager.hpp index 6a0cdce..5c996ec 100644 --- a/Plugins/ZShell/Services/desktopstatemanager.hpp +++ b/Plugins/ZShell/Services/desktopstatemanager.hpp @@ -7,18 +7,18 @@ namespace ZShell::services { class DesktopStateManager : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -explicit DesktopStateManager(QObject *parent = nullptr); + public: + explicit DesktopStateManager(QObject* parent = nullptr); -Q_INVOKABLE void saveLayout(const QVariantMap& layout); -Q_INVOKABLE QVariantMap getLayout(); + Q_INVOKABLE void saveLayout(const QVariantMap& layout); + Q_INVOKABLE QVariantMap getLayout(); -private: -[[nodiscard]] QString getConfigFilePath() const; + private: + [[nodiscard]] QString getConfigFilePath() const; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/diskinfo.cpp b/Plugins/ZShell/Services/diskinfo.cpp index aca9bb8..dae8468 100644 --- a/Plugins/ZShell/Services/diskinfo.cpp +++ b/Plugins/ZShell/Services/diskinfo.cpp @@ -8,13 +8,17 @@ constexpr qreal kKib = 1024.0; } // namespace -DiskInfo::DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent) +DiskInfo::DiskInfo( + QString mount, + quint64 usedBytes, + quint64 totalBytes, + bool hasRoot, + QObject* parent) : QObject(parent) , m_mount(std::move(mount)) , m_usedBytes(usedBytes) , m_totalBytes(totalBytes) - , m_hasRoot(hasRoot) { -} + , m_hasRoot(hasRoot) {} QString DiskInfo::mount() const { return m_mount; @@ -29,12 +33,15 @@ qreal DiskInfo::total() const { } qreal DiskInfo::free() const { - const quint64 freeBytes = m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0; + const quint64 freeBytes = + m_totalBytes > m_usedBytes ? m_totalBytes - m_usedBytes : 0; return static_cast(freeBytes) / kKib; } qreal DiskInfo::perc() const { - return m_totalBytes > 0 ? static_cast(m_usedBytes) / static_cast(m_totalBytes) : 0.0; + return m_totalBytes > 0 ? static_cast(m_usedBytes) / + static_cast(m_totalBytes) + : 0.0; } bool DiskInfo::hasRoot() const { diff --git a/Plugins/ZShell/Services/diskinfo.hpp b/Plugins/ZShell/Services/diskinfo.hpp index 34acdff..3b51b0b 100644 --- a/Plugins/ZShell/Services/diskinfo.hpp +++ b/Plugins/ZShell/Services/diskinfo.hpp @@ -6,41 +6,46 @@ namespace ZShell::services { class DiskInfo : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("DiskInfo is created by DiskUsage") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("DiskInfo is created by DiskUsage") -Q_PROPERTY(QString mount READ mount CONSTANT) -Q_PROPERTY(qreal used READ used NOTIFY usedChanged) -Q_PROPERTY(qreal total READ total NOTIFY totalChanged) -Q_PROPERTY(qreal free READ free NOTIFY freeChanged) -Q_PROPERTY(qreal perc READ perc NOTIFY percChanged) -Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged) + Q_PROPERTY(QString mount READ mount CONSTANT) + Q_PROPERTY(qreal used READ used NOTIFY usedChanged) + Q_PROPERTY(qreal total READ total NOTIFY totalChanged) + Q_PROPERTY(qreal free READ free NOTIFY freeChanged) + Q_PROPERTY(qreal perc READ perc NOTIFY percChanged) + Q_PROPERTY(bool hasRoot READ hasRoot NOTIFY hasRootChanged) -public: -DiskInfo(QString mount, quint64 usedBytes, quint64 totalBytes, bool hasRoot, QObject* parent = nullptr); + public: + DiskInfo( + QString mount, + quint64 usedBytes, + quint64 totalBytes, + bool hasRoot, + QObject* parent = nullptr); -[[nodiscard]] QString mount() const; -[[nodiscard]] qreal used() const; -[[nodiscard]] qreal total() const; -[[nodiscard]] qreal free() const; -[[nodiscard]] qreal perc() const; -[[nodiscard]] bool hasRoot() const; + [[nodiscard]] QString mount() const; + [[nodiscard]] qreal used() const; + [[nodiscard]] qreal total() const; + [[nodiscard]] qreal free() const; + [[nodiscard]] qreal perc() const; + [[nodiscard]] bool hasRoot() const; -void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot); + void update(quint64 usedBytes, quint64 totalBytes, bool hasRoot); -signals: -void usedChanged(); -void totalChanged(); -void freeChanged(); -void percChanged(); -void hasRootChanged(); + signals: + void usedChanged(); + void totalChanged(); + void freeChanged(); + void percChanged(); + void hasRootChanged(); -private: -QString m_mount; -quint64 m_usedBytes; -quint64 m_totalBytes; -bool m_hasRoot; + private: + QString m_mount; + quint64 m_usedBytes; + quint64 m_totalBytes; + bool m_hasRoot; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/gpu.hpp b/Plugins/ZShell/Services/gpu.hpp index e8cf84d..4b91122 100644 --- a/Plugins/ZShell/Services/gpu.hpp +++ b/Plugins/ZShell/Services/gpu.hpp @@ -8,81 +8,81 @@ namespace ZShell::services { class Gpu : public TickingService { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -enum Type { - Auto, // user override is empty (config "") — defer to detected autoType - None, // no usable GPU - Nvidia, // queried via nvidia-smi - Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent -}; -Q_ENUM(Type) + public: + enum Type { + Auto, // user override is empty (config "") — defer to detected autoType + None, // no usable GPU + Nvidia, // queried via nvidia-smi + Generic, // queried via /sys/class/drm/card*/device/gpu_busy_percent + }; + Q_ENUM(Type) -private: -Q_PROPERTY(Type type READ type NOTIFY typeChanged) -Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged) -Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged) -Q_PROPERTY(QString name READ name NOTIFY nameChanged) -Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) -Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) -Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged) -Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged) + private: + Q_PROPERTY(Type type READ type NOTIFY typeChanged) + Q_PROPERTY(Type userType READ userType NOTIFY userTypeChanged) + Q_PROPERTY(Type autoType READ autoType NOTIFY autoTypeChanged) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) + Q_PROPERTY(qreal temperature READ temperature NOTIFY temperatureChanged) + Q_PROPERTY(qreal memoryUsed READ memoryUsed NOTIFY memoryUsedChanged) + Q_PROPERTY(qreal memoryTotal READ memoryTotal NOTIFY memoryTotalChanged) -public: -explicit Gpu(QObject* parent = nullptr); + public: + explicit Gpu(QObject* parent = nullptr); -[[nodiscard]] Type type() const; -[[nodiscard]] Type userType() const; -[[nodiscard]] Type autoType() const; -[[nodiscard]] QString name() const; -[[nodiscard]] qreal percentage() const; -[[nodiscard]] qreal temperature() const; -[[nodiscard]] qreal memoryUsed() const; -[[nodiscard]] qreal memoryTotal() const; + [[nodiscard]] Type type() const; + [[nodiscard]] Type userType() const; + [[nodiscard]] Type autoType() const; + [[nodiscard]] QString name() const; + [[nodiscard]] qreal percentage() const; + [[nodiscard]] qreal temperature() const; + [[nodiscard]] qreal memoryUsed() const; + [[nodiscard]] qreal memoryTotal() const; -signals: -void typeChanged(); -void userTypeChanged(); -void autoTypeChanged(); -void nameChanged(); -void percentageChanged(); -void temperatureChanged(); -void memoryUsedChanged(); -void memoryTotalChanged(); + signals: + void typeChanged(); + void userTypeChanged(); + void autoTypeChanged(); + void nameChanged(); + void percentageChanged(); + void temperatureChanged(); + void memoryUsedChanged(); + void memoryTotalChanged(); -protected: -void tick() override; + protected: + void tick() override; -private: -void detectTypeOnce(); -void detectNameOnce(); -void readGenericUsage(); -void startNvidiaUsage(); -void readGpuTemperature(); + private: + void detectTypeOnce(); + void detectNameOnce(); + void readGenericUsage(); + void startNvidiaUsage(); + void readGpuTemperature(); -void setUserType(Type value); -void setAutoType(Type value); -void setName(QString value); -void setMemoryUsed(qreal value); -void setMemoryTotal(qreal value); + void setUserType(Type value); + void setAutoType(Type value); + void setName(QString value); + void setMemoryUsed(qreal value); + void setMemoryTotal(qreal value); -[[nodiscard]] static Type parseType(const QString& s); -[[nodiscard]] static QString cleanName(QString s); + [[nodiscard]] static Type parseType(const QString& s); + [[nodiscard]] static QString cleanName(QString s); -Type m_userType = Auto; -Type m_autoType = None; -QString m_name; -qreal m_percentage = 0.0; -qreal m_temperature = 0.0; -qreal m_memoryUsed = 0.0; -qreal m_memoryTotal = 0.0; + Type m_userType = Auto; + Type m_autoType = None; + QString m_name; + qreal m_percentage = 0.0; + qreal m_temperature = 0.0; + qreal m_memoryUsed = 0.0; + qreal m_memoryTotal = 0.0; -QProcess* m_typeProc = nullptr; -QProcess* m_nameProc = nullptr; -QProcess* m_nvidiaProc = nullptr; + QProcess* m_typeProc = nullptr; + QProcess* m_nameProc = nullptr; + QProcess* m_nvidiaProc = nullptr; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/hyprsunsetmanager.cpp b/Plugins/ZShell/Services/hyprsunsetmanager.cpp index 40aa81f..0b33f6f 100644 --- a/Plugins/ZShell/Services/hyprsunsetmanager.cpp +++ b/Plugins/ZShell/Services/hyprsunsetmanager.cpp @@ -9,14 +9,14 @@ namespace ZShell::services { HyprsunsetManager::HyprsunsetManager(QObject* parent) : QObject(parent) { connect(&m_timer, &QTimer::timeout, this, &HyprsunsetManager::apply); connect(&m_manualTimer, &QTimer::timeout, this, [this] { - m_manualToggle = false; - emit manualToggleChanged(); - apply(); - }); + m_manualToggle = false; + emit manualToggleChanged(); + apply(); + }); connect(&m_startCooldown, &QTimer::timeout, this, [this] { - m_startAllowed = true; - apply(); - }); + m_startAllowed = true; + apply(); + }); m_startCooldown.start(2000); m_manualTimer.setSingleShot(true); @@ -51,16 +51,14 @@ int HyprsunsetManager::temp() const { } void HyprsunsetManager::setActiveAuto(bool activeAuto) { - if (activeAuto == m_activeAuto) - return; + if (activeAuto == m_activeAuto) return; m_activeAuto = activeAuto; emit activeAutoChanged(); } void HyprsunsetManager::setManualToggle(bool toggle) { - if (toggle == m_manualToggle) - return; + if (toggle == m_manualToggle) return; m_manualToggle = toggle; emit manualToggleChanged(); @@ -69,8 +67,7 @@ void HyprsunsetManager::setManualToggle(bool toggle) { } void HyprsunsetManager::setEndTime(const int& time) { - if (time == m_endTime) - return; + if (time == m_endTime) return; m_endTime = time; emit endTimeChanged(); @@ -78,8 +75,7 @@ void HyprsunsetManager::setEndTime(const int& time) { } void HyprsunsetManager::setStartTime(const int& time) { - if (time == m_startTime) - return; + if (time == m_startTime) return; m_startTime = time; emit startTimeChanged(); @@ -87,8 +83,7 @@ void HyprsunsetManager::setStartTime(const int& time) { } void HyprsunsetManager::setTemp(const int& temp) { - if (temp == m_temp) - return; + if (temp == m_temp) return; m_temp = temp; emit tempChanged(); @@ -104,8 +99,7 @@ void HyprsunsetManager::toggle() { } void HyprsunsetManager::start() { - if (m_enabled && m_initialized) - return; + if (m_enabled && m_initialized) return; m_initialized = true; m_enabled = true; @@ -113,13 +107,13 @@ void HyprsunsetManager::start() { emit enabledChanged(); m_process.setProgram("hyprctl"); - m_process.setArguments({"hyprsunset", "temperature", QString::number(m_temp)}); + m_process.setArguments( + {"hyprsunset", "temperature", QString::number(m_temp)}); m_process.startDetached(); } void HyprsunsetManager::end() { - if (!m_enabled && m_initialized) - return; + if (!m_enabled && m_initialized) return; m_initialized = true; m_enabled = false; @@ -132,12 +126,11 @@ void HyprsunsetManager::end() { } void HyprsunsetManager::apply() { - if (m_manualToggle || !m_activeAuto || !m_startAllowed) - return; + if (m_manualToggle || !m_activeAuto || !m_startAllowed) return; const auto current = QTime::currentTime(); const auto currentMin = current.hour() * 60 + current.minute(); - bool isDarkTime; + bool isDarkTime = false; if (m_startTime <= m_endTime) { isDarkTime = (currentMin >= m_startTime && currentMin < m_endTime); @@ -152,4 +145,4 @@ void HyprsunsetManager::apply() { } } -}; +}; // namespace ZShell::services diff --git a/Plugins/ZShell/Services/hyprsunsetmanager.hpp b/Plugins/ZShell/Services/hyprsunsetmanager.hpp index ebdbda9..7241c0d 100644 --- a/Plugins/ZShell/Services/hyprsunsetmanager.hpp +++ b/Plugins/ZShell/Services/hyprsunsetmanager.hpp @@ -10,57 +10,62 @@ namespace ZShell::services { class HyprsunsetManager : public QObject { -Q_OBJECT -QML_ELEMENT -Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) -Q_PROPERTY(int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) -Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) -Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged) -Q_PROPERTY(bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY activeAutoChanged) -Q_PROPERTY(bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY manualToggleChanged) + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) + Q_PROPERTY( + int startTime READ startTime WRITE setStartTime NOTIFY startTimeChanged) + Q_PROPERTY(int endTime READ endTime WRITE setEndTime NOTIFY endTimeChanged) + Q_PROPERTY(int temp READ temp WRITE setTemp NOTIFY tempChanged) + Q_PROPERTY( + bool activeAuto READ activeAuto WRITE setActiveAuto NOTIFY + activeAutoChanged) + Q_PROPERTY( + bool manualToggle READ manualToggle WRITE setManualToggle NOTIFY + manualToggleChanged) -public: -explicit HyprsunsetManager(QObject* parent = nullptr); + public: + explicit HyprsunsetManager(QObject* parent = nullptr); -[[nodiscard]] int startTime() const; -[[nodiscard]] int endTime() const; -[[nodiscard]] bool enabled() const; -[[nodiscard]] int temp() const; -[[nodiscard]] bool activeAuto() const; -[[nodiscard]] bool manualToggle() const; + [[nodiscard]] int startTime() const; + [[nodiscard]] int endTime() const; + [[nodiscard]] bool enabled() const; + [[nodiscard]] int temp() const; + [[nodiscard]] bool activeAuto() const; + [[nodiscard]] bool manualToggle() const; -Q_INVOKABLE void toggle(); -Q_INVOKABLE void apply(); + Q_INVOKABLE void toggle(); + Q_INVOKABLE void apply(); -void setStartTime(const int& time); -void setEndTime(const int& time); -void setTemp(const int& temp); -void setActiveAuto(bool activeAuto); -void setManualToggle(bool toggle); + void setStartTime(const int& time); + void setEndTime(const int& time); + void setTemp(const int& temp); + void setActiveAuto(bool activeAuto); + void setManualToggle(bool toggle); -signals: -void enabledChanged(); -void startTimeChanged(); -void activeAutoChanged(); -void endTimeChanged(); -void tempChanged(); -void manualToggleChanged(); + signals: + void enabledChanged(); + void startTimeChanged(); + void activeAutoChanged(); + void endTimeChanged(); + void tempChanged(); + void manualToggleChanged(); -private: -int m_startTime; -int m_endTime; -bool m_enabled = false; -bool m_manualToggle = false; -bool m_activeAuto; -bool m_startAllowed = false; -bool m_initialized = false; -QTimer m_startCooldown; -int m_temp; -QProcess m_process; -QTimer m_timer; -QTimer m_manualTimer; -void start(); -void end(); + private: + int m_startTime; + int m_endTime; + bool m_enabled = false; + bool m_manualToggle = false; + bool m_activeAuto; + bool m_startAllowed = false; + bool m_initialized = false; + QTimer m_startCooldown; + int m_temp; + QProcess m_process; + QTimer m_timer; + QTimer m_manualTimer; + void start(); + void end(); }; -}; +}; // namespace ZShell::services diff --git a/Plugins/ZShell/Services/memory.cpp b/Plugins/ZShell/Services/memory.cpp index 50f5f77..eb8020c 100644 --- a/Plugins/ZShell/Services/memory.cpp +++ b/Plugins/ZShell/Services/memory.cpp @@ -5,9 +5,7 @@ namespace ZShell::services { -Memory::Memory(QObject* parent) - : TickingService(parent) { -} +Memory::Memory(QObject* parent) : TickingService(parent) {} qreal Memory::used() const { return m_used; @@ -29,8 +27,10 @@ void Memory::tick() { const QByteArray data = f.readAll(); f.close(); - static const QRegularExpression reTotal(QStringLiteral("MemTotal: *(\\d+)")); - static const QRegularExpression reAvail(QStringLiteral("MemAvailable: *(\\d+)")); + static const QRegularExpression reTotal( + QStringLiteral("MemTotal: *(\\d+)")); + static const QRegularExpression reAvail( + QStringLiteral("MemAvailable: *(\\d+)")); const QString text = QString::fromLatin1(data); const auto totalMatch = reTotal.match(text); diff --git a/Plugins/ZShell/Services/memory.hpp b/Plugins/ZShell/Services/memory.hpp index f5d0fb6..e21bbc5 100644 --- a/Plugins/ZShell/Services/memory.hpp +++ b/Plugins/ZShell/Services/memory.hpp @@ -8,32 +8,32 @@ namespace ZShell::services { class Memory : public TickingService { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -Q_PROPERTY(qreal used READ used NOTIFY changed) -Q_PROPERTY(qreal total READ total NOTIFY changed) -Q_PROPERTY(qreal percentage READ percentage NOTIFY changed) + Q_PROPERTY(qreal used READ used NOTIFY changed) + Q_PROPERTY(qreal total READ total NOTIFY changed) + Q_PROPERTY(qreal percentage READ percentage NOTIFY changed) -public: -explicit Memory(QObject* parent = nullptr); + public: + explicit Memory(QObject* parent = nullptr); -[[nodiscard]] qreal used() const; -[[nodiscard]] qreal total() const; -[[nodiscard]] qreal percentage() const; + [[nodiscard]] qreal used() const; + [[nodiscard]] qreal total() const; + [[nodiscard]] qreal percentage() const; -signals: -void changed(); + signals: + void changed(); -protected: -void tick() override; + protected: + void tick() override; -private: -qreal m_used = 0.0; -qreal m_total = 1.0; -quint64 m_lastUsed = 0; -quint64 m_lastTotal = 0; + private: + qreal m_used = 0.0; + qreal m_total = 1.0; + quint64 m_lastUsed = 0; + quint64 m_lastTotal = 0; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/sensorslib.cpp b/Plugins/ZShell/Services/sensorslib.cpp index 995434a..2e81315 100644 --- a/Plugins/ZShell/Services/sensorslib.cpp +++ b/Plugins/ZShell/Services/sensorslib.cpp @@ -14,7 +14,7 @@ namespace ZShell::services::sensorslib { namespace { -std::atomic g_initOk{ false }; +std::atomic g_initOk{false}; std::once_flag g_initFlag; void doInit() { @@ -25,14 +25,16 @@ void doInit() { } g_initOk.store(true, std::memory_order_release); std::atexit([] { - if (g_initOk.load(std::memory_order_acquire)) { - sensors_cleanup(); - } - }); + if (g_initOk.load(std::memory_order_acquire)) { + sensors_cleanup(); + } + }); } -[[nodiscard]] std::optional readTempInput(const sensors_chip_name* chip, const sensors_feature* feat) { - const sensors_subfeature* sf = sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT); +[[nodiscard]] std::optional readTempInput( + const sensors_chip_name* chip, const sensors_feature* feat) { + const sensors_subfeature* sf = + sensors_get_subfeature(chip, feat, SENSORS_SUBFEATURE_TEMP_INPUT); if (!sf) { return std::nullopt; } @@ -43,7 +45,8 @@ void doInit() { return value; } -[[nodiscard]] QByteArray featureLabel(const sensors_chip_name* chip, const sensors_feature* feat) { +[[nodiscard]] QByteArray featureLabel( + const sensors_chip_name* chip, const sensors_feature* feat) { char* raw = sensors_get_label(chip, feat); if (!raw) { return {}; @@ -59,7 +62,8 @@ bool labelEquals(const QByteArray& label, const char* literal) { bool labelStartsWith(const QByteArray& label, const char* prefix) { const auto n = std::strlen(prefix); - return static_cast(label.size()) >= n && std::memcmp(label.constData(), prefix, n) == 0; + return static_cast(label.size()) >= n && + std::memcmp(label.constData(), prefix, n) == 0; } } // namespace @@ -74,13 +78,15 @@ std::optional cpuPackageTemp() { return std::nullopt; } - std::optional primary; // Package id N / Tdie + std::optional primary; // Package id N / Tdie std::optional fallback; // Tctl int chipNr = 0; - while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) { + while (const sensors_chip_name* chip = + sensors_get_detected_chips(nullptr, &chipNr)) { int featNr = 0; - while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) { + while (const sensors_feature* feat = + sensors_get_features(chip, &featNr)) { if (feat->type != SENSORS_FEATURE_TEMP) { continue; } @@ -89,7 +95,8 @@ std::optional cpuPackageTemp() { continue; } - if (labelStartsWith(label, "Package id ") || labelEquals(label, "Tdie")) { + if (labelStartsWith(label, "Package id ") || + labelEquals(label, "Tdie")) { if (auto v = readTempInput(chip, feat)) { primary = v; } @@ -116,13 +123,15 @@ std::optional gpuPciAverageTemp() { int countFallback = 0; int chipNr = 0; - while (const sensors_chip_name* chip = sensors_get_detected_chips(nullptr, &chipNr)) { + while (const sensors_chip_name* chip = + sensors_get_detected_chips(nullptr, &chipNr)) { if (chip->bus.type != SENSORS_BUS_TYPE_PCI) { continue; } int featNr = 0; - while (const sensors_feature* feat = sensors_get_features(chip, &featNr)) { + while (const sensors_feature* feat = + sensors_get_features(chip, &featNr)) { if (feat->type != SENSORS_FEATURE_TEMP) { continue; } @@ -131,10 +140,14 @@ std::optional gpuPciAverageTemp() { continue; } - const bool tempIndexed = labelStartsWith(label, "temp") && label.size() > 4 && - std::isdigit(static_cast(label[4])); - const bool isPrimary = tempIndexed || labelEquals(label, "GPU core") || labelEquals(label, "edge"); - const bool isFallback = labelEquals(label, "junction") || labelEquals(label, "mem"); + const bool tempIndexed = + labelStartsWith(label, "temp") && label.size() > 4 && + std::isdigit(static_cast(label[4])); + const bool isPrimary = tempIndexed || + labelEquals(label, "GPU core") || + labelEquals(label, "edge"); + const bool isFallback = labelEquals(label, "junction") || + labelEquals(label, "mem"); if (!isPrimary && !isFallback) { continue; diff --git a/Plugins/ZShell/Services/service.cpp b/Plugins/ZShell/Services/service.cpp index ef30d0a..6aa1e9f 100644 --- a/Plugins/ZShell/Services/service.cpp +++ b/Plugins/ZShell/Services/service.cpp @@ -5,9 +5,7 @@ namespace ZShell::services { -Service::Service(QObject* parent) - : QObject(parent) { -} +Service::Service(QObject* parent) : QObject(parent) {} void Service::ref(QObject* sender) { if (m_refs.isEmpty()) { diff --git a/Plugins/ZShell/Services/service.hpp b/Plugins/ZShell/Services/service.hpp index 3bb47b9..23679bb 100644 --- a/Plugins/ZShell/Services/service.hpp +++ b/Plugins/ZShell/Services/service.hpp @@ -6,19 +6,19 @@ namespace ZShell::services { class Service : public QObject { -Q_OBJECT + Q_OBJECT -public: -explicit Service(QObject* parent = nullptr); + public: + explicit Service(QObject* parent = nullptr); -void ref(QObject* sender); -void unref(QObject* sender); + void ref(QObject* sender); + void unref(QObject* sender); -private: -QSet m_refs; + private: + QSet m_refs; -virtual void start() = 0; -virtual void stop() = 0; + virtual void start() = 0; + virtual void stop() = 0; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/serviceref.cpp b/Plugins/ZShell/Services/serviceref.cpp index 69ae40c..42f9afd 100644 --- a/Plugins/ZShell/Services/serviceref.cpp +++ b/Plugins/ZShell/Services/serviceref.cpp @@ -5,8 +5,7 @@ namespace ZShell::services { ServiceRef::ServiceRef(Service* service, QObject* parent) - : QObject(parent) - , m_service(service) { + : QObject(parent), m_service(service) { if (m_service) { m_service->ref(this); } diff --git a/Plugins/ZShell/Services/serviceref.hpp b/Plugins/ZShell/Services/serviceref.hpp index 9fb9623..a0a5216 100644 --- a/Plugins/ZShell/Services/serviceref.hpp +++ b/Plugins/ZShell/Services/serviceref.hpp @@ -7,22 +7,24 @@ namespace ZShell::services { class ServiceRef : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(ZShell::services::Service* service READ service WRITE setService NOTIFY serviceChanged) + Q_PROPERTY( + ZShell::services::Service* service READ service WRITE setService NOTIFY + serviceChanged) -public: -explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr); + public: + explicit ServiceRef(Service* service = nullptr, QObject* parent = nullptr); -[[nodiscard]] Service* service() const; -void setService(Service* service); + [[nodiscard]] Service* service() const; + void setService(Service* service); -signals: -void serviceChanged(); + signals: + void serviceChanged(); -private: -QPointer m_service; + private: + QPointer m_service; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/storage.cpp b/Plugins/ZShell/Services/storage.cpp index f9973c6..4e9d52f 100644 --- a/Plugins/ZShell/Services/storage.cpp +++ b/Plugins/ZShell/Services/storage.cpp @@ -24,12 +24,14 @@ struct Accum { }; [[nodiscard]] QString sysfsRealPath(uint major, uint minor) { - const QString link = QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor); + const QString link = + QStringLiteral("/sys/dev/block/%1:%2").arg(major).arg(minor); const QString resolved = QFileInfo(link).canonicalFilePath(); return resolved; } -[[nodiscard]] bool readDevtFromSysfs(const QString& sysfsBlockDir, uint& major, uint& minor) { +[[nodiscard]] bool readDevtFromSysfs( + const QString& sysfsBlockDir, uint& major, uint& minor) { QFile f(sysfsBlockDir + QStringLiteral("/dev")); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return false; @@ -50,6 +52,7 @@ struct Accum { QStringList resolveByDevt(uint major, uint minor, int depth = 0); +// NOLINTNEXTLINE(misc-no-recursion) QStringList resolveAtNode(const QString& node, int depth) { if (node.isEmpty() || depth > 8) { return {}; @@ -62,18 +65,20 @@ QStringList resolveAtNode(const QString& node, int depth) { if (QFileInfo::exists(node + QStringLiteral("/partition"))) { const QString diskNode = nodeInfo.path(); - return { QFileInfo(diskNode).fileName() }; + return {QFileInfo(diskNode).fileName()}; } const QDir slavesDir(node + QStringLiteral("/slaves")); if (slavesDir.exists()) { - const QStringList slaves = slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + const QStringList slaves = + slavesDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); if (!slaves.isEmpty()) { QStringList out; for (const QString& slave : slaves) { uint sm = 0; uint sn = 0; - const QString slaveDir = QStringLiteral("/sys/class/block/") + slave; + const QString slaveDir = + QStringLiteral("/sys/class/block/") + slave; if (!readDevtFromSysfs(slaveDir, sm, sn)) { continue; } @@ -88,18 +93,17 @@ QStringList resolveAtNode(const QString& node, int depth) { } } - return { nodeInfo.fileName() }; + return {nodeInfo.fileName()}; } +// NOLINTNEXTLINE(misc-no-recursion) QStringList resolveByDevt(uint major, uint minor, int depth) { return resolveAtNode(sysfsRealPath(major, minor), depth); } } // namespace -Storage::Storage(QObject* parent) - : TickingService(parent) { -} +Storage::Storage(QObject* parent) : TickingService(parent) {} qreal Storage::percentage() const { qreal totalUsed = 0.0; @@ -124,7 +128,8 @@ bool Storage::sameOrder(const QList& a, const QList& b) { } QQmlListProperty Storage::disksProp() { - return QQmlListProperty(this, nullptr, &Storage::disksCount, &Storage::disksAt); + return QQmlListProperty( + this, nullptr, &Storage::disksCount, &Storage::disksAt); } qsizetype Storage::disksCount(QQmlListProperty* prop) { @@ -157,30 +162,11 @@ DiskInfo* Storage::primaryDisk() const { bool Storage::isPseudoFs(QByteArrayView fsType) { static constexpr const char* kPseudo[] = { - "tmpfs", - "devtmpfs", - "proc", - "sysfs", - "cgroup", - "cgroup2", - "overlay", - "squashfs", - "devpts", - "mqueue", - "ramfs", - "rpc_pipefs", - "autofs", - "configfs", - "debugfs", - "tracefs", - "securityfs", - "pstore", - "bpf", - "binfmt_misc", - "hugetlbfs", - "fusectl", - "efivarfs", - "selinuxfs", + "tmpfs", "devtmpfs", "proc", "sysfs", "cgroup", + "cgroup2", "overlay", "squashfs", "devpts", "mqueue", + "ramfs", "rpc_pipefs", "autofs", "configfs", "debugfs", + "tracefs", "securityfs", "pstore", "bpf", "binfmt_misc", + "hugetlbfs", "fusectl", "efivarfs", "selinuxfs", }; for (const char* p : kPseudo) { if (fsType == QByteArrayView(p)) { @@ -194,7 +180,7 @@ QStringList Storage::resolveToPhysicalDisks(const QString& devicePath) { if (devicePath.isEmpty() || !devicePath.startsWith(QLatin1Char('/'))) { return {}; } - struct stat st {}; + struct stat st{}; if (::stat(devicePath.toLocal8Bit().constData(), &st) != 0) { return {}; } @@ -232,7 +218,8 @@ void Storage::tick() { const QByteArray device = v.device(); const auto totalBytes = static_cast(v.bytesTotal()); const auto availBytes = static_cast(v.bytesAvailable()); - const quint64 usedBytes = totalBytes > availBytes ? totalBytes - availBytes : 0; + const quint64 usedBytes = + totalBytes > availBytes ? totalBytes - availBytes : 0; const bool isRoot = v.rootPath() == QStringLiteral("/"); DeviceEntry& e = byDevice[device]; @@ -244,7 +231,8 @@ void Storage::tick() { for (auto it = byDevice.constBegin(); it != byDevice.constEnd(); ++it) { const DeviceEntry& e = it.value(); - const QStringList disks = resolveToPhysicalDisks(QString::fromLocal8Bit(e.device)); + const QStringList disks = + resolveToPhysicalDisks(QString::fromLocal8Bit(e.device)); if (disks.isEmpty()) { continue; } @@ -269,14 +257,23 @@ void Storage::tick() { next.reserve(byDisk.size()); for (auto it = byDisk.constBegin(); it != byDisk.constEnd(); ++it) { if (DiskInfo* survivor = existing.take(it.key())) { - survivor->update(it.value().usedBytes, it.value().totalBytes, it.value().hasRoot); + survivor->update( + it.value().usedBytes, + it.value().totalBytes, + it.value().hasRoot); next.append(survivor); } else { - next.append(new DiskInfo(it.key(), it.value().usedBytes, it.value().totalBytes, it.value().hasRoot, this)); + next.append(new DiskInfo( + it.key(), + it.value().usedBytes, + it.value().totalBytes, + it.value().hasRoot, + this)); } } - std::sort(next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) { + std::sort( + next.begin(), next.end(), [](const DiskInfo* a, const DiskInfo* b) { if (a->hasRoot() != b->hasRoot()) { return a->hasRoot(); } @@ -284,7 +281,8 @@ void Storage::tick() { }); bool manualCleared = false; - if (DiskInfo* m = m_manualPrimaryDisk.data(); m && existing.contains(m->mount())) { + if (DiskInfo* m = m_manualPrimaryDisk.data(); + m && existing.contains(m->mount())) { m_manualPrimaryDisk.clear(); manualCleared = true; } diff --git a/Plugins/ZShell/Services/storage.hpp b/Plugins/ZShell/Services/storage.hpp index 2138c04..0c61466 100644 --- a/Plugins/ZShell/Services/storage.hpp +++ b/Plugins/ZShell/Services/storage.hpp @@ -12,44 +12,51 @@ namespace ZShell::services { class Storage : public TickingService { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) -Q_PROPERTY(QQmlListProperty disks READ disksProp NOTIFY disksChanged) -Q_PROPERTY(ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk WRITE setManualPrimaryDisk NOTIFY - manualPrimaryDiskChanged) -Q_PROPERTY(ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY primaryDiskChanged) + Q_PROPERTY(qreal percentage READ percentage NOTIFY percentageChanged) + Q_PROPERTY( + QQmlListProperty disks READ disksProp NOTIFY + disksChanged) + Q_PROPERTY( + ZShell::services::DiskInfo* manualPrimaryDisk READ manualPrimaryDisk + WRITE setManualPrimaryDisk NOTIFY manualPrimaryDiskChanged) + Q_PROPERTY( + ZShell::services::DiskInfo* primaryDisk READ primaryDisk NOTIFY + primaryDiskChanged) -public: -explicit Storage(QObject* parent = nullptr); + public: + explicit Storage(QObject* parent = nullptr); -[[nodiscard]] qreal percentage() const; -[[nodiscard]] QQmlListProperty disksProp(); -[[nodiscard]] DiskInfo* manualPrimaryDisk() const; -void setManualPrimaryDisk(DiskInfo* disk); -[[nodiscard]] DiskInfo* primaryDisk() const; + [[nodiscard]] qreal percentage() const; + [[nodiscard]] QQmlListProperty disksProp(); + [[nodiscard]] DiskInfo* manualPrimaryDisk() const; + void setManualPrimaryDisk(DiskInfo* disk); + [[nodiscard]] DiskInfo* primaryDisk() const; -signals: -void disksChanged(); -void percentageChanged(); -void manualPrimaryDiskChanged(); -void primaryDiskChanged(); + signals: + void disksChanged(); + void percentageChanged(); + void manualPrimaryDiskChanged(); + void primaryDiskChanged(); -protected: -void tick() override; + protected: + void tick() override; -private: -[[nodiscard]] static QStringList resolveToPhysicalDisks(const QString& devicePath); -[[nodiscard]] static bool isPseudoFs(QByteArrayView fsType); -[[nodiscard]] static bool sameOrder(const QList& a, const QList& b); + private: + [[nodiscard]] static QStringList resolveToPhysicalDisks( + const QString& devicePath); + [[nodiscard]] static bool isPseudoFs(QByteArrayView fsType); + [[nodiscard]] static bool sameOrder( + const QList& a, const QList& b); -static qsizetype disksCount(QQmlListProperty* prop); -static DiskInfo* disksAt(QQmlListProperty* prop, qsizetype i); + static qsizetype disksCount(QQmlListProperty* prop); + static DiskInfo* disksAt(QQmlListProperty* prop, qsizetype i); -QList m_disks; -QPointer m_manualPrimaryDisk; + QList m_disks; + QPointer m_manualPrimaryDisk; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/tickingservice.hpp b/Plugins/ZShell/Services/tickingservice.hpp index 95124eb..324707f 100644 --- a/Plugins/ZShell/Services/tickingservice.hpp +++ b/Plugins/ZShell/Services/tickingservice.hpp @@ -6,30 +6,31 @@ namespace ZShell::services { class TickingService : public Service { -Q_OBJECT + Q_OBJECT -Q_PROPERTY(int updateInterval READ updateInterval NOTIFY updateIntervalChanged) + Q_PROPERTY( + int updateInterval READ updateInterval NOTIFY updateIntervalChanged) -public: -explicit TickingService(QObject* parent = nullptr); + public: + explicit TickingService(QObject* parent = nullptr); -[[nodiscard]] int updateInterval() const; + [[nodiscard]] int updateInterval() const; -signals: -void updateIntervalChanged(); + signals: + void updateIntervalChanged(); -protected: -void start() final; -void stop() final; + protected: + void start() final; + void stop() final; -virtual void tick() = 0; + virtual void tick() = 0; -private: -void applyInterval(int ms); + private: + void applyInterval(int ms); -QTimer* m_timer; -int m_interval = 1000; -bool m_running = false; + QTimer* m_timer; + int m_interval = 1000; + bool m_running = false; }; } // namespace ZShell::services diff --git a/Plugins/ZShell/Services/usagefmt.cpp b/Plugins/ZShell/Services/usagefmt.cpp index e455fe8..1012da1 100644 --- a/Plugins/ZShell/Services/usagefmt.cpp +++ b/Plugins/ZShell/Services/usagefmt.cpp @@ -16,18 +16,18 @@ namespace ZShell::services::usagefmt { FormatResult UsageFmt::formatKib(qreal kib, qreal total) const { if (!finitePositive(kib) || !finitePositive(total)) { - return { 0.0, 0.0, "KiB" }; + return {0.0, 0.0, "KiB"}; } if (total >= kGib) { - return { kib / kGib, total / kGib, "TiB" }; + return {kib / kGib, total / kGib, "TiB"}; } if (total >= kMib) { - return { kib / kMib, total / kMib, "GiB" }; + return {kib / kMib, total / kMib, "GiB"}; } if (total >= kKib) { - return { kib / kKib, total / kKib, "MiB" }; + return {kib / kKib, total / kKib, "MiB"}; } - return { kib, total, "KiB" }; + return {kib, total, "KiB"}; } } // namespace ZShell::services::usagefmt diff --git a/Plugins/ZShell/Services/usagefmt.hpp b/Plugins/ZShell/Services/usagefmt.hpp index 97d38b7..ca910d4 100644 --- a/Plugins/ZShell/Services/usagefmt.hpp +++ b/Plugins/ZShell/Services/usagefmt.hpp @@ -15,19 +15,20 @@ struct FormatResult { Q_PROPERTY(qreal total MEMBER total CONSTANT) Q_PROPERTY(QString unit MEMBER unit CONSTANT) -public: + public: qreal value; qreal total; QString unit; }; class UsageFmt : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -Q_INVOKABLE [[nodiscard]] FormatResult formatKib(qreal kib, qreal total) const; + public: + Q_INVOKABLE [[nodiscard]] FormatResult formatKib( + qreal kib, qreal total) const; }; } // namespace ZShell::services::usagefmt diff --git a/Plugins/ZShell/appdb.cpp b/Plugins/ZShell/appdb.cpp index 8614df3..a62cf59 100644 --- a/Plugins/ZShell/appdb.cpp +++ b/Plugins/ZShell/appdb.cpp @@ -7,259 +7,271 @@ 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 = metaObject(); - 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(); } 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(); + auto* app = m_apps.value(id); + if (app) { + const auto before = getSortedApps(); - app->incrementFrequency(); + app->incrementFrequency(); - if (before != getSortedApps()) { - emit appsChanged(); - } - } else { - qWarning() << "AppDb::incrementFrequency: could not find app with id" << id; - } + if (before != getSortedApps()) { + emit appsChanged(); + } + } else { + qWarning() << "AppDb::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(); + 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; } 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(); - } + if (before != getSortedApps()) { + 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.keyBegin(); it != m_apps.keyEnd(); ++it) { + const auto& id = *it; + if (!newIds.contains(id)) { + dirty = true; + m_apps.take(id)->deleteLater(); + } + } - if (dirty) { - emit appsChanged(); - } + if (dirty) { + emit appsChanged(); + } } } // namespace ZShell diff --git a/Plugins/ZShell/appdb.hpp b/Plugins/ZShell/appdb.hpp index 48c7187..00a9ece 100644 --- a/Plugins/ZShell/appdb.hpp +++ b/Plugins/ZShell/appdb.hpp @@ -9,98 +9,102 @@ namespace ZShell { class AppEntry : public QObject { -Q_OBJECT -QML_ELEMENT - QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb") -// The actual DesktopEntry, but we don't have access to the type so it's a QObject -Q_PROPERTY(QObject* entry READ entry CONSTANT) + // The actual DesktopEntry, but we don't have access to the type so it's a QObject + Q_PROPERTY(QObject* entry READ entry CONSTANT) -Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged) -Q_PROPERTY(QString id READ id CONSTANT) -Q_PROPERTY(QString name READ name NOTIFY nameChanged) -Q_PROPERTY(QString comment READ comment NOTIFY commentChanged) -Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged) -Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged) -Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged) -Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged) -Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged) + Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged) + Q_PROPERTY(QString id READ id CONSTANT) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString comment READ comment NOTIFY commentChanged) + Q_PROPERTY(QString execString READ execString NOTIFY execStringChanged) + Q_PROPERTY(QString startupClass READ startupClass NOTIFY startupClassChanged) + Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged) + Q_PROPERTY(QString categories READ categories NOTIFY categoriesChanged) + Q_PROPERTY(QString keywords READ keywords NOTIFY keywordsChanged) -public: -explicit AppEntry(QObject* entry, quint32 frequency, QObject* parent = nullptr); + public: + explicit AppEntry( + QObject* entry, quint32 frequency, QObject* parent = nullptr); -[[nodiscard]] QObject* entry() const; + [[nodiscard]] QObject* entry() const; -[[nodiscard]] quint32 frequency() const; -void setFrequency(quint32 frequency); -void incrementFrequency(); + [[nodiscard]] quint32 frequency() const; + void setFrequency(quint32 frequency); + void incrementFrequency(); -[[nodiscard]] QString id() const; -[[nodiscard]] QString name() const; -[[nodiscard]] QString comment() const; -[[nodiscard]] QString execString() const; -[[nodiscard]] QString startupClass() const; -[[nodiscard]] QString genericName() const; -[[nodiscard]] QString categories() const; -[[nodiscard]] QString keywords() const; + [[nodiscard]] QString id() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString comment() const; + [[nodiscard]] QString execString() const; + [[nodiscard]] QString startupClass() const; + [[nodiscard]] QString genericName() const; + [[nodiscard]] QString categories() const; + [[nodiscard]] QString keywords() const; -signals: -void frequencyChanged(); -void nameChanged(); -void commentChanged(); -void execStringChanged(); -void startupClassChanged(); -void genericNameChanged(); -void categoriesChanged(); -void keywordsChanged(); + signals: + void frequencyChanged(); + void nameChanged(); + void commentChanged(); + void execStringChanged(); + void startupClassChanged(); + void genericNameChanged(); + void categoriesChanged(); + void keywordsChanged(); -private: -QObject* m_entry; -quint32 m_frequency; + private: + QObject* m_entry; + quint32 m_frequency; }; class AppDb : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + 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(QQmlListProperty apps READ apps NOTIFY appsChanged) + 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( + QQmlListProperty apps READ apps NOTIFY appsChanged) -public: -explicit AppDb(QObject* parent = nullptr); + public: + explicit AppDb(QObject* parent = nullptr); -[[nodiscard]] QString uuid() const; + [[nodiscard]] QString uuid() const; -[[nodiscard]] QString path() const; -void setPath(const QString& path); + [[nodiscard]] QString path() const; + void setPath(const QString& path); -[[nodiscard]] QObjectList entries() const; -void setEntries(const QObjectList& entries); + [[nodiscard]] QObjectList entries() const; + void setEntries(const QObjectList& entries); -[[nodiscard]] QQmlListProperty apps(); + [[nodiscard]] QQmlListProperty apps(); -Q_INVOKABLE void incrementFrequency(const QString& id); + Q_INVOKABLE void incrementFrequency(const QString& id); -signals: -void pathChanged(); -void entriesChanged(); -void appsChanged(); + signals: + void pathChanged(); + void entriesChanged(); + void appsChanged(); -private: -QTimer* m_timer; + private: + QTimer* m_timer; -const QString m_uuid; -QString m_path; -QObjectList m_entries; -QHash m_apps; -mutable QList m_sortedApps; + const QString m_uuid; + QString m_path; + QObjectList m_entries; + QHash m_apps; + mutable QList m_sortedApps; -QList& getSortedApps() const; -quint32 getFrequency(const QString& id) const; -void updateAppFrequencies(); -void updateApps(); + QList& getSortedApps() const; + quint32 getFrequency(const QString& id) const; + void updateAppFrequencies(); + void updateApps(); }; } // namespace ZShell diff --git a/Plugins/ZShell/imageanalyser.cpp b/Plugins/ZShell/imageanalyser.cpp index 08e3eec..cbab4b5 100644 --- a/Plugins/ZShell/imageanalyser.cpp +++ b/Plugins/ZShell/imageanalyser.cpp @@ -9,215 +9,255 @@ namespace ZShell { ImageAnalyser::ImageAnalyser(QObject* parent) - : QObject(parent) - , m_futureWatcher(new QFutureWatcher(this)) - , m_source("") - , m_sourceItem(nullptr) - , m_rescaleSize(128) - , m_dominantColour(0, 0, 0) - , m_luminance(0) { - QObject::connect(m_futureWatcher, &QFutureWatcher::finished, this, [this]() { - if (!m_futureWatcher->future().isResultReadyAt(0)) { - return; - } + : QObject(parent) + , m_futureWatcher(new QFutureWatcher(this)) + , m_source("") + , m_sourceItem(nullptr) + , m_rescaleSize(128) + , m_dominantColour(0, 0, 0) + , m_luminance(0) { + QObject::connect( + m_futureWatcher, + &QFutureWatcher::finished, + this, + [this]() { + if (!m_futureWatcher->future().isResultReadyAt(0)) { + return; + } - const auto result = m_futureWatcher->result(); - if (m_dominantColour != result.first) { - m_dominantColour = result.first; - emit dominantColourChanged(); - } - if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) { - m_luminance = result.second; - emit luminanceChanged(); - } - }); + const auto result = m_futureWatcher->result(); + if (m_dominantColour != result.first) { + m_dominantColour = result.first; + emit dominantColourChanged(); + } + if (!qFuzzyCompare(m_luminance + 1.0, result.second + 1.0)) { + m_luminance = result.second; + emit luminanceChanged(); + } + }); } QString ImageAnalyser::source() const { - return m_source; + return m_source; } void ImageAnalyser::setSource(const QString& source) { - if (m_source == source) { - return; - } + if (m_source == source) { + return; + } - m_source = source; - emit sourceChanged(); + m_source = source; + emit sourceChanged(); - if (m_sourceItem) { - m_sourceItem = nullptr; - emit sourceItemChanged(); - } + if (m_sourceItem) { + m_sourceItem = nullptr; + emit sourceItemChanged(); + } - requestUpdate(); + requestUpdate(); } QQuickItem* ImageAnalyser::sourceItem() const { - return m_sourceItem; + return m_sourceItem; } void ImageAnalyser::setSourceItem(QQuickItem* sourceItem) { - if (m_sourceItem == sourceItem) { - return; - } + if (m_sourceItem == sourceItem) { + return; + } - m_sourceItem = sourceItem; - emit sourceItemChanged(); + m_sourceItem = sourceItem; + emit sourceItemChanged(); - if (!m_source.isEmpty()) { - m_source = ""; - emit sourceChanged(); - } + if (!m_source.isEmpty()) { + m_source = ""; + emit sourceChanged(); + } - requestUpdate(); + requestUpdate(); } int ImageAnalyser::rescaleSize() const { - return m_rescaleSize; + return m_rescaleSize; } void ImageAnalyser::setRescaleSize(int rescaleSize) { - if (m_rescaleSize == rescaleSize) { - return; - } + if (m_rescaleSize == rescaleSize) { + return; + } - m_rescaleSize = rescaleSize; - emit rescaleSizeChanged(); + m_rescaleSize = rescaleSize; + emit rescaleSizeChanged(); - requestUpdate(); + requestUpdate(); } QColor ImageAnalyser::dominantColour() const { - return m_dominantColour; + return m_dominantColour; } qreal ImageAnalyser::luminance() const { - return m_luminance; + return m_luminance; } void ImageAnalyser::requestUpdate() { - if (m_source.isEmpty() && !m_sourceItem) { - return; - } + if (m_source.isEmpty() && !m_sourceItem) { + return; + } - if (!m_sourceItem || (m_sourceItem->window() && m_sourceItem->window()->isVisible() && m_sourceItem->width() > 0 && - m_sourceItem->height() > 0)) { - update(); - } else if (m_sourceItem) { - if (!m_sourceItem->window()) { - QObject::connect(m_sourceItem, &QQuickItem::windowChanged, this, &ImageAnalyser::requestUpdate, - Qt::SingleShotConnection); - } else if (!m_sourceItem->window()->isVisible()) { - QObject::connect(m_sourceItem->window(), &QQuickWindow::visibleChanged, this, &ImageAnalyser::requestUpdate, - Qt::SingleShotConnection); - } - if (m_sourceItem->width() <= 0) { - QObject::connect( - m_sourceItem, &QQuickItem::widthChanged, this, &ImageAnalyser::requestUpdate, Qt::SingleShotConnection); - } - if (m_sourceItem->height() <= 0) { - QObject::connect(m_sourceItem, &QQuickItem::heightChanged, this, &ImageAnalyser::requestUpdate, - Qt::SingleShotConnection); - } - } + if (!m_sourceItem || + (m_sourceItem->window() && m_sourceItem->window()->isVisible() && + m_sourceItem->width() > 0 && m_sourceItem->height() > 0)) { + update(); + } else if (m_sourceItem) { + if (!m_sourceItem->window()) { + QObject::connect( + m_sourceItem, + &QQuickItem::windowChanged, + this, + &ImageAnalyser::requestUpdate, + Qt::SingleShotConnection); + } else if (!m_sourceItem->window()->isVisible()) { + QObject::connect( + m_sourceItem->window(), + &QQuickWindow::visibleChanged, + this, + &ImageAnalyser::requestUpdate, + Qt::SingleShotConnection); + } + if (m_sourceItem->width() <= 0) { + QObject::connect( + m_sourceItem, + &QQuickItem::widthChanged, + this, + &ImageAnalyser::requestUpdate, + Qt::SingleShotConnection); + } + if (m_sourceItem->height() <= 0) { + QObject::connect( + m_sourceItem, + &QQuickItem::heightChanged, + this, + &ImageAnalyser::requestUpdate, + Qt::SingleShotConnection); + } + } } void ImageAnalyser::update() { - if (m_source.isEmpty() && !m_sourceItem) { - return; - } + if (m_source.isEmpty() && !m_sourceItem) { + return; + } - if (m_futureWatcher->isRunning()) { - m_futureWatcher->cancel(); - } + if (m_futureWatcher->isRunning()) { + m_futureWatcher->cancel(); + } - if (m_sourceItem) { - const QSharedPointer grabResult = m_sourceItem->grabToImage(); - QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, [grabResult, this]() { - m_futureWatcher->setFuture(QtConcurrent::run(&ImageAnalyser::analyse, grabResult->image(), m_rescaleSize)); - }); - } else { - m_futureWatcher->setFuture(QtConcurrent::run([=, this](QPromise& promise) { - const QImage image(m_source); - analyse(promise, image, m_rescaleSize); - })); - } + if (m_sourceItem) { + const QSharedPointer grabResult = + m_sourceItem->grabToImage(); + QObject::connect( + grabResult.data(), + &QQuickItemGrabResult::ready, + this, + [grabResult, this]() { + m_futureWatcher->setFuture( + QtConcurrent::run( + &ImageAnalyser::analyse, + grabResult->image(), + m_rescaleSize)); + }); + } else { + m_futureWatcher->setFuture( + QtConcurrent::run([=, this](QPromise& promise) { + const QImage image(m_source); + analyse(promise, image, m_rescaleSize); + })); + } } -void ImageAnalyser::analyse(QPromise& promise, const QImage& image, int rescaleSize) { - if (image.isNull()) { - qWarning() << "ImageAnalyser::analyse: image is null"; - return; - } +void ImageAnalyser::analyse( + QPromise& promise, const QImage& image, int rescaleSize) { + if (image.isNull()) { + qWarning() << "ImageAnalyser::analyse: image is null"; + return; + } - QImage img = image; + QImage img = image; - if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) { - img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation); - } + if (rescaleSize > 0 && + (img.width() > rescaleSize || img.height() > rescaleSize)) { + img = img.scaled( + rescaleSize, + rescaleSize, + Qt::KeepAspectRatio, + Qt::FastTransformation); + } - if (promise.isCanceled()) { - return; - } + if (promise.isCanceled()) { + return; + } - if (img.format() != QImage::Format_ARGB32) { - img = img.convertToFormat(QImage::Format_ARGB32); - } + if (img.format() != QImage::Format_ARGB32) { + img = img.convertToFormat(QImage::Format_ARGB32); + } - if (promise.isCanceled()) { - return; - } + if (promise.isCanceled()) { + return; + } - const uchar* data = img.bits(); - const int width = img.width(); - const int height = img.height(); - const qsizetype bytesPerLine = img.bytesPerLine(); + const uchar* data = img.bits(); + const int width = img.width(); + const int height = img.height(); + const qsizetype bytesPerLine = img.bytesPerLine(); - std::unordered_map colours; - qreal totalLuminance = 0.0; - int count = 0; + std::unordered_map colours; + qreal totalLuminance = 0.0; + int count = 0; - for (int y = 0; y < height; ++y) { - const uchar* line = data + y * bytesPerLine; - for (int x = 0; x < width; ++x) { - if (promise.isCanceled()) { - return; - } + for (int y = 0; y < height; ++y) { + const uchar* line = data + y * bytesPerLine; + for (int x = 0; x < width; ++x) { + if (promise.isCanceled()) { + return; + } - const uchar* pixel = line + x * 4; + const uchar* pixel = line + x * 4; - if (pixel[3] == 0) { - continue; - } + if (pixel[3] == 0) { + continue; + } - const quint32 mr = static_cast(pixel[0] & 0xF8); - const quint32 mg = static_cast(pixel[1] & 0xF8); - const quint32 mb = static_cast(pixel[2] & 0xF8); - ++colours[(mr << 16) | (mg << 8) | mb]; + const quint32 mr = static_cast(pixel[0] & 0xF8); + const quint32 mg = static_cast(pixel[1] & 0xF8); + const quint32 mb = static_cast(pixel[2] & 0xF8); + ++colours[(mr << 16) | (mg << 8) | mb]; - const qreal r = pixel[0] / 255.0; - const qreal g = pixel[1] / 255.0; - const qreal b = pixel[2] / 255.0; - totalLuminance += std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b); - ++count; - } - } + const qreal r = pixel[0] / 255.0; + const qreal g = pixel[1] / 255.0; + const qreal b = pixel[2] / 255.0; + totalLuminance += + std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b); + ++count; + } + } - quint32 dominantColour = 0; - int maxCount = 0; - for (const auto& [colour, colourCount] : colours) { - if (promise.isCanceled()) { - return; - } + quint32 dominantColour = 0; + int maxCount = 0; + for (const auto& [colour, colourCount] : colours) { + if (promise.isCanceled()) { + return; + } - if (colourCount > maxCount) { - dominantColour = colour; - maxCount = colourCount; - } - } + if (colourCount > maxCount) { + dominantColour = colour; + maxCount = colourCount; + } + } - promise.addResult(qMakePair(QColor((0xFFu << 24) | dominantColour), count == 0 ? 0.0 : totalLuminance / count)); + promise.addResult(qMakePair( + QColor((0xFFu << 24) | dominantColour), + count == 0 ? 0.0 : totalLuminance / count)); } } // namespace ZShell diff --git a/Plugins/ZShell/imageanalyser.hpp b/Plugins/ZShell/imageanalyser.hpp index 8871617..1c7a893 100644 --- a/Plugins/ZShell/imageanalyser.hpp +++ b/Plugins/ZShell/imageanalyser.hpp @@ -9,53 +9,59 @@ namespace ZShell { class ImageAnalyser : public QObject { -Q_OBJECT -QML_ELEMENT + Q_OBJECT + QML_ELEMENT -Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) -Q_PROPERTY(QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY sourceItemChanged) -Q_PROPERTY(int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged) -Q_PROPERTY(QColor dominantColour READ dominantColour NOTIFY dominantColourChanged) -Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged) + Q_PROPERTY(QString source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY( + QQuickItem* sourceItem READ sourceItem WRITE setSourceItem NOTIFY + sourceItemChanged) + Q_PROPERTY( + int rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY + rescaleSizeChanged) + Q_PROPERTY( + QColor dominantColour READ dominantColour NOTIFY dominantColourChanged) + Q_PROPERTY(qreal luminance READ luminance NOTIFY luminanceChanged) -public: -explicit ImageAnalyser(QObject* parent = nullptr); + public: + explicit ImageAnalyser(QObject* parent = nullptr); -[[nodiscard]] QString source() const; -void setSource(const QString& source); + [[nodiscard]] QString source() const; + void setSource(const QString& source); -[[nodiscard]] QQuickItem* sourceItem() const; -void setSourceItem(QQuickItem* sourceItem); + [[nodiscard]] QQuickItem* sourceItem() const; + void setSourceItem(QQuickItem* sourceItem); -[[nodiscard]] int rescaleSize() const; -void setRescaleSize(int rescaleSize); + [[nodiscard]] int rescaleSize() const; + void setRescaleSize(int rescaleSize); -[[nodiscard]] QColor dominantColour() const; -[[nodiscard]] qreal luminance() const; + [[nodiscard]] QColor dominantColour() const; + [[nodiscard]] qreal luminance() const; -Q_INVOKABLE void requestUpdate(); + Q_INVOKABLE void requestUpdate(); -signals: -void sourceChanged(); -void sourceItemChanged(); -void rescaleSizeChanged(); -void dominantColourChanged(); -void luminanceChanged(); + signals: + void sourceChanged(); + void sourceItemChanged(); + void rescaleSizeChanged(); + void dominantColourChanged(); + void luminanceChanged(); -private: -using AnalyseResult = QPair; + private: + using AnalyseResult = QPair; -QFutureWatcher* const m_futureWatcher; + QFutureWatcher* const m_futureWatcher; -QString m_source; -QQuickItem* m_sourceItem; -int m_rescaleSize; + QString m_source; + QQuickItem* m_sourceItem; + int m_rescaleSize; -QColor m_dominantColour; -qreal m_luminance; + QColor m_dominantColour; + qreal m_luminance; -void update(); -static void analyse(QPromise& promise, const QImage& image, int rescaleSize); + void update(); + static void analyse( + QPromise& promise, const QImage& image, int rescaleSize); }; } // namespace ZShell diff --git a/Plugins/ZShell/qalculator.cpp b/Plugins/ZShell/qalculator.cpp index c58962f..944449e 100644 --- a/Plugins/ZShell/qalculator.cpp +++ b/Plugins/ZShell/qalculator.cpp @@ -4,8 +4,7 @@ namespace ZShell { -Qalculator::Qalculator(QObject* parent) - : QObject(parent) { +Qalculator::Qalculator(QObject* parent) : QObject(parent) { if (!CALCULATOR) { new Calculator(); CALCULATOR->loadExchangeRates(); @@ -24,7 +23,11 @@ QString Qalculator::eval(const QString& expr, bool printExpr) const { std::string parsed; std::string result = CALCULATOR->calculateAndPrint( - CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options), 100, eo, po, &parsed); + CALCULATOR->unlocalizeExpression(expr.toStdString(), eo.parse_options), + 100, + eo, + po, + &parsed); std::string error; while (CALCULATOR->message()) { diff --git a/Plugins/ZShell/qalculator.hpp b/Plugins/ZShell/qalculator.hpp index 67051a6..e3380ed 100644 --- a/Plugins/ZShell/qalculator.hpp +++ b/Plugins/ZShell/qalculator.hpp @@ -6,14 +6,15 @@ namespace ZShell { class Qalculator : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -explicit Qalculator(QObject* parent = nullptr); + public: + explicit Qalculator(QObject* parent = nullptr); -Q_INVOKABLE [[nodiscard]] QString eval(const QString& expr, bool printExpr = true) const; + Q_INVOKABLE [[nodiscard]] QString eval( + const QString& expr, bool printExpr = true) const; }; } // namespace ZShell diff --git a/Plugins/ZShell/requests.cpp b/Plugins/ZShell/requests.cpp index 3b8130c..f6e7e7e 100644 --- a/Plugins/ZShell/requests.cpp +++ b/Plugins/ZShell/requests.cpp @@ -7,9 +7,7 @@ namespace ZShell { Requests::Requests(QObject* parent) - : QObject(parent) - , m_manager(new QNetworkAccessManager(this)) { -} + : QObject(parent), m_manager(new QNetworkAccessManager(this)) {} void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const { if (!onSuccess.isCallable()) { @@ -20,13 +18,15 @@ void Requests::get(const QUrl& url, QJSValue onSuccess, QJSValue onError) const QNetworkRequest request(url); auto reply = m_manager->get(request); - QObject::connect(reply, &QNetworkReply::finished, [reply, onSuccess, onError]() { + QObject::connect( + reply, &QNetworkReply::finished, [reply, onSuccess, onError]() { if (reply->error() == QNetworkReply::NoError) { - onSuccess.call({ QString(reply->readAll()) }); + onSuccess.call({QString(reply->readAll())}); } else if (onError.isCallable()) { - onError.call({ reply->errorString() }); + onError.call({reply->errorString()}); } else { - qWarning() << "Requests::get: request failed with error" << reply->errorString(); + qWarning() << "Requests::get: request failed with error" + << reply->errorString(); } reply->deleteLater(); diff --git a/Plugins/ZShell/requests.hpp b/Plugins/ZShell/requests.hpp index 40e751b..6e340d4 100644 --- a/Plugins/ZShell/requests.hpp +++ b/Plugins/ZShell/requests.hpp @@ -7,17 +7,20 @@ namespace ZShell { class Requests : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -explicit Requests(QObject* parent = nullptr); + public: + explicit Requests(QObject* parent = nullptr); -Q_INVOKABLE void get(const QUrl& url, QJSValue callback, QJSValue onError = QJSValue()) const; + Q_INVOKABLE void get( + const QUrl& url, + QJSValue callback, + QJSValue onError = QJSValue()) const; -private: -QNetworkAccessManager* m_manager; + private: + QNetworkAccessManager* m_manager; }; } // namespace ZShell diff --git a/Plugins/ZShell/toaster.cpp b/Plugins/ZShell/toaster.cpp index cc6088c..a8aa59f 100644 --- a/Plugins/ZShell/toaster.cpp +++ b/Plugins/ZShell/toaster.cpp @@ -6,7 +6,13 @@ namespace ZShell { -Toast::Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout, QObject* parent) +Toast::Toast( + const QString& title, + const QString& message, + const QString& icon, + Type type, + int timeout, + QObject* parent) : QObject(parent) , m_closed(false) , m_title(title) @@ -94,22 +100,25 @@ void Toast::unlock(QObject* sender) { } } -Toaster::Toaster(QObject* parent) - : QObject(parent) { -} +Toaster::Toaster(QObject* parent) : QObject(parent) {} QQmlListProperty Toaster::toasts() { return QQmlListProperty(this, &m_toasts); } -void Toaster::toast(const QString& title, const QString& message, const QString& icon, Toast::Type type, int timeout) { +void Toaster::toast( + const QString& title, + const QString& message, + const QString& icon, + Toast::Type type, + int timeout) { auto* toast = new Toast(title, message, icon, type, timeout, this); QObject::connect(toast, &Toast::finishedClose, this, [toast, this]() { - if (m_toasts.removeOne(toast)) { - emit toastsChanged(); - toast->deleteLater(); - } - }); + if (m_toasts.removeOne(toast)) { + emit toastsChanged(); + toast->deleteLater(); + } + }); m_toasts.push_front(toast); emit toastsChanged(); } diff --git a/Plugins/ZShell/toaster.hpp b/Plugins/ZShell/toaster.hpp index 0fb0d53..c2e546a 100644 --- a/Plugins/ZShell/toaster.hpp +++ b/Plugins/ZShell/toaster.hpp @@ -8,75 +8,80 @@ namespace ZShell { class Toast : public QObject { -Q_OBJECT -QML_ELEMENT -QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster") + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Toast instances can only be retrieved from a Toaster") -Q_PROPERTY(bool closed READ closed NOTIFY closedChanged) -Q_PROPERTY(QString title READ title CONSTANT) -Q_PROPERTY(QString message READ message CONSTANT) -Q_PROPERTY(QString icon READ icon CONSTANT) -Q_PROPERTY(int timeout READ timeout CONSTANT) -Q_PROPERTY(Type type READ type CONSTANT) + Q_PROPERTY(bool closed READ closed NOTIFY closedChanged) + Q_PROPERTY(QString title READ title CONSTANT) + Q_PROPERTY(QString message READ message CONSTANT) + Q_PROPERTY(QString icon READ icon CONSTANT) + Q_PROPERTY(int timeout READ timeout CONSTANT) + Q_PROPERTY(Type type READ type CONSTANT) -public: -enum class Type { - Info = 0, - Success, - Warning, - Error -}; -Q_ENUM(Type) + public: + enum class Type { Info = 0, Success, Warning, Error }; + Q_ENUM(Type) -explicit Toast(const QString& title, const QString& message, const QString& icon, Type type, int timeout, - QObject* parent = nullptr); + explicit Toast( + const QString& title, + const QString& message, + const QString& icon, + Type type, + int timeout, + QObject* parent = nullptr); -[[nodiscard]] bool closed() const; -[[nodiscard]] QString title() const; -[[nodiscard]] QString message() const; -[[nodiscard]] QString icon() const; -[[nodiscard]] int timeout() const; -[[nodiscard]] Type type() const; + [[nodiscard]] bool closed() const; + [[nodiscard]] QString title() const; + [[nodiscard]] QString message() const; + [[nodiscard]] QString icon() const; + [[nodiscard]] int timeout() const; + [[nodiscard]] Type type() const; -Q_INVOKABLE void close(); -Q_INVOKABLE void lock(QObject* sender); -Q_INVOKABLE void unlock(QObject* sender); + Q_INVOKABLE void close(); + Q_INVOKABLE void lock(QObject* sender); + Q_INVOKABLE void unlock(QObject* sender); -signals: -void closedChanged(); -void finishedClose(); + signals: + void closedChanged(); + void finishedClose(); -private: -QSet m_locks; + private: + QSet m_locks; -bool m_closed; -QString m_title; -QString m_message; -QString m_icon; -Type m_type; -int m_timeout; + bool m_closed; + QString m_title; + QString m_message; + QString m_icon; + Type m_type; + int m_timeout; }; class Toaster : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -Q_PROPERTY(QQmlListProperty toasts READ toasts NOTIFY toastsChanged) + Q_PROPERTY( + QQmlListProperty toasts READ toasts NOTIFY toastsChanged) -public: -explicit Toaster(QObject* parent = nullptr); + public: + explicit Toaster(QObject* parent = nullptr); -[[nodiscard]] QQmlListProperty toasts(); + [[nodiscard]] QQmlListProperty toasts(); -Q_INVOKABLE void toast(const QString& title, const QString& message, const QString& icon = QString(), - ZShell::Toast::Type type = Toast::Type::Info, int timeout = 5000); + Q_INVOKABLE void toast( + const QString& title, + const QString& message, + const QString& icon = QString(), + ZShell::Toast::Type type = Toast::Type::Info, + int timeout = 5000); -signals: -void toastsChanged(); + signals: + void toastsChanged(); -private: -QList m_toasts; + private: + QList m_toasts; }; } // namespace ZShell diff --git a/Plugins/ZShell/writefile.cpp b/Plugins/ZShell/writefile.cpp index 504ba86..8b29efc 100644 --- a/Plugins/ZShell/writefile.cpp +++ b/Plugins/ZShell/writefile.cpp @@ -20,15 +20,12 @@ namespace ZShell { -// ============================================================ -// saveItem -// ============================================================ - void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) { this->saveItem(target, path, QRect(), QJSValue(), QJSValue()); } -void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) { +void ZShellIo::saveItem( + QQuickItem* target, const QUrl& path, const QRect& rect) { this->saveItem(target, path, rect, QJSValue(), QJSValue()); } @@ -36,11 +33,13 @@ void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) this->saveItem(target, path, QRect(), onSaved, QJSValue()); } -void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { +void ZShellIo::saveItem( + QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { this->saveItem(target, path, QRect(), onSaved, onFailed); } -void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { +void ZShellIo::saveItem( + QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { this->saveItem(target, path, rect, onSaved, QJSValue()); } @@ -49,8 +48,7 @@ void ZShellIo::saveItem( const QUrl& path, const QRect& rect, QJSValue onSaved, - QJSValue onFailed - ) { + QJSValue onFailed) { if (!target) { qWarning() << "ZShellIo::saveItem: a target is required"; return; @@ -62,9 +60,8 @@ void ZShellIo::saveItem( } if (!target->window()) { - qWarning() << "ZShellIo::saveItem: unable to save target" - << target - << "without a window"; + qWarning() << "ZShellIo::saveItem: unable to save target" << target + << "without a window"; return; } @@ -74,11 +71,11 @@ void ZShellIo::saveItem( if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) { scaledRect = QRectF( - rect.left() * scale, - rect.top() * scale, - rect.width() * scale, - rect.height() * scale - ).toRect(); + rect.left() * scale, + rect.top() * scale, + rect.width() * scale, + rect.height() * scale) + .toRect(); } const QSharedPointer grabResult = @@ -89,66 +86,62 @@ void ZShellIo::saveItem( &QQuickItemGrabResult::ready, this, [grabResult, scaledRect, path, onSaved, onFailed, this]() { - const auto future = QtConcurrent::run([grabResult, scaledRect, path]() { - QImage image = grabResult->image(); + const auto future = + QtConcurrent::run([grabResult, scaledRect, path]() { + QImage image = grabResult->image(); - if (scaledRect.isValid()) { - image = image.copy(scaledRect); - } + if (scaledRect.isValid()) { + image = image.copy(scaledRect); + } - const QString file = path.toLocalFile(); - const QString parent = QFileInfo(file).absolutePath(); + const QString file = path.toLocalFile(); + const QString parent = QFileInfo(file).absolutePath(); - QDir().mkpath(parent); + QDir().mkpath(parent); - QSaveFile out(file); - if (!out.open(QIODevice::WriteOnly)) { - return false; - } + QSaveFile out(file); + if (!out.open(QIODevice::WriteOnly)) { + return false; + } - if (!image.save(&out, "PNG")) { - return false; - } + if (!image.save(&out, "PNG")) { + return false; + } - return out.commit(); - }); + return out.commit(); + }); auto* watcher = new QFutureWatcher(this); auto* engine = qmlEngine(this); - QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { - if (watcher->result()) { - if (onSaved.isCallable() && engine) { - onSaved.call({ - engine->toScriptValue(path.toLocalFile()), - engine->toScriptValue(path) - }); + QObject::connect( + watcher, &QFutureWatcher::finished, this, [=]() { + if (watcher->result()) { + if (onSaved.isCallable() && engine) { + onSaved.call( + {engine->toScriptValue(path.toLocalFile()), + engine->toScriptValue(path)}); + } + } else { + qWarning() + << "ZShellIo::saveItem: failed to save" << path; + if (onFailed.isCallable() && engine) { + onFailed.call({engine->toScriptValue(path)}); + } } - } else { - qWarning() << "ZShellIo::saveItem: failed to save" << path; - if (onFailed.isCallable() && engine) { - onFailed.call({ - engine->toScriptValue(path) - }); - } - } - watcher->deleteLater(); - }); + watcher->deleteLater(); + }); watcher->setFuture(future); - } - ); + }); } -// ============================================================ -// cacheImage -// ============================================================ - void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) { this->cacheImage(source, cacheDir, QJSValue(), QJSValue()); } -void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) { +void ZShellIo::cacheImage( + const QUrl& source, const QString& cacheDir, QJSValue onSaved) { this->cacheImage(source, cacheDir, onSaved, QJSValue()); } @@ -156,8 +149,7 @@ void ZShellIo::cacheImage( const QUrl& source, const QString& cacheDir, QJSValue onSaved, - QJSValue onFailed - ) { + QJSValue onFailed) { if (cacheDir.isEmpty()) { qWarning() << "ZShellIo::cacheImage: cacheDir is empty"; return; @@ -165,92 +157,87 @@ void ZShellIo::cacheImage( QImage image; if (!loadSourceImage(source, image)) { - qWarning() << "ZShellIo::cacheImage: failed to load source image" << source; + qWarning() << "ZShellIo::cacheImage: failed to load source image" + << source; auto* engine = qmlEngine(this); if (onFailed.isCallable() && engine) { - onFailed.call({ - engine->toScriptValue(source), - engine->toScriptValue(cacheDir) - }); + onFailed.call( + {engine->toScriptValue(source), + engine->toScriptValue(cacheDir)}); } return; } const auto future = QtConcurrent::run([image, cacheDir]() -> QString { - if (image.isNull()) { - return QString(); - } + if (image.isNull()) { + return QString(); + } - const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888); + const QImage normalized = + image.convertToFormat(QImage::Format_RGBA8888); - const QByteArray bytes( - reinterpret_cast(normalized.constBits()), - qsizetype(normalized.sizeInBytes()) - ); + const QByteArray bytes( + reinterpret_cast(normalized.constBits()), + normalized.sizeInBytes()); - const QByteArray digest = - QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex(); + const QByteArray digest = + QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex(); - QDir dir(cacheDir); - if (!dir.exists() && !QDir().mkpath(cacheDir)) { - return QString(); - } + QDir dir(cacheDir); + if (!dir.exists() && !QDir().mkpath(cacheDir)) { + return QString(); + } - const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png"); - - if (QFile::exists(finalPath)) { - return finalPath; - } - - QSaveFile out(finalPath); - if (!out.open(QIODevice::WriteOnly)) { - return QString(); - } - - if (!normalized.save(&out, "PNG")) { - return QString(); - } - - if (!out.commit()) { - return QString(); - } + const QString finalPath = + dir.filePath(QString::fromLatin1(digest) + ".png"); + if (QFile::exists(finalPath)) { return finalPath; - }); + } + + QSaveFile out(finalPath); + if (!out.open(QIODevice::WriteOnly)) { + return QString(); + } + + if (!normalized.save(&out, "PNG")) { + return QString(); + } + + if (!out.commit()) { + return QString(); + } + + return finalPath; + }); auto* watcher = new QFutureWatcher(this); auto* engine = qmlEngine(this); QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { - const QString finalPath = watcher->result(); + const QString finalPath = watcher->result(); - if (!finalPath.isEmpty()) { - if (onSaved.isCallable() && engine) { - onSaved.call({ - engine->toScriptValue(finalPath), - engine->toScriptValue(QUrl::fromLocalFile(finalPath)) - }); - } - } else { - qWarning() << "ZShellIo::cacheImage: failed to cache" << source; - if (onFailed.isCallable() && engine) { - onFailed.call({ - engine->toScriptValue(source), - engine->toScriptValue(cacheDir) - }); - } + if (!finalPath.isEmpty()) { + if (onSaved.isCallable() && engine) { + onSaved.call( + {engine->toScriptValue(finalPath), + engine->toScriptValue(QUrl::fromLocalFile(finalPath))}); } + } else { + qWarning() << "ZShellIo::cacheImage: failed to cache" << source; + if (onFailed.isCallable() && engine) { + onFailed.call( + {engine->toScriptValue(source), + engine->toScriptValue(cacheDir)}); + } + } - watcher->deleteLater(); - }); + watcher->deleteLater(); + }); watcher->setFuture(future); } -// ============================================================ -// loadSourceImage -// ============================================================ - bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { image = QImage(); @@ -270,22 +257,22 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { const QString providerId = source.host(); - const QString imageId = - source.path().startsWith('/') - ? source.path().mid(1) - : source.path(); + const QString imageId = source.path().startsWith('/') + ? source.path().mid(1) + : source.path(); auto* providerBase = engine->imageProvider(providerId); if (!providerBase) { qWarning() << "ZShellIo::loadSourceImage: provider not found" - << providerId; + << providerId; return false; } auto* provider = dynamic_cast(providerBase); if (!provider) { - qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider" - << providerId; + qWarning() << "ZShellIo::loadSourceImage: provider is not a " + "QQuickImageProvider" + << providerId; return false; } @@ -302,7 +289,7 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { default: qWarning() << "ZShellIo::loadSourceImage: unsupported provider type" - << providerId; + << providerId; return false; } @@ -313,17 +300,16 @@ bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const { return false; } -// ============================================================ -// File ops -// ============================================================ - -bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const { +bool ZShellIo::copyFile( + const QUrl& source, const QUrl& target, bool overwrite) const { if (!source.isLocalFile()) { - qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file"; + qWarning() << "ZShellIo::copyFile: source" << source + << "is not a local file"; return false; } if (!target.isLocalFile()) { - qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file"; + qWarning() << "ZShellIo::copyFile: target" << target + << "is not a local file"; return false; } @@ -336,7 +322,8 @@ bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) bool ZShellIo::deleteFile(const QUrl& path) const { if (!path.isLocalFile()) { - qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file"; + qWarning() << "ZShellIo::deleteFile: path" << path + << "is not a local file"; return false; } @@ -345,7 +332,8 @@ bool ZShellIo::deleteFile(const QUrl& path) const { QString ZShellIo::toLocalFile(const QUrl& url) const { if (!url.isLocalFile()) { - qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url; + qWarning() << "ZShellIo::toLocalFile: given url is not a local file" + << url; return QString(); } diff --git a/Plugins/ZShell/writefile.hpp b/Plugins/ZShell/writefile.hpp index 458d4d9..799cb98 100644 --- a/Plugins/ZShell/writefile.hpp +++ b/Plugins/ZShell/writefile.hpp @@ -10,31 +10,31 @@ namespace ZShell { class ZShellIo : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + public: + // clang-format off + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); -public: -// clang-format off -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); + Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir); + Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved); + Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed); + // clang-format on -Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir); -Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved); -Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed); -// clang-format on + Q_INVOKABLE [[nodiscard]] bool copyFile( + const QUrl& source, const QUrl& target, bool overwrite = true) const; + Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const; + Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const; -Q_INVOKABLE [[nodiscard]] bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const; -Q_INVOKABLE [[nodiscard]] bool deleteFile(const QUrl& path) const; -Q_INVOKABLE [[nodiscard]] QString toLocalFile(const QUrl& url) const; - -private: -bool loadSourceImage(const QUrl& source, QImage& image) const; + private: + bool loadSourceImage(const QUrl& source, QImage& image) const; }; } // namespace ZShell diff --git a/Plugins/ZShell/zutils.cpp b/Plugins/ZShell/zutils.cpp index ec0449f..8265485 100644 --- a/Plugins/ZShell/zutils.cpp +++ b/Plugins/ZShell/zutils.cpp @@ -27,15 +27,22 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) { this->saveItem(target, path, QRect(), onSaved, QJSValue()); } -void ZUtils::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { +void ZUtils::saveItem( + QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) { this->saveItem(target, path, QRect(), onSaved, onFailed); } -void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { +void ZUtils::saveItem( + QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) { this->saveItem(target, path, rect, onSaved, QJSValue()); } -void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) { +void ZUtils::saveItem( + QQuickItem* target, + const QUrl& path, + const QRect& rect, + QJSValue onSaved, + QJSValue onFailed) { if (!target) { qCWarning(lcZUtils) << "saveItem: a target is required"; return; @@ -47,21 +54,30 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q } if (!target->window()) { - qCWarning(lcZUtils) << "saveItem: unable to save target" << target << "without a window"; + qCWarning(lcZUtils) << "saveItem: unable to save target" << target + << "without a window"; return; } auto scaledRect = rect; const qreal scale = target->window()->devicePixelRatio(); if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) { - scaledRect = - QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect(); + scaledRect = QRectF( + rect.left() * scale, + rect.top() * scale, + rect.width() * scale, + rect.height() * scale) + .toRect(); } - const QSharedPointer grabResult = target->grabToImage(); + const QSharedPointer grabResult = + target->grabToImage(); - QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, - [grabResult, scaledRect, path, onSaved, onFailed, this]() { + QObject::connect( + grabResult.data(), + &QQuickItemGrabResult::ready, + this, + [grabResult, scaledRect, path, onSaved, onFailed, this]() { const auto future = QtConcurrent::run([=]() { QImage image = grabResult->image(); @@ -77,44 +93,52 @@ void ZUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q auto* watcher = new QFutureWatcher(this); auto* engine = qmlEngine(this); - QObject::connect(watcher, &QFutureWatcher::finished, this, [=]() { - if (watcher->result()) { - if (onSaved.isCallable()) { - QJSValueList args = { QJSValue(path.toLocalFile()) }; - if (engine) { - args << engine->toScriptValue(QVariant::fromValue(path)); + QObject::connect( + watcher, &QFutureWatcher::finished, this, [=]() { + if (watcher->result()) { + if (onSaved.isCallable()) { + QJSValueList args = {QJSValue(path.toLocalFile())}; + if (engine) { + args << engine->toScriptValue( + QVariant::fromValue(path)); + } + onSaved.call(args); } - onSaved.call(args); - } - } else { - qCWarning(lcZUtils) << "saveItem: failed to save" << path; - if (onFailed.isCallable()) { - if (engine) { - onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) }); - } else { - onFailed.call(); + } else { + qCWarning(lcZUtils) + << "saveItem: failed to save" << path; + if (onFailed.isCallable()) { + if (engine) { + onFailed.call({engine->toScriptValue( + QVariant::fromValue(path))}); + } else { + onFailed.call(); + } } } - } - watcher->deleteLater(); - }); + watcher->deleteLater(); + }); watcher->setFuture(future); }); } bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) { if (!source.isLocalFile()) { - qCWarning(lcZUtils) << "copyFile: source" << source << "is not a local file"; + qCWarning(lcZUtils) + << "copyFile: source" << source << "is not a local file"; return false; } if (!target.isLocalFile()) { - qCWarning(lcZUtils) << "copyFile: target" << target << "is not a local file"; + qCWarning(lcZUtils) + << "copyFile: target" << target << "is not a local file"; return false; } if (overwrite && QFile::exists(target.toLocalFile())) { if (!QFile::remove(target.toLocalFile())) { - qCWarning(lcZUtils) << "copyFile: overwrite was specified but failed to remove" << target.toLocalFile(); + qCWarning(lcZUtils) + << "copyFile: overwrite was specified but failed to remove" + << target.toLocalFile(); return false; } } @@ -124,7 +148,8 @@ bool ZUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) { bool ZUtils::deleteFile(const QUrl& path) { if (!path.isLocalFile()) { - qCWarning(lcZUtils) << "deleteFile: path" << path << "is not a local file"; + qCWarning(lcZUtils) + << "deleteFile: path" << path << "is not a local file"; return false; } @@ -133,7 +158,8 @@ bool ZUtils::deleteFile(const QUrl& path) { QString ZUtils::toLocalFile(const QUrl& url) { if (!url.isLocalFile()) { - qCWarning(lcZUtils) << "toLocalFile: given url is not a local file" << url; + qCWarning(lcZUtils) + << "toLocalFile: given url is not a local file" << url; return QString(); } diff --git a/Plugins/ZShell/zutils.hpp b/Plugins/ZShell/zutils.hpp index 54cfb5f..4acbaa4 100644 --- a/Plugins/ZShell/zutils.hpp +++ b/Plugins/ZShell/zutils.hpp @@ -8,28 +8,29 @@ namespace ZShell { class ZUtils : public QObject { -Q_OBJECT -QML_ELEMENT -QML_SINGLETON + Q_OBJECT + QML_ELEMENT + QML_SINGLETON -public: -// clang-format off -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); -// clang-format on + public: + // clang-format off + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); + // clang-format on -Q_INVOKABLE static bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true); -Q_INVOKABLE static bool deleteFile(const QUrl& path); -Q_INVOKABLE static QString toLocalFile(const QUrl& url); + Q_INVOKABLE static bool copyFile( + const QUrl& source, const QUrl& target, bool overwrite = true); + Q_INVOKABLE static bool deleteFile(const QUrl& path); + Q_INVOKABLE static QString toLocalFile(const QUrl& url); -Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max); + Q_INVOKABLE static qreal clamp(qreal value, qreal min, qreal max); -[[nodiscard]] QString version() const; -[[nodiscard]] QString qtVersion() const; + [[nodiscard]] QString version() const; + [[nodiscard]] QString qtVersion() const; }; } // namespace ZShell From 658be09da2ad0e8241eb4580ba4a16e221725608 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 02:10:17 +0200 Subject: [PATCH 24/44] fix(buttonrow): restore private specifier removed by clang-format --- Plugins/ZShell/Components/buttonrow.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Plugins/ZShell/Components/buttonrow.hpp b/Plugins/ZShell/Components/buttonrow.hpp index e2417e6..4ff76b6 100644 --- a/Plugins/ZShell/Components/buttonrow.hpp +++ b/Plugins/ZShell/Components/buttonrow.hpp @@ -28,6 +28,8 @@ class ButtonRow : public QQuickItem { private slots: void invalidate(); + // NOLINTNEXTLINE(readability-redundant-access-specifiers) + private: void relayout(); static qreal getMorphExpansion(const QQuickItem* item); From 1e31e14de8759cae7a7df898b79d051891f0823f Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 02:10:37 +0200 Subject: [PATCH 25/44] format(remainder): clang-format remainder of files --- Plugins/ZShell/Internal/circularindicatormanager.cpp | 4 ++-- Plugins/ZShell/Internal/hyprdevices.hpp | 2 +- Plugins/ZShell/Services/desktopmodel.cpp | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Plugins/ZShell/Internal/circularindicatormanager.cpp b/Plugins/ZShell/Internal/circularindicatormanager.cpp index 15ce0f2..fcff143 100644 --- a/Plugins/ZShell/Internal/circularindicatormanager.cpp +++ b/Plugins/ZShell/Internal/circularindicatormanager.cpp @@ -15,8 +15,8 @@ constexpr qint32 TAIL_DEGREES_OFFSET = -20; constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250; constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520; -constexpr std::array DELAY_TO_EXPAND_IN_MS = - {0, 1350, 2700, 4050}; +constexpr std::array DELAY_TO_EXPAND_IN_MS = { + 0, 1350, 2700, 4050}; constexpr std::array DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717}; diff --git a/Plugins/ZShell/Internal/hyprdevices.hpp b/Plugins/ZShell/Internal/hyprdevices.hpp index b554240..b65ea9a 100644 --- a/Plugins/ZShell/Internal/hyprdevices.hpp +++ b/Plugins/ZShell/Internal/hyprdevices.hpp @@ -76,4 +76,4 @@ class HyprDevices : public QObject { QList m_keyboards; }; -} +} // namespace ZShell::internal::hypr diff --git a/Plugins/ZShell/Services/desktopmodel.cpp b/Plugins/ZShell/Services/desktopmodel.cpp index fb361e4..e9446b9 100644 --- a/Plugins/ZShell/Services/desktopmodel.cpp +++ b/Plugins/ZShell/Services/desktopmodel.cpp @@ -14,7 +14,8 @@ int DesktopModel::rowCount(const QModelIndex& parent) const { } QVariant DesktopModel::data(const QModelIndex& index, int role) const { - if (!index.isValid() || index.row() >= static_cast(m_items.size())) return QVariant(); + if (!index.isValid() || index.row() >= static_cast(m_items.size())) + return QVariant(); const DesktopItem& item = m_items[index.row()]; switch (role) { @@ -243,7 +244,9 @@ void DesktopModel::massMove( } emit dataChanged( - index(0, 0), index(static_cast(m_items.size()) - 1, 0), {GridXRole, GridYRole}); + index(0, 0), + index(static_cast(m_items.size()) - 1, 0), + {GridXRole, GridYRole}); saveCurrentLayout(); } From 944694966b2bcb83f8c4fad7082e079cc5774d29 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:08:32 +0200 Subject: [PATCH 26/44] tidy(blobmaterial&circularindicatormanager): warning fixes, readability and modernize headers. --- Plugins/ZShell/Blobs/blobmaterial.cpp | 3 +-- Plugins/ZShell/Internal/circularindicatormanager.cpp | 8 +++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobmaterial.cpp b/Plugins/ZShell/Blobs/blobmaterial.cpp index e32b25e..cdd5cd7 100644 --- a/Plugins/ZShell/Blobs/blobmaterial.cpp +++ b/Plugins/ZShell/Blobs/blobmaterial.cpp @@ -1,7 +1,6 @@ #include "blobmaterial.hpp" -#include - +#include #include static_assert( diff --git a/Plugins/ZShell/Internal/circularindicatormanager.cpp b/Plugins/ZShell/Internal/circularindicatormanager.cpp index fcff143..0d3a1b8 100644 --- a/Plugins/ZShell/Internal/circularindicatormanager.cpp +++ b/Plugins/ZShell/Internal/circularindicatormanager.cpp @@ -89,17 +89,15 @@ void CircularIndicatorManager::setProgress(qreal progress) { qreal CircularIndicatorManager::duration() const { if (m_type == IndeterminateAnimationType::Advance) { return advance::TOTAL_DURATION_IN_MS; - } else { - return retreat::TOTAL_DURATION_IN_MS; } + return retreat::TOTAL_DURATION_IN_MS; } qreal CircularIndicatorManager::completeEndDuration() const { if (m_type == IndeterminateAnimationType::Advance) { return advance::DURATION_TO_COMPLETE_END_IN_MS; - } else { - return retreat::DURATION_TO_COMPLETE_END_IN_MS; - } + } return retreat::DURATION_TO_COMPLETE_END_IN_MS; + } CircularIndicatorManager::IndeterminateAnimationType From 728bdc85bc88017020d4349e984d0514a2e25566 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:17:52 +0200 Subject: [PATCH 27/44] fix(clang-tidy/analyzer): resolving false positives --- .clang-tidy | 5 +++++ Plugins/ZShell/Internal/cachingimagemanager.cpp | 1 + Plugins/ZShell/Models/filesystemmodel.cpp | 2 ++ Plugins/ZShell/Services/audiocollector.cpp | 1 + Plugins/ZShell/appdb.cpp | 1 + Plugins/ZShell/qalculator.cpp | 1 + 6 files changed, 11 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index 5246243..99690c3 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,6 +2,11 @@ Checks: > -*, bugprone-*, + -readability-redundant-inline-specifier + -bugprone-throwing-static-initialization + -bugprone-invalid-enum-default-initialization + -bugprone-suspicious-include + -bugprone-branch-clone -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-implicit-widening-of-multiplication-result, diff --git a/Plugins/ZShell/Internal/cachingimagemanager.cpp b/Plugins/ZShell/Internal/cachingimagemanager.cpp index e53bf42..58e3112 100644 --- a/Plugins/ZShell/Internal/cachingimagemanager.cpp +++ b/Plugins/ZShell/Internal/cachingimagemanager.cpp @@ -170,6 +170,7 @@ void CachingImageManager::updateSource(const QString& path) { }); watcher->setFuture(future); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } QUrl CachingImageManager::cachePath() const { diff --git a/Plugins/ZShell/Models/filesystemmodel.cpp b/Plugins/ZShell/Models/filesystemmodel.cpp index d0769d5..02f18f0 100644 --- a/Plugins/ZShell/Models/filesystemmodel.cpp +++ b/Plugins/ZShell/Models/filesystemmodel.cpp @@ -258,6 +258,7 @@ void FileSystemModel::watchDirIfRecursive(const QString& path) { }); watcher->setFuture(future); } + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } void FileSystemModel::update() { @@ -405,6 +406,7 @@ void FileSystemModel::updateEntriesForDir(const QString& dir) { }); watcher->setFuture(future); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } void FileSystemModel::applyChanges( diff --git a/Plugins/ZShell/Services/audiocollector.cpp b/Plugins/ZShell/Services/audiocollector.cpp index a9382e3..02f7e2a 100644 --- a/Plugins/ZShell/Services/audiocollector.cpp +++ b/Plugins/ZShell/Services/audiocollector.cpp @@ -253,6 +253,7 @@ AudioCollector::AudioCollector(QObject* parent) , m_writeBuffer(&m_buffer2) {} AudioCollector::~AudioCollector() { + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) stop(); } diff --git a/Plugins/ZShell/appdb.cpp b/Plugins/ZShell/appdb.cpp index a62cf59..a34398e 100644 --- a/Plugins/ZShell/appdb.cpp +++ b/Plugins/ZShell/appdb.cpp @@ -9,6 +9,7 @@ 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(); + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) const auto tmo = metaObject(); for (const auto& prop : diff --git a/Plugins/ZShell/qalculator.cpp b/Plugins/ZShell/qalculator.cpp index 944449e..af73867 100644 --- a/Plugins/ZShell/qalculator.cpp +++ b/Plugins/ZShell/qalculator.cpp @@ -6,6 +6,7 @@ namespace ZShell { Qalculator::Qalculator(QObject* parent) : QObject(parent) { if (!CALCULATOR) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) new Calculator(); CALCULATOR->loadExchangeRates(); CALCULATOR->loadGlobalDefinitions(); From d4e4152bdc7e839c73ad52f4f522c1884cf74d54 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:18:42 +0200 Subject: [PATCH 28/44] leftover format file --- Plugins/ZShell/Internal/circularindicatormanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Plugins/ZShell/Internal/circularindicatormanager.cpp b/Plugins/ZShell/Internal/circularindicatormanager.cpp index 0d3a1b8..2c4bebe 100644 --- a/Plugins/ZShell/Internal/circularindicatormanager.cpp +++ b/Plugins/ZShell/Internal/circularindicatormanager.cpp @@ -96,8 +96,8 @@ qreal CircularIndicatorManager::duration() const { qreal CircularIndicatorManager::completeEndDuration() const { if (m_type == IndeterminateAnimationType::Advance) { return advance::DURATION_TO_COMPLETE_END_IN_MS; - } return retreat::DURATION_TO_COMPLETE_END_IN_MS; - + } + return retreat::DURATION_TO_COMPLETE_END_IN_MS; } CircularIndicatorManager::IndeterminateAnimationType From e347671edd8e611a1f66be4f03226eb75e86a6d5 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:23:36 +0200 Subject: [PATCH 29/44] missing commas clang-tidy --- .clang-tidy | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 99690c3..216ec0e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,11 +2,11 @@ Checks: > -*, bugprone-*, - -readability-redundant-inline-specifier - -bugprone-throwing-static-initialization - -bugprone-invalid-enum-default-initialization - -bugprone-suspicious-include - -bugprone-branch-clone + -readability-redundant-inline-specifier, + -bugprone-throwing-static-initialization, + -bugprone-invalid-enum-default-initialization, + -bugprone-suspicious-include, + -bugprone-branch-clone, -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-implicit-widening-of-multiplication-result, From dcc535d213260171aebbdd8e2a07c2264a65612a Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:27:01 +0200 Subject: [PATCH 30/44] unused nolint --- Plugins/ZShell/qalculator.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/ZShell/qalculator.cpp b/Plugins/ZShell/qalculator.cpp index af73867..944449e 100644 --- a/Plugins/ZShell/qalculator.cpp +++ b/Plugins/ZShell/qalculator.cpp @@ -6,7 +6,6 @@ namespace ZShell { Qalculator::Qalculator(QObject* parent) : QObject(parent) { if (!CALCULATOR) { - // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) new Calculator(); CALCULATOR->loadExchangeRates(); CALCULATOR->loadGlobalDefinitions(); From 532e5a0a03b9b7fcf15242d5718f37cd990d9392 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:38:16 +0200 Subject: [PATCH 31/44] clang-tidy: reordering --- .clang-tidy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 216ec0e..3faab18 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: > -*, bugprone-*, - -readability-redundant-inline-specifier, -bugprone-throwing-static-initialization, -bugprone-invalid-enum-default-initialization, -bugprone-suspicious-include, @@ -17,6 +16,7 @@ Checks: > readability-else-after-return, readability-make-member-function-const, readability-redundant-*, + -readability-redundant-inline-specifier, readability-simplify-*, modernize-use-override, modernize-use-nullptr, From 87c9f85ac744b1385e99f7c19b648d353408a387 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:38:39 +0200 Subject: [PATCH 32/44] fix(hyprextras): false flag recursion --- Plugins/ZShell/Internal/hyprextras.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Plugins/ZShell/Internal/hyprextras.cpp b/Plugins/ZShell/Internal/hyprextras.cpp index 7adea83..fd28611 100644 --- a/Plugins/ZShell/Internal/hyprextras.cpp +++ b/Plugins/ZShell/Internal/hyprextras.cpp @@ -56,6 +56,7 @@ static QString luaEscapeString(const QString& s) { static QString luaValue(const QVariant& v); +// NOLINTNEXTLINE(misc-no-recursion) static QString luaArray(const QVariantList& list) { QStringList parts; parts.reserve(list.size()); @@ -80,6 +81,7 @@ static QString luaArray(const QStringList& list) { QLatin1String(" }"); } +// NOLINTNEXTLINE(misc-no-recursion) static QString luaMapFromHash(const QVariantHash& hash) { QStringList parts; parts.reserve(hash.size()); @@ -93,6 +95,7 @@ static QString luaMapFromHash(const QVariantHash& hash) { QLatin1String(" }"); } +// NOLINTNEXTLINE(misc-no-recursion) static QString luaMap(const QVariantMap& map) { QStringList parts; parts.reserve(map.size()); @@ -106,6 +109,7 @@ static QString luaMap(const QVariantMap& map) { QLatin1String(" }"); } +// NOLINTNEXTLINE(misc-no-recursion) static QString luaValue(const QVariant& v) { if (!v.isValid() || v.isNull()) { return QLatin1String("nil"); @@ -242,6 +246,7 @@ static QVariant parseGetOptionValue(const QJsonObject& obj) { return {}; } +// NOLINTNEXTLINE(misc-no-recursion) static void insertNestedValue( QVariantMap& root, const QStringList& path, const QVariant& value) { if (path.isEmpty()) { From 49a65becffe5d954c1e8182fa253c10eb85abc36 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:44:35 +0200 Subject: [PATCH 33/44] CI/CD: updated cpp to reduce false flags, focus on dev tidy check --- .gitea/workflows/cpp.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index ffe41a3..c554b29 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -16,7 +16,9 @@ jobs: fetch-depth: 0 - name: Configure - run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release + run: cmake -B build -G Ninja -DENABLE_MODULES=plugin \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_FLAGS="-I/usr/include/qt6/QtCanvasPainter" - name: Build run: ninja -C build @@ -26,4 +28,4 @@ jobs: run: | set -o pipefail run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct' + grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error' From 25e1610e2bebdf4e0efd944ed1313dbc5b431b3e Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 03:54:47 +0200 Subject: [PATCH 34/44] CI/CD: updated, should not stop the check now due to diagnostic demotion --- .gitea/workflows/cpp.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index c554b29..406a5e4 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -27,5 +27,6 @@ jobs: continue-on-error: true run: | set -o pipefail - run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error' + run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" \ + -extra-arg=-Wno-unknown-argument 2>&1 | \ + grep -v 'Suppressed\|non-user\|non-system\|Found compiler error' From b8f79c51ae8bc8f8e6677c64d8ea396e3017fb1b Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:02:22 +0200 Subject: [PATCH 35/44] CI/CD: updated, researched other CI/CD implementations --- .gitea/workflows/cpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index 406a5e4..6c11717 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -26,7 +26,7 @@ jobs: - name: clang-tidy continue-on-error: true run: | + sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json set -o pipefail - run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" \ - -extra-arg=-Wno-unknown-argument 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|Found compiler error' + run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ + grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error' From d2368c8c101024c6f68e8c4400e9d302d16071cb Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:17:43 +0200 Subject: [PATCH 36/44] CI/CD: testing warnings as errors for CI/CD only --- .gitea/workflows/cpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index 6c11717..e8e58c3 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -24,9 +24,9 @@ jobs: run: ninja -C build - name: clang-tidy - continue-on-error: true run: | sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json set -o pipefail - run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error' + run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" \ + -warnings-as-errors='*' 2>&1 | \ + grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter' From 1cdcecae68ae8346a1813dafde18e6d2d26a39cf Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:27:44 +0200 Subject: [PATCH 37/44] CI/CD: revert --- .gitea/workflows/cpp.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index e8e58c3..ffe41a3 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -16,17 +16,14 @@ jobs: fetch-depth: 0 - name: Configure - run: cmake -B build -G Ninja -DENABLE_MODULES=plugin \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS="-I/usr/include/qt6/QtCanvasPainter" + run: cmake -B build -G Ninja -DENABLE_MODULES=plugin -DCMAKE_BUILD_TYPE=Release - name: Build run: ninja -C build - name: clang-tidy + continue-on-error: true run: | - sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json set -o pipefail - run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" \ - -warnings-as-errors='*' 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter' + run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ + grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct' From 3c9df8259422ea7f7f237938567daa1e42abed1d Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:36:22 +0200 Subject: [PATCH 38/44] CI/CD: attempt only user tidy warnings --- .gitea/workflows/cpp.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index ffe41a3..5f5dd3d 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -24,6 +24,7 @@ jobs: - name: clang-tidy continue-on-error: true run: | + sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json set -o pipefail run-clang-tidy -p build -j $(nproc) -header-filter="Plugins/.*" 2>&1 | \ - grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct' + grep -v 'Suppressed\|non-user\|non-system\|unknown argument.*mno-direct\|Found compiler error\|warnings generated\.\|^Use -header-filter' From 85944ec9663d3373313fca8dc6609ea9b0cf3c56 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:42:39 +0200 Subject: [PATCH 39/44] force harmless lint warning to check ci/cd --- .gitea/workflows/cpp.yml | 1 - Plugins/ZShell/qalculator.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/cpp.yml b/.gitea/workflows/cpp.yml index 5f5dd3d..32c6a38 100644 --- a/.gitea/workflows/cpp.yml +++ b/.gitea/workflows/cpp.yml @@ -22,7 +22,6 @@ jobs: run: ninja -C build - name: clang-tidy - continue-on-error: true run: | sed -i 's/-mno-direct-extern-access //g' build/compile_commands.json set -o pipefail diff --git a/Plugins/ZShell/qalculator.cpp b/Plugins/ZShell/qalculator.cpp index 944449e..dfe4310 100644 --- a/Plugins/ZShell/qalculator.cpp +++ b/Plugins/ZShell/qalculator.cpp @@ -5,6 +5,7 @@ namespace ZShell { Qalculator::Qalculator(QObject* parent) : QObject(parent) { + int x; if (!CALCULATOR) { new Calculator(); CALCULATOR->loadExchangeRates(); From 45350210ccee9a7ec9427e48ce33a476ef8f7760 Mon Sep 17 00:00:00 2001 From: AramJonghu Date: Tue, 30 Jun 2026 04:49:55 +0200 Subject: [PATCH 40/44] fix&revert(qalculator): unassigned this to calculator pointer fixed, revert of succesful test --- Plugins/ZShell/qalculator.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Plugins/ZShell/qalculator.cpp b/Plugins/ZShell/qalculator.cpp index dfe4310..7549445 100644 --- a/Plugins/ZShell/qalculator.cpp +++ b/Plugins/ZShell/qalculator.cpp @@ -5,9 +5,8 @@ namespace ZShell { Qalculator::Qalculator(QObject* parent) : QObject(parent) { - int x; if (!CALCULATOR) { - new Calculator(); + calculator = new Calculator(); CALCULATOR->loadExchangeRates(); CALCULATOR->loadGlobalDefinitions(); CALCULATOR->loadLocalDefinitions(); From 157bdd8fd9f613bc943276298b8d8e2382c54459 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:25:12 +0200 Subject: [PATCH 41/44] 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}` : "…" } } From deaeac4dbdcdf64c8f15c37ca08199edd0a764ba Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:31:23 +0200 Subject: [PATCH 42/44] 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 From 38b9362515514ca89f68528bc706d971ebd4289d Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 13:43:34 +0200 Subject: [PATCH 43/44] 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") - } - } - } -} From 61019204d8cdedd9ee7ae61e9fb5e53fd45653a6 Mon Sep 17 00:00:00 2001 From: zach Date: Tue, 30 Jun 2026 15:35:25 +0200 Subject: [PATCH 44/44] formatter --- Plugins/ZShell/Blobs/blobgroup.cpp | 3 +- Plugins/ZShell/Blobs/blobgroup.hpp | 46 ++++++------- Plugins/ZShell/Blobs/blobrect.cpp | 57 ++++++++-------- Plugins/ZShell/Blobs/blobrect.hpp | 101 +++++++++++++++++------------ Plugins/ZShell/Blobs/blobshape.cpp | 12 ++-- Plugins/ZShell/Blobs/blobshape.hpp | 48 +++++++------- Plugins/ZShell/appdb.cpp | 65 +++++++++++-------- Plugins/ZShell/appdb.hpp | 57 ++++++++-------- Plugins/ZShell/zutils.hpp | 22 +++---- 9 files changed, 222 insertions(+), 189 deletions(-) diff --git a/Plugins/ZShell/Blobs/blobgroup.cpp b/Plugins/ZShell/Blobs/blobgroup.cpp index 40d7487..c286650 100644 --- a/Plugins/ZShell/Blobs/blobgroup.cpp +++ b/Plugins/ZShell/Blobs/blobgroup.cpp @@ -26,8 +26,7 @@ void BlobGroup::setColor(const QColor& c) { } void BlobGroup::setCornerFill(bool e) { - if (m_cornerFill == e) - return; + if (m_cornerFill == e) return; m_cornerFill = e; emit cornerFillChanged(); markDirty(); diff --git a/Plugins/ZShell/Blobs/blobgroup.hpp b/Plugins/ZShell/Blobs/blobgroup.hpp index c8bebde..eb3ca35 100644 --- a/Plugins/ZShell/Blobs/blobgroup.hpp +++ b/Plugins/ZShell/Blobs/blobgroup.hpp @@ -9,11 +9,15 @@ class BlobShape; class BlobInvertedRect; class BlobGroup : public QObject { -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) + 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); @@ -27,14 +31,12 @@ Q_PROPERTY(bool cornerFill READ cornerFill WRITE setCornerFill NOTIFY cornerFill void setColor(const QColor& c); -[[nodiscard]] bool cornerFill() const { - return m_cornerFill; -} + [[nodiscard]] bool cornerFill() const { return m_cornerFill; } -void setCornerFill(bool e); + void setCornerFill(bool e); -void addShape(BlobShape* shape); -void removeShape(BlobShape* shape); + void addShape(BlobShape* shape); + void removeShape(BlobShape* shape); void setInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect); @@ -49,16 +51,16 @@ void removeShape(BlobShape* shape); void markShapeDirty(BlobShape* source); void ensurePhysicsUpdated(); -signals: -void smoothingChanged(); -void colorChanged(); -void cornerFillChanged(); + 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; + 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 6c87135..f7dc72c 100644 --- a/Plugins/ZShell/Blobs/blobrect.cpp +++ b/Plugins/ZShell/Blobs/blobrect.cpp @@ -32,11 +32,9 @@ void BlobRect::updatePolish() { QMetaObject::invokeMethod( this, [this]() { - if (m_group) - m_group->markDirty(); - }, - Qt::QueuedConnection - ); + if (m_group) m_group->markDirty(); + }, + Qt::QueuedConnection); } } else { QMetaObject::invokeMethod( @@ -184,8 +182,7 @@ bool BlobRect::isExcluded(const BlobShape* other) const { bool BlobRect::isCornerExcluded(const BlobShape* other) const { for (const auto& ptr : m_excludeCorners) { - if (ptr == other) - return true; + if (ptr == other) return true; } return false; } @@ -203,8 +200,15 @@ QQmlListProperty BlobRect::exclude() { } QQmlListProperty BlobRect::excludeCorners() { - return QQmlListProperty(this, nullptr, &excludeCornersAppend, &excludeCornersCount, &excludeCornersAt, - &excludeCornersClear, &excludeCornersReplace, &excludeCornersRemoveLast); + return QQmlListProperty( + this, + nullptr, + &excludeCornersAppend, + &excludeCornersCount, + &excludeCornersAt, + &excludeCornersClear, + &excludeCornersReplace, + &excludeCornersRemoveLast); } void BlobRect::excludeAppend(QQmlListProperty* prop, BlobRect* rect) { @@ -249,11 +253,11 @@ void BlobRect::excludeRemoveLast(QQmlListProperty* prop) { emit self->excludeChanged(); } -void BlobRect::excludeCornersAppend(QQmlListProperty* prop, BlobRect* rect) { +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(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeCornersChanged(); } @@ -262,36 +266,33 @@ qsizetype BlobRect::excludeCornersCount(QQmlListProperty* prop) { return self->m_excludeCorners.size(); } -BlobRect* BlobRect::excludeCornersAt(QQmlListProperty* prop, qsizetype index) { +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; + if (self->m_excludeCorners.isEmpty()) return; self->m_excludeCorners.clear(); - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeCornersChanged(); } -void BlobRect::excludeCornersReplace(QQmlListProperty* prop, qsizetype index, BlobRect* rect) { +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(); + 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; + if (self->m_excludeCorners.isEmpty()) return; self->m_excludeCorners.removeLast(); - if (self->m_group) - self->m_group->markDirty(); + if (self->m_group) self->m_group->markDirty(); emit self->excludeCornersChanged(); } @@ -319,11 +320,9 @@ void BlobRect::checkAtRest(float speed) { QMetaObject::invokeMethod( this, [this]() { - if (m_group) - m_group->markDirty(); - }, - Qt::QueuedConnection - ); + if (m_group) m_group->markDirty(); + }, + Qt::QueuedConnection); } } } diff --git a/Plugins/ZShell/Blobs/blobrect.hpp b/Plugins/ZShell/Blobs/blobrect.hpp index f99aa20..51a6157 100644 --- a/Plugins/ZShell/Blobs/blobrect.hpp +++ b/Plugins/ZShell/Blobs/blobrect.hpp @@ -8,18 +8,32 @@ #include class BlobRect : public BlobShape { -Q_OBJECT -QML_ELEMENT -Q_PROPERTY(qreal stiffness READ stiffness WRITE setStiffness NOTIFY stiffnessChanged) -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) -Q_PROPERTY( - qreal bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius NOTIFY bottomRightRadiusChanged) + Q_OBJECT + QML_ELEMENT + Q_PROPERTY( + qreal stiffness READ stiffness WRITE setStiffness NOTIFY + stiffnessChanged) + 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) + Q_PROPERTY( + qreal bottomRightRadius READ bottomRightRadius WRITE + setBottomRightRadius NOTIFY bottomRightRadiusChanged) public: explicit BlobRect(QQuickItem* parent = nullptr); @@ -52,12 +66,12 @@ Q_PROPERTY( } } -QQmlListProperty exclude(); -QQmlListProperty excludeCorners(); + 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; + 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 { return m_topLeftRadius; } @@ -77,16 +91,16 @@ void cornerRadii(float out[4]) const override; void setBottomRightRadius(qreal r); -signals: -void stiffnessChanged(); -void dampingChanged(); -void deformScaleChanged(); -void excludeChanged(); -void excludeCornersChanged(); -void topLeftRadiusChanged(); -void topRightRadiusChanged(); -void bottomLeftRadiusChanged(); -void bottomRightRadiusChanged(); + signals: + void stiffnessChanged(); + void dampingChanged(); + void deformScaleChanged(); + void excludeChanged(); + void excludeCornersChanged(); + void topLeftRadiusChanged(); + void topRightRadiusChanged(); + void bottomLeftRadiusChanged(); + void bottomRightRadiusChanged(); protected: void updatePolish() override; @@ -121,20 +135,25 @@ void bottomRightRadiusChanged(); qreal m_bottomLeftRadius = -1; qreal m_bottomRightRadius = -1; -QList > m_exclude; -QList > m_excludeCorners; + QList> m_exclude; + QList> m_excludeCorners; -static void excludeAppend(QQmlListProperty* prop, BlobRect* rect); -static qsizetype excludeCount(QQmlListProperty* prop); -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 excludeAppend(QQmlListProperty* prop, BlobRect* rect); + static qsizetype excludeCount(QQmlListProperty* prop); + 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); + 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 6d7cec2..99d4eec 100644 --- a/Plugins/ZShell/Blobs/blobshape.cpp +++ b/Plugins/ZShell/Blobs/blobshape.cpp @@ -38,8 +38,7 @@ static float cornerFillFactor(float sd, float smoothFactor) { return std::max(outside, inside); } -BlobShape::BlobShape(QQuickItem* parent) - : QQuickItem(parent) { +BlobShape::BlobShape(QQuickItem* parent) : QQuickItem(parent) { setFlag(ItemHasContents); } @@ -312,13 +311,10 @@ void BlobShape::updatePolish() { const float cTlX = ri.cx - ri.hw, cTlY = ri.cy - ri.hh; for (qsizetype j = 0; cornerFill && j < rectCount; ++j) { - if (j == i) - continue; - if (riExcludeMask & (1 << j)) - continue; + if (j == i) continue; + if (riExcludeMask & (1 << j)) continue; BlobShape* const sj = rectShapes[j]; - if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) - continue; + if (si->isCornerExcluded(sj) || sj->isCornerExcluded(si)) continue; const auto& rj = m_cachedRects[j]; 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); diff --git a/Plugins/ZShell/Blobs/blobshape.hpp b/Plugins/ZShell/Blobs/blobshape.hpp index e5a297c..8bdc4da 100644 --- a/Plugins/ZShell/Blobs/blobshape.hpp +++ b/Plugins/ZShell/Blobs/blobshape.hpp @@ -55,11 +55,11 @@ class BlobShape : public QQuickItem { virtual bool isExcluded(const BlobShape*) const { return false; } -virtual bool isCornerExcluded(const BlobShape* /*other*/) const { - return false; -} + virtual bool isCornerExcluded(const BlobShape* /*other*/) const { + return false; + } -virtual void cornerRadii(float out[4]) const; + virtual void cornerRadii(float out[4]) const; virtual void updatePhysics() {} @@ -67,25 +67,25 @@ virtual void cornerRadii(float out[4]) const; virtual void unregisterFromGroup(); void updateCenteredDeformMatrix(); -BlobGroup* m_group = nullptr; -qreal m_radius = 0; -QMatrix4x4 m_deformMatrix; // identity by default -QMatrix4x4 m_centeredDeformMatrix; + BlobGroup* m_group = nullptr; + qreal m_radius = 0; + 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; -float m_cachedPaddedH = 0; -float m_pendingDw = 0; -float m_pendingDh = 0; -QRectF m_localPaddedRect; -QVector m_cachedRects; -int m_cachedMyIndex = -2; -float m_pendingDx = 0; -float m_pendingDy = 0; -bool m_cachedHasInverted = false; -float m_cachedInvertedRadius = 0; -float m_cachedInvertedOuter[4] = {}; -float m_cachedInvertedInner[4] = {}; + // Cached data from updatePolish + 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; + float m_pendingDx = 0; + float m_pendingDy = 0; + bool m_cachedHasInverted = false; + float m_cachedInvertedRadius = 0; + float m_cachedInvertedOuter[4] = {}; + float m_cachedInvertedInner[4] = {}; }; diff --git a/Plugins/ZShell/appdb.cpp b/Plugins/ZShell/appdb.cpp index c66ac59..e095174 100644 --- a/Plugins/ZShell/appdb.cpp +++ b/Plugins/ZShell/appdb.cpp @@ -10,23 +10,28 @@ 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) { + : 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" }) { + {"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, metaProp.notifySignal(), this, thisMetaProp.notifySignal()); } QObject::connect(m_entry, &QObject::destroyed, this, [this]() { - m_entry = nullptr; - deleteLater(); - }); + m_entry = nullptr; + deleteLater(); + }); } QObject* AppEntry::entry() const { @@ -118,7 +123,9 @@ AppDb::AppDb(QObject* parent) db.open(); QSqlQuery query(db); - query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); + query.exec( + "CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, " + "frequency INTEGER)"); } QString AppDb::uuid() const { @@ -145,7 +152,9 @@ void AppDb::setPath(const QString& path) { db.open(); QSqlQuery query(db); - query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)"); + query.exec( + "CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, " + "frequency INTEGER)"); updateAppFrequencies(); } @@ -183,7 +192,9 @@ void AppDb::setFavoriteApps(const QStringList& favApps) { if (re.isValid()) { m_favoriteAppsRegex << re; } else { - qCWarning(lcAppDb) << "setFavoriteApps: regular expression is not valid:" << re.pattern(); + qCWarning(lcAppDb) + << "setFavoriteApps: regular expression is not valid:" + << re.pattern(); } } @@ -191,8 +202,7 @@ void AppDb::setFavoriteApps(const QStringList& favApps) { } QString AppDb::regexifyString(const QString& original) const { - if (original.startsWith('^') && original.endsWith('$')) - return original; + if (original.startsWith('^') && original.endsWith('$')) return original; const QString escaped = QRegularExpression::escape(original); return QStringLiteral("^%1$").arg(escaped); @@ -206,9 +216,10 @@ void AppDb::incrementFrequency(const QString& id) { 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.prepare( + "INSERT INTO frequencies (id, frequency) " + "VALUES (:id, 1) " + "ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1"); query.bindValue(":id", id); query.exec(); @@ -221,7 +232,8 @@ void AppDb::incrementFrequency(const QString& id) { emit appsChanged(); } } else { - qCWarning(lcAppDb) << "incrementFrequency: could not find app with id" << id; + qCWarning(lcAppDb) << "incrementFrequency: could not find app with id" + << id; } } @@ -232,15 +244,16 @@ QList& AppDb::getSortedApps() const { QSet favSet; favSet.reserve(m_sortedApps.size()); for (const auto* app : std::as_const(m_sortedApps)) { - if (isFavorite(app)) - favSet.insert(app->id()); + if (isFavorite(app)) favSet.insert(app->id()); } - std::sort(m_sortedApps.begin(), m_sortedApps.end(), [&favSet](AppEntry* a, AppEntry* b) { + 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 (aIsFav != bIsFav) return aIsFav; if (a->frequency() != b->frequency()) return a->frequency() > b->frequency(); return a->name().localeAwareCompare(b->name()) < 0; @@ -293,10 +306,10 @@ void AppDb::updateApps() { 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(); - } - }); + if (m_apps.remove(id)) { + emit appsChanged(); + } + }); m_apps.insert(id, newEntry); } } diff --git a/Plugins/ZShell/appdb.hpp b/Plugins/ZShell/appdb.hpp index 2e2a723..f0f328f 100644 --- a/Plugins/ZShell/appdb.hpp +++ b/Plugins/ZShell/appdb.hpp @@ -65,11 +65,16 @@ class AppDb : public QObject { Q_OBJECT 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) + 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: explicit AppDb(QObject* parent = nullptr); @@ -82,36 +87,36 @@ Q_PROPERTY(QQmlListProperty apps READ apps NOTIFY appsChanged) [[nodiscard]] QObjectList entries() const; void setEntries(const QObjectList& entries); -[[nodiscard]] QStringList favoriteApps() const; -void setFavoriteApps(const QStringList& favApps); + [[nodiscard]] QStringList favoriteApps() const; + void setFavoriteApps(const QStringList& favApps); -[[nodiscard]] QQmlListProperty apps(); + [[nodiscard]] QQmlListProperty apps(); Q_INVOKABLE void incrementFrequency(const QString& id); -signals: -void pathChanged(); -void entriesChanged(); -void favoriteAppsChanged(); -void appsChanged(); + signals: + void pathChanged(); + void entriesChanged(); + void favoriteAppsChanged(); + void appsChanged(); private: 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; + 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(); + 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(); }; } // namespace ZShell diff --git a/Plugins/ZShell/zutils.hpp b/Plugins/ZShell/zutils.hpp index 97bdaaf..1e037cf 100644 --- a/Plugins/ZShell/zutils.hpp +++ b/Plugins/ZShell/zutils.hpp @@ -12,18 +12,18 @@ class ZUtils : public QObject { QML_ELEMENT QML_SINGLETON -Q_PROPERTY(QString version READ version CONSTANT) -Q_PROPERTY(QString qtVersion READ qtVersion CONSTANT) + 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); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); -Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); -// clang-format on + public: + // clang-format off + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved); + Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed); + // clang-format on Q_INVOKABLE static bool copyFile( const QUrl& source, const QUrl& target, bool overwrite = true);