Skip to content

Step 1: Scaffold

In this tutorial you rebuild the built-in Spray effect, one feature per step. Spray releases characters a few at a time from one point on the canvas and sends each one to its place in the text, fading it into a color gradient as it travels. This is the built-in effect running on the input you use throughout the tutorial:

tte --input-file tutorial_input.txt spray

The built-in Spray effect on the tutorial input

By the last step, tte --input-file tutorial_input.txt myspray looks the same.

Your copy is called MySpray and runs as tte myspray. TTE rejects two effects with the same command name, so it can't be called spray.

In this step you write the smallest effect that runs: it shows the input text for one frame and stops.

Engine chapters used: Architecture, Frame loop.

Create the input file

Every step runs on the same three lines of text. Save them as tutorial_input.txt in the directory you run tte from:

tutorial_input.txt
Welcome to the TTE Effect Guide.
Every character in this text is an EffectCharacter.
This tutorial rebuilds the Spray effect, one step at a time.

Create the module

Create myspray.py in your community effects directory:

mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/terminaltexteffects/effects"

Then add this code to ${XDG_CONFIG_HOME:-$HOME/.config}/terminaltexteffects/effects/myspray.py. Each part is explained below.

myspray.py
"""MySpray, tutorial step 1: the smallest effect that runs.

The effect shows the input text in place for one frame. Later steps build it up into a copy of the built-in
Spray effect.
"""

from __future__ import annotations

from dataclasses import dataclass

from terminaltexteffects.engine.base_config import BaseConfig
from terminaltexteffects.engine.base_effect import BaseEffect, BaseEffectIterator
from terminaltexteffects.utils import argutils


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


@dataclass
class MySprayConfig(BaseConfig):
    """Configuration for the MySpray effect. It has no options yet."""

    parser_spec = argutils.ParserSpec(
        name="myspray",
        help="Sprays the characters from a single point.",
        description="myspray | Sprays the characters from a single point.",
        epilog="Example: tte myspray",
    )


class MySprayIterator(BaseEffectIterator[MySprayConfig]):
    """Produce the frames of the MySpray effect."""

    def __init__(self, effect: MySpray) -> None:
        """Set up every character before the first frame."""
        super().__init__(effect)
        self.shown = False
        for character in self.terminal.get_characters():
            self.terminal.set_character_visibility(character, is_visible=True)

    def __next__(self) -> str:
        """Return one frame showing the text, then stop."""
        if not self.shown:
            self.shown = True
            return self.frame
        raise StopIteration


class MySpray(BaseEffect[MySprayConfig]):
    """Sprays the characters from a single point."""

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

    @property
    def _iterator_cls(self) -> type[MySprayIterator]:
        return MySprayIterator

What each part does

get_effect_resources()

TTE calls this function when it loads your module. It returns three things: the command name, the effect class, and the configuration class. The command name must match parser_spec.name in the configuration.

MySprayConfig

The configuration is a dataclass that subclasses BaseConfig. It has no options yet, only parser_spec, which describes the myspray subcommand: its one-line help in tte -h, and the description and epilog shown by tte myspray -h. You add options in Step 6.

MySprayIterator

The iterator does the work. Its __init__ runs once, before the first frame:

  • super().__init__(effect) copies the configuration and builds self.terminal, which holds one character for each symbol of the input except spaces.
  • The loop makes every character visible. Characters start hidden, so without this the frame would be blank.

__next__ returns self.frame, the rendered canvas, the first time it is called, then raises StopIteration to end the effect. It never calls self.update(), because no character has anything to animate yet. The characters stay inactive, but the terminal draws every visible character whether or not it is active, so the text still appears. See Active and visible are separate.

MySpray

The effect class is the public face of your effect. Its two properties tell BaseEffect which configuration and iterator classes to use. You rarely add anything else to it.

Run it

From the directory that holds tutorial_input.txt, run:

tte --input-file tutorial_input.txt myspray

The text appears at once, with no animation, and the command exits:

MySpray after step 1: the text appears in one frame

Your effect is also listed in tte -h, and tte myspray -h shows its help:

usage: tte myspray [-h]

myspray | Sprays the characters from a single point.

options:
  -h, --help  show this help message and exit

Example: tte myspray

If tte myspray reports an invalid choice, TTE didn't load your module. Check that the file is directly inside the effects directory and that get_effect_resources() is spelled correctly.

Next step

Step 2 holds the characters back and releases a few each frame, so the text builds up over time.