Skip to content

Scene

Module: terminaltexteffects.engine.animation

A Scene is a collection of Frames that can be played in sequence. Scenes can be looped and synced to movement.

Methods:

Name Description
add_frame

Adds a Frame to the Scene.

activate

Activates the Scene.

get_next_visual

Gets the next CharacterVisual in the Scene.

apply_gradient_to_symbols

Applies a gradient effect to a sequence of symbols.

reset_scene

Resets the Scene.

Attributes:

Name Type Description
scene_id str

the ID of the Scene

is_looping bool

Whether the Scene should loop

sync SyncMetric | None

The type of sync to use for the Scene

ease EasingFunction | None

The easing function to use for the Scene

no_color bool

Whether to ignore colors

use_xterm_colors bool

Whether to convert all colors to XTerm-256 colors

frames list[Frame]

The list of Frames in the Scene

played_frames list[Frame]

The list of Frames that have been played

frame_index_map dict[int, Frame]

A mapping of frame index to Frame

easing_total_steps int

The total number of steps in the easing function

easing_current_step int

The current step in the easing function

preexisting_colors ColorPair | None

The preexisting colors parsed from the input.

Source code in terminaltexteffects/engine/animation.py
class Scene:
    """A Scene is a collection of Frames that can be played in sequence. Scenes can be looped and synced to movement.

    Methods:
        add_frame: Adds a Frame to the Scene.
        activate: Activates the Scene.
        get_next_visual: Gets the next CharacterVisual in the Scene.
        apply_gradient_to_symbols: Applies a gradient effect to a sequence of symbols.
        reset_scene: Resets the Scene.

    Attributes:
        scene_id (str): the ID of the Scene
        is_looping (bool): Whether the Scene should loop
        sync (Scene.SyncMetric | None): The type of sync to use for the Scene
        ease (easing.EasingFunction | None): The easing function to use for the Scene
        no_color (bool): Whether to ignore colors
        use_xterm_colors (bool): Whether to convert all colors to XTerm-256 colors
        frames (list[Frame]): The list of Frames in the Scene
        played_frames (list[Frame]): The list of Frames that have been played
        frame_index_map (dict[int, Frame]): A mapping of frame index to Frame
        easing_total_steps (int): The total number of steps in the easing function
        easing_current_step (int): The current step in the easing function
        preexisting_colors (graphics.ColorPair | None): The preexisting colors parsed from the input.

    """

    xterm_color_map: typing.ClassVar[dict[str, int]] = {}

    class SyncMetric(Enum):
        """Enum for specifying the type of sync to use for a Scene.

        Attributes:
            DISTANCE (int): Sync to a Waypoint based on distance from the Waypoint
            STEP (int): Sync to a Waypoint based on the number of steps taken towards the Waypoint

        """

        DISTANCE = auto()
        STEP = auto()

    def __init__(
        self,
        scene_id: str,
        *,
        is_looping: bool = False,
        sync: SyncMetric | None = None,
        ease: easing.EasingFunction | None = None,
        no_color: bool = False,
        use_xterm_colors: bool = False,
    ) -> None:
        """Initialize a Scene.

        Args:
            scene_id (str): the ID of the Scene
            is_looping (bool, optional): Whether the Scene should loop. Defaults to False.
            sync (Scene.SyncMetric | None, optional): The type of sync to use for the Scene. Defaults to None.
            ease (easing.EasingFunction | None, optional): The easing function to use for the Scene. Defaults to None.
            no_color (bool, optional): Whether to colors should be ignored. Defaults to False.
            use_xterm_colors (bool, optional): Whether to convert all colors to XTerm-256 colors. Defaults to False.

        """
        self.scene_id = scene_id
        self.is_looping = is_looping
        self.sync: Scene.SyncMetric | None = sync
        self.ease: easing.EasingFunction | None = ease
        self.no_color = no_color
        self.use_xterm_colors = use_xterm_colors
        self.frames: list[Frame] = []
        self.played_frames: list[Frame] = []
        self.frame_index_map: dict[int, Frame] = {}
        self.easing_total_steps: int = 0
        self.easing_current_step: int = 0
        self.preexisting_colors: graphics.ColorPair | None = None

    def _get_color_code(self, color: graphics.Color | None) -> str | int | None:
        """Get the color code for the given color.

        RGB colors are converted to XTerm-256 colors if use_xterm_colors is True.
        If no_color is True, returns None. Otherwise, returns the RGB color.

        Args:
            color (graphics.Color | None): the color to get the code for

        Returns:
            str | int | None: the color code

        """
        if color:
            if self.no_color:
                return None
            if self.use_xterm_colors:
                if color.xterm_color is not None:
                    return color.xterm_color
                if color.rgb_color in self.xterm_color_map:
                    return self.xterm_color_map[color.rgb_color]
                xterm_color = hexterm.hex_to_xterm(color.rgb_color)
                self.xterm_color_map[color.rgb_color] = xterm_color
                return xterm_color
            return color.rgb_color
        return None

    def add_frame(
        self,
        symbol: str,
        duration: int,
        *,
        colors: graphics.ColorPair | None = None,
        bold: bool = False,
        dim: bool = False,
        italic: bool = False,
        underline: bool = False,
        blink: bool = False,
        reverse: bool = False,
        hidden: bool = False,
        strike: bool = False,
    ) -> None:
        """Add a Frame to the Scene with the given symbol, duration, color, and graphical modes.

        Args:
            symbol (str): the symbol to show
            duration (int): the number of frames to use the Frame
            colors (graphics.ColorPair | None, optional): the colors to use. Defaults to None.
            bold (bool, optional): bold mode. Defaults to False.
            dim (bool, optional): dim mode. Defaults to False.
            italic (bool, optional): italic mode. Defaults to False.
            underline (bool, optional): underline mode. Defaults to False.
            blink (bool, optional): blink mode. Defaults to False.
            reverse (bool, optional): reverse mode. Defaults to False.
            hidden (bool, optional): hidden mode. Defaults to False.
            strike (bool, optional): strike mode. Defaults to False.

        Raises:
            FrameDurationError: if the frame duration is less than 1

        """
        # override fg and bg colors if they are set in the Scene due to existing color handling = always
        if self.preexisting_colors:
            colors = self.preexisting_colors

        # get the color code for the fg and bg colors
        if colors:
            char_vis_fg_color = self._get_color_code(colors.fg_color)
            char_vis_bg_color = self._get_color_code(colors.bg_color)
        else:
            char_vis_fg_color = None
            char_vis_bg_color = None

        if duration < 1:
            raise FrameDurationError(duration)
        char_vis = CharacterVisual(
            symbol,
            bold=bold,
            dim=dim,
            italic=italic,
            underline=underline,
            blink=blink,
            reverse=reverse,
            hidden=hidden,
            strike=strike,
            colors=colors,
            _fg_color_code=char_vis_fg_color,
            _bg_color_code=char_vis_bg_color,
        )
        frame = Frame(char_vis, duration)
        self.frames.append(frame)
        for _ in range(frame.duration):
            self.frame_index_map[self.easing_total_steps] = frame
            self.easing_total_steps += 1

    def activate(self) -> CharacterVisual:
        """Activate the Scene by returning the first frame `CharacterVisual`.

        Called by the `Animation` object when the Scene is activated.

        Raises:
            ActivateEmptySceneError: if the Scene has no frames

        Returns:
            CharacterVisual: the next CharacterVisual in the Scene

        """
        if self.frames:
            return self.frames[0].character_visual
        raise ActivateEmptySceneError(self)

    def get_next_visual(self) -> CharacterVisual:
        """Get the next CharacterVisual in the Scene.

        Retrieve the current frame from `frames`, then increment the `ticks_elapsed` attribute of the Frame.
        If the `ticks_elapsed` equals the duration of the current frame, reset `ticks_elapsed` to `0` and move the
        current frame from `frames` to `played_frames`. If the `Scene` is set to `loop` and
        all frames have been played, refill `frames` with the frames from `played_frames` and clear
        `played_frames`. Return the `CharacterVisual` of the current frame.

        Returns:
            CharacterVisual: The visual of the current frame in the Scene.

        """
        current_frame = self.frames[0]
        next_visual = current_frame.character_visual
        current_frame.ticks_elapsed += 1
        if current_frame.ticks_elapsed == current_frame.duration:
            current_frame.ticks_elapsed = 0
            self.played_frames.append(self.frames.pop(0))
            if self.is_looping and not self.frames:
                self.frames.extend(self.played_frames)
                self.played_frames.clear()
        return next_visual

    def apply_gradient_to_symbols(
        self,
        symbols: typing.Sequence[str],
        duration: int,
        *,
        fg_gradient: graphics.Gradient | None = None,
        bg_gradient: graphics.Gradient | None = None,
    ) -> None:
        """Apply a gradient effect to a sequence of symbols and add each symbol as a frame to the Scene.

        Args:
            symbols (Sequence[str]): The sequence of symbols to apply the gradient to.
            duration (int): The duration to show each frame.
            fg_gradient (graphics.Gradient | None): The foreground gradient to apply. Defaults to None.
            bg_gradient (graphics.Gradient | None): The background gradient to apply. Defaults to None.

        Returns:
            None

        Raises:
            ApplyGradientToSymbolsNoGradientsError: if both fg_gradient and bg_gradient are None
            ApplyGradientToSymbolsEmptyGradientsError: if both fg_gradient and bg_gradient are empty

        """
        T = typing.TypeVar("T")
        R = typing.TypeVar("R")

        def cyclic_distribution(
            larger_seq: typing.Sequence[T],
            smaller_seq: typing.Sequence[R],
        ) -> typing.Generator[tuple[T, R], None, None]:
            """Distributes the elements of a smaller sequence cyclically across a larger sequence with overflow.

            Example:
                cyclic_distribution([1, 2, 3, 4, 5], [a, b]) -> [(1, a), (2, a), (3, a), (4, b), (5, b)]

            Args:
                larger_seq (typing.Sequence[T]): the larger sequence
                smaller_seq (typing.Sequence[R]): the smaller sequence

            Yields:
                typing.Generator[tuple[T, R], None, None]: a generator yielding tuples of elements from the
                    larger and smaller sequences

            """
            repeat_factor = len(larger_seq) // len(smaller_seq)
            overflow_count = len(larger_seq) % len(smaller_seq)
            overflow_used = False
            smaller_index = 0
            current_repeat_factor = 0
            for larger_seq_element in larger_seq:
                if current_repeat_factor >= repeat_factor:
                    if overflow_count:
                        if overflow_used:
                            smaller_index += 1
                            current_repeat_factor = 0
                            overflow_used = False
                        else:
                            overflow_used = True
                            overflow_count -= 1
                    else:
                        smaller_index += 1
                        current_repeat_factor = 0

                current_repeat_factor += 1
                yield larger_seq_element, smaller_seq[smaller_index]

        if fg_gradient is None and bg_gradient is None:
            raise ApplyGradientToSymbolsNoGradientsError
        if not ((fg_gradient and fg_gradient.spectrum) or (bg_gradient and bg_gradient.spectrum)):
            raise ApplyGradientToSymbolsEmptyGradientsError
        for symbol in symbols:
            if len(symbol) > 1:
                raise ApplyGradientToSymbolsInvalidSymbolError(symbol)
        color_pairs: list[graphics.ColorPair] = []
        if fg_gradient and fg_gradient.spectrum and bg_gradient and bg_gradient.spectrum:
            if len(fg_gradient.spectrum) >= len(bg_gradient.spectrum):
                color_pairs = [
                    graphics.ColorPair(fg=fg_color, bg=bg_color)
                    for fg_color, bg_color in cyclic_distribution(fg_gradient.spectrum, bg_gradient.spectrum)
                ]
            else:
                color_pairs = [
                    graphics.ColorPair(fg=fg_color, bg=bg_color)
                    for bg_color, fg_color in cyclic_distribution(bg_gradient.spectrum, fg_gradient.spectrum)
                ]
        elif fg_gradient and fg_gradient.spectrum:
            color_pairs = [graphics.ColorPair(fg=color, bg=None) for color in fg_gradient.spectrum]
        elif bg_gradient and bg_gradient.spectrum:
            color_pairs = [graphics.ColorPair(fg=None, bg=color) for color in bg_gradient.spectrum]

        if len(symbols) >= len(color_pairs):
            for symbol, colors in cyclic_distribution(symbols, color_pairs):
                self.add_frame(symbol, duration, colors=colors)
        else:
            for colors, symbol in cyclic_distribution(color_pairs, symbols):
                self.add_frame(symbol, duration, colors=colors)

    def reset_scene(self) -> None:
        """Reset the Scene."""
        for sequence in self.frames:
            sequence.ticks_elapsed = 0
            self.played_frames.append(sequence)
        self.frames.clear()
        self.frames.extend(self.played_frames)
        self.played_frames.clear()

    def __eq__(self, other: object) -> bool:
        """Check if two Scene objects are equal based on their scene_id."""
        if not isinstance(other, Scene):
            return NotImplemented
        return self.scene_id == other.scene_id

    def __hash__(self) -> int:
        """Return the hash value of the Scene based on its scene_id."""
        return hash(self.scene_id)

