Merge pull request #851 from lonerOrz/feat/mm

feat: Add circular progress bar to MediaMini widget
This commit is contained in:
Lysec
2025-11-23 21:01:33 +01:00
committed by GitHub
13 changed files with 174 additions and 0 deletions
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -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"
+4
View File
@@ -231,6 +231,10 @@
"description": "Отображать аудиовизуализатор при воспроизведении музыки.",
"label": "Показывать визуализатор"
},
"show-progress-ring": {
"description": "Отображать круговой индикатор прогресса воспроизведения трека.",
"label": "Показывать кольцо прогресса"
},
"use-fixed-width": {
"description": "Если включено, виджет всегда будет использовать максимальную ширину вместо динамической подстройки под содержимое.",
"label": "Использовать фиксированную ширину"
+4
View File
@@ -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"
+4
View File
@@ -231,6 +231,10 @@
"description": "Відображати аудіовізуалізатор під час відтворення музики.",
"label": "Показувати візуалізатор"
},
"show-progress-ring": {
"description": "Відображати круговий індикатор прогресу, що показує просування треку.",
"label": "Показувати кільце прогресу"
},
"use-fixed-width": {
"description": "Коли увімкнено, віджет завжди використовуватиме максимальну ширину замість динамічного налаштування до вмісту.",
"label": "Використовувати фіксовану ширину"
+4
View File
@@ -231,6 +231,10 @@
"description": "播放音乐时显示音频可视化器。",
"label": "显示可视化器"
},
"show-progress-ring": {
"description": "显示显示曲目进度的圆形进度指示器。",
"label": "显示进度环"
},
"use-fixed-width": {
"description": "启用后,小部件将始终使用最大宽度,而不根据内容动态调整。",
"label": "使用固定宽度"
+124
View File
@@ -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
@@ -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")
+1
View File
@@ -161,6 +161,7 @@ Singleton {
"showAlbumArt": false,
"showArtistFirst": true,
"showVisualizer": false,
"showProgressRing": true,
"visualizerType": "linear"
},
"Microphone": {