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 want: &[u16] = if bold { &[900, 700] } else { &[400] };
1415 let mut fonts = crate::kernel::vector_font::CUSTOM_FONTS.lock();
1416 for fam in family_list.split(',') {
1417 let key = fam
1418 .trim()
1419 .trim_matches('"')
1420 .trim_matches('\'')
1421 .to_lowercase();
1422 if key.is_empty() {
1423 continue;
1424 }
1425 let mut hit: Option<alloc::string::String> = None;
1440 for w in want {
1441 let wk = crate::kernel::vector_font::font_key(&key, *w);
1442 let ok = match fonts.get(&wk) {
1443 Some(f) => crate::kernel::font_select::covers_all(s, |c| {
1444 f.primary_has_glyph(c)
1445 }),
1446 None => false,
1447 };
1448 if ok {
1449 hit = Some(wk);
1450 break;
1451 }
1452 }
1453 if let Some(wk) = hit {
1454 if let Some(font) = fonts.get_mut(&wk) {
1455 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1456 return;
1457 }
1458 }
1459 if let Some(font) = fonts.get_mut(&key) {
1460 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1461 return;
1462 }
1463 }
1464 if !family_list.is_empty() {
1468 static MISS: spin::Mutex<alloc::collections::BTreeSet<alloc::string::String>> =
1469 spin::Mutex::new(alloc::collections::BTreeSet::new());
1470 let mut m = MISS.lock();
1471 if m.insert(alloc::string::String::from(family_list)) {
1472 crate::warn!("[FONT] 該当フォント無しで既定へ family={:?}", family_list);
1473 }
1474 }
1475 }
1476 self.draw_string_ja_size_ext_bg_ls_ws(x, y, s, size, color, italic, bg, ls, ws);
1477 }
1478
1479 #[allow(clippy::too_many_arguments)]
1480 pub fn draw_string_ja_size_ext_bg_ls_ws(
1481 &self,
1482 x: u32,
1483 y: u32,
1484 s: &str,
1485 size: u32,
1486 color: Color,
1487 italic: bool,
1488 bg: Color,
1489 ls: i32,
1490 ws: i32,
1491 ) {
1492 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1493 if let Some(font) = font_lock.as_mut() {
1494 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1495 }
1496 }
1497
1498 #[allow(clippy::too_many_arguments)]
1500 fn draw_glyphs_with_font(
1501 &self,
1502 font: &mut crate::kernel::vector_font::VectorFont,
1503 x: u32,
1504 y: u32,
1505 s: &str,
1506 size: u32,
1507 color: Color,
1508 italic: bool,
1509 bg: Color,
1510 ls: i32,
1511 ws: i32,
1512 ) {
1513 let font_opt: Option<&mut crate::kernel::vector_font::VectorFont> = Some(font);
1521 if let Some(font) = font_opt {
1522 let mut cur_x = x as i32;
1523 let mut cur_y = y as i32;
1524 let (target_ptr, w, h) = self.resolve_target();
1525 let stride = self.resolve_stride();
1526 let clip = self.clip.get();
1527
1528 for c in s.chars() {
1529 if c == '\n' {
1530 cur_x = x as i32;
1531 cur_y += size as i32;
1532 continue;
1533 }
1534
1535 if size >= 40 {
1537 static GLYPH_DIAG: core::sync::atomic::AtomicU32 =
1538 core::sync::atomic::AtomicU32::new(0);
1539 let n = GLYPH_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1540 if n < 6 {
1541 let g = font.get_color_glyph(c, size, color.0, bg.0);
1542 match g {
1543 Some(gl) => crate::warn!(
1544 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} cur=({},{}) gw={} gh={} xoff={} yoff={} adv={} px0=0x{:08x}",
1545 c, size, w, h, cur_x, cur_y,
1546 gl.width, gl.height, gl.x_offset, gl.y_offset, gl.advance,
1547 gl.data.first().copied().unwrap_or(0)
1548 ),
1549 None => crate::warn!(
1550 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} -> NO GLYPH (fallback path)",
1551 c, size, w, h
1552 ),
1553 }
1554 }
1555 }
1556
1557 if let Some(glyph) = font.get_color_glyph(c, size, color.0, bg.0) {
1558 let gx = cur_x + glyph.x_offset;
1559 let gy = cur_y + glyph.y_offset + (size as i32 * 14 / 16);
1560
1561 let mut written = 0u32;
1562
1563 for row in 0..glyph.height {
1564 let py = gy + row as i32;
1565 if py < 0 || py >= h as i32 {
1566 continue;
1567 }
1568 if let Some((_, cy0, _, cy1)) = clip {
1569 if (py as u32) < cy0 || (py as u32) >= cy1 {
1570 continue;
1571 }
1572 }
1573 let row_offset = (py as usize) * (stride as usize);
1574
1575 for col in 0..glyph.width {
1576 let mut px = gx + col as i32;
1577 if italic {
1578 let shift = ((glyph.height as i32 - 1 - row as i32) * 2) / 7;
1579 px += shift;
1580 }
1581 if px < 0 || px >= w as i32 {
1582 continue;
1583 }
1584 if let Some((cx0, _, cx1, _)) = clip {
1585 if (px as u32) < cx0 || (px as u32) >= cx1 {
1586 continue;
1587 }
1588 }
1589 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
1590 let a = (pixel_color >> 24) & 0xFF;
1591 if a > 0 {
1592 unsafe {
1593 let ptr = target_ptr.add(row_offset + px as usize);
1594 if a == 0xFF {
1595 core::ptr::write_volatile(ptr, Color(pixel_color).to_hardware_format());
1596 } else {
1597 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(ptr)).0 | 0xFF00_0000;
1598 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(pixel_color, bg_val);
1599 core::ptr::write_volatile(ptr, Color(blended).to_hardware_format());
1600 }
1601 }
1602 written += 1;
1603 }
1604 }
1605 }
1606 if size >= 40 {
1607 static PX_DIAG: core::sync::atomic::AtomicU32 =
1608 core::sync::atomic::AtomicU32::new(0);
1609 HERO_TEXT_FRAME.store(
1612 FRAME_NO.load(core::sync::atomic::Ordering::Relaxed),
1613 core::sync::atomic::Ordering::Relaxed,
1614 );
1615 if PX_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 4 {
1616 let (tp, tw, th) = self.resolve_target();
1617 crate::warn!(
1621 "[TARGET][TEXT] c={:?} target=0x{:x} w={} h={} calls={} gx={} gy={} clip={:?} rt=0x{:x} bb=0x{:x} vram=0x{:x} use_bb={}",
1622 c, tp as usize, tw, th, written, gx, gy, self.clip.get(),
1623 self.render_target.get() as usize,
1624 self.back_buffer as usize,
1625 self.vram as usize,
1626 self.use_back_buffer.get()
1627 );
1628 }
1629 }
1630 cur_x += glyph.advance as i32 + ls + if c == ' ' { ws } else { 0 };
1631 } else {
1632 let code = c as u32;
1633 let char_width = if code > 127 {
1634 size as i32
1635 } else {
1636 size as i32 / 2
1637 };
1638 if code < 128 {
1639 let mut cx = cur_x;
1640 if italic {
1641 cx += 2;
1642 }
1643 self.boxfill(
1645 cx as u32,
1646 cur_y as u32,
1647 cx as u32 + (size / 2),
1648 cur_y as u32 + size,
1649 bg,
1650 );
1651 self.draw_char(cx as u32, cur_y as u32, c, color);
1652 } else {
1653 let mut cx = cur_x;
1655 if italic {
1656 cx += 3;
1657 }
1658 self.boxfill(
1659 cx as u32,
1660 cur_y as u32,
1661 cx as u32 + size,
1662 cur_y as u32 + size,
1663 bg,
1664 );
1665 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1666 self.draw_char_ja_16x16(cx as u32, cur_y as u32, bitmap, color);
1667 } else {
1668 self.draw_char_ja_tofu(cx as u32, cur_y as u32, color);
1669 }
1670 }
1671 cur_x += char_width + ls + if c == ' ' { ws } else { 0 };
1672 }
1673
1674 if cur_x > w as i32 - size as i32 {
1675 cur_x = x as i32;
1676 cur_y += size as i32;
1677 if cur_y >= h as i32 {
1678 break;
1679 }
1680 }
1681 }
1682 } else {
1683 let mut cur_x = x;
1685 let (_, _w, _) = self.resolve_target();
1686 for c in s.chars() {
1687 let code = c as u32;
1688 let mut cx = cur_x;
1689 if italic {
1690 cx += 2;
1691 }
1692 let char_w = if code < 128 { size / 2 } else { size };
1693 self.boxfill(cx, y, cx + char_w, y + size, bg);
1694 if code < 128 {
1695 self.draw_char(cx, y, c, color);
1696 cur_x = (cur_x as i32 + size as i32 / 2 + ls).max(0) as u32;
1697 } else {
1698 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1699 self.draw_char_ja_16x16(cx, y, bitmap, color);
1700 } else {
1701 self.draw_char_ja_tofu(cx, y, color);
1702 }
1703 cur_x = (cur_x as i32 + size as i32 + ls).max(0) as u32;
1704 }
1705 }
1706 }
1707 }
1708
1709 pub fn draw_text_emphasis(
1716 &self,
1717 x: u32,
1718 y: u32,
1719 s: &str,
1720 size: u32,
1721 mark: &str,
1722 color: Color,
1723 ls: i32,
1724 ws: i32,
1725 ) {
1726 if mark.is_empty() {
1727 return;
1728 }
1729 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1730 let Some(font) = font_lock.as_mut() else {
1731 return;
1732 };
1733 let mut cur_x = x as i32;
1734 let mut cur_y = y as i32;
1735 let (_, w, h) = self.resolve_target();
1736 let r = ((size as i32) / 6).max(1);
1738 for c in s.chars() {
1739 if c == '\n' {
1740 cur_x = x as i32;
1741 cur_y += size as i32;
1742 continue;
1743 }
1744 let advance = if let Some(glyph) = font.get_color_glyph(c, size, color.0, color.0) {
1746 glyph.advance as i32
1747 } else {
1748 let code = c as u32;
1749 if code > 127 {
1750 size as i32
1751 } else {
1752 size as i32 / 2
1753 }
1754 };
1755 if !c.is_whitespace() {
1756 let cx = cur_x + advance / 2;
1757 let cy = cur_y - r * 3;
1758 if cx >= 0 && cy >= 0 && (cx as u32) < w && (cy as u32) < h {
1759 match mark {
1760 "circle" => self.draw_circle_outline(cx as u32, cy as u32, r as u32, color),
1761 "triangle" => self.draw_triangle_fill(cx as u32, cy as u32, r as u32, color),
1762 "sesame" => {
1763 self.draw_circle_fill((cx - r) as u32, cy as u32, (r / 2).max(1) as u32, color);
1764 self.draw_circle_fill((cx + r) as u32, cy as u32, (r / 2).max(1) as u32, color);
1765 }
1766 _ => self.draw_circle_fill(cx as u32, cy as u32, r as u32, color),
1769 }
1770 }
1771 }
1772 cur_x += advance + ls + if c == ' ' { ws } else { 0 };
1773 }
1774 }
1775
1776 fn draw_circle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
1778 let r_i = r as i32;
1779 for dy in -r_i..=r_i {
1780 for dx in -r_i..=r_i {
1781 if dx * dx + dy * dy <= r_i * r_i {
1782 let px = cx as i32 + dx;
1783 let py = cy as i32 + dy;
1784 if px >= 0 && py >= 0 {
1785 self.draw_pixel(px as u32, py as u32, color);
1786 }
1787 }
1788 }
1789 }
1790 }
1791
1792 fn draw_circle_outline(&self, cx: u32, cy: u32, r: u32, color: Color) {
1794 let r_i = r as i32;
1795 let r_inner = (r_i - 1).max(0);
1796 for dy in -r_i..=r_i {
1797 for dx in -r_i..=r_i {
1798 let d2 = dx * dx + dy * dy;
1799 if d2 <= r_i * r_i && d2 >= r_inner * r_inner {
1800 let px = cx as i32 + dx;
1801 let py = cy as i32 + dy;
1802 if px >= 0 && py >= 0 {
1803 self.draw_pixel(px as u32, py as u32, color);
1804 }
1805 }
1806 }
1807 }
1808 }
1809
1810 fn draw_triangle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
1812 let r_i = r as i32;
1813 for dy in 0..=(r_i * 2) {
1814 let py = cy as i32 - r_i + dy;
1815 if py < 0 {
1816 continue;
1817 }
1818 let half_w = (dy * r_i) / (r_i * 2).max(1);
1819 for dx in -half_w..=half_w {
1820 let px = cx as i32 + dx;
1821 if px >= 0 {
1822 self.draw_pixel(px as u32, py as u32, color);
1823 }
1824 }
1825 }
1826 }
1827
1828 fn draw_char_ja_16x16(&self, x: u32, y: u32, bitmap: &[u8; 8], color: Color) {
1830 for row in 0..8 {
1831 let byte = bitmap[row];
1832 for col in 0..8 {
1833 if (byte & (0x80 >> col)) != 0 {
1834 let px = x + col as u32 * 2;
1835 let py = y + row as u32 * 2;
1836 self.draw_pixel(px, py, color);
1837 self.draw_pixel(px + 1, py, color);
1838 self.draw_pixel(px, py + 1, color);
1839 self.draw_pixel(px + 1, py + 1, color);
1840 }
1841 }
1842 }
1843 }
1844
1845 fn draw_char_ja_tofu(&self, x: u32, y: u32, color: Color) {
1847 for dy in 0..16 {
1848 for dx in 0..16 {
1849 if dx == 0 || dx == 15 || dy == 0 || dy == 15 {
1850 self.draw_pixel(x + dx, y + dy, color);
1851 }
1852 }
1853 }
1854 }
1855
1856 pub fn draw_svg_icon(&self, x: u32, y: u32, svg_data: &[u8], size: u32, tint: Color) {
1862 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(svg_data, size) {
1863 for row in 0..size {
1864 for col in 0..size {
1865 let alpha = alpha_mask[(row * size + col) as usize];
1866 if alpha > 0 {
1867 self.draw_pixel_alpha(x + col, y + row, tint, alpha);
1868 }
1869 }
1870 }
1871 }
1872 }
1873
1874 pub fn draw_bmp(
1876 &self,
1877 x: u32,
1878 y: u32,
1879 target_width: u32,
1880 target_height: u32,
1881 bmp_data: &[u8],
1882 ) -> Result<(), &'static str> {
1883 if bmp_data.len() < 54 {
1884 return Err("Data too short");
1885 }
1886 if &bmp_data[0..2] != b"BM" {
1887 return Err("Not a BMP file");
1888 }
1889
1890 let off_bits =
1891 u32::from_le_bytes(bmp_data[10..14].try_into().map_err(|_| "Invalid offset")?) as usize;
1892 let width = i32::from_le_bytes(bmp_data[18..22].try_into().map_err(|_| "Invalid width")?);
1893 let height = i32::from_le_bytes(bmp_data[22..26].try_into().map_err(|_| "Invalid height")?);
1894 let bit_count = u16::from_le_bytes(
1895 bmp_data[28..30]
1896 .try_into()
1897 .map_err(|_| "Invalid bitcount")?,
1898 );
1899 let compression = u32::from_le_bytes(
1900 bmp_data[30..34]
1901 .try_into()
1902 .map_err(|_| "Invalid compression")?,
1903 );
1904
1905 if compression != 0 {
1906 return Err("Only uncompressed BMP is supported");
1907 }
1908 if bit_count != 24 && bit_count != 32 {
1909 return Err("Only 24-bit or 32-bit BMP is supported");
1910 }
1911
1912 let abs_height = height.unsigned_abs();
1913 let is_bottom_up = height > 0;
1914 let bytes_per_pixel = (bit_count / 8) as usize;
1915 let row_stride = (width as usize * bytes_per_pixel).div_ceil(4) * 4;
1916
1917 if target_width == 0 || target_height == 0 {
1918 return Ok(());
1919 }
1920
1921 for row in 0..target_height {
1922 let src_row_idx = (row * abs_height) / target_height;
1923 let src_row = if is_bottom_up {
1924 abs_height - 1 - src_row_idx
1925 } else {
1926 src_row_idx
1927 };
1928 let row_offset = off_bits + (src_row as usize * row_stride);
1929 if row_offset + (width as usize * bytes_per_pixel) > bmp_data.len() {
1930 return Err("BMP pixel data out of bounds");
1931 }
1932 for col in 0..target_width {
1933 let src_col = (col * width as u32) / target_width;
1934 let pixel_offset = row_offset + (src_col as usize * bytes_per_pixel);
1935 let b = bmp_data[pixel_offset];
1936 let g = bmp_data[pixel_offset + 1];
1937 let r = bmp_data[pixel_offset + 2];
1938 let a = if bytes_per_pixel == 4 {
1939 bmp_data[pixel_offset + 3]
1940 } else {
1941 255
1942 };
1943
1944 let color = Color(0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32));
1945 self.draw_pixel_alpha(x + col, y + row, color, a);
1946 }
1947 }
1948 Ok(())
1949 }
1950
1951 pub fn draw_image(
1953 &self,
1954 x: u32,
1955 y: u32,
1956 target_width: u32,
1957 target_height: u32,
1958 data: &[u8],
1959 ) -> Result<(), &'static str> {
1960 if target_width == 0 || target_height == 0 {
1961 return Ok(());
1962 }
1963
1964 if data.len() > 8 && &data[0..8] == b"\x89PNG\r\n\x1a\n" {
1965 let mut decoder = zune_png::PngDecoder::new(data);
1966 match decoder.decode_raw() {
1967 Ok(pixels) => {
1968 let info = decoder.get_info().ok_or("Failed to get PNG info")?;
1969 let width = info.width as u32;
1970 let height = info.height as u32;
1971 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
1972
1973 for row in 0..target_height {
1974 let src_row = (row * height) / target_height;
1975 for col in 0..target_width {
1976 let src_col = (col * width) / target_width;
1977 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
1978 if idx + bytes_per_pixel <= pixels.len() {
1979 let (r, g, b, a) = match bytes_per_pixel {
1980 1 => (pixels[idx], pixels[idx], pixels[idx], 255),
1981 2 => (pixels[idx], pixels[idx], pixels[idx], pixels[idx + 1]),
1982 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2], 255),
1983 4 => (
1984 pixels[idx],
1985 pixels[idx + 1],
1986 pixels[idx + 2],
1987 pixels[idx + 3],
1988 ),
1989 _ => (0, 0, 0, 255),
1990 };
1991 let color = Color(
1992 0xFF000000
1993 | ((r as u32) << 16)
1994 | ((g as u32) << 8)
1995 | (b as u32),
1996 );
1997 self.draw_pixel_alpha(x + col, y + row, color, a);
1998 }
1999 }
2000 }
2001 Ok(())
2002 }
2003 Err(_) => Err("Failed to decode PNG"),
2004 }
2005 } else if data.len() > 2 && data[0] == 0xFF && data[1] == 0xD8 {
2006 let mut decoder = zune_jpeg::JpegDecoder::new(data);
2007 match decoder.decode() {
2008 Ok(pixels) => {
2009 let info = decoder.info().ok_or("Failed to get JPEG info")?;
2010 let width = info.width as u32;
2011 let height = info.height as u32;
2012 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
2013
2014 for row in 0..target_height {
2015 let src_row = (row * height) / target_height;
2016 for col in 0..target_width {
2017 let src_col = (col * width) / target_width;
2018 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
2019 if idx + bytes_per_pixel <= pixels.len() {
2020 let (r, g, b) = match bytes_per_pixel {
2021 1 => (pixels[idx], pixels[idx], pixels[idx]),
2022 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2]),
2023 _ => (0, 0, 0),
2024 };
2025 let color = Color(
2026 0xFF000000
2027 | ((r as u32) << 16)
2028 | ((g as u32) << 8)
2029 | (b as u32),
2030 );
2031 self.draw_pixel_alpha(x + col, y + row, color, 255);
2032 }
2033 }
2034 }
2035 Ok(())
2036 }
2037 Err(_) => Err("Failed to decode JPEG"),
2038 }
2039 } else if data.len() > 2 && &data[0..2] == b"BM" {
2040 self.draw_bmp(x, y, target_width, target_height, data)
2041 } else {
2042 Err("Unsupported image format")
2043 }
2044 }
2045
2046 pub fn draw_gradient_rect(
2049 &self,
2050 x0: u32,
2051 y0: u32,
2052 x1: u32,
2053 y1: u32,
2054 c1: Color,
2055 c2: Color,
2056 vertical: bool,
2057 ) {
2058 if x1 <= x0 || y1 <= y0 {
2059 return;
2060 }
2061 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2062 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2063 let b1 = (c1.0 & 0xFF) as f32;
2064 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2065 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2066 let b2 = (c2.0 & 0xFF) as f32;
2067 if vertical {
2068 let h = (y1 - y0) as f32;
2069 for dy in 0..(y1 - y0) {
2070 let t = dy as f32 / h.max(1.0);
2071 let r = ((1.0 - t) * r1 + t * r2) as u32;
2072 let g = ((1.0 - t) * g1 + t * g2) as u32;
2073 let b = ((1.0 - t) * b1 + t * b2) as u32;
2074 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2075 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2076 }
2077 } else {
2078 let w = (x1 - x0) as f32;
2079 for dx in 0..(x1 - x0) {
2080 let t = dx as f32 / w.max(1.0);
2081 let r = ((1.0 - t) * r1 + t * r2) as u32;
2082 let g = ((1.0 - t) * g1 + t * g2) as u32;
2083 let b = ((1.0 - t) * b1 + t * b2) as u32;
2084 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2085 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2086 }
2087 }
2088 }
2089
2090 pub fn draw_radial_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2093 if x1 <= x0 || y1 <= y0 {
2094 return;
2095 }
2096 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2097 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2098 let b1 = (c1.0 & 0xFF) as f32;
2099 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2100 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2101 let b2 = (c2.0 & 0xFF) as f32;
2102 let w = (x1 - x0) as f32;
2103 let h = (y1 - y0) as f32;
2104 let cx = w / 2.0;
2105 let cy = h / 2.0;
2106 let max_dist = libm::sqrtf(cx * cx + cy * cy).max(1.0);
2107 for dy in 0..(y1 - y0) {
2108 for dx in 0..(x1 - x0) {
2109 let px = dx as f32 - cx;
2110 let py = dy as f32 - cy;
2111 let dist = libm::sqrtf(px * px + py * py);
2112 let t = (dist / max_dist).min(1.0);
2113 let r = ((1.0 - t) * r1 + t * r2) as u32;
2114 let g = ((1.0 - t) * g1 + t * g2) as u32;
2115 let b = ((1.0 - t) * b1 + t * b2) as u32;
2116 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2117 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2118 }
2119 }
2120 }
2121
2122 pub fn draw_conic_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2127 if x1 <= x0 || y1 <= y0 {
2128 return;
2129 }
2130 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2131 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2132 let b1 = (c1.0 & 0xFF) as f32;
2133 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2134 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2135 let b2 = (c2.0 & 0xFF) as f32;
2136 let w = (x1 - x0) as f32;
2137 let h = (y1 - y0) as f32;
2138 let cx = w / 2.0;
2139 let cy = h / 2.0;
2140 const PI: f32 = core::f32::consts::PI;
2141 for dy in 0..(y1 - y0) {
2142 for dx in 0..(x1 - x0) {
2143 let px = dx as f32 - cx;
2144 let py = dy as f32 - cy;
2145 let mut angle = libm::atan2f(px, -py);
2147 if angle < 0.0 {
2148 angle += 2.0 * PI;
2149 }
2150 let t = angle / (2.0 * PI); let r = ((1.0 - t) * r1 + t * r2) as u32;
2152 let g = ((1.0 - t) * g1 + t * g2) as u32;
2153 let b = ((1.0 - t) * b1 + t * b2) as u32;
2154 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2155 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2156 }
2157 }
2158 }
2159
2160 fn sample_repeating_colors(colors: &[Color], t: f32) -> u32 {
2163 let Some(&first) = colors.first() else {
2164 return 0xFF000000;
2165 };
2166 if colors.len() < 2 {
2167 return first.0;
2168 }
2169 let scaled = t * (colors.len() - 1) as f32;
2170 let seg = (scaled as usize).min(colors.len() - 2);
2171 let local_t = scaled - seg as f32;
2172 let c1 = colors[seg];
2173 let c2 = colors[seg + 1];
2174 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2175 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2176 let b1 = (c1.0 & 0xFF) as f32;
2177 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2178 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2179 let b2 = (c2.0 & 0xFF) as f32;
2180 let r = ((1.0 - local_t) * r1 + local_t * r2) as u32;
2181 let g = ((1.0 - local_t) * g1 + local_t * g2) as u32;
2182 let b = ((1.0 - local_t) * b1 + local_t * b2) as u32;
2183 0xFF000000 | (r << 16) | (g << 8) | b
2184 }
2185
2186 pub fn draw_repeating_linear_gradient_rect(
2190 &self,
2191 x0: u32,
2192 y0: u32,
2193 x1: u32,
2194 y1: u32,
2195 colors: &[Color],
2196 cycle_px: i32,
2197 vertical: bool,
2198 ) {
2199 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2200 return;
2201 }
2202 let cycle = cycle_px.max(1) as f32;
2203 if vertical {
2204 for dy in 0..(y1 - y0) {
2205 let t = (dy as f32 % cycle) / cycle;
2206 let c = Self::sample_repeating_colors(colors, t);
2207 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2208 }
2209 } else {
2210 for dx in 0..(x1 - x0) {
2211 let t = (dx as f32 % cycle) / cycle;
2212 let c = Self::sample_repeating_colors(colors, t);
2213 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2214 }
2215 }
2216 }
2217
2218 pub fn draw_repeating_radial_gradient_rect(
2222 &self,
2223 x0: u32,
2224 y0: u32,
2225 x1: u32,
2226 y1: u32,
2227 colors: &[Color],
2228 cycle_px: i32,
2229 ) {
2230 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2231 return;
2232 }
2233 let cycle = cycle_px.max(1) as f32;
2234 let w = (x1 - x0) as f32;
2235 let h = (y1 - y0) as f32;
2236 let cx = w / 2.0;
2237 let cy = h / 2.0;
2238 for dy in 0..(y1 - y0) {
2239 for dx in 0..(x1 - x0) {
2240 let px = dx as f32 - cx;
2241 let py = dy as f32 - cy;
2242 let dist = libm::sqrtf(px * px + py * py);
2243 let t = (dist % cycle) / cycle;
2244 let c = Self::sample_repeating_colors(colors, t);
2245 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2246 }
2247 }
2248 }
2249
2250 pub fn draw_shadow_rect(
2252 &self,
2253 x0: i32,
2254 y0: i32,
2255 x1: i32,
2256 y1: i32,
2257 ox: i32,
2258 oy: i32,
2259 color: Color,
2260 ) {
2261 if x1 <= x0 || y1 <= y0 {
2262 return;
2263 }
2264 let sx0 = (x0 + ox).max(0) as u32;
2265 let sy0 = (y0 + oy).max(0) as u32;
2266 let sx1 = (x1 + ox).max(0) as u32;
2267 let sy1 = (y1 + oy).max(0) as u32;
2268 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2269 let alpha = if alpha == 0 { 80u8 } else { alpha }; if sx1 <= sx0 || sy1 <= sy0 {
2271 return;
2272 }
2273 for y in sy0..sy1 {
2274 for x in sx0..sx1 {
2275 self.draw_pixel_alpha(x, y, Color(color.0 | 0xFF000000), alpha);
2276 }
2277 }
2278 }
2279
2280 pub fn draw_shadow_rect_rounded(
2284 &self,
2285 x0: i32,
2286 y0: i32,
2287 x1: i32,
2288 y1: i32,
2289 ox: i32,
2290 oy: i32,
2291 radius: (i32, i32, i32, i32),
2292 color: Color,
2293 ) {
2294 if x1 <= x0 || y1 <= y0 {
2295 return;
2296 }
2297 let sx0 = (x0 + ox).max(0) as u32;
2298 let sy0 = (y0 + oy).max(0) as u32;
2299 let sx1 = (x1 + ox).max(0) as u32;
2300 let sy1 = (y1 + oy).max(0) as u32;
2301 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2302 let alpha = if alpha == 0 { 80u8 } else { alpha };
2303 if sx1 <= sx0 || sy1 <= sy0 {
2304 return;
2305 }
2306 let w = x1 - x0;
2307 let h = y1 - y0;
2308 let has_radius = radius != (0, 0, 0, 0);
2309
2310 for y in sy0..sy1 {
2311 let ry = (y as i32) - oy - y0;
2312 for x in sx0..sx1 {
2313 let rx = (x as i32) - ox - x0;
2314 if has_radius && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(rx, ry, w, h, radius) {
2315 continue;
2316 }
2317 self.draw_pixel_alpha(x, y, Color(color.0 | 0xFF000000), alpha);
2318 }
2319 }
2320 }
2321
2322 pub fn draw_inset_shadow_rect(
2324 &self,
2325 x0: i32,
2326 y0: i32,
2327 x1: i32,
2328 y1: i32,
2329 ox: i32,
2330 oy: i32,
2331 blur: i32,
2332 radius: (i32, i32, i32, i32),
2333 color: Color,
2334 ) {
2335 if x1 <= x0 || y1 <= y0 {
2336 return;
2337 }
2338 let ix0 = x0.max(0) as u32;
2339 let iy0 = y0.max(0) as u32;
2340 let ix1 = x1.max(0) as u32;
2341 let iy1 = y1.max(0) as u32;
2342 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2343 let alpha = if alpha == 0 { 90u8 } else { alpha };
2344 let shadow_color = color.0 | 0xFF000000;
2345 let w = x1 - x0;
2346 let h = y1 - y0;
2347 let has_radius = radius != (0, 0, 0, 0);
2348 let shadow_depth = blur.clamp(4, 30);
2349
2350 for y in iy0..iy1 {
2351 let ry = (y as i32) - y0;
2352 for x in ix0..ix1 {
2353 let rx = (x as i32) - x0;
2354 if has_radius && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(rx, ry, w, h, radius) {
2355 continue;
2356 }
2357 let d_left = rx - ox;
2358 let d_top = ry - oy;
2359 let d_right = (w - rx) + ox;
2360 let d_bottom = (h - ry) + oy;
2361 let min_dist = d_left.min(d_top).min(d_right).min(d_bottom);
2362 if min_dist >= 0 && min_dist < shadow_depth {
2363 let factor = 1.0 - (min_dist as f32 / shadow_depth as f32);
2364 let pix_alpha = ((alpha as f32) * factor) as u8;
2365 if pix_alpha > 0 {
2366 self.draw_pixel_alpha(x, y, Color(shadow_color), pix_alpha);
2367 }
2368 }
2369 }
2370 }
2371 }
2372
2373 pub fn draw_rounded_rect_fill(
2374 &self,
2375 x0: i32,
2376 y0: i32,
2377 x1: i32,
2378 y1: i32,
2379 radius: (i32, i32, i32, i32),
2380 color: Color,
2381 ) {
2382 let (tl, tr, br, bl) = radius;
2383 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2384 self.boxfill(x0 as u32, y0 as u32, x1 as u32, y1 as u32, color);
2385 return;
2386 }
2387 let w = x1 - x0;
2388 let h = y1 - y0;
2389
2390 let tl = tl.min(w / 2).min(h / 2).max(0);
2391 let tr = tr.min(w / 2).min(h / 2).max(0);
2392 let br = br.min(w / 2).min(h / 2).max(0);
2393 let bl = bl.min(w / 2).min(h / 2).max(0);
2394
2395 let top_max_r = tl.max(tr);
2402 let bot_max_r = bl.max(br);
2403 if top_max_r + bot_max_r <= h {
2404 self.boxfill(x0 as u32, (y0 + top_max_r) as u32, x1 as u32, (y1 - bot_max_r) as u32, color);
2405 }
2406
2407 if top_max_r > 0 {
2409 self.boxfill((x0 + tl) as u32, y0 as u32, (x1 - tr) as u32, (y0 + top_max_r) as u32, color);
2410 }
2411
2412 if bot_max_r > 0 {
2414 self.boxfill((x0 + bl) as u32, (y1 - bot_max_r) as u32, (x1 - br) as u32, y1 as u32, color);
2415 }
2416
2417 if tl > 0 {
2420 let r2 = tl * tl;
2421 for dy in 0..tl {
2422 for dx in 0..tl {
2423 if dx * dx + dy * dy <= r2 {
2424 self.draw_pixel((x0 + tl - 1 - dx) as u32, (y0 + tl - 1 - dy) as u32, color);
2425 }
2426 }
2427 }
2428 if top_max_r > tl {
2430 self.boxfill(x0 as u32, (y0 + tl) as u32, (x0 + tl) as u32, (y0 + top_max_r) as u32, color);
2431 }
2432 }
2433 if tr > 0 {
2435 let r2 = tr * tr;
2436 for dy in 0..tr {
2437 for dx in 0..tr {
2438 if dx * dx + dy * dy <= r2 {
2439 self.draw_pixel((x1 - tr + dx) as u32, (y0 + tr - 1 - dy) as u32, color);
2440 }
2441 }
2442 }
2443 if top_max_r > tr {
2444 self.boxfill((x1 - tr) as u32, (y0 + tr) as u32, x1 as u32, (y0 + top_max_r) as u32, color);
2445 }
2446 }
2447 if bl > 0 {
2449 let r2 = bl * bl;
2450 for dy in 0..bl {
2451 for dx in 0..bl {
2452 if dx * dx + dy * dy <= r2 {
2453 self.draw_pixel((x0 + bl - 1 - dx) as u32, (y1 - bl + dy) as u32, color);
2454 }
2455 }
2456 }
2457 if bot_max_r > bl {
2458 self.boxfill(x0 as u32, (y1 - bot_max_r) as u32, (x0 + bl) as u32, (y1 - bl) as u32, color);
2459 }
2460 }
2461 if br > 0 {
2463 let r2 = br * br;
2464 for dy in 0..br {
2465 for dx in 0..br {
2466 if dx * dx + dy * dy <= r2 {
2467 self.draw_pixel((x1 - br + dx) as u32, (y1 - br + dy) as u32, color);
2468 }
2469 }
2470 }
2471 if bot_max_r > br {
2472 self.boxfill((x1 - br) as u32, (y1 - bot_max_r) as u32, x1 as u32, (y1 - br) as u32, color);
2473 }
2474 }
2475 }
2476
2477 pub fn draw_rounded_rect_border(
2478 &self,
2479 x0: i32,
2480 y0: i32,
2481 x1: i32,
2482 y1: i32,
2483 radius: (i32, i32, i32, i32),
2484 color: Color,
2485 _border_style: &str,
2486 ) {
2487 if (color.0 >> 24) & 0xFF == 0 {
2497 return;
2498 }
2499 let (tl, tr, br, bl) = radius;
2500 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2501 self.boxfill(x0 as u32, y0 as u32, x1 as u32, (y0 + 1) as u32, color);
2503 self.boxfill(x0 as u32, (y1 - 1) as u32, x1 as u32, y1 as u32, color);
2504 self.boxfill(x0 as u32, y0 as u32, (x0 + 1) as u32, y1 as u32, color);
2505 self.boxfill((x1 - 1) as u32, y0 as u32, x1 as u32, y1 as u32, color);
2506 return;
2507 }
2508 let w = x1 - x0;
2509 let h = y1 - y0;
2510 let tl = tl.min(w / 2).min(h / 2).max(0);
2511 let tr = tr.min(w / 2).min(h / 2).max(0);
2512 let br = br.min(w / 2).min(h / 2).max(0);
2513 let bl = bl.min(w / 2).min(h / 2).max(0);
2514
2515 self.boxfill((x0 + tl) as u32, y0 as u32, (x1 - tr) as u32, (y0 + 1) as u32, color);
2517 self.boxfill((x0 + bl) as u32, (y1 - 1) as u32, (x1 - br) as u32, y1 as u32, color);
2518 self.boxfill(x0 as u32, (y0 + tl) as u32, (x0 + 1) as u32, (y1 - bl) as u32, color);
2519 self.boxfill((x1 - 1) as u32, (y0 + tr) as u32, x1 as u32, (y1 - br) as u32, color);
2520
2521 let draw_arc = |cx: i32, cy: i32, r: i32, quadrant: i32| {
2523 if r <= 0 { return; }
2524 let mut x = r;
2525 let mut y = 0;
2526 let mut err = 0;
2527 while x >= y {
2528 let px1 = cx + x - 1; let py1 = cy + y;
2529 let px2 = cx + y - 1; let py2 = cy + x - 1;
2530 let px3 = cx - x; let py3 = cy + y;
2531 let px4 = cx - y; let py4 = cy + x - 1;
2532 let px5 = cx - x; let py5 = cy - y - 1;
2533 let px6 = cx - y; let py6 = cy - x;
2534 let px7 = cx + x - 1; let py7 = cy - y - 1;
2535 let px8 = cx + y - 1; let py8 = cy - x;
2536
2537 match quadrant {
2538 1 => { self.draw_pixel(px1 as u32, py1 as u32, color);
2540 self.draw_pixel(px2 as u32, py2 as u32, color);
2541 }
2542 2 => { self.draw_pixel(px3 as u32, py3 as u32, color);
2544 self.draw_pixel(px4 as u32, py4 as u32, color);
2545 }
2546 3 => { self.draw_pixel(px5 as u32, py5 as u32, color);
2548 self.draw_pixel(px6 as u32, py6 as u32, color);
2549 }
2550 4 => { self.draw_pixel(px7 as u32, py7 as u32, color);
2552 self.draw_pixel(px8 as u32, py8 as u32, color);
2553 }
2554 _ => {}
2555 }
2556
2557 if err <= 0 {
2558 y += 1;
2559 err += 2 * y + 1;
2560 }
2561 if err > 0 {
2562 x -= 1;
2563 err -= 2 * x + 1;
2564 }
2565 }
2566 };
2567
2568 draw_arc(x0 + tl, y0 + tl, tl, 3);
2569 draw_arc(x1 - tr, y0 + tr, tr, 4);
2570 draw_arc(x1 - br, y1 - br, br, 1);
2571 draw_arc(x0 + bl, y1 - bl, bl, 2);
2572 }
2573
2574
2575}
2576
2577pub struct Mouse {
2579 pub x: u32,
2580 pub y: u32,
2581 pub is_clicked: bool,
2582 pub click_frame: u8,
2583 pub is_text_cursor: bool,
2586}
2587
2588impl Mouse {
2589 pub const SIZE: u32 = 32;
2590
2591 pub const fn new(x: u32, y: u32) -> Self {
2592 Self {
2593 x,
2594 y,
2595 is_clicked: false,
2596 click_frame: 0,
2597 is_text_cursor: false,
2598 }
2599 }
2600
2601 pub fn draw(&self, screen: &Screen) {
2602 let (target_ptr, w, h) = screen.resolve_target();
2603 let stride = screen.resolve_stride();
2604 let cursor_size = Self::SIZE;
2605
2606 let (draw_x, draw_y) = if self.is_clicked {
2608 (self.x + 1, self.y + 1)
2609 } else {
2610 (self.x, self.y)
2611 };
2612
2613 if self.is_text_cursor {
2614 self.draw_text_cursor(screen, draw_x, draw_y);
2615 return;
2616 }
2617
2618 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(ICON_CURSOR_64, cursor_size)
2619 {
2620 let theme = crate::kernel::config::get_config().theme;
2621 let mut c_fill = theme.mouse_cursor;
2622 let c_stroke = theme.mouse_stroke;
2623
2624 if self.is_clicked {
2626 let r = ((c_fill >> 16) & 0xFF) * 8 / 10;
2627 let g = ((c_fill >> 8) & 0xFF) * 8 / 10;
2628 let b = (c_fill & 0xFF) * 8 / 10;
2629 c_fill = 0xFF000000 | (r << 16) | (g << 8) | b;
2630 }
2631
2632 for row in 0..cursor_size {
2633 let py = draw_y + row;
2634 if py >= h {
2635 continue;
2636 }
2637
2638 let max_col = (w.saturating_sub(draw_x)).min(cursor_size) as usize;
2639 if max_col == 0 {
2640 continue;
2641 }
2642
2643 let offset = (py * stride + draw_x) as usize;
2644 unsafe {
2645 let mut row_buf = [0u32; 32];
2646 let dst = target_ptr.add(offset);
2647
2648 let src = if !screen.back_buffer.is_null() {
2650 let src_offset = (py * w + draw_x) as usize;
2651 screen.back_buffer.add(src_offset)
2652 } else {
2653 dst
2654 };
2655
2656 core::ptr::copy_nonoverlapping(src, row_buf.as_mut_ptr(), max_col);
2657
2658 for col in 0..max_col {
2660 let mask_idx = (row * cursor_size + col as u32) as usize;
2661 let alpha = alpha_mask[mask_idx];
2662 if alpha > 0 {
2663 let mut is_edge = false;
2665 if row == 0
2666 || row == cursor_size - 1
2667 || col == 0
2668 || col as u32 == cursor_size - 1
2669 {
2670 is_edge = true;
2671 } else {
2672 'outer: for dy in -1..=1 {
2674 for dx in -1..=1 {
2675 let ny = row as i32 + dy;
2676 let nx = col as i32 + dx;
2677 let n_idx = (ny * cursor_size as i32 + nx) as usize;
2678 if alpha_mask[n_idx] < 80 {
2679 is_edge = true;
2680 break 'outer;
2681 }
2682 }
2683 }
2684 }
2685
2686 let bg = row_buf[col];
2687 let r_bg = (bg >> 16) & 0xFF;
2688 let g_bg = (bg >> 8) & 0xFF;
2689 let b_bg = bg & 0xFF;
2690
2691 let fg_color = if is_edge { c_stroke } else { c_fill };
2693 let r_fg = (fg_color >> 16) & 0xFF;
2694 let g_fg = (fg_color >> 8) & 0xFF;
2695 let b_fg = fg_color & 0xFF;
2696
2697 let a = alpha as u32;
2698 let inv_a = 255 - a;
2699
2700 let r = (r_fg * a + r_bg * inv_a) / 255;
2701 let g = (g_fg * a + g_bg * inv_a) / 255;
2702 let b = (b_fg * a + b_bg * inv_a) / 255;
2703
2704 row_buf[col] = (r << 16) | (g << 8) | b;
2705 }
2706 }
2707
2708 core::ptr::copy_nonoverlapping(row_buf.as_ptr(), dst, max_col);
2710 }
2711 }
2712
2713 if self.is_clicked && self.click_frame > 0 && self.click_frame < 8 {
2715 let spark_color = theme.mouse_cursor; let center_x = draw_x as i32;
2717 let center_y = draw_y as i32;
2718 let r1 = 4 + (self.click_frame as i32) * 2; let r2 = r1 + 4; let dirs = [
2723 (0, -1), (1, -1), (1, 0), (1, 1),
2724 (0, 1), (-1, 1), (-1, 0), (-1, -1)
2725 ];
2726
2727 for &(dx, dy) in dirs.iter() {
2728 for r in r1..=r2 {
2729 let px = center_x + dx * r;
2730 let py = center_y + dy * r;
2731 if px >= 0 && px < w as i32 && py >= 0 && py < h as i32 {
2732 let offset = (py as u32 * stride + px as u32) as usize;
2733 unsafe {
2734 let dst = target_ptr.add(offset);
2735 *dst = spark_color;
2736 }
2737 }
2738 }
2739 }
2740 }
2741 }
2742 }
2743
2744 fn draw_text_cursor(&self, screen: &Screen, draw_x: u32, draw_y: u32) {
2751 let theme = crate::kernel::config::get_config().theme;
2752 let color = Color(theme.mouse_cursor);
2753 let size = Self::SIZE;
2754 let bar_h = size.saturating_sub(4);
2755 let cx = draw_x + size / 2;
2756 let top_y = draw_y + 2;
2757 let bottom_y = top_y + bar_h;
2758 for dx in 0..=(size / 4) {
2760 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, top_y, color);
2761 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, bottom_y, color);
2762 }
2763 for y in top_y..=bottom_y {
2765 screen.draw_pixel(cx, y, color);
2766 }
2767 }
2768
2769 pub fn move_to(&mut self, screen: &Screen, new_x: u32, new_y: u32) {
2771 self.x = new_x.clamp(0, screen.width - 1);
2772 self.y = new_y.clamp(0, screen.height - 1);
2773 }
2774}
2775
2776pub static mut SPLASH_ACTIVE: bool = false;
2780pub static LOGO_PNG: &[u8] = include_bytes!("../../images/os_logo.png");
2781
2782static mut BOOT_LOG_COUNT: usize = 0;
2783static mut BOOT_LOG_BUFFER: [[u8; 128]; 16] = [[0; 128]; 16];
2784static mut BOOT_LOG_LENS: [usize; 16] = [0; 16];
2785
2786static mut CURRENT_LINE_BUFFER: [u8; 256] = [0; 256];
2787static mut CURRENT_LINE_LEN: usize = 0;
2788
2789pub unsafe fn write_splash_char(c: u8) {
2793 if !SPLASH_ACTIVE {
2794 return;
2795 }
2796 if c == b'\n' {
2797 if CURRENT_LINE_LEN > 0 {
2798 if let Ok(s) = core::str::from_utf8(&CURRENT_LINE_BUFFER[..CURRENT_LINE_LEN]) {
2799 add_boot_log(s);
2800 }
2801 CURRENT_LINE_LEN = 0;
2802 }
2803 } else {
2804 if CURRENT_LINE_LEN < 255 {
2805 CURRENT_LINE_BUFFER[CURRENT_LINE_LEN] = c;
2806 CURRENT_LINE_LEN += 1;
2807 }
2808 }
2809}
2810
2811pub unsafe fn write_splash_str(s: &str) {
2815 if !SPLASH_ACTIVE {
2816 return;
2817 }
2818 for &b in s.as_bytes() {
2819 write_splash_char(b);
2820 }
2821}
2822
2823pub unsafe fn add_boot_log(s: &str) {
2827 if !SPLASH_ACTIVE {
2828 return;
2829 }
2830 const MAX_LINE_CHARS: usize = 55;
2831
2832 for line in s.lines() {
2833 let line = line.trim_end();
2834 if line.is_empty() {
2835 continue;
2836 }
2837
2838 let mut chars = line.as_bytes();
2839 while !chars.is_empty() {
2840 let chunk_len = chars.len().min(MAX_LINE_CHARS);
2841 let chunk = &chars[..chunk_len];
2842 chars = &chars[chunk_len..];
2843
2844 if BOOT_LOG_COUNT < 16 {
2845 let idx = BOOT_LOG_COUNT;
2846 BOOT_LOG_BUFFER[idx][..chunk_len].copy_from_slice(chunk);
2847 BOOT_LOG_LENS[idx] = chunk_len;
2848 BOOT_LOG_COUNT += 1;
2849 } else {
2850 for i in 0..15 {
2851 BOOT_LOG_BUFFER[i] = BOOT_LOG_BUFFER[i + 1];
2852 BOOT_LOG_LENS[i] = BOOT_LOG_LENS[i + 1];
2853 }
2854 BOOT_LOG_BUFFER[15][..chunk_len].copy_from_slice(chunk);
2855 BOOT_LOG_LENS[15] = chunk_len;
2856 }
2857 }
2858 }
2859 redraw_splash();
2860}
2861
2862pub unsafe fn draw_splash_logo() {
2866 if !SPLASH_ACTIVE {
2867 return;
2868 }
2869 if let Some(ref screen) = crate::CURRENT_SCREEN {
2870 screen.clear(Color(0xFF000000));
2871 let mut decoder = zune_png::PngDecoder::new(LOGO_PNG);
2872 if decoder.decode_raw().is_ok() {
2873 if let Some(info) = decoder.get_info() {
2874 let img_w = info.width as u32;
2875 let img_h = info.height as u32;
2876 let area_w = 480u32;
2877 let area_h = screen.height;
2878 let target_w = area_w.min(img_w);
2879 let target_h = (target_w * img_h) / img_w;
2880 let target_h = target_h.min(area_h);
2881 let target_w = (target_h * img_w) / img_h;
2882
2883 let x = (area_w - target_w) / 2 + 20;
2884 let y = (screen.height - target_h) / 2;
2885 let _ = screen.draw_image(x, y, target_w, target_h, LOGO_PNG);
2886 }
2887 }
2888 redraw_splash();
2889 }
2890}
2891
2892pub unsafe fn redraw_splash() {
2896 if !SPLASH_ACTIVE {
2897 return;
2898 }
2899 if let Some(ref screen) = crate::CURRENT_SCREEN {
2900 let area_x = 520u32;
2901 let area_y = 20u32;
2902 let area_w = screen.width.saturating_sub(area_x).saturating_sub(20);
2903 let area_h = screen.height.saturating_sub(area_y).saturating_sub(20);
2904 screen.boxfill(
2905 area_x,
2906 area_y,
2907 area_x + area_w,
2908 area_y + area_h,
2909 Color(0xFF000000),
2910 );
2911
2912 let start_y = (screen.height.saturating_sub(40)) as i32;
2913 let line_height = 28i32;
2914
2915 for i in 0..BOOT_LOG_COUNT {
2916 let y = start_y - (BOOT_LOG_COUNT - 1 - i) as i32 * line_height;
2917 if y >= area_y as i32 {
2918 if let Ok(s) = core::str::from_utf8(&BOOT_LOG_BUFFER[i][..BOOT_LOG_LENS[i]]) {
2919 screen.draw_string_monospace(area_x + 10, y as u32, s, Color(0xFFCCCCCC));
2920 }
2921 }
2922 }
2923 }
2924}
2925
2926pub struct SplashWriter;
2927
2928impl core::fmt::Write for SplashWriter {
2929 fn write_str(&mut self, s: &str) -> core::fmt::Result {
2930 unsafe {
2931 write_splash_str(s);
2932 }
2933 Ok(())
2934 }
2935}