Skip to content

EventHandler

Module: terminaltexteffects.engine.base_character

Register and handle events related to a character.

Events related to character state changes (e.g. scene complete) can be registered with the EventHandler. When an event is triggered, the EventHandler will take the specified action (e.g. activate a Path). The EventHandler is used by the EffectCharacter class to handle events related to the character.

Attributes:

Name Type Description
character EffectCharacter

The character that the EventHandler is handling events for.

registered_events dict[tuple[Event, str], list[tuple[Action, str]]]

A dictionary of registered events. The key is a tuple of the event and the caller ID (waypoint id/scene id). The value is a list of tuples of the action and the action target (path id/scene id).

layer int

The layer of the character. The layer determines the order in which characters are printed.

Note

SEGMENT_ENTERED/EXITED events will trigger the first time the character enters or exits a segment. If looping, each loop will trigger the event, but not backwards motion as is possible with the bounce easing functions.

Source code in terminaltexteffects/engine/base_character.py
class EventHandler:
    """Register and handle events related to a character.

    Events related to character state changes (e.g. scene complete) can be registered with the EventHandler.
    When an event is triggered, the EventHandler will take the specified action (e.g. activate a Path).
    The EventHandler is used by the EffectCharacter class to handle events related to the character.

    Attributes:
        character (EffectCharacter): The character that the EventHandler is handling events for.
        registered_events (dict[tuple[Event, str], list[tuple[Action, str]]]): A dictionary of registered events.
            The key is a tuple of the event and the caller ID (waypoint id/scene id).
            The value is a list of tuples of the action and the action target (path id/scene id).
        layer (int): The layer of the character. The layer determines the order in which characters are printed.

    Note:
        SEGMENT_ENTERED/EXITED events will trigger the first time the character enters or exits a segment.
        If looping, each loop will trigger the event, but not backwards motion as is possible with
        the bounce easing functions.

    """

    def __init__(self, character: EffectCharacter) -> None:
        """Initialize the instance with the EffectCharacter object.

        Args:
            character (EffectCharacter): The character for which the EventHandler is handling events.

        """
        self.character = character
        self.registered_events: dict[
            tuple[EventHandler.Event, animation.Scene | motion.Waypoint | motion.Path],
            list[
                tuple[
                    EventHandler.Action,
                    animation.Scene | motion.Waypoint | motion.Path | int | Coord | EventHandler.Callback | None,
                ]
            ],
        ] = {}

    class Event(Enum):
        """An Event that can be registered with the EventHandler.

        Register Events with the EventHandler using the register_event method of the EventHandler class.

        Attributes:
            SEGMENT_ENTERED (Event): A path segment has been entered.
            SEGMENT_EXITED (Event): A path segment has been exited.
            PATH_ACTIVATED (Event): A path has been activated.
            PATH_COMPLETE (Event): A path has been completed.
            PATH_HOLDING (Event): A path has entered the holding state.
            SCENE_ACTIVATED (Event): An animation scene has been activated.
            SCENE_COMPLETE (Event): An animation scene has completed.

        """

        SEGMENT_ENTERED = auto()
        SEGMENT_EXITED = auto()
        PATH_ACTIVATED = auto()
        PATH_COMPLETE = auto()
        PATH_HOLDING = auto()
        SCENE_ACTIVATED = auto()
        SCENE_COMPLETE = auto()

    class Action(Enum):
        """Actions that can be taken when an event is triggered.

        An Action is taken when an Event is triggered. Register Actions with the EventHandler using the
        register_event method of the EventHandler class.

        Attributes:
            ACTIVATE_PATH (Action): Activates a path. The action target is the path ID.
            ACTIVATE_SCENE (Action): Activates an animation scene. The action target is the scene ID.
            DEACTIVATE_PATH (Action): Deactivates a path. The action target is the path ID.
            DEACTIVATE_SCENE (Action): Deactivates an animation scene. The action target is the scene ID.
            RESET_APPEARANCE (Action): Resets the appearance of the character to the input symbol and color.
            SET_LAYER (Action): Sets the layer of the character. The action target is the layer number.
            SET_COORDINATE (Action): Sets the coordinate of the character. The action target is the coordinate.
            CALLBACK (Action): Calls a callback function. The action target is an EventHandler.Callback object.

        """

        ACTIVATE_PATH = auto()
        ACTIVATE_SCENE = auto()
        DEACTIVATE_PATH = auto()
        DEACTIVATE_SCENE = auto()
        RESET_APPEARANCE = auto()
        SET_LAYER = auto()
        SET_COORDINATE = auto()
        CALLBACK = auto()

    @dataclass(init=False)
    class Callback:
        """A callback action target that can be taken when an event is triggered.

        Register callback actions with the EventHandler using the register_event method of the EventHandler class.

        The callback function will be called with the character and any additional arguments when the event
        is triggered. The character will be the first argument passed to the callback function followed by any
        additional arguments in the order they were passed to the Callback object.

        Example:
            Create a callback to set the character's visibility to False. The following code would be
            used within an effect where 'self' is the EffectIterator instance:

                cb = EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False))

        """

        callback: typing.Callable
        args: tuple[typing.Any, ...]

        def __init__(self, callback: typing.Callable, *args: typing.Any) -> None:
            """Initialize the instance with the callback function and arguments.

            Args:
                callback (typing.Callable): The callback function to call.
                args (tuple[typing.Any,...]): A tuple of arguments to pass to the callback function. The first argument
                    will be the character, followed by any additional arguments.

            """
            self.callback = callback
            self.args = args

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.ACTIVATE_SCENE, Action.DEACTIVATE_SCENE],
        target: animation.Scene,
    ) -> None: ...

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.ACTIVATE_PATH, Action.DEACTIVATE_PATH],
        target: motion.Path,
    ) -> None: ...

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.SET_COORDINATE],
        target: Coord,
    ) -> None: ...

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.SET_LAYER],
        target: int,
    ) -> None: ...

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.CALLBACK],
        target: Callback,
    ) -> None: ...

    @typing.overload
    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: typing.Literal[Action.RESET_APPEARANCE],
    ) -> None: ...

    def register_event(
        self,
        event: Event,
        caller: animation.Scene | motion.Waypoint | motion.Path,
        action: Action,
        target: animation.Scene | motion.Path | int | Coord | Callback | None = None,
    ) -> None:
        """Register an event to be handled by the EventHandler.

        Args:
            event (Event): The event to register.
            caller (animation.Scene | motion.Waypoint | motion.Path): The object that triggers the event.
            action (Action): The action to take when the event is triggered.
            target (animation.Scene | motion.Path | int | Coord | Callback): The target of the action.

        Raises:
            ValueError: If the caller or target object is not the correct type for the event or action.

        Example:
            Register an event to activate a scene when a Path is complete:
            `event_handler.register_event(EventHandler.Event.PATH_COMPLETE, some_path,
                EventHandler.Action.ACTIVATE_SCENE, some_scene)`

        """
        event_caller_map = {
            EventHandler.Event.SEGMENT_ENTERED: motion.Waypoint,
            EventHandler.Event.SEGMENT_EXITED: motion.Waypoint,
            EventHandler.Event.PATH_ACTIVATED: motion.Path,
            EventHandler.Event.PATH_COMPLETE: motion.Path,
            EventHandler.Event.PATH_HOLDING: motion.Path,
            EventHandler.Event.SCENE_ACTIVATED: animation.Scene,
            EventHandler.Event.SCENE_COMPLETE: animation.Scene,
        }

        action_target_map = {
            EventHandler.Action.ACTIVATE_PATH: motion.Path,
            EventHandler.Action.ACTIVATE_SCENE: animation.Scene,
            EventHandler.Action.DEACTIVATE_PATH: motion.Path,
            EventHandler.Action.DEACTIVATE_SCENE: animation.Scene,
            EventHandler.Action.RESET_APPEARANCE: type(None),
            EventHandler.Action.SET_LAYER: int,
            EventHandler.Action.SET_COORDINATE: Coord,
            EventHandler.Action.CALLBACK: EventHandler.Callback,
        }

        if event_caller_map[event] != caller.__class__:
            raise EventRegistrationCallerError(event, caller, event_caller_map[event])

        if (action is EventHandler.Action.RESET_APPEARANCE and target is not None) or (
            action_target_map[action] != target.__class__
        ):
            raise EventRegistrationTargetError(action, target, action_target_map[action])

        new_event = (event, caller)
        new_action = (action, target)
        if new_event not in self.registered_events:
            self.registered_events[new_event] = []
        self.registered_events[new_event].append(new_action)

    def _handle_event(self, event: Event, caller: animation.Scene | motion.Waypoint | motion.Path) -> None:
        """Handle an event by taking the specified action.

        Args:
            event (Event): An event to handle. If the event is not registered, nothing happens.
            caller (animation.Scene | motion.Waypoint | motion.Path): The object triggering the call.

        """
        action_map = {
            EventHandler.Action.ACTIVATE_PATH: self.character.motion.activate_path,
            EventHandler.Action.ACTIVATE_SCENE: self.character.animation.activate_scene,
            EventHandler.Action.DEACTIVATE_PATH: self.character.motion.deactivate_path,
            EventHandler.Action.DEACTIVATE_SCENE: self.character.animation.deactivate_scene,
            EventHandler.Action.RESET_APPEARANCE: lambda _: self.character.animation.set_appearance(
                self.character.input_symbol,
            ),
            EventHandler.Action.SET_LAYER: lambda layer: setattr(self.character, "layer", layer),
            EventHandler.Action.SET_COORDINATE: lambda coord: setattr(self.character.motion, "current_coord", coord),
            EventHandler.Action.CALLBACK: lambda callback: callback.callback(self.character, *callback.args),
        }

        if (event, caller) not in self.registered_events:
            return
        for event_action in self.registered_events[(event, caller)]:
            action, target = event_action
            action_map[action](target)  # type: ignore[operator]

