Skip to content

Smoke

Demo

Quick Start

smoke.py
from terminaltexteffects.effects import Smoke

effect = Smoke("YourTextHere")
with effect.terminal_output() as terminal:
    for frame in effect:
        terminal.print(frame)

Smoke floods the canvas colorizing any characters it crosses.

Classes:

Name Description
Smoke

Smoke effect class.

SmokeConfig

Configuration dataclass for the Smoke effect.

SmokeIterator

Effect iterator for the Smoke effect.

Smoke

Bases: BaseEffect[SmokeConfig]

Smoke floods the canvas colorizing any characters it crosses.

Source code in terminaltexteffects/effects/effect_smoke.py
class Smoke(BaseEffect[SmokeConfig]):
    """Smoke floods the canvas colorizing any characters it crosses."""

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

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

SmokeConfig dataclass

Bases: BaseConfig

Effect configuration dataclass.

Source code in terminaltexteffects/effects/effect_smoke.py
@dataclass
class SmokeConfig(BaseConfig):
    """Effect configuration dataclass."""

    parser_spec: ParserSpec = ParserSpec(
        name="smoke",
        help="Smoke floods the canvas colorizing any characters it crosses.",
        description="Smoke floods the canvas colorizing any characters it crosses.",
        epilog="Example: terminaltexteffects smoke --starting-color 7A7A7A --smoke-symbols ░ ▒ ▓ ▒ ░ "
        "--smoke-gradient-stops 242424 FFFFFF --use-whole-canvas --final-gradient-stops 8A008A 00D1FF "
        "ffffff --final-gradient-steps 12 --final-gradient-direction vertical ",
    )

    starting_color: tte.Color = ArgSpec(
        name="--starting-color",
        type=argutils.ColorArg.type_parser,
        default=tte.Color("#7A7A7A"),
        metavar=argutils.ColorArg.METAVAR,
        help="Color of the text before being colorized by the smoke.",
    )  # pyright: ignore[reportAssignmentType]
    "tte.Color : Color of the text before being colorized by the smoke."

    smoke_symbols: tuple[str, ...] = ArgSpec(
        name="--smoke-symbols",
        type=argutils.Symbol.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=("░", "▒", "▓", "▒", "░"),
        metavar=argutils.Symbol.METAVAR,
        help=("Symbols to use for the smoke. Strings will be used in sequence to create an animation."),
    )  # pyright: ignore[reportAssignmentType]

    ("tuple[str, ...]: Symbols to use for the smoke. Strings will be used in sequence to create an animation.")

    smoke_gradient_stops: tuple[tte.Color, ...] = ArgSpec(
        name="--smoke-gradient-stops",
        type=argutils.ColorArg.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(tte.Color("#242424"), tte.Color("#FFFFFF")),
        metavar=argutils.ColorArg.METAVAR,
        help=(
            "Space separated, unquoted, list of colors for the smoke gradient. "
            "Smoke will transition through this gradient before moving through the final gradient stops. "
        ),
    )  # pyright: ignore[reportAssignmentType]
    (
        "tuple[Color, ...]: Space separated, unquoted, list of colors for the smoke gradient. "
        "Smoke will transition through this gradient before moving through the final gradient stops. "
    )

    use_whole_canvas: bool = ArgSpec(
        name="--use-whole-canvas",
        action="store_true",
        default=False,
        help="If True, the entire canvas will be flooded. Otherwise the effect is limited to the text boundary.",
    )  # pyright: ignore[reportAssignmentType]
    "bool : If True, the entire canvas will be flooded. Otherwise the effect is limited to the text boundary."

    final_gradient_stops: tuple[tte.Color, ...] = ArgSpec(
        name="--final-gradient-stops",
        type=argutils.ColorArg.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(tte.Color("#8A008A"), tte.Color("#00D1FF"), tte.Color("#FFFFFF")),
        metavar=argutils.ColorArg.METAVAR,
        help=(
            "Space separated, unquoted, list of colors for the character gradient (applied across the canvas). "
            "If only one color is provided, the characters will be displayed in that color."
        ),
    )  # pyright: ignore[reportAssignmentType]
    (
        "tuple[Color, ...]: Space separated, unquoted, list of colors for the character gradient "
        "(applied across the canvas). If only one color is provided, the characters will be displayed in that color."
    )

    final_gradient_steps: tuple[int, ...] | int = ArgSpec(
        name="--final-gradient-steps",
        type=argutils.PositiveInt.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(12,),
        metavar=argutils.PositiveInt.METAVAR,
        help=(
            "Space separated, unquoted, list of the number of gradient steps to use. More steps will "
            "create a smoother and longer gradient animation."
        ),
    )  # pyright: ignore[reportAssignmentType]
    (
        "tuple[int, ...] | int: Space separated, unquoted, list of the number of gradient steps to use. More "
        "steps will create a smoother and longer gradient animation."
    )

    final_gradient_direction: tte.Gradient.Direction = ArgSpec(
        name="--final-gradient-direction",
        type=argutils.GradientDirection.type_parser,
        default=tte.Gradient.Direction.VERTICAL,
        metavar=argutils.GradientDirection.METAVAR,
        help="Direction of the final gradient.",
    )  # pyright: ignore[reportAssignmentType]
    "Gradient.Direction : Direction of the final gradient."

