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 }
159}
160
161impl HeadlessRenderer {
166 pub fn render_from_applier(
169 &self,
170 applier: &mut MemoryApplier,
171 root: NodeId,
172 ) -> RecordedRenderScene {
173 let mut operations = Vec::new();
174 self.render_node_from_applier(applier, root, Point::default(), &mut operations);
175 RecordedRenderScene::new(operations)
176 }
177
178 #[allow(clippy::only_used_in_recursion)]
179 fn render_node_from_applier(
180 &self,
181 applier: &mut MemoryApplier,
182 node_id: NodeId,
183 parent_offset: Point,
184 operations: &mut Vec<RenderOp>,
185 ) {
186 let node_data = match applier.with_node::<LayoutNode, _>(node_id, |node| {
188 let state = node.layout_state();
189 let modifier_slices = node.modifier_slices_snapshot();
190 let children: Vec<NodeId> = node.children.iter().copied().collect();
191 (state, modifier_slices, children)
192 }) {
193 Ok(data) => data,
194 Err(_) => return, };
196
197 let (layout_state, modifier_slices, children) = node_data;
198
199 if !layout_state.is_placed {
201 return;
202 }
203
204 let abs_x = parent_offset.x + layout_state.position.x;
206 let abs_y = parent_offset.y + layout_state.position.y;
207
208 let rect = Rect {
209 x: abs_x,
210 y: abs_y,
211 width: layout_state.size.width,
212 height: layout_state.size.height,
213 };
214
215 let size = Size {
216 width: rect.width,
217 height: rect.height,
218 };
219
220 let mut behind = Vec::new();
222 let mut overlay = Vec::new();
223
224 for command in modifier_slices.draw_commands() {
225 match command {
226 ModifierDrawCommand::Behind(func) => {
227 for primitive in func(size) {
228 behind.push(RenderOp::Primitive {
229 node_id,
230 layer: PaintLayer::Behind,
231 primitive: translate_primitive(primitive, rect.x, rect.y),
232 });
233 }
234 }
235 ModifierDrawCommand::Overlay(func) => {
236 for primitive in func(size) {
237 overlay.push(RenderOp::Primitive {
238 node_id,
239 layer: PaintLayer::Overlay,
240 primitive: translate_primitive(primitive, rect.x, rect.y),
241 });
242 }
243 }
244 }
245 }
246
247 operations.append(&mut behind);
248
249 if let Some(text) = modifier_slices.text_content() {
251 operations.push(RenderOp::Text {
252 node_id,
253 rect,
254 value: text.to_string(),
255 });
256 }
257
258 let child_offset = Point {
260 x: abs_x + layout_state.content_offset.x,
261 y: abs_y + layout_state.content_offset.y,
262 };
263
264 for child_id in children {
266 self.render_node_from_applier(applier, child_id, child_offset, operations);
267 }
268
269 operations.append(&mut overlay);
270 }
271}
272
273#[cfg(test)]
274#[path = "tests/renderer_tests.rs"]
275mod tests;