362 lines
10 KiB
QML
362 lines
10 KiB
QML
/*
|
|
SPDX-FileCopyrightText: 2024 User
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
import QtQuick
|
|
import QtQuick.Effects
|
|
import org.kde.plasma.components as PlasmaComponents3
|
|
import org.kde.plasma.core as PlasmaCore
|
|
import org.kde.kirigami as Kirigami
|
|
|
|
Item {
|
|
id: root
|
|
|
|
// Properties that can be set from the outside
|
|
property int animationType: 0 // 0=BusyIndicator, 1=PulsingIcon, 2=BouncingIcon, etc.
|
|
property int animationDuration: 1200
|
|
property real animationIntensity: 0.3
|
|
property bool active: false
|
|
property var iconSource: null
|
|
|
|
// Animation properties that external icon can bind to
|
|
property real iconScale: 1.0
|
|
property real iconRotation: 0
|
|
property real iconOpacity: 1.0
|
|
property real iconOffsetY: 0 // For bouncing
|
|
|
|
// Animation type constants for clarity
|
|
readonly property int typeBusyIndicator: 0
|
|
readonly property int typePulsingIcon: 1
|
|
readonly property int typeBouncingIcon: 2
|
|
readonly property int typeRotatingIcon: 3
|
|
readonly property int typeScalingIcon: 4
|
|
readonly property int typeFadingIcon: 5
|
|
readonly property int typeGlowEffect: 6
|
|
|
|
onActiveChanged: {
|
|
if (active) {
|
|
startAnimation();
|
|
} else {
|
|
stopAnimation();
|
|
}
|
|
}
|
|
|
|
onAnimationTypeChanged: {
|
|
if (active) {
|
|
stopAnimation();
|
|
startAnimation();
|
|
}
|
|
}
|
|
|
|
function startAnimation() {
|
|
switch(animationType) {
|
|
case typeBusyIndicator:
|
|
// Show busy indicator on top of icon (don't hide the icon)
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
break;
|
|
case typePulsingIcon:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
pulseAnimation.restart();
|
|
break;
|
|
case typeBouncingIcon:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
bounceAnimation.restart();
|
|
break;
|
|
case typeRotatingIcon:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
rotateAnimation.restart();
|
|
break;
|
|
case typeScalingIcon:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
scaleAnimation.restart();
|
|
break;
|
|
case typeFadingIcon:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
fadeAnimation.restart();
|
|
break;
|
|
case typeGlowEffect:
|
|
// Reset to normal first, then start animation
|
|
iconScale = 1.0;
|
|
iconOpacity = 1.0;
|
|
iconRotation = 0;
|
|
iconOffsetY = 0;
|
|
glowAnimation.restart();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function stopAnimation() {
|
|
pulseAnimation.stop();
|
|
bounceAnimation.stop();
|
|
rotateAnimation.stop();
|
|
scaleAnimation.stop();
|
|
fadeAnimation.stop();
|
|
glowAnimation.stop();
|
|
|
|
// Reset all animation properties
|
|
iconScale = 1.0;
|
|
iconRotation = 0;
|
|
iconOpacity = 1.0;
|
|
iconOffsetY = 0;
|
|
glowEffect.opacity = 0;
|
|
}
|
|
|
|
// Glow effect - Only additional visual element needed
|
|
Item {
|
|
id: glowEffect
|
|
anchors.centerIn: parent
|
|
width: parent.width * 1.2
|
|
height: parent.height * 1.2
|
|
visible: animationType === typeGlowEffect && active
|
|
opacity: 0
|
|
z: 5
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: parent.width
|
|
height: parent.height
|
|
radius: width / 2
|
|
color: "transparent"
|
|
border.color: Kirigami.Theme.highlightColor
|
|
border.width: 2
|
|
}
|
|
|
|
Repeater {
|
|
model: 2
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: parent.width + (index * 4)
|
|
height: parent.height + (index * 4)
|
|
radius: width / 2
|
|
color: "transparent"
|
|
border.color: Kirigami.Theme.highlightColor
|
|
border.width: 1
|
|
opacity: 0.4 / (index + 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ANIMATION DEFINITIONS - These modify the exported properties
|
|
|
|
// 2. Pulsing Icon Animation - Smooth sine wave
|
|
SequentialAnimation {
|
|
id: pulseAnimation
|
|
loops: Animation.Infinite
|
|
running: false
|
|
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconScale"
|
|
from: 1.0
|
|
to: 1.0 + (animationIntensity * 0.6)
|
|
duration: animationDuration / 2
|
|
easing.type: Easing.InOutSine
|
|
}
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconScale"
|
|
from: 1.0 + (animationIntensity * 0.6)
|
|
to: 1.0
|
|
duration: animationDuration / 2
|
|
easing.type: Easing.InOutSine
|
|
}
|
|
}
|
|
|
|
// 3. Bouncing Icon Animation - REALISTIC PHYSICS with multiple bounces
|
|
SequentialAnimation {
|
|
id: bounceAnimation
|
|
loops: Animation.Infinite
|
|
running: false
|
|
|
|
// First big bounce
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: 0
|
|
to: -(30 * animationIntensity) // Higher initial bounce
|
|
duration: animationDuration / 6
|
|
easing.type: Easing.OutQuad // Deceleration going up
|
|
}
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: -(30 * animationIntensity)
|
|
to: 0
|
|
duration: animationDuration / 6
|
|
easing.type: Easing.InQuad // Acceleration going down
|
|
}
|
|
|
|
// Second smaller bounce
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: 0
|
|
to: -(18 * animationIntensity) // 60% of original height
|
|
duration: animationDuration / 8
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: -(18 * animationIntensity)
|
|
to: 0
|
|
duration: animationDuration / 8
|
|
easing.type: Easing.InQuad
|
|
}
|
|
|
|
// Third tiny bounce
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: 0
|
|
to: -(8 * animationIntensity) // 40% of second bounce
|
|
duration: animationDuration / 10
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOffsetY"
|
|
from: -(8 * animationIntensity)
|
|
to: 0
|
|
duration: animationDuration / 10
|
|
easing.type: Easing.InQuad
|
|
}
|
|
|
|
// Rest period
|
|
PauseAnimation { duration: animationDuration / 3 }
|
|
}
|
|
|
|
// 4. Rotating Icon Animation
|
|
RotationAnimation {
|
|
id: rotateAnimation
|
|
target: root
|
|
property: "iconRotation"
|
|
from: 0
|
|
to: 360
|
|
duration: animationDuration
|
|
loops: Animation.Infinite
|
|
running: false
|
|
easing.type: Easing.Linear
|
|
}
|
|
|
|
// 5. Scaling Icon Animation - Complex bounce-scale with elastic settle
|
|
SequentialAnimation {
|
|
id: scaleAnimation
|
|
loops: Animation.Infinite
|
|
running: false
|
|
|
|
// Quick shrink
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconScale"
|
|
from: 1.0
|
|
to: 1.0 - (animationIntensity * 0.4)
|
|
duration: animationDuration / 6
|
|
easing.type: Easing.InQuad
|
|
}
|
|
// Big bounce grow
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconScale"
|
|
from: 1.0 - (animationIntensity * 0.4)
|
|
to: 1.0 + (animationIntensity * 0.8)
|
|
duration: animationDuration / 3
|
|
easing.type: Easing.OutBack
|
|
easing.overshoot: 2.0
|
|
}
|
|
// Settle back
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconScale"
|
|
from: 1.0 + (animationIntensity * 0.8)
|
|
to: 1.0
|
|
duration: animationDuration / 2
|
|
easing.type: Easing.OutElastic
|
|
easing.amplitude: 1.5
|
|
}
|
|
}
|
|
|
|
// 6. Fading Icon Animation - Dramatic fade
|
|
SequentialAnimation {
|
|
id: fadeAnimation
|
|
loops: Animation.Infinite
|
|
running: false
|
|
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOpacity"
|
|
from: 1.0
|
|
to: 0.1
|
|
duration: animationDuration / 2
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
NumberAnimation {
|
|
target: root
|
|
property: "iconOpacity"
|
|
from: 0.1
|
|
to: 1.0
|
|
duration: animationDuration / 2
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
}
|
|
|
|
// 7. Glow Effect Animation
|
|
SequentialAnimation {
|
|
id: glowAnimation
|
|
loops: Animation.Infinite
|
|
running: false
|
|
|
|
NumberAnimation {
|
|
target: glowEffect
|
|
property: "opacity"
|
|
from: 0
|
|
to: 0.8
|
|
duration: animationDuration / 3
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
NumberAnimation {
|
|
target: glowEffect
|
|
property: "opacity"
|
|
from: 0.8
|
|
to: 0.3
|
|
duration: animationDuration / 3
|
|
easing.type: Easing.InOutQuad
|
|
}
|
|
NumberAnimation {
|
|
target: glowEffect
|
|
property: "opacity"
|
|
from: 0.3
|
|
to: 0
|
|
duration: animationDuration / 3
|
|
easing.type: Easing.InQuad
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
}
|
|
} |