Skip to content

Blackhole

Demo

Quick Start

blackhole.py
from terminaltexteffects.effects.effect_blackhole import Blackhole

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

Creates a blackhole in a starfield, consumes the stars, explodes the input data back into position.

Classes:

Name Description
BlackholeConfig

Configuration for the Blackhole effect.

Blackhole

Creates a blackhole in a starfield, consumes the stars, explodes the input data back into position.

BlackholeIterator

Iterator for the Blackhole effect. Does not normally need to be called directly.

Blackhole

Bases: BaseEffect[BlackholeConfig]

Creates a blackhole in a starfield, consumes the stars, explodes the input data back into position.

Attributes:

Name Type Description
effect_config BlackholeConfig

Configuration for the Blackhole effect.

terminal_config TerminalConfig

Configuration for the terminal.

Source code in terminaltexteffects/effects/effect_blackhole.py
class Blackhole(BaseEffect[BlackholeConfig]):
    """Creates a blackhole in a starfield, consumes the stars, explodes the input data back into position.

    Attributes:
        effect_config (BlackholeConfig): Configuration for the Blackhole effect.
        terminal_config (TerminalConfig): Configuration for the terminal.

    """

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

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

BlackholeConfig dataclass

Bases: BaseConfig

Configuration for the Blackhole effect.

Attributes:

Name Type Description
blackhole_color Color

Color for the stars that comprise the blackhole border.

star_colors tuple[Color, ...]

Tuple of colors from which character colors will be chosen and applied after the explosion, but before the cooldown to final color.

final_gradient_stops tuple[Color, ...]

Tuple of colors for the character gradient. If only one color is provided, the characters will be displayed in that color.

final_gradient_steps tuple[int, ...] | int

Tuple of the number of gradient steps to use. More steps will create a smoother and longer gradient animation. Valid values are n > 0.

final_gradient_direction Direction

Direction of the final gradient.

Source code in terminaltexteffects/effects/effect_blackhole.py
@dataclass
class BlackholeConfig(BaseConfig):
    """Configuration for the Blackhole effect.

    Attributes:
        blackhole_color (Color): Color for the stars that comprise the blackhole border.
        star_colors (tuple[Color, ...]): Tuple of colors from which character colors will be chosen and applied
            after the explosion, but before the cooldown to final color.
        final_gradient_stops (tuple[Color, ...]): Tuple of colors for the character gradient. If only one color is
            provided, the characters will be displayed in that color.
        final_gradient_steps (tuple[int, ...] | int): Tuple of the number of gradient steps to use. More steps will
            create a smoother and longer gradient animation. Valid values are n > 0.
        final_gradient_direction (Gradient.Direction): Direction of the final gradient.

    """

    parser_spec: ParserSpec = ParserSpec(
        name="blackhole",
        help="Characters are consumed by a black hole and explode outwards.",
        description="blackhole | Characters are consumed by a black hole and explode outwards.",
        epilog="Example: terminaltexteffects blackhole --star-colors ffcc0d ff7326 ff194d bf2669 702a8c 049dbf "
        "--final-gradient-stops 8A008A 00D1FF FFFFFF --final-gradient-steps 12 --final-gradient-direction vertical",
    )
    blackhole_color: Color = ArgSpec(
        name="--blackhole-color",
        type=argutils.ColorArg.type_parser,
        default=Color("#ffffff"),
        metavar=argutils.ColorArg.METAVAR,
        help="Color for the stars that comprise the blackhole border.",
    )  # pyright: ignore[reportAssignmentType]

    "Color : Color for the stars that comprise the blackhole border."

    star_colors: tuple[Color, ...] = ArgSpec(
        name="--star-colors",
        type=argutils.ColorArg.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(
            Color("#ffcc0d"),
            Color("#ff7326"),
            Color("#ff194d"),
            Color("#bf2669"),
            Color("#702a8c"),
            Color("#049dbf"),
        ),
        metavar=argutils.ColorArg.METAVAR,
        help="List of colors from which character colors will be chosen and applied after the explosion, but before "
        "the cooldown to final color.",
    )  # pyright: ignore[reportAssignmentType]

    (
        "tuple[Color, ...] : Tuple of colors from which character colors will be chosen and applied after the "
        "explosion, but before the cooldown to final color."
    )

    final_gradient_stops: tuple[Color, ...] = ArgSpec(
        name="--final-gradient-stops",
        type=argutils.ColorArg.type_parser,
        nargs="+",
        action=argutils.TupleAction,
        default=(Color("#8A008A"), Color("#00D1FF"), 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, ...] : Tuple of colors for the final color gradient. 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=9,
        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 : Int or Tuple of ints for the number of gradient steps to use. More steps will create "
        "a smoother and longer gradient animation."
    )

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

    "Gradient.Direction : Direction of the final gradient."

