Skip to content

Instantly share code, notes, and snippets.

@paulmasri
Last active February 19, 2018 09:33
Show Gist options
  • Save paulmasri/a2144bc62ff4c3001abf27869713304f to your computer and use it in GitHub Desktop.
Save paulmasri/a2144bc62ff4c3001abf27869713304f to your computer and use it in GitHub Desktop.
QML sync animations problem
/*
Problem: I have two objects that have animations. I want the animations to be synchronised.
However one of the animations depends on an asynchronous function `grabToImage()`
Below are 3 versions of the script.
The first one is out of sync.
The second one is in-sync but is a horribly messy solution. I posed the question: Is there a neater way to achieve this?
The third one uses promises, using Ben Lau's QuickPromise
VERSION 1: out of sync
----------------------
In this fictional example when you click on root:
1. action() is called
2. action() changes m
3. onMChanged calls myBeta.doTheThing()
4. myBeta.doTheThing() initiates grabToImage() which runs in a separate thread
5. action() changes n
6. myAlpha.height changes because it's dependent on n, but there's a Behaviour on height so this starts animating
7. A little while later grabToImage() completes and the callback kicks off whizzyAnimation
*/
Item {
id: root
property int m
property int n
onMChanged: myBeta.doTheThing(m)
function action(a, b) {
m = a
n = b
}
Alpha {
id: myAlpha
height: Math.log(root.n)
Behavior on height {
NumberAnimation { duration: 1000 }
}
}
Beta {
id: myBeta
function doTheThing(m) {
grabToImage(function(result) {
theImage.source = result.url
whizzyAnimation.to = m
whizzyAnimation.start()
})
}
}
Image {
id: theImage
property real p
PropertyAnimation {
id: whizzyAnimation
target: theImage
property: "p"
to: 7
duration: 1000
}
}
MouseArea {
anchors.fill: parent
onClicked: root.action(mouseX, mouseY)
}
}
/*
VERSION 2: in-sync but sooooo ugly
----------------------------------
In this fictional example when you click on root:
1. action() is called
2. action() calls myBeta.doTheThing() passing through both arguments
4. myBeta.doTheThing() initiates grabToImage() which runs in a separate thread
5. A little while later grabToImage() completes and the callback kicks off whizzyAnimation
6. Then the callback calls actionPromise() passing through those original arguments
7. actionPromise() changes n
8. myAlpha.height changes because it's dependent on n, but there's a Behaviour on height so this starts animating
*/
Item {
id: root
property int m
property int n
function action(a, b) {
myBeta.doTheThing(a, b)
}
function actionPromise(a, b)
m = a
n = b
}
Alpha {
id: myAlpha
height: Math.log(root.n)
Behavior on height {
NumberAnimation { duration: 1000 }
}
}
Beta {
id: myBeta
function doTheThing(a, b) {
grabToImage(function(result) {
theImage.source = result.url
whizzyAnimation.to = a
whizzyAnimation.start()
root.actionPromise(a, b)
})
}
}
Image {
id: theImage
property real p
PropertyAnimation {
id: whizzyAnimation
target: theImage
property: "p"
to: 7
duration: 1000
}
}
MouseArea {
anchors.fill: parent
onClicked: root.action(mouseX, mouseY)
}
}
/*
VERSION 3: in-sync and neat
---------------------------
This has 2 chained promises.
The inner promise handles grabToImage() and resolves upon callback.
The outer promise handles myBeta.doTheThing() such that it resolves on completion and is self-contained (i.e. it doesn't reference the caller).
In this fictional example when you click on root:
1. action() is called
2. action() calls myBeta.doTheThing() passing through both arguments
4. myBeta.doTheThing() calls grabItemToImage() which wraps grabToImage() in a promise.
5. grabItemToImage initiates grabToImage() and returns a promise.
6. myBeta.doTheThing() in turn returns that promise and action() ends.
7. A little while later grabToImage() completes and the callback resolves the inner promise passing the image as the result.
8. myBeta.doTheThing() does the .then clause, assigning the image and initiating whizzyAnimation.
9. Finally myBeta.doTheThing() returns a promise which it immediately resolves passing the original two arguments as the result, in an array.
10. This causes action() to do the .then clause, which extracts the two original arguments and completes its processing. This includes changing n.
11. myAlpha.height changes because it's dependent on n, but there's a Behaviour on height so this starts animating. Because steps 7-11 happen one after the other without any delays, both animations are in sync.
*/
import QuickPromise 1.0
Item {
id: root
property int m
property int n
function action(a, b) {
myBeta.doTheThing(a, b).then(function(result) {
var a = result[0]
var b = result[1]
m = a
n = b
})
}
function grabItemToImage(item) {
return Q.promise(function(resolve) {
item.grabToImage(function (result) {
resolve(result);
});
});
}
Alpha {
id: myAlpha
height: Math.log(root.n)
Behavior on height {
NumberAnimation { duration: 1000 }
}
}
Beta {
id: myBeta
function doTheThing(a, b) {
return grabItemToImage(myBeta).then(function(result) {
theImage.source = result.url
whizzyAnimation.to = a
whizzyAnimation.start()
return Q.promise(function(resolve)) {
return resolve([a, b])
}
})
}
}
Image {
id: theImage
property real p
PropertyAnimation {
id: whizzyAnimation
target: theImage
property: "p"
to: 7
duration: 1000
}
}
MouseArea {
anchors.fill: parent
onClicked: root.action(mouseX, mouseY)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment