# fframes-kit > fframes-kit is a Rust component library for fframes videos: Remotion's model (props, children, time set by the parent) with Manim's animation names, LaTeX formulas typeset by a built-in Typst, and explainer kits for math, physics, chemistry, computer science, machine learning, biology and 3D. # fframes-kit docs > Animated components for fframes videos, grouped by what they do, each with its explanation, its code and a rendered demo. Page: https://fframes-kit.pages.dev/docs/ fframes-kit is a library of animated components for [fframes](https://github.com/dmtrKovalenko/fframes) videos. It takes Remotion's model (a video is a function of the frame), Manim's names (`Write`, `Transform`, `MathTex`) and draws LaTeX with a built-in Typst, so formulas need no TeX installation. ## How the docs are organised - **Start** covers installing the kit, the first video and the optional LaTeX setup. - **Core** is the part every video uses: time containers and fades, text and formulas, layout and shapes, transitions and effects, overlays and media, [slides and decks](https://fframes-kit.pages.dev/docs/slides.md), and how to write your own component. - **Kits** are the subject libraries behind cargo features: math, physics, chemistry, computer science, machine learning, biology and 3D. Every component section shows the explanation first, then a video rendered from the kit's gallery, then the code. The code on these pages is the same code the crate's documentation tests compile, with the setup lines hidden. --- # Use with AI agents > The fframes-kit docs as Markdown, llms.txt and JSON for coding agents, an installable Agent Skill, and a prompt to start from. Page: https://fframes-kit.pages.dev/docs/ai/ Coding agents such as Claude Code, Cursor or Codex write better fframes-kit code when they read the reference instead of guessing props. The site gives them the same docs you read, in forms they handle well. ## Every page as Markdown Replace the trailing slash of a docs URL with `.md`: ```text https://fframes-kit.pages.dev/docs/math/function-plot/ the page https://fframes-kit.pages.dev/docs/math/function-plot.md the same page as Markdown ``` The Markdown has the explanation, a link to the rendered gallery clip, the example code and the API table of every field. The menu at the top of each page copies it, opens it, or starts a chat about it in Claude or ChatGPT. ## llms.txt and friends | File | What it holds | | --- | --- | | [/llms.txt](https://fframes-kit.pages.dev/llms.txt) | An index in the [llms.txt](https://llmstxt.org) format: every page with a one-line description and a link to its Markdown. | | [/llms-full.txt](https://fframes-kit.pages.dev/llms-full.txt) | Every page in one file, in sidebar order. Large: give an agent a page or two instead when you can. | | [/llms-small.txt](https://fframes-kit.pages.dev/llms-small.txt) | Every component in a few lines: what it is, its cargo feature, its page, and each field as `name: Type = default` with its description. | | [/components.json](https://fframes-kit.pages.dev/components.json) | The same catalog as JSON: name, summary, feature, page and Markdown URLs, gallery scenes and clips, source location, fields and methods. | Point an agent at `llms.txt` first; it finds the pages it needs from there. ## The Agent Skill The repository has an [Agent Skill](https://agentskills.io) for making videos with the kit: the component model, timing, theming, picking a component, the explainer kits, formulas, and checking a video without watching it. ```bash npx skills add khanhthanhdev/fframes-kit ``` Or copy `skills/fframes-kit` from the repository into your project's `.claude/skills/` (or `.agents/skills/`). Agents that change the kit itself read `AGENTS.md` at the repository root. ## A prompt to start from ```text Make a 20 second fframes video with fframes-kit that explains the derivative of x². Read https://fframes-kit.pages.dev/llms.txt first, then the Markdown of the pages you use. Use only components and fields that the docs list. Check the result with the video's `strip` and `inspect` commands before you finish. ``` --- # Getting started > Add the kit to a video made by cargo fframes new and show a title and a lower third. Page: https://fframes-kit.pages.dev/docs/start/getting-started/ Add the kit to a video made by `cargo fframes new` and show a title and a lower third. ## 1. Create the project The kit builds against the crates.io release of fframes, the one `cargo fframes new` pins: ```bash cargo install cargo-fframes cargo fframes new my-video --backend cpu --yes ``` - `--backend cpu` renders with the CPU backend. Leave it out on a machine with a GPU to get the Skia Vulkan (or Metal) backend. ## 2. Add the kit One dependency line in `[dependencies]`: ```toml fframes-kit = "0.3" ``` - The kit is 0.x: its API may still change between minor versions. `"0.3"` takes fixes but no breaking release. - `{ git = "https://github.com/khanhthanhdev/fframes-kit" }` instead takes the unreleased `main`; pin a `rev` to keep a project on one commit. - Explainer kits are features, for example `fframes-kit = { ..., features = ["math"] }`; see [components.md](https://fframes-kit.pages.dev/docs/basics.md). - A project that takes fframes from a checkout or a git repository instead of crates.io patches it, so the kit uses the same fframes. Two copies of fframes do not mix; their types differ and the project does not compile: ```toml [patch.crates-io] fframes = { path = "../fframes/fframes" } # or { git = "", rev = "" } ``` The patch applies while that fframes has a version the kit accepts (1.3 or a later 1.x). ## 3. A first video Replace `src/lib.rs` with the following (the names are those of a project called `kit-sample`; `main.rs` stays as generated). One import brings the kit, `svgr!` and the fframes types a video needs: ```rust title="Not compiled" use fframes::include_media_dir; use fframes_kit::prelude::*; // Every file of `media/` is embedded into the binary. Fonts are used by family name; the // kit's default theme uses DM Sans, which `cargo fframes new` already put there. include_media_dir!(pub struct KitSampleMedia, "media"); #[derive(Debug)] pub struct KitSampleVideo<'a> { pub media: &'a KitSampleMedia, title: String, theme: Theme, } impl<'a> KitSampleVideo<'a> { pub fn new(media: &'a KitSampleMedia, title: &str) -> Self { // The kit's defaults with an orange accent and a light background. let theme = Theme { colors: Colors { accent: Color::hex("#ff5500"), background: Color::hex("#f8fafc"), title: Color::hex("#0f172a"), text: Color::hex("#0f172a"), muted: Color::hex("#475569"), ..Colors::default() }, ..Theme::default() }; Self { media, title: title.to_owned(), theme } } } impl Video for KitSampleVideo<'_> { const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080; fn duration(&self) -> Duration<'_> { Duration::Seconds(6.0) } fn audio(&self) -> AudioMap<'_> { AudioMap::none() } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, &self.theme); let c = self.theme.colors; let style = self.theme.typography.title; let title = self.title.as_str(); // The title fades in and out over the whole video ... let heading = ui.show(SceneFade::new( &ui, children(move |_| { svgr!( {title} ) }), )); // ... and a lower third shows from 1 s to 5 s. let lower_third = ui.show(Sequence { from: 1.0, duration: Some(4.0), children: children(|ui| { ui.show(LowerThird { name: "Ada Lovelace".into(), role: "Mathematician".into(), ..ui.defaults() }) }), }); svgr!( {heading} {lower_third} ) } } ``` - `Ui` carries the frame, the context and the theme. `ui.show(component)` renders a component, and `..ui.defaults()` fills the props you do not set from the theme. - `Sequence` shifts time: inside it, the lower third starts at its own 0 s, one second into the video, and is gone after `duration`. - The `Theme` is a plain value. Build it once in the constructor, override the fields you need (`colors`, `typography`, `spacing`, ...) and keep the rest with `..Theme::default()`. ## 4. Check it ```bash cargo run --release -- inspect # "no problems found" cargo run --release -- strip -n 8 # one image of the whole video cargo run --release -- render # the video file ``` [components.md](https://fframes-kit.pages.dev/docs/basics.md) goes on from here: the time containers, fades, `Anim`, transitions, effects and writing your own components. --- # LaTeX setup > The latex feature of fframes-kit (MathTex, Tex, ChemEq, see components.md) typesets MathTex formulas with Typst, built into the kit, so most videos need… Page: https://fframes-kit.pages.dev/docs/start/latex-setup/ The `latex` feature of `fframes-kit` (`MathTex`, `Tex`, `ChemEq`, see [components.md](https://fframes-kit.pages.dev/docs/text-and-formulas/formulas.md)) typesets `MathTex` formulas with Typst, built into the kit, so most videos need nothing installed. TeX is needed only for: - `Tex` (text mode) and TikZ pictures; - `ChemEq` and the chemistry kit's reactions (mhchem); - templates with a preamble, compiler or wrapper of your own; - formulas that fail with `TexError::NeedsTex` and are switched to TeX with `.backend(TexBackend::Tex)`, or every formula after `TexEngine::with_backend(TexBackend::Tex)`. For those the kit runs the TeX installed on your machine. It needs: - `latex` (or `xelatex` / `lualatex` for `TexCompiler::XeLatex` / `LuaLatex`); - `dvisvgm`. MiKTeX and TeX Live both ship them. Formulas that are already in a video's cache render without TeX, so only the person who adds or changes such a formula needs it. ## Windows: MiKTeX 1. Install MiKTeX from [https://miktex.org/download](https://miktex.org/download), or run `choco install miktex`. 2. Enable automatic package installation, so a missing package installs itself instead of opening a dialog that stalls the render until the timeout: ```powershell initexmf --set-config-value "[MPM]AutoInstall=1" ``` 3. Check it in a new terminal: ```powershell latex --version dvisvgm --version ``` If the terminal does not find `latex`, the kit still looks in the default MiKTeX folders: - `%LOCALAPPDATA%\Programs\MiKTeX\miktex\bin\x64` (an install for the current user); - `C:\Program Files\MiKTeX\miktex\bin\x64`. ### Without a working installer If the installer cannot download (proxies, a broken Chocolatey package), MiKTeX's command-line setup works offline in two steps. Get `miktexsetup_standalone.exe` from the same download page, then: ```powershell miktexsetup_standalone --remote-package-repository=https://mirrors.mit.edu/CTAN/systems/win32/miktex/tm/packages/ --local-package-repository=C:\miktex-repo --package-set=basic download miktexsetup_standalone --local-package-repository=C:\miktex-repo --package-set=basic --shared=no install ``` - Any mirror listed on [https://ctan.org/mirrors](https://ctan.org/mirrors) works for `--remote-package-repository`. MiKTeX only accepts mirrors that are registered, so pick one from that list. - `--shared=no` installs for the current user. - The first formula that uses a package outside the basic set installs it. This takes 20 to 30 s once, for example for mhchem and chemfig. ## Ubuntu: TeX Live (or MiKTeX) ```bash sudo apt install texlive-latex-extra texlive-science texlive-pictures dvisvgm latex --version && dvisvgm --version ``` `texlive-science` brings mhchem, siunitx and physics, `texlive-pictures` circuitikz. Add `texlive-xetex` or `texlive-luatex` for the other compilers. MiKTeX for Linux works too ([https://miktex.org/download](https://miktex.org/download)). Enable its automatic installation as on Windows. ## macOS: MacTeX ```bash brew install --cask mactex-no-gui eval "$(/usr/libexec/path_helper)" # or open a new terminal latex --version && dvisvgm --version ``` ## Pointing the kit at a TeX folder `FFRAMES_TEX_BIN=` makes the kit look for the tools only in that folder. This is useful when `PATH` is not refreshed yet, or to pick one of several installations. Pointing it at an empty folder hides TeX. Use that to check that a video renders from its committed cache alone. ## The cache - `TexEngine::new(dir)` writes one small `.svg` per formula into `dir`, for both backends. - The file name of a TeX formula is a hash of the compiler, the preamble and the source. A Typst formula's is a hash of the template's definitions, the source and the Typst and mitex versions the kit pins. A changed formula gets a new file. - Old files stay until you delete them. To start over, empty the directory and render the video once. - Commit the directory. Then CI and other machines render without TeX. - The web editor has no file system. `TexEngine::with_embedded(tex_cache!("tex-cache"))` compiles the directory's files into the build, and the engine reads them before the directory. The gallery does this; its `build.rs` rebuilds the crate when a file is added. - The same TeX and dvisvgm versions reproduce the files byte for byte on any system, and so does the same kit version for Typst: the kit writes `\n` line ends. - With `core.autocrlf` on (the Git for Windows default), put a `.gitattributes` with `*.svg -text` into the cache directory. Otherwise git reports the files as changed after every regeneration (see `gallery/tex-cache`). - When TeX is missing and a formula is not in the cache, `typeset` returns `TexError::NotInstalled`. The error names the formula and says how to install TeX for the current system. ## Errors - **`TexError::Compile`**: the TeX log from the first `!` line, and the whole `.tex` file the kit wrote, for an error in the formula. - **`TexError::Timeout`**: a run took longer than the timeout (120 s, `TexEngine::with_timeout`). With MiKTeX this usually means a package dialog is waiting. Enable automatic installation (step 2 above). - **`TexError::Convert`**: `dvisvgm` failed. Its output is in the error. - **`TexError::NeedsTex`**: the formula uses a command the Typst backend cannot typeset (`\ce`, `\begin{tikzpicture}`, an unknown macro, physics' `\div`, `\qty(x)`). The error names the command. Typeset that formula with `MathTex::new(..).backend(TexBackend::Tex)`; the kit never switches by itself. - **`TexError::Typst`**: mitex or Typst rejected the formula (`\frac{a}`, an unbalanced brace). The message and the formula are in the error. - **`TexError::NotInstalled` in the web editor** (any tool): the editor cannot typeset and the embedded cache has no SVG for the formula. Render natively once, then rebuild the editor with the cache embedded (`TexEngine::with_embedded(tex_cache!("tex-cache"))`), and draw `Formula::unset(source)` for what is still missing (see [components.md](https://fframes-kit.pages.dev/docs/text-and-formulas/formulas.md)). The kit runs TeX with `-interaction=nonstopmode -halt-on-error` and never with `--shell-escape`, so a formula cannot run commands. --- # Basics > fframes-kit (crate fframes_kit) is a component library for fframes videos: Remotion's model (props, children, time set by the parent) with Manim's names… Page: https://fframes-kit.pages.dev/docs/basics/ `fframes-kit` (crate `fframes_kit`) is a component library for fframes videos: Remotion's model (props, children, time set by the parent) with Manim's names for animations. Every code block in this file is compiled as a doctest of the kit. - [One import](https://fframes-kit.pages.dev/docs/basics/one-import.md): The prelude also re-exports the fframes types a video needs (Video, Frame, Svgr, Color, Transform, svgr!, timeline!, Easing, Scene, Duration, ...). - [The model](https://fframes-kit.pages.dev/docs/basics/the-model.md): The model - [Time containers and fades](https://fframes-kit.pages.dev/docs/basics/time-containers-and-fades.md): Gallery scenes: Sequence, Series, Loop, Freeze, FadeIn, FadeOut, Fill. - [Animated props](https://fframes-kit.pages.dev/docs/basics/animated-props.md): Anim is a fixed value or a timeline! stored once in the video; ui.sample(&anim) reads it at the current time. - [Theme](https://fframes-kit.pages.dev/docs/basics/theme.md): Theme holds typed groups: colors, typography, spacing, radius, stroke, shadow, motion. --- # One import > The prelude also re-exports the fframes types a video needs (Video, Frame, Svgr, Color, Transform, svgr!, timeline!, Easing, Scene, Duration, ...). Page: https://fframes-kit.pages.dev/docs/basics/one-import/ ```rust use fframes_kit::prelude::*; ``` The prelude also re-exports the fframes types a video needs (`Video`, `Frame`, `Svgr`, `Color`, `Transform`, `svgr!`, `timeline!`, `Easing`, `Scene`, `Duration`, ...). --- # The model > The model Page: https://fframes-kit.pages.dev/docs/basics/the-model/ - A component is a props struct. `ui.show(component)` renders it at the current time and returns an `Svgr`, so it goes anywhere `{expr}` is allowed in `svgr!`. - Build a `Ui` once at the top of `render_frame`: `Ui::new(frame, ctx, &self.theme)` in a video, `Ui::for_scene(frame, ctx, &self.theme, self)` in a scene. Scenes get the video's context, so only `for_scene` knows the scene's length that exit animations end with. - **Time is local.** A component has no start time. Its parent places it with a `Sequence { from, duration, children }`; inside, `ui.seconds()` starts at 0 and `ui.duration` is the sequence's length in frames. Sequences nest and their offsets add up. - **Props are plain data**: numbers, `&str`/`String`, `Color`, `Transform`, enums, `Option`, `Anim` (a fixed value or a stored `timeline!`) and `children` closures. - **Defaults come from the theme**: set what you need and finish with `..ui.defaults()`. - **SVG ids** (clip paths, gradients, filters) come from `ui.id(key, index, kind)`: from a `key: Option<&str>` prop when given, else from the item index. Never from a counter. - Children are a closure that receives the container's own `Ui`: `children(|ui| ...)`. ```rust use fframes_kit::prelude::*; fn card<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let fade = FadeIn { shift: (0.0, -40.0), delay: 0.2, ..ui.defaults() }; let accent = ui.theme.colors.accent; fade.apply(ui, svgr!()) } fn layout<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { // `card` starts at 1s and lasts 3s; inside it, time starts at 0 ui.show(Sequence { from: 1.0, duration: Some(3.0), children: children(card) }) } ``` --- # Time containers and fades > Gallery scenes: Sequence, Series, Loop, Freeze, FadeIn, FadeOut, Fill. Page: https://fframes-kit.pages.dev/docs/basics/time-containers-and-fades/ | Component | Props | What it does | | --- | --- | --- | | `Sequence` | `from`, `duration: Option`, `children` | shows children from `from` seconds, for `duration` or until the parent ends | | `Series` | `items: Vec<(f32, Children)>`, `overlap` | children back to back, each for its seconds; `overlap` starts each one earlier | | `Loop` | `duration`, `times: Option`, `children` | repeats children every `duration` seconds | | `Freeze` | `at`, `children` | children frozen at `at` seconds of their time | | `Fill` | `background: Option`, `children` | full-canvas group (Remotion's `AbsoluteFill`) | | `FadeIn` | `shift`, `delay`, `run_time`, `rate_func` | `Enter`: fades in while moving by `shift` into place; `fade.apply(ui, content)` | | `FadeOut` | `shift`, `delay`, `run_time`, `rate_func` | `Exit`: fades out before the end of `ui.duration` (`delay` seconds before it) | Gallery scenes: `Sequence`, `Series`, `Loop`, `Freeze`, `FadeIn`, `FadeOut`, `Fill`. Gallery clip of `Sequence`: https://fframes-kit.pages.dev/media/scenes/Sequence-c55dbbba.mp4 Gallery clip of `Series`: https://fframes-kit.pages.dev/media/scenes/Series-29964794.mp4 Gallery clip of `Loop`: https://fframes-kit.pages.dev/media/scenes/Loop-482b7757.mp4 Gallery clip of `Freeze`: https://fframes-kit.pages.dev/media/scenes/Freeze-67701e3c.mp4 Gallery clip of `FadeIn`: https://fframes-kit.pages.dev/media/scenes/FadeIn-0de626b6.mp4 Gallery clip of `FadeOut`: https://fframes-kit.pages.dev/media/scenes/FadeOut-c9c65098.mp4 Gallery clip of `Fill`: https://fframes-kit.pages.dev/media/scenes/Fill-d69f144a.mp4 Repeat a local animation, or hold it at a particular local time: ```rust use fframes_kit::prelude::*; fn timed<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let sequence = ui.show(Sequence { from: 0.5, duration: Some(1.0), children: children(|ui| ui.show(Dot { ..ui.defaults() })), }); let series = ui.show(Series { items: vec![ (1.0, children(|ui| ui.show(Circle { ..ui.defaults() }))), (1.0, children(|ui| ui.show(Square { ..ui.defaults() }))), ], overlap: 0.0, }); let repeated = ui.show(Loop { duration: 2.0, times: Some(3), children: children(|ui| ui.show(Circle { enter: Some(Create::default().into()), ..ui.defaults() })), }); let frozen = ui.show(Freeze { at: 0.5, children: children(|ui| ui.show(Square { enter: Some(Create::default().into()), ..ui.defaults() })), }); let fill = ui.show(Fill { background: Some(ui.theme.colors.background), children: children(|ui| ui.show(Circle { cx: 1200.0, cy: 540.0, radius: 80.0, ..ui.defaults() })), }); svgr!({fill}{sequence}{series}{repeated}{frozen}) } ``` `RateFunc` has Manim's names: `Smooth` (default), `Linear`, `RushInto`, `RushFrom`, `ThereAndBack`, `DoubleSmooth`, plus `Easing(fframes::animation::Easing)` for CSS curves and springs. `motion` has `progress`, `clamp01`, `lerp`, `stagger(i, step)`, `spring`, `hash`, `noise`, the easing curves `expo_in`, `expo_out`, `cubic_in_out` and `quart_out` on `0..=1`, `timecode(frames, fps)` (`hh:mm:ss:ff`, an error for 0 fps) and `thousands(n)` (`12,480`). ```rust use fframes_kit::prelude::*; assert_eq!(motion::progress(0.5, 0.0, 2.0), 0.25); assert_eq!(RateFunc::Linear.apply(0.3, 1.0), 0.3); assert_eq!(motion::noise(7, 1.25), motion::noise(7, 1.25)); assert_eq!(motion::timecode(3_725, 60).unwrap(), "00:01:02:05"); assert_eq!(motion::thousands(12_480), "12,480"); ``` For a video cut to music, `Beat { period, downbeat }` is the beat grid: beat 0 is the first downbeat, at `downbeat` seconds, and beats are `period` seconds apart. It stores the period rather than the BPM, so a measured period is used exactly; `Beat::from_bpm(bpm, downbeat)` makes one from a tempo. `time(beat)` and `frame(beat, fps)` place things on the grid (frames are rounded, so scene edges never drift), `beat_at(seconds)` reads it back, and `snap(beats)` and `soft(beats)` are springs released `beats` ago (their `(stiffness, damping)` are the fields `snap_spring` (320, 22) and `soft_spring` (170, 15)). `Beat::pulse(beat, sharpness)` is 1 on every beat and decays until the next. Gallery scene: `Beat`. Gallery clip of `Beat`: https://fframes-kit.pages.dev/media/scenes/Beat-733c40f5.mp4 ```rust use fframes_kit::prelude::*; const GRID: Beat = Beat::new(0.5, 2.0); // 120 BPM, first downbeat at 2 s assert_eq!(GRID.time(4.0), 4.0); assert_eq!(GRID.frame(4.0, 30), 120); assert_eq!(GRID.beat_at(3.0), 2.0); assert_eq!(Beat::pulse(3.0, 8.0), 1.0); ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Sequence Shows its children from `from` seconds on, for `duration` seconds or until the parent ends. Inside, time starts at 0 and `ui.duration` is the sequence's length. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `f32` | `0.0` | Start in the parent's local seconds. | | `duration` | `Option` | `None` | Length in seconds; `None` runs until the parent ends. | | `children` | `Children` | `no_children()` | What it shows; a closure taking the `Ui`, whose time starts at 0 at `from`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/time.rs#L27) ### Series Children one after the other, each for its own number of seconds. Each item starts `overlap` seconds before the previous one ends, so both show during the overlap. | Field | Type | Default | Description | | --- | --- | --- | --- | | `items` | `Vec<(f32, Children)>` | — | `(seconds, children)` in play order. | | `overlap` | `f32` | — | Seconds each item overlaps the one before it; 0 plays them back to back. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/time.rs#L65) ### Loop Repeats its children every `duration` seconds, `times` times or forever (`None`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `duration` | `f32` | `1.0` | Seconds one repeat lasts. | | `times` | `Option` | `None` | How many repeats before it stops showing; `None` for forever. | | `children` | `Children` | `no_children()` | What it repeats; a closure taking the `Ui` whose time restarts at 0 each repeat. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/time.rs#L110) ### Freeze Shows its children frozen at `at` seconds of their local time. | Field | Type | Default | Description | | --- | --- | --- | --- | | `at` | `f32` | `0.0` | Seconds of the children's local time to show, whatever the frame. | | `children` | `Children` | `no_children()` | What it draws, frozen at `at`; a closure taking the `Ui`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/time.rs#L156) ### Fill A group covering the whole canvas, optionally on a background colour (Remotion's `AbsoluteFill`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `background` | `Option` | `None` | Colour painted behind the children; `None` for none. | | `children` | `Children` | `no_children()` | What it draws over the background, a closure taking the `Ui`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/time.rs#L185) --- # Animated props > Anim is a fixed value or a timeline! stored once in the video; ui.sample(&anim) reads it at the current time. Page: https://fframes-kit.pages.dev/docs/basics/animated-props/ `Anim` is a fixed value or a `timeline!` stored once in the video; `ui.sample(&anim)` reads it at the current time. ```rust use fframes::animation::KeyFramesAnimation; use fframes_kit::prelude::*; struct Pulse { radius: KeyFramesAnimation, } impl Pulse { fn new() -> Self { Self { radius: timeline!(at 0.0 => 1.0, animate 40.0_f32 => 80.0, Easing::EaseInOut) } } fn draw<'a>(&'a self, ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let radius: Anim<'a, f32> = (&self.radius).into(); let fixed: Anim<'a, f32> = 20.0.into(); let (r, small) = (ui.sample(&radius), ui.sample(&fixed)); svgr!() } } ``` --- # Theme > Theme holds typed groups: colors, typography, spacing, radius, stroke, shadow, motion. Page: https://fframes-kit.pages.dev/docs/basics/theme/ `Theme` holds typed groups: `colors`, `typography`, `spacing`, `radius`, `stroke`, `shadow`, `motion`. Override with struct update syntax: ```rust use fframes_kit::prelude::*; let theme = Theme { colors: Colors { accent: Color::hex("#ff5500"), ..Colors::default() }, motion: Motion { stagger: Stagger { tight: 0.04, loose: 0.1 }, ..Motion::default() }, ..Theme::default() }; assert_eq!(theme.typography.body.font().family, "DM Sans"); ``` Gallery scene: `Theme`. Gallery clip of `Theme`: https://fframes-kit.pages.dev/media/scenes/Theme-e744acd0.mp4 The default typography uses DM Sans: ship the font in the video's media folder. --- # Text > Text components measure with the font files of the video's media, so a layout is the same in the editor and in the render. Page: https://fframes-kit.pages.dev/docs/text-and-formulas/text/ Text components measure with the font files of the video's media, so a layout is the same in the editor and in the render. When a family cannot be measured (a system font, or the editor before fonts load), each falls back to a plain ``, as listed. `text` is a `Cow`: pass `"literal".into()` or `format!(..).into()`. `style` is a `TextStyle` from `theme.typography`, and its `family`, `size` and `weight` are what is measured and drawn. `anchor` is `TextAnchor::{Start, Middle, End}`. | Component | Props | What it does | Without a font | | --- | --- | --- | --- | | `FitText` | `text`, `x`, `y`, `style`, `max_width`, `min_size` (24), `step` (4), `fill`, `anchor`, `opacity` | one line at the largest size up to `style.size` that fits `max_width`; `fit.size(ui)` gives the size | `style.size` | | `Letters` | `text`, `x`, `y`, `style`, `fill`, `tracking`, `look: Box LetterLook>` | each letter moved, faded and scaled about its center by `look(i)` (`LetterLook { dx, dy, opacity, scale }`) | one line with `look(0)` | | `TextReveal` | `text`, `x`, `y`, `style`, `fill`, `by` (`RevealBy::Word`, `Letter`), `shift`, `delay`, `stagger`, `run_time`, `rate_func` | words or letters fade in one after another while they move by `shift` into place | the whole line as unit 0 | | `Typewriter` | `text`, `x`, `y`, `style`, `fill`, `delay`, `chars_per_second` (24), `cursor: Option`, `cursor_width`, `blink` (0.5 s) | types one character at a time, with a cursor that blinks once the text is complete | no cursor | | `Counter` | `from`, `to`, `x`, `y`, `style`, `fill`, `anchor` (`End`), `decimals`, `thousands` (true), `prefix`, `suffix`, `delay`, `run_time`, `rate_func` | counts from `from` to `to`; right aligned, so the digits stay in place as the number grows | the same | | `Paragraph` | `text`, `x`, `y`, `width`, `style`, `fill`, `align: TextAlign`, `opacity` | wraps into lines of at most `width` pixels, `style.line_height` apart; `\n` starts a line | one unwrapped line | | `HudLabel` | `text`, `x`, `y`, `style` (caption), `fill` (muted), `anchor`, `letter_spacing` (2.5), `uppercase` (true), `opacity` | a small tracked label, like `01 / RENDER` | the same | | `Title` | `text`, `x`, `y`, `style`, `fill`, `include_underline` (true), `match_underline_width_to_text` (false), `underline_width` (1720), `underline_buff`, `underline_stroke`, `underline_stroke_width`, `opacity` | a centered title with an optional underline | fixed underline width | Gallery scenes: `Letters`, `TextReveal`, `Typewriter`, `Counter`, `Paragraph`, `HudLabel`, `Title`. Gallery clip of `Letters`: https://fframes-kit.pages.dev/media/scenes/Letters-ef6be6a5.mp4 Gallery clip of `TextReveal`: https://fframes-kit.pages.dev/media/scenes/TextReveal-6526ed72.mp4 Gallery clip of `Typewriter`: https://fframes-kit.pages.dev/media/scenes/Typewriter-b0fc3a72.mp4 Gallery clip of `Counter`: https://fframes-kit.pages.dev/media/scenes/Counter-bfc37422.mp4 Gallery clip of `Paragraph`: https://fframes-kit.pages.dev/media/scenes/Paragraph-41491469.mp4 Gallery clip of `HudLabel`: https://fframes-kit.pages.dev/media/scenes/HudLabel-e8a6bedd.mp4 Gallery clip of `Title`: https://fframes-kit.pages.dev/media/scenes/Title-c0476731.mp4 Fit a line, type another and draw an underlined title: ```rust use fframes_kit::prelude::*; fn typing<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let fitted = ui.show(FitText { text: "A title that fits".into(), x: 200.0, y: 300.0, max_width: 600.0, ..ui.defaults() }); let typed = ui.show(Typewriter { text: "Typed, then erased".into(), x: 200.0, y: 500.0, chars_per_second: 16.0, erase: Some(0.25), ..ui.defaults() }); let title = ui.show(Title { text: "An underlined title".into(), y: 700.0, match_underline_width_to_text: true, ..ui.defaults() }); svgr!({fitted}{typed}{title}) } ``` Gallery scene: `Label`. Gallery clip of `Label`: https://fframes-kit.pages.dev/media/scenes/Label-68b0cfd5.mp4 `Label` positions text next to a point. Its `direction` is an `Anchor`, and `buff` is the distance from the point in pixels. ```rust use fframes_kit::prelude::*; fn label<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(Label { text: "Right".into(), x: 960.0, y: 600.0, direction: Anchor::Right, buff: 35.0, ..ui.defaults() }) } ``` ```rust use fframes_kit::prelude::*; /// A stat: a label, then the number counting up under it. fn stat<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let label = ui.show(HudLabel { text: "frames rendered".into(), x: 96.0, y: 400.0, ..ui.defaults() }); let number = ui.show(Counter { to: 12_480.0, x: 96.0, y: 520.0, anchor: TextAnchor::Start, delay: 0.2, ..ui.defaults() }); let title = ui.show(TextReveal { text: "Twelve thousand frames".into(), x: 96.0, y: 700.0, ..ui.defaults() }); svgr!({label}{number}{title}) } let c = Counter { to: 1_000.0, decimals: 1, suffix: " ms".into(), ..Counter::default() }; assert_eq!(c.format(c.value(100.0)), "1,000.0 ms"); ``` `Letters` takes any per-letter animation. Here each letter drops in with a spring, 60 ms after the one before: ```rust use fframes_kit::prelude::*; fn drop_in<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let t = ui.seconds(); ui.show(Letters { text: "LAUNCH".into(), x: 96.0, y: 600.0, look: Box::new(move |i| { let s = motion::spring(t - motion::stagger(i, 0.06), 260.0, 18.0); LetterLook { dy: (1.0 - s) * -120.0, opacity: motion::clamp01(s * 2.0), ..LetterLook::default() } }), ..ui.defaults() }) } ``` ### Fitting text into lines These are Remotion's `@remotion/layout-utils` text fitters, measured like the components above. Both return `None` when the family cannot be measured; draw the text at `style.size` then. Gallery scene: `FitText`. Gallery clip of `FitText`: https://fframes-kit.pages.dev/media/scenes/FitText-1aa030fc.mp4 - **`text::fit_text_on_n_lines(ui, text, style, max_width, max_lines)`** gives `FitLines { size, lines }`: the largest size up to `style.size` at which `text` breaks at spaces into at most `max_lines` lines of `max_width` pixels. Each line is measured whole, so none is wider than `max_width`. It is also `None` when a single word is wider than the box at size 1. - **`text::fill_text_box(max_width, max_lines)`** is an empty `TextBox`. - `add(ui, style, " word")` puts a word on the last line, or starts a new line for it. - It returns `BoxFit { exceeds_box, new_line }`. - A word that does not fit is not added. - `lines()` gives the lines so far. ```rust use fframes_kit::{prelude::*, text::fit_text_on_n_lines}; /// A quote as large as it can be on three lines of 1400 pixels. fn quote<'a>(ui: &mut Ui<'a, '_, '_>, text: &'a str) -> Svgr<'a> { let style = ui.theme.typography.title; let Some(fit) = fit_text_on_n_lines(ui, text, style, 1400.0, 3) else { return ui.show(Paragraph { text: text.into(), x: 260.0, y: 400.0, width: 1400.0, ..ui.defaults() }); }; let line_height = fit.size as f32 * style.line_height; let lines: Vec> = fit .lines .into_iter() .enumerate() .map(|(i, line)| { let y = 400.0 + i as f32 * line_height; svgr!({line}) }) .collect(); svgr!({lines}) } ``` ### Numbers and Manim's text mobjects | Component | Props | What it does | Without a font | | --- | --- | --- | --- | | `DecimalNumber` | `value: ValueTracker`, `x`, `y`, `style` (title), `fill`, `anchor` (`Start`), `num_decimal_places` (2), `include_sign`, `group_with_commas` (true), `unit`, `opacity` | the value with every digit in a slot as wide as the widest digit, so digits do not jitter; `anchor: End` keeps them in place across a carry | one line | | `Integer` | as `DecimalNumber` without `num_decimal_places` | the value rounded to a whole number | one line | | `Variable` | `label`, `value`, `x`, `y`, `style` (heading), `fill`, `value_fill` (accent), `num_decimal_places` (2), `unit`, `opacity` | `label = value`, the label in place and the number after it | one line | | `BulletedList` | `items: Vec>`, `x`, `y`, `style` (body), `fill`, `bullet_fill` (accent), `indent`, `line_spacing`, `opacity`, `delay` (0), `stagger` (`None`), `run_time` (enter) | one line per item after a round bullet (a circle, no glyph needed); `baseline(i)` gives a line's `y`. With `stagger: Some(s)` item `i` fades and rises in from `delay + i * s` seconds over `run_time`, and `revealed_at(i)` gives when it is in | the same | | `Title` | `text`, `x` (960), `y` (130), `style`, `fill`, `include_underline` (true), `match_underline_width_to_text`, `underline_width` (1720), `underline_buff`, `underline_stroke`, `underline_stroke_width`, `opacity` | a centered heading over an underline | the underline `underline_width` wide | | `MarkupText` | `markup`, `x`, `y`, `style` (heading), `fill`, `anchor`, `opacity` | one line with Pango tags: ``, ``, ``, `` with `foreground`, `size` (pixels or `xx-small` to `xx-large`), `weight`, `style`, `underline` | the same | `value` is a `ValueTracker`: a fixed number (`3.5.into()`), a stored `timeline!` (`(&self.price).into()`), or a value computed from time in `render_frame` (`f(ui.seconds()).into()`), which is Manim's `always_redraw`. It is an `f32`, exact to about seven significant digits. Gallery scenes: `DecimalNumber`, `Integer`, `Variable`, `BulletedList`, `MarkupText`. Gallery clip of `DecimalNumber`: https://fframes-kit.pages.dev/media/scenes/DecimalNumber-6b83f8b7.mp4 Gallery clip of `Integer`: https://fframes-kit.pages.dev/media/scenes/Integer-2a84ecd9.mp4 Gallery clip of `Variable`: https://fframes-kit.pages.dev/media/scenes/Variable-dd4aa3ee.mp4 Gallery clip of `BulletedList`: https://fframes-kit.pages.dev/media/scenes/BulletedList-72a8ff17.mp4 Gallery clip of `MarkupText`: https://fframes-kit.pages.dev/media/scenes/MarkupText-f498668c.mp4 ```rust use fframes_kit::prelude::*; fn readout<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let t = ui.seconds(); let title = ui.show(Title { text: "Free fall".into(), ..ui.defaults() }); let height = ui.show(Variable { label: "h".into(), value: (100.0 - 4.905 * t * t).max(0.0).into(), x: 200.0, y: 400.0, unit: " m", ..ui.defaults() }); let steps = ui.show(BulletedList { items: vec!["drop the ball".into(), "read the height".into()], x: 200.0, y: 560.0, ..ui.defaults() }); let note = ui.show(MarkupText { markup: "no air resistance".into(), x: 200.0, y: 760.0, ..ui.defaults() }); svgr!({title}{height}{steps}{note}) } let n = DecimalNumber { include_sign: true, unit: " m", ..DecimalNumber::default() }; assert_eq!(n.format(1234.5), "+1,234.50 m"); ``` An integer that counts frames at 30 fps and a list that reveals one item every half second: ```rust use fframes_kit::prelude::*; fn count<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(Integer { value: (ui.seconds() * 30.0).into(), x: 960.0, y: 560.0, anchor: TextAnchor::Middle, unit: " frames", ..ui.defaults() }) } fn list<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(BulletedList { items: vec!["Choose a scene".into(), "Animate its parts".into(), "Render the video".into()], x: 300.0, y: 400.0, style: ui.theme.typography.heading, delay: 0.2, stagger: Some(0.5), ..ui.defaults() }) } ``` Bold and italic in `MarkupText` draw with the family's bold and italic faces, so put those font files into the media. Unknown tags and attributes are drawn without their style, and markup that is not well formed is drawn as plain text; `inspect` warns about both. `inspect --pops` reports changing digits as pops "like a counter"; that is the number counting, not a layout jump. ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### FitText One line of text at the largest size, up to `style.size`, that fits into `max_width` pixels. It shrinks by `step` pixels down to `min_size`, where it stays even if it is still too wide. Without a measurable font it keeps `style.size`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The words, on one line. | | `x` | `f32` | `0.0` | The anchor point on the baseline. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: its size in pixels is the largest size tried; family and weight are kept. | | `max_width` | `f32` | `1600.0` | Widest the line may be, in pixels. | | `min_size` | `usize` | `24` | Smallest font size in pixels it shrinks to, even if the text is still too wide. | | `step` | `usize` | `4` | Pixels the font size shrinks by at each try (at least 1). | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `anchor` | `TextAnchor` | `TextAnchor::Start` | Which point of the text sits at `x`: start, middle or end. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | Methods: - `fn size(&self, ui: &Ui) -> usize`: The font size the text is drawn at. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/fit.rs#L17) ### Letters A line of text drawn letter by letter, each letter with its own `LetterLook` from `look(index)`. `(x, y)` is the start of the baseline and `tracking` adds pixels after every letter. Letters at opacity 0 are left out. Without a measurable font the line is one `` with the look of letter 0. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The line of text; each character is drawn on its own. | | `x` | `f32` | `0.0` | Left of the first letter, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the letters. | | `tracking` | `f32` | `0.0` | Extra pixels after every letter. | | `look` | `Box LetterLook>` | `Box::new(\|_\| LetterLook::default())` | Gives the `LetterLook` of letter `index`, counting from 0; letters at opacity 0 are not drawn. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/letters.rs#L38) ### TextReveal A line whose words or letters fade in one after another while they move by `shift` into place. Unit `i` starts `delay + i * stagger` seconds into the container and takes `run_time`. `(x, y)` is the start of the baseline. Without a measurable font the whole line fades in as unit 0. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The words or letters that fade in; the whole line is drawn as one when the font cannot be measured. | | `x` | `f32` | `0.0` | Left of the line, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `by` | `RevealBy` | `RevealBy::Word` | Whether it reveals word by word or letter by letter. | | `shift` | `(f32, f32)` | `(0.0, 24.0)` | Canvas units, `y` down: `(0.0, 24.0)` rises 24 units into place. | | `delay` | `f32` | `0.0` | Seconds after the container's start before the first unit begins. | | `stagger` | `f32` | `theme.motion.stagger.tight` | Seconds between the starts of two units. | | `run_time` | `f32` | `theme.motion.duration.enter` | Seconds each unit takes to fade and move into place. | | `rate_func` | `RateFunc` | `theme.motion.easing.enter` | Easing of each unit over its run time. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/reveal.rs#L30) ### Typewriter Types its text one character at a time, `chars_per_second` from `delay` seconds into the container, with a cursor after the last character (Manim's `TypeWithCursor`). The cursor is steady while typing and blinks every `blink` seconds once the text is complete. With `erase` it deletes the text again at the same rate, from the end, finishing `erase` seconds before the end of the container (Manim's `UntypeWithCursor`). `(x, y)` is the start of the baseline. Without a measurable font there is no cursor. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The words that are typed. | | `x` | `f32` | `0.0` | Left of the text, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.body` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the text. | | `delay` | `f32` | `0.0` | Seconds after the container's start before typing begins. | | `chars_per_second` | `f32` | `24.0` | Characters typed (and erased) per second. | | `cursor` | `Option` | `Some(theme.colors.accent)` | `None` draws no cursor. | | `cursor_width` | `f32` | `3.0` | Cursor width in pixels; its height is the font size. | | `blink` | `f32` | `0.5` | Seconds the cursor is on, then off, after typing; 0 keeps it on. | | `erase` | `Option` | `None` | Seconds before the end of the container when the erasing is done; `None` keeps the text. | Methods: - `fn typed(&self, t: f32) -> usize`: Characters typed `t` seconds into the container. - `fn shown(&self, t: f32, end: Option) -> usize`: Characters shown `t` seconds into a container that ends at `end` seconds: typed, less the erased ones. - `fn cursor_on(&self, t: f32) -> bool`: Whether the cursor shows `t` seconds into the container; it is steady while erasing. - `fn cursor_on_until(&self, t: f32, end: Option) -> bool`: `cursor_on` in a container that ends at `end` seconds. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/typewriter.rs#L20) ### Counter A number that counts from `from` to `to` over `run_time` seconds, starting `delay` seconds into the container: `"0" -> "12,480"`. It is right aligned by default, so the digits stay in place while the number grows. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `f64` | `0.0` | The value shown before `delay`. | | `to` | `f64` | `0.0` | The value reached after `run_time`. | | `x` | `f32` | `0.0` | Position of the number's anchor, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `anchor` | `TextAnchor` | `TextAnchor::End` | Which point of the text sits at `x`: start, middle or end. | | `decimals` | `usize` | `0` | Digits after the decimal point. | | `thousands` | `bool` | `true` | Groups the digits by three with commas. | | `prefix` | `Cow` | `Cow::Borrowed("")` | Text written before the number, such as a currency sign; empty for none. | | `suffix` | `Cow` | `Cow::Borrowed("")` | Text written after the number, such as a unit; empty for none. | | `delay` | `f32` | `0.0` | Seconds after the container's start before it begins to count. | | `run_time` | `f32` | `theme.motion.duration.slow * 2.0` | Seconds the count takes. | | `rate_func` | `RateFunc` | `theme.motion.easing.standard` | Easing of the count over its run time. | Methods: - `fn value(&self, t: f32) -> f64`: The value `t` seconds into the container. - `fn format(&self, value: f64) -> String`: The text shown for `value`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/counter.rs#L19) ### Paragraph Text wrapped into lines of at most `width` pixels (`Frame::text_break_lines`), aligned inside that width. `(x, y)` is the start of the first baseline; lines are `style.line_height` apart and `\n` starts a new one. Without a measurable font it is one unwrapped line. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The words; `\n` starts a new line. | | `x` | `f32` | `0.0` | Left edge of the wrap box in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the first line in pixels of the canvas; later lines go below by the line height. | | `width` | `f32` | `800.0` | Width in pixels the text wraps into; lines are aligned inside it. | | `style` | `TextStyle` | `theme.typography.body` | The font: family, size in pixels, weight and line height. | | `fill` | `Color` | `theme.colors.text` | Colour of the text. | | `align` | `TextAlign` | `TextAlign::Left` | How the lines sit inside `width`: left, center or right. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/paragraph.rs#L18) ### HudLabel A small tracked label, like the annotations of an engineering drawing ("01 / RENDER"). `uppercase` turns the text to capitals. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The words; shown in capitals when `uppercase` is set. | | `x` | `f32` | `0.0` | Position of the text's anchor point, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.caption` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.muted` | Colour of the text. | | `anchor` | `TextAnchor` | `TextAnchor::Start` | Which point of the text sits at `x`: start, middle or end. | | `letter_spacing` | `f32` | `2.5` | Pixels between letters. | | `uppercase` | `bool` | `true` | `true` writes the text in capitals. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | Methods: - `fn draw(self) -> Svgr`: The label as a `` element; it needs no `Ui`, so it can be drawn anywhere. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/label.rs#L16) ### Title Manim's `Title`: a line of text centered on `x` near the top, over an underline `underline_width` wide, or as wide as the text with `match_underline_width_to_text` (and a measurable font). | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The title words. | | `x` | `f32` | `960.0` | Horizontal center of the title and its underline, in pixels of the canvas. | | `y` | `f32` | `130.0` | The baseline. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `include_underline` | `bool` | `true` | `true` draws a line under the text. | | `match_underline_width_to_text` | `bool` | `false` | `true` makes the underline as wide as the text instead of `underline_width` (when the font can be measured). | | `underline_width` | `f32` | `1720.0` | Width of the underline in pixels. | | `underline_buff` | `f32` | `28.0` | Pixels from the baseline down to the underline. | | `underline_stroke` | `Color` | `theme.colors.title` | Colour of the underline. | | `underline_stroke_width` | `f32` | `3.0` | Thickness of the underline in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/title.rs#L17) ### Label One line of text whose box sits `buff` pixels from `(x, y)` in `direction`: `Top` puts it above the point, `TopRight` up and to the right, `Center` on it. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `Cow` | `Cow::Borrowed("")` | The text, one line. | | `x` | `f32` | `0.0` | X in pixels of the canvas of the point the text sits next to. | | `y` | `f32` | `0.0` | Y in pixels of the canvas of the point the text sits next to (y down). | | `direction` | `Anchor` | `Anchor::Right` | Which side of the point the text box sits on (`Top` above it, `TopRight` up and right, `Center` on it). | | `buff` | `f32` | `theme.spacing.sm` | Pixels between the point and the text's box. | | `style` | `TextStyle` | `theme.typography.body` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `enter` | `Option` | `None` | Fade in at the start; `None` for none. | | `exit` | `Option` | `None` | Fade out before the end; `None` for none. | Methods: - `fn size(&self, ui: &Ui) -> (f32, f32)`: Width and height of the text's box, see `text_size`. - `fn bounds(&self, ui: &Ui) -> Rect`: The box the text is drawn in, in pixels. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/label.rs#L26) ### DecimalNumber Manim's `DecimalNumber`: a number that follows a `ValueTracker`, drawn so its digits do not jitter while it changes. Every digit gets a slot as wide as the widest digit of the font, so a number keeps its width while its digit count stays the same, and with `anchor: End` the digits stay in place across a carry (`9.99 -> 10.00`). The value is an `f32`, exact to about seven significant digits. | Field | Type | Default | Description | | --- | --- | --- | --- | | `value` | `ValueTracker` | `0.0.into()` | The `ValueTracker` whose value is written; it is read at every frame. | | `x` | `f32` | `0.0` | Position of the number's anchor, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `anchor` | `TextAnchor` | `TextAnchor::Start` | Which point of the number (with its unit) sits at `x`: start, middle or end. | | `num_decimal_places` | `usize` | `2` | Digits after the decimal point. | | `include_sign` | `bool` | `false` | A `+` before positive numbers. | | `group_with_commas` | `bool` | `true` | `true` groups the digits by three with commas. | | `unit` | `&str` | `""` | Text written after the number, such as `" kg"`; empty for none. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | Methods: - `fn format(&self, value: f64) -> String`: The text shown for `value`, with the unit. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/decimal.rs#L50) ### Integer Manim's `Integer`: a `DecimalNumber` without decimals, rounded to the nearest whole number. | Field | Type | Default | Description | | --- | --- | --- | --- | | `value` | `ValueTracker` | `d.value` | The `ValueTracker` whose value is written; it is read at every frame. | | `x` | `f32` | `d.x` | Position of the number's anchor, in pixels of the canvas. | | `y` | `f32` | `d.y` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `d.style` | The font: family, size in pixels and weight. | | `fill` | `Color` | `d.fill` | Colour of the text. | | `anchor` | `TextAnchor` | `d.anchor` | Which point of the number (with its unit) sits at `x`: start, middle or end. | | `include_sign` | `bool` | `d.include_sign` | `true` writes a `+` before positive numbers. | | `group_with_commas` | `bool` | `d.group_with_commas` | `true` groups the digits by three with commas. | | `unit` | `&str` | `d.unit` | Text written after the number, such as `" kg"`; empty for none. | | `opacity` | `f32` | `d.opacity` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/decimal.rs#L187) ### Variable Manim's `Variable`: `label = value`, the value a `DecimalNumber` that follows a `ValueTracker`. `(x, y)` is the start of the label on the baseline; the label stays in place and the number grows to the right. For a value computed from time, pass `value: f(ui.seconds()).into()` in `render_frame`, which runs every frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `label` | `Cow` | `Cow::Borrowed("")` | The name written before the ` = `. | | `value` | `ValueTracker` | `0.0.into()` | The `ValueTracker` whose value is written; it is read at every frame. | | `x` | `f32` | `0.0` | Left of the label, in pixels of the canvas; the number follows it. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.heading` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the label and the `=`. | | `value_fill` | `Color` | `theme.colors.accent` | The color of the number; the label and `=` take `fill`. | | `num_decimal_places` | `usize` | `2` | Digits after the decimal point. | | `unit` | `&str` | `""` | Text written after the number, such as `" kg"`; empty for none. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/decimal.rs#L264) ### BulletedList Manim's `BulletedList`: one line per item, each after a round bullet. `(x, y)` is the left of the first bullet on the first baseline; lines are `line_spacing` times the font size apart. The bullets are circles, so they need no glyph in the font. | Field | Type | Default | Description | | --- | --- | --- | --- | | `items` | `Vec>` | `Vec::new()` | One line per item, each after a bullet. | | `x` | `f32` | `0.0` | Left of the first bullet, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the first item, in pixels of the canvas. | | `style` | `TextStyle` | `style` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the text. | | `bullet_fill` | `Color` | `theme.colors.accent` | Colour of the bullets. | | `indent` | `f32` | `style.size as f32 * 1.1` | Pixels from the bullet's left to the text. | | `line_spacing` | `f32` | `style.line_height * 1.25` | Baseline distance as a multiple of the font size. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `delay` | `f32` | `0.0` | Seconds before the first item comes in, when `stagger` is set. | | `stagger` | `Option` | `None` | Seconds between the starts of two items coming in; `None` (the default) shows every item from the start. | | `run_time` | `f32` | `theme.motion.duration.enter` | Seconds each item takes to fade and rise into place, when `stagger` is set. | Methods: - `fn baseline(&self, index: usize) -> f32`: The baseline of item `index`. - `fn revealed_at(&self, index: usize) -> f32`: Seconds into the container when item `index` is fully in: 0 without a `stagger`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/bulleted_list.rs#L22) ### MarkupText Manim's `MarkupText`: one line with Pango-style tags, `"a bold word"`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `markup` | `Cow` | `Cow::Borrowed("")` | The text with Pango-style tags: ``, ``, `` and ``. | | `x` | `f32` | `0.0` | Position of the text's anchor point, in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline of the text, in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.heading` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the text outside any `` colour. | | `anchor` | `TextAnchor` | `TextAnchor::Start` | Which point of the text sits at `x`: start, middle or end. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text/markup.rs#L29) --- # Formulas > Feature latex: fframes-kit = { features = ["latex"] }. Page: https://fframes-kit.pages.dev/docs/text-and-formulas/formulas/ Feature `latex`: `fframes-kit = { features = ["latex"] }`. Formulas are LaTeX, typeset by one of two backends (`TexBackend`): - **Typst** (the default for `MathTex`): the kit converts the LaTeX with [mitex](https://github.com/mitex-rs/mitex) and typesets it with Typst in process, in New Computer Modern. Nothing has to be installed, and a formula takes a few milliseconds. - **TeX**: the kit runs the TeX installed on the machine, as Manim does: `latex` makes a DVI file, `dvisvgm --no-fonts` turns it into paths. Any package your TeX has works: mhchem, TikZ, chemfig, circuitikz. `Tex`, `ChemEq`, TikZ pictures and templates with a preamble of your own always use it. Installing and checking TeX on Windows, Ubuntu and macOS is in [latex-setup.md](https://fframes-kit.pages.dev/docs/start/latex-setup.md). Gallery scenes: `FormulaView`, `WriteFormula`, `FormulaParts`, `TikzDiagram`, `EulerJourney`. Gallery clip of `FormulaView`: https://fframes-kit.pages.dev/media/scenes/FormulaView-de6d0d88.mp4 Gallery clip of `WriteFormula`: https://fframes-kit.pages.dev/media/scenes/WriteFormula-10d1fc96.mp4 Gallery clip of `FormulaParts`: https://fframes-kit.pages.dev/media/scenes/FormulaParts-f14358cc.mp4 Gallery clip of `TikzDiagram`: https://fframes-kit.pages.dev/media/scenes/TikzDiagram-e349c626.mp4 Gallery clip of `EulerJourney`: https://fframes-kit.pages.dev/media/scenes/EulerJourney-2e7bd22b.mp4 Each glyph becomes a ``. | Type | What it is | | --- | --- | | `TexEngine::new(cache_dir)` | keeps every SVG in `cache_dir`; finds `latex`, `xelatex`, `lualatex` and `dvisvgm` and runs them with a timeout (120 s, `with_timeout`); `with_backend(TexBackend::Tex)` makes TeX the default for `MathTex` | | `MathTex::new(tex)` | math mode, as in an `align*` environment (`&` aligns, `\\` breaks a line), `.template(..)`, `.backend(TexBackend::..)`, `.typeset(&engine) -> Result` | | `Tex::new(text)` | text mode with TeX: `$..$`, whole TikZ pictures, anything a document body holds | | `ChemEq::new("2H2 + O2 -> 2H2O")` | `\ce{..}` with the chemistry template and TeX, one part per species | | `TexTemplate` | `compiler` (`TexCompiler::Latex`, `XeLatex`, `LuaLatex`), `preamble`, `body_wrapper` (with `YourTextHere`); `default()` (standalone, amsmath, amssymb), `chemistry()`, `physics()`, `tikz()`, `add_to_preamble` | | `Formula` | the glyphs in TeX points, `part_bbox(part)`, `size_pt()`, `backend()`; implements `Geometry` | | `FormulaView` | the component: `of`, `size` (pixels of 10 pt text, 48), `x`, `y`, `anchor` (`Anchor::TopLeft`, `Center`, ...), `color` (replaces black, keeps the colours the source sets), `color_map: &[(Part, Color)]`, `opacity`, `enter`, `exit`, `emphasis`, `emphasize: Option` (the part `emphasis` applies to), `key` | | `TransformMatchingTex` | `from`, `to` (two `FormulaView`s), `delay`, `run_time`, `rate_func`, `match_tokens` (false) | Gallery scenes: `MathTex`, `TransformMatchingTex`, `ChemEq`. Gallery clip of `MathTex`: https://fframes-kit.pages.dev/media/scenes/MathTex-ad45c652.mp4 Gallery clip of `TransformMatchingTex`: https://fframes-kit.pages.dev/media/scenes/TransformMatchingTex-22eb35a8.mp4 Gallery clip of `ChemEq`: https://fframes-kit.pages.dev/media/scenes/ChemEq-2addcdd9.mp4 Which backend typesets a `MathTex`: its own `.backend(..)` first. Without one, a template other than `TexTemplate::default()` and `TexTemplate::physics()` as they are (any `add_to_preamble`, another compiler or wrapper) uses TeX, and the kit's two templates use the engine's backend, Typst unless `with_backend` says otherwise. Rules: - Typeset in the video's constructor or a `OnceLock`, never in `render_frame`. A new formula takes a few milliseconds with Typst and about a second with TeX; one from the cache takes a file read. - `{{ }}` marks a part, as in Manim: `{{a}}^2 + {{b}}^2 = {{c}}^2` has the parts 0, 1 and 2 (`Part::Index`). `\part{name}{..}` names one (`Part::Label("name")`). A part cannot hold an `&`. A part that is a single binary operator or relation (`{{+}}`, `{{=}}`) keeps its spacing, so `a {{+}} b` is spaced like `a + b` with both backends. - A command Typst cannot typeset fails with `TexError::NeedsTex`, which names the command (`\ce`, `\begin{tikzpicture}`, an unknown macro). The kit never falls back to TeX on its own: write `.backend(TexBackend::Tex)` on that formula. - With `TexTemplate::physics()`, Typst knows `\dv`, `\pdv`, `\vb`, `\vu`, `\abs`, `\norm`, `\ev`, `\ket`, `\bra`, `\braket`, `\grad`, `\curl`, `\qty{x}`, `\SI{9.81}{m/s^2}`, `\si{kg}` and `\unit{m/s}` with physics' and siunitx' meaning, and draws each with the glyphs TeX draws. Two need TeX (`NeedsTex`): physics' `\div` (the divergence; Typst reads `\div` as `÷`) and `\qty(x)` / `\qty[x]`, whose brackets follow the argument. Units by name (`\meter`, `\per`) are unknown commands. - `TransformMatchingTex` matches labels first, then parts with the same source, then glyphs outside parts with the same outline. Matched glyphs move, the rest fades out and in. - With `match_tokens: true`, glyphs outside parts of Typst formulas match by the LaTeX token they come from (`2#1` is the second `2`) before their outline: `x^2` into `2x` moves both glyphs although the `2` changes size. Formulas without tokens (TeX) give one `inspect` warning and match as without. - Glyphs of different backends never match. A transform between a TeX and a Typst formula fades every glyph, and `inspect` warns about it: typeset both with the same backend. - `ChemEq` makes each species a part: separate `+` and arrows with spaces. - Commit the cache directory: it holds the formulas of both backends. Open it at a path that does not depend on the working directory, `concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache")`, and machines without TeX (CI, other people) render from it. A TeX formula that is neither cached nor typesettable is a `TexError::NotInstalled` that names it. - The web editor runs neither TeX nor Typst and cannot read the cache directory. Embed the cache into the build: `TexEngine::new(dir).with_embedded(fframes_kit::latex::tex_cache!("tex-cache"))`, with the path relative to the crate's `Cargo.toml`. The engine reads embedded files first, so the editor draws every formula a native render has cached, from both backends. Add a `build.rs` with `println!("cargo:rerun-if-changed=tex-cache");` so that new cache files are embedded too (see `gallery/build.rs`). The folder must exist, or Cargo rebuilds the crate every time; the first native render creates it. - A formula changed since the last native render is not in the embedded cache. Draw `Formula::unset(source)` when `typeset` fails: the view then shows the source as text, and `inspect` lists it as the missing font `fframes-kit formula not typeset`. The `eprintln!` of a failure is silent in the browser. - With `emphasize` and a `Write` entrance, the part and the rest of the formula each stagger their glyphs over the whole `Write`. ```rust use std::sync::OnceLock; use fframes_kit::prelude::*; struct Formulas { pythagoras: Formula, solved: Formula, } fn typeset() -> Result { let engine = TexEngine::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache")); Ok(Formulas { pythagoras: MathTex::new("{{a}}^2 + {{b}}^2 = {{c}}^2").typeset(&engine)?, solved: MathTex::new("{{a}}^2 = {{c}}^2 - {{b}}^2").typeset(&engine)?, }) } // filled in the video's constructor: `FORMULAS.get_or_init(typeset)` static FORMULAS: OnceLock> = OnceLock::new(); fn solve<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let Ok(f) = FORMULAS.get_or_init(typeset) else { return Svgr::empty(); }; let theme = ui.theme; let view = |of| FormulaView { size: 120.0, x: 960.0, y: 540.0, anchor: Anchor::Center, ..FormulaView::new(of).themed(theme) }; let map = [(Part::Index(0), theme.colors.accent)]; let from = FormulaView { color_map: &map, ..view(&f.pythagoras) }; ui.show(TransformMatchingTex { delay: 0.5, ..TransformMatchingTex::new(from, view(&f.solved)) }) } let reaction = ChemEq::new("CH4 + 2O2 -> CO2 + 2H2O"); assert_eq!(fframes_kit::latex::chem_source(reaction.ce).matches("{{").count(), 4); ``` ## API ### FormulaView Draws a `Formula` (Manim's `MathTex` on screen). | Field | Type | Default | Description | | --- | --- | --- | --- | | `of` | `&Formula` | — | The typeset formula to draw. A formula that was not typeset is drawn as plain `` of its source. | | `size` | `f32` | — | Height in pixels of 10 pt text (the TeX default size). | | `x` | `f32` | — | Position of the `anchor` point of the formula in pixels of the canvas. | | `y` | `f32` | — | Position of the `anchor` point of the formula in pixels of the canvas. | | `anchor` | `Anchor` | — | The point of the formula's box at `(x, y)`. | | `color` | `Color` | — | Replaces black, the color of everything the source does not color itself. | | `color_map` | `&[(Part, Color)]` | — | Colors of single parts, over `color` and the source's colors. | | `opacity` | `f32` | — | 0 transparent to 1 opaque. | | `enter` | `Option` | — | How it appears; `None` shows it from the start. | | `exit` | `Option` | — | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | — | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | | `emphasize` | `Option` | — | The part `emphasis` applies to; the whole formula when `None`. | | `key` | `Option<&str>` | — | Names it when two of the same kind share a frame. | It has no theme defaults: build it with `FormulaView::new(..)`. Methods: - `fn new(of: &Formula) -> Self` - `fn themed(self, t: &Theme) -> Self`: The same view with the theme's colors. - `fn scale(&self) -> f64`: Pixels per TeX point. - `fn transform(&self) -> Affine`: From TeX points to the canvas. - `fn bounds(&self) -> Rect`: The box of the whole formula on the canvas, for a matcher around it. - `fn part_bbox(&self, part: Part) -> Option`: The box of a part on the canvas. - `fn shapes(&self) -> Vec`: One shape per glyph on the canvas, in drawing order. Feature `latex` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/latex/component.rs#L360) ### TransformMatchingTex One formula into another (Manim's `TransformMatchingTex`). Parts with the same `\part{..}` label match first, then `{{ }}` parts with the same source, then glyphs outside parts with the same outline. Matched glyphs move to their new place (a part with a different number of glyphs morphs as one shape), the rest of `from` fades out and the rest of `to` fades in. Before the start it draws `from`, after the end `to`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `FormulaView` | — | The formula that is shown before the start and that its matched glyphs leave. | | `to` | `FormulaView` | — | The formula that is shown after the end and that its matched glyphs arrive at. | | `delay` | `f32` | — | Seconds after the container's start before the transform begins. | | `run_time` | `f32` | — | Seconds the transform takes. | | `rate_func` | `RateFunc` | — | Easing of the transform over its run time. | | `match_tokens` | `bool` | — | Match glyphs outside parts by their token (text and occurrence, `2#1`), then by outline. Needs Typst formulas; with others it warns and matches as without. | It has no theme defaults: build it with `TransformMatchingTex::new(..)`. Methods: - `fn new(from: FormulaView, to: FormulaView) -> Self`: From `from` to `to` in Manim's 1 second, smooth. - `fn shapes_at(&self, p: f32) -> Vec`: The shapes `p` of the way from `from` to `to`. - `fn matching(&self) -> (usize, usize, usize)`: Glyph counts: (matched pairs of groups, glyphs fading out, glyphs fading in). Feature `latex` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/latex/matching.rs#L201) --- # Coordinate systems > The explain layer measures in units of ui.space, 8 units tall with y up, like Manim (see the explain module docs for NumberLine, Axes, NumberPlane and… Page: https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems/ The explain layer measures in units of `ui.space`, 8 units tall with `y` up, like Manim (see the `explain` module docs for `NumberLine`, `Axes`, `NumberPlane` and their graphs). These build on it: | Component | Props | What it does | | --- | --- | --- | | `Scaling` | `Linear`, `LogBase(base)` on `Axes::x_scaling`, `y_scaling` and `NumberLine::scaling` | a logarithmic axis; its `Range` is in exponents (`Range::new(0.0, 3.0, 1.0)` runs from 1 to 1000), everything else takes values: `c2p(250.0, ..)`, the numbers to write, a graph's `x_range` | | `NumberLine::unit_interval(theme)` | a `NumberLine` | Manim's `UnitInterval`: 0 to 1 in tenths, 10 units long | | `ComplexPlane` | `plane: NumberPlane` | the imaginary numbers written `i`, `2i`, `-i`; `n2p((re, im))`, `p2n`, `label(z, decimals)` writes `3+2i` at its point (`format_complex`) | | `PolarPlane` | `center`, `size`, `radius_max` (4), `radius_step` (1), `azimuth_step` (12 spokes), `azimuth_units` (`PiRadians`, `TauRadians`, `Degrees`, `Gradians`, `None`), `azimuth_offset`, `azimuth_direction` (`Ccw`, `Cw`), `radius_numbers`, line and number paints, `enter`, `exit`, `labels_enter`, `labels_exit` | circles and spokes with the radii and azimuths written; `pr2pt((r, θ))`, `pt2pr` | | `SampleSpace` | `columns: Vec` (each `p`, `label`, `rows: Vec`), `center`, `width`, `height`, `colors`, `fill_opacity`, `stroke`, `label_style`, `brace_depth`, `enter`, `exit`, `labels_enter`, `labels_exit` | a rectangle split into columns by probability and each column into rows, so a cell's area is the joint probability; column labels under braces; `cells_in(space)` gives the rectangles | Gallery scenes: `NumberLine`, `NumberPlane`, `SinAndCosFunctionPlot`, `GraphAreaPlot`, `Canvas`, `VGroup`, `Leader`, `Sim`, `ComplexPlane`, `PolarPlane`, `LogBase`, `SampleSpace`, `Axes`, `FunctionGraph`, `ParametricFunction`. Gallery clip of `NumberLine`: https://fframes-kit.pages.dev/media/scenes/NumberLine-bfc8cc7b.mp4 Gallery clip of `NumberPlane`: https://fframes-kit.pages.dev/media/scenes/NumberPlane-17523024.mp4 Gallery clip of `SinAndCosFunctionPlot`: https://fframes-kit.pages.dev/media/scenes/SinAndCosFunctionPlot-5b0b63e1.mp4 Gallery clip of `GraphAreaPlot`: https://fframes-kit.pages.dev/media/scenes/GraphAreaPlot-0816b5b1.mp4 Gallery clip of `Canvas`: https://fframes-kit.pages.dev/media/scenes/Canvas-e1472c7c.mp4 Gallery clip of `VGroup`: https://fframes-kit.pages.dev/media/scenes/VGroup-15cbfeb3.mp4 Gallery clip of `Leader`: https://fframes-kit.pages.dev/media/scenes/Leader-7e34df5d.mp4 Gallery clip of `Sim`: https://fframes-kit.pages.dev/media/scenes/Sim-a6cb4918.mp4 Gallery clip of `ComplexPlane`: https://fframes-kit.pages.dev/media/scenes/ComplexPlane-a172c884.mp4 Gallery clip of `PolarPlane`: https://fframes-kit.pages.dev/media/scenes/PolarPlane-0fdc6b05.mp4 Gallery clip of `LogBase`: https://fframes-kit.pages.dev/media/scenes/LogBase-4a026ff1.mp4 Gallery clip of `SampleSpace`: https://fframes-kit.pages.dev/media/scenes/SampleSpace-beda76de.mp4 Gallery clip of `Axes`: https://fframes-kit.pages.dev/media/scenes/Axes-70de6973.mp4 Gallery clip of `FunctionGraph`: https://fframes-kit.pages.dev/media/scenes/FunctionGraph-49a2e20e.mp4 Gallery clip of `ParametricFunction`: https://fframes-kit.pages.dev/media/scenes/ParametricFunction-d3e584c4.mp4 `ComplexValueTracker` is a `(re, im)` that follows a `timeline!` of `(f32, f32)`, read with `ui.sample` like a `ValueTracker`. Gallery scene: `ValueTracker`. Gallery clip of `ValueTracker`: https://fframes-kit.pages.dev/media/scenes/ValueTracker-54f517d5.mp4 ```rust use fframes_kit::prelude::*; fn planes<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let plane = ComplexPlane::default(); let z = (2.0, 1.0 + f64::from(ui.seconds())); let (x, y) = plane.n2p(ui, z); let label = plane.label(ui, z, 1); let plane = ui.show(plane); let dot = ui.show(Dot { cx: x, cy: y, ..ui.defaults() }); let label = ui.show(label); let growth = Axes { x_range: Range::new(0.0, 10.0, 1.0), y_range: Range::new(0.0, 3.0, 1.0), y_scaling: Scaling::LogBase(10.0), y_numbers: vec![10.0, 100.0, 1000.0], ..ui.defaults() }; let curve = growth.plot(ui, |x| 2f64.powf(x)); let growth = ui.show(growth); let curve = ui.show(curve); svgr!({plane}{dot}{label}{growth}{curve}) } let polar = PolarPlane::default(); let p = polar.pr2pt_in(Space::FULL_HD, (2.0, 1.0)); let (r, theta) = polar.pt2pr_in(Space::FULL_HD, p); assert!((r - 2.0).abs() < 1e-9 && (theta - 1.0).abs() < 1e-9); assert_eq!(fframes_kit::explain::format_complex((0.0, -1.0), 0), "-i"); ``` Draw a plane, arrange shapes, or put a separate coordinate system in part of the frame: ```rust use fframes_kit::prelude::*; fn coordinate_examples<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let group = VGroup::new(vec![Circle { radius: 70.0, ..ui.defaults() }.shape()]) .move_to(960.0, 540.0); vec![ ui.show(NumberLine { ..ui.defaults() }), ui.show(NumberPlane { faded_line_ratio: 2, ..ui.defaults() }), ui.show(Canvas { space: Space::frame(96.0, 220.0, 820.0, 780.0, 6.0), children: children(|ui| ui.show(Axes::default())), }), ui.show(group), ui.show(Leader { from: (500.0, 500.0), to: (700.0, 400.0), text: "point".into(), ..ui.defaults() }), ui.show(SampleSpace { columns: vec![SampleColumn::new(1.0, "weather", vec![SampleRow::new(0.5, "rain"), SampleRow::new(0.5, "dry")])], ..ui.defaults() }), ] } ``` Axes can also construct a function graph, filled area, Riemann rectangles and a parametric curve in the same coordinate system: ```rust use fframes_kit::prelude::*; fn plot<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes::default(); let graph = FunctionGraph { ..axes.plot(ui, |x| x * x) }; let area = Area { ..axes.area(ui, |x| x * x, (0.0, 1.0)) }; let rectangles = RiemannRectangles { ..axes.riemann_rectangles(ui, |x| x * x, (0.0, 1.0), 0.2) }; let curve = ParametricFunction { ..axes.plot_parametric(ui, |t| (t.cos(), t.sin()), (0.0, std::f64::consts::TAU)) }; let axes = ui.show(axes); let (area, rectangles, graph, curve) = (ui.show(area), ui.show(rectangles), ui.show(graph), ui.show(curve)); svgr!({axes}{area}{rectangles}{graph}{curve}) } ``` The `PiRadians` azimuths need a font with `π`, `TauRadians` one with `τ` (DM Sans has no `τ`, and `inspect` reports the fallback). ### Implicit curves and vector fields The functions are plain `fn`s (closures that capture nothing), so what they trace is cached by the function's address and the props, once per render and not per frame. | Component | Props | What it does | | --- | --- | --- | | `ImplicitFunction` | `f: fn(x, y) -> f64`, `x_range`, `y_range`, `resolution` (128 cells across), `axes`, `space`, paint, `enter`, `exit`, `emphasis` | the curve where `f(x, y) = 0`, by marching squares; `ImplicitFunction::new(ui, f)` over the frame, `axes.plot_implicit_curve(ui, f)` over the axes' ranges | | `VectorField` | `f: fn(x, y) -> (f64, f64)`, `x_range`, `y_range` (the grid, as `Range`s), `colors`, `min_magnitude` (0), `max_magnitude` (2), `color` | not drawn itself: the field, its grid and the colour of a vector by its length (`color_for`, `color_at`) | | `ArrowVectorField` | `field`, `length: fn(f64) -> f64` (`sigmoid_length`, Manim's), `stroke_width`, `tip_length`, `tip_width`, `opacity`, `enter`, `exit`, `emphasis` | an arrow from every grid point along the vector, coloured by its length; zero vectors draw nothing | | `StreamLines` | `field`, `dt` (0.05), `virtual_time` (3), `max_anchors_per_line` (100), `padding` (3), `n_repeats`, `noise_factor` (half the grid step), `seed`, `stroke_width`, `opacity`, `flow: Option`, `enter`, `exit` | a line from every grid point, moved by seeded noise, along the field until it leaves the grid by `padding`; each line takes the colour of the field at its middle. `flow` is Manim's `start_animation`: a piece `time_width` of the line long runs along it `speed` times a second, every line from its own start | Gallery scenes: `ImplicitFunction`, `ArrowVectorField`, `StreamLines`. Gallery clip of `ImplicitFunction`: https://fframes-kit.pages.dev/media/scenes/ImplicitFunction-ddd149a7.mp4 Gallery clip of `ArrowVectorField`: https://fframes-kit.pages.dev/media/scenes/ArrowVectorField-5a2e07ae.mp4 Gallery clip of `StreamLines`: https://fframes-kit.pages.dev/media/scenes/StreamLines-99737856.mp4 ```rust use fframes_kit::prelude::*; fn fields<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes::default(); let curve = ui.show(ImplicitFunction { ..axes.plot_implicit_curve(ui, |x, y| x * y * y - x * x * y - 2.0) }); let axes = ui.show(axes); let arrows = ui.show(ArrowVectorField { field: VectorField { f: |x, y| (-y, x), ..ui.defaults() }, ..ui.defaults() }); let lines = ui.show(StreamLines { field: VectorField { f: |x, y| ((x / 2.0).sin() - (y / 2.0).cos(), (x / 2.0).sin()), ..ui.defaults() }, flow: Some(Flow::default()), ..ui.defaults() }); svgr!({lines}{arrows}{axes}{curve}) } ``` ### Graphs Manim's `Graph` is `explain::Graph`; the prelude's `Graph` is the computer science kit's searchable graph, so write the module path. Gallery scenes: `GraphLayouts`, `Graph`. Gallery clip of `GraphLayouts`: https://fframes-kit.pages.dev/media/scenes/GraphLayouts-14b85051.mp4 Gallery clip of `Graph`: https://fframes-kit.pages.dev/media/scenes/Graph-a118b86f.mp4 | Component | Props | What it does | | --- | --- | --- | | `explain::Graph` | `vertices` (names), `edges: Vec<(usize, usize)>`, `layout: GraphLayout` (spring), `layout_scale` (2 units), `center`, `labels`, `radius` (0.1 units), `vertex_fill`, `vertex_stroke`, `label_style`, `label_fill`, `edge_color`, `edge_width`, `edge_bend` (pixels), `tip_length`, `vertex_styles: Vec`, `edge_styles: Vec`, `enter`, `exit`, `labels_enter`, `labels_exit` | vertices placed by the layout, joined by straight or bent edges; with `labels` each vertex holds its name. `Graph::new(ui, &names, &edges)`, `vertex_point(space, v)` gives a vertex's pixel | | `DiGraph` | `graph: Graph` | the same with a tip on every edge; two edges between the same vertices bend apart | | `VertexStyle` | `vertex`, `fill`, `stroke`, `radius`, `label` | one vertex's look and text | | `EdgeStyle` | `edge: (from, to)`, `color`, `width`, `bend`, `label` | one edge's look, and a label beside its middle | Gallery scene: `DiGraph`. Gallery clip of `DiGraph`: https://fframes-kit.pages.dev/media/scenes/DiGraph-f5db138d.mp4 `GraphLayout` (in the prelude) has networkx's layouts: `Circular`, `KamadaKawai`, `Planar`, `Random { seed }`, `Shell { shells }`, `Spectral`, `Spiral`, `Spring { seed, iterations }` (the default), `Partite { parts }`, `Tree { root }` and `Function(fn(count, edges, scale))` for your own positions. `layout::graph_layout(&layout, count, &edges, scale)` returns the positions alone, centered and scaled to `scale` and pushed `0.4 * scale / √count` apart. The same arguments give the same positions everywhere, and a graph computes its layout once. `Planar` computes no embedding: it is `Spring` with seed 0, so crossings can stay. `Spectral` is `Spring` above 400 vertices. ```rust use fframes_kit::{explain, prelude::*}; fn graphs<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let tree = ui.show(explain::Graph { layout: GraphLayout::Tree { root: 0 }, labels: true, enter: Some(Create::default().into()), ..explain::Graph::new(ui, &["a", "b", "c", "d"], &[(0, 1), (0, 2), (2, 3)]) }); let flow = ui.show(DiGraph { graph: explain::Graph { layout: GraphLayout::Circular, center: (4.0, 0.0), edge_styles: vec![EdgeStyle { edge: (0, 1), label: Some("start".into()), ..EdgeStyle::default() }], ..explain::Graph::new(ui, &["x", "y", "z"], &[(0, 1), (1, 2), (2, 1)]) }, }); svgr!({tree}{flow}) } ``` ### Typed matrices and tables Each wraps the explain layer's `Matrix` or `Table`, which gives the place, gaps, labels, lines and animations; the wrapper fills the entries. Gallery scenes: `Matrix`, `Table`, `MathTable`, `TypedTables`, `DecimalMatrix`, `IntegerMatrix`, `MobjectMatrix`, `DecimalTable`, `IntegerTable`, `MobjectTable`. Gallery clip of `Matrix`: https://fframes-kit.pages.dev/media/scenes/Matrix-bb87808c.mp4 Gallery clip of `Table`: https://fframes-kit.pages.dev/media/scenes/Table-8e66d634.mp4 Gallery clip of `MathTable`: https://fframes-kit.pages.dev/media/scenes/MathTable-280de55b.mp4 Gallery clip of `TypedTables`: https://fframes-kit.pages.dev/media/scenes/TypedTables-ff23877f.mp4 Gallery clip of `DecimalMatrix`: https://fframes-kit.pages.dev/media/scenes/DecimalMatrix-8b980430.mp4 Gallery clip of `IntegerMatrix`: https://fframes-kit.pages.dev/media/scenes/IntegerMatrix-7ee78357.mp4 Gallery clip of `MobjectMatrix`: https://fframes-kit.pages.dev/media/scenes/MobjectMatrix-dae438d2.mp4 Gallery clip of `DecimalTable`: https://fframes-kit.pages.dev/media/scenes/DecimalTable-730fcf6a.mp4 Gallery clip of `IntegerTable`: https://fframes-kit.pages.dev/media/scenes/IntegerTable-1de5e559.mp4 Gallery clip of `MobjectTable`: https://fframes-kit.pages.dev/media/scenes/MobjectTable-184f08f3.mp4 | Component | Props | What it does | | --- | --- | --- | | `DecimalMatrix`, `DecimalTable` | `values: Vec>`, `num_decimal_places` (1), `include_sign`, `matrix` or `table` | numbers written like `DecimalNumber` (`1,234.5`); `to_matrix()` / `to_table()` give the text version, for `cell` and `bounds` | | `IntegerMatrix`, `IntegerTable` | `values: Vec>`, `matrix` or `table` | whole numbers | | `MobjectMatrix`, `MobjectTable` | `entries: Vec>`, `matrix` or `table` | drawn entries, each in the middle of its cell, a column as wide as its widest entry; a table's labels stay text. `cell(..)` gives a cell's area | | `Mobject` | `svgr`, `width`, `height` | something drawn around the origin and the box it takes; `Mobject::from_shapes(&shapes)` centers shapes and measures them | | `MathTable` (feature `latex`) | `formulas: Vec>`, `size`, `color`, `table` | typeset formulas in a table; typeset them once with a `TexEngine`, like `MathTex` | ```rust use fframes_kit::prelude::*; fn tables<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let odds = ui.show(DecimalTable { values: vec![vec![0.25, 0.75], vec![0.6, 0.4]], num_decimal_places: 2, table: Table { col_labels: vec!["heads".into(), "tails".into()], row_labels: vec!["fair".into(), "bent".into()], ..ui.defaults() }, ..ui.defaults() }); let dot = Mobject::from_shapes(&[Shape { path: kurbo::Shape::to_path(&kurbo::Circle::new((0.0, 0.0), 20.0), 0.1), fill: Some(ui.theme.colors.accent), stroke: None, stroke_width: 0.0, dash: None, opacity: 1.0, }]); let shapes = ui.show(MobjectMatrix { entries: vec![vec![dot.clone(), dot]], matrix: Matrix { x: 1400.0, ..ui.defaults() }, }); svgr!({odds}{shapes}) } fn decimal_matrix<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(DecimalMatrix { values: vec![vec![0.5, -1.25]], num_decimal_places: 2, include_sign: true, matrix: ui.defaults(), }) } fn integer_matrix<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(IntegerMatrix { values: vec![vec![1, 2], vec![3, 4]], matrix: ui.defaults() }) } fn integer_table<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(IntegerTable { values: vec![vec![12, 8]], table: ui.defaults() }) } fn mobject_table<'a>(ui: &mut Ui<'a, '_, '_>, dot: Mobject<'a>) -> Svgr<'a> { ui.show(MobjectTable { entries: vec![vec![dot]], table: ui.defaults() }) } fn formula_table<'a>(ui: &mut Ui<'a, '_, '_>, formula: &'a Formula) -> Svgr<'a> { ui.show(MathTable { formulas: vec![vec![formula]], ..ui.defaults() }) } ``` ### Cameras `MovingCamera` is Manim's `MovingCameraScene`: `center` and `width` (units of `ui.space`) say which part of the canvas fills the picture, and both are `Anim`s, so the frame pans and zooms. `full_frame_width(space)` is the frame of the whole canvas (14.2 units on 1080p); `auto_zoom(space, &boxes, margin)` returns the center and width that show some pixel boxes, like `self.camera.auto_zoom`. `ZoomedInset` is Manim's `ZoomedScene`: the children, plus a copy of what is inside `frame` magnified into `display`, popping out of the frame over `run_time` (0 shows it at once). Both are ``s; the inset renders its children twice. Gallery scenes: `MovingCamera`, `ZoomedInset`. Gallery clip of `MovingCamera`: https://fframes-kit.pages.dev/media/scenes/MovingCamera-56e6678e.mp4 Gallery clip of `ZoomedInset`: https://fframes-kit.pages.dev/media/scenes/ZoomedInset-ea1e881f.mp4 ```rust use fframes_kit::prelude::*; use fframes_kit::{explain::{auto_zoom, full_frame_width}, kurbo::Rect}; fn zoom_on_a_box<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let full = full_frame_width(ui.space); let (center, width) = auto_zoom(ui.space, &[Rect::new(1200.0, 400.0, 1500.0, 700.0)], 0.5) .unwrap_or(((0.0, 0.0), full)); let p = RateFunc::Smooth.apply(motion::progress(ui.seconds(), 0.0, 1.0), 1.0); ui.show(MovingCamera { center: (center.0 * p, center.1 * p).into(), width: motion::lerp(full, width, p).into(), children: children(|ui| ui.show(Circle { cx: 1350.0, cy: 550.0, radius: 150.0, ..ui.defaults() })), }) } fn inset<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(ZoomedInset { frame: Rect::new(1200.0, 400.0, 1500.0, 700.0), display: Rect::new(100.0, 100.0, 700.0, 700.0), children: children(|ui| ui.show(Circle { cx: 1350.0, cy: 550.0, radius: 100.0, ..ui.defaults() })), ..ui.defaults() }) } ``` ### 3D (feature `3d`) `three::ThreeDView` is Manim's `ThreeDScene`, with `Surface`, `ThreeDAxes`, the solids, the polyhedra and `ConvexHull3D`; see [three-d.md](https://fframes-kit.pages.dev/docs/three-d.md). ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### NumberLine A horizontal number line (Manim `NumberLine`): `range` spread over `length` units centered on `center`, with ticks at every step and the `numbers` written below them. `NumberLine::unit_interval` is Manim's `UnitInterval`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `range` | `Range` | `Range::new(-8.0, 8.0, 1.0)` | In axis units, see `Scaling`. | | `scaling` | `Scaling` | `Scaling::Linear` | How values are spread along the line: `Linear` or `LogBase`; `range` is then in axis units (exponents). | | `length` | `f64` | `12.0` | Length in units of `ui.space`. | | `center` | `(f64, f64)` | `(0.0, 0.0)` | The middle of the line, in units of `ui.space`. | | `color` | `Color` | `theme.colors.text` | Colour of the line, its tip and its ticks. | | `stroke_width` | `f32` | `theme.stroke.thin` | Width of the line and the ticks in pixels. | | `tips` | `bool` | `false` | Arrow tip at the right end. | | `tip_length` | `f32` | `24.0` | Length of the arrow tip in pixels. | | `tip_width` | `f32` | `20.0` | Width of the arrow tip in pixels. | | `include_ticks` | `bool` | `true` | Draws a tick at every step of `range`. | | `tick_size` | `f32` | `10.0` | Half the length of a tick, in pixels. | | `numbers` | `Vec` | `Vec::new()` | Values written below their ticks (Manim `numbers_to_include`). | | `decimals` | `usize` | `0` | Digits after the decimal point of the written numbers. | | `number_style` | `TextStyle` | `theme.typography.caption` | The font of the numbers: family, size in pixels and weight. | | `number_fill` | `Color` | `theme.colors.text` | Colour of the numbers. | | `enter` | `Option` | `None` | How the line and its ticks appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the line and its ticks leave at the end of their container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played on the line while it is shown. | | `numbers_enter` | `Option` | `None` | How the numbers appear and go; the line itself uses `enter` and `exit`. | | `numbers_exit` | `Option` | `None` | Fade the numbers out before the end of the container; `None` for none. | Methods: - `fn unit_interval(t: &Theme) -> Self`: Manim's `UnitInterval`: 0 to 1 in tenths, 10 units long, 0, 0.5 and 1 written. - `fn n2p_in(&self, space: Space, x: f64) -> Point`: The pixel of the number `x` in `space` (Manim `number_to_point`, `n2p`). - `fn n2p(&self, ui: &Ui, x: f64) -> (f32, f32)`: The pixel of the number `x` in `ui.space`. - `fn p2n(&self, ui: &Ui, px: f32) -> f64`: The number at the pixel column `px` (Manim `point_to_number`, `p2n`). - `fn p2n_in(&self, space: Space, px: f64) -> f64`: The number at the pixel column `px` in `space`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/axes.rs#L257) ### Axes Two number lines that cross where `x = 0` and `y = 0` (Manim `Axes`), or at the ends of the ranges that do not contain 0. Build the graphs and lines that go on them with `Axes::plot`, `Axes::vertical_line`, `Axes::riemann_rectangles` and `Axes::area`; they keep the axes' mapping. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x_range` | `Range` | `Range::new(-6.0, 6.0, 1.0)` | In axis units, see `Scaling`. | | `y_range` | `Range` | `Range::new(-3.0, 3.0, 1.0)` | Range of the y-axis, `Range::new(min, max, step)`, in axis units; see `Scaling`. | | `x_scaling` | `Scaling` | `Scaling::Linear` | How x values are spread along the x-axis: `Linear` or `LogBase`; `x_range` is then in axis units (exponents). | | `y_scaling` | `Scaling` | `Scaling::Linear` | How y values are spread along the y-axis: `Linear` or `LogBase`; `y_range` is then in axis units (exponents). | | `x_length` | `f64` | `12.0` | Length of the x-axis in units of `ui.space`. | | `y_length` | `f64` | `6.0` | Length of the y-axis in units of `ui.space`. | | `center` | `(f64, f64)` | `(0.0, 0.0)` | The middle of the axes' box, in units of `ui.space`. | | `color` | `Color` | `theme.colors.text` | Colour of the axis lines, their tips and their ticks. | | `stroke_width` | `f32` | `theme.stroke.thin` | Width of the axis lines and the ticks in pixels. | | `tips` | `bool` | `true` | Arrow tips at the positive ends. | | `tip_length` | `f32` | `24.0` | Length of an arrow tip in pixels. | | `tip_width` | `f32` | `20.0` | Width of an arrow tip in pixels. | | `include_ticks` | `bool` | `true` | Draws a tick at every step of each range. | | `tick_size` | `f32` | `10.0` | Half the length of a tick, in pixels. | | `x_numbers` | `Vec` | `Vec::new()` | Values written below the x-axis (Manim `numbers_to_include`). A number where the y-axis crosses is left out, as in Manim. | | `y_numbers` | `Vec` | `Vec::new()` | Values written left of the y-axis. | | `decimals` | `usize` | `0` | Digits after the decimal point of the written numbers. | | `number_style` | `TextStyle` | `theme.typography.caption` | The font of the numbers: family, size in pixels and weight. | | `number_fill` | `Color` | `theme.colors.text` | Colour of the numbers. | | `x_label` | `Option>` | `None` | Written up and right of the end of the x-axis (Manim `get_axis_labels`). | | `y_label` | `Option>` | `None` | Text written up and right of the end of the y-axis; `None` for none. | | `label_style` | `TextStyle` | `theme.typography.body` | The font of the axis labels: family, size in pixels and weight. | | `label_fill` | `Color` | `theme.colors.title` | Colour of the axis labels. | | `enter` | `Option` | `None` | How the axis lines and ticks appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the axis lines and ticks leave at the end of their container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played on the lines while they are shown. | | `labels_enter` | `Option` | `None` | How the numbers and labels appear and go; the lines use `enter` and `exit`. | | `labels_exit` | `Option` | `None` | Fade the numbers and labels out before the end of the container; `None` for none. | Methods: - `fn map_in(&self, space: Space) -> AxesMap`: The mapping in `space`. - `fn map(&self, ui: &Ui) -> AxesMap`: The mapping in `ui.space`. - `fn c2p(&self, ui: &Ui, x: f64, y: f64) -> (f32, f32)`: The pixel of the data point `(x, y)` (Manim `c2p`). - `fn p2c(&self, ui: &Ui, x: f32, y: f32) -> (f64, f64)`: The data point at a pixel (Manim `p2c`). - `fn plot(&self, ui: &Ui, f: Func) -> FunctionGraph`: The graph of `f` over the whole x range (Manim `axes.plot(f)`); change `x_range`, the paint or `enter` with struct update syntax. - `fn plot_parametric(&self, ui: &Ui, f: Curve, t_range: (f64, f64)) -> ParametricFunction`: The curve `t -> f(t)` in the axes' data units (Manim `plot_parametric_curve`). - `fn vertical_line(&self, ui: &Ui, x: f64, y: f64) -> DashedLine`: A dashed line from the data point `(x, y)` straight down (or up) to the x-axis, in the highlight colour (Manim `get_vertical_line(c2p(x, y))`). - `fn graph_label(&self, ui: &Ui, graph: &FunctionGraph, text: impl Into>, x: f64, direction: Anchor) -> Label`: A label next to the point of `graph` above `x`, in the graph's colour (Manim `get_graph_label(graph, label, x_val, direction)`). - `fn riemann_rectangles(&self, ui: &Ui, f: Func, x_range: (f64, f64), dx: f64) -> RiemannRectangles`: Rectangles of width `dx` under `f` from `x_range.0` to `x_range.1` (Manim `get_riemann_rectangles`). - `fn area(&self, ui: &Ui, f: Func, x_range: (f64, f64)) -> Area`: The region between `f` and the x-axis from `x_range.0` to `x_range.1`; set `bounded` for the region between two graphs (Manim `get_area`). - `fn plot_implicit_curve(&self, ui: &Ui, f: Implicit) -> ImplicitFunction`: The curve where `f(x, y) = 0` over the axes' ranges (Manim `plot_implicit_curve`). [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/axes.rs#L449) ### NumberPlane Axes over a grid of lines at every tick (Manim `NumberPlane`), filling Manim's whole frame by default. `faded_line_ratio` 2 adds a fainter line between two grid lines, 3 two of them. | Field | Type | Default | Description | | --- | --- | --- | --- | | `axes` | `Axes` | — | The axes drawn over the grid; they set the ranges, size, numbers and labels, and the grid lines follow their ticks. | | `line_color` | `Color` | `theme.colors.accent` | Colour of the grid lines. | | `line_width` | `f32` | `theme.stroke.thin` | Width of the grid lines in pixels. | | `line_opacity` | `f32` | `0.45` | 0 transparent to 1 opaque, for the grid lines at the ticks. | | `faded_line_ratio` | `usize` | `1` | Grid lines per tick step: 1 draws none between ticks, 2 adds one fainter line between two ticks, 3 adds two. | | `faded_opacity` | `f32` | `0.18` | 0 transparent to 1 opaque, for the fainter lines between ticks. | | `enter` | `Option` | `None` | How the grid appears and goes; the axes use their own `enter` and `exit`. | | `exit` | `Option` | `None` | How the grid leaves at the end of its container; `None` stays. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/axes.rs#L797) ### ComplexPlane A `NumberPlane` of complex numbers (Manim `ComplexPlane`): the real part along x, the imaginary part along y, whose numbers are written `i`, `2i`, `-i`. Complex numbers are `(re, im)` pairs; `ComplexValueTracker` animates one. | Field | Type | Default | Description | | --- | --- | --- | --- | | `plane` | `NumberPlane` | `NumberPlane::themed(theme)` | The plane it draws; its axes give the ranges, size and center, with the real part along x and the imaginary part along y. | Methods: - `fn n2p_in(&self, space: Space, z: (f64, f64)) -> Point`: The pixel of `z` in `space`. - `fn n2p(&self, ui: &Ui, z: (f64, f64)) -> (f32, f32)`: The pixel of the complex number `z` (Manim `number_to_point`, `n2p`). - `fn p2n_in(&self, space: Space, p: Point) -> (f64, f64)`: The complex number at a pixel in `space`. - `fn p2n(&self, ui: &Ui, x: f32, y: f32) -> (f64, f64)`: The complex number at a pixel (Manim `point_to_number`, `p2n`). - `fn label(&self, ui: &Ui, z: (f64, f64), decimals: usize) -> Label`: `z` written as `a+bi` up and right of its point, with `decimals` decimals. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/complex_plane.rs#L22) ### PolarPlane A polar grid (Manim `PolarPlane`): circles every `radius_step` up to `radius_max` and `azimuth_step` spokes, `size` units across, with the radii written along the first spoke and the azimuths around the outside. Points are `(r, θ)`, θ in radians. | Field | Type | Default | Description | | --- | --- | --- | --- | | `center` | `(f64, f64)` | `(0.0, 0.0)` | The middle, in units of `ui.space`. | | `size` | `f64` | `7.0` | The diameter of the outer circle, in units of `ui.space`. | | `radius_max` | `f64` | `4.0` | The radius of the outer circle in units of radius; the grid is `size` units across, so this sets the scale of `(r, θ)` points. | | `radius_step` | `f64` | `1.0` | Radius between two circles, in units of radius; also the step of the written radii. | | `azimuth_step` | `usize` | `12` | Number of spokes. | | `azimuth_units` | `AzimuthUnits` | `AzimuthUnits::PiRadians` | How the azimuths around the outside are written: in π or τ radians, degrees, gradians, or not at all. | | `azimuth_offset` | `f64` | `0.0` | The angle of θ = 0 from the positive x-axis, in radians. | | `azimuth_direction` | `AzimuthDirection` | `AzimuthDirection::Ccw` | Which way θ grows from `azimuth_offset`: counterclockwise or clockwise. | | `line_color` | `Color` | `theme.colors.accent` | Colour of the circles and spokes. | | `line_width` | `f32` | `theme.stroke.thin` | Width of the circles, spokes and axis lines in pixels. | | `line_opacity` | `f32` | `0.45` | 0 transparent to 1 opaque, for the circles and spokes; the outer circle and first spoke are always opaque. | | `axis_color` | `Color` | `theme.colors.text` | The circle at `radius_max` and the spoke at θ = 0. | | `radius_numbers` | `bool` | `true` | Writes the radii along the first spoke. | | `decimals` | `usize` | `0` | Digits after the decimal point of the written radii, and of degrees and gradians. | | `number_style` | `TextStyle` | `theme.typography.caption` | The font of the radii and azimuths: family, size in pixels and weight. | | `number_fill` | `Color` | `theme.colors.text` | Colour of the radii and azimuths. | | `enter` | `Option` | `None` | How the circles and spokes appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the circles and spokes leave at the end of their container; `None` stays. | | `labels_enter` | `Option` | `None` | How the numbers appear and go; the grid uses `enter` and `exit`. | | `labels_exit` | `Option` | `None` | Fade the numbers out before the end of the container; `None` for none. | Methods: - `fn pr2pt_in(&self, space: Space, (r, theta): (f64, f64)) -> Point`: The pixel of the polar point `(r, θ)` in `space`. - `fn pr2pt(&self, ui: &Ui, rt: (f64, f64)) -> (f32, f32)`: The pixel of the polar point `(r, θ)` (Manim `polar_to_point`, `pr2pt`). - `fn pt2pr_in(&self, space: Space, p: Point) -> (f64, f64)`: The polar point `(r, θ)` at a pixel in `space`, θ in `0..τ`. - `fn pt2pr(&self, ui: &Ui, x: f32, y: f32) -> (f64, f64)`: The polar point at a pixel (Manim `point_to_polar`, `pt2pr`). - `fn azimuth_label(&self, k: usize) -> Option`: The azimuth of spoke `k` written in `azimuth_units`, `None` without numbers. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/polar_plane.rs#L54) ### SampleSpace A rectangle of probabilities (Manim `SampleSpace`): `columns` split it left to right by their `p`, and each column's `rows` split it top to bottom, so the area of a cell is the probability of both. Probabilities that do not add up to 1 are scaled so they fill the rectangle. A row is coloured by its index in `colors`, a column without rows by its own. | Field | Type | Default | Description | | --- | --- | --- | --- | | `columns` | `Vec` | `Vec::new()` | The strips from left to right, each as wide as its probability and divided into rows from the top. | | `center` | `(f64, f64)` | `(0.0, 0.0)` | The middle, in units of `ui.space`. | | `width` | `f64` | `6.0` | In units of `ui.space`. | | `height` | `f64` | `4.0` | Height of the rectangle in units of `ui.space`. | | `colors` | `Vec` | `vec![theme.colors.accent, theme.colors.highlight, theme.colors.muted]` | Fill colours of the rows by their index (a column without rows uses its own index); repeats when there are fewer than rows, and an empty list uses `stroke`. | | `fill_opacity` | `f32` | `0.75` | 0 transparent to 1 opaque, for the cell fills. | | `stroke` | `Color` | `theme.colors.text` | Colour of the cell outlines and the braces. | | `stroke_width` | `f32` | `theme.stroke.thin` | Width of the cell outlines in pixels. | | `label_style` | `TextStyle` | `theme.typography.body` | The font of the row and column labels: family, size in pixels and weight. | | `label_fill` | `Color` | `theme.colors.title` | Colour of the row and column labels. | | `brace_depth` | `f32` | `18.0` | How far the brace under a column bulges, in pixels. | | `enter` | `Option` | `None` | How the cells appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the cells leave at the end of their container; `None` stays. | | `labels_enter` | `Option` | `None` | How the labels and braces appear and go; the cells use `enter` and `exit`. | | `labels_exit` | `Option` | `None` | Fade the labels and braces out before the end of the container; `None` for none. | Methods: - `fn bounds_in(&self, space: Space) -> Rect`: The whole rectangle in pixels of `space`. - `fn cells_in(&self, space: Space) -> Vec<(Rect, Vec)>`: The pixel rectangle of every column and of every cell in it, `(column, cells)`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/sample_space.rs#L62) ### Canvas Content drawn in another coordinate system: its children see `space` as `ui.space` (Manim's frame, moved and scaled), for a graph in one half of the screen or a figure in a card. | Field | Type | Default | Description | | --- | --- | --- | --- | | `space` | `Space` | `Space::FULL_HD` | The coordinate system the children see as `ui.space`: units, origin and canvas box. | | `children` | `Children` | `no_children()` | What it draws in that space (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/canvas.rs#L14) ### VGroup Shapes moved and animated together (Manim `VGroup`). Build one from the `shape()` of shape components, line them up with `VGroup::arrange` or `VGroup::arrange_in_grid` and draw it with one `enter`, `exit` and `emphasis`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `parts` | `Vec` | — | The shapes drawn together, in pixels of the canvas. | | `enter` | `Option` | — | How the parts appear together; `None` shows them from the start. | | `exit` | `Option` | — | How the parts leave together at the end of their container; `None` stays. | | `emphasis` | `Option` | — | An emphasis animation (`Indicate`, `Wiggle`, ..) played on the parts while they are shown. | It has no theme defaults: build it with `VGroup::new(..)`. Methods: - `fn new(parts: Vec) -> Self` - `fn bounds(&self) -> Option`: The box around every part, `None` for an empty group. - `fn shift(self, dx: f64, dy: f64) -> Self`: Moves every part by `(dx, dy)` pixels (Manim `shift`). - `fn move_to(self, x: f64, y: f64) -> Self`: Moves the group so the center of its box is at `(x, y)` (Manim `move_to`). - `fn arrange(self, direction: Anchor, buff: f64) -> Self`: Lines the parts up one after another in `direction`, `buff` pixels apart, around the group's current center (Manim `arrange(direction, buff)`). - `fn arrange_in_grid(self, columns: usize, buff: (f64, f64)) -> Self`: Puts the parts in rows of `columns` cells (Manim `arrange_in_grid`), around the group's current center. `buff` is `(between columns, between rows)`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/vgroup.rs#L16) ### Leader A line from `from` to `to` with text at its end, on the side the line points to, and a dot on `from` when `dot` is above 0. The line draws with `enter` (`Create` from `from`), the text fades with `label_enter`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `(f32, f32)` | `(0.0, 0.0)` | Where the line starts, `(x, y)` in pixels of the canvas; the dot sits here. | | `to` | `(f32, f32)` | `(100.0, -60.0)` | Where the line ends and the text sits, `(x, y)` in pixels of the canvas. | | `text` | `Cow` | `Cow::Borrowed("")` | The text at the end of the line. | | `style` | `TextStyle` | `theme.typography.caption` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `color` | `Color` | `theme.colors.muted` | Colour of the line and the dot. | | `stroke_width` | `f32` | `theme.stroke.thin` | Outline width in pixels. | | `dot` | `f32` | `6.0` | Radius of the dot on `from`, 0 for none. | | `buff` | `f32` | `theme.spacing.xs` | Pixels between `to` and the text's box. | | `enter` | `Option` | `None` | How the line and dot appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the line and dot leave at the end of the container; `None` stays. | | `label_enter` | `Option` | `None` | How the text fades in; `None` shows it from the start. | | `label_exit` | `Option` | `None` | How the text fades out before the end; `None` for none. | Methods: - `fn label(&self) -> Label`: The label at the end, placed on the side the line points to. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/label.rs#L142) ### FunctionGraph The graph of `f` from `x_range.0` to `x_range.1` on axes (Manim `axes.plot(f)`), usually built with `Axes::plot`. Its samples are cached; a function that is not finite somewhere leaves a gap there. | Field | Type | Default | Description | | --- | --- | --- | --- | | `f` | `Func` | `\|x\| x` | The function `y = f(x)` in data units; a plain `fn`, so its samples can be cached. Where it is not finite the graph has a gap. | | `x_range` | `(f64, f64)` | `(-1.0, 1.0)` | The first and last `x` plotted, in data units. | | `axes` | `AxesMap` | `AxesMap::default()` | The mapping of the axes the graph sits on (`axes.map(ui)`, or built by `Axes::plot`). | | `samples` | `usize` | `256` | Straight pieces of the sampled curve before smoothing. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn point(&self, x: f64) -> (f32, f32)`: The pixel on the graph above `x` (Manim `input_to_graph_point`, `i2gp`). [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/graph.rs#L172) ### ParametricFunction The curve `t -> f(t)` for `t` from `t_range.0` to `t_range.1` (Manim `ParametricFunction`), in data units of `axes` when set, otherwise in units of `space`. Build one with `ParametricFunction::new` to take `ui.space`, or with `Axes::plot_parametric`. Its samples are cached. | Field | Type | Default | Description | | --- | --- | --- | --- | | `f` | `Curve` | `\|t\| (theme.cos(), theme.sin())` | The curve `t -> (x, y)`; a plain `fn`. Its points are in data units of `axes` when set, otherwise in units of `space`. | | `t_range` | `(f64, f64)` | `(0.0, std::f64::consts::TAU)` | The first and last `t` plotted. | | `axes` | `Option` | `None` | Axes whose data units the curve is in; `None` takes units of `space`. | | `space` | `Space` | `Space::FULL_HD` | The coordinate system in units of which the curve is drawn when `axes` is `None`. | | `samples` | `usize` | `256` | Number of straight pieces the sampled curve has before smoothing. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn new(ui: &Ui, f: Curve, t_range: (f64, f64)) -> Self`: The curve of `f` over `t_range` in `ui.space`, with the theme's paint. - `fn point(&self, t: f64) -> (f32, f32)`: The pixel of the curve at `t`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/graph.rs#L203) ### ImplicitFunction The curve where `f(x, y) = 0` (Manim `ImplicitFunction`, `axes.plot_implicit_curve`), traced by marching squares on a grid `resolution` cells wide over `x_range` by `y_range`. Its points are in data units of `axes` when set, otherwise in units of `space`; build one with `ImplicitFunction::new` to take `ui.space`, or with `Axes::plot_implicit_curve`. The curve is traced once and cached. | Field | Type | Default | Description | | --- | --- | --- | --- | | `f` | `Implicit` | `\|x` | The function of `(x, y)` whose zeros make the curve; a plain `fn`. | | `x_range` | `(f64, f64)` | `(-7.1, 7.1)` | The left and right edge of the box searched, in data units of `axes` when set, otherwise in units of `space`. | | `y_range` | `(f64, f64)` | `(-4.0, 4.0)` | The bottom and top edge of the box searched, in data units of `axes` when set, otherwise in units of `space`. | | `resolution` | `usize` | `128` | Grid cells across `x_range`; a finer grid follows tighter turns. | | `axes` | `Option` | `None` | Axes whose data units the curve is in; `None` takes units of `space`. | | `space` | `Space` | `Space::FULL_HD` | The coordinate system in units of which the curve is drawn when `axes` is `None`. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn new(ui: &Ui, f: Implicit) -> Self`: The curve of `f` over Manim's frame, in `ui.space`, with the theme's paint. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/implicit.rs#L142) ### ArrowVectorField An arrow at every grid point of a `VectorField` (Manim `ArrowVectorField`), starting at the point, pointing along the vector, `length(magnitude)` units long and coloured by the magnitude. Zero vectors draw nothing. | Field | Type | Default | Description | | --- | --- | --- | --- | | `field` | `VectorField` | `VectorField::themed(theme)` | The vector field: the function, the box of grid points the arrows start from, and the colour by length. | | `length` | `fn(f64) -> f64` | `sigmoid_length` | The arrow's length in units for a vector of this magnitude. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the arrow shafts in pixels. | | `tip_length` | `f32` | `14.0` | Length of the arrow tips in pixels; shortened to half of a short arrow. | | `tip_width` | `f32` | `12.0` | Width of the arrow tips in pixels, scaled down with a shortened tip. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How the arrows appear; `None` shows them from the start. | | `exit` | `Option` | `None` | How the arrows leave at the end of their container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played on the arrows while they are shown. | Methods: - `fn shapes(&self, space: Space) -> Vec`: The arrows as shapes, in pixels of `space`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/vector_field.rs#L127) ### StreamLines Lines that follow a `VectorField` (Manim `StreamLines`): from every grid point, moved by a seeded `noise_factor`, a point moves along the field in steps of `dt` for `virtual_time`, until it leaves the grid by more than `padding` units. Each line takes the colour of the field's magnitude at its middle. | Field | Type | Default | Description | | --- | --- | --- | --- | | `field` | `VectorField` | `VectorField::themed(theme)` | The vector field the lines follow, with the box of grid points they start from and the colour by length. | | `dt` | `f64` | `0.05` | Seconds of virtual time per step of a line. | | `virtual_time` | `f64` | `3.0` | Seconds of virtual time each line is followed for; with `dt` it sets how long the lines get. | | `max_anchors_per_line` | `usize` | `100` | Most points kept per line; longer runs keep every n-th point. | | `padding` | `f64` | `3.0` | Units of `ui.space` outside the field's box a line may wander before it ends. | | `n_repeats` | `usize` | `1` | How many lines start from every grid point. | | `noise_factor` | `Option` | `None` | How far, in units, a start moves from its grid point; `None` is half the grid step. | | `seed` | `u64` | `0` | Seed of the start offsets and of where each line begins its flow; the same seed gives the same picture. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the lines in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `flow` | `Option` | `None` | A piece of every line that runs along it, over and over; `None` draws the whole lines. | | `enter` | `Option` | `None` | How the lines appear when there is no `flow` (`Create`, ..); `None` shows them from the start. | | `exit` | `Option` | `None` | How the lines leave at the end of their container; `None` stays. | Methods: - `fn lines(&self) -> Arc<[(f64, f64)]>`: The traced lines in units of `ui.space`, separated by a `(NaN, NaN)` point. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/stream_lines.rs#L55) ### Graph Vertices joined by edges (Manim `Graph`), placed by `layout` within `layout_scale` units of `center` (the layout is computed once and cached). Vertices are dots `radius` units wide; with `labels` they carry their names and grow to hold them. Edges are straight, or bent by `edge_bend` pixels. `vertex_styles` and `edge_styles` change single vertices and edges. `DiGraph` draws the same with tips on the edges. | Field | Type | Default | Description | | --- | --- | --- | --- | | `vertices` | `Vec>` | `Vec::new()` | The vertex names; edges refer to a vertex by its index here, and `labels` writes the names inside the dots. | | `edges` | `Vec<(usize, usize)>` | `Vec::new()` | Pairs of vertex indices `(from, to)`; an edge to the same vertex or to a missing one is skipped. | | `layout` | `GraphLayout` | `GraphLayout::default()` | How the vertices are placed: circular, spring, tree, shell and so on. | | `layout_scale` | `f64` | `2.0` | How far from `center` the layout reaches, in units of `ui.space`. | | `center` | `(f64, f64)` | `(0.0, 0.0)` | The middle of the layout, in units of `ui.space`. | | `labels` | `bool` | `false` | Writes each vertex's name inside its dot, which grows to hold it. | | `radius` | `f64` | `0.1` | Radius of a vertex dot in units of `ui.space`. | | `vertex_fill` | `Color` | `theme.colors.text` | Colour of the vertex dots. | | `vertex_stroke` | `Option` | `None` | Outline colour of the vertex dots; `None` draws no outline. | | `label_style` | `TextStyle` | `theme.typography.body` | The font of the vertex names: family, size in pixels and weight. | | `label_fill` | `Color` | `theme.colors.background` | Colour of the vertex names. | | `edge_color` | `Color` | `theme.colors.text` | Colour of the edges and their tips. | | `edge_width` | `f32` | `theme.stroke.regular` | Width of the edges in pixels. | | `edge_bend` | `f64` | `0.0` | Pixels the middle of an edge bows to its left; 0 is straight. | | `tip_length` | `f64` | `16.0` | Length of the arrow tip of a `DiGraph` edge in pixels. | | `vertex_styles` | `Vec` | `Vec::new()` | Looks that replace the graph's for single vertices. | | `edge_styles` | `Vec` | `Vec::new()` | Looks that replace the graph's for single edges, and their labels. | | `enter` | `Option` | `None` | How the vertices and edges appear (`Create`, `GrowFromCenter`, ..); `None` shows them from the start. | | `exit` | `Option` | `None` | How the vertices and edges leave at the end of their container; `None` stays. | | `labels_enter` | `Option` | `None` | Fade the vertex names and edge labels in at the start; `None` for none. | | `labels_exit` | `Option` | `None` | Fade the vertex names and edge labels out before the end; `None` for none. | Methods: - `fn new(ui: &Ui, vertices: &[&str], edges: &[(usize, usize)]) -> Self`: A graph of these vertices and edges with the theme's look and the spring layout. - `fn layout_positions(&self) -> Arc<[(f64, f64)]>`: The layout's positions in units around the origin, computed once per layout, vertex count, edges and scale. - `fn vertex_point(&self, space: Space, v: usize) -> Option`: Where vertex `v` is, in pixels of `space` (Manim `graphv.get_center()`). [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/graph_mobject.rs#L56) ### DiGraph A `Graph` whose edges point from their first vertex to their second (Manim `DiGraph`): each edge ends in a tip at the edge of its target, and the two edges of a pair that point both ways bend apart. | Field | Type | Default | Description | | --- | --- | --- | --- | | `graph` | `Graph` | `Graph::themed(theme)` | The graph it draws; its edges point from their first vertex to their second. | Methods: - `fn new(ui: &Ui, vertices: &[&str], edges: &[(usize, usize)]) -> Self`: A directed graph of these vertices and edges with the theme's look. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/graph_mobject.rs#L339) ### Matrix Entries in rows and columns between square brackets (Manim `Matrix`), centered on `(x, y)`. The brackets draw with `enter` (`Create`), the entries fade with `entries_enter`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `entries` | `Vec>>` | `Vec::new()` | Row by row; a shorter row is padded with empty entries. | | `x` | `f32` | `960.0` | Horizontal middle of the matrix in pixels of the canvas. | | `y` | `f32` | `540.0` | Vertical middle of the matrix in pixels of the canvas. | | `buff` | `(f32, f32)` | `(theme.spacing.xl, theme.spacing.lg + theme.spacing.md)` | Pixels between the columns and between the rows (Manim `h_buff`, `v_buff`). | | `bracket_buff` | `f32` | `theme.spacing.lg` | Pixels between the entries and the brackets (Manim `bracket_h_buff`). | | `style` | `TextStyle` | `theme.typography.body` | The font of the entries: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the entries. | | `color` | `Color` | `theme.colors.title` | The brackets' colour. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the brackets in pixels. | | `enter` | `Option` | `None` | How the brackets appear (`Create`, ..); `None` shows them from the start. | | `exit` | `Option` | `None` | How the brackets leave at the end of their container; `None` stays. | | `entries_enter` | `Option` | `Some(FadeIn::themed(theme))` | Fade the entries in at the start; `None` shows them from the start. | | `entries_exit` | `Option` | `None` | Fade the entries out before the end; `None` for none. | Methods: - `fn cell(&self, ui: &Ui, row: usize, column: usize) -> Option`: The area of entry `(row, column)`, half the gaps around it (Manim `get_entries`), to put a highlight behind it. `None` outside the matrix. - `fn bounds(&self, ui: &Ui) -> Option`: The box around the brackets, to place what comes next to the matrix. `None` for a matrix without entries. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/matrix.rs#L20) ### Table Text in rows and columns with lines between them (Manim `Table`), centered on `(x, y)`. `col_labels` go above the columns and `row_labels` left of the rows, in `label_style`. The lines draw with `enter` (`Create`), the text fades with `entries_enter`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `rows` | `Vec>>` | `Vec::new()` | The body, row by row; a shorter row is padded with empty cells. | | `col_labels` | `Vec>` | `Vec::new()` | Labels above the columns, empty for none. | | `row_labels` | `Vec>` | `Vec::new()` | Labels left of the rows, empty for none. | | `x` | `f32` | `960.0` | Horizontal middle of the table in pixels of the canvas. | | `y` | `f32` | `540.0` | Vertical middle of the table in pixels of the canvas. | | `buff` | `(f32, f32)` | `(theme.spacing.xl, theme.spacing.lg + theme.spacing.md)` | Pixels between the columns and between the rows (Manim `h_buff`, `v_buff`); the lines run through the middle of them. | | `style` | `TextStyle` | `theme.typography.body` | The font of the body cells: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.text` | Colour of the body cells' text. | | `label_style` | `TextStyle` | `TextStyle { weight: 600, ..theme.typography.body }` | The font of the row and column labels: family, size in pixels and weight. | | `label_fill` | `Color` | `theme.colors.accent` | Colour of the row and column labels. | | `line_color` | `Color` | `theme.colors.muted` | Colour of the lines between the cells. | | `stroke_width` | `f32` | `theme.stroke.thin` | Width of the lines in pixels. | | `outer_lines` | `bool` | `false` | Draws the frame around the table too (Manim `include_outer_lines`). | | `enter` | `Option` | `None` | How the lines appear (`Create`, ..); `None` shows them from the start. | | `exit` | `Option` | `None` | How the lines leave at the end of their container; `None` stays. | | `entries_enter` | `Option` | `Some(FadeIn::themed(theme))` | Fade the text in at the start; `None` shows it from the start. | | `entries_exit` | `Option` | `None` | Fade the text out before the end; `None` for none. | Methods: - `fn cell(&self, ui: &Ui, row: usize, column: usize) -> Option`: The area of body cell `(row, column)` between its lines (Manim `get_cell`), to put a highlight behind it. `None` outside the table. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/table.rs#L170) ### MathTable A `Table` of typeset formulas (Manim `MathTable`), typeset beforehand with a `latex::TexEngine` like any `MathTex`. `size` is the height in pixels of 10 pt text, as in `FormulaView`; `table` gives the labels, place, lines and animations. | Field | Type | Default | Description | | --- | --- | --- | --- | | `formulas` | `Vec>` | `Vec::new()` | The typeset formulas row by row, each centered in its cell. | | `size` | `f32` | `40.0` | Height in pixels of 10 pt text, as in `FormulaView`. | | `color` | `Color` | `theme.colors.title` | Colour of the formulas. | | `table` | `Table` | `Table::themed(theme)` | The table that gives the labels, place, lines and animations. | Methods: - `fn to_mobject_table(&self, ui: &mut Ui) -> MobjectTable`: The `MobjectTable` it draws: every formula centered on the origin. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_table.rs#L224) ### DecimalMatrix A `Matrix` of numbers written with `num_decimal_places` places (Manim `DecimalMatrix`, one place by default). `matrix` gives the place and look; its `entries` are replaced. | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec>` | `Vec::new()` | The numbers row by row; they replace the entries of `matrix`. | | `num_decimal_places` | `usize` | `1` | Digits after the decimal point of each number. | | `include_sign` | `bool` | `false` | Writes a `+` before positive numbers. | | `matrix` | `Matrix` | `Matrix::themed(theme)` | The matrix that gives the place, gaps, font, brackets and animations; its `entries` are replaced by `values`. | Methods: - `fn to_matrix(&self) -> Matrix`: The `Matrix` it draws, with the numbers written. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_matrix.rs#L92) ### IntegerMatrix A `Matrix` of whole numbers (Manim `IntegerMatrix`). `matrix` gives the place and look; its `entries` are replaced. | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec>` | `Vec::new()` | The whole numbers row by row; they replace the entries of `matrix`. | | `matrix` | `Matrix` | `Matrix::themed(theme)` | The matrix that gives the place, gaps, font, brackets and animations; its `entries` are replaced by `values`. | Methods: - `fn to_matrix(&self) -> Matrix`: The `Matrix` it draws, with the numbers written. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_matrix.rs#L141) ### MobjectMatrix A `Matrix` whose entries are drawn `Mobject`s (Manim `MobjectMatrix`), each in the middle of its cell; a column is as wide as its widest entry. `matrix` gives the place, gaps, brackets and animations; its `entries` are not used. | Field | Type | Default | Description | | --- | --- | --- | --- | | `entries` | `Vec>` | `Vec::new()` | The drawn entries row by row, each in the middle of its cell; a shorter row is padded with empty ones. | | `matrix` | `Matrix` | `Matrix::themed(theme)` | The matrix that gives the place, gaps, brackets and animations; its `entries` are not used. | Methods: - `fn cell(&self, row: usize, column: usize) -> Option`: The area of entry `(row, column)`, half the gaps around it. `None` outside the matrix. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_matrix.rs#L188) ### DecimalTable A `Table` of numbers written with `num_decimal_places` places (Manim `DecimalTable`, one place by default). `table` gives the labels, place and look; its `rows` are replaced. | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec>` | `Vec::new()` | The numbers row by row; they replace the `rows` of `table`. | | `num_decimal_places` | `usize` | `1` | Digits after the decimal point of each number. | | `include_sign` | `bool` | `false` | Writes a `+` before positive numbers. | | `table` | `Table` | `Table::themed(theme)` | The table that gives the labels, place, font, lines and animations; its `rows` are replaced by `values`. | Methods: - `fn to_table(&self) -> Table`: The `Table` it draws, with the numbers written. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_table.rs#L23) ### IntegerTable A `Table` of whole numbers (Manim `IntegerTable`). `table` gives the labels, place and look; its `rows` are replaced. | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec>` | `Vec::new()` | The whole numbers row by row; they replace the `rows` of `table`. | | `table` | `Table` | `Table::themed(theme)` | The table that gives the labels, place, font, lines and animations; its `rows` are replaced by `values`. | Methods: - `fn to_table(&self) -> Table`: The `Table` it draws, with the numbers written. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_table.rs#L72) ### MobjectTable A `Table` whose body cells are drawn `Mobject`s (Manim `MobjectTable`), each in the middle of its cell; the labels stay text. `table` gives the labels, place, lines and animations; its `rows` are not used. | Field | Type | Default | Description | | --- | --- | --- | --- | | `entries` | `Vec>` | `Vec::new()` | The drawn body cells row by row, each in the middle of its cell. | | `table` | `Table` | `Table::themed(theme)` | The table that gives the labels, place, lines and animations; its `rows` are not used. | Methods: - `fn cell(&self, ui: &Ui, row: usize, column: usize) -> Option`: The area of body cell `(row, column)` between its lines. `None` outside the table. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/typed_table.rs#L119) ### MovingCamera The canvas seen through a camera frame (Manim's `MovingCameraScene`): `center` and `width` in units of `ui.space` say which part of the canvas fills the picture, `width` of `full_frame_width` showing it all. Both follow an `Anim`, so the camera can pan and zoom; `auto_zoom` gives the frame around some boxes. The children draw in canvas pixels as always. | Field | Type | Default | Description | | --- | --- | --- | --- | | `center` | `Anim<(f32, f32)>` | — | The point of the canvas at the middle of the picture, in units of `ui.space`; an `Anim` pans the camera. | | `width` | `Anim` | — | Width of the canvas shown, in units of `ui.space`; smaller zooms in, `full_frame_width` shows it all. An `Anim` zooms the camera. | | `children` | `Children` | — | What the camera looks at (a closure taking the `Ui`), drawn in canvas pixels. | Methods: - `fn view(space: Space, center: (f32, f32), width: f32) -> Affine`: The map from canvas pixels to the picture for a frame at `center` `width` units wide. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/camera.rs#L44) ### ZoomedInset Manim's `ZoomedScene`: the children, and a second, magnified copy of what is inside `frame` drawn in `display` (both pixel boxes), with the frame outlined and the display bordered. The display pops out of the frame over `run_time` after `delay` (Manim's `get_zoomed_display_pop_out_animation`); a `run_time` of 0 shows it at once. The children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `key` | `Option<&str>` | `None` | Names it when two of the same kind share a frame. | | `frame` | [`Rect`](https://fframes-kit.pages.dev/docs/layout-and-shapes/shapes.md) | `Rect::new(929.625, 509.625, 990.375, 570.375)` | The part of the canvas that is magnified. | | `display` | [`Rect`](https://fframes-kit.pages.dev/docs/layout-and-shapes/shapes.md) | `Rect::new(1447.5, 67.5, 1852.5, 472.5)` | Where the magnified copy is drawn. | | `frame_color` | `Color` | `theme.colors.highlight` | Colour of the outline of `frame`. | | `border_color` | `Color` | `theme.colors.highlight` | Colour of the border of `display`. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the frame outline and the display border in pixels. | | `background` | `Color` | `theme.colors.background` | Fills the display under the copy. | | `delay` | `f32` | `0.0` | Seconds after the container's start before the display begins to pop out of the frame. | | `run_time` | `f32` | `0.0` | Seconds the display takes to pop out of the frame; 0 shows it at once at `delay`. | | `rate_func` | `RateFunc` | `RateFunc::Smooth` | Easing of the pop-out over its run time. | | `children` | `Children` | `Box::new(\|_\| Svgr::empty())` | What is drawn and magnified (a closure taking the `Ui`); it renders twice. | Methods: - `fn display_at(&self, p: f32) -> Rect`: Where the display is at progress `p` of the pop-out. - `fn zoom(&self, p: f32) -> Affine`: The map from canvas pixels to the display at progress `p`: `frame` onto the display. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/explain/camera.rs#L101) ## See also - [Shapes](https://fframes-kit.pages.dev/docs/layout-and-shapes/shapes.md) --- # Shapes > Every shape is a component with its geometry props, the paint props fill, stroke, stroke_width, dash, opacity, and the animation props enter, exit… Page: https://fframes-kit.pages.dev/docs/layout-and-shapes/shapes/ Every shape is a component with its geometry props, the paint props `fill`, `stroke`, `stroke_width`, `dash`, `opacity`, and the animation props `enter`, `exit`, `emphasis`. Positions are canvas pixels with `y` down, angles are radians, counterclockwise on screen as in Manim. Shapes are placed by their center (`cx`, `cy`) unless points give them. Manim shapes are accent outlines by default, Remotion shapes (and Manim's `Dot`, `Annulus`, `Sector`) are filled. | Shape | Props | Source | | --- | --- | --- | | `Circle` | `cx`, `cy`, `radius` | Manim, Remotion | | `Dot` | `cx`, `cy`, `radius` (8) | Manim | | `Ellipse` | `cx`, `cy`, `width`, `height` | Manim, Remotion | | `Arc` | `cx`, `cy`, `radius`, `start_angle`, `angle` | Manim | | `Annulus` | `cx`, `cy`, `inner_radius`, `outer_radius` | Manim | | `Sector` | `cx`, `cy`, `radius`, `start_angle`, `angle` | Manim | | `Line`, `DashedLine` | `x1`, `y1`, `x2`, `y2` (`dash` is `(12, 12)` on `DashedLine`) | Manim | | `Polygon` | `points: Vec<(f32, f32)>` | Manim, Remotion | | `RegularPolygon` | `cx`, `cy`, `radius`, `n`, `start_angle: Option` | Manim | | `Triangle` | `cx`, `cy`, `radius` (equilateral, pointing up) | Manim, Remotion | | `Rectangle`, `Square`, `RoundedRectangle` | `cx`, `cy`, `width`/`height` or `side_length`, `corner_radius` | Manim | | `Star` | `cx`, `cy`, `n`, `outer_radius`, `inner_radius` | Manim, Remotion | | `Arrow`, `DoubleArrow` | `x1`, `y1`, `x2`, `y2`, `tip_length`, `tip_width`, `tip_start`, `tip_end` | Manim | | `Vector` | `x`, `y`, `dx`, `dy`, `tip_length`, `tip_width`, `tip_start`, `tip_end` | Manim | | `CurvedArrow` | `x1`, `y1`, `x2`, `y2`, `angle`, `tip_length`, `tip_width`, `tip_start`, `tip_end`; the shaft an outline, the tip filled | Manim | | `Brace` | `x1`, `y1`, `x2`, `y2`, `depth`; `brace.tip()` places a label | Manim | | `Rect` | `cx`, `cy`, `width`, `height`, `corner_radius` | Remotion | | `BlockArrow` | `cx`, `cy`, `length`, `head_width`, `head_length`, `shaft_width`, `angle` | Remotion `Arrow` | | `Callout` | `cx`, `cy`, `width`, `height`, `corner_radius`, `tail_x`, `tail_y`, `tail_width` | Remotion | | `Heart` | `cx`, `cy`, `height` | Remotion | | `Pie` | `cx`, `cy`, `radius`, `progress`, `rotation`, `closed` | Remotion | | `Spark` | `cx`, `cy`, `size`, `points`, `inner` | Remotion | | `TextPath` | `text`, `x`, `y` (baseline start), `style: TextStyle` | Manim `Text` | Gallery scenes: `Circle`, `Dot`, `Ellipse`, `Arc`, `Annulus`, `Sector`, `Line`, `DashedLine`, `Polygon`, `RegularPolygon`, `Triangle`, `Rectangle`, `Square`, `RoundedRectangle`, `Star`, `Arrow`, `Vector`, `DoubleArrow`, `CurvedArrow`, `Brace`, `Rect`, `BlockArrow`, `Callout`, `Heart`, `Pie`, `Spark`. Gallery clip of `Circle`: https://fframes-kit.pages.dev/media/scenes/Circle-88a3e8ed.mp4 Gallery clip of `Dot`: https://fframes-kit.pages.dev/media/scenes/Dot-459dd9da.mp4 Gallery clip of `Ellipse`: https://fframes-kit.pages.dev/media/scenes/Ellipse-54da7e4b.mp4 Gallery clip of `Arc`: https://fframes-kit.pages.dev/media/scenes/Arc-8503fbc1.mp4 Gallery clip of `Annulus`: https://fframes-kit.pages.dev/media/scenes/Annulus-ccf82553.mp4 Gallery clip of `Sector`: https://fframes-kit.pages.dev/media/scenes/Sector-ffae562a.mp4 Gallery clip of `Line`: https://fframes-kit.pages.dev/media/scenes/Line-054fadb6.mp4 Gallery clip of `DashedLine`: https://fframes-kit.pages.dev/media/scenes/DashedLine-ce52d03d.mp4 Gallery clip of `Polygon`: https://fframes-kit.pages.dev/media/scenes/Polygon-e8dee0b9.mp4 Gallery clip of `RegularPolygon`: https://fframes-kit.pages.dev/media/scenes/RegularPolygon-298f3184.mp4 Gallery clip of `Triangle`: https://fframes-kit.pages.dev/media/scenes/Triangle-efc13bf2.mp4 Gallery clip of `Rectangle`: https://fframes-kit.pages.dev/media/scenes/Rectangle-3abc1aba.mp4 Gallery clip of `Square`: https://fframes-kit.pages.dev/media/scenes/Square-c32538c5.mp4 Gallery clip of `RoundedRectangle`: https://fframes-kit.pages.dev/media/scenes/RoundedRectangle-7f0e8ee4.mp4 Gallery clip of `Star`: https://fframes-kit.pages.dev/media/scenes/Star-a9ca121e.mp4 Gallery clip of `Arrow`: https://fframes-kit.pages.dev/media/scenes/Arrow-db3f4589.mp4 Gallery clip of `Vector`: https://fframes-kit.pages.dev/media/scenes/Vector-27588fb9.mp4 Gallery clip of `DoubleArrow`: https://fframes-kit.pages.dev/media/scenes/DoubleArrow-a42e75d0.mp4 Gallery clip of `CurvedArrow`: https://fframes-kit.pages.dev/media/scenes/CurvedArrow-56d4547d.mp4 Gallery clip of `Brace`: https://fframes-kit.pages.dev/media/scenes/Brace-8d1f7eb0.mp4 Gallery clip of `Rect`: https://fframes-kit.pages.dev/media/scenes/Rect-8c5facac.mp4 Gallery clip of `BlockArrow`: https://fframes-kit.pages.dev/media/scenes/BlockArrow-985a35ec.mp4 Gallery clip of `Callout`: https://fframes-kit.pages.dev/media/scenes/Callout-75608d2c.mp4 Gallery clip of `Heart`: https://fframes-kit.pages.dev/media/scenes/Heart-f5015e92.mp4 Gallery clip of `Pie`: https://fframes-kit.pages.dev/media/scenes/Pie-b11d1799.mp4 Gallery clip of `Spark`: https://fframes-kit.pages.dev/media/scenes/Spark-b2f0575c.mp4 `shape.shape()` gives the `Shape` (path and paint) and every shape implements `Geometry` (`path()`, `length()`, `bbox()`). The path is a `kurbo::BezPath`; `fframes_kit::kurbo` is re-exported. `TextPath` draws glyph outlines from the font files of the video's media and falls back to a `` (faded in word by word by `Write`) when the family has none. The basic shapes can share an entrance, as in the gallery: ```rust use fframes_kit::prelude::*; fn shapes<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let enter = Some(Create::default().into()); let dot = ui.show(Dot { cx: 100.0, cy: 300.0, radius: 20.0, ..ui.defaults() }); let arc = ui.show(Arc { cx: 1200.0, cy: 300.0, radius: 100.0, angle: 1.2, ..ui.defaults() }); let ellipse = ui.show(Ellipse { cx: 250.0, cy: 300.0, width: 300.0, height: 150.0, enter, ..ui.defaults() }); let ring = ui.show(Annulus { cx: 600.0, cy: 300.0, inner_radius: 60.0, outer_radius: 100.0, ..ui.defaults() }); let sector = ui.show(Sector { cx: 900.0, cy: 300.0, radius: 100.0, angle: 1.2, ..ui.defaults() }); let line = ui.show(Line { x1: 100.0, y1: 500.0, x2: 400.0, y2: 500.0, enter, ..ui.defaults() }); let dash = ui.show(DashedLine { x1: 500.0, y1: 500.0, x2: 800.0, y2: 500.0, enter, ..ui.defaults() }); let polygon = ui.show(Polygon { points: vec![(1000.0, 400.0), (1200.0, 400.0), (1100.0, 600.0)], enter, ..ui.defaults() }); let regular = ui.show(RegularPolygon { cx: 1400.0, cy: 300.0, radius: 100.0, n: 6, enter, ..ui.defaults() }); let triangle = ui.show(Triangle { cx: 1600.0, cy: 300.0, radius: 100.0, enter, ..ui.defaults() }); svgr!({dot}{arc}{ellipse}{ring}{sector}{line}{dash}{polygon}{regular}{triangle}) } fn more_shapes<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let rect = ui.show(Rectangle { cx: 250.0, cy: 300.0, width: 250.0, height: 150.0, ..ui.defaults() }); let rounded = ui.show(RoundedRectangle { cx: 600.0, cy: 300.0, width: 250.0, height: 150.0, corner_radius: 30.0, ..ui.defaults() }); let star = ui.show(Star { cx: 950.0, cy: 300.0, outer_radius: 100.0, inner_radius: 40.0, ..ui.defaults() }); let pie = ui.show(Pie { cx: 1250.0, cy: 300.0, radius: 100.0, progress: motion::progress(ui.seconds(), 0.0, 1.5), ..ui.defaults() }); let spark = ui.show(Spark { cx: 1550.0, cy: 300.0, size: 150.0, ..ui.defaults() }); let square = ui.show(Square { cx: 1500.0, cy: 600.0, side_length: 150.0, ..ui.defaults() }); let remotion_rect = ui.show(Rect { cx: 200.0, cy: 600.0, width: 150.0, height: 100.0, ..ui.defaults() }); let heart = ui.show(Heart { cx: 1300.0, cy: 600.0, height: 150.0, ..ui.defaults() }); let arrow = ui.show(BlockArrow { cx: 400.0, cy: 600.0, length: 300.0, head_length: 100.0, head_width: 150.0, shaft_width: 60.0, ..ui.defaults() }); let bubble = ui.show(Callout { cx: 1000.0, cy: 600.0, width: 300.0, height: 150.0, tail_x: 900.0, tail_y: 750.0, ..ui.defaults() }); svgr!({rect}{rounded}{star}{pie}{spark}{square}{remotion_rect}{heart}{arrow}{bubble}) } fn arrows<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let vector = ui.show(Vector { x: 200.0, y: 300.0, dx: 200.0, dy: -100.0, ..ui.defaults() }); let double = ui.show(DoubleArrow { x1: 500.0, y1: 300.0, x2: 800.0, y2: 300.0, ..ui.defaults() }); let curved = ui.show(CurvedArrow { x1: 900.0, y1: 300.0, x2: 1200.0, y2: 300.0, ..ui.defaults() }); let brace = ui.show(Brace { x1: 500.0, y1: 600.0, x2: 1000.0, y2: 600.0, depth: 40.0, ..ui.defaults() }); svgr!({vector}{double}{curved}{brace}) } ``` ### Matchers and dashes Matchers draw around, behind, across or under a box: a `kurbo::Rect` in canvas pixels. Take it from `shape.bbox()` of any shape, `text_path.bounds(ui.ctx)` (the inked glyphs, descenders included, as Manim measures text), `label.bounds(ui)` (a `Label`'s cap-height box) or `view.bounds()` of a formula. Gallery scenes: `Matchers`, `BackgroundRectangle`, `Cross`, `CurvesAsSubmobjects`, `DashedVMobject`, `SurroundingRectangle`, `Underline`. Gallery clip of `Matchers`: https://fframes-kit.pages.dev/media/scenes/Matchers-4548fec5.mp4 Gallery clip of `BackgroundRectangle`: https://fframes-kit.pages.dev/media/scenes/BackgroundRectangle-1443fb8b.mp4 Gallery clip of `Cross`: https://fframes-kit.pages.dev/media/scenes/Cross-2f0a7834.mp4 Gallery clip of `CurvesAsSubmobjects`: https://fframes-kit.pages.dev/media/scenes/CurvesAsSubmobjects-1fc0857d.mp4 Gallery clip of `DashedVMobject`: https://fframes-kit.pages.dev/media/scenes/DashedVMobject-4e8a5db2.mp4 Gallery clip of `SurroundingRectangle`: https://fframes-kit.pages.dev/media/scenes/SurroundingRectangle-a8ce1abf.mp4 Gallery clip of `Underline`: https://fframes-kit.pages.dev/media/scenes/Underline-7bf62e58.mp4 | Shape | Props (defaults) | Source | | --- | --- | --- | | `SurroundingRectangle` | `around`, `buff` (`SMALL_BUFF`, 13.5), `corner_radius` (0); highlight outline | Manim | | `BackgroundRectangle` | `around`, `buff` (0), `corner_radius` (0); the background colour at 75% alpha | Manim | | `Cross` | `around`, `scale_factor` (1); red, thick | Manim | | `Underline` | `around`, `buff` (`SMALL_BUFF`) | Manim | | `DashedVMobject` | `path`, `num_dashes` (15), `dashed_ratio` (0.5), `dash_offset` (0) | Manim | | `CurvesAsSubmobjects` | `path`, `colors` (a gradient along the curves), `stroke_width` | Manim | `SMALL_BUFF` is Manim's 0.1 units on a 1080 px tall canvas. `DashedVMobject` spaces its dashes by arc length like Manim: an open path starts and ends with a dash, a closed one has as many gaps as dashes, and every dash is its own subpath (`dash_path` builds the path alone). `CurvesAsSubmobjects` draws each curve of the path as its own part, so `Write` draws them one after another; `curves(path)` gives the parts. ```rust use fframes_kit::prelude::*; fn boxed<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let word = TextPath { text: "Energy", x: 200.0, y: 300.0, ..ui.defaults() }; let around = word.bounds(ui.ctx); let word = ui.show(word); let background = ui.show(BackgroundRectangle { around, buff: 20.0, ..ui.defaults() }); let underline = ui.show(Underline { around, enter: Some(Create::default().into()), ..ui.defaults() }); let frame = ui.show(SurroundingRectangle { around, corner_radius: 12.0, enter: Some(Create::default().into()), ..ui.defaults() }); let circle = Circle { cx: 960.0, cy: 540.0, radius: 120.0, ..ui.defaults() }; let cross = ui.show(Cross { around: circle.bbox(), ..ui.defaults() }); let dashed = ui.show(DashedVMobject { path: circle.path(), num_dashes: 24, ..ui.defaults() }); let curves = ui.show(CurvesAsSubmobjects { path: circle.path(), ..ui.defaults() }); svgr!({background}{word}{underline}{frame}{dashed}{cross}{curves}) } ``` ### Arcs, dots and labels | Shape | Props (defaults) | Source | | --- | --- | --- | | `ArcBetweenPoints` | `x1`, `y1`, `x2`, `y2`, `angle` (a quarter turn), `radius: Option` | Manim | | `CurvedDoubleArrow` | as `CurvedArrow`, a tip at both ends | Manim | | `TangentialArc` | `line1`, `line2` (two points each), `radius` (50), `corner` ((1, 1)) | Manim | | `CubicBezier` | `start`, `start_handle`, `end_handle`, `end` | Manim | | `AnnularSector` | `cx`, `cy`, `inner_radius`, `outer_radius`, `start_angle`, `angle`; filled | Manim | | `ArcPolygon` | `points`, `angle` (an eighth turn), `radius: Option` | Manim | | `ArcPolygonFromArcs` | `arcs: Vec` | Manim | | `AnnotationDot` | `cx`, `cy`, `radius` (10.4); accent fill, light outline | Manim | | `LabeledDot` | `label`, `cx`, `cy`, `radius: Option` (fits the label), `label_style`, `label_color` | Manim | | `LabeledLine`, `LabeledArrow` | `label`, `x1`, `y1`, `x2`, `y2`, `label_position` (0.5), `label_style`, `label_color`, `label_box` (true) | Manim | A positive `angle` turns counterclockwise on screen, so an arc drawn left to right sags below its chord. A `radius` takes the shorter arc of that radius instead, on the other side when it is negative. `TangentialArc` rounds the corner where two lines cross: `corner` picks the side of each line, `1` along its direction and `-1` against it. `ArcPolygonFromArcs` joins arcs that do not meet with straight lines. Gallery scenes: `Arcs`, `ArcBetweenPoints`, `CurvedDoubleArrow`, `TangentialArc`, `CubicBezier`, `AnnularSector`, `ArcPolygon`, `ArcPolygonFromArcs`, `AnnotationDot`, `LabeledDot`, `LabeledLine`, `LabeledArrow`. Gallery clip of `Arcs`: https://fframes-kit.pages.dev/media/scenes/Arcs-07a6de0b.mp4 Gallery clip of `ArcBetweenPoints`: https://fframes-kit.pages.dev/media/scenes/ArcBetweenPoints-8dc8a0d5.mp4 Gallery clip of `CurvedDoubleArrow`: https://fframes-kit.pages.dev/media/scenes/CurvedDoubleArrow-bba7b438.mp4 Gallery clip of `TangentialArc`: https://fframes-kit.pages.dev/media/scenes/TangentialArc-f8412de7.mp4 Gallery clip of `CubicBezier`: https://fframes-kit.pages.dev/media/scenes/CubicBezier-a59b7b81.mp4 Gallery clip of `AnnularSector`: https://fframes-kit.pages.dev/media/scenes/AnnularSector-bf90805d.mp4 Gallery clip of `ArcPolygon`: https://fframes-kit.pages.dev/media/scenes/ArcPolygon-692f219b.mp4 Gallery clip of `ArcPolygonFromArcs`: https://fframes-kit.pages.dev/media/scenes/ArcPolygonFromArcs-8414a160.mp4 Gallery clip of `AnnotationDot`: https://fframes-kit.pages.dev/media/scenes/AnnotationDot-37e10b82.mp4 Gallery clip of `LabeledDot`: https://fframes-kit.pages.dev/media/scenes/LabeledDot-c6d48158.mp4 Gallery clip of `LabeledLine`: https://fframes-kit.pages.dev/media/scenes/LabeledLine-45128fda.mp4 Gallery clip of `LabeledArrow`: https://fframes-kit.pages.dev/media/scenes/LabeledArrow-dae523b8.mp4 A `ShapeLabel` is `"text".into()`, drawn in a font from the media, or, with the `latex` feature, `(&formula).into()`. The label is drawn as outlines with the shape, so `Create` and `Write` draw both. Without a font file it falls back to a `` element. The labelled lines put a box of the background colour behind the label, so the line stops at it. Curved arrows draw their shaft as an outline and their tips filled. ```rust use fframes_kit::prelude::*; fn distance<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let a = ui.show(LabeledDot { label: "A".into(), cx: 400.0, cy: 540.0, ..ui.defaults() }); let d = ui.show(LabeledLine { label: "d = 5".into(), x1: 440.0, y1: 540.0, x2: 1100.0, y2: 540.0, enter: Some(Write::default().into()), ..ui.defaults() }); let bend = ui.show(ArcBetweenPoints { x1: 400.0, y1: 600.0, x2: 1100.0, y2: 600.0, ..ui.defaults() }); svgr!({a}{d}{bend}) } ``` More arc constructions from the gallery: ```rust use fframes_kit::prelude::*; fn arcs<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let double = ui.show(CurvedDoubleArrow { x1: 100.0, y1: 300.0, x2: 400.0, y2: 300.0, angle: -1.0, ..ui.defaults() }); let tangent = ui.show(TangentialArc { line1: ((500.0, 400.0), (800.0, 400.0)), line2: ((500.0, 400.0), (500.0, 200.0)), radius: 120.0, ..ui.defaults() }); let curve = ui.show(CubicBezier { start: (900.0, 400.0), start_handle: (1000.0, 200.0), end_handle: (1200.0, 200.0), end: (1300.0, 400.0), ..ui.defaults() }); let sector = ui.show(AnnularSector { cx: 300.0, cy: 700.0, inner_radius: 70.0, outer_radius: 140.0, angle: 2.2, ..ui.defaults() }); let polygon = ui.show(ArcPolygon { points: vec![(600.0, 560.0), (720.0, 760.0), (480.0, 760.0)], angle: -0.8, ..ui.defaults() }); let arc1 = ArcBetweenPoints { x1: 850.0, y1: 700.0, x2: 1100.0, y2: 700.0, angle: 1.4, ..ui.defaults() }; let arc2 = ArcBetweenPoints { x1: 1100.0, y1: 700.0, x2: 850.0, y2: 700.0, angle: 1.4, ..ui.defaults() }; let lens = ui.show(ArcPolygonFromArcs { arcs: vec![arc1, arc2], ..ui.defaults() }); let dot = ui.show(AnnotationDot { cx: 1300.0, cy: 700.0, ..ui.defaults() }); let arrow = ui.show(LabeledArrow { label: "distance".into(), x1: 1400.0, y1: 700.0, x2: 1750.0, y2: 700.0, ..ui.defaults() }); svgr!({double}{tangent}{curve}{sector}{polygon}{lens}{dot}{arrow}) } ``` ### Angles, polygrams and tips | Shape | Props (defaults) | Source | | --- | --- | --- | | `Angle` | `line1`, `line2` (two points each), `radius` (54), `quadrant` ((1, 1)), `other_angle`, `dot`, `dot_radius` (5), `dot_distance` (0.55), `label`, `label_buff` (`3 * SMALL_BUFF`) | Manim | | `RightAngle` | as `Angle`, drawn as a square corner | Manim | | `Elbow` | `x`, `y`, `width` (27), `angle` | Manim | | `TangentLine` | `path`, `alpha` (0.5, by arc length), `length` (135) | Manim | | `Polygram` | `vertex_groups: Vec>`, each group a closed polygon | Manim | | `RegularPolygram` | `cx`, `cy`, `radius` (100), `num_vertices` (5), `density` (2), `start_angle: Option` | Manim | | `ConvexHull` | `points` | Manim | | `Cutout` | `outer`, `holes` (paths); filled | Manim | | `ScreenRectangle`, `FullScreenRectangle` | `cx`, `cy` (the canvas centre), `height` (540, 1080), `aspect_ratio` (16:9) | Manim | | `LabeledPolygram` | `vertex_groups`, `label`, `precision` (1 px), `label_style`, `label_color` | Manim | An angle runs counterclockwise on screen from `line1` to `line2` around the point where they cross. `quadrant` picks the half of each line the mark sits on, `1` along its direction and `-1` against it, and `other_angle` marks the rest of the turn instead. `value()` gives the marked angle in radians. `TangentLine` touches any path at `alpha` of its length; `kits::math::Tangent` is the tangent of a function graph on `Axes`. `RegularPolygram` with `density` `d` joins every `d`-th vertex, `{6/2}` gives two triangles. `Cutout` reverses the holes, so they stay empty under either fill rule. `LabeledPolygram` puts its label at the inside point furthest from every edge (`polylabel`), Manim's pole of inaccessibility. Gallery scenes: `Angles`, `Polygrams`, `RegularPolygram`, `FullScreenRectangle`, `Angle`, `RightAngle`, `Elbow`, `TangentLine`, `Polygram`, `ConvexHull`, `Cutout`, `ScreenRectangle`, `LabeledPolygram`. Gallery clip of `Angles`: https://fframes-kit.pages.dev/media/scenes/Angles-cf9b741b.mp4 Gallery clip of `Polygrams`: https://fframes-kit.pages.dev/media/scenes/Polygrams-c72a2f7d.mp4 Gallery clip of `RegularPolygram`: https://fframes-kit.pages.dev/media/scenes/RegularPolygram-9ac85d5a.mp4 Gallery clip of `FullScreenRectangle`: https://fframes-kit.pages.dev/media/scenes/FullScreenRectangle-de3733b0.mp4 Gallery clip of `Angle`: https://fframes-kit.pages.dev/media/scenes/Angle-92296eb8.mp4 Gallery clip of `RightAngle`: https://fframes-kit.pages.dev/media/scenes/RightAngle-5740a0af.mp4 Gallery clip of `Elbow`: https://fframes-kit.pages.dev/media/scenes/Elbow-3000ce48.mp4 Gallery clip of `TangentLine`: https://fframes-kit.pages.dev/media/scenes/TangentLine-1e03dc08.mp4 Gallery clip of `Polygram`: https://fframes-kit.pages.dev/media/scenes/Polygram-3184aec7.mp4 Gallery clip of `ConvexHull`: https://fframes-kit.pages.dev/media/scenes/ConvexHull-af2c129c.mp4 Gallery clip of `Cutout`: https://fframes-kit.pages.dev/media/scenes/Cutout-cd5ec160.mp4 Gallery clip of `ScreenRectangle`: https://fframes-kit.pages.dev/media/scenes/ScreenRectangle-d98024d6.mp4 Gallery clip of `LabeledPolygram`: https://fframes-kit.pages.dev/media/scenes/LabeledPolygram-cf967e60.mp4 Every arrow (`Arrow`, `Vector`, `DoubleArrow`, `CurvedArrow`, `CurvedDoubleArrow`, `LabeledArrow`) has `tip_start` and `tip_end`, an `Option`: `Triangle`, `TriangleFilled` (the default), `Circle`, `CircleFilled`, `Square`, `SquareFilled` and `Stealth`, Manim's `ArrowTriangleTip` to `StealthTip`. The tip ends at the arrow's point, `tip_length` along the shaft and `tip_width` across it. Outline tips are drawn unfilled on their own, and the shaft runs into the notch of a stealth tip. ```rust use fframes_kit::prelude::*; fn corner<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let (a, b) = (((400.0, 600.0), (800.0, 600.0)), ((400.0, 600.0), (700.0, 380.0))); let background = ui.show(FullScreenRectangle { fill: Some(ui.theme.colors.background), ..ui.defaults() }); // a Greek letter needs a font that has it, or a formula with the `latex` feature let theta = ui.show(Angle { line1: a, line2: b, label: "a".into(), ..ui.defaults() }); let arrow = ui.show(Arrow { x1: 900.0, y1: 600.0, x2: 1300.0, y2: 600.0, tip_start: Some(ArrowTip::Circle), tip_end: Some(ArrowTip::Stealth), enter: Some(GrowArrow::default().into()), ..ui.defaults() }); let star = ui.show(RegularPolygram { cx: 1600.0, cy: 600.0, num_vertices: 7, density: 3, ..ui.defaults() }); svgr!({background}{theta}{arrow}{star}) } ``` ```rust use fframes_kit::prelude::*; fn geometry<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let right = ui.show(RightAngle { line1: ((200.0, 400.0), (400.0, 400.0)), line2: ((200.0, 400.0), (200.0, 200.0)), radius: 48.0, ..ui.defaults() }); let elbow = ui.show(Elbow { x: 500.0, y: 400.0, width: 80.0, angle: 0.4, ..ui.defaults() }); let circle = Circle { cx: 850.0, cy: 350.0, radius: 90.0, ..ui.defaults() }; let tangent = ui.show(TangentLine { path: circle.path(), alpha: 0.15, length: 240.0, ..ui.defaults() }); let circle = ui.show(circle); let vertices = vec![(200.0, 650.0), (400.0, 650.0), (300.0, 850.0)]; let polygram = ui.show(Polygram { vertex_groups: vec![vertices.clone()], ..ui.defaults() }); let labeled = ui.show(LabeledPolygram { vertex_groups: vec![vec![(600.0, 650.0), (900.0, 650.0), (750.0, 850.0)]], label: "region".into(), ..ui.defaults() }); let hull = ui.show(ConvexHull { points: vertices, ..ui.defaults() }); let screen = ui.show(ScreenRectangle { cx: 1400.0, cy: 350.0, height: 220.0, ..ui.defaults() }); let outer = Circle { cx: 1300.0, cy: 750.0, radius: 120.0, ..ui.defaults() }.path(); let hole = Circle { cx: 1300.0, cy: 750.0, radius: 50.0, ..ui.defaults() }.path(); let cutout = ui.show(Cutout { outer, holes: vec![hole], ..ui.defaults() }); svgr!({right}{elbow}{circle}{tangent}{polygram}{labeled}{hull}{screen}{cutout}) } ``` ### Braces, boolean operations, SVG and images | Shape | Props (defaults) | Source | | --- | --- | --- | | `ArcBrace` | `cx`, `cy`, `radius` (135), `start_angle` (-1), `angle` (2), `depth` (24), `inside` | Manim | | `Union`, `Intersection` | `paths: Vec`; filled | Manim | | `Difference`, `Exclusion` | `subject`, `clip`; filled | Manim | | `SvgShape` | `parts` (from `SvgShape::parse`), `cx`, `cy` (the canvas centre), `height` (270), `fill`, `stroke` | Manim `SVGMobject` | | `SvgPath` | `path` (from `paths::parse_path`) | Manim `VMobjectFromSVGPath` | `Brace` is Manim's `BraceBetweenPoints`: its tip points to the right of the direction from `(x1, y1)` to `(x2, y2)`, below for a brace drawn left to right; a negative `depth` or swapped points put it on the other side. `BraceLabel` is `BraceLabel` and `BraceText`. `ArcBrace` bends a brace round an arc, its tip outward, or toward the center when `inside`; `tip()` is where its label goes. Gallery scenes: `Braces`, `Boolean`, `SvgShape`, `BraceLabel`, `ArcBrace`, `Union`, `Intersection`, `Difference`, `Exclusion`, `SvgPath`. Gallery clip of `Braces`: https://fframes-kit.pages.dev/media/scenes/Braces-91c8618e.mp4 Gallery clip of `Boolean`: https://fframes-kit.pages.dev/media/scenes/Boolean-46fa08fb.mp4 Gallery clip of `SvgShape`: https://fframes-kit.pages.dev/media/scenes/SvgShape-04de744c.mp4 Gallery clip of `BraceLabel`: https://fframes-kit.pages.dev/media/scenes/BraceLabel-5e5b8ad1.mp4 Gallery clip of `ArcBrace`: https://fframes-kit.pages.dev/media/scenes/ArcBrace-bb6d7b5c.mp4 Gallery clip of `Union`: https://fframes-kit.pages.dev/media/scenes/Union-f262db8a.mp4 Gallery clip of `Intersection`: https://fframes-kit.pages.dev/media/scenes/Intersection-55d42221.mp4 Gallery clip of `Difference`: https://fframes-kit.pages.dev/media/scenes/Difference-7250ffc9.mp4 Gallery clip of `Exclusion`: https://fframes-kit.pages.dev/media/scenes/Exclusion-3ffe15e9.mp4 Gallery clip of `SvgPath`: https://fframes-kit.pages.dev/media/scenes/SvgPath-8d51321b.mp4 The boolean operations read their paths with the nonzero fill rule, as SVG and Manim do, so a self-crossing star keeps its middle. Curves are flattened to `BOOLEAN_TOLERANCE` (0.25 px) and the result is straight lines, holes running against their outline. Combining detailed paths takes time: when the inputs do not change, compute the path once with `boolean(a, b, BooleanOp::..)` and keep it in `self`. `SvgShape::parse` reads an SVG file once, keep the result in `self`; a clone shares the parts. Every path is a part with the `id` of the path or of the nearest group around it, its fill and its outline, and keeps the file's paint unless `fill` or `stroke` replace it. `fill_part(key, color)` and `stroke_part(key, color)` paint the parts with an `id` or the one at an index, `only(|index, part| ..)` keeps some, and `part(key)` reads one. A gradient draws in its first stop's colour and a pattern black. Text and images in the file are not drawn: convert text to paths before export. `inspect` warns about all four. Manim's `ImageMobject` is an `` of a picture from the media; `PhotoFrame` adds a border and rounded corners, and `KenBurns` a slow zoom. ```rust use fframes_kit::{paths, prelude::*}; fn drawings<'a>(ui: &mut Ui<'a, '_, '_>, logo: &SvgShape) -> Svgr<'a> { let accent = ui.theme.colors.accent; let logo = ui.show(SvgShape { cx: 400.0, height: Some(300.0), enter: Some(Write::default().into()), ..logo.clone().fill_part("roof", accent) }); let a = Circle { cx: 900.0, cy: 540.0, radius: 120.0, ..ui.defaults() }.path(); let b = Circle { cx: 1000.0, cy: 540.0, radius: 120.0, ..ui.defaults() }.path(); let lens = ui.show(Intersection { paths: vec![a, b], ..ui.defaults() }); let wave = paths::parse_path("M1200 540 C1300 440 1400 640 1500 540").unwrap_or_default(); let wave = ui.show(SvgPath { path: wave, ..ui.defaults() }); let Some(photo) = ui.ctx.get_image("photo.png") else { return svgr!({logo}{lens}{wave}); }; let photo = svgr!(); svgr!({logo}{lens}{wave}{photo}) } ``` ```rust use fframes_kit::prelude::*; fn braces<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let brace = Brace { x1: 200.0, y1: 400.0, x2: 700.0, y2: 400.0, depth: 30.0, ..ui.defaults() }; let label = ui.show(BraceLabel { brace, text: "length".into(), ..ui.defaults() }); let arc = ui.show(ArcBrace { cx: 1200.0, cy: 500.0, radius: 135.0, angle: 2.0, ..ui.defaults() }); svgr!({label}{arc}) } fn booleans<'a>(ui: &mut Ui<'a, '_, '_>, a: &fframes_kit::kurbo::BezPath, b: &fframes_kit::kurbo::BezPath) -> Vec> { vec![ ui.show(Union { paths: vec![a.clone(), b.clone()], ..ui.defaults() }), ui.show(Difference { subject: a.clone(), clip: b.clone(), ..ui.defaults() }), ui.show(Exclusion { subject: a.clone(), clip: b.clone(), ..ui.defaults() }), ] } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Dot A small filled circle that marks a point (Manim `Dot`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `8.0` | Radius in pixels. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L101) ### Annulus A ring between two circles (Manim `Annulus`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `inner_radius` | `f32` | `50.0` | Radius in pixels of the hole. | | `outer_radius` | `f32` | `100.0` | Radius in pixels of the outer edge. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L166) ### Sector A slice of a disc from `start_angle` by `angle` (Manim `Sector`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels. | | `start_angle` | `f32` | `0.0` | Where the slice starts, in radians counterclockwise on screen from the right (0 is right). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | How far the slice turns, in radians; counterclockwise on screen for a positive value. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L188) ### Circle A circle (Manim `Circle`, Remotion `Circle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L85) ### Ellipse An ellipse in a `width` by `height` box (Manim `Ellipse`, Remotion `Ellipse`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `width` | `f32` | `200.0` | Full width in pixels. | | `height` | `f32` | `100.0` | Full height in pixels. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L117) ### Arc Part of a circle's outline from `start_angle` by `angle` (Manim `Arc`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels. | | `start_angle` | `f32` | `0.0` | Where the arc starts, in radians counterclockwise on screen from the right (0 is right). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | How far the arc turns, in radians; counterclockwise on screen for a positive value. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L140) ### Line A straight line from `(x1, y1)` to `(x2, y2)` (Manim `Line`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L217) ### DashedLine A dashed straight line (Manim `DashedLine`); `dash` is the dash and gap length. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `Some((12.0, 12.0))` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L238) ### Polygon A closed polygon through `points` (Manim `Polygon`, Remotion `Polygon`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `points` | `Vec<(f32, f32)>` | `vec![(0.0, -100.0), (100.0, 100.0), (-100.0, 100.0)]` | Corner points `(x, y)` in pixels of the canvas, joined in order and closed. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L260) ### RegularPolygon A regular polygon with `n` corners on a circle of `radius` (Manim `RegularPolygon`). `start_angle` is the first corner; `None` is Manim's default (right for an even `n`, top for an odd one). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels of the circle the corners lie on. | | `n` | `usize` | `6` | Number of corners (at least 3). | | `start_angle` | `Option` | `None` | Radians of the first corner, counterclockwise on screen from the right; `None` puts it on the right for an even `n`, at the top for an odd one. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L272) ### Triangle An equilateral triangle pointing up, its corners on a circle of `radius` (Manim `Triangle`, Remotion `Triangle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels of the circle the corners lie on. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L294) ### Rectangle A `width` by `height` rectangle (Manim `Rectangle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `width` | `f32` | `400.0` | Full width in pixels. | | `height` | `f32` | `200.0` | Full height in pixels. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L311) ### Square A square with sides of `side_length` (Manim `Square`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `side_length` | `f32` | `200.0` | Length of each side in pixels. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L331) ### RoundedRectangle A rectangle with round corners (Manim `RoundedRectangle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `width` | `f32` | `400.0` | Full width in pixels. | | `height` | `f32` | `200.0` | Full height in pixels. | | `corner_radius` | `f32` | `theme.radius.md` | Radius of the rounded corners in pixels; 0 for square corners. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L349) ### Star A star with `n` points (Manim `Star`, Remotion `Star`). The first point is at the top; `inner_radius` is where the edges meet between the points. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `n` | `usize` | `5` | Number of points (at least 2). | | `outer_radius` | `f32` | `100.0` | Radius in pixels of the points. | | `inner_radius` | `f32` | `38.2` | Radius in pixels of the notches between the points. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/basic.rs#L372) ### Arrow An arrow from `(x1, y1)` to `(x2, y2)` (Manim `Arrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `None` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arrows.rs#L170) ### DoubleArrow An arrow with a tip at both ends (Manim `DoubleArrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arrows.rs#L242) ### Vector An arrow from `(x, y)` by `(dx, dy)` (Manim `Vector`, which starts at the origin). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `dx` | `f32` | `200.0` | Horizontal offset in pixels from the start to the tip; positive is right. | | `dy` | `f32` | `0.0` | Vertical offset in pixels from the start to the tip; positive is down. | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `None` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arrows.rs#L206) ### CurvedArrow An arrow along a circular arc that turns by `angle` from `(x1, y1)` to `(x2, y2)`, counterclockwise on screen for a positive angle (Manim `CurvedArrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | Turn in radians of the arc from the start to the end, counterclockwise on screen for a positive value. | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `None` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arrows.rs#L318) ### Brace A curly brace along `(x1, y1)` to `(x2, y2)` whose tip points `depth` pixels to the right of that direction on screen: below for a brace drawn left to right (Manim `Brace` under a mobject). `Brace::tip` is where a label goes. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the span the brace runs along. | | `y1` | `f32` | `0.0` | Start y in pixels of the span the brace runs along (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the span the brace runs along. | | `y2` | `f32` | `0.0` | End y in pixels of the span the brace runs along (y down). | | `depth` | `f32` | `24.0` | How far in pixels the tip sticks out, to the right of the direction from start to end on screen; negative turns the brace over. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn tip(&self) -> (f32, f32)`: The point of the tip. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/brace.rs#L8) ### Rect A rectangle, square corners unless `corner_radius` is set (Remotion `Rect`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `width` | `f32` | `200.0` | Width in pixels. | | `height` | `f32` | `200.0` | Height in pixels. | | `corner_radius` | `f32` | `0.0` | Radius of the rounded corners in pixels; 0 for square corners. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L13) ### BlockArrow A block arrow `length` long pointing in `angle` (counterclockwise on screen, 0 is right), centered on `(cx, cy)` (Remotion `Arrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `length` | `f32` | `300.0` | Total length in pixels, tail to point. | | `head_width` | `f32` | `185.0` | Width in pixels of the arrowhead across the shaft. | | `head_length` | `f32` | `120.0` | Length in pixels of the arrowhead (at most `length`). | | `shaft_width` | `f32` | `80.0` | Width in pixels of the shaft. | | `angle` | `f32` | `0.0` | Direction it points, in radians counterclockwise on screen from the right (0 is right). | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L36) ### Callout A speech bubble: a rounded box with a tail that points at `(tail_x, tail_y)`. The tail overlaps the box, so give it a fill and no outline. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `width` | `f32` | `400.0` | Width in pixels of the box. | | `height` | `f32` | `200.0` | Height in pixels of the box. | | `corner_radius` | `f32` | `theme.radius.md` | Radius of the rounded corners in pixels; 0 for square corners. | | `tail_x` | `f32` | `-100.0` | X in pixels of the canvas the tail points at. | | `tail_y` | `f32` | `200.0` | Y in pixels of the canvas the tail points at. | | `tail_width` | `f32` | `60.0` | Width in pixels of the tail where it meets the box. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L80) ### Heart A heart `height` tall and 1.1 times as wide (Remotion `Heart`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `height` | `f32` | `200.0` | Height in pixels; the width is 1.1 times it. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L124) ### Pie The `progress` (0 to 1) part of a disc, clockwise from the top turned by `rotation` radians clockwise (Remotion `Pie`). With `closed` false only the arc is drawn. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels. | | `progress` | `f32` | `0.5` | Share of the disc drawn, 0 to 1 (clamped); 1 is the full circle. | | `rotation` | `f32` | `0.0` | Radians the start turns clockwise from the top. | | `closed` | `bool` | `true` | Joins the arc to the center with straight edges; `false` draws only the arc. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L152) ### Spark A sparkle: `points` sharp points joined by curves that pass `inner` times the size from the center (Remotion's star with rounded inner corners). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `size` | `f32` | `100.0` | Distance in pixels from the center to each point. | | `points` | `usize` | `4` | Number of points (at least 2). | | `inner` | `f32` | `0.2` | Distance of the curves' control point from the center as a fraction of `size`; smaller gives thinner points. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/remotion.rs#L190) ### BackgroundRectangle A filled rectangle behind a box, so it reads over a busy picture (Manim `BackgroundRectangle`): the background colour at Manim's 75% opacity, no outline. | Field | Type | Default | Description | | --- | --- | --- | --- | | `around` | `Rect` | `Rect::ZERO` | The box it is drawn around, in pixels of the canvas. | | `buff` | `f32` | `0.0` | Pixels the rectangle extends beyond the box on every side. | | `corner_radius` | `f32` | `0.0` | Radius of the rounded corners in pixels; 0 for square corners. | | `fill` | `Option` | `paint.fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `paint.stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `paint.stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/matchers.rs#L39) ### Cross Two lines from corner to corner of a box, scaled by `scale_factor` about its center (Manim `Cross`): red and thick. | Field | Type | Default | Description | | --- | --- | --- | --- | | `around` | `Rect` | `Rect::ZERO` | The box the lines run across, in pixels of the canvas. | | `scale_factor` | `f32` | `1.0` | Scales the lines about the box's center; 1 reaches the corners. | | `fill` | `Option` | — | `None` leaves the inside empty. | | `stroke` | `Option` | — | `None` draws no outline. | | `stroke_width` | `f32` | — | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/matchers.rs#L60) ### CurvesAsSubmobjects A path split into its curves (Manim `CurvesAsSubmobjects`), each drawn on its own so `Write` draws them one after another. The stroke runs from the first to the last of `colors` along the curves, like Manim's `set_color_by_gradient`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `path` | `BezPath` | `BezPath::new()` | The path to split into its curves, in pixels of the canvas. | | `colors` | `Vec` | `vec![theme.colors.accent]` | The gradient along the curves; one colour paints them all. | | `stroke_width` | `f32` | `theme.stroke.regular` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shapes(&self) -> Vec`: One shape per curve, in path order. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/dashed.rs#L106) ### DashedVMobject Dashes along any path, spaced by arc length (Manim `DashedVMobject`). See `dash_path`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `path` | `BezPath` | `BezPath::new()` | The path to dash, in pixels of the canvas. | | `num_dashes` | `usize` | `15` | Number of dashes along the path. | | `dashed_ratio` | `f32` | `0.5` | Share of the path's length (0 to 1) that is dashes; the rest is gaps. | | `dash_offset` | `f32` | `0.0` | Moves the pattern along the path, in dash-plus-gap lengths (0 to 1 wraps around). | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/dashed.rs#L75) ### SurroundingRectangle A rectangle `buff` outside a box (Manim `SurroundingRectangle`), in the highlight colour. | Field | Type | Default | Description | | --- | --- | --- | --- | | `around` | `Rect` | `Rect::ZERO` | The box it is drawn around, in pixels of the canvas. | | `buff` | `f32` | `SMALL_BUFF` | Gap in pixels between the box and the rectangle. | | `corner_radius` | `f32` | `0.0` | Radius of the rounded corners in pixels; 0 for square corners. | | `fill` | `Option` | — | `None` leaves the inside empty. | | `stroke` | `Option` | — | `None` draws no outline. | | `stroke_width` | `f32` | — | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/matchers.rs#L22) ### Underline A line `buff` below a box, as wide as it (Manim `Underline`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `around` | `Rect` | `Rect::ZERO` | The box it is drawn around, in pixels of the canvas. | | `buff` | `f32` | `SMALL_BUFF` | Gap in pixels between the bottom of the box and the line. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/matchers.rs#L83) ### ArcBetweenPoints The arc from `(x1, y1)` to `(x2, y2)` that turns by `angle`, counterclockwise on screen for a positive angle (Manim `ArcBetweenPoints`). A `radius` replaces the angle with the shorter arc of that radius (negative for the other side). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | Turn in radians, counterclockwise on screen for a positive value. | | `radius` | `Option` | `None` | `Some` replaces `angle` with the shorter arc of this radius in pixels (negative for the other side); `None` uses `angle`. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn turn(&self) -> f64`: The turn of the arc: `angle`, or the one `radius` gives. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L44) ### CurvedDoubleArrow An arrow along an arc with a tip at both ends (Manim `CurvedDoubleArrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | Turn in radians of the arc from the start to the end, counterclockwise on screen for a positive value. | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L79) ### TangentialArc The arc of `radius` that touches two lines (Manim `TangentialArc`): the first from `line1.0` to `line1.1`, the second likewise. Of the four corners where the lines cross, `corner` picks the one on the side of each line's direction (`1`) or against it (`-1`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `line1` | `((f32, f32), (f32, f32))` | `((0.0, 0.0), (200.0, 0.0))` | The first line as two points `((x1, y1), (x2, y2))` in pixels of the canvas. | | `line2` | `((f32, f32), (f32, f32))` | `((0.0, 0.0), (0.0, 200.0))` | The second line, in the same form. | | `radius` | `f32` | `50.0` | Radius of the arc in pixels. | | `corner` | `(f32, f32)` | `(1.0, 1.0)` | Which of the four corners where the lines cross the arc fills: for each line `1` is the side of its direction, `-1` against it. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L117) ### CubicBezier A cubic Bézier curve from `start` to `end` pulled by two handles (Manim `CubicBezier`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `start` | `(f32, f32)` | `(0.0, 0.0)` | Where the curve starts, `(x, y)` in pixels of the canvas. | | `start_handle` | `(f32, f32)` | `(60.0, -100.0)` | Control point pulling the curve away from `start`, in pixels. | | `end_handle` | `(f32, f32)` | `(140.0, -100.0)` | Control point pulling the curve toward `end`, in pixels. | | `end` | `(f32, f32)` | `(200.0, 0.0)` | Where the curve ends, `(x, y)` in pixels of the canvas. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L163) ### AnnularSector The part of a ring from `start_angle` by `angle` (Manim `AnnularSector`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `inner_radius` | `f32` | `50.0` | Radius in pixels of the inner edge. | | `outer_radius` | `f32` | `100.0` | Radius in pixels of the outer edge. | | `start_angle` | `f32` | `0.0` | Where it starts, in radians counterclockwise on screen from the right (0 is right). | | `angle` | `f32` | `std::f32::consts::FRAC_PI_2` | Turn in radians, counterclockwise on screen for a positive value. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L185) ### ArcPolygon A closed shape of arcs between consecutive `points`, each turning by `angle` (Manim `ArcPolygon`); a `radius` replaces the angle as on `ArcBetweenPoints`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `points` | `Vec<(f32, f32)>` | `vec![(0.0, -100.0), (100.0, 100.0), (-100.0, 100.0)]` | Corner points `(x, y)` in pixels of the canvas; arcs join them in order and back to the first. | | `angle` | `f32` | `std::f32::consts::FRAC_PI_4` | Turn in radians of each arc between consecutive points. | | `radius` | `Option` | `None` | `Some` replaces `angle` with the shorter arc of this radius in pixels on each side; `None` uses `angle`. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L220) ### ArcPolygonFromArcs A closed shape of the given arcs (Manim `ArcPolygonFromArcs`): each one after the other, joined by straight lines where one does not end where the next starts. | Field | Type | Default | Description | | --- | --- | --- | --- | | `arcs` | `Vec` | `Vec::new()` | The arcs, drawn in order; a straight line joins one that ends away from where the next starts. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/arcs.rs#L270) ### AnnotationDot A dot that marks a point for an annotation (Manim `AnnotationDot`): a third larger than a `Dot`, filled in the accent colour with a light outline. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `10.4` | Radius in pixels. | | `fill` | `Option` | — | `None` leaves the inside empty. | | `stroke` | `Option` | — | `None` draws no outline. | | `stroke_width` | `f32` | — | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/labeled.rs#L170) ### LabeledDot A dot with a label inside (Manim `LabeledDot`). Without a `radius` it is `SMALL_BUFF` larger than half the label's longer side, as in Manim. | Field | Type | Default | Description | | --- | --- | --- | --- | | `label` | `ShapeLabel` | `ShapeLabel::None` | The text or formula inside the dot; `ShapeLabel::None` for none. | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `Option` | `None` | Radius in pixels; `None` fits the label (half its longer side plus `SMALL_BUFF`). | | `label_style` | `TextStyle` | `theme.typography.body` | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | `theme.colors.title` | Colour of the label text. | | `fill` | `Option` | `Some(theme.colors.accent)` | `None` leaves the inside empty. | | `stroke` | `Option` | `None` | `None` draws no outline. | | `stroke_width` | `f32` | `theme.stroke.regular` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/labeled.rs#L191) ### LabeledLine A line with a label on it (Manim `LabeledLine`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `label` | `ShapeLabel` | `ShapeLabel::None` | Text or formula written with it; `ShapeLabel::None` for none. | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `label_position` | `f32` | `0.5` | Where along the line the label sits, 0 at the start to 1 at the end. | | `label_style` | `TextStyle` | — | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | — | Colour of the label text. | | `label_box` | `bool` | `true` | A box in the background colour behind the label, so the line does not cross it. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn path(&self) -> BezPath`: The line or arrow without its label. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/labeled.rs#L395) ### LabeledArrow An arrow with a label on it (Manim `LabeledArrow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `label` | `ShapeLabel` | `ShapeLabel::None` | Text or formula written with it; `ShapeLabel::None` for none. | | `x1` | `f32` | `0.0` | Start x in pixels of the canvas. | | `y1` | `f32` | `0.0` | Start y in pixels of the canvas (y down). | | `x2` | `f32` | `200.0` | End x in pixels of the canvas. | | `y2` | `f32` | `0.0` | End y in pixels of the canvas (y down). | | `tip_length` | `f32` | `24.0` | Length in pixels of each tip from its point back along the shaft; shortened to fit a short arrow. | | `tip_width` | `f32` | `20.0` | Width in pixels of each tip across the shaft. | | `tip_start` | `Option` | `None` | The tip at the start; `None` for none. | | `tip_end` | `Option` | `Some(ArrowTip::TriangleFilled)` | The tip at the end; `None` for none. | | `label_position` | `f32` | `0.5` | Where along the line the label sits, 0 at the start to 1 at the end. | | `label_style` | `TextStyle` | — | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | — | Colour of the label text. | | `label_box` | `bool` | `true` | A box in the background colour behind the label, so the line does not cross it. | | `fill` | `Option` | `Paint::arrow(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::arrow(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::arrow(theme).stroke_width` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn path(&self) -> BezPath`: The line or arrow without its label. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/labeled.rs#L410) ### Angle The mark of the angle between two lines (Manim `Angle`): an arc of `radius` about where they cross, counterclockwise on screen from the first line to the second. | Field | Type | Default | Description | | --- | --- | --- | --- | | `line1` | `Segment` | `((0.0, 0.0), (200.0, 0.0))` | The first line as two points `((x1, y1), (x2, y2))` in pixels of the canvas; the mark starts from it. | | `line2` | `Segment` | `((0.0, 0.0), (0.0, -200.0))` | The second line, in the same form; the mark turns to it. | | `radius` | `f32` | `ANGLE_RADIUS` | Radius of the mark in pixels, about where the lines cross. | | `quadrant` | `(f32, f32)` | `(1.0, 1.0)` | Which half of each line the mark sits on: `1` along its direction, `-1` against. | | `other_angle` | `bool` | `false` | Marks the other angle, clockwise from the first line to the second. | | `elbow` | `bool` | — | A square corner instead of an arc. | | `dot` | `bool` | `false` | A dot inside the mark, `dot_distance` of the way out from the corner. | | `dot_radius` | `f32` | `5.0` | Radius of the dot in pixels. | | `dot_distance` | `f32` | `0.55` | How far out the dot sits, as a fraction of the mark's radius (0 at the corner, 1 on the mark). | | `dot_color` | `Color` | `theme.colors.title` | Colour of the dot. | | `label` | `ShapeLabel` | `ShapeLabel::None` | A label beside the mark, `label_buff` beyond its radius. | | `label_buff` | `f32` | `3.0 * SMALL_BUFF` | Pixels between the mark's radius and the label. | | `label_style` | `TextStyle` | `theme.typography.body` | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | `theme.colors.title` | Colour of the label text. | | `stroke` | `Option` | `Some(theme.colors.accent)` | `None` draws no outline. | | `stroke_width` | `f32` | `theme.stroke.regular` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn value(&self) -> f32`: The angle the mark spans in radians, always positive (Manim `get_value`). - `fn path(&self) -> BezPath`: The mark alone: an arc, or a square corner. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/angles.rs#L256) ### RightAngle The square mark of a right angle between two lines (Manim `RightAngle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `line1` | `Segment` | `((0.0, 0.0), (200.0, 0.0))` | The first line as two points `((x1, y1), (x2, y2))` in pixels of the canvas; the mark starts from it. | | `line2` | `Segment` | `((0.0, 0.0), (0.0, -200.0))` | The second line, in the same form; the mark turns to it. | | `radius` | `f32` | `ANGLE_RADIUS` | Radius of the mark in pixels, about where the lines cross. | | `quadrant` | `(f32, f32)` | `(1.0, 1.0)` | Which half of each line the mark sits on: `1` along its direction, `-1` against. | | `other_angle` | `bool` | `false` | Marks the other angle, clockwise from the first line to the second. | | `elbow` | `bool` | — | A square corner instead of an arc. | | `dot` | `bool` | `false` | A dot inside the mark, `dot_distance` of the way out from the corner. | | `dot_radius` | `f32` | `5.0` | Radius of the dot in pixels. | | `dot_distance` | `f32` | `0.55` | How far out the dot sits, as a fraction of the mark's radius (0 at the corner, 1 on the mark). | | `dot_color` | `Color` | `theme.colors.title` | Colour of the dot. | | `label` | `ShapeLabel` | `ShapeLabel::None` | A label beside the mark, `label_buff` beyond its radius. | | `label_buff` | `f32` | `3.0 * SMALL_BUFF` | Pixels between the mark's radius and the label. | | `label_style` | `TextStyle` | `theme.typography.body` | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | `theme.colors.title` | Colour of the label text. | | `stroke` | `Option` | `Some(theme.colors.accent)` | `None` draws no outline. | | `stroke_width` | `f32` | `theme.stroke.regular` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn value(&self) -> f32`: The angle the mark spans in radians, always positive (Manim `get_value`). - `fn path(&self) -> BezPath`: The mark alone: an arc, or a square corner. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/angles.rs#L262) ### Elbow Two lines that meet at a right angle, an L of `width` with its corner up and to the right of `(x, y)`, turned by `angle` about it (Manim `Elbow`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `0.0` | X in pixels of the point the elbow is drawn from; its corner lies `width` up and to the right of it. | | `y` | `f32` | `0.0` | Y in pixels of the point the elbow is drawn from. | | `width` | `f32` | `27.0` | Length in pixels of each of the two lines. | | `angle` | `f32` | `0.0` | Rotation in radians about `(x, y)`, counterclockwise on screen. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/angles.rs#L267) ### TangentLine A line of `length` touching `path` at `alpha` of its length (Manim `TangentLine`). On axes, `kits::math::Tangent` draws the tangent of a function. | Field | Type | Default | Description | | --- | --- | --- | --- | | `path` | `BezPath` | `BezPath::new()` | The path to touch, in pixels of the canvas. | | `alpha` | `f32` | `0.5` | Where along the path's length the line touches it, 0 (start) to 1 (end). | | `length` | `f32` | `135.0` | Length of the line in pixels, centered on the point where it touches. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/angles.rs#L293) ### Polygram Several closed polygons in one shape (Manim `Polygram`), one per group of vertices. | Field | Type | Default | Description | | --- | --- | --- | --- | | `vertex_groups` | `Vec>` | — | One list of corner points `(x, y)` in pixels of the canvas per closed polygon. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L32) ### RegularPolygram A regular star polygon (Manim `RegularPolygram`): `num_vertices` corners on a circle of `radius`, each joined to the one `density` steps on. `start_angle` is the first corner; `None` is Manim's default (right for an even count, top for an odd one). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `100.0` | Radius in pixels. | | `num_vertices` | `usize` | `5` | Number of corners on the circle (at least 3). | | `density` | `usize` | `2` | How many corners on each corner joins to; 2 on 5 corners draws a pentagram. | | `start_angle` | `Option` | `None` | Radians of the first corner, counterclockwise on screen from the right; `None` puts it on the right for an even count, at the top for an odd one. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L65) ### ConvexHull The smallest convex polygon around `points` (Manim `ConvexHull`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `points` | `Vec<(f32, f32)>` | `vec![(0.0, 0.0), (100.0, 20.0), (60.0, 90.0), (20.0, 40.0)]` | The points to wrap, `(x, y)` in pixels of the canvas. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L118) ### Cutout A filled shape with holes (Manim `Cutout`): `outer` with every path of `holes` cut out. The holes are turned against the outline, so they stay empty however they were drawn. | Field | Type | Default | Description | | --- | --- | --- | --- | | `outer` | `BezPath` | `kurbo::Rect::new(-100.0, -100.0, 100.0, 100.0).to_path(TOLERANCE)` | The outline of the filled shape, in pixels of the canvas. | | `holes` | `Vec` | `Vec::new()` | Paths cut out of `outer`; they stay empty however they were drawn. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L130) ### ScreenRectangle A rectangle in a screen's proportions (Manim `ScreenRectangle`): 16:9, half the canvas tall, in its middle. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `960.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `540.0` | Center y in pixels of the canvas (y down). | | `height` | `f32` | `540.0` | Height in pixels; the width is `height * aspect_ratio`. | | `aspect_ratio` | `f32` | `16.0 / 9.0` | Width divided by height. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L161) ### FullScreenRectangle A rectangle over the whole 1920 x 1080 canvas (Manim `FullScreenRectangle`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `960.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `540.0` | Center y in pixels of the canvas (y down). | | `height` | `f32` | `1080.0` | Height in pixels; the width is `height * aspect_ratio`. | | `aspect_ratio` | `f32` | `16.0 / 9.0` | Width divided by height. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/polygram.rs#L180) ### LabeledPolygram Closed polygons with a label where it fits best (Manim `LabeledPolygram`): at the point inside furthest from every edge, found to within `precision` pixels. | Field | Type | Default | Description | | --- | --- | --- | --- | | `vertex_groups` | `Vec>` | `Vec::new()` | One list of corner points `(x, y)` in pixels of the canvas per closed polygon. | | `label` | `ShapeLabel` | `ShapeLabel::None` | The text or formula placed inside; `ShapeLabel::None` for none. | | `precision` | `f32` | `1.0` | How closely in pixels the label point is found; smaller is exact but slower. | | `label_style` | `TextStyle` | `theme.typography.body` | The label's font: family, size in pixels and weight. | | `label_color` | `Color` | `theme.colors.title` | Colour of the label text. | | `fill` | `Option` | `paint.fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `paint.stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `paint.stroke_width` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn label_point(&self) -> Option<(f32, f32)>`: Where the label goes: the inside point furthest from the edges. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/labeled.rs#L442) ### ArcBrace A brace bent along an arc (Manim `ArcBrace`): round the circle of `radius` about `(cx, cy)` from `start_angle` turning `angle` (radians, counterclockwise on screen), its tip `depth` pixels outward, or toward the center when `inside`. `ArcBrace::tip` is where a label goes. | Field | Type | Default | Description | | --- | --- | --- | --- | | `cx` | `f32` | `0.0` | Center x in pixels of the canvas. | | `cy` | `f32` | `0.0` | Center y in pixels of the canvas (y down). | | `radius` | `f32` | `135.0` | Radius in pixels of the circle the brace follows. | | `start_angle` | `f32` | `-1.0` | Where the brace starts, in radians counterclockwise on screen from the right. | | `angle` | `f32` | `2.0` | How far the brace turns, in radians; counterclockwise on screen for a positive value, clockwise for a negative one. | | `depth` | `f32` | `24.0` | How far in pixels the tip sticks out of the arc; negative turns the brace over. | | `inside` | `bool` | `false` | Puts the tip toward the center instead of outward. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. - `fn tip(&self) -> (f32, f32)`: The point of the tip. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/brace.rs#L74) ### Union The area inside any of `paths` (Manim `Union`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `paths` | `Vec` | `Vec::new()` | The paths to combine, in pixels of the canvas. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/boolean.rs#L100) ### Intersection The area inside all of `paths` (Manim `Intersection`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `paths` | `Vec` | `Vec::new()` | The paths to intersect, in pixels of the canvas. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/boolean.rs#L112) ### Difference The area of `subject` outside `clip` (Manim `Difference`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `subject` | `BezPath` | `BezPath::new()` | The path to cut from, in pixels of the canvas. | | `clip` | `BezPath` | `BezPath::new()` | The path cut out of `subject`. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/boolean.rs#L124) ### Exclusion The area inside exactly one of `subject` and `clip` (Manim `Exclusion`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `subject` | `BezPath` | `BezPath::new()` | The first path, in pixels of the canvas. | | `clip` | `BezPath` | `BezPath::new()` | The second path; the overlap with `subject` is left empty. | | `fill` | `Option` | `Paint::solid(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::solid(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::solid(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/boolean.rs#L138) ### SvgShape An SVG drawing as a shape (Manim `SVGMobject`). Parse the file once with `SvgShape::parse` and keep it; a clone shares the parts. The drawing is centered on `(cx, cy)` and scaled to `height`. Every path keeps the file's paint unless `fill` or `stroke` replace it; `fill_part`, `stroke_part` and `only` change single parts. Text and images in the file are not drawn (convert text to paths before export) and a gradient is drawn in its first stop's colour; `inspect` warns about them. | Field | Type | Default | Description | | --- | --- | --- | --- | | `parts` | `Arc>` | `Arc::default()` | The paths of the file with their paint, from `SvgShape::parse`; clones share them. | | `cx` | `f32` | `960.0` | Center x in pixels of the canvas the drawing is placed on. | | `cy` | `f32` | `540.0` | Center y in pixels of the canvas the drawing is placed on (y down). | | `height` | `Option` | `Some(270.0)` | The drawn height; `None` keeps the file's size and position. | | `fill` | `Option` | `None` | Replaces the fill of every filled part. | | `stroke` | `Option` | `None` | Replaces the outline color of every outlined part. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | | `unsupported` | `Arc>` | `Arc::default()` | What the file holds that is not drawn as it is; `inspect` lists it. | Methods: - `fn parse(svg: &str) -> Result`: The paths of `svg` with the default placement: 270 px tall in the middle of the canvas. - `fn part(&self, key: impl Into) -> Option<&SvgPart>`: The first part that `key` names. - `fn fill_part(self, key: impl Into, color: Color) -> Self`: Fills the parts that `key` names with `color`. - `fn stroke_part(self, key: impl Into, color: Color) -> Self`: Outlines the parts that `key` names in `color`, keeping their width (2 px when they had no outline). - `fn only(self, keep: impl Fn(usize, &SvgPart) -> bool) -> Self`: Keeps the parts for which `keep(index, part)` is true. Indexes stay those of the file for the closure, not for later calls. - `fn shapes(&self) -> Vec`: Every part placed on the canvas with its paint. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/svg_import.rs#L194) ### SvgPath One path given as SVG path data (Manim `VMobjectFromSVGPath`), in canvas pixels as written: `SvgPath { path: paths::parse_path("M0 0 L100 0")?, ..ui.defaults() }`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `path` | `BezPath` | `BezPath::new()` | The path as SVG path data, in pixels of the canvas as written. | | `fill` | `Option` | `Paint::outline(theme).fill` | `None` leaves the inside empty. | | `stroke` | `Option` | `Paint::outline(theme).stroke` | `None` draws no outline. | | `stroke_width` | `f32` | `Paint::outline(theme).stroke_width` | Outline width in pixels. | | `dash` | `Option<(f32, f32)>` | `None` | Dash and gap lengths of the outline. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn shape(&self) -> Shape`: The path and paint as a `Shape`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/shapes/svg_import.rs#L372) ### BraceLabel A `Brace` with text at its tip (Manim `BraceLabel`, `brace.get_text`). The label sits on the side the tip points to; the brace draws with its own `enter`, the text fades with `label_enter`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `brace` | `Brace` | `Brace::themed(theme)` | The brace to label; it draws with its own `enter` and `exit`. | | `text` | `Cow` | `Cow::Borrowed("")` | The text at the brace's tip. | | `style` | `TextStyle` | `theme.typography.body` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.title` | Colour of the text. | | `buff` | `f32` | `theme.spacing.sm` | Pixels between the tip and the text's box. | | `label_enter` | `Option` | `None` | How the text fades in; `None` shows it from the start. | | `label_exit` | `Option` | `None` | How the text fades out before the end; `None` for none. | Methods: - `fn label(&self) -> Label`: The label at the tip, on the side the tip points to; centered on it for a brace of zero length. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/label.rs#L253) --- # Geometry animations > Animations are plain data. Page: https://fframes-kit.pages.dev/docs/layout-and-shapes/geometry-animations/ Animations are plain data. Put them into a shape's `enter`, `exit` or `emphasis` with `.into()`. Each has Manim's fields: `delay`, `run_time`, `rate_func`, plus its own. Gallery scene: `SquareToCircle`. Gallery clip of `SquareToCircle`: https://fframes-kit.pages.dev/media/scenes/SquareToCircle-7bad6189.mp4 | Animation | Slot | What it does | | --- | --- | --- | | `FadeIn`, `FadeOut` | enter, exit | fade with an optional `shift` | | `Create`, `Uncreate` | enter, exit | draws the outline from its start (and fades the fill in), or back | | `DrawBorderThenFill` | enter | draws the outline, then fills (`run_time` 2) | | `Write` | enter | `DrawBorderThenFill` per glyph of a `TextPath`, `lag_ratio` | | `Unwrite { reverse }` | exit | `Write` backwards; with `reverse` (the default) the first glyph goes first | | `ShowPartial { start, end }` | enter | the drawn part of the outline moves from one window to another | | `ShowIncreasingSubsets`, `ShowSubmobjectsOneByOne` | enter | the parts appear one more at a time, or one at a time with the last staying | | `SpiralIn { scale_factor, fade_in_fraction }` | enter | the parts fly in on a spiral around the center of the whole | | `AddTextLetterByLetter`, `RemoveTextLetterByLetter` | enter, exit | glyphs appear, or vanish from the last, `time_per_char` each (or in `run_time`) | | `GrowFromCenter`, `GrowFromPoint { point }`, `GrowArrow` | enter | grows from nothing about the center, a point, or the path's start | | `GrowFromEdge { edge: Anchor }` | enter | grows from a point of the bounding box (`Anchor::Bottom` is Manim's `DOWN`) | | `SpinInFromNothing { angle }` | enter | grows while turning into place | | `Indicate { scale_factor, color }` | emphasis | scales up and tints, then back | | `Circumscribe { color, buff }` | emphasis | a rectangle runs once around the shape | | `Flash { point, num_lines, flash_radius, line_length }` | emphasis | lines shoot out once | | `Wiggle { scale_value, rotation_angle, n_wiggles }` | emphasis | shakes the shape | | `Blink { blinks, time_on, time_off, hide_at_end }` | emphasis | on, then off, `blinks` times (a step: no `rate_func`) | | `FocusOn { point, color, opacity }` | emphasis | a frame-sized disc shrinks onto the point and vanishes | | `Rotate { angle, about_point }` | emphasis | turns by `angle` (a half turn; positive is counterclockwise, as in Manim) and stays | | `Rotating { angle, about_point, forever }` | emphasis | turns at an even speed, `angle` per `run_time` (a full turn in 5 s), once or `forever` | | `FadeToColor { color }` | emphasis | fill and outline turn into `color` and stay | | `ScaleInPlace { scale_factor }` | emphasis | scales about the center (twice) and stays | | `ShrinkToCenter` | exit | shrinks to nothing about the center | Gallery scenes: `Create`, `Uncreate`, `DrawBorderThenFill`, `Write`, `GrowFromCenter`, `GrowFromPoint`, `GrowArrow`, `SpinInFromNothing`, `Indicate`, `Circumscribe`, `Flash`, `Wiggle`, `Unwrite`, `ShowPartial`, `ShowIncreasingSubsets`, `ShowSubmobjectsOneByOne`, `SpiralIn`, `AddTextLetterByLetter`, `RemoveTextLetterByLetter`, `GrowFromEdge`, `Blink`, `FocusOn`, `Rotate`, `Rotating`, `FadeToColor`, `ScaleInPlace`, `ShrinkToCenter`. Gallery clip of `Create`: https://fframes-kit.pages.dev/media/scenes/Create-366d699d.mp4 Gallery clip of `Uncreate`: https://fframes-kit.pages.dev/media/scenes/Uncreate-b3bd5a1f.mp4 Gallery clip of `DrawBorderThenFill`: https://fframes-kit.pages.dev/media/scenes/DrawBorderThenFill-9134b77f.mp4 Gallery clip of `Write`: https://fframes-kit.pages.dev/media/scenes/Write-bbd317fc.mp4 Gallery clip of `GrowFromCenter`: https://fframes-kit.pages.dev/media/scenes/GrowFromCenter-f0576bbc.mp4 Gallery clip of `GrowFromPoint`: https://fframes-kit.pages.dev/media/scenes/GrowFromPoint-721b4b1d.mp4 Gallery clip of `GrowArrow`: https://fframes-kit.pages.dev/media/scenes/GrowArrow-176cec8e.mp4 Gallery clip of `SpinInFromNothing`: https://fframes-kit.pages.dev/media/scenes/SpinInFromNothing-c8253fdc.mp4 Gallery clip of `Indicate`: https://fframes-kit.pages.dev/media/scenes/Indicate-dd38f43b.mp4 Gallery clip of `Circumscribe`: https://fframes-kit.pages.dev/media/scenes/Circumscribe-1ee1a4e7.mp4 Gallery clip of `Flash`: https://fframes-kit.pages.dev/media/scenes/Flash-7bcb83b7.mp4 Gallery clip of `Wiggle`: https://fframes-kit.pages.dev/media/scenes/Wiggle-0f4443f3.mp4 Gallery clip of `Unwrite`: https://fframes-kit.pages.dev/media/scenes/Unwrite-3ad336db.mp4 Gallery clip of `ShowPartial`: https://fframes-kit.pages.dev/media/scenes/ShowPartial-af10af09.mp4 Gallery clip of `ShowIncreasingSubsets`: https://fframes-kit.pages.dev/media/scenes/ShowIncreasingSubsets-0ff5b84b.mp4 Gallery clip of `ShowSubmobjectsOneByOne`: https://fframes-kit.pages.dev/media/scenes/ShowSubmobjectsOneByOne-e4e226ee.mp4 Gallery clip of `SpiralIn`: https://fframes-kit.pages.dev/media/scenes/SpiralIn-821b5d37.mp4 Gallery clip of `AddTextLetterByLetter`: https://fframes-kit.pages.dev/media/scenes/AddTextLetterByLetter-f23ab833.mp4 Gallery clip of `RemoveTextLetterByLetter`: https://fframes-kit.pages.dev/media/scenes/RemoveTextLetterByLetter-3b59039b.mp4 Gallery clip of `GrowFromEdge`: https://fframes-kit.pages.dev/media/scenes/GrowFromEdge-c277cd8c.mp4 Gallery clip of `Blink`: https://fframes-kit.pages.dev/media/scenes/Blink-4c0e860f.mp4 Gallery clip of `FocusOn`: https://fframes-kit.pages.dev/media/scenes/FocusOn-529f4b8a.mp4 Gallery clip of `Rotate`: https://fframes-kit.pages.dev/media/scenes/Rotate-1486fd1b.mp4 Gallery clip of `Rotating`: https://fframes-kit.pages.dev/media/scenes/Rotating-9c87769c.mp4 Gallery clip of `FadeToColor`: https://fframes-kit.pages.dev/media/scenes/FadeToColor-03242fc8.mp4 Gallery clip of `ScaleInPlace`: https://fframes-kit.pages.dev/media/scenes/ScaleInPlace-b7e2fe21.mp4 Gallery clip of `ShrinkToCenter`: https://fframes-kit.pages.dev/media/scenes/ShrinkToCenter-44836517.mp4 Enter and emphasis animations count from the start of the enclosing container plus `delay`; exit animations end `delay` seconds before its end. They change only the transform, opacity and drawn part of the outline, never the path. The parts are the glyphs of a `TextPath`, or the shapes given to `animate::draw_animated`; a single shape is one part. For a `` line, `AddTextLetterByLetter` is a `Typewriter { cursor: None, .. }`, `AddTextWordByWord` a `TextReveal { by: RevealBy::Word, run_time: 0.0, shift: (0.0, 0.0), .. }`, and Manim's `TypeWithCursor` and `UntypeWithCursor` are a `Typewriter`, with `erase: Some(seconds)` for the second. Gallery scenes: `TextPath`, `TypeWithCursor`. Gallery clip of `TextPath`: https://fframes-kit.pages.dev/media/scenes/TextPath-5e4a74de.mp4 Gallery clip of `TypeWithCursor`: https://fframes-kit.pages.dev/media/scenes/TypeWithCursor-28ed436f.mp4 | Component | Props | What it does | | --- | --- | --- | | `Morph` (Manim `Transform`) | `from: Shape`, `to: Shape`, `delay`, `run_time`, `rate_func` | morphs path, paint and width; `Morph::new(from, to)` | | `ReplacementTransform` | the same | `Morph` that draws `to` itself once done | | `MoveAlongPath` | `path: BezPath`, `delay`, `run_time`, `rate_func` | `Enter`: moves content from the path's start to its end | | `ShowPassingFlash` | `shape`, `time_width`, timing | a piece of the outline runs along it once; `::new(shape)` | | `ShowPassingFlashWithThinningStrokeWidth` | `shape`, `n_segments`, `time_width`, timing | the same with a wide head and a thinning tail | | `Broadcast` | `point`, `radius`, `color`, `n_mobs`, `lag_ratio`, opacities, timing | rings grow out of a point one after another and fade | | `AnimatedBoundary` | `shape`, `colors`, `max_stroke_width`, `cycle_rate`, `back_and_forth` | the outline redraws itself in one colour after another, forever; `::new(shape, colors)` | | `ApplyWave` | `shape`, `direction`, `amplitude` (px), `ripples`, `time_width`, timing | a wave crosses the shape left to right and bends it; `::new(shape)` | | `Homotopy`, `SmoothedVectorizedHomotopy` | `shape`, `homotopy: fn(x, y, t) -> (x, y)`, timing | moves every point while `t` runs from 0 to 1, in `ui.space` units; the smoothed one redraws the handles | | `ComplexHomotopy` | `shape`, `homotopy: fn((re, im), t) -> (re, im)`, timing | the same on complex numbers | | `PhaseFlow` | `shape`, `function: fn(x, y) -> (dx, dy)`, `virtual_time`, timing | the points flow along a field for `virtual_time` | | `TracedPath` | `point: &dyn Fn(f32) -> (f32, f32)`, `stroke`, `stroke_width`, `dissipating_time`, `delay` | the line a moving point has drawn so far | | `TransformFromCopy` | `from`, `to`, timing | morphs a copy while `from` stays | | `ClockwiseTransform`, `CounterclockwiseTransform` | `from`, `to`, `path_arc`, timing | morphs with every point on an arc (a half turn) | | `CyclicReplace`, `Swap` | `shapes` (`first`, `second`), `path_arc`, timing | each shape moves on an arc (a quarter turn) to the next one's place | | `FadeTransform`, `FadeTransformPieces` | `from`, `to` (lists for the pieces), `stretch`, timing | `from` fades out moving onto `to`'s box while `to` fades in from `from`'s; pieces pair by index and the rest fade in place (Manim splits pieces until the counts match) | | `TransformMatchingShapes` | `from: Vec`, `to: Vec`, timing | pieces with the same `shape_key` (form, whatever the place and size) morph, the others fade; pieces of one key pair in order, where Manim morphs them as a group | | `TransformAnimations` | `from`, `from_anim: EnterAnim`, `to`, `to_anim`, timing | morphs while the look goes from the first animation's to the second's | | `ApplyMatrix` | `shape`, `matrix: [[f64; 2]; 2]`, `about_point`, timing (3 s) | maps every point by the matrix in `ui.space` units, `y` up | | `ApplyPointwiseFunction`, `ApplyPointwiseFunctionToCenter` | `shape`, `function: fn(x, y) -> (x, y)`, timing (3 s) | moves every point, or the whole shape by its center | | `ApplyComplexFunction` | `shape`, `function: fn((re, im)) -> (re, im)`, timing | the same on complex numbers, on arcs as wide as the angle of `function(1)` | | `ApplyFunction` | `shape`, `function: fn(Shape) -> Shape`, timing | `Morph` into the shape the function returns | | `LaggedStart` | `lag_ratio`, `run_time`, `children` | child `i` starts `i * lag_ratio * run_time` seconds in | | `AnimationGroup` | `children` | every child at once | | `Succession` | `run_time`, `children` | each child `run_time` after the one before | | `LaggedStart::map` (Manim `LaggedStartMap`) | `items`, `f(ui, &item)` | a `LaggedStart` with one child per item, lag ratio 0.05 | | `ChangeSpeed` | `run_time`, `speed: Vec<(fraction, speed)>`, `children` | the children play slower or faster over `run_time` and still start and end on time | | `ChangeDecimalToValue` | `number: DecimalNumber`, `target`, timing | counts the number from its tracker's value to `target` | | `ChangingDecimal` | `number`, `number_update_func: fn(alpha) -> value`, timing | shows the function of the progress | Gallery scenes: `Transform`, `ReplacementTransform`, `MoveAlongPath`, `LaggedStart`, `AnimationGroup`, `Succession`, `ShowPassingFlash`, `ShowPassingFlashWithThinningStrokeWidth`, `Broadcast`, `AnimatedBoundary`, `ApplyWave`, `Homotopy`, `SmoothedVectorizedHomotopy`, `ComplexHomotopy`, `PhaseFlow`, `TracedPath`, `TransformFromCopy`, `ClockwiseTransform`, `CounterclockwiseTransform`, `Swap`, `CyclicReplace`, `FadeTransform`, `FadeTransformPieces`, `TransformMatchingShapes`, `TransformAnimations`, `ApplyMatrix`, `ApplyPointwiseFunction`, `ApplyComplexFunction`, `ApplyFunction`, `ApplyPointwiseFunctionToCenter`, `ChangeDecimalToValue`, `ChangingDecimal`, `ChangeSpeed`, `LaggedStartMap`. Gallery clip of `Transform`: https://fframes-kit.pages.dev/media/scenes/Transform-1eb4cb74.mp4 Gallery clip of `ReplacementTransform`: https://fframes-kit.pages.dev/media/scenes/ReplacementTransform-556737d9.mp4 Gallery clip of `MoveAlongPath`: https://fframes-kit.pages.dev/media/scenes/MoveAlongPath-141dab9b.mp4 Gallery clip of `LaggedStart`: https://fframes-kit.pages.dev/media/scenes/LaggedStart-dca6adb5.mp4 Gallery clip of `AnimationGroup`: https://fframes-kit.pages.dev/media/scenes/AnimationGroup-4d111427.mp4 Gallery clip of `Succession`: https://fframes-kit.pages.dev/media/scenes/Succession-a98cb5a9.mp4 Gallery clip of `ShowPassingFlash`: https://fframes-kit.pages.dev/media/scenes/ShowPassingFlash-c13a4126.mp4 Gallery clip of `ShowPassingFlashWithThinningStrokeWidth`: https://fframes-kit.pages.dev/media/scenes/ShowPassingFlashWithThinningStrokeWidth-3cc1606b.mp4 Gallery clip of `Broadcast`: https://fframes-kit.pages.dev/media/scenes/Broadcast-71d71a97.mp4 Gallery clip of `AnimatedBoundary`: https://fframes-kit.pages.dev/media/scenes/AnimatedBoundary-a15432a3.mp4 Gallery clip of `ApplyWave`: https://fframes-kit.pages.dev/media/scenes/ApplyWave-f53ee065.mp4 Gallery clip of `Homotopy`: https://fframes-kit.pages.dev/media/scenes/Homotopy-6d979651.mp4 Gallery clip of `SmoothedVectorizedHomotopy`: https://fframes-kit.pages.dev/media/scenes/SmoothedVectorizedHomotopy-47f61aee.mp4 Gallery clip of `ComplexHomotopy`: https://fframes-kit.pages.dev/media/scenes/ComplexHomotopy-0b88b145.mp4 Gallery clip of `PhaseFlow`: https://fframes-kit.pages.dev/media/scenes/PhaseFlow-8b3bd0aa.mp4 Gallery clip of `TracedPath`: https://fframes-kit.pages.dev/media/scenes/TracedPath-4b6f7f5c.mp4 Gallery clip of `TransformFromCopy`: https://fframes-kit.pages.dev/media/scenes/TransformFromCopy-bf8a7911.mp4 Gallery clip of `ClockwiseTransform`: https://fframes-kit.pages.dev/media/scenes/ClockwiseTransform-5ad68226.mp4 Gallery clip of `CounterclockwiseTransform`: https://fframes-kit.pages.dev/media/scenes/CounterclockwiseTransform-9eb26cdd.mp4 Gallery clip of `Swap`: https://fframes-kit.pages.dev/media/scenes/Swap-c6d833a3.mp4 Gallery clip of `CyclicReplace`: https://fframes-kit.pages.dev/media/scenes/CyclicReplace-406b374b.mp4 Gallery clip of `FadeTransform`: https://fframes-kit.pages.dev/media/scenes/FadeTransform-2ffa839e.mp4 Gallery clip of `FadeTransformPieces`: https://fframes-kit.pages.dev/media/scenes/FadeTransformPieces-109c2b39.mp4 Gallery clip of `TransformMatchingShapes`: https://fframes-kit.pages.dev/media/scenes/TransformMatchingShapes-0ed27a17.mp4 Gallery clip of `TransformAnimations`: https://fframes-kit.pages.dev/media/scenes/TransformAnimations-7eab6b60.mp4 Gallery clip of `ApplyMatrix`: https://fframes-kit.pages.dev/media/scenes/ApplyMatrix-818901e4.mp4 Gallery clip of `ApplyPointwiseFunction`: https://fframes-kit.pages.dev/media/scenes/ApplyPointwiseFunction-c7a4e51a.mp4 Gallery clip of `ApplyComplexFunction`: https://fframes-kit.pages.dev/media/scenes/ApplyComplexFunction-5fc7b864.mp4 Gallery clip of `ApplyFunction`: https://fframes-kit.pages.dev/media/scenes/ApplyFunction-9186d23e.mp4 Gallery clip of `ApplyPointwiseFunctionToCenter`: https://fframes-kit.pages.dev/media/scenes/ApplyPointwiseFunctionToCenter-203b85d4.mp4 Gallery clip of `ChangeDecimalToValue`: https://fframes-kit.pages.dev/media/scenes/ChangeDecimalToValue-b708aed6.mp4 Gallery clip of `ChangingDecimal`: https://fframes-kit.pages.dev/media/scenes/ChangingDecimal-b0390fac.mp4 Gallery clip of `ChangeSpeed`: https://fframes-kit.pages.dev/media/scenes/ChangeSpeed-348f3445.mp4 Gallery clip of `LaggedStartMap`: https://fframes-kit.pages.dev/media/scenes/LaggedStartMap-de7474cd.mp4 The homotopies cut the path into about `HOMOTOPY_SEGMENTS` (256) pieces and map every point each frame; `PhaseFlow` takes up to `PHASE_FLOW_STEPS` (120) Runge-Kutta steps per point. `TracedPath` calls `point` again for every frame of the trail, at most `MAX_TRACE_SAMPLES` (240) times, on a fixed grid of times so the drawn part does not shimmer; keep `point` cheap, or give it a `dissipating_time`. The prelude calls Manim's `Transform` `Morph`, because `Transform` there is the SVG transform; it is also `fframes_kit::animate::Transform`. ```rust use fframes_kit::prelude::*; /// Manim's SquareToCircle: create a square, morph it into a circle, fade it out. fn square_to_circle<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let square = Square { cx: 960.0, cy: 540.0, side_length: 400.0, ..ui.defaults() }; let circle = Circle { cx: 960.0, cy: 540.0, radius: 220.0, fill: Some(ui.theme.colors.highlight), ..ui.defaults() }; let (from, to) = (square.shape(), circle.shape()); let create = ui.show(Sequence { from: 0.0, duration: Some(1.2), children: children(move |ui| { ui.show(Square { enter: Some(Create::default().into()), ..square.clone() }) }), }); let morph = ui.show(Sequence { from: 1.2, duration: None, children: children(move |ui| { let morph = ui.show(Morph::new(from.clone(), to.clone())); FadeOut { run_time: 0.8, ..ui.defaults() }.apply(ui, morph) }), }); svgr!({create}{morph}) } ``` The gallery also compares morphs and point mappings. These functions return separate alternatives; show one at a time, or arrange them in a grid: ```rust use fframes_kit::prelude::*; fn morphs<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let a = Square { cx: 600.0, cy: 540.0, side_length: 240.0, ..ui.defaults() }.shape(); let b = Circle { cx: 1200.0, cy: 540.0, radius: 135.0, ..ui.defaults() }.shape(); vec![ ui.show(fframes_kit::animate::Transform::new(a.clone(), b.clone())), ui.show(ReplacementTransform::new(a.clone(), b.clone())), ui.show(TransformFromCopy::new(a.clone(), b.clone())), ui.show(ClockwiseTransform::new(a.clone(), b.clone())), ui.show(CounterclockwiseTransform::new(a.clone(), b.clone())), ui.show(Swap::new(a.clone(), b.clone())), ui.show(CyclicReplace::new(vec![a.clone(), b.clone()])), ui.show(FadeTransform::new(a.clone(), b.clone())), ui.show(FadeTransformPieces::new(vec![a.clone()], vec![b.clone()])), ui.show(TransformMatchingShapes::new(vec![a.clone()], vec![b.clone()])), ui.show(TransformAnimations::new(a, Create::default().into(), b, GrowFromCenter::default().into())), ] } fn mappings<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let shape = Square { cx: 960.0, cy: 540.0, side_length: 300.0, ..ui.defaults() }.shape(); let h = Homotopy::new(shape.clone(), |x, y, t| (x + 0.5 * t * y.sin(), y)); vec![ ui.show(Homotopy::new(shape.clone(), |x, y, t| (x + 0.5 * t * y.sin(), y))), ui.show(SmoothedVectorizedHomotopy { homotopy: h }), ui.show(ComplexHomotopy::new(shape.clone(), |(re, im), t| (re - t * im, im + t * re))), ui.show(PhaseFlow::new(shape.clone(), |x, y| (-y, x))), ui.show(ApplyMatrix::new(shape.clone(), [[1.0, 1.0], [0.0, 1.0]])), ui.show(ApplyPointwiseFunction::new(shape.clone(), |x, y| (x + 0.3 * y.sin(), y))), ui.show(ApplyPointwiseFunctionToCenter::new(shape.clone(), |x, y| (-x, y + 1.0))), ui.show(ApplyComplexFunction::new(shape.clone(), |(re, im)| (-im, re))), ui.show(ApplyFunction::new(shape.clone(), |s| Shape { path: paths::translate_path(&s.path, 200.0, 0.0), ..s })), ui.show(ApplyWave { amplitude: 60.0, ..ApplyWave::new(shape) }), ] } fn highlights<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let shape = Square { cx: 960.0, cy: 540.0, side_length: 300.0, ..ui.defaults() }.shape(); let colors = vec![ui.theme.colors.accent, ui.theme.colors.highlight]; vec![ ui.show(ShowPassingFlash::new(shape.clone())), ui.show(ShowPassingFlashWithThinningStrokeWidth::new(shape.clone())), ui.show(AnimatedBoundary::new(shape, colors)), ui.show(Broadcast { point: (960.0, 540.0), radius: 360.0, ..ui.defaults() }), ] } fn point(t: f32) -> (f32, f32) { (960.0 + 420.0 * (1.4 * t).sin(), 540.0 + 240.0 * (2.1 * t).sin()) } fn trace<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let trail = ui.show(TracedPath { point: &point, stroke_width: 6.0, ..ui.defaults() }); let (cx, cy) = point(ui.seconds()); let dot = ui.show(Dot { cx, cy, radius: 20.0, ..ui.defaults() }); svgr!({trail}{dot}) } fn outline_text<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(TextPath { text: "Written", enter: Some(Write::default().into()), ..ui.defaults() }) } ``` The timing groups all take a list of child closures. `ChangeSpeed` changes the local clock seen by its child, and the decimal animations supply values to a `DecimalNumber`: ```rust use fframes_kit::prelude::*; fn dots<'a>() -> Vec> { (0..3).map(|i| children(move |ui| { ui.show(Dot { cx: 600.0 + 200.0 * i as f32, cy: 540.0, radius: 60.0, enter: Some(GrowFromCenter::default().into()), ..ui.defaults() }) })).collect() } fn groups<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(LaggedStart { lag_ratio: 0.25, run_time: 0.8, children: dots() }), ui.show(AnimationGroup { children: dots() }), ui.show(Succession { run_time: 0.8, children: dots() }), ui.show(ChangeSpeed { run_time: 2.0, speed: vec![(0.0, 0.2), (1.0, 2.0)], children: children(|ui| ui.show(Dot { cx: 300.0 + 600.0 * (ui.seconds() / 2.0).clamp(0.0, 1.0), cy: 540.0, ..ui.defaults() })) }), ] } fn numbers<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let n = DecimalNumber { x: 960.0, y: 540.0, anchor: TextAnchor::Middle, ..ui.defaults() }; vec![ ui.show(ChangeDecimalToValue::new(n.clone(), 1000.0)), ui.show(ChangingDecimal::new(n, |a| (a * std::f32::consts::PI).sin())), ] } ``` ### Manim's imperative animations Manim changes a mobject in place and plays the change. In the kit a frame is a function of its time, so there is no mobject to change: these names become the components above, or a value computed in `render` from `ui.seconds()`. | Manim | fframes-kit | | --- | --- | | `self.wait(1)` | nothing to call: start what follows 1 second later (`Sequence { from }`) | | `ApplyMethod(m.shift, RIGHT)`, `m.animate.shift(RIGHT)` | `Morph::new(shape.clone(), moved)`, `moved` the shape with the change made | | `m.generate_target()`, `MoveToTarget(m)` | the same: the target is a second `Shape` | | `m.save_state()`, `Restore(m)` | keep a clone of the shape, `Morph::new(changed, saved)` | | `UpdateFromFunc(m, f)`, updaters | compute the prop from `ui.seconds()` in `render`, which runs every frame | | `UpdateFromAlphaFunc(m, f)` | the same with `alpha = rate.apply(motion::progress(t, start, end), run_time)` | | `MaintainPositionRelativeTo(m, leader)` | place `m` at the leader's position, computed from the same time, plus the offset | ```rust use fframes_kit::prelude::*; /// `ApplyMethod(square.shift, 2 * RIGHT)`, then `Restore(square)`: morph to a moved copy and /// back to the saved one. `self.wait(0.5)` is the half second between them. fn shift_and_restore<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let saved = Square { cx: 660.0, cy: 540.0, side_length: 200.0, ..ui.defaults() }.shape(); let shifted = Shape { path: paths::translate_path(&saved.path, 270.0, 0.0), ..saved.clone() }; let (a, b) = (shifted.clone(), saved.clone()); let restore = children(move |ui| ui.show(Morph::new(a.clone(), b.clone()))); if ui.seconds() < 1.5 { ui.show(Morph::new(saved, shifted)) } else { ui.show(Sequence { from: 1.5, duration: None, children: restore }) } } /// `UpdateFromFunc`, `UpdateFromAlphaFunc` and `MaintainPositionRelativeTo`: a dot crosses in 2 seconds from /// second 1, and a second dot follows it 80 pixels above. fn follow<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let alpha = RateFunc::Smooth.apply(motion::progress(ui.seconds(), 1.0, 3.0), 2.0); let x = motion::lerp(360.0, 1560.0, alpha); let leader = ui.show(Dot { cx: x, cy: 540.0, ..ui.defaults() }); let follower = ui.show(Dot { cx: x, cy: 460.0, ..ui.defaults() }); svgr!({leader}{follower}) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### TextPath Text drawn as glyph outlines, so `Create`, `Write` and `DrawBorderThenFill` can draw it (Manim's `Text` as a `VMobject`). `(x, y)` is the start of the baseline. | Field | Type | Default | Description | | --- | --- | --- | --- | | `text` | `&str` | `""` | The text to draw. | | `x` | `f32` | `0.0` | Start of the baseline in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline in pixels of the canvas. | | `style` | `TextStyle` | `theme.typography.title` | The font: a family from the video's media, its size in pixels and weight. | | `fill` | `Option` | `Some(theme.colors.title)` | Colour of the inside of the glyphs; `None` leaves it empty. | | `stroke` | `Option` | `None` | Colour of the glyph outlines; `None` draws no outline. | | `stroke_width` | `f32` | `theme.stroke.thin` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `enter` | `Option` | `None` | How it appears (`Create`, `Write`, `DrawBorderThenFill`, ..); `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | | `emphasis` | `Option` | `None` | An emphasis animation (`Indicate`, `Wiggle`, ..) played while it is shown. | Methods: - `fn parts(&self, ctx: &FFramesContext) -> Option>`: One shape per glyph, or `None` when the font has no file in the media. - `fn bounds(&self, ctx: &FFramesContext) -> Rect`: The box of the inked glyphs, descenders included (what Manim measures around text), or the estimated box when the font has no file in the media. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/text_outline.rs#L177) ### Transform Morphs `from` into `to`: path, paint and width (Manim's `Transform`). Before it starts it draws `from`, after it ends the morphed shape, which looks like `to`. The prelude calls it `Morph`, because `Transform` there is the SVG transform. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `Transform::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: From `from` to `to` in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform.rs#L46) ### ReplacementTransform `Transform` that draws `to` itself once the morph has finished (Manim's `ReplacementTransform`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ReplacementTransform::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: From `from` to `to` in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform.rs#L87) ### ShowPassingFlash A piece of the outline `time_width` long runs along it once; nothing is drawn before or after (Manim's `ShowPassingFlash`). Draw the shape itself separately to keep it. A shape without an outline flashes in its fill colour. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape; its outline is what the effect draws (its fill colour is used if it has no stroke), the shape itself is not drawn. | | `time_width` | `f32` | — | Length of the flash as a fraction of the outline's length. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ShowPassingFlash::new(..)`. Methods: - `fn new(shape: Shape) -> Self`: Along `shape` in Manim's 1 second, smooth, a tenth of it long. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/indicate_ii.rs#L127) ### ShowPassingFlashWithThinningStrokeWidth `n_segments` passing flashes over each other: the head is wide, the tail thins out behind it (Manim's `ShowPassingFlashWithThinningStrokeWidth`). The widest is the shape's stroke width (at least 2). | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape; its outline is what the effect draws (its fill colour is used if it has no stroke), the shape itself is not drawn. | | `n_segments` | `usize` | — | How many flashes are layered; more make a smoother taper (at least 2). | | `time_width` | `f32` | — | Length of the longest, thinnest flash as a fraction of the outline's length. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ShowPassingFlashWithThinningStrokeWidth::new(..)`. Methods: - `fn new(shape: Shape) -> Self`: Along `shape` in Manim's 1 second, smooth, 10 segments, a tenth of it long. - `fn segments(&self) -> Vec<(f32, f32)>`: `(stroke width, time width)` of every flash that draws something: Manim's widths from 0 to the widest against time widths from `time_width` to 0, without the ends. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/indicate_ii.rs#L172) ### Broadcast Rings that grow out of `point` to `radius` one after another while they fade (Manim's `Broadcast`). Each ring runs as a part of a `LaggedStart` of `n_mobs`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `point` | `(f32, f32)` | `(960.0, 540.0)` | Where the rings start, in pixels of the canvas. | | `radius` | `f32` | `270.0` | The radius the rings grow to. | | `color` | `Color` | `theme.colors.accent` | Colour of the rings. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the rings in pixels. | | `n_mobs` | `usize` | `5` | How many rings are sent out, one after another (at least 1). | | `initial_opacity` | `f32` | `1.0` | Opacity of a ring when it starts, 0 transparent to 1 opaque. | | `final_opacity` | `f32` | `0.0` | Opacity of a ring when it has reached `radius`. | | `initial_width` | `f32` | `0.0` | The width the rings start at. | | `lag_ratio` | `f32` | `0.2` | Fraction of a ring's time between the starts of two rings. | | `delay` | `f32` | `0.0` | Seconds after the container's start before it begins. | | `run_time` | `f32` | `3.0` | Seconds the whole broadcast takes. | | `rate_func` | `RateFunc` | `RateFunc::Smooth` | Easing of the animation over its run time. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/indicate_ii.rs#L235) ### AnimatedBoundary An outline that keeps drawing itself in one colour after another, the previous colour fading out under it (Manim's `AnimatedBoundary`). It never ends; put it in a `Sequence` to stop it. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape whose outline is traced; the shape itself is not drawn. | | `colors` | `Vec` | — | The colours it draws in, one after another; empty draws nothing. | | `max_stroke_width` | `f32` | — | Outline width in pixels of the colour being drawn; the previous colour thins from this to 0. | | `cycle_rate` | `f32` | — | Colours per second. | | `back_and_forth` | `bool` | — | Every other colour draws from the end back. | | `draw_rate_func` | `RateFunc` | — | Easing of each colour's drawing along the outline. | | `fade_rate_func` | `RateFunc` | — | Easing of the previous colour fading out. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | It has no theme defaults: build it with `AnimatedBoundary::new(..)`. Methods: - `fn new(shape: Shape, colors: Vec) -> Self`: Around `shape` in `colors`, Manim's defaults otherwise. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/indicate_ii.rs#L310) ### ApplyWave A wave runs across the shape from left to right and bends it by `amplitude` pixels in `direction` (Manim's `ApplyWave`). The shape is drawn as built before and after. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is bent by the wave; drawn as built before and after. | | `direction` | `(f32, f32)` | — | Canvas units, `y` down: `(0.0, -1.0)` is Manim's `UP`. | | `amplitude` | `f32` | — | How far points are nudged at the crest, in pixels of the canvas. | | `wave_func` | `RateFunc` | — | Easing that shapes each ripple of the wave. | | `time_width` | `f32` | — | The wave's length as a fraction of the shape's width. | | `ripples` | `usize` | — | Number of crests that pass across the shape (at least 1). | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the wave takes to cross the shape. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyWave::new(..)`. Methods: - `fn new(shape: Shape) -> Self`: Up by 27 pixels (Manim's 0.2 units), one ripple in 2 seconds. - `fn path_at(&self, p: f32) -> BezPath`: The path at progress `p`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/indicate_ii.rs#L405) ### Homotopy Moves every point of the shape through `homotopy(x, y, t)` while `t` runs from 0 to 1 (Manim's `Homotopy`). Points are in the units of `ui.space`, `y` up; before the start the shape is at `t = 0`, after the end at `t = 1`. The path is cut into about `HOMOTOPY_SEGMENTS` pieces first, so straight lines bend. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape whose points move; drawn unchanged before `delay`. | | `homotopy` | `fn(f64, f64, f64) -> (f64, f64)` | — | Maps a point `(x, y)` in units of `ui.space` (y up) and the progress `t` from 0 to 1 to its new position. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `Homotopy::new(..)`. Methods: - `fn new(shape: Shape, homotopy: fn(f64, f64, f64) -> (f64, f64)) -> Self`: Manim's 3 seconds, smooth. - `fn path_at(&self, space: Space, t: f32) -> BezPath`: The path at progress `t`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/homotopy.rs#L84) ### SmoothedVectorizedHomotopy `Homotopy` whose path gets smooth handles after every step (Manim's `SmoothedVectorizedHomotopy`), for maps that would leave corners. | Field | Type | Default | Description | | --- | --- | --- | --- | | `homotopy` | `Homotopy` | — | The homotopy to play; its path is smoothed after every step. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/homotopy.rs#L127) ### ComplexHomotopy `Homotopy` on complex numbers: `homotopy((re, im), t)` gives where the point `re + i im` is at `t` (Manim's `ComplexHomotopy`), in the units of `ui.space`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape whose points move; drawn unchanged before `delay`. | | `homotopy` | `fn((f64, f64), f64) -> (f64, f64)` | — | Maps the point `(re, im)` in units of `ui.space` and the progress `t` from 0 to 1 to its new position. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ComplexHomotopy::new(..)`. Methods: - `fn new(shape: Shape, homotopy: fn((f64, f64), f64) -> (f64, f64)) -> Self`: Manim's 3 seconds, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/homotopy.rs#L144) ### PhaseFlow Lets every point of the shape flow along the field `function(x, y) -> (dx, dy)` for `virtual_time` time units by the end (Manim's `PhaseFlow`), in the units of `ui.space`. The flow takes at most `PHASE_FLOW_STEPS` Runge-Kutta steps per point and frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape whose points move; drawn unchanged before `delay`. | | `function` | `fn(f64, f64) -> (f64, f64)` | — | The vector field: gives the velocity `(dx, dy)` at `(x, y)`, in units of `ui.space` per time unit. | | `virtual_time` | `f32` | — | Time units of flow reached by the end of the run (1.0 is one unit of the field's time). | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `PhaseFlow::new(..)`. Methods: - `fn new(shape: Shape, function: fn(f64, f64) -> (f64, f64)) -> Self`: One time unit in 1 second, linear, like Manim. - `fn flow(&self, x: f64, y: f64, time: f64) -> (f64, f64)`: Where the point `(x, y)` has flowed after `time` time units. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/homotopy.rs#L184) ### TracedPath The line a moving point has drawn so far (Manim's `TracedPath`). `point(t)` is where the point is `t` local seconds in, in canvas units; the line runs through it at every frame from `delay` (or the last `dissipating_time` seconds) up to now. The trail is sampled again every frame, at most `MAX_TRACE_SAMPLES` times, on a fixed grid of times so the old part does not shimmer. | Field | Type | Default | Description | | --- | --- | --- | --- | | `point` | `&dyn Fn(f32) -> (f32, f32)` | `&origin` | Where the point is `t` local seconds in, in pixels of the canvas. | | `stroke` | `Color` | `theme.colors.highlight` | Colour of the line. | | `stroke_width` | `f32` | `theme.stroke.regular` | Width of the line in pixels. | | `dissipating_time` | `Option` | `None` | Seconds of trail to keep; `None` keeps it all. | | `delay` | `f32` | `0.0` | Local second where the trail starts. | Methods: - `fn times(&self, now: f32, fps: usize) -> Vec`: The times sampled at `now` with `fps` frames per second: the start, every frame on the grid since, and `now`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/traced_path.rs#L23) ### TransformFromCopy A morph of `from` into `to` while `from` stays where it is (Manim's `TransformFromCopy`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `TransformFromCopy::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: From `from` to `to` in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L96) ### ClockwiseTransform A morph whose points travel clockwise on arcs of `path_arc` radians (Manim's `ClockwiseTransform`, a half turn). | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `path_arc` | `f32` | — | Negative turns clockwise. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ClockwiseTransform::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: From `from` to `to` on half turns in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L141) ### CounterclockwiseTransform A morph whose points travel counterclockwise on arcs of `path_arc` radians (Manim's `CounterclockwiseTransform`, a half turn). | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `path_arc` | `f32` | — | Positive turns counterclockwise. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `CounterclockwiseTransform::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: From `from` to `to` on half turns in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L181) ### CyclicReplace Every shape moves on an arc to where the next one is, the last to the first (Manim's `CyclicReplace`, arcs of a quarter turn). The shapes keep their own paths. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shapes` | `Vec` | — | The shapes; each moves to where the next one is, the last to the first. | | `path_arc` | `f32` | — | Radians of the arc each shape travels on; positive turns counterclockwise, 0 is a straight line. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `CyclicReplace::new(..)`. Methods: - `fn new(shapes: Vec) -> Self`: Manim's quarter turns in 1 second, smooth. - `fn offset(&self, i: usize, p: f32) -> Vec2`: How far shape `i` has moved at progress `p`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L221) ### Swap Two shapes trade places on arcs (Manim's `Swap`, a `CyclicReplace` of two). | Field | Type | Default | Description | | --- | --- | --- | --- | | `first` | `Shape` | — | The shape that moves to where `second` is. | | `second` | `Shape` | — | The shape that moves to where `first` is. | | `path_arc` | `f32` | — | Radians of the arc each shape travels on; positive turns counterclockwise, 0 is a straight line. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `Swap::new(..)`. Methods: - `fn new(first: Shape, second: Shape) -> Self`: Manim's quarter turns in 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L278) ### FadeTransform `from` fades out while it moves onto `to`, and `to` fades in from where `from` was (Manim's `FadeTransform`). With `stretch` each takes the other's box exactly; without, its height. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `to` | `Shape` | — | The shape it ends as. | | `stretch` | `bool` | — | `true` stretches each shape to the other's box exactly; `false` scales by the height only. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `FadeTransform::new(..)`. Methods: - `fn new(from: Shape, to: Shape) -> Self`: Stretched, in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L366) ### FadeTransformPieces `FadeTransform` piece by piece (Manim's `FadeTransformPieces`): piece `i` of `from` goes to piece `i` of `to`. Manim splits pieces until the counts match; here the pieces over the shorter list fade out or in where they are. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Vec` | — | The pieces it starts as; piece `i` goes to piece `i` of `to`. | | `to` | `Vec` | — | The pieces it ends as; extra pieces beyond the length of `from` fade in where they are. | | `stretch` | `bool` | — | `true` stretches each shape to the other's box exactly; `false` scales by the height only. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `FadeTransformPieces::new(..)`. Methods: - `fn new(from: Vec, to: Vec) -> Self`: Stretched, in Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L413) ### TransformMatchingShapes Pieces of `from` that have the same shape as pieces of `to` move there, the others fade out while the rest of `to` fades in (Manim's `TransformMatchingShapes`). Shapes match by `shape_key`, as in Manim, wherever they are and whatever their size. Manim transforms all pieces of one key as a group; here they pair in order. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Vec` | — | The pieces it starts as; each moves to the piece of `to` with the same shape. | | `to` | `Vec` | — | The pieces it ends as; those with no match in `from` fade in. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `TransformMatchingShapes::new(..)`. Methods: - `fn new(from: Vec, to: Vec) -> Self`: In Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L528) ### TransformAnimations Runs `from_anim` on `from` and `to_anim` on `to` at once while one shape morphs into the other (Manim's `TransformAnimations`): the shape and its look go from the first animation's to the second's over `run_time`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `Shape` | — | The shape it starts as; drawn until `delay` has passed. | | `from_anim` | `EnterAnim` | — | The enter animation applied to `from` while it morphs. | | `to` | `Shape` | — | The shape it ends as. | | `to_anim` | `EnterAnim` | — | The enter animation applied to `to` while it morphs. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `TransformAnimations::new(..)`. Methods: - `fn new(from: Shape, from_anim: EnterAnim, to: Shape, to_anim: EnterAnim) -> Self`: In Manim's 1 second, smooth; time the two animations to run over the same second. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/transform_ii.rs#L616) ### ApplyMatrix Moves every point of the shape by the 2×2 `matrix` about `about_point` (Manim's `ApplyMatrix`, about the origin of `ui.space` in 3 seconds). The matrix and the point are in the units of `ui.space`, `y` up: `[[0, -1], [1, 0]]` turns a quarter counterclockwise. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is transformed; drawn unchanged until `delay` has passed. | | `matrix` | `[[f64; 2]; 2]` | — | The 2x2 matrix, row by row, applied to each point's offset from `about_point`, in units of `ui.space` with y up. | | `about_point` | `Option<(f64, f64)>` | — | In units; `None` is the origin of the space. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyMatrix::new(..)`. Methods: - `fn new(shape: Shape, matrix: [[f64; 2]; 2]) -> Self`: About the origin in Manim's 3 seconds, smooth. - `fn map(&self, space: Space, p: Point) -> Point`: Where the pixel `p` ends up. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/apply.rs#L31) ### ApplyPointwiseFunction Moves every point of the shape to `function(x, y)` (Manim's `ApplyPointwiseFunction`, 3 seconds), in the units of `ui.space`, `y` up. The path is cut into small pieces first like a `super::Homotopy`, so straight lines bend. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is transformed; drawn unchanged until `delay` has passed. | | `function` | `fn(f64, f64) -> (f64, f64)` | — | Maps a point `(x, y)` in units of `ui.space` (y up) to where it ends up. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyPointwiseFunction::new(..)`. Methods: - `fn new(shape: Shape, function: fn(f64, f64) -> (f64, f64)) -> Self`: Manim's 3 seconds, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/apply.rs#L89) ### ApplyPointwiseFunctionToCenter Moves the whole shape so its center goes to `function(center)` (Manim's `ApplyPointwiseFunctionToCenter`, 3 seconds), in the units of `ui.space`. The shape keeps its form. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is transformed; drawn unchanged until `delay` has passed. | | `function` | `fn(f64, f64) -> (f64, f64)` | — | Maps the shape's center `(x, y)` in units of `ui.space` (y up) to where the center ends up. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyPointwiseFunctionToCenter::new(..)`. Methods: - `fn new(shape: Shape, function: fn(f64, f64) -> (f64, f64)) -> Self`: Manim's 3 seconds, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/apply.rs#L222) ### ApplyComplexFunction Moves every point of the shape to `function(re, im)` of the complex number at it (Manim's `ApplyComplexFunction`, 1 second), in the units of `ui.space`. As in Manim the points travel on arcs as wide as the angle of `function(1)`, so a rotation turns. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is transformed; drawn unchanged until `delay` has passed. | | `function` | `fn((f64, f64)) -> (f64, f64)` | — | Maps the complex number `(re, im)` at a point, in units of `ui.space`, to where the point ends up. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyComplexFunction::new(..)`. Methods: - `fn new(shape: Shape, function: fn((f64, f64)) -> (f64, f64)) -> Self`: Manim's 1 second, smooth. - `fn path_arc(&self) -> f64`: The angle of `function(1)`: how far the points turn on their way. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/apply.rs#L131) ### ApplyFunction Morphs the shape into `function(shape)` (Manim's `ApplyFunction`, a `Transform` in 1 second). | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape that is transformed; drawn unchanged until `delay` has passed. | | `function` | `fn(Shape) -> Shape` | — | Builds the shape it morphs into from a copy of `shape`. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ApplyFunction::new(..)`. Methods: - `fn new(shape: Shape, function: fn(Shape) -> Shape) -> Self`: Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/apply.rs#L179) ### LaggedStart Starts each child `lag_ratio * run_time` seconds after the one before it (Manim's `LaggedStart`). Each child's time starts at its own start, so its enter animations run then; every child stays until the parent ends. | Field | Type | Default | Description | | --- | --- | --- | --- | | `lag_ratio` | `f32` | — | Fraction of `run_time` between two starts. | | `run_time` | `f32` | — | The run time of one child's animation. | | `children` | `Vec` | — | One entry per child; each is a closure taking the `Ui`, started in order. | It has no theme defaults: build it with `LaggedStart::map(..)`. Methods: - `fn map(items: impl IntoIterator, f: impl Fn(&mut Ui, &T) -> Svgr) -> Self`: One child per item, drawn by `f` (Manim's `LaggedStartMap`), with Manim's lag ratio of 0.05 and children that run 1 second. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/group.rs#L14) ### AnimationGroup Every child at once (Manim's `AnimationGroup` with no lag). | Field | Type | Default | Description | | --- | --- | --- | --- | | `children` | `Vec` | — | The children, all started together; each is a closure taking the `Ui`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/group.rs#L83) ### Succession Each child starts when the one before it has run `run_time` seconds (Manim's `Succession`, a `LaggedStart` with `lag_ratio` 1). | Field | Type | Default | Description | | --- | --- | --- | --- | | `run_time` | `f32` | — | Seconds each child runs before the next one starts. | | `children` | `Vec` | — | The children in order; each is a closure taking the `Ui`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/group.rs#L97) ### ChangeSpeed Plays its children faster or slower over the first `run_time` seconds (Manim's `ChangeSpeed`). `speed` is a curve of `(fraction of run_time, speed)` points, straight between them and flat beyond them; no points is speed 1. The children see their time `t` as `run_time · S(t / run_time) / S(1)`, `S` the area under the curve, so they start and end where they would anyway and only the pace in between changes. After `run_time` their time is the real one again. | Field | Type | Default | Description | | --- | --- | --- | --- | | `run_time` | `f32` | — | Seconds over which the pace changes; after that the children run at real time. | | `speed` | `Vec<(f32, f32)>` | — | Curve of `(fraction of run_time, speed)` points, straight between them and flat beyond; empty for speed 1. | | `children` | `Children` | — | What it plays at the changed pace (a closure taking the `Ui`). | Methods: - `fn remap(&self, t: f32) -> f32`: The time the children see at local second `t`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/speed.rs#L19) ### ChangeDecimalToValue Counts a [`DecimalNumber`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) from the value of its tracker to `target` (Manim's `ChangeDecimalToValue`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `number` | [`DecimalNumber`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) | — | The number to change; its current value is the start. | | `target` | `f32` | — | The value it counts to by the end. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ChangeDecimalToValue::new(..)`. Methods: - `fn new(number: DecimalNumber, target: f32) -> Self`: In Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/speed.rs#L104) ### ChangingDecimal Shows `number_update_func(alpha)` in a [`DecimalNumber`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) while the animation's progress runs from 0 to 1 (Manim's `ChangingDecimal`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `number` | [`DecimalNumber`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) | — | The number to show; its `value` is replaced while the animation runs. | | `number_update_func` | `fn(f32) -> f32` | — | Gives the value to show from the progress, 0 to 1. | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the animation takes. | | `rate_func` | `RateFunc` | — | Easing of the animation over its run time. | It has no theme defaults: build it with `ChangingDecimal::new(..)`. Methods: - `fn new(number: DecimalNumber, number_update_func: fn(f32) -> f32) -> Self`: In Manim's 1 second, smooth. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/animate/speed.rs#L145) ## See also - [Text](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) --- # Paths > fframes_kit::paths has Remotion's @remotion/paths in snake_case over kurbo::BezPath: get_length, get_point_at_length, get_tangent_at_length, evolve_path… Page: https://fframes-kit.pages.dev/docs/layout-and-shapes/paths/ `fframes_kit::paths` has Remotion's `@remotion/paths` in snake_case over `kurbo::BezPath`: `get_length`, `get_point_at_length`, `get_tangent_at_length`, `evolve_path`, `interpolate_path`, `translate_path`, `scale_path`, `reverse_path`, `cut_path`, `warp_path`, `normalize_path`, `get_bounding_box`, `get_subpaths`, `extend_view_box`, `center_path`, `parse_path`, `serialize`, `reset_path`, `reduce_instructions`, and `align_points(a, b, n)` (the same number of subpaths and cubic segments in both, turned to start at the nearest points), which `Morph` runs on. ```rust use fframes_kit::prelude::*; let square = paths::parse_path("M0 0 L10 0 L10 10 L0 10 Z").unwrap(); assert_eq!(paths::get_length(&square), 40.0); assert_eq!(paths::get_length(&paths::cut_path(&square, 20.0)), 20.0); let half = paths::interpolate_path(0.5, &square, &paths::scale_path(&square, 2.0, 2.0)); assert!((paths::get_bounding_box(&half).width() - 15.0).abs() < 1e-9); ``` --- # Transitions > TransitionSeries is Remotion's : sequences one after another, with a transition between two of them. Page: https://fframes-kit.pages.dev/docs/motion/transitions/ `TransitionSeries` is Remotion's ``: sequences one after another, with a transition between two of them. A transition overlaps its two sequences by its duration, so both show during it and the series is shorter than its sequences by the sum of the transitions. The entering sequence is drawn on top. Gallery scenes: `TransitionSeries`, `TransitionProgress`, `BeforeOverlap`, `SceneOverlap`. Gallery clip of `TransitionSeries`: https://fframes-kit.pages.dev/media/scenes/TransitionSeries-f2a72933.mp4 Gallery clip of `TransitionProgress`: https://fframes-kit.pages.dev/media/scenes/TransitionProgress-ef838b6c.mp4 Gallery clip of `BeforeOverlap`: https://fframes-kit.pages.dev/media/scenes/BeforeOverlap-040ddd16.mp4 Gallery clip of `SceneOverlap`: https://fframes-kit.pages.dev/media/scenes/SceneOverlap-58c304a3.mp4 | Item or component | Props | What it does | | --- | --- | --- | | `TransitionSeries` | `key: Option<&str>`, `items` | the series; `key` keeps clip ids apart when two series share a frame | | `TransitionSeries::sequence(seconds, children)` | | a sequence; inside, time starts at 0 and `ui.duration` is its length | | `TransitionSeries::transition(timing, presentation)` | | a transition between the sequences on either side | | `Fade` | `should_fade_out_exiting_scene` (false) | the entering sequence fades in over the exiting one (Remotion `fade()`) | | `Slide` | `direction` (`FromLeft`), `size` (1920 x 1080) | the entering sequence pushes the exiting one out (`slide()`) | | `Wipe` | `direction` (`FromLeft`), `size` | an edge moves across and uncovers the entering sequence (`wipe()`) | | `Flip` | `direction` (`FromLeft`), `size` | the exiting sequence turns half a turn and the entering one is on its back (`flip()`); drawn flat as a scale, so `direction` only picks the axis and there is no perspective | | `ClockWipe` | `size` | a hand sweeps clockwise from twelve o'clock and uncovers the entering sequence (`clockWipe()`) | | `Iris` | `size` | the entering sequence shows through a circle that opens from the middle (`iris()`) | | `CircleWipe` | `center` (0.5, 0.5, a share of `size`), `color` (`colors.title`), `size` | a disc of `color` grows from `center` over the exiting sequence, then the entering one opens from the same point | | `SlantWipe` | `color` (`colors.background`), `edge` (`colors.highlight`), `back` (`colors.accent`), `skew` (320), `size` | a slanted panel with a stripe in front and behind sweeps across, covers everything at the middle and leaves the entering sequence behind it | | `NoTransition` | | a hard cut at the middle of the overlap (`none()`); the series keeps the overlap, so the sequences can animate it themselves with `ui.transition_progress()` | | `PushCut` | `cut_progress` (5/11), `outgoing_scale` (1.04), `incoming_start_scale` (1.04), `incoming_end_scale` (1.07), `origin` (0.5, 0.5), `flash_color` (#f5f2ed), `flash_opacity` (0.2), `flash_frames` (2), `size` | a hard editorial cut with a punch-in on both sides and a short flash at the edit (`pushCut()`); use a linear timing | | `ZoomInOut` | `scale` (1.5), `size` | the exiting sequence zooms in and cross-fades into the entering one, which zooms back out (`zoomInOut()`); `scale` is the kit's, Remotion has no options | | `DreamyZoom` | `rotation` (6°), `scale` (1.2), `flash_color` (white), `blur` (12), `size` | both sides zoom, turn and blur through a flash at the middle (`dreamyZoom()`) | | `LinearBlur` | `intensity` (0.1, a share of the width), `axes` (1, 1), `size` | the entering sequence fades in while both blur, most at the middle (`linearBlur()`); `axes` weighs the blur along x and y, a slanted direction is not available | | `BlurSlide` | `direction` (`FromLeft`), `blur` (0.5, a share of the size), `size` | both sides pan like a fast camera move with a motion blur along it (`blurSlide()`) | | `CrossZoom` | `strength` (0.4), `size` | both sides zoom about a centre that moves across and blur, cross-fading at the middle (`crossZoom()`); an approximation: the blur is even, not along rays | | `Dissolve` | `line_width` (0.1), `spread_color` (#ff0000), `hot_color` (#e6e633), `pow` (5), `intensity` (1), `seed`, `frequency` (0.004), `size` | the exiting sequence burns away with a glowing edge (`dissolve()`); the burn follows seeded `feTurbulence` noise, not the luminance of the pixels as in Remotion | | `FilmBurn` | `seed` (2.31), `blur` (6), `size` | a burning film overlay flares over a blurred cross-fade (`filmBurn()`); an approximation of the GL original from a radial gradient and noise | | `SwapCards` | `reflection` (0.4), `perspective` (0.2), `depth` (3), `floor` (`colors.background`), `size` | the two sequences trade places like cards on a floor, with reflections (`swap()`); a 2D approximation of the 3D original. Manim's `Swap` is another thing | | `Timing::Linear` | `duration`, `easing: RateFunc` | Remotion's `linearTiming`; the theme default is `motion.duration.slow` with `motion.easing.standard` | | `Timing::Spring` | `mass`, `stiffness`, `damping`, `duration: Option` | `springTiming`: lasts until the spring settles, or is squeezed into `duration` | | `SceneFade` | `fade_in`, `fade_out`, `rate_func`, `children` | fades at both edges of the enclosing sequence or scene; `SceneFade::new(ui, children)` takes the theme's `motion.duration.fast` | Gallery scenes: `Fade`, `Slide`, `Wipe`, `Flip`, `ClockWipe`, `Iris`, `CircleWipe`, `SlantWipe`, `NoTransition`, `PushCut`, `ZoomInOut`, `DreamyZoom`, `LinearBlur`, `BlurSlide`, `CrossZoom`, `Dissolve`, `FilmBurn`, `SwapCards`, `SceneFade`. Gallery clip of `Fade`: https://fframes-kit.pages.dev/media/scenes/Fade-c5735273.mp4 Gallery clip of `Slide`: https://fframes-kit.pages.dev/media/scenes/Slide-5030082e.mp4 Gallery clip of `Wipe`: https://fframes-kit.pages.dev/media/scenes/Wipe-3d308f57.mp4 Gallery clip of `Flip`: https://fframes-kit.pages.dev/media/scenes/Flip-8a09102b.mp4 Gallery clip of `ClockWipe`: https://fframes-kit.pages.dev/media/scenes/ClockWipe-255f78db.mp4 Gallery clip of `Iris`: https://fframes-kit.pages.dev/media/scenes/Iris-f7ec117b.mp4 Gallery clip of `CircleWipe`: https://fframes-kit.pages.dev/media/scenes/CircleWipe-ca577091.mp4 Gallery clip of `SlantWipe`: https://fframes-kit.pages.dev/media/scenes/SlantWipe-30a4f862.mp4 Gallery clip of `NoTransition`: https://fframes-kit.pages.dev/media/scenes/NoTransition-cf763597.mp4 Gallery clip of `PushCut`: https://fframes-kit.pages.dev/media/scenes/PushCut-da22a129.mp4 Gallery clip of `ZoomInOut`: https://fframes-kit.pages.dev/media/scenes/ZoomInOut-abb791b6.mp4 Gallery clip of `DreamyZoom`: https://fframes-kit.pages.dev/media/scenes/DreamyZoom-db8cae46.mp4 Gallery clip of `LinearBlur`: https://fframes-kit.pages.dev/media/scenes/LinearBlur-17119b0d.mp4 Gallery clip of `BlurSlide`: https://fframes-kit.pages.dev/media/scenes/BlurSlide-fe140596.mp4 Gallery clip of `CrossZoom`: https://fframes-kit.pages.dev/media/scenes/CrossZoom-97d82046.mp4 Gallery clip of `Dissolve`: https://fframes-kit.pages.dev/media/scenes/Dissolve-dec92f12.mp4 Gallery clip of `FilmBurn`: https://fframes-kit.pages.dev/media/scenes/FilmBurn-dc644700.mp4 Gallery clip of `SwapCards`: https://fframes-kit.pages.dev/media/scenes/SwapCards-a9fa00b7.mp4 Gallery clip of `SceneFade`: https://fframes-kit.pages.dev/media/scenes/SceneFade-43efa230.mp4 `direction` is `FromLeft`, `FromRight`, `FromTop` or `FromBottom`. `size` is the canvas in view-box units. Change it when the view box is not 1920 x 1080. A transition that is longer than a sequence next to it is shortened to that sequence. A transition that is first, last or next to another transition is skipped. `inspect` warns about both. ```rust use fframes_kit::prelude::*; fn panel<'a>(ui: &mut Ui<'a, '_, '_>, background: Color) -> Svgr<'a> { ui.show(Fill { background: Some(background), children: children(|_| Svgr::empty()) }) } fn fading_scene<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(SceneFade::new(ui, children(|ui| ui.show(Circle { ..ui.defaults() })))) } /// Two 1.5 s panels with a 0.8 s wipe: 2.2 s in all. fn wipe<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let c = ui.theme.colors; let timing: Timing = ui.defaults(); ui.show(TransitionSeries { key: None, items: vec![ TransitionSeries::sequence(1.5, children(move |ui| panel(ui, c.surface))), TransitionSeries::transition(timing, Wipe { direction: Direction::FromRight, ..Wipe::default() }), TransitionSeries::sequence(1.5, children(move |ui| panel(ui, c.accent))), ], }) } ``` The blurring presentations (`DreamyZoom`, `LinearBlur`, `BlurSlide`, `CrossZoom`, `FilmBurn`) run an `feGaussianBlur` over each side during the transition, up to about 60 ms more a frame than a fade on the CPU backend at 1080p. They blur only what is on the canvas (`size`), so the CPU and Skia backends draw the same, and they keep the alpha of the blurred side, so the canvas does not darken at its edges. They expect sequences that cover the canvas, as a `Fill` does: a translucent sequence turns opaque where the blur leaves it a quarter opaque. Not yet available, because they need pixel shaders: `crosswarp`, `ripple`, `bookFlip`, `cube` and `custom-html-in-canvas`. Inside a sequence, `ui.transition_progress()` is Remotion's `useTransitionProgress()`: `entering` runs from 0 to 1 while the sequence comes in and is 1 otherwise, `exiting` runs from 0 to 1 while it goes away and is 0 otherwise, and `in_series` is false outside a series (1 and 0 then). `ui.transition_seconds(side)` is the length of a running transition. The kit cannot change `fn audio`, so it gives the times to fade a sequence's sound with instead: `audio_fades(&lengths, fps)` (or `series.audio_fades(fps)`) returns, per sequence, where it starts and ends and how long its fades are, in seconds from the series start. `fade.apply(track)` sets them on an `AudioTrack`. ```rust use fframes::{AudioMap, AudioTimestamp::Second, AudioTrack}; use fframes_kit::prelude::*; const SERIES_START: f32 = 2.0; fn audio() -> AudioMap<'static> { let fades = audio_fades( &[ SeriesLength::Sequence(4.0), SeriesLength::Transition(Timing::default()), SeriesLength::Sequence(4.0), ], 30, ); let track = |file, fade: &AudioFade| { let range = Second(SERIES_START + fade.start)..Second(SERIES_START + fade.end); fade.apply(AudioTrack::new(file, range)) }; AudioMap::from([track("one.wav", &fades[0]), track("two.wav", &fades[1])]) } /// A bar that fills while the sequence comes in. fn bar<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let width = 1200.0 * ui.transition_progress().entering; svgr!() } ``` A presentation of your own implements `Presentation`. `apply` gets the progress, 0 when the transition starts and 1 when it ends, for both sides. A spring may overshoot 1. `side` is `Side::Entering` or `Side::Exiting`, and `id` is unique to the transition in the frame, for clip paths, masks and gradients. ```rust use fframes_kit::prelude::*; /// The entering sequence grows from the center of the canvas. struct Zoom; impl Presentation for Zoom { fn apply<'a>(&self, progress: f32, side: Side, content: Svgr<'a>, _ui: &Ui<'a, '_, '_>, _id: &str) -> Svgr<'a> { if side == Side::Exiting { return content; } let s = motion::lerp(0.6, 1.0, progress); let shift = Transform::translate(960.0 * (1.0 - s), 540.0 * (1.0 - s)); svgr!( {content} ) } } let spring = Timing::Spring { mass: 1.0, stiffness: 100.0, damping: 10.0, duration: None }; assert!(spring.duration() > 0.3); assert_eq!(spring.progress(0.0, spring.duration()), 0.0); assert_eq!(spring.progress(spring.duration(), spring.duration()), 1.0); ``` Core scenes can transition without a series through their `Overlap`. In `render_frame`, wrap the content with `scene_transition(ui, &presentation, timing, side, key, content)`, using a `Ui` made with `Ui::for_scene`. `Side::Entering` runs over the scene's first `timing.duration()` seconds, so give the scene `Overlap::Previous` of the same length. `Side::Exiting` runs over the last seconds, and goes on the scene before it. ```rust use fframes_kit::prelude::*; #[derive(Debug)] struct Second { theme: Theme, } const SLIDE: Timing = Timing::Linear { duration: 0.5, easing: RateFunc::Smooth }; impl Scene for Second { fn duration(&self) -> Duration<'_> { Duration::Seconds(3.0) } fn overlap(&self) -> Overlap { Overlap::Previous(0.5) // starts under the end of the scene before } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { let mut ui = Ui::for_scene(frame, ctx, &self.theme, self); let content = ui.show(Fill { background: Some(ui.theme.colors.accent), children: children(|_| Svgr::empty()) }); let slide = Slide { direction: Direction::FromRight, ..Slide::default() }; scene_transition(&ui, &slide, SLIDE, Side::Entering, Some("second"), content) } } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### TransitionSeries Sequences one after another with transitions between them (Remotion's ``). A transition overlaps its two sequences by its duration, so the series is shorter than its sequences by the sum of the transitions. A transition longer than one of its sequences is shortened to it; a transition that is first, last, or next to another transition is skipped. `inspect` warns about both. | Field | Type | Default | Description | | --- | --- | --- | --- | | `key` | `Option<&str>` | — | Makes the ids of the transitions' clip paths unique when two series share a frame. | | `items` | `Vec` | — | Sequences and the transitions between them, in play order. | Methods: - `fn sequence(seconds: f32, children: Children) -> TransitionItem`: A sequence item. - `fn lengths(&self) -> Vec`: The lengths of the items, for `audio_fades`. - `fn audio_fades(&self, fps: usize) -> Vec`: Where each sequence plays and how long its audio fades at `fps`, see `audio_fades`. - `fn transition(timing: Timing, presentation: impl Presentation) -> TransitionItem`: A transition item. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/transitions/series.rs#L33) ### SceneFade Fades its children in at the start and out at the end of the enclosing `Sequence` or scene (`ui.duration`). Without a known end only the fade in runs. A 0 length skips that fade. | Field | Type | Default | Description | | --- | --- | --- | --- | | `fade_in` | `f32` | `theme.motion.duration.fast` | Seconds of the fade in. | | `fade_out` | `f32` | `theme.motion.duration.fast` | Seconds of the fade out, ending with `ui.duration`. | | `rate_func` | `RateFunc` | `theme.motion.easing.standard` | Easing of the fades over their lengths. | | `children` | `Children` | `no_children()` | What it fades; a closure taking the `Ui`. | Methods: - `fn new(ui: &Ui, children: Children) -> Self`: Fades of the theme's `motion.duration.fast` with its standard easing. - `fn opacity(&self, t: f32, length: Option) -> f32`: Opacity at `t` seconds of a container that lasts `length` seconds. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/transitions/scene_fade.rs#L16) --- # Effects > Gallery scenes: Trail, CameraMotionBlur, LightLeak, Blur, Noise, ShaderNoise. Page: https://fframes-kit.pages.dev/docs/motion/effects/ | Component | Props | What it does | | --- | --- | --- | | `Trail` | `samples` (6), `spacing` (0.05 s), `opacity` (0.5), `children` | the children at earlier times behind themselves, each copy fainter (Remotion's ``) | | `CameraMotionBlur` | `shutter_angle` (180°), `samples` (10), `children` | the children averaged over the time the shutter is open before the frame (``) | | `LightLeak` | `seed`, `hue_shift` (degrees), `length` (`None`: the container's), `opacity` (0.85), `size`, `key` | warm blobs of light flare up and fade out over `length` seconds, in screen blending (``) | | `Blur` | `radius: Anim` (8), `key`, `children` | the children blurred by a Gaussian; animate `radius` for a focus pull | | `Noise` | `seed`, `opacity` (0.12), `frequency` (0.8), `octaves` (2), `animated` (true), `size`, `key` | film grain from `feTurbulence`; `animated` moves the seed every frame | | `ShaderNoise` | `seed`, `opacity`, `scale` (2 px), `animated`, `size` | the grain as an SkSL shader on the GPU (feature `skia`); the CPU backend draws nothing and the editor a placeholder | Gallery scenes: `Trail`, `CameraMotionBlur`, `LightLeak`, `Blur`, `Noise`, `ShaderNoise`. Gallery clip of `Trail`: https://fframes-kit.pages.dev/media/scenes/Trail-d4d81050.mp4 Gallery clip of `CameraMotionBlur`: https://fframes-kit.pages.dev/media/scenes/CameraMotionBlur-99914449.mp4 Gallery clip of `LightLeak`: https://fframes-kit.pages.dev/media/scenes/LightLeak-604ba946.mp4 Gallery clip of `Blur`: https://fframes-kit.pages.dev/media/scenes/Blur-b4f85c58.mp4 Gallery clip of `Noise`: https://fframes-kit.pages.dev/media/scenes/Noise-bdf404ff.mp4 Gallery clip of `ShaderNoise`: https://fframes-kit.pages.dev/media/scenes/ShaderNoise-a59dcd5e.mp4 The `ShaderNoise` gallery clip uses Skia; the ordinary CPU renderer cannot display it. `Trail` and `CameraMotionBlur` render their children once per copy, at most `effects::MAX_SAMPLES` (16) times a frame. The bench card behind an eight-copy `Trail` (`BenchTrail` in the gallery) renders its five seconds in about 1.8 times the time of `BenchKit` on the CPU backend. The copies run at a finer frame rate than the video's, so the children should count in local seconds (`ui.seconds()`, `ui.frames()`); `frame.global_index` keeps the video frame. Before the local start there is nothing to repeat. A clip path, gradient or filter inside the children has the same id in every copy, so all copies draw one copy's definition: fine while it stays the same, wrong for one that animates. `key` names the SVG ids when two of the same effect share a frame. The glow and the drop shadow are the `Glow` and `DropShadow` filter definitions above. A ball with a trail over a light leak, and a title that comes into focus: ```rust use fframes_kit::prelude::*; fn ball<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let x = 200.0 + 600.0 * motion::clamp01(ui.seconds() / 2.0); svgr!() } fn shot<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let leak = ui.show(LightLeak { seed: 3, length: Some(3.0), ..ui.defaults() }); let grain = ui.show(Noise { opacity: 0.12, ..ui.defaults() }); let trail = ui.show(Trail { samples: 8, children: children(ball), ..ui.defaults() }); let focus = motion::clamp01(ui.seconds() - 0.5); let title = ui.show(Blur { radius: Anim::Fixed(16.0 * (1.0 - focus)), key: Some("title"), children: children(|ui| ui.show(HudLabel { text: "Launch".into(), x: 200.0, y: 300.0, ..ui.defaults() })), }); svgr!({trail}{leak}{title}{grain}) } let blur = CameraMotionBlur { samples: 4, ..CameraMotionBlur::themed(&Theme::default()) }; assert_eq!(blur.offsets(30).len(), 4); fn shader_grain<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(ShaderNoise { opacity: 0.45, scale: 6.0, ..ui.defaults() }) } ``` ### Colour effects Each colour effect is a wrapper, `Effect { children, ..ui.defaults() }`, drawn through one SVG filter in sRGB, so the CPU and Skia backends agree. The main amount is an `Anim`; to drive it from time, compute it from `ui.seconds()` and pass `Anim::Fixed`. `key` names the filter when two effects of one kind share a frame. Gallery scenes: `ColorEffects`, `Brightness`, `Contrast`, `Saturation`, `HueRotate`, `Grayscale`, `Invert`, `Tint`, `Duotone`, `Levels`, `Curves`, `Exposure`, `ColorCorrection`, `WhiteBalance`, `Vibrance`, `ShadowsHighlights`, `ColorKey`, `ThermalVision`, `Lut`, `ColorStack`. Gallery clip of `ColorEffects`: https://fframes-kit.pages.dev/media/scenes/ColorEffects-6561eb0a.mp4 Gallery clip of `Brightness`: https://fframes-kit.pages.dev/media/scenes/Brightness-4532e323.mp4 Gallery clip of `Contrast`: https://fframes-kit.pages.dev/media/scenes/Contrast-62d743fb.mp4 Gallery clip of `Saturation`: https://fframes-kit.pages.dev/media/scenes/Saturation-67eeef6c.mp4 Gallery clip of `HueRotate`: https://fframes-kit.pages.dev/media/scenes/HueRotate-79a4e79a.mp4 Gallery clip of `Grayscale`: https://fframes-kit.pages.dev/media/scenes/Grayscale-581c84c3.mp4 Gallery clip of `Invert`: https://fframes-kit.pages.dev/media/scenes/Invert-5172e656.mp4 Gallery clip of `Tint`: https://fframes-kit.pages.dev/media/scenes/Tint-a28c5952.mp4 Gallery clip of `Duotone`: https://fframes-kit.pages.dev/media/scenes/Duotone-7cc3ae47.mp4 Gallery clip of `Levels`: https://fframes-kit.pages.dev/media/scenes/Levels-c8d31b3e.mp4 Gallery clip of `Curves`: https://fframes-kit.pages.dev/media/scenes/Curves-945747a5.mp4 Gallery clip of `Exposure`: https://fframes-kit.pages.dev/media/scenes/Exposure-d9ee5678.mp4 Gallery clip of `ColorCorrection`: https://fframes-kit.pages.dev/media/scenes/ColorCorrection-178547cc.mp4 Gallery clip of `WhiteBalance`: https://fframes-kit.pages.dev/media/scenes/WhiteBalance-7d9b1ad4.mp4 Gallery clip of `Vibrance`: https://fframes-kit.pages.dev/media/scenes/Vibrance-75b77500.mp4 Gallery clip of `ShadowsHighlights`: https://fframes-kit.pages.dev/media/scenes/ShadowsHighlights-1db2af54.mp4 Gallery clip of `ColorKey`: https://fframes-kit.pages.dev/media/scenes/ColorKey-ebc3b53d.mp4 Gallery clip of `ThermalVision`: https://fframes-kit.pages.dev/media/scenes/ThermalVision-3b3e2df4.mp4 Gallery clip of `Lut`: https://fframes-kit.pages.dev/media/scenes/Lut-a71bc74f.mp4 Gallery clip of `ColorStack`: https://fframes-kit.pages.dev/media/scenes/ColorStack-32367096.mp4 | Component | Props (defaults) | What it does | | --- | --- | --- | | `Brightness` | `amount` (1.3) | CSS `brightness()`: the colours times `amount` | | `Contrast` | `amount` (1.4) | CSS `contrast()` about mid grey | | `Saturation` | `amount` (1.6) | SVG `saturate`: 0 grey, 1 unchanged | | `HueRotate` | `degrees` (90) | the hues turned round the colour wheel (Remotion's hue) | | `Grayscale` | `amount` (1) | CSS `grayscale()` | | `Invert` | `amount` (1) | CSS `invert()` | | `Tint` | `color` (accent), `amount` (0.8) | the picture in shades of `color` | | `Duotone` | `dark` (background), `light` (accent), `amount` (1) | dark tones `dark`, light tones `light` | | `Exposure` | `stops` (0.5) | every stop doubles the light | | `WhiteBalance` | `temperature` (0.5, -1 cold to 1 warm), `tint` (0, -1 green to 1 magenta) | gains of up to a fifth on red, green and blue | | `Vibrance` | `amount` (0.8) | half as strong as `Saturation`; approximate, a matrix cannot spare colours that are already saturated | | `ColorCorrection` | `brightness` (1), `contrast` (1.1), `saturation` (1.2), `hue` (0) | the four in that order, one matrix | | `ColorStack` | `matrices: Vec` | several matrices composed into one `feColorMatrix`: a stack costs one filter | | `Levels` | `input` (0.08, 0.92), `gamma` (1.2), `output` (0, 1) | Photoshop's levels on every channel | | `Curves` | `points` (an S curve), `red`, `green`, `blue` (`None`) | a smooth monotone curve through the points, then per channel | | `Lut` | `red`, `green`, `blue` (lists of outputs) | a 1D table per channel; approximate, a `.cube` LUT maps colours in 3D | | `ShadowsHighlights` | `shadows` (0.6), `highlights` (0.3) | lifts dark tones, pulls light ones down; approximate, a curve on the pixel's own value | | `ThermalVision` | `palette` (`THERMAL_PALETTE`) | the luminance mapped onto the palette, cold to hot | | `ColorKey` | `color` (green), `tolerance` (0.4), `softness` (0.2) | a chroma key; linear in the colour, so it suits saturated screens | `ColorMatrix` holds the matrix maths (`brightness`, `contrast`, `exposure`, `saturation`, `grayscale`, `hue_rotate`, `invert`, `tint`, `duotone`, `white_balance`, `vibrance`, `mix`, `then`, `apply`). `a.then(b)` is one matrix that does `a`, then `b`. Unlike two nested effects, it does not clamp the colours in between. The table effects (`Levels`, `Curves`, `Lut`, `ShadowsHighlights`, `ThermalVision`, `ColorKey`) do not reduce to a matrix: nest them around or inside a stack, one filter each. ```rust use fframes_kit::prelude::*; fn photo<'a>(_ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } // curves inside a two-matrix stack that warms up over two seconds: two filters fn graded<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let warmth = motion::clamp01(ui.seconds() / 2.0); ui.show(ColorStack { matrices: vec![ColorMatrix::exposure(0.3), ColorMatrix::white_balance(warmth, 0.0)], children: children(|ui| ui.show(Curves { children: children(photo), ..ui.defaults() })), ..ui.defaults() }) } let red = ColorMatrix::grayscale(1.0).apply([1.0, 0.0, 0.0, 1.0]); assert!((red[0] - 0.213).abs() < 1e-6); ``` The gallery compares individual treatments of the same picture. Each value below is a separate alternative; arrange the returned layers as thumbnails to compare them: ```rust use fframes_kit::prelude::*; fn photo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn treatments<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(Brightness { amount: 1.3.into(), children: children(photo), ..ui.defaults() }), ui.show(Contrast { amount: 1.4.into(), children: children(photo), ..ui.defaults() }), ui.show(Saturation { amount: 1.6.into(), children: children(photo), ..ui.defaults() }), ui.show(HueRotate { degrees: (90.0 + 90.0 * ui.seconds()).into(), children: children(photo), ..ui.defaults() }), ui.show(Grayscale { amount: 1.0.into(), children: children(photo), ..ui.defaults() }), ui.show(Invert { amount: 1.0.into(), children: children(photo), ..ui.defaults() }), ui.show(Tint { amount: 0.8.into(), children: children(photo), ..ui.defaults() }), ui.show(Duotone { children: children(photo), ..ui.defaults() }), ui.show(Exposure { stops: 0.5.into(), children: children(photo), ..ui.defaults() }), ui.show(WhiteBalance { children: children(photo), ..ui.defaults() }), ui.show(Vibrance { children: children(photo), ..ui.defaults() }), ui.show(ColorCorrection { children: children(photo), ..ui.defaults() }), ui.show(Levels { children: children(photo), ..ui.defaults() }), ui.show(Lut { children: children(photo), ..ui.defaults() }), ui.show(ShadowsHighlights { children: children(photo), ..ui.defaults() }), ui.show(ThermalVision { children: children(photo), ..ui.defaults() }), ui.show(ColorKey { children: children(photo), ..ui.defaults() }), ] } ``` ### Edges and shape These work on the alpha edges of their children: text, logos and shapes on a transparent background. Lengths are in the children's user units; props follow Remotion's names and defaults. Gallery scenes: `EdgeEffects`, `Outline`, `Emboss`, `BevelAlpha`, `LongShadows`, `RoughenEdges`, `Shrinkwrap`. Gallery clip of `EdgeEffects`: https://fframes-kit.pages.dev/media/scenes/EdgeEffects-4762523b.mp4 Gallery clip of `Outline`: https://fframes-kit.pages.dev/media/scenes/Outline-76899f3c.mp4 Gallery clip of `Emboss`: https://fframes-kit.pages.dev/media/scenes/Emboss-bc0da37a.mp4 Gallery clip of `BevelAlpha`: https://fframes-kit.pages.dev/media/scenes/BevelAlpha-538116c0.mp4 Gallery clip of `LongShadows`: https://fframes-kit.pages.dev/media/scenes/LongShadows-f59d54c0.mp4 Gallery clip of `RoughenEdges`: https://fframes-kit.pages.dev/media/scenes/RoughenEdges-a6ae6eb2.mp4 Gallery clip of `Shrinkwrap`: https://fframes-kit.pages.dev/media/scenes/Shrinkwrap-9c51771e.mp4 | Component | Props (defaults) | What it does | | --- | --- | --- | | `Outline` | `width` (8), `color` (white), `opacity` (1), `outline_only` (false) | a solid outline from `feMorphology`; the region grows a tenth of the box, wider outlines are cut | | `Emboss` | `amount` (0.7), `size` (26), `line_width` (7), `depth` (0.75), `angle` (0), `light_angle` (135, from the top left), `offset` (0), `area` | Remotion's raised dash relief: a lit and shaded dash pattern masked by the children's alpha; renders the children twice | | `BevelAlpha` | `edge_thickness` (2), `light_angle` (-60, clockwise from above), `light_color` (white), `light_intensity` (0.4) | highlights and shadows along the alpha edges, from the slope of the blurred alpha | | `LongShadows` | `length` (240), `angle` (45, clockwise from the right), `color` (`#777777`), `opacity` (1), `area` | the silhouette as a solid directional shadow, clipped to `area` | | `RoughenEdges` | `amount` (1), `border` (26.5), `scale` (0.07), `seed` (231.2) | a torn-paper edge: blurred alpha shifted by noise and cut at a threshold | | `Shrinkwrap` | `amount` (1), `highlight_intensity` (0.75), `wrinkle_density` (0.42), `edge_tension` (0.45), `seed` | plastic wrap glints on `feTurbulence` wrinkles and a highlight along the edges | `LongShadows` doubles its copies: at most eight merges give 256 copies, one unit apart for a shadow up to 255 units long. Overlapping copies of a semi-transparent silhouette add up. `BevelAlpha` and `Shrinkwrap` measure slopes with `feConvolveMatrix` over neighbouring device pixels, so a draft render at a smaller scale shows a stronger bevel. The SVG lighting filters would scale correctly, but at half size the CPU and Skia backends draw them about 0.6% apart. Remotion's `shrinkwrap()` also displaces the picture along the wrinkles. That needs `feDisplacementMap`, so it waits with the other displacement effects. ```rust use fframes_kit::prelude::*; fn logo<'a>(_ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn badge<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let grow = motion::clamp01(ui.seconds()); ui.show(LongShadows { length: Anim::Fixed(240.0 * grow), children: children(|ui| ui.show(Outline { width: Anim::Fixed(6.0), children: children(logo), ..ui.defaults() })), ..ui.defaults() }) } ``` For separate edge treatments of the same logo: ```rust use fframes_kit::prelude::*; fn logo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn edge_treatments<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(Emboss { children: children(logo), ..ui.defaults() }), ui.show(BevelAlpha { children: children(logo), ..ui.defaults() }), ui.show(RoughenEdges { children: children(logo), ..ui.defaults() }), ui.show(Shrinkwrap { children: children(logo), ..ui.defaults() }), ] } ``` ### Noise | Component | Props (defaults) | What it does | | --- | --- | --- | | `WhiteNoise` | `amount` (1), `seed` (0), `grain` (2), `area` | grey static over the opaque parts; derive `seed` from the frame to make it move | | `FractalNoise` | `fractal_type` (`Basic`, or `Turbulent`), `scale` (100), `complexity` (6), `evolution` (0°), `cycle_evolution`, `cycle` (1), `contrast` (1), `brightness` (0), `offset_x`, `offset_y`, `seed`, `invert`, `amount` (1), `area` | layered grey noise for clouds and smoke, mixed over the opaque parts | Both draw `feTurbulence` on a rectangle over `area`, masked by the children's alpha, so the children render twice. `FractalNoise::evolution` cross-fades to the next seed on every full turn; with `cycle_evolution` the seeds repeat after `cycle` turns. Remotion's other fractal types, `noiseType`, `subInfluence` and `subScaling` have no `feTurbulence` equivalent. Gallery scenes: `NoiseEffects`, `WhiteNoise`, `FractalNoise`. Gallery clip of `NoiseEffects`: https://fframes-kit.pages.dev/media/scenes/NoiseEffects-a2bef52f.mp4 Gallery clip of `WhiteNoise`: https://fframes-kit.pages.dev/media/scenes/WhiteNoise-b90671bf.mp4 Gallery clip of `FractalNoise`: https://fframes-kit.pages.dev/media/scenes/FractalNoise-36f91d23.mp4 Noise detail near the pixel size samples differently on the CPU and Skia backends. A white noise of one-unit grain in a fifth-size thumbnail differed in 1.2% of the pixels; at about five output pixels per speck it differed in 0.07%. Keep `grain` and the finest `FractalNoise` layer (`scale / 2^(complexity - 1)`) at a few output pixels where the backends must agree. `motion::noise_2d`, `noise_3d` and `noise_4d` are simplex noise for driving motion (Remotion's `noise2D`, `noise3D`, `noise4D`). They return -1 to 1, the same for the same seed and coordinates. The seed is a number or a string. ```rust use fframes_kit::prelude::*; fn bobbing<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let y = 540.0 + 80.0 * motion::noise_2d("buoy", 0.0, ui.seconds() * 0.6); svgr!() } fn storm<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let turn = 90.0 * ui.seconds(); ui.show(FractalNoise { evolution: Anim::Fixed(turn), amount: Anim::Fixed(0.6), children: children(bobbing), ..ui.defaults() }) } fn static_noise<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(WhiteNoise { grain: 5.0, children: children(bobbing), ..ui.defaults() }) } ``` The displacement effects (noise displacement, turbulent displace, wave, waves, zigzag) are not yet available. They need `feDisplacementMap`, which the CPU backend draws wrongly: the `svgr` crate applies its `scale` twice and reads the map premultiplied (resvg issue 1160). They come back when a fixed `svgr` arrives with an upstream update. ### Pattern overlays | Component | Props (defaults) | What it does | | --- | --- | --- | | `Scanlines` | `amount` (0.15), `spacing` (4), `thickness` (1), `offset`, `premultiply`, `area` | CRT lines: the lines light the picture and the gaps darken it; animate `offset` to roll them | | `DotGrid` | `dot_size` (16), `grid_size` (20), `invert` | the children seen through round holes, or with the holes cut out | | `Checkerboard` | `colors` (two pale blues), `cell_size` (80), `gap` (0), `angle`, `offset_x`, `offset_y`, `mask_to_source_alpha` | cells over the children | | `Gridlines` | `grid_size` (64), `line_width` (2), `line_color` (white), `background_color` (transparent), `rotation`, `offset_x`, `offset_y`, `mask_to_source_alpha` | a square grid over the children | | `Lines` | `colors`, `direction` (`Horizontal` or `Vertical`), `thickness` (40), `gap` (0), `angle`, `offset`, `mask_to_source_alpha` | stripes over the children | | `Rings` | `colors`, `center` (0.5, 0.5), `thickness` (40), `gap` (0), `offset`, `mask_to_source_alpha` | concentric rings; animate `offset` to make them expand | | `LinearGradientTint` | `start` (0, 0.5), `end` (1, 0.5), `start_color` (black), `end_color` (white), `amount` (0.5) | the colours pulled towards a gradient, the alpha kept | | `LinearGradient` | `start`, `end`, `start_color`, `end_color` | a gradient filling the area; it replaces the layer, so it has no children | | `Vignette` | `amount` (0.5), `radius` (0.65), `center`, `feather` (0.35), `roundness` (1), `color` (black), `mode` (`Color` or `Alpha`) | darkened edges, or edges fading out with `Alpha` | Positions such as `start`, `end` and `center` run from 0 to 1 across `area` (1920 by 1080 by default). With `mask_to_source_alpha` a pattern covers only the opaque parts of the children, which then render twice; without it, it covers the whole area. Gallery scenes: `OverlayEffects`, `Scanlines`, `DotGrid`, `Checkerboard`, `Gridlines`, `Lines`, `Rings`, `LinearGradientTint`, `LinearGradient`, `Vignette`. Gallery clip of `OverlayEffects`: https://fframes-kit.pages.dev/media/scenes/OverlayEffects-498e32b9.mp4 Gallery clip of `Scanlines`: https://fframes-kit.pages.dev/media/scenes/Scanlines-ef5a45c2.mp4 Gallery clip of `DotGrid`: https://fframes-kit.pages.dev/media/scenes/DotGrid-a4e212bf.mp4 Gallery clip of `Checkerboard`: https://fframes-kit.pages.dev/media/scenes/Checkerboard-056d86ea.mp4 Gallery clip of `Gridlines`: https://fframes-kit.pages.dev/media/scenes/Gridlines-7ad2cd09.mp4 Gallery clip of `Lines`: https://fframes-kit.pages.dev/media/scenes/Lines-b86ae8e8.mp4 Gallery clip of `Rings`: https://fframes-kit.pages.dev/media/scenes/Rings-e80d3a7c.mp4 Gallery clip of `LinearGradientTint`: https://fframes-kit.pages.dev/media/scenes/LinearGradientTint-4fac3238.mp4 Gallery clip of `LinearGradient`: https://fframes-kit.pages.dev/media/scenes/LinearGradient-2ad8cad8.mp4 Gallery clip of `Vignette`: https://fframes-kit.pages.dev/media/scenes/Vignette-8f673e50.mp4 The patterns are drawn as paths, not SVG `` tiles: the backends round a small tile to whole pixels differently, so its repeats drift apart across the picture. One pattern draws at most `MAX_PATTERN_SHAPES` (20,000) stripes, cells or dots, and a finer one is drawn coarser. `Rings` draws at most `MAX_RINGS` (1,024). As with noise, keep the lines and dots at a few output pixels where the backends must agree. `Vignette` is drawn with gradients, so it needs no filter. Its curve is a smoothstep sampled at 16 points. Between `roundness` 0 and 1 the rectangle and the ellipse are mixed; Remotion mixes the distances instead, so those shapes differ a little. `Gridlines` cannot tilt in 3D: Remotion's `rotationX`, `rotationY` and `perspective` are not available. ```rust use fframes_kit::prelude::*; fn crt<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let roll = 8.0 * ui.seconds(); ui.show(Vignette { amount: Anim::Fixed(0.7), children: children(move |ui| { ui.show(Scanlines { amount: Anim::Fixed(0.3), offset: roll, children: children(|_| svgr!()), ..ui.defaults() }) }), ..ui.defaults() }) } ``` Compare the gallery's masks and overlays on one source: ```rust use fframes_kit::prelude::*; fn source<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn overlays<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(DotGrid { children: children(source), ..ui.defaults() }), ui.show(Checkerboard { children: children(source), ..ui.defaults() }), ui.show(Gridlines { children: children(source), ..ui.defaults() }), ui.show(Lines { children: children(source), ..ui.defaults() }), ui.show(Rings { children: children(source), ..ui.defaults() }), ui.show(LinearGradientTint { children: children(source), ..ui.defaults() }), ui.show(LinearGradient { start_color: Color::hex("#2a9d8f"), end_color: Color::hex("#f6d365"), ..ui.defaults() }), ] } ``` ### Textures | Component | Props (defaults) | What it does | | --- | --- | --- | | `Pattern` | `scale` (0.1), `crop` (left, top, right, bottom insets), `gap_x`, `gap_y`, `offset_u`, `offset_v`, `row_offset`, `row_offset_every` (0: never repeats), `column_offset`, `column_offset_every`, `origin` (0, 0), `wrap` (true) | the children repeated as a grid of small copies, staggered by row or column | | `Speckle` | `density` (0.08), `size` (4), `randomness` (1) | small round holes punched through the children | | `Flannel` | `amount` (0.7), `size` (96), `softness` (0.18), `base_color`, `stripe_color` | plaid cloth printed on the children, shaded by their brightness | | `Burlap` | `amount` (0.55), `size` (5), `roughness` (0.7), `seed`, `color` (black) | woven sacking: strand gaps and blotches darken the picture | | `Paper` | `amount` (1), `color_front`, `color_back`, `contrast` (0.3), `roughness` (0.4), `fiber` (0.3), `fiber_size` (0.2), `crumples` (0.3), `crumple_size` (0.35), `folds` (0.65), `fold_count` (5), `seed` (6), `scale` (0.6) | a lit relief of fibres, crumples and creases shading and tinting the picture | `Pattern` renders the children once and places the copies with ``, at most `MAX_TILES` (1,024); a smaller `scale` is drawn larger. It replaces the children and is transparent between the tiles. The other textures keep the children's alpha. Gallery scenes: `TextureEffects`, `PaperEffects`, `Pattern`, `Speckle`, `Flannel`, `Burlap`, `Paper`. Gallery clip of `TextureEffects`: https://fframes-kit.pages.dev/media/scenes/TextureEffects-dab344e0.mp4 Gallery clip of `PaperEffects`: https://fframes-kit.pages.dev/media/scenes/PaperEffects-2c53af96.mp4 Gallery clip of `Pattern`: https://fframes-kit.pages.dev/media/scenes/Pattern-aab8989b.mp4 Gallery clip of `Speckle`: https://fframes-kit.pages.dev/media/scenes/Speckle-09533492.mp4 Gallery clip of `Flannel`: https://fframes-kit.pages.dev/media/scenes/Flannel-93822a42.mp4 Gallery clip of `Burlap`: https://fframes-kit.pages.dev/media/scenes/Burlap-8ec0ff07.mp4 Gallery clip of `Paper`: https://fframes-kit.pages.dev/media/scenes/Paper-bc7e00c3.mp4 These are approximations of Remotion's per-pixel shaders: - `Flannel` leaves out the pixel-sized thread ripple. - `Burlap` leaves out the over and under weave, the strand jitter and the dashes; its strands are repeating gradients. - `Paper` lights a relief built from `feTurbulence` and drawn creases. It does not displace the picture along the folds (that needs `feDisplacementMap`), its tint uses the average light, and `drops` and `fade` are not available. Keep strands, fibres and holes a few output pixels wide, as with noise. `Paper`'s strong light magnifies how differently the CPU and Skia backends sample `feTurbulence`: the gallery's paper differs in about 1% of the pixels at full size and 6.6% at half size. That is why it has no e2e frame. ```rust use fframes_kit::prelude::*; fn photo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn wallpaper<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(Paper { children: children(|ui| { ui.show(Pattern { scale: 0.2, row_offset: 192.0, row_offset_every: 2, children: children(photo), ..ui.defaults() }) }), ..ui.defaults() }) } fn textures<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(Speckle { children: children(photo), ..ui.defaults() }), ui.show(Flannel { children: children(photo), ..ui.defaults() }), ui.show(Burlap { children: children(photo), ..ui.defaults() }), ] } ``` ### Halftone and light | Component | Props (defaults) | What it does | | --- | --- | --- | | `Halftone` | `shape` (`Circle`, `Square`, `Line`), `dot_size` (20), `dot_spacing` (`None`: the dot size), `rotation` (degrees), `offset`, `color` (`Solid`, `Source`), `dot_color` (red), `invert` | the children printed as dots that grow where they are dark; the dots replace them | | `HalftoneLinearGradient` | `first_dot_size` (0), `second_dot_size` (40), `first_stop` (0, 0.5), `second_stop` (1, 0.5), `grid_size` (24), `color`, `dot_color` (black), `mask_to_source_alpha` | dots growing along a gradient over the children, or the children seen only through them | | `Starburst` | `rays` (12, 2 to 100), `colors` (accent and background), `rotation` (degrees), `smoothness` (0 to 1), `origin` (0.5, 0.5) | rays of colour from a point; it replaces the layer and takes no children | | `LightTrail` | `direction` (180, degrees clockwise from the right), `distance` (80, animatable), `intensity` (1), `decay` (0.9), `threshold` (0), `samples` (32, at most `MAX_TRAIL_SAMPLES`), `color` (white) | a streak of light added behind the children's silhouette | `Halftone` compares every pixel's brightness with a field of gradients that peaks in the dot middles, inside one filter over the `area`. Remotion sizes a whole dot by the brightness at its middle, so here a dot on an edge in the picture takes the edge's shape. Circles are drawn one by one, at most `MAX_PATTERN_SHAPES`; squares and lines are gradients. `Starburst` blends a ray into the next in twelve steps, smoothly across the boundary. `LightTrail` averages copies of the silhouette by doubling, like `LongShadows`, and its threshold looks only at the alpha (Remotion also lets bright semi-transparent pixels through). Gallery scenes: `HalftoneEffects`, `HalftoneLinearGradient`, `LightTrail`, `Halftone`, `Starburst`. Gallery clip of `HalftoneEffects`: https://fframes-kit.pages.dev/media/scenes/HalftoneEffects-649f43fd.mp4 Gallery clip of `HalftoneLinearGradient`: https://fframes-kit.pages.dev/media/scenes/HalftoneLinearGradient-6863f281.mp4 Gallery clip of `LightTrail`: https://fframes-kit.pages.dev/media/scenes/LightTrail-3821b46c.mp4 Gallery clip of `Halftone`: https://fframes-kit.pages.dev/media/scenes/Halftone-c9cd7e9a.mp4 Gallery clip of `Starburst`: https://fframes-kit.pages.dev/media/scenes/Starburst-ed36bcd5.mp4 ```rust use fframes_kit::prelude::*; fn photo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn poster<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let rays = ui.show(Starburst { rays: 16, rotation: 10.0 * ui.seconds(), smoothness: 0.4, ..ui.defaults() }); let dots = ui.show(Halftone { dot_size: 32.0, rotation: 45.0, color: HalftoneColor::Source, children: children(photo), ..ui.defaults() }); let gradient = ui.show(HalftoneLinearGradient { grid_size: 72.0, second_dot_size: 60.0, children: children(photo), ..ui.defaults() }); let trail = ui.show(LightTrail { distance: Anim::Fixed(240.0), children: children(|_| svgr!()), ..ui.defaults() }); svgr!({rays}{dots}{gradient}{trail}) } ``` ### Geometry and reveals | Component | Props (defaults) | What it does | | --- | --- | --- | | `Mirror` | `direction` (`Horizontal`, `Vertical`), `position` (0.5, animatable), `invert` | the part past `position` shows the other side reflected | | `Tile` | `rect` (x, y, width, height: 640, 360, 640, 360), `horizontal` (true), `vertical` (true) | `rect` of the children repeated across the area, every other copy mirrored | | `Skew` | `x` (20 degrees), `y` (0), `origin` (0.5, 0.5) | slanted like CSS `skewX` then `skewY` | | `Scale` | `scale` (1), `horizontal` (true), `vertical` (true) | scaled about the middle of the area | | `Translate` | `x`, `y` (0), `uv` (false: units; true: shares of the area) | moved | | `VenetianBlinds` | `progress` (0.5), `direction` (`Vertical`, `Horizontal`), `slats` (12) | shown through blinds opening from the middle of each slat | | `Evolve` | `progress` (0.5), `direction` (`Left`, `Right`, `Top`, `Bottom`), `feather` (0.1) | revealed by an edge sweeping across, softened over `feather` | | `TvSignalOff` | `amount` (1) | television test bars mixed in inside the children's alpha | All of them except `TvSignalOff` work inside the `area` from the origin and cut what leaves it, like Remotion's canvas. `progress`, `position`, `scale`, `amount` and the offsets and angles are animatable. Remotion's `tile()` finds the rectangle to repeat from the opaque pixels; fframes cannot measure the children, so `Tile` takes it as `rect`. Where a mirrored side reaches past the area, Remotion stretches the edge pixels and `Mirror` leaves it transparent. `TvSignalOff` draws the bars over the children, so semi-transparent parts mix a little differently. Gallery scenes: `GeometryEffects`, `TvSignalOff`, `VenetianBlinds`, `EffectMirror`, `Tile`, `Skew`, `Scale`, `Translate`, `Evolve`. Gallery clip of `GeometryEffects`: https://fframes-kit.pages.dev/media/scenes/GeometryEffects-9734b0e5.mp4 Gallery clip of `TvSignalOff`: https://fframes-kit.pages.dev/media/scenes/TvSignalOff-3e93fddf.mp4 Gallery clip of `VenetianBlinds`: https://fframes-kit.pages.dev/media/scenes/VenetianBlinds-5f54f982.mp4 Gallery clip of `EffectMirror`: https://fframes-kit.pages.dev/media/scenes/EffectMirror-e4a2a152.mp4 Gallery clip of `Tile`: https://fframes-kit.pages.dev/media/scenes/Tile-51fab1c2.mp4 Gallery clip of `Skew`: https://fframes-kit.pages.dev/media/scenes/Skew-ba606906.mp4 Gallery clip of `Scale`: https://fframes-kit.pages.dev/media/scenes/Scale-f892dc9e.mp4 Gallery clip of `Translate`: https://fframes-kit.pages.dev/media/scenes/Translate-bb5fdef6.mp4 Gallery clip of `Evolve`: https://fframes-kit.pages.dev/media/scenes/Evolve-e6ef8250.mp4 The `Evolve` gallery clip uses Skia: the ordinary CPU renderer shows the soft edge in the same position across this example's frames. `Mirror` and `Tile` lay a slightly larger copy under the exact one so that no seam shows where copies meet; they render their children twice. ```rust use fframes_kit::prelude::*; fn photo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn intro<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let progress = Anim::Fixed((ui.seconds() / 1.5).min(1.0)); ui.show(VenetianBlinds { progress, slats: 8, children: children(|ui| { ui.show(TvSignalOff { amount: Anim::Fixed(0.5), children: children(|ui| ui.show(Mirror { children: children(photo), ..ui.defaults() })), ..ui.defaults() }) }), ..ui.defaults() }) } fn transforms<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { vec![ ui.show(Tile { rect: (560.0, 240.0, 800.0, 600.0), children: children(photo), ..ui.defaults() }), ui.show(Skew { x: Anim::Fixed(15.0), children: children(photo), ..ui.defaults() }), ui.show(Scale { scale: Anim::Fixed(0.8), children: children(photo), ..ui.defaults() }), ui.show(Translate { x: Anim::Fixed(100.0), children: children(photo), ..ui.defaults() }), ui.show(Evolve { progress: Anim::Fixed(0.5), children: children(photo), ..ui.defaults() }), ] } ``` ### Region and progressive blur | Component | Props (defaults) | What it does | | --- | --- | --- | | `RegionBlur` | `top_left` (0.25, 0.25), `bottom_right` (0.75, 0.75), `radius` (40, animatable), `feather` (0 units), `roundness` (0 to 1) | the children blurred inside a rectangle with soft edges and round corners | | `LinearProgressiveBlur` | `start` (0, 0.5), `end` (1, 0.5), `start_blur` (0), `end_blur` (50) | the blur grows along a line | | `RadialProgressiveBlur` | `center` (0.5, 0.5), `width` (1), `height` (1), `rotation` (degrees), `start` (0), `start_blur` (0), `end_blur` (50) | the blur grows from the centre out to an ellipse | Positions are shares of the `area`. Radii are Remotion's: a Gaussian with a standard deviation of a third of the radius, so `RegionBlur { radius: 30.0, .. }` blurs like `Blur { radius: 10.0, .. }`. The progressive blurs draw blurred copies under graded masks: one copy for every 6 units of radius, at most `MAX_BLUR_LEVELS` (8) after the sharp one, cross-faded in between. `blur_levels(start, end)` returns the radii. Near the edges of the area a blurred copy fades out, where Remotion repeats the edge pixels, so the less blurred copy below shows through. Gallery scenes: `BlurEffects`, `RegionBlur`, `LinearProgressiveBlur`, `RadialProgressiveBlur`. Gallery clip of `BlurEffects`: https://fframes-kit.pages.dev/media/scenes/BlurEffects-8ee5f603.mp4 Gallery clip of `RegionBlur`: https://fframes-kit.pages.dev/media/scenes/RegionBlur-ef9ee91e.mp4 Gallery clip of `LinearProgressiveBlur`: https://fframes-kit.pages.dev/media/scenes/LinearProgressiveBlur-4b9a1d54.mp4 Gallery clip of `RadialProgressiveBlur`: https://fframes-kit.pages.dev/media/scenes/RadialProgressiveBlur-2804f7c9.mp4 What they cost, from the bench card filling a 1080p frame (`BenchKit`, 5 seconds, CPU backend, the median of three `render` runs): | Scene | Seconds | Against `BenchKit` | | --- | --- | --- | | `BenchKit` | 1.05 | 1.0 | | `BenchRegionBlur` | 1.80 | 1.7 | | `BenchLinearProgressiveBlur` | 7.39 | 7.0 | | `BenchRadialProgressiveBlur` | 7.38 | 7.0 | Each blurred copy is a blur of the whole area, about 5 ms a frame at 1080p on the CPU backend; the defaults (0 to 50) take eight, a smaller `end_blur` fewer. ```rust use fframes_kit::prelude::*; fn photo<'a>(_: &mut Ui<'a, '_, '_>) -> Svgr<'a> { svgr!() } fn tilt_shift<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(RadialProgressiveBlur { width: 1.2, height: 0.5, start: 0.4, end_blur: 36.0, children: children(|ui| { ui.show(RegionBlur { top_left: (0.7, 0.05), bottom_right: (0.95, 0.2), feather: 24.0, roundness: 0.5, children: children(photo), ..ui.defaults() }) }), ..ui.defaults() }) } fn linear_blur<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(LinearProgressiveBlur { start: (0.0, 0.25), end: (0.0, 1.0), end_blur: 60.0, children: children(photo), ..ui.defaults() }) } ``` ### Not yet available These `@remotion/effects` effects move or sample single pixels, which SVG filters cannot do. They need per-pixel code and are not in the kit yet: fisheye, barrel distortion, sphere, cylinder, pixelate, linear and radial progressive pixelate, pixel dissolve, chromatic aberration, zoom blur, lens blur, page turn, shatter, tear, corner pin, contour lines, liquid contours and shine. `Halftone` approximates `halftone()` without its `sampling`, and `Lut` approximates a LUT with per-channel tables instead of reading a LUT file. The displacement effects wait for a fixed `svgr` (see "Noise"). ### Remotion animation utilities `makeTransform` from `@remotion/animation-utils` is `fframes::Transform`, which the prelude exports. It writes its parts in a fixed order: translate, rotate, scale, skew X, skew Y. `interpolateStyles` is `motion::progress` over the input range, then `motion::lerp` for each value. ```rust use fframes_kit::prelude::*; // makeTransform([translate(40, 0), rotate(15), scale(1.5)]) let transform = Transform { translate_x: 40.0, rotate: 15.0_f64.into(), scale: 1.5_f64.into(), ..Default::default() }; assert_eq!(transform.to_string(), "translate(40 0) rotate(15) scale(1.5)"); // interpolateStyles(frame, [0, 20], [{ opacity: 0, translateY: 40 }, { opacity: 1, translateY: 0 }]) let t = motion::progress(5.0, 0.0, 20.0); let (opacity, y) = (motion::lerp(0.0, 1.0, t), motion::lerp(40.0, 0.0, t)); assert_eq!((opacity, y), (0.25, 30.0)); ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Trail The children at earlier local times behind themselves, each copy fainter, like Remotion's ``. `samples` copies (at most `MAX_SAMPLES`) are `spacing` seconds apart; the newest has `opacity` and they fade out linearly behind it. Before the local start there is nothing to repeat, so the trail grows in. | Field | Type | Default | Description | | --- | --- | --- | --- | | `samples` | `usize` | `6` | Number of earlier copies, at most `MAX_SAMPLES`; each one renders the children again. | | `spacing` | `f32` | `0.05` | Seconds between one copy and the next, going back in time from the newest. | | `opacity` | `f32` | `0.5` | Opacity of the newest copy, 0 transparent to 1 opaque; older copies fade out linearly behind it. | | `children` | `Children` | `no_children()` | The content that leaves the trail (a closure taking the `Ui`). | Methods: - `fn copy(&self, i: usize) -> (f32, f32)`: How far back copy `i` (from 1, the newest) is and how opaque. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/motion_blur.rs#L23) ### CameraMotionBlur Motion blur as a film camera records it, like Remotion's ``: the children are averaged over the time the shutter is open before each frame. `shutter_angle` is in degrees, 360 for the whole frame interval; `samples` (at most `MAX_SAMPLES`) are the moments averaged. Each is drawn over the earlier ones with opacity `1 / k` for the `k`-th, so all count the same where the children are opaque. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shutter_angle` | `f32` | `180.0` | Degrees the shutter stays open per frame: 360 is the whole frame interval, 180 half of it (Remotion's default). | | `samples` | `usize` | `10` | Moments averaged over the open shutter, at most `MAX_SAMPLES`; more is smoother and costs more renders. | | `children` | `Children` | `no_children()` | The content that is blurred by its own motion (a closure taking the `Ui`). | Methods: - `fn offsets(&self, fps: usize) -> Vec`: Seconds before the frame of each sample at `fps`, the earliest first and the frame itself last. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/motion_blur.rs#L83) ### LightLeak Light leaking into the camera, like Remotion's ``: warm blobs of light flare up over the picture and fade out again within `length` seconds (the enclosing container's length when `None`), brightest at `opacity` in the middle. `seed` picks where the blobs are and how they drift, and `hue_shift` turns their colours round the colour wheel in degrees (180 for blue light). Drawn with radial gradients in screen blending, so it works on every backend; `size` is the area from the top left and `key` names the gradients when two leaks share a frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `seed` | `u64` | `0` | Picks where the blobs sit and how they drift; the same seed gives the same leak. | | `hue_shift` | `f32` | `0.0` | Degrees the blob colours are turned round the colour wheel; 180 turns the warm light blue. | | `length` | `Option` | `None` | Seconds the leak takes to flare up and fade out, local to the parent container; `None` uses the container's length. | | `opacity` | `f32` | `0.85` | Peak opacity of the blobs, 0 transparent to 1 opaque, reached in the middle of the leak. | | `size` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the leak covers from the top left, in pixels of the canvas. | | `key` | `Option<&str>` | `None` | Names the gradients when two leaks share a frame. | Methods: - `fn flares(&self, progress: f32) -> Vec`: The blobs at `progress` through the leak, 0 to 1. Each flares up and fades along a half sine from its own start, so they peak one after another. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/light_leak.rs#L26) ### Blur Its children blurred by a Gaussian of `radius` (the standard deviation, in view-box units), which can be animated: a focus pull, a background pushed back behind a title. A radius of 0 draws the children unfiltered. `key` names the filter when two blurs share a frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `radius` | `Anim` | `Anim::Fixed(8.0)` | Gaussian standard deviation in view-box units; 0 draws the children unblurred. Animate it for a focus pull. | | `key` | `Option<&str>` | `None` | Names the filter when two blurs share a frame. | | `children` | `Children` | `no_children()` | The content that is blurred (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/blur.rs#L18) ### Noise Film grain over a `size` area from the top left: grey `feTurbulence` noise at `opacity`, drawn by every backend. `frequency` is the noise's base frequency (higher is finer) and `octaves` its detail. The same `seed` gives the same grain; with `animated` the seed moves on every frame so the grain flickers like film. `key` names the filter when two noises share a frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `seed` | `u32` | `0` | Seeds the grain; the same seed draws the same grain. | | `opacity` | `f32` | `0.12` | 0 transparent to 1 opaque. | | `frequency` | `f32` | `0.8` | Base frequency of the noise: higher is finer grain. | | `octaves` | `u32` | `2` | Layers of detail added to the noise; more is richer and slower. | | `animated` | `bool` | `true` | `true` moves the seed on every frame so the grain flickers like film; `false` keeps it still. | | `size` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the grain area from the top left, in pixels of the canvas. | | `key` | `Option<&str>` | `None` | Names the filter when two noises share a frame. | Methods: - `fn seed_at(&self, index: usize) -> u32`: The turbulence seed at local frame `index`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/noise.rs#L17) ### ShaderNoise Film grain on the GPU: a grey value per `scale` pixel cell at `opacity`, over a `size` area from the top left. The same `seed` gives the same grain; with `animated` it moves on every frame. Only the Skia backend runs shaders: the CPU backend draws nothing and the editor a placeholder, so use `Noise` where those matter. | Field | Type | Default | Description | | --- | --- | --- | --- | | `seed` | `u32` | `0` | Seeds the grain; the same seed draws the same grain. | | `opacity` | `f32` | `0.12` | 0 transparent to 1 opaque. | | `scale` | `f32` | `2.0` | Side of one grain cell in pixels of the canvas; at least 1. | | `animated` | `bool` | `true` | `true` moves the seed on every frame so the grain flickers; `false` keeps it still. | | `size` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the grain area from the top left, in pixels of the canvas. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/noise.rs#L132) ### Brightness Its children brighter or darker (CSS `brightness()`): `amount` 1 unchanged, 0 black. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.3)` | Brightness factor on the colours: 1 unchanged, 0 black, above 1 brighter. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L300) ### Contrast Its children with more or less contrast (CSS `contrast()`): `amount` 1 unchanged, 0 flat grey. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.4)` | Contrast factor about mid grey: 1 unchanged, 0 flat grey, above 1 stronger. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L310) ### Saturation Its children more or less saturated (SVG `saturate`): `amount` 1 unchanged, 0 grey. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.6)` | Saturation factor: 1 unchanged, 0 grey, above 1 more saturated. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L321) ### HueRotate Its children with their hues turned round the colour wheel by `degrees` (SVG `hueRotate`), Remotion's hue effect. | Field | Type | Default | Description | | --- | --- | --- | --- | | `degrees` | `Anim` | `Anim::Fixed(90.0)` | Angle in degrees the hues are turned round the colour wheel. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L331) ### Grayscale Its children in grey (CSS `grayscale()`): `amount` 1 fully grey. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 unchanged to 1 fully grey. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L342) ### Invert Its children as a negative (CSS `invert()`): `amount` 1 fully inverted. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 unchanged to 1 fully inverted (0.5 is flat grey). | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L352) ### Tint Its children in shades of `color`, mixed with the original by `amount`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `color` | `Color` | `theme.colors.accent` | Colour the picture is tinted with; the picture turns shades of it. | | `amount` | `Anim` | `Anim::Fixed(0.8)` | 0 the original to 1 fully tinted. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L362) ### Duotone Its children in two colours: dark tones turn `dark` and light tones `light`, mixed with the original by `amount`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `dark` | `Color` | `theme.colors.background` | Colour the dark tones turn. | | `light` | `Color` | `theme.colors.accent` | Colour the light tones turn. | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 the original to 1 fully two-coloured. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L374) ### Levels Photoshop's levels on every colour channel: the `input` black and white points stretch to 0 and 1, `gamma` bends the mid tones (above 1 brighter), and the result lands between the `output` black and white points. | Field | Type | Default | Description | | --- | --- | --- | --- | | `input` | `(f32, f32)` | `(0.08, 0.92)` | Black and white points of the input, 0 to 1; they stretch to 0 and 1. | | `gamma` | `Anim` | `Anim::Fixed(1.2)` | Mid-tone curve: above 1 brighter, below 1 darker, 1 unchanged. | | `output` | `(f32, f32)` | `(0.0, 1.0)` | Black and white points of the result, 0 to 1; 0 and 1 stretch to them. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L487) ### Curves Tone curves: a smooth curve through `points` (input, output, both 0 to 1, sorted by input) on every colour channel, then the optional per-channel curves `red`, `green` and `blue`. The default is a gentle S curve that adds contrast. | Field | Type | Default | Description | | --- | --- | --- | --- | | `points` | `&[(f32, f32)]` | `&[(0.0, 0.0), (0.25, 0.2), (0.75, 0.82), (1.0, 1.0)]` | `(input, output)` pairs of the curve on every channel, both 0 to 1 and sorted by input; the default is a gentle S curve. | | `red` | `Option<&[(f32, f32)]>` | — | An extra curve applied to the red channel after `points`; `None` for none. | | `green` | `Option<&[(f32, f32)]>` | — | An extra curve applied to the green channel after `points`; `None` for none. | | `blue` | `Option<&[(f32, f32)]>` | — | An extra curve applied to the blue channel after `points`; `None` for none. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L512) ### Exposure Its children exposed brighter or darker by `stops`: every stop doubles the light. | Field | Type | Default | Description | | --- | --- | --- | --- | | `stops` | `Anim` | `Anim::Fixed(0.5)` | Photographic stops to brighten (positive) or darken (negative); each stop doubles the light. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L391) ### ColorCorrection A basic colour correction in one matrix: `brightness`, then `contrast`, then `saturation` (1 leaves each unchanged), then the hues turned by `hue` degrees. | Field | Type | Default | Description | | --- | --- | --- | --- | | `brightness` | `f32` | `1.0` | Brightness factor applied first; 1 unchanged. | | `contrast` | `f32` | `1.1` | Contrast factor applied second, about mid grey; 1 unchanged. | | `saturation` | `f32` | `1.2` | Saturation factor applied third; 1 unchanged, 0 grey. | | `hue` | `f32` | `0.0` | Degrees the hues are turned last. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L429) ### WhiteBalance Its children warmer or colder: `temperature` from -1 (blue) to 1 (orange) and `tint` from -1 (green) to 1 (magenta). | Field | Type | Default | Description | | --- | --- | --- | --- | | `temperature` | `Anim` | `Anim::Fixed(0.5)` | -1 cold (blue) to 1 warm (orange). | | `tint` | `f32` | `0.0` | -1 green to 1 magenta. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L402) ### Vibrance Its children with gentler extra saturation than `Saturation`; see `ColorMatrix::vibrance` for how it approximates vibrance. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.8)` | Extra saturation, gentler than `Saturation`: 1 saturates half as much as `Saturation` at 2; 0 unchanged. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L417) ### ShadowsHighlights Shadows and highlights: `shadows` lifts the dark tones (up to a sixth at 1) and `highlights` pulls the light tones down, through a curve on every channel. An approximation: the real effect looks at each pixel's neighbourhood, this curve only at the pixel's own value. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shadows` | `Anim` | `Anim::Fixed(0.6)` | How much the dark tones are lifted, 0 to 1 (up to a sixth of the range). | | `highlights` | `f32` | `0.3` | How much the light tones are pulled down, 0 to 1 (up to a sixth of the range). | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L565) ### ColorKey A chroma key: what has the hue of `color` (a green or blue screen) turns transparent. Colours whose share of that hue is within `tolerance` of the key's vanish, and `softness` is the width of the fade into opaque. Linear in the colour, so it works best on saturated screens; a grey `color` keys nothing. | Field | Type | Default | Description | | --- | --- | --- | --- | | `color` | `Color` | `Color::rgb(0, 255, 0)` | Colour of the screen to remove; a grey colour keys nothing. | | `tolerance` | `f32` | `0.4` | 0 to 1: how far from the key hue still turns fully transparent; larger removes more. | | `softness` | `f32` | `0.2` | 0 to 1: width of the fade from transparent to opaque at the edge of the key. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L635) ### ThermalVision Its children as a thermal camera sees them: the luminance mapped onto `palette`, cold to hot, evenly spaced (the default is `THERMAL_PALETTE`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `palette` | `&[Color]` | `&THERMAL_PALETTE` | Colours the luminance is mapped onto, evenly spaced from cold (dark) to hot (light); empty maps to black and white. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L601) ### Lut A look-up table per colour channel: `red`, `green` and `blue` list the outputs for evenly spaced inputs from 0 to 1 (an empty list leaves the channel unchanged). An approximation of a LUT file: a `.cube` LUT maps colours in 3D, these tables map each channel on its own, so they hold curves and colour casts but not hue-dependent looks. | Field | Type | Default | Description | | --- | --- | --- | --- | | `red` | `&[f32]` | `&[0.05, 0.3, 0.62, 0.86, 1.0]` | Red outputs for evenly spaced inputs from 0 to 1; empty leaves red unchanged. | | `green` | `&[f32]` | `&[0.02, 0.27, 0.56, 0.82, 0.97]` | Green outputs for evenly spaced inputs from 0 to 1; empty leaves green unchanged. | | `blue` | `&[f32]` | `&[0.12, 0.3, 0.5, 0.72, 0.88]` | Blue outputs for evenly spaced inputs from 0 to 1; empty leaves blue unchanged. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L539) ### ColorStack Several colour matrices drawn as one: `matrices` in order, chained with `ColorMatrix::then` into one `feColorMatrix`, so the stack costs one filter. Nested colour effects clamp between steps and a stack does not; see `then`. The table effects (`Levels`, `Curves`, `Lut`, `ShadowsHighlights`, `ThermalVision`, `ColorKey`) do not reduce to a matrix: nest them around or inside the stack. | Field | Type | Default | Description | | --- | --- | --- | --- | | `matrices` | `Vec` | `Vec::new()` | The colour matrices, applied in order and drawn as one filter. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/color.rs#L452) ### Outline A solid outline `width` units wide around the opaque parts of its children (Remotion's `outline()`), in `color` at `opacity`. With `outline_only` the children are replaced by the filled silhouette in the outline colour. The filter region grows a tenth of the children's box on every side; wider outlines are cut there. | Field | Type | Default | Description | | --- | --- | --- | --- | | `width` | `Anim` | `Anim::Fixed(8.0)` | Outline width in the children's user units. Animatable; the region grows a tenth of the box, so wider outlines are cut. | | `color` | `Color` | `Color::WHITE` | Colour of the outline; its alpha multiplies `opacity`. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `outline_only` | `bool` | `false` | `true` replaces the children with their silhouette filled in the outline colour; `false` draws the outline behind them. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L34) ### Emboss A raised relief of repeated dashes pressed into its children (Remotion's `emboss()`): cells `size` units square, each holding a dash `line_width` wide, lit from `light_angle` (degrees counter-clockwise from the right, 135 the top left) so one side of each dash catches the light and the other falls into shadow. `depth` (0 to 1) is the height of the relief, `amount` (0 to 1) its strength, `angle` turns the pattern and `offset` scrolls it (animate it to move the relief). The source colours and transparency stay: the relief is a pattern over the `area` from the origin, masked by the children's alpha, so the children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.7)` | 0 to 1 strength of the relief. Animatable. | | `size` | `f32` | `26.0` | Side of one dash cell in the children's user units. | | `line_width` | `f32` | `7.0` | Width of each dash in the children's user units. | | `depth` | `f32` | `0.75` | 0 to 1 height of the relief; deeper dashes catch more light and shade. | | `angle` | `f32` | `0.0` | Degrees the dash pattern is turned. | | `light_angle` | `f32` | `135.0` | Degrees counter-clockwise from the right that the light comes from: 135 is the top left. | | `offset` | `f32` | `0.0` | Scrolls the pattern along its own horizontal axis, in user units; animate it to move the relief. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the relief covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask and pattern ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is embossed, by its alpha (a closure taking the `Ui`). | Methods: - `fn facing(&self) -> [f32; 4]`: How much each side of a dash faces the light, -1 to 1: top, bottom, left and right in the pattern's own turned frame. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L385) ### BevelAlpha Highlights and shadows along the alpha edges of its children, so text, logos and shapes look raised (Remotion's `bevelAlpha()`). `edge_thickness` is the width of the rounded bevel, `light_angle` the light's direction in degrees clockwise from above (0 lights the top edges), `light_color` the colour added to the lit edges (its alpha sets the strength) and `light_intensity` the strength of both, 0 to 1. A fully opaque picture has no alpha edges and stays as it is. | Field | Type | Default | Description | | --- | --- | --- | --- | | `edge_thickness` | `Anim` | `Anim::Fixed(2.0)` | Width of the rounded bevel in the children's user units; 0 or less leaves the picture unchanged. Animatable. | | `light_angle` | `f32` | `-60.0` | Degrees clockwise from straight up that the light comes from: 0 lights the top edges, 90 the right ones. | | `light_color` | `Color` | `Color::WHITE` | Colour added to the lit edges; its alpha sets the strength of the light. | | `light_intensity` | `f32` | `0.4` | 0 to 1 strength of the lit and the shaded edges. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L74) ### LongShadows The alpha silhouette of its children turned into a solid shadow `length` units long towards `angle` (degrees clockwise from the right, 45 down and right), in `color` at `opacity` (Remotion's `longShadows()`): text and logos with long shadows, or a flat 3D extrusion. Drawn inside the `area` from the origin, so like Remotion the shadow stops at the canvas edge. | Field | Type | Default | Description | | --- | --- | --- | --- | | `length` | `Anim` | `Anim::Fixed(240.0)` | Length of the shadow in the children's user units; under 1 draws none, 255 or more is the practical limit. Animatable. | | `angle` | `f32` | `45.0` | Degrees clockwise from the right the shadow points: 45 is down and right. | | `color` | `Color` | `Color::rgb(0x77, 0x77, 0x77)` | Colour of the shadow; its alpha multiplies `opacity`. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the shadow is drawn in from the top left, in the children's user units; it stops at the edge. | | `key` | `Option<&str>` | `None` | Names the filter when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that casts the shadow, drawn on top of it (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L287) ### RoughenEdges The edge of its children roughened like torn paper (Remotion's `roughenEdges()`): `amount` 0 to 1, `border` how far the edge moves in and out, `scale` the size of the noise (smaller is finer) and `seed` the pattern; derive the seed from the frame to animate the edge. The alpha is blurred, shifted by `feTurbulence` noise and cut at a threshold, so the edge stays smooth at any scale. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 unchanged to 1 fully roughened. Animatable. | | `border` | `f32` | `26.5` | How far the edge moves in and out, in the children's user units; 0 or less leaves it unchanged. | | `scale` | `f32` | `0.07` | Size of the noise that tears the edge, 0.01 to 4: smaller is finer. | | `seed` | `f32` | `231.2` | Picks the tear pattern (rounded to a whole number); derive it from the frame to animate the edge. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L137) ### Shrinkwrap Its children shrink-wrapped in plastic (Remotion's `shrinkwrap()`): glossy glints on `feTurbulence` wrinkles, brightest at `highlight_intensity`, a stretched highlight along the edges facing the top left at `edge_tension`, all scaled by `amount` (0 unchanged). `wrinkle_density` from 0 (broad folds) to 1 (tight texture) and `seed` pick the wrinkles. Remotion also displaces the picture along the wrinkles; that needs `feDisplacementMap`, which the CPU backend draws wrongly, so it is left out. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 unchanged to 1 full strength of every wrap effect. Animatable. | | `highlight_intensity` | `f32` | `0.75` | Brightness of the glossy glints on the wrinkles; 0 for none. | | `wrinkle_density` | `f32` | `0.42` | 0 broad folds to 1 tight texture. | | `edge_tension` | `f32` | `0.45` | 0 to 1 strength of the stretched highlight along the edges that face the top left. | | `seed` | `u32` | `0` | Picks the wrinkle pattern. | | `key` | `Option<&str>` | `None` | Names the filter when two effects of this kind share a frame. | | `children` | `Children` | `ui::no_children()` | The content the filter is applied to (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/edges.rs#L191) ### WhiteNoise Grey static over its children (Remotion's `whiteNoise()`): TV static, signal loss, snow. `amount` mixes it in, 0 unchanged and 1 the visible pixels fully replaced; the children's alpha stays. `seed` picks the pattern: derive it from the frame (`ui.frame.index as u32`) to make the static move. `grain` is the size of a speck in units: keep it at least two output pixels (2 for a full-size render, more for a scaled down one), since finer noise aliases and each backend samples it differently. Drawn with `feTurbulence` over the `area` from the origin; the children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 the children unchanged to 1 the visible pixels fully replaced by static. Animatable. | | `seed` | `u32` | `0` | Picks the pattern; derive it from the frame (`ui.frame.index as u32`) to make the static move. | | `grain` | `f32` | `2.0` | Size of a speck in the children's user units; keep it at least 2 output pixels or it aliases. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the static covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter and mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the static is drawn over, only where it is opaque (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/distort.rs#L62) ### FractalNoise Layered grey noise for clouds, smoke and organic textures (Remotion's `fractalNoise()`), drawn over its children where they are opaque and mixed in by `amount` (1 replaces the colours). | Field | Type | Default | Description | | --- | --- | --- | --- | | `fractal_type` | `FractalType` | `FractalType::Basic` | `Basic` for soft layered clouds, `Turbulent` for sharp ridges like smoke or marble veins. | | `scale` | `f32` | `100.0` | Size of the largest cells in the children's user units; bigger is broader clouds. | | `complexity` | `u32` | `6` | Number of layers of detail, 1 to 10; each is half the size and half the strength of the one before. | | `evolution` | `Anim` | `Anim::Fixed(0.0)` | Degrees that morph the texture in place; each full turn moves on to the next seed, cross-fading on the way. Animatable. | | `cycle_evolution` | `bool` | `false` | `true` repeats the seeds after `cycle` turns of `evolution` so the texture loops. | | `cycle` | `u32` | `1` | Turns of `evolution` after which the seeds repeat when `cycle_evolution` is on; at least 1. | | `contrast` | `f32` | `1.0` | Stretches the greys about mid grey: 1 unchanged, 0 flat grey, above 1 harder. | | `brightness` | `f32` | `0.0` | Added to the greys after `contrast`: 0 unchanged, positive lighter, negative darker (about -0.5 to 0.5). | | `offset_x` | `f32` | `0.0` | Horizontal shift of the texture in the children's user units. | | `offset_y` | `f32` | `0.0` | Vertical shift of the texture in the children's user units. | | `seed` | `u32` | `0` | Picks the texture; the same seed draws the same one. | | `invert` | `bool` | `false` | `true` flips the greys, light for dark. | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 the children unchanged to 1 the opaque pixels fully replaced by the noise. Animatable. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the noise covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter and mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the noise is drawn over, only where it is opaque (a closure taking the `Ui`). | Methods: - `fn seeds(&self, evolution: f32) -> (u32, u32, f32)`: The two seeds `evolution` degrees cross-fades between, and how far it is from the first to the second. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/distort.rs#L150) ### Pattern Its children repeated in a grid of small copies (Remotion's `pattern()`). The copies are the children cut to `crop` (left, top, right and bottom insets in units) and scaled by `scale`, `gap_x` and `gap_y` units apart. The grid starts at `origin` moved by `offset_u`, `offset_v` (0 to 1 across the area). Each row is moved right by `row_offset` units more than the one above, starting over every `row_offset_every` rows (0 never), and each column down by `column_offset` units the same way; without `wrap` only the tiles right of and below the origin are drawn. It replaces the children, transparent between the tiles. The children render once and are reused with ``, at most `MAX_TILES` times. | Field | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `f32` | `0.1` | Size of each copy as a share of the cropped children; 0.1 makes copies a tenth as wide and tall. Smaller scales are drawn larger once they would pass `MAX_TILES` copies. | | `crop` | `(f32, f32, f32, f32)` | `(0.0, 0.0, 0.0, 0.0)` | `(left, top, right, bottom)` insets in the children's user units that are cut off before copying. | | `gap_x` | `f32` | `0.0` | Width in user units between copies side by side. | | `gap_y` | `f32` | `0.0` | Height in user units between copies above each other. | | `offset_u` | `f32` | `0.0` | Horizontal shift of the grid start as a 0 to 1 fraction of the area's width, added to `origin`. | | `offset_v` | `f32` | `0.0` | Vertical shift of the grid start as a 0 to 1 fraction of the area's height, added to `origin`. | | `row_offset` | `f32` | `0.0` | User units each row moves right more than the one above, restarting every `row_offset_every` rows. | | `row_offset_every` | `u32` | `0` | Rows after which `row_offset` starts over; 0 never restarts it. | | `column_offset` | `f32` | `0.0` | User units each column moves down more than the one before, restarting every `column_offset_every` columns. | | `column_offset_every` | `u32` | `0` | Columns after which `column_offset` starts over; 0 never restarts it. | | `origin` | `(f32, f32)` | `(0.0, 0.0)` | Where the grid starts, as 0 to 1 fractions of `area` (x, y). | | `wrap` | `bool` | `true` | `true` fills the whole area; `false` draws only the copies right of and below the origin. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the copies fill from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip and tile ids when two patterns share a frame. | | `children` | `Children` | `no_children()` | The content that is copied (a closure taking the `Ui`). | Methods: - `fn tiles(&self) -> (f32, Vec<(f32, f32)>)`: The scale the tiles are drawn at and the top left corner of every tile that reaches into the area. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/textures.rs#L45) ### Scanlines Horizontal CRT lines over its children (Remotion's `scanlines()`). Each line `thickness` units tall, one every `spacing` units, lights the picture and the gaps darken it, so the average brightness stays: `amount` is the strength, 0 unchanged. The light is added to every channel, or with `premultiply` scales the colours, which follows the picture's brightness more closely. `offset` scrolls the lines (animate it for a rolling screen). Keep the lines at least two output pixels apart, finer lines alias. The children render twice, their alpha stays. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.15)` | 0 unchanged to 1 full strength of the lines. Animatable. | | `spacing` | `f32` | `4.0` | Distance between the middles of two lines in the children's user units; keep it at least 2 output pixels or it aliases. | | `thickness` | `f32` | `1.0` | Height of each lit line in user units; at most `spacing`. | | `offset` | `f32` | `0.0` | Scrolls the lines down by this many user units; animate it for a rolling screen. | | `premultiply` | `bool` | `false` | `true` scales the colours instead of adding light to every channel, which follows the picture's brightness more closely. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the lines cover from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter and mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the lines are laid over (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L161) ### DotGrid Its children seen through a grid of round holes (Remotion's `dotGrid()`): dots `dot_size` units across, `grid_size` units apart, the first centred half a cell from the origin. With `invert` the dots are cut out of the children instead. | Field | Type | Default | Description | | --- | --- | --- | --- | | `dot_size` | `f32` | `16.0` | Diameter of each dot in the children's user units. | | `grid_size` | `f32` | `20.0` | Distance between dot centers in user units; the first dot is half a cell from the origin. | | `invert` | `bool` | `false` | `false` shows the children only through the dots; `true` cuts the dots out of them. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the grid covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask id when two grids share a frame. | | `children` | `Children` | `no_children()` | The content seen through the dots (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L244) ### Checkerboard A checkerboard over its children (Remotion's `checkerboard()`): square cells `cell_size` units wide in `colors` taken in turn along each row and column, `gap` units of the children showing between them. `angle` turns the board about the middle of the area in degrees and `offset_x`, `offset_y` move it; with `mask_to_source_alpha` it covers only the opaque parts of the children (which then render twice). | Field | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `Vec` | `PALE_BLUES.to_vec()` | Colours of the cells, taken in turn along every row and column; empty draws no board. | | `cell_size` | `f32` | `80.0` | Side of one square cell in the children's user units. | | `gap` | `f32` | `0.0` | Width in user units of the children showing between cells. | | `angle` | `f32` | `0.0` | Degrees the board is turned about the middle of the area. | | `offset_x` | `f32` | `0.0` | Horizontal shift of the board in user units. | | `offset_y` | `f32` | `0.0` | Vertical shift of the board in user units. | | `mask_to_source_alpha` | `bool` | `false` | `true` covers only the opaque parts of the children, which then render twice. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the board covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip and mask ids when two boards share a frame. | | `children` | `Children` | `no_children()` | The content the board is laid over (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L318) ### Gridlines A square grid of lines over its children (Remotion's `gridlines()`): lines `line_width` units wide in `line_color`, `grid_size` units apart, over a `background_color` (transparent by default). `rotation` turns the grid about the middle of the area in degrees and `offset_x`, `offset_y` move it; with `mask_to_source_alpha` it covers only the opaque parts of the children (which then render twice). Remotion's `rotationX`, `rotationY` and `perspective` tilt the grid in 3D, which SVG cannot draw; they are not available. | Field | Type | Default | Description | | --- | --- | --- | --- | | `grid_size` | `f32` | `64.0` | Distance between line middles in the children's user units. | | `line_width` | `f32` | `2.0` | Width of each line in user units; 0 draws none. | | `line_color` | `Color` | `Color::WHITE` | Colour of the lines. | | `background_color` | `Color` | `Color::TRANSPARENT` | Colour filled between the lines; transparent shows the children. | | `rotation` | `f32` | `0.0` | Degrees the grid is turned about the middle of the area. | | `offset_x` | `f32` | `0.0` | Horizontal shift of the grid in user units. | | `offset_y` | `f32` | `0.0` | Vertical shift of the grid in user units. | | `mask_to_source_alpha` | `bool` | `false` | `true` covers only the opaque parts of the children, which then render twice. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the grid covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip and mask ids when two grids share a frame. | | `children` | `Children` | `no_children()` | The content the grid is laid over (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L395) ### Lines Stripes over its children (Remotion's `lines()`): bands `thickness` units wide in `colors` taken in turn, `gap` units of the children showing between them, running `direction`. `angle` turns them about the middle of the area in degrees and `offset` moves them across; with `mask_to_source_alpha` they cover only the opaque parts of the children (which then render twice). | Field | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `Vec` | `PALE_BLUES.to_vec()` | Colours of the stripes, taken in turn; empty draws none. | | `direction` | `LineDirection` | `LineDirection::Horizontal` | `Horizontal` stripes run left to right and stack downwards; `Vertical` ones run top to bottom. | | `thickness` | `f32` | `40.0` | Width of each stripe in the children's user units; 0 draws none. | | `gap` | `f32` | `0.0` | Width in user units of the children showing between stripes. | | `angle` | `f32` | `0.0` | Degrees the stripes are turned about the middle of the area. | | `offset` | `f32` | `0.0` | Moves the stripes across their direction, in user units. | | `mask_to_source_alpha` | `bool` | `false` | `true` covers only the opaque parts of the children, which then render twice. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the stripes cover from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip and mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the stripes are laid over (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L492) ### Rings Concentric rings over its children (Remotion's `rings()`): rings `thickness` units wide in `colors` taken in turn, `gap` units of the children showing between them, round `center` (0 to 1 across the area). `offset` moves the rings outwards (animate it to make them expand); with `mask_to_source_alpha` they cover only the opaque parts of the children (which then render twice). Draws at most `MAX_RINGS` rings. | Field | Type | Default | Description | | --- | --- | --- | --- | | `colors` | `Vec` | `PALE_BLUES.to_vec()` | Colours of the rings, taken in turn from the middle outwards; empty draws none. | | `center` | `(f32, f32)` | `(0.5, 0.5)` | Middle of the rings as 0 to 1 fractions of `area` (x, y). | | `thickness` | `f32` | `40.0` | Width of each ring in the children's user units; 0 draws none. | | `gap` | `f32` | `0.0` | Width in user units of the children showing between rings. | | `offset` | `f32` | `0.0` | Moves the rings outwards in user units; animate it to make them expand. | | `mask_to_source_alpha` | `bool` | `false` | `true` covers only the opaque parts of the children, which then render twice. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the rings cover from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip and mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the rings are laid over (a closure taking the `Ui`). | Methods: - `fn bands(&self) -> Vec<(i64, f32, f32)>`: The rings that reach into the area: their index in the colour cycle and their inner and outer radius, from the middle outwards. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L574) ### LinearGradientTint Its children's colours pulled towards a gradient (Remotion's `linearGradientTint()`): the tint runs from `start_color` at `start` to `end_color` at `end` (0 to 1 across the area) and `amount` mixes it in, 0 unchanged and 1 the gradient's colours. The alpha stays. Drawn with an `feImage` of the gradient in a filter over the area. | Field | Type | Default | Description | | --- | --- | --- | --- | | `start` | `(f32, f32)` | `(0.0, 0.5)` | Where the gradient starts, as 0 to 1 fractions of `area` (x, y). | | `end` | `(f32, f32)` | `(1.0, 0.5)` | Where the gradient ends, as 0 to 1 fractions of `area` (x, y). | | `start_color` | `Color` | `Color::BLACK` | Colour of the gradient at `start`, held before it. | | `end_color` | `Color` | `Color::WHITE` | Colour of the gradient at `end`, held after it. | | `amount` | `Anim` | `Anim::Fixed(0.5)` | 0 the children unchanged to 1 fully the gradient's colours; their alpha stays. Animatable. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the gradient covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is tinted (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L689) ### LinearGradient A gradient filling the area (Remotion's `linearGradient()`), from `start_color` at `start` to `end_color` at `end` (0 to 1 across the area). It replaces the layer, so it takes no children: put it inside a group with the layers it covers. | Field | Type | Default | Description | | --- | --- | --- | --- | | `start` | `(f32, f32)` | `(0.0, 0.5)` | Where the gradient starts, as 0 to 1 fractions of `area` (x, y). | | `end` | `(f32, f32)` | `(1.0, 0.5)` | Where the gradient ends, as 0 to 1 fractions of `area` (x, y). | | `start_color` | `Color` | `Color::BLACK` | Colour of the gradient at `start`, held before it. | | `end_color` | `Color` | `Color::WHITE` | Colour of the gradient at `end`, held after it. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the rectangle the gradient fills from the top left, in user units. | | `key` | `Option<&str>` | `None` | Names the gradient id when two gradients share a frame. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L763) ### Vignette Darkened edges round its children (Remotion's `vignette()`). The middle up to `radius` stays clear, then the vignette grows over `feather` to `amount` (both 0 to 1, where 1 is the edge of the area seen from `center`). `roundness` 1 makes it an ellipse with the area's proportions and 0 a rectangle; in between the two shapes are mixed (Remotion mixes the distances, so the in-between shapes differ a little). `mode` lays `color` over the edges, also where the children are transparent, or fades the children out towards them. The curve is a smoothstep sampled at 16 points and drawn with gradients, so it costs no filter. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.5)` | 0 to 1 strength of the vignette at full darkness. Animatable. | | `radius` | `f32` | `0.65` | 0 to 1 distance from `center` up to which the picture stays clear, where 1 is the edge of the area. | | `center` | `(f32, f32)` | `(0.5, 0.5)` | Middle of the clear area as 0 to 1 fractions of `area` (x, y). | | `feather` | `f32` | `0.35` | 0 to 1 width over which the vignette grows from clear to full `amount`, after `radius`. | | `roundness` | `f32` | `1.0` | 0 a rectangle to 1 an ellipse with the area's proportions. | | `color` | `Color` | `Color::BLACK` | Colour laid over the edges in `Color` mode. | | `mode` | `VignetteMode` | `VignetteMode::Color` | `Color` lays `color` over the edges, also where the children are transparent; `Alpha` fades the children out towards them. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the vignette covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the gradient and mask ids when two vignettes share a frame. | | `children` | `Children` | `no_children()` | The content that is darkened (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/patterns.rs#L826) ### Speckle Small holes punched through its children (Remotion's `speckle()`): a share `density` of the cells of a grid gets a round hole `size` units across. `randomness` moves the holes off the cell centres and varies their size (0 keeps a regular grid). One cell is 2.5 holes wide; a grid finer than about 20,000 holes is drawn coarser. | Field | Type | Default | Description | | --- | --- | --- | --- | | `density` | `f32` | `0.08` | 0 to 1 share of the grid cells that get a hole. | | `size` | `f32` | `4.0` | Diameter of a hole in the children's user units; one cell is 2.5 holes wide. | | `randomness` | `f32` | `1.0` | 0 to 1: how far holes move off the cell centers and vary in size; 0 keeps a regular grid. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the holes cover from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask id when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the holes are punched through (a closure taking the `Ui`). | Methods: - `fn holes(&self) -> Vec<(f32, f32, f32)>`: The holes: their centres and radii. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/textures.rs#L216) ### Flannel Plaid cloth printed on its children (Remotion's `flannel()`): wide and fine stripes of `stripe_color` across and down a `base_color` cloth, a check `size` units wide, shaded by the children's brightness and mixed in by `amount`. `softness` blurs the stripe edges. The children's alpha stays. Remotion's thread-level weave (a pixel-sized ripple) is left out. Drawn with an `feImage` of the plaid in a filter over the area. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.7)` | 0 the children unchanged to 1 fully plaid, shaded by their brightness. Animatable. | | `size` | `f32` | `96.0` | Width in the children's user units of one wide check; the fine stripes repeat at half of it. | | `softness` | `f32` | `0.18` | 0 to 1 blur of the stripe edges; 0 is crisp. | | `base_color` | `Color` | `Color::hex("#c92f3d")` | Colour of the cloth between the stripes. | | `stripe_color` | `Color` | `Color::hex("#241015")` | Colour of the stripes; crossings are drawn darker. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the plaid covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is printed with plaid (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/textures.rs#L312) ### Burlap Coarse woven sacking over its children (Remotion's `burlap()`): strands `size` units wide run across and down, the gaps between them and blotchy `roughness` darken the picture towards `color`, mixed in by `amount`. `seed` picks the blotches. Remotion's over and under weave, strand jitter and dashes are left out. The strands are repeating gradients, so they stay sharp at any size, but keep them a few output pixels wide; finer cloth aliases. The children render twice, their alpha stays. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(0.55)` | 0 the children unchanged to 1 fully woven; at most the share `color` covers the gaps. Animatable. | | `size` | `f32` | `5.0` | Width of one strand in the children's user units; keep it a few output pixels wide or it aliases. | | `roughness` | `f32` | `0.7` | 0 to 1 strength of the blotchy darkening on top of the weave. | | `seed` | `u32` | `0` | Picks the blotches. | | `color` | `Color` | `Color::BLACK` | Colour the gaps and blotches darken the picture towards. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the cloth covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the gradient, filter and mask ids when two burlaps share a frame. | | `children` | `Children` | `no_children()` | The content that is covered with burlap, by its alpha (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/textures.rs#L449) ### Paper Paper texture on its children (Remotion's `paper()`): fibres, crumples, fine roughness and `fold_count` creases make a relief lit from the top right, which shades the picture and tints it between `color_back` (in the shade) and `color_front`, mixed in by `amount`. `contrast` is the strength of the light, `fiber`, `crumples`, `roughness` and `folds` the strength of each part and `fiber_size`, `crumple_size` their sizes, all relative to `scale` (the texture is `scale` times the area's height across five units). `seed` picks the texture and the creases. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 the children unchanged to 1 fully paper. Animatable. | | `color_front` | `Color` | `Color::hex("#9fadbc")` | Tint of the lit parts of the paper. | | `color_back` | `Color` | `Color::WHITE` | Tint of the shaded parts of the paper. | | `contrast` | `f32` | `0.3` | 0 to 1 strength of the light that shades the relief. | | `roughness` | `f32` | `0.4` | 0 to 1 strength of the fine grain. | | `fiber` | `f32` | `0.3` | 0 to 1 strength of the fibres. | | `fiber_size` | `f32` | `0.2` | Size of the fibres relative to `scale`. | | `crumples` | `f32` | `0.3` | 0 to 1 strength of the crumples. | | `crumple_size` | `f32` | `0.35` | Size of the crumples relative to `scale`. | | `folds` | `f32` | `0.65` | 0 to 1 strength of the creases. | | `fold_count` | `u32` | `5` | Number of creases; at most 64. | | `seed` | `u32` | `6` | Picks the texture and where the creases lie. | | `scale` | `f32` | `0.6` | Size of the texture: `scale` times the area's height spans five units. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the paper covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter and gradient ids when two papers share a frame. | | `children` | `Children` | `no_children()` | The content that is printed on paper (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/textures.rs#L570) ### Halftone Its children printed as a grid of dots (Remotion's `halftone()`): a dot every `dot_spacing` units (`dot_size` when `None`), as large as `dot_size` where the children are black and shrinking to nothing where they are white or transparent (the other way round with `invert`). The grid is centred on the area, turned by `rotation` degrees clockwise and moved by `offset`. Dots are `dot_color` or, with `HalftoneColor::Source`, the children's colours; they replace the children. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `HalftoneShape` | `HalftoneShape::Circle` | `Circle`, `Square`, or `Line` stripes as long as the area and as thick as a dot. | | `dot_size` | `f32` | `20.0` | Width of the largest dot in the children's user units, reached where the children are black. | | `dot_spacing` | `Option` | `None` | Distance between dot centers in user units; `None` uses `dot_size`. Below `dot_size` later dots overdraw earlier ones. | | `rotation` | `f32` | `0.0` | Degrees the grid is turned clockwise about the middle of the area. | | `offset` | `(f32, f32)` | `(0.0, 0.0)` | `(x, y)` shift of the grid in user units; it is taken off the centered position, so positive values move the dots left and up. | | `color` | `HalftoneColor` | `HalftoneColor::Solid` | `Solid` paints the dots `dot_color`; `Source` takes the children's colours under each dot. | | `dot_color` | `Color` | `Color::rgb(0xff, 0, 0)` | Colour of the dots when `color` is `Solid`; its alpha is kept. | | `invert` | `bool` | `false` | `true` makes dots grow where the children are white instead of black. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the grid covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter and gradient ids when two halftones share a frame. | | `children` | `Children` | `no_children()` | The content that is printed as dots (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/halftone.rs#L62) ### HalftoneLinearGradient Dots growing along a gradient over its children (Remotion's `halftoneLinearGradient()`): a dot every `grid_size` units, centred on the area, `first_dot_size` wide at `first_stop` growing to `second_dot_size` at `second_stop` (0 to 1 across the area). Solid dots in `dot_color` are laid over the children, only over their opaque parts with `mask_to_source_alpha`; `HalftoneColor::Source` keeps the children only inside the dots. | Field | Type | Default | Description | | --- | --- | --- | --- | | `first_dot_size` | `f32` | `0.0` | Dot width in user units at `first_stop`; 0 draws no dot there. | | `second_dot_size` | `f32` | `40.0` | Dot width in user units at `second_stop`. | | `first_stop` | `(f32, f32)` | `(0.0, 0.5)` | Where the gradient starts, as 0 to 1 fractions of `area` (x, y). | | `second_stop` | `(f32, f32)` | `(1.0, 0.5)` | Where the gradient ends, as 0 to 1 fractions of `area` (x, y). | | `grid_size` | `f32` | `24.0` | Distance between dot centers in user units; the grid is centered on the area. | | `color` | `HalftoneColor` | `HalftoneColor::Solid` | `Solid` lays dots of `dot_color` over the children; `Source` keeps the children only inside the dots. | | `dot_color` | `Color` | `Color::BLACK` | Colour of the dots when `color` is `Solid`. | | `mask_to_source_alpha` | `bool` | `false` | `true` draws solid dots only over the opaque parts of the children. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the grid covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content the dots are laid over (a closure taking the `Ui`). | Methods: - `fn dots(&self) -> Vec<(f32, f32, f32)>`: The dots `(x, y, radius)`; a grid finer than `MAX_PATTERN_SHAPES` dots is drawn coarser. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/halftone.rs#L282) ### Starburst Rays of colour from a point (Remotion's `starburst()`): `rays` (2 to 100) wedges round `origin` (0 to 1 across the area), coloured with `colors` in turn and turned `rotation` degrees clockwise; the first ray starts on the left. `smoothness` (0 to 1) blends each ray into the next over that share of a ray. It replaces the layer, so it takes no children. | Field | Type | Default | Description | | --- | --- | --- | --- | | `rays` | `u32` | `12` | Number of wedges round `origin`, 2 to 100. | | `colors` | `Vec` | `vec![theme.colors.accent, theme.colors.background]` | Colours of the wedges, used in turn; empty draws nothing. | | `rotation` | `f32` | `0.0` | Degrees the rays are turned clockwise; the first ray starts on the left. | | `smoothness` | `f32` | `0.0` | 0 to 1: the share of a ray over which it blends into the next; 0 for hard edges. | | `origin` | `(f32, f32)` | `(0.5, 0.5)` | Where the rays meet, as 0 to 1 fractions of `area` (x, y). | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the rays fill from the top left, in pixels of the canvas. | | `key` | `Option<&str>` | `None` | Names the clip id when two starbursts share a frame. | Methods: - `fn wedges(&self) -> Vec<(f32, f32, usize)>`: The rays as `(start, end, colour index)`, in degrees clockwise from the right. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/light_leak.rs#L302) ### LightTrail A streak of light trailing its children (Remotion's `lightTrail()`): copies of their silhouette in `color`, `distance` units long towards `direction` (degrees clockwise from the right, 180 to the left), each copy `decay` (0 to 1) as bright as the one before, added to the children at `intensity`. `samples` (1 to 64) sets how Remotion spaces the copies, so the falloff and brightness follow it; `threshold` (0 to 1) leaves out pixels less opaque than it. Drawn inside the `area` from the origin. | Field | Type | Default | Description | | --- | --- | --- | --- | | `direction` | `f32` | `180.0` | Degrees clockwise from the right the trail points: 180 trails to the left. | | `distance` | `Anim` | `Anim::Fixed(80.0)` | Length of the trail in the children's user units; 0 or less draws none. Animatable. | | `intensity` | `f32` | `1.0` | Brightness of the trail added to the children; 1 is Remotion's default, 0 draws none. | | `decay` | `f32` | `0.9` | 0 to 1: how bright each copy is compared with the one before it; lower fades faster. | | `threshold` | `f32` | `0.0` | 0 to 1: pixels less opaque than this leave no trail; 0 trails every pixel. | | `samples` | `u32` | `32` | 1 to 64: how Remotion spaces the copies; the falloff and brightness follow it. | | `color` | `Color` | `Color::WHITE` | Colour of the trail; its alpha scales the brightness. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the trail is drawn in from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that leaves the trail (a closure taking the `Ui`). | Methods: - `fn gain(&self) -> f32`: How bright the trail of a solid silhouette is, before the colour's alpha: Remotion's sum of the sample weights over their count, times `intensity`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/light_leak.rs#L173) ### Mirror Its children reflected about a line (Remotion's `mirror()`): the part past `position` (0 to 1 across the area; the right, or the bottom with `MirrorDirection::Vertical`) shows the other side mirrored, or the other way round with `invert`. Where the mirrored side reaches past the area, Remotion stretches the edge pixels; here it stays transparent. The children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `direction` | `MirrorDirection` | `MirrorDirection::Horizontal` | `Horizontal` reflects about a vertical line (left and right match), `Vertical` about a horizontal line. | | `position` | `Anim` | `Anim::Fixed(0.5)` | Where the mirror line is, 0 to 1 across the area (from the left, or from the top for `Vertical`). Animatable. | | `invert` | `bool` | `false` | `false` keeps the near side and mirrors it onto the far side; `true` does the opposite. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the effect covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask and clip ids when two mirrors share a frame. | | `children` | `Children` | `no_children()` | The content that is mirrored (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/geometry.rs#L38) ### Tile Its children repeated across the area like tiles, every other copy mirrored so the edges meet (Remotion's `tile()`): the copies of `rect` (x, y, width, height) are laid side by side with `horizontal` and above and below with `vertical`. Remotion finds the rectangle from the opaque pixels; here it is given, as fframes cannot measure the children. At most `MAX_TILES` copies are drawn, each twice: a slightly larger one under the exact ones hides the seams between them. The children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `rect` | `(f32, f32, f32, f32)` | `(640.0, 360.0, 640.0, 360.0)` | The `(x, y, width, height)` rectangle of the children that is repeated, in the children's user units; it stays where it is. | | `horizontal` | `bool` | `true` | `true` repeats the rectangle side by side across the area. | | `vertical` | `bool` | `true` | `true` repeats the rectangle above and below across the area. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the copies fill from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask, clip and tile ids when two tiles share a frame. | | `children` | `Children` | `no_children()` | The content that is tiled (a closure taking the `Ui`). | Methods: - `fn copies(&self) -> Vec<(i64, i64)>`: The copies as (column, row) counted from the original, at most `MAX_TILES`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/geometry.rs#L135) ### Skew Its children slanted (Remotion's `skew()`): `x` degrees along the horizontal, then `y` along the vertical, like CSS `skewX` and `skewY`, about `origin` (0 to 1 across the area). Cut to the area. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `Anim` | `Anim::Fixed(20.0)` | Degrees of slant along the horizontal, like CSS `skewX`. Animatable. | | `y` | `Anim` | `Anim::Fixed(0.0)` | Degrees of slant along the vertical, like CSS `skewY`, applied after `x`. Animatable. | | `origin` | `(f32, f32)` | `(0.5, 0.5)` | Fixed point of the slant, as 0 to 1 fractions of `area` (x, y); `(0.5, 0.5)` is the middle. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the result is cut to from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip id when two skews share a frame. | | `children` | `Children` | `no_children()` | The content that is slanted (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/geometry.rs#L255) ### Scale Its children scaled by `scale` about the middle of the area (Remotion's `scale()`), only across with `vertical` off and only up and down with `horizontal` off. Cut to the area. | Field | Type | Default | Description | | --- | --- | --- | --- | | `scale` | `Anim` | `Anim::Fixed(1.0)` | Scale factor about the middle of the area: 1 unchanged, 2 twice as big. Animatable; negative values count as 0. | | `horizontal` | `bool` | `true` | `false` leaves the width unscaled. | | `vertical` | `bool` | `true` | `false` leaves the height unscaled. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the result is cut to from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip id when two scales share a frame. | | `children` | `Children` | `no_children()` | The content that is scaled (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/geometry.rs#L308) ### Translate Its children moved by `x` and `y` (Remotion's `xyTranslate()`), or with `uv` by those shares of the area, 1 the full width or height (Remotion's `uvTranslate()`). Cut to the area. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `Anim` | `Anim::Fixed(0.0)` | Horizontal shift in the children's user units (right is positive), or with `uv` a share of the area's width. Animatable. | | `y` | `Anim` | `Anim::Fixed(0.0)` | Vertical shift in the children's user units (down is positive), or with `uv` a share of the area's height. Animatable. | | `uv` | `bool` | `false` | `true` reads `x` and `y` as shares of the area's width and height (1 is the full size); `false` as user units. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the result is cut to from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the clip id when two translates share a frame. | | `children` | `Children` | `no_children()` | The content that is moved (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/geometry.rs#L362) ### VenetianBlinds Its children shown through blinds that open (Remotion's `venetianBlinds()`): `slats` strips across the area, each showing a band from its middle that is `progress` (0 hidden to 1 open) of the strip wide. | Field | Type | Default | Description | | --- | --- | --- | --- | | `progress` | `Anim` | `Anim::Fixed(0.5)` | 0 hidden to 1 fully open: the share of each slat's width that shows. Animatable. | | `direction` | `BlindsDirection` | `BlindsDirection::Vertical` | Whether the slats stand upright side by side or lie one above the other. | | `slats` | `u32` | `12` | Number of strips across the area; at least 1. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the blinds cover from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content shown through the blinds (a closure taking the `Ui`). | Methods: - `fn bands(&self, progress: f32) -> Vec<(f32, f32, f32, f32)>`: The open bands as `(x, y, w, h)` at `progress`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/reveal.rs#L30) ### Evolve Its children revealed by an edge sweeping across the area (Remotion's `evolve()`): from the `direction` side, `progress` (0 hidden to 1 shown) of the way across, the edge softened over `feather` (0 to 1) of the area behind it. | Field | Type | Default | Description | | --- | --- | --- | --- | | `progress` | `Anim` | `Anim::Fixed(0.5)` | 0 hidden to 1 fully shown: how far the edge has swept across the area. Animatable. | | `direction` | `EvolveDirection` | `EvolveDirection::Left` | The side of the area the reveal starts from. | | `feather` | `f32` | `0.1` | 0 to 1 share of the area over which the edge softens behind `progress`; 0 for a hard edge. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the sweep covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the mask when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is revealed (a closure taking the `Ui`). | Methods: - `fn stops(&self, progress: f32) -> Vec<(f32, f32)>`: The mask along the sweep as `(offset, opacity)` stops at `progress`: shown up to `progress`, then a smoothstep down to hidden over `feather`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/reveal.rs#L114) ### TvSignalOff Its children replaced by television test bars (Remotion's `tvSignalOff()`), mixed in by `amount` (0 to 1) inside the children's alpha: seven colour bars, a row of thin bars, white running into a dark ramp and a grey scale. The bars are drawn over the children, so semi-transparent parts mix a little differently from Remotion. The children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `amount` | `Anim` | `Anim::Fixed(1.0)` | 0 the children only to 1 fully test bars, inside the children's alpha. Animatable. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the test picture from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filter ids when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that the test bars replace (a closure taking the `Ui`). | Methods: - `fn bars(&self) -> Vec<(f32, f32, f32, f32, Color)>`: The flat parts of the test picture as `(x, y, w, h, colour)`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/reveal.rs#L232) ### RegionBlur Its children blurred inside a rectangle (Remotion's `regionBlur()`): from `top_left` to `bottom_right` (0 to 1 across the area), blurred by `radius` like Remotion's (a Gaussian of a third of it), the edge softened over `feather` units and the corners rounded by `roundness` (0 square to 1 fully round). The blurred copy is laid over the sharp children, so the children render twice. | Field | Type | Default | Description | | --- | --- | --- | --- | | `top_left` | `(f32, f32)` | `(0.25, 0.25)` | Top left corner of the blurred rectangle, as 0 to 1 fractions of `area`. | | `bottom_right` | `(f32, f32)` | `(0.75, 0.75)` | Bottom right corner of the blurred rectangle, as 0 to 1 fractions of `area`. | | `radius` | `Anim` | `Anim::Fixed(40.0)` | Blur radius like Remotion's (a Gaussian of a third of it); 0 or less shows the children unblurred. Animatable. | | `feather` | `f32` | `0.0` | Width in `area` units over which the rectangle's edge softens; 0 for a hard edge. | | `roundness` | `f32` | `0.0` | Corner rounding: 0 square to 1 fully round. | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the effect covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filters when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is blurred inside the rectangle (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/progressive_blur.rs#L84) ### LinearProgressiveBlur Its children blurred more and more along a line (Remotion's `linearProgressiveBlur()`): `start_blur` at `start`, growing to `end_blur` at `end` (both 0 to 1 across the area), radii like Remotion's (a Gaussian of a third of them). Drawn as blurred copies under graded masks, one for every 6 units of radius and at most `MAX_BLUR_LEVELS`, so the children render up to nine times. | Field | Type | Default | Description | | --- | --- | --- | --- | | `start` | `(f32, f32)` | `(0.0, 0.5)` | Where the blur starts, as 0 to 1 fractions of `area` (x, y); it has `start_blur` there. | | `end` | `(f32, f32)` | `(1.0, 0.5)` | Where the blur ends, as 0 to 1 fractions of `area` (x, y); it has `end_blur` there. | | `start_blur` | `f32` | `0.0` | Blur radius at `start`, like Remotion's (a Gaussian of a third of it); 0 is sharp. | | `end_blur` | `f32` | `50.0` | Blur radius at `end`, like Remotion's (a Gaussian of a third of it). | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the effect covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filters when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is blurred (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/progressive_blur.rs#L157) ### RadialProgressiveBlur Its children blurred more and more away from a point (Remotion's `radialProgressiveBlur()`): `start_blur` inside `start` (0 the centre to 1 the ellipse), growing to `end_blur` on and outside an ellipse round `center`, `width` by `height` (shares of the area) and turned `rotation` degrees. Radii are like Remotion's (a Gaussian of a third of them). Drawn as blurred copies under graded masks, one for every 6 units of radius and at most `MAX_BLUR_LEVELS`, so the children render up to nine times. | Field | Type | Default | Description | | --- | --- | --- | --- | | `center` | `(f32, f32)` | `(0.5, 0.5)` | Center of the ellipse as 0 to 1 fractions of `area` (x, y). | | `width` | `f32` | `1.0` | Width of the ellipse as a share of the area's width, 1 spanning it. | | `height` | `f32` | `1.0` | Height of the ellipse as a share of the area's height, 1 spanning it. | | `rotation` | `f32` | `0.0` | Degrees the ellipse is turned. | | `start` | `f32` | `0.0` | 0 to 1 share of the ellipse inside which the blur stays `start_blur`: 0 only at the centre, 1 all the way to the ellipse. | | `start_blur` | `f32` | `0.0` | Blur radius inside `start`, like Remotion's (a Gaussian of a third of it); 0 is sharp. | | `end_blur` | `f32` | `50.0` | Blur radius on and outside the ellipse, like Remotion's (a Gaussian of a third of it). | | `area` | `(f32, f32)` | `(1920.0, 1080.0)` | Width and height of the area the effect covers from the top left, in the children's user units. | | `key` | `Option<&str>` | `None` | Names the filters when two of these effects share a frame. | | `children` | `Children` | `no_children()` | The content that is blurred (a closure taking the `Ui`). | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/effects/progressive_blur.rs#L225) --- # Overlays > Overlays sit on top of a scene: who is speaking, what is said, where in the video we are. Page: https://fframes-kit.pages.dev/docs/media-and-overlays/overlays/ Overlays sit on top of a scene: who is speaking, what is said, where in the video we are. They take their times from the container they are shown in (`ui.seconds()` and `ui.duration`), so the same lower third works in any scene or `Sequence`, and the ones that leave do so before the container ends. | Component | Props | What it does | | --- | --- | --- | | `Appear` | `Appear::new(ui, from, center, children)`, then `until`, `rise` (60), `scale_from` (0.92), `enter`, `exit`, `rate_func` | children rise, grow about `center` and fade in at `from` seconds, and leave upward at `until` (or the end of the container); inside, time starts at `from` | | `LowerThird` | `name`, `role`, `x`, `y`, `name_style`, `role_style`, `name_fill`, `role_fill`, `bar`, `bar_width` (6), `background: Option`, `min_width`, `padding`, `delay`, `stagger`, `run_time`, `exit`, `rate_func` | an accent bar grows, then the name and the role slide out from behind it; all of it leaves in reverse order before the end. With `background`, a plate as wide as the longer line | | `Captions` | `file`, `x`, `y`, `width`, `style`, `fill`, `align`, `video_time` (true) | the cue of a WebVTT file that plays now, wrapped into `width`; cue times are video seconds, or container seconds with `video_time: false` | | `WordCaptions` | `pages: Vec`, `x`, `y`, `width`, `max_lines` (2), `style` (title), `fill`, `highlight` (accent), `pop` (1.12), `fade` (0.08 s), `uppercase`, `pill: Option`, `pill_opacity`, `padding`, `entrance` (`PageEntrance::Pop`, `Fade`, `Rise`, `None`), `enter`, `exit` | TikTok-style captions: the page that plays now, centred on `(x, y)`, the word being said in `highlight` and grown by `pop`, its neighbours moved aside; a page that needs more than `max_lines` lines shrinks. Times are milliseconds of the container | | `CaptionStack` | `items: Vec` (`Caption::new(at, tag, line)`), `x`, `y`, `gap`, `tag_style`, `line_style`, `tag_fill`, `line_fill`, `tag_spacing`, `rise`, `enter`, `leave`, `rate_func` | timed captions without a file: each rises in at its `at` and leaves upward while the next comes in; the last stays | | `ChapterList` | `chapters: Vec<(f32, Cow)>`, `x`, `y`, `width`, `row_height`, `gap`, `style`, `fill`, `active_fill`, `highlight`, `radius`, `spring` | an agenda whose highlight springs to the chapter playing now | | `Annotation` | `target`, `label`, `text`, `color`, `style`, `letter_spacing`, `stroke_width`, `delay`, `run_time`, `rate_func` | a dot on `target`, an elbow line to `label` and the text at its end; the line draws on, the text fades in | | `ProgressBar` | `x`, `y`, `width`, `height` (8), `radius`, `track`, `fill`, `progress: Option` | how far the container is (`None`), or a given value from 0 to 1 | | `Check` | `x`, `y`, `size`, `color`, `stroke_width` (5), `delay`, `run_time`, `rate_func` | a check mark that draws itself on | Gallery scenes: `Appear`, `LowerThird`, `Captions`, `CaptionStack`, `ChapterList`, `Annotation`, `ProgressBar`, `Check`. Gallery clip of `Appear`: https://fframes-kit.pages.dev/media/scenes/Appear-6a73b190.mp4 Gallery clip of `LowerThird`: https://fframes-kit.pages.dev/media/scenes/LowerThird-692ce2fb.mp4 Gallery clip of `Captions`: https://fframes-kit.pages.dev/media/scenes/Captions-decfb3bc.mp4 Gallery clip of `CaptionStack`: https://fframes-kit.pages.dev/media/scenes/CaptionStack-8fe1319e.mp4 Gallery clip of `ChapterList`: https://fframes-kit.pages.dev/media/scenes/ChapterList-e8e1bf6e.mp4 Gallery clip of `Annotation`: https://fframes-kit.pages.dev/media/scenes/Annotation-279b2abc.mp4 Gallery clip of `ProgressBar`: https://fframes-kit.pages.dev/media/scenes/ProgressBar-b9371738.mp4 Gallery clip of `Check`: https://fframes-kit.pages.dev/media/scenes/Check-614084d2.mp4 `Annotation` is the HUD callout with a line; `Callout` (under Shapes) is the speech bubble. `Captions` has no glow of its own: wrap it in a `` with the `Glow` decoration. ```rust use fframes_kit::prelude::*; fn annotations<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let appear = Appear::new(ui, 0.5, (960.0, 540.0), children(|ui| { ui.show(Check { x: 960.0, y: 540.0, size: 80.0, ..ui.defaults() }) })); let appear = ui.show(appear); let note = ui.show(Annotation { target: (960.0, 540.0), label: (1200.0, 400.0), text: "Ready".into(), ..ui.defaults() }); let captions = ui.show(CaptionStack { items: vec![Caption::new(0.0, "01", "First"), Caption::new(2.0, "02", "Next")], x: 200.0, y: 800.0, ..ui.defaults() }); svgr!({appear}{note}{captions}) } ``` A speaker introduced over the first four seconds of a scene, with captions from a file under it and the video's progress at the bottom: ```rust use fframes_kit::prelude::*; fn speaker<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let intro = Sequence { from: 0.5, duration: Some(4.0), children: children(|ui| { ui.show(LowerThird { name: "Ada Lovelace".into(), role: "Analyst".into(), ..ui.defaults() }) }) }; let intro = ui.show(intro); let captions = ui.show(Captions { file: "talk.vtt", y: 980.0, ..ui.defaults() }); let progress = ui.show(ProgressBar { y: 1064.0, height: 4.0, ..ui.defaults() }); svgr!({intro}{captions}{progress}) } let list = ChapterList { chapters: vec![(0.0, "Intro".into()), (5.0, "Demo".into())], ..ChapterList::default() }; assert_eq!(list.active(6.0), 1); ``` ### Word captions and voiceover timing Word timings are data you bring. Nothing here speaks, records or recognises speech. - **Caption files.** - `CaptionWord { text, start_ms, end_ms, timestamp_ms, confidence }` is Remotion's `Caption`. - `text::parse_srt` and `text::parse_vtt` read the cues of a subtitle file: one `CaptionWord` each, trimmed, with confidence 1. - `text::serialize_srt(&lines)` writes one cue per line of words. - `text::from_whisper_json` reads the words of openai-whisper (`segments[].words[]` or `words[]`, in seconds) and of whisper.cpp (`transcription[]` with `offsets` in milliseconds; run it with `-ml 1` for one word per entry). Transcript words keep their leading space: `" world"`. - **Pages.** `overlay::tiktok_pages(&words, combine_within_ms)` is `createTikTokStyleCaptions`. A word that starts with a space starts a new page once the page has run for `combine_within_ms`. Subtitle cues have no leading space, so their pages break only between voiceover blocks. - **Voiceover.** `VoiceoverScript` is manim-voiceover's timing. - A block is `VoiceoverBlock::new(text, audio, words)`: a recorded file and its words. - `` in the text marks a moment. It is timed at the first word after it, counted in letters, so a transcript that splits words differently still lines up. - `VoiceoverScript::in_sequence(blocks, start, gap)` plays the blocks one after the other; `VoiceoverScript::new` plays them at their own `start`. - It gives: - `block(i)`: start, duration and end; - `bookmark("A")`, `time_until_bookmark("A", t)` and `remaining(i, t)`; - `subcaptions()`: the words in container time, for `tiktok_pages`; - `audio_tracks()`, for `fn audio`; - `sequence(i, children)`: a `Sequence` as long as the block. For a `Series`, give an item `block(i).duration`. Times are seconds of the container the script is used in (milliseconds for the words). Give the video's `fn audio` the tracks of a script that runs on video time. Gallery scenes: `WordCaptions` and `Voiceover`. Gallery clip of `WordCaptions`: https://fframes-kit.pages.dev/media/scenes/WordCaptions-71c58692.mp4 Gallery clip of `Voiceover`: https://fframes-kit.pages.dev/media/scenes/Voiceover-67368fd4.mp4 ```rust use fframes_kit::{overlay::tiktok_pages, prelude::*, text::from_whisper_json}; /// Narration with a dot that appears at its bookmark and the words under it. struct Narrated { script: VoiceoverScript<'static>, pages: Vec, } impl Narrated { fn new(transcript: &str) -> Self { let words = from_whisper_json(transcript).unwrap_or_default(); let block = VoiceoverBlock::new("Here is a dot.", "voice.wav", words); let script = VoiceoverScript::in_sequence(vec![block], 0.5, 0.0); let pages = tiktok_pages(&script.subcaptions(), 1200.0); Self { script, pages } } fn draw<'a>(&self, ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let at = self.script.bookmark("dot").unwrap_or(0.0); let opacity = motion::progress(ui.seconds(), at, at + 0.3); let captions = ui.show(WordCaptions { pages: self.pages.clone(), ..ui.defaults() }); svgr!({captions}) } fn audio(&self) -> AudioMap<'static> { AudioMap::from(self.script.audio_tracks()) } } let narrated = Narrated::new( r#"{"words": [{"word": " Here", "start": 0.0, "end": 0.3}, {"word": " is", "start": 0.3, "end": 0.45}, {"word": " a", "start": 0.6, "end": 0.7}, {"word": " dot.", "start": 0.7, "end": 1.1}]}"#, ); assert_eq!(narrated.script.bookmark("dot"), Some(1.1)); assert_eq!(narrated.pages.len(), 1); let _ = narrated.audio(); ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Appear Shows its children from `from` seconds on: they rise `rise` units into place, grow from `scale_from` about `center` and fade in over `enter` seconds. They leave the same way up over `exit` seconds, ending at `until` seconds, or at the end of the container (`ui.duration`) when `until` is `None`. Inside, time starts at `from`, as in a `Sequence`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `f32` | — | Seconds after the container's start when the children appear; inside, their time starts at `from`. | | `until` | `Option` | — | Seconds when the children are gone, after leaving over `exit`; `None` ends with the container. | | `center` | `(f32, f32)` | — | The point the content scales about, in canvas units. | | `rise` | `f32` | — | Pixels the children rise into place as they enter, and sink as they leave. | | `scale_from` | `f32` | — | Scale the children grow from as they enter, 1 for no growth. | | `enter` | `f32` | — | Seconds the children take to fade, rise and grow in. | | `exit` | `f32` | — | Seconds the children take to leave before `until` or the end of the container; 0 cuts them off. | | `rate_func` | `RateFunc` | — | Easing of the entrance over `enter` seconds. | | `children` | `Children` | — | What it shows (a closure taking the `Ui`). | Methods: - `fn new(ui: &Ui, from: f32, center: (f32, f32), children: Children) -> Self`: Times and motion from the theme: `motion.duration.enter` and `exit`, a 60 unit rise, scaling from 0.92. - `fn state(&self, t: f32, end: Option) -> (f32, f32)`: `(entered, left)`, both from 0 to 1, at `t` seconds of a container that ends at `end`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/appear.rs#L15) ### LowerThird A name and a role next to an accent bar, the caption of a speaker. The bar grows first, then the name and the role slide in from behind it, `stagger` seconds apart, and all of it leaves in reverse order before the end of the container. `(x, y)` is the top left of the bar. With `background`, a plate as wide as the longer line sits behind the text (without a measurable font it is `min_width` wide). | Field | Type | Default | Description | | --- | --- | --- | --- | | `name` | `Cow` | `Cow::Borrowed("")` | The speaker's name, the larger first line. | | `role` | `Cow` | `Cow::Borrowed("")` | The role or subtitle under the name; empty for none. | | `x` | `f32` | `96.0` | Left edge of the bar, in pixels of the canvas. | | `y` | `f32` | `820.0` | Top of the bar, in pixels of the canvas. | | `name_style` | `TextStyle` | `theme.typography.heading` | The font of the name: family, size in pixels and weight. | | `role_style` | `TextStyle` | `theme.typography.body` | The font of the role: family, size in pixels and weight. | | `name_fill` | `Color` | `theme.colors.title` | Colour of the name. | | `role_fill` | `Color` | `theme.colors.muted` | Colour of the role. | | `bar` | `Color` | `theme.colors.accent` | Colour of the accent bar. | | `bar_width` | `f32` | `6.0` | Width of the accent bar in pixels. | | `background` | `Option` | `None` | Colour of the plate behind the text; `None` for no plate. | | `min_width` | `f32` | `400.0` | Least width in pixels of the text area on the plate, and its width when the font cannot be measured. | | `padding` | `f32` | `theme.spacing.md` | Space between the bar and the text, and around the text on the plate. | | `delay` | `f32` | `0.0` | Seconds after the container's start before the bar begins to grow. | | `stagger` | `f32` | `theme.motion.stagger.loose` | Seconds between the starts of the bar, the name and the role. | | `run_time` | `f32` | `theme.motion.duration.enter` | Seconds each part takes to come in. | | `exit` | `f32` | `theme.motion.duration.exit` | Seconds of the exit, which ends with the container; 0 never leaves. | | `rate_func` | `RateFunc` | `theme.motion.easing.enter` | Easing of each part coming in. | Methods: - `fn height(&self) -> f32`: Height of the bar: the two lines and the padding around them. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/lower_third.rs#L20) ### Captions The cue of a subtitle (`.vtt`) file that plays now, wrapped into `width` pixels. Cue times are seconds of the video, or of the container with `video_time: false`. A missing file draws nothing (and `inspect` reports it). | Field | Type | Default | Description | | --- | --- | --- | --- | | `file` | `&str` | `""` | A subtitle file of the video's media. | | `x` | `f32` | `160.0` | Left edge of the caption box, in pixels of the canvas. | | `y` | `f32` | `900.0` | Baseline of the first line, in pixels of the canvas; later lines go below. | | `width` | `f32` | `1600.0` | Width in pixels the cue wraps into; it is aligned inside this width. | | `style` | `TextStyle` | `theme.typography.heading` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.caption` | Colour of the text. | | `align` | `TextAlign` | `TextAlign::Center` | How the lines sit inside `width`: left, center or right. | | `video_time` | `bool` | `true` | `true` reads cue times as seconds of the whole video; `false` as seconds of the container. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/captions.rs#L18) ### WordCaptions TikTok-style captions: the page of `pages` that plays now, centred on `(x, y)` and wrapped into `width` pixels, with the word being said in `highlight`, scaled by `pop`. Times are milliseconds of the container. A page that needs more than `max_lines` lines is shrunk until it fits. Between pages and after the last one nothing is drawn. | Field | Type | Default | Description | | --- | --- | --- | --- | | `pages` | `Vec` | `Vec::new()` | The pages of words, each with its own timing, as made by `tiktok_pages`; one shows at a time. | | `x` | `f32` | `960.0` | Horizontal center of the caption, in pixels of the canvas. | | `y` | `f32` | `860.0` | Vertical center of the caption block, in pixels of the canvas. | | `width` | `f32` | `1400.0` | Width in pixels the words wrap into. | | `max_lines` | `usize` | `2` | Most lines a page may take; a longer page is shrunk until it fits. | | `style` | `TextStyle` | `theme.typography.title` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.caption` | Colour of the words that are not being said. | | `highlight` | `Color` | `theme.colors.accent` | Colour of the word being said. | | `pop` | `f32` | `1.12` | The size of the word being said, 1 for none. | | `fade` | `f32` | `0.08` | Seconds a word takes to light up and to fade back. | | `uppercase` | `bool` | `false` | `true` writes every word in capitals. | | `pill` | `Option` | `None` | A rounded box behind the page, in this colour. | | `pill_opacity` | `f32` | `0.75` | Opacity of the `pill`, 0 transparent to 1 opaque. | | `padding` | `f32` | `24.0` | Space between the text and the edge of the pill. | | `entrance` | `PageEntrance` | `PageEntrance::Pop` | How a page comes in over `enter` seconds. | | `enter` | `f32` | `0.15` | Seconds of the entrance. | | `exit` | `f32` | `0.1` | Seconds the page takes to fade out before the next one starts, or the last one ends. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/word_captions.rs#L106) ### CaptionStack Timed captions without a subtitle file: each `Caption` rises `rise` units into place at its `at` seconds and leaves upward while the next one comes in. The last stays. `(x, y)` is the baseline of the tag; the line is `gap` pixels below it. | Field | Type | Default | Description | | --- | --- | --- | --- | | `items` | `Vec` | `Vec::new()` | The cards, in order of `at`; each shows from its `at` seconds. | | `x` | `f32` | `96.0` | Left edge of the tag and line, in pixels of the canvas. | | `y` | `f32` | `860.0` | Baseline of the tag in pixels of the canvas; the line sits `gap` below. | | `gap` | `f32` | `88.0` | Pixels from the tag's baseline to the line's baseline. | | `tag_style` | `TextStyle` | `theme.typography.caption` | The font of the small tag: family, size in pixels and weight. | | `line_style` | `TextStyle` | `theme.typography.heading` | The font of the line: family, size in pixels and weight. | | `tag_fill` | `Color` | `theme.colors.accent` | Colour of the tag. | | `line_fill` | `Color` | `theme.colors.title` | Colour of the line. | | `tag_spacing` | `f32` | `4.0` | Extra space between letters of the tag, in pixels. | | `rise` | `f32` | `40.0` | Pixels a card rises into place as it enters (and about three quarters of that as it leaves). | | `enter` | `f32` | `theme.motion.duration.enter` | Seconds a card takes to fade and rise in. | | `leave` | `f32` | `theme.motion.duration.exit` | Seconds before the next caption's `at` that this one starts to leave. | | `rate_func` | `RateFunc` | `theme.motion.easing.enter` | Easing of a card rising into place. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/captions.rs#L107) ### ChapterList A list of chapters with a highlight that springs to the chapter playing now (an agenda, the steps of a tutorial). Each chapter is `(start seconds, title)`, in order; before the first start the highlight is on the first row. `(x, y)` is the top left of the list. | Field | Type | Default | Description | | --- | --- | --- | --- | | `chapters` | `Vec<(f32, Cow)>` | `Vec::new()` | Each chapter as `(start in seconds, title)`, in order; the highlight moves to a chapter when it starts. | | `x` | `f32` | `96.0` | Left edge of the list, in pixels of the canvas. | | `y` | `f32` | `240.0` | Top of the first row, in pixels of the canvas. | | `width` | `f32` | `720.0` | Width of the highlight bar in pixels. | | `row_height` | `f32` | `72.0` | Height of one row, and of the highlight, in pixels. | | `gap` | `f32` | `12.0` | Pixels between two rows. | | `style` | `TextStyle` | `theme.typography.body` | The font: family, size in pixels and weight. | | `fill` | `Color` | `theme.colors.muted` | Colour of the titles that are not playing. | | `active_fill` | `Color` | `theme.colors.title` | Colour of the title of the chapter playing now. | | `highlight` | `Color` | `theme.colors.surface` | Colour of the bar behind the current chapter. | | `radius` | `f32` | `theme.radius.md` | Corner radius of the highlight bar in pixels. | | `spring` | `(f32, f32)` | `(130.0 / 0.45, 20.0 / 0.45)` | `(stiffness, damping)` of the highlight's mass-1 spring. | Methods: - `fn active(&self, t: f32) -> usize`: The chapter playing at `t`. - `fn highlight_row(&self, t: f32) -> f32`: The top of the highlight at `t`, in rows from the first (between two rows while it moves). [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/chapters.rs#L17) ### Annotation An annotation: a dot on the `target`, an elbow line to `label` (first across, then up or down) and the text at its end. The line draws on over `run_time` seconds from `delay`; the text fades in over the last 40 %. (The shape called `Callout` is the Remotion speech bubble, so this one has another name.) | Field | Type | Default | Description | | --- | --- | --- | --- | | `target` | `(f32, f32)` | `(0.0, 0.0)` | The point the dot marks, in pixels of the canvas. | | `label` | `(f32, f32)` | `(0.0, 0.0)` | Where the line ends and the text sits, in pixels of the canvas; the text goes right of it when it is right of `target`, else left. | | `text` | `Cow` | `Cow::Borrowed("")` | The words at the end of the line. | | `color` | `Color` | `theme.colors.accent` | Colour of the dot, line and text. | | `style` | `TextStyle` | `theme.typography.caption` | The font: family, size in pixels and weight. | | `letter_spacing` | `f32` | `2.0` | Extra space between letters of the text, in pixels. | | `stroke_width` | `f32` | `theme.stroke.thin` | Width of the line and the ring around the dot, in pixels. | | `delay` | `f32` | `0.0` | Seconds after the container's start before it begins. | | `run_time` | `f32` | `theme.motion.duration.slow` | Seconds the line takes to draw on; the text fades in over the last 40 %. | | `rate_func` | `RateFunc` | `theme.motion.easing.standard` | Easing of the drawing over its run time. | Methods: - `fn draw(self, progress: f32) -> Svgr`: The annotation `progress` (0 to 1) of the way drawn. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/annotation.rs#L18) ### ProgressBar A bar that fills from the left: with `progress: None` it shows how far the container is through `ui.duration` (a video progress bar), otherwise the given value from 0 to 1. The filled part is never thinner than half a pixel, so it is visible from the start. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `96.0` | Left edge of the bar in pixels of the canvas. | | `y` | `f32` | `1000.0` | Top of the bar in pixels of the canvas. | | `width` | `f32` | `1728.0` | Full width of the track in pixels. | | `height` | `f32` | `8.0` | Height of the bar in pixels. | | `radius` | `f32` | `0.0` | Corner radius of the track and the filled part in pixels; 0 for square corners. | | `track` | `Color` | `theme.colors.surface` | Colour of the unfilled track behind the bar. | | `fill` | `Color` | `theme.colors.accent` | Colour of the filled part. | | `progress` | `Option` | `None` | How full the bar is, 0 to 1; `None` follows how far the container is through `ui.duration`. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/progress.rs#L15) ### Check A check mark that draws itself on from `delay` seconds over `run_time`. `(x, y)` is the start of the short stroke; the mark is 27 by 20 units times `size`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `0.0` | Start of the short stroke, in pixels of the canvas. | | `y` | `f32` | `0.0` | Start of the short stroke, in pixels of the canvas. | | `size` | `f32` | `1.0` | Scale of the mark, which is 27 by 20 pixels at 1. | | `color` | `Color` | `theme.colors.accent` | Colour of the mark. | | `stroke_width` | `f32` | `5.0` | Width of the mark's line in pixels. | | `delay` | `f32` | `0.0` | Seconds after the container's start before it begins. | | `run_time` | `f32` | `theme.motion.duration.base` | Seconds the animation takes. | | `rate_func` | `RateFunc` | `theme.motion.easing.standard` | Easing of the drawing over its run time. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/overlay/progress.rs#L75) --- # Data, audio, decoration and media > Gallery scenes: BarChart, SpectrumBars, Waveform, DrawOn, CornerMarks, Glow, DropShadow, PhotoFrame, KenBurns. Page: https://fframes-kit.pages.dev/docs/media-and-overlays/data-audio-decoration-and-media/ | Component | Props | What it does | | --- | --- | --- | | `BarChart` | `bars: Vec<(Cow, f64)>`, `x`, `y` (baseline), `bar_width` (126), `gap` (62), `max_height` (330), `max_value`, `radius`, `fill`, `highlight: Option`, `highlight_fill`, `value_style`, `value_fill`, `label_style`, `label_fill`, `decimals`, `delay`, `stagger`, `run_time`, `rate_func` | bars grow one after another, each value counting up above its bar and its label below; a bar starts as a hairline | | `SpectrumBars` | `values`, `x`, `y`, `width`, `bar_width`, `gain` (60), `min_height`, `max_height` (720), `radius` (`None`: round ends), `fill`, `align` (`BarAlign::Center`, `Bottom`) | one rounded bar per value, `value * gain` pixels high; NaN draws the shortest bar | | `Waveform` | `values`, `x`, `y` (baseline), `width`, `gain` (1200), `max_height` (600), `fill`, `stroke`, `stroke_width`, `opacity` | a smooth closed wave through the values, starting and ending on the baseline (`audio::wave_path` gives the path data) | | `DrawOn` | `DrawOn::new(ui, shape)`, then `delay`, `run_time`, `rate_func` | `Create` on any `Shape` you built: an underline, a tick, a hand-drawn arrow draws on from its start | | `CornerMarks` | `x`, `y`, `width`, `height`, `arm` (24), `color`, `stroke_width`, `enter: Option` | crop-mark corners around a rectangle, static or drawn on with `enter` | | `Glow` | `id` (`kit-glow`), `shadow` (`theme.shadow.glow`) | a filter definition; draw it in ``, then `filter={glow.url()}` | | `DropShadow` | `id` (`kit-card`), `shadow` (`theme.shadow.card`) | the same for the drop shadow under a card | | `PhotoFrame` | `image`, `key`, `x`, `y`, `width`, `height`, `radius`, `stroke`, `stroke_width` (12), `opacity` | an image of the media clipped to a rounded, stroked frame | | `KenBurns` | `image`, `key`, `x`, `y`, `width`, `height`, `from`, `zoom` (1.06), `zoom_per_second` (0.02), `drift`, `fade` | an image slowly pushed in and drifted sideways from `from` seconds, clipped to its box | Gallery scenes: `BarChart`, `SpectrumBars`, `Waveform`, `DrawOn`, `CornerMarks`, `Glow`, `DropShadow`, `PhotoFrame`, `KenBurns`. Gallery clip of `BarChart`: https://fframes-kit.pages.dev/media/scenes/BarChart-b59040a1.mp4 Gallery clip of `SpectrumBars`: https://fframes-kit.pages.dev/media/scenes/SpectrumBars-9ecb0420.mp4 Gallery clip of `Waveform`: https://fframes-kit.pages.dev/media/scenes/Waveform-6b248ab1.mp4 Gallery clip of `DrawOn`: https://fframes-kit.pages.dev/media/scenes/DrawOn-59a70651.mp4 Gallery clip of `CornerMarks`: https://fframes-kit.pages.dev/media/scenes/CornerMarks-8fad2938.mp4 Gallery clip of `Glow`: https://fframes-kit.pages.dev/media/scenes/Glow-b0e89087.mp4 Gallery clip of `DropShadow`: https://fframes-kit.pages.dev/media/scenes/DropShadow-7efaa726.mp4 Gallery clip of `PhotoFrame`: https://fframes-kit.pages.dev/media/scenes/PhotoFrame-f3aea3e1.mp4 Gallery clip of `KenBurns`: https://fframes-kit.pages.dev/media/scenes/KenBurns-f46a4d8d.mp4 `audio::spectrum(ui, file, SampleSize::S256, smooth)` gives the values for the audio components: the spectrum of `file` at the current frame, or `None` when the file is not in the video's or the scene's `fn audio`. The frame is the container's, so a scene's file should start with the scene. `PhotoFrame` and `KenBurns` draw nothing for a missing image; `key` names their clip path when two are in one frame. `HudLabel::draw()`, `CornerMarks::draw()` (the static marks) and `Annotation::draw(progress)` need no `Ui`, so helpers of a video that only have a `Frame` can draw them too (`examples/fframes-studio-intro/src/brand.rs` does). A level meter on a podcast cover, a highlighted word that gets underlined, and a glowing title: ```rust use fframes_kit::prelude::*; fn cover<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let values = audio::spectrum(ui, "episode.mp3", fframes::SampleSize::S256, 4) .map(|s| s.into_iter().take(48).collect()) .unwrap_or_default(); let meter = ui.show(SpectrumBars { values, x: 160.0, y: 820.0, width: 1600.0, ..ui.defaults() }); let path = BezPath::from_svg("M200 660 H820").unwrap_or_default(); let stroke = Some(ui.theme.colors.accent); let underline = Shape { path, fill: None, stroke, stroke_width: 8.0, dash: None, opacity: 1.0 }; let underline = ui.show(DrawOn { delay: 0.4, ..DrawOn::new(ui, underline) }); let glow = Glow::default(); let url = glow.url(); let defs = ui.show(glow); let title = ui.show(HudLabel { text: "Episode 12".into(), x: 200.0, y: 620.0, ..ui.defaults() }); svgr!({defs}{title}{underline}{meter}) } let chart = BarChart { bars: vec![("a".into(), 10.0), ("b".into(), 20.0)], ..BarChart::default() }; assert_eq!(chart.grown(1, 0.0), 0.0); ``` An audio wave, crop marks, and two ways to display an image: ```rust use fframes_kit::prelude::*; fn media_examples<'a>(ui: &mut Ui<'a, '_, '_>) -> Vec> { let shadow = DropShadow::default(); vec![ ui.show(Waveform { values: vec![0.1, 0.8, 0.2], ..ui.defaults() }), ui.show(CornerMarks { x: 360.0, y: 400.0, width: 480.0, height: 320.0, ..ui.defaults() }), ui.show(shadow), ui.show(PhotoFrame { image: "photo.png", ..ui.defaults() }), ui.show(KenBurns { image: "photo.png", drift: 40.0, ..ui.defaults() }), ] } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### BarChart Bars that grow one after another from a baseline, each with its value counting up above it and its label below. `(x, y)` is the left end of the baseline; the tallest bar is `max_height` high when `max_value` is `None`. A bar is never thinner than half a pixel, so a growing bar starts as a hairline. | Field | Type | Default | Description | | --- | --- | --- | --- | | `bars` | `Vec<(Cow, f64)>` | `Vec::new()` | `(label, value)` of each bar, left to right. | | `x` | `f32` | `0.0` | Left end of the baseline in pixels of the canvas. | | `y` | `f32` | `0.0` | Baseline in pixels of the canvas; bars grow up from it. | | `bar_width` | `f32` | `126.0` | Width of one bar in pixels. | | `gap` | `f32` | `62.0` | Space between neighbouring bars in pixels. | | `max_height` | `f32` | `330.0` | Height in pixels of the bar whose value is `max_value`. | | `max_value` | `Option` | `None` | The value drawn `max_height` high; `None` uses the largest value. | | `radius` | `f32` | `8.0` | Corner radius of the bars in pixels. | | `fill` | `Color` | `theme.colors.muted` | Colour of the bars. | | `highlight` | `Option` | `None` | The index of a bar drawn in `highlight_fill`. | | `highlight_fill` | `Color` | `theme.colors.accent` | Colour of the highlighted bar. | | `value_style` | `TextStyle` | `theme.typography.body` | The font of the values above the bars. | | `value_fill` | `Color` | `theme.colors.title` | Colour of the values above the bars. | | `label_style` | `TextStyle` | `theme.typography.caption` | The font of the labels below the bars. | | `label_fill` | `Color` | `theme.colors.muted` | Colour of the labels below the bars. | | `decimals` | `usize` | `0` | Digits after the decimal point of the counting values. | | `delay` | `f32` | `0.0` | Seconds after the container's start before the first bar begins to grow. | | `stagger` | `f32` | `theme.motion.stagger.loose` | Seconds between the start of one bar and the start of the next. | | `run_time` | `f32` | `theme.motion.duration.slow` | Seconds one bar takes to grow. | | `rate_func` | `RateFunc` | `theme.motion.easing.enter` | Easing of each bar's growth over its run time. | Methods: - `fn grown(&self, i: usize, t: f32) -> f32`: How far bar `i` has grown at `t`, from 0 to 1 (past 1 while a spring overshoots). [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/data/bar_chart.rs#L19) ### SpectrumBars One rounded bar per value, spread over `width` from `x`. A value becomes `value * gain` pixels, kept between `min_height` and `max_height`; NaN draws the shortest bar. Pass the values of `spectrum` (often `.step_by(n)` or `center_spectrum_low_frequencies`). | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec` | `Vec::new()` | One bar per value, left to right; empty draws nothing. | | `x` | `f32` | `0.0` | Left edge of the bars in pixels of the canvas. | | `y` | `f32` | `0.0` | Vertical anchor of the bars in pixels of the canvas: their middle with `BarAlign::Center`, their foot with `BarAlign::Bottom`. | | `width` | `f32` | `1920.0` | Width in pixels the bars are spread over. | | `bar_width` | `f32` | `24.0` | Width of one bar in pixels. | | `gain` | `f32` | `60.0` | Pixels of bar height per unit of value. | | `min_height` | `f32` | `24.0` | Shortest bar in pixels; also used for NaN. | | `max_height` | `f32` | `720.0` | Tallest bar in pixels. | | `radius` | `Option` | `None` | `None` rounds the ends fully (half the bar width). | | `fill` | `Color` | `theme.colors.accent` | Colour of the bars. | | `align` | `BarAlign` | `BarAlign::Center` | Whether the bars grow up and down from `y` or only up. | Methods: - `fn height(&self, value: f32) -> f32`: The height in pixels a value is drawn at. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/audio/spectrum.rs#L42) ### Waveform The audio spectrum (or any values) as a filled smooth wave over a baseline, like the layered waves of an audio announcement. See `wave_path` for the geometry. | Field | Type | Default | Description | | --- | --- | --- | --- | | `values` | `Vec` | `Vec::new()` | The wave heights, spread evenly between the start and the end of the baseline; empty gives a flat line. | | `x` | `f32` | `0.0` | Left end of the baseline in pixels of the canvas. | | `y` | `f32` | `1080.0` | The baseline. | | `width` | `f32` | `1920.0` | Length of the baseline in pixels. | | `gain` | `f32` | `1200.0` | Pixels of wave height per unit of value. | | `max_height` | `f32` | `600.0` | Highest the wave rises over the baseline, in pixels. | | `fill` | `Option` | `Some(theme.colors.accent)` | Colour of the area under the wave; `None` leaves it empty. | | `stroke` | `Option` | `None` | Colour of the outline; `None` draws no outline. | | `stroke_width` | `f32` | `theme.stroke.thin` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/audio/waveform.rs#L89) ### DrawOn Any `Shape` drawn on from the start of its path, the stroke reveal of an underline, a tick or a hand-drawn arrow. It is `Create` on a shape you built: give the shape a `stroke` (and no `fill` for a line). | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `Shape` | — | The shape to draw; give it a `stroke` (and no `fill` for a line). | | `delay` | `f32` | — | Seconds after the container's start before it begins. | | `run_time` | `f32` | — | Seconds the drawing takes. | | `rate_func` | `RateFunc` | — | Easing of the drawing over its run time. | It has no theme defaults: build it with `DrawOn::new(..)`. Methods: - `fn new(ui: &Ui, shape: Shape) -> Self`: `shape` with the theme's `Create` timing. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/decor/draw_on.rs#L15) ### CornerMarks Crop-mark corners around the rectangle `(x, y, width, height)`: an L of `arm` pixels at each corner, like the brackets around a viewfinder. With `enter` (such as `Create`) the marks draw on; without, they are one static path. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `0.0` | Left edge of the rectangle in pixels of the canvas. | | `y` | `f32` | `0.0` | Top edge of the rectangle in pixels of the canvas. | | `width` | `f32` | `0.0` | Width of the rectangle in pixels. | | `height` | `f32` | `0.0` | Height of the rectangle in pixels. | | `arm` | `f32` | `24.0` | Length of each leg of a corner mark in pixels. | | `color` | `Color` | `theme.colors.muted` | Colour of the marks. | | `stroke_width` | `f32` | `theme.stroke.thin` | Line width of the marks in pixels. | | `enter` | `Option` | `None` | How the marks draw on (such as `Create`); `None` shows them from the start. | Methods: - `fn d(&self) -> String`: The four marks as SVG path data, clockwise from the top left. - `fn draw(&self) -> Svgr`: The static marks (`enter` is ignored), as one ``; it needs no `Ui`. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/decor/corners.rs#L16) ### Glow The theme's `shadow.glow` as a filter definition: a soft halo in the accent colour. Draw it once, then use `filter={glow.url()}` on what should glow. | Field | Type | Default | Description | | --- | --- | --- | --- | | `id` | `Cow` | — | The filter's id, unique in the frame. | | `shadow` | `Shadow` | — | The shadow the filter draws: colour (its alpha is the opacity), offset and blur. | Methods: - `fn url(&self) -> String`: `url(#id)`, the value of a `filter` attribute. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/decor/filters.rs#L69) ### DropShadow The theme's `shadow.card` as a filter definition: the drop shadow under a card. Draw it once, then use `filter={shadow.url()}` on the card. | Field | Type | Default | Description | | --- | --- | --- | --- | | `id` | `Cow` | — | The filter's id, unique in the frame. | | `shadow` | `Shadow` | — | The shadow the filter draws: colour (its alpha is the opacity), offset and blur. | Methods: - `fn url(&self) -> String`: `url(#id)`, the value of a `filter` attribute. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/decor/filters.rs#L76) ### PhotoFrame An image of the video's media in a stroked frame with rounded corners; the image is clipped to the frame and covers it (`xMidYMid slice`). A missing image draws the frame alone. `key` names the clip path when two frames are drawn in one frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `image` | `&str` | `""` | Name of the image in the video's media; empty or missing draws the frame alone. | | `key` | `Option<&str>` | `None` | Names the clip path when two frames share a frame; `None` for none. | | `x` | `f32` | `0.0` | Left edge of the frame in pixels of the canvas. | | `y` | `f32` | `0.0` | Top edge of the frame in pixels of the canvas. | | `width` | `f32` | `480.0` | Width of the frame in pixels. | | `height` | `f32` | `320.0` | Height of the frame in pixels. | | `radius` | `f32` | `theme.radius.card` | Corner radius of the frame and the image clip in pixels. | | `stroke` | `Color` | `theme.colors.title` | Colour of the frame outline. | | `stroke_width` | `f32` | `12.0` | Outline width in pixels. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/decor/photo_frame.rs#L13) ### KenBurns An image of the video's media slowly pushed in from `from` seconds and drifted sideways, clipped to the box `(x, y, width, height)`, which it covers (`xMidYMid slice`). It fades in over `fade` seconds. The scale is `zoom + zoom_per_second * t` and the center moves `drift / 4` pixels a second, `t` seconds after `from`. A missing image draws nothing; `key` names the clip path when two are drawn in one frame. | Field | Type | Default | Description | | --- | --- | --- | --- | | `image` | `&str` | `""` | Name of the image in the video's media; a missing image draws nothing. | | `key` | `Option<&str>` | `None` | Names the clip path when two are drawn in one frame; `None` for none. | | `x` | `f32` | `0.0` | Left edge of the box in pixels of the canvas. | | `y` | `f32` | `0.0` | Top edge of the box in pixels of the canvas. | | `width` | `f32` | `1920.0` | Width of the box in pixels. | | `height` | `f32` | `1080.0` | Height of the box in pixels. | | `from` | `f32` | `0.0` | Seconds after the container's start the image appears and begins to push in. | | `zoom` | `f32` | `1.06` | Scale of the image at `from`; 1 covers the box exactly. | | `zoom_per_second` | `f32` | `0.02` | How much the scale grows each second after `from`. | | `drift` | `f32` | `0.0` | Sideways drift: a quarter of it in pixels per second to the right, 0 stays; negative goes left. | | `fade` | `f32` | `theme.motion.duration.base` | Seconds the image fades in over from `from`. | Methods: - `fn at(&self, t: f32) -> (f32, f32)`: `(scale, center x)` at `t` seconds of the container. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/media/ken_burns.rs#L16) --- # Cursors, emoji, sound effects and GIFs > The kit draws its own mouse cursors (@remotion/mac-cursors), so they need no image files. Page: https://fframes-kit.pages.dev/docs/media-and-overlays/cursors-emoji-sound-effects-and-gi-fs/ The kit draws its own mouse cursors (`@remotion/mac-cursors`), so they need no image files. Each cursor fades in over its `fade` seconds. | Component | Props | What it does | | --- | --- | --- | | `Cursor` | `shape`, `style`, `x`, `y`, `size`, `scale`, `opacity`, `shadow` | a still cursor with its hotspot at `(x, y)`. `CursorShape::{Arrow, Pointer, IBeam, Grab, Grabbing, Crosshair}` (`CursorShape::ALL`), `CursorStyle::{Dark, Light}` | | `CursorPath` | `points: Vec`, `shape`, `style`, `size`, `travel`, `motion`, `fade`, `click: ClickLook` | moves through timed points and arrives at each on time. It leaves a point `travel` seconds before the next is due. `CursorPoint::new(at, x, y).click()` presses the cursor and sends out a ring when it arrives. `position(t)` and `clicks()` give the path | | `Click` | `x`, `y`, `at`, `shape`, `style`, `size`, `fade`, `look: ClickLook` | one click at `at` seconds: the cursor presses and a ring spreads out | | `Drag` | `from`, `to`, `start`, `run_time`, `motion`, `style`, `size`, `swap`, `fade`, `children` | the open hand closes, carries its children from `from` to `to`, and opens again | | `TypeAt` | `typewriter: Typewriter`, `shape`, `style`, `size`, `lead`, `fade`, `click` | a text cursor clicks at the typewriter's position `lead` seconds before it starts typing | Gallery scenes: `Cursor`, `CursorPath`, `Click`, `Drag`, `TypeAt`. Gallery clip of `Cursor`: https://fframes-kit.pages.dev/media/scenes/Cursor-8aa7aa32.mp4 Gallery clip of `CursorPath`: https://fframes-kit.pages.dev/media/scenes/CursorPath-cd86d439.mp4 Gallery clip of `Click`: https://fframes-kit.pages.dev/media/scenes/Click-13ad6aa1.mp4 Gallery clip of `Drag`: https://fframes-kit.pages.dev/media/scenes/Drag-f82d950f.mp4 Gallery clip of `TypeAt`: https://fframes-kit.pages.dev/media/scenes/TypeAt-8918c502.mp4 `CursorMotion::Spring { stiffness, damping }` (the default) and `CursorMotion::Smooth { overshoot }` both overshoot the target slightly and settle on it by the time it is due. `ClickLook` sets the ring's `color`, `radius` and `stroke_width`, how far the cursor presses (`depth`, `press` seconds) and how long the ring lasts (`run_time`). ```rust use fframes_kit::prelude::*; fn demo<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let path = ui.show(CursorPath { points: vec![ CursorPoint::new(0.3, 400.0, 700.0), CursorPoint::new(1.2, 960.0, 540.0).click(), CursorPoint::new(2.4, 1300.0, 620.0).click(), ], shape: CursorShape::Pointer, travel: 0.9, ..ui.defaults() }); let drag = ui.show(Drag { from: (700.0, 300.0), to: (1200.0, 300.0), start: 1.0, children: children(|_| svgr!()), ..ui.defaults() }); svgr!({path}{drag}) } ``` ```rust use fframes_kit::prelude::*; fn typing_cursor<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let cursor = ui.show(Cursor { x: 400.0, y: 400.0, shape: CursorShape::Pointer, ..ui.defaults() }); let defaults: TypeAt = ui.defaults(); let typing = ui.show(TypeAt { typewriter: Typewriter { text: "render the gallery".into(), x: 620.0, y: 553.0, delay: 1.0, ..defaults.typewriter }, ..defaults }); svgr!({cursor}{typing}) } ``` ### Animated emoji `AnimatedEmoji` (feature `lottie`, `@remotion/animated-emoji`) plays one of Google's Noto animated emoji. These files are licensed CC BY 4.0 and the kit does not ship them. Download the ones a video uses into its media folder, and show `emoji::ATTRIBUTION` in the video or its description. Gallery scene: `AnimatedEmoji`. Gallery clip of `AnimatedEmoji`: https://fframes-kit.pages.dev/media/scenes/AnimatedEmoji-03f3e801.mp4 - `emoji::NAMES` lists the emoji by name and Noto code. `emoji::url(name, tone)` gives the download URL, and `emoji::file_name(name, tone)` the file name the component looks for, e.g. `thumbs-up-medium.json`. - The props are `name`, `skin_tone: Option`, `x`, `y`, `size` (the longer side), `start`, `speed`, `looping` and `fade`. A skin tone applies only to the emoji whose `skin_tones` is set (hands and gestures); the others ignore it. - When the file is missing, the component draws nothing, and `inspect` warns with the URL to download and the name to save it as. ```rust use fframes_kit::prelude::*; fn thumbs<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(AnimatedEmoji { name: "thumbs-up", skin_tone: Some(SkinTone::Medium), size: 200.0, ..ui.defaults() }) } assert_eq!(emoji::file_name("thumbs-up", Some(SkinTone::Medium)), "thumbs-up-medium.json"); ``` ### Sound effects The crate's `sfx/` folder holds a small pack of CC0 sounds (`@remotion/sfx`). Each is under 1 s long, mono, and peaks at -3 dBFS: | `Sfx` | File | Sound | | --- | --- | --- | | `Whoosh` | `whoosh.wav` | a stick swung through the air, for transitions | | `Click` | `click.wav` | a mouse click | | `Ding` | `ding.wav` | a bell | | `Pop` | `pop.wav` | a short pop, for things that appear | | `SwooshDown` | `swoosh-down.wav` | a falling sweep, for things that leave | | `Shutter` | `shutter.wav` | a camera shutter | | `Switch` | `switch.wav` | a toggle switch | Gallery scenes: `SoundEffects`, `TransitionSfx`. Gallery clip of `SoundEffects`: https://fframes-kit.pages.dev/media/scenes/SoundEffects-7c969b99.mp4 Gallery clip of `TransitionSfx`: https://fframes-kit.pages.dev/media/scenes/TransitionSfx-9485fa12.mp4 `sfx/LICENSE.md` names the source of each file. Copy the files you use into the video's media folder: `include_media_dir!` cannot read the pack folder itself, because of its `LICENSE.md`. `Sfx::File("mine.wav")` plays a file of your own, and a file of your own with a pack name replaces the kit's. A video's audio is set in `fn audio`, so the helpers return `AudioTrack`s to add to it. Each hit plays at `sfx::DEFAULT_GAIN_DB` (-6 dB). Its rate changes by up to `sfx::RATE_VARIATION` (4 %), so repeats do not sound alike. The change depends only on the hit's index, so every render sounds the same. - `sfx::at(&[1.0, 2.5], Sfx::Pop)` gives one hit at each time. - `sfx::on_transitions(&series_lengths, fps, Sfx::Whoosh)` gives one hit where each transition of a series starts, i.e. where its two sequences start to overlap. `sfx::transition_times` gives those times. - `sfx::on_beats(&beat, 0.0..8.0, Sfx::Click)` gives one hit on each whole beat of the range. - `sfx::on_clicks(&path)` gives one click sound per click of a `CursorPath`. ```rust use fframes::AudioMap; use fframes_kit::prelude::*; const FADE: Timing = Timing::Linear { duration: 0.5, easing: RateFunc::Smooth }; fn audio() -> AudioMap<'static> { let series = [ SeriesLength::Sequence(2.0), SeriesLength::Transition(FADE), SeriesLength::Sequence(2.0), ]; let mut tracks = sfx::on_transitions(&series, 30, Sfx::Whoosh); tracks.extend(sfx::on_beats(&Beat::from_bpm(120.0, 0.0), 0.0..4.0, Sfx::Click)); tracks.extend(sfx::at(&[3.2], Sfx::Ding)); AudioMap::from(tracks) } ``` Check the levels with `audio analyze`. Hits that overlap add up. ### GIFs `Gif` (feature `gif`, `@remotion/gif`) plays an animated GIF. Decode the file once with `Gif::decode(bytes)` in the video's constructor and keep the `GifData`. The component's props are `gif`, `x`, `y`, `width`, `height`, `fit: GifFit::{Fill, Contain, Cover}`, `start`, `speed`, `looping` and `fade`. Gallery scene: `Gif`. Gallery clip of `Gif`: https://fframes-kit.pages.dev/media/scenes/Gif-82eae3f6.mp4 - Each frame shows for its own delay. A delay under 20 ms counts as 100 ms, as in browsers. - With `looping` the GIF starts again; without it, the last frame stays. - `include_media_dir!` does not take `.gif` files, so read the bytes with `include_bytes!` or from a file. - The web editor draws a `placeholder` rectangle of the same size instead of the GIF. ```rust use std::sync::LazyLock; use fframes_kit::prelude::*; static SPINNER: LazyLock> = LazyLock::new(|| Gif::decode(include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../gallery/media/gif/bounce.gif"))).ok()); fn spinner<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let Some(gif) = SPINNER.as_ref() else { return Svgr::empty(); }; ui.show(Gif { gif, x: 760.0, y: 340.0, width: 400.0, height: 400.0, fit: GifFit::Contain, ..ui.defaults() }) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Cursor A mouse cursor with its hotspot at `(x, y)` (the tip of the arrow, the middle of the I-beam). `size` is the height in pixels of the 24 unit design, `scale` grows or shrinks it about the hotspot (a press), `shadow` adds a soft shadow below it. The kit draws its own shapes; they are not the artwork of any operating system. | Field | Type | Default | Description | | --- | --- | --- | --- | | `shape` | `CursorShape` | `CursorShape::Arrow` | Which cursor is drawn: arrow, pointer, I-beam, grab, grabbing or crosshair. | | `style` | `CursorStyle` | `CursorStyle::Dark` | Colours of the cursor: `Dark` (black with a white outline) or `Light` (white with a black outline). | | `x` | `f32` | `960.0` | Horizontal position of the hotspot in pixels of the canvas. | | `y` | `f32` | `540.0` | Vertical position of the hotspot in pixels of the canvas, down. | | `size` | `f32` | `40.0` | Height in pixels of the 24 unit design. | | `scale` | `f32` | `1.0` | Grows or shrinks the cursor about its hotspot; 1 is normal, below 1 is a press, 0 or less draws nothing. | | `opacity` | `f32` | `1.0` | 0 transparent to 1 opaque. | | `shadow` | `bool` | `true` | Adds a soft shadow below the cursor. | Methods: - `fn draw(&self) -> Svgr`: The cursor without a `Ui`; nothing when it is invisible. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/cursor.rs#L132) ### CursorPath A cursor that moves through timed points, in the order of their times. It leaves a point `travel` seconds before the next one is due (or at once when they are closer) and arrives on time, with `motion`. A point with `click` presses the cursor and sends out a ring when it arrives; `sfx::on_clicks` gives the sounds. The cursor fades in over `fade` seconds from the first point's time. | Field | Type | Default | Description | | --- | --- | --- | --- | | `points` | `Vec` | `Vec::new()` | The timed points the cursor passes through, in the order of their times; empty draws nothing. | | `shape` | `CursorShape` | `CursorShape::Arrow` | Which cursor is drawn: arrow, pointer, I-beam, grab, grabbing or crosshair. | | `style` | `CursorStyle` | `CursorStyle::Dark` | Colours of the cursor: `Dark` (black with a white outline) or `Light` (white with a black outline). | | `size` | `f32` | `40.0` | Height in pixels of the 24 unit design. | | `travel` | `f32` | `0.6` | Seconds a move takes, ending when the next point is due (shorter when the points are closer in time). | | `motion` | `CursorMotion` | `CursorMotion::default()` | How the cursor travels to the next point: a smooth ease or a spring, both arriving on time. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds the cursor takes to fade in from the first point's time. | | `click` | `ClickLook` | `ClickLook::themed(theme)` | How a click looks: the press depth and duration and the ring that goes out. | Methods: - `fn position(&self, t: f32) -> (f32, f32)`: Where the hotspot is at `t` seconds: the first point before it, the last after. - `fn clicks(&self) -> impl Iterator`: The times of the clicks, in seconds. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/cursor.rs#L383) ### Click A cursor at `(x, y)` that clicks at `at` seconds: it presses, then a ring goes out. It fades in over `fade` seconds from the start of its container. | Field | Type | Default | Description | | --- | --- | --- | --- | | `x` | `f32` | `960.0` | Horizontal position of the cursor in pixels of the canvas. | | `y` | `f32` | `540.0` | Vertical position of the cursor in pixels of the canvas, down. | | `at` | `f32` | `0.5` | Second of the click, local to the parent container. | | `shape` | `CursorShape` | `CursorShape::Pointer` | Which cursor is drawn: arrow, pointer, I-beam, grab, grabbing or crosshair. | | `style` | `CursorStyle` | `CursorStyle::Dark` | Colours of the cursor: `Dark` (black with a white outline) or `Light` (white with a black outline). | | `size` | `f32` | `40.0` | Height in pixels of the 24 unit design. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds the cursor takes to fade in from the start of its container. | | `look` | `ClickLook` | `ClickLook::themed(theme)` | How the click looks: the press depth and duration and the ring that goes out. | [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/cursor.rs#L489) ### Drag The grabbing hand picks up its children at `from` at `start` seconds and carries them to `to` in `run_time` seconds. The open hand closes over `swap` seconds before the move and opens again after it; the children move with the cursor. The cursor fades in over `fade` seconds from the start of the container. | Field | Type | Default | Description | | --- | --- | --- | --- | | `from` | `(f32, f32)` | `(760.0, 540.0)` | Where the cursor picks the children up, in pixels of the canvas; the children keep their own place there. | | `to` | `(f32, f32)` | `(1160.0, 540.0)` | Where the cursor drops the children, in pixels of the canvas. | | `start` | `f32` | `0.6` | Second the carry starts, local to the parent container; the hand closes just before. | | `run_time` | `f32` | `0.9` | Seconds the carry takes. | | `motion` | `CursorMotion` | `CursorMotion::default()` | How the cursor travels from `from` to `to`: a smooth ease or a spring, arriving on time. | | `style` | `CursorStyle` | `CursorStyle::Dark` | Colours of the cursor: `Dark` (black with a white outline) or `Light` (white with a black outline). | | `size` | `f32` | `40.0` | Height in pixels of the 24 unit design. | | `swap` | `f32` | `0.12` | Seconds the hand takes to close before the move and to open after it. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds the cursor takes to fade in from the start of its container. | | `children` | `Children` | `Box::new(\|_\| Svgr::empty())` | What is carried, drawn at its own place and moved by the cursor's offset (a closure taking the `Ui`). | Methods: - `fn position(&self, t: f32) -> (f32, f32)`: Where the cursor, and the offset of the children from where they were, is at `t`. - `fn grip(&self, t: f32) -> f32`: How closed the hand is at `t`, from 0 (open) to 1. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/cursor.rs#L555) ### TypeAt A text cursor that clicks into a field and the [`Typewriter`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) that then types there. The cursor sits just left of the text's start, fades in over `fade` seconds from the start of the container and clicks `lead` seconds before the typing starts (the typewriter's `delay`), so the delay should be at least `fade + lead`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `typewriter` | [`Typewriter`](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) | `Typewriter { delay: 0.9, ..Typewriter::themed(theme) }` | The text that is typed after the click; its `x`, `y` and `delay` place the cursor and time the click. | | `shape` | `CursorShape` | `CursorShape::IBeam` | Which cursor is drawn; the I-beam by default. | | `style` | `CursorStyle` | `CursorStyle::Dark` | Colours of the cursor: `Dark` (black with a white outline) or `Light` (white with a black outline). | | `size` | `f32` | `40.0` | Height in pixels of the 24 unit design; also sets how far left of the text the cursor sits. | | `lead` | `f32` | `0.4` | Seconds the click comes before the typing starts. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds the cursor takes to fade in from the start of its container. | | `click` | `ClickLook` | `ClickLook::themed(theme)` | How the click looks: the press depth and duration and the ring that goes out. | Methods: - `fn cursor_at(&self) -> (f32, f32)`: Where the cursor's hotspot is: left of the start of the text, at its middle. - `fn click_at(&self) -> f32`: The second of the click. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/cursor.rs#L655) ### AnimatedEmoji A Noto animated emoji (Remotion's ``), played from `start` seconds of its container from the file `file_name` gives in the media folder. `(x, y)` is the top left of a `size` pixel square. It fades in over `fade` seconds and plays at `speed`, again and again with `looping`, otherwise its last frame stays. A missing file draws nothing, and `inspect` warns with the address to download it from. The tone applies to the emoji that have skin tones. | Field | Type | Default | Description | | --- | --- | --- | --- | | `name` | `&str` | `"grinning"` | Name of the emoji from `NAMES`, which also names its `.json` file in the media folder. | | `skin_tone` | `Option` | `None` | Skin tone of a hand emoji; `None` for the default, and ignored for emoji without skin tones. | | `x` | `f32` | `880.0` | Left edge in pixels of the canvas. | | `y` | `f32` | `460.0` | Top edge in pixels of the canvas. | | `size` | `f32` | `160.0` | Side of the square the emoji fills, in pixels. | | `start` | `f32` | `0.0` | Second it starts playing, local to the parent container. | | `speed` | `f32` | `1.0` | Playback speed; 1 is normal, 2 twice as fast. | | `looping` | `bool` | `true` | Plays again and again; `false` keeps the last frame. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds it takes to fade in from `start`. | Methods: - `fn file(&self) -> String`: The file this emoji plays. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/emoji.rs#L210) ### Gif An animated GIF in the box `(x, y, width, height)`, playing from `start` seconds of its container at `speed`. Each frame shows for its own delay (a delay under 20 ms is 100 ms, as in browsers); with `looping` it starts again, otherwise the last frame stays. It fades in over `fade` seconds. Decode the file once with `Gif::decode` and keep the `GifData` in the video. The editor (wasm32) draws a `placeholder` rectangle of the same size. | Field | Type | Default | Description | | --- | --- | --- | --- | | `gif` | `&GifData` | `&NO_GIF` | The decoded GIF from `Gif::decode`; the default one draws nothing. | | `x` | `f32` | `0.0` | Left edge of the box in pixels of the canvas. | | `y` | `f32` | `0.0` | Top edge of the box in pixels of the canvas. | | `width` | `f32` | `320.0` | Width of the box in pixels. | | `height` | `f32` | `320.0` | Height of the box in pixels. | | `fit` | `GifFit` | `GifFit::Fill` | How the GIF fills the box: stretched, contained or covering. | | `start` | `f32` | `0.0` | Second it starts playing, local to the parent container. | | `speed` | `f32` | `1.0` | Playback speed; 1 is normal, 2 twice as fast. | | `looping` | `bool` | `true` | Plays again and again; `false` keeps the last frame. | | `fade` | `f32` | `theme.motion.duration.fast` | Seconds it takes to fade in from `start`. | | `placeholder` | `Color` | `theme.colors.surface` | Colour of the rectangle the editor draws in place of the GIF. | Methods: - `fn decode(bytes: &[u8]) -> Result`: Decodes every frame of a GIF file, composited onto the frames before it, and turns each into an image once. Call it in the video's constructor, not per frame. - `fn frame_at(&self, t: f32) -> usize`: The frame shown at `t` seconds of the container. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/assets/gif.rs#L108) ## See also - [Text](https://fframes-kit.pages.dev/docs/text-and-formulas/text.md) --- # Slides and decks > The slides module makes a video that is also a deck. Page: https://fframes-kit.pages.dev/docs/slides/ The `slides` module makes a video that is also a deck. A `Slide` is a layout (a title page, a section divider, bullets, an image, two columns, cards, figures, bars, a timeline, a formula, code, a quotation or your own drawing) whose parts come in one after another and that then holds. A `Deck` plays slides as the scenes of a video, with a transition between them. The same video renders with the fframes command line; a presenter can use the slides' reveal times and notes. No cargo feature is needed for slides or decks. The prelude brings the module, `Deck` and `SlideLayout`. Write a slide as `slides::Slide`: the prelude's `Slide` is the transition. Start with [Slides](https://fframes-kit.pages.dev/docs/slides/slides.md) for layouts, build timing and presenter stops, dress them with [backdrops, footers and themes](https://fframes-kit.pages.dev/docs/slides/backdrops-footers-and-themes.md), then see [Decks](https://fframes-kit.pages.dev/docs/slides/decks.md) for combining slides into a video and editing text in Studio. For a head start, choose a [starter template](https://fframes-kit.pages.dev/docs/slides/starter-templates.md); [A complete deck](https://fframes-kit.pages.dev/docs/slides/a-complete-deck.md) puts it all together. - [Slides](https://fframes-kit.pages.dev/docs/slides/slides.md): slides::Slide::new(key, layout) makes a slide with the layout's own build-in, a hold of 1 second and no notes. - [Backdrops, footers and themes](https://fframes-kit.pages.dev/docs/slides/backdrops-footers-and-themes.md): A slide's backdrop is drawn behind its layout, in the theme's colours. - [Starter templates](https://fframes-kit.pages.dev/docs/slides/starter-templates.md): slides::templates provides editable example decks for common presentations. - [Decks](https://fframes-kit.pages.dev/docs/slides/decks.md): Deck::new(theme, slides) makes one fframes Scene per slide, named by the slide's key, so frame agenda@end on the command line finds it. - [A complete deck](https://fframes-kit.pages.dev/docs/slides/a-complete-deck.md): A whole video: the pitch template, renamed and extended with a slide of your own, in the sunset theme over a glow, with a footer and a slide transition… --- # Slides > slides::Slide::new(key, layout) makes a slide with the layout's own build-in, a hold of 1 second and no notes. Page: https://fframes-kit.pages.dev/docs/slides/slides/ `slides::Slide::new(key, layout)` makes a slide with the layout's own build-in, a hold of 1 second and no notes. Its fields: | Field | What it does | | --- | --- | | `key: &'static str` | the slide's stable name: its scene name in a deck, the key of its clips and stops in fframes Studio. Unique in a deck; keep it when the slide changes | | `layout: SlideLayout` | what the slide shows | | `build: Option` | seconds of the build-in: `None` keeps the layout's timing, a value stretches or squeezes it, `Some(0.0)` shows every part from the first frame | | `hold: f32` | seconds the built slide stays (1) | | `notes: Option<&str>` | speaker notes, not drawn | | `backdrop: Backdrop` | what is drawn behind the layout: `Backdrop::Plain` (the theme's background) by default | | `footer: Option` | a label and page number along the bottom; `None` by default | A slide lasts `seconds()`, its build-in plus its hold, so its last frame shows it fully built. Every part of a layout fades in and rises into place over 0.6 seconds; the parts start 0.15 to 0.3 seconds apart. Time is the slide's own and starts at 0 with it. | Layout | Draws | Build-in | | --- | --- | --- | | `Title { title, subtitle }` | a large centred title (up to two lines) over an accent rule and a muted subtitle | title, rule, subtitle: 0.9 s | | `Section { title }` | a large title (up to three lines) beside an accent bar, centred in the height | bar, title: 0.75 s | | `Bullets { title, items, stagger }` | a heading over one bullet per item, each up to two lines; `SlideLayout::bullets(title, items)` takes the default `stagger` of 0.6 s | heading, then bullet `i` from `0.3 + i * stagger` s | | `Image { title, image, caption }` | a heading over an image of the media, fitted into the slide with rounded corners, and a muted caption; a card with the file name when the image is missing | heading, image, caption: 1.2 s | | `TwoColumns { title, left, right }` | a heading over two cards of text (`\n` starts a line), as tall as the longer text | heading, left, right: 1.2 s | | `FeatureCards { title, items }` | up to three numbered cards with a heading and description each | heading, then cards staggered by 0.45 s | | `Flow { title, steps, caption }` | up to four connected, editable steps and a text or pre-typeset formula caption | heading, then steps and caption staggered by 0.45 s | | `Stats { title, items }` | up to four cards, each a large figure in the accent colour over a rule and a label; the first number in each figure counts up from 0, keeping its decimals, thousands separators and the text around it (`$`, `%`, `k`) | heading, then cards staggered by 0.3 s; each figure counts for 1.2 s | | `Timeline { title, events }` | up to five milestones on a line that draws from left to right, each a date in the accent colour over a dot and a description of up to three lines | heading, then milestones staggered by 0.4 s | | `Bars { title, items, caption }` | up to eight labelled bars that grow in from the left, scaled to the largest value, which is drawn in the accent colour; values below 0 count as 0 and are written with two decimals; a muted caption under them | heading, then bars staggered by 0.2 s, then the caption | | `Equation { title, formula, caption }` | a large, pre-typeset formula, written in stroke by stroke over 1.5 s, over an accent rule and a caption of up to two lines; requires `latex` | heading, formula, caption: 2.4 s | | `Code { title, code, language }` | a heading over the code on a card, in JetBrains Mono (ship the font), indentation kept, `language` as a label | heading, card: 0.9 s | | `HighlightedCode { title, code, language }` | a syntax-coloured `CodeBlock` with line numbers; requires `cs` | heading, panel, then a focus bar moves through nonblank lines every 0.6 s | | `Quote { text, author }` | an accent quotation mark over the centred quotation (up to four lines) and its author | mark, words, author: 1.2 s | | `Custom(fn)` | your function draws over the background with the slide's `Ui` | none: give the slide a `build` | The layouts draw on a 1920x1080 canvas over the theme's background, in the theme's fonts and colours: headings in `typography.heading`, titles in `typography.title`, bullets and cards in `typography.body` a quarter larger, accents in `colors.accent`. Text is measured with the fonts of the media and stays inside its box: it wraps, shrinks down to half its size, and is cut with "…" after that. Bullets that do not fit even then are left out and reported as a warning. Without a measurable font (system fonts in the web editor) the text is drawn at its full size. `reveal_times()` gives the seconds into the slide when each bullet of a `Bullets` layout is fully in, the places a presenter stops at; other layouts have none. `notes()` gives the notes. Both are plain data, read without rendering. With `studio` and a listening props source, rendering also suggests presenter pauses. Suggestions are proposals, never applied automatically, and do not change the drawing. `Slide` reports each bullet's `reveal_times()`, labels it with the bullet text and attaches the slide's notes. `BulletedList` uses `revealed_at(i)` and the item text. Stops less than 0.4 seconds apart are coalesced, keeping the later one; simultaneous bullets keep the last. `LaggedStart` and `Succession` report child starts; `Series` and `TransitionSeries` report the laid-out sequence ends, including overlaps. `ChapterList` reports chapter starts and titles. The `cs` trace views (`ArrayView`, `SortVisualizer`, `BinarySearch`, `LinkedList`, `Stack`, `Queue`, `GraphView`, `TreeView`, `HashTable`, `StateMachine`, `CallStack` and `RecursionTree`) report step starts and the step's note, even with drawn notes disabled. `script.suggest_stops(ui)` on a `VoiceoverScript` reports block starts with plain text as speaker notes and bookmark times labelled by their marks. Custom components report local seconds with `Ui::suggest_stops`. The closure is evaluated only while the source listens. `SuggestedStop` and the `Ui` methods are available without `studio`, where reporting is a lazy no-op, so no `cfg` is needed at call sites: ```rust use fframes_kit::{prelude::*, props::SuggestedStop}; fn suggest(ui: &Ui<'_, '_, '_>) { ui.suggest_stops_keyed("demo", || vec![SuggestedStop { seconds: 1.5, label: Some("Ready".into()), notes: Some("Explain the result.".into()), }]); } ``` `Ui` adds all enclosing time offsets: `Ui::for_scene` reports scene seconds and `Ui::new` reports video seconds, never `frame.global_index`. Sub-frame copies preserve the same root start. `Freeze` and `Loop`, including their descendants, report nothing. Non-finite or negative local times are dropped. Reports are sorted and spaced at least 0.4 seconds apart; the later stop wins, and the last input wins equal-time ties, including its label and notes. With `studio`, implement `PropsSource::suggest_stops(&self, origin: &str, stops: &[SuggestedStop])` to collect them; its default does nothing. Replace the entire batch for each origin, including empty batches. The opaque origin contains the caller location, an optional component key and the reporting `Ui`'s root start, not the current frame. Repeated renders replace the same batch. `Ui::show` and `Component::render` preserve the caller location. Use distinct keys with `suggest_stops_keyed` when custom instances share a call site and root start; custom forwarding helpers can use `#[track_caller]` to preserve their callers too. `Slide` and keyed `TransitionSeries` include their keys automatically. The receiving store is responsible for collection lifetime, cross-origin merging and transport limits on counts and text sizes; the kit does not persist or accept suggestions. A slide is also a component: `ui.show(slide)` draws it at the time of any `Ui`. ```rust use fframes_kit::prelude::*; fn agenda() -> slides::Slide<'static> { slides::Slide { notes: Some("Three things, one at a time."), hold: 2.0, ..slides::Slide::new( "agenda", SlideLayout::bullets("Agenda", vec!["Why decks", "How they build", "Presenting"]), ) } } fn draw<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { ui.show(agenda()) } // to the hundredth of a second let cs = |times: Vec| times.iter().map(|t| (t * 100.0).round() as u32).collect::>(); let slide = agenda(); assert_eq!(cs(slide.reveal_times()), [90, 150, 210]); assert_eq!(cs(vec![slide.seconds()]), [410]); // the same slide built in half the time let quick = slides::Slide { build: Some(1.05), ..agenda() }; assert_eq!(cs(quick.reveal_times()), [45, 75, 105]); ``` Two layouts made for numbers and plans: ```rust use fframes_kit::prelude::*; let traction = slides::Slide::new( "traction", SlideLayout::Stats { title: "Traction", items: vec![("1,240", "paying teams"), ("$86k", "monthly revenue"), ("18%", "monthly growth")], }, ); let roadmap = slides::Slide::new( "roadmap", SlideLayout::Timeline { title: "Roadmap", events: vec![("Q1", "Calendar sync"), ("Q2", "Enterprise"), ("Q3", "Forecasting")], }, ); // to the hundredth of a second: the last figure starts at 0.9 s and counts for 1.2 s assert_eq!((traction.build_seconds() * 100.0).round(), 210.0); assert_eq!((roadmap.build_seconds() * 100.0).round(), 170.0); ``` `Bars` suits attention weights, shares and scores; `Equation` gives one formula a slide of its own. Typeset the formula with a `TexEngine` before rendering, as for a `Flow` caption: ```rust use std::sync::Arc; use fframes_kit::{latex::{MathTex, TexEngine}, prelude::*}; let engine = TexEngine::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache")); let softmax = MathTex::new(r"\alpha_{ij} = \frac{\exp(s_{ij})}{\sum_k \exp(s_{ik})}").typeset(&engine)?; let equation = slides::Slide::new( "softmax", SlideLayout::Equation { title: "Turn scores into weights", formula: Arc::new(softmax), caption: Some("Every weight is positive and a row sums to 1"), }, ); let weights = slides::Slide::new( "weights", SlideLayout::Bars { title: "Where \"it\" looks", items: vec![("animal", 0.49), ("street", 0.14), ("tired", 0.2)], caption: None, }, ); assert_eq!((equation.build_seconds() * 100.0).round(), 240.0); assert_eq!((weights.build_seconds() * 100.0).round(), 130.0); ``` Gallery scenes: `SlidesTitle`, `SlidesBullets`, `SlidesTwoColumns`, `SlidesStats`, `SlidesTimeline`, `SlidesBars`, `SlidesEquation`, `SlideStops`. Gallery clip of `SlidesTitle`: https://fframes-kit.pages.dev/media/scenes/SlidesTitle-602bb413.mp4 Gallery clip of `SlidesBullets`: https://fframes-kit.pages.dev/media/scenes/SlidesBullets-07f1f57b.mp4 Gallery clip of `SlidesTwoColumns`: https://fframes-kit.pages.dev/media/scenes/SlidesTwoColumns-af3bd6ec.mp4 Gallery clip of `SlidesStats`: https://fframes-kit.pages.dev/media/scenes/SlidesStats-979f89b9.mp4 Gallery clip of `SlidesTimeline`: https://fframes-kit.pages.dev/media/scenes/SlidesTimeline-91a0f5e7.mp4 Gallery clip of `SlidesBars`: https://fframes-kit.pages.dev/media/scenes/SlidesBars-2ad3dcbc.mp4 Gallery clip of `SlidesEquation`: https://fframes-kit.pages.dev/media/scenes/SlidesEquation-ebb108de.mp4 Gallery clip of `SlideStops`: https://fframes-kit.pages.dev/media/scenes/SlideStops-bf3e0c68.mp4 ## API ### Slide One slide of a `Deck`: a layout that builds in, then holds. | Field | Type | Default | Description | | --- | --- | --- | --- | | `key` | `&str` | — | The slide's stable name: its scene name in a deck, the key of its clips in fframes Studio's timeline and of its stops. Unique in a deck; keep it when the slide changes. | | `layout` | `SlideLayout` | — | What the slide shows. | | `build` | `Option` | — | Seconds the build-in takes. `None` keeps the layout's own timing; a value stretches or squeezes it to that length, and 0 shows every part from the first frame. | | `hold` | `f32` | — | Seconds the built slide stays before it ends, 1 by default. | | `notes` | `Option<&str>` | — | Speaker notes, for presenting; not drawn. | | `backdrop` | `Backdrop` | — | What is drawn behind the layout, `Backdrop::Plain` (the theme's background) by default. | | `footer` | `Option` | — | A label and page number along the bottom; `None` for none. `Deck::footer` sets one on every slide but title pages. | It has no theme defaults: build it with `Slide::new(..)`. Methods: - `fn new(key: &str, layout: SlideLayout) -> Self`: A slide with the layout's own build-in, a hold of 1 second, no notes, a plain backdrop and no footer. - `fn build_seconds(&self) -> f32`: Seconds the build-in takes: `build`, or the layout's own timing without one. - `fn seconds(&self) -> f32`: Seconds the slide lasts: its build-in plus its hold. - `fn reveal_times(&self) -> Vec`: Seconds into the slide when each bullet of a `SlideLayout::Bullets` is fully in, one per item in order; empty for the other layouts. A presenter stops at these. - `fn notes(&self) -> Option<&str>`: The speaker notes. [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/slides/slide.rs#L19) --- # Backdrops, footers and themes > A slide's backdrop is drawn behind its layout, in the theme's colours. Page: https://fframes-kit.pages.dev/docs/slides/backdrops-footers-and-themes/ A slide's `backdrop` is drawn behind its layout, in the theme's colours. Every backdrop is still, so two slides with the same one cross a transition without a seam. | `Backdrop` | Draws | | --- | --- | | `Plain` | the theme's background colour (the default) | | `Glow` | an accent glow from the top right and a fainter highlight glow from the bottom left | | `Grid` | a fine grid of dots in the muted colour under an accent glow from the top left | | `Gradient` | a diagonal gradient from the background to the surface colour, with an accent band down the left edge | A `SlideFooter { text, page }` draws `text` in the bottom left after a small accent square, and `page: Some((3, 12))` as `03 / 12` in the bottom right, both in the muted colour under the content area. It does not build in. `Deck::backdrop(backdrop)` sets the backdrop of every slide; `Deck::footer(text)` gives every slide but title pages a footer numbered by its place in the deck. `slides::themes` has four `Theme`s with palettes made for slides; typography, spacing and motion are the defaults: | Function | Look | Suits | | --- | --- | --- | | `midnight()` | deep navy, periwinkle accent, gold highlight | talks and lectures | | `paper()` | warm off-white, ink text, vermilion accent | reports and reviews | | `sunset()` | plum dusk, coral accent, sunny highlight | launches and pitches | | `forest()` | dark pine, mint accent, straw highlight | science and sustainability | ```rust use fframes_kit::prelude::*; let slide = slides::Slide { backdrop: slides::Backdrop::Glow, footer: Some(slides::SlideFooter { text: "Northstar · Seed round", page: Some((2, 10)) }), ..slides::Slide::new("problem", SlideLayout::bullets("The problem", vec!["Plans go stale"])) }; let deck = Deck::new(slides::themes::sunset(), vec![slide]) .backdrop(slides::Backdrop::Grid) .footer("Northstar"); let slide = deck.slides().next().unwrap(); assert_eq!(slide.backdrop, slides::Backdrop::Grid); assert_eq!(slide.footer.and_then(|f| f.page), Some((1, 1))); ``` Gallery scene: `SlidesBackdrop`. Gallery clip of `SlidesBackdrop`: https://fframes-kit.pages.dev/media/scenes/SlidesBackdrop-19190c5e.mp4 --- # Starter templates > slides::templates provides editable example decks for common presentations. Page: https://fframes-kit.pages.dev/docs/slides/starter-templates/ `slides::templates` provides editable example decks for common presentations. Each function returns slides to customize. The lecture templates require `cs` for syntax highlighting; the research and attention templates require `latex` to typeset their equations. Marketing and product templates need no feature. | Function | Use case | Slide key prefix | | --- | --- | --- | | `pitch_deck()` | a complete ten-slide startup pitch | `pitch-` | | `quarterly_review()` | a complete quarterly business review | `review-` | | `conference_talk()` | a complete conference talk on video as code | `talk-` | | `marketing()` | a marketing presentation | `marketing-` | | `product_launch()` | a product launch | `product-` | | `research_talk(&engine)` | a walkthrough of the Transformer paper | `research-` | | `attention_lecture(&engine)` | a complete eighteen-slide lecture on the attention mechanism | `attention-` | | `python_lecture()` | a Python lecture | `python-` | | `web_lecture()` | a web development lecture | `web-` | The pitch, review and talk are complete: every slide has speaker notes and a hold long enough to speak to it, and together they use every layout but `Image`'s photo, which is a placeholder until the media has one. Their docs name the theme, backdrop and footer they read best with: `sunset()` and `Glow` for the pitch, `paper()` and `Gradient` for the review, `midnight()` and `Grid` for the talk. The talk's code slide is drawn in JetBrains Mono; ship the font. Replace the example content with your own before constructing a deck. Change a slide's `layout`, `build`, `hold` or `notes`, or add, remove and reorder slides in the vector. Keep keys unique within a deck and stable when editing slides; they identify scenes and Studio clips and stops. For example, customize the opening of a product launch: ```rust use fframes_kit::prelude::*; let mut slides = slides::templates::product_launch(); if let Some(opening) = slides.first_mut() { opening.layout = SlideLayout::Title { title: "Introducing Northstar", subtitle: Some("A simpler way to plan"), }; opening.hold = 2.0; opening.notes = Some("Introduce the team and the product."); } let deck = Deck::new(Theme::default(), slides); ``` With `cs` enabled, change the lecture source and language in the layout: ```rust use fframes_kit::{prelude::*, kits::cs::Language}; let mut slides = slides::templates::python_lecture(); slides[2].layout = SlideLayout::HighlightedCode { title: "A function", code: "def square(x):\n return x * x", language: Language::Python, }; ``` These are ordinary slides: their layouts, build and hold timing, transitions, presenter stops and Studio editing follow the rules in [Slides](https://fframes-kit.pages.dev/docs/slides/slides.md) and [Decks](https://fframes-kit.pages.dev/docs/slides/decks.md). A `HighlightedCode` uses the `cs` kit's `CodeBlock` to colour Python, HTML and JavaScript and move the focus between lines. Its source is changed in Rust, not in Studio, and is not executed. Use short examples: only the first 20 lines are drawn, long lines end in an ellipsis, and more lines shrink the text. `Code` remains a plain-text alternative without `cs`. `FeatureCards` draws at most three cards, each with editable heading and description. `Flow` draws up to four connected steps; use `slides::FlowCaption::Text` for an editable text caption or, with `latex`, `slides::FlowCaption::Formula` for a shared, pre-typeset `Formula`. The product image slide uses `product.png` as a placeholder; replace it with a file name from your video's media. The research template presents [Attention Is All You Need](https://arxiv.org/abs/1706.03762) by Vaswani et al. It covers the encoder and decoder, positional encoding, attention, training, inference and limitations. Its results cite Table 2 of the [2017 NeurIPS paper](https://papers.neurips.cc/paper_files/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf); the later arXiv revision reports a different English-to-French score. Speaker notes include additional detail and references. Replace or expand these slides for your own talk. `attention_lecture(&engine)` teaches the mechanism itself in eighteen slides. It starts from attention as a soft dictionary lookup, then gives queries, keys and values, the projections, the scores, the scaling by the square root of the key dimension, softmax and the weighted sum an `Equation` slide each. A worked example follows the word "it" in "The animal didn't cross the street because it was too tired": one `Bars` slide shows its illustrative scores, the next their softmax, with half the weight on "animal". The lecture closes with the whole formula as a `Flow`, causal masking, multi-head attention, the numbers of the base model and the quadratic cost, a recap and further reading. Its nine formulas are typeset when the deck is built, and every slide has speaker notes. It reads best with `themes::midnight()`, the `Grid` backdrop and a footer. Create the research deck with a `TexEngine` in your video's constructor, before rendering: ```rust use fframes_kit::{latex::TexEngine, prelude::*}; let engine = TexEngine::new(concat!(env!("CARGO_MANIFEST_DIR"), "/tex-cache")); let slides = slides::templates::research_talk(&engine)?; let deck = Deck::new(Theme::default(), slides); let lecture = Deck::new(slides::themes::midnight(), slides::templates::attention_lecture(&engine)?) .backdrop(slides::Backdrop::Grid) .footer("Attention, step by step"); assert_eq!(lecture.slides().count(), 18); ``` `MathTex` uses Typst by default and caches the result. The formula caption is changed by typesetting a replacement before rendering, not in Studio. Embed the formula cache for the web editor as described in [LaTeX setup](https://fframes-kit.pages.dev/docs/start/latex-setup.md). Gallery scenes: `TemplatePitch`, `TemplateReview`, `TemplateTalk`, `TemplateAttention`, `TemplateAttentionMask`, `TemplateMarketing`, `TemplateProduct`, `TemplateResearch`, `TemplatePython`, `TemplateWebHtml`, `TemplateWeb`, `TemplateResearchQkv`, `TemplateResearchAttention`, `TemplateResearchTraining`. Gallery clip of `TemplatePitch`: https://fframes-kit.pages.dev/media/scenes/TemplatePitch-fdc4c036.mp4 Gallery clip of `TemplateReview`: https://fframes-kit.pages.dev/media/scenes/TemplateReview-eaf1ce8a.mp4 Gallery clip of `TemplateTalk`: https://fframes-kit.pages.dev/media/scenes/TemplateTalk-472ac739.mp4 Gallery clip of `TemplateAttention`: https://fframes-kit.pages.dev/media/scenes/TemplateAttention-ac06e222.mp4 Gallery clip of `TemplateAttentionMask`: https://fframes-kit.pages.dev/media/scenes/TemplateAttentionMask-50d5960b.mp4 Gallery clip of `TemplateMarketing`: https://fframes-kit.pages.dev/media/scenes/TemplateMarketing-e284e4ab.mp4 Gallery clip of `TemplateProduct`: https://fframes-kit.pages.dev/media/scenes/TemplateProduct-017de39a.mp4 Gallery clip of `TemplateResearch`: https://fframes-kit.pages.dev/media/scenes/TemplateResearch-059d4000.mp4 Gallery clip of `TemplatePython`: https://fframes-kit.pages.dev/media/scenes/TemplatePython-a8406eb8.mp4 Gallery clip of `TemplateWebHtml`: https://fframes-kit.pages.dev/media/scenes/TemplateWebHtml-2859e89a.mp4 Gallery clip of `TemplateWeb`: https://fframes-kit.pages.dev/media/scenes/TemplateWeb-3b8428d1.mp4 Gallery clip of `TemplateResearchQkv`: https://fframes-kit.pages.dev/media/scenes/TemplateResearchQkv-fe0fb3b4.mp4 Gallery clip of `TemplateResearchAttention`: https://fframes-kit.pages.dev/media/scenes/TemplateResearchAttention-f41448cc.mp4 Gallery clip of `TemplateResearchTraining`: https://fframes-kit.pages.dev/media/scenes/TemplateResearchTraining-322d7f08.mp4 --- # Decks > Deck::new(theme, slides) makes one fframes Scene per slide, named by the slide's key, so frame agenda@end on the command line finds it. Page: https://fframes-kit.pages.dev/docs/slides/decks/ `Deck::new(theme, slides)` makes one fframes `Scene` per slide, named by the slide's key, so `frame agenda@end` on the command line finds it. Give the video `Duration::Auto`, return `deck.scenes()` from `define_scenes` and draw them with `ctx.render_scenes(&frame)`. Between two slides the deck plays a `Fade` of the theme's `motion.duration.base` (0.4 s). `.transition(presentation, timing)` takes any presentation of the transitions (`Slide`, `Wipe`, `Iris`, ...), `.no_transition()` cuts. With a transition each slide starts that long before the previous one ends (`Overlap::Previous`): its own time starts with the transition, so it builds in while it comes in, and the slide before it holds under it. `transition_seconds()` gives the length. `deck.slides()` gives the slides in play order, for their keys, `seconds()`, `reveal_times()` and `notes()`. ```rust use fframes_kit::prelude::*; #[derive(Debug)] struct Talk { deck: Deck<'static>, } impl Talk { fn new() -> Self { let deck = Deck::new( Theme::default(), vec![ slides::Slide::new( "intro", SlideLayout::Title { title: "Decks in fframes", subtitle: Some("A short talk") }, ), slides::Slide::new("why", SlideLayout::bullets("Why", vec!["One file", "Real video"])), slides::Slide::new( "end", SlideLayout::Quote { text: "Thank you", author: None }, ), ], ) .transition(Wipe::default(), Timing::default()); Self { deck } } } impl Video for Talk { const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080; fn duration(&self) -> Duration<'_> { Duration::Auto } fn audio(&self) -> AudioMap<'_> { AudioMap::none() } fn define_scenes(&self) -> Scenes<'_> { self.deck.scenes() } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { svgr!( {ctx.render_scenes(&frame)} ) } } let talk = Talk::new(); let keys: Vec<&str> = talk.deck.slides().map(|s| s.key).collect(); assert_eq!(keys, ["intro", "why", "end"]); assert_eq!(talk.deck.transition_seconds(), 0.8); ``` ### Editable slide text With the kit's `studio` feature the text of the slides is editable in fframes Studio's Props panel: `deck.with_props(state, view)` gives every slide's `Ui` the props source `view` makes of `state` for the slide's scene. The editable objects are `SlideText`s keyed `title`, `subtitle`, `item-1`, `item-2`, ..., `left`, `right`, `step-1`, `step-2`, ..., `caption`, `quote`, `author`, `bar-1`, `bar-2`, ..., `stat-1-value`, `stat-1-label`, ..., `event-1-date`, `event-1-text`, ..., each with its `text` and `fill`. A figure edited in Studio no longer counts up. Edited text fits the same box as the code's. The example uses fframes Studio's `PropsStore`, which is not a dependency of the kit: ```rust title="Not compiled" let store = Arc::new(PropsStore::default()); let deck = Deck::new(theme, slides) .with_props(store.clone(), |store, scene| Box::new(store.scene_view(scene))); ``` --- # A complete deck > A whole video: the pitch template, renamed and extended with a slide of your own, in the sunset theme over a glow, with a footer and a slide transition… Page: https://fframes-kit.pages.dev/docs/slides/a-complete-deck/ A whole video: the pitch template, renamed and extended with a slide of your own, in the sunset theme over a glow, with a footer and a slide transition between the slides. Render it with the fframes command line, or step through it in fframes Studio, which stops at the slides' reveal times. ```rust use fframes_kit::prelude::*; #[derive(Debug)] struct Pitch { deck: Deck<'static>, } impl Pitch { fn new() -> Self { let mut slides = slides::templates::pitch_deck(); slides[0].layout = SlideLayout::Title { title: "Lumen", subtitle: Some("Lighting design in an afternoon"), }; // one more slide before the closing page let team = slides::Slide { hold: 4.0, notes: Some("Who we are, in one breath each."), ..slides::Slide::new( "pitch-team", SlideLayout::FeatureCards { title: "The team", items: vec![ ("Ana", "CEO, ten years in lighting design."), ("Ben", "CTO, built rendering engines."), ("Chloe", "Design, ran a studio of twenty."), ], }, ) }; let close = slides.len() - 1; slides.insert(close, team); let deck = Deck::new(slides::themes::sunset(), slides) .backdrop(slides::Backdrop::Glow) .footer("Lumen · Seed round") .transition(Slide::default(), Timing::default()); Self { deck } } } impl Video for Pitch { const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080; fn duration(&self) -> Duration<'_> { Duration::Auto } fn audio(&self) -> AudioMap<'_> { AudioMap::none() } fn define_scenes(&self) -> Scenes<'_> { self.deck.scenes() } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { svgr!( {ctx.render_scenes(&frame)} ) } } let pitch = Pitch::new(); assert_eq!(pitch.deck.slides().count(), 11); // the title page has no footer; the team slide is number 10 of 11 let footers: Vec<_> = pitch.deck.slides().map(|s| s.footer.and_then(|f| f.page)).collect(); assert_eq!(footers[0], None); assert_eq!(footers[9], Some((10, 11))); // the presenter's script let script: Vec<&str> = pitch.deck.slides().filter_map(|s| s.notes()).collect(); assert_eq!(script.len(), 11); ``` The quarterly review and the talk are built the same way: ```rust use fframes_kit::prelude::*; let review = Deck::new(slides::themes::paper(), slides::templates::quarterly_review()) .backdrop(slides::Backdrop::Gradient) .footer("Q3 review"); let talk = Deck::new(slides::themes::midnight(), slides::templates::conference_talk()) .backdrop(slides::Backdrop::Grid) .footer("Video as code") .transition(Fade::default(), Timing::default()); let minutes = |deck: &Deck| deck.slides().map(slides::Slide::seconds).sum::() / 60.0; assert!(minutes(&review) > 0.5 && minutes(&talk) > 0.5); ``` --- # Your own component > A component is a struct and one render. Page: https://fframes-kit.pages.dev/docs/advanced/your-own-component/ A component is a struct and one `render`. Components with theme defaults implement `Themed`. ```rust use fframes_kit::prelude::*; pub struct Badge<'a> { pub label: &'a str, pub fill: Color, pub x: f32, pub y: f32, } impl Themed for Badge<'_> { fn themed(theme: &Theme) -> Self { Self { label: "", fill: theme.colors.accent, x: 0.0, y: 0.0 } } } impl<'a> Component<'a> for Badge<'a> { fn render(self, ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let s = ui.theme.typography.caption; let enter: FadeIn = ui.defaults(); let badge = svgr!( {self.label} ); enter.apply(ui, badge) } } // A list of different components is a `Vec`. fn badges<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let list: Vec> = vec![ Box::new(Badge { label: "new", ..ui.defaults() }), Box::new(Badge { label: "beta", x: 300.0, ..ui.defaults() }), ]; ui.show(list) } ``` --- # Tag form (ui!) > ui! is svgr! markup that also takes component tags. Page: https://fframes-kit.pages.dev/docs/advanced/tag-form-ui/ `ui!` is `svgr!` markup that also takes component tags. Each tag is expanded to the struct form above, so the two forms draw the same frames. The struct form stays the main documented way. The tag form is shorter for markup that nests containers. Gallery scenes: `TagsSquareToCircle`, `TagsFadeIn`. Gallery clip of `TagsSquareToCircle`: https://fframes-kit.pages.dev/media/scenes/TagsSquareToCircle-7bad6189.mp4 Gallery clip of `TagsFadeIn`: https://fframes-kit.pages.dev/media/scenes/TagsFadeIn-0de626b6.mp4 - A tag whose name starts with an uppercase letter is a component; everything else is passed to `svgr!` unchanged. - Attributes are fields: - a quoted value or an `{expr}` goes through `.into()`: `name="Ada"`, `enter={fade}`; - a number or `true`/`false` alone in braces is written as is, so the field's type decides it: `x={60.0}`, `times={3}`; - a bare attribute is `true`: ``. Quoted values suit text fields. A number field needs braces: `cx={320.0}`, not `cx="320"`. - Children fill the component's `children: Children` field. They are a closure over the container's `ui`, so tags inside them see its local time. - The closure runs every frame, so build what it draws inside it. - A value it uses must be `Copy`, a reference, or `'a`. A `Svgr` made outside cannot be moved in. - Containers whose children are a `Vec` (`LaggedStart`, `AnimationGroup`) keep the struct form. - Every component with theme defaults works as a tag, your own included. That is every component that implements `Themed`; the time containers do too. - It needs a variable `ui: Ui` in scope. ```rust use fframes_kit::prelude::*; fn card<'a>(frame: Frame, ctx: &FFramesContext<'a, '_>, theme: &'a Theme) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, theme); let create: Option = Some(Create::default().into()); ui!( {format!("{:.1} s", ui.seconds())} ) } ``` A dashed attribute is an error that names the field (`min-width` is `min_width`): ```rust title="Does not compile" use fframes_kit::prelude::*; fn card<'a>(frame: Frame, ctx: &FFramesContext<'a, '_>, theme: &'a Theme) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, theme); ui!() } ``` Without a `ui` in scope the compiler reports "cannot find value `ui`" at the first tag. Create one with `let mut ui = Ui::new(frame, ctx, &self.theme)`: ```rust title="Does not compile" use fframes_kit::prelude::*; fn card<'a>(frame: Frame, ctx: &FFramesContext<'a, '_>, theme: &'a Theme) -> Svgr<'a> { ui!() } ``` --- # Editable props (fframes Studio) > fframes Studio can change a component's props without code edits. Page: https://fframes-kit.pages.dev/docs/advanced/editable-props-fframes-studio/ fframes Studio can change a component's props without code edits. In the Props panel, select the component on the canvas, change a value, and the preview updates. The values are saved in the project's `props.json`. Gallery scene: `Editable`. Without a props source, it shows the component's original values. Gallery clip of `Editable`: https://fframes-kit.pages.dev/media/scenes/Editable-32d05407.mp4 A component is editable when it has a key: - in the struct form, `ui.show(LowerThird { .. }.editable("speaker"))`; - in a tag, ``. Keys must be unique within a scene. - Without the kit feature `studio`, `editable` renders the component unchanged. Code that marks components therefore builds the same everywhere. - With `studio`, every kit component implements `Props`. Its editable fields are numbers, `bool`, text, `Color`, `Option` (`null` is none) and a fixed `Anim`. - Children, slices, keyframes and styles stay in code. - Your own components get it with `#[derive(Props)]`. Use `#[props(skip)]` to keep a field out, and `#[props(min = 0.0, max = 100.0, step = 1.0, unit = "px")]` to bound a number. - Enums whose variants have no fields get it with `#[derive(PropChoice)]`. The `Ui` takes its values from a `PropsSource`, set with `Ui::with_props`: ```rust use fframes_kit::prelude::*; fn title<'a>(frame: Frame, ctx: &FFramesContext<'a, '_>, theme: &'a Theme) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, theme); ui.show( LowerThird { name: "Ada".into(), ..ui.defaults() } .editable("speaker"), ) } fn with_source<'a>( frame: Frame, ctx: &FFramesContext<'a, '_>, theme: &'a Theme, source: impl PropsSource + 'a, ) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, theme).with_props(source); ui!() } ``` When the component renders with a source: - the source's values for it replace the code's values (`PropsSource::values`); - it is drawn inside a `` with the id the source gives (`PropsSource::element_id`); - while the source listens, it hears the schema, the code's values and the drawn values (`PropsSource::rendered`); - a value that does not fit its field is skipped with a warning in `inspect`. The source is made per `Ui`, so it can hold one snapshot of the values; the `Ui`s of containers share it. ### With fframes Studio The kit does not depend on Studio. Studio's source is the `PropsStore` of the `fframes-studio-kit` crate in the fframes Studio repository. It holds the project's `props.json`, names each object with Studio's editor identity (the scene's `editor_instance_key`, else its name; `video` outside scenes; the type name; the key; `main`), and answers the props panel: ```rust title="Not compiled" use fframes_studio_kit::PropsStore; // render_frame of a video, and of a scene let mut ui = Ui::new(frame, ctx, &self.theme).with_props(self.props.view()); let mut ui = Ui::for_scene(frame, ctx, theme, self).with_props(props.scene_view(self)); // the project's Studio worker let props = Arc::new(PropsStore::load("props.json")?); // exports read the same document props.listen(); // record each editable object's schema and values for the panel let config = config.with_props_args(&args).with_props(props.hooks()); // export: run_export_with_props(&video, &options, &args, &props.hooks()) // export capabilities: print_export_capabilities_with_props::() ``` ## API ### Editable A component with an editor key, see the module docs. | Field | Type | Default | Description | | --- | --- | --- | --- | | `key` | `Cow` | — | The key the editor stores this component's values under. | | `component` | `C` | — | The component whose props the key makes editable. | It has no theme defaults: build it with `Editable::new(..)`. Methods: - `fn new(key: impl Into>, component: C) -> Self` [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/props.rs#L44) --- # A whole video > The gallery (gallery/ of the repository) has one scene per component; render one with cargo run --release -p components-gallery -- frame Sequence@50%. Page: https://fframes-kit.pages.dev/docs/advanced/a-whole-video/ ```rust use fframes_kit::prelude::*; #[derive(Debug)] pub struct MyVideo { pub theme: Theme, } impl Video for MyVideo { const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080; fn duration(&self) -> Duration<'_> { Duration::Seconds(6.0) } fn audio(&self) -> AudioMap<'_> { AudioMap::none() } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { let mut ui = Ui::new(frame, ctx, &self.theme); let c = self.theme.colors; let title = self.theme.typography.title; let content = ui.show(Series { items: vec![ (3.0, children(move |ui| { let fade = FadeIn { shift: (0.0, -40.0), ..ui.defaults() }; let out: FadeOut = ui.defaults(); let text = svgr!( "Hello" ); let entered = fade.apply(ui, text); out.apply(ui, entered) })), (3.0, children(move |ui| { let fade: FadeIn = ui.defaults(); fade.apply(ui, svgr!()) })), ], overlap: 0.0, }); svgr!( {content} ) } } ``` The gallery (`gallery/` of the repository) has one scene per component; render one with `cargo run --release -p components-gallery -- frame Sequence@50%`. --- # Math kit > The math kit is the math feature of fframes-kit (fframes-kit = { .., features = ["math"] }). Page: https://fframes-kit.pages.dev/docs/math/ Cargo feature: `math` (`fframes-kit = { version = "..", features = ["math"] }`) The math kit is the `math` feature of `fframes-kit` (`fframes-kit = { .., features = ["math"] }`). Its figures are built from the explain layer (`Axes`, `NumberLine`, `FunctionGraph`, see the `explain` module docs) and come with the prelude. Animated values are `ValueTracker`s: a `timeline!` stored in the video and read with `ui.sample`. Every entry names its scene in the components gallery (`cargo run --release -p components-gallery -- strip -n 8`). The calculus figures are overlays. They take the mapping of the line or axes they sit on and draw only themselves, so you show the line or axes once and put several figures on it. `FunctionPlot` draws its axes too. An `Axes` with `y_scaling: Scaling::LogBase(10.0)` (or `x_scaling`) is logarithmic; its range is in exponents and the figures on it take values. The geometry figures (`UnitCircle`, `LinearTransform`, `VectorSum`, `Pythagoras`) stand alone and are placed in units of `ui.space`; show them in a `ui.with_space(..)` to draw them at another scale. - [NumberPoint and Interval](https://fframes-kit.pages.dev/docs/math/number-point-and-interval.md): NumberPoint is a dot on a number line at a tracked value, with a label above it that can write the value. - [FunctionPlot](https://fframes-kit.pages.dev/docs/math/function-plot.md): Axes with a graph, its label and a tracer: a dot that follows a tracked x along the graph, with a dashed line down to the x-axis and the point's… - [Tangent and Secant](https://fframes-kit.pages.dev/docs/math/tangent-and-secant.md): Tangent is the tangent of a function at a tracked x, a line of fixed length centered on the point. - [RiemannSum and AreaUnder](https://fframes-kit.pages.dev/docs/math/riemann-sum-and-area-under.md): RiemannSum draws Riemann rectangles with each width of dxs in turn, cross-fading from one to the next, and can write the sum. - [UnitCircle](https://fframes-kit.pages.dev/docs/math/unit-circle.md): The unit circle at a tracked angle (radians, counterclockwise): the radius to the point, the cosine along the x-axis, the sine up to the point and the… - [LinearTransform and VectorSum](https://fframes-kit.pages.dev/docs/math/linear-transform-and-vector-sum.md): LinearTransform is Manim's ApplyMatrix on a LinearTransformationScene. - [Pythagoras](https://fframes-kit.pages.dev/docs/math/pythagoras.md): A right triangle with legs a and b and a square on each side, each drawn with its own squares_enter and labeled with its area (a² = 9, b² = 16, c² = 25).… - [FractionBar and FractionPie](https://fframes-kit.pages.dev/docs/math/fraction-bar-and-fraction-pie.md): A bar or a circle cut into denominator equal parts, of which the first numerator fill one after another (stagger seconds apart, from delay). - [MatrixView](https://fframes-kit.pages.dev/docs/math/matrix-view.md): A matrix product worked out entry by entry: a × b = c, centered on (x, y). - [Plots](https://fframes-kit.pages.dev/docs/math/plots.md): fframes_kit::plot draws data on axes. - [NormalFit](https://fframes-kit.pages.dev/docs/math/normal-fit.md): Samples pouring into a histogram, then the normal curve that fits them. samples is a slice, and shown (a tracker) says how many of them are binned so… - [Epicycles](https://fframes-kit.pages.dev/docs/math/epicycles.md): A chain of turning circles whose end draws a closed path, as in the 3Blue1Brown Fourier series. fourier(path, samples, terms) resamples the path along… --- # NumberPoint and Interval > NumberPoint is a dot on a number line at a tracked value, with a label above it that can write the value. Page: https://fframes-kit.pages.dev/docs/math/number-point-and-interval/ Cargo feature: `math` (`fframes-kit = { version = "..", features = ["math"] }`) `NumberPoint` is a dot on a number line at a tracked value, with a label above it that can write the value. `Interval` marks a range of the line with a filled dot at a closed end and a ring at an open one. Gallery scenes: `NumberPoint`, `Interval`, `NumberLinePoint`. Gallery clip of `NumberPoint`: https://fframes-kit.pages.dev/media/scenes/NumberPoint-5d659dc0.mp4 Gallery clip of `Interval`: https://fframes-kit.pages.dev/media/scenes/Interval-76f6ff56.mp4 Gallery clip of `NumberLinePoint`: https://fframes-kit.pages.dev/media/scenes/NumberLinePoint-6a342b08.mp4 ```rust use fframes::animation::KeyFramesAnimation; use fframes_kit::prelude::*; fn figure<'a>(ui: &mut Ui<'a, '_, '_>, x: &'a KeyFramesAnimation) -> Svgr<'a> { let line = NumberLine { range: Range::new(-4.0, 4.0, 1.0), length: 12.0, numbers: (-4..=4).map(f64::from).collect(), ..ui.defaults() }; let point = ui.show(NumberPoint { line: line.clone(), value: x.into(), label: "x = ".into(), decimals: Some(1), ..ui.defaults() }); // [-1, 2) let interval = ui.show(Interval { line: line.clone(), from: -1.0, to: 2.0, closed: (true, false), ..ui.defaults() }); let line = ui.show(line); svgr!({line}{interval}{point}) } ``` The point and interval can also be shown separately on the same kind of number line: ```rust use fframes_kit::prelude::*; fn point<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let line = NumberLine { range: Range::new(-4.0, 4.0, 1.0), ..ui.defaults() }; let point = ui.show(NumberPoint { line: line.clone(), value: 1.5.into(), label: "x = ".into(), decimals: Some(1), ..ui.defaults() }); let line = ui.show(line); svgr!({line}{point}) } fn interval<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let line = NumberLine { range: Range::new(-4.0, 4.0, 1.0), ..ui.defaults() }; let interval = ui.show(Interval { line: line.clone(), from: -1.0, to: 2.0, closed: (true, false), ..ui.defaults() }); let line = ui.show(line); svgr!({line}{interval}) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### NumberPoint A dot on a number line at `value`, with `label` above it (Manim's `Dot(line.n2p(x))` with a `ValueTracker`). With `decimals` the value is written after the label: `label: "x = "` and `decimals: Some(1)` write `x = 2.5`. The line is drawn separately. | Field | Type | Default | Description | | --- | --- | --- | --- | | `line` | [`NumberLine`](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) | `NumberLine::themed(theme)` | The number line the dot sits on; it only maps numbers to points and is not drawn by this component. | | `value` | `ValueTracker` | `0.0.into()` | The number on `line` the dot marks; a tracker, so it can slide along the line. | | `label` | `Cow` | `Cow::Borrowed("")` | Text written above the dot; empty for none. | | `decimals` | `Option` | `None` | Digits after the decimal point of the value written after the label; `None` writes the label only. | | `radius` | `f32` | `12.0` | Radius of the dot in pixels. | | `color` | `Color` | `theme.colors.highlight` | Fill of the dot. | | `label_style` | `TextStyle` | `theme.typography.caption` | The font of the label: family, size in pixels and weight. | | `label_fill` | `Color` | `theme.colors.title` | Colour of the label. | | `buff` | `f32` | `theme.spacing.md` | Pixels between the dot and the label. | | `enter` | `Option` | `None` | Fade in at the start; `None` for none. | | `exit` | `Option` | `None` | Fade out before the end; `None` for none. | Feature `math` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/kits/math/number_line.rs#L21) ### Interval The numbers from `from` to `to` marked on a number line: a thick segment with a filled dot at a closed end and a ring at an open one (`[a, b)` is `closed: (true, false)`). The line is drawn separately. | Field | Type | Default | Description | | --- | --- | --- | --- | | `line` | [`NumberLine`](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) | `NumberLine::themed(theme)` | The number line the interval sits on; it only maps numbers to points and is not drawn by this component. | | `from` | `f64` | `0.0` | The first end, as a number on `line`. | | `to` | `f64` | `1.0` | The second end, as a number on `line`. | | `closed` | `(bool, bool)` | `(true, true)` | Whether the `from` end and the `to` end are included: a filled dot when `true`, a ring when `false`. | | `color` | `Color` | `theme.colors.accent` | Colour of the segment and the end dots. | | `stroke_width` | `f32` | `theme.stroke.thick` | Segment width in pixels; a ring's outline is half of it. | | `radius` | `f32` | `12.0` | Radius of the end dots and rings. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | Feature `math` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/kits/math/number_line.rs#L120) ## See also - [Coordinate systems](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) --- # FunctionPlot > Axes with a graph, its label and a tracer: a dot that follows a tracked x along the graph, with a dashed line down to the x-axis and the point's… Page: https://fframes-kit.pages.dev/docs/math/function-plot/ Cargo feature: `math` (`fframes-kit = { version = "..", features = ["math"] }`) Axes with a graph, its label and a tracer: a dot that follows a tracked `x` along the graph, with a dashed line down to the x-axis and the point's coordinates. Build `graph` with `axes.plot` on the same axes. Gallery scene: `FunctionPlot`. Gallery clip of `FunctionPlot`: https://fframes-kit.pages.dev/media/scenes/FunctionPlot-c92bfcf0.mp4 ```rust use fframes::animation::KeyFramesAnimation; use fframes_kit::prelude::*; fn figure<'a>(ui: &mut Ui<'a, '_, '_>, x: &'a KeyFramesAnimation) -> Svgr<'a> { let axes = Axes { x_range: Range::new(-3.0, 3.0, 1.0), y_range: Range::new(-1.0, 4.0, 1.0), x_length: 9.0, y_length: 5.0, ..ui.defaults() }; let graph = axes.plot(ui, |x| 0.4 * x * x); ui.show(FunctionPlot { graph, axes, label: "0.4 x²".into(), label_at: 2.5, tracer: Some(x.into()), ..ui.defaults() }) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### FunctionPlot | Field | Type | Default | Description | | --- | --- | --- | --- | | `axes` | [`Axes`](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) | `Axes::themed(theme)` | The axes the graph is drawn on. | | `graph` | [`FunctionGraph`](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) | `FunctionGraph::themed(theme)` | The curve, built with `Axes::plot` on the same axes. | | `label` | `Cow` | `Cow::Borrowed("")` | Written next to the graph at `label_at`, empty for none. | | `label_at` | `f64` | `0.0` | The x, in the axes' coordinates, where the label sits on the graph. | | `label_direction` | `Anchor` | `Anchor::TopRight` | Which side of that point the label is placed on. | | `label_enter` | `Option` | `None` | How the label appears; `None` shows it from the start. | | `tracer` | `Option` | `None` | `x` of the tracer, `None` for no tracer. | | `decimals` | `usize` | `2` | Decimals of the coordinates written at the tracer. | | `tracer_color` | `Color` | `theme.colors.highlight` | Colour of the tracer dot, its dashed line and its coordinates. | | `tracer_enter` | `Option` | `None` | Fade in of the tracer at the start; `None` for none. | | `tracer_exit` | `Option` | `None` | Fade out of the tracer before the end; `None` for none. | Feature `math` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/kits/math/function_plot.rs#L21) ## See also - [Coordinate systems](https://fframes-kit.pages.dev/docs/layout-and-shapes/coordinate-systems.md) --- # Tangent and Secant > Tangent is the tangent of a function at a tracked x, a line of fixed length centered on the point. Page: https://fframes-kit.pages.dev/docs/math/tangent-and-secant/ Cargo feature: `math` (`fframes-kit = { version = "..", features = ["math"] }`) `Tangent` is the tangent of a function at a tracked `x`, a line of fixed length centered on the point. `Secant` goes through `x` and `x + dx`, with the dashed `dx` and `dy` legs of Manim's `get_secant_slope_group`. Animate `dx` to 0 and it ends on the tangent. Both take the axes' mapping, `axes.map(ui)`. Gallery scenes: `Tangent`, `Secant`, `TangentSecant`. Gallery clip of `Tangent`: https://fframes-kit.pages.dev/media/scenes/Tangent-7f81e61a.mp4 Gallery clip of `Secant`: https://fframes-kit.pages.dev/media/scenes/Secant-16faf54c.mp4 Gallery clip of `TangentSecant`: https://fframes-kit.pages.dev/media/scenes/TangentSecant-54883dda.mp4 ```rust use fframes::animation::KeyFramesAnimation; use fframes_kit::prelude::*; fn parabola(x: f64) -> f64 { 0.4 * x * x } fn figure<'a>(ui: &mut Ui<'a, '_, '_>, dx: &'a KeyFramesAnimation) -> Svgr<'a> { let axes = Axes { x_range: Range::new(-3.0, 3.0, 1.0), y_range: Range::new(0.0, 4.0, 1.0), ..ui.defaults() }; let map = axes.map(ui); let secant = ui.show(Secant { f: parabola, axes: map, x: 1.0.into(), dx: dx.into(), ..ui.defaults() }); let tangent = ui.show(Tangent { f: parabola, axes: map, x: (-1.5).into(), ..ui.defaults() }); let graph = ui.show(axes.plot(ui, parabola)); let axes = ui.show(axes); svgr!({axes}{graph}{secant}{tangent}) } ``` To show either overlay alone, draw its graph and axes separately: ```rust use fframes_kit::prelude::*; fn parabola(x: f64) -> f64 { 0.4 * x * x } fn tangent<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes { x_range: Range::new(-3.0, 3.0, 1.0), y_range: Range::new(0.0, 4.0, 1.0), ..ui.defaults() }; let line = ui.show(Tangent { f: parabola, axes: axes.map(ui), x: 1.0.into(), ..ui.defaults() }); let graph = ui.show(axes.plot(ui, parabola)); let axes = ui.show(axes); svgr!({axes}{graph}{line}) } fn secant<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes { x_range: Range::new(-3.0, 3.0, 1.0), y_range: Range::new(0.0, 4.0, 1.0), ..ui.defaults() }; let line = ui.show(Secant { f: parabola, axes: axes.map(ui), x: 1.0.into(), dx: 1.5.into(), ..ui.defaults() }); let graph = ui.show(axes.plot(ui, parabola)); let axes = ui.show(axes); svgr!({axes}{graph}{line}) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### Tangent The tangent of `f` at `x`, `length` pixels long and centered on the point, with a dot on the point when `dot` is above 0 (Manim's `TangentLine`). `x` is a `ValueTracker`, so the line can slide along the graph. | Field | Type | Default | Description | | --- | --- | --- | --- | | `f` | `Func` | `\|x\| x` | The function, taking x in the axes' coordinates and returning y. | | `axes` | `AxesMap` | `AxesMap::default()` | The axes (an `AxesMap`) the function sits on; turns graph coordinates into canvas points. | | `x` | `ValueTracker` | `0.0.into()` | The x, in the axes' coordinates, of the point of tangency; a tracker so it can slide along the graph. | | `length` | `f32` | `400.0` | Length of the line in pixels. | | `color` | `Color` | `theme.colors.highlight` | Colour of the line and its dot. | | `stroke_width` | `f32` | `theme.stroke.regular` | Line width in pixels. | | `dot` | `f32` | `10.0` | Radius in pixels of the dot on the point; `0` for no dot. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | Feature `math` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/kits/math/calculus.rs#L72) ### Secant The secant of `f` through `x` and `x + dx`, extended `extend` pixels past both points, with dots on them and, when `legs` is set, the dashed `dx` and `dy` legs (Manim's `get_secant_slope_group`). As `dx` goes to 0 it turns into the tangent. | Field | Type | Default | Description | | --- | --- | --- | --- | | `f` | `Func` | `\|x\| x` | The function, taking x in the axes' coordinates and returning y. | | `axes` | `AxesMap` | `AxesMap::default()` | The axes (an `AxesMap`) the function sits on; turns graph coordinates into canvas points. | | `x` | `ValueTracker` | `0.0.into()` | The x, in the axes' coordinates, of the first point; a tracker so it can slide along the graph. | | `dx` | `ValueTracker` | `1.0.into()` | The distance from `x` to the second point, in the axes' x units; as it goes to 0 the secant becomes the tangent. | | `extend` | `f32` | `120.0` | Pixels the line runs past each of the two points. | | `legs` | `bool` | `true` | Draws the dashed `dx` and `dy` legs of the slope triangle when `true`. | | `color` | `Color` | `theme.colors.highlight` | Colour of the secant line and its dots. | | `legs_color` | `Color` | `theme.colors.muted` | Colour of the dashed legs. | | `stroke_width` | `f32` | `theme.stroke.regular` | Line width in pixels; the legs draw at half of it. | | `dot` | `f32` | `10.0` | Radius in pixels of the dots on the two points; `0` for no dots. | | `enter` | `Option` | `None` | How it appears; `None` shows it from the start. | | `exit` | `Option` | `None` | How it leaves at the end of its container; `None` stays. | Feature `math` · [Source](https://github.com/khanhthanhdev/fframes-kit/blob/main/fframes-kit/src/kits/math/calculus.rs#L143) --- # RiemannSum and AreaUnder > RiemannSum draws Riemann rectangles with each width of dxs in turn, cross-fading from one to the next, and can write the sum. Page: https://fframes-kit.pages.dev/docs/math/riemann-sum-and-area-under/ Cargo feature: `math` (`fframes-kit = { version = "..", features = ["math"] }`) `RiemannSum` draws Riemann rectangles with each width of `dxs` in turn, cross-fading from one to the next, and can write the sum. `AreaUnder` reveals the area under a graph from left to right and writes the integral of the part shown so far. Build their `rects` and `area` with `axes.riemann_rectangles` and `axes.area`. `integral(f, g, a, b, n)` is the trapezoid rule they use. Gallery scenes: `RiemannSum`, `AreaUnder`, `Area`, `RiemannRectangles`. Gallery clip of `RiemannSum`: https://fframes-kit.pages.dev/media/scenes/RiemannSum-4d8aa677.mp4 Gallery clip of `AreaUnder`: https://fframes-kit.pages.dev/media/scenes/AreaUnder-941ff31b.mp4 Gallery clip of `Area`: https://fframes-kit.pages.dev/media/scenes/Area-7c7c71b7.mp4 Gallery clip of `RiemannRectangles`: https://fframes-kit.pages.dev/media/scenes/RiemannRectangles-69f74c8e.mp4 ```rust use fframes_kit::prelude::*; fn figure<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes { x_range: Range::new(0.0, 3.0, 1.0), y_range: Range::new(0.0, 4.0, 1.0), ..ui.defaults() }; let f = |x: f64| 0.4 * x * x; let rects = RiemannRectangles { ..axes.riemann_rectangles(ui, f, (0.0, 2.0), 0.5) }; let region = Area { ..axes.area(ui, f, (2.0, 3.0)) }; let sum = ui.show(RiemannSum { rects, dxs: vec![0.5, 0.25, 0.1, 0.05], sum: Some(Label { text: "sum = ".into(), x: 1400.0, y: 300.0, ..ui.defaults() }), ..ui.defaults() }); let area = ui.show(AreaUnder { area: region, delay: 1.0, value: Some(Label { text: "area = ".into(), x: 1400.0, y: 360.0, ..ui.defaults() }), ..ui.defaults() }); let graph = ui.show(axes.plot(ui, f)); let axes = ui.show(axes); svgr!({axes}{sum}{area}{graph}) } // the integral of 0.4 x² from 0 to 3 is 0.4 * 27 / 3 let area = fframes_kit::kits::math::integral(|x| 0.4 * x * x, None, 0.0, 3.0, 256); assert!((area - 3.6).abs() < 1e-3); ``` `Area` and `RiemannRectangles` also draw directly, without the animated wrappers: ```rust use fframes_kit::prelude::*; fn figure<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> { let axes = Axes { x_range: Range::new(0.0, 3.0, 1.0), y_range: Range::new(0.0, 4.0, 1.0), ..ui.defaults() }; let f = |x: f64| 0.4 * x * x; let rects = ui.show(axes.riemann_rectangles(ui, f, (0.0, 2.0), 0.5)); let area = ui.show(axes.area(ui, f, (2.0, 3.0))); let graph = ui.show(axes.plot(ui, f)); let axes = ui.show(axes); svgr!({axes}{rects}{area}{graph}) } ``` ## API Components are plain structs. Set the fields you need and fill the rest with `..ui.defaults()`: the defaults below come from the current theme (`theme.` values are fields of `Theme`). ### RiemannSum Riemann rectangles that get finer: `rects` is drawn with each width of `dxs` in turn, `hold` seconds each, cross-fading to the next over `crossfade` seconds, from `delay`. `sum` writes the Riemann sum after its text with `decimals`, fading with the rectangles. Build `rects` with `Axes::riemann_rectangles`. | Field | Type | Default | Description | | --- | --- | --- | --- | | `rects` | `RiemannRectangles` | `RiemannRectangles::themed(theme)` | The rectangles, built with `Axes::riemann_rectangles`; their width is replaced by each of `dxs` in turn. | | `dxs` | `Vec` | `vec![0.5, 0.25, 0.1]` | Rectangle widths, in the x units of the axes, shown one after the other (finer each time). | | `delay` | `f32` | `0.0` | Seconds after the container's start before the first set of rectangles appears. | | `hold` | `f32` | `1.0` | Seconds each set of rectangles stays before the next begins to replace it. | | `crossfade` | `f32` | `theme.motion.duration.base` | Seconds one set takes to fade into the next. | | `sum` | [`Option