Sample Code

Below you will find a number of examples of how you can use gewel to create simple animations. Each is self-contained and designed to illustrate just one of two specific concepts. Each code sample is followed by a gif image that illustrates the result of running it.

You can combine the concepts illustrated here to create all kinds of different animations of your own.

Each of the code samples is mostly made up of scripting phase code. (See The Scripting Phase.) We can think of scripting phase code as code that writes a script describing what various objects in the animation should do at various times.

The lines in the scripting phase that are most critical to the feature the code sample is attempting to illustrate are highlighted.

The final two lines of each code sample are rendering phase code. (See The Rendering Phase.) In order to keep the samples simple and portable, and to produce the animations shown in this document, we simply write each Scene we produce out to a .gif file.

In many cases, you will want to use a Mp4Recorder instead of a GifRecorder in the rendering phase. When you are developing and interactively debugging, you are likely to want to use a Player. When you use a Player, you get interactive controls and you don’t have to wait for the entire scene to render before you start playing it.

If you would like to switch to using a player, replace the rendering phase lines:

recorder = GifRecorder('solar_system.gif')
recorder.record(scene)

with

from gewel.player import Player

player = Player(scene)
player.mainloop()

in any of the code samples below. Instead of rendering and saving the scene to a .gif file, it will pop up an interactive player that looks like this:

A player showing a solar-system image.

A player.

Note

Each of the code samples can be copied by moving to the upper right corner of the code sample and clicking on the copy to clipboard icon that appears.

Fading

Using fade_to()

Here is an example of how fade_to() works. We will create a MarkerDot, which is a class derived from Drawable and give it an initial alpha of 0.0, meaning it is entirely transparent. We will then fade it in to full opacity (alpha = 1.0) and then fade it back to 0.0 so it is fully transparent again. We will then wrap it in a Scene and save that scene to a .gif file.

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerDot, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9drawable = MarkerDot(x=320, y=240, width=240, color=ORANGE, alpha=0.0)
10
11drawable.fade_to(1.0, duration=2.0)
12drawable.fade_to(0.0, duration=2.0)
13
14scene = Scene([background, drawable])
15
16# Rendering phase:
17
18recorder = GifRecorder('fade_to.gif')
19recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of fade_to().

Moving and Rotating

Using move_to()

Here is an example of how move_to() works. We will create a XYDrawable and move it, then wrap it in a Scene and save that scene to a .gif file.

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerUpArrow, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9drawable = MarkerUpArrow(x=32, y=240, height=64, color=ORANGE, line_width=3)
10
11drawable.move_to(608, 240, duration=2.0)
12
13scene = Scene([background, drawable])
14
15# Rendering phase:
16
17recorder = GifRecorder('move_to.gif')
18recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of move_to().

Using rotate_to()

Here is an example of how rotate_to() works. We will create a XYDrawable and rotate it, then wrap it in a Scene and save that scene to a .gif file.

 1import numpy as np
 2
 3from gewel.color import ORANGE
 4from gewel.draw import MarkerUpArrow, Background, Scene
 5from gewel.record import GifRecorder
 6
 7# Scripting phase:
 8
 9background = Background()
10
11drawable = MarkerUpArrow(x=320, y=240, height=64, color=ORANGE, line_width=3)
12
13drawable.rotate_to(2 * np.pi, duration=2.0)
14
15scene = Scene([background, drawable])
16
17# Rendering phase:
18
19recorder = GifRecorder('rotate_to.gif')
20recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of rotate_to() (or rotate_to() as in Using rotate_to_degrees() below).

Using rotate_to_degrees()

In Using rotate_to() we used rotate_to() to rotate a XYDrawable. If you prefer degrees to radians, you can use rotate_to_degrees() instead of rotate_to(). All you have to do is change line 13 in the code above to line 11 in the code below and remove the now unneeded import numpy as np at the top to produce the following code:

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerUpArrow, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9drawable = MarkerUpArrow(x=320, y=240, height=64, color=ORANGE, line_width=3)
10
11drawable.rotate_to_degrees(360, duration=2.0)
12
13scene = Scene([background, drawable])
14
15# Rendering phase:
16
17recorder = GifRecorder('rotate_to_degrees.gif')
18recorder.record(scene)

