1use std::sync::{Arc, Mutex};
2
3struct CharCallback {
4 buffer: Arc<Mutex<Vec<char>>>,
5}
6
7impl minifb::InputCallback for CharCallback {
8 fn add_char(&mut self, uni_char: u32) {
9 if let Some(c) = char::from_u32(uni_char) {
10 if let Ok(mut buf) = self.buffer.lock() {
11 buf.push(c);
12 }
13 }
14 }
15}
16
17pub struct Window {
19 pub window: minifb::Window,
20 pub width: usize,
21 pub height: usize,
22 pub framebuffer_raw: Vec<u32>,
23 pub is_fullscreen: bool,
24 pub aa: f32,
27 input_buffer: Arc<Mutex<Vec<char>>>,
28 clip_stack: Vec<(usize, usize, usize, usize)>,
29}
30
31impl Window {
32 pub fn default() -> Self {
34 let input_buffer = Arc::new(Mutex::new(Vec::new()));
35 let mut window = minifb::Window::new(
36 "minifb-ui",
37 1280,
38 720,
39 minifb::WindowOptions {
40 resize: false,
41 scale: minifb::Scale::X1,
42 scale_mode: minifb::ScaleMode::AspectRatioStretch,
43 ..Default::default()
44 },
45 )
46 .unwrap();
47 window.set_input_callback(Box::new(CharCallback {
48 buffer: Arc::clone(&input_buffer),
49 }));
50 Self {
51 window,
52 width: 1280,
53 height: 720,
54 framebuffer_raw: vec![0u32; 1280 * 720],
55 is_fullscreen: false,
56 aa: 1.0,
57 input_buffer,
58 clip_stack: Vec::new(),
59 }
60 }
61
62 pub fn custom(name: &str, width: usize, height: usize, borders: bool, resizable: bool) -> Self {
64 let input_buffer = Arc::new(Mutex::new(Vec::new()));
65 let mut window = minifb::Window::new(
66 name,
67 width,
68 height,
69 minifb::WindowOptions {
70 resize: resizable,
71 scale: minifb::Scale::X1,
72 scale_mode: minifb::ScaleMode::Center,
73 borderless: borders,
74 ..Default::default()
75 },
76 )
77 .unwrap();
78 window.set_input_callback(Box::new(CharCallback {
79 buffer: Arc::clone(&input_buffer),
80 }));
81 Self {
82 window,
83 width,
84 height,
85 framebuffer_raw: vec![0u32; width * height],
86 is_fullscreen: false,
87 aa: 1.0,
88 input_buffer,
89 clip_stack: Vec::new(),
90 }
91 }
92
93 pub fn get_typed_chars(&self) -> Vec<char> {
95 if let Ok(mut buf) = self.input_buffer.lock() {
96 buf.drain(..).collect()
97 } else {
98 Vec::new()
99 }
100 }
101
102 pub fn draw_pixel(&mut self, x: usize, y: usize, color: &crate::color::Color) {
104 if x >= self.width || y >= self.height {
105 return;
106 }
107 let alpha = color.a as u32;
108 if alpha == 0 {
109 return;
110 }
111 let idx = y * self.width + x;
112 if alpha == 255 {
113 self.framebuffer_raw[idx] = color.as_u32();
114 return;
115 }
116 let bg = self.framebuffer_raw[idx];
117 let bg_r = (bg >> 16) & 0xFF;
118 let bg_g = (bg >> 8) & 0xFF;
119 let bg_b = bg & 0xFF;
120 let inv = 255 - alpha;
121 let r = (color.r as u32 * alpha + bg_r * inv) / 255;
122 let g = (color.g as u32 * alpha + bg_g * inv) / 255;
123 let b = (color.b as u32 * alpha + bg_b * inv) / 255;
124 self.framebuffer_raw[idx] = (r << 16) | (g << 8) | b;
125 }
126
127 pub fn get_pixel(&self, x: usize, y: usize) -> u32 {
128 self.framebuffer_raw[y * self.width + x]
129 }
130
131 pub fn clear(&mut self, color: &crate::color::Color) {
132 self.framebuffer_raw.fill(color.as_u32());
133 }
134
135 pub fn draw_line(
139 &mut self,
140 x0: isize,
141 y0: isize,
142 x1: isize,
143 y1: isize,
144 th: usize,
145 color: crate::color::Color,
146 ) {
147 if self.aa > 0.0 {
148 self.draw_line_aa_impl(x0 as f32, y0 as f32, x1 as f32, y1 as f32, th as f32, &color);
149 } else {
150 self.draw_line_aliased(x0, y0, x1, y1, th, &color);
151 }
152 }
153
154 pub fn draw_line_dashed(
156 &mut self,
157 x0: isize,
158 y0: isize,
159 x1: isize,
160 y1: isize,
161 th: usize,
162 dash_len: usize,
163 gap_len: usize,
164 color: crate::color::Color,
165 ) {
166 let dx = (x1 - x0) as f32;
167 let dy = (y1 - y0) as f32;
168 let total_len = (dx * dx + dy * dy).sqrt();
169 if total_len < 0.5 {
170 return;
171 }
172 let dir_x = dx / total_len;
173 let dir_y = dy / total_len;
174 let pattern_len = (dash_len + gap_len) as f32;
175
176 let mut dist = 0.0f32;
177 while dist < total_len {
178 let seg_end = (dist + dash_len as f32).min(total_len);
179 let sx = x0 as f32 + dir_x * dist;
180 let sy = y0 as f32 + dir_y * dist;
181 let ex = x0 as f32 + dir_x * seg_end;
182 let ey = y0 as f32 + dir_y * seg_end;
183
184 if self.aa > 0.0 {
185 self.draw_line_aa_impl(sx, sy, ex, ey, th as f32, &color);
186 } else {
187 self.draw_line_aliased(sx as isize, sy as isize, ex as isize, ey as isize, th, &color);
188 }
189
190 dist += pattern_len;
191 }
192 }
193
194 pub fn draw_line_dotted(
196 &mut self,
197 x0: isize,
198 y0: isize,
199 x1: isize,
200 y1: isize,
201 th: usize,
202 spacing: usize,
203 color: crate::color::Color,
204 ) {
205 self.draw_line_dashed(x0, y0, x1, y1, th, 1, spacing, color);
206 }
207
208 pub fn draw_rect_f(&mut self, x: usize, y: usize, w: usize, h: usize, radius: usize, color: &crate::color::Color, blur: usize) {
213 if radius > 0 {
214 if blur > 0 {
215 self.blur_region_rounded(x, y, w, h, radius, blur);
216 }
217 if color.a == 0 {
218 return;
219 }
220 let base_alpha = color.a as f32 / 255.0;
221 if self.aa > 0.0 {
222 self.draw_rounded_rect_f_sdf(x as f32, y as f32, w as f32, h as f32, radius as f32, color, base_alpha);
223 } else if color.a == 255 {
224 self.draw_rounded_rect_f_aliased(x, y, w, h, radius, color);
225 } else {
226 self.draw_rounded_rect_f_sdf(x as f32, y as f32, w as f32, h as f32, radius as f32, color, base_alpha);
227 }
228 } else {
229 if blur > 0 {
230 self.blur_region(x, y, w, h, blur);
231 }
232 let alpha = color.a as u32;
233 if alpha == 0 {
234 return;
235 }
236 if alpha == 255 {
237 let value = color.as_u32();
238 let start = y * self.width + x;
239 for dy in 0..h {
240 let row = &mut self.framebuffer_raw[start + dy * self.width..][..w];
241 row.fill(value);
242 }
243 return;
244 }
245 let fg_r = color.r as u32;
246 let fg_g = color.g as u32;
247 let fg_b = color.b as u32;
248 let inv = 255 - alpha;
249 for dy in 0..h {
250 let row = (y + dy) * self.width + x;
251 for dx in 0..w {
252 let idx = row + dx;
253 let bg = self.framebuffer_raw[idx];
254 let bg_r = (bg >> 16) & 0xFF;
255 let bg_g = (bg >> 8) & 0xFF;
256 let bg_b = bg & 0xFF;
257 let r = (fg_r * alpha + bg_r * inv) / 255;
258 let g = (fg_g * alpha + bg_g * inv) / 255;
259 let b = (fg_b * alpha + bg_b * inv) / 255;
260 self.framebuffer_raw[idx] = (r << 16) | (g << 8) | b;
261 }
262 }
263 }
264 }
265
266 pub fn draw_rect(&mut self, x: usize, y: usize, w: usize, h: usize, radius: usize, color: &crate::color::Color) {
269 if radius > 0 {
270 if self.aa > 0.0 {
271 self.draw_rounded_rect_sdf(x as f32, y as f32, w as f32, h as f32, radius as f32, 1.0, color);
272 } else {
273 self.draw_rounded_rect_aliased(x, y, w, h, radius, color);
274 }
275 } else {
276 let value = color.as_u32();
277 if h >= 1 {
278 let top = y * self.width + x;
279 self.framebuffer_raw[top..top + w].fill(value);
280 }
281 if h >= 2 {
282 let bottom = (y + h - 1) * self.width + x;
283 self.framebuffer_raw[bottom..bottom + w].fill(value);
284 }
285 if w >= 2 && h >= 2 {
286 let left_start = (y + 1) * self.width + x;
287 let right_start = (y + 1) * self.width + x + w - 1;
288 for dy in 0..(h - 2) {
289 let offset = dy * self.width;
290 self.framebuffer_raw[left_start + offset] = value;
291 self.framebuffer_raw[right_start + offset] = value;
292 }
293 }
294 }
295 }
296
297 pub fn draw_ellipse_f(
302 &mut self,
303 cx: isize,
304 cy: isize,
305 rx: usize,
306 ry: usize,
307 color: &crate::color::Color,
308 blur: usize,
309 ) {
310 if blur > 0 {
311 let bx = (cx - rx as isize).max(0) as usize;
312 let by = (cy - ry as isize).max(0) as usize;
313 let bw = (rx * 2).min(self.width.saturating_sub(bx));
314 let bh = (ry * 2).min(self.height.saturating_sub(by));
315 self.blur_region(bx, by, bw, bh, blur);
316 }
317 if color.a == 0 {
318 return;
319 }
320 let base_alpha = color.a as f32 / 255.0;
321 if rx == ry {
322 if self.aa > 0.0 {
323 self.draw_circle_f_sdf(cx as f32, cy as f32, rx as f32, color, base_alpha);
324 } else if color.a == 255 {
325 self.draw_circle_f_aliased(cx, cy, rx, color);
326 } else {
327 self.draw_circle_f_sdf(cx as f32, cy as f32, rx as f32, color, base_alpha);
328 }
329 } else {
330 if self.aa > 0.0 {
331 self.draw_ellipse_f_sdf(cx as f32, cy as f32, rx as f32, ry as f32, color, base_alpha);
332 } else if color.a == 255 {
333 self.draw_ellipse_f_aliased(cx, cy, rx, ry, color);
334 } else {
335 self.draw_ellipse_f_sdf(cx as f32, cy as f32, rx as f32, ry as f32, color, base_alpha);
336 }
337 }
338 }
339
340 pub fn draw_ellipse(
342 &mut self,
343 cx: isize,
344 cy: isize,
345 rx: usize,
346 ry: usize,
347 color: &crate::color::Color,
348 ) {
349 if rx == ry {
350 if self.aa > 0.0 {
351 self.draw_circle_sdf(cx as f32, cy as f32, rx as f32, 1.0, color);
352 } else {
353 self.draw_circle_aliased(cx, cy, rx, color);
354 }
355 } else {
356 if self.aa > 0.0 {
357 self.draw_ellipse_sdf(cx as f32, cy as f32, rx as f32, ry as f32, 1.0, color);
358 } else {
359 self.draw_ellipse_aliased(cx, cy, rx, ry, color);
360 }
361 }
362 }
363
364 pub fn draw_gradient_h(
370 &mut self,
371 x: usize,
372 y: usize,
373 w: usize,
374 h: usize,
375 radius: usize,
376 color_left: &crate::color::Color,
377 color_right: &crate::color::Color,
378 ) {
379 if w == 0 || h == 0 {
380 return;
381 }
382 if radius > 0 {
383 let aa = self.aa;
384 let fx = x as f32;
385 let fy = y as f32;
386 let fw = w as f32;
387 let fh = h as f32;
388 let r = (radius as f32).min(fw / 2.0).min(fh / 2.0);
389
390 let min_px = ((fx - 1.0).floor() as isize).max(0);
391 let max_px = ((fx + fw + 1.0).ceil() as isize).min(self.width as isize - 1);
392 let min_py = ((fy - 1.0).floor() as isize).max(0);
393 let max_py = ((fy + fh + 1.0).ceil() as isize).min(self.height as isize - 1);
394
395 for py in min_py..=max_py {
396 for px in min_px..=max_px {
397 let pfx = px as f32 + 0.5;
398 let pfy = py as f32 + 0.5;
399 let t = ((pfx - fx) / (fw - 1.0).max(1.0)).clamp(0.0, 1.0);
400 let col = color_left.lerp(color_right, t);
401 if aa > 0.0 {
402 let dist = rounded_rect_sdf(pfx, pfy, fx, fy, fw, fh, r);
403 let coverage = (aa * 0.5 - dist).clamp(0.0, 1.0) / aa;
404 if coverage > 0.0 {
405 self.blend_pixel(px, py, &col, coverage);
406 }
407 } else {
408 self.blend_pixel(px, py, &col, 1.0);
409 }
410 }
411 }
412 } else {
413 for dy in 0..h {
414 let row = (y + dy) * self.width;
415 for dx in 0..w {
416 let t = dx as f32 / (w - 1).max(1) as f32;
417 let r = (color_left.r as f32 * (1.0 - t) + color_right.r as f32 * t) as u32;
418 let g = (color_left.g as f32 * (1.0 - t) + color_right.g as f32 * t) as u32;
419 let b = (color_left.b as f32 * (1.0 - t) + color_right.b as f32 * t) as u32;
420 self.framebuffer_raw[row + x + dx] = (r << 16) | (g << 8) | b;
421 }
422 }
423 }
424 }
425
426 pub fn draw_gradient_v(
429 &mut self,
430 x: usize,
431 y: usize,
432 w: usize,
433 h: usize,
434 radius: usize,
435 color_top: &crate::color::Color,
436 color_bottom: &crate::color::Color,
437 ) {
438 if w == 0 || h == 0 {
439 return;
440 }
441 if radius > 0 {
442 let aa = self.aa;
443 let fx = x as f32;
444 let fy = y as f32;
445 let fw = w as f32;
446 let fh = h as f32;
447 let r = (radius as f32).min(fw / 2.0).min(fh / 2.0);
448
449 if aa > 0.0 {
450 let min_px = ((fx - 1.0).floor() as isize).max(0);
451 let max_px = ((fx + fw + 1.0).ceil() as isize).min(self.width as isize - 1);
452 let min_py = ((fy - 1.0).floor() as isize).max(0);
453 let max_py = ((fy + fh + 1.0).ceil() as isize).min(self.height as isize - 1);
454
455 for py in min_py..=max_py {
456 let t = (py as f32 - fy) / (fh - 1.0).max(1.0);
457 let t = t.clamp(0.0, 1.0);
458 let row_color = color_top.lerp(color_bottom, t);
459 for px in min_px..=max_px {
460 let pfx = px as f32 + 0.5;
461 let pfy = py as f32 + 0.5;
462 let dist = rounded_rect_sdf(pfx, pfy, fx, fy, fw, fh, r);
463 let coverage = (aa * 0.5 - dist).clamp(0.0, 1.0) / aa;
464 if coverage > 0.0 {
465 self.blend_pixel(px, py, &row_color, coverage);
466 }
467 }
468 }
469 } else {
470 let ri = radius.min(w / 2).min(h / 2);
471 for dy in 0..h {
472 let t = dy as f32 / (h - 1).max(1) as f32;
473 let cr = (color_top.r as f32 * (1.0 - t) + color_bottom.r as f32 * t) as u32;
474 let cg = (color_top.g as f32 * (1.0 - t) + color_bottom.g as f32 * t) as u32;
475 let cb = (color_top.b as f32 * (1.0 - t) + color_bottom.b as f32 * t) as u32;
476 let value = (cr << 16) | (cg << 8) | cb;
477
478 let (row_start, row_end) = if dy < ri {
479 let cy = ri - dy;
480 let dx = ri - isqrt(ri * ri - cy * cy);
481 (x + dx, x + w - dx)
482 } else if dy >= h - ri {
483 let cy = dy - (h - 1 - ri);
484 let dx = ri - isqrt(ri * ri - cy * cy);
485 (x + dx, x + w - dx)
486 } else {
487 (x, x + w)
488 };
489
490 let start = (y + dy) * self.width + row_start;
491 let len = row_end - row_start;
492 self.framebuffer_raw[start..start + len].fill(value);
493 }
494 }
495 } else {
496 for dy in 0..h {
497 let t = dy as f32 / (h - 1).max(1) as f32;
498 let r = (color_top.r as f32 * (1.0 - t) + color_bottom.r as f32 * t) as u32;
499 let g = (color_top.g as f32 * (1.0 - t) + color_bottom.g as f32 * t) as u32;
500 let b = (color_top.b as f32 * (1.0 - t) + color_bottom.b as f32 * t) as u32;
501 let value = (r << 16) | (g << 8) | b;
502 let row = (y + dy) * self.width + x;
503 self.framebuffer_raw[row..row + w].fill(value);
504 }
505 }
506 }
507
508 pub fn draw_box_shadow(
514 &mut self,
515 x: isize,
516 y: isize,
517 w: usize,
518 h: usize,
519 radius: usize,
520 offset_x: isize,
521 offset_y: isize,
522 spread: isize,
523 blur: usize,
524 color: &crate::color::Color,
525 ) {
526 let sx = x + offset_x - spread;
527 let sy = y + offset_y - spread;
528 let sw = (w as isize + spread * 2).max(0) as f32;
529 let sh = (h as isize + spread * 2).max(0) as f32;
530 let sr = (radius as f32 + spread as f32).max(0.0);
531 let blur_f = blur as f32;
532
533 let fx = sx as f32;
534 let fy = sy as f32;
535 let draw_x = (fx - blur_f).floor() as isize;
536 let draw_y = (fy - blur_f).floor() as isize;
537 let draw_max_x = (fx + sw + blur_f).ceil() as isize;
538 let draw_max_y = (fy + sh + blur_f).ceil() as isize;
539
540 for py in draw_y..draw_max_y {
541 if py < 0 || py >= self.height as isize {
542 continue;
543 }
544 for px in draw_x..draw_max_x {
545 if px < 0 || px >= self.width as isize {
546 continue;
547 }
548
549 let pfx = px as f32 + 0.5;
550 let pfy = py as f32 + 0.5;
551 let dist = rounded_rect_sdf(pfx, pfy, fx, fy, sw, sh, sr);
552
553 if dist >= blur_f || dist < 0.0 {
554 if dist < 0.0 {
555 let alpha = color.a as f32 / 255.0;
556 if alpha > 0.0 {
557 self.blend_pixel(px, py, color, alpha);
558 }
559 }
560 continue;
561 }
562
563 let alpha = (color.a as f32 / 255.0) * (1.0 - dist / blur_f);
564 if alpha > 0.0 {
565 self.blend_pixel(px, py, color, alpha);
566 }
567 }
568 }
569 }
570
571 pub fn draw_bezier_quad(
575 &mut self,
576 x0: f32,
577 y0: f32,
578 x1: f32,
579 y1: f32,
580 x2: f32,
581 y2: f32,
582 th: usize,
583 color: &crate::color::Color,
584 ) {
585 let chord = ((x2 - x0).powi(2) + (y2 - y0).powi(2)).sqrt();
586 let control_net = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt()
587 + ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
588 let steps = ((chord + control_net) * 2.0).ceil() as usize;
589 let steps = steps.max(32);
590
591 let mut prev_x = x0;
592 let mut prev_y = y0;
593
594 for i in 1..=steps {
595 let t = i as f32 / steps as f32;
596 let inv = 1.0 - t;
597 let cur_x = inv * inv * x0 + 2.0 * inv * t * x1 + t * t * x2;
598 let cur_y = inv * inv * y0 + 2.0 * inv * t * y1 + t * t * y2;
599
600 self.draw_line_aa_impl(prev_x, prev_y, cur_x, cur_y, th as f32, color);
601
602 prev_x = cur_x;
603 prev_y = cur_y;
604 }
605 }
606
607 pub fn draw_bezier_cubic(
609 &mut self,
610 x0: f32,
611 y0: f32,
612 x1: f32,
613 y1: f32,
614 x2: f32,
615 y2: f32,
616 x3: f32,
617 y3: f32,
618 th: usize,
619 color: &crate::color::Color,
620 ) {
621 let chord = ((x3 - x0).powi(2) + (y3 - y0).powi(2)).sqrt();
622 let control_net = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt()
623 + ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
624 + ((x3 - x2).powi(2) + (y3 - y2).powi(2)).sqrt();
625 let steps = ((chord + control_net) * 2.0).ceil() as usize;
626 let steps = steps.max(32);
627
628 let mut prev_x = x0;
629 let mut prev_y = y0;
630
631 for i in 1..=steps {
632 let t = i as f32 / steps as f32;
633 let inv = 1.0 - t;
634 let cur_x = inv * inv * inv * x0
635 + 3.0 * inv * inv * t * x1
636 + 3.0 * inv * t * t * x2
637 + t * t * t * x3;
638 let cur_y = inv * inv * inv * y0
639 + 3.0 * inv * inv * t * y1
640 + 3.0 * inv * t * t * y2
641 + t * t * t * y3;
642
643 self.draw_line_aa_impl(prev_x, prev_y, cur_x, cur_y, th as f32, color);
644
645 prev_x = cur_x;
646 prev_y = cur_y;
647 }
648 }
649
650 pub fn draw_text(&mut self, x: usize, y: usize, text: &crate::ui::text::Text, size: f32, color: &crate::color::Color) {
654 use fontdue::layout::{Layout, LayoutSettings, CoordinateSystem, TextStyle};
655
656 let fg_r = color.r as u32;
657 let fg_g = color.g as u32;
658 let fg_b = color.b as u32;
659
660 let fonts = text.font.as_slice();
661
662 let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
663 layout.reset(&LayoutSettings {
664 x: x as f32,
665 y: y as f32,
666 ..Default::default()
667 });
668
669 layout.append(&fonts, &TextStyle::new(&text.text, size, 0));
670
671 for glyph in layout.glyphs() {
672 let (metrics, bitmap) = text.font.font.rasterize_config(glyph.key);
673
674 let glyph_x = glyph.x as i32;
675 let glyph_y = glyph.y as i32;
676
677 for row in 0..metrics.height {
678 for col in 0..metrics.width {
679 let px = glyph_x + col as i32;
680 let py = glyph_y + row as i32;
681
682 if px < 0 || py < 0 { continue; }
683 let (px, py) = (px as usize, py as usize);
684 if px >= self.width || py >= self.height { continue; }
685
686 let alpha = bitmap[row * metrics.width + col] as u32;
687 if alpha == 0 { continue; }
688
689 let idx = py * self.width + px;
690 let bg = self.framebuffer_raw[idx];
691 let bg_r = (bg >> 16) & 0xFF;
692 let bg_g = (bg >> 8) & 0xFF;
693 let bg_b = bg & 0xFF;
694
695 let r = (fg_r * alpha + bg_r * (255 - alpha)) / 255;
696 let g = (fg_g * alpha + bg_g * (255 - alpha)) / 255;
697 let b = (fg_b * alpha + bg_b * (255 - alpha)) / 255;
698
699 self.framebuffer_raw[idx] = (r << 16) | (g << 8) | b;
700 }
701 }
702 }
703 }
704
705 pub fn update(&mut self) {
709 self.window
710 .update_with_buffer(self.framebuffer_raw.as_slice(), self.width, self.height)
711 .unwrap();
712 }
713
714 pub fn get_mouse_state(&self) -> MouseState {
715 let pos = self.window.get_mouse_pos(minifb::MouseMode::Clamp).unwrap();
716 let lmb = self.window.get_mouse_down(minifb::MouseButton::Left);
717 let rmb = self.window.get_mouse_down(minifb::MouseButton::Right);
718 MouseState {
719 pos_x: pos.0,
720 pos_y: pos.1,
721 rmb_clicked: rmb,
722 lmb_clicked: lmb,
723 }
724 }
725
726 pub fn draw_text_centered(
730 &mut self,
731 x: usize,
732 y: usize,
733 w: usize,
734 h: usize,
735 text: &crate::ui::text::Text,
736 size: f32,
737 color: &crate::color::Color,
738 ) {
739 let text_w = text.get_width_precise(size);
740 let lm = text.font.font.horizontal_line_metrics(size).unwrap();
741 let tx = (x as f32 + (w as f32 / 2.0) - (text_w / 2.0)).max(0.0) as usize;
742 let ty = (y as f32 + (h as f32 / 2.0) - (lm.ascent / 2.0) + (lm.descent / 3.0)).max(0.0) as usize;
743 self.draw_text(tx, ty, text, size, color);
744 }
745
746 pub fn blur_region(&mut self, rx: usize, ry: usize, rw: usize, rh: usize, radius: usize) {
751 if radius == 0 || rw == 0 || rh == 0 { return; }
752 let x0 = rx.min(self.width);
753 let y0 = ry.min(self.height);
754 let x1 = (rx + rw).min(self.width);
755 let y1 = (ry + rh).min(self.height);
756 let w = x1 - x0;
757 let h = y1 - y0;
758 if w == 0 || h == 0 { return; }
759
760 let r = radius as i32;
761 let diam = (2 * r + 1) as u32;
762 let buf = &mut self.framebuffer_raw;
763 let fb_w = self.width;
764 let mut temp = vec![0u32; w * h];
765
766 for row in 0..h {
768 let (mut sr, mut sg, mut sb) = (0u32, 0u32, 0u32);
769 let buf_row = (y0 + row) * fb_w + x0;
770 for dx in -r..=r {
771 let sx = dx.clamp(0, w as i32 - 1) as usize;
772 let p = buf[buf_row + sx];
773 sr += (p >> 16) & 0xFF; sg += (p >> 8) & 0xFF; sb += p & 0xFF;
774 }
775 for col in 0..w {
776 temp[row * w + col] = ((sr / diam) << 16) | ((sg / diam) << 8) | (sb / diam);
777 let old = (col as i32 - r).clamp(0, w as i32 - 1) as usize;
778 let p = buf[buf_row + old];
779 sr -= (p >> 16) & 0xFF; sg -= (p >> 8) & 0xFF; sb -= p & 0xFF;
780 let new = (col as i32 + r + 1).clamp(0, w as i32 - 1) as usize;
781 let p = buf[buf_row + new];
782 sr += (p >> 16) & 0xFF; sg += (p >> 8) & 0xFF; sb += p & 0xFF;
783 }
784 }
785
786 for col in 0..w {
788 let (mut sr, mut sg, mut sb) = (0u32, 0u32, 0u32);
789 for dy in -r..=r {
790 let sy = dy.clamp(0, h as i32 - 1) as usize;
791 let p = temp[sy * w + col];
792 sr += (p >> 16) & 0xFF; sg += (p >> 8) & 0xFF; sb += p & 0xFF;
793 }
794 for row in 0..h {
795 buf[(y0 + row) * fb_w + x0 + col] = ((sr / diam) << 16) | ((sg / diam) << 8) | (sb / diam);
796 let old = (row as i32 - r).clamp(0, h as i32 - 1) as usize;
797 let p = temp[old * w + col];
798 sr -= (p >> 16) & 0xFF; sg -= (p >> 8) & 0xFF; sb -= p & 0xFF;
799 let new = (row as i32 + r + 1).clamp(0, h as i32 - 1) as usize;
800 let p = temp[new * w + col];
801 sr += (p >> 16) & 0xFF; sg += (p >> 8) & 0xFF; sb += p & 0xFF;
802 }
803 }
804 }
805
806 pub fn blur_region_rounded(
809 &mut self,
810 rx: usize, ry: usize, rw: usize, rh: usize,
811 corner_radius: usize, blur_radius: usize,
812 ) {
813 if blur_radius == 0 || rw == 0 || rh == 0 { return; }
814 let r2 = (corner_radius * corner_radius) as isize;
815 let corners = [
816 (rx, ry, rx + corner_radius, ry + corner_radius, rx + corner_radius, ry + corner_radius),
817 (rx + rw - corner_radius, ry, rx + rw, ry + corner_radius, rx + rw - corner_radius, ry + corner_radius),
818 (rx, ry + rh - corner_radius, rx + corner_radius, ry + rh, rx + corner_radius, ry + rh - corner_radius),
819 (rx + rw - corner_radius, ry + rh - corner_radius, rx + rw, ry + rh, rx + rw - corner_radius, ry + rh - corner_radius),
820 ];
821
822 let mut saved: Vec<(usize, u32)> = Vec::new();
824 for &(x0, y0, x1, y1, cx, cy) in &corners {
825 for py in y0..y1 {
826 for px in x0..x1 {
827 let dx = px as isize - cx as isize;
828 let dy = py as isize - cy as isize;
829 if dx * dx + dy * dy > r2 {
830 let idx = py * self.width + px;
831 if idx < self.framebuffer_raw.len() {
832 saved.push((idx, self.framebuffer_raw[idx]));
833 }
834 }
835 }
836 }
837 }
838
839 self.blur_region(rx, ry, rw, rh, blur_radius);
840
841 for &(idx, val) in &saved {
843 if idx < self.framebuffer_raw.len() {
844 self.framebuffer_raw[idx] = val;
845 }
846 }
847 }
848
849 pub fn push_clip(&mut self, x: usize, y: usize, w: usize, h: usize) {
854 let (cx, cy, cw, ch) = if let Some(&(px, py, pw, ph)) = self.clip_stack.last() {
855 let x0 = x.max(px);
856 let y0 = y.max(py);
857 let x1 = (x + w).min(px + pw);
858 let y1 = (y + h).min(py + ph);
859 if x1 > x0 && y1 > y0 {
860 (x0, y0, x1 - x0, y1 - y0)
861 } else {
862 (x, y, 0, 0)
863 }
864 } else {
865 (x, y, w, h)
866 };
867 self.clip_stack.push((cx, cy, cw, ch));
868 }
869
870 pub fn pop_clip(&mut self) {
872 self.clip_stack.pop();
873 }
874
875 pub fn current_clip(&self) -> Option<(usize, usize, usize, usize)> {
877 self.clip_stack.last().copied()
878 }
879
880 #[inline]
882 fn clip_test(&self, x: usize, y: usize) -> bool {
883 if let Some(&(cx, cy, cw, ch)) = self.clip_stack.last() {
884 x >= cx && x < cx + cw && y >= cy && y < cy + ch
885 } else {
886 true
887 }
888 }
889
890 pub fn draw_text_wrapped(
895 &mut self,
896 x: usize,
897 y: usize,
898 text: &crate::ui::text::Text,
899 size: f32,
900 color: &crate::color::Color,
901 max_width: usize,
902 ) -> usize {
903 use fontdue::layout::{Layout, LayoutSettings, CoordinateSystem, TextStyle};
904
905 let lm = text.font.font.horizontal_line_metrics(size).unwrap();
906 let line_height = (lm.ascent - lm.descent + lm.line_gap).ceil() as usize;
907
908 let words: Vec<&str> = text.text.split_whitespace().collect();
909 if words.is_empty() {
910 return 0;
911 }
912
913 let fonts = text.font.as_slice();
914 let mut lines: Vec<String> = Vec::new();
915 let mut current_line = String::new();
916
917 for word in &words {
918 let test = if current_line.is_empty() {
919 word.to_string()
920 } else {
921 format!("{} {}", current_line, word)
922 };
923
924 let mut layout = Layout::new(CoordinateSystem::PositiveYDown);
926 layout.reset(&LayoutSettings::default());
927 layout.append(&fonts, &TextStyle::new(&test, size, 0));
928 let test_width = layout.glyphs().last().map_or(0.0, |g| {
929 g.x + text.font.font.metrics(g.parent, size).advance_width
930 });
931
932 if test_width <= max_width as f32 || current_line.is_empty() {
933 current_line = test;
934 } else {
935 lines.push(current_line);
936 current_line = word.to_string();
937 }
938 }
939 if !current_line.is_empty() {
940 lines.push(current_line);
941 }
942
943 let total_height = lines.len() * line_height;
944 for (i, line) in lines.iter().enumerate() {
945 let line_text = crate::ui::text::Text::new(line, text.font.clone());
946 self.draw_text(x, y + i * line_height, &line_text, size, color);
947 }
948
949 total_height
950 }
951
952
953 #[inline]
956 fn blend_pixel(&mut self, x: isize, y: isize, color: &crate::color::Color, alpha: f32) {
957 if x < 0 || y < 0 || x >= self.width as isize || y >= self.height as isize {
958 return;
959 }
960 if !self.clip_test(x as usize, y as usize) {
961 return;
962 }
963 let a = (alpha * 255.0) as u32;
964 if a == 0 {
965 return;
966 }
967 let idx = y as usize * self.width + x as usize;
968 if a >= 255 {
969 self.framebuffer_raw[idx] = color.as_u32();
970 return;
971 }
972 let bg = self.framebuffer_raw[idx];
973 let bg_r = (bg >> 16) & 0xFF;
974 let bg_g = (bg >> 8) & 0xFF;
975 let bg_b = bg & 0xFF;
976 let inv = 255 - a;
977 let r = (color.r as u32 * a + bg_r * inv) / 255;
978 let g = (color.g as u32 * a + bg_g * inv) / 255;
979 let b = (color.b as u32 * a + bg_b * inv) / 255;
980 self.framebuffer_raw[idx] = (r << 16) | (g << 8) | b;
981 }
982
983 #[inline]
984 fn set_pixel_safe(&mut self, x: isize, y: isize, value: u32) {
985 if x >= 0 && x < self.width as isize && y >= 0 && y < self.height as isize {
986 self.framebuffer_raw[y as usize * self.width + x as usize] = value;
987 }
988 }
989
990 fn draw_line_aa_impl(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, thickness: f32, color: &crate::color::Color) {
992 if thickness <= 1.5 && self.aa > 0.0 {
993 self.draw_line_wu(x0, y0, x1, y1, color);
994 } else if self.aa > 0.0 {
995 self.draw_line_thick_sdf(x0, y0, x1, y1, thickness, color);
996 } else {
997 self.draw_line_aliased(x0 as isize, y0 as isize, x1 as isize, y1 as isize, thickness as usize, color);
998 }
999 }
1000
1001 fn draw_line_wu(&mut self, mut x0: f32, mut y0: f32, mut x1: f32, mut y1: f32, color: &crate::color::Color) {
1003 let steep = (y1 - y0).abs() > (x1 - x0).abs();
1004 if steep {
1005 std::mem::swap(&mut x0, &mut y0);
1006 std::mem::swap(&mut x1, &mut y1);
1007 }
1008 if x0 > x1 {
1009 std::mem::swap(&mut x0, &mut x1);
1010 std::mem::swap(&mut y0, &mut y1);
1011 }
1012
1013 let dx = x1 - x0;
1014 let dy = y1 - y0;
1015 let gradient = if dx < 0.001 { 1.0 } else { dy / dx };
1016
1017 let xend = x0.round();
1018 let yend = y0 + gradient * (xend - x0);
1019 let xgap = 1.0 - (x0 + 0.5).fract();
1020 let xpxl1 = xend as isize;
1021 let ypxl1 = yend.floor() as isize;
1022 if steep {
1023 self.blend_pixel(ypxl1, xpxl1, color, (1.0 - yend.fract()) * xgap);
1024 self.blend_pixel(ypxl1 + 1, xpxl1, color, yend.fract() * xgap);
1025 } else {
1026 self.blend_pixel(xpxl1, ypxl1, color, (1.0 - yend.fract()) * xgap);
1027 self.blend_pixel(xpxl1, ypxl1 + 1, color, yend.fract() * xgap);
1028 }
1029 let mut intery = yend + gradient;
1030
1031 let xend2 = x1.round();
1032 let yend2 = y1 + gradient * (xend2 - x1);
1033 let xgap2 = (x1 + 0.5).fract();
1034 let xpxl2 = xend2 as isize;
1035 let ypxl2 = yend2.floor() as isize;
1036 if steep {
1037 self.blend_pixel(ypxl2, xpxl2, color, (1.0 - yend2.fract()) * xgap2);
1038 self.blend_pixel(ypxl2 + 1, xpxl2, color, yend2.fract() * xgap2);
1039 } else {
1040 self.blend_pixel(xpxl2, ypxl2, color, (1.0 - yend2.fract()) * xgap2);
1041 self.blend_pixel(xpxl2, ypxl2 + 1, color, yend2.fract() * xgap2);
1042 }
1043
1044 for x in (xpxl1 + 1)..xpxl2 {
1045 let ipart = intery.floor() as isize;
1046 let fpart = intery.fract();
1047 if steep {
1048 self.blend_pixel(ipart, x, color, 1.0 - fpart);
1049 self.blend_pixel(ipart + 1, x, color, fpart);
1050 } else {
1051 self.blend_pixel(x, ipart, color, 1.0 - fpart);
1052 self.blend_pixel(x, ipart + 1, color, fpart);
1053 }
1054 intery += gradient;
1055 }
1056 }
1057
1058 fn draw_line_thick_sdf(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, thickness: f32, color: &crate::color::Color) {
1060 let dx = x1 - x0;
1061 let dy = y1 - y0;
1062 let len_sq = dx * dx + dy * dy;
1063 if len_sq < 0.001 {
1064 return;
1065 }
1066 let half = thickness / 2.0;
1067 let aa = self.aa;
1068
1069 let min_x = (x0.min(x1) - half - aa).floor() as isize;
1070 let max_x = (x0.max(x1) + half + aa).ceil() as isize;
1071 let min_y = (y0.min(y1) - half - aa).floor() as isize;
1072 let max_y = (y0.max(y1) + half + aa).ceil() as isize;
1073
1074 let min_x = min_x.max(0);
1075 let max_x = max_x.min(self.width as isize - 1);
1076 let min_y = min_y.max(0);
1077 let max_y = max_y.min(self.height as isize - 1);
1078
1079 for py in min_y..=max_y {
1080 for px in min_x..=max_x {
1081 let fx = px as f32 + 0.5;
1082 let fy = py as f32 + 0.5;
1083
1084 let t = ((fx - x0) * dx + (fy - y0) * dy) / len_sq;
1085 let t = t.clamp(0.0, 1.0);
1086 let closest_x = x0 + t * dx;
1087 let closest_y = y0 + t * dy;
1088 let dist = ((fx - closest_x).powi(2) + (fy - closest_y).powi(2)).sqrt();
1089
1090 let coverage = ((half + aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1091 if coverage > 0.0 {
1092 self.blend_pixel(px, py, color, coverage);
1093 }
1094 }
1095 }
1096 }
1097
1098 fn draw_line_aliased(&mut self, x0: isize, y0: isize, x1: isize, y1: isize, th: usize, color: &crate::color::Color) {
1100 let dx = (x1 - x0).abs();
1101 let dy = (y1 - y0).abs();
1102 let sx = if x0 < x1 { 1isize } else { -1 };
1103 let sy = if y0 < y1 { 1isize } else { -1 };
1104 let value = color.as_u32();
1105 let half = (th / 2) as isize;
1106
1107 let mut x = x0;
1108 let mut y = y0;
1109 let mut err = dx - dy;
1110
1111 loop {
1112 for oy in -half..=half {
1113 for ox in -half..=half {
1114 let px = x + ox;
1115 let py = y + oy;
1116 if px >= 0 && px < self.width as isize && py >= 0 && py < self.height as isize {
1117 self.framebuffer_raw[py as usize * self.width + px as usize] = value;
1118 }
1119 }
1120 }
1121
1122 if x == x1 && y == y1 { break; }
1123
1124 let e2 = err * 2;
1125 if e2 > -dy { err -= dy; x += sx; }
1126 if e2 < dx { err += dx; y += sy; }
1127 }
1128 }
1129
1130 fn draw_circle_f_sdf(&mut self, cx: f32, cy: f32, radius: f32, color: &crate::color::Color, base_alpha: f32) {
1132 let aa = self.aa;
1133 let min_x = ((cx - radius - aa).floor() as isize).max(0);
1134 let max_x = ((cx + radius + aa).ceil() as isize).min(self.width as isize - 1);
1135 let min_y = ((cy - radius - aa).floor() as isize).max(0);
1136 let max_y = ((cy + radius + aa).ceil() as isize).min(self.height as isize - 1);
1137
1138 for py in min_y..=max_y {
1139 for px in min_x..=max_x {
1140 let fx = px as f32 + 0.5;
1141 let fy = py as f32 + 0.5;
1142 let dist = ((fx - cx).powi(2) + (fy - cy).powi(2)).sqrt() - radius;
1143 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1144 let final_alpha = coverage * base_alpha;
1145 if final_alpha > 0.0 {
1146 self.blend_pixel(px, py, color, final_alpha);
1147 }
1148 }
1149 }
1150 }
1151
1152 fn draw_circle_sdf(&mut self, cx: f32, cy: f32, radius: f32, thickness: f32, color: &crate::color::Color) {
1154 let aa = self.aa;
1155 let half = thickness / 2.0;
1156 let min_x = ((cx - radius - half - aa).floor() as isize).max(0);
1157 let max_x = ((cx + radius + half + aa).ceil() as isize).min(self.width as isize - 1);
1158 let min_y = ((cy - radius - half - aa).floor() as isize).max(0);
1159 let max_y = ((cy + radius + half + aa).ceil() as isize).min(self.height as isize - 1);
1160
1161 for py in min_y..=max_y {
1162 for px in min_x..=max_x {
1163 let fx = px as f32 + 0.5;
1164 let fy = py as f32 + 0.5;
1165 let dist_from_center = ((fx - cx).powi(2) + (fy - cy).powi(2)).sqrt();
1166 let dist = (dist_from_center - radius).abs() - half;
1167 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1168 if coverage > 0.0 {
1169 self.blend_pixel(px, py, color, coverage);
1170 }
1171 }
1172 }
1173 }
1174
1175 fn draw_circle_f_aliased(&mut self, cx: isize, cy: isize, radius: usize, color: &crate::color::Color) {
1177 let r = radius as isize;
1178 let value = color.as_u32();
1179 for dy in -r..=r {
1180 let half_w = isqrt_i(r * r - dy * dy);
1181 let py = cy + dy;
1182 if py < 0 || py >= self.height as isize { continue; }
1183 let start_x = (cx - half_w).max(0) as usize;
1184 let end_x = ((cx + half_w + 1) as usize).min(self.width);
1185 if start_x >= end_x { continue; }
1186 let row = py as usize * self.width;
1187 self.framebuffer_raw[row + start_x..row + end_x].fill(value);
1188 }
1189 }
1190
1191 fn draw_circle_aliased(&mut self, cx: isize, cy: isize, radius: usize, color: &crate::color::Color) {
1193 let value = color.as_u32();
1194 let mut x = radius as isize;
1195 let mut y: isize = 0;
1196 let mut err: isize = 1 - x;
1197
1198 while x >= y {
1199 self.set_pixel_safe(cx + x, cy + y, value);
1200 self.set_pixel_safe(cx - x, cy + y, value);
1201 self.set_pixel_safe(cx + x, cy - y, value);
1202 self.set_pixel_safe(cx - x, cy - y, value);
1203 self.set_pixel_safe(cx + y, cy + x, value);
1204 self.set_pixel_safe(cx - y, cy + x, value);
1205 self.set_pixel_safe(cx + y, cy - x, value);
1206 self.set_pixel_safe(cx - y, cy - x, value);
1207
1208 y += 1;
1209 if err < 0 {
1210 err += 2 * y + 1;
1211 } else {
1212 x -= 1;
1213 err += 2 * (y - x) + 1;
1214 }
1215 }
1216 }
1217
1218 fn draw_ellipse_f_sdf(&mut self, cx: f32, cy: f32, rx: f32, ry: f32, color: &crate::color::Color, base_alpha: f32) {
1220 let aa = self.aa;
1221 let min_x = ((cx - rx - aa).floor() as isize).max(0);
1222 let max_x = ((cx + rx + aa).ceil() as isize).min(self.width as isize - 1);
1223 let min_y = ((cy - ry - aa).floor() as isize).max(0);
1224 let max_y = ((cy + ry + aa).ceil() as isize).min(self.height as isize - 1);
1225
1226 for py in min_y..=max_y {
1227 for px in min_x..=max_x {
1228 let fx = px as f32 + 0.5;
1229 let fy = py as f32 + 0.5;
1230 let nx = (fx - cx) / rx;
1231 let ny = (fy - cy) / ry;
1232 let d = (nx * nx + ny * ny).sqrt();
1233 let r_min = rx.min(ry);
1234 let dist = (d - 1.0) * r_min;
1235 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1236 let final_alpha = coverage * base_alpha;
1237 if final_alpha > 0.0 {
1238 self.blend_pixel(px, py, color, final_alpha);
1239 }
1240 }
1241 }
1242 }
1243
1244 fn draw_ellipse_sdf(&mut self, cx: f32, cy: f32, rx: f32, ry: f32, thickness: f32, color: &crate::color::Color) {
1246 let aa = self.aa;
1247 let half = thickness / 2.0;
1248 let min_x = ((cx - rx - half - aa).floor() as isize).max(0);
1249 let max_x = ((cx + rx + half + aa).ceil() as isize).min(self.width as isize - 1);
1250 let min_y = ((cy - ry - half - aa).floor() as isize).max(0);
1251 let max_y = ((cy + ry + half + aa).ceil() as isize).min(self.height as isize - 1);
1252
1253 for py in min_y..=max_y {
1254 for px in min_x..=max_x {
1255 let fx = px as f32 + 0.5;
1256 let fy = py as f32 + 0.5;
1257 let nx = (fx - cx) / rx;
1258 let ny = (fy - cy) / ry;
1259 let d = (nx * nx + ny * ny).sqrt();
1260 let r_min = rx.min(ry);
1261 let dist = ((d - 1.0) * r_min).abs() - half;
1262 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1263 if coverage > 0.0 {
1264 self.blend_pixel(px, py, color, coverage);
1265 }
1266 }
1267 }
1268 }
1269
1270 fn draw_ellipse_f_aliased(&mut self, cx: isize, cy: isize, rx: usize, ry: usize, color: &crate::color::Color) {
1272 let value = color.as_u32();
1273 let rx_i = rx as isize;
1274 let ry_i = ry as isize;
1275 for dy in -ry_i..=ry_i {
1276 let py = cy + dy;
1277 if py < 0 || py >= self.height as isize { continue; }
1278 let half_w = (rx_i as f32 * (1.0 - (dy * dy) as f32 / (ry_i * ry_i) as f32).sqrt()) as isize;
1279 let start_x = (cx - half_w).max(0) as usize;
1280 let end_x = ((cx + half_w + 1) as usize).min(self.width);
1281 if start_x >= end_x { continue; }
1282 let row = py as usize * self.width;
1283 self.framebuffer_raw[row + start_x..row + end_x].fill(value);
1284 }
1285 }
1286
1287 fn draw_ellipse_aliased(&mut self, cx: isize, cy: isize, rx: usize, ry: usize, color: &crate::color::Color) {
1289 let value = color.as_u32();
1290 let a = rx as f32;
1291 let b = ry as f32;
1292 let steps = ((rx + ry) * 4).max(64);
1293 let step_angle = std::f32::consts::TAU / steps as f32;
1294
1295 for i in 0..steps {
1296 let angle = i as f32 * step_angle;
1297 let px = (cx as f32 + a * angle.cos()) as isize;
1298 let py = (cy as f32 + b * angle.sin()) as isize;
1299 self.set_pixel_safe(px, py, value);
1300 }
1301 }
1302
1303 fn draw_rounded_rect_f_sdf(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32, color: &crate::color::Color, base_alpha: f32) {
1305 let r = radius.min(w / 2.0).min(h / 2.0);
1306 let aa = self.aa;
1307 let min_px = ((x - aa).floor() as isize).max(0);
1308 let max_px = ((x + w + aa).ceil() as isize).min(self.width as isize - 1);
1309 let min_py = ((y - aa).floor() as isize).max(0);
1310 let max_py = ((y + h + aa).ceil() as isize).min(self.height as isize - 1);
1311
1312 let value = color.as_u32();
1313
1314 for py in min_py..=max_py {
1315 for px in min_px..=max_px {
1316 let pfx = px as f32 + 0.5;
1317 let pfy = py as f32 + 0.5;
1318 let dist = rounded_rect_sdf(pfx, pfy, x, y, w, h, r);
1319 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1320 let final_alpha = coverage * base_alpha;
1321 if final_alpha >= 1.0 {
1322 self.framebuffer_raw[py as usize * self.width + px as usize] = value;
1323 } else if final_alpha > 0.0 {
1324 self.blend_pixel(px, py, color, final_alpha);
1325 }
1326 }
1327 }
1328 }
1329
1330 fn draw_rounded_rect_sdf(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32, thickness: f32, color: &crate::color::Color) {
1332 let r = radius.min(w / 2.0).min(h / 2.0);
1333 let aa = self.aa;
1334 let half = thickness / 2.0;
1335 let min_px = ((x - half - aa).floor() as isize).max(0);
1336 let max_px = ((x + w + half + aa).ceil() as isize).min(self.width as isize - 1);
1337 let min_py = ((y - half - aa).floor() as isize).max(0);
1338 let max_py = ((y + h + half + aa).ceil() as isize).min(self.height as isize - 1);
1339
1340 for py in min_py..=max_py {
1341 for px in min_px..=max_px {
1342 let pfx = px as f32 + 0.5;
1343 let pfy = py as f32 + 0.5;
1344 let dist = rounded_rect_sdf(pfx, pfy, x, y, w, h, r).abs() - half;
1345 let coverage = ((aa * 0.5 - dist) / aa).clamp(0.0, 1.0);
1346 if coverage > 0.0 {
1347 self.blend_pixel(px, py, color, coverage);
1348 }
1349 }
1350 }
1351 }
1352
1353 fn draw_rounded_rect_f_aliased(&mut self, x: usize, y: usize, w: usize, h: usize, radius: usize, color: &crate::color::Color) {
1355 let r = radius.min(w / 2).min(h / 2);
1356 let value = color.as_u32();
1357
1358 for dy in 0..h {
1359 let (row_start, row_end) = if dy < r {
1360 let cy = r - dy;
1361 let dx = r - isqrt(r * r - cy * cy);
1362 (x + dx, x + w - dx)
1363 } else if dy >= h - r {
1364 let cy = dy - (h - 1 - r);
1365 let dx = r - isqrt(r * r - cy * cy);
1366 (x + dx, x + w - dx)
1367 } else {
1368 (x, x + w)
1369 };
1370
1371 let start = (y + dy) * self.width + row_start;
1372 let len = row_end - row_start;
1373 self.framebuffer_raw[start..start + len].fill(value);
1374 }
1375 }
1376
1377 fn draw_rounded_rect_aliased(&mut self, x: usize, y: usize, w: usize, h: usize, radius: usize, color: &crate::color::Color) {
1379 let r = radius.min(w / 2).min(h / 2);
1380 let value = color.as_u32();
1381
1382 for px in (x + r)..(x + w - r) {
1383 self.framebuffer_raw[y * self.width + px] = value;
1384 self.framebuffer_raw[(y + h - 1) * self.width + px] = value;
1385 }
1386 for py in (y + r)..(y + h - r) {
1387 self.framebuffer_raw[py * self.width + x] = value;
1388 self.framebuffer_raw[py * self.width + x + w - 1] = value;
1389 }
1390
1391 let cx_tl = x + r;
1392 let cy_tl = y + r;
1393 let cx_tr = x + w - 1 - r;
1394 let cy_tr = y + r;
1395 let cx_bl = x + r;
1396 let cy_bl = y + h - 1 - r;
1397 let cx_br = x + w - 1 - r;
1398 let cy_br = y + h - 1 - r;
1399
1400 let mut ix = r as isize;
1401 let mut iy: isize = 0;
1402 let mut err: isize = 1 - ix;
1403
1404 while ix >= iy {
1405 self.set_pixel_safe(cx_tl as isize - ix, cy_tl as isize - iy, value);
1406 self.set_pixel_safe(cx_tl as isize - iy, cy_tl as isize - ix, value);
1407 self.set_pixel_safe(cx_tr as isize + ix, cy_tr as isize - iy, value);
1408 self.set_pixel_safe(cx_tr as isize + iy, cy_tr as isize - ix, value);
1409 self.set_pixel_safe(cx_bl as isize - ix, cy_bl as isize + iy, value);
1410 self.set_pixel_safe(cx_bl as isize - iy, cy_bl as isize + ix, value);
1411 self.set_pixel_safe(cx_br as isize + ix, cy_br as isize + iy, value);
1412 self.set_pixel_safe(cx_br as isize + iy, cy_br as isize + ix, value);
1413
1414 iy += 1;
1415 if err < 0 {
1416 err += 2 * iy + 1;
1417 } else {
1418 ix -= 1;
1419 err += 2 * (iy - ix) + 1;
1420 }
1421 }
1422 }
1423}
1424
1425pub struct MouseState {
1426 pub pos_x: f32,
1427 pub pos_y: f32,
1428 pub rmb_clicked: bool,
1429 pub lmb_clicked: bool
1430}
1431
1432#[inline]
1434fn isqrt(n: usize) -> usize {
1435 (n as f32).sqrt() as usize
1436}
1437
1438#[inline]
1440fn isqrt_i(n: isize) -> isize {
1441 (n as f32).sqrt() as isize
1442}
1443
1444#[inline]
1447fn rounded_rect_sdf(px: f32, py: f32, x: f32, y: f32, w: f32, h: f32, r: f32) -> f32 {
1448 let cx = x + w / 2.0;
1449 let cy = y + h / 2.0;
1450 let dx = (px - cx).abs() - (w / 2.0 - r);
1451 let dy = (py - cy).abs() - (h / 2.0 - r);
1452 let outside = (dx.max(0.0).powi(2) + dy.max(0.0).powi(2)).sqrt();
1453 let inside = dx.max(dy).min(0.0);
1454 outside + inside - r
1455}