Last active
February 19, 2018 09:33
-
-
Save paulmasri/a2144bc62ff4c3001abf27869713304f to your computer and use it in GitHub Desktop.
QML sync animations problem
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
/* | |
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