SyncMetric

Bases: Enum

Enum for specifying the type of sync to use for a Scene.

Attributes:

Name Type Description
DISTANCE int

Sync to a Waypoint based on distance from the Waypoint

STEP int

Sync to a Waypoint based on the number of steps taken towards the Waypoint

Source code in terminaltexteffects/engine/animation.py
class SyncMetric(Enum):
    """Enum for specifying the type of sync to use for a Scene.

    Attributes:
        DISTANCE (int): Sync to a Waypoint based on distance from the Waypoint
        STEP (int): Sync to a Waypoint based on the number of steps taken towards the Waypoint

    """

    DISTANCE = auto()
    STEP = auto()

__eq__(other)

Check if two Scene objects are equal based on their scene_id.

Source code in terminaltexteffects/engine/animation.py
def __eq__(self, other: object) -> bool:
    """Check if two Scene objects are equal based on their scene_id."""
    if not isinstance(other, Scene):
        return NotImplemented
    return self.scene_id == other.scene_id

__hash__()

Return the hash value of the Scene based on its scene_id.

Source code in terminaltexteffects/engine/animation.py
def __hash__(self) -> int:
    """Return the hash value of the Scene based on its scene_id."""
    return hash(self.scene_id)

__init__(scene_id, *, is_looping=False, sync=None, ease=None, no_color=False, use_xterm_colors=False)

