1use crate::backend::Backend;
9use crate::color::{Color, lerp_color, palette, rgb};
10use crate::event::Event;
11use crate::font;
12
13pub struct Canvas<'b, B: Backend> {
14 backend: &'b mut B,
15}
16
17impl<'b, B: Backend> Canvas<'b, B> {
18 pub fn new(backend: &'b mut B) -> Self { Self { backend } }
19
20 pub fn width(&self) -> u32 { self.backend.width() }
23 pub fn height(&self) -> u32 { self.backend.height() }
24 pub fn size(&self) -> (u32, u32) { self.backend.size() }
25
26 pub fn fill_rect(&mut self, x: u32, y: u32, w: u32, h: u32, color: Color) {
27 self.backend.fill_rect(x, y, w, h, color);
28 }
29
30 pub fn draw_text(&mut self, x: u32, y: u32, text: &str, color: Color) {
31 self.backend.draw_text(x, y, text, color);
32 }
33
34 pub fn hline(&mut self, x: u32, y: u32, w: u32, color: Color) {
35 self.backend.hline(x, y, w, color);
36 }
37
38 pub fn vline(&mut self, x: u32, y: u32, h: u32, color: Color) {
39 self.backend.vline(x, y, h, color);
40 }
41
42 pub fn draw_rect(&mut self, x: u32, y: u32, w: u32, h: u32, color: Color) {
43 self.backend.draw_rect(x, y, w, h, color);
44 }
45
46 pub fn clear(&mut self, color: Color) {
47 self.backend.clear(color);
48 }
49
50 pub fn present(&mut self) {
51 self.backend.present();
52 }
53
54 pub fn poll_event(&mut self) -> Option<Event> {
55 self.backend.poll_event()
56 }
57
58 pub fn panel(&mut self, x: u32, y: u32, w: u32, h: u32, fill: Color, border: Color) {
62 self.fill_rect(x, y, w, h, fill);
63 self.draw_rect(x, y, w, h, border);
64 }
65
66 pub fn title_bar(&mut self, x: u32, y: u32, w: u32, h: u32, title: &str, bg: Color) {
68 self.fill_rect(x, y, w, h, bg);
69 let pad = 8u32;
70 self.draw_text(x + pad, y + (h - font::CHAR_H) / 2, title, palette::WHITE);
71 }
72
73 pub fn button(
75 &mut self,
76 x: u32, y: u32, w: u32, h: u32,
77 label: &str,
78 fill: Color, border: Color, text_color: Color,
79 ) {
80 self.panel(x, y, w, h, fill, border);
81 let lw = font::text_width(label);
82 let tx = x + w.saturating_sub(lw) / 2;
83 let ty = y + h.saturating_sub(font::CHAR_H) / 2;
84 self.draw_text(tx, ty, label, text_color);
85 }
86
87 pub fn progress_bar(
89 &mut self,
90 x: u32, y: u32, w: u32, h: u32,
91 percent: u32,
92 track: Color, fill: Color, border: Color,
93 ) {
94 let pct = percent.min(100);
95 let fill_w = w * pct / 100;
96 self.fill_rect(x, y, w, h, track);
97 if fill_w > 0 { self.fill_rect(x, y, fill_w, h, fill); }
98 self.draw_rect(x, y, w, h, border);
99 }
100
101 pub fn divider_v(&mut self, x: u32, y: u32, h: u32) {
103 self.vline(x, y, h, palette::DIVIDER);
104 }
105
106 pub fn divider_h(&mut self, x: u32, y: u32, w: u32) {
108 self.hline(x, y, w, palette::DIVIDER);
109 }
110
111 pub fn centered_text(&mut self, x: u32, y: u32, w: u32, text: &str, color: Color) {
113 let tw = font::text_width(text);
114 let tx = x + w.saturating_sub(tw) / 2;
115 self.draw_text(tx, y, text, color);
116 }
117
118 pub fn right_text(&mut self, x: u32, y: u32, w: u32, text: &str, color: Color) {
120 let tw = font::text_width(text);
121 let tx = x + w.saturating_sub(tw);
122 self.draw_text(tx, y, text, color);
123 }
124
125 pub fn fill_rounded(&mut self, x: u32, y: u32, w: u32, h: u32, color: Color) {
127 if w < 2 || h < 2 { self.fill_rect(x, y, w, h, color); return; }
128 self.fill_rect(x + 1, y, w - 2, h, color); self.fill_rect(x, y + 1, 1, h - 2, color); self.fill_rect(x + w - 1, y + 1, 1, h - 2, color); }
132
133 pub fn gradient_h(&mut self, x: u32, y: u32, w: u32, h: u32, left: Color, right: Color) {
135 if w == 0 { return; }
136 let steps = (w - 1).max(1);
137 for i in 0..w {
138 let c = lerp_color(left, right, i, steps);
139 self.fill_rect(x + i, y, 1, h, c);
140 }
141 }
142
143 pub fn gradient_v(&mut self, x: u32, y: u32, w: u32, h: u32, top: Color, bottom: Color) {
145 if h == 0 { return; }
146 let steps = (h - 1).max(1);
147 for i in 0..h {
148 let c = lerp_color(top, bottom, i, steps);
149 self.fill_rect(x, y + i, w, 1, c);
150 }
151 }
152
153 pub fn shadow_panel(&mut self, x: u32, y: u32, w: u32, h: u32, fill: Color, border: Color) {
155 self.fill_rect(x + 4, y + 4, w, h, palette::BLACK);
156 self.panel(x, y, w, h, fill, border);
157 }
158
159 pub fn accent_bar(&mut self, x: u32, y: u32, h: u32, color: Color) {
161 self.fill_rect(x, y, 3, h, color);
162 }
163
164 pub fn dot(&mut self, x: u32, y: u32, color: Color) {
166 self.fill_rect(x, y, 4, 4, color);
167 }
168
169 pub fn icon_tile(&mut self, x: u32, y: u32, size: u32, bg: Color, label: &str) {
171 self.fill_rounded(x, y, size, size, bg);
172 self.centered_text(x, y + size.saturating_sub(font::CHAR_H) / 2, size, label, palette::WHITE);
173 }
174
175 pub fn gradient_progress(
177 &mut self,
178 x: u32, y: u32, w: u32, h: u32,
179 percent: u32,
180 track: Color, fill_l: Color, fill_r: Color, border: Color,
181 ) {
182 let pct = percent.min(100);
183 let fill_w = w * pct / 100;
184 self.fill_rect(x, y, w, h, track);
185 if fill_w > 0 {
186 let steps = fill_w.saturating_sub(1).max(1);
187 for i in 0..fill_w {
188 let c = lerp_color(fill_l, fill_r, i, steps);
189 self.fill_rect(x + i, y, 1, h, c);
190 }
191 }
192 self.draw_rect(x, y, w, h, border);
193 }
194
195 pub fn fill_round4(&mut self, x: u32, y: u32, w: u32, h: u32, color: Color) {
199 if w < 8 || h < 8 { self.fill_rect(x, y, w, h, color); return; }
200 self.fill_rect(x, y + 4, w, h.saturating_sub(8), color);
202 self.fill_rect(x + 4, y, w.saturating_sub(8), 1, color);
204 self.fill_rect(x + 2, y + 1, w.saturating_sub(4), 1, color);
205 self.fill_rect(x + 1, y + 2, w.saturating_sub(2), 2, color);
206 self.fill_rect(x + 1, y + h - 4, w.saturating_sub(2), 2, color);
208 self.fill_rect(x + 2, y + h - 2, w.saturating_sub(4), 1, color);
209 self.fill_rect(x + 4, y + h - 1, w.saturating_sub(8), 1, color);
210 }
211
212 pub fn toggle_switch(&mut self, x: u32, y: u32, on: bool, accent: Color) {
214 let (tw, th) = (52u32, 26u32);
215 let bg = if on { accent } else { rgb(0x4A, 0x4A, 0x4A) };
216 self.fill_rect(x + th / 2, y, tw.saturating_sub(th), th, bg);
218 self.fill_round4(x, y, th, th, bg);
219 self.fill_round4(x + tw.saturating_sub(th), y, th, th, bg);
220 let kx = if on { x + tw.saturating_sub(th) + 2 } else { x + 2 };
222 self.fill_round4(kx, y + 2, th - 4, th - 4, palette::WHITE);
223 }
224
225 pub fn action_row(
229 &mut self, x: u32, y: u32, w: u32,
230 title: &str, subtitle: &str, value: &str,
231 hovered: bool, last: bool,
232 ) -> u32 {
233 let h = 52u32;
234 let bg = if hovered { rgb(0x24, 0x24, 0x3C) } else { palette::CARD_BG };
235 self.fill_rect(x, y, w, h, bg);
236 self.draw_text(x + 16, y + 10, title, palette::TEXT);
237 if !subtitle.is_empty() {
238 self.draw_text(x + 16, y + 30, subtitle, palette::TEXT_DIM);
239 }
240 if !value.is_empty() {
241 self.right_text(x, y + 18, w.saturating_sub(16), value, palette::TEXT_DIM);
242 }
243 if !last {
244 self.hline(x + 16, y + h - 1, w.saturating_sub(16), palette::CARD_BORDER);
245 }
246 h
247 }
248
249 pub fn action_row_toggle(
252 &mut self, x: u32, y: u32, w: u32,
253 title: &str, subtitle: &str,
254 on: bool, accent: Color,
255 hovered: bool, last: bool,
256 ) -> u32 {
257 let h = 52u32;
258 let bg = if hovered { rgb(0x24, 0x24, 0x3C) } else { palette::CARD_BG };
259 self.fill_rect(x, y, w, h, bg);
260 self.draw_text(x + 16, y + 10, title, palette::TEXT);
261 if !subtitle.is_empty() {
262 self.draw_text(x + 16, y + 30, subtitle, palette::TEXT_DIM);
263 }
264 self.toggle_switch(x + w.saturating_sub(68), y + (h - 26) / 2, on, accent);
265 if !last {
266 self.hline(x + 16, y + h - 1, w.saturating_sub(16), palette::CARD_BORDER);
267 }
268 h
269 }
270
271 pub fn action_row_nav(
274 &mut self, x: u32, y: u32, w: u32,
275 title: &str, subtitle: &str,
276 hovered: bool, last: bool,
277 ) -> u32 {
278 let h = 52u32;
279 let bg = if hovered { rgb(0x24, 0x24, 0x3C) } else { palette::CARD_BG };
280 self.fill_rect(x, y, w, h, bg);
281 self.draw_text(x + 16, y + 10, title, palette::TEXT);
282 if !subtitle.is_empty() {
283 self.draw_text(x + 16, y + 30, subtitle, palette::TEXT_DIM);
284 }
285 self.draw_text(x + w.saturating_sub(24), y + 18, ">", palette::TEXT_DIM);
286 if !last {
287 self.hline(x + 16, y + h - 1, w.saturating_sub(16), palette::CARD_BORDER);
288 }
289 h
290 }
291
292 pub fn search_bar(
294 &mut self, x: u32, y: u32, w: u32,
295 text: &str, focused: bool, accent: Color,
296 ) {
297 let h = 36u32;
298 self.fill_round4(x, y, w, h, rgb(0x1E, 0x1E, 0x30));
299 let border = if focused { accent } else { palette::CARD_BORDER };
300 self.draw_rect(x, y, w, h, border);
301 if focused {
302 self.draw_rect(x + 1, y + 1, w - 2, h - 2,
303 lerp_color(border, palette::CARD_BG, 1, 2));
304 }
305 self.draw_text(x + 8, y + 10, "O", palette::TEXT_DIM);
307 let qx = x + 28;
308 if text.is_empty() {
309 self.draw_text(qx, y + 10, "Search...", palette::TEXT_DIM);
310 } else {
311 self.draw_text(qx, y + 10, text, palette::TEXT);
312 }
313 }
314
315 pub fn gnome_headerbar(
317 &mut self, x: u32, y: u32, w: u32,
318 title: &str, has_back: bool, bg: Color,
319 ) {
320 let h = 48u32;
321 self.fill_rect(x, y, w, h, bg);
322 self.hline(x, y + h - 1, w, palette::CARD_BORDER);
323 if has_back {
324 self.fill_round4(x + 8, y + 7, 34, 34, rgb(0x30, 0x30, 0x4A));
325 self.draw_text(x + 18, y + 16, "<", palette::TEXT);
326 }
327 self.centered_text(x, y + (h - 16) / 2, w, title, palette::TEXT);
328 let mx = x + w.saturating_sub(50);
330 self.fill_round4(mx, y + 7, 34, 34, rgb(0x30, 0x30, 0x4A));
331 self.fill_rect(mx + 7, y + 21, 4, 4, palette::TEXT_DIM);
332 self.fill_rect(mx + 15, y + 21, 4, 4, palette::TEXT_DIM);
333 self.fill_rect(mx + 23, y + 21, 4, 4, palette::TEXT_DIM);
334 }
335
336 pub fn spinner(&mut self, cx: u32, cy: u32, frame: u32, color: Color) {
338 const DOTS: &[(i32, i32)] = &[
339 (0, -10), (7, -7), (10, 0), (7, 7),
340 (0, 10), (-7, 7), (-10, 0), (-7, -7),
341 ];
342 let head = (frame / 4) as usize % 8;
343 for (i, &(dx, dy)) in DOTS.iter().enumerate() {
344 let age = (i + 8 - head) % 8;
345 let c = lerp_color(color, palette::SURFACE, age as u32, 7);
346 let px = cx as i32 + dx - 1;
347 let py = cy as i32 + dy - 1;
348 if px >= 0 && py >= 0 {
349 self.fill_rect(px as u32, py as u32, 3, 3, c);
350 }
351 }
352 }
353
354 pub fn toast(&mut self, x: u32, y: u32, w: u32, message: &str, accent: Color) {
356 let h = 44u32;
357 self.fill_rect(x + 4, y + 4, w, h, rgb(0, 0, 0));
359 self.fill_round4(x, y, w, h, rgb(0x2E, 0x2E, 0x46));
361 self.draw_rect(x, y, w, h, rgb(0x44, 0x44, 0x66));
362 self.fill_rect(x + 12, y + (h - 8) / 2, 8, 8, accent);
364 self.draw_text(x + 28, y + (h - 16) / 2, message, palette::TEXT);
366 self.draw_text(x + w.saturating_sub(22), y + (h - 16) / 2, "x", palette::TEXT_DIM);
368 }
369
370 pub fn avatar(&mut self, x: u32, y: u32, size: u32, initials: &str, color: Color) {
372 self.fill_round4(x, y, size, size, color);
373 let tw = font::text_width(initials);
374 let tx = x + size.saturating_sub(tw) / 2;
375 let ty = y + size.saturating_sub(font::CHAR_H) / 2;
376 self.draw_text(tx, ty, initials, palette::WHITE);
377 }
378
379 pub fn chip(&mut self, x: u32, y: u32, text: &str, bg: Color, fg: Color) {
381 let tw = font::text_width(text);
382 let w = tw + 16;
383 let h = 22u32;
384 self.fill_round4(x, y, w, h, bg);
385 self.draw_text(x + 8, y + 3, text, fg);
386 }
387
388 pub fn backend_mut(&mut self) -> &mut B { self.backend }
390}