diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index b1aff5469..504a971eb 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -1472,20 +1472,28 @@ "label": "Hiding mode" }, "show-background": { - "description": "Show the background container for the media player widget.", + "description": "Show the background container.", "label": "Show background" }, + "rounded-corners": { + "description": "Enable rounded corners on the widget edges.", + "label": "Rounded corners" + }, + "show-album-art": { + "description": "Show the album artwork and track information (title and artist).", + "label": "Show album art & title" + }, "show-buttons": { - "description": "Show media control buttons (play/pause, previous, next) on the media player widget.", + "description": "Show media control buttons (play/pause, previous, next).", "label": "Show buttons" }, - "visualizer-type": { - "description": "Choose a visualization type for the desktop media player background.", - "label": "Visualization type" + "show-visualizer": { + "description": "Show the audio visualizer overlay.", + "label": "Show visualizer" }, - "visualizer-visibility": { - "description": "Control when the audio visualizer is visible.", - "label": "Audio visualizer visibility" + "visualizer-type": { + "description": "Choose a visualization type.", + "label": "Visualization type" } }, "title": "Desktop Widgets", diff --git a/Modules/DesktopWidgets/DesktopWidgets.qml b/Modules/DesktopWidgets/DesktopWidgets.qml index 0852287dc..e34798bd5 100644 --- a/Modules/DesktopWidgets/DesktopWidgets.qml +++ b/Modules/DesktopWidgets/DesktopWidgets.qml @@ -34,22 +34,53 @@ Variants { id: screenLoader required property ShellScreen modelData - // Reactive property for widgets on this specific screen - property var screenWidgets: { + property ListModel screenWidgetsModel: ListModel {} + + property var settingsWatcher: Settings.data.desktopWidgets.monitorWidgets + onSettingsWatcherChanged: Qt.callLater(updateModel) + + Component.onCompleted: Qt.callLater(updateModel) + + function updateModel() { if (!modelData || !modelData.name) { - return []; + screenWidgetsModel.clear(); + return; } + var monitorWidgets = Settings.data.desktopWidgets.monitorWidgets || []; + var newWidgets = []; for (var i = 0; i < monitorWidgets.length; i++) { if (monitorWidgets[i].name === modelData.name) { - return monitorWidgets[i].widgets || []; + newWidgets = monitorWidgets[i].widgets || []; + break; } } - return []; + + // Update in-place to preserve delegates + for (var j = 0; j < Math.min(newWidgets.length, screenWidgetsModel.count); j++) { + var item = screenWidgetsModel.get(j); + var newData = newWidgets[j]; + // Update each property individually + for (var key in newData) { + if (item[key] !== newData[key]) { + item[key] = newData[key]; + } + } + } + + // Remove excess + while (screenWidgetsModel.count > newWidgets.length) { + screenWidgetsModel.remove(screenWidgetsModel.count - 1); + } + + // Append new + for (var k = screenWidgetsModel.count; k < newWidgets.length; k++) { + screenWidgetsModel.append(newWidgets[k]); + } } // Only create PanelWindow if enabled AND screen has widgets - active: modelData && Settings.data.desktopWidgets.enabled && screenWidgets.length > 0 && !PowerProfileService.noctaliaPerformanceMode + active: modelData && Settings.data.desktopWidgets.enabled && screenWidgetsModel.count > 0 && !PowerProfileService.noctaliaPerformanceMode sourceComponent: PanelWindow { id: window @@ -264,33 +295,33 @@ Variants { // Load widgets dynamically from per-monitor array Repeater { - model: screenLoader.screenWidgets + model: screenLoader.screenWidgetsModel delegate: Loader { id: widgetLoader // Bind to registeredWidgets and pluginReloadCounter to re-evaluate when plugins register/unregister - active: (modelData.id in root.registeredWidgets) && (root.pluginReloadCounter >= 0) + active: (model.id in root.registeredWidgets) && (root.pluginReloadCounter >= 0) - property var widgetData: modelData + required property var model property int widgetIndex: index sourceComponent: { // Access registeredWidgets and pluginReloadCounter to create reactive binding var _ = root.pluginReloadCounter; var widgets = root.registeredWidgets; - return widgets[modelData.id] || null; + return widgets[model.id] || null; } onLoaded: { if (item) { item.screen = window.screen; item.parent = widgetsContainer; - item.widgetData = widgetData; + item.widgetData = Qt.binding(function() { return widgetLoader.model; }); item.widgetIndex = widgetIndex; // Inject plugin API for plugin widgets - if (DesktopWidgetRegistry.isPluginWidget(modelData.id)) { - var pluginId = modelData.id.replace("plugin:", ""); + if (DesktopWidgetRegistry.isPluginWidget(model.id)) { + var pluginId = model.id.replace("plugin:", ""); var api = PluginService.getPluginAPI(pluginId); if (api && item.hasOwnProperty("pluginApi")) { item.pluginApi = api; diff --git a/Modules/DesktopWidgets/DraggableDesktopWidget.qml b/Modules/DesktopWidgets/DraggableDesktopWidget.qml index 9f6b7a186..dd0cb2625 100644 --- a/Modules/DesktopWidgets/DraggableDesktopWidget.qml +++ b/Modules/DesktopWidgets/DraggableDesktopWidget.qml @@ -22,6 +22,7 @@ Item { readonly property bool isScaling: internal.isScaling property bool showBackground: (widgetData && widgetData.showBackground !== undefined) ? widgetData.showBackground : true + property bool roundedCorners: (widgetData && widgetData.roundedCorners !== undefined) ? widgetData.roundedCorners : true property real widgetScale: (widgetData && widgetData.scale !== undefined) ? widgetData.scale : 1.0 property real minScale: 0.5 @@ -265,7 +266,7 @@ Item { Rectangle { id: container anchors.fill: parent - radius: Style.radiusL + radius: root.roundedCorners ? Style.radiusL : 0 color: Color.mSurface border { width: 1 diff --git a/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml b/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml index a18340ee0..93cdc903b 100644 --- a/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml +++ b/Modules/DesktopWidgets/Widgets/DesktopMediaPlayer.qml @@ -17,6 +17,10 @@ DraggableDesktopWidget { // Widget settings readonly property string hideMode: (widgetData.hideMode !== undefined) ? widgetData.hideMode : "visible" readonly property bool showButtons: (widgetData.showButtons !== undefined) ? widgetData.showButtons : true + readonly property bool showAlbumArt: (widgetData.showAlbumArt !== undefined) ? widgetData.showAlbumArt : true + readonly property bool showVisualizer: (widgetData.showVisualizer !== undefined) ? widgetData.showVisualizer : true + readonly property string visualizerType: (widgetData.visualizerType && widgetData.visualizerType !== "") ? widgetData.visualizerType : "linear" + readonly property bool roundedCorners: (widgetData.roundedCorners !== undefined) ? widgetData.roundedCorners : true readonly property bool hasPlayer: MediaService.currentPlayer !== null readonly property bool isPlaying: MediaService.isPlaying readonly property bool hasActiveTrack: hasPlayer && (MediaService.trackTitle || MediaService.trackArtist) @@ -52,13 +56,9 @@ DraggableDesktopWidget { readonly property bool showPrev: hasPlayer && MediaService.canGoPrevious readonly property bool showNext: hasPlayer && MediaService.canGoNext readonly property int visibleButtonCount: root.showButtons ? (1 + (showPrev ? 1 : 0) + (showNext ? 1 : 0)) : 0 - readonly property int baseWidth: 400 * Style.uiScaleRatio - readonly property int buttonWidth: 32 * Style.uiScaleRatio - readonly property int buttonSpacing: Style.marginXS - readonly property int controlsWidth: visibleButtonCount * buttonWidth + (visibleButtonCount > 1 ? (visibleButtonCount - 1) * buttonSpacing : 0) - implicitWidth: baseWidth - (3 - visibleButtonCount) * (buttonWidth + buttonSpacing) - implicitHeight: contentLayout.implicitHeight + Style.marginM * 2 + implicitWidth: 400 * Style.uiScaleRatio + implicitHeight: 64 * Style.uiScaleRatio + Style.marginM * 2 width: implicitWidth height: implicitHeight @@ -80,7 +80,7 @@ DraggableDesktopWidget { sourceItem: Rectangle { width: root.width - Style.marginXS * 2 height: root.height - Style.marginXS * 2 - radius: Math.max(0, Style.radiusL - Style.marginXS) + radius: root.roundedCorners ? Math.max(0, Style.radiusL - Style.marginXS) : 0 color: "white" antialiasing: true smooth: true @@ -92,27 +92,29 @@ DraggableDesktopWidget { } // Visualizer visibility mode - readonly property string visualizerVisibility: (widgetData && widgetData.visualizerVisibility !== undefined) ? widgetData.visualizerVisibility : "always" readonly property bool shouldShowVisualizer: { - if (!(widgetData && widgetData.visualizerType) || widgetData.visualizerType === "" || widgetData.visualizerType === "none") + if (!root.showVisualizer) return false; - if (visualizerVisibility === "always") - return true; - if (visualizerVisibility === "with-background") - return root.showBackground; - return true; // default to always visible + if (root.visualizerType === "" || root.visualizerType === "none") + return false; + return true; } // Visualizer overlay (visibility controlled by visualizerVisibility setting) Loader { anchors.fill: parent - anchors.margins: Style.marginXS + anchors.leftMargin: Style.marginXS + anchors.rightMargin: Style.marginXS + anchors.topMargin: Style.marginXS + anchors.bottomMargin: 0 z: 0 clip: true active: shouldShowVisualizer layer.enabled: true layer.smooth: true - layer.samples: 4 + layer.samples: 8 + layer.mipmap: true + layer.textureSize: Qt.size(width * 2, height * 2) layer.effect: MultiEffect { maskEnabled: true maskThresholdMin: 0.95 @@ -120,8 +122,8 @@ DraggableDesktopWidget { maskSource: ShaderEffectSource { sourceItem: Rectangle { width: root.width - Style.marginXS * 2 - height: root.height - Style.marginXS * 2 - radius: Math.max(0, Style.radiusL - Style.marginXS) + height: root.height - Style.marginXS + radius: root.roundedCorners ? Math.max(0, Style.radiusL - Style.marginXS) : 0 color: "white" antialiasing: true smooth: true @@ -132,8 +134,7 @@ DraggableDesktopWidget { } sourceComponent: { - var visualizerType = (widgetData && widgetData.visualizerType) ? widgetData.visualizerType : ""; - switch (visualizerType) { + switch (root.visualizerType) { case "linear": return linearComponent; case "mirrored": @@ -151,7 +152,7 @@ DraggableDesktopWidget { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: 0.6 + opacity: 1.0 } } @@ -161,7 +162,7 @@ DraggableDesktopWidget { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: 0.6 + opacity: 1.0 } } @@ -171,19 +172,45 @@ DraggableDesktopWidget { anchors.fill: parent values: CavaService.values fillColor: Color.mPrimary - opacity: 0.6 + opacity: 1.0 } } } RowLayout { id: contentLayout - anchors.fill: parent + states: [ + State { + when: root.showButtons + AnchorChanges { + target: contentLayout + anchors.horizontalCenter: undefined + anchors.verticalCenter: undefined + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + } + }, + State { + when: !root.showButtons + AnchorChanges { + target: contentLayout + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + anchors.top: undefined + anchors.bottom: undefined + anchors.left: undefined + anchors.right: undefined + } + } + ] anchors.margins: Style.marginM spacing: Style.marginS z: 2 Item { + visible: root.showAlbumArt Layout.preferredWidth: 64 * Style.uiScaleRatio Layout.preferredHeight: 64 * Style.uiScaleRatio Layout.alignment: Qt.AlignVCenter @@ -208,7 +235,9 @@ DraggableDesktopWidget { } ColumnLayout { + visible: root.showAlbumArt Layout.fillWidth: true + Layout.alignment: root.showButtons ? Qt.AlignVCenter : Qt.AlignCenter spacing: 0 NText { @@ -238,6 +267,7 @@ DraggableDesktopWidget { spacing: Style.marginXS z: 10 visible: root.showButtons + Layout.alignment: root.showAlbumArt ? Qt.AlignVCenter : Qt.AlignCenter NIconButton { visible: showPrev diff --git a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml index e4b02e83f..061002299 100644 --- a/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml +++ b/Modules/Panels/Settings/DesktopWidgets/WidgetSettings/MediaPlayerSettings.qml @@ -12,18 +12,26 @@ ColumnLayout { property var widgetMetadata: null property bool valueShowBackground: widgetData.showBackground !== undefined ? widgetData.showBackground : widgetMetadata.showBackground - property string valueVisualizerType: widgetData.visualizerType !== undefined ? widgetData.visualizerType : widgetMetadata.visualizerType + property string valueVisualizerType: (widgetData.visualizerType && widgetData.visualizerType !== "") ? widgetData.visualizerType : (widgetMetadata.visualizerType || "linear") property string valueHideMode: widgetData.hideMode !== undefined ? widgetData.hideMode : widgetMetadata.hideMode - property string valueVisualizerVisibility: widgetData.visualizerVisibility !== undefined ? widgetData.visualizerVisibility : (widgetMetadata.visualizerVisibility !== undefined ? widgetMetadata.visualizerVisibility : "always") property bool valueShowButtons: widgetData.showButtons !== undefined ? widgetData.showButtons : (widgetMetadata.showButtons !== undefined ? widgetMetadata.showButtons : true) + property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : (widgetMetadata.showAlbumArt !== undefined ? widgetMetadata.showAlbumArt : true) + property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : (widgetMetadata.showVisualizer !== undefined ? widgetMetadata.showVisualizer : true) + property bool valueRoundedCorners: widgetData.roundedCorners !== undefined ? widgetData.roundedCorners : (widgetMetadata.roundedCorners !== undefined ? widgetMetadata.roundedCorners : true) function saveSettings() { var settings = Object.assign({}, widgetData || {}); settings.showBackground = valueShowBackground; settings.visualizerType = valueVisualizerType; settings.hideMode = valueHideMode; - settings.visualizerVisibility = valueVisualizerVisibility; settings.showButtons = valueShowButtons; + settings.showAlbumArt = valueShowAlbumArt; + settings.showVisualizer = valueShowVisualizer; + settings.roundedCorners = valueRoundedCorners; + + // Clean up legacy property + delete settings.visualizerVisibility; + return settings; } @@ -35,6 +43,30 @@ ColumnLayout { onToggled: checked => valueShowBackground = checked } + NToggle { + Layout.fillWidth: true + label: I18n.tr("settings.desktop-widgets.media-player.rounded-corners.label") + description: I18n.tr("settings.desktop-widgets.media-player.rounded-corners.description") + checked: valueRoundedCorners + onToggled: checked => valueRoundedCorners = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("settings.desktop-widgets.media-player.show-album-art.label") + description: I18n.tr("settings.desktop-widgets.media-player.show-album-art.description") + checked: valueShowAlbumArt + onToggled: checked => valueShowAlbumArt = checked + } + + NToggle { + Layout.fillWidth: true + label: I18n.tr("settings.desktop-widgets.media-player.show-visualizer.label") + description: I18n.tr("settings.desktop-widgets.media-player.show-visualizer.description") + checked: valueShowVisualizer + onToggled: checked => valueShowVisualizer = checked + } + NToggle { Layout.fillWidth: true label: I18n.tr("settings.desktop-widgets.media-player.show-buttons.label") @@ -47,11 +79,8 @@ ColumnLayout { Layout.fillWidth: true label: I18n.tr("settings.desktop-widgets.media-player.visualizer-type.label") description: I18n.tr("settings.desktop-widgets.media-player.visualizer-type.description") + enabled: valueShowVisualizer model: [ - { - "key": "", - "name": I18n.tr("options.visualizer-types.none") - }, { "key": "linear", "name": I18n.tr("options.visualizer-types.linear") @@ -69,25 +98,6 @@ ColumnLayout { onSelected: key => valueVisualizerType = key } - NComboBox { - Layout.fillWidth: true - label: I18n.tr("settings.desktop-widgets.media-player.visualizer-visibility.label") - description: I18n.tr("settings.desktop-widgets.media-player.visualizer-visibility.description") - enabled: valueVisualizerType && valueVisualizerType !== "" && valueVisualizerType !== "none" - model: [ - { - "key": "always", - "name": I18n.tr("options.visualizer-visibility.always") - }, - { - "key": "with-background", - "name": I18n.tr("options.visualizer-visibility.with-background") - } - ] - currentKey: valueVisualizerVisibility - onSelected: key => valueVisualizerVisibility = key - } - NComboBox { Layout.fillWidth: true label: I18n.tr("settings.desktop-widgets.media-player.hide-mode.label") diff --git a/Services/UI/DesktopWidgetRegistry.qml b/Services/UI/DesktopWidgetRegistry.qml index 1387a37b3..c8ed712ac 100644 --- a/Services/UI/DesktopWidgetRegistry.qml +++ b/Services/UI/DesktopWidgetRegistry.qml @@ -67,7 +67,10 @@ Singleton { "showBackground": true, "visualizerType": "linear", "hideMode": "visible", - "showButtons": true + "showButtons": true, + "showAlbumArt": true, + "showVisualizer": true, + "roundedCorners": true }, "Weather": { "allowUserSettings": true,