From a878067e163c0f3e5015344ece456c4f4e14349b Mon Sep 17 00:00:00 2001 From: Damocles Date: Thu, 16 Apr 2026 20:55:10 +0200 Subject: [PATCH] mpris: crossfade album art on session switch --- modules/Mpris.qml | 65 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 8 deletions(-) diff --git a/modules/Mpris.qml b/modules/Mpris.qml index 0ffbbb7..f783643 100644 --- a/modules/Mpris.qml +++ b/modules/Mpris.qml @@ -122,7 +122,7 @@ M.BarSection { panelTitle: "Now Playing" contentWidth: 280 - // Album art — always 1:1 + // Album art — always 1:1, crossfades on session switch Item { width: parent.width height: width @@ -133,22 +133,71 @@ M.BarSection { color: M.Theme.base02 } + // Outgoing art — snaps to current opacity, then fades out + Image { + id: _artImgPrev + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + asynchronous: true + opacity: 0 + + NumberAnimation { + id: _prevFadeOut + target: _artImgPrev + property: "opacity" + to: 0 + duration: 300 + easing.type: Easing.InOutCubic + } + } + + // Incoming art — fades in once loaded Image { id: _artImg anchors.fill: parent fillMode: Image.PreserveAspectCrop - visible: _hasArt asynchronous: true - source: root._cachedArt + opacity: 0 property bool _hasArt: false - onStatusChanged: if (status === Image.Ready) - _hasArt = true + + onStatusChanged: { + if (status === Image.Ready && source !== "") { + _hasArt = true; + _artFadeIn.start(); + _prevFadeOut.start(); + } else if (status === Image.Error) { + _hasArt = false; + } + } + + NumberAnimation { + id: _artFadeIn + target: _artImg + property: "opacity" + to: 1 + duration: 300 + easing.type: Easing.InOutCubic + } + Connections { target: root function on_CachedArtChanged() { - if (!root._cachedArt) + if (!root._cachedArt) { + _artFadeIn.stop(); + _prevFadeOut.stop(); _artImg._hasArt = false; + _artImg.opacity = 0; + _artImgPrev.opacity = 0; + _artImg.source = ""; + } else if (root._cachedArt !== _artImg.source) { + _prevFadeOut.stop(); + _artFadeIn.stop(); + _artImgPrev.source = _artImg.source; + _artImgPrev.opacity = _artImg.opacity; + _artImg.opacity = 0; + _artImg.source = root._cachedArt; + } } } } @@ -187,7 +236,7 @@ M.BarSection { anchors.right: parent.right anchors.bottom: parent.bottom height: 40 - visible: _artImg.visible + visible: _artImg._hasArt gradient: Gradient { GradientStop { position: 0 @@ -206,7 +255,7 @@ M.BarSection { color: M.Theme.base04 font.pixelSize: 28 font.family: M.Theme.iconFontFamily - visible: _artImg.status !== Image.Ready + visible: !_artImg._hasArt } }