feat: add anim for dash media cover

This commit is contained in:
2 * r + 2 * t 2026-04-27 18:03:56 +10:00
parent 1eac4e4bcb
commit 9cd9d7facd
2 changed files with 151 additions and 2 deletions

View file

@ -0,0 +1,79 @@
import QtQuick
import M3Shapes
import Caelestia.Config
import qs.components
import qs.services
MaterialShape {
id: root
property list<int> shapes: {
if (containsIcon)
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
return [MaterialShape.SoftBurst, MaterialShape.Cookie9Sided, MaterialShape.Pentagon, MaterialShape.Pill, MaterialShape.Sunny, MaterialShape.Cookie4Sided, MaterialShape.Oval];
}
property int shapeIndex
property real cRotation
property real lRotation
property bool containsIcon
property bool animated: true
property int morphAnimRotation: 60
property alias morphAnimDelay: morphAnimDelay.duration
property alias rotateAnimDuration: rotateAnim.duration
readonly property easingCurve scaleCurve: [0.00, 0.56, 0.55, 1.29, 1, 1]
implicitSize: 38
rotation: cRotation + lRotation
shape: shapes[shapeIndex]
color: Colours.palette.m3primary
animationDuration: Tokens.anim.durations.expressiveSlowSpatial
animationEasing: Tokens.anim.expressiveSlowSpatial
scale: {
const t = scaleCurve.valueForProgress(morphProgress);
return 1 + 0.15 * Math.sin(t * Math.PI) / 2;
}
SequentialAnimation {
running: root.animated
loops: Animation.Infinite
PropertyAction {
target: root
property: "shapeIndex"
value: (root.shapeIndex + 1) % root.shapes.length
}
RotationAnimation {
target: root
property: "lRotation"
to: (root.lRotation + root.morphAnimRotation) % 360
duration: Tokens.anim.durations.expressiveSlowSpatial
easing: Tokens.anim.expressiveSlowSpatial
direction: RotationAnimation.Shortest
}
PauseAnimation {
id: morphAnimDelay
duration: 20
}
}
RotationAnimation on cRotation {
id: rotateAnim
running: root.animated
from: 0
to: 360
easing.type: Easing.Linear
loops: Animation.Infinite
duration: 10000
}
Behavior on color {
CAnim {}
}
}

View file

@ -20,6 +20,7 @@ Item {
}
readonly property real arcCoverGap: Tokens.spacing.extraSmall
property bool coverHadPrevious
anchors.top: parent.top
anchors.bottom: parent.bottom
@ -110,9 +111,35 @@ Item {
anchors.centerIn: parent
grade: 200
text: "art_track"
text: image.status === Image.Error ? "broken_image" : "art_track"
color: Colours.palette.m3onSurfaceVariant
fontStyle: Tokens.font.icon.size((parent.width * 0.4) || 1).build()
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 {
@ -130,6 +157,49 @@ Item {
layer.effect: Mask {
maskSource: coverShape
}
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();
}
}
}
}
}
}
}