Skip to content

Events

Paths and scenes run on their own once activated, but effects often need something to happen at a particular moment: when a character lands, start a scene; when a scene ends, start the next path. Each character's EventHandler, character.event_handler, runs actions when events happen to that character's paths, waypoints, and scenes.

Registering an event

from terminaltexteffects import EventHandler

character.event_handler.register_event(
    EventHandler.Event.PATH_COMPLETE,  # when this happens...
    fall,                              # ...to this path, waypoint, or scene...
    EventHandler.Action.SET_LAYER,     # ...do this...
    0,                                 # ...with this target.
)

terminaltexteffects also exports the enums as Event and Action, so you can write Event.PATH_COMPLETE.

You register events once, during setup. They then fire every time their event happens, for the rest of the effect. Several actions can be registered for the same event; they run in the order you registered them.

Events

Event Fires when Caller
PATH_ACTIVATED A path is activated. Path
SEGMENT_ENTERED The character starts the segment that ends at a waypoint. Waypoint
SEGMENT_EXITED The character reaches a waypoint. Waypoint
PATH_HOLDING The character reaches the end of a path with hold_time, before holding. Path
PATH_COMPLETE A path finishes, after any hold. Path
SCENE_ACTIVATED A scene is activated. Scene
SCENE_COMPLETE A scene finishes. Scene

The caller is the object the event is about. For path and scene activation and completion, you can pass the path or scene's ID string instead of the object.

Activation events fire immediately when the path or scene is activated, even during setup and even if the character isn't active yet. Register events for a path or scene before you activate it, or the activation event is already over.

Segment events fire once per traversal of the segment. A looping path fires them again on each loop.

Actions

Action Target Does
ACTIVATE_PATH Path or path ID Starts that path from the character's current position.
DEACTIVATE_PATH Path, path ID, or None Stops that path, or whichever path is active.
ACTIVATE_SCENE Scene or scene ID Starts that scene.
DEACTIVATE_SCENE Scene, scene ID, or None Stops that scene, or whichever scene is active.
SET_LAYER int Moves the character to that layer.
SET_COORDINATE Coord Moves the character to that position instantly.
RESET_APPEARANCE none Restores the input symbol and its input colors.
CALLBACK EventHandler.Callback Calls your function.

Callbacks

When no built-in action fits, use a callback. EventHandler.Callback(function, *args) calls function(character, *args) when the event fires:

def hide(character, terminal):
    terminal.set_character_visibility(character, is_visible=False)

character.event_handler.register_event(
    Event.PATH_COMPLETE, fall, Action.CALLBACK, EventHandler.Callback(hide, self.terminal)
)

Callbacks can do anything, including activating other paths. Keep them short: they run inside the character's tick.

The Hop example

Hop makes each column of text hop to the top of the canvas and fall back, one column after another. Events do all the sequencing:

  • PATH_ACTIVATED on the rise sets layer 1, so rising characters pass in front.
  • PATH_HOLDING on the rise calls a callback that changes the symbol to * while the character waits at the top.
  • PATH_COMPLETE on the rise activates the fall path.
  • PATH_COMPLETE on the fall restores the input symbol and sets layer 0.

The iterator only activates each column's rise. Save it as hop.py in your community effects directory:

hop.py
"""Hop: characters hop to the top of the canvas and back, one column at a time.

An Effect Guide example for the Events chapter. Events chain the two paths, change the character's
symbol at the top, and restore it when the character lands.
"""

from __future__ import annotations

from dataclasses import dataclass

from terminaltexteffects import Coord, EffectCharacter, EventHandler, easing
from terminaltexteffects.engine.base_config import BaseConfig
from terminaltexteffects.engine.base_effect import BaseEffect, BaseEffectIterator
from terminaltexteffects.utils import argutils

Event = EventHandler.Event
Action = EventHandler.Action


def get_effect_resources() -> tuple[str, type[BaseEffect], type[BaseConfig]]:
    """Return the command name, effect class, and configuration class TTE registers."""
    return "hop", Hop, HopConfig


@dataclass
class HopConfig(BaseConfig):
    """Configuration for the Hop effect."""

    parser_spec = argutils.ParserSpec(
        name="hop",
        help="Characters hop to the top of the canvas and back.",
        description="hop | Characters hop to the top of the canvas and back.",
        epilog="Example: tte hop",
    )


class HopIterator(BaseEffectIterator[HopConfig]):
    """Produce the frames of the Hop effect."""

    def __init__(self, effect: Hop) -> None:
        """Give every character a hop, and queue the columns."""
        super().__init__(effect)
        top = self.terminal.canvas.top
        for character in self.terminal.get_characters():
            self.terminal.set_character_visibility(character, is_visible=True)
            rise = character.motion.new_path(speed=0.5, ease=easing.out_quad, hold_time=10, path_id="rise")
            rise.new_waypoint(Coord(character.input_coord.column, top))
            fall = character.motion.new_path(speed=0.5, ease=easing.out_bounce)
            fall.new_waypoint(character.input_coord)

            handler = character.event_handler
            # Rising characters pass in front of the others.
            handler.register_event(Event.PATH_ACTIVATED, rise, Action.SET_LAYER, 1)
            # At the top, show a different symbol while the path holds.
            handler.register_event(
                Event.PATH_HOLDING,
                rise,
                Action.CALLBACK,
                EventHandler.Callback(lambda char: char.animation.set_appearance("*")),
            )
            # When the rise completes, fall back home.
            handler.register_event(Event.PATH_COMPLETE, rise, Action.ACTIVATE_PATH, fall)
            # On landing, restore the input symbol and return to the base layer.
            handler.register_event(Event.PATH_COMPLETE, fall, Action.RESET_APPEARANCE)
            handler.register_event(Event.PATH_COMPLETE, fall, Action.SET_LAYER, 0)
        self.columns: list[list[EffectCharacter]] = self.terminal.get_characters_grouped(order="column_left_to_right")
        self.columns.reverse()
        self.frame_count = 0

    def __next__(self) -> str:
        """Start one column hopping every third frame."""
        if self.columns or self.active_characters:
            if self.columns and self.frame_count % 3 == 0:
                for character in self.columns.pop():
                    character.motion.activate_path("rise")
                    self.active_characters.add(character)
            self.frame_count += 1
            self.update()
            return self.frame
        raise StopIteration


class Hop(BaseEffect[HopConfig]):
    """Characters hop to the top of the canvas and back."""

    @property
    def _config_cls(self) -> type[HopConfig]:
        return HopConfig

    @property
    def _iterator_cls(self) -> type[HopIterator]:
        return HopIterator
tte --input-file concept_input.txt hop

Columns hop to the top, turn into stars, and bounce back into place

When events fire in a tick

During a character's tick, motion moves first and animation steps second (see Ticking a character), and each fires its own events as it goes, so path events for a tick run before scene events for that tick.

An action that activates a path or scene changes what the character is doing straight away. The engine stops processing the old path for the rest of that tick, so the new path starts moving on the next tick.

Where to go next

  • Tutorial step 4: Layer uses PATH_ACTIVATED and PATH_COMPLETE to change layers.
  • Motion shows chain_paths(), a shortcut for PATH_COMPLETE and ACTIVATE_PATH.