Layout
At the end of this chapter, the window has a header, a sidebar beside a content area, and a footer — and resizing the window keeps them in place.
Boxes
A frame is a tree of boxes. A box is a NodeSpec: a row or a column,
with a size, padding, a gap between its children, and paint.
You open a box with ui.with(spec, |ui| { ... }). The closure declares
its children. When the closure returns, the box is closed.
//! Step 2 — rows, columns and sizes. Builds on step 1 by putting the text
//! inside boxes: a header row, a body with a fixed sidebar beside a
//! growing content area, and a footer. Chapter 3 of the book.
//!
//! Run: cargo run -p kui-native --example tutorial_02_layout
use kui_native::{Align, App, NodeSpec, TextStyle, Ui};
struct Layout;
impl App for Layout {
fn view(&mut self, ui: &mut Ui<'_>) {
let t = ui.theme();
// The root: a column that fills the window. `with` opens a box,
// runs the closure for its children, and closes it.
ui.with(NodeSpec::column().fill().bg(t.bg), |ui| {
// A row: children go left to right. `SpaceBetween` pushes the
// title and the badge to opposite ends.
ui.with(
NodeSpec::row()
.grow_width()
.pad(16.0)
.main_align(Align::SpaceBetween)
.cross_align(Align::Center)
.bg(t.surface)
.border(1.0, t.border),
|ui| {
ui.text("Layout", TextStyle::new(18.0).color(t.fg));
ui.text_in(
NodeSpec::row()
.pad_xy(10.0, 4.0)
.radius(10.0)
.bg(t.accent_soft),
"step 2",
TextStyle::new(12.0).color(t.accent),
);
},
);
// The body takes whatever height the header and footer leave.
ui.with(NodeSpec::row().grow_width().grow_height(), |ui| {
// A fixed width...
ui.with(
NodeSpec::column()
.width(160.0)
.grow_height()
.pad(16.0)
.gap(8.0)
.bg(t.sunken),
|ui| {
for item in ["Inbox", "Drafts", "Sent"] {
ui.text(item, TextStyle::new(14.0).color(t.muted));
}
},
);
// ...and the rest. `grow_width` shares the leftover space;
// with one grower it takes all of it.
ui.with(
NodeSpec::column()
.grow_width()
.grow_height()
.center()
.gap(8.0),
|ui| {
ui.text("Sizes are three words", TextStyle::new(20.0).color(t.fg));
ui.text(
"fit (the default), fixed, or grow",
TextStyle::new(14.0).color(t.muted),
);
},
);
});
ui.text_in(
NodeSpec::row().grow_width().pad_xy(16.0, 8.0).bg(t.surface),
"a footer, as tall as its text",
TextStyle::new(12.0).color(t.faint),
);
});
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
kui_native::app("Layout").size(560.0, 360.0).run(Layout)
}
Sizes are three words
Every box has a width and a height, and each is one of three things:
| word | meaning | spelled |
|---|---|---|
| fit | as big as its children | the default |
| fixed | this many logical pixels | .width(160.0) |
| grow | share the parent’s leftover space | .grow_width() |
.fill() is grow on both axes. Two growers split the leftover evenly.
A grower with no siblings that grow takes all of it.
The sidebar above is fixed at 160. The content area grows, so it takes the rest. When the window is resized, the sidebar stays and the content moves.
Rows and columns
A row lays its children left to right. A column lays them top to bottom. That axis is the main axis; the other is the cross axis.
.main_align(..) says where leftover space goes along the main axis:
Start, Center, End, or SpaceBetween to push children apart.
.cross_align(..) aligns children on the other axis. .center() is
both at once.
The header is a row with SpaceBetween, so its title sits at the left
and its badge at the right, whatever the width.
Padding, gap, paint
.pad(16.0) is space inside the box, on all four sides. .pad_xy(x, y)
sets the horizontal and vertical padding separately. .gap(8.0) is
space between children.
.bg(colour), .radius(r) and .border(width, colour) paint the box.
A box with no paint is invisible and only takes up room.
ui.text_in(spec, text, style) is a box with one text child. It is how
you give a text a background or a size of its own.
Try this
- Make the sidebar grow too. What happens to the split? (Two growers: half each.)
- Change the body’s
row()tocolumn(). The sidebar is now above the content, and its fixed width becomes a fixed height? No — width is still width. Give it.height(80.0)and see. - Remove
.fill()from the root. The tree shrinks to fit its content.
Where this is decided
- Every layout prop, with its Node, Lua and C spelling:
docs/props.md, container props. - The layout solver, pass by pass:
docs/design.md, Layout.