Action

Bases: Enum

Actions that can be taken when an event is triggered.

An Action is taken when an Event is triggered. Register Actions with the EventHandler using the register_event method of the EventHandler class.

Attributes:

Name Type Description
ACTIVATE_PATH Action

Activates a path. The action target is the path ID.

ACTIVATE_SCENE Action

Activates an animation scene. The action target is the scene ID.

DEACTIVATE_PATH Action

Deactivates a path. The action target is the path ID.

DEACTIVATE_SCENE Action

Deactivates an animation scene. The action target is the scene ID.

RESET_APPEARANCE Action

Resets the appearance of the character to the input symbol and color.

SET_LAYER Action

Sets the layer of the character. The action target is the layer number.

SET_COORDINATE Action

Sets the coordinate of the character. The action target is the coordinate.

CALLBACK Action

Calls a callback function. The action target is an EventHandler.Callback object.

Source code in terminaltexteffects/engine/base_character.py
class Action(Enum):
    """Actions that can be taken when an event is triggered.

    An Action is taken when an Event is triggered. Register Actions with the EventHandler using the
    register_event method of the EventHandler class.

    Attributes:
        ACTIVATE_PATH (Action): Activates a path. The action target is the path ID.
        ACTIVATE_SCENE (Action): Activates an animation scene. The action target is the scene ID.
        DEACTIVATE_PATH (Action): Deactivates a path. The action target is the path ID.
        DEACTIVATE_SCENE (Action): Deactivates an animation scene. The action target is the scene ID.
        RESET_APPEARANCE (Action): Resets the appearance of the character to the input symbol and color.
        SET_LAYER (Action): Sets the layer of the character. The action target is the layer number.
        SET_COORDINATE (Action): Sets the coordinate of the character. The action target is the coordinate.
        CALLBACK (Action): Calls a callback function. The action target is an EventHandler.Callback object.

    """

    ACTIVATE_PATH = auto()
    ACTIVATE_SCENE = auto()
    DEACTIVATE_PATH = auto()
    DEACTIVATE_SCENE = auto()
    RESET_APPEARANCE = auto()
    SET_LAYER = auto()
    SET_COORDINATE = auto()
    CALLBACK = auto()

