From d04ae8a81de40c622aad7badb0b6d1a0213c602b Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Mon, 24 Nov 2025 02:55:03 +0800 Subject: [PATCH 1/3] feat: Add circular progress bar to MediaMini widget --- Modules/Bar/Widgets/MediaMini.qml | 122 ++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 82fe9df0..347ec589 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -321,14 +321,78 @@ Item { Layout.preferredWidth: Math.round(21 * scaling) Layout.preferredHeight: Math.round(21 * scaling) + // Background for progress circle + Rectangle { + anchors.fill: parent + radius: width / 2 + color: Color.transparent + } + + // Progress circle + Canvas { + id: progressCanvas + anchors.fill: parent + anchors.margins: 0 // Align exactly with parent to avoid clipping + z: 0 // Behind the album art + + // Calculate progress ratio: 0 to 1 + property real progressRatio: { + if (!MediaService.currentPlayer || MediaService.trackLength <= 0) + return 0; + const r = MediaService.currentPosition / MediaService.trackLength; + if (isNaN(r) || !isFinite(r)) + return 0; + return Math.max(0, Math.min(1, r)); + } + + onProgressRatioChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d"); + var centerX = width / 2; + var centerY = height / 2; + var radius = Math.min(width, height) / 2 - 3; // Larger radius to create visible ring + + ctx.reset(); + + // Background circle (full track, not played yet) + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.lineWidth = 3; // Thicker line for better visibility + ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility + ctx.stroke(); + + // Progress arc (played portion) + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI); + ctx.lineWidth = 3; // Thicker line for better visibility + ctx.strokeStyle = Color.mPrimary; // Use primary color for progress + ctx.lineCap = "round"; + ctx.stroke(); + } + } + + // Connection to update progress when media position changes + Connections { + target: MediaService + function onCurrentPositionChanged() { + progressCanvas.requestPaint(); + } + function onTrackLengthChanged() { + progressCanvas.requestPaint(); + } + } + NImageCircled { id: trackArt anchors.fill: parent + anchors.margins: 4 // Make album art slightly smaller to reveal progress ring underneath imagePath: MediaService.trackArtUrl fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play" fallbackIconSize: 10 borderWidth: 0 border.color: Color.transparent + z: 1 // In front of the progress circle } } } @@ -484,6 +548,52 @@ Item { } } + // Progress circle for vertical layout - follows background radius + Canvas { + id: progressCanvasVertical + anchors.fill: parent + anchors.margins: 0 // Align with parent container (mainContainer which matches mediaMini) + visible: isVerticalBar + z: 0 // Behind other content + + // Calculate progress ratio: 0 to 1 + property real progressRatio: { + if (!MediaService.currentPlayer || MediaService.trackLength <= 0) + return 0; + const r = MediaService.currentPosition / MediaService.trackLength; + if (isNaN(r) || !isFinite(r)) + return 0; + return Math.max(0, Math.min(1, r)); + } + + onProgressRatioChanged: requestPaint() + + onPaint: { + var ctx = getContext("2d"); + var centerX = width / 2; + var centerY = height / 2; + // Align with mediaMini radius which is circular in vertical mode + var radius = Math.min(width, height) / 2 - 4; // Position ring near the outer edge of background + + ctx.reset(); + + // Background circle (full track, not played yet) + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); + ctx.lineWidth = 3; // Thicker line for better visibility in vertical layout + ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility + ctx.stroke(); + + // Progress arc (played portion) + ctx.beginPath(); + ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI); + ctx.lineWidth = 3; // Thicker line for better visibility in vertical layout + ctx.strokeStyle = Color.mPrimary; // Use primary color for progress + ctx.lineCap = "round"; + ctx.stroke(); + } + } + // Vertical layout for left/right bars - icon only Item { id: verticalLayout @@ -507,10 +617,22 @@ Item { pointSize: Style.fontSizeL * scaling verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter + z: 1 // In front of the progress circle } } } + // Connection to update vertical progress when media position changes + Connections { + target: MediaService + function onCurrentPositionChanged() { + progressCanvasVertical.requestPaint(); + } + function onTrackLengthChanged() { + progressCanvasVertical.requestPaint(); + } + } + // Mouse area for hover detection MouseArea { id: mouseArea From a5fdf67b05b3cb17f10e53aba85cd42aba6bdf30 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Mon, 24 Nov 2025 03:36:57 +0800 Subject: [PATCH 2/3] fix: Optimize MediaMini widget progress ring display --- Assets/Translations/en.json | 4 ++++ Modules/Bar/Widgets/MediaMini.qml | 16 +++++++++------- .../Bar/WidgetSettings/MediaMiniSettings.qml | 9 +++++++++ Services/UI/BarWidgetRegistry.qml | 1 + 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 39783217..88a8b69c 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -231,6 +231,10 @@ "description": "Display an audio visualizer when music is playing.", "label": "Show visualizer" }, + "show-progress-ring": { + "description": "Display a circular progress indicator showing track progress.", + "label": "Show progress ring" + }, "use-fixed-width": { "description": "When enabled, the widget will always use the maximum width instead of dynamically adjusting to content.", "label": "Use Fixed Width" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 347ec589..75453eb1 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -42,6 +42,7 @@ Item { 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 string scrollingMode: (widgetSettings.scrollingMode !== undefined) ? widgetSettings.scrollingMode : widgetMetadata.scrollingMode + readonly property bool showProgressRing: (widgetSettings.showProgressRing !== undefined) ? widgetSettings.showProgressRing : widgetMetadata.showProgressRing // Maximum widget width with user settings support readonly property real maxWidth: (widgetSettings.maxWidth !== undefined) ? widgetSettings.maxWidth : Math.max(widgetMetadata.maxWidth, screen ? screen.width * 0.06 : 0) @@ -333,6 +334,7 @@ Item { id: progressCanvas anchors.fill: parent anchors.margins: 0 // Align exactly with parent to avoid clipping + visible: showProgressRing // Control visibility with setting z: 0 // Behind the album art // Calculate progress ratio: 0 to 1 @@ -351,21 +353,21 @@ Item { var ctx = getContext("2d"); var centerX = width / 2; var centerY = height / 2; - var radius = Math.min(width, height) / 2 - 3; // Larger radius to create visible ring + var radius = Math.min(width, height) / 2 - (1.25 * scaling); // Larger radius, accounting for line width to approach edge ctx.reset(); // Background circle (full track, not played yet) ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); - ctx.lineWidth = 3; // Thicker line for better visibility + ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility ctx.stroke(); // Progress arc (played portion) ctx.beginPath(); ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI); - ctx.lineWidth = 3; // Thicker line for better visibility + ctx.lineWidth = 3 * scaling; // Thicker line width based on scaling property ctx.strokeStyle = Color.mPrimary; // Use primary color for progress ctx.lineCap = "round"; ctx.stroke(); @@ -386,7 +388,7 @@ Item { NImageCircled { id: trackArt anchors.fill: parent - anchors.margins: 4 // Make album art slightly smaller to reveal progress ring underneath + anchors.margins: showProgressRing ? (2.5 * scaling) : 0.5 // Make album art smaller only when progress ring is visible, scaled with widget imagePath: MediaService.trackArtUrl fallbackIcon: MediaService.isPlaying ? "media-pause" : "media-play" fallbackIconSize: 10 @@ -553,7 +555,7 @@ Item { id: progressCanvasVertical anchors.fill: parent anchors.margins: 0 // Align with parent container (mainContainer which matches mediaMini) - visible: isVerticalBar + visible: isVerticalBar && showProgressRing // Control visibility with setting z: 0 // Behind other content // Calculate progress ratio: 0 to 1 @@ -580,14 +582,14 @@ Item { // Background circle (full track, not played yet) ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI); - ctx.lineWidth = 3; // Thicker line for better visibility in vertical layout + ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.4); // More opaque for better visibility ctx.stroke(); // Progress arc (played portion) ctx.beginPath(); ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progressRatio * 2 * Math.PI); - ctx.lineWidth = 3; // Thicker line for better visibility in vertical layout + ctx.lineWidth = 1.5 * scaling; // Line width based on scaling property, thinner for vertical layout ctx.strokeStyle = Color.mPrimary; // Use primary color for progress ctx.lineCap = "round"; ctx.stroke(); diff --git a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml index 02b0a520..48311685 100644 --- a/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml +++ b/Modules/Panels/Settings/Bar/WidgetSettings/MediaMiniSettings.qml @@ -23,6 +23,7 @@ ColumnLayout { property string valueScrollingMode: widgetData.scrollingMode || widgetMetadata.scrollingMode property int valueMaxWidth: widgetData.maxWidth !== undefined ? widgetData.maxWidth : widgetMetadata.maxWidth property bool valueUseFixedWidth: widgetData.useFixedWidth !== undefined ? widgetData.useFixedWidth : widgetMetadata.useFixedWidth + property bool valueShowProgressRing: widgetData.showProgressRing !== undefined ? widgetData.showProgressRing : widgetMetadata.showProgressRing Component.onCompleted: { if (widgetData && widgetData.hideMode !== undefined) { @@ -41,6 +42,7 @@ ColumnLayout { settings.scrollingMode = valueScrollingMode; settings.maxWidth = parseInt(widthInput.text) || widgetMetadata.maxWidth; settings.useFixedWidth = valueUseFixedWidth; + settings.showProgressRing = valueShowProgressRing; return settings; } @@ -130,6 +132,13 @@ ColumnLayout { onToggled: checked => valueUseFixedWidth = checked } + NToggle { + label: I18n.tr("bar.widget-settings.media-mini.show-progress-ring.label") + description: I18n.tr("bar.widget-settings.media-mini.show-progress-ring.description") + checked: valueShowProgressRing + onToggled: checked => valueShowProgressRing = checked + } + NComboBox { label: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.label") description: I18n.tr("bar.widget-settings.media-mini.scrolling-mode.description") diff --git a/Services/UI/BarWidgetRegistry.qml b/Services/UI/BarWidgetRegistry.qml index f064c748..931b583b 100644 --- a/Services/UI/BarWidgetRegistry.qml +++ b/Services/UI/BarWidgetRegistry.qml @@ -161,6 +161,7 @@ Singleton { "showAlbumArt": false, "showArtistFirst": true, "showVisualizer": false, + "showProgressRing": true, "visualizerType": "linear" }, "Microphone": { From f56109ad35c43fe1dcb450085e568eba61222f53 Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Mon, 24 Nov 2025 03:40:00 +0800 Subject: [PATCH 3/3] feat: Add progress ring setting translations for all languages --- Assets/Translations/de.json | 4 ++++ Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/nl.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/ru.json | 4 ++++ Assets/Translations/tr.json | 4 ++++ Assets/Translations/uk-UA.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ 9 files changed, 36 insertions(+) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index 82af9292..26e45bc1 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -231,6 +231,10 @@ "description": "Audio-Visualizer anzeigen, wenn Musik abgespielt wird.", "label": "Visualizer anzeigen" }, + "show-progress-ring": { + "description": "Runden Fortschrittsindikator anzeigen, der den Titelfortschritt anzeigt.", + "label": "Fortschrittsring anzeigen" + }, "use-fixed-width": { "description": "Wenn aktiviert, verwendet das Widget immer die maximale Breite, anstatt sich dynamisch an den Inhalt anzupassen.", "label": "Feste Breite verwenden" diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index 23e6086f..52e538e7 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -231,6 +231,10 @@ "description": "Mostrar un visualizador de audio cuando se reproduce música.", "label": "Mostrar visualizador" }, + "show-progress-ring": { + "description": "Mostrar un indicador de progreso circular que muestre el progreso de la pista.", + "label": "Mostrar anillo de progreso" + }, "use-fixed-width": { "description": "Cuando está activado, el widget siempre usará el ancho máximo en lugar de ajustarse dinámicamente al contenido.", "label": "Usar Ancho Fijo" diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index 95433471..ce37824a 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -231,6 +231,10 @@ "description": "Afficher un visualiseur audio quand la musique est en cours de lecture.", "label": "Afficher le visualiseur" }, + "show-progress-ring": { + "description": "Afficher un indicateur de progression circulaire montrant la progression de la piste.", + "label": "Afficher l'anneau de progression" + }, "use-fixed-width": { "description": "Lorsque activé, le widget utilisera toujours la largeur maximale au lieu de s'ajuster dynamiquement au contenu.", "label": "Utiliser une Largeur Fixe" diff --git a/Assets/Translations/nl.json b/Assets/Translations/nl.json index 669d2ea6..36352925 100644 --- a/Assets/Translations/nl.json +++ b/Assets/Translations/nl.json @@ -231,6 +231,10 @@ "description": "Toon een audiovisualizer wanneer muziek wordt afgespeeld.", "label": "Visualizer tonen" }, + "show-progress-ring": { + "description": "Toon een circulaire voortgangsindicator die het bestandsspoor voortgang toont.", + "label": "Voortgangscirkel tonen" + }, "use-fixed-width": { "description": "Indien ingeschakeld gebruikt de widget altijd de maximale breedte in plaats van zich aan te passen aan de inhoud.", "label": "Vaste breedte gebruiken" diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 8f66cdb5..1048696b 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -231,6 +231,10 @@ "description": "Exibir um visualizador de áudio quando música está sendo reproduzida.", "label": "Mostrar visualizador" }, + "show-progress-ring": { + "description": "Exibir um indicador de progresso circular mostrando o progresso da faixa.", + "label": "Mostrar anel de progresso" + }, "use-fixed-width": { "description": "Quando ativado, o widget sempre usará a largura máxima em vez de ajustar dinamicamente ao conteúdo.", "label": "Usar Largura Fixa" diff --git a/Assets/Translations/ru.json b/Assets/Translations/ru.json index e0f882e2..2215bbde 100644 --- a/Assets/Translations/ru.json +++ b/Assets/Translations/ru.json @@ -231,6 +231,10 @@ "description": "Отображать аудиовизуализатор при воспроизведении музыки.", "label": "Показывать визуализатор" }, + "show-progress-ring": { + "description": "Отображать круговой индикатор прогресса воспроизведения трека.", + "label": "Показывать кольцо прогресса" + }, "use-fixed-width": { "description": "Если включено, виджет всегда будет использовать максимальную ширину вместо динамической подстройки под содержимое.", "label": "Использовать фиксированную ширину" diff --git a/Assets/Translations/tr.json b/Assets/Translations/tr.json index c0d5d982..a5fa3e54 100644 --- a/Assets/Translations/tr.json +++ b/Assets/Translations/tr.json @@ -231,6 +231,10 @@ "description": "Müzik çalarken bir ses görselleştirici göster.", "label": "Görselleştiriciyi göster" }, + "show-progress-ring": { + "description": "Parça ilerlemesini gösteren dairesel bir ilerleme göstergesi gösterin.", + "label": "İlerleme halkası göster" + }, "use-fixed-width": { "description": "Etkinleştirildiğinde, widget dinamik olarak içerik göre ayarlamak yerine her zaman maksimum genişliği kullanır.", "label": "Sabit Genişlik Kullan" diff --git a/Assets/Translations/uk-UA.json b/Assets/Translations/uk-UA.json index 08efe119..ae5963f1 100644 --- a/Assets/Translations/uk-UA.json +++ b/Assets/Translations/uk-UA.json @@ -231,6 +231,10 @@ "description": "Відображати аудіовізуалізатор під час відтворення музики.", "label": "Показувати візуалізатор" }, + "show-progress-ring": { + "description": "Відображати круговий індикатор прогресу, що показує просування треку.", + "label": "Показувати кільце прогресу" + }, "use-fixed-width": { "description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.", "label": "Використовувати фіксовану ширину" diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index 60faab70..4d5b858f 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -231,6 +231,10 @@ "description": "播放音乐时显示音频可视化器。", "label": "显示可视化器" }, + "show-progress-ring": { + "description": "显示显示曲目进度的圆形进度指示器。", + "label": "显示进度环" + }, "use-fixed-width": { "description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。", "label": "使用固定宽度"