From 942fd86bc88803f827ac60904646d03d585e7def Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:54:43 +1000 Subject: [PATCH] feat: improve dash media --- components/effects/Mask.qml | 7 ++ modules/dashboard/dash/Media.qml | 123 ++++++++++++++++++------- plugin/src/Caelestia/Config/tokens.hpp | 2 +- 3 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 components/effects/Mask.qml diff --git a/components/effects/Mask.qml b/components/effects/Mask.qml new file mode 100644 index 00000000..fb463adf --- /dev/null +++ b/components/effects/Mask.qml @@ -0,0 +1,7 @@ +import QtQuick.Effects + +MultiEffect { + maskEnabled: true + maskSpreadAtMin: 1 + maskThresholdMin: 0.5 +} diff --git a/modules/dashboard/dash/Media.qml b/modules/dashboard/dash/Media.qml index a01d501d..7d806325 100644 --- a/modules/dashboard/dash/Media.qml +++ b/modules/dashboard/dash/Media.qml @@ -1,8 +1,14 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Shapes +import QtQuick.Effects +import M3Shapes import Caelestia.Config import Caelestia.Services +import Caelestia.Components import qs.components +import qs.components.effects import qs.services import qs.utils @@ -14,6 +20,10 @@ Item { return active?.length ? (active.position % active.length) / active.length : 0; } + readonly property real arcCoverGap: Tokens.spacing.small + readonly property real arcRadius: (cover.width + Tokens.sizes.dashboard.mediaProgressThickness) / 2 + arcCoverGap + readonly property real arcGap: (Tokens.spacing.extraSmall + Tokens.sizes.dashboard.mediaProgressThickness) / arcRadius * 180 / Math.PI + anchors.top: parent.top anchors.bottom: parent.bottom implicitWidth: Tokens.sizes.dashboard.mediaWidth @@ -38,40 +48,23 @@ Item { Shape { preferredRendererType: Shape.CurveRenderer + opacity: Math.min(1, remainingArc.sweepAngle) ShapePath { fillColor: "transparent" - strokeColor: Colours.layer(Colours.palette.m3surfaceContainerHigh, 2) - strokeWidth: root.Tokens.sizes.dashboard.mediaProgressThickness - capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap + strokeColor: Colours.palette.m3secondaryContainer + strokeWidth: Math.min(1, remainingArc.sweepAngle) * root.Tokens.sizes.dashboard.mediaProgressThickness + capStyle: ShapePath.RoundCap PathAngleArc { + id: remainingArc + centerX: cover.x + cover.width / 2 centerY: cover.y + cover.height / 2 - radiusX: (cover.width + root.Tokens.sizes.dashboard.mediaProgressThickness) / 2 + root.Tokens.spacing.small - radiusY: (cover.height + root.Tokens.sizes.dashboard.mediaProgressThickness) / 2 + root.Tokens.spacing.small - startAngle: -90 - root.Tokens.sizes.dashboard.mediaProgressSweep / 2 - sweepAngle: root.Tokens.sizes.dashboard.mediaProgressSweep - } - - Behavior on strokeColor { - CAnim {} - } - } - - ShapePath { - fillColor: "transparent" - strokeColor: Colours.palette.m3primary - strokeWidth: root.Tokens.sizes.dashboard.mediaProgressThickness - capStyle: root.Tokens.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap - - PathAngleArc { - centerX: cover.x + cover.width / 2 - centerY: cover.y + cover.height / 2 - radiusX: (cover.width + root.Tokens.sizes.dashboard.mediaProgressThickness) / 2 + root.Tokens.spacing.small - radiusY: (cover.height + root.Tokens.sizes.dashboard.mediaProgressThickness) / 2 + root.Tokens.spacing.small - startAngle: -90 - root.Tokens.sizes.dashboard.mediaProgressSweep / 2 - sweepAngle: root.Tokens.sizes.dashboard.mediaProgressSweep * root.playerProgress + radiusX: root.arcRadius + radiusY: root.arcRadius + startAngle: -90 - root.Tokens.sizes.dashboard.mediaProgressSweep / 2 + root.playerProgress * root.Tokens.sizes.dashboard.mediaProgressSweep + root.arcGap + sweepAngle: Math.max(0.1, root.Tokens.sizes.dashboard.mediaProgressSweep * (1 - root.playerProgress) - root.arcGap) } Behavior on strokeColor { @@ -80,17 +73,78 @@ Item { } } - StyledClippingRect { + WavyLine { + anchors.fill: cover + anchors.margins: -lineWidth * amplitudeMultiplier * 2 - lineWidth - root.arcCoverGap + + lineWidth: Tokens.sizes.dashboard.mediaProgressThickness + color: Colours.palette.m3primary + pathType: WavyLine.Arc + radius: root.arcRadius + frequency: 8 + startAngle: -fullAngle / 2 + fullAngle: Tokens.sizes.dashboard.mediaProgressSweep + value: root.playerProgress + + Anim on waveProgress { + running: true + paused: !Players.active?.isPlaying + from: 0 + to: 1 + duration: 2000 + easing.type: Easing.Linear + loops: Animation.Infinite + } + + Behavior on color { + CAnim {} + } + } + + Item { id: cover anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.margins: Tokens.padding.large + Tokens.sizes.dashboard.mediaProgressThickness + Tokens.spacing.small - + anchors.margins: Tokens.padding.large + Tokens.sizes.dashboard.mediaProgressThickness + root.arcCoverGap implicitHeight: width - color: Colours.tPalette.m3surfaceContainerHigh - radius: Infinity + + // Slight glow to separate from bg + layer.enabled: true + layer.effect: MultiEffect { + shadowEnabled: true + blurMax: 1 + shadowColor: Colours.palette.m3outline + shadowOpacity: 0.3 + } + + Item { + id: coverShape + + anchors.fill: parent + layer.enabled: true + + MaterialShape { + implicitSize: cover.width + shape: MaterialShape.Cookie12Sided + color: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2) + + Anim on rotation { + running: true + paused: !Players.active?.isPlaying + from: 360 + to: 0 + duration: 23500 + easing.type: Easing.Linear + loops: Animation.Infinite + } + + Behavior on color { + CAnim {} + } + } + } MaterialIcon { anchors.centerIn: parent @@ -111,6 +165,11 @@ Item { fillMode: Image.PreserveAspectCrop sourceSize.width: width sourceSize.height: height + + layer.enabled: true + layer.effect: Mask { + maskSource: coverShape + } } } diff --git a/plugin/src/Caelestia/Config/tokens.hpp b/plugin/src/Caelestia/Config/tokens.hpp index 712881f3..2ebb70c9 100644 --- a/plugin/src/Caelestia/Config/tokens.hpp +++ b/plugin/src/Caelestia/Config/tokens.hpp @@ -184,7 +184,7 @@ class DashboardTokens : public ConfigObject { CONFIG_PROPERTY(int, dateTimeWidth, 110) CONFIG_PROPERTY(int, mediaWidth, 200) CONFIG_PROPERTY(int, mediaProgressSweep, 180) - CONFIG_PROPERTY(int, mediaProgressThickness, 8) + CONFIG_PROPERTY(int, mediaProgressThickness, 6) CONFIG_PROPERTY(int, resourceProgressThickness, 10) CONFIG_PROPERTY(int, weatherWidth, 275) CONFIG_PROPERTY(int, mediaCoverArtSize, 150)