Layout Overview
ftui-layout is the deterministic geometry engine for FrankenTUI. It has no
knowledge of colors, text, or rendering — it turns Rects and Constraints
into more Rects, and it decides where split boundaries live when a user
drags an edge with a trackpad. That’s it.
Two pillars anchor the crate. They share a file tree and a crate name, but they serve very different purposes, and most widgets only ever need the first one.
The two pillars
1D and 2D constraint vocabulary: Percentage, Length, Min, Max,
Ratio, Fill, FitContent, FitMin. One call to split(area).
A serializable split-tree with undo/redo, drag-resize state machines, semantic input, magnetic docking, and replay-safe journaling.
Pane workspacesEverything else in the crate — breakpoints, the e-graph optimizer, incremental cache, dep-graph — supports one of these two pillars.
Mental model
┌─────────────────────────────────────────────────────────────┐
│ ftui-layout │
│ │
│ ┌─────────────────────┐ ┌───────────────────────────┐ │
│ │ Flex + Grid │ │ Pane workspace │ │
│ │ (constraint solver)│ │ (9K-line split-tree) │ │
│ │ │ │ │ │
│ │ Rect ──split──▶ │ │ PaneTree + Operations │ │
│ │ Vec<Rect> │ │ PaneInteractionTimeline │ │
│ │ │ │ PaneDragResizeMachine │ │
│ │ stateless, │ │ PaneSemanticInputEvent │ │
│ │ per-frame │ │ magnetic docking │ │
│ └─────────────────────┘ └───────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ 90%+ of widget layout User-driven workspace UX │
│ (status bar, sidebar, grid) (IDE-style tmux-style panes) │
└─────────────────────────────────────────────────────────────┘Which one do I use?
Reach for Flex / Grid when…
You want to divide a Rect into sub-rects for a widget tree. You know at
build time (or at render time) the constraints: “left sidebar is 30 cells,
main is Fill, footer is 1 row.” The decision is per-frame and
stateless — you call split() inside view() and the result lives one
frame.
Reach for PaneTree when…
The user interactively manages layout. They drag dividers. They split panes with a keybind. They swap, close, undo, redo, and you need that entire history to be serializable to disk and replayable byte-for-byte. Think tmux, Zellij, or the editor/terminal pane of an IDE.
You almost never need both at once
A widget built with Flex inside a single pane leaf is the normal pattern.
The pane system positions the leaf; the leaf uses Flex to lay out its
internals. They do not fight.
The shared philosophy
Both pillars obey the same five rules:
- Deterministic. Same inputs, same outputs, every time. No RNG, no clock-dependent behavior, no global state.
- Serializable. Constraints and pane trees can be written to disk and
read back. Forward-compatible
extensionsbags let you round-trip future fields. - Validated. Malformed trees are rejected with diagnostic reports. Overspecified constraint sets produce a best-effort solve, not a panic.
- Allocation-conscious.
Rectsis aSmallVec<[Rect; 8]>; most layouts never touch the heap. - Host-agnostic. The crate does not import
ftui-render. Terminal and web backends consume the same outputs.
ftui-layout has zero unsafe code (#![forbid(unsafe_code)] at the
crate root). The complexity lives in the algorithms, not in raw pointers.
A representative example — both pillars in one screen
A three-pane workspace (editor | terminal / preview), where the editor is
internally a Flex-based status bar over a content area:
┌──────────────────────┬──────────────────────┐
│ editor pane (leaf) │ terminal pane (leaf) │
│ ┌──────────────────┐ │ │
│ │ gutter │ content │ │ ──────────────────── │
│ │ │ │ │ preview pane (leaf) │
│ └──────────────────┘ │ │
│ status bar │ │
└──────────────────────┴──────────────────────┘
└── Flex::vertical() ──┘ └── PaneTree: HSplit(left, VSplit(top, bot)) ──┘The PaneTree owns the vertical divider between columns and the
horizontal divider in the right column. The user can drag them. Inside the
editor leaf, a Flex::vertical owns the split between content and
status bar — the user cannot drag that divider because the widget author
didn’t expose it.
Where to go next
Constraint variants, split(area), alignment, overflow.
LayoutSizeHint, FitContent, letting widgets measure themselves.
Breakpoints Xs / Sm / Md / Lg / Xl and ResponsiveLayout.
Map of the 9K-line pane subsystem.
Pane system overviewEquality saturation over constraint expressions.
E-graph optimizerHow widgets consume Rects inside view(&mut Frame).