Initialize a Scene.

Parameters:

Name Type Description Default
scene_id str

the ID of the Scene

required
is_looping bool

Whether the Scene should loop. Defaults to False.

False
sync SyncMetric | None

The type of sync to use for the Scene. Defaults to None.

None
ease EasingFunction | None

The easing function to use for the Scene. Defaults to None.

None
no_color bool

Whether to colors should be ignored. Defaults to False.

False
use_xterm_colors bool

Whether to convert all colors to XTerm-256 colors. Defaults to False.

False
Source code in terminaltexteffects/engine/animation.py
def __init__(
    self,
    scene_id: str,
    *,
    is_looping: bool = False,
    sync: SyncMetric | None = None,
    ease: easing.EasingFunction | None = None,
    no_color: bool = False,
    use_xterm_colors: bool = False,
) -> None:
    """Initialize a Scene.

    Args:
        scene_id (str): the ID of the Scene
        is_looping (bool, optional): Whether the Scene should loop. Defaults to False.
        sync (Scene.SyncMetric | None, optional): The type of sync to use for the Scene. Defaults to None.
        ease (easing.EasingFunction | None, optional): The easing function to use for the Scene. Defaults to None.
        no_color (bool, optional): Whether to colors should be ignored. Defaults to False.
        use_xterm_colors (bool, optional): Whether to convert all colors to XTerm-256 colors. Defaults to False.

    """
    self.scene_id = scene_id
    self.is_looping = is_looping
    self.sync: Scene.SyncMetric | None = sync
    self.ease: easing.EasingFunction | None = ease
    self.no_color = no_color
    self.use_xterm_colors = use_xterm_colors
    self.frames: list[Frame] = []
    self.played_frames: list[Frame] = []
    self.frame_index_map: dict[int, Frame] = {}
    self.easing_total_steps: int = 0
    self.easing_current_step: int = 0
    self.preexisting_colors: graphics.ColorPair | None = None