That code produces the exact same gif as the one in Using rotate_to().

Markers

In the examples above, we created objects of the classes MarkerDot and MarkerUpArrow, but we did not properly introduce the classes. Both are derived from the abstract base class MarkerDot, which has other derived classes such as MarkerX, MarkerO, and MarkerPlus. These are all illustrated in the following code sample:

 1from gewel.color import BLUE, BLACK, ORANGE, PURPLE, RED
 2from gewel.draw import Background, Scene, MarkerX, MarkerO, \
 3    MarkerPlus, MarkerDot, MarkerUpArrow
 4from gewel.record import GifRecorder
 5
 6# Scripting phase:
 7
 8background = Background()
 9
10mp = MarkerPlus(80, 120, line_width=5, width=100, color=BLUE)
11mo = MarkerO(200, 360, line_width=5, width=100, color=ORANGE)
12mx = MarkerX(320, 120, line_width=5, width=100, color=BLACK)
13md = MarkerDot(440, 360, line_width=5, width=100, color=RED)
14ma = MarkerUpArrow(560, 120, line_width=5, width=50, height=100, color=PURPLE)
15
16mp.rotate_to_degrees(360, duration=3.0)
17ma.rotate_to_degrees(-360, duration=3.0)
18
19mo.move_to(200, 120, duration=1.5)
20md.move_to(440, 120, duration=1.5)
21
22mo.move_to(200, 360, duration=1.5)
23md.move_to(440, 360, duration=1.5)
24
25scene = Scene([background, mx, mp, ma, mo, md])
26
27# Rendering phase:
28
29recorder = GifRecorder('markers.gif')
30recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

Various markers in action.

PNGs

Markers are good enough for sample animations that are just trying to teach someone about APIs, but they are rarely sufficient for real animations. Often you will want to import .png files created elsewhere into your animations. PngDrawable is the class that lets you do this.

Here is an example of how to construct a PngDrawable and rotate it 360 degrees, then wrap it in a Scene and save that scene to a .gif file. Note that if you want to run this code sample you will also need to download donut.png and put it in the same directory you run the sample code from.

 1import numpy as np
 2
 3from gewel.draw import PngDrawable, Background, Scene
 4from gewel.record import GifRecorder
 5
 6# Scripting phase:
 7
 8background = Background()
 9
10drawable = PngDrawable('donut.png', x=320, y=240)
11
12drawable.rotate_to(2 * np.pi, duration=2.0)
13
14scene = Scene([background, drawable])
15
16# Rendering phase:
17
18recorder = GifRecorder('png_drawable.gif')
19recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

A spinning donut.

Non-Linear Movement and Scaffolding

move_to() is a great way to create movement in your animations. But it is limited to linear movement. There are two more advanced methods we can use: quadratic_move_to(), which follows a quadratic path, and bezier_move_to(), which follows a cubic path.

When working with these higher degree polynomial paths, it is sometimes useful to have a visual guide indicating the desired path. We call these visual guides scaffolding. All of the methods that move objects have and optional scaffolding argument whose default value is False. If it is set to true, scaffolding is generated and returned as a Drawable.

We illustrate both quadratic and cubic paths, along with the use of scaffolding, below.

Using quadratic_move_to()

Here is an example of how quadratic_move_to() works. We will create a XYDrawable and move it, then wrap it in a Scene and save that scene to a .gif file. In order to make it more clear what is happening, we will also use the scaffolding feature to show where the current position, control point, and final position are, as well as the quadratic path we expect the object to follow.

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerUpArrow, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9drawable = MarkerUpArrow(x=32, y=440, height=64, color=ORANGE, line_width=3)
10
11scaffold = drawable.quadratic_move_to(320, 40, 608, 440, duration=2.0, scaffold=True)
12
13scene = Scene([background, drawable, scaffold])
14
15# Rendering phase:
16
17recorder = GifRecorder('quad_move_to.gif')
18recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of quadratic_move_to().

