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 mut behind = Vec::new();
112 let mut overlay = Vec::new();
113
114 let size = Size {
115 width: rect.width,
116 height: rect.height,
117 };
118
119 for command in data.modifier_slices().draw_commands() {
122 match command {
123 ModifierDrawCommand::Behind(func) => {
124 for primitive in func(size) {
125 behind.push(RenderOp::Primitive {
126 node_id,
127 layer: PaintLayer::Behind,
128 primitive: translate_primitive(primitive, rect.x, rect.y),
129 });
130 }
131 }
132 ModifierDrawCommand::Overlay(func) => {
133 for primitive in func(size) {
134 overlay.push(RenderOp::Primitive {
135 node_id,
136 layer: PaintLayer::Overlay,
137 primitive: translate_primitive(primitive, rect.x, rect.y),
138 });
139 }
140 }
141 }
142 }
143
144 (behind, overlay)
145}
146
147fn translate_primitive(primitive: DrawPrimitive, dx: f32, dy: f32) -> DrawPrimitive {
148 match primitive {
149 DrawPrimitive::Rect { rect, brush } => DrawPrimitive::Rect {
150 rect: rect.translate(dx, dy),
151 brush,
152 },
153 DrawPrimitive::RoundRect { rect, brush, radii } => DrawPrimitive::RoundRect {
154 rect: rect.translate(dx, dy),
155 brush,
156 radii,
157 },
158 DrawPrimitive::Image {
159 rect,
160 image,
161 alpha,
162 color_filter,
163 src_rect,
164 } => DrawPrimitive::Image {
165 rect: rect.translate(dx, dy),
166 image,
167 alpha,
168 color_filter,
169 src_rect,
170 },
171 }
172}
173
174impl HeadlessRenderer {
179 pub fn render_from_applier(
182 &self,
183 applier: &mut MemoryApplier,
184 root: NodeId,
185 ) -> RecordedRenderScene {
186 let mut operations = Vec::new();
187 self.render_node_from_applier(applier, root, Point::default(), &mut operations);
188 RecordedRenderScene::new(operations)
189 }
190
191 #[allow(clippy::only_used_in_recursion)]
192 fn render_node_from_applier(
193 &self,
194 applier: &mut MemoryApplier,
195 node_id: NodeId,
196 parent_offset: Point,
197 operations: &mut Vec<RenderOp>,
198 ) {
199 let node_data = match applier.with_node::<LayoutNode, _>(node_id, |node| {
201 let state = node.layout_state();
202 let modifier_slices = node.modifier_slices_snapshot();
203 let children: Vec<NodeId> = node.children.iter().copied().collect();
204 (state, modifier_slices, children)
205 }) {
206 Ok(data) => data,
207 Err(_) => return, };
209
210 let (layout_state, modifier_slices, children) = node_data;
211
212 if !layout_state.is_placed {
214 return;
215 }
216
217 let abs_x = parent_offset.x + layout_state.position.x;
219 let abs_y = parent_offset.y + layout_state.position.y;
220
221 let rect = Rect {
222 x: abs_x,
223 y: abs_y,
224 width: layout_state.size.width,
225 height: layout_state.size.height,
226 };
227
228 let size = Size {
229 width: rect.width,
230 height: rect.height,
231 };
232
233 let mut behind = Vec::new();
235 let mut overlay = Vec::new();
236
237 for command in modifier_slices.draw_commands() {
238 match command {
239 ModifierDrawCommand::Behind(func) => {
240 for primitive in func(size) {
241 behind.push(RenderOp::Primitive {
242 node_id,
243 layer: PaintLayer::Behind,
244 primitive: translate_primitive(primitive, rect.x, rect.y),
245 });
246 }
247 }
248 ModifierDrawCommand::Overlay(func) => {
249 for primitive in func(size) {
250 overlay.push(RenderOp::Primitive {
251 node_id,
252 layer: PaintLayer::Overlay,
253 primitive: translate_primitive(primitive, rect.x, rect.y),
254 });
255 }
256 }
257 }
258 }
259
260 operations.append(&mut behind);
261
262 if let Some(text) = modifier_slices.text_content() {
264 operations.push(RenderOp::Text {
265 node_id,
266 rect,
267 value: text.to_string(),
268 });
269 }
270
271 let child_offset = Point {
273 x: abs_x + layout_state.content_offset.x,
274 y: abs_y + layout_state.content_offset.y,
275 };
276
277 for child_id in children {
279 self.render_node_from_applier(applier, child_id, child_offset, operations);
280 }
281
282 operations.append(&mut overlay);
283 }
284}
285
286#[cfg(test)]
287#[path = "tests/renderer_tests.rs"]
288mod tests;