Callback dataclass

A callback action target that can be taken when an event is triggered.

Register callback actions with the EventHandler using the register_event method of the EventHandler class.

The callback function will be called with the character and any additional arguments when the event is triggered. The character will be the first argument passed to the callback function followed by any additional arguments in the order they were passed to the Callback object.

Example

Create a callback to set the character's visibility to False. The following code would be used within an effect where 'self' is the EffectIterator instance:

cb = EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False))
Source code in terminaltexteffects/engine/base_character.py
@dataclass(init=False)
class Callback:
    """A callback action target that can be taken when an event is triggered.

    Register callback actions with the EventHandler using the register_event method of the EventHandler class.

    The callback function will be called with the character and any additional arguments when the event
    is triggered. The character will be the first argument passed to the callback function followed by any
    additional arguments in the order they were passed to the Callback object.

    Example:
        Create a callback to set the character's visibility to False. The following code would be
        used within an effect where 'self' is the EffectIterator instance:

            cb = EventHandler.Callback(lambda c: self.terminal.set_character_visibility(c, is_visible=False))

    """

    callback: typing.Callable
    args: tuple[typing.Any, ...]

    def __init__(self, callback: typing.Callable, *args: typing.Any) -> None:
        """Initialize the instance with the callback function and arguments.

        Args:
            callback (typing.Callable): The callback function to call.
            args (tuple[typing.Any,...]): A tuple of arguments to pass to the callback function. The first argument
                will be the character, followed by any additional arguments.

        """
        self.callback = callback
        self.args = args

__init__(callback, *args)

Initialize the instance with the callback function and arguments.

Parameters:

Name Type Description Default
callback Callable

The callback function to call.

required
args tuple[Any, ...]

A tuple of arguments to pass to the callback function. The first argument will be the character, followed by any additional arguments.

()
Source code in terminaltexteffects/engine/base_character.py
def __init__(self, callback: typing.Callable, *args: typing.Any) -> None:
    """Initialize the instance with the callback function and arguments.

    Args:
        callback (typing.Callable): The callback function to call.
        args (tuple[typing.Any,...]): A tuple of arguments to pass to the callback function. The first argument
            will be the character, followed by any additional arguments.

    """
    self.callback = callback
    self.args = args

Event

Bases: Enum

An Event that can be registered with the EventHandler.

Register Events with the EventHandler using the register_event method of the EventHandler class.

Attributes:

Name Type Description
SEGMENT_ENTERED Event

A path segment has been entered.

SEGMENT_EXITED Event

A path segment has been exited.

PATH_ACTIVATED Event

A path has been activated.

PATH_COMPLETE Event

A path has been completed.

PATH_HOLDING Event

A path has entered the holding state.

SCENE_ACTIVATED Event

An animation scene has been activated.

SCENE_COMPLETE Event

An animation scene has completed.

Source code in terminaltexteffects/engine/base_character.py
class Event(Enum):
    """An Event that can be registered with the EventHandler.

    Register Events with the EventHandler using the register_event method of the EventHandler class.

    Attributes:
        SEGMENT_ENTERED (Event): A path segment has been entered.
        SEGMENT_EXITED (Event): A path segment has been exited.
        PATH_ACTIVATED (Event): A path has been activated.
        PATH_COMPLETE (Event): A path has been completed.
        PATH_HOLDING (Event): A path has entered the holding state.
        SCENE_ACTIVATED (Event): An animation scene has been activated.
        SCENE_COMPLETE (Event): An animation scene has completed.

    """

    SEGMENT_ENTERED = auto()
    SEGMENT_EXITED = auto()
    PATH_ACTIVATED = auto()
    PATH_COMPLETE = auto()
    PATH_HOLDING = auto()
    SCENE_ACTIVATED = auto()
    SCENE_COMPLETE = auto()

__init__(character)

Initialize the instance with the EffectCharacter object.

Parameters:

Name Type Description Default
character EffectCharacter

The character for which the EventHandler is handling events.

required
Source code in terminaltexteffects/engine/base_character.py
def __init__(self, character: EffectCharacter) -> None:
    """Initialize the instance with the EffectCharacter object.

    Args:
        character (EffectCharacter): The character for which the EventHandler is handling events.

    """
    self.character = character
    self.registered_events: dict[
        tuple[EventHandler.Event, animation.Scene | motion.Waypoint | motion.Path],
        list[
            tuple[
                EventHandler.Action,
                animation.Scene | motion.Waypoint | motion.Path | int | Coord | EventHandler.Callback | None,
            ]
        ],
    ] = {}

register_event(event, caller, action, target=None)

register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.ACTIVATE_SCENE, Action.DEACTIVATE_SCENE], target: animation.Scene) -> None
register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.ACTIVATE_PATH, Action.DEACTIVATE_PATH], target: motion.Path) -> None
register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.SET_COORDINATE], target: Coord) -> None
register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.SET_LAYER], target: int) -> None
register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.CALLBACK], target: Callback) -> None
register_event(event: Event, caller: animation.Scene | motion.Waypoint | motion.Path, action: typing.Literal[Action.RESET_APPEARANCE]) -> None

Register an event to be handled by the EventHandler.

Parameters:

Name Type Description Default
event Event

The event to register.

required
caller Scene | Waypoint | Path

The object that triggers the event.

required
action Action

The action to take when the event is triggered.

required
target Scene | Path | int | Coord | Callback

The target of the action.

None

Raises:

Type Description
ValueError

If the caller or target object is not the correct type for the event or action.

Example

Register an event to activate a scene when a Path is complete: event_handler.register_event(EventHandler.Event.PATH_COMPLETE, some_path, EventHandler.Action.ACTIVATE_SCENE, some_scene)

Source code in terminaltexteffects/engine/base_character.py
def register_event(
    self,
    event: Event,
    caller: animation.Scene | motion.Waypoint | motion.Path,
    action: Action,
    target: animation.Scene | motion.Path | int | Coord | Callback | None = None,
) -> None:
    """Register an event to be handled by the EventHandler.

    Args:
        event (Event): The event to register.
        caller (animation.Scene | motion.Waypoint | motion.Path): The object that triggers the event.
        action (Action): The action to take when the event is triggered.
        target (animation.Scene | motion.Path | int | Coord | Callback): The target of the action.

    Raises:
        ValueError: If the caller or target object is not the correct type for the event or action.

    Example:
        Register an event to activate a scene when a Path is complete:
        `event_handler.register_event(EventHandler.Event.PATH_COMPLETE, some_path,
            EventHandler.Action.ACTIVATE_SCENE, some_scene)`

    """
    event_caller_map = {
        EventHandler.Event.SEGMENT_ENTERED: motion.Waypoint,
        EventHandler.Event.SEGMENT_EXITED: motion.Waypoint,
        EventHandler.Event.PATH_ACTIVATED: motion.Path,
        EventHandler.Event.PATH_COMPLETE: motion.Path,
        EventHandler.Event.PATH_HOLDING: motion.Path,
        EventHandler.Event.SCENE_ACTIVATED: animation.Scene,
        EventHandler.Event.SCENE_COMPLETE: animation.Scene,
    }

    action_target_map = {
        EventHandler.Action.ACTIVATE_PATH: motion.Path,
        EventHandler.Action.ACTIVATE_SCENE: animation.Scene,
        EventHandler.Action.DEACTIVATE_PATH: motion.Path,
        EventHandler.Action.DEACTIVATE_SCENE: animation.Scene,
        EventHandler.Action.RESET_APPEARANCE: type(None),
        EventHandler.Action.SET_LAYER: int,
        EventHandler.Action.SET_COORDINATE: Coord,
        EventHandler.Action.CALLBACK: EventHandler.Callback,
    }

    if event_caller_map[event] != caller.__class__:
        raise EventRegistrationCallerError(event, caller, event_caller_map[event])

    if (action is EventHandler.Action.RESET_APPEARANCE and target is not None) or (
        action_target_map[action] != target.__class__
    ):
        raise EventRegistrationTargetError(action, target, action_target_map[action])

    new_event = (event, caller)
    new_action = (action, target)
    if new_event not in self.registered_events:
        self.registered_events[new_event] = []
    self.registered_events[new_event].append(new_action)