diff --git a/Assets/Wallpaper/noctalia.png b/Assets/Wallpaper/noctalia.png new file mode 100644 index 0000000..a9b41a2 Binary files /dev/null and b/Assets/Wallpaper/noctalia.png differ diff --git a/Commons/Color.qml b/Commons/Color.qml index d7636a0..7abc21f 100644 --- a/Commons/Color.qml +++ b/Commons/Color.qml @@ -102,7 +102,7 @@ Singleton { // FileView to load custom colors data from colors.json FileView { id: customColorsFile - path: Settings.configDir + "colors.json" + path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : "" watchChanges: true onFileChanged: { Logger.log("Color", "Reloading colors from disk") @@ -112,6 +112,13 @@ Singleton { Logger.log("Color", "Writing colors to disk") writeAdapter() } + + // Trigger initial load when path changes from empty to actual path + onPathChanged: { + if (path === Settings.configDir + "colors.json") { + reload() + } + } onLoadFailed: function (error) { if (error.toString().includes("No such file") || error === 2) { // File doesn't exist, create it with default values diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 097f5e9..3072636 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -26,11 +26,13 @@ Singleton { property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos" property string defaultLocation: "Tokyo" + property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png" // Used to access via Settings.data.xxx.yyy readonly property alias data: adapter property bool isLoaded: false + property bool directoriesCreated: false // Signal emitted when settings are loaded after startupcale changes signal settingsLoaded @@ -71,34 +73,93 @@ Singleton { // ----------------------------------------------------- // If the settings structure has changed, ensure - // backward compatibility + // backward compatibility by upgrading the settings function upgradeSettingsData() { - for (var i = 0; i < adapter.bar.widgets.left.length; i++) { - var obj = adapter.bar.widgets.left[i] - if (typeof obj === "string") { - adapter.bar.widgets.left[i] = { - "id": obj + + const sections = ["left", "center", "right"] + + // ----------------- + // 1st. check our settings are not super old, when we only had the widget type as a plain string + for (var s = 0; s < sections.length; s++) { + const sectionName = sections[s] + for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) { + var widget = adapter.bar.widgets[sectionName][i] + if (typeof widget === "string") { + adapter.bar.widgets[sectionName][i] = { + "id": widget + } } } } - for (var i = 0; i < adapter.bar.widgets.center.length; i++) { - var obj = adapter.bar.widgets.center[i] - if (typeof obj === "string") { - adapter.bar.widgets.center[i] = { - "id": obj + + // ----------------- + // 2nd. migrate global settings to user settings + for (var s = 0; s < sections.length; s++) { + const sectionName = sections[s] + for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) { + var widget = adapter.bar.widgets[sectionName][i] + + // Check if widget registry supports user settings, if it does not, then there is nothing to do + const reg = BarWidgetRegistry.widgetMetadata[widget.id] + if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) { + continue } - } - } - for (var i = 0; i < adapter.bar.widgets.right.length; i++) { - var obj = adapter.bar.widgets.right[i] - if (typeof obj === "string") { - adapter.bar.widgets.right[i] = { - "id": obj + + // Check that the widget was not previously migrated and skip if necessary + const keys = Object.keys(widget) + if (keys.length > 1) { + continue } + + migrateWidget(widget) + Logger.log("Settings", JSON.stringify(widget)) } } } + // ----------------------------------------------------- + function migrateWidget(widget) { + Logger.log("Settings", `Migrating '${widget.id}' widget`) + + switch (widget.id) { + case "ActiveWindow": + widget.showIcon = adapter.bar.showActiveWindowIcon + break + case "Battery": + widget.alwaysShowPercentage = adapter.bar.alwaysShowBatteryPercentage + break + case "Brightness": + widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage + break + case "Clock": + widget.showDate = adapter.location.showDateWithClock + widget.use12HourClock = adapter.location.use12HourClock + widget.reverseDayMonth = adapter.location.reverseDayMonth + widget.showSeconds = BarWidgetRegistry.widgetMetadata[widget.id].showSeconds + break + case "MediaMini": + widget.showAlbumArt = adapter.audio.showMiniplayerAlbumArt + widget.showVisualizer = adapter.audio.showMiniplayerCava + widget.visualizerType = BarWidgetRegistry.widgetMetadata[widget.id].visualizerType + break + case "NotificationHistory": + widget.showUnreadBadge = BarWidgetRegistry.widgetMetadata[widget.id].showUnreadBadge + widget.hideWhenZero = BarWidgetRegistry.widgetMetadata[widget.id].hideWhenZero + break + case "SidePanelToggle": + widget.useDistroLogo = adapter.bar.useDistroLogo + break + case "SystemMonitor": + widget.showNetworkStats = adapter.bar.showNetworkStats + break + case "Volume": + widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage + break + case "Workspace": + widget.labelMode = adapter.bar.showWorkspaceLabel + break + } + } // ----------------------------------------------------- // Kickoff essential services function kickOffServices() { @@ -117,14 +178,15 @@ Singleton { } // ----------------------------------------------------- - Item { - Component.onCompleted: { + // Ensure directories exist before FileView tries to read files + Component.onCompleted: { + // ensure settings dir exists + Quickshell.execDetached(["mkdir", "-p", configDir]) + Quickshell.execDetached(["mkdir", "-p", cacheDir]) + Quickshell.execDetached(["mkdir", "-p", cacheDirImages]) - // ensure settings dir exists - Quickshell.execDetached(["mkdir", "-p", configDir]) - Quickshell.execDetached(["mkdir", "-p", cacheDir]) - Quickshell.execDetached(["mkdir", "-p", cacheDirImages]) - } + // Mark directories as created and trigger file loading + directoriesCreated = true } // Don't write settings to disk immediately @@ -138,12 +200,16 @@ Singleton { FileView { id: settingsFileView - path: settingsFile + path: directoriesCreated ? settingsFile : "" watchChanges: true onFileChanged: reload() onAdapterUpdated: saveTimer.start() - Component.onCompleted: function () { - reload() + + // Trigger initial load when path changes from empty to actual path + onPathChanged: { + if (path === settingsFile) { + reload() + } } onLoaded: function () { if (!isLoaded) { @@ -174,15 +240,16 @@ Singleton { // bar property JsonObject bar: JsonObject { - property string position: "top" // Possible values: "top", "bottom" - property bool showActiveWindowIcon: true - property bool alwaysShowBatteryPercentage: false - property bool showNetworkStats: false + property string position: "top" // "top" or "bottom" property real backgroundOpacity: 1.0 - property bool useDistroLogo: false - property string showWorkspaceLabel: "none" property list monitors: [] + property bool showActiveWindowIcon: true // TODO: delete + property bool alwaysShowBatteryPercentage: false // TODO: delete + property bool showNetworkStats: false // TODO: delete + property bool useDistroLogo: false // TODO: delete + property string showWorkspaceLabel: "none" // TODO: delete + // Widget configuration for modular bar system property JsonObject widgets widgets: JsonObject { @@ -236,9 +303,10 @@ Singleton { property JsonObject location: JsonObject { property string name: defaultLocation property bool useFahrenheit: false - property bool reverseDayMonth: false - property bool use12HourClock: false - property bool showDateWithClock: false + + property bool reverseDayMonth: false // TODO: delete + property bool use12HourClock: false // TODO: delete + property bool showDateWithClock: false // TODO: delete } // screen recorder @@ -267,6 +335,7 @@ Singleton { property int transitionDuration: 1500 // 1500 ms property string transitionType: "random" property real transitionEdgeSmoothness: 0.05 + property string defaultWallpaper: root.defaultWallpaper property list monitors: [] } @@ -299,25 +368,27 @@ Singleton { property JsonObject notifications: JsonObject { property bool doNotDisturb: false property list monitors: [] + // Last time the user opened the notification history (ms since epoch) + property real lastSeenTs: 0 } // audio property JsonObject audio: JsonObject { - property bool showMiniplayerAlbumArt: false - property bool showMiniplayerCava: false - property string visualizerType: "linear" property int volumeStep: 5 property int cavaFrameRate: 60 - // MPRIS controls + property string visualizerType: "linear" property list mprisBlacklist: [] property string preferredPlayer: "" + + property bool showMiniplayerAlbumArt: false // TODO: delete + property bool showMiniplayerCava: false // TODO: delete } // ui property JsonObject ui: JsonObject { - property string fontDefault: "Roboto" // Default font for all text - property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal - property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays + property string fontDefault: "Roboto" + property string fontFixed: "DejaVu Sans Mono" + property string fontBillboard: "Inter" property list monitorsScaling: [] property bool idleInhibitorEnabled: false } diff --git a/Commons/Time.qml b/Commons/Time.qml index d7ec78e..c086f5b 100644 --- a/Commons/Time.qml +++ b/Commons/Time.qml @@ -9,52 +9,38 @@ Singleton { id: root property var date: new Date() - property string time: { - let timeFormat = Settings.data.location.use12HourClock ? "h:mm AP" : "HH:mm" - let timeString = Qt.formatDateTime(date, timeFormat) - if (Settings.data.location.showDateWithClock) { - let dayName = date.toLocaleDateString(Qt.locale(), "ddd") - dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) - let day = date.getDate() - let month = date.toLocaleDateString(Qt.locale(), "MMM") - - return timeString + " - " + (Settings.data.location.reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`) - } - - return timeString + // Returns a Unix Timestamp (in seconds) + readonly property int timestamp: { + return Math.floor(date / 1000) } - readonly property string dateString: { + + function formatDate(reverseDayMonth = true) { let now = date let dayName = now.toLocaleDateString(Qt.locale(), "ddd") dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) let day = now.getDate() let suffix if (day > 3 && day < 21) - suffix = 'th' + suffix = 'th' else - switch (day % 10) { + switch (day % 10) { case 1: - suffix = "st" - break + suffix = "st" + break case 2: - suffix = "nd" - break + suffix = "nd" + break case 3: - suffix = "rd" - break + suffix = "rd" + break default: - suffix = "th" - } + suffix = "th" + } let month = now.toLocaleDateString(Qt.locale(), "MMMM") let year = now.toLocaleDateString(Qt.locale(), "yyyy") - return `${dayName}, ` - + (Settings.data.location.reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`) - } - // Returns a Unix Timestamp (in seconds) - readonly property int timestamp: { - return Math.floor(date / 1000) + return `${dayName}, ` + (reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`) } diff --git a/Modules/Background/Background.qml b/Modules/Background/Background.qml index a1eca8b..3730838 100644 --- a/Modules/Background/Background.qml +++ b/Modules/Background/Background.qml @@ -3,6 +3,8 @@ import Quickshell import Quickshell.Wayland import qs.Commons import qs.Services +import qs.Modules.SettingsPanel +import qs.Widgets Variants { id: backgroundVariants @@ -20,6 +22,8 @@ Variants { // Internal state management property string transitionType: "fade" property real transitionProgress: 0 + // Scaling support for widgets that rely on it + property real scaling: ScalingService.getScreenScale(screen) readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness readonly property var allTransitions: WallpaperService.allTransitions @@ -87,6 +91,15 @@ Variants { left: true } + Connections { + target: ScalingService + function onScaleChanged(screenName, scale) { + if ((screen !== null) && (screenName === screen.name)) { + scaling = scale + } + } + } + Timer { id: debounceTimer interval: 333 diff --git a/Modules/Bar/Bar.qml b/Modules/Bar/Bar.qml index 30308e0..dfcb721 100644 --- a/Modules/Bar/Bar.qml +++ b/Modules/Bar/Bar.qml @@ -76,6 +76,7 @@ Variants { widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), + "widgetId": modelData.id, "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.left.length @@ -103,6 +104,7 @@ Variants { widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), + "widgetId": modelData.id, "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.center.length @@ -131,6 +133,7 @@ Variants { widgetProps: { "screen": root.modelData || null, "scaling": ScalingService.getScreenScale(screen), + "widgetId": modelData.id, "barSection": parent.objectName, "sectionWidgetIndex": index, "sectionWidgetsCount": Settings.data.bar.widgets.right.length diff --git a/Modules/Bar/Widgets/ActiveWindow.qml b/Modules/Bar/Widgets/ActiveWindow.qml index 65f900e..8fb5961 100644 --- a/Modules/Bar/Widgets/ActiveWindow.qml +++ b/Modules/Bar/Widgets/ActiveWindow.qml @@ -12,6 +12,27 @@ RowLayout { id: root property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon + readonly property real minWidth: 160 readonly property real maxWidth: 400 Layout.alignment: Qt.AlignVCenter @@ -74,7 +95,7 @@ RowLayout { Layout.preferredWidth: Style.fontSizeL * scaling * 1.2 Layout.preferredHeight: Style.fontSizeL * scaling * 1.2 Layout.alignment: Qt.AlignVCenter - visible: getTitle() !== "" && Settings.data.bar.showActiveWindowIcon + visible: getTitle() !== "" && showIcon IconImage { id: windowIcon diff --git a/Modules/Bar/Widgets/Battery.qml b/Modules/Bar/Widgets/Battery.qml index b4654fe..9b8aef5 100644 --- a/Modules/Bar/Widgets/Battery.qml +++ b/Modules/Bar/Widgets/Battery.qml @@ -11,11 +11,42 @@ Item { property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" - property int sectionWidgetIndex: 0 + property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - // Track if we've already notified to avoid spam + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + // Resolve settings: try user settings or defaults from BarWidgetRegistry + readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage + !== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + readonly property real warningThreshold: widgetSettings.warningThreshold + !== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold + + // Test mode + readonly property bool testMode: false + readonly property int testPercent: 50 + readonly property bool testCharging: true + + // Main properties + readonly property var battery: UPower.displayDevice + readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery + && battery.isPresent) + readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) + readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false) property bool hasNotifiedLowBattery: false implicitWidth: pill.width @@ -23,15 +54,14 @@ Item { // Helper to evaluate and possibly notify function maybeNotify(percent, charging) { - const p = Math.round(percent) - // Only notify exactly at 15%, not at 0% or any other percentage - if (!charging && p === 15 && !root.hasNotifiedLowBattery) { + // Only notify once we are a below threshold + if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) { + root.hasNotifiedLowBattery = true + // Maybe go with toast ? Quickshell.execDetached( ["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`]) - root.hasNotifiedLowBattery = true - } - // Reset when charging starts or when battery recovers above 20% - if (charging || p > 20) { + } else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) { + // Reset when charging starts or when battery recovers 5% above threshold root.hasNotifiedLowBattery = false } } @@ -40,19 +70,10 @@ Item { Connections { target: UPower.displayDevice function onPercentageChanged() { - let battery = UPower.displayDevice - let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent - let percent = isReady ? (battery.percentage * 100) : 0 - let charging = isReady ? battery.state === UPowerDeviceState.Charging : false - root.maybeNotify(percent, charging) } function onStateChanged() { - let battery = UPower.displayDevice - let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent - let charging = isReady ? battery.state === UPowerDeviceState.Charging : false - // Reset notification flag when charging starts if (charging) { root.hasNotifiedLowBattery = false @@ -63,15 +84,6 @@ Item { NPill { id: pill - // Test mode - property bool testMode: false - property int testPercent: 50 - property bool testCharging: true - property var battery: UPower.displayDevice - property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent) - property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0) - property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false) - rightOpen: BarWidgetRegistry.getNPillDirection(root) icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, charging, isReady) @@ -81,7 +93,7 @@ Item { iconCircleColor: Color.mPrimary collapsedIconColor: Color.mOnSurface autoHide: false - forceOpen: isReady && (testMode || battery.isLaptopBattery) && Settings.data.bar.alwaysShowBatteryPercentage + forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) tooltipText: { let lines = [] diff --git a/Modules/Bar/Widgets/Brightness.qml b/Modules/Bar/Widgets/Brightness.qml index c16866c..30948c3 100644 --- a/Modules/Bar/Widgets/Brightness.qml +++ b/Modules/Bar/Widgets/Brightness.qml @@ -10,10 +10,28 @@ Item { property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" - property int sectionWidgetIndex: 0 + property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + // Used to avoid opening the pill on Quickshell startup property bool firstBrightnessReceived: false @@ -69,6 +87,7 @@ Item { var monitor = getMonitor() return monitor ? (Math.round(monitor.brightness * 100) + "%") : "" } + forceOpen: userAlwaysShowPercentage tooltipText: { var monitor = getMonitor() if (!monitor) diff --git a/Modules/Bar/Widgets/Clock.qml b/Modules/Bar/Widgets/Clock.qml index ee57b57..3b472d9 100644 --- a/Modules/Bar/Widgets/Clock.qml +++ b/Modules/Bar/Widgets/Clock.qml @@ -10,24 +10,70 @@ Rectangle { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + // Resolve settings: try user settings or defaults from BarWidgetRegistry + readonly property bool showDate: widgetSettings.showDate !== undefined ? widgetSettings.showDate : widgetMetadata.showDate + readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock + readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds + readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth + !== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth + implicitWidth: clock.width + Style.marginM * 2 * scaling implicitHeight: Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) color: Color.mSurfaceVariant // Clock Icon with attached calendar - NClock { + NText { id: clock - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter + text: { + const now = Time.date + const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm") + const timeString = Qt.formatDateTime(now, timeFormat) - NTooltip { - id: tooltip - text: `${Time.dateString}.` - target: clock - positionAbove: Settings.data.bar.position === "bottom" + if (showDate) { + let dayName = now.toLocaleDateString(Qt.locale(), "ddd") + dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) + let day = now.getDate() + let month = now.toLocaleDateString(Qt.locale(), "MMM") + return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`) + } + return timeString } + anchors.centerIn: parent + font.pointSize: Style.fontSizeS * scaling + font.weight: Style.fontWeightBold + } + NTooltip { + id: tooltip + text: `${Time.formatDate(reverseDayMonth)}.` + target: clock + positionAbove: Settings.data.bar.position === "bottom" + } + + MouseArea { + id: clockMouseArea + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true onEntered: { if (!PanelService.getPanel("calendarPanel")?.active) { tooltip.show() diff --git a/Modules/Bar/Widgets/CustomButton.qml b/Modules/Bar/Widgets/CustomButton.qml index de8f96d..08d3dc8 100644 --- a/Modules/Bar/Widgets/CustomButton.qml +++ b/Modules/Bar/Widgets/CustomButton.qml @@ -13,11 +13,13 @@ NIconButton { property var screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - // Get user settings from Settings data + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { var section = barSection.replace("Section", "").toLowerCase() if (section && sectionWidgetIndex >= 0) { @@ -30,30 +32,27 @@ NIconButton { } // Use settings or defaults from BarWidgetRegistry - readonly property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon - readonly property string userLeftClickExec: widgetSettings.leftClickExec - || BarWidgetRegistry.widgetMetadata["CustomButton"].leftClickExec - readonly property string userRightClickExec: widgetSettings.rightClickExec - || BarWidgetRegistry.widgetMetadata["CustomButton"].rightClickExec - readonly property string userMiddleClickExec: widgetSettings.middleClickExec - || BarWidgetRegistry.widgetMetadata["CustomButton"].middleClickExec - readonly property bool hasExec: (userLeftClickExec || userRightClickExec || userMiddleClickExec) + readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon + readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec + readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec + readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec + readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec) sizeRatio: 0.8 - icon: userIcon + icon: customIcon tooltipText: { if (!hasExec) { return "Custom Button - Configure in settings" } else { var lines = [] - if (userLeftClickExec !== "") { - lines.push(`Left click: ${userLeftClickExec}.`) + if (leftClickExec !== "") { + lines.push(`Left click: ${leftClickExec}.`) } - if (userRightClickExec !== "") { - lines.push(`Right click: ${userRightClickExec}.`) + if (rightClickExec !== "") { + lines.push(`Right click: ${rightClickExec}.`) } - if (userMiddleClickExec !== "") { - lines.push(`Middle click: ${userMiddleClickExec}.`) + if (middleClickExec !== "") { + lines.push(`Middle click: ${middleClickExec}.`) } return lines.join("
") } @@ -61,9 +60,9 @@ NIconButton { opacity: hasExec ? Style.opacityFull : Style.opacityMedium onClicked: { - if (userLeftClickExec) { - Quickshell.execDetached(["sh", "-c", userLeftClickExec]) - Logger.log("CustomButton", `Executing command: ${userLeftClickExec}`) + if (leftClickExec) { + Quickshell.execDetached(["sh", "-c", leftClickExec]) + Logger.log("CustomButton", `Executing command: ${leftClickExec}`) } else if (!hasExec) { // No script was defined, open settings var settingsPanel = PanelService.getPanel("settingsPanel") @@ -73,16 +72,16 @@ NIconButton { } onRightClicked: { - if (userRightClickExec) { - Quickshell.execDetached(["sh", "-c", userRightClickExec]) - Logger.log("CustomButton", `Executing command: ${userRightClickExec}`) + if (rightClickExec) { + Quickshell.execDetached(["sh", "-c", rightClickExec]) + Logger.log("CustomButton", `Executing command: ${rightClickExec}`) } } onMiddleClicked: { - if (userMiddleClickExec) { - Quickshell.execDetached(["sh", "-c", userMiddleClickExec]) - Logger.log("CustomButton", `Executing command: ${userMiddleClickExec}`) + if (middleClickExec) { + Quickshell.execDetached(["sh", "-c", middleClickExec]) + Logger.log("CustomButton", `Executing command: ${middleClickExec}`) } } } diff --git a/Modules/Bar/Widgets/KeyboardLayout.qml b/Modules/Bar/Widgets/KeyboardLayout.qml index b9b44a5..7de3a6d 100644 --- a/Modules/Bar/Widgets/KeyboardLayout.qml +++ b/Modules/Bar/Widgets/KeyboardLayout.qml @@ -12,9 +12,6 @@ Item { property ShellScreen screen property real scaling: 1.0 - property string barSection: "" - property int sectionWidgetIndex: 0 - property int sectionWidgetsCount: 0 // Use the shared service for keyboard layout property string currentLayout: KeyboardLayoutService.currentLayout diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 2483dbc..141698a 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -12,18 +12,44 @@ RowLayout { property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool showAlbumArt: (widgetSettings.showAlbumArt + !== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt + readonly property bool showVisualizer: (widgetSettings.showVisualizer + !== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer + readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType + !== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType + readonly property real minWidth: 160 readonly property real maxWidth: 400 + function getTitle() { + return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") + } + Layout.alignment: Qt.AlignVCenter spacing: Style.marginS * scaling visible: MediaService.currentPlayer !== null && MediaService.canPlay Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0 - function getTitle() { - return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "") - } - // A hidden text element to safely measure the full title width NText { id: fullTitleMetrics @@ -58,8 +84,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear" - && MediaService.isPlaying + active: showVisualizer && visualizerType == "linear" && MediaService.isPlaying z: 0 sourceComponent: LinearSpectrum { @@ -74,8 +99,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored" - && MediaService.isPlaying + active: showVisualizer && visualizerType == "mirrored" && MediaService.isPlaying z: 0 sourceComponent: MirroredSpectrum { @@ -90,8 +114,7 @@ RowLayout { Loader { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave" - && MediaService.isPlaying + active: showVisualizer && visualizerType == "wave" && MediaService.isPlaying z: 0 sourceComponent: WaveSpectrum { @@ -115,12 +138,12 @@ RowLayout { font.pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter Layout.alignment: Qt.AlignVCenter - visible: !Settings.data.audio.showMiniplayerAlbumArt && getTitle() !== "" && !trackArt.visible + visible: !showAlbumArt && getTitle() !== "" && !trackArt.visible } ColumnLayout { Layout.alignment: Qt.AlignVCenter - visible: Settings.data.audio.showMiniplayerAlbumArt + visible: showAlbumArt spacing: 0 Item { diff --git a/Modules/Bar/Widgets/Microphone.qml b/Modules/Bar/Widgets/Microphone.qml index f4e1c1a..15f4437 100644 --- a/Modules/Bar/Widgets/Microphone.qml +++ b/Modules/Bar/Widgets/Microphone.qml @@ -12,10 +12,28 @@ Item { property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" - property int sectionWidgetIndex: 0 + property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + // Used to avoid opening the pill on Quickshell startup property bool firstInputVolumeReceived: false property int wheelAccumulator: 0 @@ -78,6 +96,7 @@ Item { collapsedIconColor: Color.mOnSurface autoHide: false // Important to be false so we can hover as long as we want text: Math.floor(AudioService.inputVolume * 100) + "%" + forceOpen: alwaysShowPercentage tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." diff --git a/Modules/Bar/Widgets/NightLight.qml b/Modules/Bar/Widgets/NightLight.qml index 6ea2e20..c9f302e 100644 --- a/Modules/Bar/Widgets/NightLight.qml +++ b/Modules/Bar/Widgets/NightLight.qml @@ -4,6 +4,7 @@ import QtQuick.Controls import Quickshell import Quickshell.Wayland import qs.Commons +import qs.Modules.SettingsPanel import qs.Services import qs.Widgets @@ -14,7 +15,6 @@ NIconButton { property real scaling: 1.0 sizeRatio: 0.8 - colorBg: Color.mSurfaceVariant colorFg: Color.mOnSurface colorBorder: Color.transparent @@ -26,7 +26,7 @@ NIconButton { onRightClicked: { var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.Display + settingsPanel.requestedTab = SettingsPanel.Tab.Brightness settingsPanel.open(screen) } } diff --git a/Modules/Bar/Widgets/NotificationHistory.qml b/Modules/Bar/Widgets/NotificationHistory.qml index 48a62fd..31657f1 100644 --- a/Modules/Bar/Widgets/NotificationHistory.qml +++ b/Modules/Bar/Widgets/NotificationHistory.qml @@ -13,6 +13,45 @@ NIconButton { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge + !== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge + readonly property bool hideWhenZero: (widgetSettings.hideWhenZero + !== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero + + function lastSeenTs() { + return Settings.data.notifications?.lastSeenTs || 0 + } + + function computeUnreadCount() { + var since = lastSeenTs() + var count = 0 + var model = NotificationService.historyModel + for (var i = 0; i < model.count; i++) { + var item = model.get(i) + var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp + if (ts > since) + count++ + } + return count + } + sizeRatio: 0.8 icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications" tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'." @@ -21,7 +60,40 @@ NIconButton { colorBorder: Color.transparent colorBorderHover: Color.transparent - onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(screen, this) + onClicked: { + var panel = PanelService.getPanel("notificationHistoryPanel") + panel?.toggle(screen, this) + Settings.data.notifications.lastSeenTs = Time.timestamp * 1000 + } onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb + + Loader { + anchors.right: parent.right + anchors.top: parent.top + anchors.rightMargin: -4 * scaling + anchors.topMargin: -4 * scaling + z: 2 + active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0) + sourceComponent: Rectangle { + id: badge + readonly property int count: computeUnreadCount() + readonly property string label: count <= 99 ? String(count) : "99+" + readonly property real pad: 8 * scaling + height: 16 * scaling + width: Math.max(height, textNode.implicitWidth + pad) + radius: height / 2 + color: Color.mError + border.color: Color.mSurface + border.width: 1 + visible: count > 0 || !hideWhenZero + NText { + id: textNode + anchors.centerIn: parent + text: badge.label + font.pointSize: Style.fontSizeXXS * scaling + color: Color.mOnError + } + } + } } diff --git a/Modules/Bar/Widgets/SidePanelToggle.qml b/Modules/Bar/Widgets/SidePanelToggle.qml index b9572fb..14a8c6f 100644 --- a/Modules/Bar/Widgets/SidePanelToggle.qml +++ b/Modules/Bar/Widgets/SidePanelToggle.qml @@ -1,3 +1,4 @@ +import QtQuick import Quickshell import Quickshell.Widgets import QtQuick.Effects @@ -11,7 +12,28 @@ NIconButton { property ShellScreen screen property real scaling: 1.0 - icon: Settings.data.bar.useDistroLogo ? "" : "widgets" + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool useDistroLogo: (widgetSettings.useDistroLogo + !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo + + icon: useDistroLogo ? "" : "widgets" tooltipText: "Open side panel." sizeRatio: 0.8 @@ -24,14 +46,13 @@ NIconButton { onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this) onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen) - // When enabled, draw the distro logo instead of the icon glyph IconImage { id: logo anchors.centerIn: parent width: root.width * 0.6 height: width - source: Settings.data.bar.useDistroLogo ? DistroLogoService.osLogo : "" - visible: false //Settings.data.bar.useDistroLogo && source !== "" + source: useDistroLogo ? DistroLogoService.osLogo : "" + visible: useDistroLogo && source !== "" smooth: true } diff --git a/Modules/Bar/Widgets/Spacer.qml b/Modules/Bar/Widgets/Spacer.qml index 5a62372..dc2651c 100644 --- a/Modules/Bar/Widgets/Spacer.qml +++ b/Modules/Bar/Widgets/Spacer.qml @@ -12,11 +12,13 @@ Item { property var screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 - // Get user settings from Settings data - make it reactive + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] property var widgetSettings: { var section = barSection.replace("Section", "").toLowerCase() if (section && sectionWidgetIndex >= 0) { @@ -29,19 +31,10 @@ Item { } // Use settings or defaults from BarWidgetRegistry - readonly property int userWidth: { - var section = barSection.replace("Section", "").toLowerCase() - if (section && sectionWidgetIndex >= 0) { - var widgets = Settings.data.bar.widgets[section] - if (widgets && sectionWidgetIndex < widgets.length) { - return widgets[sectionWidgetIndex].width || BarWidgetRegistry.widgetMetadata["Spacer"].width - } - } - return BarWidgetRegistry.widgetMetadata["Spacer"].width - } + readonly property int spacerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width // Set the width based on user settings - implicitWidth: userWidth * scaling + implicitWidth: spacerWidth * scaling implicitHeight: Style.barHeight * scaling width: implicitWidth height: implicitHeight @@ -51,6 +44,6 @@ Item { anchors.fill: parent color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint visible: Settings.data.general.debugMode || false - radius: 2 * scaling + radius: Style.radiusXXS * scaling } } diff --git a/Modules/Bar/Widgets/SystemMonitor.qml b/Modules/Bar/Widgets/SystemMonitor.qml index e57d599..91f3fd8 100644 --- a/Modules/Bar/Widgets/SystemMonitor.qml +++ b/Modules/Bar/Widgets/SystemMonitor.qml @@ -11,6 +11,34 @@ RowLayout { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool showCpuUsage: (widgetSettings.showCpuUsage + !== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage + readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp + readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage + !== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage + readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent + !== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent + readonly property bool showNetworkStats: (widgetSettings.showNetworkStats + !== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats + Layout.alignment: Qt.AlignVCenter spacing: Style.marginS * scaling @@ -34,6 +62,7 @@ RowLayout { id: cpuUsageLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter + visible: showCpuUsage NIcon { id: cpuUsageIcon @@ -59,6 +88,7 @@ RowLayout { // spacing is thin here to compensate for the vertical thermometer icon spacing: Style.marginXXS * scaling Layout.alignment: Qt.AlignVCenter + visible: showCpuTemp NIcon { text: "thermometer" @@ -81,6 +111,7 @@ RowLayout { id: memoryUsageLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter + visible: showMemoryUsage NIcon { text: "memory" @@ -88,7 +119,7 @@ RowLayout { } NText { - text: `${SystemStatService.memGb}G` + text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G` font.family: Settings.data.ui.fontFixed font.pointSize: Style.fontSizeS * scaling font.weight: Style.fontWeightMedium @@ -103,7 +134,7 @@ RowLayout { id: networkDownloadLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter - visible: Settings.data.bar.showNetworkStats + visible: showNetworkStats NIcon { text: "download" @@ -126,7 +157,7 @@ RowLayout { id: networkUploadLayout spacing: Style.marginXS * scaling Layout.alignment: Qt.AlignVCenter - visible: Settings.data.bar.showNetworkStats + visible: showNetworkStats NIcon { text: "upload" diff --git a/Modules/Bar/Widgets/Tray.qml b/Modules/Bar/Widgets/Tray.qml index 06de40f..5c1b090 100644 --- a/Modules/Bar/Widgets/Tray.qml +++ b/Modules/Bar/Widgets/Tray.qml @@ -15,6 +15,7 @@ Rectangle { property ShellScreen screen property real scaling: 1.0 + readonly property real itemSize: 24 * scaling function onLoaded() { diff --git a/Modules/Bar/Widgets/Volume.qml b/Modules/Bar/Widgets/Volume.qml index 84f8b22..80e79db 100644 --- a/Modules/Bar/Widgets/Volume.qml +++ b/Modules/Bar/Widgets/Volume.qml @@ -12,10 +12,28 @@ Item { property ShellScreen screen property real scaling: 1.0 + + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" property string barSection: "" - property int sectionWidgetIndex: 0 + property int sectionWidgetIndex: -1 property int sectionWidgetsCount: 0 + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage + !== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + // Used to avoid opening the pill on Quickshell startup property bool firstVolumeReceived: false property int wheelAccumulator: 0 @@ -63,6 +81,7 @@ Item { collapsedIconColor: Color.mOnSurface autoHide: false // Important to be false so we can hover as long as we want text: Math.floor(AudioService.volume * 100) + "%" + forceOpen: alwaysShowPercentage tooltipText: "Volume: " + Math.round(AudioService.volume * 100) + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." diff --git a/Modules/Bar/Widgets/Workspace.qml b/Modules/Bar/Widgets/Workspace.qml index 051bdea..d8bb543 100644 --- a/Modules/Bar/Widgets/Workspace.qml +++ b/Modules/Bar/Widgets/Workspace.qml @@ -14,6 +14,26 @@ Item { property ShellScreen screen property real scaling: 1.0 + // Widget properties passed from Bar.qml for per-instance settings + property string widgetId: "" + property string barSection: "" + property int sectionWidgetIndex: -1 + property int sectionWidgetsCount: 0 + + property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId] + property var widgetSettings: { + var section = barSection.replace("Section", "").toLowerCase() + if (section && sectionWidgetIndex >= 0) { + var widgets = Settings.data.bar.widgets[section] + if (widgets && sectionWidgetIndex < widgets.length) { + return widgets[sectionWidgetIndex] + } + } + return {} + } + + readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode + property bool isDestroying: false property bool hovered: false @@ -22,8 +42,8 @@ Item { property bool effectsActive: false property color effectColor: Color.mPrimary - property int horizontalPadding: Math.round(16 * scaling) - property int spacingBetweenPills: Math.round(8 * scaling) + property int horizontalPadding: Math.round(Style.marginS * scaling) + property int spacingBetweenPills: Math.round(Style.marginXS * scaling) signal workspaceChanged(int workspaceId, color accentColor) @@ -124,7 +144,7 @@ Item { Rectangle { id: workspaceBackground - width: parent.width - Style.marginS * scaling * 2 + width: parent.width height: Math.round(Style.capsuleHeight * scaling) radius: Math.round(Style.radiusM * scaling) @@ -145,7 +165,7 @@ Item { model: localWorkspaces Item { id: workspacePillContainer - height: (Settings.data.bar.showWorkspaceLabel !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling) + height: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling) width: root.calculatedWsWidth(model) Rectangle { @@ -153,15 +173,13 @@ Item { anchors.fill: parent Loader { - active: (Settings.data.bar.showWorkspaceLabel !== "none") + active: (labelMode !== "none") sourceComponent: Component { Text { - // Center horizontally x: (pill.width - width) / 2 - // Center vertically accounting for font metrics y: (pill.height - height) / 2 + (height - contentHeight) / 2 text: { - if (Settings.data.bar.showWorkspaceLabel === "name" && model.name && model.name.length > 0) { + if (labelMode === "name" && model.name && model.name.length > 0) { return model.name.substring(0, 2) } else { return model.idx.toString() diff --git a/Modules/SettingsPanel/Extras/BarSectionEditor.qml b/Modules/SettingsPanel/Bar/BarSectionEditor.qml similarity index 90% rename from Modules/SettingsPanel/Extras/BarSectionEditor.qml rename to Modules/SettingsPanel/Bar/BarSectionEditor.qml index fb17c34..7a1684a 100644 --- a/Modules/SettingsPanel/Extras/BarSectionEditor.qml +++ b/Modules/SettingsPanel/Bar/BarSectionEditor.qml @@ -39,7 +39,7 @@ NBox { const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => { return acc + character.charCodeAt(0) }, 0) - switch (totalSum % 10) { + switch (totalSum % 5) { case 0: return Color.mPrimary case 1: @@ -50,16 +50,6 @@ NBox { return Color.mError case 4: return Color.mOnSurface - case 5: - return Qt.darker(Color.mPrimary, 1.3) - case 6: - return Qt.darker(Color.mSecondary, 1.3) - case 7: - return Qt.darker(Color.mTertiary, 1.3) - case 8: - return Qt.darker(Color.mError, 1.3) - case 9: - return Qt.darker(Color.mOnSurface, 1.3) } } @@ -75,7 +65,7 @@ NBox { text: sectionName + " Section" font.pointSize: Style.fontSizeL * scaling font.weight: Style.fontWeightBold - color: Color.mSecondary + color: Color.mOnSurface Layout.alignment: Qt.AlignVCenter } @@ -89,7 +79,7 @@ NBox { description: "" placeholder: "Select a widget to add..." onSelected: key => comboBox.currentKey = key - popupHeight: 240 * scaling + popupHeight: 340 * scaling Layout.alignment: Qt.AlignVCenter } @@ -188,13 +178,33 @@ NBox { colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight) colorFgHover: Color.mOnPrimary onClicked: { - var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { - "widgetIndex": index, - "widgetData": modelData, - "widgetId": modelData.id, - "parent": Overlay.overlay - }) - dialog.open() + var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml")) + function instantiateAndOpen() { + var dialog = component.createObject(root, { + "widgetIndex": index, + "widgetData": modelData, + "widgetId": modelData.id, + "parent": Overlay.overlay + }) + if (dialog) { + dialog.open() + } else { + Logger.error("BarSectionEditor", "Failed to create settings dialog instance") + } + } + if (component.status === Component.Ready) { + instantiateAndOpen() + } else if (component.status === Component.Error) { + Logger.error("BarSectionEditor", component.errorString()) + } else { + component.statusChanged.connect(function () { + if (component.status === Component.Ready) { + instantiateAndOpen() + } else if (component.status === Component.Error) { + Logger.error("BarSectionEditor", component.errorString()) + } + }) + } } } } @@ -221,7 +231,7 @@ NBox { MouseArea { id: flowDragArea anchors.fill: parent - z: 999 // Above all widgets to ensure it gets events first + z: -1 // Ensure this mouse area is below the Settings and Close buttons // Critical properties for proper event handling acceptedButtons: Qt.LeftButton diff --git a/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml new file mode 100644 index 0000000..9ba0045 --- /dev/null +++ b/Modules/SettingsPanel/Bar/BarWidgetSettingsDialog.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services +import "./WidgetSettings" as WidgetSettings + +// Widget Settings Dialog Component +Popup { + id: settingsPopup + + property int widgetIndex: -1 + property var widgetData: null + property string widgetId: "" + + // Center popup in parent + x: (parent.width - width) * 0.5 + y: (parent.height - height) * 0.5 + + width: 420 * scaling + height: content.implicitHeight + padding * 2 + padding: Style.marginXL * scaling + modal: true + + background: Rectangle { + id: bgRect + color: Color.mSurface + radius: Style.radiusL * scaling + border.color: Color.mPrimary + border.width: Style.borderM * scaling + } + + // Load settings when popup opens with data + onOpened: { + if (widgetData && widgetId) { + loadWidgetSettings() + } + } + + function loadWidgetSettings() { + const widgetSettingsMap = { + "ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml", + "Battery": "WidgetSettings/BatterySettings.qml", + "Brightness": "WidgetSettings/BrightnessSettings.qml", + "Clock": "WidgetSettings/ClockSettings.qml", + "CustomButton": "WidgetSettings/CustomButtonSettings.qml", + "MediaMini": "WidgetSettings/MediaMiniSettings.qml", + "Microphone": "WidgetSettings/MicrophoneSettings.qml", + "NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml", + "Workspace": "WidgetSettings/WorkspaceSettings.qml", + "SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml", + "Spacer": "WidgetSettings/SpacerSettings.qml", + "SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml", + "Volume": "WidgetSettings/VolumeSettings.qml" + } + + const source = widgetSettingsMap[widgetId] + if (source) { + // Use setSource to pass properties at creation time + settingsLoader.setSource(source, { + "widgetData": widgetData, + "widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId] + }) + } + } + + ColumnLayout { + id: content + width: parent.width + spacing: Style.marginM * scaling + + // Title + RowLayout { + Layout.fillWidth: true + + NText { + text: `${settingsPopup.widgetId} Settings` + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mPrimary + Layout.fillWidth: true + } + + NIconButton { + icon: "close" + onClicked: settingsPopup.close() + } + } + + // Separator + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: Color.mOutline + } + + // Settings based on widget type + // Will be triggered via settingsLoader.setSource() + Loader { + id: settingsLoader + Layout.fillWidth: true + } + + // Action buttons + RowLayout { + Layout.fillWidth: true + Layout.topMargin: Style.marginM * scaling + + Item { + Layout.fillWidth: true + } + + NButton { + text: "Cancel" + outlined: true + onClicked: settingsPopup.close() + } + + NButton { + text: "Apply" + icon: "check" + onClicked: { + if (settingsLoader.item && settingsLoader.item.saveSettings) { + var newSettings = settingsLoader.item.saveSettings() + root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) + settingsPopup.close() + } + } + } + } + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/ActiveWindowSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/ActiveWindowSettings.qml new file mode 100644 index 0000000..eabf587 --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/ActiveWindowSettings.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showIcon = valueShowIcon + return settings + } + + NToggle { + id: showIcon + Layout.fillWidth: true + label: "Show app icon" + checked: root.valueShowIcon + onToggled: checked => root.valueShowIcon = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml new file mode 100644 index 0000000..54b589a --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/BatterySettings.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage + !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NToggle { + label: "Always show percentage" + checked: root.valueAlwaysShowPercentage + onToggled: checked => root.valueAlwaysShowPercentage = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/BrightnessSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/BrightnessSettings.qml new file mode 100644 index 0000000..6054e9c --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/BrightnessSettings.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage + !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NToggle { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml new file mode 100644 index 0000000..cef94a8 --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/ClockSettings.qml @@ -0,0 +1,54 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate + property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock + property bool valueShowSeconds: widgetData.showSeconds !== undefined ? widgetData.showSeconds : widgetMetadata.showSeconds + property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showDate = valueShowDate + settings.use12HourClock = valueUse12h + settings.showSeconds = valueShowSeconds + settings.reverseDayMonth = valueReverseDayMonth + return settings + } + + NToggle { + label: "Show date" + checked: valueShowDate + onToggled: checked => valueShowDate = checked + } + + NToggle { + label: "Use 12-hour clock" + checked: valueUse12h + onToggled: checked => valueUse12h = checked + } + + NToggle { + label: "Show seconds" + checked: valueShowSeconds + onToggled: checked => valueShowSeconds = checked + } + + NToggle { + label: "Reverse day and month" + checked: valueReverseDayMonth + onToggled: checked => valueReverseDayMonth = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/CustomButtonSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/CustomButtonSettings.qml new file mode 100644 index 0000000..b7c896f --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/CustomButtonSettings.qml @@ -0,0 +1,58 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.icon = iconInput.text + settings.leftClickExec = leftClickExecInput.text + settings.rightClickExec = rightClickExecInput.text + settings.middleClickExec = middleClickExecInput.text + return settings + } + + // Icon setting + NTextInput { + id: iconInput + Layout.fillWidth: true + label: "Icon Name" + description: "Choose a name from the Material Icon set." + placeholderText: "Enter icon name (e.g., favorite, home, settings)" + text: widgetData?.icon || widgetMetadata.icon + } + + NTextInput { + id: leftClickExecInput + Layout.fillWidth: true + label: "Left Click Command" + placeholderText: "Enter command to execute (app or custom script)" + text: widgetData?.leftClickExec || widgetMetadata.leftClickExec + } + + NTextInput { + id: rightClickExecInput + Layout.fillWidth: true + label: "Right Click Command" + placeholderText: "Enter command to execute (app or custom script)" + text: widgetData?.rightClickExec || widgetMetadata.rightClickExec + } + + NTextInput { + id: middleClickExecInput + Layout.fillWidth: true + label: "Middle Click Command" + placeholderText: "Enter command to execute (app or custom script)" + text: widgetData.middleClickExec || widgetMetadata.middleClickExec + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/MediaMiniSettings.qml new file mode 100644 index 0000000..fb70f9d --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/MediaMiniSettings.qml @@ -0,0 +1,62 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer + property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showAlbumArt = valueShowAlbumArt + settings.showVisualizer = valueShowVisualizer + settings.visualizerType = valueVisualizerType + return settings + } + + NToggle { + label: "Show album art" + checked: valueShowAlbumArt + onToggled: checked => valueShowAlbumArt = checked + } + + NToggle { + label: "Show visualizer" + checked: valueShowVisualizer + onToggled: checked => valueShowVisualizer = checked + } + + NComboBox { + visible: valueShowVisualizer + label: "Visualizer type" + model: ListModel { + ListElement { + key: "linear" + name: "Linear" + } + ListElement { + key: "mirrored" + name: "Mirrored" + } + ListElement { + key: "wave" + name: "Wave" + } + } + currentKey: valueVisualizerType + onSelected: key => valueVisualizerType = key + minimumWidth: 200 * scaling + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/MicrophoneSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/MicrophoneSettings.qml new file mode 100644 index 0000000..6054e9c --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/MicrophoneSettings.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage + !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NToggle { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/NotificationHistorySettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/NotificationHistorySettings.qml new file mode 100644 index 0000000..751a832 --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/NotificationHistorySettings.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueShowUnreadBadge: widgetData.showUnreadBadge !== undefined ? widgetData.showUnreadBadge : widgetMetadata.showUnreadBadge + property bool valueHideWhenZero: widgetData.hideWhenZero !== undefined ? widgetData.hideWhenZero : widgetMetadata.hideWhenZero + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showUnreadBadge = valueShowUnreadBadge + settings.hideWhenZero = valueHideWhenZero + return settings + } + + NToggle { + label: "Show unread badge" + checked: valueShowUnreadBadge + onToggled: checked => valueShowUnreadBadge = checked + } + + NToggle { + label: "Hide badge when zero" + checked: valueHideWhenZero + onToggled: checked => valueHideWhenZero = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml new file mode 100644 index 0000000..abb2b7e --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/SidePanelToggleSettings.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.useDistroLogo = valueUseDistroLogo + return settings + } + + NToggle { + label: "Use distro logo instead of icon" + checked: valueUseDistroLogo + onToggled: checked => valueUseDistroLogo = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml new file mode 100644 index 0000000..8de5f6e --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/SpacerSettings.qml @@ -0,0 +1,30 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.width = parseInt(widthInput.text) || widgetMetadata.width + return settings + } + + NTextInput { + id: widthInput + Layout.fillWidth: true + label: "Width" + description: "Spacing width in pixels" + text: widgetData.width || widgetMetadata.width + placeholderText: "Enter width in pixels" + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/SystemMonitorSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/SystemMonitorSettings.qml new file mode 100644 index 0000000..4f2459b --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/SystemMonitorSettings.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local, editable state for checkboxes + property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage + property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp + property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage + property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent + !== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent + property bool valueShowNetworkStats: widgetData.showNetworkStats + !== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.showCpuUsage = valueShowCpuUsage + settings.showCpuTemp = valueShowCpuTemp + settings.showMemoryUsage = valueShowMemoryUsage + settings.showMemoryAsPercent = valueShowMemoryAsPercent + settings.showNetworkStats = valueShowNetworkStats + return settings + } + + NToggle { + id: showCpuUsage + Layout.fillWidth: true + label: "CPU usage" + checked: valueShowCpuUsage + onToggled: checked => valueShowCpuUsage = checked + } + + NToggle { + id: showCpuTemp + Layout.fillWidth: true + label: "CPU temperature" + checked: valueShowCpuTemp + onToggled: checked => valueShowCpuTemp = checked + } + + NToggle { + id: showMemoryUsage + Layout.fillWidth: true + label: "Memory usage" + checked: valueShowMemoryUsage + onToggled: checked => valueShowMemoryUsage = checked + } + + NToggle { + id: showMemoryAsPercent + Layout.fillWidth: true + label: "Show memory as percentage" + checked: valueShowMemoryAsPercent + onToggled: checked => valueShowMemoryAsPercent = checked + } + + NToggle { + id: showNetworkStats + Layout.fillWidth: true + label: "Network traffic" + checked: valueShowNetworkStats + onToggled: checked => valueShowNetworkStats = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml new file mode 100644 index 0000000..6054e9c --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/VolumeSettings.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + // Local state + property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage + !== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.alwaysShowPercentage = valueAlwaysShowPercentage + return settings + } + + NToggle { + label: "Always show percentage" + checked: valueAlwaysShowPercentage + onToggled: checked => valueAlwaysShowPercentage = checked + } +} diff --git a/Modules/SettingsPanel/Bar/WidgetSettings/WorkspaceSettings.qml b/Modules/SettingsPanel/Bar/WidgetSettings/WorkspaceSettings.qml new file mode 100644 index 0000000..1e44dae --- /dev/null +++ b/Modules/SettingsPanel/Bar/WidgetSettings/WorkspaceSettings.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.Commons +import qs.Widgets +import qs.Services + +ColumnLayout { + id: root + spacing: Style.marginM * scaling + + // Properties to receive data from parent + property var widgetData: null + property var widgetMetadata: null + + function saveSettings() { + var settings = Object.assign({}, widgetData || {}) + settings.labelMode = labelModeCombo.currentKey + return settings + } + + NComboBox { + id: labelModeCombo + + label: "Label Mode" + model: ListModel { + ListElement { + key: "none" + name: "None" + } + ListElement { + key: "index" + name: "Index" + } + ListElement { + key: "name" + name: "Name" + } + } + currentKey: widgetData.labelMode || widgetMetadata.labelMode + onSelected: key => labelModeCombo.currentKey = key + minimumWidth: 200 * scaling + } +} diff --git a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml b/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml deleted file mode 100644 index f9aa98d..0000000 --- a/Modules/SettingsPanel/Extras/BarWidgetSettingsDialog.qml +++ /dev/null @@ -1,186 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Effects -import QtQuick.Layouts -import qs.Commons -import qs.Widgets -import qs.Services - -// Widget Settings Dialog Component -Popup { - id: settingsPopup - - property int widgetIndex: -1 - property var widgetData: null - property string widgetId: "" - - // Center popup in parent - x: (parent.width - width) * 0.5 - y: (parent.height - height) * 0.5 - - width: 420 * scaling - height: content.implicitHeight + padding * 2 - padding: Style.marginXL * scaling - modal: true - - background: Rectangle { - id: bgRect - color: Color.mSurface - radius: Style.radiusL * scaling - border.color: Color.mPrimary - border.width: Style.borderM * scaling - } - - ColumnLayout { - id: content - width: parent.width - spacing: Style.marginM * scaling - - // Title - RowLayout { - Layout.fillWidth: true - - NText { - text: "Widget Settings: " + settingsPopup.widgetId - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mPrimary - Layout.fillWidth: true - } - - NIconButton { - icon: "close" - onClicked: settingsPopup.close() - } - } - - // Separator - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: Color.mOutline - } - - // Settings based on widget type - Loader { - id: settingsLoader - Layout.fillWidth: true - sourceComponent: { - if (settingsPopup.widgetId === "CustomButton") { - return customButtonSettings - } else if (settingsPopup.widgetId === "Spacer") { - return spacerSettings - } - // Add more widget settings components here as needed - return null - } - } - - // Action buttons - RowLayout { - Layout.fillWidth: true - Layout.topMargin: Style.marginM * scaling - - Item { - Layout.fillWidth: true - } - - NButton { - text: "Cancel" - outlined: true - onClicked: settingsPopup.close() - } - - NButton { - text: "Save" - onClicked: { - if (settingsLoader.item && settingsLoader.item.saveSettings) { - var newSettings = settingsLoader.item.saveSettings() - root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings) - settingsPopup.close() - } - } - } - } - } - - // CustomButton settings component - Component { - id: customButtonSettings - - ColumnLayout { - spacing: Style.marginM * scaling - - function saveSettings() { - var settings = Object.assign({}, settingsPopup.widgetData) - settings.icon = iconInput.text - settings.leftClickExec = leftClickExecInput.text - settings.rightClickExec = rightClickExecInput.text - settings.middleClickExec = middleClickExecInput.text - return settings - } - - // Icon setting - NTextInput { - id: iconInput - Layout.fillWidth: true - Layout.bottomMargin: Style.marginXL * scaling - label: "Icon Name" - description: "Use Material Icon names from the icon set." - text: settingsPopup.widgetData.icon || "" - placeholderText: "Enter icon name (e.g., favorite, home, settings)" - } - - NTextInput { - id: leftClickExecInput - Layout.fillWidth: true - label: "Left Click Command" - description: "Command or application to run when left clicked." - text: settingsPopup.widgetData.leftClickExec || "" - placeholderText: "Enter command to execute (app or custom script)" - } - - NTextInput { - id: rightClickExecInput - Layout.fillWidth: true - label: "Right Click Command" - description: "Command or application to run when right clicked." - text: settingsPopup.widgetData.rightClickExec || "" - placeholderText: "Enter command to execute (app or custom script)" - } - - NTextInput { - id: middleClickExecInput - Layout.fillWidth: true - label: "Middle Click Command" - description: "Command or application to run when middle clicked." - text: settingsPopup.widgetData.middleClickExec || "" - placeholderText: "Enter command to execute (app or custom script)" - } - } - } - - // Spacer settings component - Component { - id: spacerSettings - - ColumnLayout { - spacing: Style.marginM * scaling - - function saveSettings() { - var settings = Object.assign({}, settingsPopup.widgetData) - settings.width = parseInt(widthInput.text) || 20 - return settings - } - - NTextInput { - id: widthInput - Layout.fillWidth: true - label: "Width (pixels)" - description: "Width of the spacer in pixels." - text: settingsPopup.widgetData.width || "20" - placeholderText: "Enter width in pixels" - } - } - } -} diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 8bfbe77..ea8c701 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -39,7 +39,7 @@ NPanel { General, Network, ScreenRecorder, - TimeWeather, + Weather, Wallpaper, WallpaperSelector } @@ -90,8 +90,8 @@ NPanel { Tabs.NetworkTab {} } Component { - id: timeWeatherTab - Tabs.TimeWeatherTab {} + id: weatherTab + Tabs.WeatherTab {} } Component { id: colorSchemeTab @@ -156,10 +156,10 @@ NPanel { "icon": "brightness_6", "source": brightnessTab }, { - "id": SettingsPanel.Tab.TimeWeather, - "label": "Time & Weather", - "icon": "schedule", - "source": timeWeatherTab + "id": SettingsPanel.Tab.Weather, + "label": "Weather", + "icon": "partly_cloudy_day", + "source": weatherTab }, { "id": SettingsPanel.Tab.ColorScheme, "label": "Color Scheme", diff --git a/Modules/SettingsPanel/Tabs/AudioTab.qml b/Modules/SettingsPanel/Tabs/AudioTab.qml index a5dd9a1..d60ae38 100644 --- a/Modules/SettingsPanel/Tabs/AudioTab.qml +++ b/Modules/SettingsPanel/Tabs/AudioTab.qml @@ -242,21 +242,7 @@ ColumnLayout { Layout.bottomMargin: Style.marginS * scaling } - // Miniplayer section - NToggle { - label: "Show Album Art In Bar Media Player" - description: "Show the album art of the currently playing song next to the title." - checked: Settings.data.audio.showMiniplayerAlbumArt - onToggled: checked => Settings.data.audio.showMiniplayerAlbumArt = checked - } - - NToggle { - label: "Show Audio Visualizer In Bar Media Player" - description: "Shows an audio visualizer in the background of the miniplayer." - checked: Settings.data.audio.showMiniplayerCava - onToggled: checked => Settings.data.audio.showMiniplayerCava = checked - } - // Preferred player (persistent) + // Preferred player NTextInput { label: "Preferred Player" description: "Substring to match MPRIS player (identity/bus/desktop)." diff --git a/Modules/SettingsPanel/Tabs/BarTab.qml b/Modules/SettingsPanel/Tabs/BarTab.qml index e1bf63a..c543018 100644 --- a/Modules/SettingsPanel/Tabs/BarTab.qml +++ b/Modules/SettingsPanel/Tabs/BarTab.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts import qs.Commons import qs.Services import qs.Widgets -import qs.Modules.SettingsPanel.Extras +import qs.Modules.SettingsPanel.Bar ColumnLayout { id: root @@ -70,57 +70,6 @@ ColumnLayout { } } } - - NToggle { - label: "Show Active Window's Icon" - description: "Display the app icon next to the title of the currently focused window." - checked: Settings.data.bar.showActiveWindowIcon - onToggled: checked => Settings.data.bar.showActiveWindowIcon = checked - } - - NToggle { - label: "Show Battery Percentage" - description: "Display battery percentage at all times." - checked: Settings.data.bar.alwaysShowBatteryPercentage - onToggled: checked => Settings.data.bar.alwaysShowBatteryPercentage = checked - } - - NToggle { - label: "Show Network Statistics" - description: "Display network upload and download speeds in the system monitor." - checked: Settings.data.bar.showNetworkStats - onToggled: checked => Settings.data.bar.showNetworkStats = checked - } - - NToggle { - label: "Replace SidePanel toggle with distro logo" - description: "Show distro logo instead of the SidePanel toggle button in the bar." - checked: Settings.data.bar.useDistroLogo - onToggled: checked => { - Settings.data.bar.useDistroLogo = checked - } - } - - NComboBox { - label: "Show Workspaces Labels" - description: "Show the workspace name or index within the workspace indicator." - model: ListModel { - ListElement { - key: "none" - name: "None" - } - ListElement { - key: "index" - name: "Index" - } - ListElement { - key: "name" - name: "Name" - } - } - currentKey: Settings.data.bar.showWorkspaceLabel - onSelected: key => Settings.data.bar.showWorkspaceLabel = key - } } NDivider { @@ -138,7 +87,7 @@ ColumnLayout { text: "Widgets Positioning" font.pointSize: Style.fontSizeXXL * scaling font.weight: Style.fontWeightBold - color: Color.mOnSurface + color: Color.mSecondary Layout.bottomMargin: Style.marginS * scaling } diff --git a/Modules/SettingsPanel/Tabs/BrightnessTab.qml b/Modules/SettingsPanel/Tabs/BrightnessTab.qml index 4bbcace..0b02d4a 100644 --- a/Modules/SettingsPanel/Tabs/BrightnessTab.qml +++ b/Modules/SettingsPanel/Tabs/BrightnessTab.qml @@ -299,8 +299,7 @@ ColumnLayout { currentKey: Settings.data.nightLight.manualSunrise placeholder: "Select start time" onSelected: key => Settings.data.nightLight.manualSunrise = key - - preferredWidth: 120 * scaling + minimumWidth: 120 * scaling } Item {// add a little more spacing @@ -316,8 +315,7 @@ ColumnLayout { currentKey: Settings.data.nightLight.manualSunset placeholder: "Select stop time" onSelected: key => Settings.data.nightLight.manualSunset = key - - preferredWidth: 120 * scaling + minimumWidth: 120 * scaling } } } diff --git a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml index 5196387..de5ba2c 100644 --- a/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml +++ b/Modules/SettingsPanel/Tabs/ColorSchemeTab.qml @@ -8,6 +8,7 @@ import qs.Widgets ColumnLayout { id: root + spacing: 0 // Cache for scheme JSON (can be flat or {dark, light}) property var schemeColorsCache: ({}) @@ -103,225 +104,218 @@ ColumnLayout { } } + // Main Toggles - Dark Mode / Matugen ColumnLayout { - spacing: 0 + spacing: Style.marginL * scaling + Layout.fillWidth: true - Item { - Layout.fillWidth: true - Layout.preferredHeight: 0 + // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) + NToggle { + label: "Dark Mode" + description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." + checked: Settings.data.colorSchemes.darkMode + enabled: true + onToggled: checked => Settings.data.colorSchemes.darkMode = checked } - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true + // Use Matugen + NToggle { + label: "Enable Matugen" + description: "Automatically generate colors based on your active wallpaper." + checked: Settings.data.colorSchemes.useWallpaperColors + onToggled: checked => { + if (checked) { + // Check if matugen is installed + matugenCheck.running = true + } else { + Settings.data.colorSchemes.useWallpaperColors = false + ToastService.showNotice("Matugen", "Disabled") - // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) - NToggle { - label: "Dark Mode" - description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available." - checked: Settings.data.colorSchemes.darkMode - enabled: true - onToggled: checked => Settings.data.colorSchemes.darkMode = checked - } + if (Settings.data.colorSchemes.predefinedScheme) { - // Use Matugen - NToggle { - label: "Enable Matugen" - description: "Automatically generate colors based on your active wallpaper." - checked: Settings.data.colorSchemes.useWallpaperColors - onToggled: checked => { - if (checked) { - // Check if matugen is installed - matugenCheck.running = true - } else { - Settings.data.colorSchemes.useWallpaperColors = false - ToastService.showNotice("Matugen", "Disabled") - - if (Settings.data.colorSchemes.predefinedScheme) { - - ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) - } + ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme) } } - } + } + } + } - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } - ColumnLayout { - spacing: Style.marginS * scaling - Layout.fillWidth: true + // Predefined Color Schemes + ColumnLayout { + spacing: Style.marginM * scaling + Layout.fillWidth: true - NText { - text: "Predefined Color Schemes" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } + NText { + text: "Predefined Color Schemes" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + } + + NText { + text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper." + font.pointSize: Style.fontSizeM * scaling + color: Color.mOnSurfaceVariant + Layout.fillWidth: true + wrapMode: Text.WordWrap + } + + // Color Schemes Grid + GridLayout { + columns: 3 + rowSpacing: Style.marginM * scaling + columnSpacing: Style.marginM * scaling + Layout.fillWidth: true + + Repeater { + model: ColorSchemeService.schemes + + Rectangle { + id: schemeCard + + property string schemePath: modelData - NText { - text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen." - font.pointSize: Style.fontSizeM * scaling - color: Color.mOnSurfaceVariant Layout.fillWidth: true - wrapMode: Text.WordWrap - } - } + Layout.preferredHeight: 120 * scaling + radius: Style.radiusM * scaling + color: getSchemeColor(modelData, "mSurface") + border.width: Math.max(1, Style.borderL * scaling) + border.color: (!Settings.data.colorSchemes.useWallpaperColors + && (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline + scale: root.cardScaleLow - // Color Schemes Grid - GridLayout { - columns: 3 - rowSpacing: Style.marginM * scaling - columnSpacing: Style.marginM * scaling - Layout.fillWidth: true + // Mouse area for selection + MouseArea { + anchors.fill: parent + onClicked: { + // Disable useWallpaperColors when picking a predefined color scheme + Settings.data.colorSchemes.useWallpaperColors = false + Logger.log("ColorSchemeTab", "Disabled matugen setting") - Repeater { - model: ColorSchemeService.schemes + Settings.data.colorSchemes.predefinedScheme = schemePath + ColorSchemeService.applyScheme(schemePath) + } + hoverEnabled: true + cursorShape: Qt.PointingHandCursor - Rectangle { - id: schemeCard - - property string schemePath: modelData - - Layout.fillWidth: true - Layout.preferredHeight: 120 * scaling - radius: Style.radiusM * scaling - color: getSchemeColor(modelData, "mSurface") - border.width: Math.max(1, Style.borderL * scaling) - border.color: (!Settings.data.colorSchemes.useWallpaperColors - && (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline - scale: root.cardScaleLow - - // Mouse area for selection - MouseArea { - anchors.fill: parent - onClicked: { - // Disable useWallpaperColors when picking a predefined color scheme - Settings.data.colorSchemes.useWallpaperColors = false - Logger.log("ColorSchemeTab", "Disabled matugen setting") - - Settings.data.colorSchemes.predefinedScheme = schemePath - ColorSchemeService.applyScheme(schemePath) - } - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - onEntered: { - schemeCard.scale = root.cardScaleHigh - } - - onExited: { - schemeCard.scale = root.cardScaleLow - } + onEntered: { + schemeCard.scale = root.cardScaleHigh } - // Card content - ColumnLayout { - anchors.fill: parent - anchors.margins: Style.marginXL * scaling + onExited: { + schemeCard.scale = root.cardScaleLow + } + } + + // Card content + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginXL * scaling + spacing: Style.marginS * scaling + + // Scheme name + NText { + text: { + // Remove json and the full path + var chunks = schemePath.replace(".json", "").split("/") + return chunks[chunks.length - 1] + } + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: getSchemeColor(modelData, "mOnSurface") + Layout.fillWidth: true + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + + // Color swatches + RowLayout { + id: swatches + spacing: Style.marginS * scaling + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter - // Scheme name - NText { - text: { - // Remove json and the full path - var chunks = schemePath.replace(".json", "").split("/") - return chunks[chunks.length - 1] - } - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: getSchemeColor(modelData, "mOnSurface") - Layout.fillWidth: true - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter + readonly property int swatchSize: 20 * scaling + + // Primary color swatch + Rectangle { + width: swatches.swatchSize + height: swatches.swatchSize + radius: width * 0.5 + color: getSchemeColor(modelData, "mPrimary") } - // Color swatches - RowLayout { - id: swatches + // Secondary color swatch + Rectangle { + width: swatches.swatchSize + height: swatches.swatchSize + radius: width * 0.5 + color: getSchemeColor(modelData, "mSecondary") + } - spacing: Style.marginS * scaling - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter + // Tertiary color swatch + Rectangle { + width: swatches.swatchSize + height: swatches.swatchSize + radius: width * 0.5 + color: getSchemeColor(modelData, "mTertiary") + } - readonly property int swatchSize: 20 * scaling - - // Primary color swatch - Rectangle { - width: swatches.swatchSize - height: swatches.swatchSize - radius: width * 0.5 - color: getSchemeColor(modelData, "mPrimary") - } - - // Secondary color swatch - Rectangle { - width: swatches.swatchSize - height: swatches.swatchSize - radius: width * 0.5 - color: getSchemeColor(modelData, "mSecondary") - } - - // Tertiary color swatch - Rectangle { - width: swatches.swatchSize - height: swatches.swatchSize - radius: width * 0.5 - color: getSchemeColor(modelData, "mTertiary") - } - - // Error color swatch - Rectangle { - width: swatches.swatchSize - height: swatches.swatchSize - radius: width * 0.5 - color: getSchemeColor(modelData, "mError") - } + // Error color swatch + Rectangle { + width: swatches.swatchSize + height: swatches.swatchSize + radius: width * 0.5 + color: getSchemeColor(modelData, "mError") } } + } - // Selection indicator (Checkmark) - Rectangle { - visible: !Settings.data.colorSchemes.useWallpaperColors - && (Settings.data.colorSchemes.predefinedScheme === schemePath) - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Style.marginS * scaling - width: 24 * scaling - height: 24 * scaling - radius: width * 0.5 - color: Color.mPrimary + // Selection indicator (Checkmark) + Rectangle { + visible: !Settings.data.colorSchemes.useWallpaperColors + && (Settings.data.colorSchemes.predefinedScheme === schemePath) + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Style.marginS * scaling + width: 24 * scaling + height: 24 * scaling + radius: width * 0.5 + color: Color.mPrimary - NText { - anchors.centerIn: parent - text: "✓" - font.pointSize: Style.fontSizeXS * scaling - font.weight: Style.fontWeightBold - color: Color.mOnPrimary - } + NText { + anchors.centerIn: parent + text: "✓" + font.pointSize: Style.fontSizeXS * scaling + font.weight: Style.fontWeightBold + color: Color.mOnPrimary } + } - // Smooth animations - Behavior on scale { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutCubic - } + // Smooth animations + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutCubic } + } - Behavior on border.color { - ColorAnimation { - duration: Style.animationNormal - } + Behavior on border.color { + ColorAnimation { + duration: Style.animationNormal } + } - Behavior on border.width { - NumberAnimation { - duration: Style.animationFast - } + Behavior on border.width { + NumberAnimation { + duration: Style.animationFast } } } diff --git a/Modules/SettingsPanel/Tabs/GeneralTab.qml b/Modules/SettingsPanel/Tabs/GeneralTab.qml index 7407112..fe4dbcd 100644 --- a/Modules/SettingsPanel/Tabs/GeneralTab.qml +++ b/Modules/SettingsPanel/Tabs/GeneralTab.qml @@ -70,52 +70,6 @@ ColumnLayout { onToggled: checked => Settings.data.general.dimDesktop = checked } - NToggle { - label: "Auto-hide Dock" - description: "Automatically hide the dock when not in use." - checked: Settings.data.dock.autoHide - onToggled: checked => Settings.data.dock.autoHide = checked - } - - ColumnLayout { - spacing: Style.marginXXS * scaling - Layout.fillWidth: true - - NText { - text: "Dock Background Opacity" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - } - - NText { - text: "Adjust the background opacity of the dock." - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.fillWidth: true - } - - RowLayout { - NSlider { - Layout.fillWidth: true - from: 0 - to: 1 - stepSize: 0.01 - value: Settings.data.dock.backgroundOpacity - onMoved: Settings.data.dock.backgroundOpacity = value - cutoutColor: Color.mSurface - } - - NText { - text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" - Layout.alignment: Qt.AlignVCenter - Layout.leftMargin: Style.marginS * scaling - color: Color.mOnSurface - } - } - } - ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true @@ -175,7 +129,70 @@ ColumnLayout { } } } + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Dock + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + NText { + text: "Dock" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mSecondary + Layout.bottomMargin: Style.marginS * scaling + } + + NToggle { + label: "Auto-hide Dock" + description: "Automatically hide the dock when not in use." + checked: Settings.data.dock.autoHide + onToggled: checked => Settings.data.dock.autoHide = checked + } + + ColumnLayout { + spacing: Style.marginXXS * scaling + Layout.fillWidth: true + + NText { + text: "Dock Background Opacity" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + } + + NText { + text: "Adjust the background opacity of the dock." + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + + RowLayout { + NSlider { + Layout.fillWidth: true + from: 0 + to: 1 + stepSize: 0.01 + value: Settings.data.dock.backgroundOpacity + onMoved: Settings.data.dock.backgroundOpacity = value + cutoutColor: Color.mSurface + } + + NText { + text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%" + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + color: Color.mOnSurface + } + } + } + } NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginXL * scaling @@ -206,6 +223,7 @@ ColumnLayout { currentKey: Settings.data.ui.fontDefault placeholder: "Select default font..." popupHeight: 420 * scaling + minimumWidth: 300 * scaling onSelected: function (key) { Settings.data.ui.fontDefault = key } @@ -218,6 +236,7 @@ ColumnLayout { currentKey: Settings.data.ui.fontFixed placeholder: "Select monospace font..." popupHeight: 320 * scaling + minimumWidth: 300 * scaling onSelected: function (key) { Settings.data.ui.fontFixed = key } @@ -230,6 +249,7 @@ ColumnLayout { currentKey: Settings.data.ui.fontBillboard placeholder: "Select display font..." popupHeight: 320 * scaling + minimumWidth: 300 * scaling onSelected: function (key) { Settings.data.ui.fontBillboard = key } diff --git a/Modules/SettingsPanel/Tabs/LauncherTab.qml b/Modules/SettingsPanel/Tabs/LauncherTab.qml index 6ca4ece..28bb9f0 100644 --- a/Modules/SettingsPanel/Tabs/LauncherTab.qml +++ b/Modules/SettingsPanel/Tabs/LauncherTab.qml @@ -52,20 +52,6 @@ ColumnLayout { } } - NToggle { - label: "Enable Clipboard History" - description: "Show clipboard history in the launcher." - checked: Settings.data.appLauncher.enableClipboardHistory - onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked - } - - NToggle { - label: "Use App2Unit for Launching" - description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration." - checked: Settings.data.appLauncher.useApp2Unit - onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked - } - ColumnLayout { spacing: Style.marginXXS * scaling Layout.fillWidth: true @@ -105,6 +91,20 @@ ColumnLayout { } } } + + NToggle { + label: "Enable Clipboard History" + description: "Show clipboard history in the launcher." + checked: Settings.data.appLauncher.enableClipboardHistory + onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked + } + + NToggle { + label: "Use App2Unit for Launching" + description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration." + checked: Settings.data.appLauncher.useApp2Unit + onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked + } } NDivider { diff --git a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml b/Modules/SettingsPanel/Tabs/WeatherTab.qml similarity index 61% rename from Modules/SettingsPanel/Tabs/TimeWeatherTab.qml rename to Modules/SettingsPanel/Tabs/WeatherTab.qml index 2fa89dd..667aca5 100644 --- a/Modules/SettingsPanel/Tabs/TimeWeatherTab.qml +++ b/Modules/SettingsPanel/Tabs/WeatherTab.qml @@ -52,46 +52,6 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } - // Time section - ColumnLayout { - spacing: Style.marginL * scaling - Layout.fillWidth: true - - NText { - text: "Time Format" - font.pointSize: Style.fontSizeXXL * scaling - font.weight: Style.fontWeightBold - color: Color.mSecondary - } - - NToggle { - label: "Use 12-Hour Clock" - description: "Display time in 12-hour format (AM/PM) instead of 24-hour." - checked: Settings.data.location.use12HourClock - onToggled: checked => Settings.data.location.use12HourClock = checked - } - - NToggle { - label: "Reverse Day/Month" - description: "Display date as dd/mm instead of mm/dd." - checked: Settings.data.location.reverseDayMonth - onToggled: checked => Settings.data.location.reverseDayMonth = checked - } - - NToggle { - label: "Show Date with Clock" - description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)." - checked: Settings.data.location.showDateWithClock - onToggled: checked => Settings.data.location.showDateWithClock = checked - } - } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } - // Weather section ColumnLayout { spacing: Style.marginM * scaling @@ -111,10 +71,4 @@ ColumnLayout { onToggled: checked => Settings.data.location.useFahrenheit = checked } } - - NDivider { - Layout.fillWidth: true - Layout.topMargin: Style.marginXL * scaling - Layout.bottomMargin: Style.marginXL * scaling - } } diff --git a/Services/BarWidgetRegistry.qml b/Services/BarWidgetRegistry.qml index 39afcd3..b82a3e1 100644 --- a/Services/BarWidgetRegistry.qml +++ b/Services/BarWidgetRegistry.qml @@ -38,6 +38,26 @@ Singleton { }) property var widgetMetadata: ({ + "ActiveWindow": { + "allowUserSettings": true, + "showIcon": true + }, + "Battery": { + "allowUserSettings": true, + "alwaysShowPercentage": false, + "warningThreshold": 30 + }, + "Brightness": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "Clock": { + "allowUserSettings": true, + "showDate": false, + "use12HourClock": false, + "showSeconds": false, + "reverseDayMonth": true + }, "CustomButton": { "allowUserSettings": true, "icon": "favorite", @@ -45,10 +65,44 @@ Singleton { "rightClickExec": "", "middleClickExec": "" }, + "Microphone": { + "allowUserSettings": true, + "alwaysShowPercentage": false + }, + "NotificationHistory": { + "allowUserSettings": true, + "showUnreadBadge": true, + "hideWhenZero": true + }, "Spacer": { "allowUserSettings": true, - "icon": "space_bar", "width": 20 + }, + "SystemMonitor": { + "allowUserSettings": true, + "showCpuUsage": true, + "showCpuTemp": true, + "showMemoryUsage": true, + "showMemoryAsPercent": false, + "showNetworkStats": false + }, + "Workspace": { + "allowUserSettings": true, + "labelMode": "index" + }, + "MediaMini": { + "allowUserSettings": true, + "showAlbumArt": false, + "showVisualizer": false, + "visualizerType": "linear" + }, + "SidePanelToggle": { + "allowUserSettings": true, + "useDistroLogo": false + }, + "Volume": { + "allowUserSettings": true, + "alwaysShowPercentage": false } }) diff --git a/Services/CavaService.qml b/Services/CavaService.qml index 6cfb735..12f39e7 100644 --- a/Services/CavaService.qml +++ b/Services/CavaService.qml @@ -37,9 +37,7 @@ Singleton { Process { id: process stdinEnabled: true - running: (Settings.data.audio.visualizerType !== "none") - && (PanelService.getPanel("sidePanel").active || Settings.data.audio.showMiniplayerCava - || (PanelService.lockScreen && PanelService.lockScreen.active)) + running: true command: ["cava", "-p", "/dev/stdin"] onExited: { stdinEnabled = true diff --git a/Services/GitHubService.qml b/Services/GitHubService.qml index 2523a50..99dc283 100644 --- a/Services/GitHubService.qml +++ b/Services/GitHubService.qml @@ -45,7 +45,7 @@ Singleton { property string version: "Unknown" property var contributors: [] - property double timestamp: 0 + property real timestamp: 0 } } diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml index da6ab32..f30269f 100644 --- a/Services/NotificationService.qml +++ b/Services/NotificationService.qml @@ -80,7 +80,7 @@ Singleton { JsonAdapter { id: historyAdapter property var history: [] - property double timestamp: 0 + property real timestamp: 0 } } @@ -201,12 +201,17 @@ Singleton { const items = historyAdapter.history || [] for (var i = 0; i < items.length; i++) { const it = items[i] + // Coerce legacy second-based timestamps to milliseconds + var ts = it.timestamp + if (typeof ts === "number" && ts < 1e12) { + ts = ts * 1000 + } historyModel.append({ "summary": it.summary || "", "body": it.body || "", "appName": it.appName || "", "urgency": it.urgency, - "timestamp": it.timestamp ? new Date(it.timestamp) : new Date() + "timestamp": ts ? new Date(ts) : new Date() }) } } catch (e) { @@ -225,7 +230,10 @@ Singleton { "body": n.body, "appName": n.appName, "urgency": n.urgency, - "timestamp": (n.timestamp instanceof Date) ? n.timestamp.getTime() : n.timestamp + "timestamp"// Always persist in milliseconds + : (n.timestamp instanceof Date) ? n.timestamp.getTime( + ) : (typeof n.timestamp === "number" + && n.timestamp < 1e12 ? n.timestamp * 1000 : n.timestamp) }) } historyAdapter.history = arr diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 7f99a0d..311bdae 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -216,7 +216,11 @@ Singleton { // ------------------------------------------------------------------- // Get specific monitor wallpaper - now from cache function getWallpaper(screenName) { - return currentWallpapers[screenName] || "" + var path = currentWallpapers[screenName] || "" + if (path === "") { + return Settings.data.wallpaper.defaultWallpaper || "" + } + return path } // ------------------------------------------------------------------- diff --git a/Widgets/NButton.qml b/Widgets/NButton.qml index 5648c46..75c9bc5 100644 --- a/Widgets/NButton.qml +++ b/Widgets/NButton.qml @@ -77,10 +77,12 @@ Rectangle { RowLayout { id: contentRow anchors.centerIn: parent - spacing: Style.marginS * scaling + spacing: Style.marginXS * scaling // Icon (optional) NIcon { + Layout.alignment: Qt.AlignVCenter + layoutTopMargin: 1 * scaling visible: root.icon !== "" text: root.icon font.pointSize: root.iconSize @@ -105,6 +107,7 @@ Rectangle { // Text NText { + Layout.alignment: Qt.AlignVCenter visible: root.text !== "" text: root.text font.pointSize: root.fontSize diff --git a/Widgets/NCheckbox.qml b/Widgets/NCheckbox.qml index 48d76e5..4b5962d 100644 --- a/Widgets/NCheckbox.qml +++ b/Widgets/NCheckbox.qml @@ -27,6 +27,11 @@ RowLayout { visible: root.label !== "" || root.description !== "" } + // Spacer to push the checkbox to the far right + Item { + Layout.fillWidth: true + } + Rectangle { id: box @@ -39,13 +44,13 @@ RowLayout { Behavior on color { ColorAnimation { - duration: Style.animationNormal + duration: Style.animationFast } } Behavior on border.color { ColorAnimation { - duration: Style.animationNormal + duration: Style.animationFast } } diff --git a/Widgets/NClock.qml b/Widgets/NClock.qml deleted file mode 100644 index aa8ce33..0000000 --- a/Widgets/NClock.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick -import qs.Commons -import qs.Services -import qs.Widgets - -Rectangle { - id: root - - signal entered - signal exited - signal clicked - - width: textItem.paintedWidth - height: textItem.paintedHeight - color: Color.transparent - - NText { - id: textItem - text: Time.time - anchors.centerIn: parent - font.pointSize: Style.fontSizeS * scaling - font.weight: Style.fontWeightBold - } - - MouseArea { - id: clockMouseArea - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - onEntered: root.entered() - onExited: root.exited() - onClicked: root.clicked() - } -} diff --git a/Widgets/NComboBox.qml b/Widgets/NComboBox.qml index 813eee5..57bc0bb 100644 --- a/Widgets/NComboBox.qml +++ b/Widgets/NComboBox.qml @@ -8,8 +8,7 @@ import qs.Widgets RowLayout { id: root - readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling - property real preferredWidth: 320 * scaling + property real minimumWidth: 280 * scaling property real popupHeight: 180 * scaling property string label: "" @@ -20,9 +19,11 @@ RowLayout { property string currentKey: "" property string placeholder: "" + readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling + signal selected(string key) - spacing: Style.marginS * scaling + spacing: Style.marginL * scaling Layout.fillWidth: true function findIndexByKey(key) { @@ -39,11 +40,15 @@ RowLayout { description: root.description } + Item { + Layout.fillWidth: true + } + ComboBox { id: combo - Layout.preferredWidth: root.preferredWidth - Layout.preferredHeight: height + Layout.minimumWidth: root.minimumWidth + Layout.preferredHeight: root.preferredHeight model: model currentIndex: findIndexByKey(currentKey) onActivated: { diff --git a/Widgets/NIcon.qml b/Widgets/NIcon.qml index 4a244aa..ac5a0ec 100644 --- a/Widgets/NIcon.qml +++ b/Widgets/NIcon.qml @@ -1,8 +1,11 @@ import QtQuick import qs.Commons import qs.Widgets +import QtQuick.Layouts Text { + // Optional layout nudge for optical alignment when used inside Layouts + property real layoutTopMargin: 0 text: "question_mark" font.family: "Material Symbols Rounded" font.pointSize: Style.fontSizeL * scaling @@ -12,4 +15,5 @@ Text { } color: Color.mOnSurface verticalAlignment: Text.AlignVCenter + Layout.topMargin: layoutTopMargin } diff --git a/Widgets/NLabel.qml b/Widgets/NLabel.qml index b9dc96e..853181a 100644 --- a/Widgets/NLabel.qml +++ b/Widgets/NLabel.qml @@ -20,6 +20,7 @@ ColumnLayout { font.capitalization: Font.Capitalize color: labelColor visible: label !== "" + Layout.fillWidth: true } NText { diff --git a/Widgets/NPill.qml b/Widgets/NPill.qml index 794caa9..2011c9d 100644 --- a/Widgets/NPill.qml +++ b/Widgets/NPill.qml @@ -39,13 +39,13 @@ Item { property bool shouldAnimateHide: false // Exposed width logic - readonly property int pillHeight: Style.baseWidgetSize * sizeRatio * scaling - readonly property int iconSize: Style.baseWidgetSize * sizeRatio * scaling - readonly property int pillPaddingHorizontal: Style.marginM * scaling + readonly property int iconSize: Math.round(Style.baseWidgetSize * sizeRatio * scaling) + readonly property int pillHeight: iconSize + readonly property int pillPaddingHorizontal: Style.marginS * scaling readonly property int pillOverlap: iconSize * 0.5 readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap) - width: iconSize + (effectiveShown ? maxPillWidth - pillOverlap : 0) + width: iconSize + Math.max(0, pill.width - pillOverlap) height: pillHeight Rectangle { @@ -67,7 +67,13 @@ Item { NText { id: textItem - anchors.centerIn: parent + anchors.verticalCenter: parent.verticalCenter + x: { + // Little tweak to have a better text horizontal centering + var centerX = (parent.width - width) / 2 + var offset = rightOpen ? Style.marginXS * scaling : -Style.marginXS * scaling + return centerX + offset + } text: root.text font.pointSize: Style.fontSizeXS * scaling font.weight: Style.fontWeightBold @@ -102,8 +108,7 @@ Item { border.width: Math.max(1, Style.borderS * scaling) border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent - anchors.left: rightOpen ? parent.left : undefined - anchors.right: rightOpen ? undefined : parent.right + x: rightOpen ? 0 : (parent.width - width) Behavior on color { ColorAnimation { diff --git a/Widgets/NText.qml b/Widgets/NText.qml index 00f5561..c15198d 100644 --- a/Widgets/NText.qml +++ b/Widgets/NText.qml @@ -13,4 +13,5 @@ Text { font.kerning: true color: Color.mOnSurface renderType: Text.QtRendering + verticalAlignment: Text.AlignVCenter }