1use cranpose_core::{MemoryApplier, NodeId};
2use cranpose_ui_graphics::DrawPrimitive;
3
4use crate::{
5 layout::{LayoutBox, LayoutNodeData, LayoutTree},
6 modifier::{DrawCommand as ModifierDrawCommand, Point, Rect, Size},
7 widgets::LayoutNode,
8};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum PaintLayer {
13 Behind,
15 Content,
17 Overlay,
19}
20
21#[derive(Clone, Debug, PartialEq)]
23pub enum RenderOp {
24 Primitive {
26 node_id: NodeId,
28 layer: PaintLayer,
30 primitive: DrawPrimitive,
32 },
33 Text {
35 node_id: NodeId,
37 rect: Rect,
39 value: String,
41 },
42}
43
44#[derive(Clone, Debug, Default, PartialEq)]
46pub struct RecordedRenderScene {
47 operations: Vec<RenderOp>,
48}
49
50impl RecordedRenderScene {
51 pub fn new(operations: Vec<RenderOp>) -> Self {
53 Self { operations }
54 }
55
56 pub fn operations(&self) -> &[RenderOp] {
58 &self.operations
59 }
60
61 pub fn primitives_for(&self, layer: PaintLayer) -> impl Iterator<Item = &DrawPrimitive> {
63 self.operations.iter().filter_map(move |op| match op {
64 RenderOp::Primitive {
65 layer: op_layer,
66 primitive,
67 ..
68 } if *op_layer == layer => Some(primitive),
69 _ => None,
70 })
71 }
72}
73
74#[derive(Default)]
76pub struct HeadlessRenderer;
77
78impl HeadlessRenderer {
79 pub fn new() -> Self {
81 Self
82 }
83
84 pub fn render(&self, tree: &LayoutTree) -> RecordedRenderScene {
87 let mut operations = Vec::new();
88 self.render_box(tree.root(), &mut operations);
89 RecordedRenderScene::new(operations)
90 }
91
92 fn render_box(&self, layout: &LayoutBox, operations: &mut Vec<RenderOp>) {
93 let rect = layout.rect;
94 let (mut behind, overlay) = evaluate_modifier(layout.node_id, &layout.node_data, rect);
95
96 operations.append(&mut behind);
97
98 if let Some(text) = layout.node_data.modifier_slices().text_content() {
99 operations.push(RenderOp::Text {
100 node_id: layout.node_id,
101 rect,
102 value: text.to_string(),
103 });
104 }
105
106 for child in &layout.children {
107 self.render_box(child, operations);
108 }
109
110 append_overlay(operations, layout.node_id, rect, overlay);
111 }
112
113 pub fn render_from_applier(
116 &self,
117 applier: &mut MemoryApplier,
118 root: NodeId,
119 ) -> RecordedRenderScene {
120 let mut operations = Vec::new();
121 let mut child_stack = Vec::new();
122 self.render_node_from_applier(
123 applier,
124 root,
125 Point::default(),
126 &mut operations,
127 &mut child_stack,
128 );
129 RecordedRenderScene::new(operations)
130 }
131
132 fn render_node_from_applier(
137 &self,
138 applier: &mut MemoryApplier,
139 node_id: NodeId,
140 parent_offset: Point,
141 operations: &mut Vec<RenderOp>,
142 child_stack: &mut Vec<NodeId>,
143 ) {
144 let first_child = child_stack.len();
145 let Ok(Some((layout_state, modifier_slices))) =
146 applier.with_node::<LayoutNode, _>(node_id, |node| {
147 let state = node.layout_state();
148 state.is_placed().then(|| {
149 child_stack.extend_from_slice(&node.children);
150 (state, node.modifier_slices_snapshot())
151 })
152 })
153 else {
154 return;
155 };
156
157 let abs_x = parent_offset.x + layout_state.position().x;
158 let abs_y = parent_offset.y + layout_state.position().y;
159
160 let rect = Rect {
161 x: abs_x,
162 y: abs_y,
163 width: layout_state.size().width,
164 height: layout_state.size().height,
165 };
166
167 let size = Size {
168 width: rect.width,
169 height: rect.height,
170 };
171
172 let (behind, overlay) =
173 collect_primitives_from_commands(node_id, rect, size, modifier_slices.draw_commands());
174 operations.extend(behind);
175
176 if let Some(text) = modifier_slices.text_content() {
177 operations.push(RenderOp::Text {
178 node_id,
179 rect,
180 value: text.to_string(),
181 });
182 }
183
184 let child_offset = Point {
185 x: abs_x + layout_state.content_offset().x,
186 y: abs_y + layout_state.content_offset().y,
187 };
188
189 for index in first_child..child_stack.len() {
190 let child_id = child_stack[index];
191 self.render_node_from_applier(applier, child_id, child_offset, operations, child_stack);
192 }
193 child_stack.truncate(first_child);
194
195 append_overlay(operations, node_id, rect, overlay);
196 }
197}
198
199enum PendingOverlay<'a> {
200 Recorded(std::vec::IntoIter<DrawPrimitive>),
201 Command(&'a crate::draw::DrawCommandFn),
202}
203
204fn evaluate_modifier(
205 node_id: NodeId,
206 data: &LayoutNodeData,
207 rect: Rect,
208) -> (Vec<RenderOp>, Vec<PendingOverlay<'_>>) {
209 let size = Size {
210 width: rect.width,
211 height: rect.height,
212 };
213
214 collect_primitives_from_commands(node_id, rect, size, data.modifier_slices().draw_commands())
215}
216
217fn collect_primitives_from_commands(
218 node_id: NodeId,
219 rect: Rect,
220 size: Size,
221 commands: &[ModifierDrawCommand],
222) -> (Vec<RenderOp>, Vec<PendingOverlay<'_>>) {
223 let mut behind = Vec::new();
224 let mut overlay = Vec::new();
225 for command in commands {
226 match command {
227 ModifierDrawCommand::Behind(func) => {
228 append_primitives(
229 &mut behind,
230 node_id,
231 rect,
232 PaintLayer::Behind,
233 record(func, size),
234 );
235 }
236 ModifierDrawCommand::Overlay(func) => {
237 overlay.push(PendingOverlay::Command(func));
238 }
239 ModifierDrawCommand::WithContent(func) => {
240 let primitives = record(func, size);
241 let last_content = primitives
242 .iter()
243 .rposition(|primitive| matches!(primitive, DrawPrimitive::Content));
244 let mut primitives = primitives.into_iter();
245 if let Some(last_content) = last_content {
246 append_primitives(
247 &mut behind,
248 node_id,
249 rect,
250 PaintLayer::Behind,
251 primitives.by_ref().take(last_content),
252 );
253 let _ = primitives.next();
254 }
255 if !primitives.as_slice().is_empty() {
256 overlay.push(PendingOverlay::Recorded(primitives));
257 }
258 }
259 }
260 }
261 (behind, overlay)
262}
263
264fn record(func: &crate::draw::DrawCommandFn, size: Size) -> Vec<DrawPrimitive> {
265 use cranpose_ui_graphics::DrawScope as _;
266 let mut recording = cranpose_ui_graphics::CommandRecording::default();
267 let mut scope = crate::draw::command_draw_scope(size, &mut recording);
268 func(&mut scope);
269 scope.into_primitives()
270}
271
272fn append_overlay(
273 operations: &mut Vec<RenderOp>,
274 node_id: NodeId,
275 rect: Rect,
276 pending: Vec<PendingOverlay<'_>>,
277) {
278 let size = Size {
279 width: rect.width,
280 height: rect.height,
281 };
282 for part in pending {
283 let primitives = match part {
284 PendingOverlay::Recorded(primitives) => primitives,
285 PendingOverlay::Command(func) => record(func, size).into_iter(),
286 };
287 append_primitives(operations, node_id, rect, PaintLayer::Overlay, primitives);
288 }
289}
290
291fn append_primitives(
294 operations: &mut Vec<RenderOp>,
295 node_id: NodeId,
296 rect: Rect,
297 layer: PaintLayer,
298 primitives: impl IntoIterator<Item = DrawPrimitive>,
299) {
300 operations.extend(
301 cranpose_ui_graphics::translate_recorded(primitives, rect.x, rect.y)
302 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
303 .map(|primitive| RenderOp::Primitive {
304 node_id,
305 layer,
306 primitive,
307 }),
308 );
309}
310
311#[cfg(test)]
312#[path = "tests/renderer_tests.rs"]
313mod tests;