1use crate::layout::{LayoutBox, LayoutNodeData, LayoutTree};
2use crate::modifier::{DrawCommand as ModifierDrawCommand, Point, Rect, Size};
3use crate::widgets::LayoutNode;
4use cranpose_core::{MemoryApplier, NodeId};
5use cranpose_ui_graphics::DrawPrimitive;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum PaintLayer {
10 Behind,
11 Content,
12 Overlay,
13}
14
15#[derive(Clone, Debug, PartialEq)]
17pub enum RenderOp {
18 Primitive {
19 node_id: NodeId,
20 layer: PaintLayer,
21 primitive: DrawPrimitive,
22 },
23 Text {
24 node_id: NodeId,
25 rect: Rect,
26 value: String,
27 },
28}
29
30#[derive(Clone, Debug, Default, PartialEq)]
32pub struct RecordedRenderScene {
33 operations: Vec<RenderOp>,
34}
35
36impl RecordedRenderScene {
37 pub fn new(operations: Vec<RenderOp>) -> Self {
38 Self { operations }
39 }
40
41 pub fn operations(&self) -> &[RenderOp] {
43 &self.operations
44 }
45
46 pub fn into_operations(self) -> Vec<RenderOp> {
48 self.operations
49 }
50
51 pub fn primitives_for(&self, layer: PaintLayer) -> impl Iterator<Item = &DrawPrimitive> {
53 self.operations.iter().filter_map(move |op| match op {
54 RenderOp::Primitive {
55 layer: op_layer,
56 primitive,
57 ..
58 } if *op_layer == layer => Some(primitive),
59 _ => None,
60 })
61 }
62}
63
64#[derive(Default)]
66pub struct HeadlessRenderer;
67
68impl HeadlessRenderer {
69 pub fn new() -> Self {
70 Self
71 }
72
73 pub fn render(&self, tree: &LayoutTree) -> RecordedRenderScene {
74 let mut operations = Vec::new();
75 self.render_box(tree.root(), &mut operations);
76 RecordedRenderScene::new(operations)
77 }
78
79 #[allow(clippy::only_used_in_recursion)]
80 fn render_box(&self, layout: &LayoutBox, operations: &mut Vec<RenderOp>) {
81 let rect = layout.rect;
82 let (mut behind, mut overlay) = evaluate_modifier(layout.node_id, &layout.node_data, rect);
83
84 operations.append(&mut behind);
85
86 if let Some(text) = layout.node_data.modifier_slices().text_content() {
90 operations.push(RenderOp::Text {
91 node_id: layout.node_id,
92 rect,
93 value: text.to_string(),
94 });
95 }
96
97 for child in &layout.children {
99 self.render_box(child, operations);
100 }
101
102 operations.append(&mut overlay);
103 }
104}
105
106fn evaluate_modifier(
107 node_id: NodeId,
108 data: &LayoutNodeData,
109 rect: Rect,
110) -> (Vec<RenderOp>, Vec<RenderOp>) {
111 let size = Size {
112 width: rect.width,
113 height: rect.height,
114 };
115
116 let behind = collect_primitives_from_commands(
117 node_id,
118 rect,
119 size,
120 data.modifier_slices().draw_commands(),
121 PaintLayer::Behind,
122 );
123 let overlay = collect_primitives_from_commands(
124 node_id,
125 rect,
126 size,
127 data.modifier_slices().draw_commands(),
128 PaintLayer::Overlay,
129 );
130 (behind, overlay)
131}
132
133fn collect_primitives_from_commands(
134 node_id: NodeId,
135 rect: Rect,
136 size: Size,
137 commands: &[ModifierDrawCommand],
138 layer: PaintLayer,
139) -> Vec<RenderOp> {
140 let split_with_content = |primitives: Vec<DrawPrimitive>, layer| {
141 let Some(last_content_idx) = primitives
142 .iter()
143 .rposition(|primitive| matches!(primitive, DrawPrimitive::Content))
144 else {
145 return if layer == PaintLayer::Overlay {
146 primitives
147 .into_iter()
148 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
149 .collect()
150 } else {
151 Vec::new()
152 };
153 };
154
155 primitives
156 .into_iter()
157 .enumerate()
158 .filter_map(|(index, primitive)| {
159 if matches!(primitive, DrawPrimitive::Content) {
160 return None;
161 }
162 let is_before = index < last_content_idx;
163 match layer {
164 PaintLayer::Behind if is_before => Some(primitive),
165 PaintLayer::Overlay if !is_before => Some(primitive),
166 _ => None,
167 }
168 })
169 .collect()
170 };
171
172 let run = |func: &crate::draw::DrawCommandFn| {
173 use cranpose_ui_graphics::DrawScope as _;
174 let mut scope = crate::draw::command_draw_scope(size);
175 func(&mut scope);
176 scope.into_primitives()
177 };
178 let mut ops = Vec::new();
179 for command in commands {
180 let primitives = match (layer, command) {
181 (PaintLayer::Behind, ModifierDrawCommand::Behind(func)) => run(func)
182 .into_iter()
183 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
184 .collect(),
185 (PaintLayer::Overlay, ModifierDrawCommand::Overlay(func)) => run(func)
186 .into_iter()
187 .filter(|primitive| !matches!(primitive, DrawPrimitive::Content))
188 .collect(),
189 (PaintLayer::Behind | PaintLayer::Overlay, ModifierDrawCommand::WithContent(func)) => {
190 split_with_content(run(func), layer)
191 }
192 _ => Vec::new(),
193 };
194 for primitive in primitives {
195 ops.push(RenderOp::Primitive {
196 node_id,
197 layer,
198 primitive: translate_primitive(primitive, rect.x, rect.y),
199 });
200 }
201 }
202 ops
203}
204
205fn translate_primitive(primitive: DrawPrimitive, dx: f32, dy: f32) -> DrawPrimitive {
206 match primitive {
207 DrawPrimitive::Content => DrawPrimitive::Content,
208 DrawPrimitive::Blend {
209 primitive,
210 blend_mode,
211 } => DrawPrimitive::Blend {
212 primitive: Box::new(translate_primitive(*primitive, dx, dy)),
213 blend_mode,
214 },
215 DrawPrimitive::Rect {
216 rect,
217 brush,
218 stroke,
219 } => DrawPrimitive::Rect {
220 rect: rect.translate(dx, dy),
221 brush,
222 stroke,
223 },
224 DrawPrimitive::RoundRect {
225 rect,
226 brush,
227 radii,
228 stroke,
229 } => DrawPrimitive::RoundRect {
230 rect: rect.translate(dx, dy),
231 brush,
232 radii,
233 stroke,
234 },
235 DrawPrimitive::Arc {
236 rect,
237 brush,
238 center,
239 radius,
240 start_angle,
241 sweep_angle,
242 stroke,
243 inner_radius,
244 } => DrawPrimitive::Arc {
245 rect: rect.translate(dx, dy),
246 brush,
247 center: Point::new(center.x + dx, center.y + dy),
251 radius,
252 start_angle,
253 sweep_angle,
254 stroke,
255 inner_radius,
256 },
257 DrawPrimitive::Image {
258 rect,
259 image,
260 alpha,
261 color_filter,
262 sampling,
263 src_rect,
264 } => DrawPrimitive::Image {
265 rect: rect.translate(dx, dy),
266 image,
267 alpha,
268 color_filter,
269 sampling,
270 src_rect,
271 },
272 DrawPrimitive::Text(mut text) => {
273 text.rect = text.rect.translate(dx, dy);
274 DrawPrimitive::Text(text)
275 }
276 DrawPrimitive::Shadow(shadow) => {
277 use cranpose_ui_graphics::ShadowPrimitive;
278 DrawPrimitive::Shadow(match shadow {
279 ShadowPrimitive::Drop {
280 shape,
281 cutout,
282 blur_radius,
283 blend_mode,
284 } => ShadowPrimitive::Drop {
285 shape: Box::new(translate_primitive(*shape, dx, dy)),
286 cutout: cutout.map(|cutout| Box::new(translate_primitive(*cutout, dx, dy))),
287 blur_radius,
288 blend_mode,
289 },
290 ShadowPrimitive::Inner {
291 fill,
292 cutout,
293 blur_radius,
294 blend_mode,
295 clip_rect,
296 } => ShadowPrimitive::Inner {
297 fill: Box::new(translate_primitive(*fill, dx, dy)),
298 cutout: Box::new(translate_primitive(*cutout, dx, dy)),
299 blur_radius,
300 blend_mode,
301 clip_rect: clip_rect.translate(dx, dy),
302 },
303 })
304 }
305 }
306}
307
308impl HeadlessRenderer {
313 pub fn render_from_applier(
316 &self,
317 applier: &mut MemoryApplier,
318 root: NodeId,
319 ) -> RecordedRenderScene {
320 let mut operations = Vec::new();
321 self.render_node_from_applier(applier, root, Point::default(), &mut operations);
322 RecordedRenderScene::new(operations)
323 }
324
325 #[allow(clippy::only_used_in_recursion)]
326 fn render_node_from_applier(
327 &self,
328 applier: &mut MemoryApplier,
329 node_id: NodeId,
330 parent_offset: Point,
331 operations: &mut Vec<RenderOp>,
332 ) {
333 let node_data = match applier.with_node::<LayoutNode, _>(node_id, |node| {
335 let state = node.layout_state();
336 let modifier_slices = node.modifier_slices_snapshot();
337 let children: Vec<NodeId> = node.children.clone();
338 (state, modifier_slices, children)
339 }) {
340 Ok(data) => data,
341 Err(_) => return, };
343
344 let (layout_state, modifier_slices, children) = node_data;
345
346 if !layout_state.is_placed {
348 return;
349 }
350
351 let abs_x = parent_offset.x + layout_state.position.x;
353 let abs_y = parent_offset.y + layout_state.position.y;
354
355 let rect = Rect {
356 x: abs_x,
357 y: abs_y,
358 width: layout_state.size.width,
359 height: layout_state.size.height,
360 };
361
362 let size = Size {
363 width: rect.width,
364 height: rect.height,
365 };
366
367 let mut behind = Vec::new();
369 let mut overlay = Vec::new();
370 behind.extend(collect_primitives_from_commands(
371 node_id,
372 rect,
373 size,
374 modifier_slices.draw_commands(),
375 PaintLayer::Behind,
376 ));
377 overlay.extend(collect_primitives_from_commands(
378 node_id,
379 rect,
380 size,
381 modifier_slices.draw_commands(),
382 PaintLayer::Overlay,
383 ));
384
385 operations.append(&mut behind);
386
387 if let Some(text) = modifier_slices.text_content() {
389 operations.push(RenderOp::Text {
390 node_id,
391 rect,
392 value: text.to_string(),
393 });
394 }
395
396 let child_offset = Point {
398 x: abs_x + layout_state.content_offset.x,
399 y: abs_y + layout_state.content_offset.y,
400 };
401
402 for child_id in children {
404 self.render_node_from_applier(applier, child_id, child_offset, operations);
405 }
406
407 operations.append(&mut overlay);
408 }
409}
410
411#[cfg(test)]
412#[path = "tests/renderer_tests.rs"]
413mod tests;