The path that the object follows is defined by its initial location \((x_0, y_0)\), a control point \((x_1, y_1)\), and it’s final location \((x_2, y_2)\). The initial location is the location of the object before the method is called. The control point and final location are specified as parameters.

Notice that the current and final positions as well as the control point and the path of motion are rendered as scaffolding.

Using bezier_move_to()

Here is an example of how bezier_move_to() works. We will create a XYDrawable and move it, then wrap it in a Scene and save that scene to a .gif file. In order to make it more clear what is happening, we will also use the scaffolding feature to show where the current position, two control points, and final position are, as well as the path we expect the object to follow.

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerUpArrow, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9drawable = MarkerUpArrow(x=32, y=440, height=64, color=ORANGE, line_width=3)
10
11scaffold = drawable.bezier_move_to(
12    x1=320, y1=40, x2=580, y2=100, x3=608, y3=440,
13    duration=2.0, scaffold=True
14)
15
16scene = Scene([background, drawable, scaffold])
17
18# Rendering phase:
19
20recorder = GifRecorder('bezier_move_to.gif')
21recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of bezier_move_to().

The path that the object follows is defined by its initial location \((x_0, y_0)\), two control points \((x_1, y_1)\) and \((x_2, y_2)\), and it’s final location \((x_3, y_3)\). The initial location is the location of the object before the method is called. The control points and final location are specified as parameters.

Notice that the current and final positions as well as the control points and the path of motion are rendered as scaffolding.

Update Time

Scripting-time methods like move_to(), rotate_to(), and fade_to() take an optional parameter update_time that has a default value of True. If this parameter is True then the object’s notion of what time it should start the next action (known as the next-action time) is increased by the duration parameter. Setting update_time=False causes this not to happen. This enables the object to take several actions simultaneously.

For example, suppose we want an object to move, but also rotate at the same time. We can call move_to() with update_time = False, then call rotate_to(). Since the next-action time was not updated during the move, the rotation starts when the move started. In addition to the update_time parameter, we can use the wait() method to update the time but not create any additional motion.

The following example demonstrates several how we can create a variety of effects by combining these techniques. Note that we also add a TimeClock to the scene to keep track of time. This class is normally added for visual debugging during scene development and removed before final rendering. Watch the clock and the motion and that should help you reason about what the code is doing and why rotation and motion happen when they do.

 1from gewel.color import ORANGE
 2from gewel.draw import MarkerUpArrow, Background, Scene, TimeClock
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8clock = TimeClock(x=20, y=30, font_size=20, z=10.0)
 9
10drawable = MarkerUpArrow(x=32, y=240, height=64, color=ORANGE, line_width=3)
11
12# Rotate while moving. We do this by not updating the
13# drawable's next-action time during the move. As a
14# result, the next action, which is the rotation, starts
15# at the same time the move started. Since we did not
16# add update_time=False to the rotation, the next-action time
17# will be set to when the rotation ends, two seconds
18# into the scene.
19drawable.move_to(608, 240, duration=2.0, update_time=False)
20drawable.rotate_to_degrees(360, duration=2.0)
21
22# The next-action time is now two seconds into the scene.
23# Let's wait two seconds doing nothing. That will leave the
24# next-action time set to four seconds into the scene.
25drawable.wait(duration=2.0)
26
27# Now let's move back, again without updating the next-action
28# time.
29drawable.move_to(32, 240, duration=2.0, update_time=False)
30
31# But this time we will wait for one second while we are moving,
32# so the next-action time will be one second after the move
33# started, which will be five seconds into the scene.
34drawable.wait(duration=1.0)
35
36# And them spin back during the final second of motion.
37# This should begin at five seconds into the scene, when
38# we have moved halfway back to the starting position. It
39# should end six seconds into the scene when we are back
40# at the initial position.
41drawable.rotate_to_degrees(0, duration=1.0)
42
43# Now wait for another two seconds. This takes us to the
44# end of the scene at the eight second mark.
45drawable.wait(duration=2.0)
46
47scene = Scene([background, drawable, clock])
48
49# Rendering phase:
50
51recorder = GifRecorder('update_time.gif')
52recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of the update_time = False parameter to and move_to() and the method wait().

Motion Linking

