Layout
Tuika uses integer-native flex layout for application structure. Flex is the
general container, Flow is the concise choice for intrinsic items that wrap,
and Grid is a deliberately small equal-column grid. All three measure ordinary
third-party View implementations through the same MeasureRequest contract.
Flex containers and items
Container style controls direction, wrapping, padding, gaps, and alignment:
use tuika::prelude::*;
let tags = Flex::row()
.wrap(FlexWrap::Wrap)
.column_gap(1)
.row_gap(1)
.align_content(AlignContent::SpaceBetween)
.auto(element(Text::raw("rust")))
.auto(element(Text::raw("terminal-ui")));
# let _ = tags;align positions items within one line. align_content distributes the lines
themselves when wrapping creates more than one. justify distributes space on
each line. Use row_gap and column_gap when the two axes need different
spacing; gap sets both.
Child sizing belongs to FlexItemStyle, separately from the container:
# use tuika::prelude::*;
let item = FlexItemStyle::default()
.basis(Dimension::Fixed(12))
.grow(1)
.shrink(1)
.min_main(6)
.max_main(20)
.align_self(Align::Center);
let row = Flex::row().styled(item, element(Text::raw("resizable")));
# let _ = row;The basis is the starting main-axis size. Positive free space is shared by
grow; negative free space is removed according to shrink, without crossing
the item’s min/max constraints. Integer cells are assigned by rounding track
boundaries, so the final child reaches the exact container boundary without
losing or double-counting a cell.
Application layouts with view!
view!’s row and col are Flex containers, not a separate layout system.
Nest them for side panels and multiple growing panes. styled(style) { node }
passes a complete FlexItemStyle to a direct row/column child; grow and
fixed remain shorthand for common cases.
use tuika::prelude::*;
let width = 100; // the host's current viewport width
let body = FlexItemStyle::default()
.basis(Dimension::Flex(1))
.grow(1)
.min_main(1);
let screen = view! {
col {
styled(FlexItemStyle::default().shrink(10)) { text("my tool") }
styled(body) {
row(gap = 1) {
fixed(if width >= 80 { 20 } else { 0 }) {
boxed(title = "Navigation") { text("sessions") }
}
grow(2) { boxed(title = "Content") { text("transcript") } }
grow(1) { boxed(title = "Details") { text("activity") } }
}
}
styled(FlexItemStyle::default().min_main(1)) {
node(KeyHints::new([("q", "quit")]))
}
}
};
# let _ = screen;The host chooses whether panels exist, what sizes they need, and which chrome
may shrink. min_main(1) protects the body and footer when there is room for
both; at smaller sizes the solver clips to the available rectangle. Shrink
weights are proportional, not strict collapse priorities. For strict policies,
the host can omit secondary chrome before building the frame.
Dimension::Flex(n) starts at zero during allocation but contributes the
child’s intrinsic size when the container itself is measured. Set grow(n)
explicitly with styled. The compact grow(n) syntax supplies the weight and
a Fixed(0) basis, so that child contributes no preferred main-axis size.
All these forms preserve frame-borrowed views.
Wrapping and line alignment
FlexWrap::Wrap starts a new line when the next item’s outer main size no
longer fits. Each line resolves grow, shrink, justification, and item alignment
independently. AlignContent::{Start, Center, End, Stretch, SpaceBetween} then
places completed lines on the cross axis. NoWrap remains the default.
Flow packages the common intrinsic wrapping case:
# use tuika::prelude::*;
let flow = Flow::new()
.gap(1)
.item(element(Text::raw("one")))
.item(element(Text::raw("two")))
.item(element(Text::raw("a-longer-item")));
# let _ = flow;Choose Flex when children need grow/shrink, fixed or percentage bases, or
per-item alignment. Choose Flow when every child is intrinsic and wrapping is
the main behavior.
Flow versus Grid
Flow packs variable-width items and wraps wherever the next item stops
fitting. Grid::new(columns) creates a fixed number of equal-width columns,
fills them row-major, and derives each row’s height from its tallest cell.
# use tuika::prelude::*;
let grid = Grid::new(3)
.column_gap(1)
.row_gap(1)
.cell(element(Text::raw("a")))
.cell(element(Text::raw("b")))
.cell(element(Text::raw("c")));
# let _ = grid;Grid intentionally omits CSS Grid’s named lines, implicit tracks, spanning, dense packing, and independent track definitions. Those features add a large constraint language while terminal applications usually need a stable column count and predictable clipping. Compose nested Flex/Grid containers when a screen needs a more irregular structure.
Measurement requests
View::measure_request receives optional known axes and an AvailableSpace
mode for unresolved axes:
Definite(n)means at mostncells are available.MinContentasks for the smallest useful intrinsic contribution.MaxContentasks for the preferred unconstrained contribution.known_widthandknown_heightmean the parent has already fixed that axis.
Flex and Grid preserve those modes when measuring third-party views and add a
known axis once a fixed basis or grid track has been resolved. Existing views
that only implement measure remain compatible through the default adapter.
Because MeasureRequest and AvailableSpace are non-exhaustive, downstream
implementations should match with a fallback arm so future request metadata can
be added compatibly.
Migrating existing layouts
Migrating AppShell
AppShell has been removed for the next minor release. Use the application
layout above or Flex::scoped_column() directly. This keeps one layout
abstraction for both conventional chrome and multi-pane applications.
Place header/before_main children before the growing body, and
status/after_main/footer children after it. For the previous shrink
weights, apply FlexItemStyle::default().shrink(10) before the body,
.shrink(20) after it, and .shrink(1).min_main(1) to the footer. Give the
body a Dimension::Flex(1) basis, .grow(1), and .min_main(1) to preserve
its preferred intrinsic measurement and remaining-space allocation.
Replace top_rule and bottom_rule with ordinary Rule children at the
desired positions and a .shrink(u16::MAX) item style. Unlike the former
shell’s separators, Rule does not implicitly choose a semantic role: apply
the active stylesheet’s Role::Rule style when building the frame, or use
view_fn to resolve it from RenderCtx during rendering.
For action/permission/resume pickers, SelectionScreen remains available with
the same API, borrowed-row rendering, and automatic viewport around selection.
Other layout migrations
LayoutStyle::gapbecame independentrow_gapandcolumn_gapfields. UseFlex::gap(n)when both should remain equal.Item::dimensionmoved intoItem::style.basis. PreferItem::styled(FlexItemStyle, intrinsic)for direct solver use.Flex::{auto,grow,fixed}remain concise compatibility builders. UseFlex::styledwhen shrink, min/max, or per-child alignment matters.- Existing no-wrap layouts keep their behavior unless
FlexWrap::Wrapis set.
The public solve_layout
result includes child rectangles and flex-line metadata for hosts that need hit
testing, scrolling, or layout inspection without painting.