blackhole_color = ArgSpec(name='--blackhole-color', type=(argutils.ColorArg.type_parser), default=(Color('#ffffff')), metavar=(argutils.ColorArg.METAVAR), help='Color for the stars that comprise the blackhole border.') class-attribute instance-attribute

Color : Color for the stars that comprise the blackhole border.

final_gradient_direction = ArgSpec(name='--final-gradient-direction', type=(argutils.GradientDirection.type_parser), default=(Gradient.Direction.DIAGONAL), 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=9, 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 : Int or Tuple of ints for 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=(Color('#8A008A'), Color('#00D1FF'), 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, ...] : Tuple of colors for the final color gradient. If only one color is provided, the characters will be displayed in that color.

star_colors = ArgSpec(name='--star-colors', type=(argutils.ColorArg.type_parser), nargs='+', action=(argutils.TupleAction), default=(Color('#ffcc0d'), Color('#ff7326'), Color('#ff194d'), Color('#bf2669'), Color('#702a8c'), Color('#049dbf')), metavar=(argutils.ColorArg.METAVAR), help='List of colors from which character colors will be chosen and applied after the explosion, but before the cooldown to final color.') class-attribute instance-attribute

tuple[Color, ...] : Tuple of colors from which character colors will be chosen and applied after the explosion, but before the cooldown to final color.

BlackholeIterator

Bases: BaseEffectIterator[BlackholeConfig]

Iterator for the Blackhole effect.

Source code in terminaltexteffects/effects/effect_blackhole.py
class BlackholeIterator(BaseEffectIterator[BlackholeConfig]):
    """Iterator for the Blackhole effect."""

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

        Args:
            effect (Blackhole): The Blackhole effect instance.

        """
        super().__init__(effect)
        self.pending_chars: list[EffectCharacter] = []
        self.blackhole_chars: list[EffectCharacter] = []
        self.awaiting_consumption_chars: list[EffectCharacter] = []
        self.blackhole_radius = max(
            min(
                round(self.terminal.canvas.width * 0.3),
                round(self.terminal.canvas.height * 0.20),
            ),
            3,
        )
        self.character_final_color_map: dict[EffectCharacter, Color] = {}
        self.preexisting_colors_present = any(
            any((character.animation.input_fg_color, character.animation.input_bg_color))
            for character in self.terminal.get_characters()
        )
        self.build()

    def prepare_blackhole(self) -> None:
        """Prepare the blackhole and starfield characters."""
        star_symbols = ["*", "'", "`", "¤", "•", "°", "·"]
        starfield_colors = Gradient(Color("#4a4a4d"), Color("#ffffff"), steps=6).spectrum
        gradient_map = {}
        for color in starfield_colors:
            gradient_map[color] = Gradient(color, Color("#000000"), steps=10)
        available_chars = list(self.terminal._input_characters)
        while len(self.blackhole_chars) < self.blackhole_radius * 3 and available_chars:
            self.blackhole_chars.append(available_chars.pop(random.randrange(0, len(available_chars))))
        black_hole_ring_positions = geometry.find_coords_on_circle(
            self.terminal.canvas.center,
            self.blackhole_radius,
            len(self.blackhole_chars),
        )
        for position_index, character in enumerate(self.blackhole_chars):
            starting_pos = black_hole_ring_positions[position_index]
            blackhole_path = character.motion.new_path(path_id="blackhole", speed=0.7, ease=easing.in_out_sine)
            blackhole_path.new_waypoint(starting_pos)
            blackhole_scn = character.animation.new_scene(scene_id="blackhole")
            blackhole_scn.add_frame("*", 1, colors=ColorPair(fg=self.config.blackhole_color))
            character.event_handler.register_event(
                EventHandler.Event.PATH_ACTIVATED,
                blackhole_path,
                EventHandler.Action.SET_LAYER,
                1,
            )
            # make rotation waypoints
            blackhole_rotation_path = character.motion.new_path(path_id="blackhole_rotation", speed=0.45, loop=True)
            for coord in black_hole_ring_positions[position_index:] + black_hole_ring_positions[:position_index]:
                blackhole_rotation_path.new_waypoint(coord, waypoint_id=str(len(blackhole_rotation_path.waypoints)))
        for character in self.terminal.get_characters():
            self.terminal.set_character_visibility(character, is_visible=True)
            starting_scn = character.animation.new_scene()
            star_symbol = random.choice(star_symbols)
            star_color = random.choice(starfield_colors)
            starting_scn.add_frame(star_symbol, 1, colors=ColorPair(fg=star_color))
            character.animation.activate_scene(starting_scn)
            if character not in self.blackhole_chars:
                starfield_coord = self.terminal.canvas.random_coord()
                character.motion.set_coordinate(starfield_coord)

                singularity_path = character.motion.new_path(
                    path_id="singularity",
                    speed=random.uniform(0.17, 0.30),
                    ease=easing.in_expo,
                )
                singularity_path.new_waypoint(self.terminal.canvas.center)
                consumed_scn = character.animation.new_scene()
                for color in gradient_map[star_color]:
                    consumed_scn.add_frame(star_symbol, 1, colors=ColorPair(fg=color))
                consumed_scn.add_frame(" ", 1)
                consumed_scn.sync = Scene.SyncMetric.DISTANCE
                character.event_handler.register_event(
                    EventHandler.Event.PATH_ACTIVATED,
                    singularity_path,
                    EventHandler.Action.SET_LAYER,
                    2,
                )
                character.event_handler.register_event(
                    EventHandler.Event.PATH_ACTIVATED,
                    singularity_path,
                    EventHandler.Action.ACTIVATE_SCENE,
                    consumed_scn,
                )
                self.awaiting_consumption_chars.append(character)
        random.shuffle(self.awaiting_consumption_chars)

    def rotate_blackhole(self) -> None:
        """Rotate the blackhole characters."""
        for character in self.blackhole_chars:
            character.motion.activate_path("blackhole_rotation")
            self.active_characters.add(character)

    def collapse_blackhole(self) -> None:
        """Collapse the blackhole characters."""
        black_hole_ring_positions = geometry.find_coords_on_circle(
            self.terminal.canvas.center,
            self.blackhole_radius + 3,
            len(self.blackhole_chars),
        )
        unstable_symbols = ["◦", "◎", "◉", "●", "◉", "◎", "◦"]
        point_char_made = False
        for character in self.blackhole_chars:
            next_pos = black_hole_ring_positions.pop(0)
            expand_path = character.motion.new_path(speed=0.2, ease=easing.in_expo)
            expand_path.new_waypoint(next_pos)
            collapse_path = character.motion.new_path(speed=0.3, ease=easing.in_expo)
            collapse_path.new_waypoint(self.terminal.canvas.center)
            character.event_handler.register_event(
                EventHandler.Event.PATH_COMPLETE,
                expand_path,
                EventHandler.Action.ACTIVATE_PATH,
                collapse_path,
            )
            if not point_char_made:
                point_scn = character.animation.new_scene()
                for _ in range(3):
                    for symbol in unstable_symbols:
                        point_scn.add_frame(
                            symbol,
                            3,
                            colors=ColorPair(fg=random.choice(self.config.star_colors)),
                        )
                character.event_handler.register_event(
                    EventHandler.Event.PATH_COMPLETE,
                    collapse_path,
                    EventHandler.Action.ACTIVATE_SCENE,
                    point_scn,
                )
                character.event_handler.register_event(
                    EventHandler.Event.PATH_COMPLETE,
                    collapse_path,
                    EventHandler.Action.SET_LAYER,
                    3,
                )
                point_char_made = True

            character.motion.activate_path(expand_path)
            self.active_characters.add(character)

    def explode_singularity(self) -> None:
        """Explode the singularity characters."""
        star_colors = [
            Color("#ffcc0d"),
            Color("#ff7326"),
            Color("#ff194d"),
            Color("#bf2669"),
            Color("#702a8c"),
            Color("#049dbf"),
        ]
        for character in self.terminal.get_characters():
            nearby_coord = geometry.find_coords_on_circle(character.input_coord, 3, 5)[random.randrange(0, 5)]
            nearby_path = character.motion.new_path(speed=random.randint(3, 4) / 10, ease=easing.out_expo)
            nearby_path.new_waypoint(nearby_coord)
            input_path = character.motion.new_path(speed=random.randint(4, 6) / 100, ease=easing.in_cubic)
            input_path.new_waypoint(character.input_coord)
            explode_scn = character.animation.new_scene()
            explode_star_color = random.choice(star_colors)
            explode_scn.add_frame(character.input_symbol, 1, colors=ColorPair(fg=explode_star_color))
            cooling_scn = character.animation.new_scene()
            if self.terminal.config.existing_color_handling == "dynamic" and self.preexisting_colors_present:
                if not any((character.animation.input_fg_color, character.animation.input_bg_color)):
                    cooling_scn.add_frame(character.input_symbol, 1, colors=ColorPair())
                else:
                    cooling_gradient_fg = None
                    cooling_gradient_bg = None
                    if character.animation.input_fg_color:
                        cooling_gradient_fg = Gradient(
                            explode_star_color,
                            character.animation.input_fg_color,
                            steps=10,
                        )
                    if character.animation.input_bg_color:
                        cooling_gradient_bg = Gradient(
                            explode_star_color,
                            character.animation.input_bg_color,
                            steps=10,
                        )
                    cooling_scn.apply_gradient_to_symbols(
                        character.input_symbol,
                        20,
                        fg_gradient=cooling_gradient_fg,
                        bg_gradient=cooling_gradient_bg,
                    )
            else:
                cooling_gradient = Gradient(explode_star_color, self.character_final_color_map[character], steps=10)
                cooling_scn.apply_gradient_to_symbols(character.input_symbol, 20, fg_gradient=cooling_gradient)
            character.event_handler.register_event(
                EventHandler.Event.PATH_COMPLETE,
                nearby_path,
                EventHandler.Action.ACTIVATE_PATH,
                input_path,
            )
            character.event_handler.register_event(
                EventHandler.Event.PATH_COMPLETE,
                nearby_path,
                EventHandler.Action.ACTIVATE_SCENE,
                cooling_scn,
            )
            character.animation.activate_scene(explode_scn)
            character.motion.activate_path(nearby_path)
            self.active_characters.add(character)

    def build(self) -> None:
        """Build the Blackhole effect."""
        final_gradient = 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,
        )
        for character in self.terminal.get_characters():
            self.character_final_color_map[character] = final_gradient_mapping[character.input_coord]
        self.prepare_blackhole()
        self.formation_delay = max(100 // len(self.blackhole_chars), 6)
        self.f_delay = self.formation_delay
        self.phase = "forming"
        self.awaiting_blackhole_chars = list(self.blackhole_chars)

    def __next__(self) -> str:
        """Return the next frame in the Blackhole effect."""
        if self.active_characters or self.phase != "complete":
            if self.phase == "forming":
                if self.awaiting_blackhole_chars:
                    if not self.f_delay:
                        next_char = self.awaiting_blackhole_chars.pop(0)
                        next_char.motion.activate_path("blackhole")
                        next_char.animation.activate_scene("blackhole")
                        self.active_characters.add(next_char)
                        self.f_delay = self.formation_delay
                    else:
                        self.f_delay -= 1
                elif not self.active_characters:
                    self.rotate_blackhole()
                    self.phase = "consuming"
            elif self.phase == "consuming":
                if self.awaiting_consumption_chars:
                    for char in self.awaiting_consumption_chars:
                        char.motion.activate_path("singularity")

                        self.active_characters.add(char)
                    self.awaiting_consumption_chars.clear()

                elif all(character in self.blackhole_chars for character in self.active_characters):
                    self.phase = "collapsing"

            elif self.phase == "collapsing":
                self.collapse_blackhole()
                self.phase = "exploding"
            elif self.phase == "exploding" and all(
                character.motion.active_path is None and character.animation.active_scene is None
                for character in self.blackhole_chars
            ):
                self.explode_singularity()
                self.phase = "complete"

            self.update()
            return self.frame

        raise StopIteration

__init__(effect)

Initialize the Blackhole effect iterator.

Parameters:

Name Type Description Default
effect Blackhole

The Blackhole effect instance.

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

    Args:
        effect (Blackhole): The Blackhole effect instance.

    """
    super().__init__(effect)
    self.pending_chars: list[EffectCharacter] = []
    self.blackhole_chars: list[EffectCharacter] = []
    self.awaiting_consumption_chars: list[EffectCharacter] = []
    self.blackhole_radius = max(
        min(
            round(self.terminal.canvas.width * 0.3),
            round(self.terminal.canvas.height * 0.20),
        ),
        3,
    )
    self.character_final_color_map: dict[EffectCharacter, Color] = {}
    self.preexisting_colors_present = any(
        any((character.animation.input_fg_color, character.animation.input_bg_color))
        for character in self.terminal.get_characters()
    )
    self.build()

__next__()

Return the next frame in the Blackhole effect.

Source code in terminaltexteffects/effects/effect_blackhole.py
def __next__(self) -> str:
    """Return the next frame in the Blackhole effect."""
    if self.active_characters or self.phase != "complete":
        if self.phase == "forming":
            if self.awaiting_blackhole_chars:
                if not self.f_delay:
                    next_char = self.awaiting_blackhole_chars.pop(0)
                    next_char.motion.activate_path("blackhole")
                    next_char.animation.activate_scene("blackhole")
                    self.active_characters.add(next_char)
                    self.f_delay = self.formation_delay
                else:
                    self.f_delay -= 1
            elif not self.active_characters:
                self.rotate_blackhole()
                self.phase = "consuming"
        elif self.phase == "consuming":
            if self.awaiting_consumption_chars:
                for char in self.awaiting_consumption_chars:
                    char.motion.activate_path("singularity")

                    self.active_characters.add(char)
                self.awaiting_consumption_chars.clear()

            elif all(character in self.blackhole_chars for character in self.active_characters):
                self.phase = "collapsing"

        elif self.phase == "collapsing":
            self.collapse_blackhole()
            self.phase = "exploding"
        elif self.phase == "exploding" and all(
            character.motion.active_path is None and character.animation.active_scene is None
            for character in self.blackhole_chars
        ):
            self.explode_singularity()
            self.phase = "complete"

        self.update()
        return self.frame

    raise StopIteration

build()

Build the Blackhole effect.

Source code in terminaltexteffects/effects/effect_blackhole.py
def build(self) -> None:
    """Build the Blackhole effect."""
    final_gradient = 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,
    )
    for character in self.terminal.get_characters():
        self.character_final_color_map[character] = final_gradient_mapping[character.input_coord]
    self.prepare_blackhole()
    self.formation_delay = max(100 // len(self.blackhole_chars), 6)
    self.f_delay = self.formation_delay
    self.phase = "forming"
    self.awaiting_blackhole_chars = list(self.blackhole_chars)

collapse_blackhole()

Collapse the blackhole characters.

Source code in terminaltexteffects/effects/effect_blackhole.py
def collapse_blackhole(self) -> None:
    """Collapse the blackhole characters."""
    black_hole_ring_positions = geometry.find_coords_on_circle(
        self.terminal.canvas.center,
        self.blackhole_radius + 3,
        len(self.blackhole_chars),
    )
    unstable_symbols = ["◦", "◎", "◉", "●", "◉", "◎", "◦"]
    point_char_made = False
    for character in self.blackhole_chars:
        next_pos = black_hole_ring_positions.pop(0)
        expand_path = character.motion.new_path(speed=0.2, ease=easing.in_expo)
        expand_path.new_waypoint(next_pos)
        collapse_path = character.motion.new_path(speed=0.3, ease=easing.in_expo)
        collapse_path.new_waypoint(self.terminal.canvas.center)
        character.event_handler.register_event(
            EventHandler.Event.PATH_COMPLETE,
            expand_path,
            EventHandler.Action.ACTIVATE_PATH,
            collapse_path,
        )
        if not point_char_made:
            point_scn = character.animation.new_scene()
            for _ in range(3):
                for symbol in unstable_symbols:
                    point_scn.add_frame(
                        symbol,
                        3,
                        colors=ColorPair(fg=random.choice(self.config.star_colors)),
                    )
            character.event_handler.register_event(
                EventHandler.Event.PATH_COMPLETE,
                collapse_path,
                EventHandler.Action.ACTIVATE_SCENE,
                point_scn,
            )
            character.event_handler.register_event(
                EventHandler.Event.PATH_COMPLETE,
                collapse_path,
                EventHandler.Action.SET_LAYER,
                3,
            )
            point_char_made = True

        character.motion.activate_path(expand_path)
        self.active_characters.add(character)

explode_singularity()

Explode the singularity characters.

Source code in terminaltexteffects/effects/effect_blackhole.py
def explode_singularity(self) -> None:
    """Explode the singularity characters."""
    star_colors = [
        Color("#ffcc0d"),
        Color("#ff7326"),
        Color("#ff194d"),
        Color("#bf2669"),
        Color("#702a8c"),
        Color("#049dbf"),
    ]
    for character in self.terminal.get_characters():
        nearby_coord = geometry.find_coords_on_circle(character.input_coord, 3, 5)[random.randrange(0, 5)]
        nearby_path = character.motion.new_path(speed=random.randint(3, 4) / 10, ease=easing.out_expo)
        nearby_path.new_waypoint(nearby_coord)
        input_path = character.motion.new_path(speed=random.randint(4, 6) / 100, ease=easing.in_cubic)
        input_path.new_waypoint(character.input_coord)
        explode_scn = character.animation.new_scene()
        explode_star_color = random.choice(star_colors)
        explode_scn.add_frame(character.input_symbol, 1, colors=ColorPair(fg=explode_star_color))
        cooling_scn = character.animation.new_scene()
        if self.terminal.config.existing_color_handling == "dynamic" and self.preexisting_colors_present:
            if not any((character.animation.input_fg_color, character.animation.input_bg_color)):
                cooling_scn.add_frame(character.input_symbol, 1, colors=ColorPair())
            else:
                cooling_gradient_fg = None
                cooling_gradient_bg = None
                if character.animation.input_fg_color:
                    cooling_gradient_fg = Gradient(
                        explode_star_color,
                        character.animation.input_fg_color,
                        steps=10,
                    )
                if character.animation.input_bg_color:
                    cooling_gradient_bg = Gradient(
                        explode_star_color,
                        character.animation.input_bg_color,
                        steps=10,
                    )
                cooling_scn.apply_gradient_to_symbols(
                    character.input_symbol,
                    20,
                    fg_gradient=cooling_gradient_fg,
                    bg_gradient=cooling_gradient_bg,
                )
        else:
            cooling_gradient = Gradient(explode_star_color, self.character_final_color_map[character], steps=10)
            cooling_scn.apply_gradient_to_symbols(character.input_symbol, 20, fg_gradient=cooling_gradient)
        character.event_handler.register_event(
            EventHandler.Event.PATH_COMPLETE,
            nearby_path,
            EventHandler.Action.ACTIVATE_PATH,
            input_path,
        )
        character.event_handler.register_event(
            EventHandler.Event.PATH_COMPLETE,
            nearby_path,
            EventHandler.Action.ACTIVATE_SCENE,
            cooling_scn,
        )
        character.animation.activate_scene(explode_scn)
        character.motion.activate_path(nearby_path)
        self.active_characters.add(character)

prepare_blackhole()

Prepare the blackhole and starfield characters.

Source code in terminaltexteffects/effects/effect_blackhole.py
def prepare_blackhole(self) -> None:
    """Prepare the blackhole and starfield characters."""
    star_symbols = ["*", "'", "`", "¤", "•", "°", "·"]
    starfield_colors = Gradient(Color("#4a4a4d"), Color("#ffffff"), steps=6).spectrum
    gradient_map = {}
    for color in starfield_colors:
        gradient_map[color] = Gradient(color, Color("#000000"), steps=10)
    available_chars = list(self.terminal._input_characters)
    while len(self.blackhole_chars) < self.blackhole_radius * 3 and available_chars:
        self.blackhole_chars.append(available_chars.pop(random.randrange(0, len(available_chars))))
    black_hole_ring_positions = geometry.find_coords_on_circle(
        self.terminal.canvas.center,
        self.blackhole_radius,
        len(self.blackhole_chars),
    )
    for position_index, character in enumerate(self.blackhole_chars):
        starting_pos = black_hole_ring_positions[position_index]
        blackhole_path = character.motion.new_path(path_id="blackhole", speed=0.7, ease=easing.in_out_sine)
        blackhole_path.new_waypoint(starting_pos)
        blackhole_scn = character.animation.new_scene(scene_id="blackhole")
        blackhole_scn.add_frame("*", 1, colors=ColorPair(fg=self.config.blackhole_color))
        character.event_handler.register_event(
            EventHandler.Event.PATH_ACTIVATED,
            blackhole_path,
            EventHandler.Action.SET_LAYER,
            1,
        )
        # make rotation waypoints
        blackhole_rotation_path = character.motion.new_path(path_id="blackhole_rotation", speed=0.45, loop=True)
        for coord in black_hole_ring_positions[position_index:] + black_hole_ring_positions[:position_index]:
            blackhole_rotation_path.new_waypoint(coord, waypoint_id=str(len(blackhole_rotation_path.waypoints)))
    for character in self.terminal.get_characters():
        self.terminal.set_character_visibility(character, is_visible=True)
        starting_scn = character.animation.new_scene()
        star_symbol = random.choice(star_symbols)
        star_color = random.choice(starfield_colors)
        starting_scn.add_frame(star_symbol, 1, colors=ColorPair(fg=star_color))
        character.animation.activate_scene(starting_scn)
        if character not in self.blackhole_chars:
            starfield_coord = self.terminal.canvas.random_coord()
            character.motion.set_coordinate(starfield_coord)

            singularity_path = character.motion.new_path(
                path_id="singularity",
                speed=random.uniform(0.17, 0.30),
                ease=easing.in_expo,
            )
            singularity_path.new_waypoint(self.terminal.canvas.center)
            consumed_scn = character.animation.new_scene()
            for color in gradient_map[star_color]:
                consumed_scn.add_frame(star_symbol, 1, colors=ColorPair(fg=color))
            consumed_scn.add_frame(" ", 1)
            consumed_scn.sync = Scene.SyncMetric.DISTANCE
            character.event_handler.register_event(
                EventHandler.Event.PATH_ACTIVATED,
                singularity_path,
                EventHandler.Action.SET_LAYER,
                2,
            )
            character.event_handler.register_event(
                EventHandler.Event.PATH_ACTIVATED,
                singularity_path,
                EventHandler.Action.ACTIVATE_SCENE,
                consumed_scn,
            )
            self.awaiting_consumption_chars.append(character)
    random.shuffle(self.awaiting_consumption_chars)

rotate_blackhole()

Rotate the blackhole characters.

Source code in terminaltexteffects/effects/effect_blackhole.py
def rotate_blackhole(self) -> None:
    """Rotate the blackhole characters."""
    for character in self.blackhole_chars:
        character.motion.activate_path("blackhole_rotation")
        self.active_characters.add(character)

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_blackhole.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 "blackhole", Blackhole, BlackholeConfig