Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ works inside surfaces, docks and windows alike.
- **A stack of frames.** Layout is a stack of frames on the context
(`blit.layout.Frame`): a content box, a pen, the axis items advance along,
the gap between them and how they align across it. Every container (panel,
window, popup, dock, scroll region, columns, stack) pushes its frame at its
window, popup, dock, scroll region, columns, stack, flow) pushes its frame at its
begin and pops it at its end, so a panel opened inside a window leaves the
window's layout where it was. Outside any container, widgets lay out down a
column over the screen.
Expand All @@ -944,6 +944,27 @@ works inside surfaces, docks and windows alike.
blit.widget.text_field(?ctx, "find", ?find, "find");
blit.widget.end_stack(?ctx);
```
- **Flows.** `begin_flow(?ctx, key, s)`/`end_flow` open a flow as the next
item of the current frame: its items go left to right, and one that would
pass the right edge starts the next row. A row is as tall as its tallest
item, and its items sit in that height as `align` says, read from what the
row measured last frame. An item wider than a whole row takes the row alone
at its width and cuts its label to it, as a control placed by hand does
with `blit.text.fit_box` or `fit_pair`. `blit.widget.flow(?ctx)` gives the
options at the layout style's gaps (`margin.r` between items, `margin.b`
between rows): adjust `gap`, `row_gap`, `align`, the flow's own `w` and `h`
and `item_w`/`item_h`, as for a stack. A flow is sized in its parent as a
stack is, and its width fitted to its content is its items in one row, so
`limit(fit(), 0, max)` wraps at `max`. Flows and stacks nest in each other,
and in windows, docked bodies and overlays.
```mach
blit.widget.size_next(?ctx, blit.layout.limit(blit.layout.fit(), 0.0::f32, max_w), blit.layout.auto());
blit.widget.begin_flow(?ctx, "readout", blit.widget.flow(?ctx));
# one item per label and value pair, cut to the row when wider than it
val r: blit.context.Area = blit.widget.place(?ctx, blit.layout.fit(), blit.layout.fit(), pair_w, line_h);
blit.text.fit_pair(?ctx, blit.theme.LABEL, "gen", 3, faint, gen, ink, pair_gap, r.y0, r);
blit.widget.end_flow(?ctx);
```
- **Sizing.** Each side of an item is `blit.layout.fixed(px)`, `fit()` (what its
content needs), `fill(weight)` (a share of the space the other items leave)
or `frac(f)` (a fraction of the space left), and `limit(s, min, max)` clamps
Expand Down
44 changes: 39 additions & 5 deletions demo/gallery/src/sections/layout.mach
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# layout: stacks with sizing rules, same-line rows, columns and controls
# placed by hand
# layout: stacks with sizing rules, flows, same-line rows, columns and
# controls placed by hand
#
# layout is a stack of frames. a stack lays its items along an axis, each
# side sized fixed, fit to its content, filling a share of what is left or a
# fraction of it. same_line puts the next item beside the last, columns lay
# whole widgets side by side, and place hands back a rect for anything drawn
# fraction of it. a flow lays its items left to right and wraps them onto
# rows. same_line puts the next item beside the last, columns lay whole
# widgets side by side, and place hands back a rect for anything drawn
# outside blit's widgets.

use std.types.size.usize;
use std.types.string.str;
use std.types.string.str_len;

use blit;

Expand Down Expand Up @@ -60,6 +63,38 @@ pub fun show(ctx: *blit.context.Context, user: ptr) {
blit.widget.size_next(ctx, blit.layout.limit(blit.layout.frac(0.5::f32), blit.context.px(ctx, 120.0::f32), 0.0::f32), blit.layout.auto());
blit.widget.button(ctx, "half, at least 120");

# a flow: tags wrap onto rows as the width runs out
blit.controls.separator_label(ctx, "A flow");
var tags: blit.layout.Flow = blit.widget.flow(ctx);
tags.item_w = blit.layout.fit();
blit.widget.begin_flow(ctx, "tags", tags);
val names: [9]str = [9]str{"layout", "stacks", "flows", "rows", "gaps", "alignment", "wrapping", "sizing", "truncation"};
var k: usize = 0;
for (k < 9) {
blit.widget.button(ctx, names[k]);
k = k + 1;
}
blit.widget.end_flow(ctx);

# label and value pairs, centered in their rows, each cut to the row
# when it is wider than one
var pairs: blit.layout.Flow = blit.widget.flow(ctx);
pairs.align = blit.layout.Align.center{};
blit.widget.begin_flow(ctx, "pairs", pairs);
val t: *blit.theme.Theme = blit.context.theme_of(ctx);
val sp: f32 = blit.context.px(ctx, 6.0::f32);
val lh: f32 = blit.context.line_height(ctx);
val keys: [4]str = [4]str{"generation", "population", "rule", "note"};
val vals: [4]str = [4]str{"1,024", "53,211", "B3/S23", "a value long enough to be cut when the row is narrow"};
var p: usize = 0;
for (p < 4) {
val w: f32 = blit.context.text_width(ctx, keys[p]) + sp + blit.context.text_width(ctx, vals[p]);
val r: blit.context.Area = blit.widget.place(ctx, blit.layout.fit(), blit.layout.fit(), w, lh);
blit.text.fit_pair(ctx, blit.theme.LABEL, keys[p], str_len(keys[p]), t.text_faint, vals[p], t.text, sp, r.y0, r);
p = p + 1;
}
blit.widget.end_flow(ctx);

blit.controls.separator_label(ctx, "Same line");
blit.widget.text(ctx, "label");
blit.widget.same_line(ctx);
Expand All @@ -76,7 +111,6 @@ pub fun show(ctx: *blit.context.Context, user: ptr) {

# a control drawn by hand takes its place in the layout like a widget
blit.controls.separator_label(ctx, "Placed by hand");
val t: *blit.theme.Theme = blit.context.theme_of(ctx);
val h: f32 = blit.context.px(ctx, 24.0::f32);
val r: blit.context.Area = blit.widget.place(ctx, blit.layout.fill(1.0::f32), blit.layout.fixed(h), 0.0::f32, h);
val w: f32 = (r.x1 - r.x0) / 8.0::f32;
Expand Down
92 changes: 92 additions & 0 deletions src/dock.mach
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ use std.types.option.opt;
use std.types.result.res;
use std.types.size.usize;
use std.types.string.str;
use std.types.string.str_len;

use allo: std.allocator;
use std.allocator.page;
Expand All @@ -76,6 +77,7 @@ use bid: blit.id;
use blit.input;
use blit.interact;
use blit.layout;
use blit.overlay;
use blit.state;
use blit.tabs;
use blit.theme;
Expand Down Expand Up @@ -2341,3 +2343,93 @@ test scroll__wheel_stops_at_the_band_a_popup_opens {
if (sel freed.err) { ret 1; }
ret 0;
}

# what the flow probe lays out: a docked window, a floating one and an
# overlay, each holding the same flow, and the rects its items took in each
#[testing]
rec Flows {
wins: [2]widget.Window;
r: [3][3]context.Area;
}

# a label wider than any row of the probe's flows
#[testing]
val LONG: str = "a label far too long to fit in any row of this flow";

# a flow at most 100 wide, centered in its rows: two items sharing a row, a
# button wider than a whole row, and an item after it
#[testing]
fun flow_of(ctx: *context.Context, r: *context.Area) {
var s: layout.Flow = widget.flow(ctx);
s.align = layout.Align.center{};
widget.size_next(ctx, layout.limit(layout.fit(), 0.0::f32, 100.0::f32), layout.auto());
widget.begin_flow(ctx, "flow", s);
r[0] = widget.place(ctx, layout.fit(), layout.fit(), 60.0::f32, 10.0::f32);
r[1] = widget.place(ctx, layout.fit(), layout.fit(), 30.0::f32, 20.0::f32);
widget.size_next(ctx, layout.fit(), layout.auto());
widget.button(ctx, LONG);
r[2] = widget.place(ctx, layout.fit(), layout.fit(), 20.0::f32, 10.0::f32);
widget.end_flow(ctx);
}

#[testing]
fun flows_ui(ctx: *context.Context, user: ptr) {
val fl: *Flows = user::*Flows;
val sid: u64 = context.id_of(ctx, "main");
if (empty(ctx, sid)) { add(ctx, root(ctx, sid), context.id_of(ctx, "A")); }
space(ctx, "main", context.Area{x0: 0.0::f32, y0: 0.0::f32, x1: 200.0::f32, y1: 300.0::f32});
var i: usize = 0;
for (i < 2) {
val w: widget.WindowArea = widget.begin_window(ctx, fl.wins[i]);
if (w.body != 0) { flow_of(ctx, ?fl.r[i][0]); }
widget.end_window(ctx, w);
i = i + 1;
}
var o: overlay.Overlay = overlay.on_surface(overlay.BOTTOM_RIGHT);
val sh: overlay.Shown = overlay.begin(ctx, "hud", o);
flow_of(ctx, ?fl.r[2][0]);
overlay.end(ctx, sh);
}

test flow__wraps_alike_in_a_docked_window_a_window_and_an_overlay {
var al: allo.Allocator;
val made: err[allo.Error] = page.make(?al);
if (sel made.err) { ret 1; }
var fl: Flows;
fl.wins[0] = widget.window("A", 0.0::f32, 0.0::f32, 160.0::f32, 160.0::f32);
fl.wins[1] = widget.window("B", 220.0::f32, 20.0::f32, 160.0::f32, 160.0::f32);
var d: driver.Driver;
val e: err[allo.Error] = driver.init(?d, ?al, 600.0::f32, 400.0::f32, flows_ui, (?fl)::ptr);
if (sel e.err) { ret 1; }
driver.step(?d);
driver.settle(?d);
if (node_of(?d.ctx, win(?d, "A")) == 0 || node_of(?d.ctx, win(?d, "B")) != 0) { ret 2; }

val g: f32 = context.px(?d.ctx, context.look_of(?d.ctx, theme.LAYOUT).margin.r);
val rg: f32 = context.px(?d.ctx, context.look_of(?d.ctx, theme.LAYOUT).margin.b);
val paths: [3]str = [3]str{"A", "B", "hud"};
var i: usize = 0;
for (i < 3) {
val r: *context.Area = ?fl.r[i][0];
val x: f32 = r[0].x0;
val y: f32 = r[1].y0;
# the first row is as tall as its taller item, the shorter centered in it
if (r[0].y0 != y + 5.0::f32 || r[1].x0 != x + 60.0::f32 + g) { ret 3; }
if (r[2].x0 != x) { ret 4; }
# the button took the second row alone, cut to the flow's 100 pixels,
# and the last item the third row
val b: u64 = bid.of_label(driver.find(?d, paths[i]), LONG);
val br: opt[context.Area] = driver.rect(?d, b);
if (sel br.none) { ret 5; }
val bh: f32 = br.some.y1 - br.some.y0;
if (br.some.x1 - br.some.x0 != 100.0::f32) { ret 6; }
if (r[2].y0 != y + 20.0::f32 + rg + bh + rg) { ret 7; }
val shown: str = driver.text_of(?d, b);
if (str_len(shown) == 0 || str_len(shown) >= str_len(LONG)) { ret 8; }
i = i + 1;
}

val freed: err[allo.Error] = driver.free(?d);
if (sel freed.err) { ret 1; }
ret 0;
}
Loading
Loading