Transitions
Transitions between segments and content reused across them.
Cuts and fades
Cuts, cross-fades, fade-through and zoom-through, with flash and light-leak overlays and a transition sound.

View on GitHub
gaanim examples/transitions/cuts_and_fades.pyCode · 113 lines
"""Cuts and fades: cut, cross_fade, fade_through and zoom_through, with flash and light-leak overlays.
A transition belongs to the segment it enters, ``scene.segment(name,
transition)``, and plays at its start without lengthening the scene. Every
segment here shows one card; the label below the cards is the transition that
brought the current card in.
"""
import os
from gaanim import Overlay, Scene, Transition, Zones
HOLD = 1.6
scene = Scene()
background = scene.canvas.color("background")
accent = scene.canvas.color("accent")
page = scene.layout.zones(Zones.rows(["6fr", "1fr"], gap="24px", padding="page_padding"))
ROLES = ["accent", "success", "chart", "danger"]
SHAPES = [
lambda: scene.geometry.circle(1.0),
lambda: scene.geometry.square(1.6),
lambda: scene.geometry.regular_polygon(3, 1.0),
lambda: scene.geometry.star(5, 1.0, 0.45),
lambda: scene.geometry.regular_polygon(6, 1.0),
]
captions = []
def card(index):
"""Fill the stage with card ``index``: a theme color holding a large dark shape."""
stage = page[0]
color = scene.canvas.color(ROLES[index % len(ROLES)])
scene.geometry.rounded_rect(stage.width, stage.height, 0.3).fill(color).place(stage)
SHAPES[index % len(SHAPES)]().fill(background).place(stage.inset("96px"), fit="contain")
def caption(*lines):
"""Show ``lines`` of code below the stage from the cursor on, in place of the previous caption.
Persistent drawables stay out of transitions, so the caption reads through
them; hud() also keeps it still, on its strip of background, while
zoom_through moves the camera.
"""
for label in captions[-1:]:
label.opacity(0)
strip = page[1].inset(("-12px", "-120px", "-120px"))
rows = page[1].split(Zones.rows(len(lines)))
label = scene.geometry.group(
[scene.geometry.rect(strip.width, strip.height).fill(background).place(strip)]
+ [scene.text(line, role="code").place(row, anchor="left") for line, row in zip(lines, rows)]
)
scene.persist(label.hud())
captions.append(label)
def enter(code, transition):
"""Caption ``code`` (one line or a list of lines), start the next card's segment with ``transition``.
Returns the segment start.
"""
caption(*([code] if isinstance(code, str) else code))
start = scene.cursor
scene.segment(f"card {len(captions)}", transition)
card(len(captions))
scene.wait(HOLD)
return start
scene.segment("card 0")
card(0)
scene.wait(HOLD)
# A cut switches at once; an overlay is drawn above it, centered on the cut
# (on the midpoint of any other transition). flash rises and fades over its
# duration, white unless given a color.
cut = enter("Transition.cut()", Transition.cut())
flash = enter("Transition.cut(overlay=Overlay.flash())", Transition.cut(overlay=Overlay.flash()))
tinted = enter("Transition.cut(overlay=Overlay.flash(accent, 0.6))", Transition.cut(overlay=Overlay.flash(accent, 0.6)))
# cross_fade blends the two segments; light_leak drifts soft, screen-blended
# light around the midpoint (seed picks the layout, hue is in turns: 0.1 is
# warm orange, 0.6 blue; intensity goes up to 4).
fade = enter("Transition.cross_fade(0.8)", Transition.cross_fade(0.8))
leak = enter("Transition.cross_fade(0.8, overlay=Overlay.light_leak())", Transition.cross_fade(0.8, overlay=Overlay.light_leak()))
blue = Overlay.light_leak(seed=3, hue=0.6, intensity=2)
blue_leak = enter(
["blue = Overlay.light_leak(seed=3, hue=0.6, intensity=2)", "Transition.cross_fade(0.8, overlay=blue)"],
Transition.cross_fade(0.8, overlay=blue),
)
# fade_through fades out to a color in the first half and in from it in the
# second; zoom_through zooms the camera into the outgoing segment and back out
# of the incoming one. sound= plays a file from assets/ as the transition starts.
through = enter("Transition.fade_through(0.8, background)", Transition.fade_through(0.8, background))
zoom = enter('Transition.zoom_through(0.8, sound="whoosh.wav")', Transition.zoom_through(0.8, sound="whoosh.wav"))
zoom_off_center = enter(
"Transition.zoom_through(0.8, center=(4, 1), max_zoom=8)",
Transition.zoom_through(0.8, center=(4, 1), max_zoom=8),
)
if snapshots := os.environ.get("GAANIM_SNAPSHOTS"):
# Just after each cut, while the flashes fade, and partway through every
# other transition.
scene.snapshots(
snapshots,
[cut + 0.2, flash + 0.06, tinted + 0.12]
+ [start + 0.4 for start in (fade, leak, blue_leak)]
+ [through + 0.25, zoom + 0.25, zoom_off_center + 0.25],
)
else:
scene.render()Reveals
Vector reveals: wipes, clock wipes, iris shapes, blinds, push and slide, with their directions and a spring easing.

