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)>>, pub round_clip: Cell<Option<(i32, i32, i32, i32, i32)>>,
61 clip_stack: Cell<[Option<(u32, u32, u32, u32)>; 8]>,
62 clip_depth: Cell<usize>,
63
64 render_target: Cell<*mut u32>,
66 target_width: Cell<u32>,
67 target_height: Cell<u32>,
68 saved_target_clip: Cell<Option<Option<(u32, u32, u32, u32)>>>,
69
70 vsync: Cell<bool>, page: Cell<u32>, fb_virtual_height: Cell<u32>, fb_size: Cell<u32>, prev_content_region: Cell<Option<(u32, u32, u32, u32)>>,
77 page_cursor: Cell<[(u32, u32); 2]>,
80 vsync_init_count: Cell<u8>,
82}
83
84pub static TEXT_DRAW_TICKS: core::sync::atomic::AtomicUsize =
86 core::sync::atomic::AtomicUsize::new(0);
87
88pub static BOXFILL_TICKS: core::sync::atomic::AtomicUsize =
90 core::sync::atomic::AtomicUsize::new(0);
91pub static SHADOW_TICKS: core::sync::atomic::AtomicUsize =
93 core::sync::atomic::AtomicUsize::new(0);
94
95pub static RRECT_TICKS: core::sync::atomic::AtomicUsize =
97 core::sync::atomic::AtomicUsize::new(0);
98
99pub static PIXBG_TICKS: core::sync::atomic::AtomicUsize =
102 core::sync::atomic::AtomicUsize::new(0);
103
104pub struct PixBgTimer(usize);
106impl PixBgTimer {
107 #[allow(clippy::new_without_default)]
108 pub fn new() -> Self {
109 Self(crate::kernel::timer::get_ticks())
110 }
111}
112impl Drop for PixBgTimer {
113 fn drop(&mut self) {
114 PIXBG_TICKS.fetch_add(
115 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
116 core::sync::atomic::Ordering::Relaxed,
117 );
118 }
119}
120
121pub struct RRectTimer(usize);
123impl RRectTimer {
124 #[allow(clippy::new_without_default)]
125 pub fn new() -> Self {
126 Self(crate::kernel::timer::get_ticks())
127 }
128}
129impl Drop for RRectTimer {
130 fn drop(&mut self) {
131 RRECT_TICKS.fetch_add(
132 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
133 core::sync::atomic::Ordering::Relaxed,
134 );
135 }
136}
137
138struct AccTimer(usize, &'static core::sync::atomic::AtomicUsize);
139impl Drop for AccTimer {
140 fn drop(&mut self) {
141 self.1.fetch_add(
142 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
143 core::sync::atomic::Ordering::Relaxed,
144 );
145 }
146}
147
148struct TextDrawTimer(usize);
150impl Drop for TextDrawTimer {
151 fn drop(&mut self) {
152 TEXT_DRAW_TICKS.fetch_add(
153 crate::kernel::timer::get_ticks().wrapping_sub(self.0),
154 core::sync::atomic::Ordering::Relaxed,
155 );
156 }
157}
158
159impl Screen {
160 pub fn new(vram: *mut u32, width: u32, height: u32, pitch: u32) -> Self {
161 Self {
162 vram,
163 back_buffer: core::ptr::null_mut(),
164 use_back_buffer: core::cell::Cell::new(false),
165 width,
166 height,
167 pitch,
168 clip: Cell::new(None),
169 round_clip: Cell::new(None),
170 clip_stack: Cell::new([None; 8]),
171 clip_depth: Cell::new(0),
172 render_target: Cell::new(core::ptr::null_mut()),
173 target_width: Cell::new(width),
174 target_height: Cell::new(height),
175 saved_target_clip: Cell::new(None),
176 vsync: Cell::new(false),
177 page: Cell::new(0),
178 fb_virtual_height: Cell::new(0),
179 fb_size: Cell::new(0),
180 prev_content_region: Cell::new(None),
181 page_cursor: Cell::new([(0, 0), (0, 0)]),
182 vsync_init_count: Cell::new(0),
183 }
184 }
185
186 pub fn vsync_enabled(&self) -> bool {
187 self.vsync.get()
188 }
189
190 pub fn has_pending_vsync_sync(&self) -> bool {
191 self.prev_content_region.get().is_some()
192 }
193
194 pub fn debug_geometry(&self) {
197 let addr = self.vram as usize;
198 let region = if addr >= crate::kernel::mmu::DEVICE_REGION_START {
199 "uncached/Device OK"
200 } else {
201 "CACHED RAM (<0x38000000) !!"
202 };
203 crate::println!(
204 "[FB] vram=0x{:08X} ({}) | {}x{} | pitch={} (width*4={}) | virt_h={} | size={}",
205 addr,
206 region,
207 self.width,
208 self.height,
209 self.pitch,
210 self.width * 4,
211 self.fb_virtual_height.get(),
212 self.fb_size.get()
213 );
214 }
215
216 pub fn set_fb_geometry(&self, virtual_height: u32, size: u32) {
219 self.fb_virtual_height.set(virtual_height);
220 self.fb_size.set(size);
221 }
222
223 pub fn enable_vsync(&self) -> bool {
228 let virt_h = self.fb_virtual_height.get();
229 let size = self.fb_size.get();
230 let two_pages = virt_h >= self.height * 2
231 && (size == 0 || size as u64 >= (self.pitch as u64) * (self.height as u64) * 2);
232 if !two_pages {
233 self.vsync.set(false);
234 return false;
235 }
236 let _ = crate::kernel::mailbox::set_virtual_offset(0, 0);
238 self.page.set(0);
239 self.vsync.set(true);
240 self.vsync_init_count.set(2);
242 self.prev_content_region.set(None);
243 self.page_cursor.set([(0, 0), (0, 0)]);
244 true
245 }
246
247 pub fn set_render_target(&self, buf: *mut u32, w: u32, h: u32) {
248 let old_clip = self.clip.get();
250 self.saved_target_clip.set(Some(old_clip));
251 self.clip.set(None);
252 self.render_target.set(buf);
253 self.target_width.set(w);
254 self.target_height.set(h);
255 }
256
257 pub fn reset_render_target(&self) {
258 self.render_target.set(core::ptr::null_mut());
259 self.target_width.set(self.width);
260 self.target_height.set(self.height);
261 if let Some(saved) = self.saved_target_clip.take() {
263 self.clip.set(saved);
264 }
265 }
266
267 pub fn resolve_target(&self) -> (*mut u32, u32, u32) {
268 let custom_target = self.render_target.get();
269 if !custom_target.is_null() {
270 (
271 custom_target,
272 self.target_width.get(),
273 self.target_height.get(),
274 )
275 } else {
276 let base_ptr = if self.use_back_buffer.get() && !self.back_buffer.is_null() {
277 self.back_buffer
278 } else {
279 self.vram
280 };
281 (base_ptr, self.width, self.height)
282 }
283 }
284
285 pub fn resolve_stride(&self) -> u32 {
287 let custom_target = self.render_target.get();
288 if !custom_target.is_null() {
289 self.target_width.get()
290 } else if self.use_back_buffer.get() && !self.back_buffer.is_null() {
291 self.width
292 } else {
293 self.pitch / 4
294 }
295 }
296
297 pub fn enable_double_buffering(&mut self) {
298 if self.back_buffer.is_null() {
299 let Ok(layout) = core::alloc::Layout::array::<u32>((self.width * self.height) as usize)
301 else {
302 return; };
304 unsafe {
305 self.back_buffer = alloc::alloc::alloc_zeroed(layout) as *mut u32;
306 }
307 if self.back_buffer.is_null() {
308 return; }
310 }
311 self.use_back_buffer.set(true);
312 }
313
314 pub fn flush(&self) {
315 if self.use_back_buffer.get() && !self.back_buffer.is_null() {
316 let stride = self.pitch / 4;
317 let w = self.width;
318 let h = self.height;
319
320 if self.vsync.get() {
321 let page = self.page.get();
324 let page_off_px = page * h * stride; let dst = self.vram as u32 + page_off_px * 4;
326 if stride == w {
327 crate::kernel::dma::dma_memcpy(5, self.back_buffer as u32, dst, w * h * 4);
328 } else {
329 crate::kernel::dma::dma_memcpy_2d(
330 5,
331 self.back_buffer as u32,
332 dst,
333 w * 4,
334 h,
335 w * 4,
336 stride * 4,
337 );
338 }
339 let _ = crate::kernel::mailbox::set_virtual_offset(0, page * h);
341 let _ = crate::kernel::mailbox::wait_vsync();
343 self.page.set(1 - page);
345 } else if stride == w {
346 crate::kernel::dma::dma_memcpy(
347 5,
348 self.back_buffer as u32,
349 self.vram as u32,
350 w * h * 4,
351 );
352 } else {
353 crate::kernel::dma::dma_memcpy_2d(
354 5,
355 self.back_buffer as u32,
356 self.vram as u32,
357 w * 4,
358 h,
359 w * 4,
360 stride * 4,
361 );
362 }
363 }
364 }
365
366 pub fn flush_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
367 if self.vsync.get() {
370 self.flush();
371 return;
372 }
373 if self.use_back_buffer.get() && !self.back_buffer.is_null() {
374 let stride = self.pitch / 4;
375 let w = self.width;
376 let h = self.height;
377
378 let cx0 = x0.min(w - 1);
379 let cx1 = x1.min(w - 1).max(cx0);
380 let cy0 = y0.min(h - 1);
381 let cy1 = y1.min(h - 1).max(cy0);
382
383 let rect_w = cx1 - cx0 + 1;
384 let rect_h = cy1 - cy0 + 1;
385
386 if rect_w * rect_h >= DMA_THRESHOLD_PIXELS {
387 let src_offset = (cy0 * w + cx0) * 4;
388 let dst_offset = (cy0 * stride + cx0) * 4;
389 crate::kernel::dma::dma_memcpy_2d(
390 5,
391 self.back_buffer as u32 + src_offset,
392 self.vram as u32 + dst_offset,
393 rect_w * 4,
394 rect_h,
395 w * 4,
396 stride * 4,
397 );
398 } else {
399 for y in cy0..=cy1 {
400 unsafe {
401 let src = self.back_buffer.add((y * w + cx0) as usize);
402 let dst = self.vram.add((y * stride + cx0) as usize);
403 core::ptr::copy_nonoverlapping(src, dst, rect_w as usize);
404 }
405 }
406 }
407 }
408 }
409
410 pub fn scroll_rect(&self, x: u32, y: u32, w: u32, h: u32, dy: i32) {
418 if dy == 0 || w == 0 || h == 0 {
419 return;
420 }
421 let abs_dy = dy.unsigned_abs();
422 if abs_dy >= h {
423 return;
424 }
425 let (target_ptr, buf_w, buf_h) = self.resolve_target();
426 if x >= buf_w || y >= buf_h {
427 return;
428 }
429 let actual_w = w.min(buf_w - x);
430 let actual_h = h.min(buf_h - y);
431 if abs_dy >= actual_h {
432 return;
433 }
434
435 let stride = self.resolve_stride() as usize;
436 let move_h = (actual_h - abs_dy) as usize;
437 let copy_w = actual_w as usize;
438
439 unsafe {
440 if dy > 0 {
441 let src_y = (y + abs_dy) as usize;
443 let dst_y = y as usize;
444 for row in 0..move_h {
445 let src = target_ptr.add((src_y + row) * stride + x as usize);
446 let dst = target_ptr.add((dst_y + row) * stride + x as usize);
447 core::ptr::copy(src, dst, copy_w);
448 }
449 } else {
450 let src_y = y as usize;
452 let dst_y = (y + abs_dy) as usize;
453 for row in (0..move_h).rev() {
454 let src = target_ptr.add((src_y + row) * stride + x as usize);
455 let dst = target_ptr.add((dst_y + row) * stride + x as usize);
456 core::ptr::copy(src, dst, copy_w);
457 }
458 }
459 }
460 }
461
462 pub fn draw_mouse_direct(&self, mouse: &Mouse) {
464 let prev = self.use_back_buffer.get();
465 self.use_back_buffer.set(false);
466 mouse.draw(self);
467 self.use_back_buffer.set(prev);
468 }
469
470 pub fn flush_with_cursor(&self, mouse: &Mouse) {
473 if !self.use_back_buffer.get() || self.back_buffer.is_null() {
474 self.flush();
475 return;
476 }
477 let cursor_size = Mouse::SIZE as usize;
478 let w = self.width as usize;
479 let h = self.height as usize;
480 let cx = mouse.x as usize;
481 let cy = mouse.y as usize;
482
483 let rows = cursor_size.min(h.saturating_sub(cy));
485 let cols = cursor_size.min(w.saturating_sub(cx));
486 let mut saved = alloc::vec![0u32; cursor_size * cursor_size];
487 unsafe {
488 for row in 0..rows {
489 let src = self.back_buffer.add((cy + row) * w + cx);
490 core::ptr::copy_nonoverlapping(
491 src,
492 saved.as_mut_ptr().add(row * cursor_size),
493 cols,
494 );
495 }
496 }
497
498 mouse.draw(self);
500 self.flush();
501
502 unsafe {
504 for row in 0..rows {
505 let dst = self.back_buffer.add((cy + row) * w + cx);
506 core::ptr::copy_nonoverlapping(saved.as_ptr().add(row * cursor_size), dst, cols);
507 }
508 }
509 }
510
511 pub fn flush_dirty_pageflip(&self, content: Option<(u32, u32, u32, u32)>, mouse: &Mouse) {
516 if !self.vsync.get() || !self.use_back_buffer.get() || self.back_buffer.is_null() {
517 self.flush_with_cursor(mouse);
519 return;
520 }
521 let w = self.width;
522 let h = self.height;
523 let stride = self.pitch / 4;
524 let cs = Mouse::SIZE;
525
526 #[inline]
527 fn union(a: (u32, u32, u32, u32), b: (u32, u32, u32, u32)) -> (u32, u32, u32, u32) {
528 (a.0.min(b.0), a.1.min(b.1), a.2.max(b.2), a.3.max(b.3))
529 }
530
531 #[inline]
532 fn cursor_rect(x: u32, y: u32, cs: u32, w: u32, h: u32) -> (u32, u32, u32, u32) {
533 (x.min(w), y.min(h), (x + cs).min(w), (y + cs).min(h))
534 }
535
536 let page = self.page.get(); let c_new = cursor_rect(mouse.x, mouse.y, cs, w, h);
539 let old_c = self.page_cursor.get()[page as usize];
541 let c_old = cursor_rect(old_c.0, old_c.1, cs, w, h);
542
543 let content_now = content.map(|c| (c.0.min(w), c.1.min(h), c.2.min(w), c.3.min(h)));
545
546 let region = if self.vsync_init_count.get() > 0 {
556 self.vsync_init_count.set(self.vsync_init_count.get() - 1);
557 (0, 0, w, h)
558 } else {
559 let content_region = match (content_now, self.prev_content_region.get()) {
561 (Some(a), Some(b)) => Some(union(a, b)),
562 (Some(a), None) => Some(a),
563 (None, Some(b)) => Some(b),
564 (None, None) => None,
565 };
566 let mut r = union(c_new, c_old);
567 if let Some(cr) = content_region {
568 r = union(r, cr);
569 }
570 r
571 };
572
573 let x0 = region.0.min(w);
574 let y0 = region.1.min(h);
575 let x1 = region.2.min(w).max(x0);
576 let y1 = region.3.min(h).max(y0);
577
578 let cx = mouse.x as usize;
580 let cy = mouse.y as usize;
581 let wsz = w as usize;
582 let hsz = h as usize;
583 let rows = (cs as usize).min(hsz.saturating_sub(cy));
584 let cols = (cs as usize).min(wsz.saturating_sub(cx));
585 let mut saved = alloc::vec![0u32; (cs * cs) as usize];
586 unsafe {
587 for row in 0..rows {
588 let src = self.back_buffer.add((cy + row) * wsz + cx);
589 core::ptr::copy_nonoverlapping(
590 src,
591 saved.as_mut_ptr().add(row * cs as usize),
592 cols,
593 );
594 }
595 }
596 mouse.draw(self); if x1 > x0 && y1 > y0 {
600 let rect_w = x1 - x0;
601 let rect_h = y1 - y0;
602 let src_off = y0 * w + x0;
603 let dst_off = page * h * stride + y0 * stride + x0;
604 crate::kernel::dma::dma_memcpy_2d(
605 5,
606 self.back_buffer as u32 + src_off * 4,
607 self.vram as u32 + dst_off * 4,
608 rect_w * 4,
609 rect_h,
610 w * 4,
611 stride * 4,
612 );
613 let _ = crate::kernel::mailbox::set_virtual_offset(0, page * h);
614 let _ = crate::kernel::mailbox::wait_vsync();
615 self.page.set(1 - page);
616 }
617
618 unsafe {
620 for row in 0..rows {
621 let dst = self.back_buffer.add((cy + row) * wsz + cx);
622 core::ptr::copy_nonoverlapping(saved.as_ptr().add(row * cs as usize), dst, cols);
623 }
624 }
625
626 let mut pc = self.page_cursor.get();
628 pc[page as usize] = (mouse.x, mouse.y);
629 self.page_cursor.set(pc);
630 self.prev_content_region.set(content_now);
631 }
632
633 pub fn set_clip(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
634 self.clip.set(Some((x0, y0, x1, y1)));
635 }
636
637 pub fn clip_rect(&self) -> Option<(u32, u32, u32, u32)> {
639 self.clip.get()
640 }
641
642 pub fn clear_clip(&self) {
643 self.clip.set(None);
644 }
645
646 pub fn push_clip(&self, x0: u32, y0: u32, x1: u32, y1: u32) {
649 let depth = self.clip_depth.get();
650 if depth < 8 {
651 let mut stack = self.clip_stack.get();
652 stack[depth] = self.clip.get();
653 self.clip_stack.set(stack);
654 self.clip_depth.set(depth + 1);
655 }
656 let new_clip = if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
657 (cx0.max(x0), cy0.max(y0), cx1.min(x1), cy1.min(y1))
658 } else {
659 (x0, y0, x1, y1)
660 };
661 self.clip.set(Some(new_clip));
662 }
663
664 pub fn pop_clip(&self) {
666 let depth = self.clip_depth.get();
667 if depth > 0 {
668 let stack = self.clip_stack.get();
669 self.clip.set(stack[depth - 1]);
670 self.clip_depth.set(depth - 1);
671 } else {
672 self.clip.set(None);
673 }
674 }
675
676 pub fn clear(&self, color: Color) {
677 if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
680 self.boxfill(cx0, cy0, cx1, cy1, color);
681 return;
682 }
683
684 let (target_ptr, _w, h) = self.resolve_target();
685
686 let stride = self.resolve_stride();
690 let color_val = color.to_hardware_format();
691 crate::kernel::dma::dma_fill_rect(
692 5, &color_val as *const _ as u32,
694 target_ptr as u32,
695 stride,
696 h,
697 stride * 4,
698 );
699 }
700
701 #[inline]
707 fn outside_round_clip(&self, x: u32, y: u32) -> bool {
708 match self.round_clip.get() {
709 None => false,
710 Some((x0, y0, x1, y1, r)) => {
711 let (x, y) = (x as i32, y as i32);
712 let inset = crate::kernel::draw_clip::round_clip_inset(y, y0, y1, r);
713 inset > 0 && (x < x0 + inset || x >= x1 - inset)
714 }
715 }
716 }
717
718 pub fn draw_pixel(&self, x: u32, y: u32, color: Color) {
719 if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
720 if x < cx0 || x >= cx1 || y < cy0 || y >= cy1 {
721 return;
722 }
723 }
724 if self.outside_round_clip(x, y) {
725 return;
726 }
727
728 let a = (color.0 >> 24) & 0xFF;
737 let color = if a == 0 || a == 0xFF {
738 color
739 } else {
740 Color(crate::os_lib::web_engine::alpha_blend::blend_argb_over(
741 color.0,
742 self.read_pixel(x, y).0 | 0xFF00_0000,
743 ))
744 };
745
746 let (target_ptr, w, h) = self.resolve_target();
747 if x < w && y < h {
750 let stride = self.resolve_stride();
751 unsafe {
752 let ptr = target_ptr.add((y * stride + x) as usize);
753 core::ptr::write_volatile(ptr, color.to_hardware_format());
754 }
755 }
756 }
757
758 pub fn read_pixel(&self, x: u32, y: u32) -> Color {
759 let (target_ptr, w, h) = self.resolve_target();
760 if x < w && y < h {
761 let stride = self.resolve_stride();
762 unsafe {
763 let ptr = target_ptr.add((y * stride + x) as usize);
764 Color::from_hardware_format(core::ptr::read_volatile(ptr))
765 }
766 } else {
767 Color::BLACK
768 }
769 }
770
771 pub fn draw_pixel_alpha(&self, x: u32, y: u32, color: Color, alpha: u8) {
779 if alpha == 0 {
780 return;
781 }
782 if let Some((cx0, cy0, cx1, cy1)) = self.clip.get() {
783 if x < cx0 || x >= cx1 || y < cy0 || y >= cy1 {
784 return;
785 }
786 }
787 if self.outside_round_clip(x, y) {
788 return;
789 }
790
791 let (target_ptr, w, h) = self.resolve_target();
792 if x >= w || y >= h {
793 return;
794 }
795 let stride = self.resolve_stride();
796 unsafe {
797 let ptr = target_ptr.add((y * stride + x) as usize);
798 if alpha == 255 {
799 core::ptr::write_volatile(ptr, color.to_hardware_format());
800 return;
801 }
802
803 let bg_val = core::ptr::read_volatile(ptr);
804 let a = alpha as u32;
806 let inv_a = 255 - a;
807
808 let fg_r = (color.0 >> 16) & 0xFF;
809 let fg_g = (color.0 >> 8) & 0xFF;
810 let fg_b = color.0 & 0xFF;
811
812 let fg_rb_term = ((fg_b * a + 127) << 16) | (fg_r * a + 127);
813 let bg_rb = bg_val & 0x00FF_00FF;
814 let rb_prod = fg_rb_term + bg_rb * inv_a;
815 let r = (rb_prod & 0xFFFF) / 255;
816 let b = (rb_prod >> 16) / 255;
817 let g = (fg_g * a + 127 + ((bg_val >> 8) & 0xFF) * inv_a) / 255;
818
819 let blended = 0xFF00_0000 | (b << 16) | (g << 8) | r;
820 core::ptr::write_volatile(ptr, blended);
821 }
822 }
823
824 pub fn draw_string_vector(&self, x: u32, y: u32, s: &str, color: Color, size_px: u32) {
826 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
827 if let Some(ref mut font) = *font_lock {
828 let (_, w, h) = self.resolve_target();
829 let mut cur_x = x as i32;
830 for c in s.chars() {
831 if let Some(glyph) = font.get_glyph(c, size_px) {
832 let gx = cur_x + glyph.x_offset;
833 let gy = y as i32 + glyph.y_offset + (size_px as i32 * 85 / 100);
835
836 for row in 0..glyph.height {
837 let py = gy + row as i32;
838 if py < 0 || py >= h as i32 {
839 continue;
840 }
841 for col in 0..glyph.width {
842 let px = gx + col as i32;
843 if px < 0 || px >= w as i32 {
844 continue;
845 }
846
847 let alpha = glyph.data[(row * glyph.width + col) as usize];
848 if alpha > 0 {
849 self.draw_pixel_alpha(px as u32, py as u32, color, alpha);
850 }
851 }
852 }
853
854 cur_x += glyph.advance as i32;
855 }
856 }
857 }
858 }
859
860 pub fn draw_string_vector_bg(
862 &self,
863 x: u32,
864 y: u32,
865 s: &str,
866 color: Color,
867 size_px: u32,
868 bg: Color,
869 ) {
870 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
871 if let Some(ref mut font) = *font_lock {
872 let (_, w, h) = self.resolve_target();
873 let mut cur_x = x as i32;
874 for c in s.chars() {
875 if let Some(glyph) = font.get_color_glyph(c, size_px, color.0, bg.0) {
876 let gx = cur_x + glyph.x_offset;
877 let gy = y as i32 + glyph.y_offset + (size_px as i32 * 85 / 100);
878
879 for row in 0..glyph.height {
880 let py = gy + row as i32;
881 if py < 0 || py >= h as i32 {
882 continue;
883 }
884 for col in 0..glyph.width {
885 let px = gx + col as i32;
886 if px < 0 || px >= w as i32 {
887 continue;
888 }
889 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
890 if (pixel_color >> 24) > 0 {
891 self.draw_pixel(px as u32, py as u32, Color(pixel_color));
893 }
894 }
895 }
896 cur_x += glyph.advance as i32;
897 } else {
898 cur_x += (size_px / 2) as i32;
899 }
900 }
901 }
902 }
903
904 pub fn draw_vscrollbar_styled(
919 &self,
920 track_x: u32,
921 track_y: i32,
922 track_h: i32,
923 content_h: i32,
924 viewport_h: i32,
925 scroll_pos: i32,
926 thumb_width: u32,
927 thumb_color: Color,
928 track_color: Option<Color>,
929 ) {
930 if content_h <= viewport_h || track_h <= 0 || viewport_h <= 0 || thumb_width == 0 {
931 return;
932 }
933 if let Some(track_col) = track_color {
934 self.boxfill(
935 track_x,
936 track_y.max(0) as u32,
937 track_x + thumb_width,
938 (track_y + track_h).max(0) as u32,
939 track_col,
940 );
941 }
942 let max_scroll = (content_h - viewport_h).max(1);
943 let thumb_h = ((track_h * viewport_h) / content_h).clamp(24.min(track_h), track_h);
944 let ratio = scroll_pos.clamp(0, max_scroll) as f32 / max_scroll as f32;
945 let thumb_y = track_y + (ratio * (track_h - thumb_h) as f32) as i32;
946 self.boxfill(
947 track_x,
948 thumb_y.max(0) as u32,
949 track_x + thumb_width,
950 (thumb_y + thumb_h).max(0) as u32,
951 thumb_color,
952 );
953 }
954
955 pub fn draw_vscrollbar(
956 &self,
957 track_x: u32,
958 track_y: i32,
959 track_h: i32,
960 content_h: i32,
961 viewport_h: i32,
962 scroll_pos: i32,
963 ) {
964 let thumb_color = Color(crate::kernel::config::get_config().theme.scrollbar_thumb);
965 self.draw_vscrollbar_styled(
966 track_x,
967 track_y,
968 track_h,
969 content_h,
970 viewport_h,
971 scroll_pos,
972 6,
973 thumb_color,
974 None,
975 );
976 }
977
978 pub fn boxfill(&self, x0: u32, y0: u32, x1: u32, y1: u32, color: Color) {
987 let a = (color.0 >> 24) & 0xFF;
992 if a == 0 {
1006 return;
1007 }
1008 if let Some((rx0, ry0, rx1, ry1, r)) = self.round_clip.get() {
1010 let (bx0, by0, bx1, by1) = (x0 as i32, y0 as i32, x1 as i32, y1 as i32);
1011 let touches_corner = by0 < ry0 + r || by1 >= ry1 - r;
1012 if touches_corner && bx1 >= bx0 && by1 >= by0 {
1013 self.round_clip.set(None);
1014 let mut run_start: Option<i32> = None;
1015 for y in by0..=by1 {
1016 let inset = crate::kernel::draw_clip::round_clip_inset(y, ry0, ry1, r);
1017 if inset == 0 {
1018 run_start.get_or_insert(y);
1019 continue;
1020 }
1021 if let Some(s0) = run_start.take() {
1022 self.boxfill(x0, s0 as u32, x1, (y - 1) as u32, color);
1023 }
1024 let lx = bx0.max(rx0 + inset);
1025 let hx = bx1.min(rx1 - inset - 1);
1026 if hx >= lx && y >= 0 {
1027 self.boxfill(lx.max(0) as u32, y as u32, hx.max(0) as u32, y as u32, color);
1028 }
1029 }
1030 if let Some(s0) = run_start {
1031 self.boxfill(x0, s0 as u32, x1, y1, color);
1032 }
1033 self.round_clip.set(Some((rx0, ry0, rx1, ry1, r)));
1034 return;
1035 }
1036 }
1037 if a != 0xFF {
1038 self.boxfill_alpha(x0, y0, x1, y1, color, a as u8);
1039 return;
1040 }
1041
1042 let x0 = if x0 >= 0x8000_0000 {
1044 if x1 >= 0x8000_0000 { return; }
1045 0
1046 } else {
1047 x0
1048 };
1049 let y0 = if y0 >= 0x8000_0000 {
1050 if y1 >= 0x8000_0000 { return; }
1051 0
1052 } else {
1053 y0
1054 };
1055
1056 let (target_ptr, w, h) = self.resolve_target();
1057 if (color.0 & 0x00FF_FFFF) == 0x00FF_6B6B {
1062 static BG_DIAG: core::sync::atomic::AtomicU32 =
1063 core::sync::atomic::AtomicU32::new(0);
1064 if BG_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 3 {
1065 crate::warn!(
1066 "[TARGET][BG] hero fill target=0x{:x} w={} h={} rect=({},{})-({},{})",
1067 target_ptr as usize, w, h, x0, y0, x1, y1
1068 );
1069 }
1070 }
1071 let (cx0, cy0, cx1, cy1) =
1078 match crate::kernel::draw_clip::clip_fill_rect(x0, y0, x1, y1, w, h, self.clip.get()) {
1079 Some(r) => r,
1080 None => return,
1081 };
1082
1083 let rect_w = cx1 - cx0 + 1;
1084 let rect_h = cy1 - cy0 + 1;
1085 let stride = self.resolve_stride();
1086
1087 if rect_w * rect_h >= DMA_THRESHOLD_PIXELS {
1088 let dst_offset = (cy0 * stride + cx0) * 4;
1089 let color_val = color.to_hardware_format();
1090 crate::kernel::dma::dma_fill_rect(
1091 5,
1092 &color_val as *const _ as u32,
1093 target_ptr as u32 + dst_offset,
1094 rect_w,
1095 rect_h,
1096 stride * 4,
1097 );
1098 } else {
1099 let c = color.to_hardware_format();
1100 for y in cy0..=cy1 {
1101 unsafe {
1102 let row_ptr = target_ptr.add((y * stride + cx0) as usize);
1103 let count = (cx1 - cx0 + 1) as usize;
1104 let slice = core::slice::from_raw_parts_mut(row_ptr, count);
1105 slice.fill(c);
1106 }
1107 }
1108 }
1109 }
1110
1111 pub fn boxfill_alpha(&self, x0: u32, y0: u32, x1: u32, y1: u32, color: Color, alpha: u8) {
1115 if alpha == 0 {
1116 return;
1117 }
1118
1119 let x0 = if x0 >= 0x8000_0000 {
1121 if x1 >= 0x8000_0000 { return; }
1122 0
1123 } else {
1124 x0
1125 };
1126 let y0 = if y0 >= 0x8000_0000 {
1127 if y1 >= 0x8000_0000 { return; }
1128 0
1129 } else {
1130 y0
1131 };
1132
1133 if alpha == 255 {
1134 self.boxfill(x0, y0, x1, y1, color);
1135 return;
1136 }
1137 if self.round_clip.get().is_some() {
1139 let (target_w, target_h) = {
1140 let (_, w, h) = self.resolve_target();
1141 (w, h)
1142 };
1143 if let Some((cx0, cy0, cx1, cy1)) = crate::kernel::draw_clip::clip_fill_rect(
1144 x0,
1145 y0,
1146 x1,
1147 y1,
1148 target_w,
1149 target_h,
1150 self.clip.get(),
1151 ) {
1152 for y in cy0..=cy1 {
1153 for x in cx0..=cx1 {
1154 self.draw_pixel_alpha(x, y, color, alpha);
1155 }
1156 }
1157 }
1158 return;
1159 }
1160
1161 let (target_ptr, w, h) = self.resolve_target();
1162 let (cx0, cy0, cx1, cy1) =
1163 match crate::kernel::draw_clip::clip_fill_rect(x0, y0, x1, y1, w, h, self.clip.get()) {
1164 Some(r) => r,
1165 None => return,
1166 };
1167
1168 let rect_w = cx1 - cx0 + 1;
1169 let stride = self.resolve_stride();
1170
1171 let a = alpha as u32;
1172 let inv_a = 255 - a;
1173 let fg_r = (color.0 >> 16) & 0xFF;
1174 let fg_g = (color.0 >> 8) & 0xFF;
1175 let fg_b = color.0 & 0xFF;
1176 let fg_r_term = fg_r * a + 127;
1177 let fg_g_term = fg_g * a + 127;
1178 let fg_b_term = fg_b * a + 127;
1179 let fg_rb_term = (fg_b_term << 16) | fg_r_term;
1180
1181 let rect_w_usize = rect_w as usize;
1182 let stride_usize = stride as usize;
1183
1184 for y in cy0..=cy1 {
1185 unsafe {
1186 let mut ptr = target_ptr.add(y as usize * stride_usize + cx0 as usize);
1187 for _ in 0..rect_w_usize {
1188 let bg_val = core::ptr::read_volatile(ptr);
1189 let bg_rb = bg_val & 0x00FF_00FF;
1192 let rb_prod = fg_rb_term + bg_rb * inv_a;
1193 let r = (rb_prod & 0xFFFF) / 255;
1194 let b = (rb_prod >> 16) / 255;
1195 let g = (fg_g_term + ((bg_val >> 8) & 0xFF) * inv_a) / 255;
1196
1197 core::ptr::write_volatile(ptr, 0xFF00_0000 | (b << 16) | (g << 8) | r);
1198 ptr = ptr.add(1);
1199 }
1200 }
1201 }
1202 }
1203
1204 pub fn draw_scanline_colors(&self, x0: i32, y: i32, colors: &[u32]) {
1208 if colors.is_empty() {
1209 return;
1210 }
1211
1212 if self.round_clip.get().is_some() {
1214 for (i, &col) in colors.iter().enumerate() {
1215 let px = x0 + i as i32;
1216 if px >= 0 {
1217 let a = ((col >> 24) & 0xFF) as u8;
1218 self.draw_pixel_alpha(px as u32, y as u32, Color(col), a);
1219 }
1220 }
1221 return;
1222 }
1223
1224 let (target_ptr, w, h) = self.resolve_target();
1225 if y < 0 || y >= h as i32 {
1226 return;
1227 }
1228 let (clip_x0, clip_y0, clip_x1, clip_y1) = match self.clip.get() {
1229 Some((cx0, cy0, cx1, cy1)) => (cx0.min(w), cy0.min(h), cx1.min(w), cy1.min(h)),
1230 None => (0, 0, w, h),
1231 };
1232 let uy = y as u32;
1233 if uy < clip_y0 || uy >= clip_y1 {
1234 return;
1235 }
1236
1237 let len = colors.len() as i32;
1238 let line_x0 = x0;
1239 let line_x1 = x0 + len;
1240 let vis_x0 = line_x0.max(clip_x0 as i32);
1241 let vis_x1 = line_x1.min(clip_x1 as i32);
1242 if vis_x0 >= vis_x1 {
1243 return;
1244 }
1245
1246 let src_start = (vis_x0 - line_x0) as usize;
1247 let count = (vis_x1 - vis_x0) as usize;
1248 let stride = self.resolve_stride() as usize;
1249
1250 unsafe {
1251 let mut ptr = target_ptr.add(y as usize * stride + vis_x0 as usize);
1252 for i in 0..count {
1253 let c = colors[src_start + i];
1254 let a = ((c >> 24) & 0xFF) as u32;
1255 if a == 0 {
1256 ptr = ptr.add(1);
1257 continue;
1258 }
1259 let r = (c >> 16) & 0xFF;
1260 let g = (c >> 8) & 0xFF;
1261 let b = c & 0xFF;
1262
1263 if a == 255 {
1264 let hw = 0xFF00_0000 | (b << 16) | (g << 8) | r;
1265 core::ptr::write_volatile(ptr, hw);
1266 } else {
1267 let inv_a = 255 - a;
1268 let bg_val = core::ptr::read_volatile(ptr);
1269 let bg_rb = bg_val & 0x00FF_00FF;
1270 let fg_rb_term = ((b * a + 127) << 16) | (r * a + 127);
1271 let rb_prod = fg_rb_term + bg_rb * inv_a;
1272 let out_r = (rb_prod & 0xFFFF) / 255;
1273 let out_b = (rb_prod >> 16) / 255;
1274 let out_g = ((g * a + 127) + ((bg_val >> 8) & 0xFF) * inv_a) / 255;
1275 core::ptr::write_volatile(ptr, 0xFF00_0000 | (out_b << 16) | (out_g << 8) | out_r);
1276 }
1277 ptr = ptr.add(1);
1278 }
1279 }
1280 }
1281
1282 pub fn draw_char(&self, x: u32, y: u32, c: char, color: Color) {
1283 let code = c as usize;
1284 if code >= 256 {
1285 return;
1286 }
1287 let font = &FONT_DATA[code];
1288 let (target_ptr, w, h) = self.resolve_target();
1289 let stride = self.resolve_stride();
1290 let clip = self.clip.get();
1291 let hw_color = color.to_hardware_format();
1292
1293 for i in 0..16u32 {
1294 let py = y + i;
1295 if py >= h {
1296 continue;
1297 }
1298 if let Some((_, cy0, _, cy1)) = clip {
1299 if py < cy0 || py >= cy1 {
1300 continue;
1301 }
1302 }
1303 let d = font[i as usize];
1304 if d == 0 {
1305 continue;
1306 }
1307 unsafe {
1308 let row_ptr = target_ptr.add((py * stride) as usize);
1309 for bit in 0..8u32 {
1310 if (d & (0x80 >> bit)) != 0 {
1311 let px = x + bit;
1312 if px < w {
1313 if let Some((cx0, _, cx1, _)) = clip {
1314 if px < cx0 || px >= cx1 {
1315 continue;
1316 }
1317 }
1318 core::ptr::write_volatile(row_ptr.add(px as usize), hw_color);
1319 }
1320 }
1321 }
1322 }
1323 }
1324 }
1325
1326 pub fn draw_string(&self, x: u32, y: u32, s: &str, color: Color) {
1327 let mut cur_x = x;
1328 for c in s.chars() {
1329 self.draw_char(cur_x, y, c, color);
1330 cur_x += 8;
1331 }
1332 }
1333
1334 pub fn measure_string_monospace(&self, s: &str) -> u32 {
1335 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1336 if let Some(ref mut font) = *font_lock {
1337 font.get_string_width(s, 16)
1338 } else {
1339 let mut width = 0;
1340 for c in s.chars() {
1341 width += if (c as u32) < 128 { 8 } else { 16 };
1342 }
1343 width
1344 }
1345 }
1346
1347 pub fn draw_string_monospace(&self, x: u32, y: u32, s: &str, color: Color) {
1348 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1349 if let Some(font) = font_lock.as_mut() {
1350 let mut cur_x = x as i32;
1351 let mut cur_y = y as i32;
1352 let (target_ptr, w, h) = self.resolve_target();
1353 let stride = self.resolve_stride();
1354 let clip = self.clip.get();
1355
1356 for c in s.chars() {
1357 if c == '\n' {
1358 cur_x = x as i32;
1359 cur_y += 16;
1360 continue;
1361 }
1362
1363 if let Some(glyph) = font.get_glyph(c, 16) {
1364 let gx = cur_x + glyph.x_offset;
1365 let gy = cur_y + glyph.y_offset + (16 * 14 / 16);
1366
1367 for row in 0..glyph.height {
1368 let py = gy + row as i32;
1369 if py < 0 || py >= h as i32 {
1370 continue;
1371 }
1372 if let Some((_, cy0, _, cy1)) = clip {
1373 if (py as u32) < cy0 || (py as u32) >= cy1 {
1374 continue;
1375 }
1376 }
1377 let row_offset = (py as usize) * (stride as usize);
1378
1379 for col in 0..glyph.width {
1380 let px = gx + col as i32;
1381 if px < 0 || px >= w as i32 {
1382 continue;
1383 }
1384 if let Some((cx0, _, cx1, _)) = clip {
1385 if (px as u32) < cx0 || (px as u32) >= cx1 {
1386 continue;
1387 }
1388 }
1389 let alpha = glyph.data[(row * glyph.width + col) as usize];
1390 if alpha > 0 {
1391 unsafe {
1392 let ptr = target_ptr.add(row_offset + px as usize);
1393 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(ptr)).0 | 0xFF00_0000;
1394 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(
1395 ((alpha as u32) << 24) | (color.0 & 0x00FF_FFFF),
1396 bg_val,
1397 );
1398 core::ptr::write_volatile(ptr, Color(blended).to_hardware_format());
1399 }
1400 }
1401 }
1402 }
1403 cur_x += glyph.advance as i32;
1404 } else {
1405 let char_width = if (c as u32) > 127 { 16 } else { 8 };
1406 cur_x += char_width;
1407 }
1408
1409 if cur_x > w as i32 - 16 {
1410 break;
1411 }
1412 }
1413 } else {
1414 let mut cur_x = x;
1415 let (_, w, _) = self.resolve_target();
1416 for c in s.chars() {
1417 if c == '\n' {
1418 continue;
1419 }
1420 let code = c as u32;
1421 if code < 128 {
1422 self.draw_char(cur_x, y, c, color);
1423 cur_x += 8;
1424 } else {
1425 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1426 self.draw_char_ja_16x16(cur_x, y, bitmap, color);
1427 } else {
1428 self.draw_char_ja_tofu(cur_x, y, color);
1429 }
1430 cur_x += 16;
1431 }
1432 if cur_x >= w.saturating_sub(8) {
1433 break;
1434 }
1435 }
1436 }
1437 }
1438
1439 pub fn draw_string_monospace_bg(&self, x: u32, y: u32, s: &str, color: Color, bg: Color) {
1441 let mut font_lock = crate::kernel::vector_font::GLOBAL_TERMINAL_FONT.lock();
1442 if let Some(ref mut font) = *font_lock {
1443 let mut cur_x = x as i32;
1444 let mut cur_y = y as i32;
1445 let (_, w, h) = self.resolve_target();
1446
1447 for c in s.chars() {
1448 if c == '\n' {
1449 cur_x = x as i32;
1450 cur_y += 16;
1451 continue;
1452 }
1453
1454 if let Some(glyph) = font.get_color_glyph(c, 16, color.0, bg.0) {
1455 let gx = cur_x + glyph.x_offset;
1456 let gy = cur_y + glyph.y_offset + (16 * 14 / 16);
1457
1458 for row in 0..glyph.height {
1459 let py = gy + row as i32;
1460 if py < 0 || py >= h as i32 {
1461 continue;
1462 }
1463 for col in 0..glyph.width {
1464 let px = gx + col as i32;
1465 if px < 0 || px >= w as i32 {
1466 continue;
1467 }
1468 let pixel_color = glyph.data[(row * glyph.width + col) as usize];
1469 if (pixel_color >> 24) > 0 {
1470 self.draw_pixel(px as u32, py as u32, Color(pixel_color));
1472 }
1473 }
1474 }
1475 cur_x += glyph.advance as i32;
1476 } else {
1477 let char_width = if (c as u32) > 127 { 16 } else { 8 };
1478 cur_x += char_width;
1479 }
1480
1481 if cur_x > w as i32 - 16 {
1482 break;
1483 }
1484 }
1485 } else {
1486 let mut cur_x = x;
1488 let (_, w, _) = self.resolve_target();
1489 for c in s.chars() {
1490 if c == '\n' {
1491 continue;
1492 }
1493 let code = c as u32;
1494 let char_w = if code < 128 { 8u32 } else { 16u32 };
1495 self.boxfill(cur_x, y, cur_x + char_w, y + 16, bg);
1497 if code < 128 {
1498 self.draw_char(cur_x, y, c, color);
1499 cur_x += 8;
1500 } else {
1501 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1502 self.draw_char_ja_16x16(cur_x, y, bitmap, color);
1503 } else {
1504 self.draw_char_ja_tofu(cur_x, y, color);
1505 }
1506 cur_x += 16;
1507 }
1508 if cur_x >= w.saturating_sub(8) {
1509 break;
1510 }
1511 }
1512 }
1513 }
1514
1515 pub fn measure_string_ja(&self, s: &str, size: u32) -> u32 {
1516 let mut width = 0;
1517 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1518 if let Some(ref mut font) = *font_lock {
1519 for c in s.chars() {
1520 if let Some(glyph) = font.get_glyph(c, size) {
1521 width += glyph.advance;
1522 } else {
1523 width += if (c as u32) < 128 { size / 2 } else { size };
1524 }
1525 }
1526 } else {
1527 for c in s.chars() {
1528 width += if (c as u32) < 128 { 8 } else { 16 };
1529 }
1530 }
1531 width
1532 }
1533
1534 pub fn draw_string_ja(&self, x: u32, y: u32, s: &str, color: Color) {
1536 self.draw_string_ja_size(x, y, s, 16, color);
1537 }
1538
1539 pub fn draw_string_ja_size(&self, x: u32, y: u32, s: &str, size: u32, color: Color) {
1540 self.draw_string_ja_size_ext(x, y, s, size, color, false);
1541 }
1542
1543 pub fn draw_string_ja_size_ext(
1544 &self,
1545 x: u32,
1546 y: u32,
1547 s: &str,
1548 size: u32,
1549 color: Color,
1550 italic: bool,
1551 ) {
1552 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1553 if let Some(font) = font_lock.as_mut() {
1554 let mut cur_x = x as i32;
1555 let mut cur_y = y as i32;
1556 let (_, w, h) = self.resolve_target();
1557
1558 for c in s.chars() {
1559 if c == '\n' {
1560 cur_x = x as i32;
1561 cur_y += size as i32;
1562 continue;
1563 }
1564
1565 if let Some(glyph) = font.get_glyph(c, size) {
1566 let gx = cur_x + glyph.x_offset;
1567 let gy = cur_y + glyph.y_offset + (size as i32 * 14 / 16);
1568
1569 for row in 0..glyph.height {
1570 let py = gy + row as i32;
1571 if py < 0 || py >= h as i32 {
1572 continue;
1573 }
1574 for col in 0..glyph.width {
1575 let mut px = gx + col as i32;
1576 if italic {
1577 let shift = ((glyph.height as i32 - 1 - row as i32) * 2) / 7;
1581 px += shift;
1582 }
1583 if px < 0 || px >= w as i32 {
1584 continue;
1585 }
1586 let alpha = glyph.data[(row * glyph.width + col) as usize];
1587 self.draw_pixel_alpha(px as u32, py as u32, color, alpha);
1588 }
1589 }
1590 cur_x += glyph.advance as i32;
1591 } else {
1592 let code = c as u32;
1593 let char_width = if code > 127 {
1594 size as i32
1595 } else {
1596 size as i32 / 2
1597 };
1598 if code < 128 {
1599 let mut cx = cur_x;
1600 if italic {
1601 cx += 2;
1602 }
1603 self.draw_char(cx as u32, cur_y as u32, c, color);
1604 } else {
1605 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1607 let mut cx = cur_x;
1610 if italic {
1611 cx += 3;
1612 }
1613 self.draw_char_ja_16x16(cx as u32, cur_y as u32, bitmap, color);
1614 } else {
1615 self.draw_char_ja_tofu(cur_x as u32, cur_y as u32, color);
1616 }
1617 }
1618 cur_x += char_width;
1619 }
1620
1621 if cur_x > w as i32 - size as i32 {
1622 cur_x = x as i32;
1623 cur_y += size as i32;
1624 if cur_y >= h as i32 {
1625 break;
1626 }
1627 }
1628 }
1629 } else {
1630 let mut cur_x = x;
1631 for c in s.chars() {
1632 let code = c as u32;
1633 let mut cx = cur_x;
1634 if italic {
1635 cx += 2;
1636 }
1637 if code < 128 {
1638 self.draw_char(cx, y, c, color);
1639 cur_x += size / 2;
1640 } else {
1641 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1642 self.draw_char_ja_16x16(cx, y, bitmap, color);
1643 } else {
1644 self.draw_char_ja_tofu(cx, y, color);
1645 }
1646 cur_x += size;
1647 }
1648 }
1649 }
1650 }
1651
1652 pub fn draw_string_ja_size_bg(
1653 &self,
1654 x: u32,
1655 y: u32,
1656 s: &str,
1657 size: u32,
1658 color: Color,
1659 bg: Color,
1660 ) {
1661 self.draw_string_ja_size_ext_bg(x, y, s, size, color, false, bg);
1662 }
1663
1664 pub fn draw_string_ja_size_ext_bg(
1665 &self,
1666 x: u32,
1667 y: u32,
1668 s: &str,
1669 size: u32,
1670 color: Color,
1671 italic: bool,
1672 bg: Color,
1673 ) {
1674 self.draw_string_ja_size_ext_bg_ls(x, y, s, size, color, italic, bg, 0);
1675 }
1676
1677 #[allow(clippy::too_many_arguments)]
1679 pub fn draw_string_ja_size_ext_bg_ls(
1680 &self,
1681 x: u32,
1682 y: u32,
1683 s: &str,
1684 size: u32,
1685 color: Color,
1686 italic: bool,
1687 bg: Color,
1688 ls: i32,
1689 ) {
1690 self.draw_string_ja_size_ext_bg_ls_ws(x, y, s, size, color, italic, bg, ls, 0);
1691 }
1692
1693 #[allow(clippy::too_many_arguments)]
1695 #[allow(clippy::too_many_arguments)]
1718 pub fn draw_string_family(
1719 &self,
1720 x: u32,
1721 y: u32,
1722 s: &str,
1723 size: u32,
1724 color: Color,
1725 italic: bool,
1726 bg: Color,
1727 ls: i32,
1728 ws: i32,
1729 family_list: &str,
1730 weight: u16,
1731 ) -> u16 {
1732 if !family_list.is_empty() {
1733 let drawn = crate::kernel::vector_font::with_family_font_ext(s, family_list, weight, |font, got| {
1737 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1738 got
1739 });
1740 if let Some(got) = drawn {
1741 return got;
1742 }
1743 if !family_list.is_empty() {
1747 static MISS: spin::Mutex<alloc::collections::BTreeSet<alloc::string::String>> =
1748 spin::Mutex::new(alloc::collections::BTreeSet::new());
1749 let mut m = MISS.lock();
1750 if m.insert(alloc::string::String::from(family_list)) {
1751 crate::warn!("[FONT] 該当フォント無しで既定へ family={:?}", family_list);
1752 }
1753 }
1754 }
1755 self.draw_string_ja_size_ext_bg_ls_ws(x, y, s, size, color, italic, bg, ls, ws);
1756 0
1757 }
1758
1759 #[allow(clippy::too_many_arguments)]
1760 pub fn draw_string_ja_size_ext_bg_ls_ws(
1761 &self,
1762 x: u32,
1763 y: u32,
1764 s: &str,
1765 size: u32,
1766 color: Color,
1767 italic: bool,
1768 bg: Color,
1769 ls: i32,
1770 ws: i32,
1771 ) {
1772 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
1773 if let Some(font) = font_lock.as_mut() {
1774 self.draw_glyphs_with_font(font, x, y, s, size, color, italic, bg, ls, ws);
1775 }
1776 }
1777
1778 #[allow(clippy::too_many_arguments)]
1780 fn draw_glyphs_with_font(
1781 &self,
1782 font: &mut crate::kernel::vector_font::VectorFont,
1783 x: u32,
1784 y: u32,
1785 s: &str,
1786 size: u32,
1787 color: Color,
1788 italic: bool,
1789 bg: Color,
1790 ls: i32,
1791 ws: i32,
1792 ) {
1793 let font_opt: Option<&mut crate::kernel::vector_font::VectorFont> = Some(font);
1801 if let Some(font) = font_opt {
1802 let mut cur_x = x as i32;
1803 let mut cur_y = y as i32;
1804 let (target_ptr, w, h) = self.resolve_target();
1805 let stride = self.resolve_stride();
1806 let clip = self.clip.get();
1807
1808 for c in s.chars() {
1809 if c == '\n' {
1810 cur_x = x as i32;
1811 cur_y += size as i32;
1812 continue;
1813 }
1814
1815 if size >= 40 {
1817 static GLYPH_DIAG: core::sync::atomic::AtomicU32 =
1818 core::sync::atomic::AtomicU32::new(0);
1819 let n = GLYPH_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1820 if n < 6 {
1821 let g = font.get_color_glyph(c, size, color.0, bg.0);
1822 match g {
1823 Some(gl) => crate::warn!(
1824 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} cur=({},{}) gw={} gh={} xoff={} yoff={} adv={} px0=0x{:08x}",
1825 c, size, w, h, cur_x, cur_y,
1826 gl.width, gl.height, gl.x_offset, gl.y_offset, gl.advance,
1827 gl.data.first().copied().unwrap_or(0)
1828 ),
1829 None => crate::warn!(
1830 "[GLYPH][DIAG] c={:?} size={} target_w={} target_h={} -> NO GLYPH (fallback path)",
1831 c, size, w, h
1832 ),
1833 }
1834 }
1835 }
1836
1837 if let Some(glyph) = font.get_color_glyph(c, size, color.0, bg.0) {
1838 let gx = cur_x + glyph.x_offset;
1839 let gy = cur_y + glyph.y_offset + (size as i32 * 14 / 16);
1840
1841 let mut written = 0u32;
1842
1843 let clip_y0 = clip.map(|c| c.1 as i32).unwrap_or(0).max(0);
1844 let clip_y1 = clip.map(|c| c.3 as i32).unwrap_or(h as i32).min(h as i32);
1845 let row_start = (clip_y0 - gy).clamp(0, glyph.height as i32) as usize;
1846 let row_end = (clip_y1 - gy).clamp(0, glyph.height as i32) as usize;
1847
1848 if !italic {
1849 let clip_x0 = clip.map(|c| c.0 as i32).unwrap_or(0).max(0);
1850 let clip_x1 = clip.map(|c| c.2 as i32).unwrap_or(w as i32).min(w as i32);
1851 let col_start = (clip_x0 - gx).clamp(0, glyph.width as i32) as usize;
1852 let col_end = (clip_x1 - gx).clamp(0, glyph.width as i32) as usize;
1853
1854 if row_start < row_end && col_start < col_end {
1855 let gw = glyph.width as usize;
1856 let stride_usize = stride as usize;
1857 for row in row_start..row_end {
1858 let py = (gy + row as i32) as usize;
1859 let row_offset = py * stride_usize;
1860 let glyph_row_offset = row * gw;
1861 unsafe {
1862 let mut dst_ptr = target_ptr.add(row_offset + ((gx + col_start as i32) as usize));
1863 let mut src_idx = glyph_row_offset + col_start;
1864 for _ in col_start..col_end {
1865 let pixel_color = *glyph.data.get_unchecked(src_idx);
1866 let a = (pixel_color >> 24) & 0xFF;
1867 if a == 0xFF {
1868 let hw = Color(pixel_color).to_hardware_format();
1869 core::ptr::write_volatile(dst_ptr, hw);
1870 written += 1;
1871 } else if a > 0 {
1872 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(dst_ptr)).0 | 0xFF00_0000;
1873 let eff_a = crate::os_lib::web_engine::font_gamma::correct_glyph_coverage(
1874 a as u8,
1875 pixel_color,
1876 bg_val,
1877 );
1878 let adjusted_pixel_color = ((eff_a as u32) << 24) | (pixel_color & 0x00FF_FFFF);
1879 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(adjusted_pixel_color, bg_val);
1880 core::ptr::write_volatile(dst_ptr, Color(blended).to_hardware_format());
1881 written += 1;
1882 }
1883 dst_ptr = dst_ptr.add(1);
1884 src_idx += 1;
1885 }
1886 }
1887 }
1888 }
1889 } else {
1890 let gw = glyph.width as usize;
1892 let gh = glyph.height as i32;
1893 for row in row_start..row_end {
1894 let py = gy + row as i32;
1895 let row_offset = (py as usize) * (stride as usize);
1896 let shift = ((gh - 1 - row as i32) * 2) / 7;
1897
1898 for col in 0..glyph.width {
1899 let px = gx + col as i32 + shift;
1900 if px < 0 || px >= w as i32 {
1901 continue;
1902 }
1903 if let Some((cx0, _, cx1, _)) = clip {
1904 if (px as u32) < cx0 || (px as u32) >= cx1 {
1905 continue;
1906 }
1907 }
1908 let pixel_color = glyph.data[row * gw + col as usize];
1909 let a = (pixel_color >> 24) & 0xFF;
1910 if a > 0 {
1911 unsafe {
1912 let ptr = target_ptr.add(row_offset + px as usize);
1913 if a == 0xFF {
1914 core::ptr::write_volatile(ptr, Color(pixel_color).to_hardware_format());
1915 } else {
1916 let bg_val = Color::from_hardware_format(core::ptr::read_volatile(ptr)).0 | 0xFF00_0000;
1917 let eff_a = crate::os_lib::web_engine::font_gamma::correct_glyph_coverage(
1918 a as u8,
1919 pixel_color,
1920 bg_val,
1921 );
1922 let adjusted_pixel_color = ((eff_a as u32) << 24) | (pixel_color & 0x00FF_FFFF);
1923 let blended = crate::os_lib::web_engine::alpha_blend::blend_argb_over(adjusted_pixel_color, bg_val);
1924 core::ptr::write_volatile(ptr, Color(blended).to_hardware_format());
1925 }
1926 }
1927 written += 1;
1928 }
1929 }
1930 }
1931 }
1932 if size >= 40 {
1933 static PX_DIAG: core::sync::atomic::AtomicU32 =
1934 core::sync::atomic::AtomicU32::new(0);
1935 HERO_TEXT_FRAME.store(
1938 FRAME_NO.load(core::sync::atomic::Ordering::Relaxed),
1939 core::sync::atomic::Ordering::Relaxed,
1940 );
1941 if PX_DIAG.fetch_add(1, core::sync::atomic::Ordering::Relaxed) < 4 {
1942 let (tp, tw, th) = self.resolve_target();
1943 crate::warn!(
1947 "[TARGET][TEXT] c={:?} target=0x{:x} w={} h={} calls={} gx={} gy={} clip={:?} rt=0x{:x} bb=0x{:x} vram=0x{:x} use_bb={}",
1948 c, tp as usize, tw, th, written, gx, gy, self.clip.get(),
1949 self.render_target.get() as usize,
1950 self.back_buffer as usize,
1951 self.vram as usize,
1952 self.use_back_buffer.get()
1953 );
1954 }
1955 }
1956 cur_x += glyph.advance as i32 + ls + if c == ' ' { ws } else { 0 };
1957 } else {
1958 let code = c as u32;
1959 let char_width = if code > 127 {
1960 size as i32
1961 } else {
1962 size as i32 / 2
1963 };
1964 if code < 128 {
1965 let mut cx = cur_x;
1966 if italic {
1967 cx += 2;
1968 }
1969 self.boxfill(
1971 cx as u32,
1972 cur_y as u32,
1973 cx as u32 + (size / 2),
1974 cur_y as u32 + size,
1975 bg,
1976 );
1977 self.draw_char(cx as u32, cur_y as u32, c, color);
1978 } else {
1979 let mut cx = cur_x;
1981 if italic {
1982 cx += 3;
1983 }
1984 self.boxfill(
1985 cx as u32,
1986 cur_y as u32,
1987 cx as u32 + size,
1988 cur_y as u32 + size,
1989 bg,
1990 );
1991 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
1992 self.draw_char_ja_16x16(cx as u32, cur_y as u32, bitmap, color);
1993 } else {
1994 self.draw_char_ja_tofu(cx as u32, cur_y as u32, color);
1995 }
1996 }
1997 cur_x += char_width + ls + if c == ' ' { ws } else { 0 };
1998 }
1999
2000 if cur_x > w as i32 - size as i32 {
2001 cur_x = x as i32;
2002 cur_y += size as i32;
2003 if cur_y >= h as i32 {
2004 break;
2005 }
2006 }
2007 }
2008 } else {
2009 let mut cur_x = x;
2011 let (_, _w, _) = self.resolve_target();
2012 for c in s.chars() {
2013 let code = c as u32;
2014 let mut cx = cur_x;
2015 if italic {
2016 cx += 2;
2017 }
2018 let char_w = if code < 128 { size / 2 } else { size };
2019 self.boxfill(cx, y, cx + char_w, y + size, bg);
2020 if code < 128 {
2021 self.draw_char(cx, y, c, color);
2022 cur_x = (cur_x as i32 + size as i32 / 2 + ls).max(0) as u32;
2023 } else {
2024 if let Some(bitmap) = crate::kernel::font_ja::get_font_bitmap(code) {
2025 self.draw_char_ja_16x16(cx, y, bitmap, color);
2026 } else {
2027 self.draw_char_ja_tofu(cx, y, color);
2028 }
2029 cur_x = (cur_x as i32 + size as i32 + ls).max(0) as u32;
2030 }
2031 }
2032 }
2033 }
2034
2035 pub fn draw_text_emphasis(
2042 &self,
2043 x: u32,
2044 y: u32,
2045 s: &str,
2046 size: u32,
2047 mark: &str,
2048 color: Color,
2049 ls: i32,
2050 ws: i32,
2051 ) {
2052 if mark.is_empty() {
2053 return;
2054 }
2055 let mut font_lock = crate::kernel::vector_font::GLOBAL_VECTOR_FONT.lock();
2056 let Some(font) = font_lock.as_mut() else {
2057 return;
2058 };
2059 let mut cur_x = x as i32;
2060 let mut cur_y = y as i32;
2061 let (_, w, h) = self.resolve_target();
2062 let r = ((size as i32) / 6).max(1);
2064 for c in s.chars() {
2065 if c == '\n' {
2066 cur_x = x as i32;
2067 cur_y += size as i32;
2068 continue;
2069 }
2070 let advance = if let Some(glyph) = font.get_color_glyph(c, size, color.0, color.0) {
2072 glyph.advance as i32
2073 } else {
2074 let code = c as u32;
2075 if code > 127 {
2076 size as i32
2077 } else {
2078 size as i32 / 2
2079 }
2080 };
2081 if !c.is_whitespace() {
2082 let cx = cur_x + advance / 2;
2083 let cy = cur_y - r * 3;
2084 if cx >= 0 && cy >= 0 && (cx as u32) < w && (cy as u32) < h {
2085 match mark {
2086 "circle" => self.draw_circle_outline(cx as u32, cy as u32, r as u32, color),
2087 "triangle" => self.draw_triangle_fill(cx as u32, cy as u32, r as u32, color),
2088 "sesame" => {
2089 self.draw_circle_fill((cx - r) as u32, cy as u32, (r / 2).max(1) as u32, color);
2090 self.draw_circle_fill((cx + r) as u32, cy as u32, (r / 2).max(1) as u32, color);
2091 }
2092 _ => self.draw_circle_fill(cx as u32, cy as u32, r as u32, color),
2095 }
2096 }
2097 }
2098 cur_x += advance + ls + if c == ' ' { ws } else { 0 };
2099 }
2100 }
2101
2102 fn draw_circle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
2104 let r_i = r as i32;
2105 for dy in -r_i..=r_i {
2106 for dx in -r_i..=r_i {
2107 if dx * dx + dy * dy <= r_i * r_i {
2108 let px = cx as i32 + dx;
2109 let py = cy as i32 + dy;
2110 if px >= 0 && py >= 0 {
2111 self.draw_pixel(px as u32, py as u32, color);
2112 }
2113 }
2114 }
2115 }
2116 }
2117
2118 fn draw_circle_outline(&self, cx: u32, cy: u32, r: u32, color: Color) {
2120 let r_i = r as i32;
2121 let r_inner = (r_i - 1).max(0);
2122 for dy in -r_i..=r_i {
2123 for dx in -r_i..=r_i {
2124 let d2 = dx * dx + dy * dy;
2125 if d2 <= r_i * r_i && d2 >= r_inner * r_inner {
2126 let px = cx as i32 + dx;
2127 let py = cy as i32 + dy;
2128 if px >= 0 && py >= 0 {
2129 self.draw_pixel(px as u32, py as u32, color);
2130 }
2131 }
2132 }
2133 }
2134 }
2135
2136 fn draw_triangle_fill(&self, cx: u32, cy: u32, r: u32, color: Color) {
2138 let r_i = r as i32;
2139 for dy in 0..=(r_i * 2) {
2140 let py = cy as i32 - r_i + dy;
2141 if py < 0 {
2142 continue;
2143 }
2144 let half_w = (dy * r_i) / (r_i * 2).max(1);
2145 for dx in -half_w..=half_w {
2146 let px = cx as i32 + dx;
2147 if px >= 0 {
2148 self.draw_pixel(px as u32, py as u32, color);
2149 }
2150 }
2151 }
2152 }
2153
2154 fn draw_char_ja_16x16(&self, x: u32, y: u32, bitmap: &[u8; 8], color: Color) {
2156 for row in 0..8 {
2157 let byte = bitmap[row];
2158 for col in 0..8 {
2159 if (byte & (0x80 >> col)) != 0 {
2160 let px = x + col as u32 * 2;
2161 let py = y + row as u32 * 2;
2162 self.draw_pixel(px, py, color);
2163 self.draw_pixel(px + 1, py, color);
2164 self.draw_pixel(px, py + 1, color);
2165 self.draw_pixel(px + 1, py + 1, color);
2166 }
2167 }
2168 }
2169 }
2170
2171 fn draw_char_ja_tofu(&self, x: u32, y: u32, color: Color) {
2173 for dy in 0..16 {
2174 for dx in 0..16 {
2175 if dx == 0 || dx == 15 || dy == 0 || dy == 15 {
2176 self.draw_pixel(x + dx, y + dy, color);
2177 }
2178 }
2179 }
2180 }
2181
2182 pub fn draw_svg_icon(&self, x: u32, y: u32, svg_data: &[u8], size: u32, tint: Color) {
2188 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(svg_data, size) {
2189 for row in 0..size {
2190 for col in 0..size {
2191 let alpha = alpha_mask[(row * size + col) as usize];
2192 if alpha > 0 {
2193 self.draw_pixel_alpha(x + col, y + row, tint, alpha);
2194 }
2195 }
2196 }
2197 }
2198 }
2199
2200 pub fn draw_bmp(
2202 &self,
2203 x: u32,
2204 y: u32,
2205 target_width: u32,
2206 target_height: u32,
2207 bmp_data: &[u8],
2208 ) -> Result<(), &'static str> {
2209 if bmp_data.len() < 54 {
2210 return Err("Data too short");
2211 }
2212 if &bmp_data[0..2] != b"BM" {
2213 return Err("Not a BMP file");
2214 }
2215
2216 let off_bits =
2217 u32::from_le_bytes(bmp_data[10..14].try_into().map_err(|_| "Invalid offset")?) as usize;
2218 let width = i32::from_le_bytes(bmp_data[18..22].try_into().map_err(|_| "Invalid width")?);
2219 let height = i32::from_le_bytes(bmp_data[22..26].try_into().map_err(|_| "Invalid height")?);
2220 let bit_count = u16::from_le_bytes(
2221 bmp_data[28..30]
2222 .try_into()
2223 .map_err(|_| "Invalid bitcount")?,
2224 );
2225 let compression = u32::from_le_bytes(
2226 bmp_data[30..34]
2227 .try_into()
2228 .map_err(|_| "Invalid compression")?,
2229 );
2230
2231 if compression != 0 {
2232 return Err("Only uncompressed BMP is supported");
2233 }
2234 if bit_count != 24 && bit_count != 32 {
2235 return Err("Only 24-bit or 32-bit BMP is supported");
2236 }
2237
2238 let abs_height = height.unsigned_abs();
2239 let is_bottom_up = height > 0;
2240 let bytes_per_pixel = (bit_count / 8) as usize;
2241 let row_stride = (width as usize * bytes_per_pixel).div_ceil(4) * 4;
2242
2243 if target_width == 0 || target_height == 0 {
2244 return Ok(());
2245 }
2246
2247 for row in 0..target_height {
2248 let src_row_idx = (row * abs_height) / target_height;
2249 let src_row = if is_bottom_up {
2250 abs_height - 1 - src_row_idx
2251 } else {
2252 src_row_idx
2253 };
2254 let row_offset = off_bits + (src_row as usize * row_stride);
2255 if row_offset + (width as usize * bytes_per_pixel) > bmp_data.len() {
2256 return Err("BMP pixel data out of bounds");
2257 }
2258 for col in 0..target_width {
2259 let src_col = (col * width as u32) / target_width;
2260 let pixel_offset = row_offset + (src_col as usize * bytes_per_pixel);
2261 let b = bmp_data[pixel_offset];
2262 let g = bmp_data[pixel_offset + 1];
2263 let r = bmp_data[pixel_offset + 2];
2264 let a = if bytes_per_pixel == 4 {
2265 bmp_data[pixel_offset + 3]
2266 } else {
2267 255
2268 };
2269
2270 let color = Color(0xFF000000 | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32));
2271 self.draw_pixel_alpha(x + col, y + row, color, a);
2272 }
2273 }
2274 Ok(())
2275 }
2276
2277 pub fn draw_image(
2279 &self,
2280 x: u32,
2281 y: u32,
2282 target_width: u32,
2283 target_height: u32,
2284 data: &[u8],
2285 ) -> Result<(), &'static str> {
2286 if target_width == 0 || target_height == 0 {
2287 return Ok(());
2288 }
2289
2290 if data.len() > 8 && &data[0..8] == b"\x89PNG\r\n\x1a\n" {
2291 let mut decoder = zune_png::PngDecoder::new(data);
2292 match decoder.decode_raw() {
2293 Ok(pixels) => {
2294 let info = decoder.get_info().ok_or("Failed to get PNG info")?;
2295 let width = info.width as u32;
2296 let height = info.height as u32;
2297 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
2298
2299 for row in 0..target_height {
2300 let src_row = (row * height) / target_height;
2301 for col in 0..target_width {
2302 let src_col = (col * width) / target_width;
2303 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
2304 if idx + bytes_per_pixel <= pixels.len() {
2305 let (r, g, b, a) = match bytes_per_pixel {
2306 1 => (pixels[idx], pixels[idx], pixels[idx], 255),
2307 2 => (pixels[idx], pixels[idx], pixels[idx], pixels[idx + 1]),
2308 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2], 255),
2309 4 => (
2310 pixels[idx],
2311 pixels[idx + 1],
2312 pixels[idx + 2],
2313 pixels[idx + 3],
2314 ),
2315 _ => (0, 0, 0, 255),
2316 };
2317 let color = Color(
2318 0xFF000000
2319 | ((r as u32) << 16)
2320 | ((g as u32) << 8)
2321 | (b as u32),
2322 );
2323 self.draw_pixel_alpha(x + col, y + row, color, a);
2324 }
2325 }
2326 }
2327 Ok(())
2328 }
2329 Err(_) => Err("Failed to decode PNG"),
2330 }
2331 } else if data.len() > 2 && data[0] == 0xFF && data[1] == 0xD8 {
2332 let mut decoder = zune_jpeg::JpegDecoder::new(data);
2333 match decoder.decode() {
2334 Ok(pixels) => {
2335 let info = decoder.info().ok_or("Failed to get JPEG info")?;
2336 let width = info.width as u32;
2337 let height = info.height as u32;
2338 let bytes_per_pixel = pixels.len() / (width as usize * height as usize).max(1);
2339
2340 for row in 0..target_height {
2341 let src_row = (row * height) / target_height;
2342 for col in 0..target_width {
2343 let src_col = (col * width) / target_width;
2344 let idx = ((src_row * width + src_col) as usize) * bytes_per_pixel;
2345 if idx + bytes_per_pixel <= pixels.len() {
2346 let (r, g, b) = match bytes_per_pixel {
2347 1 => (pixels[idx], pixels[idx], pixels[idx]),
2348 3 => (pixels[idx], pixels[idx + 1], pixels[idx + 2]),
2349 _ => (0, 0, 0),
2350 };
2351 let color = Color(
2352 0xFF000000
2353 | ((r as u32) << 16)
2354 | ((g as u32) << 8)
2355 | (b as u32),
2356 );
2357 self.draw_pixel_alpha(x + col, y + row, color, 255);
2358 }
2359 }
2360 }
2361 Ok(())
2362 }
2363 Err(_) => Err("Failed to decode JPEG"),
2364 }
2365 } else if data.len() > 2 && &data[0..2] == b"BM" {
2366 self.draw_bmp(x, y, target_width, target_height, data)
2367 } else {
2368 Err("Unsupported image format")
2369 }
2370 }
2371
2372 pub fn draw_gradient_rect(
2375 &self,
2376 x0: u32,
2377 y0: u32,
2378 x1: u32,
2379 y1: u32,
2380 c1: Color,
2381 c2: Color,
2382 vertical: bool,
2383 ) {
2384 if x1 <= x0 || y1 <= y0 {
2385 return;
2386 }
2387 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2388 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2389 let b1 = (c1.0 & 0xFF) as f32;
2390 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2391 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2392 let b2 = (c2.0 & 0xFF) as f32;
2393 if vertical {
2394 let h = (y1 - y0) as f32;
2395 for dy in 0..(y1 - y0) {
2396 let t = dy as f32 / h.max(1.0);
2397 let r = ((1.0 - t) * r1 + t * r2) as u32;
2398 let g = ((1.0 - t) * g1 + t * g2) as u32;
2399 let b = ((1.0 - t) * b1 + t * b2) as u32;
2400 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2401 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2402 }
2403 } else {
2404 let w = (x1 - x0) as f32;
2405 for dx in 0..(x1 - x0) {
2406 let t = dx as f32 / w.max(1.0);
2407 let r = ((1.0 - t) * r1 + t * r2) as u32;
2408 let g = ((1.0 - t) * g1 + t * g2) as u32;
2409 let b = ((1.0 - t) * b1 + t * b2) as u32;
2410 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2411 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2412 }
2413 }
2414 }
2415
2416 pub fn draw_radial_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2419 if x1 <= x0 || y1 <= y0 {
2420 return;
2421 }
2422 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2423 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2424 let b1 = (c1.0 & 0xFF) as f32;
2425 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2426 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2427 let b2 = (c2.0 & 0xFF) as f32;
2428 let w = (x1 - x0) as f32;
2429 let h = (y1 - y0) as f32;
2430 let cx = w / 2.0;
2431 let cy = h / 2.0;
2432 let max_dist = libm::sqrtf(cx * cx + cy * cy).max(1.0);
2433 for dy in 0..(y1 - y0) {
2434 for dx in 0..(x1 - x0) {
2435 let px = dx as f32 - cx;
2436 let py = dy as f32 - cy;
2437 let dist = libm::sqrtf(px * px + py * py);
2438 let t = (dist / max_dist).min(1.0);
2439 let r = ((1.0 - t) * r1 + t * r2) as u32;
2440 let g = ((1.0 - t) * g1 + t * g2) as u32;
2441 let b = ((1.0 - t) * b1 + t * b2) as u32;
2442 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2443 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2444 }
2445 }
2446 }
2447
2448 pub fn draw_conic_gradient_rect(&self, x0: u32, y0: u32, x1: u32, y1: u32, c1: Color, c2: Color) {
2453 if x1 <= x0 || y1 <= y0 {
2454 return;
2455 }
2456 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2457 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2458 let b1 = (c1.0 & 0xFF) as f32;
2459 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2460 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2461 let b2 = (c2.0 & 0xFF) as f32;
2462 let w = (x1 - x0) as f32;
2463 let h = (y1 - y0) as f32;
2464 let cx = w / 2.0;
2465 let cy = h / 2.0;
2466 const PI: f32 = core::f32::consts::PI;
2467 for dy in 0..(y1 - y0) {
2468 for dx in 0..(x1 - x0) {
2469 let px = dx as f32 - cx;
2470 let py = dy as f32 - cy;
2471 let mut angle = libm::atan2f(px, -py);
2473 if angle < 0.0 {
2474 angle += 2.0 * PI;
2475 }
2476 let t = angle / (2.0 * PI); let r = ((1.0 - t) * r1 + t * r2) as u32;
2478 let g = ((1.0 - t) * g1 + t * g2) as u32;
2479 let b = ((1.0 - t) * b1 + t * b2) as u32;
2480 let c = 0xFF000000 | (r << 16) | (g << 8) | b;
2481 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2482 }
2483 }
2484 }
2485
2486 fn sample_repeating_colors(colors: &[Color], t: f32) -> u32 {
2489 let Some(&first) = colors.first() else {
2490 return 0xFF000000;
2491 };
2492 if colors.len() < 2 {
2493 return first.0;
2494 }
2495 let scaled = t * (colors.len() - 1) as f32;
2496 let seg = (scaled as usize).min(colors.len() - 2);
2497 let local_t = scaled - seg as f32;
2498 let c1 = colors[seg];
2499 let c2 = colors[seg + 1];
2500 let r1 = ((c1.0 >> 16) & 0xFF) as f32;
2501 let g1 = ((c1.0 >> 8) & 0xFF) as f32;
2502 let b1 = (c1.0 & 0xFF) as f32;
2503 let r2 = ((c2.0 >> 16) & 0xFF) as f32;
2504 let g2 = ((c2.0 >> 8) & 0xFF) as f32;
2505 let b2 = (c2.0 & 0xFF) as f32;
2506 let r = ((1.0 - local_t) * r1 + local_t * r2) as u32;
2507 let g = ((1.0 - local_t) * g1 + local_t * g2) as u32;
2508 let b = ((1.0 - local_t) * b1 + local_t * b2) as u32;
2509 0xFF000000 | (r << 16) | (g << 8) | b
2510 }
2511
2512 pub fn draw_repeating_linear_gradient_rect(
2516 &self,
2517 x0: u32,
2518 y0: u32,
2519 x1: u32,
2520 y1: u32,
2521 colors: &[Color],
2522 cycle_px: i32,
2523 vertical: bool,
2524 ) {
2525 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2526 return;
2527 }
2528 let cycle = cycle_px.max(1) as f32;
2529 if vertical {
2530 for dy in 0..(y1 - y0) {
2531 let t = (dy as f32 % cycle) / cycle;
2532 let c = Self::sample_repeating_colors(colors, t);
2533 self.boxfill(x0, y0 + dy, x1, y0 + dy + 1, Color(c));
2534 }
2535 } else {
2536 for dx in 0..(x1 - x0) {
2537 let t = (dx as f32 % cycle) / cycle;
2538 let c = Self::sample_repeating_colors(colors, t);
2539 self.boxfill(x0 + dx, y0, x0 + dx + 1, y1, Color(c));
2540 }
2541 }
2542 }
2543
2544 pub fn draw_repeating_radial_gradient_rect(
2548 &self,
2549 x0: u32,
2550 y0: u32,
2551 x1: u32,
2552 y1: u32,
2553 colors: &[Color],
2554 cycle_px: i32,
2555 ) {
2556 if x1 <= x0 || y1 <= y0 || colors.is_empty() {
2557 return;
2558 }
2559 let cycle = cycle_px.max(1) as f32;
2560 let w = (x1 - x0) as f32;
2561 let h = (y1 - y0) as f32;
2562 let cx = w / 2.0;
2563 let cy = h / 2.0;
2564 for dy in 0..(y1 - y0) {
2565 for dx in 0..(x1 - x0) {
2566 let px = dx as f32 - cx;
2567 let py = dy as f32 - cy;
2568 let dist = libm::sqrtf(px * px + py * py);
2569 let t = (dist % cycle) / cycle;
2570 let c = Self::sample_repeating_colors(colors, t);
2571 self.boxfill(x0 + dx, y0 + dy, x0 + dx + 1, y0 + dy + 1, Color(c));
2572 }
2573 }
2574 }
2575
2576 pub fn draw_shadow_rect(
2578 &self,
2579 x0: i32,
2580 y0: i32,
2581 x1: i32,
2582 y1: i32,
2583 ox: i32,
2584 oy: i32,
2585 color: Color,
2586 ) {
2587 if x1 <= x0 || y1 <= y0 {
2588 return;
2589 }
2590 let sx0 = (x0 + ox).max(0) as u32;
2591 let sy0 = (y0 + oy).max(0) as u32;
2592 let sx1 = (x1 + ox).max(0) as u32;
2593 let sy1 = (y1 + oy).max(0) as u32;
2594 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2595 let alpha = if alpha == 0 { 80u8 } else { alpha }; if sx1 <= sx0 || sy1 <= sy0 {
2597 return;
2598 }
2599 for y in sy0..sy1 {
2600 for x in sx0..sx1 {
2601 self.draw_pixel_alpha(x, y, Color(color.0 | 0xFF000000), alpha);
2602 }
2603 }
2604 }
2605
2606 #[allow(clippy::too_many_arguments)]
2614 pub fn draw_shadow_rect_rounded(
2615 &self,
2616 x0: i32,
2617 y0: i32,
2618 x1: i32,
2619 y1: i32,
2620 ox: i32,
2621 oy: i32,
2622 blur: i32,
2623 radius: (i32, i32, i32, i32),
2624 color: Color,
2625 ) {
2626 if x1 <= x0 || y1 <= y0 {
2627 return;
2628 }
2629 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2630 let alpha = if alpha == 0 { 80u8 } else { alpha };
2631 let blur = blur.max(0);
2632 let half = (blur + 1) / 2;
2633 let (rx0, ry0, rx1, ry1) = (x0 + ox, y0 + oy, x1 + ox, y1 + oy);
2635 let sx0 = (rx0 - half).max(0);
2636 let sy0 = (ry0 - half).max(0);
2637 let sx1 = (rx1 + half).clamp(0, self.width as i32);
2638 let sy1 = (ry1 + half).clamp(0, self.height as i32);
2639 if sx1 <= sx0 || sy1 <= sy0 {
2640 return;
2641 }
2642 let w = x1 - x0;
2643 let h = y1 - y0;
2644 let has_radius = radius != (0, 0, 0, 0);
2645 let rgb = Color(color.0 | 0xFF000000);
2646
2647 for y in sy0..sy1 {
2648 for x in sx0..sx1 {
2649 let a = if blur == 0 {
2650 if has_radius
2651 && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(
2652 x - rx0,
2653 y - ry0,
2654 w,
2655 h,
2656 radius,
2657 )
2658 {
2659 continue;
2660 }
2661 alpha
2662 } else {
2663 let dx = (rx0 - x).max(x - (rx1 - 1)).max(0) as f32;
2665 let dy = (ry0 - y).max(y - (ry1 - 1)).max(0) as f32;
2666 let d = if dx > 0.0 || dy > 0.0 {
2667 libm::sqrtf(dx * dx + dy * dy)
2668 } else {
2669 -((x - rx0).min(rx1 - 1 - x).min(y - ry0).min(ry1 - 1 - y) as f32)
2670 };
2671 let c = crate::os_lib::web_engine::box_shadow::coverage(d, blur);
2672 if c <= 0.0 {
2673 continue;
2674 }
2675 (alpha as f32 * c) as u8
2676 };
2677 if a > 0 {
2678 self.draw_pixel_alpha(x as u32, y as u32, rgb, a);
2679 }
2680 }
2681 }
2682 }
2683
2684 pub fn draw_inset_shadow_rect(
2686 &self,
2687 x0: i32,
2688 y0: i32,
2689 x1: i32,
2690 y1: i32,
2691 ox: i32,
2692 oy: i32,
2693 blur: i32,
2694 radius: (i32, i32, i32, i32),
2695 color: Color,
2696 ) {
2697 if x1 <= x0 || y1 <= y0 {
2698 return;
2699 }
2700 let ix0 = x0.max(0) as u32;
2701 let iy0 = y0.max(0) as u32;
2702 let ix1 = x1.max(0) as u32;
2703 let iy1 = y1.max(0) as u32;
2704 let alpha = ((color.0 >> 24) & 0xFF) as u8;
2705 let alpha = if alpha == 0 { 90u8 } else { alpha };
2706 let shadow_color = color.0 | 0xFF000000;
2707 let w = x1 - x0;
2708 let h = y1 - y0;
2709 let has_radius = radius != (0, 0, 0, 0);
2710 let shadow_depth = blur.clamp(4, 30);
2711
2712 for y in iy0..iy1 {
2713 let ry = (y as i32) - y0;
2714 for x in ix0..ix1 {
2715 let rx = (x as i32) - x0;
2716 if has_radius && !crate::os_lib::web_engine::draw_helpers::point_in_rounded_rect(rx, ry, w, h, radius) {
2717 continue;
2718 }
2719 let d_left = rx - ox;
2720 let d_top = ry - oy;
2721 let d_right = (w - rx) + ox;
2722 let d_bottom = (h - ry) + oy;
2723 let min_dist = d_left.min(d_top).min(d_right).min(d_bottom);
2724 if min_dist >= 0 && min_dist < shadow_depth {
2725 let factor = 1.0 - (min_dist as f32 / shadow_depth as f32);
2726 let pix_alpha = ((alpha as f32) * factor) as u8;
2727 if pix_alpha > 0 {
2728 self.draw_pixel_alpha(x, y, Color(shadow_color), pix_alpha);
2729 }
2730 }
2731 }
2732 }
2733 }
2734
2735 pub fn fill_signed(&self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) {
2737 if let Some((a, b, c, d)) = crate::kernel::draw_clip::signed_rect_to_fill(x0, y0, x1, y1) {
2738 self.boxfill(a, b, c, d, color);
2739 }
2740 }
2741
2742 pub fn fill_signed_alpha(&self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color, alpha: u8) {
2744 if let Some((a, b, c, d)) = crate::kernel::draw_clip::signed_rect_to_fill(x0, y0, x1, y1) {
2745 self.boxfill_alpha(a, b, c, d, color, alpha);
2746 }
2747 }
2748
2749 pub fn draw_rounded_rect_fill(
2750 &self,
2751 x0: i32,
2752 y0: i32,
2753 x1: i32,
2754 y1: i32,
2755 radius: (i32, i32, i32, i32),
2756 color: Color,
2757 ) {
2758 let (tl, tr, br, bl) = radius;
2759 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2760 self.fill_signed(x0, y0, x1, y1, color);
2761 return;
2762 }
2763 let w = x1 - x0;
2764 let h = y1 - y0;
2765
2766 let tl = tl.min(w / 2).min(h / 2).max(0);
2767 let tr = tr.min(w / 2).min(h / 2).max(0);
2768 let br = br.min(w / 2).min(h / 2).max(0);
2769 let bl = bl.min(w / 2).min(h / 2).max(0);
2770
2771 let top_max_r = tl.max(tr);
2778 let bot_max_r = bl.max(br);
2779 if top_max_r + bot_max_r <= h {
2780 self.fill_signed(x0, y0 + top_max_r, x1, y1 - bot_max_r, color);
2781 }
2782
2783 if top_max_r > 0 {
2785 self.fill_signed(x0 + tl, y0, x1 - tr, y0 + top_max_r, color);
2786 }
2787
2788 if bot_max_r > 0 {
2790 self.fill_signed(x0 + bl, y1 - bot_max_r, x1 - br, y1, color);
2791 }
2792
2793 if tl > 0 {
2796 let r2 = tl * tl;
2797 for dy in 0..tl {
2798 for dx in 0..tl {
2799 if dx * dx + dy * dy <= r2 {
2800 self.draw_pixel((x0 + tl - 1 - dx) as u32, (y0 + tl - 1 - dy) as u32, color);
2801 }
2802 }
2803 }
2804 if top_max_r > tl {
2806 self.fill_signed(x0, y0 + tl, x0 + tl, y0 + top_max_r, color);
2807 }
2808 }
2809 if tr > 0 {
2811 let r2 = tr * tr;
2812 for dy in 0..tr {
2813 for dx in 0..tr {
2814 if dx * dx + dy * dy <= r2 {
2815 self.draw_pixel((x1 - tr + dx) as u32, (y0 + tr - 1 - dy) as u32, color);
2816 }
2817 }
2818 }
2819 if top_max_r > tr {
2820 self.fill_signed(x1 - tr, y0 + tr, x1, y0 + top_max_r, color);
2821 }
2822 }
2823 if bl > 0 {
2825 let r2 = bl * bl;
2826 for dy in 0..bl {
2827 for dx in 0..bl {
2828 if dx * dx + dy * dy <= r2 {
2829 self.draw_pixel((x0 + bl - 1 - dx) as u32, (y1 - bl + dy) as u32, color);
2830 }
2831 }
2832 }
2833 if bot_max_r > bl {
2834 self.fill_signed(x0, y1 - bot_max_r, x0 + bl, y1 - bl, color);
2835 }
2836 }
2837 if br > 0 {
2839 let r2 = br * br;
2840 for dy in 0..br {
2841 for dx in 0..br {
2842 if dx * dx + dy * dy <= r2 {
2843 self.draw_pixel((x1 - br + dx) as u32, (y1 - br + dy) as u32, color);
2844 }
2845 }
2846 }
2847 if bot_max_r > br {
2848 self.fill_signed(x1 - br, y1 - bot_max_r, x1, y1 - br, color);
2849 }
2850 }
2851 }
2852
2853 pub fn draw_rounded_rect_border(
2854 &self,
2855 x0: i32,
2856 y0: i32,
2857 x1: i32,
2858 y1: i32,
2859 radius: (i32, i32, i32, i32),
2860 color: Color,
2861 _border_style: &str,
2862 ) {
2863 if (color.0 >> 24) & 0xFF == 0 {
2873 return;
2874 }
2875 let (tl, tr, br, bl) = radius;
2876 if tl <= 0 && tr <= 0 && br <= 0 && bl <= 0 {
2877 self.boxfill(x0 as u32, y0 as u32, x1 as u32, (y0 + 1) as u32, color);
2879 self.boxfill(x0 as u32, (y1 - 1) as u32, x1 as u32, y1 as u32, color);
2880 self.boxfill(x0 as u32, y0 as u32, (x0 + 1) as u32, y1 as u32, color);
2881 self.boxfill((x1 - 1) as u32, y0 as u32, x1 as u32, y1 as u32, color);
2882 return;
2883 }
2884 let w = x1 - x0;
2885 let h = y1 - y0;
2886 let tl = tl.min(w / 2).min(h / 2).max(0);
2887 let tr = tr.min(w / 2).min(h / 2).max(0);
2888 let br = br.min(w / 2).min(h / 2).max(0);
2889 let bl = bl.min(w / 2).min(h / 2).max(0);
2890
2891 self.boxfill((x0 + tl) as u32, y0 as u32, (x1 - tr) as u32, (y0 + 1) as u32, color);
2893 self.boxfill((x0 + bl) as u32, (y1 - 1) as u32, (x1 - br) as u32, y1 as u32, color);
2894 self.boxfill(x0 as u32, (y0 + tl) as u32, (x0 + 1) as u32, (y1 - bl) as u32, color);
2895 self.boxfill((x1 - 1) as u32, (y0 + tr) as u32, x1 as u32, (y1 - br) as u32, color);
2896
2897 let draw_arc = |cx: i32, cy: i32, r: i32, quadrant: i32| {
2899 if r <= 0 { return; }
2900 let mut x = r;
2901 let mut y = 0;
2902 let mut err = 0;
2903 while x >= y {
2904 let px1 = cx + x - 1; let py1 = cy + y;
2905 let px2 = cx + y - 1; let py2 = cy + x - 1;
2906 let px3 = cx - x; let py3 = cy + y;
2907 let px4 = cx - y; let py4 = cy + x - 1;
2908 let px5 = cx - x; let py5 = cy - y - 1;
2909 let px6 = cx - y; let py6 = cy - x;
2910 let px7 = cx + x - 1; let py7 = cy - y - 1;
2911 let px8 = cx + y - 1; let py8 = cy - x;
2912
2913 match quadrant {
2914 1 => { self.draw_pixel(px1 as u32, py1 as u32, color);
2916 self.draw_pixel(px2 as u32, py2 as u32, color);
2917 }
2918 2 => { self.draw_pixel(px3 as u32, py3 as u32, color);
2920 self.draw_pixel(px4 as u32, py4 as u32, color);
2921 }
2922 3 => { self.draw_pixel(px5 as u32, py5 as u32, color);
2924 self.draw_pixel(px6 as u32, py6 as u32, color);
2925 }
2926 4 => { self.draw_pixel(px7 as u32, py7 as u32, color);
2928 self.draw_pixel(px8 as u32, py8 as u32, color);
2929 }
2930 _ => {}
2931 }
2932
2933 if err <= 0 {
2934 y += 1;
2935 err += 2 * y + 1;
2936 }
2937 if err > 0 {
2938 x -= 1;
2939 err -= 2 * x + 1;
2940 }
2941 }
2942 };
2943
2944 draw_arc(x0 + tl, y0 + tl, tl, 3);
2945 draw_arc(x1 - tr, y0 + tr, tr, 4);
2946 draw_arc(x1 - br, y1 - br, br, 1);
2947 draw_arc(x0 + bl, y1 - bl, bl, 2);
2948 }
2949
2950
2951}
2952
2953pub struct Mouse {
2955 pub x: u32,
2956 pub y: u32,
2957 pub is_clicked: bool,
2958 pub click_frame: u8,
2959 pub is_text_cursor: bool,
2962}
2963
2964impl Mouse {
2965 pub const SIZE: u32 = 32;
2966
2967 pub const fn new(x: u32, y: u32) -> Self {
2968 Self {
2969 x,
2970 y,
2971 is_clicked: false,
2972 click_frame: 0,
2973 is_text_cursor: false,
2974 }
2975 }
2976
2977 pub fn draw(&self, screen: &Screen) {
2978 let (target_ptr, w, h) = screen.resolve_target();
2979 let stride = screen.resolve_stride();
2980 let cursor_size = Self::SIZE;
2981
2982 let (draw_x, draw_y) = if self.is_clicked {
2984 (self.x + 1, self.y + 1)
2985 } else {
2986 (self.x, self.y)
2987 };
2988
2989 if self.is_text_cursor {
2990 self.draw_text_cursor(screen, draw_x, draw_y);
2991 return;
2992 }
2993
2994 if let Some(alpha_mask) = crate::os_lib::svg::get_or_rasterize(ICON_CURSOR_64, cursor_size)
2995 {
2996 let theme = crate::kernel::config::get_config().theme;
2997 let mut c_fill = theme.mouse_cursor;
2998 let c_stroke = theme.mouse_stroke;
2999
3000 if self.is_clicked {
3002 let r = ((c_fill >> 16) & 0xFF) * 8 / 10;
3003 let g = ((c_fill >> 8) & 0xFF) * 8 / 10;
3004 let b = (c_fill & 0xFF) * 8 / 10;
3005 c_fill = 0xFF000000 | (r << 16) | (g << 8) | b;
3006 }
3007
3008 for row in 0..cursor_size {
3009 let py = draw_y + row;
3010 if py >= h {
3011 continue;
3012 }
3013
3014 let max_col = (w.saturating_sub(draw_x)).min(cursor_size) as usize;
3015 if max_col == 0 {
3016 continue;
3017 }
3018
3019 let offset = (py * stride + draw_x) as usize;
3020 unsafe {
3021 let mut row_buf = [0u32; 32];
3022 let dst = target_ptr.add(offset);
3023
3024 let src = if !screen.back_buffer.is_null() {
3026 let src_offset = (py * w + draw_x) as usize;
3027 screen.back_buffer.add(src_offset)
3028 } else {
3029 dst
3030 };
3031
3032 core::ptr::copy_nonoverlapping(src, row_buf.as_mut_ptr(), max_col);
3033
3034 for col in 0..max_col {
3036 let mask_idx = (row * cursor_size + col as u32) as usize;
3037 let alpha = alpha_mask[mask_idx];
3038 if alpha > 0 {
3039 let mut is_edge = false;
3041 if row == 0
3042 || row == cursor_size - 1
3043 || col == 0
3044 || col as u32 == cursor_size - 1
3045 {
3046 is_edge = true;
3047 } else {
3048 'outer: for dy in -1..=1 {
3050 for dx in -1..=1 {
3051 let ny = row as i32 + dy;
3052 let nx = col as i32 + dx;
3053 let n_idx = (ny * cursor_size as i32 + nx) as usize;
3054 if alpha_mask[n_idx] < 80 {
3055 is_edge = true;
3056 break 'outer;
3057 }
3058 }
3059 }
3060 }
3061
3062 let bg = row_buf[col];
3063 let r_bg = (bg >> 16) & 0xFF;
3064 let g_bg = (bg >> 8) & 0xFF;
3065 let b_bg = bg & 0xFF;
3066
3067 let fg_color = if is_edge { c_stroke } else { c_fill };
3069 let r_fg = (fg_color >> 16) & 0xFF;
3070 let g_fg = (fg_color >> 8) & 0xFF;
3071 let b_fg = fg_color & 0xFF;
3072
3073 let a = alpha as u32;
3074 let inv_a = 255 - a;
3075
3076 let r = (r_fg * a + r_bg * inv_a) / 255;
3077 let g = (g_fg * a + g_bg * inv_a) / 255;
3078 let b = (b_fg * a + b_bg * inv_a) / 255;
3079
3080 row_buf[col] = (r << 16) | (g << 8) | b;
3081 }
3082 }
3083
3084 core::ptr::copy_nonoverlapping(row_buf.as_ptr(), dst, max_col);
3086 }
3087 }
3088
3089 if self.is_clicked && self.click_frame > 0 && self.click_frame < 8 {
3091 let spark_color = theme.mouse_cursor; let center_x = draw_x as i32;
3093 let center_y = draw_y as i32;
3094 let r1 = 4 + (self.click_frame as i32) * 2; let r2 = r1 + 4; let dirs = [
3099 (0, -1), (1, -1), (1, 0), (1, 1),
3100 (0, 1), (-1, 1), (-1, 0), (-1, -1)
3101 ];
3102
3103 for &(dx, dy) in dirs.iter() {
3104 for r in r1..=r2 {
3105 let px = center_x + dx * r;
3106 let py = center_y + dy * r;
3107 if px >= 0 && px < w as i32 && py >= 0 && py < h as i32 {
3108 let offset = (py as u32 * stride + px as u32) as usize;
3109 unsafe {
3110 let dst = target_ptr.add(offset);
3111 *dst = spark_color;
3112 }
3113 }
3114 }
3115 }
3116 }
3117 }
3118 }
3119
3120 fn draw_text_cursor(&self, screen: &Screen, draw_x: u32, draw_y: u32) {
3127 let theme = crate::kernel::config::get_config().theme;
3128 let color = Color(theme.mouse_cursor);
3129 let size = Self::SIZE;
3130 let bar_h = size.saturating_sub(4);
3131 let cx = draw_x + size / 2;
3132 let top_y = draw_y + 2;
3133 let bottom_y = top_y + bar_h;
3134 for dx in 0..=(size / 4) {
3136 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, top_y, color);
3137 screen.draw_pixel(cx.saturating_sub(size / 8) + dx, bottom_y, color);
3138 }
3139 for y in top_y..=bottom_y {
3141 screen.draw_pixel(cx, y, color);
3142 }
3143 }
3144
3145 pub fn move_to(&mut self, screen: &Screen, new_x: u32, new_y: u32) {
3147 self.x = new_x.clamp(0, screen.width - 1);
3148 self.y = new_y.clamp(0, screen.height - 1);
3149 }
3150}
3151
3152pub static mut SPLASH_ACTIVE: bool = false;
3156pub static LOGO_PNG: &[u8] = include_bytes!("../../images/os_logo.png");
3157
3158static mut BOOT_LOG_COUNT: usize = 0;
3159static mut BOOT_LOG_BUFFER: [[u8; 128]; 16] = [[0; 128]; 16];
3160static mut BOOT_LOG_LENS: [usize; 16] = [0; 16];
3161
3162static mut CURRENT_LINE_BUFFER: [u8; 256] = [0; 256];
3163static mut CURRENT_LINE_LEN: usize = 0;
3164
3165pub unsafe fn write_splash_char(c: u8) {
3169 if !SPLASH_ACTIVE {
3170 return;
3171 }
3172 if CURRENT_LINE_LEN > 255 {
3175 CURRENT_LINE_LEN = 0;
3176 }
3177 if c == b'\n' {
3178 if CURRENT_LINE_LEN > 0 {
3179 if let Ok(s) = core::str::from_utf8(&CURRENT_LINE_BUFFER[..CURRENT_LINE_LEN.min(255)]) {
3180 add_boot_log(s);
3181 }
3182 CURRENT_LINE_LEN = 0;
3183 }
3184 } else {
3185 if CURRENT_LINE_LEN < 255 {
3186 CURRENT_LINE_BUFFER[CURRENT_LINE_LEN] = c;
3187 CURRENT_LINE_LEN += 1;
3188 }
3189 }
3190}
3191
3192pub unsafe fn write_splash_str(s: &str) {
3196 if !SPLASH_ACTIVE {
3197 return;
3198 }
3199 for &b in s.as_bytes() {
3200 write_splash_char(b);
3201 }
3202}
3203
3204pub unsafe fn add_boot_log(s: &str) {
3208 if !SPLASH_ACTIVE {
3209 return;
3210 }
3211 const MAX_LINE_CHARS: usize = 55;
3212
3213 for line in s.lines() {
3214 let line = line.trim_end();
3215 if line.is_empty() {
3216 continue;
3217 }
3218
3219 let mut chars = line.as_bytes();
3220 while !chars.is_empty() {
3221 let chunk_len = chars.len().min(MAX_LINE_CHARS);
3222 let chunk = &chars[..chunk_len];
3223 chars = &chars[chunk_len..];
3224
3225 if BOOT_LOG_COUNT < 16 {
3226 let idx = BOOT_LOG_COUNT;
3227 BOOT_LOG_BUFFER[idx][..chunk_len].copy_from_slice(chunk);
3228 BOOT_LOG_LENS[idx] = chunk_len;
3229 BOOT_LOG_COUNT += 1;
3230 } else {
3231 for i in 0..15 {
3232 BOOT_LOG_BUFFER[i] = BOOT_LOG_BUFFER[i + 1];
3233 BOOT_LOG_LENS[i] = BOOT_LOG_LENS[i + 1];
3234 }
3235 BOOT_LOG_BUFFER[15][..chunk_len].copy_from_slice(chunk);
3236 BOOT_LOG_LENS[15] = chunk_len;
3237 }
3238 }
3239 }
3240 redraw_splash();
3241}
3242
3243pub unsafe fn draw_splash_logo() {
3247 if !SPLASH_ACTIVE {
3248 return;
3249 }
3250 if let Some(ref screen) = crate::CURRENT_SCREEN {
3251 screen.clear(Color(0xFF000000));
3252 let mut decoder = zune_png::PngDecoder::new(LOGO_PNG);
3253 if decoder.decode_raw().is_ok() {
3254 if let Some(info) = decoder.get_info() {
3255 let img_w = info.width as u32;
3256 let img_h = info.height as u32;
3257 let area_w = 480u32;
3258 let area_h = screen.height;
3259 let target_w = area_w.min(img_w);
3260 let target_h = (target_w * img_h) / img_w;
3261 let target_h = target_h.min(area_h);
3262 let target_w = (target_h * img_w) / img_h;
3263
3264 let x = (area_w - target_w) / 2 + 20;
3265 let y = (screen.height - target_h) / 2;
3266 let _ = screen.draw_image(x, y, target_w, target_h, LOGO_PNG);
3267 }
3268 }
3269 redraw_splash();
3270 }
3271}
3272
3273pub unsafe fn redraw_splash() {
3277 if !SPLASH_ACTIVE {
3278 return;
3279 }
3280 if let Some(ref screen) = crate::CURRENT_SCREEN {
3281 let area_x = 520u32;
3282 let area_y = 20u32;
3283 let area_w = screen.width.saturating_sub(area_x).saturating_sub(20);
3284 let area_h = screen.height.saturating_sub(area_y).saturating_sub(20);
3285 screen.boxfill(
3286 area_x,
3287 area_y,
3288 area_x + area_w,
3289 area_y + area_h,
3290 Color(0xFF000000),
3291 );
3292
3293 let start_y = (screen.height.saturating_sub(40)) as i32;
3294 let line_height = 28i32;
3295
3296 for i in 0..BOOT_LOG_COUNT {
3297 let y = start_y - (BOOT_LOG_COUNT - 1 - i) as i32 * line_height;
3298 if y >= area_y as i32 {
3299 if let Ok(s) = core::str::from_utf8(&BOOT_LOG_BUFFER[i][..BOOT_LOG_LENS[i]]) {
3300 screen.draw_string_monospace(area_x + 10, y as u32, s, Color(0xFFCCCCCC));
3301 }
3302 }
3303 }
3304 }
3305}
3306
3307pub struct SplashWriter;
3308
3309impl core::fmt::Write for SplashWriter {
3310 fn write_str(&mut self, s: &str) -> core::fmt::Result {
3311 unsafe {
3312 write_splash_str(s);
3313 }
3314 Ok(())
3315 }
3316}