1use crate::kernel::font::FONT_DATA;
2use core::cell::Cell;
3
4static ICON_CURSOR_64: &[u8] = include_bytes!("../../icons/ic_fluent_cursor_64_regular.svg");
5
6#[derive(Copy, Clone)]
7pub struct Color(pub u32);
8
9impl Color {
10 pub fn to_hardware_format(self) -> u32 {
11 let val = self.0;
12 let a = val & 0xFF000000;
13 let r = (val >> 16) & 0xFF;
14 let g = (val >> 8) & 0xFF;
15 let b = val & 0xFF;
16 a | (b << 16) | (g << 8) | r
17 }
18
19 pub fn from_hardware_format(val: u32) -> Color {
20 let a = val & 0xFF000000;
21 let r = (val >> 16) & 0xFF;
22 let g = (val >> 8) & 0xFF;
23 let b = val & 0xFF;
24 Color(a | (b << 16) | (g << 8) | r)
25 }
26}
27
28impl Color {
29 pub const BLACK: Color = Color(0xFF000000);
30 pub const WHITE: Color = Color(0xFFFFFFFF);
31 pub const LIGHT_GRAY: Color = Color(0xFFC6C6C6);
32 pub const DARK_GRAY: Color = Color(0xFF848484);
33 pub const BLUE: Color = Color(0xFF000084);
34 pub const DARK_RED: Color = Color(0xFF840000);
35 pub const INACTIVE_TITLE: Color = Color(0xFF5A5A5A);
36}
37
38const DMA_THRESHOLD_PIXELS: u32 = 2048; pub static FRAME_NO: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
45pub static HERO_TEXT_FRAME: core::sync::atomic::AtomicU32 =
47 core::sync::atomic::AtomicU32::new(u32::MAX);
48
49
50pub struct Screen {
51 pub vram: *mut u32,
52 pub back_buffer: *mut u32,
53 pub use_back_buffer: core::cell::Cell<bool>,
54 pub width: u32,
55 pub height: u32,
56 pub pitch: u32, pub clip: Cell<Option<(u32, u32, u32, u32)>>, clip_stack: Cell<[Option<(u32, u32, u32, u32)>; 4]>,
59 clip_depth: Cell<usize>,
60
61 render_target: Cell<*mut u32>,
63 target_width: Cell<u32>,
64 target_height: Cell<u32>,
65
66 vsync: Cell<bool>, page: Cell<u32>, fb_virtual_height: Cell<u32>, fb_size: Cell<u32>, prev_content_region: Cell<Option<(u32, u32, u32, u32)>>,
73 page_cursor: Cell<[(u32, u32); 2]>,
76 vsync_init_count: Cell<u8>,
78}
79
80pub static TEXT_DRAW_TICKS: core::sync::atomic::AtomicUsize =
82 core::sync::atomic::AtomicUsize::new(0);
83
84pub static BOXFILL_TICKS: core::sync::atomic::AtomicUsize =
86 core::sync::atomic::AtomicUsize::new(0);
87pub static SHADOW_TICKS: core::sync::atomic::AtomicUsize =
89 core::sync::atomic::AtomicUsize::new(0);
90
91pub static RRECT_TICKS: core::sync::atomic::AtomicUsize =
93 core::sync::atomic::AtomicUsize::new(0);
94
95pub static PIXBG_TICKS: core::sync::atomic::AtomicUsize =
98 core::sync::atomic::AtomicUsize::new(0);
99
100pub struct PixBgTimer(usize);
102impl PixBgTimer {
103 #[allow(clippy::new_without_default)]
104 pub fn new() -> Self {
105 Self(crate::kernel::timer::get_ticks())
106 }
107}
108impl Drop for PixBgTimer {
109 fn drop(&mut self) {
110 PIXBG_TICKS.fetch_add(
111 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
112 core::sync::atomic::Ordering::Relaxed,
113 );
114 }
115}
116
117pub struct RRectTimer(usize);
119impl RRectTimer {
120 #[allow(clippy::new_without_default)]
121 pub fn new() -> Self {
122 Self(crate::kernel::timer::get_ticks())
123 }
124}
125impl Drop for RRectTimer {
126 fn drop(&mut self) {
127 RRECT_TICKS.fetch_add(
128 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
129 core::sync::atomic::Ordering::Relaxed,
130 );
131 }
132}
133
134struct AccTimer(usize, &'static core::sync::atomic::AtomicUsize);
135impl Drop for AccTimer {
136 fn drop(&mut self) {
137 self.1.fetch_add(
138 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
139 core::sync::atomic::Ordering::Relaxed,
140 );
141 }
142}
143
144struct TextDrawTimer(usize);
146impl Drop for TextDrawTimer {
147 fn drop(&mut self) {
148 TEXT_DRAW_TICKS.fetch_add(
149 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
150 core::sync::atomic::Ordering::Relaxed,
151 );
152 }
153}
154
155impl Screen {
156 pub fn new(vram: *mut u32, width: u32, height: u32, pitch: u32) -> Self {
157 Self {
158 vram,
159 back_buffer: core::ptr::null_mut(),
160 use_back_buffer: core::cell::Cell::new(false),
161 width,
162 height,
163 pitch,
164 clip: Cell::new(None),
165 clip_stack: Cell::new([None; 4]),
166 clip_depth: Cell::new(0),
167 render_target: Cell::new(core::ptr::null_mut()),
168 target_width: Cell::new(width),
169 target_height: Cell::new(height),
170 vsync: Cell::new(false),
171 page: Cell::new(0),
172 fb_virtual_height: Cell::new(0),
173 fb_size: Cell::new(0),
174 prev_content_region: Cell::new(None),
175 page_cursor: Cell::new([(0, 0), (0, 0)]),
176 vsync_init_count: Cell::new(0),
177 }
178 }
179
180 pub fn vsync_enabled(&self) -> bool {
181 self.vsync.get()
182 }
183
184 pub fn has_pending_vsync_sync(&self) -> bool {
185 self.prev_content_region.get().is_some()
186 }
187
188 pub fn debug_geometry(&self) {
191 let addr = self.vram as usize;
192 let region = if addr >= crate::kernel::mmu::DEVICE_REGION_START {
193 "uncached/Device OK"
194 } else {
195 "CACHED RAM (<0x38000000) !!"
196 };
197 crate::println!(
198 "[FB] vram=0x{:08X} ({}) | {}x{} | pitch={} (width*4={}) | virt_h={} | size={}",
199 addr,
200 region,
201 self.width,
202 self.height,
203 self.pitch,
204 self.width * 4,
205 self.fb_virtual_height.get(),
206 self.fb_size.get()
207 );
208 }
209
210 pub fn set_fb_geometry(&self, virtual_height: u32, size: u32) {
213 self.fb_virtual_height.set(virtual_height);
214 self.fb_size.set(size);
215 }
216
217 pub fn enable_vsync(&self) -> bool {
222 let virt_h = self.fb_virtual_height.get();
223 let size = self.fb_size.get();
224 let two_pages = virt_h >= self.height * 2
225 && (size == 0 || size as u64 >= (self.pitch as u64) * (self.height as u64) * 2);
226 if !two_pages {
227 self.vsync.set(false);
228 return false;
229 }
230 let _ = crate::kernel::mailbox::set_virtual_offset(0, 0);
232 self.page.set(0);
233 self.vsync.set(true);
234 self.vsync_init_count.set(2);
236 self.prev_content_region.set(None);
237 self.page_cursor.set([(0, 0), (0, 0)]);
238 true
239 }
240
241 pub fn set_render_target(&self, buf: *mut u32, w: u32, h: u32) {
242 self.render_target.set(buf);
243 self.target_width.set(w);
244 self.target_height.set(h);
245 }
246
247 pub fn reset_render_target(&self) {
248 self.render_target.set(core::ptr::null_mut());
249 self.target_width.set(self.width);
250 self.target_height.set(self.height);
251 }
252
253 pub fn resolve_target(&self) -> (*mut u32, u32, u32) {
254 let custom_target = self.render_target.get();
255 if !custom_target.is_null() {
256 (
257 custom_target,
258 self.target_width.get(),
259 self.target_height.get(),
260 )
261 } else {
262 let base_ptr = if self.use_back_buffer.get() && !self.back_buffer.is_null() {
263 self.back_buffer
264 } else {
265 self.vram
266 };
267 (base_ptr, self.width, self.height)
268 }
269 }
270
271 pub fn resolve_stride(&self) -> u32 {
273 let custom_target = self.render_target.get();
274 if !custom_target.is_null() {
275 self.target_width.get()
276 } else if self.use_back_buffer.get() && !self.back_buffer.is_null() {
277 self.width
278 } else {
279 self.pitch / 4
280 }
281 }
282
283 pub fn enable_double_buffering(&mut self) {
284 if self.back_buffer.is_null() {
285 let Ok(layout) = core::alloc::Layout::array::<u32>((self.width * self.height) as usize)
287 else {
288 return; };
290 unsafe {
291 self.back_buffer = alloc::alloc::alloc_zeroed(layout) as *mut u32;
292 }
293 if self.back_buffer.is_null() {
294 return; }
296 }
297 self.use_back_buffer.set(true);
298 }
299
300 pub fn flush(&self) {
301 if self.use_back_buffer.get() && !self.back_buffer.is_null() {
302 let stride = self.pitch / 4;
303 let w = self.width;
304 let h = self.height;
305
306 if self.vsync.get() {
307 let page = self.page.get();
310 let page_off_px = page * h * stride; let dst = self.vram as u32 + page_off_px * 4;
312 if stride == w {
313 crate::kernel::dma::dma_memcpy(5, self.back_buffer as u32, dst, w * h * 4);
314 } else {
315 crate::kernel::dma::dma_memcpy_2d(
316 5,
317 self.back_buffer as u32,
318 dst,
319 w * 4,
320 h,
321 w * 4,
322 stride * 4,
323 );
324 }
325 let _ = crate::kernel::mailbox::set_virtual_offset(0, page * h);
327 let _ = crate::kernel::mailbox::wait_vsync();
329 self.page.set(1 - page);
331 } else if stride == w {
332 crate::kernel::dma::dma_memcpy(
333 5,
334 self.back_buffer as u32,
335 self.vram as u32,
336 w * h * 4,
337 );
338 } else {
339 crate::kernel::dma::dma_memcpy_2d(
340 5,
341 self.back_buffer as u32,
342 self.vram as u32,
343 w * 4,
344 h,
345 w * 4,
346 stride * 4,
347 );
348 }
349 }
350 }
351
352 pub fn flush_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
353 if self.vsync.get() {
356 self.flush();
357 return;
358 }
359 if self.use_back_buffer.get() && !self.back_buffer.is_null() {
360 let stride = self.pitch / 4;
361 let w = self.width;
362 let h = self.height;
363
364 let cx0 = x0.min(w - 1);
365 let cx1 = x1.min(w - 1).max(cx0);
366 let cy0 = y0.min(h - 1);
367 let cy1 = y1.min(h - 1).max(cy0);
368
369 let rect_w = cx1 - cx0 + 1;
370 let rect_h = cy1 - cy0 + 1;
371
372 if rect_w * rect_h >= DMA_THRESHOLD_PIXELS {
373 let src_offset = (cy0 * w + cx0) * 4;
374 let dst_offset = (cy0 * stride + cx0) * 4;
375 crate::kernel::dma::dma_memcpy_2d(
376 5,
377 self.back_buffer as u32 + src_offset,
378 self.vram as u32 + dst_offset,
379 rect_w * 4,
380 rect_h,
381 w * 4,
382 stride * 4,
383 );
384 } else {
385 for y in cy0..=cy1 {
386 unsafe {
387 let src = self.back_buffer.add((y * w + cx0) as usize);
388 let dst = self.vram.add((y * stride + cx0) as usize);
389 core::ptr::copy_nonoverlapping(src, dst, rect_w as usize);
390 }
391 }
392 }
393 }
394 }
395
396 pub fn draw_mouse_direct(&self, mouse: &Mouse) {
398 let prev = self.use_back_buffer.get();
399 self.use_back_buffer.set(false);
400 mouse.draw(self);
401 self.use_back_buffer.set(prev);
402 }
403
404 pub fn flush_with_cursor(&self, mouse: &Mouse) {
407 if !self.use_back_buffer.get() || self.back_buffer.is_null() {
408 self.flush();
409 return;
410 }
411 let cursor_size = Mouse::SIZE as usize;
412 let w = self.width as usize;
413 let h = self.height as usize;
414 let cx = mouse.x as usize;
415 let cy = mouse.y as usize;
416
417 let rows = cursor_size.min(h.saturating_sub(cy));
419 let cols = cursor_size.min(w.saturating_sub(cx));
420 let mut saved = alloc::vec![0u32; cursor_size * cursor_size];
421 unsafe {
422 for row in 0..rows {
423 let src = self.back_buffer.add((cy + row) * w + cx);
424 core::ptr::copy_nonoverlapping(
425 src,
426 saved.as_mut_ptr().add(row * cursor_size),
427 cols,
428 );
429 }
430 }
431
432 mouse.draw(self);
434 self.flush();
435
436 unsafe {
438 for row in 0..rows {
439 let dst = self.back_buffer.add((cy + row) * w + cx);
440 core::ptr::copy_nonoverlapping(saved.as_ptr().add(row * cursor_size), dst, cols);
441 }
442 }
443 }
444
445 pub fn flush_dirty_pageflip(&self, content: Option<(u32, u32, u32, u32)>, mouse: &Mouse) {
450 if !self.vsync.get() || !self.use_back_buffer.get() || self.back_buffer.is_null() {
451 self.flush_with_cursor(mouse);
453 return;
454 }
455 let w = self.width;
456 let h = self.height;
457 let stride = self.pitch / 4;
458 let cs = Mouse::SIZE;
459
460 #[inline]
461 fn union(a: (u32, u32, u32, u32), b: (u32, u32, u32, u32)) -> (u32, u32, u32, u32) {
462 (a.0.min(b.0), a.1.min(b.1), a.2.max(b.2), a.3.max(b.3))
463 }
464
465 #[inline]
466 fn cursor_rect(x: u32, y: u32, cs: u32, w: u32, h: u32) -> (u32, u32, u32, u32) {
467 (x.min(w), y.min(h), (x + cs).min(w), (y + cs).min(h))
468 }
469
470 let page = self.page.get(); let c_new = cursor_rect(mouse.x, mouse.y, cs, w, h);
473 let old_c = self.page_cursor.get()[page as usize];
475 let c_old = cursor_rect(old_c.0, old_c.1, cs, w, h);
476
477 let content_now = content.map(|c| (c.0.min(w), c.1.min(h), c.2.min(w), c.3.min(h)));
479
480 let region = if self.vsync_init_count.get() > 0 {
490 self.vsync_init_count.set(self.vsync_init_count.get() - 1);
491 (0, 0, w, h)
492 } else {
493 let content_region = match (content_now, self.prev_content_region.get()) {
495 (Some(a), Some(b)) => Some(union(a, b)),
496 (Some(a), None) => Some(a),
497 (None, Some(b)) => Some(b),
498 (None, None) => None,
499 };
500 let mut r = union(c_new, c_old);
501 if let Some(cr) = content_region {
502 r = union(r, cr);
503 }
504 r
505 };
506
507 let x0 = region.0.min(w);
508 let y0 = region.1.min(h);
509 let x1 = region.2.min(w).max(x0);
510 let y1 = region.3.min(h).max(y0);
511
512 let cx = mouse.x as usize;
514 let cy = mouse.y as usize;
515 let wsz = w as usize;
516 let hsz = h as usize;
517 let rows = (cs as usize).min(hsz.saturating_sub(cy));
518 let cols = (cs as usize).min(wsz.saturating_sub(cx));
519 let mut saved = alloc::vec![0u32; (cs * cs) as usize];
520 unsafe {
521 for row in 0..rows {
522 let src = self.back_buffer.add((cy + row) * wsz + cx);
523 core::ptr::copy_nonoverlapping(
524 src,
525 saved.as_mut_ptr().add(row * cs as usize),
526 cols,
527 );
528 }
529 }
530 mouse.draw(self); if x1 > x0 && y1 > y0 {
534 let rect_w = x1 - x0;
535 let rect_h = y1 - y0;
536 let src_off = y0 * w + x0;
537 let dst_off = page * h * stride + y0 * stride + x0;
538 crate::kernel::dma::dma_memcpy_2d(
539 5,
540 self.back_buffer as u32 + src_off * 4,
541 self.vram as u32 + dst_off * 4,
542 rect_w * 4,
543 rect_h,
544 w * 4,
545 stride * 4,
546 );
547 let _ = crate::kernel::mailbox::set_virtual_offset(0, page * h);
548 let _ = crate::kernel::mailbox::wait_vsync();
549 self.page.set(1 - page);
550 }
551
552 unsafe {
554 for row in 0..rows {
555 let dst = self.back_buffer.add((cy + row) * wsz + cx);
556 core::ptr::copy_nonoverlapping(saved.as_ptr().add(row * cs as usize), dst, cols);
557 }
558 }
559
560 let mut pc = self.page_cursor.get();
562 pc[page as usize] = (mouse.x, mouse.y);
563 self.page_cursor.set(pc);
564 self.prev_content_region.set(content_now);
565 }
566
567 pub fn set_clip(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
568 self.clip.set(Some((x0, y0, x1, y1)));
569 }
570
571 pub fn clip_rect(&self) -> Option<(u32, u32, u32, u32)> {
573 self.clip.get()
574 }
575
576 pub fn clear_clip(&self) {
577 self.clip.set(None);
578 }
579
580 pub fn push_clip(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
583 let depth = self.clip_depth.get();
584 if depth < 4 {
585 let mut stack = self.clip_stack.get();
586 stack[depth] = self.clip.get();
587 self.clip_stack.set(stack);
588 self.clip_depth.set(depth + 1);
589 }
590 let new_clip = if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
591 (cx0.max(x0), cy0.max(y0), cx1.min(x1), cy1.min(y1))
592 } else {
593 (x0, y0, x1, y1)
594 };
595 self.clip.set(Some(new_clip));
596 }
597
598 pub fn pop_clip(&self) {
600 let depth = self.clip_depth.get();
601 if depth > 0 {
602 let stack = self.clip_stack.get();
603 self.clip.set(stack[depth - 1]);
604 self.clip_depth.set(depth - 1);
605 } else {
606 self.clip.set(None);
607 }
608 }
609
610 pub fn clear(&self, color: Color) {
611 if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
614 self.boxfill(cx0, cy0, cx1, cy1, color);
615 return;
616 }
617
618 let (target_ptr, _w, h) = self.resolve_target();
619
620 let stride = self.resolve_stride();
624 let color_val = color.to_hardware_format();
625 crate::kernel::dma::dma_fill_rect(
626 5, &color_val as *const _ as u32,
628 target_ptr as u32,
629 stride,
630 h,
631 stride * 4,
632 );
633 }
634
635 pub fn draw_pixel(&self, x: u32, y: u32, color: Color) {
640 if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
641 if x < cx0 || x >= cx1 || y < cy0 || y >= cy1 {
642 return;
643 }
644 }
645
646 let a = (color.0 >> 24) & 0xFF;
655 let color = if a == 0 || a == 0xFF {
656 color
657 } else {
658 Color(crate::os_lib::web_engine::alpha_blend::blend_argb_over(
659 color.0,
660 self.read_pixel(x, y).0 | 0xFF00_0000,
661 ))
662 };
663
664 let (target_ptr, w, h) = self.resolve_target();
665 if x < w && y < h {
668 let stride = self.resolve_stride();
669 unsafe {
670 let ptr = target_ptr.add((y * stride + x) as usize);
671 core::ptr::write_volatile(ptr, color.to_hardware_format());
672 }
673 }
674 }
675
676 pub fn read_pixel(&self, x: u32, y: u32) -> Color {
677 let (target_ptr, w, h) = self.resolve_target();
678 if x < w && y < h {
679 let stride = self.resolve_stride();
680 unsafe {
681 let ptr = target_ptr.add((y * stride + x) as usize);
682 Color::from_hardware_format(core::ptr::read_volatile(ptr))
683 }
684 } else {
685 Color::BLACK
686 }
687 }
688
689 pub fn draw_pixel_alpha(&self, x: u32, y: u32, color: Color, alpha: u8) {
697 if alpha == 0 {
698 return;
699 }
700 if alpha == 255 {
701 self.draw_pixel(x, y, color);
702 return;
703 }
704
705 let bg = self.read_pixel(x, y);
706 let a = alpha as u32;
707 let inv_a = 255 - a;
708
709 let fg_r = (color.0 >> 16) & 0xFF;
710 let fg_g = (color.0 >> 8) & 0xFF;
711 let fg_b = color.0 & 0xFF;
712
713 let bg_r = (bg.0 >> 16) & 0xFF;
714 let bg_g = (bg.0 >> 8) & 0xFF;
715 let bg_b = bg.0 & 0xFF;
716
717 let r = ((fg_r * a + bg_r * inv_a) / 255) & 0xFF;
718 let g = ((fg_g * a + bg_g * inv_a) / 255) & 0xFF;
719 let b = ((fg_b * a + bg_b * inv_a) / 255) & 0xFF;
720
721 let blended = 0xFF000000 | (r << 16) | (g << 8) | b;
722 self.draw_pixel(x, y, Color(blended));
723 }
724
725 pub fn draw_string_vector(&self, x: u32, y: u32, s: &str, color: Color, size_px: u32) {
727 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
728 if let Some(ref mut font) = *font_lock {
729 let (_, w, h) = self.resolve_target();
730 let mut cur_x = x as i32;
731 for c in s.chars() {
732 if let Some(glyph) = font.get_glyph(c, size_px) {
733 let gx = cur_x + glyph.x_offset;
734 let gy = y as i32 + glyph.y_offset + (size_px as i32 * 85 / 100);
736
737 for row in 0..glyph.height {
738 let py = gy + row as i32;
739 if py < 0 || py >= h as i32 {
740 continue;
741 }
742 for col in 0..glyph.width {
743 let px = gx + col as i32;
744 if px < 0 || px >= w as i32 {
745 continue;
746 }
747
748 let alpha = glyph.data[(row * glyph.width + col) as usize];
749 if alpha > 0 {
750 self.draw_pixel_alpha(px as u32, py as u32, color, alpha);
751 }
752 }
753 }
754
755 cur_x += glyph.advance as i32;
756 }
757 }
758 }
759 }
760
761 pub fn draw_string_vector_bg(
763 &self,
764 x: u32,
765 y: u32,
766 s: &str,
767 color: Color,
768 size_px: u32,
769 bg: Color,
770 ) {
771 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
772 if let Some(ref mut font) = *font_lock {
773 let (_, w, h) = self.resolve_target();
774 let mut cur_x = x as i32;
775 for c in s.chars() {
776 if let Some(glyph) = font.get_color_glyph(c, size_px, color.0, bg.0) {
777 let gx = cur_x + glyph.x_offset;
778 let gy = y as i32 + glyph.y_offset + (size_px as i32 * 85 / 100);
779
780 for row in 0..glyph.height {
781 let py = gy + row as i32;
782 if py < 0 || py >= h as i32 {
783 continue;
784 }
785 for col in 0..glyph.width {
786 let px = gx + col as i32;
787 if px < 0 || px >= w as i32 {
788 continue;
789 }
790 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
791 if (pixel_color >> 24) > 0 {
792 self.draw_pixel(px as u32, py as u32, Color(pixel_color));
794 }
795 }
796 }
797 cur_x += glyph.advance as i32;
798 } else {
799 cur_x += (size_px / 2) as i32;
800 }
801 }
802 }
803 }
804
805 pub fn draw_vscrollbar(
819 &self,
820 track_x: u32,
821 track_y: i32,
822 track_h: i32,
823 content_h: i32,
824 viewport_h: i32,
825 scroll_pos: i32,
826 ) {
827 if content_h <= viewport_h || track_h <= 0 || viewport_h <= 0 {
828 return;
829 }
830 let max_scroll = (content_h - viewport_h).max(1);
831 let thumb_h = ((track_h * viewport_h) / content_h).clamp(24.min(track_h), track_h);
832 let ratio = scroll_pos.clamp(0, max_scroll) as f32 / max_scroll as f32;
833 let thumb_y = track_y + (ratio * (track_h - thumb_h) as f32) as i32;
834 self.boxfill(
835 track_x,
836 thumb_y.max(0) as u32,
837 track_x + 6,
838 (thumb_y + thumb_h).max(0) as u32,
839 Color(crate::kernel::config::get_config().theme.scrollbar_thumb),
840 );
841 }
842
843 pub fn boxfill(&self, x0: u32, y0: u32, x1: u32, y1: u32, color: Color) {
852 let a = (color.0 >> 24) & 0xFF;
857 if a == 0 {
871 return;
872 }
873 if a != 0xFF {
874 let (target_w, target_h) = {
879 let (_, w, h) = self.resolve_target();
880 (w, h)
881 };
882 if let Some((cx0, cy0, cx1, cy1)) = crate::kernel::draw_clip::clip_fill_rect(
883 x0,
884 y0,
885 x1,
886 y1,
887 target_w,
888 target_h,
889 self.clip.get(),
890 ) {
891 for y in cy0..=cy1 {
892 for x in cx0..=cx1 {
893 self.draw_pixel(x, y, color);
894 }
895 }
896 }
897 return;
898 }
899
900 let (target_ptr, w, h) = self.resolve_target();
901 if (color.0 & 0x00FF_FFFF) == 0x00FF_6B6B {
906 static BG_DIAG: core::sync::atomic::AtomicU32 =
907 core::sync::atomic::AtomicU32::new(0);
908 if BG_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 3 {
909 crate::warn!(
910 "[TARGET][BG] hero fill target=0x{:x} w={} h={} rect=({},{})-({},{})",
911 target_ptr as usize, w, h, x0, y0, x1, y1
912 );
913 }
914 }
915 let (cx0, cy0, cx1, cy1) =
922 match crate::kernel::draw_clip::clip_fill_rect(x0, y0, x1, y1, w, h, self.clip.get()) {
923 Some(r) => r,
924 None => return,
925 };
926
927 let rect_w = cx1 - cx0 + 1;
928 let rect_h = cy1 - cy0 + 1;
929 let stride = self.resolve_stride();
930
931 if rect_w * rect_h >= DMA_THRESHOLD_PIXELS {
932 let dst_offset = (cy0 * stride + cx0) * 4;
933 let color_val = color.to_hardware_format();
934 crate::kernel::dma::dma_fill_rect(
935 5,
936 &color_val as *const _ as u32,
937 target_ptr as u32 + dst_offset,
938 rect_w,
939 rect_h,
940 stride * 4,
941 );
942 } else {
943 let c = color.to_hardware_format();
944 for y in cy0..=cy1 {
945 unsafe {
946 let row_ptr = target_ptr.add((y * stride + cx0) as usize);
947 let count = (cx1 - cx0 + 1) as usize;
948 let slice = core::slice::from_raw_parts_mut(row_ptr, count);
949 slice.fill(c);
950 }
951 }
952 }
953 }
954
955 pub fn draw_char(&self, x: u32, y: u32, c: char, color: Color) {
956 let code = c as usize;
957 if code >= 256 {
958 return;
959 }
960 let font = &FONT_DATA[code];
961 let (target_ptr, w, h) = self.resolve_target();
962 let stride = self.resolve_stride();
963 let clip = self.clip.get();
964 let hw_color = color.to_hardware_format();
965
966 for i in 0..16u32 {
967 let py = y + i;
968 if py >= h {
969 continue;
970 }
971 if let Some((_, cy0, _, cy1)) = clip {
972 if py < cy0 || py >= cy1 {
973 continue;
974 }
975 }
976 let d = font[i as usize];
977 if d == 0 {
978 continue;
979 }
980 unsafe {
981 let row_ptr = target_ptr.add((py * stride) as usize);
982 for bit in 0..8u32 {
983 if (d & (0x80 >> bit)) != 0 {
984 let px = x + bit;
985 if px < w {
986 if let Some((cx0, _, cx1, _)) = clip {
987 if px < cx0 || px >= cx1 {
988 continue;
989 }
990 }
991 core::ptr::write_volatile(row_ptr.add(px as usize), hw_color);
992 }
993 }
994 }
995 }
996 }
997 }
998
999 pub fn draw_string(&self, x: u32, y: u32, s: &str, color: Color) {
1000 let mut cur_x = x;
1001 for c in s.chars() {
1002 self.draw_char(cur_x, y, c, color);
1003 cur_x += 8;
1004 }
1005 }
1006
1007 pub fn measure_string_monospace(&self, s: &str) -> u32 {
1008 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1009 if let Some(ref mut font) = *font_lock {
1010 font.get_string_width(s, 16)
1011 } else {
1012 let mut width = 0;
1013 for c in s.chars() {
1014 width += if (c as u32) < 128 { 8 } else { 16 };
1015 }
1016 width
1017 }
1018 }
1019
1020 pub fn draw_string_monospace(&self, x: u32, y: u32, s: &str, color: Color) {
1021 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1022 if let Some(font) = font_lock.as_mut() {
1023 let mut cur_x = x as i32;
1024 let mut cur_y = y as i32;
1025 let (target_ptr, w, h) = self.resolve_target();
1026 let stride = self.resolve_stride();
1027 let clip = self.clip.get();
1028
1029 for c in s.chars() {
1030 if c == '\n' {
1031 cur_x = x as i32;
1032 cur_y += 16;
1033 continue;
1034 }
1035
1036 if let Some(glyph) = font.get_glyph(c, 16) {
1037 let gx = cur_x + glyph.x_offset;
1038 let gy = cur_y + glyph.y_offset + (16 * 14 / 16);
1039
1040 for row in 0..glyph.height {
1041 let py = gy + row as i32;
1042 if py < 0 || py >= h as i32 {
1043 continue;
1044 }
1045 if let Some((_, cy0, _, cy1)) = clip {
1046 if (py as u32) < cy0 || (py as u32) >= cy1 {
1047 continue;
1048 }
1049 }
1050 let row_offset = (py as usize) * (stride as usize);
1051
1052 for col in 0..glyph.width {
1053 let px = gx + col as i32;
1054 if px < 0 || px >= w as i32 {
1055 continue;
1056 }
1057 if let Some((cx0, _, cx1, _)) = clip {
1058 if (px as u32) < cx0 || (px as u32) >= cx1 {
1059 continue;
1060 }
1061 }
1062 let alpha = glyph.data[(row * glyph.width + col) as usize];
1063 if alpha > 0 {
1064 unsafe {
1065 let ptr = target_ptr.add(row_offset + px as usize);
1066 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(ptr)).0 | 0xFF00_0000;
1067 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(
1068 ((alpha as u32) << 24) | (color.0 & 0x00FF_FFFF),
1069 bg_val,
1070 );
1071 core::ptr::write_volatile(ptr, Color(blended).to_hardware_format());
1072 }
1073 }
1074 }
1075 }
1076 cur_x += glyph.advance as i32;
1077 } else {
1078 let char_width = if (c as u32) > 127 { 16 } else { 8 };
1079 cur_x += char_width;
1080 }
1081
1082 if cur_x > w as i32 - 16 {
1083 break;
1084 }
1085 }
1086 } else {
1087 let mut cur_x = x;
1088 let (_, w, _) = self.resolve_target();
1089 for c in s.chars() {
1090 if c == '\n' {
1091 continue;
1092 }
1093 let code = c as u32;
1094 if code < 128 {
1095 self.draw_char(cur_x, y, c, color);
1096 cur_x += 8;
1097 } else {
1098 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1099 self.draw_char_ja_16x16(cur_x, y, bitmap, color);
1100 } else {
1101 self.draw_char_ja_tofu(cur_x, y, color);
1102 }
1103 cur_x += 16;
1104 }
1105 if cur_x >= w.saturating_sub(8) {
1106 break;
1107 }
1108 }
1109 }
1110 }
1111
1112 pub fn draw_string_monospace_bg(&self, x: u32, y: u32, s: &str, color: Color, bg: Color) {
1114 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1115 if let Some(ref mut font) = *font_lock {
1116 let mut cur_x = x as i32;
1117 let mut cur_y = y as i32;
1118 let (_, w, h) = self.resolve_target();
1119
1120 for c in s.chars() {
1121 if c == '\n' {
1122 cur_x = x as i32;
1123 cur_y += 16;
1124 continue;
1125 }
1126
1127 if let Some(glyph) = font.get_color_glyph(c, 16, color.0, bg.0) {
1128 let gx = cur_x + glyph.x_offset;
1129 let gy = cur_y + glyph.y_offset + (16 * 14 / 16);
1130
1131 for row in 0..glyph.height {
1132 let py = gy + row as i32;
1133 if py < 0 || py >= h as i32 {
1134 continue;
1135 }
1136 for col in 0..glyph.width {
1137 let px = gx + col as i32;
1138 if px < 0 || px >= w as i32 {
1139 continue;
1140 }
1141 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
1142 if (pixel_color >> 24) > 0 {
1143 self.draw_pixel(px as u32, py as u32, Color(pixel_color));
1145 }
1146 }
1147 }
1148 cur_x += glyph.advance as i32;
1149 } else {
1150 let char_width = if (c as u32) > 127 { 16 } else { 8 };
1151 cur_x += char_width;
1152 }
1153
1154 if cur_x > w as i32 - 16 {
1155 break;
1156 }
1157 }
1158 } else {
1159 let mut cur_x = x;
1161 let (_, w, _) = self.resolve_target();
1162 for c in s.chars() {
1163 if c == '\n' {
1164 continue;
1165 }
1166 let code = c as u32;
1167 let char_w = if code < 128 { 8u32 } else { 16u32 };
1168 self.boxfill(cur_x, y, cur_x + char_w, y + 16, bg);
1170 if code < 128 {
1171 self.draw_char(cur_x, y, c, color);
1172 cur_x += 8;
1173 } else {
1174 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1175 self.draw_char_ja_16x16(cur_x, y, bitmap, color);
1176 } else {
1177 self.draw_char_ja_tofu(cur_x, y, color);
1178 }
1179 cur_x += 16;
1180 }
1181 if cur_x >= w.saturating_sub(8) {
1182 break;
1183 }
1184 }
1185 }
1186 }
1187
1188 pub fn measure_string_ja(&self, s: &str, size: u32) -> u32 {
1189 let mut width = 0;
1190 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1191 if let Some(ref mut font) = *font_lock {
1192 for c in s.chars() {
1193 if let Some(glyph) = font.get_glyph(c, size) {
1194 width += glyph.advance;
1195 } else {
1196 width += if (c as u32) < 128 { size / 2 } else { size };
1197 }
1198 }
1199 } else {
1200 for c in s.chars() {
1201 width += if (c as u32) < 128 { 8 } else { 16 };
1202 }
1203 }
1204 width
1205 }
1206
1207 pub fn draw_string_ja(&self, x: u32, y: u32, s: &str, color: Color) {
1209 self.draw_string_ja_size(x, y, s, 16, color);
1210 }
1211
1212 pub fn draw_string_ja_size(&self, x: u32, y: u32, s: &str, size: u32, color: Color) {
1213 self.draw_string_ja_size_ext(x, y, s, size, color, false);
1214 }
1215
1216 pub fn draw_string_ja_size_ext(
1217 &self,
1218 x: u32,
1219 y: u32,
1220 s: &str,
1221 size: u32,
1222 color: Color,
1223 italic: bool,
1224 ) {
1225 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1226 if let Some(font) = font_lock.as_mut() {
1227 let mut cur_x = x as i32;
1228 let mut cur_y = y as i32;
1229 let (_, w, h) = self.resolve_target();
1230
1231 for c in s.chars() {
1232 if c == '\n' {
1233 cur_x = x as i32;
1234 cur_y += size as i32;
1235 continue;
1236 }
1237
1238 if let Some(glyph) = font.get_glyph(c, size) {
1239 let gx = cur_x + glyph.x_offset;
1240 let gy = cur_y + glyph.y_offset + (size as i32 * 14 / 16);
1241
1242 for row in 0..glyph.height {
1243 let py = gy + row as i32;
1244 if py < 0 || py >= h as i32 {
1245 continue;
1246 }
1247 for col in 0..glyph.width {
1248 let mut px = gx + col as i32;
1249 if italic {
1250 let shift = ((glyph.height as i32 - 1 - row as i32) * 2) / 7;
1254 px += shift;
1255 }
1256 if px < 0 || px >= w as i32 {
1257 continue;
1258 }
1259 let alpha = glyph.data[(row * glyph.width + col) as usize];
1260 self.draw_pixel_alpha(px as u32, py as u32, color, alpha);
1261 }
1262 }
1263 cur_x += glyph.advance as i32;
1264 } else {
1265 let code = c as u32;
1266 let char_width = if code > 127 {
1267 size as i32
1268 } else {
1269 size as i32 / 2
1270 };
1271 if code < 128 {
1272 let mut cx = cur_x;
1273 if italic {
1274 cx += 2;
1275 }
1276 self.draw_char(cx as u32, cur_y as u32, c, color);
1277 } else {
1278 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1280 let mut cx = cur_x;
1283 if italic {
1284 cx += 3;
1285 }
1286 self.draw_char_ja_16x16(cx as u32, cur_y as u32, bitmap, color);
1287 } else {
1288 self.draw_char_ja_tofu(cur_x as u32, cur_y as u32, color);
1289 }
1290 }
1291 cur_x += char_width;
1292 }
1293
1294 if cur_x > w as i32 - size as i32 {
1295 cur_x = x as i32;
1296 cur_y += size as i32;
1297 if cur_y >= h as i32 {
1298 break;
1299 }
1300 }
1301 }
1302 } else {
1303 let mut cur_x = x;
1304 for c in s.chars() {
1305 let code = c as u32;
1306 let mut cx = cur_x;
1307 if italic {
1308 cx += 2;
1309 }
1310 if code < 128 {
1311 self.draw_char(cx, y, c, color);
1312 cur_x += size / 2;
1313 } else {
1314 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1315 self.draw_char_ja_16x16(cx, y, bitmap, color);
1316 } else {
1317 self.draw_char_ja_tofu(cx, y, color);
1318 }
1319 cur_x += size;
1320 }
1321 }
1322 }
1323 }
1324
1325 pub fn draw_string_ja_size_bg(
1326 &self,
1327 x: u32,
1328 y: u32,
1329 s: &str,
1330 size: u32,
1331 color: Color,
1332 bg: Color,
1333 ) {
1334 self.draw_string_ja_size_ext_bg(x, y, s, size, color, false, bg);
1335 }
1336
1337 pub fn draw_string_ja_size_ext_bg(
1338 &self,
1339 x: u32,
1340 y: u32,
1341 s: &str,
1342 size: u32,
1343 color: Color,
1344 italic: bool,
1345 bg: Color,
1346 ) {
1347 self.draw_string_ja_size_ext_bg_ls(x, y, s, size, color, italic, bg, 0);
1348 }
1349
1350 #[allow(clippy::too_many_arguments)]
1352 pub fn draw_string_ja_size_ext_bg_ls(
1353 &self,
1354 x: u32,
1355 y: u32,
1356 s: &str,
1357 size: u32,
1358 color: Color,
1359 italic: bool,
1360 bg: Color,
1361 ls: i32,
1362 ) {
1363 self.draw_string_ja_size_ext_bg_ls_ws(x, y, s, size, color, italic, bg, ls, 0);
1364 }
1365
1366 #[allow(clippy::too_many_arguments)]
1368 #[allow(clippy::too_many_arguments)]
1390 pub fn draw_string_family(
1391 &self,
1392 x: u32,
1393 y: u32,
1394 s: &str,
1395 size: u32,
1396 color: Color,
1397 italic: bool,
1398 bg: Color,
1399 ls: i32,
1400 ws: i32,
1401 family_list: &str,
1402 bold: bool,
1403 ) {
1404 if !family_list.is_empty() {
1405 let drawn = crate::kernel::vector_font::with_family_font(s, family_list, bold, |font| {
1409 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1410 });
1411 if drawn.is_some() {
1412 return;
1413 }
1414 if !family_list.is_empty() {
1418 static MISS: spin::Mutex<alloc::collections::BTreeSet<alloc::string::String>> =
1419 spin::Mutex::new(alloc::collections::BTreeSet::new());
1420 let mut m = MISS.lock();
1421 if m.insert(alloc::string::String::from(family_list)) {
1422 crate::warn!("[FONT] 該当フォント無しで既定へ family={:?}", family_list);
1423 }
1424 }
1425 }
1426 self.draw_string_ja_size_ext_bg_ls_ws(x, y, s, size, color, italic, bg, ls, ws);
1427 }
1428
1429 #[allow(clippy::too_many_arguments)]
1430 pub fn draw_string_ja_size_ext_bg_ls_ws(
1431 &self,
1432 x: u32,
1433 y: u32,
1434 s: &str,
1435 size: u32,
1436 color: Color,
1437 italic: bool,
1438 bg: Color,
1439 ls: i32,
1440 ws: i32,
1441 ) {
1442 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1443 if let Some(font) = font_lock.as_mut() {
1444 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1445 }
1446 }
1447
1448 #[allow(clippy::too_many_arguments)]
1450 fn draw_glyphs_with_font(
1451 &self,
1452 font: &mut crate::kernel::vector_font::VectorFont,
1453 x: u32,
1454 y: u32,
1455 s: &str,
1456 size: u32,
1457 color: Color,
1458 italic: bool,
1459 bg: Color,
1460 ls: i32,
1461 ws: i32,
1462 ) {
1463 let font_opt: Option<&mut crate::kernel::vector_font::VectorFont> = Some(font);
1471 if let Some(font) = font_opt {
1472 let mut cur_x = x as i32;
1473 let mut cur_y = y as i32;
1474 let (target_ptr, w, h) = self.resolve_target();
1475 let stride = self.resolve_stride();
1476 let clip = self.clip.get();
1477
1478 for c in s.chars() {
1479 if c == '\n' {
1480 cur_x = x as i32;
1481 cur_y += size as i32;
1482 continue;
1483 }
1484
1485 if size >= 40 {
1487 static GLYPH_DIAG: core::sync::atomic::AtomicU32 =
1488 core::sync::atomic::AtomicU32::new(0);
1489 let n = GLYPH_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1490 if n < 6 {
1491 let g = font.get_color_glyph(c, size, color.0, bg.0);
1492 match g {
1493 Some(gl) => crate::warn!(
1494 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} cur=({},{}) gw={} gh={} xoff={} yoff={} adv={} px0=0x{:08x}",
1495 c, size, w, h, cur_x, cur_y,
1496 gl.width, gl.height, gl.x_offset, gl.y_offset, gl.advance,
1497 gl.data.first().copied().unwrap_or(0)
1498 ),
1499 None => crate::warn!(
1500 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} -> NO GLYPH (fallback path)",
1501 c, size, w, h
1502 ),
1503 }
1504 }
1505 }
1506
1507 if let Some(glyph) = font.get_color_glyph(c, size, color.0, bg.0) {
1508 let gx = cur_x + glyph.x_offset;
1509 let gy = cur_y + glyph.y_offset + (size as i32 * 14 / 16);
1510
1511 let mut written = 0u32;
1512
1513 for row in 0..glyph.height {
1514 let py = gy + row as i32;
1515 if py < 0 || py >= h as i32 {
1516 continue;
1517 }
1518 if let Some((_, cy0, _, cy1)) = clip {
1519 if (py as u32) < cy0 || (py as u32) >= cy1 {
1520 continue;
1521 }
1522 }
1523 let row_offset = (py as usize) * (stride as usize);
1524
1525 for col in 0..glyph.width {
1526 let mut px = gx + col as i32;
1527 if italic {
1528 let shift = ((glyph.height as i32 - 1 - row as i32) * 2) / 7;
1529 px += shift;
1530 }
1531 if px < 0 || px >= w as i32 {
1532 continue;
1533 }
1534 if let Some((cx0, _, cx1, _)) = clip {
1535 if (px as u32) < cx0 || (px as u32) >= cx1 {
1536 continue;
1537 }
1538 }
1539 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
1540 let a = (pixel_color >> 24) & 0xFF;
1541 if a > 0 {
1542 unsafe {
1543 let ptr = target_ptr.add(row_offset + px as usize);
1544 if a == 0xFF {
1545 core::ptr::write_volatile(ptr, Color(pixel_color).to_hardware_format());
1546 } else {
1547 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(ptr)).0 | 0xFF00_0000;
1548 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(pixel_color, bg_val);
1549 core::ptr::write_volatile(ptr, Color(blended).to_hardware_format());
1550 }
1551 }
1552 written += 1;
1553 }
1554 }
1555 }
1556 if size >= 40 {
1557 static PX_DIAG: core::sync::atomic::AtomicU32 =
1558 core::sync::atomic::AtomicU32::new(0);
1559 HERO_TEXT_FRAME.store(
1562 FRAME_NO.load(core::sync::atomic::Ordering::Relaxed),
1563 core::sync::atomic::Ordering::Relaxed,
1564 );
1565 if PX_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 4 {
1566 let (tp, tw, th) = self.resolve_target();
1567 crate::warn!(
1571 "[TARGET][TEXT] c={:?} target=0x{:x} w={} h={} calls={} gx={} gy={} clip={:?} rt=0x{:x} bb=0x{:x} vram=0x{:x} use_bb={}",
1572 c, tp as usize, tw, th, written, gx, gy, self.clip.get(),
1573 self.render_target.get() as usize,
1574 self.back_buffer as usize,
1575 self.vram as usize,
1576 self.use_back_buffer.get()
1577 );
1578 }
1579 }
1580 cur_x += glyph.advance as i32 + ls + if c == ' ' { ws } else { 0 };
1581 } else {
1582 let code = c as u32;
1583 let char_width = if code > 127 {
1584 size as i32
1585 } else {
1586 size as i32 / 2
1587 };
1588 if code < 128 {
1589 let mut cx = cur_x;
1590 if italic {
1591 cx += 2;
1592 }
1593 self.boxfill(
1595 cx as u32,
1596 cur_y as u32,
1597 cx as u32 + (size / 2),
1598 cur_y as u32 + size,
1599 bg,
1600 );
1601 self.draw_char(cx as u32, cur_y as u32, c, color);
1602 } else {
1603 let mut cx = cur_x;
1605 if italic {
1606 cx += 3;
1607 }
1608 self.boxfill(
1609 cx as u32,
1610 cur_y as u32,
1611 cx as u32 + size,
1612 cur_y as u32 + size,
1613 bg,
1614 );
1615 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1616 self.draw_char_ja_16x16(cx as u32, cur_y as u32, bitmap, color);
1617 } else {
1618 self.draw_char_ja_tofu(cx as u32, cur_y as u32, color);
1619 }
1620 }
1621 cur_x += char_width + ls + if c == ' ' { ws } else { 0 };
1622 }
1623
1624 if cur_x > w as i32 - size as i32 {
1625 cur_x = x as i32;
1626 cur_y += size as i32;
1627 if cur_y >= h as i32 {
1628 break;
1629 }
1630 }
1631 }
1632 } else {
1633 let mut cur_x = x;
1635 let (_, _w, _) = self.resolve_target();
1636 for c in s.chars() {
1637 let code = c as u32;
1638 let mut cx = cur_x;
1639 if italic {
1640 cx += 2;
1641 }
1642 let char_w = if code < 128 { size / 2 } else { size };
1643 self.boxfill(cx, y, cx + char_w, y + size, bg);
1644 if code < 128 {
1645 self.draw_char(cx, y, c, color);
1646 cur_x = (cur_x as i32 + size as i32 / 2 + ls).max(0) as u32;
1647 } else {
1648 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1649 self.draw_char_ja_16x16(cx, y, bitmap, color);
1650 } else {
1651 self.draw_char_ja_tofu(cx, y, color);
1652 }
1653 cur_x = (cur_x as i32 + size as i32 + ls).max(0) as u32;
1654 }
1655 }
1656 }
1657 }
1658
1659 pub fn draw_text_emphasis(
1666 &self,
1667 x: u32,
1668 y: u32,
1669 s: &str,
1670 size: u32,
1671 mark: &str,
1672 color: Color,
1673 ls: i32,
1674 ws: i32,
1675 ) {
1676 if mark.is_empty() {
1677 return;
1678 }
1679 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1680 let Some(font) = font_lock.as_mut() else {
1681 return;
1682 };
1683 let mut cur_x = x as i32;
1684 let mut cur_y = y as i32;
1685 let (_, w, h) = self.resolve_target();
1686 let r = ((size as i32) / 6).max(1);
1688 for c in s.chars() {
1689 if c == '\n' {
1690 cur_x = x as i32;
1691 cur_y += size as i32;
1692 continue;
1693 }
1694 let advance = if let Some(glyph) = font.get_color_glyph(c, size, color.0, color.0) {
1696 glyph.advance as i32
1697 } else {
1698 let code = c as u32;
1699 if code > 127 {
1700 size as i32
1701 } else {
1702 size as i32 / 2
1703 }
1704 };
1705 if !c.is_whitespace() {
1706 let cx = cur_x + advance / 2;
1707 let cy = cur_y - r * 3;
1708 if cx >= 0 && cy >= 0 && (cx as u32) < w && (cy as u32) < h {
1709 match mark {
1710 "circle" => self.draw_circle_outline(cx as u32, cy as u32, r as u32, color),
1711 "triangle" => self.draw_triangle_fill(cx as u32, cy as u32, r as u32, color),
1712 "sesame" => {
1713 self.draw_circle_fill((cx - r) as u32, cy as u32, (r / 2).max(1) as u32, color);
1714 self.draw_circle_fill((cx + r) as u32, cy as u32, (r / 2).max(1) as u32, color);
1715 }
1716 _ => self.draw_circle_fill(cx as u32, cy as u32, r as u32, color),
1719 }
1720 }
1721 }
1722 cur_x += advance + ls + if c == ' ' { ws } else { 0 };
1723 }
1724 }
1725
1726 fn draw_circle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
1728 let r_i = r as i32;
1729 for dy in -r_i..=r_i {
1730 for dx in -r_i..=r_i {
1731 if dx * dx + dy * dy <= r_i * r_i {
1732 let px = cx as i32 + dx;
1733 let py = cy as i32 + dy;
1734 if px >= 0 && py >= 0 {
1735 self.draw_pixel(px as u32, py as u32, color);
1736 }
1737 }
1738 }
1739 }
1740 }
1741
1742 fn draw_circle_outline(&self, cx: u32, cy: u32, r: u32, color: Color) {
1744 let r_i = r as i32;
1745 let r_inner = (r_i - 1).max(0);
1746 for dy in -r_i..=r_i {
1747 for dx in -r_i..=r_i {
1748 let d2 = dx * dx + dy * dy;
1749 if d2 <= r_i * r_i && d2 >= r_inner * r_inner {
1750 let px = cx as i32 + dx;
1751 let py = cy as i32 + dy;
1752 if px >= 0 && py >= 0 {
1753 self.draw_pixel(px as u32, py as u32, color);
1754 }
1755 }
1756 }
1757 }
1758 }
1759
1760 fn draw_triangle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
1762 let r_i = r as i32;
1763 for dy in 0..=(r_i * 2) {
1764 let py = cy as i32 - r_i + dy;
1765 if py < 0 {
1766 continue;
1767 }
1768 let half_w = (dy * r_i) / (r_i * 2).max(1);
1769 for dx in -half_w..=half_w {
1770 let px = cx as i32 + dx;
1771 if px >= 0 {
1772 self.draw_pixel(px as u32, py as u32, color);
1773 }
1774 }
1775 }
1776 }
1777
1778 fn draw_char_ja_16x16(&self, x: u32, y: u32, bitmap: &[u8; 8], color: Color) {
1780 for row in 0..8 {
1781 let byte = bitmap[row];
1782 for col in 0..8 {
1783 if (byte & (0x80 >> col)) != 0 {
1784 let px = x + col as u32 * 2;
1785 let py = y + row as u32 * 2;
1786 self.draw_pixel(px, py, color);
1787 self.draw_pixel(px + 1, py, color);
1788 self.draw_pixel(px, py + 1, color);
1789 self.draw_pixel(px + 1, py + 1, color);
1790 }
1791 }
1792 }
1793 }
1794
1795 fn draw_char_ja_tofu(&self, x: u32, y: u32, color: Color) {
1797 for dy in 0..16 {
1798 for dx in 0..16 {
1799 if dx == 0 || dx == 15 || dy == 0 || dy == 15 {
1800 self.draw_pixel(x + dx, y + dy, color);
1801 }
1802 }
1803 }
1804 }
1805
1806 pub fn draw_svg_icon(&self, x: u32, y: u32, svg_data: &[u8], size: u32, tint: Color) {
1812 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(svg_data, size) {
1813 for row in 0..size {
1814 for col in 0..size {
1815 let alpha = alpha_mask[(row * size + col) as usize];
1816 if alpha > 0 {
1817 self.draw_pixel_alpha(x + col, y + row, tint, alpha);
1818 }
1819 }
1820 }
1821 }
1822 }
1823
1824 pub fn draw_bmp(
1826 &self,
1827 x: u32,
1828 y: u32,
1829 target_width: u32,
1830 target_height: u32,
1831 bmp_data: &[u8],
1832 ) -> Result<(), &'static str> {
1833 if bmp_data.len() < 54 {
1834 return Err("Data too short");
1835 }
1836 if &bmp_data[0..2] != b"BM" {
1837 return Err("Not a BMP file");
1838 }
1839
1840 let off_bits =
1841 u32::from_le_bytes(bmp_data[10..14].try_into().map_err(|_| "Invalid offset")?) as usize;
1842 let width = i32::from_le_bytes(bmp_data[18..22].try_into().map_err(|_| "Invalid width")?);
1843 let height = i32::from_le_bytes(bmp_data[22..26].try_into().map_err(|_| "Invalid height")?);
1844 let bit_count = u16::from_le_bytes(
1845 bmp_data[28..30]
1846 .try_into()
1847 .map_err(|_| "Invalid bitcount")?,
1848 );
1849 let compression = u32::from_le_bytes(
1850 bmp_data[30..34]
1851 .try_into()
1852 .map_err(|_| "Invalid compression")?,
1853 );
1854
1855 if compression != 0 {
1856 return Err("Only uncompressed BMP is supported");
1857 }
1858 if bit_count != 24 && bit_count != 32 {
1859 return Err("Only 24-bit or 32-bit BMP is supported");
1860 }
1861
1862 let abs_height = height.unsigned_abs();
1863 let is_bottom_up = height > 0;
1864 let bytes_per_pixel = (bit_count / 8) as usize;
1865 let row_stride = (width as usize * bytes_per_pixel).div_ceil(4) * 4;
1866
1867 if target_width == 0 || target_height == 0 {
1868 return Ok(());
1869 }
1870
1871 for row in 0..target_height {
1872 let src_row_idx = (row * abs_height) / target_height;
1873 let src_row = if is_bottom_up {
1874 abs_height - 1 - src_row_idx
1875 } else {
1876 src_row_idx
1877 };
1878 let row_offset = off_bits + (src_row as usize * row_stride);
1879 if row_offset + (width as usize * bytes_per_pixel) > bmp_data.len() {
1880 return Err("BMP pixel data out of bounds");
1881 }
1882 for col in 0..target_width {
1883 let src_col = (col * width as u32) / target_width;
1884 let pixel_offset = row_offset + (src_col as usize * bytes_per_pixel);
1885 let b = bmp_data[pixel_offset];
1886 let g = bmp_data[pixel_offset + 1];
1887 let r = bmp_data[pixel_offset + 2];
1888 let a = if bytes_per_pixel == 4 {
1889 bmp_data[pixel_offset + 3]
1890 } else {
1891 255
1892 };
1893
1894 let color = Color(0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32));
1895 self.draw_pixel_alpha(x + col, y + row, color, a);
1896 }
1897 }
1898 Ok(())
1899 }
1900
1901 pub fn draw_image(
1903 &self,
1904 x: u32,
1905 y: u32,
1906 target_width: u32,
1907 target_height: u32,
1908 data: &[u8],
1909 ) -> Result<(), &'static str> {
1910 if target_width == 0 || target_height == 0 {
1911 return Ok(());
1912 }
1913
1914 if data.len() > 8 && &data[0..8] == b"\x89PNG\r\n\x1a\n" {
1915 let mut decoder = zune_png::PngDecoder::new(data);
1916 match decoder.decode_raw() {
1917 Ok(pixels) => {
1918 let info = decoder.get_info().ok_or("Failed to get PNG info")?;
1919 let width = info.width as u32;
1920 let height = info.height as u32;
1921 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
1922
1923 for row in 0..target_height {
1924 let src_row = (row * height) / target_height;
1925 for col in 0..target_width {
1926 let src_col = (col * width) / target_width;
1927 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
1928 if idx + bytes_per_pixel <= pixels.len() {
1929 let (r, g, b, a) = match bytes_per_pixel {
1930 1 => (pixels[idx], pixels[idx], pixels[idx], 255),
1931 2 => (pixels[idx], pixels[idx], pixels[idx], pixels[idx + 1]),
1932 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2], 255),
1933 4 => (
1934 pixels[idx],
1935 pixels[idx + 1],
1936 pixels[idx + 2],
1937 pixels[idx + 3],
1938 ),
1939 _ => (0, 0, 0, 255),
1940 };
1941 let color = Color(
1942 0xFF000000
1943 | ((r as u32) << 16)
1944 | ((g as u32) << 8)
1945 | (b as u32),
1946 );
1947 self.draw_pixel_alpha(x + col, y + row, color, a);
1948 }
1949 }
1950 }
1951 Ok(())
1952 }
1953 Err(_) => Err("Failed to decode PNG"),
1954 }
1955 } else if data.len() > 2 && data[0] == 0xFF && data[1] == 0xD8 {
1956 let mut decoder = zune_jpeg::JpegDecoder::new(data);
1957 match decoder.decode() {
1958 Ok(pixels) => {
1959 let info = decoder.info().ok_or("Failed to get JPEG info")?;
1960 let width = info.width as u32;
1961 let height = info.height as u32;
1962 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
1963
1964 for row in 0..target_height {
1965 let src_row = (row * height) / target_height;
1966 for col in 0..target_width {
1967 let src_col = (col * width) / target_width;
1968 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
1969 if idx + bytes_per_pixel <= pixels.len() {
1970 let (r, g, b) = match bytes_per_pixel {
1971 1 => (pixels[idx], pixels[idx], pixels[idx]),
1972 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2]),
1973 _ => (0, 0, 0),
1974 };
1975 let color = Color(
1976 0xFF000000
1977 | ((r as u32) << 16)
1978 | ((g as u32) << 8)
1979 | (b as u32),
1980 );
1981 self.draw_pixel_alpha(x + col, y + row, color, 255);
1982 }
1983 }
1984 }
1985 Ok(())
1986 }
1987 Err(_) => Err("Failed to decode JPEG"),
1988 }
1989 } else if data.len() > 2 && &data[0..2] == b"BM" {
1990 self.draw_bmp(x, y, target_width, target_height, data)
1991 } else {
1992 Err("Unsupported image format")
1993 }
1994 }
1995
1996 pub fn draw_gradient_rect(
1999 &self,
2000 x0: u32,
2001 y0: u32,
2002 x1: u32,
2003 y1: u32,
2004 c1: Color,
2005 c2: Color,
2006 vertical: bool,
2007 ) {
2008 if x1 <= x0 || y1 <= y0 {
2009 return;
2010 }
2011 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2012 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2013 let b1 = (c1.0 & 0xFF) as f32;
2014 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2015 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2016 let b2 = (c2.0 & 0xFF) as f32;
2017 if vertical {
2018 let h = (y1 - y0) as f32;
2019 for dy in 0..(y1 - y0) {
2020 let t = dy as f32 / h.max(1.0);
2021 let r = ((1.0 - t) * r1 + t * r2) as u32;
2022 let g = ((1.0 - t) * g1 + t * g2) as u32;
2023 let b = ((1.0 - t) * b1 + t * b2) as u32;
2024 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2025 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2026 }
2027 } else {
2028 let w = (x1 - x0) as f32;
2029 for dx in 0..(x1 - x0) {
2030 let t = dx as f32 / w.max(1.0);
2031 let r = ((1.0 - t) * r1 + t * r2) as u32;
2032 let g = ((1.0 - t) * g1 + t * g2) as u32;
2033 let b = ((1.0 - t) * b1 + t * b2) as u32;
2034 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2035 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2036 }
2037 }
2038 }
2039
2040 pub fn draw_radial_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2043 if x1 <= x0 || y1 <= y0 {
2044 return;
2045 }
2046 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2047 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2048 let b1 = (c1.0 & 0xFF) as f32;
2049 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2050 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2051 let b2 = (c2.0 & 0xFF) as f32;
2052 let w = (x1 - x0) as f32;
2053 let h = (y1 - y0) as f32;
2054 let cx = w / 2.0;
2055 let cy = h / 2.0;
2056 let max_dist = libm::sqrtf(cx * cx + cy * cy).max(1.0);
2057 for dy in 0..(y1 - y0) {
2058 for dx in 0..(x1 - x0) {
2059 let px = dx as f32 - cx;
2060 let py = dy as f32 - cy;
2061 let dist = libm::sqrtf(px * px + py * py);
2062 let t = (dist / max_dist).min(1.0);
2063 let r = ((1.0 - t) * r1 + t * r2) as u32;
2064 let g = ((1.0 - t) * g1 + t * g2) as u32;
2065 let b = ((1.0 - t) * b1 + t * b2) as u32;
2066 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2067 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2068 }
2069 }
2070 }
2071
2072 pub fn draw_conic_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2077 if x1 <= x0 || y1 <= y0 {
2078 return;
2079 }
2080 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2081 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2082 let b1 = (c1.0 & 0xFF) as f32;
2083 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2084 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2085 let b2 = (c2.0 & 0xFF) as f32;
2086 let w = (x1 - x0) as f32;
2087 let h = (y1 - y0) as f32;
2088 let cx = w / 2.0;
2089 let cy = h / 2.0;
2090 const PI: f32 = core::f32::consts::PI;
2091 for dy in 0..(y1 - y0) {
2092 for dx in 0..(x1 - x0) {
2093 let px = dx as f32 - cx;
2094 let py = dy as f32 - cy;
2095 let mut angle = libm::atan2f(px, -py);
2097 if angle < 0.0 {
2098 angle += 2.0 * PI;
2099 }
2100 let t = angle / (2.0 * PI); let r = ((1.0 - t) * r1 + t * r2) as u32;
2102 let g = ((1.0 - t) * g1 + t * g2) as u32;
2103 let b = ((1.0 - t) * b1 + t * b2) as u32;
2104 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2105 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2106 }
2107 }
2108 }
2109
2110 fn sample_repeating_colors(colors: &[Color], t: f32) -> u32 {
2113 let Some(&first) = colors.first() else {
2114 return 0xFF000000;
2115 };
2116 if colors.len() < 2 {
2117 return first.0;
2118 }
2119 let scaled = t * (colors.len() - 1) as f32;
2120 let seg = (scaled as usize).min(colors.len() - 2);
2121 let local_t = scaled - seg as f32;
2122 let c1 = colors[seg];
2123 let c2 = colors[seg + 1];
2124 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2125 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2126 let b1 = (c1.0 & 0xFF) as f32;
2127 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2128 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2129 let b2 = (c2.0 & 0xFF) as f32;
2130 let r = ((1.0 - local_t) * r1 + local_t * r2) as u32;
2131 let g = ((1.0 - local_t) * g1 + local_t * g2) as u32;
2132 let b = ((1.0 - local_t) * b1 + local_t * b2) as u32;
2133 0xFF000000 | (r << 16) | (g << 8) | b
2134 }
2135
2136 pub fn draw_repeating_linear_gradient_rect(
2140 &self,
2141 x0: u32,
2142 y0: u32,
2143 x1: u32,
2144 y1: u32,
2145 colors: &[Color],
2146 cycle_px: i32,
2147 vertical: bool,
2148 ) {
2149 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2150 return;
2151 }
2152 let cycle = cycle_px.max(1) as f32;
2153 if vertical {
2154 for dy in 0..(y1 - y0) {
2155 let t = (dy as f32 % cycle) / cycle;
2156 let c = Self::sample_repeating_colors(colors, t);
2157 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2158 }
2159 } else {
2160 for dx in 0..(x1 - x0) {
2161 let t = (dx as f32 % cycle) / cycle;
2162 let c = Self::sample_repeating_colors(colors, t);
2163 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2164 }
2165 }
2166 }
2167
2168 pub fn draw_repeating_radial_gradient_rect(
2172 &self,
2173 x0: u32,
2174 y0: u32,
2175 x1: u32,
2176 y1: u32,
2177 colors: &[Color],
2178 cycle_px: i32,
2179 ) {
2180 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2181 return;
2182 }
2183 let cycle = cycle_px.max(1) as f32;
2184 let w = (x1 - x0) as f32;
2185 let h = (y1 - y0) as f32;
2186 let cx = w / 2.0;
2187 let cy = h / 2.0;
2188 for dy in 0..(y1 - y0) {
2189 for dx in 0..(x1 - x0) {
2190 let px = dx as f32 - cx;
2191 let py = dy as f32 - cy;
2192 let dist = libm::sqrtf(px * px + py * py);
2193 let t = (dist % cycle) / cycle;
2194 let c = Self::sample_repeating_colors(colors, t);
2195 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2196 }
2197 }
2198 }
2199
2200 pub fn draw_shadow_rect(
2202 &self,
2203 x0: i32,
2204 y0: i32,
2205 x1: i32,
2206 y1: i32,
2207 ox: i32,
2208 oy: i32,
2209 color: Color,
2210 ) {
2211 if x1 <= x0 || y1 <= y0 {
2212 return;
2213 }
2214 let sx0 = (x0 + ox).max(0) as u32;
2215 let sy0 = (y0 + oy).max(0) as u32;
2216 let sx1 = (x1 + ox).max(0) as u32;
2217 let sy1 = (y1 + oy).max(0) as u32;
2218 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2219 let alpha = if alpha == 0 { 80u8 } else { alpha }; if sx1 <= sx0 || sy1 <= sy0 {
2221 return;
2222 }
2223 for y in sy0..sy1 {
2224 for x in sx0..sx1 {
2225 self.draw_pixel_alpha(x, y, Color(color.0 | 0xFF000000), alpha);
2226 }
2227 }
2228 }
2229
2230 #[allow(clippy::too_many_arguments)]
2238 pub fn draw_shadow_rect_rounded(
2239 &self,
2240 x0: i32,
2241 y0: i32,
2242 x1: i32,
2243 y1: i32,
2244 ox: i32,
2245 oy: i32,
2246 blur: i32,
2247 radius: (i32, i32, i32, i32),
2248 color: Color,
2249 ) {
2250 if x1 <= x0 || y1 <= y0 {
2251 return;
2252 }
2253 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2254 let alpha = if alpha == 0 { 80u8 } else { alpha };
2255 let blur = blur.max(0);
2256 let half = (blur + 1) / 2;
2257 let (rx0, ry0, rx1, ry1) = (x0 + ox, y0 + oy, x1 + ox, y1 + oy);
2259 let sx0 = (rx0 - half).max(0);
2260 let sy0 = (ry0 - half).max(0);
2261 let sx1 = (rx1 + half).clamp(0, self.width as i32);
2262 let sy1 = (ry1 + half).clamp(0, self.height as i32);
2263 if sx1 <= sx0 || sy1 <= sy0 {
2264 return;
2265 }
2266 let w = x1 - x0;
2267 let h = y1 - y0;
2268 let has_radius = radius != (0, 0, 0, 0);
2269 let rgb = Color(color.0 | 0xFF000000);
2270
2271 for y in sy0..sy1 {
2272 for x in sx0..sx1 {
2273 let a = if blur == 0 {
2274 if has_radius
2275 && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(
2276 x - rx0,
2277 y - ry0,
2278 w,
2279 h,
2280 radius,
2281 )
2282 {
2283 continue;
2284 }
2285 alpha
2286 } else {
2287 let dx = (rx0 - x).max(x - (rx1 - 1)).max(0) as f32;
2289 let dy = (ry0 - y).max(y - (ry1 - 1)).max(0) as f32;
2290 let d = if dx > 0.0 || dy > 0.0 {
2291 libm::sqrtf(dx * dx + dy * dy)
2292 } else {
2293 -((x - rx0).min(rx1 - 1 - x).min(y - ry0).min(ry1 - 1 - y) as f32)
2294 };
2295 let c = crate::os_lib::web_engine::box_shadow::coverage(d, blur);
2296 if c <= 0.0 {
2297 continue;
2298 }
2299 (alpha as f32 * c) as u8
2300 };
2301 if a > 0 {
2302 self.draw_pixel_alpha(x as u32, y as u32, rgb, a);
2303 }
2304 }
2305 }
2306 }
2307
2308 pub fn draw_inset_shadow_rect(
2310 &self,
2311 x0: i32,
2312 y0: i32,
2313 x1: i32,
2314 y1: i32,
2315 ox: i32,
2316 oy: i32,
2317 blur: i32,
2318 radius: (i32, i32, i32, i32),
2319 color: Color,
2320 ) {
2321 if x1 <= x0 || y1 <= y0 {
2322 return;
2323 }
2324 let ix0 = x0.max(0) as u32;
2325 let iy0 = y0.max(0) as u32;
2326 let ix1 = x1.max(0) as u32;
2327 let iy1 = y1.max(0) as u32;
2328 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2329 let alpha = if alpha == 0 { 90u8 } else { alpha };
2330 let shadow_color = color.0 | 0xFF000000;
2331 let w = x1 - x0;
2332 let h = y1 - y0;
2333 let has_radius = radius != (0, 0, 0, 0);
2334 let shadow_depth = blur.clamp(4, 30);
2335
2336 for y in iy0..iy1 {
2337 let ry = (y as i32) - y0;
2338 for x in ix0..ix1 {
2339 let rx = (x as i32) - x0;
2340 if has_radius && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(rx, ry, w, h, radius) {
2341 continue;
2342 }
2343 let d_left = rx - ox;
2344 let d_top = ry - oy;
2345 let d_right = (w - rx) + ox;
2346 let d_bottom = (h - ry) + oy;
2347 let min_dist = d_left.min(d_top).min(d_right).min(d_bottom);
2348 if min_dist >= 0 && min_dist < shadow_depth {
2349 let factor = 1.0 - (min_dist as f32 / shadow_depth as f32);
2350 let pix_alpha = ((alpha as f32) * factor) as u8;
2351 if pix_alpha > 0 {
2352 self.draw_pixel_alpha(x, y, Color(shadow_color), pix_alpha);
2353 }
2354 }
2355 }
2356 }
2357 }
2358
2359 pub fn draw_rounded_rect_fill(
2360 &self,
2361 x0: i32,
2362 y0: i32,
2363 x1: i32,
2364 y1: i32,
2365 radius: (i32, i32, i32, i32),
2366 color: Color,
2367 ) {
2368 let (tl, tr, br, bl) = radius;
2369 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2370 self.boxfill(x0 as u32, y0 as u32, x1 as u32, y1 as u32, color);
2371 return;
2372 }
2373 let w = x1 - x0;
2374 let h = y1 - y0;
2375
2376 let tl = tl.min(w / 2).min(h / 2).max(0);
2377 let tr = tr.min(w / 2).min(h / 2).max(0);
2378 let br = br.min(w / 2).min(h / 2).max(0);
2379 let bl = bl.min(w / 2).min(h / 2).max(0);
2380
2381 let top_max_r = tl.max(tr);
2388 let bot_max_r = bl.max(br);
2389 if top_max_r + bot_max_r <= h {
2390 self.boxfill(x0 as u32, (y0 + top_max_r) as u32, x1 as u32, (y1 - bot_max_r) as u32, color);
2391 }
2392
2393 if top_max_r > 0 {
2395 self.boxfill((x0 + tl) as u32, y0 as u32, (x1 - tr) as u32, (y0 + top_max_r) as u32, color);
2396 }
2397
2398 if bot_max_r > 0 {
2400 self.boxfill((x0 + bl) as u32, (y1 - bot_max_r) as u32, (x1 - br) as u32, y1 as u32, color);
2401 }
2402
2403 if tl > 0 {
2406 let r2 = tl * tl;
2407 for dy in 0..tl {
2408 for dx in 0..tl {
2409 if dx * dx + dy * dy <= r2 {
2410 self.draw_pixel((x0 + tl - 1 - dx) as u32, (y0 + tl - 1 - dy) as u32, color);
2411 }
2412 }
2413 }
2414 if top_max_r > tl {
2416 self.boxfill(x0 as u32, (y0 + tl) as u32, (x0 + tl) as u32, (y0 + top_max_r) as u32, color);
2417 }
2418 }
2419 if tr > 0 {
2421 let r2 = tr * tr;
2422 for dy in 0..tr {
2423 for dx in 0..tr {
2424 if dx * dx + dy * dy <= r2 {
2425 self.draw_pixel((x1 - tr + dx) as u32, (y0 + tr - 1 - dy) as u32, color);
2426 }
2427 }
2428 }
2429 if top_max_r > tr {
2430 self.boxfill((x1 - tr) as u32, (y0 + tr) as u32, x1 as u32, (y0 + top_max_r) as u32, color);
2431 }
2432 }
2433 if bl > 0 {
2435 let r2 = bl * bl;
2436 for dy in 0..bl {
2437 for dx in 0..bl {
2438 if dx * dx + dy * dy <= r2 {
2439 self.draw_pixel((x0 + bl - 1 - dx) as u32, (y1 - bl + dy) as u32, color);
2440 }
2441 }
2442 }
2443 if bot_max_r > bl {
2444 self.boxfill(x0 as u32, (y1 - bot_max_r) as u32, (x0 + bl) as u32, (y1 - bl) as u32, color);
2445 }
2446 }
2447 if br > 0 {
2449 let r2 = br * br;
2450 for dy in 0..br {
2451 for dx in 0..br {
2452 if dx * dx + dy * dy <= r2 {
2453 self.draw_pixel((x1 - br + dx) as u32, (y1 - br + dy) as u32, color);
2454 }
2455 }
2456 }
2457 if bot_max_r > br {
2458 self.boxfill((x1 - br) as u32, (y1 - bot_max_r) as u32, x1 as u32, (y1 - br) as u32, color);
2459 }
2460 }
2461 }
2462
2463 pub fn draw_rounded_rect_border(
2464 &self,
2465 x0: i32,
2466 y0: i32,
2467 x1: i32,
2468 y1: i32,
2469 radius: (i32, i32, i32, i32),
2470 color: Color,
2471 _border_style: &str,
2472 ) {
2473 if (color.0 >> 24) & 0xFF == 0 {
2483 return;
2484 }
2485 let (tl, tr, br, bl) = radius;
2486 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2487 self.boxfill(x0 as u32, y0 as u32, x1 as u32, (y0 + 1) as u32, color);
2489 self.boxfill(x0 as u32, (y1 - 1) as u32, x1 as u32, y1 as u32, color);
2490 self.boxfill(x0 as u32, y0 as u32, (x0 + 1) as u32, y1 as u32, color);
2491 self.boxfill((x1 - 1) as u32, y0 as u32, x1 as u32, y1 as u32, color);
2492 return;
2493 }
2494 let w = x1 - x0;
2495 let h = y1 - y0;
2496 let tl = tl.min(w / 2).min(h / 2).max(0);
2497 let tr = tr.min(w / 2).min(h / 2).max(0);
2498 let br = br.min(w / 2).min(h / 2).max(0);
2499 let bl = bl.min(w / 2).min(h / 2).max(0);
2500
2501 self.boxfill((x0 + tl) as u32, y0 as u32, (x1 - tr) as u32, (y0 + 1) as u32, color);
2503 self.boxfill((x0 + bl) as u32, (y1 - 1) as u32, (x1 - br) as u32, y1 as u32, color);
2504 self.boxfill(x0 as u32, (y0 + tl) as u32, (x0 + 1) as u32, (y1 - bl) as u32, color);
2505 self.boxfill((x1 - 1) as u32, (y0 + tr) as u32, x1 as u32, (y1 - br) as u32, color);
2506
2507 let draw_arc = |cx: i32, cy: i32, r: i32, quadrant: i32| {
2509 if r <= 0 { return; }
2510 let mut x = r;
2511 let mut y = 0;
2512 let mut err = 0;
2513 while x >= y {
2514 let px1 = cx + x - 1; let py1 = cy + y;
2515 let px2 = cx + y - 1; let py2 = cy + x - 1;
2516 let px3 = cx - x; let py3 = cy + y;
2517 let px4 = cx - y; let py4 = cy + x - 1;
2518 let px5 = cx - x; let py5 = cy - y - 1;
2519 let px6 = cx - y; let py6 = cy - x;
2520 let px7 = cx + x - 1; let py7 = cy - y - 1;
2521 let px8 = cx + y - 1; let py8 = cy - x;
2522
2523 match quadrant {
2524 1 => { self.draw_pixel(px1 as u32, py1 as u32, color);
2526 self.draw_pixel(px2 as u32, py2 as u32, color);
2527 }
2528 2 => { self.draw_pixel(px3 as u32, py3 as u32, color);
2530 self.draw_pixel(px4 as u32, py4 as u32, color);
2531 }
2532 3 => { self.draw_pixel(px5 as u32, py5 as u32, color);
2534 self.draw_pixel(px6 as u32, py6 as u32, color);
2535 }
2536 4 => { self.draw_pixel(px7 as u32, py7 as u32, color);
2538 self.draw_pixel(px8 as u32, py8 as u32, color);
2539 }
2540 _ => {}
2541 }
2542
2543 if err <= 0 {
2544 y += 1;
2545 err += 2 * y + 1;
2546 }
2547 if err > 0 {
2548 x -= 1;
2549 err -= 2 * x + 1;
2550 }
2551 }
2552 };
2553
2554 draw_arc(x0 + tl, y0 + tl, tl, 3);
2555 draw_arc(x1 - tr, y0 + tr, tr, 4);
2556 draw_arc(x1 - br, y1 - br, br, 1);
2557 draw_arc(x0 + bl, y1 - bl, bl, 2);
2558 }
2559
2560
2561}
2562
2563pub struct Mouse {
2565 pub x: u32,
2566 pub y: u32,
2567 pub is_clicked: bool,
2568 pub click_frame: u8,
2569 pub is_text_cursor: bool,
2572}
2573
2574impl Mouse {
2575 pub const SIZE: u32 = 32;
2576
2577 pub const fn new(x: u32, y: u32) -> Self {
2578 Self {
2579 x,
2580 y,
2581 is_clicked: false,
2582 click_frame: 0,
2583 is_text_cursor: false,
2584 }
2585 }
2586
2587 pub fn draw(&self, screen: &Screen) {
2588 let (target_ptr, w, h) = screen.resolve_target();
2589 let stride = screen.resolve_stride();
2590 let cursor_size = Self::SIZE;
2591
2592 let (draw_x, draw_y) = if self.is_clicked {
2594 (self.x + 1, self.y + 1)
2595 } else {
2596 (self.x, self.y)
2597 };
2598
2599 if self.is_text_cursor {
2600 self.draw_text_cursor(screen, draw_x, draw_y);
2601 return;
2602 }
2603
2604 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(ICON_CURSOR_64, cursor_size)
2605 {
2606 let theme = crate::kernel::config::get_config().theme;
2607 let mut c_fill = theme.mouse_cursor;
2608 let c_stroke = theme.mouse_stroke;
2609
2610 if self.is_clicked {
2612 let r = ((c_fill >> 16) & 0xFF) * 8 / 10;
2613 let g = ((c_fill >> 8) & 0xFF) * 8 / 10;
2614 let b = (c_fill & 0xFF) * 8 / 10;
2615 c_fill = 0xFF000000 | (r << 16) | (g << 8) | b;
2616 }
2617
2618 for row in 0..cursor_size {
2619 let py = draw_y + row;
2620 if py >= h {
2621 continue;
2622 }
2623
2624 let max_col = (w.saturating_sub(draw_x)).min(cursor_size) as usize;
2625 if max_col == 0 {
2626 continue;
2627 }
2628
2629 let offset = (py * stride + draw_x) as usize;
2630 unsafe {
2631 let mut row_buf = [0u32; 32];
2632 let dst = target_ptr.add(offset);
2633
2634 let src = if !screen.back_buffer.is_null() {
2636 let src_offset = (py * w + draw_x) as usize;
2637 screen.back_buffer.add(src_offset)
2638 } else {
2639 dst
2640 };
2641
2642 core::ptr::copy_nonoverlapping(src, row_buf.as_mut_ptr(), max_col);
2643
2644 for col in 0..max_col {
2646 let mask_idx = (row * cursor_size + col as u32) as usize;
2647 let alpha = alpha_mask[mask_idx];
2648 if alpha > 0 {
2649 let mut is_edge = false;
2651 if row == 0
2652 || row == cursor_size - 1
2653 || col == 0
2654 || col as u32 == cursor_size - 1
2655 {
2656 is_edge = true;
2657 } else {
2658 'outer: for dy in -1..=1 {
2660 for dx in -1..=1 {
2661 let ny = row as i32 + dy;
2662 let nx = col as i32 + dx;
2663 let n_idx = (ny * cursor_size as i32 + nx) as usize;
2664 if alpha_mask[n_idx] < 80 {
2665 is_edge = true;
2666 break 'outer;
2667 }
2668 }
2669 }
2670 }
2671
2672 let bg = row_buf[col];
2673 let r_bg = (bg >> 16) & 0xFF;
2674 let g_bg = (bg >> 8) & 0xFF;
2675 let b_bg = bg & 0xFF;
2676
2677 let fg_color = if is_edge { c_stroke } else { c_fill };
2679 let r_fg = (fg_color >> 16) & 0xFF;
2680 let g_fg = (fg_color >> 8) & 0xFF;
2681 let b_fg = fg_color & 0xFF;
2682
2683 let a = alpha as u32;
2684 let inv_a = 255 - a;
2685
2686 let r = (r_fg * a + r_bg * inv_a) / 255;
2687 let g = (g_fg * a + g_bg * inv_a) / 255;
2688 let b = (b_fg * a + b_bg * inv_a) / 255;
2689
2690 row_buf[col] = (r << 16) | (g << 8) | b;
2691 }
2692 }
2693
2694 core::ptr::copy_nonoverlapping(row_buf.as_ptr(), dst, max_col);
2696 }
2697 }
2698
2699 if self.is_clicked && self.click_frame > 0 && self.click_frame < 8 {
2701 let spark_color = theme.mouse_cursor; let center_x = draw_x as i32;
2703 let center_y = draw_y as i32;
2704 let r1 = 4 + (self.click_frame as i32) * 2; let r2 = r1 + 4; let dirs = [
2709 (0, -1), (1, -1), (1, 0), (1, 1),
2710 (0, 1), (-1, 1), (-1, 0), (-1, -1)
2711 ];
2712
2713 for &(dx, dy) in dirs.iter() {
2714 for r in r1..=r2 {
2715 let px = center_x + dx * r;
2716 let py = center_y + dy * r;
2717 if px >= 0 && px < w as i32 && py >= 0 && py < h as i32 {
2718 let offset = (py as u32 * stride + px as u32) as usize;
2719 unsafe {
2720 let dst = target_ptr.add(offset);
2721 *dst = spark_color;
2722 }
2723 }
2724 }
2725 }
2726 }
2727 }
2728 }
2729
2730 fn draw_text_cursor(&self, screen: &Screen, draw_x: u32, draw_y: u32) {
2737 let theme = crate::kernel::config::get_config().theme;
2738 let color = Color(theme.mouse_cursor);
2739 let size = Self::SIZE;
2740 let bar_h = size.saturating_sub(4);
2741 let cx = draw_x + size / 2;
2742 let top_y = draw_y + 2;
2743 let bottom_y = top_y + bar_h;
2744 for dx in 0..=(size / 4) {
2746 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, top_y, color);
2747 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, bottom_y, color);
2748 }
2749 for y in top_y..=bottom_y {
2751 screen.draw_pixel(cx, y, color);
2752 }
2753 }
2754
2755 pub fn move_to(&mut self, screen: &Screen, new_x: u32, new_y: u32) {
2757 self.x = new_x.clamp(0, screen.width - 1);
2758 self.y = new_y.clamp(0, screen.height - 1);
2759 }
2760}
2761
2762pub static mut SPLASH_ACTIVE: bool = false;
2766pub static LOGO_PNG: &[u8] = include_bytes!("../../images/os_logo.png");
2767
2768static mut BOOT_LOG_COUNT: usize = 0;
2769static mut BOOT_LOG_BUFFER: [[u8; 128]; 16] = [[0; 128]; 16];
2770static mut BOOT_LOG_LENS: [usize; 16] = [0; 16];
2771
2772static mut CURRENT_LINE_BUFFER: [u8; 256] = [0; 256];
2773static mut CURRENT_LINE_LEN: usize = 0;
2774
2775pub unsafe fn write_splash_char(c: u8) {
2779 if !SPLASH_ACTIVE {
2780 return;
2781 }
2782 if c == b'\n' {
2783 if CURRENT_LINE_LEN > 0 {
2784 if let Ok(s) = core::str::from_utf8(&CURRENT_LINE_BUFFER[..CURRENT_LINE_LEN]) {
2785 add_boot_log(s);
2786 }
2787 CURRENT_LINE_LEN = 0;
2788 }
2789 } else {
2790 if CURRENT_LINE_LEN < 255 {
2791 CURRENT_LINE_BUFFER[CURRENT_LINE_LEN] = c;
2792 CURRENT_LINE_LEN += 1;
2793 }
2794 }
2795}
2796
2797pub unsafe fn write_splash_str(s: &str) {
2801 if !SPLASH_ACTIVE {
2802 return;
2803 }
2804 for &b in s.as_bytes() {
2805 write_splash_char(b);
2806 }
2807}
2808
2809pub unsafe fn add_boot_log(s: &str) {
2813 if !SPLASH_ACTIVE {
2814 return;
2815 }
2816 const MAX_LINE_CHARS: usize = 55;
2817
2818 for line in s.lines() {
2819 let line = line.trim_end();
2820 if line.is_empty() {
2821 continue;
2822 }
2823
2824 let mut chars = line.as_bytes();
2825 while !chars.is_empty() {
2826 let chunk_len = chars.len().min(MAX_LINE_CHARS);
2827 let chunk = &chars[..chunk_len];
2828 chars = &chars[chunk_len..];
2829
2830 if BOOT_LOG_COUNT < 16 {
2831 let idx = BOOT_LOG_COUNT;
2832 BOOT_LOG_BUFFER[idx][..chunk_len].copy_from_slice(chunk);
2833 BOOT_LOG_LENS[idx] = chunk_len;
2834 BOOT_LOG_COUNT += 1;
2835 } else {
2836 for i in 0..15 {
2837 BOOT_LOG_BUFFER[i] = BOOT_LOG_BUFFER[i + 1];
2838 BOOT_LOG_LENS[i] = BOOT_LOG_LENS[i + 1];
2839 }
2840 BOOT_LOG_BUFFER[15][..chunk_len].copy_from_slice(chunk);
2841 BOOT_LOG_LENS[15] = chunk_len;
2842 }
2843 }
2844 }
2845 redraw_splash();
2846}
2847
2848pub unsafe fn draw_splash_logo() {
2852 if !SPLASH_ACTIVE {
2853 return;
2854 }
2855 if let Some(ref screen) = crate::CURRENT_SCREEN {
2856 screen.clear(Color(0xFF000000));
2857 let mut decoder = zune_png::PngDecoder::new(LOGO_PNG);
2858 if decoder.decode_raw().is_ok() {
2859 if let Some(info) = decoder.get_info() {
2860 let img_w = info.width as u32;
2861 let img_h = info.height as u32;
2862 let area_w = 480u32;
2863 let area_h = screen.height;
2864 let target_w = area_w.min(img_w);
2865 let target_h = (target_w * img_h) / img_w;
2866 let target_h = target_h.min(area_h);
2867 let target_w = (target_h * img_w) / img_h;
2868
2869 let x = (area_w - target_w) / 2 + 20;
2870 let y = (screen.height - target_h) / 2;
2871 let _ = screen.draw_image(x, y, target_w, target_h, LOGO_PNG);
2872 }
2873 }
2874 redraw_splash();
2875 }
2876}
2877
2878pub unsafe fn redraw_splash() {
2882 if !SPLASH_ACTIVE {
2883 return;
2884 }
2885 if let Some(ref screen) = crate::CURRENT_SCREEN {
2886 let area_x = 520u32;
2887 let area_y = 20u32;
2888 let area_w = screen.width.saturating_sub(area_x).saturating_sub(20);
2889 let area_h = screen.height.saturating_sub(area_y).saturating_sub(20);
2890 screen.boxfill(
2891 area_x,
2892 area_y,
2893 area_x + area_w,
2894 area_y + area_h,
2895 Color(0xFF000000),
2896 );
2897
2898 let start_y = (screen.height.saturating_sub(40)) as i32;
2899 let line_height = 28i32;
2900
2901 for i in 0..BOOT_LOG_COUNT {
2902 let y = start_y - (BOOT_LOG_COUNT - 1 - i) as i32 * line_height;
2903 if y >= area_y as i32 {
2904 if let Ok(s) = core::str::from_utf8(&BOOT_LOG_BUFFER[i][..BOOT_LOG_LENS[i]]) {
2905 screen.draw_string_monospace(area_x + 10, y as u32, s, Color(0xFFCCCCCC));
2906 }
2907 }
2908 }
2909 }
2910}
2911
2912pub struct SplashWriter;
2913
2914impl core::fmt::Write for SplashWriter {
2915 fn write_str(&mut self, s: &str) -> core::fmt::Result {
2916 unsafe {
2917 write_splash_str(s);
2918 }
2919 Ok(())
2920 }
2921}