In the examples above, we used scripting methods such as bezier_move_to(), with or without update_time=True, and wait() to script animation scenes.

In addition to these methods, there is another approach we can use, which is called motion linking. Motion linking connects one or more properties of a Drawable to that of another. It can be used to produce effects like one object following another.

Motion linking is typically done in two steps. First, we create one or more Drawable objects and script them as in the examples above. Second, we create one or more additional Drawable objects and assign one or more of their properties (typically things like their x and y location) to values from the first set of objects.

The key thing to remember is that the properties we are working with are time-varying values, meaning that they are not fixed, but rather vary over time. For example, once we have called the bezier_move_to() method on an object, both it’s x and y coordinates are time-varying. That’s what produces the motion. The value at time t1 is different than the value at t0.

When we do motion linking, we step outside the timeline that bezier_move_to(), wait_for(), and all the other methods we discussed above operate on, and assign a property of one object to match a property of another at all times. This is why we need to fully script the first object before we do motion linking.

Note that we can also create time-varying values as expressions of time-varying values and constants. For example, if x is a time-varying value then v = 2 * x + 10 is also. The value of v at any given time is twice that of x at the time plus 10.

Tracking With Motion Linking

Here is a simple example where we have one object track the x coordinate of another and the y coordinate track an expression of the y coordinate of the other.

 1from gewel.color import GREEN, ORANGE
 2from gewel.draw import MarkerX, MarkerPlus, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9# Script d0
10d0 = MarkerX(x=32, y=100, color=ORANGE, line_width=3)
11d0.move_to(608, 200, duration=2.0)
12d0.move_to(608, 100, duration=1.0)
13d0.move_to(32, 100, duration=1.5)
14
15# Link the motion of d1 to that of d0.
16# It will follow d0 but 100 pixels below.
17d1 = MarkerPlus(x=0, y=0, color=GREEN, line_width=3)
18d1.x = d0.x
19d1.y = d0.y + 100
20
21# Note that we can produce the same results by
22# setting x and y directly in the constructor.
23d1 = MarkerPlus(x=d0.x, y=d0.y + 100, color=GREEN, line_width=3)
24
25# Assemble the scene.
26scene = Scene([background, d0, d1])
27
28# Rendering phase:
29
30recorder = GifRecorder('motion_track_x.gif')
31recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from our use of motion tracking.

Motion Linking with track()

In addition to the tracking methods shown above, there is a track() method that can be used to have one object track another at a given x and y offset. Here is an example of how it is used.

 1from gewel.color import GREEN, ORANGE
 2from gewel.draw import MarkerX, MarkerPlus, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9# Script d0
10d0 = MarkerX(x=32, y=100, color=ORANGE, line_width=3)
11d0.move_to(32, 200, duration=1.0)
12d0.move_to(608, 100, duration=2.0)
13d0.move_to(32, 100, duration=2.0)
14
15# Link the motion of d1 to that of d0.
16# It will follow d0 but 100 pixels below.
17d1 = MarkerPlus(x=32, y=200, color=GREEN, line_width=3)
18d1.track(d0, 0.0, 100.0)
19
20# Assemble the scene.
21scene = Scene([background, d0, d1])
22
23# Rendering phase:
24
25recorder = GifRecorder('motion_track.gif')
26recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The output resulting from track().

A Motion Linked Solar System

