/* 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: { } }