activate()

Activate the Scene by returning the first frame CharacterVisual.

Called by the Animation object when the Scene is activated.

Raises:

Type Description
ActivateEmptySceneError

if the Scene has no frames

Returns:

Name Type Description
CharacterVisual CharacterVisual

the next CharacterVisual in the Scene

Source code in terminaltexteffects/engine/animation.py
def activate(self) -> CharacterVisual:
    """Activate the Scene by returning the first frame `CharacterVisual`.

    Called by the `Animation` object when the Scene is activated.

    Raises:
        ActivateEmptySceneError: if the Scene has no frames

    Returns:
        CharacterVisual: the next CharacterVisual in the Scene

    """
    if self.frames:
        return self.frames[0].character_visual
    raise ActivateEmptySceneError(self)

add_frame(symbol, duration, *, colors=None, bold=False, dim=False, italic=False, underline=False, blink=False, reverse=False, hidden=False, strike=False)

Add a Frame to the Scene with the given symbol, duration, color, and graphical modes.

Parameters:

Name Type Description Default
symbol str

the symbol to show

required
duration int

the number of frames to use the Frame

required
colors ColorPair | None

the colors to use. Defaults to None.

None
bold bool

bold mode. Defaults to False.

False
dim bool

dim mode. Defaults to False.

False
italic bool

