|
|
![]() |
![]() |
My goal is to make it possible for the programmer to specify the required behavior and still be able to actually understand what his code is doing.
Animations are fundamentally updates. They assign values to properties (or invoke processes that do such) at certain time instants.
The traditional animation loop
while (animationIsRunning) {
// update stuff
wait(interval);
}
is easy to understand, but it has several drawbacks:
FX provides a time iterated for loop which (I think) is as easy to understand but overcomes these limitations:
println("before the animation");
for (i in [1..10] dur 10000) {
// update stuff
}
println("after the animation");
The above loop describes a discrete animation which executes every second for 10 seconds. In addition, the loop body is always executed in the current thread. However the loop still blocks until all 10 iterations are complete. Currently, this is implemented with the Event Queue Hack, but proper support for continuations could be provided via code generation by the FX compiler. Since the loop blocks, it can be intuitively incorporated into a larger sequential process, e.g;
Rect {
fill: green
height: 50
width: 50
var x = 0
var y = 0
x: bind x
y: bind y
onMouseClicked: operation(e) {
for (i in [0..100] dur 1000) {
x = i;
}
for (i in [0..100] dur 1000 {
y = i;
}
}
}
The above moves the rectangle from (0, 0) to (100, 0) over 1 second, and then moves it from (100, 0) to (100, 100) over 1 second, when you click the mouse on the rectangle.
Now, sometimes we want animations to run in parallel. For this case, a nearly identical construct is provided, namely the update trigger:
trigger on (i = [1..10] dur 10000) {
// update stuff
}
println("after the trigger, but the animation is still running");
In this case the body of the trigger is executed 10 times, once per second. However, unlike the for loop, the code after the trigger executes in parallel with the trigger's body. The body of the trigger is still executed in the same thread, however, when the timer triggers it:
Rect {
fill: green
height: 50
width: 50
var x = 0
var y = 0
x: bind x
y: bind y
onMouseClicked: operation(e) {
trigger on (i = [0..100] dur 1000) {
x = i;
}
trigger on (i = [0..100] dur 1000) {
y = i;
}
}
}
In this example, the animations of x and y occur in parallel and the the rectangle moves diagonally from (0, 0) to (100, 100) over 1 second.
In the case of discrete animations such as these the "frame rate" for the loop body is duration/(sizeof input-1). For cases where an animation is conceptually continuous, a better approach is often to use a predefined (high) frame rate and interpolate between the values for each frame using some interpolation function. The syntax is open to discussion but here's how that works right now:
Rect {
fill: green
height: 50
width: 50
var x = 0
var y = 0
x: bind x
y: bind y
onMouseClicked: operation(e) {
trigger on (i = [0, 100] dur 1000 motion LINEAR) {
x = i;
}
trigger on (i = [0, 100] dur 1000 motion LINEAR) {
y = i;
}
}
}
LINEAR is a predefined function that performs linear interpolation (you can plug in your own instead). The interpolation function is of the form
function<T> (inputValues:T*, elapsedTimeAsUnitInterval: Number): T
As a shorthand for animating individual properties a form of the the assignment statement is also provided:
x = [0, 100] dur 1000 motion LINEAR; y = [0, 100] dur 1000 motion LINEAR;
which is equivalent to the two triggers above in onMouseClicked().
Finally, for the case where the for loop actually doesn't do anything:
for (i in [0, 1] dur 1000) {
// don't do anything
}
we have a shorthand called "wait"
wait(1000);
Below are a few test cases for these constructs:
| Standalone |
| FXPad |
| Standalone |
| FXPad |
| FXPad |