final_gradient_direction = ArgSpec(name='--final-gradient-direction', type=(argutils.GradientDirection.type_parser), default=(tte.Gradient.Direction.VERTICAL), metavar=(argutils.GradientDirection.METAVAR), help='Direction of the final gradient.') class-attribute instance-attribute

Gradient.Direction : Direction of the final gradient.

final_gradient_steps = ArgSpec(name='--final-gradient-steps', type=(argutils.PositiveInt.type_parser), nargs='+', action=(argutils.TupleAction), default=(12,), metavar=(argutils.PositiveInt.METAVAR), help='Space separated, unquoted, list of the number of gradient steps to use. More steps will create a smoother and longer gradient animation.') class-attribute instance-attribute

tuple[int, ...] | int: Space separated, unquoted, list of the number of gradient steps to use. More steps will create a smoother and longer gradient animation.

final_gradient_stops = ArgSpec(name='--final-gradient-stops', type=(argutils.ColorArg.type_parser), nargs='+', action=(argutils.TupleAction), default=(tte.Color('#8A008A'), tte.Color('#00D1FF'), tte.Color('#FFFFFF')), metavar=(argutils.ColorArg.METAVAR), help='Space separated, unquoted, list of colors for the character gradient (applied across the canvas). If only one color is provided, the characters will be displayed in that color.') class-attribute instance-attribute

tuple[Color, ...]: Space separated, unquoted, list of colors for the character gradient (applied across the canvas). If only one color is provided, the characters will be displayed in that color.

smoke_gradient_stops = ArgSpec(name='--smoke-gradient-stops', type=(argutils.ColorArg.type_parser), nargs='+', action=(argutils.TupleAction), default=(tte.Color('#242424'), tte.Color('#FFFFFF')), metavar=(argutils.ColorArg.METAVAR), help='Space separated, unquoted, list of colors for the smoke gradient. Smoke will transition through this gradient before moving through the final gradient stops. ') class-attribute instance-attribute

tuple[Color, ...]: Space separated, unquoted, list of colors for the smoke gradient. Smoke will transition through this gradient before moving through the final gradient stops.

smoke_symbols = ArgSpec(name='--smoke-symbols', type=(argutils.Symbol.type_parser), nargs='+', action=(argutils.TupleAction), default=('░', '▒', '▓', '▒', '░'), metavar=(argutils.Symbol.METAVAR), help='Symbols to use for the smoke. Strings will be used in sequence to create an animation.') class-attribute instance-attribute

tuple[str, ...]: Symbols to use for the smoke. Strings will be used in sequence to create an animation.

starting_color = ArgSpec(name='--starting-color', type=(argutils.ColorArg.type_parser), default=(tte.Color('#7A7A7A')), metavar=(argutils.ColorArg.METAVAR), help='Color of the text before being colorized by the smoke.') class-attribute instance-attribute

tte.Color : Color of the text before being colorized by the smoke.

use_whole_canvas = ArgSpec(name='--use-whole-canvas', action='store_true', default=False, help='If True, the entire canvas will be flooded. Otherwise the effect is limited to the text boundary.') class-attribute instance-attribute