italic mode. Defaults to False.

False
underline bool

underline mode. Defaults to False.

False
blink bool

blink mode. Defaults to False.

False
reverse bool

reverse mode. Defaults to False.

False
hidden bool

hidden mode. Defaults to False.

False
strike bool

strike mode. Defaults to False.

False

Raises:

Type Description
FrameDurationError

if the frame duration is less than 1

Source code in terminaltexteffects/engine/animation.py
def add_frame(
    self,
    symbol: str,
    duration: int,
    *,
    colors: graphics.ColorPair | None = None,
    bold: bool = False,
    dim: bool = False,
    italic: bool = False,
    underline: bool = False,
    blink: bool = False,
    reverse: bool = False,
    hidden: bool = False,
    strike: bool = False,
) -> None:
    """Add a Frame to the Scene with the given symbol, duration, color, and graphical modes.

    Args:
        symbol (str): the symbol to show
        duration (int): the number of frames to use the Frame
        colors (graphics.ColorPair | None, optional): the colors to use. Defaults to None.
        bold (bool, optional): bold mode. Defaults to False.
        dim (bool, optional): dim mode. Defaults to False.
        italic (bool, optional): italic mode. Defaults to False.
        underline (bool, optional): underline mode. Defaults to False.
        blink (bool, optional): blink mode. Defaults to False.
        reverse (bool, optional): reverse mode. Defaults to False.
        hidden (bool, optional): hidden mode. Defaults to False.
        strike (bool, optional): strike mode. Defaults to False.

    Raises:
        FrameDurationError: if the frame duration is less than 1

    """
    # override fg and bg colors if they are set in the Scene due to existing color handling = always
    if self.preexisting_colors:
        colors = self.preexisting_colors

    # get the color code for the fg and bg colors
    if colors:
        char_vis_fg_color = self._get_color_code(colors.fg_color)
        char_vis_bg_color = self._get_color_code(colors.bg_color)
    else:
        char_vis_fg_color = None
        char_vis_bg_color = None

    if duration < 1:
        raise FrameDurationError(duration)
    char_vis = CharacterVisual(
        symbol,
        bold=bold,
        dim=dim,
        italic=italic,
        underline=underline,
        blink=blink,
        reverse=reverse,
        hidden=hidden,
        strike=strike,
        colors=colors,
        _fg_color_code=char_vis_fg_color,
        _bg_color_code=char_vis_bg_color,
    )
    frame = Frame(char_vis, duration)
    self.frames.append(frame)
    for _ in range(frame.duration):
        self.frame_index_map[self.easing_total_steps] = frame
        self.easing_total_steps += 1

