Skip to main content

nesting_outer_chrome_propagates/
nesting_outer_chrome_propagates.rs

1//! Visual stress test: top-level chrome propagating INTO a nested
2//! composition's chrome cell.
3//!
4//! Mirror of `nesting_chrome_coupling.rs`: this time the chrome lives on
5//! the OUTER plain sibling, and we verify that the nested composition's
6//! inner chrome row grows to match.
7//!
8//! Layout: outer 1×2 composition.
9//! - Cell (1,1): a plain patch with a TALL axis_top (multi-line text).
10//! - Cell (1,2): a nested 1×3 composition where ALL three inner patches
11//!   are plain (no axis_top of their own).
12//!
13//! Propagation chain:
14//! 1. The outer Auto row 8 (axis_top anatomical row) is shared across
15//!    both outer blocks in the same composition row. The plain patch's
16//!    `Slot::AxisTop` cell contributes its content height; the row
17//!    resolves to that height.
18//! 2. The BACK sizer in the nested sub-Grid (at every inner top-row
19//!    block's anatomical row 8) reads outer row 8 via
20//!    `Length::track_of(outer_id, Axis::Height, ...)` and forces the
21//!    sub-Grid's inner row 8 to that same height.
22//! 3. The inner panels in the nested composition all sit below that
23//!    grown chrome row — so even though the inner patches have no
24//!    axis_top content of their own, their panels are pushed down by
25//!    the outer plain plot's axis_top height.
26//!
27//! Visually: the outer plain patch has its axis_top label rendered in
28//! the chrome row; the nested side has empty (white) space above its
29//! three panels exactly matching that chrome row's height.
30//!
31//! Writes `examples/nesting_outer_chrome_propagates.png`.
32
33use hephaestus::backend::vello::VelloRenderer;
34use hephaestus::color::{rgb8, Color};
35use hephaestus::composition::{beside, grid, Patch, Slot};
36use hephaestus::layout::Cell;
37use hephaestus::text::{draw_text_in_rect, TextRun, TextStyle};
38use hephaestus::{Affine, Brush, FillRule, Path, PickId, Renderer, SceneBuilder};
39use kurbo::Shape;
40
41fn text_cell(text: &str, size: f32) -> Cell {
42    Cell::measured(TextRun::new(text, &TextStyle::new(size), 96.0))
43}
44
45fn plain_plot(id: &str) -> Patch {
46    Patch::new(id).slot(Slot::Panel, Cell::empty())
47}
48
49fn color_for_region(region: &str) -> Color {
50    match region {
51        "panel" => rgb8(40, 60, 90),
52        "axis_top" => rgb8(200, 100, 130),
53        _ => rgb8(120, 120, 120),
54    }
55}
56
57fn main() {
58    let (w, h) = (1400u32, 400u32);
59    let dpi = 96.0;
60
61    // Top-level chrome lives on the LEFT sibling. The right side is a
62    // nested composition with three plain inner patches.
63    let outer_with_chrome = Patch::new("outer_plain")
64        .slot(
65            Slot::AxisTop,
66            text_cell(
67                "axis_top on the outer SIBLING patch\nspans multiple lines\nto propagate INTO the nested composition",
68                14.0,
69            ),
70        )
71        .slot(Slot::Panel, Cell::empty());
72    let nested = grid(
73        1,
74        3,
75        vec![
76            plain_plot("c1").into(),
77            plain_plot("c2").into(),
78            plain_plot("c3").into(),
79        ],
80    );
81    let composed = beside(outer_with_chrome, nested);
82    let layout = composed.solve(hephaestus::Size::new(w as f64, h as f64), dpi);
83
84    let mut renderer = VelloRenderer::new().expect("vello renderer init");
85    {
86        let scene = renderer.scene();
87        let stroke = hephaestus::stroke::Stroke::new(1.0);
88        let text_brush: Brush = rgb8(20, 20, 30).into();
89
90        for (_id, region, rect) in layout.iter() {
91            if region == "panel" {
92                continue;
93            }
94            let c = color_for_region(region);
95            let tint = Color::new([c.components[0], c.components[1], c.components[2], 0.20]);
96            let path: Path = rect.to_path(0.1);
97            scene.fill(
98                FillRule::NonZero,
99                Affine::IDENTITY,
100                &Brush::Solid(tint),
101                None,
102                &path,
103                PickId::Skip,
104            );
105            scene.stroke(
106                &stroke,
107                Affine::IDENTITY,
108                &Brush::Solid(c),
109                None,
110                &path,
111                PickId::Skip,
112            );
113        }
114        for (_id, region, rect) in layout.iter() {
115            if region != "panel" {
116                continue;
117            }
118            let path: Path = rect.to_path(0.1);
119            scene.fill(
120                FillRule::NonZero,
121                Affine::IDENTITY,
122                &Brush::Solid(color_for_region(region)),
123                None,
124                &path,
125                PickId::Skip,
126            );
127            scene.stroke(
128                &stroke,
129                Affine::IDENTITY,
130                &Brush::Solid(rgb8(255, 255, 255)),
131                None,
132                &path,
133                PickId::Skip,
134            );
135        }
136
137        if let Some(rect) = layout.get("outer_plain", Slot::AxisTop) {
138            let run = TextRun::new(
139                "axis_top on the outer SIBLING patch\nspans multiple lines\nto propagate INTO the nested composition",
140                &TextStyle::new(14.0),
141                96.0,
142            );
143            draw_text_in_rect(scene, &run, rect, &text_brush, PickId::Skip);
144        }
145    }
146
147    let mut pixels = vec![0u8; (w * h * 4) as usize];
148    let bg: Color = rgb8(248, 248, 252);
149    renderer
150        .render_to_buffer(w, h, bg, &mut pixels)
151        .expect("render");
152
153    let path = std::env::current_dir()
154        .unwrap()
155        .join("examples/nesting_outer_chrome_propagates.png");
156    hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
157    println!("wrote {}", path.display());
158
159    // Sanity check: all four panels share y0, even though only the outer
160    // sibling carries the axis_top content. The back sizer drives the
161    // nested inner row 8 from the outer-resolved row 8.
162    let outer_y = layout.get("outer_plain", Slot::Panel).unwrap();
163    let c1_y = layout.get("c1", Slot::Panel).unwrap();
164    let c2_y = layout.get("c2", Slot::Panel).unwrap();
165    let c3_y = layout.get("c3", Slot::Panel).unwrap();
166    println!(
167        "panel y0: outer_plain={}, c1={}, c2={}, c3={}",
168        outer_y.y0, c1_y.y0, c2_y.y0, c3_y.y0
169    );
170    println!("(all four should be equal — outer chrome propagated INTO the nested composition via back sizers)");
171}