Skip to content

Commit 0322dae

Browse files
authored
feat(lib): add loop option to next_slide and remove start/end_loop (#294)
* feat(lib): add `loop` option to `next_slide` and remove `start/end_loop` * fix(docs): PR number
1 parent 7928f60 commit 0322dae

File tree

9 files changed

+78
-117
lines changed

9 files changed

+78
-117
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ In an effort to better document changes, this CHANGELOG document is now created.
3939
[#285](https://github.com/jeertmans/manim-slides/pull/285)
4040
- Added a working `ThreeDSlide` class compatible with `manimlib`.
4141
[#285](https://github.com/jeertmans/manim-slides/pull/285)
42+
- Added `loop` option to `Slide`'s `next_slide` method.
43+
Calling `next_slide` will never fail anymore.
44+
[#294](https://github.com/jeertmans/manim-slides/pull/294)
4245

4346
### Changed
4447

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

106113
<!-- end changelog -->

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ The documentation is available [online](https://eertmans.be/manim-slides/).
8989

9090
### Basic Example
9191

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

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

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

114116
self.play(dot.animate.move_to(ORIGIN))
115117
```

docs/source/reference/api.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ use, not the methods used internally when rendering.
1414
add_to_canvas,
1515
canvas,
1616
canvas_mobjects,
17-
end_loop,
1817
mobjects_without_canvas,
1918
next_slide,
2019
remove_from_canvas,
21-
start_loop,
2220
wait_time_between_slides,
2321
wipe,
2422
zoom,

docs/source/reference/magic_example.ipynb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,9 @@
5858
" ).arrange(DOWN, buff=1.)\n",
5959
" \n",
6060
" self.play(Write(text))\n",
61-
" self.next_slide()\n",
62-
" self.start_loop()\n",
61+
" self.next_slide(loop=True)\n",
6362
" self.play(Indicate(text[-1], scale_factor=2., run_time=.5))\n",
64-
" self.end_loop()\n",
63+
" self.next_slide()\n",
6564
" self.play(FadeOut(text))"
6665
]
6766
},

example.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ def construct(self):
1616
dot = Dot()
1717

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

21-
self.start_loop() # Start loop
20+
self.next_slide(loop=True)
2221
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
23-
self.end_loop() # This will loop until user inputs a key
22+
self.next_slide()
2423

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

@@ -137,9 +136,9 @@ class Example(Slide):
137136
def construct(self):
138137
dot = Dot()
139138
self.add(dot)
140-
self.start_loop()
139+
self.next_slide(loop=True)
141140
self.play(Indicate(dot, scale_factor=2))
142-
self.end_loop()
141+
self.next_slide()
143142
square = Square()
144143
self.play(Transform(dot, square))
145144
self.next_slide()
@@ -195,17 +194,17 @@ def construct(self):
195194

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

198-
self.start_loop()
197+
self.next_slide(loop=True)
199198
self.play(FadeIn(watch_text))
200199
self.play(FadeOut(watch_text))
201-
self.end_loop()
200+
self.next_slide()
202201
self.clear()
203202

204203
dot = Dot()
205204
self.add(dot)
206-
self.start_loop()
205+
self.next_slide(loop=True)
207206
self.play(Indicate(dot, scale_factor=2))
208-
self.end_loop()
207+
self.next_slide()
209208
square = Square()
210209
self.play(Transform(dot, square))
211210
self.remove(dot)
@@ -245,9 +244,9 @@ def construct(self):
245244

246245
self.next_slide()
247246

248-
self.start_loop()
247+
self.next_slide(loop=True)
249248
self.play(MoveAlongPath(dot, circle), run_time=4, rate_func=linear)
250-
self.end_loop()
249+
self.next_slide()
251250

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

261-
self.start_loop()
260+
self.next_slide(loop=True)
262261
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
263-
self.end_loop()
262+
self.next_slide()
264263

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

@@ -292,9 +291,9 @@ def updater(m, dt):
292291

293292
self.next_slide()
294293

295-
self.start_loop()
294+
self.next_slide(loop=True)
296295
self.play(MoveAlongPath(dot, circle), run_time=4, rate_func=linear)
297-
self.end_loop()
296+
self.next_slide()
298297

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

307-
self.start_loop()
306+
self.next_slide(loop=True)
308307
self.play(MoveAlongPath(dot, circle), run_time=2, rate_func=linear)
309-
self.end_loop()
308+
self.next_slide()
310309

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

manim_slides/slide/base.py

Lines changed: 35 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ def __init__(
2929
super().__init__(*args, **kwargs)
3030
self._output_folder: Path = output_folder
3131
self._slides: List[PreSlideConfig] = []
32+
self._pre_slide_config_kwargs: MutableMapping[str, Any] = {}
3233
self._current_slide = 1
3334
self._current_animation = 0
34-
self._loop_start_animation: Optional[int] = None
35-
self._pause_start_animation = 0
35+
self._start_animation = 0
3636
self._canvas: MutableMapping[str, Mobject] = {}
3737
self._wait_time_between_slides = 0.0
3838

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

255-
def next_slide(self) -> None:
255+
def next_slide(self, loop: bool = False) -> None:
256256
"""
257-
Create a new slide with previous animations.
257+
Create a new slide with previous animations, and setup options
258+
for the next slide.
258259
259260
This usually means that the user will need to press some key before the
260261
next slide is played. By default, this is the right arrow key.
261262
263+
:param loop:
264+
If set, next slide will be looping.
262265
263266
.. note::
264267
@@ -267,7 +270,8 @@ def next_slide(self) -> None:
267270
268271
.. warning::
269272
270-
This is not allowed to call :func:`next_slide` inside a loop.
273+
When rendered with RevealJS, loops cannot be in the first nor
274+
the last slide.
271275
272276
Examples
273277
--------
@@ -290,58 +294,7 @@ def construct(self):
290294
291295
self.next_slide()
292296
self.play(FadeOut(text))
293-
"""
294-
assert (
295-
self._loop_start_animation is None
296-
), "You cannot call `self.next_slide()` inside a loop"
297-
298-
if self.wait_time_between_slides > 0.0:
299-
self.wait(self.wait_time_between_slides) # type: ignore[attr-defined]
300-
301-
self._slides.append(
302-
PreSlideConfig(
303-
start_animation=self._pause_start_animation,
304-
end_animation=self._current_animation,
305-
)
306-
)
307-
self._current_slide += 1
308-
self._pause_start_animation = self._current_animation
309-
310-
def _add_last_slide(self) -> None:
311-
"""Add a 'last' slide to the end of slides."""
312-
if (
313-
len(self._slides) > 0
314-
and self._current_animation == self._slides[-1].end_animation
315-
):
316-
return
317-
318-
self._slides.append(
319-
PreSlideConfig(
320-
start_animation=self._pause_start_animation,
321-
end_animation=self._current_animation,
322-
loop=self._loop_start_animation is not None,
323-
)
324-
)
325-
326-
def start_loop(self) -> None:
327-
"""
328-
Start a loop. End it with :func:`end_loop`.
329297
330-
A loop will automatically replay the slide, i.e., everything between
331-
:func:`start_loop` and :func:`end_loop`, upon reaching end.
332-
333-
.. warning::
334-
335-
You should always call :func:`next_slide` before calling this
336-
method. Otherwise, ...
337-
338-
.. warning::
339-
340-
When rendered with RevealJS, loops cannot be in the first nor
341-
the last slide.
342-
343-
Examples
344-
--------
345298
The following contains one slide that will loop endlessly.
346299
347300
.. manim-slides:: LoopExample
@@ -354,38 +307,46 @@ def construct(self):
354307
dot = Dot(color=BLUE, radius=1)
355308
356309
self.play(FadeIn(dot))
357-
self.next_slide()
358310
359-
self.start_loop()
311+
self.next_slide(loop=True)
360312
361313
self.play(Indicate(dot, scale_factor=2))
362314
363-
self.end_loop()
315+
self.next_slide()
364316
365317
self.play(FadeOut(dot))
366318
"""
367-
assert self._loop_start_animation is None, "You cannot nest loops"
368-
self._loop_start_animation = self._current_animation
319+
if self._current_animation > self._start_animation:
320+
if self.wait_time_between_slides > 0.0:
321+
self.wait(self.wait_time_between_slides) # type: ignore[attr-defined]
322+
323+
self._slides.append(
324+
PreSlideConfig(
325+
start_animation=self._start_animation,
326+
end_animation=self._current_animation,
327+
**self._pre_slide_config_kwargs,
328+
)
329+
)
369330

370-
def end_loop(self) -> None:
371-
"""
372-
End an existing loop.
331+
self._pre_slide_config_kwargs = dict(loop=loop)
332+
self._current_slide += 1
333+
self._start_animation = self._current_animation
334+
335+
def _add_last_slide(self) -> None:
336+
"""Add a 'last' slide to the end of slides."""
337+
if (
338+
len(self._slides) > 0
339+
and self._current_animation == self._slides[-1].end_animation
340+
):
341+
return
373342

374-
See :func:`start_loop` for more details.
375-
"""
376-
assert (
377-
self._loop_start_animation is not None
378-
), "You have to start a loop before ending it"
379343
self._slides.append(
380344
PreSlideConfig(
381-
start_animation=self._loop_start_animation,
345+
start_animation=self._start_animation,
382346
end_animation=self._current_animation,
383-
loop=True,
347+
**self._pre_slide_config_kwargs,
384348
)
385349
)
386-
self._current_slide += 1
387-
self._loop_start_animation = None
388-
self._pause_start_animation = self._current_animation
389350

390351
def _save_slides(self, use_cache: bool = True) -> None:
391352
"""

manim_slides/slide/manim.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def construct(self):
108108
109109
bye = Text("Bye!")
110110
111-
self.start_loop()
111+
self.next_slide(loop=True)
112112
self.wipe(
113113
self.mobjects_without_canvas,
114114
[bye],
@@ -121,7 +121,7 @@ def construct(self):
121121
direction=DOWN
122122
)
123123
self.wait(.5)
124-
self.end_loop()
124+
self.next_slide()
125125
126126
self.play(*[FadeOut(mobject) for mobject in self.mobjects])
127127
"""

tests/data/slides.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ def construct(self):
2626

2727
self.play(FadeIn(square))
2828

29-
self.next_slide()
29+
self.next_slide(loop=True)
3030

31-
self.start_loop()
3231
self.play(Rotate(square, +PI / 2))
3332
self.play(Rotate(square, -PI / 2))
34-
self.end_loop()
33+
34+
self.next_slide()
3535

3636
other_text = Text("Other text")
3737
self.wipe([square, circle], [other_text])

0 commit comments

Comments
 (0)