You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
spiel/search/search_index.json

1 line
40 KiB
JSON

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Spiel","text":"<p>Spiel is a framework for building and presenting richly-styled presentations in your terminal using Python.</p> <p> </p>"},{"location":"api/","title":"API Reference","text":""},{"location":"api/#decks-and-slides","title":"Decks and Slides","text":""},{"location":"api/#spiel.Deck","title":"<code>spiel.Deck</code> <code>dataclass</code>","text":"<p> Bases: <code>Sequence[Slide]</code></p> <p>Represents a \"deck\" of \"slides\": a presentation.</p>"},{"location":"api/#spiel.Deck.name","title":"<code>name: str</code> <code>instance-attribute</code>","text":"<p>The name of the <code>Deck</code>, which will be displayed in the footer.</p>"},{"location":"api/#spiel.Deck.default_transition","title":"<code>default_transition: Type[Transition] | None = Swipe</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>The default slide transition animation; used if the slide being moved to does not specify its own transition. Defaults to the <code>Swipe</code> transition. Set to <code>None</code> for no transition animation.</p>"},{"location":"api/#spiel.Deck.slide","title":"<code>slide(title='', bindings=None, transition=None)</code>","text":"<p>A decorator that creates a new slide in the deck, with the decorated function as the <code>Slide.content</code>.</p> PARAMETER DESCRIPTION <code>title</code> <p>The title to display for the slide.</p> <p> TYPE: <code>str</code> DEFAULT: <code>''</code> </p> <code>bindings</code> <p>A mapping of keys to callables to be executed when those keys are pressed, when on this slide.</p> <p> TYPE: <code>Mapping[str, Callable[..., None]] | None</code> DEFAULT: <code>None</code> </p> <code>transition</code> <p>The transition animation to use when moving to this slide. Set to <code>None</code> to use the <code>Deck.default_transition</code> of the deck this slide is in.</p> <p> TYPE: <code>Type[Transition] | None</code> DEFAULT: <code>None</code> </p>"},{"location":"api/#spiel.Deck.add_slides","title":"<code>add_slides(*slides)</code>","text":"<p>Add <code>Slide</code>s to a <code>Deck</code>.</p> <p>This function is primarily useful when adding multiple slides at once, probably generated programmatically. If adding a single slide, prefer the <code>Deck.slide</code> decorator.</p> PARAMETER DESCRIPTION <code>*slides</code> <p>The <code>Slide</code>s to add.</p> <p> TYPE: <code>Slide</code> DEFAULT: <code>()</code> </p>"},{"location":"api/#spiel.Slide","title":"<code>spiel.Slide</code> <code>dataclass</code>","text":"<p>Represents a single slide in the presentation.</p>"},{"location":"api/#spiel.Slide.title","title":"<code>title: str = ''</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>The title of the <code>Slide</code>, which will be displayed in the footer.</p>"},{"location":"api/#spiel.Slide.content","title":"<code>content: Content = Text</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>A callable that is invoked by Spiel to display the slide's content.</p> <p>The function may optionally take arguments with these names:</p> <ul> <li><code>trigger</code>: The current <code>Trigger</code> state, for use in animations.</li> </ul>"},{"location":"api/#spiel.Slide.bindings","title":"<code>bindings: Mapping[str, Callable[..., None]] = field(default_factory=dict)</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>A mapping of keys to callables to be executed when those keys are pressed, when on this slide.</p>"},{"location":"api/#spiel.Slide.transition","title":"<code>transition: Type[Transition] | None = Swipe</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>The transition animation to use when moving to this slide. Set to <code>None</code> to use the <code>Deck.default_transition</code> of the deck this slide is in.</p>"},{"location":"api/#rendering-content","title":"Rendering Content","text":""},{"location":"api/#spiel.Triggers","title":"<code>spiel.Triggers</code> <code>dataclass</code>","text":"<p> Bases: <code>Sequence[float]</code></p> <p>Provides information to <code>Slide.content</code> about the current slide's \"trigger state\".</p> <p><code>Triggers</code> is a <code>Sequence</code> of times (produced by <code>time.monotonic</code>) that the current slide was triggered at. Note that the slide will be triggered once when it starts being displayed, so the first trigger time will be the time when the slide started being displayed.</p>"},{"location":"api/#spiel.Triggers.now","title":"<code>now: float</code> <code>instance-attribute</code>","text":"<p>The time that the slide content is being rendered at. Use this is as a single consistent value to base relative times on.</p>"},{"location":"api/#spiel.Triggers.time_since_last_trigger","title":"<code>time_since_last_trigger: float</code> <code>cached</code> <code>property</code>","text":"<p>The elapsed time since the most recent trigger.</p>"},{"location":"api/#spiel.Triggers.time_since_first_trigger","title":"<code>time_since_first_trigger: float</code> <code>cached</code> <code>property</code>","text":"<p>The elapsed time since the first trigger, which is equivalent to the time since the slide started being displayed.</p>"},{"location":"api/#spiel.Triggers.triggered","title":"<code>triggered: bool</code> <code>cached</code> <code>property</code>","text":"<p>Returns whether the slide has been manually triggered (i.e., this ignores the initial trigger from when the slide starts being displayed).</p>"},{"location":"api/#spiel.Triggers.take","title":"<code>take(iter, offset=1)</code>","text":"<p>Takes elements from the iterable <code>iter</code> equal to the number of times in the <code>Triggers</code> minus the offset.</p> PARAMETER DESCRIPTION <code>iter</code> <p>The iterable to take elements from.</p> <p> TYPE: <code>Iterable[T]</code> </p> <code>offset</code> <p>This <code>offset</code> will be subtracted from the number of triggers, reducing the number of elements that will be returned. It defaults to <code>1</code> to ignore the automatic trigger from when the slide starts being shown.</p> <p> TYPE: <code>int</code> DEFAULT: <code>1</code> </p> RETURNS DESCRIPTION <code>Iterator[T]</code> <p>An iterator over the first <code>len(self) - offset</code> elements of <code>iter</code>.</p>"},{"location":"api/#transitions","title":"Transitions","text":""},{"location":"api/#spiel.Direction","title":"<code>spiel.Direction</code>","text":"<p> Bases: <code>Enum</code></p> <p>An enumeration that describes which direction a slide transition animation should move in: whether we're going to the next slide, or to the previous slide.</p>"},{"location":"api/#spiel.Direction.Next","title":"<code>Next = 'next'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Indicates that the transition should handle going to the next slide.</p>"},{"location":"api/#spiel.Direction.Previous","title":"<code>Previous = 'previous'</code> <code>class-attribute</code> <code>instance-attribute</code>","text":"<p>Indicates that the transition should handle going to the previous slide.</p>"},{"location":"api/#spiel.Transition","title":"<code>spiel.Transition</code>","text":"<p> Bases: <code>Protocol</code></p> <p>A protocol that describes how to implement a transition animation.</p> <p>See Writing Custom Transitions for more details on how to implement the protocol.</p>"},{"location":"api/#spiel.Transition.initialize","title":"<code>initialize(from_widget, to_widget, direction)</code>","text":"<p>A hook function to set up any CSS that should be present at the start of the transition.</p> PARAMETER DESCRIPTION <code>from_widget</code> <p>The widget showing the slide that we are leaving.</p> <p> TYPE: <code>Widget</code> </p> <code>to_widget</code> <p>The widget showing the slide that we are entering.</p> <p> TYPE: <code>Widget</code> </p> <code>direction</code> <p>The desired direction of the transition animation.</p> <p> TYPE: <code>Direction</code> </p>"},{"location":"api/#spiel.Transition.progress","title":"<code>progress(from_widget, to_widget, direction, progress)</code>","text":"<p>A hook function that is called each time the <code>progress</code> of the transition animation updates.</p> PARAMETER DESCRIPTION <code>from_widget</code> <p>The widget showing the slide that we are leaving.</p> <p> TYPE: <code>Widget</code> </p> <code>to_widget</code> <p>The widget showing the slide that we are entering.</p> <p> TYPE: <code>Widget</code> </p> <code>direction</code> <p>The desired direction of the transition animation.</p> <p> TYPE: <code>Direction</code> </p> <code>progress</code> <p>The progress of the animation, as a percentage (e.g., initial state is <code>0</code>, final state is <code>100</code>). Note that this is not necessarily bounded between <code>0</code> and <code>100</code>, nor is it necessarily monotonically increasing, depending on the underlying Textual animation easing function, which may overshoot or bounce. However, it will always start at <code>0</code> and end at <code>100</code>, no matter which <code>direction</code> the transition should move in.</p> <p> TYPE: <code>float</code> </p>"},{"location":"api/#spiel.Swipe","title":"<code>spiel.Swipe</code>","text":"<p> Bases: <code>Transition</code></p> <p>A transition where the current and incoming slide are placed side-by-side and gradually slide across the screen, with the current slide leaving and the incoming slide entering.</p>"},{"location":"api/#presenting-decks","title":"Presenting Decks","text":""},{"location":"api/#spiel.present","title":"<code>spiel.present(deck_path, watch_path=None)</code>","text":"<p>Present the deck defined in the given <code>deck_path</code>.</p> PARAMETER DESCRIPTION <code>deck_path</code> <p>The file to look for a deck in.</p> <p> TYPE: <code>Path | str</code> </p> <code>watch_path</code> <p>When filesystem changes are detected below this path (recursively), reload the deck from the <code>deck_path</code>. If <code>None</code> (the default), use the parent directory of the <code>deck_path</code>.</p> <p> TYPE: <code>Path | str | None</code> DEFAULT: <code>None</code> </p>"},{"location":"changelog/","title":"Changelog","text":""},{"location":"changelog/#051","title":"<code>0.5.1</code>","text":"<p>Released <code>2023-04-21</code></p>"},{"location":"changelog/#changed","title":"Changed","text":"<ul> <li>#222 Pin to Textual <code>0.11.1</code> temporarily to resolve issues with slide transitions.</li> </ul>"},{"location":"changelog/#050","title":"<code>0.5.0</code>","text":"<p>Released <code>2023-02-19</code></p>"},{"location":"changelog/#added","title":"Added","text":"<ul> <li>#207 Add a default \"swipe\" transition between slides and support for user-defined transitions.</li> </ul>"},{"location":"changelog/#046","title":"<code>0.4.6</code>","text":"<p>Released <code>2023-01-19</code></p>"},{"location":"changelog/#changed_1","title":"Changed","text":"<ul> <li>#208 Unpinned <code>textual==0.4.0</code> and allowed <code>textual&gt;=0.10.0</code>, which includes textual#1558.</li> </ul>"},{"location":"changelog/#045","title":"<code>0.4.5</code>","text":"<p>Released <code>2023-01-16</code></p>"},{"location":"changelog/#added_1","title":"Added","text":"<ul> <li>#205 Add <code>Triggers.take</code> to make gradually revealing content on a slide more straightforward.</li> </ul>"},{"location":"changelog/#fixed","title":"Fixed","text":"<ul> <li>#202 Returning un-renderable content from a slide content function now displays an error instead of crashing Spiel.</li> </ul>"},{"location":"changelog/#changed_2","title":"Changed","text":"<ul> <li>#203 The <code>Image</code> example in the demo deck is now centered inside its <code>Panel</code>.</li> </ul>"},{"location":"changelog/#044","title":"<code>0.4.4</code>","text":"<p>Released <code>2023-01-13</code></p>"},{"location":"changelog/#added_2","title":"Added","text":"<ul> <li>#185 The docs page now includes copy-to-clipboard buttons on all code snippets.</li> <li>#194 The demo slides now render their own source code directly to demo bindings functionality.</li> </ul>"},{"location":"changelog/#changed_3","title":"Changed","text":"<ul> <li>#194 The <code>Deck.slide</code> decorator now returns the decorated function, not the <code>Slide</code> it was attached to.</li> <li>#199 The CLI command <code>spiel present</code>'s <code>--watch</code> option now defaults to the parent directory of the deck file instead of the current working directory.</li> </ul>"},{"location":"changelog/#043","title":"<code>0.4.3</code>","text":"<p>Released <code>2023-01-02</code></p>"},{"location":"changelog/#added_3","title":"Added","text":"<ul> <li>#169 The Textual application title and subtitle are now set dynamically from the Spiel deck name and slide title, respectively.</li> <li>#178 <code>spiel.Deck</code> is now a <code>Sequence[Slide]</code>, and <code>spiel.Triggers</code> is now a <code>Sequence[float]</code>.</li> </ul>"},{"location":"changelog/#fixed_1","title":"Fixed","text":"<ul> <li>#168 The correct type for the <code>suspend</code> optional argument to slide-level keybinding functions is now available as <code>spiel.SuspendType</code>.</li> <li>#168 The Spiel container image no longer has a leftover copy of the <code>spiel</code> package directory inside the image under <code>/app</code>.</li> </ul>"},{"location":"changelog/#042","title":"<code>0.4.2</code>","text":"<p>Released <code>2022-12-10</code></p>"},{"location":"changelog/#added_4","title":"Added","text":"<ul> <li>#163 Added a public <code>spiel.present()</code> function that presents the deck at the given file.</li> </ul>"},{"location":"changelog/#041","title":"<code>0.4.1</code>","text":"<p>Released <code>2022-11-25</code></p>"},{"location":"changelog/#fixed_2","title":"Fixed","text":"<ul> <li>#157 Pinned to Textual v0.4.0 to work around Textual#1274.</li> </ul>"},{"location":"changelog/#040","title":"<code>0.4.0</code>","text":"<p>Released <code>2022-11-25</code></p>"},{"location":"changelog/#changed_4","title":"Changed","text":"<ul> <li>#154 Switch to Textual as the overall control and rendering engine.</li> </ul>"},{"location":"changelog/#removed","title":"Removed","text":"<ul> <li>#154 Removed library-provided <code>Example</code> slides, <code>Options</code>, and various other small features as part of the Textual migration. Some of these features will likely be reintroduced later.</li> </ul>"},{"location":"changelog/#030","title":"<code>0.3.0</code>","text":""},{"location":"changelog/#removed_1","title":"Removed","text":"<ul> <li>#129 Dropped support for Python <code>&lt;=3.9</code>.</li> </ul>"},{"location":"contributing/","title":"Contributing Guide","text":"<p>Spiel is open to contributions!</p> <ul> <li>Report bugs and request features</li> <li>General discussion</li> <li>Pull requests</li> </ul>"},{"location":"contributing/#development-environment","title":"Development Environment","text":"<p>Spiel uses:</p> <ul> <li><code>poetry</code> to manage development dependencies.</li> <li><code>pre-commit</code> to run various linters and formatters.</li> <li><code>pytest</code> for testing and <code>mypy</code> for static type-checking.</li> <li><code>mkdocs</code> with the Material theme for documentation.</li> </ul>"},{"location":"contributing/#initial-setup","title":"Initial Setup","text":"<p>To set up a local development environment after cloning the repository:</p> <ol> <li>Install <code>poetry</code>.</li> <li>Run <code>poetry shell</code> to create a virtual environment for <code>spiel</code> and spawn a new shell session with that virtual environment activated. In the future you'll run <code>poetry shell</code> again to activate the virtual environment.</li> <li>Run <code>poetry install</code> to install Spiel's dependencies.</li> <li>Run <code>pre-commit install</code> to configure <code>pre-commit</code>'s integration with <code>git</code>. Do not commit without <code>pre-commit</code> installed!</li> </ol>"},{"location":"contributing/#running-tests-and-type-checking","title":"Running Tests and Type-Checking","text":"<p>Run <code>pytest</code> to run tests.</p> <p>Run <code>mypy</code> to check types.</p>"},{"location":"contributing/#building-the-docs-locally","title":"Building the Docs Locally","text":"<p>To build the docs and start a local web server to view the results of your edits with live reloading, run <pre><code>mkdocs serve\n</code></pre> from the repository root.</p>"},{"location":"gallery/","title":"Gallery","text":"<ul> <li>pytest: It's What's For Testing by JoshKarpel.</li> </ul>"},{"location":"gallery/#submitting-to-the-gallery","title":"Submitting to the Gallery","text":"<p>If you've made a talk with Spiel, please feel free to submit a pull request to add it to the gallery.</p>"},{"location":"presenting/","title":"Presenting Decks","text":"<p>Depending on your preferred workflow, you can start a presentation in a variety of different ways.</p> <p>Sandboxed Execution</p> <p>Spiel presentations are live Python code: they can do anything that Python can do. You may want to run untrusted presentations (or even your own presentations) inside a container (but remember, even containers are not perfectly safe!). We produce a container image that can be run by (for example) Docker.</p> <p>Presentations without extra Python dependencies might just need to be bind-mounted into the container. For example, if your demo file is at <code>$PWD/presentation/deck.py</code>, you could do <pre><code>$ docker run -it --rm --mount type=bind,source=$PWD/presentation,target=/presentation ghcr.io/joshkarpel/spiel spiel present /presentation/deck.py\n</code></pre></p> <p>If the presentation has extra dependencies (like other Python packages), we recommend building a new image that inherits our image (e.g., <code>FROM ghcr.io/joshkarpel/spiel:vX.Y.Z</code>). Spiel's image itself inherits from the Python base image.</p>"},{"location":"presenting/#using-the-spiel-cli","title":"Using the <code>spiel</code> CLI","text":"<p>Installing the Spiel package provides a CLI tool called <code>spiel</code>. The <code>spiel present</code> subcommand allows you to present a deck; run <code>spiel present --help</code> to see the arguments and available options.</p>"},{"location":"presenting/#using-the-present-function","title":"Using the <code>present</code> function","text":"<p>The <code>present</code> function lets you start a presentation programmatically (i.e., from a Python script).</p> <p>If your deck is defined in <code>talk/slides.py</code> like so:</p> talk/slides.py<pre><code>#!/usr/bin/env python\n\nfrom spiel import Deck, present\n\ndeck = Deck(...)\n\n... # construct your deck\n\nif __name__ == \"__main__\":\n present(__file__)\n</code></pre> <p>You can then present the deck by running the script: <pre><code>python talk/slides.py\n</code></pre> Or by running the script as a module (you must have a <code>talk/__init__.py</code> file): <pre><code>python -m talk.slides\n</code></pre> Or by running the script via its shebang (after running <code>chmod +x talk/slides.py</code> to mark <code>talk/slides.py</code> as executable): <pre><code>talk/slides.py\n</code></pre></p>"},{"location":"quickstart/","title":"Quick Start","text":""},{"location":"quickstart/#the-most-basic-deck","title":"The Most Basic Deck","text":"<p>After installing Spiel (<code>pip install spiel</code>), create a file called <code>deck.py</code> and copy this code into it:</p> deck.py<pre><code>from rich.console import RenderableType\n\nfrom spiel import Deck, present\n\ndeck = Deck(name=\"Your Deck Name\")\n\n\n@deck.slide(title=\"Slide 1 Title\")\ndef slide_1() -&gt; RenderableType:\n return \"Your content here!\"\n\n\nif __name__ == \"__main__\":\n present(__file__)\n</code></pre> <p>That is the most basic Spiel presentation you can make. To present the deck, run <code>python deck.py</code>. You should see:</p> <p></p> <p>In the example above, you first create a <code>Deck</code> and provide the name of your presentation. Then you create slides by decorating functions with <code>@deck.slide</code>, providing the title of the slide. The slide function can return anything that Rich can render; that return value will be displayed as the slide's content when you present it. The order of the <code>@deck.slide</code>-decorated functions in your file is the order in which they will appear in your presentation.</p> <p>Running <code>python deck.py</code> started the presentation because of the call to <code>present</code> in the <code>if __name__ == \"__main__\"</code> block.</p> <p>To see available keybindings for doing things like moving between slides, press <code>?</code> to open the help view, which should look like this:</p> <p></p>"},{"location":"quickstart/#making-richer-slides","title":"Making Richer Slides","text":"<p>You can make your slides a lot prettier, of course. As mentioned above, Spiel renders its slides using Rich, so you can bring in Rich functionality to spruce up your slides. Let's explore some advanced features by recreating one of the slides from the demo deck. Update your <code>deck.py</code> file with these imports and utility definitions:</p> deck.py<pre><code>import inspect\nfrom textwrap import dedent\n\nfrom rich.box import SQUARE\nfrom rich.console import RenderableType\nfrom rich.layout import Layout\nfrom rich.markdown import Markdown\nfrom rich.padding import Padding\nfrom rich.panel import Panel\nfrom rich.style import Style\nfrom rich.syntax import Syntax\n\nfrom spiel import Deck, Slide, present\nfrom spiel.deck import Deck\n\n\nSPIEL = \"[Spiel](https://github.com/JoshKarpel/spiel)\"\nRICH = \"[Rich](https://rich.readthedocs.io/)\"\n\ndef pad_markdown(markup: str) -&gt; RenderableType:\n return Padding(Markdown(dedent(markup), justify=\"center\"), pad=(0, 5))\n</code></pre> <p>And then paste this code into your <code>deck.py</code> file below your first slide:</p> deck.py<pre><code>@deck.slide(title=\"Decks and Slides\")\ndef code() -&gt; RenderableType:\n markup = f\"\"\"\\\n ## Decks are made of Slides\n\n Here's the code for `Deck` and `Slide`!\n\n The source code is pulled directly from the definitions via [inspect.getsource](https://docs.python.org/3/library/inspect.html#inspect.getsource).\n\n ({RICH} supports syntax highlighting, so {SPIEL} does too!)\n \"\"\"\n root = Layout()\n upper = Layout(pad_markdown(markup), size=len(markup.split(\"\\n\")) + 1)\n lower = Layout()\n root.split_column(upper, lower)\n\n def make_code_panel(obj: type) -&gt; RenderableType:\n lines, line_number = inspect.getsourcelines(obj)\n return Panel(\n Syntax(\n \"\".join(lines),\n lexer=\"python\",\n line_numbers=True,\n start_line=line_number,\n ),\n box=SQUARE,\n border_style=Style(dim=True),\n height=len(lines) + 2,\n )\n\n lower.split_row(\n Layout(make_code_panel(Deck)),\n Layout(make_code_panel(Slide)),\n )\n\n return root\n</code></pre> <p>We start out by creating our text content and setting up some <code>Layout</code>s, which will let us divide the slide space into chunks. Then, we create the <code>make_code_panel</code> function to take some lines of code from the <code>Deck</code> and <code>Slide</code> classes and put them in a syntax-highlighted <code>Panel</code> (with some additional fancy Rich styling). Finally, we add the code panels to our layout side-by-side and return <code>root</code>, the top-level <code>Layout</code>.</p> <p>Run <code>python deck.py</code> again and go to the second slide (press <code>?</code> if you're not sure how to navigate!):</p> <p></p> <p>Check out the source code of the demo deck for more inspiration on ways to use Rich to make your slides beautiful! Spiel provides a <code>spiel</code> CLI tool to make this easy:</p> <ul> <li>Present the demo deck in your terminal by running <code>spiel demo present</code>.</li> <li>View the source in your terminal with <code>spiel demo source</code>.</li> <li>Copy it to use as a starting point with <code>spiel demo copy &lt;destination&gt;</code>.</li> </ul> <p>You can also check out the gallery to see talks that other users have made.</p>"},{"location":"slides/","title":"Making Slides","text":""},{"location":"slides/#slide-content-functions","title":"Slide Content Functions","text":"<p>Each slide's content is rendered by calling a \"content function\" that returns a Rich <code>RenderableType</code>.</p> <p>There are two primary ways to define these content functions. For unique slides you can use the <code>Deck.slide</code> decorator:</p> <p><pre><code>from rich.align import Align\nfrom rich.console import RenderableType\nfrom rich.text import Text\n\nfrom spiel import Deck\n\ndeck = Deck(name=\"Deck Name\")\n\n\n@deck.slide(title=\"Slide Title\")\ndef slide_content() -&gt; RenderableType:\n return Align(\n Text.from_markup(\n \"[blue]Your[/blue] [red underline]content[/red underline] [green italic]here[/green italic]!\"\n ),\n align=\"center\",\n vertical=\"middle\",\n )\n</code></pre> </p> <p>You might also find yourself wanting to create a set of slides programmatically (well, even more programmatically). You can use the <code>Deck.add_slides</code> function to add <code>Slide</code>s that you've created manually to your deck.</p> <pre><code>from rich.align import Align\nfrom rich.console import RenderableType\nfrom rich.style import Style\nfrom rich.text import Text\n\nfrom spiel import Deck, Slide\n\ndeck = Deck(name=\"Deck Name\")\n\n\ndef make_slide(\n title_prefix: str,\n text: Text,\n) -&gt; Slide:\n def content() -&gt; RenderableType:\n return Align(text, align=\"center\", vertical=\"middle\")\n\n return Slide(title=f\"{title_prefix} Slide\", content=content)\n\n\ndeck.add_slides(\n make_slide(title_prefix=\"First\", text=Text(\"Foo\", style=Style(color=\"blue\"))),\n make_slide(title_prefix=\"Second\", text=Text(\"Bar\", style=Style(color=\"red\"))),\n make_slide(title_prefix=\"Third\", text=Text(\"Baz\", style=Style(color=\"green\"))),\n)\n</code></pre> <p> </p> <p>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.</p> <p>Slides are added to the deck in execution order</p> <p>The slide order in the presentation is determined by the order that the <code>Deck.slide</code> decorator and <code>Deck.add_slides</code> 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.</p>"},{"location":"slides/#when-and-how-often-are-slide-content-functions-called","title":"When and how often are slide content functions called?","text":"<p>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.</p> <p>Here are some examples of when the content function will be called:</p> <ul> <li>When you move to the slide in Slide view.</li> <li>Sixty times per second while the slide is active in Slide view (see Triggers below).</li> <li>When you switch to Deck view.</li> <li>The active slide's content function will be called if the deck is reloaded.</li> </ul> <p>Tip</p> <p>Because of how many times they will be called, your content functions should be fast and stateless.</p> <p>If your content function needs state, it should store and use it via the Fixtures discussed below.</p>"},{"location":"slides/#fixtures","title":"Fixtures","text":"<p>The slide content function can take extra keyword arguments that provide additional information for advanced rendering techniques.</p> <p>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.</p>"},{"location":"slides/#triggers","title":"Triggers","text":"<ul> <li>Keyword: <code>triggers</code></li> <li>Type: <code>Triggers</code></li> </ul> <p>The <code>triggers</code> 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 <code>On Click</code>).</p> <p>To trigger a slide, press <code>t</code> in Slide view while displaying it. Additionally, each slide is automatically triggered once when it starts being displayed so that properties like <code>Triggers.time_since_last_trigger</code> will always have usable values.</p> <p>The <code>Triggers</code> 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. <code>time.time</code> (they are generated by <code>time.monotonic</code>). 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</p> <p><code>Triggers.now</code> is also available, representing the relative time that the slide is being rendered at.</p> <p>Triggers are reset when changing slides: if you trigger a slide, go to another slide, then back to the initial slide, the <code>triggers</code> from the first \"instance\" of showing the slide not be remembered.</p> <p><code>Trigger.now</code> resolution</p> <p>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 <code>Trigger.now</code> values.</p>"},{"location":"slides/#revealing-content-using-triggers","title":"Revealing Content using Triggers","text":"<p>A simple use case for <code>triggers</code> 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.</p> <pre><code>from rich.align import Align\nfrom rich.console import Group, RenderableType\nfrom rich.padding import Padding\nfrom rich.style import Style\nfrom rich.text import Text\n\nfrom spiel import Deck, Triggers\n\ndeck = Deck(name=\"Trigger Examples\")\n\n\n@deck.slide(title=\"Revealing Content\")\ndef reveal(triggers: Triggers) -&gt; RenderableType:\n lines = [\n Text.from_markup(\n f\"This slide has been triggered [yellow]{len(triggers)}[/yellow] time{'s' if len(triggers) &gt; 1 else ''}.\"\n ),\n Text(\"First line.\", style=Style(color=\"red\")) if len(triggers) &gt;= 1 else None,\n Text(\"Second line.\", style=Style(color=\"blue\")) if len(triggers) &gt;= 2 else None,\n Text(\"Third line.\", style=Style(color=\"green\")) if len(triggers) &gt;= 3 else None,\n ]\n\n return Group(\n *(Padding(Align.center(line), pad=(0, 0, 1, 0)) for line in lines if line is not None)\n )\n</code></pre> <p>When first displayed, the slide will look like this:</p> <p></p> <p>Note that the slide has already been triggered once, even though we haven't pressed <code>t</code> yet! As mentioned above, each slide is automatically triggered once when it starts being displayed.</p> <p>After pressing <code>t</code> to trigger the slide (really the second trigger):</p> <p></p> <p>And after pressing <code>t</code> again (really the third trigger):</p> <p></p>"},{"location":"slides/#animating-content-using-triggers","title":"Animating Content using Triggers","text":"<p>Let's build a simple animation that is driven by the time since the slide started being displayed:</p> <pre><code>from math import floor\n\nfrom rich.align import Align\nfrom rich.console import Group, RenderableType\nfrom rich.panel import Panel\nfrom rich.text import Text\n\nfrom spiel import Deck, Triggers\n\ndeck = Deck(name=\"Trigger Examples\")\n\n\n@deck.slide(title=\"Animating Content\")\ndef animate(triggers: Triggers) -&gt; RenderableType:\n bang = \"!\"\n space = \" \"\n bar_length = 5\n\n spaces_before_bang = min(floor(triggers.time_since_first_trigger), bar_length)\n spaces_after_bang = bar_length - spaces_before_bang\n\n bar = (space * spaces_before_bang) + bang + (space * spaces_after_bang)\n\n return Align(\n Group(\n Align.center(Text(f\"{triggers=}\")),\n Align.center(Text(f\"{spaces_before_bang=} | {spaces_after_bang=}\")),\n Align.center(Panel(Text(bar), expand=False, height=3)),\n ),\n align=\"center\",\n vertical=\"middle\",\n )\n</code></pre> <p>Here are some screenshots showing what the slide looks like at various times after being displayed, with no additional key presses:</p> <p> </p>"},{"location":"transitions/","title":"Slide Transitions","text":"<p>Under construction!</p> <p>Transitions are a new and experiment feature in Spiel and the interface might change dramatically from version to version. If you plan on using transitions, we recommend pinning the exact version of Spiel your presentation was developed in to ensure stability.</p>"},{"location":"transitions/#setting-transitions","title":"Setting Transitions","text":"<p>To set the default transition for the entire deck, which will be used if a slide does not override it, set <code>Deck.default_transition</code> to a type that implements the <code>Transition</code> protocol.</p> <p>For example, the default transition is <code>Swipe</code>, so not passing <code>default_transition</code> at all is equivalent to</p> <pre><code>from spiel import Deck, Swipe\n\ndeck = Deck(name=f\"Spiel Demo Deck\", default_transition=Swipe)\n</code></pre> <p>To override the deck-wide default for an individual slide, specify the transition type in the <code>@slide</code> decorator:</p> <pre><code>from spiel import Deck, Swipe\n\ndeck = Deck(name=f\"Spiel Demo Deck\")\n\n@deck.slide(title=\"My Title\", transition=Swipe)\ndef slide():\n ...\n</code></pre> <p>Or, in the arguments to <code>Slide</code>:</p> <pre><code>from spiel import Slide, Swipe\n\nslide = Slide(title=\"My Title\", transition=Swipe)\n</code></pre> <p>In either case, the specified transition will be used when transitioning to that slide. It does not matter whether the slide is the \"next\" or \"previous\" slide: the slide being moved to determines which transition effect will be used.</p>"},{"location":"transitions/#disabling-transitions","title":"Disabling Transitions","text":"<p>In any of the above examples, you can also set <code>default_transition</code>/<code>transition</code> to <code>None</code>. In that case, there will be no transition effect when moving to the slide; it will just be displayed on the next render, already in-place.</p>"},{"location":"transitions/#writing-custom-transitions","title":"Writing Custom Transitions","text":"<p>To implement your own custom transition, you must write a class which implements the <code>Transition</code> protocol.</p> <p>The protocol is:</p> Transition Protocol<pre><code>from __future__ import annotations\n\nfrom enum import Enum\nfrom typing import Protocol, runtime_checkable\n\nfrom textual.widget import Widget\n\n\nclass Direction(Enum):\n \"\"\"\n An enumeration that describes which direction a slide transition\n animation should move in: whether we're going to the next slide,\n or to the previous slide.\n \"\"\"\n\n Next = \"next\"\n \"\"\"Indicates that the transition should handle going to the next slide.\"\"\"\n\n Previous = \"previous\"\n \"\"\"Indicates that the transition should handle going to the previous slide.\"\"\"\n\n\n@runtime_checkable\nclass Transition(Protocol):\n \"\"\"\n A protocol that describes how to implement a transition animation.\n\n See [Writing Custom Transitions](./transitions.md#writing-custom-transitions)\n for more details on how to implement the protocol.\n \"\"\"\n\n def initialize(\n self,\n from_widget: Widget,\n to_widget: Widget,\n direction: Direction,\n ) -&gt; None:\n \"\"\"\n A hook function to set up any CSS that should be present at the start of the transition.\n\n Args:\n from_widget: The widget showing the slide that we are leaving.\n to_widget: The widget showing the slide that we are entering.\n direction: The desired direction of the transition animation.\n \"\"\"\n ...\n\n def progress(\n self,\n from_widget: Widget,\n to_widget: Widget,\n direction: Direction,\n progress: float,\n ) -&gt; None:\n \"\"\"\n A hook function that is called each time the `progress`\n of the transition animation updates.\n\n Args:\n from_widget: The widget showing the slide that we are leaving.\n to_widget: The widget showing the slide that we are entering.\n direction: The desired direction of the transition animation.\n progress: The progress of the animation, as a percentage\n (e.g., initial state is `0`, final state is `100`).\n Note that this is **not necessarily** bounded between `0` and `100`,\n nor is it necessarily [monotonically increasing](https://en.wikipedia.org/wiki/Monotonic_function),\n depending on the underlying Textual animation easing function,\n which may overshoot or bounce.\n However, it will always start at `0` and end at `100`,\n no matter which `direction` the transition should move in.\n \"\"\"\n ...\n</code></pre> <p>As an example, consider the <code>Swipe</code> transition included in Spiel:</p> Swipe Transition<pre><code>from __future__ import annotations\n\nfrom textual.widget import Widget\n\nfrom spiel.transitions.protocol import Direction, Transition\n\n\nclass Swipe(Transition):\n \"\"\"\n A transition where the current and incoming slide are placed side-by-side\n and gradually slide across the screen,\n with the current slide leaving and the incoming slide entering.\n \"\"\"\n\n def initialize(\n self,\n from_widget: Widget,\n to_widget: Widget,\n direction: Direction,\n ) -&gt; None:\n match direction:\n case Direction.Next:\n to_widget.styles.offset = (\"100%\", 0)\n case Direction.Previous:\n to_widget.styles.offset = (\"-100%\", 0)\n\n def progress(\n self,\n from_widget: Widget,\n to_widget: Widget,\n direction: Direction,\n progress: float,\n ) -&gt; None:\n match direction:\n case Direction.Next:\n from_widget.styles.offset = (f\"-{progress:.2f}%\", 0)\n to_widget.styles.offset = (f\"{100 - progress:.2f}%\", 0)\n case Direction.Previous:\n from_widget.styles.offset = (f\"{progress:.2f}%\", 0)\n to_widget.styles.offset = (f\"-{100 - progress:.2f}%\", 0)\n</code></pre> <p>The transition effect is implemented using Textual CSS styles on the widgets that represent the \"from\" and \"to\" widgets.</p> <p>Because the slide widgets are on different layers, they would normally both try to render in the \"upper left corner\" of the screen, and since the <code>from</code> slide is on the upper layer, it would be the one that actually gets rendered.</p> <p>In <code>Swipe.initialize</code>, the <code>to</code> widget is moved to either the left or the right (depending on the transition direction) by <code>100%</code>, i.e., it's own width. This puts the slides side-by-side, with the <code>to</code> slide fully off-screen.</p> <p>As the transition progresses, the horizontal offsets of the two widgets are adjusted in lockstep so that they appear to move across the screen. Again, the direction of offset adjustment depends on the transition direction. The absolute value of the horizontal offsets always sums to <code>100%</code>, which keeps the slides glued together as they move across the screen.</p> <p>When <code>progress=100</code> in the final state, the <code>to</code> widget will be at zero horizontal offset, and the <code>from</code> widget will be at plus or minus <code>100%</code>, fully moved off-screen.</p> <p>Contribute your transitions!</p> <p>If you have developed a cool transition, consider contributing it to Spiel!</p>"}]}