bool : If True, the entire canvas will be flooded. Otherwise the effect is limited to the text boundary.

SmokeIterator

Bases: BaseEffectIterator[SmokeConfig]

Effect iterator for the Smoke effect.

Source code in terminaltexteffects/effects/effect_smoke.py
class SmokeIterator(BaseEffectIterator[SmokeConfig]):
    """Effect iterator for the Smoke effect."""

    def __init__(self, effect: Smoke) -> None:
        """Initialize the effect iterator.

        Args:
            effect (NamedEffect): The effect to iterate over.

        """
        super().__init__(effect)
        self.pending_chars: list[tte.EffectCharacter] = []
        self.character_final_color_map: dict[tte.EffectCharacter, tte.Color] = {}
        self.gen_alg = PrimsWeighted(self.terminal, limit_to_text_boundary=not self.config.use_whole_canvas)
        self.fill_alg = BreadthFirst(
            self.terminal,
            starting_char=self.terminal.get_character_by_input_coord(
                self.terminal.canvas.random_coord(within_text_boundary=not self.config.use_whole_canvas),
            ),
            limit_to_text_boundary=not self.config.use_whole_canvas,
        )
        self.build()

    def build(self) -> None:
        """Build the effect."""
        final_gradient = tte.Gradient(*self.config.final_gradient_stops, steps=self.config.final_gradient_steps)
        final_gradient_mapping = final_gradient.build_coordinate_color_mapping(
            self.terminal.canvas.text_bottom,
            self.terminal.canvas.text_top,
            self.terminal.canvas.text_left,
            self.terminal.canvas.text_right,
            self.config.final_gradient_direction,
        )
        blk = tte.Color("#000000")
        smoke_gradient = tte.Gradient(
            *self.config.smoke_gradient_stops,
            *self.config.final_gradient_stops[::-1],
            steps=(3, 4),
        )
        for character in self.terminal.get_characters(inner_fill_chars=True, outer_fill_chars=True):
            self.terminal.set_character_visibility(character=character, is_visible=True)
            character.animation.set_appearance(
                character.input_symbol,
                colors=tte.ColorPair(fg=self.config.starting_color),
            )
            self.character_final_color_map[character] = final_gradient_mapping.get(
                character.input_coord,
                blk,
            )
            paint_gradient = tte.Gradient(
                *self.config.final_gradient_stops,
                final_gradient_mapping.get(character.input_coord, blk),
                steps=5,
            )
            paint_chars = (character.input_symbol,)
            paint_scn = character.animation.new_scene(scene_id="paint")
            paint_scn.apply_gradient_to_symbols(paint_chars, duration=5, fg_gradient=paint_gradient)

            smoke_scn = character.animation.new_scene(scene_id="smoke")
            smoke_scn.apply_gradient_to_symbols(self.config.smoke_symbols, 3, fg_gradient=smoke_gradient)
            character.event_handler.register_event(
                event=tte.Event.SCENE_COMPLETE,
                caller=smoke_scn,
                action=tte.Action.ACTIVATE_SCENE,
                target=paint_scn,
            )

        while not self.gen_alg.complete:
            self.gen_alg.step()

        # trigger effects on starting char since it will not be 'explored'
        if self.fill_alg.starting_char:
            self.fill_alg.starting_char.animation.activate_scene("smoke")
            self.active_characters.add(self.fill_alg.starting_char)

    def __next__(self) -> str:
        """Return the next frame of the effect."""
        if not self.fill_alg.complete or self.active_characters:
            if not self.fill_alg.complete:
                self.fill_alg.step()
                if self.fill_alg.explored_last_step:
                    for char in self.fill_alg.explored_last_step:
                        char.animation.activate_scene("smoke")
                        self.active_characters.add(char)

            self.update()
            return self.frame
        raise StopIteration

__init__(effect)

Initialize the effect iterator.

Parameters:

Name Type Description Default
effect NamedEffect

The effect to iterate over.

