Intrinsic Sizing
Most layout work is extrinsic: “this sidebar is 30 cells wide, that footer is 1 row tall.” But some widgets know their own size better than the caller does — a label knows how many cells its text occupies, a table knows its column widths, a list knows the height of a row. Intrinsic sizing is the contract that lets the solver ask the widget for that number.
LayoutSizeHint
A LayoutSizeHint is a 1D projection of a widget’s preferences along the
axis the Flex is solving:
pub struct LayoutSizeHint {
pub min: u16, // floor — widget will clip below this
pub preferred: u16, // ideal size for the content
pub max: Option<u16>, // ceiling (None = unbounded)
}Three constructors cover the common cases:
LayoutSizeHint::ZERO; // min=0, pref=0, max=None
LayoutSizeHint::exact(15); // min=pref=max=15
LayoutSizeHint::at_least(5, 15); // min=5, pref=15, max=NoneAnd one helper enforces the bounds:
let hint = LayoutSizeHint::at_least(5, 15);
assert_eq!(hint.clamp(10), 10); // inside range
assert_eq!(hint.clamp(3), 5); // below min → min
assert_eq!(hint.clamp(30), 30); // above pref, but max is NoneThe three intrinsic constraints
Three Constraint variants consult the measurer callback:
| Constraint | Allocation rule |
|---|---|
FitContent | Allocate hint.preferred, clamped to [hint.min, hint.max]. |
FitContentBounded { min, max } | Same, additionally clamped to the constraint’s explicit bounds. |
FitMin | Allocate exactly hint.min. Shrink-to-fit. |
When the measurer is not provided (or returns LayoutSizeHint::ZERO), all
three degrade gracefully to zero.
Worked example
use ftui_layout::{Flex, Constraint, LayoutSizeHint};
use ftui_render::Rect;
// A toolbar: left-aligned icons (fit content), stretchy spacer, clock.
let buttons = ["File", "Edit", "View", "Help"];
let flex = Flex::horizontal()
.constraints([
Constraint::FitContent, // buttons group
Constraint::Fill, // flexible gap
Constraint::FitContentBounded { // clock, 5..=12
min: 5,
max: 12,
},
])
.gap(1);
let area = Rect::new(0, 0, 80, 1);
let rects = flex.split_with_measurer(area, |index, _constraint| match index {
0 => LayoutSizeHint::exact(
buttons.iter().map(|b| b.len() as u16).sum::<u16>()
+ (buttons.len() as u16 - 1), // account for gaps inside group
),
2 => LayoutSizeHint::at_least(5, 8), // "12:34 PM"
_ => LayoutSizeHint::ZERO,
});
assert_eq!(rects.len(), 3);How the solver consumes hints
constraints: [FitContent, Fill, FitContentBounded{5,12}]
│
┌─────────────┴─────────────┐
│ │
1. Call measurer(0) 1. Call measurer(2)
-> hint(15) -> hint(min=5, pref=8)
│ │
▼ ▼
2. clamp to hint 2. clamp to hint, then
-> allocate 15 clamp to [5, 12]
-> allocate 8
│
▼
3. Fill takes everything left
-> area.width - 15 - 1 (gap) - 8 - 1 (gap)The measurer is called at most once per child. The solver caches the
hint and uses it for both the preferred-size pass and any subsequent
adjustment needed when Fill / Percentage constraints compete.
split_with_measurer_stably
There is a stable-order variant — split_with_measurer_stably — that
preserves hint ordering even when the solver would otherwise reorder by
priority. Use it when your children depend on their own index for caching
or identity, and you cannot accept reorder across frames.
Pitfalls
The measurer must be fast. It’s called during every split() call,
which happens every frame. Do not measure by laying out a sub-widget;
cache the result. If the text hasn’t changed, neither has the width.
Zero hints are a trap. LayoutSizeHint::ZERO under FitContent will
allocate zero cells, silently. If your widget appears to disappear, check
that you’re returning a non-zero hint for non-empty content.
Hints are 1D. Flex::horizontal only cares about widths; the height
of each child is the full area height. If you need 2D intrinsic sizing,
compose two Flexes (one per axis) or reach for Grid.
Where to go next
The constraint vocabulary and the solver pass order.
Flex + Grid referenceSwap entire Flex configurations by terminal width.
The longer-form explanation, including tradeoffs vs. fixed sizing.
Concepts: intrinsic sizingHow widgets participate in the measurer chain.
Widget compositionHow this piece fits in layout.
Layout overview