Guide
Layout engine #
Knots calculates the size and position of each element after your frame function returns. The model is similar to CSS flexbox, with grids, layers, and absolute positions.
The model #
Each component makes one or more elements. An element is a box with a size,
padding, and children. You set layout with these ui.Style fields:
| Field | Values | Default |
|---|---|---|
width, height |
.fixed(px), .fit(), .grow(),
.percent(f)
|
.fit() |
direction |
.row, .column, .layer, .grid |
.row |
padding |
.all(v), .xy(x, y),
.init(top, right, bottom, left)
|
0 |
gap |
Pixels between children | 0 |
justify |
.start, .center, .end,
.space_between, .space_around
|
.start |
@"align" |
.start, .center, .end, .stretch |
.start |
overflow |
.visible, .hidden, .scroll,
.scroll_x, .scroll_y
|
.visible |
position, offset |
.static, .absolute; .{ x, y } |
.static |
layer |
.base, .dropdown, .popup,
.modal
|
.base |
grid, grid_cell |
Track template; cell placement | — |
All values are in logical pixels. Knots multiplies them by the content scale of the display when it draws.
Sizing #
| Size | Result |
|---|---|
.fixed(px) |
Exactly px. |
.fit() |
The size of the content: children, gaps, and padding. For text and images, the size of the text or image. |
.grow() |
A share of the free space in the parent. |
.percent(f) |
A fraction of the parent content size. f is from 0 to 1. For
example, .percent(0.5) is half.
|
Minimum and maximum #
Each size also has min and max fields. The helper functions
do not set them, so use a struct literal:
.width = .{ .kind = .grow, .min = 200, .max = 600 },
.height = .{ .kind = .fit, .max = 400 },
Limit: a .grow() size on the cross axis fills
the parent and does not obey max. For example, in a column, a child
width of .{ .kind = .grow, .max = 600 } becomes the full column width.
To limit it, put the child in a row, or calculate a .fixed width.
How layout runs #
Layout has three passes:
-
Measure, from the leaves to the root. Knots calculates each
.fixedand.fitsize. A.fitrow adds the widths of its children and the gaps. A.fitcolumn uses the widest child. Then Knots appliesminandmax. -
Place, from the root to the leaves. For each parent, Knots resolves
.percentsizes. Then it divides the free space on the main axis equally between the.growchildren. If a child reaches itsminormax, Knots gives the remaining space to the other children. Then it positions the children withjustifyand@"align". -
Wrap text. Text with
wrap = truenow has a width. Knots breaks the text into lines at that width. If a text height changes, Knots runs the layout again, so that the parents fit the new height.
The main axis is horizontal for .row and vertical for
.column. justify works on the main axis.
@"align" works on the other axis, the cross axis.
.stretch alignment places children at the start. It does not resize them.
To fill the cross axis, give the child a .grow() size on that axis.
Rows and columns #
This toolbar puts a title at the left and two buttons at the right:
try frame.e(.{
Rect{ .key = .src(@src()), .style = &.{
.width = .grow(),
.direction = .row,
.@"align" = .center,
.gap = 8,
.padding = .xy(12, 8),
} },
.{
Text{ .key = .src(@src()), .content = "Documents" },
Spacer{ .key = .src(@src()), .style = &.{ .width = .grow() } },
Button{ .key = .src(@src()), .label = "New" },
Button{ .key = .src(@src()), .label = "Open" },
},
});
The Spacer takes all free space, so the buttons move to the right.
.justify = .space_between is a different way to do this.
Grids #
Set direction = .grid and a grid template. A track is
.{ .fixed = px } or .{ .fr = n }. Knots gives the fixed
tracks their size first. Then it divides the remaining space between the
fr tracks in proportion to n.
const columns = [_]ui.layout.Grid.Track{ .{ .fixed = 160 }, .{ .fr = 1 }, .{ .fr = 2 } };
const rows = [_]ui.layout.Grid.Track{ .{ .fixed = 48 }, .{ .fr = 1 } };
try frame.e(.{
Rect{ .key = .src(@src()), .style = &.{
.width = .grow(),
.height = .grow(),
.direction = .grid,
.gap = 8,
.grid = .{ .cols = &columns, .rows = &rows },
} },
.{
Rect{ .key = .src(@src()), .style = &.{ .grid_cell = .{ .row = 0, .col = 0, .col_span = 3 } } },
Rect{ .key = .src(@src()), .style = &.{ .grid_cell = .{ .row = 1, .col = 0 } } },
Rect{ .key = .src(@src()), .style = &.{ .grid_cell = .{ .row = 1, .col = 1, .col_span = 2 } } },
},
});
A child without grid_cell goes in row 0, column 0.
Layers and absolute positions #
direction = .layer puts all children on top of each other in the same
area. justify sets the horizontal position and @"align" sets
the vertical position. Use it for badges on icons or text on images.
position = .absolute removes a child from the flow of its parent. Knots
puts the child at the content origin of the parent plus offset. The child
does not change the size of the parent.
The layer field is different: it sets the drawing order. Elements on a
higher layer draw above elements on a lower layer and get pointer input first. The
named layers are .base (0), .dropdown (1),
.popup (10), and .modal (200). Use
ui.Layer.fromIndex(n) for other values. Layers 240 and higher are for the
host.
Overflow and scrolling #
.visiblelets children draw outside the element..hiddenclips the children to the element.-
.scroll,.scroll_x, and.scroll_yclip the children and let the user scroll. Knots shows a scrollbar when the content is larger than the element. The scroll offset is widget state, so it stays between frames.
A scroll area must have a size that does not come from its content. Use
.fixed or .grow() on the scroll axis. A
.fit() element is always as large as its content, so it never scrolls.
The theme sets the scrollbar thickness and colors. Read Themes.