From e818ac5515d45f7fc9b1821971a66a5df64f796e Mon Sep 17 00:00:00 2001 From: Zacharias-Brohn Date: Tue, 17 Feb 2026 12:46:25 +0100 Subject: [PATCH] config fixes --- Bar.qml | 8 +- Components/CustomText.qml | 2 +- Components/Toast/ToastItem.qml | 135 ++++++++++++ Components/Toast/Toasts.qml | 144 +++++++++++++ Config/AppearanceConf.qml | 2 +- Config/BackgroundConfig.qml | 1 - Config/BarConfig.qml | 1 + Config/Colors.qml | 5 - Config/Config.qml | 355 +++++++++++++++++++++++++++---- Config/DashboardConfig.qml | 1 - Config/DynamicColors.qml | 6 +- Config/General.qml | 18 ++ Config/Launcher.qml | 7 + Config/MaterialEasing.qml | 2 +- Drawers/Interactions.qml | 4 +- Drawers/Panels.qml | 12 +- Helpers/SearchWallpapers.qml | 2 +- Helpers/SystemUsage.qml | 4 +- Modules/AudioWidget.qml | 10 +- Modules/Bar/Border.qml | 4 +- Modules/Clock.qml | 2 +- Modules/CustomTextField.qml | 2 +- Modules/Dashboard/Dash/Media.qml | 18 -- Modules/GroupListView.qml | 6 +- Modules/Launcher.qml | 16 +- Modules/Lock/IdleInhibitor.qml | 2 +- Modules/Lock/LockSurface.qml | 17 +- Modules/Lock/Media.qml | 2 +- Modules/NotifBell.qml | 2 +- Modules/Resource.qml | 4 +- Modules/ResourceDetail.qml | 6 +- Modules/ResourceUsage.qml | 4 +- Modules/Resources.qml | 4 +- Modules/TrayMenu.qml | 12 +- Modules/UpdatesWidget.qml | 4 +- Modules/Workspaces.qml | 8 +- Plugins/ZShell/CMakeLists.txt | 1 + Plugins/ZShell/toaster.cpp | 117 ++++++++++ Plugins/ZShell/toaster.hpp | 82 +++++++ 39 files changed, 888 insertions(+), 144 deletions(-) create mode 100644 Components/Toast/ToastItem.qml create mode 100644 Components/Toast/Toasts.qml create mode 100644 Config/Launcher.qml create mode 100644 Plugins/ZShell/toaster.cpp create mode 100644 Plugins/ZShell/toaster.hpp diff --git a/Bar.qml b/Bar.qml index fd0bb93..fda6c64 100644 --- a/Bar.qml +++ b/Bar.qml @@ -31,7 +31,7 @@ Variants { WlrLayershell.namespace: "ZShell-Bar-Exclusion" screen: bar.screen WlrLayershell.layer: WlrLayer.Bottom - WlrLayershell.exclusionMode: Config.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto + WlrLayershell.exclusionMode: Config.barConfig.autoHide ? ExclusionMode.Ignore : ExclusionMode.Auto anchors { left: true right: true @@ -51,7 +51,7 @@ Variants { mask: Region { id: region x: 0 - y: Config.autoHide && !visibilities.bar ? 4 : 34 + y: Config.barConfig.autoHide && !visibilities.bar ? 4 : 34 property list nullRegions: [] property bool hcurrent: ( panels.popouts.hasCurrent && panels.popouts.currentName.startsWith("traymenu") ) || visibilities.sidebar || visibilities.dashboard @@ -99,7 +99,7 @@ Variants { Item { anchors.fill: parent - opacity: Config.transparency.enabled ? DynamicColors.transparency.base : 1 + opacity: Appearance.transparency.enabled ? DynamicColors.transparency.base : 1 layer.enabled: true layer.effect: MultiEffect { shadowEnabled: true @@ -142,7 +142,7 @@ Variants { anchors.left: parent.left anchors.right: parent.right implicitHeight: 34 - anchors.topMargin: Config.autoHide && !visibilities.bar ? -30 : 0 + anchors.topMargin: Config.barConfig.autoHide && !visibilities.bar ? -30 : 0 color: "transparent" radius: 0 diff --git a/Components/CustomText.qml b/Components/CustomText.qml index 3342a4a..4b30529 100644 --- a/Components/CustomText.qml +++ b/Components/CustomText.qml @@ -16,7 +16,7 @@ Text { renderType: Text.NativeRendering textFormat: Text.PlainText color: DynamicColors.palette.m3onSurface - font.family: Config.baseFont + font.family: Appearance.font.family.sans font.pointSize: 12 Behavior on color { diff --git a/Components/Toast/ToastItem.qml b/Components/Toast/ToastItem.qml new file mode 100644 index 0000000..a245813 --- /dev/null +++ b/Components/Toast/ToastItem.qml @@ -0,0 +1,135 @@ +import ZShell +import QtQuick +import QtQuick.Layouts +import qs.Modules +import qs.Components +import qs.Helpers +import qs.Config + +CustomRect { + id: root + + required property Toast modelData + + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: layout.implicitHeight + Appearance.padding.smaller * 2 + + radius: Appearance.rounding.normal + color: { + if (root.modelData.type === Toast.Success) + return DynamicColors.palette.m3successContainer; + if (root.modelData.type === Toast.Warning) + return DynamicColors.palette.m3secondary; + if (root.modelData.type === Toast.Error) + return DynamicColors.palette.m3errorContainer; + return DynamicColors.palette.m3surface; + } + + border.width: 1 + border.color: { + let colour = DynamicColors.palette.m3outlineVariant; + if (root.modelData.type === Toast.Success) + colour = DynamicColors.palette.m3success; + if (root.modelData.type === Toast.Warning) + colour = DynamicColors.palette.m3secondaryContainer; + if (root.modelData.type === Toast.Error) + colour = DynamicColors.palette.m3error; + return Qt.alpha(colour, 0.3); + } + + Elevation { + anchors.fill: parent + radius: parent.radius + opacity: parent.opacity + z: -1 + level: 3 + } + + RowLayout { + id: layout + + anchors.fill: parent + anchors.margins: Appearance.padding.smaller + anchors.leftMargin: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.normal + spacing: Appearance.spacing.normal + + CustomRect { + radius: Appearance.rounding.normal + color: { + if (root.modelData.type === Toast.Success) + return DynamicColors.palette.m3success; + if (root.modelData.type === Toast.Warning) + return DynamicColors.palette.m3secondaryContainer; + if (root.modelData.type === Toast.Error) + return DynamicColors.palette.m3error; + return DynamicColors.palette.m3surfaceContainerHigh; + } + + implicitWidth: implicitHeight + implicitHeight: icon.implicitHeight + Appearance.padding.smaller * 2 + + MaterialIcon { + id: icon + + anchors.centerIn: parent + text: root.modelData.icon + color: { + if (root.modelData.type === Toast.Success) + return DynamicColors.palette.m3onSuccess; + if (root.modelData.type === Toast.Warning) + return DynamicColors.palette.m3onSecondaryContainer; + if (root.modelData.type === Toast.Error) + return DynamicColors.palette.m3onError; + return DynamicColors.palette.m3onSurfaceVariant; + } + font.pointSize: Math.round(Appearance.font.size.large * 1.2) + } + } + + ColumnLayout { + Layout.fillWidth: true + spacing: 0 + + CustomText { + id: title + + Layout.fillWidth: true + text: root.modelData.title + color: { + if (root.modelData.type === Toast.Success) + return DynamicColors.palette.m3onSuccessContainer; + if (root.modelData.type === Toast.Warning) + return DynamicColors.palette.m3onSecondary; + if (root.modelData.type === Toast.Error) + return DynamicColors.palette.m3onErrorContainer; + return DynamicColors.palette.m3onSurface; + } + font.pointSize: Appearance.font.size.normal + elide: Text.ElideRight + } + + CustomText { + Layout.fillWidth: true + textFormat: Text.StyledText + text: root.modelData.message + color: { + if (root.modelData.type === Toast.Success) + return DynamicColors.palette.m3onSuccessContainer; + if (root.modelData.type === Toast.Warning) + return DynamicColors.palette.m3onSecondary; + if (root.modelData.type === Toast.Error) + return DynamicColors.palette.m3onErrorContainer; + return DynamicColors.palette.m3onSurface; + } + opacity: 0.8 + elide: Text.ElideRight + } + } + } + + Behavior on border.color { + CAnim {} + } +} diff --git a/Components/Toast/Toasts.qml b/Components/Toast/Toasts.qml new file mode 100644 index 0000000..3846166 --- /dev/null +++ b/Components/Toast/Toasts.qml @@ -0,0 +1,144 @@ +pragma ComponentBehavior: Bound + +import qs.Components +import qs.Config +import qs.Modules +import ZShell +import Quickshell +import QtQuick + +Item { + id: root + + readonly property int spacing: Appearance.spacing.small + property bool flag + + implicitWidth: Config.utilities.sizes.toastWidth - Appearance.padding.normal * 2 + implicitHeight: { + let h = -spacing; + for (let i = 0; i < repeater.count; i++) { + const item = repeater.itemAt(i) as ToastWrapper; + if (!item.modelData.closed && !item.previewHidden) + h += item.implicitHeight + spacing; + } + return h; + } + + Repeater { + id: repeater + + model: ScriptModel { + values: { + const toasts = []; + let count = 0; + for (const toast of Toaster.toasts) { + toasts.push(toast); + if (!toast.closed) { + count++; + if (count > Config.utilities.maxToasts) + break; + } + } + return toasts; + } + onValuesChanged: root.flagChanged() + } + + ToastWrapper {} + } + + component ToastWrapper: MouseArea { + id: toast + + required property int index + required property Toast modelData + + readonly property bool previewHidden: { + let extraHidden = 0; + for (let i = 0; i < index; i++) + if (Toaster.toasts[i].closed) + extraHidden++; + return index >= Config.utilities.maxToasts + extraHidden; + } + + onPreviewHiddenChanged: { + if (initAnim.running && previewHidden) + initAnim.stop(); + } + + opacity: modelData.closed || previewHidden ? 0 : 1 + scale: modelData.closed || previewHidden ? 0.7 : 1 + + anchors.bottomMargin: { + root.flag; // Force update + let y = 0; + for (let i = 0; i < index; i++) { + const item = repeater.itemAt(i) as ToastWrapper; + if (item && !item.modelData.closed && !item.previewHidden) + y += item.implicitHeight + root.spacing; + } + return y; + } + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + implicitHeight: toastInner.implicitHeight + + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + onClicked: modelData.close() + + Component.onCompleted: modelData.lock(this) + + Anim { + id: initAnim + + Component.onCompleted: running = !toast.previewHidden + + target: toast + properties: "opacity,scale" + from: 0 + to: 1 + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + + ParallelAnimation { + running: toast.modelData.closed + onStarted: toast.anchors.bottomMargin = toast.anchors.bottomMargin + onFinished: toast.modelData.unlock(toast) + + Anim { + target: toast + property: "opacity" + to: 0 + } + Anim { + target: toast + property: "scale" + to: 0.7 + } + } + + ToastItem { + id: toastInner + + modelData: toast.modelData + } + + Behavior on opacity { + Anim {} + } + + Behavior on scale { + Anim {} + } + + Behavior on anchors.bottomMargin { + Anim { + duration: Appearance.anim.durations.expressiveDefaultSpatial + easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial + } + } + } +} diff --git a/Config/AppearanceConf.qml b/Config/AppearanceConf.qml index 3d590dc..5ccaec5 100644 --- a/Config/AppearanceConf.qml +++ b/Config/AppearanceConf.qml @@ -35,7 +35,7 @@ JsonObject { } component FontFamily: JsonObject { - property string sans: "Rubik" + property string sans: "Segoe UI Variable Text" property string mono: "CaskaydiaCove NF" property string material: "Material Symbols Rounded" property string clock: "Rubik" diff --git a/Config/BackgroundConfig.qml b/Config/BackgroundConfig.qml index be19afe..f34b832 100644 --- a/Config/BackgroundConfig.qml +++ b/Config/BackgroundConfig.qml @@ -1,5 +1,4 @@ import Quickshell.Io -import qs.Modules import qs.Config JsonObject { diff --git a/Config/BarConfig.qml b/Config/BarConfig.qml index 4a17694..8219c92 100644 --- a/Config/BarConfig.qml +++ b/Config/BarConfig.qml @@ -1,6 +1,7 @@ import Quickshell.Io JsonObject { + property bool autoHide: false property Popouts popouts: Popouts {} property list entries: [ diff --git a/Config/Colors.qml b/Config/Colors.qml index 61dc4fb..287ea02 100644 --- a/Config/Colors.qml +++ b/Config/Colors.qml @@ -1,10 +1,5 @@ import Quickshell.Io JsonObject { - property BgColors backgrounds: BgColors {} property string schemeType: "vibrant" - - component BgColors: JsonObject { - property string hover: "#15ffffff" - } } diff --git a/Config/Config.qml b/Config/Config.qml index a41f36c..cbb21f7 100644 --- a/Config/Config.qml +++ b/Config/Config.qml @@ -2,28 +2,17 @@ pragma Singleton import Quickshell import Quickshell.Io -import qs.Modules +import ZShell +import QtQuick +import qs.Modules as Modules +import qs.Paths Singleton { + id: root - property alias appCount: adapter.appCount - property alias baseBgColor: adapter.baseBgColor - property alias baseBorderColor: adapter.baseBorderColor - property alias accentColor: adapter.accentColor - property alias wallpaperPath: adapter.wallpaperPath - property alias maxWallpapers: adapter.maxWallpapers - property alias wallust: adapter.wallust - property alias workspaceWidget: adapter.workspaceWidget - property alias colors: adapter.colors - property alias gpuType: adapter.gpuType property alias background: adapter.background - property alias useDynamicColors: adapter.useDynamicColors property alias barConfig: adapter.barConfig - property alias transparency: adapter.transparency - property alias baseFont: adapter.baseFont - property alias animScale: adapter.animScale property alias lock: adapter.lock - property alias idle: adapter.idle property alias overview: adapter.overview property alias services: adapter.services property alias notifs: adapter.notifs @@ -32,41 +21,327 @@ Singleton { property alias general: adapter.general property alias dashboard: adapter.dashboard property alias appearance: adapter.appearance - property alias autoHide: adapter.autoHide - property alias macchiato: adapter.macchiato property alias osd: adapter.osd + property alias launcher: adapter.launcher + property alias colors: adapter.colors + + function save(): void { + saveTimer.restart(); + recentlySaved = true; + recentSaveCooldown.restart(); + } + + property bool recentlySaved: false + + ElapsedTimer { + id: timer + } + + Timer { + running: true + interval: 5000 + repeat: false + + onTriggered: { + + root.save(); + } + } + + Timer { + id: saveTimer + + interval: 500 + 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); + } + } + } + + Timer { + id: recentSaveCooldown + + interval: 2000 + onTriggered: { + root.recentlySaved = false; + } + } + + 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() + } + } + + function serializeBar(): var { + return { + autoHide: barConfig.autoHide, + 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 serializeLock(): var { + return { + recolorLogo: lock.recolorLogo, + enableFprint: lock.enableFprint, + maxFprintTries: lock.maxFprintTries, + sizes: { + heightMult: lock.sizes.heightMult, + ratio: lock.sizes.ratio, + centerWidth: lock.sizes.centerWidth + } + }; + } + + function serializeGeneral(): var { + return { + logo: general.logo, + wallpaperPath: general.wallpaperPath, + wallust: general.wallust, + idle: { + timouts: general.idle.timeouts + } + } + } + + function serializeServices(): var { + return { + weatherLocation: services.weatherLocation, + useFahrenheit: services.useFahrenheit, + useTwelveHourClock: services.useTwelveHourClock, + gpuType: services.gpuType, + audioIncrement: services.audioIncrement, + brightnessIncrement: services.brightnessIncrement, + maxVolume: services.maxVolume, + defaultPlayer: services.defaultPlayer, + playerAliases: services.playerAliases + }; + } + + function serializeNotifs(): var { + return { + expire: notifs.expire, + defaultExpireTimeout: notifs.defaultExpireTimeout, + 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 serializeSidebar(): var { + return { + enabled: sidebar.enabled, + 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 + } + }; + } + + function serializeDashboard(): var { + return { + enabled: dashboard.enabled, + mediaUpdateInterval: dashboard.mediaUpdateInterval, + dragThreshold: dashboard.dragThreshold, + 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, + mediaVisualiserSize: dashboard.sizes.mediaVisualiserSize, + resourceSize: dashboard.sizes.resourceSize + } + }; + } + + function serializeOsd(): var { + return { + enabled: osd.enabled, + hideDelay: osd.hideDelay, + enableBrightness: osd.enableBrightness, + enableMicrophone: osd.enableMicrophone, + sizes: { + sliderWidth: osd.sizes.sliderWidth, + sliderHeight: osd.sizes.sliderHeight + } + }; + } + + function serializeLauncher(): var { + return { + maxAppsShown: launcher.maxAppsShown, + maxWallpapers: launcher.maxWallpapers, + wallpaperPrefix: launcher.wallpaperPrefix + } + } + + function serializeBackground(): var { + return { + wallFadeDuration: background.wallFadeDuration, + enabled: background.enabled + } + } + + function serializeAppearance(): var { + return { + rounding: { + scale: appearance.rounding.scale + }, + spacing: { + scale: appearance.spacing.scale + }, + padding: { + scale: appearance.padding.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: 300, + sessionGifSpeed: 0.7, + durations: { + scale: appearance.anim.durations.scale + } + }, + transparency: { + enabled: appearance.transparency.enabled, + base: appearance.transparency.base, + layers: appearance.transparency.layers + } + }; + } + + function serializeColors(): var { + return { + schemeType: colors.schemeType, + } + } FileView { - id: root - property var configRoot: Quickshell.env("HOME") + id: fileView - path: configRoot + "/.config/z-bar/config.json" + path: `${Paths.config}/config.json` watchChanges: true - onFileChanged: reload() - onAdapterChanged: writeAdapter() + onFileChanged: { + if ( !root.recentlySaved ) { + timer.restart(); + reload(); + } else { + reload(); + } + } + + onLoaded: { + try { + JSON.parse(text()); + const elapsed = timer.elapsedMs(); + + if ( adapter.utilities.toasts.configLoaded && !root.recentlySaved ) { + Toaster.toast(qsTr("Config loaded"), qsTr("Config loaded in %1ms").arg(elapsed), "rule_settings"); + } else if ( adapter.utilities.toasts.configLoaded && root.recentlySaved ) { + Toaster.toast(qsTr("Config saved"), qsTr("Config reloaded in %1ms").arg(elapsed), "settings_alert"); + } + } catch (e) { + Toaster.toast(qsTr("Failed to load config"), e.message, "settings_alert", Toast.Error); + } + } + + onLoadFailed: err => { + if ( err !== FileViewError.FileNotFound ) + Toaster.toast(qsTr("Failed to read config"), FileViewError.toString(err), "settings_alert", Toast.Warning); + } + + onSaveFailed: err => Toaster.toast(qsTr("Failed to save config"), FileViewError.toString(err), "settings_alert", Toast.Error) JsonAdapter { id: adapter - property int appCount: 20 - property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" - property string baseBgColor: "#801a1a1a" - property string baseBorderColor: "#444444" - property AccentColor accentColor: AccentColor {} - property int maxWallpapers: 7 - property bool wallust: false - property WorkspaceWidget workspaceWidget: WorkspaceWidget {} - property Colors colors: Colors {} - property string gpuType: "" property BackgroundConfig background: BackgroundConfig {} - property bool useDynamicColors: false property BarConfig barConfig: BarConfig {} - property Transparency transparency: Transparency {} - property string baseFont: "Segoe UI Variable Text" - property real animScale: 1.0 property LockConf lock: LockConf {} - property IdleTimeout idle: IdleTimeout {} property Overview overview: Overview {} property Services services: Services {} property NotifConfig notifs: NotifConfig {} @@ -75,9 +350,9 @@ Singleton { property General general: General {} property DashboardConfig dashboard: DashboardConfig {} property AppearanceConf appearance: AppearanceConf {} - property bool autoHide: false - property bool macchiato: false property Osd osd: Osd {} + property Launcher launcher: Launcher {} + property Colors colors: Colors {} } } } diff --git a/Config/DashboardConfig.qml b/Config/DashboardConfig.qml index 030292b..f1034fb 100644 --- a/Config/DashboardConfig.qml +++ b/Config/DashboardConfig.qml @@ -2,7 +2,6 @@ import Quickshell.Io JsonObject { property bool enabled: true - property bool showOnHover: true property int mediaUpdateInterval: 500 property int dragThreshold: 50 property Sizes sizes: Sizes {} diff --git a/Config/DynamicColors.qml b/Config/DynamicColors.qml index 434f435..03d3bc0 100644 --- a/Config/DynamicColors.qml +++ b/Config/DynamicColors.qml @@ -88,9 +88,9 @@ Singleton { } component Transparency: QtObject { - readonly property bool enabled: Config.transparency.enabled - readonly property real base: Config.transparency.base - (root.light ? 0.1 : 0) - readonly property real layers: Config.transparency.layers + readonly property bool enabled: Appearance.transparency.enabled + readonly property real base: Appearance.transparency.base - (root.light ? 0.1 : 0) + readonly property real layers: Appearance.transparency.layers } component M3TPalette: QtObject { diff --git a/Config/General.qml b/Config/General.qml index 882df88..5411a79 100644 --- a/Config/General.qml +++ b/Config/General.qml @@ -1,5 +1,23 @@ import Quickshell.Io +import Quickshell JsonObject { property string logo: "" + property string wallpaperPath: Quickshell.env("HOME") + "/Pictures/Wallpapers" + property bool wallust: false + property Idle idle: Idle {} + + component Idle: JsonObject { + property list timeouts: [ + { + timeout: 180, + idleAction: "lock" + }, + { + timeout: 300, + idleAction: "dpms off", + activeAction: "dpms on" + } + ] + } } diff --git a/Config/Launcher.qml b/Config/Launcher.qml new file mode 100644 index 0000000..2fca110 --- /dev/null +++ b/Config/Launcher.qml @@ -0,0 +1,7 @@ +import Quickshell.Io + +JsonObject { + property int maxAppsShown: 10 + property int maxWallpapers: 7 + property string wallpaperPrefix: ">" +} diff --git a/Config/MaterialEasing.qml b/Config/MaterialEasing.qml index 53748fa..cbd4718 100644 --- a/Config/MaterialEasing.qml +++ b/Config/MaterialEasing.qml @@ -4,7 +4,7 @@ import Quickshell Singleton { id: root - property real scale: Config.animScale + property real scale: Appearance.anim.durations.scale readonly property list emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1] readonly property list emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1] diff --git a/Drawers/Interactions.qml b/Drawers/Interactions.qml index e3c57d8..13ddf48 100644 --- a/Drawers/Interactions.qml +++ b/Drawers/Interactions.qml @@ -75,7 +75,7 @@ CustomMouseArea { popouts.hasCurrent = false; } - if (Config.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard) + if (Config.barConfig.autoHide && !root.visibilities.sidebar && !root.visibilities.dashboard) root.visibilities.bar = false; } } @@ -90,7 +90,7 @@ CustomMouseArea { const dragY = y - dragStart.y; // Show bar in non-exclusive mode on hover - if (!visibilities.bar && Config.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) + if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) visibilities.bar = true; if (panels.sidebar.width === 0) { diff --git a/Drawers/Panels.qml b/Drawers/Panels.qml index 255eac3..ffdab3a 100644 --- a/Drawers/Panels.qml +++ b/Drawers/Panels.qml @@ -7,6 +7,7 @@ import qs.Modules.Notifications.Sidebar as Sidebar import qs.Modules.Notifications.Sidebar.Utils as Utils import qs.Modules.Dashboard as Dashboard import qs.Modules.Osd as Osd +import qs.Components.Toast as Toasts import qs.Config Item { @@ -22,6 +23,7 @@ Item { readonly property alias utilities: utilities readonly property alias dashboard: dashboard readonly property alias osd: osd + readonly property alias toasts: toasts anchors.fill: parent // anchors.margins: 8 @@ -30,7 +32,7 @@ Item { Osd.Wrapper { id: osd - clip: session.width > 0 || sidebar.width > 0 + clip: sidebar.width > 0 screen: root.screen visibilities: root.visibilities @@ -55,6 +57,14 @@ Item { } } + Toasts.Toasts { + id: toasts + + anchors.bottom: sidebar.visible ? parent.bottom : utilities.top + anchors.right: sidebar.left + anchors.margins: Appearance.padding.normal + } + Notifications.Wrapper { id: notifications diff --git a/Helpers/SearchWallpapers.qml b/Helpers/SearchWallpapers.qml index ba1b42f..1bbca74 100644 --- a/Helpers/SearchWallpapers.qml +++ b/Helpers/SearchWallpapers.qml @@ -43,7 +43,7 @@ Searcher { id: wallpapers recursive: true - path: Config.wallpaperPath + path: Config.general.wallpaperPath filter: FileSystemModel.Images } } diff --git a/Helpers/SystemUsage.qml b/Helpers/SystemUsage.qml index d13d93b..075545c 100644 --- a/Helpers/SystemUsage.qml +++ b/Helpers/SystemUsage.qml @@ -10,7 +10,7 @@ Singleton { property real cpuPerc property real cpuTemp - readonly property string gpuType: Config.gpuType.toUpperCase() || autoGpuType + readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType property string autoGpuType: "NONE" property real gpuPerc property real gpuTemp @@ -145,7 +145,7 @@ Singleton { Process { id: gpuTypeCheck - running: !Config.gpuType + running: !Config.services.gpuType command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; 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"] stdout: StdioCollector { onStreamFinished: root.autoGpuType = text.trim() diff --git a/Modules/AudioWidget.qml b/Modules/AudioWidget.qml index 745cf3c..3fe50bd 100644 --- a/Modules/AudioWidget.qml +++ b/Modules/AudioWidget.qml @@ -13,8 +13,8 @@ Item { anchors.bottom: parent.bottom property bool expanded: false - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" - property color barColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "#ffffff" + property color textColor: DynamicColors.palette.m3tertiaryFixed + property color barColor: DynamicColors.palette.m3primary Behavior on implicitWidth { NumberAnimation { @@ -37,7 +37,7 @@ Item { anchors.right: parent.right height: 22 radius: height / 2 - color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000" + color: DynamicColors.tPalette.m3surfaceContainer Behavior on color { CAnim {} @@ -95,7 +95,7 @@ Item { Layout.alignment: Qt.AlignVCenter font.pixelSize: 18 text: Pipewire.defaultAudioSource?.audio.muted ? "mic_off" : "mic" - color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3error : "#ff4444") : root.textColor + color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? DynamicColors.palette.m3error : root.textColor } Rectangle { @@ -115,7 +115,7 @@ Item { implicitWidth: parent.width * ( Pipewire.defaultAudioSource?.audio.volume ?? 0 ) radius: parent.radius - color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3error : "#ff4444") : root.barColor + color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? DynamicColors.palette.m3error : root.barColor Behavior on color { CAnim {} diff --git a/Modules/Bar/Border.qml b/Modules/Bar/Border.qml index dbb0324..8bb4e20 100644 --- a/Modules/Bar/Border.qml +++ b/Modules/Bar/Border.qml @@ -17,7 +17,7 @@ Item { CustomRect { anchors.fill: parent - color: Config.autoHide && !root.visibilities.bar ? "transparent" : DynamicColors.palette.m3surface + color: Config.barConfig.autoHide && !root.visibilities.bar ? "transparent" : DynamicColors.palette.m3surface layer.enabled: true @@ -38,7 +38,7 @@ Item { Rectangle { anchors.fill: parent - anchors.topMargin: Config.autoHide && !root.visibilities.bar ? 4 : root.bar.implicitHeight + anchors.topMargin: Config.barConfig.autoHide && !root.visibilities.bar ? 4 : root.bar.implicitHeight topLeftRadius: 8 topRightRadius: 8 Behavior on anchors.topMargin { diff --git a/Modules/Clock.qml b/Modules/Clock.qml index b04cbb4..4e7b13e 100644 --- a/Modules/Clock.qml +++ b/Modules/Clock.qml @@ -27,7 +27,7 @@ Item { anchors.centerIn: parent text: Time.dateStr - color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white" + color: DynamicColors.palette.m3tertiary Behavior on color { CAnim {} diff --git a/Modules/CustomTextField.qml b/Modules/CustomTextField.qml index 4474599..615ec83 100644 --- a/Modules/CustomTextField.qml +++ b/Modules/CustomTextField.qml @@ -85,7 +85,7 @@ TextField { launcherWindow.visible = false; } else if ( wallpaperPickerLoader.active ) { SearchWallpapers.setWallpaper(wallpaperPickerLoader.item.currentItem.modelData.path) - if ( Config.wallust ) { + if ( Config.general.wallust ) { Wallust.generateColors(WallpaperPath.currentWallpaperPath); } closeAnim.start(); diff --git a/Modules/Dashboard/Dash/Media.qml b/Modules/Dashboard/Dash/Media.qml index b7058e6..fb4faab 100644 --- a/Modules/Dashboard/Dash/Media.qml +++ b/Modules/Dashboard/Dash/Media.qml @@ -202,24 +202,6 @@ Item { } } - AnimatedImage { - id: bongocat - - anchors.top: controls.bottom - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: Appearance.spacing.small - anchors.bottomMargin: Appearance.padding.large - anchors.margins: Appearance.padding.large * 2 - - playing: Players.active?.isPlaying ?? false - speed: Audio.beatTracker.bpm / Appearance.anim.mediaGifSpeedAdjustment - source: Paths.absolutePath(Config.paths.mediaGif) - asynchronous: true - fillMode: AnimatedImage.PreserveAspectFit - } - component Control: CustomRect { id: control diff --git a/Modules/GroupListView.qml b/Modules/GroupListView.qml index 0b0d049..5088a30 100644 --- a/Modules/GroupListView.qml +++ b/Modules/GroupListView.qml @@ -30,7 +30,7 @@ Repeater { property bool shouldShow: false property bool isExpanded: false property bool collapseAnimRunning: false - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "white" + property color textColor: DynamicColors.palette.m3onSurface function closeAll(): void { for ( const n of NotifServer.notClosed.filter( n => n.appName === modelData )) @@ -120,8 +120,8 @@ Repeater { Rectangle { id: collapseRect - property color notifyBgColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : "#E53935" - property color notifyColor: Config.useDynamicColors ? DynamicColors.palette.m3onPrimary : "#FFCDD2" + property color notifyBgColor: DynamicColors.palette.m3primary + property color notifyColor: DynamicColors.palette.m3onPrimary Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.fillHeight: true diff --git a/Modules/Launcher.qml b/Modules/Launcher.qml index 35dfca3..c72b873 100644 --- a/Modules/Launcher.qml +++ b/Modules/Launcher.qml @@ -65,17 +65,15 @@ Scope { Rectangle { id: backgroundRect - property color backgroundColor: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor + property color backgroundColor: DynamicColors.tPalette.m3surface anchors.bottom: parent.bottom - anchors.bottomMargin: Config.useDynamicColors ? 0 : -1 + anchors.bottomMargin: 0 implicitHeight: mainLayout.childrenRect.height + 20 implicitWidth: appListRect.implicitWidth x: Math.round(( parent.width - width ) / 2 ) color: backgroundColor opacity: 1 - border.width: Config.useDynamicColors ? 0 : 1 - border.color: Config.useDynamicColors ? "transparent" : Config.baseBorderColor ParallelAnimation { id: openAnim @@ -176,11 +174,9 @@ Scope { implicitWidth: appListContainer.implicitWidth + 20 implicitHeight: appListContainer.implicitHeight + 20 anchors.bottom: backgroundRect.top - anchors.bottomMargin: Config.useDynamicColors ? 0 : -1 color: backgroundRect.color topRightRadius: 8 topLeftRadius: 8 - border.color: Config.useDynamicColors ? "transparent" : backgroundRect.border.color clip: true Behavior on implicitHeight { @@ -252,7 +248,7 @@ Scope { const maxItemsOnScreen = Math.floor( maxWidth / itemWidth ); - const visible = Math.min( maxItemsOnScreen, Config.maxWallpapers, wallpaperModel.values.length ); + const visible = Math.min( maxItemsOnScreen, Config.launcher.maxWallpapers, wallpaperModel.values.length ); if ( visible === 2 ) return 1; @@ -313,7 +309,7 @@ Scope { sourceComponent: ListView { id: appListView - property color highlightColor: Config.useDynamicColors ? DynamicColors.tPalette.m3onSurface : "#FFFFFF" + property color highlightColor: DynamicColors.tPalette.m3onSurface anchors.fill: parent model: ScriptModel { @@ -325,7 +321,7 @@ Scope { } verticalLayoutDirection: ListView.BottomToTop - implicitHeight: Math.min( count, Config.appCount ) * 48 + implicitHeight: Math.min( count, Config.launcher.maxAppsShown ) * 48 preferredHighlightBegin: 0 preferredHighlightEnd: appListView.height @@ -335,7 +331,7 @@ Scope { highlight: Rectangle { radius: 4 color: appListView.highlightColor - opacity: Config.useDynamicColors ? 0.20 : 0.08 + opacity: 0.20 y: appListView.currentItem?.y implicitWidth: appListView.width diff --git a/Modules/Lock/IdleInhibitor.qml b/Modules/Lock/IdleInhibitor.qml index e4f6214..e5312de 100644 --- a/Modules/Lock/IdleInhibitor.qml +++ b/Modules/Lock/IdleInhibitor.qml @@ -26,7 +26,7 @@ Scope { } Variants { - model: Config.idle.timeouts + model: Config.general.idle.timeouts IdleMonitor { required property var modelData diff --git a/Modules/Lock/LockSurface.qml b/Modules/Lock/LockSurface.qml index 1c4648e..8a1a5f1 100644 --- a/Modules/Lock/LockSurface.qml +++ b/Modules/Lock/LockSurface.qml @@ -109,21 +109,8 @@ WlSessionLockSurface { duration: Appearance.anim.durations.expressiveFastSpatial easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial } - Modules.Anim { - target: lockContent - property: "rotation" - to: 360 - duration: Appearance.anim.durations.expressiveFastSpatial - easing.bezierCurve: Appearance.anim.curves.standardAccel - } } ParallelAnimation { - Modules.Anim { - target: lockIcon - property: "rotation" - to: 360 - easing.bezierCurve: Appearance.anim.curves.standardDecel - } Modules.Anim { target: lockIcon property: "opacity" @@ -185,7 +172,6 @@ WlSessionLockSurface { implicitWidth: size implicitHeight: size - rotation: 180 scale: 0 CustomRect { @@ -193,7 +179,7 @@ WlSessionLockSurface { anchors.fill: parent color: DynamicColors.palette.m3surface - radius: parent.radius + radius: lockContent.radius opacity: DynamicColors.transparency.enabled ? DynamicColors.transparency.base : 1 layer.enabled: true @@ -211,7 +197,6 @@ WlSessionLockSurface { text: "lock" font.pointSize: Appearance.font.size.extraLarge * 4 font.bold: true - rotation: 180 } Content { diff --git a/Modules/Lock/Media.qml b/Modules/Lock/Media.qml index 2d3d167..4509a6d 100644 --- a/Modules/Lock/Media.qml +++ b/Modules/Lock/Media.qml @@ -156,7 +156,7 @@ Item { implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2 color: active ? DynamicColors.palette[`m3${colour.toLowerCase()}`] : DynamicColors.palette[`m3${colour.toLowerCase()}Container`] - radius: active || controlState.pressed ? Appearance.rounding.normal : Math.min(implicitWidth, implicitHeight) / 2 * Math.min(1, Appearance.rounding.scale) + radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal Elevation { anchors.fill: parent diff --git a/Modules/NotifBell.qml b/Modules/NotifBell.qml index e694157..417e21e 100644 --- a/Modules/NotifBell.qml +++ b/Modules/NotifBell.qml @@ -26,7 +26,7 @@ Item { anchors.centerIn: parent - property color iconColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "white" + property color iconColor: DynamicColors.palette.m3tertiaryFixed text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4" font.family: "Material Symbols Rounded" diff --git a/Modules/Resource.qml b/Modules/Resource.qml index 719b5f9..0666385 100644 --- a/Modules/Resource.qml +++ b/Modules/Resource.qml @@ -15,8 +15,8 @@ Item { implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth implicitHeight: 22 property bool warning: percentage * 100 >= warningThreshold - property color usageColor: Config.useDynamicColors ? ( warning ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary ) : ( warning ? Config.accentColor.accents.warning : Config.accentColor.accents.primary ) - property color borderColor: Config.useDynamicColors ? ( warning ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary ) : ( warning ? Config.accentColor.accents.warningAlt : Config.accentColor.accents.primaryAlt ) + property color usageColor: warning ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary + property color borderColor: warning ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onPrimary Behavior on percentage { NumberAnimation { diff --git a/Modules/ResourceDetail.qml b/Modules/ResourceDetail.qml index 76484e2..fdf86f4 100644 --- a/Modules/ResourceDetail.qml +++ b/Modules/ResourceDetail.qml @@ -11,9 +11,9 @@ Item { required property int warningThreshold required property string details required property string iconString - property color barColor: Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary - property color warningBarColor: Config.useDynamicColors ? DynamicColors.palette.m3error : Config.accentColor.accents.warning - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "#ffffff" + property color barColor: DynamicColors.palette.m3primary + property color warningBarColor: DynamicColors.palette.m3error + property color textColor: DynamicColors.palette.m3onSurface Layout.preferredWidth: 158 Layout.preferredHeight: columnLayout.implicitHeight diff --git a/Modules/ResourceUsage.qml b/Modules/ResourceUsage.qml index 3cb8b51..415b8b0 100644 --- a/Modules/ResourceUsage.qml +++ b/Modules/ResourceUsage.qml @@ -21,7 +21,7 @@ Singleton { property double gpuUsage: 0 property double gpuMemUsage: 0 property double totalMem: 0 - readonly property string gpuType: Config.gpuType.toUpperCase() || autoGpuType + readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType property string autoGpuType: "NONE" Timer { @@ -70,7 +70,7 @@ Singleton { Process { id: gpuTypeCheck - running: !Config.gpuType + running: !Config.services.gpuType command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; 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"] stdout: StdioCollector { onStreamFinished: root.autoGpuType = text.trim() diff --git a/Modules/Resources.qml b/Modules/Resources.qml index 2855ad9..a0d7058 100644 --- a/Modules/Resources.qml +++ b/Modules/Resources.qml @@ -13,7 +13,7 @@ Item { id: root implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: 34 - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" + property color textColor: DynamicColors.palette.m3tertiaryFixed clip: true Rectangle { @@ -24,7 +24,7 @@ Item { verticalCenter: parent.verticalCenter } implicitHeight: 22 - color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000" + color: DynamicColors.tPalette.m3surfaceContainer radius: height / 2 Behavior on color { CAnim {} diff --git a/Modules/TrayMenu.qml b/Modules/TrayMenu.qml index 7555770..60d29e6 100644 --- a/Modules/TrayMenu.qml +++ b/Modules/TrayMenu.qml @@ -26,11 +26,11 @@ PanelWindow { property int biggestWidth: 0 property int menuItemCount: menuOpener.children.values.length - property color backgroundColor: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor - property color highlightColor: Config.useDynamicColors ? DynamicColors.tPalette.m3primaryContainer : "#15FFFFFF" - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "white" - property color disabledHighlightColor: Config.useDynamicColors ? DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0) : "#08FFFFFF" - property color disabledTextColor: Config.useDynamicColors ? DynamicColors.layer(DynamicColors.palette.m3onSurface, 0) : "#80FFFFFF" + property color backgroundColor: DynamicColors.tPalette.m3surface + property color highlightColor: DynamicColors.tPalette.m3primaryContainer + property color textColor: DynamicColors.palette.m3onSurface + property color disabledHighlightColor: DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0) + property color disabledTextColor: DynamicColors.layer(DynamicColors.palette.m3onSurface, 0) QsMenuOpener { id: menuOpener @@ -193,8 +193,6 @@ PanelWindow { implicitHeight: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 ) color: root.backgroundColor radius: 8 - border.width: Config.useDynamicColors ? 0 : 1 - border.color: "#40FFFFFF" clip: true Behavior on implicitWidth { diff --git a/Modules/UpdatesWidget.qml b/Modules/UpdatesWidget.qml index a849f0b..e34207e 100644 --- a/Modules/UpdatesWidget.qml +++ b/Modules/UpdatesWidget.qml @@ -9,7 +9,7 @@ Item { implicitWidth: textMetrics.width + contentRow.spacing + 30 anchors.top: parent.top anchors.bottom: parent.bottom - property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3tertiaryFixed : "#ffffff" + property color textColor: DynamicColors.palette.m3tertiaryFixed Rectangle { anchors.left: parent.left @@ -17,7 +17,7 @@ Item { anchors.verticalCenter: parent.verticalCenter implicitHeight: 22 radius: height / 2 - color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000" + color: DynamicColors.tPalette.m3surfaceContainer Behavior on color { CAnim {} } diff --git a/Modules/Workspaces.qml b/Modules/Workspaces.qml index 8d8ec41..1d1c084 100644 --- a/Modules/Workspaces.qml +++ b/Modules/Workspaces.qml @@ -43,7 +43,7 @@ Item { } } - color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : "#40000000" + color: DynamicColors.tPalette.m3surfaceContainer radius: height / 2 Behavior on color { @@ -67,7 +67,7 @@ Item { CustomText { text: workspaceIndicator.modelData.name font.pointSize: 12 - color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? ( Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary ) : ( Config.useDynamicColors ? DynamicColors.palette.m3onSurfaceVariant : "#606060" ) + color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant visible: true } @@ -79,7 +79,7 @@ Item { border.width: 1 color: "transparent" - border.color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? ( Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary ) : ( Config.useDynamicColors ? DynamicColors.palette.m3onSurfaceVariant : "#606060" ) + border.color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant scale: 1.0 @@ -91,7 +91,7 @@ Item { implicitHeight: 8 radius: implicitHeight / 2 - color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? ( Config.useDynamicColors ? DynamicColors.palette.m3primary : Config.accentColor.accents.primary ) : "transparent" + color: workspaceIndicator.modelData.id === Hyprland.focusedWorkspace.id ? DynamicColors.palette.m3primary : "transparent" } Behavior on border.color { diff --git a/Plugins/ZShell/CMakeLists.txt b/Plugins/ZShell/CMakeLists.txt index a18b405..9068c5e 100644 --- a/Plugins/ZShell/CMakeLists.txt +++ b/Plugins/ZShell/CMakeLists.txt @@ -42,6 +42,7 @@ qml_module(ZShell appdb.hpp appdb.cpp imageanalyser.hpp imageanalyser.cpp requests.hpp requests.cpp + toaster.hpp toaster.cpp LIBRARIES Qt::Gui Qt::Quick diff --git a/Plugins/ZShell/toaster.cpp b/Plugins/ZShell/toaster.cpp new file mode 100644 index 0000000..cc6088c --- /dev/null +++ b/Plugins/ZShell/toaster.cpp @@ -0,0 +1,117 @@ +#include "toaster.hpp" + +#include +#include +#include + +namespace ZShell { + +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) + , m_message(message) + , m_icon(icon) + , m_type(type) + , m_timeout(timeout) { + QTimer::singleShot(timeout, this, &Toast::close); + + if (m_icon.isEmpty()) { + switch (m_type) { + case Type::Success: + m_icon = "check_circle_unread"; + break; + case Type::Warning: + m_icon = "warning"; + break; + case Type::Error: + m_icon = "error"; + break; + default: + m_icon = "info"; + break; + } + } + + if (timeout <= 0) { + switch (m_type) { + case Type::Warning: + m_timeout = 7000; + break; + case Type::Error: + m_timeout = 10000; + break; + default: + m_timeout = 5000; + break; + } + } +} + +bool Toast::closed() const { + return m_closed; +} + +QString Toast::title() const { + return m_title; +} + +QString Toast::message() const { + return m_message; +} + +QString Toast::icon() const { + return m_icon; +} + +int Toast::timeout() const { + return m_timeout; +} + +Toast::Type Toast::type() const { + return m_type; +} + +void Toast::close() { + if (!m_closed) { + m_closed = true; + emit closedChanged(); + } + + if (m_locks.isEmpty()) { + emit finishedClose(); + } +} + +void Toast::lock(QObject* sender) { + m_locks << sender; + QObject::connect(sender, &QObject::destroyed, this, &Toast::unlock); +} + +void Toast::unlock(QObject* sender) { + if (m_locks.remove(sender) && m_closed) { + close(); + } +} + +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) { + 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(); + } + }); + m_toasts.push_front(toast); + emit toastsChanged(); +} + +} // namespace ZShell diff --git a/Plugins/ZShell/toaster.hpp b/Plugins/ZShell/toaster.hpp new file mode 100644 index 0000000..0fb0d53 --- /dev/null +++ b/Plugins/ZShell/toaster.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include + +namespace ZShell { + +class Toast : public QObject { +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) + +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); + +[[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); + +signals: +void closedChanged(); +void finishedClose(); + +private: +QSet m_locks; + +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_PROPERTY(QQmlListProperty toasts READ toasts NOTIFY toastsChanged) + +public: +explicit Toaster(QObject* parent = nullptr); + +[[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); + +signals: +void toastsChanged(); + +private: +QList m_toasts; +}; + +} // namespace ZShell