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/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/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": "使用固定宽度" diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index 82fe9df0..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) @@ -321,14 +322,79 @@ 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 + visible: showProgressRing // Control visibility with setting + 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 - (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 * 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 * scaling; // Thicker line width based on scaling property + 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: 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 borderWidth: 0 border.color: Color.transparent + z: 1 // In front of the progress circle } } } @@ -484,6 +550,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 && showProgressRing // Control visibility with setting + 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 = 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 = 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(); + } + } + // Vertical layout for left/right bars - icon only Item { id: verticalLayout @@ -507,10 +619,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 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": {