apply_gradient_to_symbols(symbols, duration, *, fg_gradient=None, bg_gradient=None)

Apply a gradient effect to a sequence of symbols and add each symbol as a frame to the Scene.

Parameters:

Name Type Description Default
symbols Sequence[str]

The sequence of symbols to apply the gradient to.

required
duration int

The duration to show each frame.

required
fg_gradient Gradient | None

The foreground gradient to apply. Defaults to None.

None
bg_gradient Gradient | None

The background gradient to apply. Defaults to None.

None

Returns:

Type Description
None

None

Raises:

Type Description
ApplyGradientToSymbolsNoGradientsError

if both fg_gradient and bg_gradient are None

ApplyGradientToSymbolsEmptyGradientsError

if both fg_gradient and bg_gradient are empty

Source code in terminaltexteffects/engine/animation.py
def apply_gradient_to_symbols(
    self,
    symbols: typing.Sequence[str],
    duration: int,
    *,
    fg_gradient: graphics.Gradient | None = None,
    bg_gradient: graphics.Gradient | None = None,
) -> None:
    """Apply a gradient effect to a sequence of symbols and add each symbol as a frame to the Scene.

    Args:
        symbols (Sequence[str]): The sequence of symbols to apply the gradient to.
        duration (int): The duration to show each frame.
        fg_gradient (graphics.Gradient | None): The foreground gradient to apply. Defaults to None.
        bg_gradient (graphics.Gradient | None): The background gradient to apply. Defaults to None.

    Returns:
        None

    Raises:
        ApplyGradientToSymbolsNoGradientsError: if both fg_gradient and bg_gradient are None
        ApplyGradientToSymbolsEmptyGradientsError: if both fg_gradient and bg_gradient are empty

    """
    T = typing.TypeVar("T")
    R = typing.TypeVar("R")

    def cyclic_distribution(
        larger_seq: typing.Sequence[T],
        smaller_seq: typing.Sequence[R],
    ) -> typing.Generator[tuple[T, R], None, None]:
        """Distributes the elements of a smaller sequence cyclically across a larger sequence with overflow.

        Example:
            cyclic_distribution([1, 2, 3, 4, 5], [a, b]) -> [(1, a), (2, a), (3, a), (4, b), (5, b)]

        Args:
            larger_seq (typing.Sequence[T]): the larger sequence
            smaller_seq (typing.Sequence[R]): the smaller sequence

        Yields:
            typing.Generator[tuple[T, R], None, None]: a generator yielding tuples of elements from the
                larger and smaller sequences

        """
        repeat_factor = len(larger_seq) // len(smaller_seq)
        overflow_count = len(larger_seq) % len(smaller_seq)
        overflow_used = False
        smaller_index = 0
        current_repeat_factor = 0
        for larger_seq_element in larger_seq:
            if current_repeat_factor >= repeat_factor:
                if overflow_count:
                    if overflow_used:
                        smaller_index += 1
                        current_repeat_factor = 0
                        overflow_used = False
                    else:
                        overflow_used = True
                        overflow_count -= 1
                else:
                    smaller_index += 1
                    current_repeat_factor = 0

            current_repeat_factor += 1
            yield larger_seq_element, smaller_seq[smaller_index]

    if fg_gradient is None and bg_gradient is None:
        raise ApplyGradientToSymbolsNoGradientsError
    if not ((fg_gradient and fg_gradient.spectrum) or (bg_gradient and bg_gradient.spectrum)):
        raise ApplyGradientToSymbolsEmptyGradientsError
    for symbol in symbols:
        if len(symbol) > 1:
            raise ApplyGradientToSymbolsInvalidSymbolError(symbol)
    color_pairs: list[graphics.ColorPair] = []
    if fg_gradient and fg_gradient.spectrum and bg_gradient and bg_gradient.spectrum:
        if len(fg_gradient.spectrum) >= len(bg_gradient.spectrum):
            color_pairs = [
                graphics.ColorPair(fg=fg_color, bg=bg_color)
                for fg_color, bg_color in cyclic_distribution(fg_gradient.spectrum, bg_gradient.spectrum)
            ]
        else:
            color_pairs = [
                graphics.ColorPair(fg=fg_color, bg=bg_color)
                for bg_color, fg_color in cyclic_distribution(bg_gradient.spectrum, fg_gradient.spectrum)
            ]
    elif fg_gradient and fg_gradient.spectrum:
        color_pairs = [graphics.ColorPair(fg=color, bg=None) for color in fg_gradient.spectrum]
    elif bg_gradient and bg_gradient.spectrum:
        color_pairs = [graphics.ColorPair(fg=None, bg=color) for color in bg_gradient.spectrum]

    if len(symbols) >= len(color_pairs):
        for symbol, colors in cyclic_distribution(symbols, color_pairs):
            self.add_frame(symbol, duration, colors=colors)
    else:
        for colors, symbol in cyclic_distribution(color_pairs, symbols):
            self.add_frame(symbol, duration, colors=colors)

