-module(etui@geometry).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/geometry.gleam").
-export([rect_new/4, rect_zero/0, right/1, bottom/1, area/1, contains/2, hit_test/3, intersect/2, union/2, resolve_sizes/2, split/3, split_h/2, split_v/2, centered_rect/3, percent_rect/3, split_with_spacing/4, split_flex/5, split_responsive/2]).
-export_type([position/0, size/0, rect/0, direction/0, constraint/0, flex_justify/0, breakpoint/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type position() :: {position, integer(), integer()}.
-type size() :: {size, integer(), integer()}.
-type rect() :: {rect, position(), size()}.
-type direction() :: horizontal | vertical.
-type constraint() :: {length, integer()} |
{min, integer()} |
{max, integer()} |
{percentage, integer()} |
{ratio, integer(), integer()} |
fill.
-type flex_justify() :: flex_start |
flex_end |
flex_center |
flex_between |
flex_around.
-type breakpoint() :: {breakpoint, integer(), list(constraint())}.
-file("src/etui/geometry.gleam", 53).
?DOC(" Create a Rect with clamped width/height to non-negative.\n").
-spec rect_new(integer(), integer(), integer(), integer()) -> rect().
rect_new(X, Y, Width, Height) ->
{rect,
{position, X, Y},
{size, gleam@int:max(0, Width), gleam@int:max(0, Height)}}.
-file("src/etui/geometry.gleam", 61).
?DOC(" Zero-sized rect at origin.\n").
-spec rect_zero() -> rect().
rect_zero() ->
{rect, {position, 0, 0}, {size, 0, 0}}.
-file("src/etui/geometry.gleam", 69).
?DOC(" X coordinate of the right edge (exclusive: x + width).\n").
-spec right(rect()) -> integer().
right(Rect) ->
erlang:element(2, erlang:element(2, Rect)) + erlang:element(
2,
erlang:element(3, Rect)
).
-file("src/etui/geometry.gleam", 74).
?DOC(" Y coordinate of the bottom edge (exclusive: y + height).\n").
-spec bottom(rect()) -> integer().
bottom(Rect) ->
erlang:element(3, erlang:element(2, Rect)) + erlang:element(
3,
erlang:element(3, Rect)
).
-file("src/etui/geometry.gleam", 79).
?DOC(" Area in cells.\n").
-spec area(rect()) -> integer().
area(Rect) ->
erlang:element(2, erlang:element(3, Rect)) * erlang:element(
3,
erlang:element(3, Rect)
).
-file("src/etui/geometry.gleam", 84).
?DOC(" Check if a position is inside the rect (inclusive of edges).\n").
-spec contains(rect(), position()) -> boolean().
contains(Rect, Pos) ->
(((erlang:element(2, Pos) >= erlang:element(2, erlang:element(2, Rect)))
andalso (erlang:element(2, Pos) < right(Rect)))
andalso (erlang:element(3, Pos) >= erlang:element(
3,
erlang:element(2, Rect)
)))
andalso (erlang:element(3, Pos) < bottom(Rect)).
-file("src/etui/geometry.gleam", 93).
?DOC(
" True if terminal cell `(x, y)` is inside `rect`.\n"
" Convenience wrapper over `contains` for use with mouse event coordinates.\n"
).
-spec hit_test(rect(), integer(), integer()) -> boolean().
hit_test(Rect, X, Y) ->
contains(Rect, {position, X, Y}).
-file("src/etui/geometry.gleam", 98).
?DOC(" Intersection of two rects. Returns the overlapping rect if any.\n").
-spec intersect(rect(), rect()) -> {ok, rect()} | {error, nil}.
intersect(A, B) ->
Left = gleam@int:max(
erlang:element(2, erlang:element(2, A)),
erlang:element(2, erlang:element(2, B))
),
Top = gleam@int:max(
erlang:element(3, erlang:element(2, A)),
erlang:element(3, erlang:element(2, B))
),
Right_edge = gleam@int:min(right(A), right(B)),
Bottom_edge = gleam@int:min(bottom(A), bottom(B)),
case (Left < Right_edge) andalso (Top < Bottom_edge) of
true ->
{ok,
{rect,
{position, Left, Top},
{size, Right_edge - Left, Bottom_edge - Top}}};
false ->
{error, nil}
end.
-file("src/etui/geometry.gleam", 115).
?DOC(" Union of two rects: smallest rect that contains both.\n").
-spec union(rect(), rect()) -> rect().
union(A, B) ->
Left = gleam@int:min(
erlang:element(2, erlang:element(2, A)),
erlang:element(2, erlang:element(2, B))
),
Top = gleam@int:min(
erlang:element(3, erlang:element(2, A)),
erlang:element(3, erlang:element(2, B))
),
Right_edge = gleam@int:max(right(A), right(B)),
Bottom_edge = gleam@int:max(bottom(A), bottom(B)),
{rect, {position, Left, Top}, {size, Right_edge - Left, Bottom_edge - Top}}.
-file("src/etui/geometry.gleam", 395).
-spec pick_size(
constraint(),
list(integer()),
list(integer()),
list(integer()),
list(integer())
) -> integer().
pick_size(Constraint, Lengths, Pcts, Ratios, Flexes) ->
case Constraint of
{length, _} ->
case Lengths of
[H | _] ->
H;
_ ->
0
end;
{percentage, _} ->
case Pcts of
[H@1 | _] ->
H@1;
_ ->
0
end;
{ratio, _, _} ->
case Ratios of
[H@2 | _] ->
H@2;
_ ->
0
end;
fill ->
case Flexes of
[H@3 | _] ->
H@3;
_ ->
0
end;
{min, _} ->
case Flexes of
[H@3 | _] ->
H@3;
_ ->
0
end;
{max, _} ->
case Flexes of
[H@3 | _] ->
H@3;
_ ->
0
end
end.
-file("src/etui/geometry.gleam", 362).
-spec assemble_sizes(
list(constraint()),
list(integer()),
list(integer()),
list(integer()),
list(integer()),
list(integer())
) -> list(integer()).
assemble_sizes(
Constraints,
Length_sizes,
Pct_sizes,
Ratio_sizes,
Flex_sizes,
Acc
) ->
case Constraints of
[] ->
lists:reverse(Acc);
[C | Cs] ->
Size = pick_size(
C,
Length_sizes,
Pct_sizes,
Ratio_sizes,
Flex_sizes
),
Ls = case Length_sizes of
[_ | T] ->
T;
_ ->
[]
end,
Ps = case Pct_sizes of
[_ | T@1] ->
T@1;
_ ->
[]
end,
Rs = case Ratio_sizes of
[_ | T@2] ->
T@2;
_ ->
[]
end,
Fs = case Flex_sizes of
[_ | T@3] ->
T@3;
_ ->
[]
end,
assemble_sizes(Cs, Ls, Ps, Rs, Fs, [Size | Acc])
end.
-file("src/etui/geometry.gleam", 303).
-spec phase_flex(
list(constraint()),
integer(),
integer(),
integer(),
list(integer())
) -> list(integer()).
phase_flex(Constraints, Flex_count, Budget, _, _) ->
case Flex_count of
0 ->
gleam@list:map(Constraints, fun(_) -> 0 end);
_ ->
Base = case Flex_count of
0 -> 0;
Gleam@denominator -> Budget div Gleam@denominator
end,
Min_max_used = gleam@list:fold(
Constraints,
0,
fun(Acc, C) -> case C of
{min, N} ->
Acc + gleam@int:max(N, Base);
{max, N@1} ->
Acc + gleam@int:min(N@1, Base);
_ ->
Acc
end end
),
Fill_count = gleam@list:count(Constraints, fun(C@1) -> case C@1 of
fill ->
true;
_ ->
false
end end),
Fill_budget = gleam@int:max(0, Budget - Min_max_used),
Fill_base = case Fill_count of
0 ->
0;
_ ->
case Fill_count of
0 -> 0;
Gleam@denominator@1 -> Fill_budget div Gleam@denominator@1
end
end,
Fill_rem = case Fill_count of
0 ->
0;
_ ->
case Fill_count of
0 -> 0;
Gleam@denominator@2 -> Fill_budget rem Gleam@denominator@2
end
end,
{Sizes, _} = gleam@list:fold(
Constraints,
{[], 0},
fun(State, C@2) ->
{Acc@1, Fill_idx} = State,
{Size, New_fill_idx} = case C@2 of
fill ->
S = case Fill_idx < Fill_rem of
true ->
Fill_base + 1;
false ->
Fill_base
end,
{S, Fill_idx + 1};
{min, N@2} ->
{gleam@int:max(N@2, Base), Fill_idx};
{max, N@3} ->
{gleam@int:min(N@3, Base), Fill_idx};
_ ->
{0, Fill_idx}
end,
{[Size | Acc@1], New_fill_idx}
end
),
lists:reverse(Sizes)
end.
-file("src/etui/geometry.gleam", 269).
-spec phase_proportional(
list(integer()),
integer(),
integer(),
integer(),
integer(),
list(integer())
) -> {list(integer()), integer()}.
phase_proportional(Demands, Budget, Total_demand, Cumsum, Prev_target, Acc) ->
case Demands of
[] ->
{lists:reverse(Acc), Prev_target};
[D | Rest] ->
New_cumsum = Cumsum + D,
Target = case Total_demand of
0 ->
0;
_ ->
case Total_demand =< Budget of
true ->
New_cumsum;
false ->
case Total_demand of
0 -> 0;
Gleam@denominator -> Budget * New_cumsum div Gleam@denominator
end
end
end,
Size = Target - Prev_target,
phase_proportional(
Rest,
Budget,
Total_demand,
New_cumsum,
Target,
[Size | Acc]
)
end.
-file("src/etui/geometry.gleam", 238).
-spec phase_percentage(
list(constraint()),
integer(),
integer(),
integer(),
integer(),
list(integer())
) -> {list(integer()), integer()}.
phase_percentage(Constraints, Denom, Base, Acc_pct, Prev_target, Acc) ->
case Constraints of
[] ->
{lists:reverse(Acc), Prev_target};
[C | Rest] ->
{Size, New_acc, New_target} = case C of
{percentage, P} ->
New_acc_pct = Acc_pct + P,
Target = case Denom of
0 ->
0;
_ ->
case Denom of
0 -> 0;
Gleam@denominator -> Base * New_acc_pct div Gleam@denominator
end
end,
S = Target - Prev_target,
{S, New_acc_pct, Target};
_ ->
{0, Acc_pct, Prev_target}
end,
phase_percentage(
Rest,
Denom,
Base,
New_acc,
New_target,
[Size | Acc]
)
end.
-file("src/etui/geometry.gleam", 215).
-spec phase_length(list(constraint()), integer(), integer(), list(integer())) -> {list(integer()),
integer()}.
phase_length(Constraints, Total, Used, Acc) ->
case Constraints of
[] ->
{lists:reverse(Acc), Used};
[C | Rest] ->
{Size, New_used} = case C of
{length, V} ->
Take = gleam@int:min(V, gleam@int:max(0, Total - Used)),
{Take, Used + Take};
_ ->
{0, Used}
end,
phase_length(Rest, Total, New_used, [Size | Acc])
end.
-file("src/etui/geometry.gleam", 148).
-spec resolve_sizes_impl(integer(), list(constraint())) -> list(integer()).
resolve_sizes_impl(Total, Constraints) ->
{Length_sizes, Length_used} = phase_length(Constraints, Total, 0, []),
Prop_budget = gleam@int:max(0, Total - Length_used),
Pct_total_pct = gleam@list:fold(Constraints, 0, fun(Acc, C) -> case C of
{percentage, P} ->
Acc + P;
_ ->
Acc
end end),
{Denom, Pct_base} = case (Total * Pct_total_pct) > (Prop_budget * 100) of
true ->
{Pct_total_pct, Prop_budget};
false ->
{100, Total}
end,
{Pct_sizes, Pct_used} = phase_percentage(
Constraints,
Denom,
Pct_base,
0,
0,
[]
),
Ratio_budget = gleam@int:max(0, Prop_budget - Pct_used),
Ratio_demands = gleam@list:map(Constraints, fun(C@1) -> case C@1 of
{ratio, A, B} ->
case B of
0 ->
0;
_ ->
case B of
0 -> 0;
Gleam@denominator -> Total * A div Gleam@denominator
end
end;
_ ->
0
end end),
Total_ratio_demand = gleam@list:fold(
Ratio_demands,
0,
fun(Acc@1, D) -> Acc@1 + D end
),
{Ratio_sizes, Ratio_used} = phase_proportional(
Ratio_demands,
Ratio_budget,
Total_ratio_demand,
0,
0,
[]
),
Flex_budget = gleam@int:max(
0,
((Total - Length_used) - Pct_used) - Ratio_used
),
Flex_count = gleam@list:count(Constraints, fun(C@2) -> case C@2 of
fill ->
true;
{min, _} ->
true;
{max, _} ->
true;
_ ->
false
end end),
Flex_sizes = phase_flex(Constraints, Flex_count, Flex_budget, 0, []),
assemble_sizes(
Constraints,
Length_sizes,
Pct_sizes,
Ratio_sizes,
Flex_sizes,
[]
).
-file("src/etui/geometry.gleam", 141).
?DOC(
" Distribute total space among constraints.\n"
"\n"
" Returns a list of sizes (one per constraint) that sum to ≤ total.\n"
" Sum equals total when Fill (or Min/Max) is present or constraints saturate.\n"
"\n"
" Algorithm (three-phase Discrete Cumulative Allocation):\n"
" 1. Length, exact, allocated first. Clamped to remaining budget in order.\n"
" 2. Percentage + Ratio, proportional from total. Cumulative to prevent jitter.\n"
" Scaled proportionally if combined demand exceeds available budget.\n"
" 3. Fill + Min + Max, divide remaining equally.\n"
" Min applies a floor; Max applies a ceiling. Fill gets equal share.\n"
).
-spec resolve_sizes(integer(), list(constraint())) -> list(integer()).
resolve_sizes(Total, Constraints) ->
case Total < 0 of
true ->
gleam@list:map(Constraints, fun(_) -> 0 end);
false ->
resolve_sizes_impl(Total, Constraints)
end.
-file("src/etui/geometry.gleam", 481).
-spec build_rects(direction(), rect(), list(integer()), integer(), list(rect())) -> list(rect()).
build_rects(Direction, Area, Sizes, Cursor, Acc) ->
Limit = case Direction of
vertical ->
erlang:element(3, erlang:element(3, Area));
horizontal ->
erlang:element(2, erlang:element(3, Area))
end,
case Sizes of
[] ->
lists:reverse(Acc);
[Size | Rest] ->
Start = gleam@int:min(Cursor, Limit),
Clamped = gleam@int:min(Size, gleam@int:max(0, Limit - Start)),
Rect = case Direction of
vertical ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)),
erlang:element(3, erlang:element(2, Area)) + Start},
{size,
erlang:element(2, erlang:element(3, Area)),
Clamped}};
horizontal ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)) + Start,
erlang:element(3, erlang:element(2, Area))},
{size,
Clamped,
erlang:element(3, erlang:element(3, Area))}}
end,
build_rects(Direction, Area, Rest, Start + Clamped, [Rect | Acc])
end.
-file("src/etui/geometry.gleam", 466).
?DOC(" Split a rect along a direction by applying constraints.\n").
-spec split(direction(), rect(), list(constraint())) -> list(rect()).
split(Direction, Area, Constraints) ->
Total = case Direction of
vertical ->
erlang:element(3, erlang:element(3, Area));
horizontal ->
erlang:element(2, erlang:element(3, Area))
end,
Sizes = resolve_sizes(Total, Constraints),
build_rects(Direction, Area, Sizes, 0, []).
-file("src/etui/geometry.gleam", 430).
?DOC(" Split horizontally (columns side-by-side). Shorthand for `split(Horizontal, ...)`.\n").
-spec split_h(rect(), list(constraint())) -> list(rect()).
split_h(Area, Constraints) ->
split(horizontal, Area, Constraints).
-file("src/etui/geometry.gleam", 435).
?DOC(" Split vertically (rows stacked). Shorthand for `split(Vertical, ...)`.\n").
-spec split_v(rect(), list(constraint())) -> list(rect()).
split_v(Area, Constraints) ->
split(vertical, Area, Constraints).
-file("src/etui/geometry.gleam", 445).
?DOC(
" Center a rect of `width × height` within `area`.\n"
" Clamps to area bounds. Common for popup placement.\n"
"\n"
" ```gleam\n"
" let popup_area = geometry.centered_rect(60, 20, screen)\n"
" ```\n"
).
-spec centered_rect(integer(), integer(), rect()) -> rect().
centered_rect(Width, Height, Area) ->
W = gleam@int:min(Width, erlang:element(2, erlang:element(3, Area))),
H = gleam@int:min(Height, erlang:element(3, erlang:element(3, Area))),
X = erlang:element(2, erlang:element(2, Area)) + ((erlang:element(
2,
erlang:element(3, Area)
)
- W)
div 2),
Y = erlang:element(3, erlang:element(2, Area)) + ((erlang:element(
3,
erlang:element(3, Area)
)
- H)
div 2),
{rect, {position, X, Y}, {size, W, H}}.
-file("src/etui/geometry.gleam", 459).
?DOC(
" Center a rect sized as a percentage of `area` (`pct_w` and `pct_h` are 0–100).\n"
" Useful for responsive popup sizing:\n"
"\n"
" ```gleam\n"
" let popup_area = geometry.percent_rect(60, 40, screen) // 60% wide, 40% tall\n"
" ```\n"
).
-spec percent_rect(integer(), integer(), rect()) -> rect().
percent_rect(Pct_w, Pct_h, Area) ->
W = (erlang:element(2, erlang:element(3, Area)) * gleam@int:clamp(
Pct_w,
0,
100
))
div 100,
H = (erlang:element(3, erlang:element(3, Area)) * gleam@int:clamp(
Pct_h,
0,
100
))
div 100,
centered_rect(W, H, Area).
-file("src/etui/geometry.gleam", 548).
-spec build_rects_spaced(
direction(),
rect(),
list(integer()),
integer(),
integer(),
list(rect())
) -> list(rect()).
build_rects_spaced(Direction, Area, Sizes, Spacing, Cursor, Acc) ->
Limit = case Direction of
vertical ->
erlang:element(3, erlang:element(3, Area));
horizontal ->
erlang:element(2, erlang:element(3, Area))
end,
case Sizes of
[] ->
lists:reverse(Acc);
[Size | Rest] ->
Start = gleam@int:min(Cursor, Limit),
Clamped = gleam@int:min(Size, gleam@int:max(0, Limit - Start)),
Rect = case Direction of
vertical ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)),
erlang:element(3, erlang:element(2, Area)) + Start},
{size,
erlang:element(2, erlang:element(3, Area)),
Clamped}};
horizontal ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)) + Start,
erlang:element(3, erlang:element(2, Area))},
{size,
Clamped,
erlang:element(3, erlang:element(3, Area))}}
end,
Next_cursor = case Rest of
[] ->
Start + Clamped;
_ ->
(Start + Clamped) + Spacing
end,
build_rects_spaced(
Direction,
Area,
Rest,
Spacing,
Next_cursor,
[Rect | Acc]
)
end.
-file("src/etui/geometry.gleam", 526).
?DOC(
" Split a rect with `spacing` cells of gap between each child.\n"
" Gap cells are taken from the total before distributing to constraints.\n"
"\n"
" ```gleam\n"
" // Two columns with a 1-cell gap\n"
" split_with_spacing(Horizontal, area, [Fill, Fill], 1)\n"
" ```\n"
).
-spec split_with_spacing(direction(), rect(), list(constraint()), integer()) -> list(rect()).
split_with_spacing(Direction, Area, Constraints, Spacing) ->
N = erlang:length(Constraints),
case N =< 1 of
true ->
split(Direction, Area, Constraints);
false ->
Gap_total = gleam@int:max(0, Spacing) * (N - 1),
Total = case Direction of
vertical ->
erlang:element(3, erlang:element(3, Area));
horizontal ->
erlang:element(2, erlang:element(3, Area))
end,
Available = gleam@int:max(0, Total - Gap_total),
Sizes = resolve_sizes(Available, Constraints),
build_rects_spaced(
Direction,
Area,
Sizes,
gleam@int:max(0, Spacing),
0,
[]
)
end.
-file("src/etui/geometry.gleam", 728).
-spec build_flex_rects(
direction(),
rect(),
list(integer()),
list(integer()),
list(rect())
) -> list(rect()).
build_flex_rects(Direction, Area, Sizes, Offsets, Acc) ->
case {Sizes, Offsets} of
{[], _} ->
lists:reverse(Acc);
{_, []} ->
lists:reverse(Acc);
{[Size | Rest_s], [Offset | Rest_o]} ->
Rect = case Direction of
vertical ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)),
erlang:element(3, erlang:element(2, Area)) + Offset},
{size, erlang:element(2, erlang:element(3, Area)), Size}};
horizontal ->
{rect,
{position,
erlang:element(2, erlang:element(2, Area)) + Offset,
erlang:element(3, erlang:element(2, Area))},
{size, Size, erlang:element(3, erlang:element(3, Area))}}
end,
build_flex_rects(Direction, Area, Rest_s, Rest_o, [Rect | Acc])
end.
-file("src/etui/geometry.gleam", 706).
-spec around_offsets(
list(integer()),
integer(),
integer(),
integer(),
list(integer())
) -> list(integer()).
around_offsets(Sizes, Leftover, N, Cursor, Acc) ->
Slot = case N of
0 ->
0;
_ ->
case N of
0 -> 0;
Gleam@denominator -> Leftover div Gleam@denominator
end
end,
Half = Slot div 2,
case Sizes of
[] ->
lists:reverse(Acc);
[S | Rest] ->
Pos = Cursor + Half,
Next = ((Pos + S) + Half) + (Slot rem 2),
around_offsets(Rest, Leftover, N, Next, [Pos | Acc])
end.
-file("src/etui/geometry.gleam", 685).
-spec between_offsets(
list(integer()),
integer(),
integer(),
integer(),
list(integer())
) -> list(integer()).
between_offsets(Sizes, Leftover, N, Cursor, Acc) ->
Gaps = gleam@int:max(1, N - 1),
Gap_size = case Gaps of
0 ->
0;
_ ->
case Gaps of
0 -> 0;
Gleam@denominator -> Leftover div Gleam@denominator
end
end,
case Sizes of
[] ->
lists:reverse(Acc);
[S | Rest] ->
Next = (Cursor + S) + Gap_size,
between_offsets(Rest, Leftover, N, Next, [Cursor | Acc])
end.
-file("src/etui/geometry.gleam", 670).
-spec start_offsets(list(integer()), integer(), integer(), list(integer())) -> list(integer()).
start_offsets(Sizes, Gap, Start, Acc) ->
case Sizes of
[] ->
lists:reverse(Acc);
[S | Rest] ->
Next = (Start + S) + Gap,
start_offsets(Rest, Gap, Next, [Start | Acc])
end.
-file("src/etui/geometry.gleam", 654).
-spec flex_offsets(
list(integer()),
flex_justify(),
integer(),
integer(),
integer()
) -> list(integer()).
flex_offsets(Sizes, Justify, Gap, Leftover, N) ->
case Justify of
flex_start ->
start_offsets(Sizes, Gap, 0, []);
flex_end ->
start_offsets(Sizes, Gap, Leftover, []);
flex_center ->
start_offsets(Sizes, Gap, Leftover div 2, []);
flex_between ->
between_offsets(Sizes, Leftover, N, 0, []);
flex_around ->
around_offsets(Sizes, Leftover, N, 0, [])
end.
-file("src/etui/geometry.gleam", 627).
?DOC(
" Flex layout: children have fixed sizes (from constraints), leftover space\n"
" distributed according to `justify`. Use for toolbars, status bars, centering\n"
" a widget in a larger area, or equal-gap grids.\n"
"\n"
" `gap` is the minimum gap between children (cells). Ignored when `justify`\n"
" provides its own spacing (Between/Around). With `FlexStart`/`End`/`Center`,\n"
" `gap` acts like `split_with_spacing`'s spacing parameter.\n"
"\n"
" ```gleam\n"
" // Center a 20-wide widget in a 80-wide area:\n"
" split_flex(Horizontal, area, [Length(20)], FlexCenter, 0)\n"
"\n"
" // Three buttons with 2-cell gap between:\n"
" split_flex(Horizontal, area, [Length(10), Length(10), Length(10)], FlexStart, 2)\n"
"\n"
" // Toolbar: left item + right item, space between:\n"
" split_flex(Horizontal, area, [Length(10), Length(10)], FlexBetween, 0)\n"
" ```\n"
).
-spec split_flex(
direction(),
rect(),
list(constraint()),
flex_justify(),
integer()
) -> list(rect()).
split_flex(Direction, Area, Constraints, Justify, Gap) ->
N = erlang:length(Constraints),
case N =:= 0 of
true ->
[];
false ->
Total = case Direction of
vertical ->
erlang:element(3, erlang:element(3, Area));
horizontal ->
erlang:element(2, erlang:element(3, Area))
end,
Gap_cells = gleam@int:max(0, Gap) * gleam@int:max(0, N - 1),
Available = gleam@int:max(0, Total - Gap_cells),
Sizes = resolve_sizes(Available, Constraints),
Content_width = gleam@list:fold(
Sizes,
0,
fun(Acc, S) -> Acc + S end
)
+ Gap_cells,
Leftover = gleam@int:max(0, Total - Content_width),
Offsets = flex_offsets(Sizes, Justify, Gap, Leftover, N),
build_flex_rects(Direction, Area, Sizes, Offsets, [])
end.
-file("src/etui/geometry.gleam", 791).
-spec pick_breakpoint(list(breakpoint()), integer()) -> list(constraint()).
pick_breakpoint(Sorted_desc, Width) ->
case Sorted_desc of
[] ->
[];
[Bp] ->
erlang:element(3, Bp);
[Bp@1 | Rest] ->
case Width >= erlang:element(2, Bp@1) of
true ->
erlang:element(3, Bp@1);
false ->
pick_breakpoint(Rest, Width)
end
end.
-file("src/etui/geometry.gleam", 774).
?DOC(
" Split `area` horizontally using the first breakpoint whose `min_width` <=\n"
" `area.size.width`, evaluated in descending order. Falls back to the last\n"
" breakpoint (assumed smallest). Returns `[area]` if `breakpoints` is empty.\n"
"\n"
" Example, two columns on wide screens, stacked on narrow:\n"
" ```gleam\n"
" geometry.split_responsive(area, [\n"
" geometry.Breakpoint(80, [Percentage(50), Percentage(50)]),\n"
" geometry.Breakpoint(0, [Percentage(100)]),\n"
" ])\n"
" ```\n"
).
-spec split_responsive(rect(), list(breakpoint())) -> list(rect()).
split_responsive(Area, Breakpoints) ->
case Breakpoints of
[] ->
[Area];
_ ->
Sorted = gleam@list:sort(
Breakpoints,
fun(A, B) ->
gleam@int:compare(
erlang:element(2, B),
erlang:element(2, A)
)
end
),
Chosen = pick_breakpoint(
Sorted,
erlang:element(2, erlang:element(3, Area))
),
split_h(Area, Chosen)
end.