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.

  • Fill

    Flexible. Divides leftover equally after Length + Percentage + Ratio.

  • FillWeighted(Int)

    Flexible with a weight: takes weight / sum_of_weights of the leftover. Fill is FillWeighted(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

  • Horizontal

    Constraints stacked along X axis (side-by-side columns).

  • Vertical

    Constraints stacked along Y axis (rows).

How leftover space is distributed once the children have been sized.

FlexWhere the leftover goes
FlexStartAfter the last child.
FlexEndBefore the first child.
FlexCenterSplit between the two edges.
FlexBetweenBetween children, nothing at the edges.
FlexAroundAround each child: edges get half of an inner gap.
FlexEvenlyEqually 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)

A rectangular area on screen.

pub type Rect {
  Rect(position: Position, size: Size)
}

Constructors

Dimensions in cells.

pub type Size {
  Size(width: Int, height: Int)
}

Constructors

  • Size(width: Int, height: Int)

Values

pub fn area(rect: Rect) -> Int

Area in cells.

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 columns(rect: Rect) -> List(Rect)

One 1-cell-wide rect per column of rect, left to right.

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 offset(rect: Rect, dx: Int, dy: Int) -> Rect

Translate a rect by dx, dy. Size is unchanged.

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 rect_zero() -> Rect

Zero-sized rect at origin.

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):

  1. Length, exact, allocated first. Clamped to remaining budget in order.
  2. Percentage + Ratio, proportional from total. Cumulative to prevent jitter. Scaled proportionally if combined demand exceeds available budget.
  3. 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 right(rect: Rect) -> Int

X coordinate of the right edge (exclusive: x + width).

pub fn rows(rect: Rect) -> List(Rect)

One 1-cell-tall rect per row of rect, top to bottom.

pub fn size(rect: Rect) -> Size

The rect’s size, dropping its position.

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)
pub fn union(a: Rect, b: Rect) -> Rect

Union of two rects: smallest rect that contains both.

✨ Search Document