get_next_visual()

Get the next CharacterVisual in the Scene.

Retrieve the current frame from frames, then increment the ticks_elapsed attribute of the Frame. If the ticks_elapsed equals the duration of the current frame, reset ticks_elapsed to 0 and move the current frame from frames to played_frames. If the Scene is set to loop and all frames have been played, refill frames with the frames from played_frames and clear played_frames. Return the CharacterVisual of the current frame.

Returns:

Name Type Description
CharacterVisual CharacterVisual

The visual of the current frame in the Scene.

Source code in terminaltexteffects/engine/animation.py
def get_next_visual(self) -> CharacterVisual:
    """Get the next CharacterVisual in the Scene.

    Retrieve the current frame from `frames`, then increment the `ticks_elapsed` attribute of the Frame.
    If the `ticks_elapsed` equals the duration of the current frame, reset `ticks_elapsed` to `0` and move the
    current frame from `frames` to `played_frames`. If the `Scene` is set to `loop` and
    all frames have been played, refill `frames` with the frames from `played_frames` and clear
    `played_frames`. Return the `CharacterVisual` of the current frame.

    Returns:
        CharacterVisual: The visual of the current frame in the Scene.

    """
    current_frame = self.frames[0]
    next_visual = current_frame.character_visual
    current_frame.ticks_elapsed += 1
    if current_frame.ticks_elapsed == current_frame.duration:
        current_frame.ticks_elapsed = 0
        self.played_frames.append(self.frames.pop(0))
        if self.is_looping and not self.frames:
            self.frames.extend(self.played_frames)
            self.played_frames.clear()
    return next_visual

reset_scene()

Reset the Scene.

Source code in terminaltexteffects/engine/animation.py
def reset_scene(self) -> None:
    """Reset the Scene."""
    for sequence in self.frames:
        sequence.ticks_elapsed = 0
        self.played_frames.append(sequence)
    self.frames.clear()
    self.frames.extend(self.played_frames)
    self.played_frames.clear()