refactor: move media cover art to separate component

This commit is contained in:
2 * r + 2 * t 2026-05-01 23:46:45 +10:00
parent 61738554e5
commit a54fe6f487
2 changed files with 163 additions and 148 deletions

View file

@ -0,0 +1,161 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Effects
import Quickshell
import M3Shapes
import Caelestia.Config
import qs.components
import qs.components.controls
import qs.components.effects
import qs.services
Item {
id: root
readonly property alias shape: shape
property bool hadPrevious
property color fallbackColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
// Slight glow to separate from bg
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 1
shadowColor: Colours.palette.m3outline
shadowOpacity: 0.3
}
Behavior on fallbackColour {
CAnim {}
}
Item {
id: shapeWrapper
anchors.fill: parent
layer.enabled: true
opacity: root.fallbackColour.a
MaterialShape {
id: shape
implicitSize: root.width
shape: MaterialShape.Cookie12Sided
color: Qt.alpha(root.fallbackColour, 1)
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
grade: 200
text: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0
animate: true
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: image.status === Image.Loading ? 1 : 0
sourceComponent: LoadingIndicator {
implicitSize: root.width * 0.3
color: Colours.palette.m3primaryContainer
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Image {
id: image
anchors.fill: parent
source: Players.getArtUrl(Players.active)
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
layer.enabled: true
layer.effect: Mask {
maskSource: shapeWrapper
}
retainWhileLoading: true
opacity: 0
onStatusChanged: {
if (!opacityInAnim.running && image.status === Image.Ready) {
opacityInAnim.type = root.hadPrevious ? Anim.DefaultEffects : Anim.StandardLarge;
opacityInAnim.start();
}
}
Anim on opacity {
id: opacityInAnim
running: false
to: 1
type: Anim.DefaultEffects
}
Behavior on source {
SequentialAnimation {
Anim {
target: image
property: "opacity"
to: 0
type: Anim.FastEffects
}
PropertyAction {
target: root
property: "hadPrevious"
value: image.source
}
PropertyAction {}
ScriptAction {
script: {
if (!opacityInAnim.running && image.status === Image.Ready) {
opacityInAnim.type = root.hadPrevious ? Anim.DefaultEffects : Anim.StandardLarge;
opacityInAnim.start();
}
}
}
}
}
}
}

View file

@ -1,14 +1,11 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import Quickshell
import M3Shapes
import Caelestia.Config import Caelestia.Config
import Caelestia.Services import Caelestia.Services
import qs.components import qs.components
import qs.components.controls import qs.components.controls
import qs.components.effects import qs.components.widgets
import qs.services import qs.services
import qs.utils import qs.utils
@ -21,8 +18,6 @@ Item {
} }
readonly property real arcCoverGap: Tokens.spacing.extraSmall readonly property real arcCoverGap: Tokens.spacing.extraSmall
property bool coverHadPrevious
property color coverFallbackColour: Colours.layer(Colours.palette.m3surfaceContainerHighest, 2)
anchors.top: parent.top anchors.top: parent.top
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -34,10 +29,6 @@ Item {
} }
} }
Behavior on coverFallbackColour {
CAnim {}
}
Timer { Timer {
running: Players.active?.isPlaying ?? false running: Players.active?.isPlaying ?? false
interval: GlobalConfig.dashboard.mediaUpdateInterval interval: GlobalConfig.dashboard.mediaUpdateInterval
@ -68,7 +59,7 @@ Item {
wavePaused: !Players.active?.isPlaying wavePaused: !Players.active?.isPlaying
} }
Item { CoverArt {
id: cover id: cover
anchors.top: parent.top anchors.top: parent.top
@ -76,143 +67,6 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Tokens.padding.medium + root.arcCoverGap + prog.thickness anchors.margins: Tokens.padding.medium + root.arcCoverGap + prog.thickness
implicitHeight: width implicitHeight: width
// 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: coverShapeWrapper
anchors.fill: parent
layer.enabled: true
opacity: root.coverFallbackColour.a
MaterialShape {
id: coverShape
implicitSize: cover.width
shape: MaterialShape.Cookie12Sided
color: Qt.alpha(root.coverFallbackColour, 1)
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
grade: 200
text: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.35) || 1).build()
opacity: image.status === Image.Null || image.status === Image.Error ? 1 : 0
animate: true
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Loader {
anchors.centerIn: parent
asynchronous: true
active: opacity > 0
opacity: image.status === Image.Loading ? 1 : 0
sourceComponent: LoadingIndicator {
implicitSize: cover.width * 0.3
color: Colours.palette.m3primaryContainer
}
Behavior on opacity {
Anim {
type: Anim.DefaultEffects
}
}
}
Image {
id: image
anchors.fill: parent
source: Players.getArtUrl(Players.active)
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
layer.enabled: true
layer.effect: Mask {
maskSource: coverShapeWrapper
}
retainWhileLoading: true
opacity: 0
onStatusChanged: {
if (!opacityInAnim.running && image.status === Image.Ready) {
opacityInAnim.type = root.coverHadPrevious ? Anim.DefaultEffects : Anim.StandardLarge;
opacityInAnim.start();
}
}
Anim on opacity {
id: opacityInAnim
running: false
to: 1
type: Anim.DefaultEffects
}
Behavior on source {
SequentialAnimation {
Anim {
target: image
property: "opacity"
to: 0
type: Anim.FastEffects
}
PropertyAction {
target: root
property: "coverHadPrevious"
value: image.source
}
PropertyAction {}
ScriptAction {
script: {
if (!opacityInAnim.running && image.status === Image.Ready) {
opacityInAnim.type = root.coverHadPrevious ? Anim.DefaultEffects : Anim.StandardLarge;
opacityInAnim.start();
}
}
}
}
}
}
} }
StyledText { StyledText {