Skip to main content

nesting_deep/
nesting_deep.rs

1//! Visual stress test: three levels of composition nesting.
2//!
3//! Layout: `beside(o1, beside(m1, beside(l1, l2)))`. Four plot panels
4//! across, with the rightmost two living inside a nested-of-a-nested
5//! composition. The deepest plot has labelled chrome; the others are
6//! plain. The bidirectional sizer pair at each of the two boundaries
7//! must converge in 3 iterations of the solver's TrackOf fixed-point
8//! loop (within `MAX_ITER = 5`).
9//!
10//! Visually: all four panels share their y range, and the axis_top
11//! height contributed by the deepest plot propagates outward through
12//! both nesting boundaries to push every other panel down by the same
13//! amount.
14//!
15//! Writes `examples/nesting_deep.png`.
16
17use hephaestus::backend::vello::VelloRenderer;
18use hephaestus::color::{rgb8, Color};
19use hephaestus::composition::{beside, Patch, Slot};
20use hephaestus::layout::Cell;
21use hephaestus::text::{draw_text_in_rect, TextRun, TextStyle};
22use hephaestus::{Affine, Brush, FillRule, Path, PickId, Renderer, SceneBuilder};
23use kurbo::Shape;
24
25fn text_cell(text: &str, size: f32) -> Cell {
26    Cell::measured(TextRun::new(text, &TextStyle::new(size), 96.0))
27}
28
29fn plain(id: &str) -> Patch {
30    Patch::new(id).slot(Slot::Panel, Cell::empty())
31}
32
33fn color_for_region(region: &str) -> Color {
34    match region {
35        "panel" => rgb8(40, 60, 90),
36        "axis_top" => rgb8(200, 100, 130),
37        "axis_bottom" => rgb8(160, 100, 130),
38        _ => rgb8(120, 120, 120),
39    }
40}
41
42fn main() {
43    let (w, h) = (1400u32, 400u32);
44    let dpi = 96.0;
45
46    // Deepest level: two plots, the first carries the chrome that needs
47    // to propagate all the way up to the root composition.
48    let leaf = beside(
49        Patch::new("leaf_l")
50            .slot(Slot::AxisTop, text_cell("axis_top from deepest leaf", 14.0))
51            .slot(Slot::AxisBottom, text_cell("axis_bottom from leaf", 11.0))
52            .slot(Slot::Panel, Cell::empty()),
53        plain("leaf_r"),
54    );
55    // Mid level: a plain plot beside the leaf composition.
56    let mid = beside(plain("mid"), leaf);
57    // Root level: a plain plot beside the mid composition.
58    let composed = beside(plain("root"), mid);
59
60    let layout = composed.solve(hephaestus::Size::new(w as f64, h as f64), dpi);
61
62    let mut renderer = VelloRenderer::new().expect("vello renderer init");
63    {
64        let scene = renderer.scene();
65        let stroke = hephaestus::stroke::Stroke::new(1.0);
66        let text_brush: Brush = rgb8(20, 20, 30).into();
67
68        for (_id, region, rect) in layout.iter() {
69            if region == "panel" {
70                continue;
71            }
72            let c = color_for_region(region);
73            let tint = Color::new([c.components[0], c.components[1], c.components[2], 0.20]);
74            let path: Path = rect.to_path(0.1);
75            scene.fill(
76                FillRule::NonZero,
77                Affine::IDENTITY,
78                &Brush::Solid(tint),
79                None,
80                &path,
81                PickId::Skip,
82            );
83            scene.stroke(
84                &stroke,
85                Affine::IDENTITY,
86                &Brush::Solid(c),
87                None,
88                &path,
89                PickId::Skip,
90            );
91        }
92        for (_id, region, rect) in layout.iter() {
93            if region != "panel" {
94                continue;
95            }
96            let path: Path = rect.to_path(0.1);
97            scene.fill(
98                FillRule::NonZero,
99                Affine::IDENTITY,
100                &Brush::Solid(color_for_region(region)),
101                None,
102                &path,
103                PickId::Skip,
104            );
105            scene.stroke(
106                &stroke,
107                Affine::IDENTITY,
108                &Brush::Solid(rgb8(255, 255, 255)),
109                None,
110                &path,
111                PickId::Skip,
112            );
113        }
114
115        if let Some(rect) = layout.get("leaf_l", Slot::AxisTop) {
116            let run = TextRun::new("axis_top from deepest leaf", &TextStyle::new(14.0), 96.0);
117            draw_text_in_rect(scene, &run, rect, &text_brush, PickId::Skip);
118        }
119        if let Some(rect) = layout.get("leaf_l", Slot::AxisBottom) {
120            let run = TextRun::new("axis_bottom from leaf", &TextStyle::new(11.0), 96.0);
121            draw_text_in_rect(scene, &run, rect, &text_brush, PickId::Skip);
122        }
123    }
124
125    let mut pixels = vec![0u8; (w * h * 4) as usize];
126    let bg: Color = rgb8(248, 248, 252);
127    renderer
128        .render_to_buffer(w, h, bg, &mut pixels)
129        .expect("render");
130
131    let path = std::env::current_dir()
132        .unwrap()
133        .join("examples/nesting_deep.png");
134    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
135    println!("wrote {}", path.display());
136
137    // Print panel y0s so the user can verify alignment across all 3 nesting levels.
138    let root_panel = layout.get("root", Slot::Panel).unwrap();
139    let mid_panel = layout.get("mid", Slot::Panel).unwrap();
140    let leaf_l_panel = layout.get("leaf_l", Slot::Panel).unwrap();
141    let leaf_r_panel = layout.get("leaf_r", Slot::Panel).unwrap();
142    println!(
143        "panel y0: root={}, mid={}, leaf_l={}, leaf_r={}",
144        root_panel.y0, mid_panel.y0, leaf_l_panel.y0, leaf_r_panel.y0
145    );
146    println!("(all four should be equal — propagation across 3 nesting levels)");
147}