orinium_browser/engine/renderer_model/
draw_command.rs1use crate::engine::layouter::types::{
4 Background, Color, Gradient, InfoNode, NodeKind, TextDecoration, TextStyle,
5};
6use smol_str::SmolStr;
7use ui_layout::{LayoutChild, LayoutNode};
8
9#[derive(Debug, Clone)]
10pub enum DrawCommand {
11 DrawText {
12 x: f32,
13 y: f32,
14 text: SmolStr,
15 style: TextStyle,
16 },
17 DrawRect {
18 x: f32,
19 y: f32,
20 width: f32,
21 height: f32,
22 color: Color,
23 },
24 DrawGradientRect {
25 x: f32,
26 y: f32,
27 width: f32,
28 height: f32,
29 gradient: Gradient,
30 },
31 DrawPolygon {
32 points: Vec<(f32, f32)>,
33 color: Color,
34 },
35 DrawEllipse {
36 center: (f32, f32),
37 radius_x: f32,
38 radius_y: f32,
39 color: Color,
40 },
41 PushClip {
42 x: f32,
43 y: f32,
44 width: f32,
45 height: f32,
46 },
47 PopClip,
48 PushTransform {
49 dx: f32,
50 dy: f32,
51 },
52 PopTransform,
53}
54
55#[derive(Default, Clone, Copy)]
57struct BoxPushState {
58 border: bool,
59 clip: bool,
60 content: bool,
61 scroll: bool,
62}
63
64pub fn generate_draw_commands(
66 cmd_buf: &mut Vec<DrawCommand>,
67 layout: &LayoutNode,
68 info: &InfoNode,
69) {
70 let mut box_states: Vec<BoxPushState> = Vec::new();
71
72 match &info.kind {
73 NodeKind::Text { .. } | NodeKind::LineBreak => unreachable!(),
74
75 NodeKind::Container {
76 scroll_offset_x,
77 scroll_offset_y,
78 style,
79 ..
80 } => {
81 for box_model in &layout.layout_box {
82 let border_box = box_model.border_box;
83 let padding_box = box_model.padding_box;
84 let content_box = box_model.content_box;
85 let is_inline = matches!(layout.layout_box, ui_layout::LayoutBox::InlineBox(_));
86
87 let mut state = BoxPushState::default();
88
89 if border_box.x != 0.0 || border_box.y != 0.0 {
90 cmd_buf.push(DrawCommand::PushTransform {
91 dx: border_box.x,
92 dy: border_box.y,
93 });
94 state.border = true;
95 }
96
97 if !is_inline {
98 let bc = &style.border_color;
102
103 let bw_top = (padding_box.y - border_box.y).max(0.0);
105 let bw_bottom = (border_box.y + border_box.height
106 - (padding_box.y + padding_box.height))
107 .max(0.0);
108 let bw_left = (padding_box.x - border_box.x).max(0.0);
109 let bw_right = (border_box.x + border_box.width
110 - (padding_box.x + padding_box.width))
111 .max(0.0);
112
113 if bw_top > 0.0 {
116 cmd_buf.push(DrawCommand::DrawRect {
117 x: 0.0,
118 y: 0.0,
119 width: border_box.width,
120 height: bw_top,
121 color: bc.top,
122 });
123 }
124
125 if bw_bottom > 0.0 {
126 cmd_buf.push(DrawCommand::DrawRect {
127 x: 0.0,
128 y: border_box.height - bw_bottom,
129 width: border_box.width,
130 height: bw_bottom,
131 color: bc.bottom,
132 });
133 }
134
135 if bw_left > 0.0 {
136 cmd_buf.push(DrawCommand::DrawRect {
137 x: 0.0,
138 y: bw_top,
139 width: bw_left,
140 height: border_box.height - bw_top - bw_bottom,
141 color: bc.left,
142 });
143 }
144
145 if bw_right > 0.0 {
146 cmd_buf.push(DrawCommand::DrawRect {
147 x: border_box.width - bw_right,
148 y: bw_top,
149 width: bw_right,
150 height: border_box.height - bw_top - bw_bottom,
151 color: bc.right,
152 });
153 }
154 }
155
156 if !is_inline && padding_box.width > 0.0 && padding_box.height > 0.0 {
157 cmd_buf.push(DrawCommand::PushClip {
158 x: padding_box.x - border_box.x,
159 y: padding_box.y - border_box.y,
160 width: padding_box.width,
161 height: padding_box.height,
162 });
163 state.clip = true;
164 }
165
166 match &style.background {
167 Background::Color(c) if c.3 > 0 => {
168 cmd_buf.push(DrawCommand::DrawRect {
169 x: padding_box.x - border_box.x,
170 y: padding_box.y - border_box.y,
171 width: padding_box.width,
172 height: padding_box.height,
173 color: *c,
174 });
175 }
176 Background::Gradient(g) => {
177 cmd_buf.push(DrawCommand::DrawGradientRect {
178 x: padding_box.x - border_box.x,
179 y: padding_box.y - border_box.y,
180 width: padding_box.width,
181 height: padding_box.height,
182 gradient: g.clone(),
183 });
184 }
185 _ => {}
186 }
187
188 let dx = content_box.x - border_box.x;
189 let dy = content_box.y - border_box.y;
190 if dx != 0.0 || dy != 0.0 {
191 cmd_buf.push(DrawCommand::PushTransform { dx, dy });
192 state.content = true;
193 }
194
195 if *scroll_offset_x != 0.0 || *scroll_offset_y != 0.0 {
196 cmd_buf.push(DrawCommand::PushTransform {
197 dx: *scroll_offset_x,
198 dy: -*scroll_offset_y,
199 });
200 state.scroll = true;
201 }
202
203 box_states.push(state);
204 }
205 }
206 }
207
208 let mut layout_iter = layout.children.iter();
209
210 for child_info in &info.children {
211 match &child_info.kind {
212 NodeKind::Text { texts, style } => {
213 for text in texts {
214 if let Some(LayoutChild::Fragment(frag_node)) = layout_iter.next() {
215 let (x, y) = frag_node.placement.offset;
216
217 cmd_buf.push(DrawCommand::DrawText {
218 x,
219 y,
220 text: text.into(),
221 style: *style,
222 });
223
224 let font_size = style.font_size;
225 let line_thickness = (font_size * 0.08).max(1.0);
226 let (line_y, draw) = match style.text_decoration {
227 TextDecoration::None => (0.0, false),
228 TextDecoration::Underline => (y + font_size, true),
229 TextDecoration::LineThrough => (y + font_size * 0.5, true),
230 TextDecoration::Overline => (y, true),
231 };
232
233 if draw {
234 cmd_buf.push(DrawCommand::DrawRect {
235 x,
236 y: line_y,
237 width: frag_node.node.width(),
238 height: line_thickness,
239 color: style.color,
240 });
241 }
242 }
243 }
244 }
245 NodeKind::LineBreak => {
246 layout_iter.next();
247 }
248 NodeKind::Container { .. } => {
249 if let Some(LayoutChild::Node(node)) = layout_iter.next() {
250 generate_draw_commands(cmd_buf, node, child_info);
251 }
252 }
253 }
254 }
255
256 if matches!(info.kind, NodeKind::Container { .. }) {
258 for state in box_states.iter().rev() {
259 if state.scroll {
260 cmd_buf.push(DrawCommand::PopTransform);
261 }
262 if state.content {
263 cmd_buf.push(DrawCommand::PopTransform);
264 }
265 if state.clip {
266 cmd_buf.push(DrawCommand::PopClip);
267 }
268 if state.border {
269 cmd_buf.push(DrawCommand::PopTransform);
270 }
271 }
272 }
273}