Skip to Content
ftui-extrasOverview

Extras overview

ftui-extras is the crate where FrankenTUI puts everything that is either large, feature-gated, or SIMD-heavy. Visual effects live here (Metaballs, Plasma, Doom Fire). So does Markdown rendering, Mermaid diagrams, tree-sitter syntax highlighting, chart widgets, and the image protocol bridges for iTerm2 and Kitty. Nothing in extras is required to ship a working TUI — it is the “if you want this, here is how” shelf.

That design choice is load-bearing. Not every app wants a 482 KB Mermaid renderer; not every app wants Markdown with every GFM extension. Keeping those on the extras shelf means a minimal ftui-widgets app stays lean and compiles fast, and the heavy modules are behind feature flags you opt into.

Source crate: ftui-extras/.

Why a separate crate

Three reasons:

  1. Optimization profile. Extras is compiled at opt-level = 3 even in debug builds. Visual effects and text-effects modules contain hot inner loops with SIMD-friendly shapes (Braille packing, plasma wave sums, metaball iso-surface sampling). opt-level = 0 runs them at single-digit FPS; opt-level = 3 holds 60.
  2. Feature gating. 20+ feature flags select which modules you pull in. Default is just canvas and charts; you opt into markdown, diagram, syntax, visual-fx, doom, quake, image, live, logging, clipboard, and so on.
  3. Crate budget. Extras alone is several MB of source. Keeping it separate lets the core crates (ftui-core, ftui-render, ftui-widgets) stay compact and individually publishable.

What’s inside

ModuleFeatureApprox sizePurpose
canvascanvas (default)56 KBPixel-level drawing primitives (lines, arcs, fills)
chartscharts (default)64 KBBar / line / pie charts built on canvas
visual_fx + effects/*visual-fx300 KBMetaballs, Plasma, Doom Fire, Screen Melt, etc.
visual_fx/gpufx-gpu26 KBOptional wgpu acceleration for supported effects
markdownmarkdown121 KBGFM + tables + code fences → styled text
mermaid + mermaid_*diagram~1.2 MBMermaid parser + layout + render
syntaxsyntax176 KBTree-sitter-backed syntax highlighting spans
text_effectstext-effects374 KBAnimated gradients, fades, ASCII art
themetheme162 KBColour themes + palette tokens (incl. TableTheme)
formsforms104 KBForm layout + validation widgets
exportexport51 KBBuffer → HTML / SVG / plain text
imageimage34 KBiTerm2 / Kitty image protocol bridges
livelive44 KBLive-updating display primitives
logginglogging46 KBtracing subscriber that writes into a widget log ring
clipboardclipboard62 KBOSC 52 clipboard plumbing
terminalterminalEmbedded terminal emulator for PTY panes
doomdoomDoom Fire + Melt effects (classic demoscene)
quakequakeQuake-console drop-down overlay

How to enable features

A minimal Cargo.toml that turns on Markdown and visual FX:

[dependencies] ftui-extras = { version = "0.3", features = ["markdown", "visual-fx"] }

Defaults (canvas, charts) are still on unless you set default-features = false.

Feature dependency graph

A few features imply others:

You can ask Cargo for exactly the set you want and nothing else.

The ThemeInputs boundary

Visual effects never reach into the global theme. They consume a single value type, ThemeInputs, and treat it as pure data (visual_fx.rs:186):

pub struct ThemeInputs { pub bg_base: PackedRgba, // deepest background pub bg_surface: PackedRgba, // cards / panels pub bg_overlay: PackedRgba, // legibility scrim pub fg_primary: PackedRgba, // primary text / lines pub fg_muted: PackedRgba, // secondary text / lines pub accent_primary: PackedRgba, pub accent_secondary: PackedRgba, pub accent_slots: [PackedRgba; 4], }

Why this matters:

  • Deterministic. Same inputs → same output pixels.
  • Testable. Unit-test an effect with a fixture theme and compare byte buffers.
  • No global state. An effect cannot accidentally depend on “the current theme” via an implicit lookup.

You build a ThemeInputs from whatever theme system you use (the ftui-extras::theme module ships a converter), then hand it to the effect.

Quality tiers and degradation

Every visual effect honours a FxQuality tier:

pub enum FxQuality { Off, // skip entirely Minimal, // one or two ops, near-static Reduced, // fewer iterations, simpler math Full, // normal detail }

The tier is derived from the frame’s DegradationLevel:

DegradationFxQuality
EssentialOnlyOff
NoStylingReduced
SimpleBordersFull
FullFull

Plus an area-based clamp: even at Full, if the target rect exceeds roughly 16 K cells (≈ 160×100), the tier drops one step to keep the effect’s cost bounded. You never have a Plasma effect unexpectedly spending 40 ms on a giant viewport.

Effects are optional by design — they exist to delight, not to inform. When the frame budget is tight, they go first. Primary content always wins.

Integration surface

From your runtime, extras look like any other widget or renderer. A typical visual FX integration:

use ftui_extras::visual_fx::{effects::MetaballsFx, ThemeInputs, FxQuality}; pub struct Model { metaballs: MetaballsFx, theme_inputs: ThemeInputs, } fn view(&self, frame: &mut ftui_render::frame::Frame) { let area = frame.buffer.bounds(); let quality = FxQuality::from_degradation(frame.degradation()); self.metaballs.render_into(area, frame, &self.theme_inputs, quality); }

No registration, no global init. The effect owns its state (ball positions, plasma phase, particle trails) and is a plain struct you store on your model.

Performance

  • Visual FX modules are compiled with opt-level = 3 regardless of the crate-level profile, so you do not need a release build to see smooth animation.
  • ThemeInputs is a plain struct, cloneable, 80 bytes on 64-bit. Pass by reference.
  • The SIMD shapes (especially Plasma’s sum-of-sines) vectorise cleanly on x86_64 AVX2 and aarch64 NEON. There is no runtime SIMD dispatch — the compiler emits what it can.

Pitfalls

  • Turning on diagram for a minimal app. Mermaid alone is ~1.2 MB of Rust source; link times grow noticeably. Don’t enable what you don’t use.
  • Ignoring the area clamp. If you force FxQuality::Full on a viewport that is 600×80, you will miss frame budgets. Let the clamp do its job.
  • Reaching into global theme from a custom effect. Don’t. Funnel the inputs through ThemeInputs so the effect stays pure.
  • Running visual FX during integration tests. Effects are non-deterministic by default (time-driven). Snapshot tests that render an effect will flap; either stub the time source or skip them.

Where next