DesktopMediaPlayer: improve options for visual customization

This commit is contained in:
Eric Handley
2025-12-27 20:52:30 -08:00
parent 8b8a86784a
commit a6e4060280
6 changed files with 156 additions and 73 deletions
+16 -8
View File
@@ -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",
+44 -13
View File
@@ -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;
@@ -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
@@ -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
@@ -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")
+4 -1
View File
@@ -67,7 +67,10 @@ Singleton {
"showBackground": true,
"visualizerType": "linear",
"hideMode": "visible",
"showButtons": true
"showButtons": true,
"showAlbumArt": true,
"showVisualizer": true,
"roundedCorners": true
},
"Weather": {
"allowUserSettings": true,