1use hephaestus::backend::vello::VelloRenderer;
14use hephaestus::color::{rgb8, Color};
15use hephaestus::composition::{grid, stack, Patch, Slot};
16use hephaestus::layout::Cell;
17use hephaestus::text::{draw_text_in_rect, TextRun, TextStyle};
18use hephaestus::{Affine, Brush, FillRule, Path, PickId, Renderer, SceneBuilder};
19use kurbo::Shape;
20
21fn text_cell(text: &str, size: f32) -> Cell {
22 Cell::measured(TextRun::new(text, &TextStyle::new(size), 96.0))
23}
24
25fn plot(id: &str, title: &str, axis_left: &str, axis_bottom: &str) -> Patch {
26 Patch::new(id)
27 .slot(Slot::Title, text_cell(title, 16.0))
28 .slot(Slot::AxisLeft, text_cell(axis_left, 11.0))
29 .slot(Slot::AxisBottom, text_cell(axis_bottom, 11.0))
30 .slot(Slot::Panel, Cell::empty())
31}
32
33fn color_for_region(region: &str) -> Color {
34 match region {
35 "panel" => rgb8(40, 60, 90),
36 "title" => rgb8(220, 180, 90),
37 "axis_left" | "axis_bottom" => rgb8(160, 100, 130),
38 _ => rgb8(120, 120, 120),
39 }
40}
41
42fn main() {
43 let (w, h) = (1200u32, 700u32);
44 let dpi = 96.0;
45
46 let row_three = grid(
47 1,
48 3,
49 vec![
50 plot("a1", "Series A1", "0\n10\n20", "0 5 10").into(),
51 plot("a2", "Series A2", "0\n50\n100", "0 5 10").into(),
52 plot("a3", "Series A3", "0\n500\n1000", "0 5 10").into(),
53 ],
54 );
55 let row_two = grid(
56 1,
57 2,
58 vec![
59 plot("b1", "Series B1 (wider)", "0\n100\n200\n300", "0 25 50").into(),
60 plot("b2", "Series B2 (wider)", "0\n5000\n10000", "0 25 50").into(),
61 ],
62 );
63 let composed = stack(row_three, row_two);
64 let layout = composed.solve(hephaestus::Size::new(w as f64, h as f64), dpi);
65
66 let mut renderer = VelloRenderer::new().expect("vello renderer init");
67 {
68 let scene = renderer.scene();
69 let stroke = hephaestus::stroke::Stroke::new(1.0);
70 let text_brush: Brush = rgb8(20, 20, 30).into();
71
72 for (_id, region, rect) in layout.iter() {
74 if region == "panel" {
75 continue;
76 }
77 let c = color_for_region(region);
78 let tint = Color::new([c.components[0], c.components[1], c.components[2], 0.20]);
79 let path: Path = rect.to_path(0.1);
80 scene.fill(
81 FillRule::NonZero,
82 Affine::IDENTITY,
83 &Brush::Solid(tint),
84 None,
85 &path,
86 PickId::Skip,
87 );
88 scene.stroke(
89 &stroke,
90 Affine::IDENTITY,
91 &Brush::Solid(c),
92 None,
93 &path,
94 PickId::Skip,
95 );
96 }
97
98 for (_id, region, rect) in layout.iter() {
100 if region != "panel" {
101 continue;
102 }
103 let path: Path = rect.to_path(0.1);
104 scene.fill(
105 FillRule::NonZero,
106 Affine::IDENTITY,
107 &Brush::Solid(color_for_region(region)),
108 None,
109 &path,
110 PickId::Skip,
111 );
112 scene.stroke(
113 &stroke,
114 Affine::IDENTITY,
115 &Brush::Solid(rgb8(255, 255, 255)),
116 None,
117 &path,
118 PickId::Skip,
119 );
120 }
121
122 for (id, region, rect) in layout.iter() {
124 if region == "panel" {
125 continue;
126 }
127 let text = match (id, region) {
128 ("a1", "title") => "Series A1",
129 ("a2", "title") => "Series A2",
130 ("a3", "title") => "Series A3",
131 ("b1", "title") => "Series B1 (wider)",
132 ("b2", "title") => "Series B2 (wider)",
133 ("a1", "axis_left") => "0\n10\n20",
134 ("a2", "axis_left") => "0\n50\n100",
135 ("a3", "axis_left") => "0\n500\n1000",
136 ("b1", "axis_left") => "0\n100\n200\n300",
137 ("b2", "axis_left") => "0\n5000\n10000",
138 (_, "axis_bottom") if id.starts_with('a') => "0 5 10",
139 (_, "axis_bottom") if id.starts_with('b') => "0 25 50",
140 _ => continue,
141 };
142 let size = if region == "title" { 16.0 } else { 11.0 };
143 let run = TextRun::new(text, &TextStyle::new(size), 96.0);
144 draw_text_in_rect(scene, &run, rect, &text_brush, PickId::Skip);
145 }
146 }
147
148 let mut pixels = vec![0u8; (w * h * 4) as usize];
149 let bg: Color = rgb8(248, 248, 252);
150 renderer
151 .render_to_buffer(w, h, bg, &mut pixels)
152 .expect("render");
153
154 let path = std::env::current_dir()
155 .unwrap()
156 .join("examples/nesting_asymmetric.png");
157 hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
158 println!("wrote {}", path.display());
159}