We can now put together some of the motion linking approaches we just saw above, along with a new one called orbit(), to create an animated solar system. Here is the code:

 1from gewel.color import YELLOW, BLUE, GRAY
 2from gewel.draw import MarkerDot, Background, Scene, TextBox, TextVerticalPosition
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9# Sun
10sun = MarkerDot(x=320, y=240, width=100, color=YELLOW)
11sun.wait(4.0)
12sun_label = TextBox(
13    "Sun", x=370, y=190, width=100, height=16,
14    vertical_position=TextVerticalPosition.BOTTOM,
15    z=1.0
16)
17sun_label.track(sun, 50, -50)
18
19# Planet
20planet = MarkerDot(x=120, y=240, width=30, color=BLUE)
21planet.orbit(sun, 200, 200, orbit_duration=4.0)
22planet_label = TextBox(
23    "Planet", x=140, y=220, width=100, height=16,
24    vertical_position=TextVerticalPosition.BOTTOM,
25    z=1.0, font_size=10.0
26)
27planet_label.track(planet, 20, -20)
28
29# Moon
30moon = MarkerDot(x=70, y=240, width=10, color=GRAY)
31moon.orbit(planet, 50, 50, orbit_duration=1.0)
32moon_label = TextBox(
33    "Moon", x=85, y=225, width=100, height=16,
34    vertical_position=TextVerticalPosition.BOTTOM,
35    z=0.9, font_size=9.0
36)
37moon_label.track(moon, 15, -15)
38
39# Assemble the scene.
40scene = Scene([background, sun, sun_label, planet, planet_label, moon, moon_label])
41
42# Rendering phase:
43
44recorder = GifRecorder('solar_system.gif')
45recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

An animated solar system.

Notice that track() causes the labels to track at a fixed offset from the celestial bodies, even when they follow complicated paths, as moon does.

Partial Motion Linking

Sometimes we don’t want one object to completely track another, but instead we want just part of it, like an endpoint or a control point, to track another.

Here is an example, using a BezierDrawable with it’s points linked to other objects. We will create some MarkerPlus objects for the end points and some MarkerX objects for the control points. We will then animate the end points so they move up and down. Next, we will construct a BezierDrawable from the points. Finally, we will render this all in a Scene and save that scene to a .gif file.

 1from gewel.color import BLUE, ORANGE, PURPLE, RED
 2from gewel.draw import Background, BezierDrawable, Scene, MarkerPlus, MarkerX
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9point0 = MarkerPlus(40, 40, line_width=2, color=RED)
10point1 = MarkerX(80, 240, line_width=2, color=ORANGE)
11point2 = MarkerX(560, 240, line_width=2, color=PURPLE)
12point3 = MarkerPlus(600, 440, line_width=2, color=BLUE)
13
14# Make the endpoints move up and down in
15# sequence.
16
17point0.move_to(40, 440, duration=1.5)
18
19point3.wait_for(point0)
20point3.move_to(600, 40, duration=1.5)
21
22point0.wait_for(point3)
23point0.move_to(40, 40, duration=1.5)
24
25point3.wait_for(point0)
26point3.move_to(600, 440, duration=1.5)
27
28# A Bezier based on the points above, some
29# of which will be in motion.
30bezier = BezierDrawable(
31    point0.x, point0.y,
32    point1.x, point1.y,
33    point2.x, point2.y,
34    point3.x, point3.y,
35)
36
37scene = Scene([background, point0, point1, point2, point3, bezier])
38
39# Rendering phase:
40
41recorder = GifRecorder('linked_bezier.gif')
42recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

Notice that as the end points move, the shape of the Bezier curve changes accordingly. This is a simple example, but it illustrates how properties of objects that are time-varying can be linked.

Here is an another example, using a QuadraticDrawable with it’s points linked to other objects. We will create some MarkerPlus objects for the end points and a MarkerX object for the control point. We will then animate the control point so it moves up and down. Next, we will construct a QuadraticDrawable from the points. Finally, we will render this all in a Scene and save that scene to a .gif file.

 1from gewel.color import BLUE, ORANGE, RED
 2from gewel.draw import Background, QuadraticCurveDrawable, Scene, MarkerPlus, MarkerX
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9point0 = MarkerPlus(40, 40, line_width=2, color=RED)
10point1 = MarkerX(40, 440, line_width=2, color=ORANGE)
11point2 = MarkerPlus(600, 440, line_width=2, color=BLUE)
12
13# Make the control point move from corner to corner.
14
15point1.move_to(600, 40, duration=1.5)
16point1.move_to(40, 440, duration=1.5)
17
18# A quadratic curve based on the points above, some
19# of which will be in motion.
20quad = QuadraticCurveDrawable(
21    point0.x, point0.y,
22    point1.x, point1.y,
23    point2.x, point2.y,
24)
25
26scene = Scene([background, point0, point1, point2, quad])
27
28# Rendering phase:
29
30recorder = GifRecorder('linked_quadratic.gif')
31recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

