orinium_browser/engine/renderer_model/
draw_command.rs

1//! Draw command definition for rendering, which represents drawing instructions.
2
3use crate::engine::layouter::types::{Color, InfoNode, NodeKind, TextDecoration, TextStyle};
4use ui_layout::LayoutNode;
5
6#[derive(Debug, Clone)]
7pub enum DrawCommand {
8    DrawText {
9        x: f32,
10        y: f32,
11        text: String,
12        style: TextStyle,
13    },
14    DrawRect {
15        x: f32,
16        y: f32,
17        width: f32,
18        height: f32,
19        color: Color,
20    },
21    DrawPolygon {
22        points: Vec<(f32, f32)>,
23        color: Color,
24    },
25    DrawEllipse {
26        center: (f32, f32),
27        radius_x: f32,
28        radius_y: f32,
29        color: Color,
30    },
31    PushClip {
32        x: f32,
33        y: f32,
34        width: f32,
35        height: f32,
36    },
37    PopClip,
38    PushTransform {
39        dx: f32,
40        dy: f32,
41    },
42    PopTransform,
43}
44
45/// LayoutNode + InfoNode → DrawCommand
46pub fn generate_draw_commands(layout: &LayoutNode, info: &InfoNode) -> Vec<DrawCommand> {
47    let mut commands = Vec::new();
48
49    match &info.kind {
50        NodeKind::Text { texts, style, .. } => {
51            debug_assert_eq!(
52                texts.len(),
53                layout.self_fragments.len(),
54                "`generate_draw_commands` may be called before layout is complete."
55            );
56            debug_assert_eq!(
57                layout.self_fragments.len(),
58                layout.placements.len(),
59                "Layout should have placements for all self fragments."
60            );
61            for ((text, placement), fragment) in texts
62                .iter()
63                .zip(&layout.placements)
64                .zip(&layout.self_fragments)
65            {
66                let (abs_x, abs_y) = placement.offset;
67
68                // テキスト
69                commands.push(DrawCommand::DrawText {
70                    x: abs_x,
71                    y: abs_y,
72                    text: text.clone(),
73                    style: *style,
74                });
75
76                // テキストデコレーション
77                let font_size = style.font_size;
78                let line_thickness = (font_size * 0.08).max(1.0);
79
80                let (line_y, draw) = match style.text_decoration {
81                    TextDecoration::None => (0.0, false),
82                    TextDecoration::Underline => (abs_y + font_size, true),
83                    TextDecoration::LineThrough => (abs_y + font_size * 0.5, true),
84                    TextDecoration::Overline => (abs_y, true),
85                };
86
87                if draw {
88                    commands.push(DrawCommand::DrawRect {
89                        x: abs_x,
90                        y: line_y,
91                        width: fragment.width(),
92                        height: line_thickness,
93                        color: style.color,
94                    });
95                }
96            }
97        }
98
99        NodeKind::Container {
100            scroll_offset_x,
101            scroll_offset_y,
102            style,
103            ..
104        } => {
105            for box_model in &layout.layout_boxes {
106                let border_box = box_model.border_box;
107                let padding_box = box_model.padding_box;
108                let content_box = box_model.content_box;
109
110                // ===== border (solid only for now) =====
111                commands.push(DrawCommand::PushTransform {
112                    dx: border_box.x,
113                    dy: border_box.y,
114                });
115
116                let bc = &style.border_color;
117
118                // top
119                let border_width = (padding_box.y - border_box.y).max(0.0);
120                commands.push(DrawCommand::DrawRect {
121                    x: 0.0,
122                    y: 0.0,
123                    width: border_box.width,
124                    height: border_width,
125                    color: bc.top,
126                });
127
128                // bottom
129                let border_width = (border_box.y + border_box.height
130                    - (padding_box.y + padding_box.height))
131                    .max(0.0);
132                commands.push(DrawCommand::DrawRect {
133                    x: 0.0,
134                    y: border_box.height - border_width,
135                    width: border_box.width,
136                    height: border_width,
137                    color: bc.bottom,
138                });
139
140                // left
141                let border_width = (padding_box.x - border_box.x).max(0.0);
142                commands.push(DrawCommand::DrawRect {
143                    x: 0.0,
144                    y: 0.0,
145                    width: border_width,
146                    height: border_box.height,
147                    color: bc.left,
148                });
149
150                // right
151                let border_width = (border_box.x + border_box.width
152                    - (padding_box.x + padding_box.width))
153                    .max(0.0);
154                commands.push(DrawCommand::DrawRect {
155                    x: border_box.width - border_width,
156                    y: 0.0,
157                    width: border_width,
158                    height: border_box.height,
159                    color: bc.right,
160                });
161
162                // ===== clip + background + content =====
163                commands.push(DrawCommand::PushClip {
164                    x: padding_box.x - border_box.x,
165                    y: padding_box.y - border_box.y,
166                    width: padding_box.width,
167                    height: padding_box.height,
168                });
169
170                // background
171                commands.push(DrawCommand::DrawRect {
172                    x: padding_box.x - border_box.x,
173                    y: padding_box.y - border_box.y,
174                    width: padding_box.width,
175                    height: padding_box.height,
176                    color: style.background_color,
177                });
178
179                // content + scroll
180                commands.push(DrawCommand::PushTransform {
181                    dx: content_box.x - border_box.x,
182                    dy: content_box.y - border_box.y,
183                });
184                commands.push(DrawCommand::PushTransform {
185                    dx: *scroll_offset_x,
186                    dy: -*scroll_offset_y,
187                });
188            }
189        }
190    }
191
192    for (child_layout, child_info) in layout.children.iter().zip(&info.children) {
193        commands.extend(generate_draw_commands(child_layout, child_info));
194    }
195
196    // Pop commands for containers
197    if matches!(info.kind, NodeKind::Container { .. }) {
198        for _ in &layout.layout_boxes {
199            commands.push(DrawCommand::PopTransform);
200            commands.push(DrawCommand::PopTransform);
201            commands.push(DrawCommand::PopClip);
202            commands.push(DrawCommand::PopTransform);
203        }
204    }
205
206    commands
207}