required
Source code in terminaltexteffects/effects/effect_smoke.py
def __init__(self, effect: Smoke) -> None:
    """Initialize the effect iterator.

    Args:
        effect (NamedEffect): The effect to iterate over.

    """
    super().__init__(effect)
    self.pending_chars: list[tte.EffectCharacter] = []
    self.character_final_color_map: dict[tte.EffectCharacter, tte.Color] = {}
    self.gen_alg = PrimsWeighted(self.terminal, limit_to_text_boundary=not self.config.use_whole_canvas)
    self.fill_alg = BreadthFirst(
        self.terminal,
        starting_char=self.terminal.get_character_by_input_coord(
            self.terminal.canvas.random_coord(within_text_boundary=not self.config.use_whole_canvas),
        ),
        limit_to_text_boundary=not self.config.use_whole_canvas,
    )
    self.build()

__next__()

Return the next frame of the effect.

Source code in terminaltexteffects/effects/effect_smoke.py
def __next__(self) -> str:
    """Return the next frame of the effect."""
    if not self.fill_alg.complete or self.active_characters:
        if not self.fill_alg.complete:
            self.fill_alg.step()
            if self.fill_alg.explored_last_step:
                for char in self.fill_alg.explored_last_step:
                    char.animation.activate_scene("smoke")
                    self.active_characters.add(char)

        self.update()
        return self.frame
    raise StopIteration

build()

Build the effect.

Source code in terminaltexteffects/effects/effect_smoke.py
def build(self) -> None:
    """Build the effect."""
    final_gradient = tte.Gradient(*self.config.final_gradient_stops, steps=self.config.final_gradient_steps)
    final_gradient_mapping = final_gradient.build_coordinate_color_mapping(
        self.terminal.canvas.text_bottom,
        self.terminal.canvas.text_top,
        self.terminal.canvas.text_left,
        self.terminal.canvas.text_right,
        self.config.final_gradient_direction,
    )
    blk = tte.Color("#000000")
    smoke_gradient = tte.Gradient(
        *self.config.smoke_gradient_stops,
        *self.config.final_gradient_stops[::-1],
        steps=(3, 4),
    )
    for character in self.terminal.get_characters(inner_fill_chars=True, outer_fill_chars=True):
        self.terminal.set_character_visibility(character=character, is_visible=True)
        character.animation.set_appearance(
            character.input_symbol,
            colors=tte.ColorPair(fg=self.config.starting_color),
        )
        self.character_final_color_map[character] = final_gradient_mapping.get(
            character.input_coord,
            blk,
        )
        paint_gradient = tte.Gradient(
            *self.config.final_gradient_stops,
            final_gradient_mapping.get(character.input_coord, blk),
            steps=5,
        )
        paint_chars = (character.input_symbol,)
        paint_scn = character.animation.new_scene(scene_id="paint")
        paint_scn.apply_gradient_to_symbols(paint_chars, duration=5, fg_gradient=paint_gradient)

        smoke_scn = character.animation.new_scene(scene_id="smoke")
        smoke_scn.apply_gradient_to_symbols(self.config.smoke_symbols, 3, fg_gradient=smoke_gradient)
        character.event_handler.register_event(
            event=tte.Event.SCENE_COMPLETE,
            caller=smoke_scn,
            action=tte.Action.ACTIVATE_SCENE,
            target=paint_scn,
        )

    while not self.gen_alg.complete:
        self.gen_alg.step()

    # trigger effects on starting char since it will not be 'explored'
    if self.fill_alg.starting_char:
        self.fill_alg.starting_char.animation.activate_scene("smoke")
        self.active_characters.add(self.fill_alg.starting_char)

get_effect_resources()

Get the command, effect class, and configuration class for the effect.

Returns:

Type Description
tuple[str, type[BaseEffect], type[BaseConfig]]

tuple[str, type[BaseEffect], type[BaseConfig]]: The command name, effect class, and configuration class.

Source code in terminaltexteffects/effects/effect_smoke.py
def get_effect_resources() -> tuple[str, type[BaseEffect], type[BaseConfig]]:
    """Get the command, effect class, and configuration class for the effect.

    Returns:
        tuple[str, type[BaseEffect], type[BaseConfig]]: The command name, effect class, and configuration class.

    """
    return "smoke", Smoke, SmokeConfig