etui/geometry
Types
A responsive breakpoint: applies constraints when area width >= min_width.
pub type Breakpoint {
Breakpoint(min_width: Int, constraints: List(Constraint))
}
Constructors
-
Breakpoint(min_width: Int, constraints: List(Constraint))
Layout constraint: how much space to claim.
Priority (highest → lowest): Length > Min/Max > Ratio/Percentage > Fill
pub type Constraint {
Length(Int)
Min(Int)
Max(Int)
Percentage(Int)
Ratio(Int, Int)
Fill
FillWeighted(Int)
}
Constructors
-
Length(Int)Fixed cell count. Highest priority. Allocated first.
-
Min(Int)At least n cells. Participates in flexible distribution with a floor.
-
Max(Int)At most n cells. Participates in flexible distribution with a ceiling.
-
Percentage(Int)Percentage of total (0..100). Computed cumulatively to avoid pixel loss.
-
Ratio(Int, Int)Rational fraction of total: numerator/denominator. Exact integer math.
Ratio(1, 3)is one-third of the total space. -
FillFlexible. Divides leftover equally after Length + Percentage + Ratio.
-
FillWeighted(Int)Flexible with a weight: takes
weight / sum_of_weightsof the leftover.FillisFillWeighted(1), so the two mix freely.// Sidebar one third, content two thirds split_h(area, [FillWeighted(1), FillWeighted(2)])A weight of 0 claims nothing.
How to split a rectangle when laying out widgets.
pub type Direction {
Horizontal
Vertical
}
Constructors
-
HorizontalConstraints stacked along X axis (side-by-side columns).
-
VerticalConstraints stacked along Y axis (rows).
How leftover space is distributed once the children have been sized.
| Flex | Where the leftover goes |
|---|---|
FlexStart | After the last child. |
FlexEnd | Before the first child. |
FlexCenter | Split between the two edges. |
FlexBetween | Between children, nothing at the edges. |
FlexAround | Around each child: edges get half of an inner gap. |
FlexEvenly | Equally between children and at both edges. |
pub type Flex {
FlexStart
FlexEnd
FlexCenter
FlexBetween
FlexAround
FlexEvenly
}
Constructors
-
FlexStart -
FlexEnd -
FlexCenter -
FlexBetween -
FlexAround -
FlexEvenly
Former name of Flex.
pub type FlexJustify =
Flex
Padding removed from each side of a rect: horizontal from left and
right, vertical from top and bottom.
pub type Margin {
Margin(horizontal: Int, vertical: Int)
}
Constructors
-
Margin(horizontal: Int, vertical: Int)
Coordinate on the screen.
pub type Position {
Position(x: Int, y: Int)
}
Constructors
-
Position(x: Int, y: Int)
Values
pub fn bottom(rect: Rect) -> Int
Y coordinate of the bottom edge (exclusive: y + height).
pub fn centered_rect(width: Int, height: Int, area: Rect) -> Rect
Center a rect of width × height within area.
Clamps to area bounds. Common for popup placement.
let popup_area = geometry.centered_rect(60, 20, screen)
pub fn clamp(rect: Rect, bounds: Rect) -> Rect
Move and shrink rect so it fits entirely inside bounds.
Returns a zero-size rect when the two do not overlap at all.
pub fn contains(rect: Rect, pos: Position) -> Bool
Check if a position is inside the rect (inclusive of edges).
pub fn hit_test(rect: Rect, x: Int, y: Int) -> Bool
True if terminal cell (x, y) is inside rect.
Convenience wrapper over contains for use with mouse event coordinates.
pub fn inner(rect: Rect, margin: Margin) -> Rect
Shrink a rect by margin on all four sides.
Matches ratatui’s Rect::inner: the origin always moves in by the margin,
and the size saturates at zero when the rect is too small to hold it. The
result can therefore sit outside the original rect once it has collapsed,
which is the behaviour callers ported from ratatui expect.
geometry.inner(area, geometry.Margin(1, 1)) // one cell of padding
pub fn intersect(a: Rect, b: Rect) -> Result(Rect, Nil)
Intersection of two rects. Returns the overlapping rect if any.
pub fn percent_rect(pct_w: Int, pct_h: Int, area: Rect) -> Rect
Center a rect sized as a percentage of area (pct_w and pct_h are 0–100).
Useful for responsive popup sizing:
let popup_area = geometry.percent_rect(60, 40, screen) // 60% wide, 40% tall
pub fn rect_new(x: Int, y: Int, width: Int, height: Int) -> Rect
Create a Rect with clamped width/height to non-negative.
pub fn resolve_sizes(
total: Int,
constraints: List(Constraint),
) -> List(Int)
Distribute total space among constraints.
Returns a list of sizes (one per constraint) that sum to ≤ total. Sum equals total when Fill (or Min/Max) is present or constraints saturate.
Algorithm (three-phase Discrete Cumulative Allocation):
- Length, exact, allocated first. Clamped to remaining budget in order.
- Percentage + Ratio, proportional from total. Cumulative to prevent jitter. Scaled proportionally if combined demand exceeds available budget.
- Fill, FillWeighted, Min and Max share the remainder by weight, bounded by their floors and ceilings. When the bounds cannot all be met the sizes are scaled to fit rather than over-allocated: the result always fits the area it was asked to fill.
Stability under resize
With Length, Percentage, Fill and FillWeighted, growing the area by
a cell never moves a boundary backwards, so a resize does not make panels
jitter.
Min, Max and Ratio do not guarantee that. A slot pinned at its floor
competes in a smaller pool than a free one, so the size at which it stops
being pinned is a step rather than a slope and its neighbours resize
sharply across it. Ratio accumulates its fractions rather than rounding
each one separately, which removes the worst of its own jitter but not all
of it. Measured over 12000 random layouts the three together affect about
0.2% of them, by a few cells.
If a layout is resized interactively and must not jitter, express it with
Percentage or FillWeighted.
pub fn split(
direction: Direction,
area: Rect,
constraints: List(Constraint),
) -> List(Rect)
Split a rect along a direction by applying constraints.
pub fn split_h(
area: Rect,
constraints: List(Constraint),
) -> List(Rect)
Split horizontally (columns side-by-side). Shorthand for split(Horizontal, ...).
pub fn split_responsive(
area: Rect,
breakpoints: List(Breakpoint),
) -> List(Rect)
Split area horizontally using the first breakpoint whose min_width <=
area.size.width, evaluated in descending order. Falls back to the last
breakpoint (assumed smallest). Returns [area] if breakpoints is empty.
Example, two columns on wide screens, stacked on narrow:
geometry.split_responsive(area, [
geometry.Breakpoint(80, [Percentage(50), Percentage(50)]),
geometry.Breakpoint(0, [Percentage(100)]),
])
pub fn split_v(
area: Rect,
constraints: List(Constraint),
) -> List(Rect)
Split vertically (rows stacked). Shorthand for split(Vertical, ...).
pub fn split_with(
direction: Direction,
area: Rect,
constraints: List(Constraint),
flex: Flex,
spacing: Int,
) -> List(Rect)
Split a rect with both a fixed gap between children and a rule for the
space nobody claimed. This is the general form; split, split_h,
split_v and split_with_spacing are all this function with some
arguments filled in.
spacing is a gap that always sits between children and is taken out of
the budget before the constraints are resolved. flex then places whatever
the constraints did not claim. The two compose: FlexBetween with a
spacing of 2 keeps at least two cells between children and spreads the rest
on top of that.
// Toolbar: fixed buttons pushed to the edges, at least 1 cell apart
split_with(Horizontal, area, [Length(8), Length(8)], FlexBetween, 1)
// A 20-wide dialog centred in the area
split_with(Horizontal, area, [Length(20)], FlexCenter, 0)
pub fn split_with_spacing(
direction: Direction,
area: Rect,
constraints: List(Constraint),
spacing: Int,
) -> List(Rect)
Split a rect with spacing cells of gap between each child.
Gap cells are taken from the total before distributing to constraints.
// Two columns with a 1-cell gap
split_with_spacing(Horizontal, area, [Fill, Fill], 1)