Last active
June 7, 2025 08:33
-
-
Save mitchcurtis/4f7814ad472fb1a318502243ee3657b0 to your computer and use it in GitHub Desktop.
FrameNumberAnimation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// FrameAnimation that operates on a property, like NumberAnimation, | |
// except the duration and easing can be change while it's animating. | |
import QtQuick | |
import QtQuick.Controls | |
import QtQuick.Layouts | |
ApplicationWindow { | |
id: window | |
width: 640 | |
height: 480 | |
visible: true | |
title: qsTr("progress %1 rotation %2").arg(frameAnimation.progress.toFixed(2)).arg(rect.rotation.toFixed(2)) | |
ColumnLayout { | |
RowLayout { | |
Label { | |
text: "Speed" | |
} | |
Slider { | |
id: speedSlider | |
from: 0.1 | |
value: 1 | |
to: 2 | |
stepSize: 0.1 | |
} | |
Label { | |
text: speedSlider.value.toFixed(2) | |
} | |
Button { | |
text: "Restart" | |
onClicked: frameAnimation.restart() | |
} | |
} | |
RowLayout { | |
Label { | |
text: "Loops" | |
} | |
Slider { | |
id: loopsSlider | |
from: 1 | |
value: 1 | |
to: 3 | |
stepSize: 1 | |
} | |
CheckBox { | |
id: infiniteLoopsCheckBox | |
text: "Infinite" | |
} | |
Label { | |
text: frameAnimation.loops | |
} | |
} | |
ButtonGroup { | |
id: easingGroup | |
buttons: easingLayout.children | |
} | |
RowLayout { | |
id: easingLayout | |
RadioButton { | |
text: "Linear" | |
checked: true | |
function easingFunction(t) { | |
return t | |
} | |
} | |
RadioButton { | |
text: "InQuart" | |
function easingFunction(t) { | |
return t * t | |
} | |
} | |
RadioButton { | |
text: "EaseInQuart" | |
function easingFunction(t) { | |
t*=2.0; | |
if(t < 1) { | |
return 0.5*t*t*t; | |
} else { | |
t -= 2.0; | |
return 0.5*(t*t*t + 2); | |
} | |
} | |
} | |
} | |
Label { | |
text: "Duration: " + frameAnimation.duration.toFixed(2) | |
} | |
Label { | |
text: "Distance each second: " + frameAnimation.progressEachSecond.toFixed(2) | |
} | |
} | |
Rectangle { | |
id: rect | |
x: (parent.width - width) / 2 | |
y: (parent.height - height) / 2 | |
width: 100 | |
height: 100 | |
color: "red" | |
Rectangle { | |
width: 4 | |
height: 12 | |
anchors.horizontalCenter: parent.horizontalCenter | |
} | |
} | |
component FrameNumberAnimation: FrameAnimation { | |
id: root | |
required property QtObject target | |
required property string property | |
property real duration | |
property var easingFunction | |
property real from: 0 | |
property real to: 0 | |
property real progressEachSecond: 1 / durationInSeconds | |
readonly property real durationInSeconds: duration / 1000 | |
property real progress: 0 | |
property int loops: 1 | |
property int __loopsCompleted: 0 | |
readonly property Binding binding: Binding { | |
target: root.target | |
property: root.property | |
value: root.from + root.to * root.easingFunction(root.progress) | |
} | |
signal finished | |
function finish() { | |
progress = 0 | |
__loopsCompleted = 0 | |
stop() | |
finished() | |
} | |
onRunningChanged: { | |
if (running) { | |
progress = 0 | |
reset() | |
} else { | |
__loopsCompleted = 0 | |
} | |
} | |
onTriggered: { | |
progress += progressEachSecond * frameTime | |
if (progress >= 1.0) { | |
progress = 0 | |
if (loops !== -1 && ++__loopsCompleted >= loops) | |
finish() | |
} | |
} | |
} | |
Component.onCompleted: frameAnimation.start() | |
FrameNumberAnimation { | |
id: frameAnimation | |
duration: 4000 * (1 / speedSlider.value) | |
to: 360 | |
target: rect | |
property: "rotation" | |
easingFunction: easingGroup.checkedButton.easingFunction | |
loops: infiniteLoopsCheckBox.checked ? -1 : loopsSlider.value | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment