Skip to content

EffectCharacter

Module: terminaltexteffects.engine.base_character

A class representing a single character from the input data.

EffectCharacters are created and managed by the Terminal and are used to apply animations and effects to individual characters. Additional non-input characters can be created with Terminal.add_character().

Methods:

Name Description
tick

Progress the character's animation and motion by one step.

Attributes:

Name Type Description
character_id int

The unique ID of the character, generated by the Terminal.

input_symbol str

The symbol for the character in the input data.

input_coord Coord

The coordinate of the character in the input data.

is_visible bool

Whether the character is currently visible and should be printed to the terminal.

animation Animation

The animation object that controls the character's appearance.

motion Motion

The motion object that controls the character's movement.

event_handler EventHandler

The event handler object that handles events related to the character.

layer int

The layer of the character. Lower layers are painted first. When characters on the same layer collide, the character with the higher character_id is painted last and wins the collision. Within a layer, characters placed by depth in 3D (see Space3D) are also ordered by their exact depth.

is_fill_character bool

Whether the character is a fill character. Fill characters are used to fill the empty cells of the Canvas.

uses_input_preexisting_colors bool

Whether engine-level existing_color_handling="always" should treat this character as input-derived and therefore replace effect-owned colors with the parsed input fg/bg pair, even when that pair is empty.

links set[EffectCharacter]

Linked neighboring characters used by spanning-tree algorithms.

neighbors dict[str, EffectCharacter | None]

Adjacent characters keyed by direction ("north", "east", "south", "west"). Reading this property on a terminal-owned character lazily prepares the terminal's complete fill-character and neighbor graph.

Source code in terminaltexteffects/engine/base_character.py
class EffectCharacter:
    """A class representing a single character from the input data.

    EffectCharacters are created and managed by the Terminal and are used to apply
    animations and effects to individual characters. Additional non-input characters
    can be created with `Terminal.add_character()`.

    Methods:
        tick: Progress the character's animation and motion by one step.

    Attributes:
        character_id (int): The unique ID of the character, generated by the Terminal.
        input_symbol (str): The symbol for the character in the input data.
        input_coord (Coord): The coordinate of the character in the input data.
        is_visible (bool): Whether the character is currently visible and should be printed to the terminal.
        animation (animation.Animation): The animation object that controls the character's appearance.
        motion (motion.Motion): The motion object that controls the character's movement.
        event_handler (EventHandler): The event handler object that handles events related to the character.
        layer (int): The layer of the character. Lower layers are painted first. When characters on the same layer
            collide, the character with the higher `character_id` is painted last and wins the collision. Within a
            layer, characters placed by depth in 3D (see `Space3D`) are also ordered by their exact depth.
        is_fill_character (bool): Whether the character is a fill character. Fill characters are used to fill
            the empty cells of the Canvas.
        uses_input_preexisting_colors (bool): Whether engine-level `existing_color_handling="always"` should treat
            this character as input-derived and therefore replace effect-owned colors with the parsed input fg/bg
            pair, even when that pair is empty.
        links (set[EffectCharacter]): Linked neighboring characters used by spanning-tree algorithms.
        neighbors (dict[str, EffectCharacter | None]): Adjacent characters keyed by direction
            (`"north"`, `"east"`, `"south"`, `"west"`). Reading this property on a terminal-owned character lazily
            prepares the terminal's complete fill-character and neighbor graph.

    """

    def __init__(self, character_id: int, symbol: str, input_column: int, input_row: int) -> None:
        """Initialize the character instance with the character ID, symbol, and input coordinates.

        Args:
            character_id (int): The unique ID of the character, generated by the Terminal.
            symbol (str): The symbol for the character in the input data.
            input_column (int): The column of the character in the input data.
            input_row (int): The row of the character in the input data.

        """
        self._character_id: int = character_id
        self._input_symbol: str = symbol
        self._input_coord: Coord = Coord(input_column, input_row)
        self._is_visible: bool = False
        self._terminal_owner_token: typing.Callable[[], _CharacterOwner | None] | None = None
        self.animation: animation.Animation = animation.Animation(self)
        self.motion: motion.Motion = motion.Motion(self)
        self.event_handler: EventHandler = EventHandler(self)
        self._layer = 0
        self._depth_order = 0.0
        self.is_fill_character = False
        self.uses_input_preexisting_colors = False
        self.links: set[EffectCharacter] = set()
        self._neighbors: dict[str, EffectCharacter | None] | None = None

    @property
    def neighbors(self) -> dict[str, EffectCharacter | None]:
        """Adjacent characters keyed by cardinal direction.

        Terminal-owned characters prepare their terminal's complete character graph
        on first access. Standalone characters receive an independent empty mapping.
        """
        if self._neighbors is None:
            owner = self._terminal_owner_token() if self._terminal_owner_token is not None else None
            if owner is not None:
                owner.prepare_character_graph()
            if self._neighbors is None:
                self._neighbors = {}
        return self._neighbors

    @property
    def input_symbol(self) -> str:
        """The symbol for the character in the input data."""
        return self._input_symbol

    @property
    def input_coord(self) -> Coord:
        """The coordinate of the character in the input data."""
        return self._input_coord

    def _set_input_coord(self, coord: Coord) -> None:
        """Set the input coordinate and synchronize the character's current position."""
        self._input_coord = coord
        self.motion.set_coordinate(coord)

    def _notify_current_visual_width_changed(self, previous_width: int) -> None:
        """Notify the owning terminal when the current visual changes display width."""
        owner = self._terminal_owner_token() if self._terminal_owner_token is not None else None
        if owner is not None:
            owner._notify_character_cell_width_changed(self, previous_width)

    @property
    def is_visible(self) -> bool:
        """Whether the character is currently visible and should be printed to the terminal."""
        return self._is_visible

    @property
    def character_id(self) -> int:
        """The unique ID of the character, generated by the Terminal."""
        return self._character_id

    @property
    def layer(self) -> int:
        """Rendering layer used to resolve character collisions."""
        return self._layer

    @layer.setter
    def layer(self, layer: int) -> None:
        """Set the rendering layer and invalidate terminal painter ordering when necessary."""
        previous_layer = self._layer
        if layer == previous_layer:
            return
        self._layer = layer
        owner = self._terminal_owner_token() if self._terminal_owner_token is not None else None
        if owner is not None:
            owner._notify_character_layer_changed(self, previous_layer)

    def _set_depth_order(self, order: float) -> None:
        """Set the order within the character's layer, from -0.5 (painted first) to 0.5, used for 3D depth.

        Characters that never set it stay at 0, so they keep their usual place among characters on their layer.
        """
        previous_order = self._depth_order
        if order == previous_order:
            return
        self._depth_order = order
        owner = self._terminal_owner_token() if self._terminal_owner_token is not None else None
        if owner is not None:
            owner._notify_character_depth_order_changed(self, previous_order)

    @property
    def is_active(self) -> bool:
        """Returns whether the character is currently active.

        A character is active when its animation has an incomplete active scene or its
        motion has an incomplete active path or an active mount. A mount keeps the character active
        after its blend finishes, until it is released.

        Returns:
            bool: True if the character is active, False if not.

        """
        return bool(not self.animation.active_scene_is_complete() or not self.motion.movement_is_complete())

    def tick(self) -> None:
        """Progress the character by one tick.

        Motion is advanced first, then animation is stepped so animation logic can react
        to the character's updated motion state for the same tick.
        """
        self.motion.move()
        self.animation.step_animation()

    def _link(self, char: EffectCharacter, *, bidirectional: bool = True) -> None:
        """Link this character with another character.

        Used for spanning-tree algorithms. When `bidirectional` is True, the reverse link
        is added to `char` as well. Links are stored in a set, so repeated links are
        idempotent.

        Args:
            char (EffectCharacter): Character being linked to this character.
            bidirectional (bool, optional): Apply the link on both characters. Defaults to True.

        """
        if bidirectional:
            char._link(self, bidirectional=False)
        self.links.add(char)

    def __hash__(self) -> int:
        """Hash by `character_id`, so sets of characters iterate in the same order on every run.

        Equality stays identity. Hashing by memory address made the order of `active_characters` vary between runs,
        and with it the order effects drew random numbers, so `--seed` didn't reproduce some effects.
        """
        return self._character_id

    def __repr__(self) -> str:
        """Return a string representation of the EffectCharacter instance."""
        return (
            f"EffectCharacter(character_id={self.character_id}, symbol='{self.input_symbol}', "
            f"input_column={self.input_coord.column}, input_row={self.input_coord.row})"
        )