View on GitHub
gaanim examples/transitions/reveals.pyCode · 111 lines
"""Vector reveals: wipe, clock_wipe, iris, blinds, push and slide, with their directions and shapes.
These transitions clip both segments with animated vector paths, so they
stay sharp at any resolution; without ``easing=`` they run on
``Easing.SMOOTH``. Every segment shows one card, and the label below the cards
is the transition that brought the current card in.
"""
import os
from gaanim import Easing, Scene, Transition, Zones
HOLD = 1.4
scene = Scene()
background = scene.canvas.color("background")
page = scene.layout.zones(Zones.rows(["6fr", "1fr"], gap="24px", padding="page_padding"))
ROLES = ["accent", "success", "chart", "danger"]
SHAPES = [
lambda: scene.geometry.circle(1.0),
lambda: scene.geometry.square(1.6),
lambda: scene.geometry.regular_polygon(3, 1.0),
lambda: scene.geometry.star(5, 1.0, 0.45),
lambda: scene.geometry.regular_polygon(6, 1.0),
]
captions = []
def card(index):
"""Fill the stage with card ``index``: a theme color holding a large dark shape."""
stage = page[0]
color = scene.canvas.color(ROLES[index % len(ROLES)])
scene.geometry.rounded_rect(stage.width, stage.height, 0.3).fill(color).place(stage)
SHAPES[index % len(SHAPES)]().fill(background).place(stage.inset("96px"), fit="contain")
def caption(code):
"""Show ``code`` below the stage from the cursor on, in place of the previous caption.
Persistent drawables stay out of transitions, so the caption, on its strip
of background, is neither clipped nor pushed.
"""
for label in captions[-1:]:
label.opacity(0)
strip = page[1].inset(("-12px", "-120px", "-120px"))
label = scene.geometry.group([
scene.geometry.rect(strip.width, strip.height).fill(background).place(strip),
scene.text(code, role="code").place(page[1], anchor="left"),
])
scene.persist(label)
captions.append(label)
def enter(code, transition):
"""Caption ``code`` and start the next card's segment with ``transition``; return its start."""
caption(code)
start = scene.cursor
scene.segment(f"card {len(captions)}", transition)
card(len(captions))
scene.wait(HOLD)
return start
scene.segment("card 0")
card(0)
scene.wait(HOLD)
starts = []
# wipe sweeps a straight edge in `direction` ("left" starts revealing at the
# right; diagonals such as "up_right" work too), softened by `feather`, a
# fraction of the travel. clock_wipe sweeps a hand clockwise from start_angle,
# in degrees counter-clockwise from +x.
starts.append(enter("Transition.wipe(0.8)", Transition.wipe(0.8)))
starts.append(enter('Transition.wipe(0.8, "down", feather=0)', Transition.wipe(0.8, "down", feather=0)))
starts.append(enter('Transition.wipe(0.8, "up_right", feather=0.5)', Transition.wipe(0.8, "up_right", feather=0.5)))
starts.append(enter("Transition.clock_wipe(0.8)", Transition.clock_wipe(0.8)))
starts.append(enter("Transition.clock_wipe(0.8, start_angle=0)", Transition.clock_wipe(0.8, start_angle=0)))
# iris opens a shape from `center` (scene units) until it fills the frame.
# Any drawable's outline works as the shape; the drawable itself still draws,
# so a stencil is made invisible.
starts.append(enter("Transition.iris(0.8)", Transition.iris(0.8)))
starts.append(enter('Transition.iris(0.8, center=(-4, 1), shape="diamond")', Transition.iris(0.8, center=(-4, 1), shape="diamond")))
starts.append(enter('Transition.iris(0.8, shape="square")', Transition.iris(0.8, shape="square")))
starts.append(enter('Transition.iris(0.8, shape="star")', Transition.iris(0.8, shape="star")))
arrow = scene.geometry.polygon([(-1, 0.3), (0.2, 0.3), (0.2, 0.7), (1, 0), (0.2, -0.7), (0.2, -0.3), (-1, -0.3)])
arrow.opacity(0)
arrow_start = enter("Transition.iris(0.8, shape=arrow)", Transition.iris(0.8, shape=arrow))
# blinds opens `count` slats together, tilted by `angle` degrees.
starts.append(enter("Transition.blinds(0.8)", Transition.blinds(0.8)))
starts.append(enter("Transition.blinds(0.8, count=4, angle=45)", Transition.blinds(0.8, count=4, angle=45)))
# push moves both segments one frame in `direction`; slide moves only the
# incoming one, over the still outgoing segment. Any Easing reshapes the
# progress, springs included.
starts.append(enter("Transition.push(0.8)", Transition.push(0.8)))
starts.append(enter('Transition.push(0.8, "left")', Transition.push(0.8, "left")))
starts.append(enter('Transition.slide(0.8, "left")', Transition.slide(0.8, "left")))
spring_start = enter(
'Transition.slide(0.8, "down", easing=Easing.spring(bounce=0.4))',
Transition.slide(0.8, "down", easing=Easing.spring(bounce=0.4)),
)
if snapshots := os.environ.get("GAANIM_SNAPSHOTS"):
# Halfway through every transition; earlier for the arrow, whose outline
# covers the frame sooner, and the spring, which lands early.
scene.snapshots(snapshots, sorted([start + 0.4 for start in starts] + [arrow_start + 0.25, spring_start + 0.2]))
else:
scene.render()Morph transitions
Drawables that travel across the cut with Transition.morph pairs declared through scene.link, and Transition.magic_move by name.

