mirror of
https://github.com/ManimCommunity/manim.git
synced 2026-06-22 10:01:47 +00:00
feat: Add animations that together simulate typing
AddTextLetterByLetterWithCursor RemoveTextLetterByLetterWithCursor Blink
This commit is contained in:
parent
1520481812
commit
5fe256880d
2 changed files with 266 additions and 2 deletions
|
|
@ -81,8 +81,10 @@ import numpy as np
|
|||
if TYPE_CHECKING:
|
||||
from manim.mobject.text.text_mobject import Text
|
||||
|
||||
from manim.constants import RIGHT
|
||||
from manim.mobject.opengl.opengl_surface import OpenGLSurface
|
||||
from manim.mobject.opengl.opengl_vectorized_mobject import OpenGLVMobject
|
||||
from manim.scene.scene import Scene
|
||||
from manim.utils.color import ManimColor
|
||||
|
||||
from .. import config
|
||||
|
|
@ -668,3 +670,204 @@ class AddTextWordByWord(Succession):
|
|||
)
|
||||
)
|
||||
super().__init__(*anims, **kwargs)
|
||||
|
||||
|
||||
class AddTextLetterByLetterWithCursor(AddTextLetterByLetter):
|
||||
"""Similar to :class:`~.AddTextLetterByLetter` , but with an additional cursor mobject at the end.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time_per_char
|
||||
Frequency of appearance of the letters.
|
||||
cursor
|
||||
:class:`~.Mobject` shown after the last added letter.
|
||||
buff
|
||||
Controls how far away the cursor is to the right of the last added letter.
|
||||
keep_cursor_y
|
||||
If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
|
||||
leave_cursor_on
|
||||
Whether to show the cursor after the animation.
|
||||
kwargs
|
||||
Additional arguments to be passed to the :class:`~.AddTextLetterByLetter` constructor.
|
||||
|
||||
.. tip::
|
||||
This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: TypingAnimation
|
||||
|
||||
class TypingAnimation(Scene):
|
||||
def construct(self):
|
||||
text = Text("Typing", color=PURPLE).scale(1.5).to_edge(LEFT)
|
||||
cursor = Rectangle(
|
||||
color = GREY_A,
|
||||
fill_color = GREY_A,
|
||||
fill_opacity = 1.0,
|
||||
height = 1.1,
|
||||
width = 0.5,
|
||||
).move_to(text[0]) # Position the cursor
|
||||
|
||||
self.play(Blink(cursor, how_many_times=2))
|
||||
self.play(AddTextLetterByLetterWithCursor(text, cursor, leave_cursor_on=False)) # Turning off the cursor is important
|
||||
self.play(Blink(cursor, how_many_times=3))
|
||||
self.play(RemoveTextLetterByLetterWithCursor(text, cursor))
|
||||
self.play(Blink(cursor, how_many_times=2, ends_with_off=True))
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: Text,
|
||||
cursor: Mobject,
|
||||
buff: float = 0.1,
|
||||
keep_cursor_y: bool = True,
|
||||
leave_cursor_on: bool = True,
|
||||
suspend_mobject_updating: bool = False,
|
||||
int_func: Callable[[np.ndarray], np.ndarray] = np.ceil,
|
||||
rate_func: Callable[[float], float] = linear,
|
||||
time_per_char: float = 0.1,
|
||||
run_time: float | None = None,
|
||||
reverse_rate_function=False,
|
||||
introducer=True,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.cursor = cursor
|
||||
self.buff = buff
|
||||
self.keep_cursor_y = keep_cursor_y
|
||||
self.leave_cursor_on = leave_cursor_on
|
||||
super().__init__(
|
||||
text,
|
||||
suspend_mobject_updating=suspend_mobject_updating,
|
||||
int_func=int_func,
|
||||
rate_func=rate_func,
|
||||
time_per_char=time_per_char,
|
||||
run_time=run_time,
|
||||
reverse_rate_function=reverse_rate_function,
|
||||
introducer=introducer,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def begin(self) -> None:
|
||||
self.y_cursor = self.cursor.get_y()
|
||||
self.cursor.initial_position = self.mobject.get_center()
|
||||
if self.keep_cursor_y:
|
||||
self.cursor.set_y(self.y_cursor)
|
||||
|
||||
self.cursor.set_opacity(0)
|
||||
self.mobject.add(self.cursor)
|
||||
super().begin()
|
||||
|
||||
def finish(self) -> None:
|
||||
if self.leave_cursor_on:
|
||||
self.cursor.set_opacity(1)
|
||||
else:
|
||||
self.cursor.set_opacity(0)
|
||||
self.mobject.remove(self.cursor)
|
||||
super().finish()
|
||||
|
||||
def clean_up_from_scene(self, scene: Scene) -> None:
|
||||
if not self.leave_cursor_on:
|
||||
scene.remove(self.cursor)
|
||||
super().clean_up_from_scene(scene)
|
||||
|
||||
def update_submobject_list(self, index: int) -> None:
|
||||
for mobj in self.all_submobs[:index]:
|
||||
mobj.set_opacity(1)
|
||||
|
||||
for mobj in self.all_submobs[index:]:
|
||||
mobj.set_opacity(0)
|
||||
|
||||
if index != 0:
|
||||
self.cursor.next_to(
|
||||
self.all_submobs[index - 1], RIGHT, buff=self.buff
|
||||
).set_y(self.cursor.initial_position[1])
|
||||
else:
|
||||
self.cursor.move_to(self.all_submobs[0]).set_y(
|
||||
self.cursor.initial_position[1]
|
||||
)
|
||||
|
||||
if self.keep_cursor_y:
|
||||
self.cursor.set_y(self.y_cursor)
|
||||
self.cursor.set_opacity(1)
|
||||
|
||||
|
||||
class RemoveTextLetterByLetterWithCursor(AddTextLetterByLetterWithCursor):
|
||||
"""Similar to :class:`~.RemoveTextLetterByLetter` , but with an additional cursor mobject at the end.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time_per_char
|
||||
Frequency of appearance of the letters.
|
||||
cursor
|
||||
:class:`~.Mobject` shown after the last added letter.
|
||||
buff
|
||||
Controls how far away the cursor is to the right of the last added letter.
|
||||
keep_cursor_y
|
||||
If ``True``, the cursor's y-coordinate is set to the center of the ``Text`` and remains the same throughout the animation. Otherwise, it is set to the center of the last added letter.
|
||||
leave_cursor_on
|
||||
Whether to show the cursor after the animation.
|
||||
kwargs
|
||||
Additional arguments to be passed to the :class:`~.AddTextLetterByLetter` constructor.
|
||||
|
||||
.. tip::
|
||||
This is currently only possible for class:`~.Text` and not for class:`~.MathTex`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: TypingAnimation
|
||||
|
||||
class TypingAnimation(Scene):
|
||||
def construct(self):
|
||||
text = Text("Typing", color=PURPLE).scale(1.5).to_edge(LEFT)
|
||||
cursor = Rectangle(
|
||||
color = GREY_A,
|
||||
fill_color = GREY_A,
|
||||
fill_opacity = 1.0,
|
||||
height = 1.1,
|
||||
width = 0.5,
|
||||
).move_to(text[0]) # Position the cursor
|
||||
|
||||
self.play(Blink(cursor, how_many_times=2))
|
||||
self.play(AddTextLetterByLetterWithCursor(text, cursor, leave_cursor_on=False)) # Turning off the cursor is important
|
||||
self.play(Blink(cursor, how_many_times=3))
|
||||
self.play(RemoveTextLetterByLetterWithCursor(text, cursor))
|
||||
self.play(Blink(cursor, how_many_times=2, ends_with_off=True))
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: Text,
|
||||
cursor: VMobject | None = None,
|
||||
buff: float = 0.1,
|
||||
keep_cursor_y: bool = True,
|
||||
leave_cursor_on: bool = True,
|
||||
suspend_mobject_updating: bool = False,
|
||||
int_func: Callable[[np.ndarray], np.ndarray] = np.ceil,
|
||||
rate_func: Callable[[float], float] = linear,
|
||||
time_per_char: float = 0.1,
|
||||
run_time: float | None = None,
|
||||
reverse_rate_function=True,
|
||||
introducer=False,
|
||||
remover=True,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
text,
|
||||
cursor=cursor,
|
||||
buff=buff,
|
||||
keep_cursor_y=keep_cursor_y,
|
||||
leave_cursor_on=leave_cursor_on,
|
||||
suspend_mobject_updating=suspend_mobject_updating,
|
||||
int_func=int_func,
|
||||
rate_func=rate_func,
|
||||
time_per_char=time_per_char,
|
||||
run_time=run_time,
|
||||
reverse_rate_function=reverse_rate_function,
|
||||
introducer=introducer,
|
||||
remover=remover,
|
||||
**kwargs,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ from manim.mobject.geometry.shape_matchers import SurroundingRectangle
|
|||
from manim.scene.scene import Scene
|
||||
|
||||
from .. import config
|
||||
from ..animation.animation import Animation
|
||||
from ..animation.animation import Animation, Wait
|
||||
from ..animation.composition import AnimationGroup, Succession
|
||||
from ..animation.creation import Create, ShowPartial, Uncreate
|
||||
from ..animation.fading import FadeIn, FadeOut
|
||||
from ..animation.movement import Homotopy
|
||||
from ..animation.transform import Transform
|
||||
from ..animation.transform import ApplyMethod, Transform
|
||||
from ..constants import *
|
||||
from ..mobject.mobject import Mobject
|
||||
from ..mobject.types.vectorized_mobject import VGroup, VMobject
|
||||
|
|
@ -654,3 +654,64 @@ class Circumscribe(Succession):
|
|||
super().__init__(
|
||||
ShowPassingFlash(frame, time_width, run_time=run_time), **kwargs
|
||||
)
|
||||
|
||||
|
||||
class Blink(Succession):
|
||||
"""Blink the mobject.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mobject
|
||||
The mobject to be blinked.
|
||||
time_on
|
||||
The duration that the mobject is shown for one blink.
|
||||
time_off
|
||||
The duration that the mobject is hidden for one blink.
|
||||
how_many_times
|
||||
The number of blinks
|
||||
ends_with_off
|
||||
Whether to show or hide the mobject at the end of the animation.
|
||||
kwargs
|
||||
Additional arguments to be passed to the :class:`~.Succession` constructor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. manim:: BlinkingCursor
|
||||
|
||||
class BlinkingCursor(Scene):
|
||||
def construct(self):
|
||||
text = Text("Typing", color=PURPLE).scale(1.5)
|
||||
cursor = Rectangle(
|
||||
color = GREY_A,
|
||||
fill_color = GREY_A,
|
||||
fill_opacity = 1.0,
|
||||
height = 1.1,
|
||||
width = 0.5,
|
||||
).next_to(text, buff=0.1)
|
||||
|
||||
self.add(text)
|
||||
self.play(Blink(cursor, how_many_times=3))
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mobject: Mobject,
|
||||
time_on: float = 0.5,
|
||||
time_off: float = 0.5,
|
||||
how_many_times: int = 1,
|
||||
ends_with_off: bool = False,
|
||||
**kwargs
|
||||
):
|
||||
animations = [
|
||||
ApplyMethod(mobject.set_opacity, 1.0, run_time=0.0),
|
||||
Wait(run_time=time_on),
|
||||
ApplyMethod(mobject.set_opacity, 0.0, run_time=0.0),
|
||||
Wait(run_time=time_off),
|
||||
] * how_many_times
|
||||
|
||||
if not ends_with_off:
|
||||
animations.append(ApplyMethod(mobject.set_opacity, 1.0, run_time=0.0))
|
||||
|
||||
super().__init__(*animations, **kwargs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue