Ratatui-style constraint layout solver.
Splits a region along a direction (:vertical splits height into rows,
:horizontal splits width into cols) according to a list of constraints:
{:length, n}— exactlyncells{:percentage, p}—p% of the available space (rounded down){:min, n}— at leastncells; grows like a{:fill, 1}if there's room. Pair with:fillor other:min/:maxslots to set a floor under what would otherwise be flexible.{:max, n}— at mostncells; behaves like a{:fill, 1}capped atn. Combine with other fills to share remaining space without overflowing.{:fill, weight}— distributes remaining space proportional to weight
Apps typically don't call this directly — vbox/1 and hbox/1 from
Harlock.Elements take a :constraints opt and the renderer invokes
the solver internally. This module exists in the public surface so
the constraint shapes are documented and stable.
Solver
Compute each slot's lower bound (
:lengthand:percentageget their full size;:min(n)getsn;:filland:maxget 0). If the lower bounds already exceed the available space, truncate from the tail and log a warning — the over-constrained behavior is identical to v0.2.Distribute the remainder across flexible slots.
:fill(weight),:min, and:maxall participate;:fillcarries its declared weight,:minand:maxcarry weight 1. (Override by writing{:fill, w}if you want explicit weighting.)Check
:maxcaps. Any slot exceeding its cap is clamped to the cap and frozen; the excess goes back into the remainder. Iterate until no new freezes happen or until we hitlength(constraints)passes (which is the absolute upper bound — each pass either freezes ≥1 slot or terminates).If
:maxcaps leave space unallocated (e.g.[{:max, 10}, {:max, 10}]in a 30-cell region), the trailing region is simply not used — children don't overflow their caps to fill the space.
Round-off from percentages and fill divisions is absorbed by the last flexible slot, so for over-fill-saturating layouts the returned sizes sum exactly to the requested total.
Summary
Types
@type constraint() :: {:length, non_neg_integer()} | {:percentage, non_neg_integer()} | {:min, non_neg_integer()} | {:max, non_neg_integer()} | {:fill, pos_integer()}
@type direction() :: :vertical | :horizontal
Functions
@spec split(Harlock.Layout.Rect.t(), direction(), [constraint()]) :: [ Harlock.Layout.Rect.t() ]