1use crate::layout::{LayoutBox, LayoutNodeData, LayoutTree};
2use crate::modifier::{DrawCommand as ModifierDrawCommand, Rect, Size};
3use cranpose_core::NodeId;
4use cranpose_ui_graphics::DrawPrimitive;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum PaintLayer {
9 Behind,
10 Content,
11 Overlay,
12}
13
14#[derive(Clone, Debug, PartialEq)]
16pub enum RenderOp {
17 Primitive {
18 node_id: NodeId,
19 layer: PaintLayer,
20 primitive: DrawPrimitive,
21 },
22 Text {
23 node_id: NodeId,
24 rect: Rect,
25 value: String,
26 },
27}
28
29#[derive(Clone, Debug, Default, PartialEq)]
31pub struct RecordedRenderScene {
32 operations: Vec<RenderOp>,
33}
34
35impl RecordedRenderScene {
36 pub fn new(operations: Vec<RenderOp>) -> Self {
37 Self { operations }
38 }
39
40 pub fn operations(&self) -> &[RenderOp] {
42 &self.operations
43 }
44
45 pub fn into_operations(self) -> Vec<RenderOp> {
47 self.operations
48 }
49
50 pub fn primitives_for(&self, layer: PaintLayer) -> impl Iterator<Item = &DrawPrimitive> {
52 self.operations.iter().filter_map(move |op| match op {
53 RenderOp::Primitive {
54 layer: op_layer,
55 primitive,
56 ..
57 } if *op_layer == layer => Some(primitive),
58 _ => None,
59 })
60 }
61}
62
63#[derive(Default)]
65pub struct HeadlessRenderer;
66
67impl HeadlessRenderer {
68 pub fn new() -> Self {
69 Self
70 }
71
72 pub fn render(&self, tree: &LayoutTree) -> RecordedRenderScene {
73 let mut operations = Vec::new();
74 self.render_box(tree.root(), &mut operations);
75 RecordedRenderScene::new(operations)
76 }
77
78 #[allow(clippy::only_used_in_recursion)]
79 fn render_box(&self, layout: &LayoutBox, operations: &mut Vec<RenderOp>) {
80 let rect = layout.rect;
81 let (mut behind, mut overlay) = evaluate_modifier(layout.node_id, &layout.node_data, rect);
82
83 operations.append(&mut behind);
84
85 if let Some(text) = layout.node_data.modifier_slices().text_content() {
89 operations.push(RenderOp::Text {
90 node_id: layout.node_id,
91 rect,
92 value: text.to_string(),
93 });
94 }
95
96 for child in &layout.children {
98 self.render_box(child, operations);
99 }
100
101 operations.append(&mut overlay);
102 }
103}
104
105fn evaluate_modifier(
106 node_id: NodeId,
107 data: &LayoutNodeData,
108 rect: Rect,
109) -> (Vec<RenderOp>, Vec<RenderOp>) {
110 let mut behind = Vec::new();
111 let mut overlay = Vec::new();
112
113 let size = Size {
114 width: rect.width,
115 height: rect.height,
116 };
117
118 for command in data.modifier_slices().draw_commands() {
121 match command {
122 ModifierDrawCommand::Behind(func) => {
123 for primitive in func(size) {
124 behind.push(RenderOp::Primitive {
125 node_id,
126 layer: PaintLayer::Behind,
127 primitive: translate_primitive(primitive, rect.x, rect.y),
128 });
129 }
130 }
131 ModifierDrawCommand::Overlay(func) => {
132 for primitive in func(size) {
133 overlay.push(RenderOp::Primitive {
134 node_id,
135 layer: PaintLayer::Overlay,
136 primitive: translate_primitive(primitive, rect.x, rect.y),
137 });
138 }
139 }
140 }
141 }
142
143 (behind, overlay)
144}
145
146fn translate_primitive(primitive: DrawPrimitive, dx: f32, dy: f32) -> DrawPrimitive {
147 match primitive {
148 DrawPrimitive::Rect { rect, brush } => DrawPrimitive::Rect {
149 rect: rect.translate(dx, dy),
150 brush,
151 },
152 DrawPrimitive::RoundRect { rect, brush, radii } => DrawPrimitive::RoundRect {
153 rect: rect.translate(dx, dy),
154 brush,
155 radii,
156 },
157 }
158}
159
160#[cfg(test)]
161#[path = "tests/renderer_tests.rs"]
162mod tests;