View on GitHub
gaanim examples/transitions/morph_transitions.pyCode · 109 lines
"""Morph transitions: drawables that travel across the cut, paired by hand or by name.
``Transition.morph`` carries each (source, target) pair from the outgoing
segment into the incoming one, changing place, size, color and shape, while
unpaired content cross-fades. Its pairs need both segments, so it is declared
with ``scene.link`` afterwards. ``Transition.magic_move`` finds the pairs
itself, by the names given with ``Drawable.named``. The label below the stage
is the transition that brought the current segment in.
"""
import os
from gaanim import Easing, Scene, Transition, Zones
HOLD = 1.4
scene = Scene()
background = scene.canvas.color("background")
page = scene.layout.zones(Zones.rows(["6fr", "1fr"], gap="24px", padding="page_padding"))
stage = page[0]
captions = []
def color(role):
"""The theme color of ``role``."""
return scene.canvas.color(role)
def caption(*lines):
"""Show ``lines`` of code below the stage from the cursor on, in place of the previous caption.
Persistent drawables stay out of transitions, so the caption reads through them.
"""
for label in captions[-1:]:
label.opacity(0)
rows = page[1].split(Zones.rows(len(lines)))
label = scene.geometry.group([scene.text(line, role="code").place(row, anchor="left") for line, row in zip(lines, rows)])
scene.persist(label)
captions.append(label)
# Five bars standing on the bottom of the stage.
starts = [scene.cursor]
bars_segment = scene.segment("bars")
columns = stage.split(Zones.columns(5, gap="48px"))
bars = []
for column, share in zip(columns, [0.45, 0.7, 1.0, 0.6, 0.35]):
bar = scene.geometry.rect(column.width, column.height * share)
bars.append(bar.place(column, anchor="bottom"))
scene.wait(HOLD)
# The tallest bar becomes a card holding three dots; the other bars fade.
caption("scene.link(bars_segment, card_segment,", "Transition.morph(0.8, pairs=[(bars[2], card)]))")
starts.append(scene.cursor)
card_segment = scene.segment("card")
card = scene.geometry.rounded_rect(stage.width * 0.6, stage.height, 0.3).fill(color("success")).place(stage)
slots = stage.inset((0, stage.width * 0.2)).split(Zones.columns(3, gap="48px"))
dots = [scene.geometry.circle(1.0).fill(background).place(slot.inset("72px"), fit="contain") for slot in slots]
scene.wait(HOLD)
scene.link(bars_segment, card_segment, Transition.morph(0.8, pairs=[(bars[2], card)]))
# Several pairs travel together, here eased. Each tile is named, for the
# magic_move below.
caption("scene.link(card_segment, tiles_segment,",
"Transition.morph(0.8, pairs=pairs, easing=Easing.SMOOTH))")
starts.append(scene.cursor)
tiles_segment = scene.segment("tiles")
NAMES = ["first", "second", "third"]
ROLES = ["accent", "chart", "danger"]
tiles = [
scene.geometry.rounded_rect(slot.width, slot.height, 0.3).fill(color(role)).place(slot).named(name)
for slot, role, name in zip(stage.split(Zones.columns(3, gap="48px")), ROLES, NAMES)
]
scene.wait(HOLD)
pairs = list(zip(dots, tiles))
scene.link(card_segment, tiles_segment, Transition.morph(0.8, pairs=pairs, easing=Easing.SMOOTH))
# magic_move pairs drawables that share a name; the new bar matches nothing,
# so it fades in.
caption("Transition.magic_move(0.8)")
starts.append(scene.cursor)
scene.segment("column", Transition.magic_move(0.8))
left, right = stage.split(Zones.columns(["1fr", "2fr"], gap="48px"))
for row, role, name in zip(left.split(Zones.rows(3, gap="24px")), ROLES, NAMES):
scene.geometry.circle(1.0).fill(color(role)).place(row, fit="contain").named(name)
scene.geometry.rounded_rect(right.width, right.height, 0.3).fill(color("success")).place(right)
scene.wait(HOLD)
# Every transition takes an easing; a spring carries the stars past their
# places and back while the fades never go back.
caption("Transition.magic_move(0.8, easing=Easing.SNAPPY)")
starts.append(scene.cursor)
scene.segment("diagonal", Transition.magic_move(0.8, easing=Easing.SNAPPY))
cells = stage.split(Zones.grid(rows=3, columns=3, gap="24px"))
for cell, role, name in zip([cells[6], cells[4], cells[2]], ROLES, NAMES):
scene.geometry.star(5, 1.0, 0.45).fill(color(role)).place(cell, fit="contain").named(name)
scene.wait(HOLD)
if snapshots := os.environ.get("GAANIM_SNAPSHOTS"):
# The bars at rest, then each transition halfway (the spring past its
# targets) and its segment settled.
scene.snapshots(
snapshots,
[starts[0] + 0.5] + [start + t for start in starts[1:-1] for t in (0.4, 1.2)]
+ [starts[-1] + 0.1, starts[-1] + 1.2],
)
else:
scene.render()Shader blends
Shader transition presets with their settings, a luma reveal from a grayscale image, and a custom WGSL swirl with uniforms.

