diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 3e75ac397..a9dbc06aa 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -965,6 +965,7 @@ "clock-style-label": "Clock style", "clock-style-minimal": "Minimal", "clock-width-description": "Width of the clock widget in pixels.", + "cpu-intensive-note": "Widgets marked with ! use more CPU and should be enabled mindfully.", "edit-mode-button-label": "Enter edit mode", "edit-mode-controls-explanation": "Left-click & drag: Move or resize the widget.\nRight-click: Open the context menu options.", "edit-mode-description": "Enable edit mode to move and reposition desktop widgets. When enabled, widgets show a drag outline and can be repositioned.", diff --git a/Commons/IconsTabler.qml b/Commons/IconsTabler.qml index f0a9226ad..64a80fb6b 100644 --- a/Commons/IconsTabler.qml +++ b/Commons/IconsTabler.qml @@ -33,6 +33,7 @@ Singleton { "media-next": "player-skip-forward-filled", "download-speed": "download", "upload-speed": "upload", + "cpu-intensive": "alert-octagon", "cpu-usage": "brand-speedtest", "cpu-temperature": "flame", "gpu-temperature": "device-desktop", diff --git a/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml b/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml index 168c7dc70..711b94abe 100644 --- a/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml +++ b/Modules/Panels/Settings/Bar/MonitorWidgetsConfig.qml @@ -117,6 +117,7 @@ NBox { for (var i = 0; i < widgetIds.length; i++) { var id = widgetIds[i]; var displayName = id; + const badges = []; if (BarWidgetRegistry.isPluginWidget(id)) { var pluginId = id.replace("plugin:", ""); var manifest = PluginRegistry.getPluginManifest(pluginId); @@ -125,10 +126,21 @@ NBox { } else { displayName = pluginId; } + badges.push({ + "icon": "plugin", + "color": Color.mSecondary + }); + } + if (BarWidgetRegistry.isCpuIntensive(id)) { + badges.push({ + "icon": "cpu-intensive", + "color": Color.mSecondary + }); } availableWidgetsModel.append({ "key": id, - "name": displayName + "name": displayName, + "badges": badges }); } } diff --git a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml index 04733a6ae..87d7964d1 100644 --- a/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml +++ b/Modules/Panels/Settings/Tabs/DesktopWidgetsTab.qml @@ -36,29 +36,80 @@ ColumnLayout { onToggled: checked => Settings.data.desktopWidgets.enabled = checked } - NButton { + ColumnLayout { enabled: Settings.data.desktopWidgets.enabled - Layout.fillWidth: true - text: DesktopWidgetRegistry.editMode ? I18n.tr("panels.desktop-widgets.edit-mode-exit-button") : I18n.tr("panels.desktop-widgets.edit-mode-button-label") - icon: "edit" - onClicked: { - DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode; - if (DesktopWidgetRegistry.editMode && Settings.data.ui.settingsPanelMode !== "window") { - var item = root.parent; - while (item) { - if (item.closeRequested !== undefined) { - item.closeRequested(); - break; + + NLabel { + description: I18n.tr("panels.desktop-widgets.cpu-intensive-note") + } + + NButton { + Layout.fillWidth: true + Layout.topMargin: Style.marginM + Layout.bottomMargin: Style.marginM + text: DesktopWidgetRegistry.editMode ? I18n.tr("panels.desktop-widgets.edit-mode-exit-button") : I18n.tr("panels.desktop-widgets.edit-mode-button-label") + icon: "edit" + onClicked: { + DesktopWidgetRegistry.editMode = !DesktopWidgetRegistry.editMode; + if (DesktopWidgetRegistry.editMode && Settings.data.ui.settingsPanelMode !== "window") { + var item = root.parent; + while (item) { + if (item.closeRequested !== undefined) { + item.closeRequested(); + break; + } + item = item.parent; } - item = item.parent; } } } + + // One NSectionEditor per monitor + Repeater { + model: Quickshell.screens + + NSectionEditor { + required property var modelData + + Layout.fillWidth: true + sectionName: modelData.name + sectionSubtitle: { + var compositorScale = CompositorService.getDisplayScale(modelData.name); + // Format scale to 2 decimal places to prevent overly long text + var formattedScale = compositorScale.toFixed(2); + return "(" + modelData.width + "x" + modelData.height + " @ " + formattedScale + "x)"; + } + + sectionId: modelData.name + screen: modelData + settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml") + widgetRegistry: DesktopWidgetRegistry + widgetModel: getWidgetsForMonitor(modelData.name) + availableWidgets: root.availableWidgetsModel + availableSections: root.getScreenNames() + sectionLabels: root.getScreenLabels() + sectionIcons: root.getScreenIcons() + draggable: false // Desktop widgets are positioned by X,Y, not list order + maxWidgets: -1 + onAddWidget: (widgetId, section) => _addWidgetToMonitor(modelData.name, widgetId) + onRemoveWidget: (section, index) => _removeWidgetFromMonitor(modelData.name, index) + onMoveWidget: (fromSection, index, toSection) => _moveWidgetToMonitor(fromSection, index, toSection) + onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsForMonitor(modelData.name, index, settings) + onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) + } + } } - NDivider { - visible: Settings.data.desktopWidgets.enabled - Layout.fillWidth: true + // Shared Plugin Settings Popup + NPluginSettingsPopup { + id: pluginSettingsDialog + parent: Overlay.overlay + showToastOnSave: false + } + + Component.onCompleted: { + // Use Qt.callLater to ensure DesktopWidgetRegistry is ready + Qt.callLater(updateAvailableWidgetsModel); } // Helper to get screen names array @@ -89,54 +140,6 @@ ColumnLayout { return icons; } - // One NSectionEditor per monitor - Repeater { - model: Quickshell.screens - - NSectionEditor { - enabled: Settings.data.desktopWidgets.enabled - required property var modelData - - Layout.fillWidth: true - sectionName: modelData.name - sectionSubtitle: { - var compositorScale = CompositorService.getDisplayScale(modelData.name); - // Format scale to 2 decimal places to prevent overly long text - var formattedScale = compositorScale.toFixed(2); - return "(" + modelData.width + "x" + modelData.height + " @ " + formattedScale + "x)"; - } - - sectionId: modelData.name - screen: modelData - settingsDialogComponent: Qt.resolvedUrl(Quickshell.shellDir + "/Modules/Panels/Settings/DesktopWidgets/DesktopWidgetSettingsDialog.qml") - widgetRegistry: DesktopWidgetRegistry - widgetModel: getWidgetsForMonitor(modelData.name) - availableWidgets: root.availableWidgetsModel - availableSections: root.getScreenNames() - sectionLabels: root.getScreenLabels() - sectionIcons: root.getScreenIcons() - draggable: false // Desktop widgets are positioned by X,Y, not list order - maxWidgets: -1 - onAddWidget: (widgetId, section) => _addWidgetToMonitor(modelData.name, widgetId) - onRemoveWidget: (section, index) => _removeWidgetFromMonitor(modelData.name, index) - onMoveWidget: (fromSection, index, toSection) => _moveWidgetToMonitor(fromSection, index, toSection) - onUpdateWidgetSettings: (section, index, settings) => _updateWidgetSettingsForMonitor(modelData.name, index, settings) - onOpenPluginSettingsRequested: manifest => pluginSettingsDialog.openPluginSettings(manifest) - } - } - - // Shared Plugin Settings Popup - NPluginSettingsPopup { - id: pluginSettingsDialog - parent: Overlay.overlay - showToastOnSave: false - } - - Component.onCompleted: { - // Use Qt.callLater to ensure DesktopWidgetRegistry is ready - Qt.callLater(updateAvailableWidgetsModel); - } - function updateAvailableWidgetsModel() { availableWidgets.clear(); try { @@ -179,6 +182,12 @@ ColumnLayout { "color": Color.mSecondary }); } + if (DesktopWidgetRegistry.isCpuIntensive(widgetId)) { + badges.push({ + "icon": "cpu-intensive", + "color": Color.mSecondary + }); + } availableWidgets.append({ "key": widgetId, diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index 4dbbc95bc..9aede24e7 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -475,6 +475,14 @@ Singleton { return id.startsWith("plugin:"); } + property var cpuIntensiveWidgets: ["AudioVisualizer"] + + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } + // Get list of plugin widget IDs function getPluginWidgets() { return Object.keys(pluginWidgets); diff --git a/Services/UI/ControlCenterWidgetRegistry.qml b/Services/UI/ControlCenterWidgetRegistry.qml index 86972d718..52bde9991 100644 --- a/Services/UI/ControlCenterWidgetRegistry.qml +++ b/Services/UI/ControlCenterWidgetRegistry.qml @@ -37,6 +37,8 @@ Singleton { } }) + property var cpuIntensiveWidgets: ["SystemStat"] + // Component definitions - these are loaded once at startup property Component airplaneModeComponent: Component { AirplaneMode {} @@ -152,4 +154,10 @@ Singleton { function getPluginWidgets() { return Object.keys(pluginWidgets); } + + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } } diff --git a/Services/UI/DesktopWidgetRegistry.qml b/Services/UI/DesktopWidgetRegistry.qml index f82a28c4f..5f1bd7f2e 100644 --- a/Services/UI/DesktopWidgetRegistry.qml +++ b/Services/UI/DesktopWidgetRegistry.qml @@ -81,6 +81,8 @@ Singleton { } }) + property var cpuIntensiveWidgets: ["SystemStat"] + // Plugin widget storage (mirroring BarWidgetRegistry pattern) property var pluginWidgets: ({}) property var pluginWidgetMetadata: ({}) @@ -111,6 +113,12 @@ Singleton { return widgetMetadata[id] !== undefined; } + function isCpuIntensive(id) { + if (pluginWidgetMetadata[id]?.cpuIntensive) + return true; + return cpuIntensiveWidgets.indexOf(id) >= 0; + } + // Check if a widget is a plugin widget function isPluginWidget(id) { return id.startsWith("plugin:"); diff --git a/Widgets/NSectionEditor.qml b/Widgets/NSectionEditor.qml index 204acc3d7..ac0cd531d 100644 --- a/Widgets/NSectionEditor.qml +++ b/Widgets/NSectionEditor.qml @@ -494,6 +494,16 @@ NBox { Layout.preferredHeight: Style.baseWidgetSize * 0.5 } + // CPU-intensive indicator icon + NIcon { + visible: root.widgetRegistry && root.widgetRegistry.isCpuIntensive(modelData.id) + icon: "cpu-intensive" + pointSize: Style.fontSizeXXS + color: root.getWidgetColor(modelData)[1] + Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.5 : 0 + Layout.preferredHeight: Style.baseWidgetSize * 0.5 + } + RowLayout { spacing: 0 Layout.preferredWidth: buttonsCount * buttonsWidth * Style.uiScaleRatio