1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! grid — the track-grid layout, reflowing live.
//!
//! Demonstrates: `Display::Grid` with every track flavor on one screen —
//! fixed `Cells`, `Percent`, content-sized `Auto`, weighted `Fr` — plus
//! column/row spans. Resize the terminal and watch fr tracks re-tile
//! exactly (largest-remainder rounding: no dropped columns); `g` cycles
//! three track recipes so the SAME children reflow under different
//! geometry — the resize story, scripted.
//!
//! Keys: g cycle grid recipe · t theme · q quit.
//!
//! Docs: docs/api.md § "layout — flex and grid".
//!
//! OWNER: DESIGN.
mod common;
use abstracttui::prelude::*;
use abstracttui::theme::themes;
/// Three track recipes over the same 8 children.
fn recipe(ix: usize) -> (Vec<Track>, &'static str) {
match ix % 3 {
0 => (
vec![Track::Fr(1.0), Track::Fr(1.0), Track::Fr(1.0)],
"3 equal fr columns",
),
1 => (
vec![Track::Cells(24), Track::Fr(2.0), Track::Fr(1.0)],
"24 cells · 2fr · 1fr",
),
_ => (
vec![Track::Percent(0.25), Track::Fr(1.0), Track::Percent(0.25)],
"25% · fr · 25%",
),
}
}
fn main() -> abstracttui::base::Result<()> {
if !abstracttui::term::have_tty() {
println!("grid: needs an interactive terminal — skipping cleanly");
return Ok(());
}
let mut app = App::new(Size::new(96, 28));
let quitter = app.quitter();
app.mount(move |cx| {
let theme = use_theme(cx);
let recipe_ix = cx.signal(0usize);
let theme_ix = cx.signal(0usize);
Element::new()
.style(LayoutStyle::column().padding(Edges::all(1)).gap(1))
.shortcut(KeyChord::plain(Key::Char('q')), move |_| quitter.quit())
.shortcut(KeyChord::plain(Key::Char('g')), move |_| {
recipe_ix.update(|i| *i += 1)
})
.shortcut(KeyChord::plain(Key::Char('t')), move |_| {
theme_ix.update(|i| *i = (*i + 1) % themes().len());
set_theme_by_id(themes()[theme_ix.get_untracked()].id);
})
.child(dyn_view(LayoutStyle::default().h(1), move || {
let (_, label) = recipe(recipe_ix.get());
text(format!("grid — {label} (g cycles · t theme · q quit)"))
}))
.child(dyn_view(LayoutStyle::default().grow(1.0), move || {
let t = theme.get().tokens;
let (cols, _) = recipe(recipe_ix.get());
// Rows: a fixed header band, then implicit rows size to
// their tallest child.
let grid_style = LayoutStyle::default()
.grid(cols, vec![Track::Cells(5)])
.gap(1)
.grow(1.0);
let mut grid = Element::new().style(grid_style);
// A spanning hero card first (col_span 2), then cards.
// The card layouts are COLUMNS: a card stacks its accent
// bar over its caption (`LayoutStyle::default()` is a
// row, which would lay them side by side, each at its
// measured width — zero for a draw closure).
grid = grid.child(card(
&t,
"hero — col_span 2",
0,
LayoutStyle::column().col_span(2),
));
for i in 1..8 {
let label: &'static str = CARD_LABELS[i % CARD_LABELS.len()];
grid = grid.child(card(&t, label, i, LayoutStyle::column()));
}
grid.build()
}))
.build()
})?;
app.run()
}
const CARD_LABELS: [&str; 4] = ["alpha", "beta", "gamma", "delta"];
/// One themed grid cell: a shadowed panel with a chart-slot accent bar —
/// visually distinct per index so reflow is easy to follow.
fn card(t: &TokenSet, label: &'static str, ix: usize, layout: LayoutStyle) -> View {
let accent = t.chart(ix % 8);
let ink = t.text_muted;
Block::new()
.border(BorderKind::Rounded)
.title(label)
.fill(t.surface)
.shadow(t.shadow_ground)
.layout(layout.min_h(4))
.child(
Element::new()
.style(LayoutStyle::default().h(1))
.draw(move |canvas, rect| {
for x in rect.x..(rect.x + (rect.w / 2).max(1)).min(rect.right()) {
canvas.put(Point::new(x, rect.y), '▄', accent, Rgba::TRANSPARENT);
}
})
.build(),
)
.child(
Element::new()
.style(LayoutStyle::default().h(1))
.draw(move |canvas, rect| {
let msg = format!("cell {ix}");
canvas.print(Point::new(rect.x, rect.y), &msg, ink, Rgba::TRANSPARENT);
})
.build(),
)
.element(t)
.build()
}