View on GitHub
gaanim examples/transitions/shader_blends.pyCode · 130 lines
"""Shader transitions: the built-in presets with their settings, a grayscale reveal map, and WGSL of our own.
Shader transitions render each segment on its own and blend the two on the
GPU: a zoom blur, a directional warp, ripples, a glitch, a reveal that
follows an image, or any function written in WGSL. Every segment shows one
card, and the label below the cards is the transition that brought the
current card in.
"""
import os
from gaanim import Scene, Transition, Zones
HOLD = 1.4
INK = "assets/ink.png"
# fn transition(uv) returns the blended color at uv ((0, 0) is the top left);
# gaanim_from and gaanim_to sample the outgoing and incoming segments,
# progress runs from 0 to 1 and the uniforms are f32 fields of gaanim_uniforms.
SWIRL = """
fn transition(uv: vec2<f32>) -> vec4<f32> {
let center = vec2<f32>(0.5);
let offset = uv - center;
let turn = gaanim_uniforms.turns * 6.2831853 * sin(progress * 3.14159265) * (1.0 - length(offset));
let q = center + vec2<f32>(
offset.x * cos(turn) - offset.y * sin(turn),
offset.x * sin(turn) + offset.y * cos(turn),
);
return mix(gaanim_from(q), gaanim_to(q), smoothstep(0.35, 0.65, progress));
}
"""
scene = Scene()
background = scene.canvas.color("background")
page = scene.layout.zones(Zones.rows(["6fr", "1fr"], gap="24px", padding="page_padding"))
ROLES = ["accent", "success", "chart", "danger"]
SHAPES = [
lambda: scene.geometry.circle(1.0),
lambda: scene.geometry.square(1.6),
lambda: scene.geometry.regular_polygon(3, 1.0),
lambda: scene.geometry.star(5, 1.0, 0.45),
lambda: scene.geometry.regular_polygon(6, 1.0),
]
captions = []
def card(index):
"""Fill the stage with card ``index``: a theme color holding a large dark shape."""
stage = page[0]
color = scene.canvas.color(ROLES[index % len(ROLES)])
scene.geometry.rounded_rect(stage.width, stage.height, 0.3).fill(color).place(stage)
SHAPES[index % len(SHAPES)]().fill(background).place(stage.inset("96px"), fit="contain")
def caption(*lines):
"""Show ``lines`` of code below the stage from the cursor on, in place of the previous caption.
A persistent drawable is composited above a shader blend, so the caption,
on its strip of background, stays sharp.
"""
for label in captions[-1:]:
label.opacity(0)
strip = page[1].inset(("-12px", "-120px", "-120px"))
rows = page[1].split(Zones.rows(len(lines)))
label = scene.geometry.group(
[scene.geometry.rect(strip.width, strip.height).fill(background).place(strip)]
+ [scene.text(line, role="code").place(row, anchor="left") for line, row in zip(lines, rows)]
)
scene.persist(label)
captions.append(label)
def enter(code, transition):
"""Caption ``code`` (one line or a list of lines), start the next card's segment with ``transition``.
Returns the segment start.
"""
caption(*([code] if isinstance(code, str) else code))
start = scene.cursor
scene.segment(f"card {len(captions)}", transition)
card(len(captions))
return start
scene.segment("card 0")
card(0)
scene.wait(HOLD)
starts = []
# Presets and their settings: cross_zoom blurs radially into the incoming
# segment (strength 0..1); directional_warp sweeps it in along (dx, dy), y
# up, with a given smoothness; ripple sends waves across the frame;
# glitch_displace slips bands sideways (strength, bands and seed).
PRESETS = [
('Transition.preset("cross_zoom", 0.8)', Transition.preset("cross_zoom", 0.8)),
('Transition.preset("cross_zoom", 0.8, strength=0.9)', Transition.preset("cross_zoom", 0.8, strength=0.9)),
('Transition.preset("directional_warp", 0.8)', Transition.preset("directional_warp", 0.8)),
('Transition.preset("directional_warp", 0.8, dx=0, dy=1)', Transition.preset("directional_warp", 0.8, dx=0, dy=1)),
('Transition.preset("ripple", 0.8)', Transition.preset("ripple", 0.8)),
('Transition.preset("ripple", 0.8, amplitude=30, speed=20)', Transition.preset("ripple", 0.8, amplitude=30, speed=20)),
('Transition.preset("glitch_displace", 0.8)', Transition.preset("glitch_displace", 0.8)),
('Transition.preset("glitch_displace", 0.8, strength=1.0, bands=8)',
Transition.preset("glitch_displace", 0.8, strength=1.0, bands=8)),
# luma reveals the incoming segment where a grayscale image is darkest
# first, or brightest with invert=True; softness blurs the edge.
('Transition.preset("luma", 0.8, image=INK)', Transition.preset("luma", 0.8, image=INK)),
('Transition.preset("luma", 0.8, image=INK, invert=True)', Transition.preset("luma", 0.8, image=INK, invert=True)),
('Transition.preset("luma", 0.8, image=INK, softness=0.4)', Transition.preset("luma", 0.8, image=INK, softness=0.4)),
('Transition.shader(SWIRL, 0.8, uniforms={"turns": 0.6})', Transition.shader(SWIRL, 0.8, uniforms={"turns": 0.6})),
]
for code, transition in PRESETS:
starts.append(enter(code, transition))
scene.wait(HOLD)
# A uniform can also be a Parameter (or Variable, Computed, scene.time),
# read on every frame of the transition.
twist = scene.viz.parameter(0.0)
starts.append(enter(
['Transition.shader(SWIRL, 0.8, uniforms={"turns": twist})', "twist.animate.set(1.5).duration(0.8)"],
Transition.shader(SWIRL, 0.8, uniforms={"turns": twist}),
))
scene.play([twist.animate.set(1.5).duration(0.8)])
scene.wait(HOLD - 0.8)
if snapshots := os.environ.get("GAANIM_SNAPSHOTS"):
# Partway through every transition.
scene.snapshots(snapshots, [start + 0.35 for start in starts])
else:
scene.render()Segment reuse
Content across segments: reuse through a cross-fade, a persistent badge, release, and a transition added later with scene.link.

