mscene.extrasΒΆ

from manim import *


class DashFlow(DashedVMobject):
    """A dashed VMobject with its dash offset continuously shifting for a flowing effect.

    Args:
        vmob (VMobject): The VMobject to animate.
        rate (float): The rate of shifting dash offset over time (default: 1.0).
        **kwargs: Additional keyword arguments passed to DashedVMobject.

    Methods:
        play():
            Resume updater to play animation.
        pause():
            Suspend updater to pause animation.
        stop():
            Clear updater to stop animation.
    """

    def __init__(self, vmob, rate=1.0, dash_offset=0, **kwargs):
        vmob = vmob.copy()
        super().__init__(vmob, dash_offset=dash_offset, **kwargs)

        self.rate = rate
        self.offset = dash_offset

        def _updater(mobj, dt):
            self.offset = (self.offset + dt * self.rate) % 1
            mobj.become(DashedVMobject(vmob, dash_offset=self.offset, **kwargs))

        self.add_updater(_updater)

    def play(self):
        self.resume_updating()

    def pause(self):
        self.suspend_updating()

    def stop(self):
        self.clear_updaters()


class DrawArc(Succession):
    def __init__(self, arc, reverse=False, run_time=2, **kwargs):
        quarter_time = run_time / 4
        half_time = run_time / 2

        if reverse:
            line = Line(
                arc.get_arc_center(),
                arc.get_end(),
                stroke_width=arc.get_stroke_width(),
                stroke_color=arc.get_stroke_color(),
            )
            create_arc = Uncreate(arc, run_time=half_time)
            rotate_line = Rotate(
                line, -arc.angle, about_point=arc.get_arc_center(), run_time=half_time
            )
        else:
            line = Line(
                arc.get_arc_center(),
                arc.get_start(),
                stroke_width=arc.get_stroke_width(),
                stroke_color=arc.get_stroke_color(),
            )
            create_arc = Create(arc, run_time=half_time)
            rotate_line = Rotate(
                line, arc.angle, about_point=arc.get_arc_center(), run_time=half_time
            )

        super().__init__(
            Create(line, run_time=quarter_time),
            AnimationGroup(
                create_arc,
                rotate_line,
            ),
            Uncreate(line, run_time=quarter_time),
            **kwargs,
        )


class FlashFade(AnimationGroup):
    """Animation for fading VMobjects with flashing outlines.

    Args:
        vmob (VMobject): The VMobject to animate.
        mode (str): "IN" to fade in, "OUT" to fade out, "AUTO" to fade in and out (default: "AUTO").
        reverse (bool): If True, reverses the animation direction (default: False).
        color (ManimColor | None): The stroke color of the flash outline (default: None).
        width (float | None): The stroke width of the flash outline (default: None).
        opacity (float | None): The stroke opacity of the flash outline (default: None).
        time_width (float): The length of the sliver relative to the length of the stroke (default: 0.5).
        duration (float): The duration of each sub-animation (default: 1.0).
        lag_ratio (float): The lag ratio between sub-animations (default: 0.125).
        **kwargs: Additional keyword arguments for AnimationGroup.
    """

    def __init__(
        self,
        vmob: VMobject,
        mode="AUTO",
        reverse=False,
        color=None,
        width=None,
        opacity=None,
        time_width=0.5,
        duration=1.0,
        lag_ratio=0.125,
        **kwargs,
    ):
        if reverse:
            vmob = vmob[::-1]
            vcopy = vmob.copy()
            vcopy.set_fill(opacity=0).set_stroke(color, width, opacity)
            for vm in vcopy:
                vm = vm.reverse_direction()
        else:
            vcopy = vmob.copy()
            vcopy.set_fill(opacity=0).set_stroke(color, width, opacity)

        mode = mode.upper()
        n = len(vmob)
        anim = []

        if mode == "IN":
            for i in range(n):
                anim.append(
                    AnimationGroup(
                        FadeIn(vmob[i]),
                        ShowPassingFlash(vcopy[i], time_width=time_width),
                        run_time=duration,
                    )
                )
        elif mode == "OUT":
            for i in range(n):
                anim.append(
                    AnimationGroup(
                        FadeOut(vmob[i]),
                        ShowPassingFlash(vcopy[i], time_width=time_width),
                        run_time=duration,
                    )
                )
        else:
            for i in range(n):
                anim.append(
                    AnimationGroup(
                        FadeIn(vmob[i].copy(), rate_func=there_and_back_with_pause),
                        ShowPassingFlash(vcopy[i], time_width=time_width),
                        run_time=duration,
                    )
                )

        super().__init__(*anim, lag_ratio=lag_ratio, **kwargs)