Notice that as the control point moves, the shape of the quadratic curve changes accordingly. This is a simple example, but it illustrates how properties of objects that are time-varying can be linked.

Advanced Motion Linking

We can use expressions and multiple objects to create a variety of effects with motion linking. In the next example, we will use motion linking to create wheels on a wagon that rotate as the wagon moves.

 1from gewel.color import BLACK, DARK_GRAY, RED
 2from gewel.draw import Box, MarkerPlus, MarkerO, Background, Scene
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9# The wagon.
10wagon = Box(x=20, y=100, width=160, height=32, color=DARK_GRAY, fill_color=RED)
11wagon.move_to(620 - wagon.width, 100, duration=1.5)
12wagon.move_to(20, 100, duration=3.0)
13
14# Wheels move with wagon and rotate as it moves.
15wheels = []
16tires = []
17for ii in range(2):
18    wheel = MarkerPlus(
19        x=wagon.x + 10 + 140 * ii, y=wagon.y + wagon.height + 4,
20        width=32, line_width=2, color=DARK_GRAY, z=1.0
21    )
22
23    tire = MarkerO(
24        x=wheel.x, y=wheel.y,
25        width=wheel.width + 2, line_width=5, color=BLACK, z=2.0
26    )
27    wheels.append(wheel)
28    tires.append(tire)
29
30    # Rotate with linear motion of the wagon. When the
31    # wheel rotates theta radians, the x distance the
32    # wagon travels is theta * r, where r is
33    # the radius of the tire. Since we know x,
34    # we can compute theta = x / r.
35    radius = (tire.width + tire.line_width) / 2
36    wheel.theta = wagon.x / radius
37
38# Assemble the scene.
39scene = Scene([background, wagon] + wheels + tires)
40
41# Rendering phase:
42
43recorder = GifRecorder('motion_track_wheels.gif')
44recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

The wagon with linked wheels.

Notice that the wagon moves at twice as fast from left to right as it does from right to left. Since the angle of the wheels is linked to position, the wheels rotate twice as fast when the wagon is moving from left to right as they do when it moves from right to left. We never had to explicitly specify that behavior for the wheels. The fact that their angle is linked to the x position of the wagon made it happen.

If you would like to learn more about how the time-varying values (tvx) that enable motion linking work, see tvx: An Introduction.

Using a Teleprompter

Teleprompter objects are boxes of scrolling text that can be synchronized to the actions of other objects on the screen. They are typically used to synchronize voice-over dialog with other actions in the animation. A voice-over artist can then read along with them to record an audio track to go with the animation. They can also be left in the final animation as subtitles.

 1from gewel.color import ORANGE, YELLOW, Color
 2from gewel.draw import MarkerDot, Background, Scene, Teleprompter, sync
 3from gewel.record import GifRecorder
 4
 5# Scripting phase:
 6
 7background = Background()
 8
 9# Create our teleprompter.
10teleprompter = Teleprompter(
11    30, 400, 580, 70,
12    font_size=24,
13    color=YELLOW,
14    fill_color=Color(0.25, 0.25, 0.25, 0.75)
15)
16
17# Start with an invisible drawable.
18drawable = MarkerDot(x=320, y=240, width=64, color=ORANGE, alpha=0.0)
19
20# Now we will alternately add text to the teleprompter
21# and start actions we are describing in the animation.
22
23teleprompter.add_script(
24    """The orange dot is a mysterious creature. 
25    
26    Sometimes you don't even realize it is there, but
27    it just slowly fades in.
28    """
29)
30
31drawable.fade_to(1.0, duration=8.0)
32
33# Whichever one is slower should wait for the other
34# before the scene continues.
35sync((drawable, teleprompter))
36
37teleprompter.add_script(
38    """But as soon as you see it...
39    It quietly moves away.
40    """
41)
42
43drawable.move_to(800, 600, duration=6.0)
44
45scene = Scene([background, drawable, teleprompter])
46
47# Rendering phase:
48
49recorder = GifRecorder('teleprompter.gif')
50recorder.record(scene)

That code produces the following gif as output:

Animated output of the code above.

A scene with a teleprompter.