View on GitHub
gaanim examples/transitions/segment_reuse.pyCode · 119 lines
"""Content across segments: reuse, persist and release, and transitions declared later with link.
Objects belong to the segment that creates them and leave with it. The first
tile is reused by the next segment, so it holds still through the cross-fade
and then moves on; the badge persists, so later transitions leave it alone
until it is released and goes out with its segment; the last transition is
added with ``scene.link`` after both segments exist. The label below the stage
is the code of the current step.
"""
import os
from gaanim import Scene, Transition, Zones
HOLD = 1.2
scene = Scene()
background = scene.canvas.color("background")
foreground = scene.canvas.color("foreground")
page = scene.layout.zones(Zones.rows(["6fr", "1fr"], gap="24px", padding="page_padding"))
stage = page[0]
captions = []
def color(role):
"""The theme color of ``role``."""
return scene.canvas.color(role)
def caption(*lines):
"""Show ``lines`` of code below the stage from the cursor on, in place of the previous caption.
Captions persist too, so no transition moves or clips them.
"""
for label in captions[-1:]:
label.opacity(0)
strip = page[1].inset(("-12px", "-120px", "-120px"))
rows = page[1].split(Zones.rows(len(lines)))
label = scene.geometry.group(
[scene.geometry.rect(strip.width, strip.height).fill(background).place(strip)]
+ [scene.text(line, role="code").place(row, anchor="left") for line, row in zip(lines, rows)]
)
scene.persist(label)
captions.append(label)
def card(role, shape):
"""Fill the stage with a card of ``role`` holding a large dark ``shape()``."""
scene.geometry.rounded_rect(stage.width, stage.height, 0.3).fill(color(role)).place(stage)
shape().fill(background).place(stage.inset("96px"), fit="contain")
# Three tiles, each the content of the first segment.
starts = [scene.cursor]
scene.segment("tiles")
caption('scene.segment("tiles")')
slots = stage.split(Zones.columns(3, gap="48px"))
tiles = [
scene.geometry.rounded_rect(slot.width, slot.height, 0.3).fill(color(role)).place(slot)
for slot, role in zip(slots, ["accent", "chart", "danger"])
]
scene.wait(HOLD)
# reuse adopts a drawable of the previous segment: it holds still while the
# transition runs, then belongs to this segment and can move on.
caption('scene.segment("focus", Transition.cross_fade(0.8))', "scene.reuse(tiles[0])")
starts.append(scene.cursor)
scene.segment("focus", Transition.cross_fade(0.8))
scene.reuse(tiles[0])
lines, aside = stage.split(Zones.columns(["2fr", "1fr"], gap="48px"))
for row in lines.split(Zones.rows(3, gap="48px")):
scene.geometry.rounded_rect(row.width, row.height, row.height / 2).fill(color("chart")).place(row)
scene.wait(0.8)
scene.play([tiles[0].animate.move_to(*aside.center).duration(0.6)])
# From the cursor on, a persistent drawable is global: it stays visible in
# later segments and their transitions leave it alone. Its z_index keeps it
# above the cards once it is released.
caption("scene.persist(badge)")
badge = scene.geometry.star(5, 0.9, 0.4).fill(foreground).place(aside).z_index(1)
scene.persist(badge)
scene.play([badge.animate.grow_from_center().duration(0.4)])
scene.wait(0.6)
caption('scene.segment("push", Transition.push(0.8, "left"))')
starts.append(scene.cursor)
scene.segment("push", Transition.push(0.8, "left"))
card("success", lambda: scene.geometry.regular_polygon(3, 1.0))
scene.wait(HOLD)
# release attaches it to the segment again: it holds still through this
# segment's transition and leaves with the segment at the next one.
caption('scene.segment("release", Transition.slide(0.8, "up"))', "scene.release(badge)")
starts.append(scene.cursor)
release_segment = scene.segment("release", Transition.slide(0.8, "up"))
scene.release(badge)
card("danger", lambda: scene.geometry.circle(1.0))
scene.wait(HOLD)
# link sets the transition between two declared segments, here after the
# fact; it replaces whatever transition the second one had.
caption('scene.link(release_segment, end_segment, Transition.wipe(0.8, "right"))')
starts.append(scene.cursor)
end_segment = scene.segment("end")
card("accent", lambda: scene.geometry.square(1.6))
scene.wait(HOLD)
scene.link(release_segment, end_segment, Transition.wipe(0.8, "right"))
if snapshots := os.environ.get("GAANIM_SNAPSHOTS"):
# The tiles; the cross-fade around the reused tile and the tile moved
# on; the badge; then the push, the slide and the wipe halfway.
scene.snapshots(
snapshots,
[starts[0] + 0.6, starts[1] + 0.4, starts[1] + 1.4, starts[1] + 2.2]
+ [start + 0.4 for start in starts[2:]],
)
else:
scene.render()