Skip to content

Commit

Permalink
feat(lib): add loop option to next_slide and remove `start/end_lo…
Browse files Browse the repository at this point in the history
…op` (#294)

* feat(lib): add `loop` option to `next_slide` and remove `start/end_loop`

* fix(docs): PR number
  • Loading branch information
jeertmans authored Oct 19, 2023
1 parent 7928f60 commit 0322dae
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 117 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ In an effort to better document changes, this CHANGELOG document is now created.
[#285](https://github.com/jeertmans/manim-slides/pull/285)
- Added a working `ThreeDSlide` class compatible with `manimlib`.
[#285](https://github.com/jeertmans/manim-slides/pull/285)
- Added `loop` option to `Slide`'s `next_slide` method.
Calling `next_slide` will never fail anymore.
[#294](https://github.com/jeertmans/manim-slides/pull/294)

### Changed

Expand Down Expand Up @@ -102,5 +105,9 @@ In an effort to better document changes, this CHANGELOG document is now created.
[#243](https://github.com/jeertmans/manim-slides/pull/243)
- Removed `PERF` verbosity level because not used anymore.
[#245](https://github.com/jeertmans/manim-slides/pull/245)
- Remove `Slide`'s method `start_loop` and `self.end_loop`
in favor to `self.next_slide(loop=True)`.
This is a **breaking change**.
[#294](https://github.com/jeertmans/manim-slides/pull/294)

<!-- end changelog -->
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ The documentation is available [online](https://eertmans.be/manim-slides/).

### Basic Example

Wrap a series of animations between `self.start_loop()` and `self.stop_loop()` when you want to loop them (until input to continue):
Call `self.next_slide()` everytime you want to create a pause between
animations, and `self.next_slide(loop=True)` if you want the next slide to loop
over animations until the user presses continue:

```python
# example.py
Expand All @@ -107,9 +109,9 @@ class BasicExample(Slide):
self.play(GrowFromCenter(circle))
self.next_slide() # Waits user to press continue to go to the next slide

self.start_loop() # Start loop
self.next_slide(loop=True) # Start loop
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
self.end_loop() # This will loop until user inputs a key
self.next_slide() # This will start a new non-looping slide

self.play(dot.animate.move_to(ORIGIN))
```
Expand Down
2 changes: 0 additions & 2 deletions docs/source/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ use, not the methods used internally when rendering.
add_to_canvas,
canvas,
canvas_mobjects,
end_loop,
mobjects_without_canvas,
next_slide,
remove_from_canvas,
start_loop,
wait_time_between_slides,
wipe,
zoom,
Expand Down
5 changes: 2 additions & 3 deletions docs/source/reference/magic_example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,9 @@
" ).arrange(DOWN, buff=1.)\n",
" \n",
" self.play(Write(text))\n",
" self.next_slide()\n",
" self.start_loop()\n",
" self.next_slide(loop=True)\n",
" self.play(Indicate(text[-1], scale_factor=2., run_time=.5))\n",
" self.end_loop()\n",
" self.next_slide()\n",
" self.play(FadeOut(text))"
]
},
Expand Down
33 changes: 16 additions & 17 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@ def construct(self):
dot = Dot()

self.play(GrowFromCenter(circle))
self.next_slide() # Waits user to press continue to go to the next slide

self.start_loop() # Start loop
self.next_slide(loop=True)
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
self.end_loop() # This will loop until user inputs a key
self.next_slide()

self.play(dot.animate.move_to(ORIGIN))

Expand Down Expand Up @@ -137,9 +136,9 @@ class Example(Slide):
def construct(self):
dot = Dot()
self.add(dot)
self.start_loop()
self.next_slide(loop=True)
self.play(Indicate(dot, scale_factor=2))
self.end_loop()
self.next_slide()
square = Square()
self.play(Transform(dot, square))
self.next_slide()
Expand Down Expand Up @@ -195,17 +194,17 @@ def construct(self):

watch_text = Text("Watch result on next slides!").shift(2 * DOWN).scale(0.5)

self.start_loop()
self.next_slide(loop=True)
self.play(FadeIn(watch_text))
self.play(FadeOut(watch_text))
self.end_loop()
self.next_slide()
self.clear()

dot = Dot()
self.add(dot)
self.start_loop()
self.next_slide(loop=True)
self.play(Indicate(dot, scale_factor=2))
self.end_loop()
self.next_slide()
square = Square()
self.play(Transform(dot, square))
self.remove(dot)
Expand Down Expand Up @@ -245,9 +244,9 @@ def construct(self):

self.next_slide()

self.start_loop()
self.next_slide(loop=True)
self.play(MoveAlongPath(dot, circle), run_time=4, rate_func=linear)
self.end_loop()
self.next_slide()

self.stop_ambient_camera_rotation()
self.move_camera(phi=75 * DEGREES, theta=30 * DEGREES)
Expand All @@ -258,9 +257,9 @@ def construct(self):
self.play(dot.animate.move_to(RIGHT * 3))
self.next_slide()

self.start_loop()
self.next_slide(loop=True)
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
self.end_loop()
self.next_slide()

self.play(dot.animate.move_to(ORIGIN))

Expand Down Expand Up @@ -292,9 +291,9 @@ def updater(m, dt):

self.next_slide()

self.start_loop()
self.next_slide(loop=True)
self.play(MoveAlongPath(dot, circle), run_time=4, rate_func=linear)
self.end_loop()
self.next_slide()

frame.remove_updater(updater)
self.play(frame.animate.set_theta(30 * DEGREES))
Expand All @@ -304,9 +303,9 @@ def updater(m, dt):
self.play(dot.animate.move_to(RIGHT * 3))
self.next_slide()

self.start_loop()
self.next_slide(loop=True)
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
self.end_loop()
self.next_slide()

self.play(dot.animate.move_to(ORIGIN))

Expand Down
109 changes: 35 additions & 74 deletions manim_slides/slide/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ def __init__(
super().__init__(*args, **kwargs)
self._output_folder: Path = output_folder
self._slides: List[PreSlideConfig] = []
self._pre_slide_config_kwargs: MutableMapping[str, Any] = {}
self._current_slide = 1
self._current_animation = 0
self._loop_start_animation: Optional[int] = None
self._pause_start_animation = 0
self._start_animation = 0
self._canvas: MutableMapping[str, Mobject] = {}
self._wait_time_between_slides = 0.0

Expand Down Expand Up @@ -252,13 +252,16 @@ def play(self, *args: Any, **kwargs: Any) -> None:
super().play(*args, **kwargs) # type: ignore[misc]
self._current_animation += 1

def next_slide(self) -> None:
def next_slide(self, loop: bool = False) -> None:
"""
Create a new slide with previous animations.
Create a new slide with previous animations, and setup options
for the next slide.
This usually means that the user will need to press some key before the
next slide is played. By default, this is the right arrow key.
:param loop:
If set, next slide will be looping.
.. note::
Expand All @@ -267,7 +270,8 @@ def next_slide(self) -> None:
.. warning::
This is not allowed to call :func:`next_slide` inside a loop.
When rendered with RevealJS, loops cannot be in the first nor
the last slide.
Examples
--------
Expand All @@ -290,58 +294,7 @@ def construct(self):
self.next_slide()
self.play(FadeOut(text))
"""
assert (
self._loop_start_animation is None
), "You cannot call `self.next_slide()` inside a loop"

if self.wait_time_between_slides > 0.0:
self.wait(self.wait_time_between_slides) # type: ignore[attr-defined]

self._slides.append(
PreSlideConfig(
start_animation=self._pause_start_animation,
end_animation=self._current_animation,
)
)
self._current_slide += 1
self._pause_start_animation = self._current_animation

def _add_last_slide(self) -> None:
"""Add a 'last' slide to the end of slides."""
if (
len(self._slides) > 0
and self._current_animation == self._slides[-1].end_animation
):
return

self._slides.append(
PreSlideConfig(
start_animation=self._pause_start_animation,
end_animation=self._current_animation,
loop=self._loop_start_animation is not None,
)
)

def start_loop(self) -> None:
"""
Start a loop. End it with :func:`end_loop`.
A loop will automatically replay the slide, i.e., everything between
:func:`start_loop` and :func:`end_loop`, upon reaching end.
.. warning::
You should always call :func:`next_slide` before calling this
method. Otherwise, ...
.. warning::
When rendered with RevealJS, loops cannot be in the first nor
the last slide.
Examples
--------
The following contains one slide that will loop endlessly.
.. manim-slides:: LoopExample
Expand All @@ -354,38 +307,46 @@ def construct(self):
dot = Dot(color=BLUE, radius=1)
self.play(FadeIn(dot))
self.next_slide()
self.start_loop()
self.next_slide(loop=True)
self.play(Indicate(dot, scale_factor=2))
self.end_loop()
self.next_slide()
self.play(FadeOut(dot))
"""
assert self._loop_start_animation is None, "You cannot nest loops"
self._loop_start_animation = self._current_animation
if self._current_animation > self._start_animation:
if self.wait_time_between_slides > 0.0:
self.wait(self.wait_time_between_slides) # type: ignore[attr-defined]

self._slides.append(
PreSlideConfig(
start_animation=self._start_animation,
end_animation=self._current_animation,
**self._pre_slide_config_kwargs,
)
)

def end_loop(self) -> None:
"""
End an existing loop.
self._pre_slide_config_kwargs = dict(loop=loop)
self._current_slide += 1
self._start_animation = self._current_animation

def _add_last_slide(self) -> None:
"""Add a 'last' slide to the end of slides."""
if (
len(self._slides) > 0
and self._current_animation == self._slides[-1].end_animation
):
return

See :func:`start_loop` for more details.
"""
assert (
self._loop_start_animation is not None
), "You have to start a loop before ending it"
self._slides.append(
PreSlideConfig(
start_animation=self._loop_start_animation,
start_animation=self._start_animation,
end_animation=self._current_animation,
loop=True,
**self._pre_slide_config_kwargs,
)
)
self._current_slide += 1
self._loop_start_animation = None
self._pause_start_animation = self._current_animation

def _save_slides(self, use_cache: bool = True) -> None:
"""
Expand Down
4 changes: 2 additions & 2 deletions manim_slides/slide/manim.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def construct(self):
bye = Text("Bye!")
self.start_loop()
self.next_slide(loop=True)
self.wipe(
self.mobjects_without_canvas,
[bye],
Expand All @@ -121,7 +121,7 @@ def construct(self):
direction=DOWN
)
self.wait(.5)
self.end_loop()
self.next_slide()
self.play(*[FadeOut(mobject) for mobject in self.mobjects])
"""
Expand Down
6 changes: 3 additions & 3 deletions tests/data/slides.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def construct(self):

self.play(FadeIn(square))

self.next_slide()
self.next_slide(loop=True)

self.start_loop()
self.play(Rotate(square, +PI / 2))
self.play(Rotate(square, -PI / 2))
self.end_loop()

self.next_slide()

other_text = Text("Other text")
self.wipe([square, circle], [other_text])
Expand Down
Loading

0 comments on commit 0322dae

Please sign in to comment.