From 6b71ee7f93b8865e53de34789e782d81d79ce11d Mon Sep 17 00:00:00 2001 From: Josh Karpel Date: Wed, 4 Jan 2023 20:28:45 -0600 Subject: [PATCH] Document slide content rendering and triggers (#188) --- README.md | 7 +- docs/assets/quickstart_basic.svg | 86 +++++++-------- docs/assets/slide_loop_1.svg | 117 ++++++++++++++++++++ docs/assets/slide_loop_2.svg | 117 ++++++++++++++++++++ docs/assets/slide_loop_3.svg | 117 ++++++++++++++++++++ docs/assets/slide_via_decorator.svg | 119 ++++++++++++++++++++ docs/assets/triggers_animation_1.svg | 116 ++++++++++++++++++++ docs/assets/triggers_animation_2.svg | 116 ++++++++++++++++++++ docs/assets/triggers_animation_3.svg | 116 ++++++++++++++++++++ docs/assets/triggers_animation_4.svg | 116 ++++++++++++++++++++ docs/assets/triggers_reveal_1.svg | 118 ++++++++++++++++++++ docs/assets/triggers_reveal_2.svg | 119 ++++++++++++++++++++ docs/assets/triggers_reveal_3.svg | 120 +++++++++++++++++++++ docs/contributing.md | 8 +- docs/examples/slide_loop.py | 25 +++++ docs/examples/slide_via_decorator.py | 18 ++++ docs/examples/triggers_animation.py | 31 ++++++ docs/examples/triggers_reveal.py | 25 +++++ docs/generate_screenshots.py | 150 ++++++++++++++++++++++++-- docs/index.md | 4 +- docs/presenting.md | 4 +- docs/quickstart.md | 21 +--- docs/slides.md | 155 +++++++++++++++++++++++++++ mkdocs.yml | 5 +- spiel/app.py | 4 +- 25 files changed, 1752 insertions(+), 82 deletions(-) create mode 100644 docs/assets/slide_loop_1.svg create mode 100644 docs/assets/slide_loop_2.svg create mode 100644 docs/assets/slide_loop_3.svg create mode 100644 docs/assets/slide_via_decorator.svg create mode 100644 docs/assets/triggers_animation_1.svg create mode 100644 docs/assets/triggers_animation_2.svg create mode 100644 docs/assets/triggers_animation_3.svg create mode 100644 docs/assets/triggers_animation_4.svg create mode 100644 docs/assets/triggers_reveal_1.svg create mode 100644 docs/assets/triggers_reveal_2.svg create mode 100644 docs/assets/triggers_reveal_3.svg create mode 100644 docs/examples/slide_loop.py create mode 100644 docs/examples/slide_via_decorator.py create mode 100644 docs/examples/triggers_animation.py create mode 100644 docs/examples/triggers_reveal.py create mode 100644 docs/slides.md diff --git a/README.md b/README.md index 9474bd3..3590c6d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ [![GitHub issues](https://img.shields.io/github/issues/JoshKarpel/spiel)](https://github.com/JoshKarpel/spiel/issues) [![GitHub pull requests](https://img.shields.io/github/issues-pr/JoshKarpel/spiel)](https://github.com/JoshKarpel/spiel/pulls) -Spiel is a framework for building and presenting [richly-styled](https://github.com/Textualize/rich) presentations in your terminal using Python. +[Spiel](https://dictionary.cambridge.org/us/dictionary/english/spiel) is a framework for building and presenting +[richly-styled](https://github.com/Textualize/rich) presentations in your terminal using Python. To see what Spiel can do without installing it, you can view the demonstration deck in a container: ```bash @@ -59,3 +60,7 @@ Check out the [Quick Start tutorial](https://www.spiel.how/quickstart) to contin ## Documentation To learn more about Spiel, take a look at the [documentation](https://www.spiel.how). + +## Contributing + +If you're interested in contributing to Spiel, check out the [Contributing Guide](https://www.spiel.how/contributing/). diff --git a/docs/assets/quickstart_basic.svg b/docs/assets/quickstart_basic.svg index c5b779c..0003d6a 100644 --- a/docs/assets/quickstart_basic.svg +++ b/docs/assets/quickstart_basic.svg @@ -1,4 +1,4 @@ - + diff --git a/docs/assets/slide_loop_1.svg b/docs/assets/slide_loop_1.svg new file mode 100644 index 0000000..ff898d1 --- /dev/null +++ b/docs/assets/slide_loop_1.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Name + + + + + + + + + + + + + + + +Foo + + + + + + +──────────────────────────────────────────────────────────── +Deck Name | First Slide      2022-12-17 03:31 PM   [1 / 3] + + + diff --git a/docs/assets/slide_loop_2.svg b/docs/assets/slide_loop_2.svg new file mode 100644 index 0000000..688f1e1 --- /dev/null +++ b/docs/assets/slide_loop_2.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Name + + + + + + + + + + + + + + + +Bar + + + + + + +──────────────────────────────────────────────────────────── +Deck Name | Second Slide     2022-12-17 03:31 PM   [2 / 3] + + + diff --git a/docs/assets/slide_loop_3.svg b/docs/assets/slide_loop_3.svg new file mode 100644 index 0000000..f383791 --- /dev/null +++ b/docs/assets/slide_loop_3.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Name + + + + + + + + + + + + + + + +Baz + + + + + + +──────────────────────────────────────────────────────────── +Deck Name | Third Slide      2022-12-17 03:31 PM   [3 / 3] + + + diff --git a/docs/assets/slide_via_decorator.svg b/docs/assets/slide_via_decorator.svg new file mode 100644 index 0000000..66b3f0b --- /dev/null +++ b/docs/assets/slide_via_decorator.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deck Name + + + + + + + + + + + + + + + +Yourcontenthere!                      + + + + + + +──────────────────────────────────────────────────────────── +Deck Name | Slide Title      2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_animation_1.svg b/docs/assets/triggers_animation_1.svg new file mode 100644 index 0000000..fbef508 --- /dev/null +++ b/docs/assets/triggers_animation_1.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + + + + + +              spaces_before_bang=0 | spaces_after_bang=5               +                              ╭────────╮                               +                              │ !      │                               +                              ╰────────╯                               + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Animating Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_animation_2.svg b/docs/assets/triggers_animation_2.svg new file mode 100644 index 0000000..d93f615 --- /dev/null +++ b/docs/assets/triggers_animation_2.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + + + + + +              spaces_before_bang=1 | spaces_after_bang=4               +                              ╭────────╮                               +                              │  !     │                               +                              ╰────────╯                               + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Animating Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_animation_3.svg b/docs/assets/triggers_animation_3.svg new file mode 100644 index 0000000..fc0c85d --- /dev/null +++ b/docs/assets/triggers_animation_3.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + + + + + +              spaces_before_bang=3 | spaces_after_bang=2               +                              ╭────────╮                               +                              │    !   │                               +                              ╰────────╯                               + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Animating Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_animation_4.svg b/docs/assets/triggers_animation_4.svg new file mode 100644 index 0000000..82862b9 --- /dev/null +++ b/docs/assets/triggers_animation_4.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + + + + + +              spaces_before_bang=5 | spaces_after_bang=0               +                              ╭────────╮                               +                              │      ! │                               +                              ╰────────╯                               + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Animating Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_reveal_1.svg b/docs/assets/triggers_reveal_1.svg new file mode 100644 index 0000000..09152aa --- /dev/null +++ b/docs/assets/triggers_reveal_1.svg @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + +                 This slide has been triggered 1 time.                  + +First line. + + + + + + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Revealing Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_reveal_2.svg b/docs/assets/triggers_reveal_2.svg new file mode 100644 index 0000000..d232d89 --- /dev/null +++ b/docs/assets/triggers_reveal_2.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + +                 This slide has been triggered 2 times.                 + +First line. + +Second line. + + + + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Revealing Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/assets/triggers_reveal_3.svg b/docs/assets/triggers_reveal_3.svg new file mode 100644 index 0000000..6e904ad --- /dev/null +++ b/docs/assets/triggers_reveal_3.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Trigger Examples + + + + + + + + + +                 This slide has been triggered 3 times.                 + +First line. + +Second line. + +Third line. + + + + + + +────────────────────────────────────────────────────────────────────── +Trigger Examples | Revealing Content   2022-12-17 03:31 PM   [1 / 1] + + + diff --git a/docs/contributing.md b/docs/contributing.md index 9e37501..817ba12 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,10 +1,10 @@ # Contributing Guide -Spiel is open to contributions! +!!! info "Spiel is open to contributions!" -- [Report bugs and request features](https://github.com/JoshKarpel/spiel/issues) -- [More general discussion](https://github.com/JoshKarpel/spiel/discussions) -- [Pull requests](https://github.com/JoshKarpel/spiel/pulls) + - [Report bugs and request features](https://github.com/JoshKarpel/spiel/issues) + - [General discussion](https://github.com/JoshKarpel/spiel/discussions) + - [Pull requests](https://github.com/JoshKarpel/spiel/pulls) ## Development Environment diff --git a/docs/examples/slide_loop.py b/docs/examples/slide_loop.py new file mode 100644 index 0000000..c142f3d --- /dev/null +++ b/docs/examples/slide_loop.py @@ -0,0 +1,25 @@ +from rich.align import Align +from rich.console import RenderableType +from rich.style import Style +from rich.text import Text + +from spiel import Deck, Slide + +deck = Deck(name="Deck Name") + + +def make_slide( + title_prefix: str, + text: Text, +) -> Slide: + def content() -> RenderableType: + return Align(text, align="center", vertical="middle") + + return Slide(title=f"{title_prefix} Slide", content=content) + + +deck.add_slides( + make_slide(title_prefix="First", text=Text("Foo", style=Style(color="blue"))), + make_slide(title_prefix="Second", text=Text("Bar", style=Style(color="red"))), + make_slide(title_prefix="Third", text=Text("Baz", style=Style(color="green"))), +) diff --git a/docs/examples/slide_via_decorator.py b/docs/examples/slide_via_decorator.py new file mode 100644 index 0000000..5905eb4 --- /dev/null +++ b/docs/examples/slide_via_decorator.py @@ -0,0 +1,18 @@ +from rich.align import Align +from rich.console import RenderableType +from rich.text import Text + +from spiel import Deck + +deck = Deck(name="Deck Name") + + +@deck.slide(title="Slide Title") +def slide_content() -> RenderableType: + return Align( + Text.from_markup( + "[blue]Your[/blue] [red underline]content[/red underline] [green italic]here[/green italic]!" + ), + align="center", + vertical="middle", + ) diff --git a/docs/examples/triggers_animation.py b/docs/examples/triggers_animation.py new file mode 100644 index 0000000..7fa7fd0 --- /dev/null +++ b/docs/examples/triggers_animation.py @@ -0,0 +1,31 @@ +from math import floor + +from rich.align import Align +from rich.console import Group, RenderableType +from rich.panel import Panel +from rich.text import Text + +from spiel import Deck, Triggers + +deck = Deck(name="Trigger Examples") + + +@deck.slide(title="Animating Content") +def animate(triggers: Triggers) -> RenderableType: + bang = "!" + space = " " + bar_length = 5 + + spaces_before_bang = min(floor(triggers.time_since_first_trigger), bar_length) + spaces_after_bang = bar_length - spaces_before_bang + + bar = (space * spaces_before_bang) + bang + (space * spaces_after_bang) + + return Align( + Group( + Align.center(Text(f"{spaces_before_bang=} | {spaces_after_bang=}")), + Align.center(Panel(Text(bar), expand=False, height=3)), + ), + align="center", + vertical="middle", + ) diff --git a/docs/examples/triggers_reveal.py b/docs/examples/triggers_reveal.py new file mode 100644 index 0000000..9cbf6fd --- /dev/null +++ b/docs/examples/triggers_reveal.py @@ -0,0 +1,25 @@ +from rich.align import Align +from rich.console import Group, RenderableType +from rich.padding import Padding +from rich.style import Style +from rich.text import Text + +from spiel import Deck, Triggers + +deck = Deck(name="Trigger Examples") + + +@deck.slide(title="Revealing Content") +def reveal(triggers: Triggers) -> RenderableType: + lines = [ + Text.from_markup( + f"This slide has been triggered [yellow]{len(triggers)}[/yellow] time{'s' if len(triggers) > 1 else ''}." + ), + Text("First line.", style=Style(color="red")) if len(triggers) >= 1 else None, + Text("Second line.", style=Style(color="blue")) if len(triggers) >= 2 else None, + Text("Third line.", style=Style(color="green")) if len(triggers) >= 3 else None, + ] + + return Group( + *(Padding(Align.center(line), pad=(0, 0, 1, 0)) for line in lines if line is not None) + ) diff --git a/docs/generate_screenshots.py b/docs/generate_screenshots.py index 2c1a125..41a4670 100755 --- a/docs/generate_screenshots.py +++ b/docs/generate_screenshots.py @@ -2,10 +2,12 @@ import os from collections.abc import Iterable +from concurrent.futures import ProcessPoolExecutor, as_completed from datetime import datetime from functools import partial from io import StringIO from pathlib import Path +from time import monotonic from more_itertools import intersperse from rich.console import Console @@ -49,7 +51,9 @@ async def auto_pilot(pilot: Pilot, name: str, keys: Iterable[str]) -> None: await pilot.app.action_quit() -def take_screenshot(name: str, deck_file: Path, size: tuple[int, int], keys: Iterable[str]) -> None: +def take_screenshot(name: str, deck_file: Path, size: tuple[int, int], keys: Iterable[str]) -> str: + print(f"Generating {name}") + SpielApp( deck_path=deck_file, watch_path=deck_file.parent, @@ -61,12 +65,138 @@ def take_screenshot(name: str, deck_file: Path, size: tuple[int, int], keys: Ite size=size, ) - -demo_deck = ROOT_DIR / "spiel" / "demo" / "demo.py" -quickstart_deck = ROOT_DIR / "docs" / "examples" / "quickstart.py" - -take_screenshot(name="demo", deck_file=demo_deck, size=(130, 35), keys=()) -take_screenshot(name="deck", deck_file=demo_deck, size=(130, 35), keys=("d", "right", "down")) -take_screenshot(name="help", deck_file=demo_deck, size=(110, 35), keys=("?",)) -take_screenshot(name="quickstart_basic", deck_file=quickstart_deck, size=(60, 20), keys=()) -take_screenshot(name="quickstart_code", deck_file=demo_deck, size=(140, 45), keys=("right",)) + return name + + +if __name__ == "__main__": + start_time = monotonic() + + demo_deck = ROOT_DIR / "spiel" / "demo" / "demo.py" + quickstart_deck = ROOT_DIR / "docs" / "examples" / "quickstart.py" + slide_via_decorator = ROOT_DIR / "docs" / "examples" / "slide_via_decorator.py" + slide_loop = ROOT_DIR / "docs" / "examples" / "slide_loop.py" + triggers_reveal = ROOT_DIR / "docs" / "examples" / "triggers_reveal.py" + triggers_animation = ROOT_DIR / "docs" / "examples" / "triggers_animation.py" + + with ProcessPoolExecutor() as pool: + futures = [ + pool.submit( + take_screenshot, + name="triggers_animation_1", + deck_file=triggers_animation, + size=(70, 15), + keys=(), + ), + pool.submit( + take_screenshot, + name="triggers_animation_2", + deck_file=triggers_animation, + size=(70, 15), + keys=("wait:1400",), + ), + pool.submit( + take_screenshot, + name="triggers_animation_3", + deck_file=triggers_animation, + size=(70, 15), + keys=("wait:2900",), + ), + pool.submit( + take_screenshot, + name="triggers_animation_4", + deck_file=triggers_animation, + size=(70, 15), + keys=("wait:5400",), + ), + pool.submit( + take_screenshot, + name="demo", + deck_file=demo_deck, + size=(130, 35), + keys=(), + ), + pool.submit( + take_screenshot, + name="deck", + deck_file=demo_deck, + size=(130, 35), + keys=("d", "right", "down"), + ), + pool.submit( + take_screenshot, + name="help", + deck_file=demo_deck, + size=(110, 35), + keys=("?",), + ), + pool.submit( + take_screenshot, + name="quickstart_basic", + deck_file=quickstart_deck, + size=(70, 20), + keys=(), + ), + pool.submit( + take_screenshot, + name="quickstart_code", + deck_file=demo_deck, + size=(140, 45), + keys=("right",), + ), + pool.submit( + take_screenshot, + name="slide_via_decorator", + deck_file=slide_via_decorator, + size=(60, 15), + keys=(), + ), + pool.submit( + take_screenshot, + name="slide_loop_1", + deck_file=slide_loop, + size=(60, 15), + keys=(), + ), + pool.submit( + take_screenshot, + name="slide_loop_2", + deck_file=slide_loop, + size=(60, 15), + keys=("right",), + ), + pool.submit( + take_screenshot, + name="slide_loop_3", + deck_file=slide_loop, + size=(60, 15), + keys=("right", "right"), + ), + pool.submit( + take_screenshot, + name="triggers_reveal_1", + deck_file=triggers_reveal, + size=(70, 15), + keys=(), + ), + pool.submit( + take_screenshot, + name="triggers_reveal_2", + deck_file=triggers_reveal, + size=(70, 15), + keys=("t",), + ), + pool.submit( + take_screenshot, + name="triggers_reveal_3", + deck_file=triggers_reveal, + size=(70, 15), + keys=("t", "t"), + ), + ] + + for future in as_completed(futures, timeout=60): + print(f"Generated {future.result()}") + + end_time = monotonic() + + print(f"Generated {len(futures)} screenshots in {end_time - start_time:0.2f} seconds") diff --git a/docs/index.md b/docs/index.md index adcb2a1..17e4124 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,8 @@ # Spiel -Spiel is a framework for building and presenting [richly-styled](https://github.com/Textualize/rich) presentations in your terminal using Python. +[Spiel](https://dictionary.cambridge.org/us/dictionary/english/spiel) +is a framework for building and presenting +[richly-styled](https://github.com/Textualize/rich) presentations in your terminal using Python. ![The first slide of the demo deck](./assets/demo.svg) ![The demo deck in "deck view"](./assets/deck.svg) diff --git a/docs/presenting.md b/docs/presenting.md index d9c5c8b..e8b8504 100644 --- a/docs/presenting.md +++ b/docs/presenting.md @@ -29,9 +29,7 @@ run `spiel present --help` to see the arguments and available options. ## Using the `present` function -The `present` function lets you start a presentation programmatically (i.e., from a Python script). - -::: spiel.present +The [`present`][spiel.present] function lets you start a presentation programmatically (i.e., from a Python script). If your deck is defined in `talk/slides.py` like so: diff --git a/docs/quickstart.md b/docs/quickstart.md index 73d0cac..6d0950f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -6,20 +6,7 @@ After installing Spiel (`pip install spiel`), create a file called `deck.py` and copy this code into it: ```python -from rich.console import RenderableType - -from spiel import Deck, present - -deck = Deck(name="Your Deck Name") - - -@deck.slide(title="Slide 1 Title") -def slide_1() -> RenderableType: - return "Your content here!" - - -if __name__ == "__main__": - present(__file__) +--8<-- "examples/quickstart.py" ``` That is the most basic Spiel presentation you can make. @@ -29,13 +16,13 @@ You should see: ![Barebones slide](./assets/quickstart_basic.svg) In the example above, you first create a `Deck` and provide the name of your presentation. -Then you create slides by decorating functions with `@deck.slide()`, providing the title of the slide. +Then you create slides by decorating functions with `@deck.slide`, providing the title of the slide. The slide function can return anything that [Rich can render](https://rich.readthedocs.io/en/stable/console.html#printing); that return value will be displayed as the slide's content when you present it. -The order of the `@deck.slide()`-decorated functions in your file is the order in which they will appear in your presentation. +The order of the `@deck.slide`-decorated functions in your file is the order in which they will appear in your presentation. -Running `python deck.py` started the presentation because of the call to `present()` in the +Running `python deck.py` started the presentation because of the call to `present` in the [`if __name__ == "__main__"` block](https://stackoverflow.com/questions/419163/what-does-if-name-main-do). To see available keybindings for doing things like moving between slides, diff --git a/docs/slides.md b/docs/slides.md new file mode 100644 index 0000000..7ce036f --- /dev/null +++ b/docs/slides.md @@ -0,0 +1,155 @@ +# Making Slides + +## Slide Content Functions + +Each slide's content is rendered by calling a "content function" that returns a +[Rich `RenderableType`](https://rich.readthedocs.io/en/stable/console.html#printing). + +There are two primary ways to define these content functions. +For unique slides you can use the [`Deck.slide`][spiel.Deck.slide] decorator: + +```python +--8<-- "examples/slide_via_decorator.py" +``` +![Slide content via decorator](./assets/slide_via_decorator.svg) + +You might also find yourself wanting to create a set of slides programmatically +(well, even more programmatically). +You can use the [`Deck.add_slides`][spiel.Deck.add_slides] function to add +[`Slide`s][spiel.Slide] that you've created manually to your deck. + +```python +--8<-- "examples/slide_loop.py" +``` + +![Slide content via loop 1](./assets/slide_loop_1.svg) +![Slide content via loop 2](./assets/slide_loop_2.svg) +![Slide content via loop 3](./assets/slide_loop_3.svg) + +This pattern is useful when you have a generic "slide template" +that you want to feed multiple values into without copying a lot of code. +You have the full power of Python to define your slides, +so you can use as much (or as little) abstraction as you want. + +!!! tip "Slides are added to the deck in execution order" + + The slide order in the presentation is determined by the order + that the `Deck.slide` decorator and `Deck.add_slides` functions are used. + The two methods can be freely mixed; + just make sure to call them in the order you want the slides to + be presented in. + +## When and how often are slide content functions called? + +The slide content function is called for a wide variety of reasons +and it is not generally possible to predict how many times or exactly when +it will be called due a mix of time-interval-based and on-demand needs. + +Here are some examples of when the content function will be called: + +- When you move to the slide in Slide view. +- Sixty times per second while the slide is active in Slide view (see [Triggers](#triggers) below). +- When you switch to Deck view. +- The active slide's content function will be called if the deck is reloaded. + +!!! tip + + Because of how many times they will be called, + your content functions should be *fast* and *stateless*. + + If your content function needs state, + it should store and use it via the [Fixtures](#fixtures) discussed below. + +## Fixtures + +The slide content function can take extra +[keyword arguments](https://docs.python.org/3/glossary.html#term-argument) +that provide additional information for advanced rendering techniques. + +To have Spiel pass your content function one of these fixtures, +include a keyword argument with the corresponding fixture name in your content function's signature. + +### Triggers + +- Keyword: `triggers` +- Type: [`Triggers`][spiel.Triggers] + +The `triggers` fixture is useful for making slides whose content depends either on +relative time (e.g., time since the slide started being displayed) +or where the content should change when the user "triggers" it +(similar to how a PowerPoint animation can be configured to run +[`On Click`](https://support.microsoft.com/en-us/office/animate-text-or-objects-305a1c94-83b1-4778-8df5-fcf7a9b7b7c6)). + +To *trigger* a slide, press `t` in Slide view while displaying it. +Additionally, each slide is automatically triggered once when it starts being +displayed so that properties like +[`Triggers.time_since_last_trigger`][spiel.Triggers.time_since_last_trigger] +will always have usable values. + +The `Triggers` object in any given call of the content function behaves like an immutable sequence of floats, +which represent relative times (in seconds) at which the slide has been triggered. +These relative times are comparable to each other, but are not comparable +to values generated by e.g. [`time.time`][time.time] (they are generated by [`time.monotonic`][time.monotonic]). +Over multiple calls of the content function, +the sequence of relative times is append-only: +any trigger time that has been added to the sequence will stay there until the + +[`Triggers.now`][spiel.Triggers.now] is also available, +representing the relative time that the slide is being rendered at. + +Triggers are reset when changing slides: +if you trigger a slide, +go to another slide, +then back to the initial slide, +the `triggers` from the first "instance" +of showing the slide *not* be remembered. + +!!! info "`Trigger.now` resolution" + + Your slide content function will be called every sixtieth of a second, + so the best time resolution you can get is about 16 milliseconds between + renders, and therefore between `Trigger.now` values. + +#### Revealing Content using Triggers + +A simple use case for `triggers` is to gradually reveal content. +We won't even use the "relative time" component for this: +we'll just track how many times the slide has been triggered. + +```python +--8<-- "examples/triggers_reveal.py" +``` + +When first displayed, the slide will look like this: + +![Triggers reveal 1](./assets/triggers_reveal_1.svg) + +Note that the slide has already been triggered once, +even though we haven't pressed `t` yet! +As mentioned above, each slide is automatically triggered once +when it starts being displayed. + +After pressing `t` to trigger the slide (really the second trigger): + +![Triggers reveal 2](./assets/triggers_reveal_2.svg) + +And after pressing `t` again (really the third trigger): + +![Triggers reveal 3](./assets/triggers_reveal_3.svg) + +#### Animating Content using Triggers + +Let's build a simple animation that is driven by the time since the slide +started being displayed: + +```python +--8<-- "examples/triggers_animation.py" +``` + +Here are some screenshots showing what the slide looks like at various times +after being displayed, with no additional key presses: + +![Triggers animation 1](./assets/triggers_animation_1.svg) +![Triggers animation 2](./assets/triggers_animation_2.svg) +![Triggers animation 3](./assets/triggers_animation_3.svg) +![Triggers animation 4](./assets/triggers_animation_4.svg) diff --git a/mkdocs.yml b/mkdocs.yml index d0a471c..29e406f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -59,7 +59,9 @@ markdown_extensions: - pymdownx.highlight: anchor_linenums: true - pymdownx.inlinehilite - - pymdownx.snippets + - pymdownx.snippets: + base_path: ['docs'] + check_paths: true - pymdownx.superfences - pymdownx.tabbed: alternate_style: true @@ -80,6 +82,7 @@ nav: - Introduction: index.md - quickstart.md - presenting.md + - slides.md - api.md - gallery.md - contributing.md diff --git a/spiel/app.py b/spiel/app.py index da8443e..6a6e24a 100644 --- a/spiel/app.py +++ b/spiel/app.py @@ -11,7 +11,7 @@ from contextlib import contextmanager, redirect_stderr, redirect_stdout from functools import cached_property, partial from pathlib import Path from time import monotonic -from typing import Callable, ContextManager, Iterator, Optional +from typing import Callable, ContextManager, Iterator from rich.style import Style from rich.text import Text @@ -210,7 +210,7 @@ class SpielApp(App[None]): return max(self.size.width // 35, 1) -def present(deck_path: Path | str, watch_path: Optional[Path | str] = None) -> None: +def present(deck_path: Path | str, watch_path: Path | str | None = None) -> None: """ Present the deck defined in the given `deck_path`.