Skip to main content

circular_layout/
circular_layout.rs

1//! circular_layout — exercises the custom-layout escape hatch.
2//!
3//! The fixture is a `stack(...)` whose direct children are arranged on
4//! the perimeter of a circle by the supplied [`LayoutFn`]. Stock paint
5//! (rounded_rect for buttons, text_sdf for labels) keeps working
6//! unchanged; only the rect distribution changes. Everything else —
7//! intrinsic measurement of children, the recursion into each child's
8//! subtree, hit-test off computed_rects — flows through the existing
9//! library code paths.
10//!
11//! Inspect `out/circular_layout.tree.txt` and `.draw_ops.txt` to see
12//! the paint stream the LayoutFn produced. The SVG / PNG show eight
13//! buttons evenly spaced around a centered title.
14//!
15//! Run: `cargo run -p aetna-core --example circular_layout`
16
17use aetna_core::prelude::*;
18
19fn circular(ctx: LayoutCtx) -> Vec<Rect> {
20    let cx = ctx.container.x + ctx.container.w * 0.5;
21    let cy = ctx.container.y + ctx.container.h * 0.5;
22    let radius = ctx.container.w.min(ctx.container.h) * 0.38;
23    let n = ctx.children.len();
24    if n == 0 {
25        return Vec::new();
26    }
27
28    ctx.children
29        .iter()
30        .enumerate()
31        .map(|(i, child)| {
32            // First child sits at the centre; others ring it.
33            if i == 0 {
34                let (w, h) = (ctx.measure)(child);
35                return Rect::new(cx - w * 0.5, cy - h * 0.5, w, h);
36            }
37            let ring_count = (n - 1) as f32;
38            let theta =
39                (i - 1) as f32 / ring_count * std::f32::consts::TAU - std::f32::consts::FRAC_PI_2;
40            let (w, h) = (ctx.measure)(child);
41            let x = cx + radius * theta.cos() - w * 0.5;
42            let y = cy + radius * theta.sin() - h * 0.5;
43            Rect::new(x, y, w, h)
44        })
45        .collect()
46}
47
48fn fixture() -> El {
49    let centre = h2("Compass").center_text();
50    let dirs = [
51        ("North", "n"),
52        ("NE", "ne"),
53        ("East", "e"),
54        ("SE", "se"),
55        ("South", "s"),
56        ("SW", "sw"),
57        ("West", "w"),
58        ("NW", "nw"),
59    ];
60
61    let mut children: Vec<El> = vec![centre];
62    for (label, k) in dirs {
63        children.push(button(label).key(k).primary());
64    }
65
66    column([
67        h1("Custom layout — circular"),
68        paragraph(
69            "Eight buttons positioned on a circle by an author-supplied \
70             LayoutFn. Stock paint, automatic hover/press, and hit-test \
71             all keep working — only the rect distribution changed.",
72        )
73        .muted(),
74        stack(children)
75            .key("compass")
76            .layout(circular)
77            .width(Size::Fill(1.0))
78            .height(Size::Fixed(360.0)),
79    ])
80    .gap(tokens::SPACE_4)
81    .padding(tokens::SPACE_7)
82}
83
84fn main() -> std::io::Result<()> {
85    let mut root = fixture();
86
87    let viewport = Rect::new(0.0, 0.0, 600.0, 540.0);
88    let bundle = render_bundle(&mut root, viewport);
89
90    let out_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("out");
91    let written = write_bundle(&bundle, &out_dir, "circular_layout")?;
92    for p in &written {
93        println!("wrote {}", p.display());
94    }
95
96    if !bundle.lint.findings.is_empty() {
97        eprintln!("\nlint findings ({}):", bundle.lint.findings.len());
98        eprint!("{}", bundle.lint.text());
99    }
100
101    Ok(())
102}