Skip to main content

bexa_ui_core/
renderer.rs

1use glyphon::Metrics;
2use glyphon::cosmic_text::Align;
3
4/// Clip rectangle (x, y, width, height) in pixel coords.
5pub type ClipRect = (f32, f32, f32, f32);
6
7#[derive(Clone, Copy)]
8pub struct QuadCommand {
9    pub rect: (f32, f32, f32, f32),
10    pub color: [f32; 4],
11    pub border_radius: f32,
12    pub border_width: f32,
13    pub border_color: [f32; 4],
14    pub clip: Option<ClipRect>,
15}
16
17pub struct TextCommand {
18    pub text: String,
19    pub pos: (f32, f32),
20    pub color: [u8; 3],
21    pub bounds: (f32, f32),
22    pub metrics: Metrics,
23    pub align: Align,
24    pub clip: Option<ClipRect>,
25    pub font_family: Option<String>,
26    /// Char indices to measure pixel widths at.
27    /// Results stored in Renderer::text_measures at the same command index.
28    pub measure_chars: Vec<usize>,
29}
30
31pub struct Renderer {
32    pub quad_commands: Vec<QuadCommand>,
33    pub text_commands: Vec<TextCommand>,
34    /// Pixel widths measured by the render layer (indexed by TextCommand index).
35    /// Each entry corresponds to `measure_chars` of the same TextCommand.
36    pub text_measures: Vec<Vec<f32>>,
37    clip_stack: Vec<ClipRect>,
38    /// Overlay commands drawn on top of everything (for dropdowns, tooltips, etc.)
39    pub overlay_quad_commands: Vec<QuadCommand>,
40    pub overlay_text_commands: Vec<TextCommand>,
41}
42
43impl Renderer {
44    pub fn new() -> Self {
45        Self {
46            quad_commands: Vec::new(),
47            text_commands: Vec::new(),
48            text_measures: Vec::new(),
49            clip_stack: Vec::new(),
50            overlay_quad_commands: Vec::new(),
51            overlay_text_commands: Vec::new(),
52        }
53    }
54
55    pub fn clear(&mut self) {
56        self.quad_commands.clear();
57        self.text_commands.clear();
58        self.text_measures.clear();
59        self.clip_stack.clear();
60        self.overlay_quad_commands.clear();
61        self.overlay_text_commands.clear();
62    }
63
64    /// Push a quad command to the overlay layer (drawn on top of everything).
65    pub fn overlay_fill_rect_styled(
66        &mut self,
67        rect: (f32, f32, f32, f32),
68        color: [f32; 4],
69        border_radius: f32,
70        border_width: f32,
71        border_color: [f32; 4],
72    ) {
73        self.overlay_quad_commands.push(QuadCommand {
74            rect,
75            color,
76            border_radius,
77            border_width,
78            border_color,
79            clip: None,
80        });
81    }
82
83    /// Push a text command to the overlay layer (drawn on top of everything).
84    pub fn overlay_draw_text(
85        &mut self,
86        text: &str,
87        pos: (f32, f32),
88        color: [u8; 3],
89        bounds: (f32, f32),
90        metrics: Metrics,
91        align: Align,
92    ) {
93        self.overlay_text_commands.push(TextCommand {
94            text: text.to_string(),
95            pos,
96            color,
97            bounds,
98            metrics,
99            align,
100            clip: None,
101            font_family: None,
102            measure_chars: vec![],
103        });
104    }
105
106    /// Push a text command with font to the overlay layer.
107    pub fn overlay_draw_text_with_font(
108        &mut self,
109        text: &str,
110        pos: (f32, f32),
111        color: [u8; 3],
112        bounds: (f32, f32),
113        metrics: Metrics,
114        align: Align,
115        font_family: &str,
116    ) {
117        self.overlay_text_commands.push(TextCommand {
118            text: text.to_string(),
119            pos,
120            color,
121            bounds,
122            metrics,
123            align,
124            clip: None,
125            font_family: Some(font_family.to_string()),
126            measure_chars: vec![],
127        });
128    }
129
130    pub fn push_clip(&mut self, clip: ClipRect) {
131        self.clip_stack.push(clip);
132    }
133
134    pub fn pop_clip(&mut self) {
135        self.clip_stack.pop();
136    }
137
138    fn current_clip(&self) -> Option<ClipRect> {
139        self.clip_stack.last().copied()
140    }
141
142    pub fn fill_rect(&mut self, rect: (f32, f32, f32, f32), color: [f32; 3]) {
143        self.quad_commands.push(QuadCommand {
144            rect,
145            color: [color[0], color[1], color[2], 1.0],
146            border_radius: 0.0,
147            border_width: 0.0,
148            border_color: [0.0; 4],
149            clip: self.current_clip(),
150        });
151    }
152
153    pub fn fill_rect_rounded(
154        &mut self,
155        rect: (f32, f32, f32, f32),
156        color: [f32; 4],
157        border_radius: f32,
158    ) {
159        self.quad_commands.push(QuadCommand {
160            rect,
161            color,
162            border_radius,
163            border_width: 0.0,
164            border_color: [0.0; 4],
165            clip: self.current_clip(),
166        });
167    }
168
169    pub fn fill_rect_styled(
170        &mut self,
171        rect: (f32, f32, f32, f32),
172        color: [f32; 4],
173        border_radius: f32,
174        border_width: f32,
175        border_color: [f32; 4],
176    ) {
177        self.quad_commands.push(QuadCommand {
178            rect,
179            color,
180            border_radius,
181            border_width,
182            border_color,
183            clip: self.current_clip(),
184        });
185    }
186
187    pub fn draw_text(
188        &mut self,
189        text: &str,
190        pos: (f32, f32),
191        color: [u8; 3],
192        bounds: (f32, f32),
193        metrics: Metrics,
194        align: Align,
195    ) {
196        self.text_commands.push(TextCommand {
197            text: text.to_string(),
198            pos,
199            color,
200            bounds,
201            metrics,
202            align,
203            clip: self.current_clip(),
204            font_family: None,
205            measure_chars: vec![],
206        });
207    }
208
209    pub fn draw_text_with_font(
210        &mut self,
211        text: &str,
212        pos: (f32, f32),
213        color: [u8; 3],
214        bounds: (f32, f32),
215        metrics: Metrics,
216        align: Align,
217        font_family: &str,
218    ) {
219        self.text_commands.push(TextCommand {
220            text: text.to_string(),
221            pos,
222            color,
223            bounds,
224            metrics,
225            align,
226            clip: self.current_clip(),
227            font_family: Some(font_family.to_string()),
228            measure_chars: vec![],
229        });
230    }
231
232    pub fn draw_text_measured(
233        &mut self,
234        text: &str,
235        pos: (f32, f32),
236        color: [u8; 3],
237        bounds: (f32, f32),
238        metrics: Metrics,
239        align: Align,
240        measure_chars: Vec<usize>,
241    ) -> usize {
242        let idx = self.text_commands.len();
243        self.text_commands.push(TextCommand {
244            text: text.to_string(),
245            pos,
246            color,
247            bounds,
248            metrics,
249            align,
250            clip: self.current_clip(),
251            font_family: None,
252            measure_chars,
253        });
254        idx
255    }
256}