character_id property

The unique ID of the character, generated by the Terminal.

input_coord property

The coordinate of the character in the input data.

input_symbol property

The symbol for the character in the input data.

is_active property

Returns whether the character is currently active.

A character is active when its animation has an incomplete active scene or its motion has an incomplete active path or an active mount. A mount keeps the character active after its blend finishes, until it is released.

Returns:

Name Type Description
bool bool

True if the character is active, False if not.

is_visible property

Whether the character is currently visible and should be printed to the terminal.

layer property writable

Rendering layer used to resolve character collisions.

neighbors property

Adjacent characters keyed by cardinal direction.

Terminal-owned characters prepare their terminal's complete character graph on first access. Standalone characters receive an independent empty mapping.

__hash__()

Hash by character_id, so sets of characters iterate in the same order on every run.

Equality stays identity. Hashing by memory address made the order of active_characters vary between runs, and with it the order effects drew random numbers, so --seed didn't reproduce some effects.

Source code in terminaltexteffects/engine/base_character.py
def __hash__(self) -> int:
    """Hash by `character_id`, so sets of characters iterate in the same order on every run.

    Equality stays identity. Hashing by memory address made the order of `active_characters` vary between runs,
    and with it the order effects drew random numbers, so `--seed` didn't reproduce some effects.
    """
    return self._character_id

__init__(character_id, symbol, input_column, input_row)

Initialize the character instance with the character ID, symbol, and input coordinates.

Parameters:

Name Type Description Default
character_id int

The unique ID of the character, generated by the Terminal.

required
symbol str

The symbol for the character in the input data.

required
input_column int

The column of the character in the input data.

required
input_row int

The row of the character in the input data.

required
Source code in terminaltexteffects/engine/base_character.py
def __init__(self, character_id: int, symbol: str, input_column: int, input_row: int) -> None:
    """Initialize the character instance with the character ID, symbol, and input coordinates.

    Args:
        character_id (int): The unique ID of the character, generated by the Terminal.
        symbol (str): The symbol for the character in the input data.
        input_column (int): The column of the character in the input data.
        input_row (int): The row of the character in the input data.

    """
    self._character_id: int = character_id
    self._input_symbol: str = symbol
    self._input_coord: Coord = Coord(input_column, input_row)
    self._is_visible: bool = False
    self._terminal_owner_token: typing.Callable[[], _CharacterOwner | None] | None = None
    self.animation: animation.Animation = animation.Animation(self)
    self.motion: motion.Motion = motion.Motion(self)
    self.event_handler: EventHandler = EventHandler(self)
    self._layer = 0
    self._depth_order = 0.0
    self.is_fill_character = False
    self.uses_input_preexisting_colors = False
    self.links: set[EffectCharacter] = set()
    self._neighbors: dict[str, EffectCharacter | None] | None = None

__repr__()

Return a string representation of the EffectCharacter instance.

Source code in terminaltexteffects/engine/base_character.py
def __repr__(self) -> str:
    """Return a string representation of the EffectCharacter instance."""
    return (
        f"EffectCharacter(character_id={self.character_id}, symbol='{self.input_symbol}', "
        f"input_column={self.input_coord.column}, input_row={self.input_coord.row})"
    )

tick()

Progress the character by one tick.

Motion is advanced first, then animation is stepped so animation logic can react to the character's updated motion state for the same tick.

Source code in terminaltexteffects/engine/base_character.py
def tick(self) -> None:
    """Progress the character by one tick.

    Motion is advanced first, then animation is stepped so animation logic can react
    to the character's updated motion state for the same tick.
    """
    self.motion.move()
    self.animation.step_animation()