1use super::{
2 api::current_ui_scale,
3 cursor::{CursorManager, CURSOR_H, CURSOR_PIXELS, CURSOR_W, TEXT_CURSOR_MAX_DIM},
4 font::{parse_psf, parse_psf2_unicode_map, FontInfo, FONT_PSF},
5 scrollback::{SbCell, ScrollbackBuffer},
6 types::*,
7};
8use crate::framebuffer::{CanvasBuffer, DirtyRectSet, MAX_DIRTY_RECTS};
9use alloc::vec::Vec;
10use core::{
11 fmt,
12 sync::atomic::{AtomicBool, AtomicU64, Ordering},
13};
14
15pub(crate) static PRESENTED_FRAMES: AtomicU64 = AtomicU64::new(0);
16pub(crate) static DOUBLE_BUFFER_MODE: AtomicBool = AtomicBool::new(false);
17pub(crate) static VGA_PRESENT_REGION_COUNT: AtomicU64 = AtomicU64::new(0);
18pub(crate) static VGA_PRESENT_PIXEL_COUNT: AtomicU64 = AtomicU64::new(0);
19
20const SCROLLBAR_W: usize = 12;
24
25use crate::framebuffer::RgbColor;
26
27#[derive(Clone, Copy)]
28pub(crate) struct ClipRect {
29 x: usize,
30 y: usize,
31 w: usize,
32 h: usize,
33}
34
35use crate::framebuffer::PRESENT_MIN_TICKS;
37
38const CLIPBOARD_CAP: usize = 8192;
40static CLIPBOARD: spin::Mutex<([u8; CLIPBOARD_CAP], usize)> =
41 spin::Mutex::new(([0u8; CLIPBOARD_CAP], 0));
42
43pub struct VgaWriter {
44 pub(crate) enabled: bool,
45 pub(crate) canvas: Option<CanvasBuffer>,
47 pub(crate) fmt: PixelFormat,
49
50 pub(crate) cols: usize,
51 pub(crate) rows: usize,
52 pub(crate) col: usize,
53 pub(crate) row: usize,
54
55 pub(crate) fg: u32,
56 pub(crate) bg: u32,
57
58 pub(crate) font: &'static [u8],
59 pub(crate) font_info: FontInfo,
60 pub(crate) glyph_mask_cache: Vec<u8>,
61 pub(crate) unicode_map: Vec<(u32, usize)>,
62 pub(crate) status_bar_height: usize,
63 pub(crate) clip: ClipRect,
64
65 pub(crate) sb: ScrollbackBuffer,
67 pub(crate) scroll_offset: usize,
68
69 console_cells: Vec<SbCell>,
72 console_dirty_cells: Vec<bool>,
73 console_row_head: usize,
74 console_pending_scroll: usize,
75 console_pending: bool,
76
77 pub(crate) cursor: CursorManager,
79
80 pub(crate) sel_active: bool,
82 pub(crate) sel_start_row: usize,
83 pub(crate) sel_start_col: usize,
84 pub(crate) sel_end_row: usize,
85 pub(crate) sel_end_col: usize,
86 pub(crate) prepared_row_cells: Vec<(usize, u32, u32)>,
87
88 pub(crate) console_defer_present: bool,
91}
92
93unsafe impl Send for VgaWriter {}
94
95impl VgaWriter {
97 #[inline]
98 fn can(&self) -> &CanvasBuffer {
99 self.canvas
100 .as_ref()
101 .expect("VgaWriter canvas not initialized")
102 }
103
104 #[inline]
105 fn canm(&mut self) -> &mut CanvasBuffer {
106 self.canvas
107 .as_mut()
108 .expect("VgaWriter canvas not initialized")
109 }
110}
111
112impl VgaWriter {
113 pub(crate) fn sb_capacity(&self) -> usize {
115 self.sb.capacity(self.rows)
116 }
117
118 pub(crate) fn sb_row_at(&self, logical_idx: usize) -> Option<&Vec<SbCell>> {
119 self.sb.row_at(logical_idx)
120 }
121
122 pub(crate) fn sb_push_row(&mut self, row: Vec<SbCell>) {
123 let rows = self.rows;
124 self.sb.push_row(row, rows);
125 }
126
127 pub(crate) fn build_glyph_mask_cache(font: &'static [u8], info: &FontInfo) -> Vec<u8> {
128 let glyph_pixels = info.glyph_w.saturating_mul(info.glyph_h);
129 let total_pixels = info.glyph_count.saturating_mul(glyph_pixels);
130 let mut cache = Vec::with_capacity(total_pixels);
131 if glyph_pixels == 0 || info.glyph_count == 0 {
132 return cache;
133 }
134
135 let row_bytes = info.glyph_w.div_ceil(8);
136 for glyph_index in 0..info.glyph_count {
137 let start = info.data_offset + glyph_index * info.bytes_per_glyph;
138 let end = start.saturating_add(info.bytes_per_glyph);
139 if end > font.len() {
140 cache.resize(cache.len().saturating_add(glyph_pixels), 0);
141 continue;
142 }
143 let glyph_ptr = font[start..end].as_ptr();
144 for gy in 0..info.glyph_h {
145 for gx in 0..info.glyph_w {
146 let byte = unsafe { *glyph_ptr.add(gy * row_bytes + gx / 8) };
147 let mask = 0x80u8 >> (gx % 8);
148 cache.push(if (byte & mask) != 0 { 1 } else { 0 });
149 }
150 }
151 }
152 cache
153 }
154
155 pub(crate) fn glyph_mask_slice(&self, glyph_index: usize) -> Option<&[u8]> {
156 let glyph_pixels = self
157 .font_info
158 .glyph_w
159 .saturating_mul(self.font_info.glyph_h);
160 if glyph_pixels == 0 {
161 return None;
162 }
163 let start = glyph_index.checked_mul(glyph_pixels)?;
164 let end = start.checked_add(glyph_pixels)?;
165 self.glyph_mask_cache.get(start..end)
166 }
167
168 pub const fn new() -> Self {
170 Self {
171 enabled: false,
172 canvas: None,
173 fmt: PixelFormat {
174 bpp: 0,
175 red_size: 0,
176 red_shift: 0,
177 green_size: 0,
178 green_shift: 0,
179 blue_size: 0,
180 blue_shift: 0,
181 },
182 cols: 0,
183 rows: 0,
184 col: 0,
185 row: 0,
186 fg: 0,
187 bg: 0,
188 font: &[],
189 font_info: FontInfo {
190 glyph_count: 0,
191 bytes_per_glyph: 0,
192 glyph_w: 8,
193 glyph_h: 16,
194 data_offset: 0,
195 unicode_table_offset: None,
196 },
197 glyph_mask_cache: Vec::new(),
198 unicode_map: Vec::new(),
199 status_bar_height: 0,
200 clip: ClipRect {
201 x: 0,
202 y: 0,
203 w: 0,
204 h: 0,
205 },
206 sb: ScrollbackBuffer::new(),
207 scroll_offset: 0,
208 console_cells: Vec::new(),
209 console_dirty_cells: Vec::new(),
210 console_row_head: 0,
211 console_pending_scroll: 0,
212 console_pending: false,
213 cursor: CursorManager::new(),
214 sel_active: false,
215 sel_start_row: 0,
216 sel_start_col: 0,
217 sel_end_row: 0,
218 sel_end_col: 0,
219 prepared_row_cells: Vec::new(),
220 console_defer_present: false,
221 }
222 }
223
224 pub(crate) fn configure(
226 &mut self,
227 fb_addr: *mut u8,
228 fb_width: usize,
229 fb_height: usize,
230 pitch: usize,
231 fmt: PixelFormat,
232 ) -> bool {
233 let Some(font_info) = parse_psf(FONT_PSF) else {
234 return false;
235 };
236 let status_bar_height = font_info.glyph_h;
237 let text_height = fb_height.saturating_sub(status_bar_height);
238 let cols = fb_width / font_info.glyph_w;
239 let rows = text_height / font_info.glyph_h;
240 if cols == 0 || rows == 0 {
241 return false;
242 }
243 let (fr, fg, fb) = color_to_rgb(Color::LightGrey);
244 let (br, bg, bb) = color_to_rgb(Color::Black);
245
246 self.enabled = true;
247 self.canvas = Some(CanvasBuffer {
248 addr: fb_addr,
249 width: fb_width,
250 height: fb_height,
251 pitch,
252 bpp: fmt.bpp,
253 back_buffer: None,
254 draw_to_back: false,
255 dirty: DirtyRectSet::empty(),
256 track_dirty: false,
257 present_pending: false,
258 last_present_tick: 0,
259 ops: crate::framebuffer::FramebufferOps::detect(),
260 present_row_buf: None,
261 });
262 self.fmt = fmt;
263 self.cols = cols;
264 self.rows = rows;
265 self.col = 0;
266 self.row = 0;
267 self.fg = fmt.pack_rgb(fr, fg, fb);
268 self.bg = fmt.pack_rgb(br, bg, bb);
269 self.font = FONT_PSF;
270 self.font_info = font_info;
271 self.glyph_mask_cache = Self::build_glyph_mask_cache(FONT_PSF, &self.font_info);
272 self.unicode_map = parse_psf2_unicode_map(FONT_PSF, &self.font_info);
273 self.status_bar_height = status_bar_height;
274 self.clip = ClipRect {
275 x: 0,
276 y: 0,
277 w: fb_width,
278 h: fb_height,
279 };
280 self.sb = ScrollbackBuffer::new();
281 self.scroll_offset = 0;
282 self.console_cells = alloc::vec![SbCell { ch: ' ', fg: self.fg, bg: self.bg }; cols * rows];
283 self.console_dirty_cells = alloc::vec![false; cols * rows];
284 self.console_row_head = 0;
285 self.console_pending_scroll = 0;
286 self.console_pending = false;
287 self.cursor = CursorManager::new();
288 self.cursor.tc_visible = false;
289 self.cursor.tc_col = 0;
290 self.cursor.tc_row = 0;
291 self.cursor.tc_w = 0;
292 self.cursor.tc_h = 0;
293 self.cursor.tc_color = 0;
294 self.sel_active = false;
295 self.prepared_row_cells = Vec::new();
296 true
297 }
298
299 #[inline]
301 pub(crate) fn pack_color(&self, color: RgbColor) -> u32 {
302 self.fmt.pack_rgb(color.r, color.g, color.b)
303 }
304
305 pub(crate) fn unpack_color(&self, value: u32) -> RgbColor {
307 fn unscale(v: u32, bits: u8) -> u8 {
309 if bits == 0 {
310 return 0;
311 }
312 let max = (1u32 << bits) - 1;
313 ((v * 255) / max) as u8
314 }
315
316 let r = unscale(
317 (value >> self.fmt.red_shift) & ((1u32 << self.fmt.red_size) - 1),
318 self.fmt.red_size,
319 );
320 let g = unscale(
321 (value >> self.fmt.green_shift) & ((1u32 << self.fmt.green_size) - 1),
322 self.fmt.green_size,
323 );
324 let b = unscale(
325 (value >> self.fmt.blue_shift) & ((1u32 << self.fmt.blue_size) - 1),
326 self.fmt.blue_size,
327 );
328 RgbColor::new(r, g, b)
329 }
330
331 pub fn set_color(&mut self, fg: Color, bg: Color) {
333 self.set_rgb_color(fg.into(), bg.into());
334 }
335
336 pub fn set_rgb_color(&mut self, fg: RgbColor, bg: RgbColor) {
338 self.fg = self.pack_color(fg);
339 self.bg = self.pack_color(bg);
340 }
341
342 pub fn text_colors(&self) -> (RgbColor, RgbColor) {
344 (self.unpack_color(self.fg), self.unpack_color(self.bg))
345 }
346
347 pub fn width(&self) -> usize {
349 self.can().width
350 }
351
352 pub fn height(&self) -> usize {
354 self.can().height
355 }
356
357 pub fn cols(&self) -> usize {
359 self.cols
360 }
361
362 pub fn rows(&self) -> usize {
364 self.rows
365 }
366
367 pub fn glyph_size(&self) -> (usize, usize) {
369 (self.font_info.glyph_w, self.font_info.glyph_h)
370 }
371
372 pub fn set_cursor_cell(&mut self, col: usize, row: usize) {
374 if !self.enabled || self.cols == 0 || self.rows == 0 {
375 return;
376 }
377 if self.cursor.tc_visible {
378 self.text_cursor_erase_hw();
379 self.cursor.tc_visible = false;
380 self.cursor.tc_dirty = true;
381 }
382 self.col = core::cmp::min(col, self.cols - 1);
383 self.row = core::cmp::min(row, self.rows - 1);
384 }
385
386 pub(crate) fn text_area_height(&self) -> usize {
388 self.can().height.saturating_sub(self.status_bar_height)
389 }
390
391 pub fn enabled(&self) -> bool {
393 self.enabled
394 }
395
396 pub fn framebuffer_info(&self) -> FramebufferInfo {
398 FramebufferInfo {
399 available: self.enabled,
400 width: self.can().width,
401 height: self.can().height,
402 pitch: self.can().pitch,
403 bpp: self.fmt.bpp,
404 red_size: self.fmt.red_size,
405 red_shift: self.fmt.red_shift,
406 green_size: self.fmt.green_size,
407 green_shift: self.fmt.green_shift,
408 blue_size: self.fmt.blue_size,
409 blue_shift: self.fmt.blue_shift,
410 text_cols: self.cols,
411 text_rows: self.rows,
412 glyph_w: self.font_info.glyph_w,
413 glyph_h: self.font_info.glyph_h,
414 double_buffer_mode: DOUBLE_BUFFER_MODE.load(Ordering::Relaxed),
415 double_buffer_enabled: self.can().draw_to_back && self.can().back_buffer.is_some(),
416 ui_scale: current_ui_scale(),
417 }
418 }
419
420 #[inline]
422 pub(crate) fn in_clip(&self, x: usize, y: usize) -> bool {
423 x >= self.clip.x
424 && y >= self.clip.y
425 && x < self.clip.x.saturating_add(self.clip.w)
426 && y < self.clip.y.saturating_add(self.clip.h)
427 }
428
429 pub(crate) fn clipped_rect(
431 &self,
432 x: usize,
433 y: usize,
434 width: usize,
435 height: usize,
436 ) -> Option<(usize, usize, usize, usize)> {
437 if width == 0 || height == 0 || !self.enabled {
438 return None;
439 }
440 let src_x2 = core::cmp::min(x.saturating_add(width), self.can().width);
441 let src_y2 = core::cmp::min(y.saturating_add(height), self.can().height);
442 let clip_x2 = self.clip.x.saturating_add(self.clip.w);
443 let clip_y2 = self.clip.y.saturating_add(self.clip.h);
444
445 let sx = core::cmp::max(x, self.clip.x);
446 let sy = core::cmp::max(y, self.clip.y);
447 let ex = core::cmp::min(src_x2, clip_x2);
448 let ey = core::cmp::min(src_y2, clip_y2);
449 if ex <= sx || ey <= sy {
450 return None;
451 }
452 Some((sx, sy, ex - sx, ey - sy))
453 }
454
455 pub(crate) fn clear_dirty(&mut self) {
457 self.canm().dirty.clear();
458 }
459
460 pub(crate) fn mark_dirty_rect(&mut self, x: usize, y: usize, width: usize, height: usize) {
462 let canvas = self.can();
463 if !canvas.track_dirty || width == 0 || height == 0 || !self.enabled {
464 return;
465 }
466 let fw = canvas.width;
467 let fh = canvas.height;
468 let src_x2 = core::cmp::min(x.saturating_add(width), fw);
469 let src_y2 = core::cmp::min(y.saturating_add(height), fh);
470 let clip_x2 = self.clip.x.saturating_add(self.clip.w);
471 let clip_y2 = self.clip.y.saturating_add(self.clip.h);
472 let sx = core::cmp::max(x, self.clip.x);
473 let sy = core::cmp::max(y, self.clip.y);
474 let ex = core::cmp::min(src_x2, clip_x2);
475 let ey = core::cmp::min(src_y2, clip_y2);
476 if ex <= sx || ey <= sy {
477 return;
478 }
479 let _ = canvas;
480 self.canm()
481 .dirty
482 .include(sx as u32, sy as u32, (ex - sx) as u32, (ey - sy) as u32);
483 }
484
485 pub fn set_clip_rect(&mut self, x: usize, y: usize, width: usize, height: usize) {
487 self.render_pending_console();
488 let x_end = core::cmp::min(x.saturating_add(width), self.can().width);
489 let y_end = core::cmp::min(y.saturating_add(height), self.can().height);
490 self.clip = ClipRect {
491 x,
492 y,
493 w: x_end.saturating_sub(x),
494 h: y_end.saturating_sub(y),
495 };
496 }
497
498 pub fn reset_clip_rect(&mut self) {
500 self.render_pending_console();
501 self.clip = ClipRect {
502 x: 0,
503 y: 0,
504 w: self.can().width,
505 h: self.can().height,
506 };
507 }
508
509 pub(crate) fn draw_to_back_buffer(&self) -> bool {
511 self.can().draw_to_back && self.can().back_buffer.is_some()
512 }
513
514 pub fn enable_double_buffer(&mut self) -> bool {
516 self.render_pending_console();
517 if !self.enabled {
518 return false;
519 }
520 if self.can().back_buffer.is_none() {
521 let mut buf = Vec::with_capacity(self.can().width.saturating_mul(self.can().height));
522 for y in 0..self.can().height {
523 for x in 0..self.can().width {
524 buf.push(self.read_hw_pixel_packed(x, y));
525 }
526 }
527 self.canm().back_buffer = Some(buf);
528 }
529 self.canm().draw_to_back = true;
530 self.canm().track_dirty = true;
531 true
533 }
534
535 pub fn disable_double_buffer(&mut self, present: bool) {
537 self.render_pending_console();
538 if present {
539 self.present_inner(true);
540 }
541 self.canm().draw_to_back = false;
542 self.canm().track_dirty = false;
543 self.clear_dirty();
544 }
545
546 pub fn present(&mut self) {
548 self.present_inner(false);
549 }
550
551 fn present_inner(&mut self, force: bool) {
552 if !self.enabled {
553 return;
554 }
555
556 if !self.console_pending && self.can().track_dirty && self.can().dirty.len == 0 {
557 self.canm().present_pending = false;
558 return;
559 }
560
561 let now = crate::process::scheduler::ticks();
563 if !force
564 && now != 0
565 && now.saturating_sub(self.can().last_present_tick) < PRESENT_MIN_TICKS
566 {
567 self.canm().present_pending = true;
568 return;
569 }
570
571 self.render_pending_console();
574
575 let bpp = self.fmt.bpp;
577 let fb_addr = self.can().addr;
578 let pitch = self.can().pitch;
579 let fb_width = self.can().width;
580 let track_dirty = self.can().track_dirty;
581
582 if track_dirty && self.can().dirty.len == 0 {
584 return;
585 }
586
587 let region_count;
589 let mut regions = [ClipRect {
590 x: 0,
591 y: 0,
592 w: 0,
593 h: 0,
594 }; MAX_DIRTY_RECTS];
595 if track_dirty {
596 region_count = self.can().dirty.len;
597 for idx in 0..region_count {
598 let r = self.can().dirty.rects[idx];
599 if r.is_valid() {
600 regions[idx] = ClipRect {
601 x: r.x0 as usize,
602 y: r.y0 as usize,
603 w: r.width() as usize,
604 h: r.height() as usize,
605 };
606 }
607 }
608 } else {
609 region_count = 1;
610 regions[0] = ClipRect {
611 x: 0,
612 y: 0,
613 w: fb_width,
614 h: self.can().height,
615 };
616 }
617
618 let buf_ptr = {
620 let Some(buf) = self.can().back_buffer.as_ref() else {
621 return;
622 };
623 buf.as_ptr()
624 };
625
626 for region_idx in 0..region_count {
628 let region = regions[region_idx];
629 if region.w == 0 || region.h == 0 {
630 continue;
631 }
632
633 if bpp == 32 {
634 let row_bytes = region.w * 4;
635 if region.x == 0 && region.w == fb_width && pitch == row_bytes {
636 unsafe {
638 core::ptr::copy_nonoverlapping(
639 buf_ptr.add(region.y * fb_width) as *const u8,
640 fb_addr.add(region.y * pitch),
641 row_bytes * region.h,
642 );
643 }
644 } else {
645 for y in region.y..(region.y + region.h) {
646 let src = unsafe { buf_ptr.add(y * fb_width + region.x) as *const u8 };
647 let dst_off = y * pitch + region.x * 4;
648 unsafe {
649 core::ptr::copy_nonoverlapping(src, fb_addr.add(dst_off), row_bytes);
650 }
651 }
652 }
653 } else {
654 let row_bytes = region.w * 3;
658 const MAX_STACK_ROW: usize = 3840 * 3;
659 if row_bytes <= MAX_STACK_ROW {
660 let mut row_buf = [0u8; MAX_STACK_ROW];
661 for y in region.y..(region.y + region.h) {
662 let src_row = y * fb_width + region.x;
663 for x in 0..region.w {
664 let packed = unsafe { *buf_ptr.add(src_row + x) };
665 let off = x * 3;
666 row_buf[off] = packed as u8;
667 row_buf[off + 1] = (packed >> 8) as u8;
668 row_buf[off + 2] = (packed >> 16) as u8;
669 }
670 let dst_off = y * pitch + region.x * 3;
671 unsafe {
672 core::ptr::copy_nonoverlapping(
673 row_buf.as_ptr(),
674 fb_addr.add(dst_off),
675 row_bytes,
676 );
677 }
678 }
679 } else {
680 let mut row_buf = alloc::vec![0u8; row_bytes];
681 for y in region.y..(region.y + region.h) {
682 let src_row = y * fb_width + region.x;
683 for x in 0..region.w {
684 let packed = unsafe { *buf_ptr.add(src_row + x) };
685 let off = x * 3;
686 row_buf[off] = packed as u8;
687 row_buf[off + 1] = (packed >> 8) as u8;
688 row_buf[off + 2] = (packed >> 16) as u8;
689 }
690 let dst_off = y * pitch + region.x * 3;
691 unsafe {
692 core::ptr::copy_nonoverlapping(
693 row_buf.as_ptr(),
694 fb_addr.add(dst_off),
695 row_bytes,
696 );
697 }
698 }
699 }
700 }
701
702 VGA_PRESENT_REGION_COUNT.fetch_add(1, Ordering::Relaxed);
703 VGA_PRESENT_PIXEL_COUNT
704 .fetch_add(region.w.saturating_mul(region.h) as u64, Ordering::Relaxed);
705 }
706
707 PRESENTED_FRAMES.fetch_add(1, Ordering::Relaxed);
708 self.clear_dirty();
709 self.canm().present_pending = false;
710 self.canm().last_present_tick = crate::process::scheduler::ticks();
711
712 if crate::hardware::virtio::gpu::is_available() {
713 if let Some(gpu) = crate::hardware::virtio::gpu::get_gpu() {
714 gpu.flush_now();
715 }
716 }
717 }
718
719 pub(crate) fn request_present(&mut self) {
720 if !self.draw_to_back_buffer() {
721 return;
722 }
723 self.canm().present_pending = true;
724 self.present_if_due(crate::process::scheduler::ticks() == 0);
726 }
727
728 pub(crate) fn present_if_due(&mut self, force: bool) {
729 if !self.canm().present_pending || !self.draw_to_back_buffer() {
730 return;
731 }
732 let now = crate::process::scheduler::ticks();
733 if force || now.saturating_sub(self.canm().last_present_tick) >= PRESENT_MIN_TICKS {
734 self.present_inner(force);
735 }
736 }
737
738 pub(crate) fn mc_save_hw(&mut self) {
743 let x = self.cursor.mc_x;
744 let y = self.cursor.mc_y;
745 let fw = self.can().width;
746 let fh = self.can().height;
747 for cy in 0..CURSOR_H {
748 for cx in 0..CURSOR_W {
749 let px = x + cx as i32;
750 let py = y + cy as i32;
751 if px < 0 || py < 0 || px as usize >= fw || py as usize >= fh {
752 self.cursor.mc_save[cy * CURSOR_W + cx] = 0;
753 continue;
754 }
755 self.cursor.mc_save[cy * CURSOR_W + cx] =
756 self.read_pixel_packed(px as usize, py as usize);
757 }
758 }
759 }
760
761 pub(crate) fn mc_draw_hw(&mut self) {
764 let x = self.cursor.mc_x;
765 let y = self.cursor.mc_y;
766 let black = self.pack_color(RgbColor::BLACK);
767 let white = self.pack_color(RgbColor::WHITE);
768 for cy in 0..CURSOR_H {
769 for cx in 0..CURSOR_W {
770 let p = CURSOR_PIXELS[cy * CURSOR_W + cx];
771 if p == 0 {
772 continue;
773 }
774 let px = x + cx as i32;
775 let py = y + cy as i32;
776 if px < 0
777 || py < 0
778 || px as usize >= self.can().width
779 || py as usize >= self.can().height
780 {
781 continue;
782 }
783 let color = if p == 1 { black } else { white };
784 self.put_pixel_raw(px as usize, py as usize, color);
785 }
786 }
787 }
788
789 pub(crate) fn mc_erase_hw(&mut self) {
792 let x = self.cursor.mc_x;
793 let y = self.cursor.mc_y;
794 for cy in 0..CURSOR_H {
795 for cx in 0..CURSOR_W {
796 if CURSOR_PIXELS[cy * CURSOR_W + cx] == 0 {
797 continue;
798 }
799 let px = x + cx as i32;
800 let py = y + cy as i32;
801 if px < 0
802 || py < 0
803 || px as usize >= self.can().width
804 || py as usize >= self.can().height
805 {
806 continue;
807 }
808 self.put_pixel_raw(
809 px as usize,
810 py as usize,
811 self.cursor.mc_save[cy * CURSOR_W + cx],
812 );
813 }
814 }
815 }
816
817 pub fn update_mouse_cursor(&mut self, x: i32, y: i32) {
819 if !self.enabled {
820 return;
821 }
822 if self.cursor.mc_visible && self.cursor.mc_x == x && self.cursor.mc_y == y {
823 self.present_if_due(false);
824 return;
825 }
826 if self.cursor.mc_visible {
827 self.mc_erase_hw();
828 }
829 self.cursor.mc_x = x;
830 self.cursor.mc_y = y;
831 self.mc_save_hw();
832 self.mc_draw_hw();
833 self.cursor.mc_visible = true;
834 self.request_present();
835 }
836
837 pub fn hide_mouse_cursor(&mut self) {
839 if self.cursor.mc_visible {
840 self.mc_erase_hw();
841 self.cursor.mc_visible = false;
842 }
843 self.request_present();
844 }
845
846 pub(crate) fn text_cursor_rect(&self) -> Option<(usize, usize, usize, usize)> {
847 if !self.enabled {
848 return None;
849 }
850 let (gw, gh) = self.glyph_size();
851 if gw == 0 || gh == 0 {
852 return None;
853 }
854 let x = self.cursor.tc_col.saturating_mul(gw);
855 let y = self.cursor.tc_row.saturating_mul(gh);
856 if x >= self.can().width || y >= self.can().height {
857 return None;
858 }
859 Some((
860 x,
861 y,
862 gw.min(TEXT_CURSOR_MAX_DIM).min(self.can().width - x),
863 gh.min(TEXT_CURSOR_MAX_DIM).min(self.can().height - y),
864 ))
865 }
866
867 pub(crate) fn text_cursor_save_hw(&mut self) {
869 let Some((x, y, w, h)) = self.text_cursor_rect() else {
870 self.cursor.tc_w = 0;
871 self.cursor.tc_h = 0;
872 return;
873 };
874 self.cursor.tc_w = w;
875 self.cursor.tc_h = h;
876 for cy in 0..h {
877 for cx in 0..w {
878 self.cursor.tc_save[cy * TEXT_CURSOR_MAX_DIM + cx] =
879 self.read_pixel_packed(x + cx, y + cy);
880 }
881 }
882 }
883
884 pub(crate) fn text_cursor_draw_hw(&mut self) {
886 let Some((x, y, w, h)) = self.text_cursor_rect() else {
887 return;
888 };
889 for cy in 0..h {
890 for cx in 0..w {
891 self.put_pixel_raw(x + cx, y + cy, self.cursor.tc_color);
892 }
893 }
894 }
895
896 pub(crate) fn text_cursor_erase_hw(&mut self) {
898 let Some((x, y, w, h)) = self.text_cursor_rect() else {
899 return;
900 };
901 let restore_w = w.min(self.cursor.tc_w);
902 let restore_h = h.min(self.cursor.tc_h);
903 for cy in 0..restore_h {
904 for cx in 0..restore_w {
905 self.put_pixel_raw(
906 x + cx,
907 y + cy,
908 self.cursor.tc_save[cy * TEXT_CURSOR_MAX_DIM + cx],
909 );
910 }
911 }
912 }
913
914 pub(crate) fn draw_text_cursor_overlay(&mut self, color: RgbColor) {
915 if !self.enabled {
916 return;
917 }
918 let packed = self.pack_color(color);
919 if self.cursor.tc_visible {
920 self.text_cursor_erase_hw();
921 self.cursor.tc_visible = false;
922 self.cursor.tc_dirty = true;
923 }
924 if packed == self.bg {
925 self.present();
926 return;
927 }
928 self.cursor.tc_col = self.col;
929 self.cursor.tc_row = self.row;
930 self.cursor.tc_color = packed;
931 self.text_cursor_save_hw();
932 self.text_cursor_draw_hw();
933 self.cursor.tc_visible = true;
934 self.present();
938 }
939
940 pub fn hide_text_cursor(&mut self) {
942 if self.cursor.tc_visible {
943 self.text_cursor_erase_hw();
944 self.cursor.tc_visible = false;
945 self.present();
946 }
947 }
948
949 pub(crate) fn sel_normalized(&self) -> (usize, usize, usize, usize) {
953 let (sr, sc, er, ec) = (
954 self.sel_start_row,
955 self.sel_start_col,
956 self.sel_end_row,
957 self.sel_end_col,
958 );
959 if sr < er || (sr == er && sc <= ec) {
960 (sr, sc, er, ec)
961 } else {
962 (er, ec, sr, sc)
963 }
964 }
965
966 pub fn pixel_to_sb_pos(&self, px: usize, py: usize) -> Option<(usize, usize)> {
968 if !self.enabled {
969 return None;
970 }
971 let gw = self.font_info.glyph_w;
972 let gh = self.font_info.glyph_h;
973 if gw == 0 || gh == 0 {
974 return None;
975 }
976 let text_h = self.text_area_height();
977 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
978 if px >= text_w || py >= text_h {
979 return None;
980 }
981 let vis_row = py / gh;
982 let vis_col = px / gw;
983 if vis_col >= self.cols {
984 return None;
985 }
986 let total_complete = self.sb.rows.len();
987 let has_partial = !self.sb.cur_row.is_empty();
988 let total_virtual = total_complete + if has_partial { 1 } else { 0 };
989 if total_virtual == 0 {
990 return None;
991 }
992 let view_end = total_virtual.saturating_sub(self.scroll_offset);
993 let view_start = view_end.saturating_sub(self.rows);
994 let display_len = view_end.saturating_sub(view_start);
995 if vis_row >= display_len {
996 return None;
997 }
998 Some((view_start + vis_row, vis_col))
999 }
1000
1001 pub fn start_selection(&mut self, px: usize, py: usize) {
1003 if let Some((row, col)) = self.pixel_to_sb_pos(px, py) {
1004 self.sel_start_row = row;
1005 self.sel_start_col = col;
1006 self.sel_end_row = row;
1007 self.sel_end_col = col;
1008 self.sel_active = true;
1009 self.render_viewport_full();
1010 }
1011 }
1012
1013 pub fn update_selection(&mut self, px: usize, py: usize) {
1015 if !self.sel_active {
1016 return;
1017 }
1018 if let Some((row, col)) = self.pixel_to_sb_pos(px, py) {
1019 if row == self.sel_end_row && col == self.sel_end_col {
1020 return;
1021 }
1022 self.sel_end_row = row;
1023 self.sel_end_col = col;
1024 self.render_viewport_full();
1025 }
1026 }
1027
1028 pub fn end_selection(&mut self) {
1030 if !self.sel_active {
1031 return;
1032 }
1033 let (start_row, start_col, end_row, end_col) = self.sel_normalized();
1034 let mut bytes: alloc::vec::Vec<u8> = alloc::vec::Vec::new();
1035 for row in start_row..=end_row {
1036 let len = if row < self.sb.rows.len() {
1037 self.sb_row_at(row).map(|r| r.len()).unwrap_or(0)
1038 } else if row == self.sb.rows.len() {
1039 self.sb.cur_row.len()
1040 } else {
1041 break;
1042 };
1043 let c0 = if row == start_row {
1044 start_col.min(len)
1045 } else {
1046 0
1047 };
1048 let c1 = if row == end_row {
1049 end_col.min(len)
1050 } else {
1051 len
1052 };
1053 for col in c0..c1 {
1054 let ch = if row < self.sb.rows.len() {
1055 self.sb_row_at(row)
1056 .and_then(|r| r.get(col))
1057 .map(|cell| cell.ch)
1058 .unwrap_or(' ')
1059 } else {
1060 self.sb.cur_row[col].ch
1061 };
1062 let mut buf = [0u8; 4];
1063 bytes.extend_from_slice(ch.encode_utf8(&mut buf).as_bytes());
1064 }
1065 if row < end_row {
1066 bytes.push(b'\n');
1067 }
1068 }
1069 if let Some(mut clip) = CLIPBOARD.try_lock() {
1070 let n = bytes.len().min(CLIPBOARD_CAP);
1071 clip.0[..n].copy_from_slice(&bytes[..n]);
1072 clip.1 = n;
1073 }
1074 }
1075
1076 pub fn clear_selection(&mut self) {
1078 if self.sel_active {
1079 self.sel_active = false;
1080 self.render_viewport_full();
1081 }
1082 }
1083
1084 pub fn clear_with(&mut self, color: RgbColor) {
1086 if !self.enabled {
1087 return;
1088 }
1089 self.console_pending = false;
1090 self.console_pending_scroll = 0;
1091 self.console_dirty_cells.fill(false);
1092 let packed = self.pack_color(color);
1093 let canvas = self.can();
1094 let (fw, fh) = (canvas.width, canvas.height);
1095
1096 if canvas.draw_to_back && canvas.back_buffer.is_some() {
1097 let _ = canvas;
1098 if let Some(buf) = self.canm().back_buffer.as_mut() {
1099 buf.fill(packed);
1100 }
1101 self.mark_dirty_rect(0, 0, fw, fh);
1102 self.col = 0;
1103 self.row = 0;
1104 return;
1105 }
1106
1107 let _ = canvas;
1108 if self.fmt.bpp == 32 {
1110 let canvas = self.can();
1111 let _row_bytes = fw * 4;
1112 for y in 0..fh {
1113 let off = y * canvas.pitch;
1114 unsafe {
1115 let ptr = canvas.addr.add(off) as *mut u32;
1116 for x in 0..fw {
1117 core::ptr::write_volatile(ptr.add(x), packed);
1118 }
1119 }
1120 }
1121 } else {
1122 for y in 0..fh {
1123 for x in 0..fw {
1124 self.write_hw_pixel_packed(x, y, packed);
1125 }
1126 }
1127 }
1128 self.col = 0;
1129 self.row = 0;
1130 }
1131
1132 pub fn clear(&mut self) {
1134 self.clear_with(self.unpack_color(self.bg));
1135 }
1136
1137 #[inline]
1139 pub(crate) fn pixel_offset(&self, x: usize, y: usize) -> Option<usize> {
1140 if x >= self.can().width || y >= self.can().height {
1141 return None;
1142 }
1143 let bytes_pp = self.fmt.bpp as usize / 8;
1144 let row = y.checked_mul(self.can().pitch)?;
1145 let col = x.checked_mul(bytes_pp)?;
1146 row.checked_add(col)
1147 }
1148
1149 pub(crate) fn write_hw_pixel_packed(&mut self, x: usize, y: usize, color: u32) {
1151 let Some(off) = self.pixel_offset(x, y) else {
1152 return;
1153 };
1154 unsafe {
1155 match self.fmt.bpp {
1156 32 => {
1157 core::ptr::write_volatile(self.can().addr.add(off) as *mut u32, color);
1158 }
1159 24 => {
1160 core::ptr::write_volatile(self.can().addr.add(off), (color & 0xFF) as u8);
1161 core::ptr::write_volatile(
1162 self.can().addr.add(off + 1),
1163 ((color >> 8) & 0xFF) as u8,
1164 );
1165 core::ptr::write_volatile(
1166 self.can().addr.add(off + 2),
1167 ((color >> 16) & 0xFF) as u8,
1168 );
1169 }
1170 _ => {}
1171 }
1172 }
1173 }
1174
1175 pub(crate) fn read_hw_pixel_packed(&self, x: usize, y: usize) -> u32 {
1177 let Some(off) = self.pixel_offset(x, y) else {
1178 return 0;
1179 };
1180 unsafe {
1181 match self.fmt.bpp {
1182 32 => core::ptr::read_volatile(self.can().addr.add(off) as *const u32),
1183 24 => {
1184 let b0 = core::ptr::read_volatile(self.can().addr.add(off)) as u32;
1185 let b1 = core::ptr::read_volatile(self.can().addr.add(off + 1)) as u32;
1186 let b2 = core::ptr::read_volatile(self.can().addr.add(off + 2)) as u32;
1187 b0 | (b1 << 8) | (b2 << 16)
1188 }
1189 _ => 0,
1190 }
1191 }
1192 }
1193
1194 pub(crate) fn read_pixel_packed(&self, x: usize, y: usize) -> u32 {
1196 if x >= self.can().width || y >= self.can().height {
1197 return 0;
1198 }
1199 if self.draw_to_back_buffer() {
1200 if let Some(buf) = self.can().back_buffer.as_ref() {
1201 return buf[y * self.can().width + x];
1202 }
1203 }
1204 self.read_hw_pixel_packed(x, y)
1205 }
1206
1207 pub(crate) fn put_pixel_raw(&mut self, x: usize, y: usize, color: u32) {
1209 if !self.enabled {
1210 return;
1211 }
1212 let canvas = self.can();
1214 let (fw, fh) = (canvas.width, canvas.height);
1215 if x >= fw || y >= fh || !self.in_clip(x, y) {
1216 return;
1217 }
1218 if canvas.draw_to_back && canvas.back_buffer.is_some() {
1219 let _ = canvas;
1220 if let Some(buf) = self.canm().back_buffer.as_mut() {
1221 buf[y * fw + x] = color;
1222 }
1223 self.mark_dirty_rect(x, y, 1, 1);
1224 return;
1225 }
1226 let _ = canvas;
1227 self.write_hw_pixel_packed(x, y, color);
1228 }
1229
1230 pub fn draw_pixel(&mut self, x: usize, y: usize, color: RgbColor) {
1232 self.render_pending_console();
1233 self.put_pixel_raw(x, y, self.pack_color(color));
1234 }
1235
1236 pub fn draw_pixel_alpha(&mut self, x: usize, y: usize, color: RgbColor, alpha: u8) {
1238 self.render_pending_console();
1239 if !self.enabled
1240 || alpha == 0
1241 || x >= self.can().width
1242 || y >= self.can().height
1243 || !self.in_clip(x, y)
1244 {
1245 return;
1246 }
1247 if alpha == 255 {
1248 self.put_pixel_raw(x, y, self.pack_color(color));
1249 return;
1250 }
1251 let dst = self.unpack_color(self.read_pixel_packed(x, y));
1252 let inv = (255u16).saturating_sub(alpha as u16);
1253 let a = alpha as u16;
1254 let blended = RgbColor::new(
1255 ((color.r as u16 * a + dst.r as u16 * inv + 127) / 255) as u8,
1256 ((color.g as u16 * a + dst.g as u16 * inv + 127) / 255) as u8,
1257 ((color.b as u16 * a + dst.b as u16 * inv + 127) / 255) as u8,
1258 );
1259 self.put_pixel_raw(x, y, self.pack_color(blended));
1260 }
1261
1262 pub fn draw_line(&mut self, x0: isize, y0: isize, x1: isize, y1: isize, color: RgbColor) {
1264 self.render_pending_console();
1265 let mut x = x0;
1266 let mut y = y0;
1267 let dx = (x1 - x0).abs();
1268 let sx = if x0 < x1 { 1 } else { -1 };
1269 let dy = -(y1 - y0).abs();
1270 let sy = if y0 < y1 { 1 } else { -1 };
1271 let mut err = dx + dy;
1272 let packed = self.pack_color(color);
1273
1274 loop {
1275 if x >= 0 && y >= 0 {
1276 self.put_pixel_raw(x as usize, y as usize, packed);
1277 }
1278 if x == x1 && y == y1 {
1279 break;
1280 }
1281 let e2 = 2 * err;
1282 if e2 >= dy {
1283 err += dy;
1284 x += sx;
1285 }
1286 if e2 <= dx {
1287 err += dx;
1288 y += sy;
1289 }
1290 }
1291 }
1292
1293 pub fn draw_rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: RgbColor) {
1295 self.render_pending_console();
1296 if width == 0 || height == 0 {
1297 return;
1298 }
1299 let x2 = x.saturating_add(width - 1);
1300 let y2 = y.saturating_add(height - 1);
1301 self.draw_line(x as isize, y as isize, x2 as isize, y as isize, color);
1302 self.draw_line(x as isize, y as isize, x as isize, y2 as isize, color);
1303 self.draw_line(x2 as isize, y as isize, x2 as isize, y2 as isize, color);
1304 self.draw_line(x as isize, y2 as isize, x2 as isize, y2 as isize, color);
1305 }
1306
1307 pub fn fill_rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: RgbColor) {
1309 self.render_pending_console();
1310 let Some((sx, sy, sw, sh)) = self.clipped_rect(x, y, width, height) else {
1311 return;
1312 };
1313 let sw = sw.min(self.can().width.saturating_sub(sx));
1314 let sh = sh.min(self.can().height.saturating_sub(sy));
1315 if sw == 0 || sh == 0 {
1316 return;
1317 }
1318 let packed = self.pack_color(color);
1319
1320 if self.draw_to_back_buffer() {
1321 let fw = self.can().width;
1322 let wrote = self
1323 .canm()
1324 .back_buffer
1325 .as_mut()
1326 .map(|buf| {
1327 for py in sy..(sy + sh) {
1328 let row = py * fw;
1329 let start = row + sx;
1330 let end = start + sw;
1331 buf[start..end].fill(packed);
1332 }
1333 })
1334 .is_some();
1335 if wrote {
1336 self.mark_dirty_rect(sx, sy, sw, sh);
1337 }
1338 return;
1339 }
1340
1341 if self.fmt.bpp == 32 {
1342 for py in sy..(sy + sh) {
1343 let Some(row_off) = py
1344 .checked_mul(self.can().pitch)
1345 .and_then(|v| v.checked_add(sx * 4))
1346 else {
1347 continue;
1348 };
1349 let count = sw;
1350 unsafe {
1351 let ptr = self.can().addr.add(row_off) as *mut u32;
1352 for i in 0..count {
1353 core::ptr::write_volatile(ptr.add(i), packed);
1354 }
1355 }
1356 }
1357 return;
1358 }
1359
1360 for py in sy..(sy + sh) {
1361 for px in sx..(sx + sw) {
1362 self.write_hw_pixel_packed(px, py, packed);
1363 }
1364 }
1365 }
1366
1367 pub fn fill_rect_alpha(
1369 &mut self,
1370 x: usize,
1371 y: usize,
1372 width: usize,
1373 height: usize,
1374 color: RgbColor,
1375 alpha: u8,
1376 ) {
1377 self.render_pending_console();
1378 if !self.enabled || width == 0 || height == 0 || alpha == 0 {
1379 return;
1380 }
1381 if alpha == 255 {
1382 self.fill_rect(x, y, width, height, color);
1383 return;
1384 }
1385 let x_end = core::cmp::min(x.saturating_add(width), self.can().width);
1386 let y_end = core::cmp::min(y.saturating_add(height), self.can().height);
1387 for py in y..y_end {
1388 for px in x..x_end {
1389 self.draw_pixel_alpha(px, py, color, alpha);
1390 }
1391 }
1392 }
1393
1394 pub fn blit_rgb(
1396 &mut self,
1397 dst_x: usize,
1398 dst_y: usize,
1399 src_width: usize,
1400 src_height: usize,
1401 pixels: &[RgbColor],
1402 ) -> bool {
1403 self.render_pending_console();
1404 let len = src_width.saturating_mul(src_height);
1405 if !self.enabled || src_width == 0 || src_height == 0 || pixels.len() < len {
1406 return false;
1407 }
1408 let x_end = core::cmp::min(dst_x.saturating_add(src_width), self.can().width);
1409 let y_end = core::cmp::min(dst_y.saturating_add(src_height), self.can().height);
1410 if x_end <= dst_x || y_end <= dst_y {
1411 return true;
1412 }
1413 let copy_w = x_end - dst_x;
1414 let copy_h = y_end - dst_y;
1415
1416 let fmt = self.fmt;
1417 if self.draw_to_back_buffer() {
1418 let fb_width = self.can().width;
1419 let wrote = self
1420 .canm()
1421 .back_buffer
1422 .as_mut()
1423 .map(|buf| {
1424 for row in 0..copy_h {
1425 let src_row = row * src_width;
1426 let dst_row = (dst_y + row) * fb_width + dst_x;
1427 for col in 0..copy_w {
1428 buf[dst_row + col] = fmt.pack_rgb(
1429 pixels[src_row + col].r,
1430 pixels[src_row + col].g,
1431 pixels[src_row + col].b,
1432 );
1433 }
1434 }
1435 })
1436 .is_some();
1437 if wrote {
1438 self.mark_dirty_rect(dst_x, dst_y, copy_w, copy_h);
1439 return true;
1440 }
1441 }
1442
1443 if fmt.bpp == 32 {
1444 let mut row_buf = alloc::vec![0u32; copy_w];
1445 for row in 0..copy_h {
1446 let src_row = row * src_width;
1447 for col in 0..copy_w {
1448 row_buf[col] = self.pack_color(pixels[src_row + col]);
1449 }
1450 if let Some(off) = self.pixel_offset(dst_x, dst_y + row) {
1451 unsafe {
1452 core::ptr::copy_nonoverlapping(
1453 row_buf.as_ptr(),
1454 self.can().addr.add(off) as *mut u32,
1455 copy_w,
1456 );
1457 }
1458 }
1459 }
1460 } else {
1461 for row in 0..copy_h {
1462 let src_row = row * src_width;
1463 for col in 0..copy_w {
1464 self.draw_pixel(dst_x + col, dst_y + row, pixels[src_row + col]);
1465 }
1466 }
1467 }
1468 true
1469 }
1470
1471 pub fn blit_rgb24(
1473 &mut self,
1474 dst_x: usize,
1475 dst_y: usize,
1476 src_width: usize,
1477 src_height: usize,
1478 bytes: &[u8],
1479 ) -> bool {
1480 self.render_pending_console();
1481 let needed = src_width.saturating_mul(src_height).saturating_mul(3);
1482 if !self.enabled || src_width == 0 || src_height == 0 || bytes.len() < needed {
1483 return false;
1484 }
1485 let x_end = core::cmp::min(dst_x.saturating_add(src_width), self.can().width);
1486 let y_end = core::cmp::min(dst_y.saturating_add(src_height), self.can().height);
1487 if x_end <= dst_x || y_end <= dst_y {
1488 return true;
1489 }
1490 let copy_w = x_end - dst_x;
1491 let copy_h = y_end - dst_y;
1492
1493 let fmt = self.fmt;
1494 if self.draw_to_back_buffer() {
1495 let fb_width = self.can().width;
1496 let wrote = self
1497 .canm()
1498 .back_buffer
1499 .as_mut()
1500 .map(|buf| {
1501 for row in 0..copy_h {
1502 let src_base = row * src_width * 3;
1503 let dst_row = (dst_y + row) * fb_width + dst_x;
1504 for col in 0..copy_w {
1505 let i = src_base + col * 3;
1506 buf[dst_row + col] = fmt.pack_rgb(bytes[i], bytes[i + 1], bytes[i + 2]);
1507 }
1508 }
1509 })
1510 .is_some();
1511 if wrote {
1512 self.mark_dirty_rect(dst_x, dst_y, copy_w, copy_h);
1513 return true;
1514 }
1515 }
1516
1517 if fmt.bpp == 32 {
1518 let mut row_buf = alloc::vec![0u32; copy_w];
1519 for row in 0..copy_h {
1520 let src_base = row * src_width * 3;
1521 for col in 0..copy_w {
1522 let i = src_base + col * 3;
1523 row_buf[col] =
1524 self.pack_color(RgbColor::new(bytes[i], bytes[i + 1], bytes[i + 2]));
1525 }
1526 if let Some(off) = self.pixel_offset(dst_x, dst_y + row) {
1527 unsafe {
1528 core::ptr::copy_nonoverlapping(
1529 row_buf.as_ptr(),
1530 self.can().addr.add(off) as *mut u32,
1531 copy_w,
1532 );
1533 }
1534 }
1535 }
1536 } else {
1537 for row in 0..copy_h {
1538 for col in 0..copy_w {
1539 let i = (row * src_width + col) * 3;
1540 let color = RgbColor::new(bytes[i], bytes[i + 1], bytes[i + 2]);
1541 self.draw_pixel(dst_x + col, dst_y + row, color);
1542 }
1543 }
1544 }
1545 true
1546 }
1547
1548 pub fn blit_rgba(
1550 &mut self,
1551 dst_x: usize,
1552 dst_y: usize,
1553 src_width: usize,
1554 src_height: usize,
1555 bytes: &[u8],
1556 global_alpha: u8,
1557 ) -> bool {
1558 self.render_pending_console();
1559 let needed = src_width.saturating_mul(src_height).saturating_mul(4);
1560 if !self.enabled
1561 || src_width == 0
1562 || src_height == 0
1563 || bytes.len() < needed
1564 || global_alpha == 0
1565 {
1566 return false;
1567 }
1568
1569 let Some((sx, sy, sw, sh)) = self.clipped_rect(dst_x, dst_y, src_width, src_height) else {
1570 return true;
1571 };
1572 let src_x0 = sx.saturating_sub(dst_x);
1573 let src_y0 = sy.saturating_sub(dst_y);
1574
1575 for row in 0..sh {
1576 let syi = src_y0 + row;
1577 for col in 0..sw {
1578 let sxi = src_x0 + col;
1579 let i = (syi * src_width + sxi) * 4;
1580 let r = bytes[i];
1581 let g = bytes[i + 1];
1582 let b = bytes[i + 2];
1583 let sa = bytes[i + 3];
1584 if sa == 0 {
1585 continue;
1586 }
1587 let a = ((sa as u16 * global_alpha as u16 + 127) / 255) as u8;
1588 let dx = sx + col;
1589 let dy = sy + row;
1590 if a == 255 {
1591 self.put_pixel_raw(dx, dy, self.pack_color(RgbColor::new(r, g, b)));
1592 } else if a != 0 {
1593 self.draw_pixel_alpha(dx, dy, RgbColor::new(r, g, b), a);
1594 }
1595 }
1596 }
1597 true
1598 }
1599
1600 pub fn blit_sprite_rgba(
1602 &mut self,
1603 dst_x: usize,
1604 dst_y: usize,
1605 sprite: SpriteRgba<'_>,
1606 global_alpha: u8,
1607 ) -> bool {
1608 self.render_pending_console();
1609 self.blit_rgba(
1610 dst_x,
1611 dst_y,
1612 sprite.width,
1613 sprite.height,
1614 sprite.pixels,
1615 global_alpha,
1616 )
1617 }
1618
1619 pub fn draw_text_at(
1621 &mut self,
1622 pixel_x: usize,
1623 pixel_y: usize,
1624 text: &str,
1625 fg: RgbColor,
1626 bg: RgbColor,
1627 ) {
1628 self.render_pending_console();
1629 if !self.enabled {
1630 return;
1631 }
1632 let gw = self.font_info.glyph_w;
1633 let gh = self.font_info.glyph_h;
1634 let fg_packed = self.pack_color(fg);
1635 let bg_packed = self.pack_color(bg);
1636 let mut cx = pixel_x;
1637 let cy = pixel_y;
1638 for ch in text.chars() {
1639 if ch == '\n' {
1640 break;
1641 }
1642 if cx + gw > self.can().width || cy + gh > self.can().height {
1643 break;
1644 }
1645 self.draw_glyph_at_pixel(cx, cy, ch, fg_packed, bg_packed);
1646 cx += gw;
1647 }
1648 }
1649
1650 pub(crate) fn glyph_index_for_char(&self, ch: char) -> usize {
1652 if ch.is_ascii() {
1653 let idx = ch as usize;
1654 if idx < self.font_info.glyph_count {
1655 return idx;
1656 }
1657 }
1658 let cp = ch as u32;
1659 if let Some((_, glyph)) = self.unicode_map.iter().find(|(u, _)| *u == cp) {
1660 return *glyph;
1661 }
1662 if let Some((_, glyph)) = self.unicode_map.iter().find(|(u, _)| *u == ('?' as u32)) {
1663 return *glyph;
1664 }
1665 if ('?' as usize) < self.font_info.glyph_count {
1666 return '?' as usize;
1667 }
1668 0
1669 }
1670
1671 pub(crate) fn draw_glyph_index_at_pixel(
1673 &mut self,
1674 pixel_x: usize,
1675 pixel_y: usize,
1676 glyph_index: usize,
1677 fg: u32,
1678 bg: u32,
1679 ) {
1680 if !self.enabled {
1681 return;
1682 }
1683 let glyph_index = core::cmp::min(glyph_index, self.font_info.glyph_count.saturating_sub(1));
1684 let gw = self.font_info.glyph_w;
1685 let gh = self.font_info.glyph_h;
1686 let glyph_pixels = gw.saturating_mul(gh);
1687 let Some(mask) = self.glyph_mask_slice(glyph_index) else {
1688 return;
1689 };
1690 let mask_ptr = mask.as_ptr();
1691
1692 if self.draw_to_back_buffer()
1693 && pixel_x + gw <= self.can().width
1694 && pixel_y + gh <= self.can().height
1695 {
1696 let fb_width = self.can().width;
1697 if let Some(buf) = self.canm().back_buffer.as_mut() {
1698 for gy in 0..gh {
1699 let row_start = (pixel_y + gy) * fb_width + pixel_x;
1700 buf[row_start..row_start + gw].fill(bg);
1701 for gx in 0..gw {
1702 let idx = gy * gw + gx;
1703 if idx >= glyph_pixels {
1704 continue;
1705 }
1706 let bit = unsafe { *mask_ptr.add(idx) };
1707 if bit != 0 {
1708 buf[row_start + gx] = fg;
1709 }
1710 }
1711 }
1712 }
1713 self.mark_dirty_rect(pixel_x, pixel_y, gw, gh);
1714 } else {
1715 if self.fmt.bpp == 32 {
1716 for gy in 0..gh {
1717 if let Some(off) = self.pixel_offset(pixel_x, pixel_y + gy) {
1718 let row_ptr = unsafe { self.can().addr.add(off) } as *mut u32;
1719 for gx in 0..gw {
1720 let idx = gy * gw + gx;
1721 let color = if idx < glyph_pixels && unsafe { *mask_ptr.add(idx) } != 0
1722 {
1723 fg
1724 } else {
1725 bg
1726 };
1727 unsafe {
1728 core::ptr::write_volatile(row_ptr.add(gx), color);
1729 }
1730 }
1731 }
1732 }
1733 } else {
1734 for gy in 0..gh {
1735 for gx in 0..gw {
1736 let idx = gy * gw + gx;
1737 if idx >= glyph_pixels {
1738 continue;
1739 }
1740 let color = if unsafe { *mask_ptr.add(idx) } != 0 {
1741 fg
1742 } else {
1743 bg
1744 };
1745 self.put_pixel_raw(pixel_x + gx, pixel_y + gy, color);
1746 }
1747 }
1748 }
1749 }
1750 }
1751
1752 pub(crate) fn draw_glyph_at_pixel(
1754 &mut self,
1755 pixel_x: usize,
1756 pixel_y: usize,
1757 ch: char,
1758 fg: u32,
1759 bg: u32,
1760 ) {
1761 let glyph_index = self.glyph_index_for_char(ch);
1762 self.draw_glyph_index_at_pixel(pixel_x, pixel_y, glyph_index, fg, bg);
1763 }
1764
1765 pub(crate) fn fill_text_span_bg(
1766 &mut self,
1767 pixel_x: usize,
1768 pixel_y: usize,
1769 width: usize,
1770 height: usize,
1771 bg: u32,
1772 ) {
1773 if self.draw_to_back_buffer()
1774 && pixel_x + width <= self.can().width
1775 && pixel_y + height <= self.can().height
1776 {
1777 let fb_width = self.can().width;
1778 if let Some(buf) = self.canm().back_buffer.as_mut() {
1779 for row in 0..height {
1780 let start = (pixel_y + row) * fb_width + pixel_x;
1781 let end = start + width;
1782 buf[start..end].fill(bg);
1783 }
1784 }
1785 self.mark_dirty_rect(pixel_x, pixel_y, width, height);
1786 return;
1787 }
1788
1789 let gh = height;
1790 let color = self.unpack_color(bg);
1791 self.fill_rect(pixel_x, pixel_y, width, gh, color);
1792 }
1793
1794 pub(crate) fn clear_text_line_pixels(&mut self, vis_row: usize) {
1795 let gh = self.font_info.glyph_h;
1796 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
1797 self.fill_text_span_bg(0, vis_row.saturating_mul(gh), text_w, gh, self.bg);
1798 }
1799
1800 pub(crate) fn visible_virtual_bounds(&self) -> (usize, usize, usize, usize, bool) {
1801 let total_complete = self.sb.rows.len();
1802 let has_partial = !self.sb.cur_row.is_empty();
1803 let total_virtual = total_complete + if has_partial { 1 } else { 0 };
1804 let view_end = total_virtual.saturating_sub(self.scroll_offset);
1805 let view_start = view_end.saturating_sub(self.rows);
1806 (
1807 total_complete,
1808 total_virtual,
1809 view_start,
1810 view_end,
1811 has_partial,
1812 )
1813 }
1814
1815 pub(crate) fn sync_live_cursor_from_view(
1816 &mut self,
1817 total_complete: usize,
1818 view_start: usize,
1819 view_end: usize,
1820 ) {
1821 let display_len = view_end.saturating_sub(view_start);
1822 self.row = if display_len > 0 { display_len - 1 } else { 0 };
1823 let last_virt = view_start + display_len.saturating_sub(1);
1824 let last_len = if last_virt < total_complete {
1825 self.sb_row_at(last_virt).map(|row| row.len()).unwrap_or(0)
1826 } else if last_virt == total_complete {
1827 self.sb.cur_row.len()
1828 } else {
1829 0
1830 };
1831 self.col = last_len.min(self.cols);
1832 }
1833
1834 pub(crate) fn selection_colors_for_cell(
1835 &self,
1836 virt_row: usize,
1837 col: usize,
1838 fg: u32,
1839 bg: u32,
1840 ) -> (u32, u32) {
1841 if !self.sel_active {
1842 return (fg, bg);
1843 }
1844 let (sel_sr, sel_sc, sel_er, sel_ec) = self.sel_normalized();
1845 let in_sel = if virt_row < sel_sr || virt_row > sel_er {
1846 false
1847 } else if sel_sr == sel_er {
1848 col >= sel_sc && col < sel_ec
1849 } else if virt_row == sel_sr {
1850 col >= sel_sc
1851 } else if virt_row == sel_er {
1852 col < sel_ec
1853 } else {
1854 true
1855 };
1856 if in_sel {
1857 (
1858 self.pack_color(RgbColor::WHITE),
1859 self.pack_color(RgbColor::new(0x26, 0x5F, 0xCC)),
1860 )
1861 } else {
1862 (fg, bg)
1863 }
1864 }
1865
1866 pub(crate) fn render_virtual_row(
1867 &mut self,
1868 virt_row: usize,
1869 vis_row: usize,
1870 total_complete: usize,
1871 has_partial: bool,
1872 ) {
1873 let glyph_h = self.font_info.glyph_h;
1874 let glyph_w = self.font_info.glyph_w;
1875 let py = vis_row.saturating_mul(glyph_h);
1876
1877 let (row_ptr, row_len) = if virt_row < total_complete {
1878 let row = match self.sb_row_at(virt_row) {
1879 Some(row) => row,
1880 None => return,
1881 };
1882 (row.as_ptr(), row.len())
1883 } else if has_partial && virt_row == total_complete {
1884 (self.sb.cur_row.as_ptr(), self.sb.cur_row.len())
1885 } else {
1886 (core::ptr::null(), 0)
1887 };
1888
1889 let cell_count = row_len.min(self.cols);
1890 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
1891 let used_width = cell_count.saturating_mul(glyph_w).min(text_w);
1892
1893 if self.draw_to_back_buffer() && py + glyph_h <= self.can().height {
1894 let fb_width = self.can().width;
1895 let default_bg = self.bg;
1896 let glyph_pixels = glyph_w.saturating_mul(glyph_h);
1897 let glyph_cache_ptr = self.glyph_mask_cache.as_ptr();
1898 self.prepared_row_cells.clear();
1899 if self.prepared_row_cells.capacity() < cell_count {
1900 self.prepared_row_cells
1901 .reserve(cell_count - self.prepared_row_cells.capacity());
1902 }
1903 for col in 0..cell_count {
1904 let cell = unsafe { &*row_ptr.add(col) };
1905 let glyph_index = self.glyph_index_for_char(cell.ch);
1906 let (draw_fg, draw_bg) =
1907 self.selection_colors_for_cell(virt_row, col, cell.fg, cell.bg);
1908 self.prepared_row_cells
1909 .push((glyph_index, draw_fg, draw_bg));
1910 }
1911 let cells_ptr = self.prepared_row_cells.as_ptr();
1912 let cells_len = self.prepared_row_cells.len();
1913 if let Some(buf) = self.canm().back_buffer.as_mut() {
1914 for gy in 0..glyph_h {
1915 let row_start = (py + gy) * fb_width;
1916 buf[row_start..row_start + text_w].fill(default_bg);
1917 }
1918
1919 for col in 0..cells_len {
1920 let (glyph_index, draw_fg, draw_bg) = unsafe { *cells_ptr.add(col) };
1921 let px = col.saturating_mul(glyph_w);
1922 if draw_bg != default_bg {
1923 for gy in 0..glyph_h {
1924 let row_start = (py + gy) * fb_width + px;
1925 buf[row_start..row_start + glyph_w].fill(draw_bg);
1926 }
1927 }
1928
1929 let glyph_base = glyph_index.saturating_mul(glyph_pixels);
1930 for gy in 0..glyph_h {
1931 let row_start = (py + gy) * fb_width + px;
1932 for gx in 0..glyph_w {
1933 let idx = glyph_base + gy * glyph_w + gx;
1934 let bit = unsafe { *glyph_cache_ptr.add(idx) };
1935 if bit != 0 {
1936 buf[row_start + gx] = draw_fg;
1937 }
1938 }
1939 }
1940 }
1941 }
1942
1943 self.mark_dirty_rect(0, py, text_w, glyph_h);
1944 return;
1945 }
1946
1947 for col in 0..cell_count {
1948 let px = col.saturating_mul(glyph_w);
1949 let cell = unsafe { &*row_ptr.add(col) };
1950 let (draw_fg, draw_bg) =
1951 self.selection_colors_for_cell(virt_row, col, cell.fg, cell.bg);
1952 self.draw_glyph_at_pixel(px, py, cell.ch, draw_fg, draw_bg);
1953 }
1954 if text_w > used_width {
1955 self.fill_text_span_bg(used_width, py, text_w - used_width, glyph_h, self.bg);
1956 }
1957 }
1958
1959 pub(crate) fn redraw_visible_rows(&mut self, start_vis_row: usize, count: usize) {
1960 let (total_complete, _, view_start, view_end, has_partial) = self.visible_virtual_bounds();
1961 let display_len = view_end.saturating_sub(view_start);
1962 let end_vis_row = start_vis_row.saturating_add(count).min(self.rows);
1963 for vis_row in start_vis_row..end_vis_row {
1964 if vis_row < display_len {
1965 self.render_virtual_row(view_start + vis_row, vis_row, total_complete, has_partial);
1966 } else {
1967 self.clear_text_line_pixels(vis_row);
1968 }
1969 }
1970 }
1971
1972 pub(crate) fn ensure_back_buffer_for_viewport(&mut self) {
1973 let canvas = self.can();
1974 if canvas.back_buffer.is_none() {
1975 let total = canvas.width.saturating_mul(canvas.height);
1976 if total > 0 {
1977 let mut buf = alloc::vec![0u32; total];
1978 if self.enabled && !canvas.addr.is_null() {
1981 if self.fmt.bpp == 32 {
1982 unsafe {
1983 for y in 0..canvas.height {
1984 core::ptr::copy_nonoverlapping(
1985 canvas.addr.add(y * canvas.pitch),
1986 buf.as_mut_ptr().add(y * canvas.width) as *mut u8,
1987 canvas.width * 4,
1988 );
1989 }
1990 }
1991 } else {
1992 for y in 0..canvas.height {
1993 for x in 0..canvas.width {
1994 buf[y * canvas.width + x] = self.read_hw_pixel_packed(x, y);
1995 }
1996 }
1997 }
1998 }
1999 let _ = canvas;
2000 self.canm().back_buffer = Some(buf);
2001 }
2002 }
2003 }
2004
2005 pub(crate) fn begin_viewport_render(&mut self) -> (bool, bool) {
2006 self.ensure_back_buffer_for_viewport();
2007 let canvas = self.can();
2008 let prev_draw_to_back = canvas.draw_to_back;
2009 let prev_track_dirty = canvas.track_dirty;
2010 let has_back = canvas.back_buffer.is_some();
2011 let _ = canvas;
2012 if has_back {
2013 let canvas = self.canm();
2014 canvas.draw_to_back = true;
2015 canvas.track_dirty = true;
2016 }
2019 (prev_draw_to_back, prev_track_dirty)
2020 }
2021
2022 pub(crate) fn end_viewport_render(&mut self, prev_draw_to_back: bool, prev_track_dirty: bool) {
2023 let has_back = self.can().back_buffer.is_some();
2024 if has_back {
2025 if !self.console_defer_present {
2026 self.request_present();
2027 let now = crate::process::scheduler::ticks();
2028 let force_present = !prev_draw_to_back || !prev_track_dirty || now == 0;
2029 self.present_if_due(force_present);
2030 }
2031 let canvas = self.canm();
2032 canvas.draw_to_back = prev_draw_to_back;
2033 canvas.track_dirty = prev_track_dirty;
2034 }
2035 }
2036
2037 pub(crate) fn finalize_live_view_state(&mut self) {
2038 let (total_complete, _, view_start, view_end, _) = self.visible_virtual_bounds();
2039 if self.scroll_offset == 0 {
2040 self.sync_live_cursor_from_view(total_complete, view_start, view_end);
2041 }
2042 }
2043
2044 pub(crate) fn render_viewport_full(&mut self) {
2045 self.render_pending_console();
2046 let (prev_draw_to_back, prev_track_dirty) = self.begin_viewport_render();
2047 let text_h = self.text_area_height();
2048 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
2049 self.fill_rect(0, 0, text_w, text_h, self.unpack_color(self.bg));
2050 self.redraw_visible_rows(0, self.rows);
2051 self.finalize_live_view_state();
2052 self.draw_scrollbar_inner();
2053 self.end_viewport_render(prev_draw_to_back, prev_track_dirty);
2054 }
2055
2056 pub(crate) fn refresh_viewport_decorations(&mut self) {
2057 let (prev_draw_to_back, prev_track_dirty) = self.begin_viewport_render();
2058 self.finalize_live_view_state();
2059 self.draw_scrollbar_inner();
2060 self.end_viewport_render(prev_draw_to_back, prev_track_dirty);
2061 }
2062
2063 pub(crate) fn set_scroll_offset_and_render(&mut self, new_offset: usize) {
2064 self.render_pending_console();
2065 let old_offset = self.scroll_offset;
2066 self.scroll_offset = new_offset;
2067 if !self.redraw_from_scrollback_incremental(old_offset) {
2068 self.redraw_from_scrollback();
2069 }
2070 }
2071
2072 pub(crate) fn move_text_view_pixels_up(&mut self, pixels: usize) {
2073 if pixels == 0 {
2074 return;
2075 }
2076 let text_h = self.text_area_height();
2077 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
2078 if pixels >= text_h {
2079 self.fill_rect(0, 0, text_w, text_h, self.unpack_color(self.bg));
2080 return;
2081 }
2082 let move_rows = text_h - pixels;
2083 if self.draw_to_back_buffer() {
2084 let row_width = self.can().width;
2085 if let Some(buf) = self.canm().back_buffer.as_mut() {
2086 buf.copy_within(pixels * row_width..text_h * row_width, 0);
2087 }
2088 self.mark_dirty_rect(0, 0, row_width, text_h);
2089 } else {
2090 unsafe {
2091 core::ptr::copy(
2092 self.can().addr.add(pixels * self.can().pitch),
2093 self.can().addr,
2094 move_rows * self.can().pitch,
2095 );
2096 }
2097 }
2098 self.fill_rect(0, move_rows, text_w, pixels, self.unpack_color(self.bg));
2099 }
2100
2101 pub(crate) fn move_text_view_pixels_down(&mut self, pixels: usize) {
2102 if pixels == 0 {
2103 return;
2104 }
2105 let text_h = self.text_area_height();
2106 let text_w = self.can().width.saturating_sub(SCROLLBAR_W);
2107 if pixels >= text_h {
2108 self.fill_rect(0, 0, text_w, text_h, self.unpack_color(self.bg));
2109 return;
2110 }
2111 let move_rows = text_h - pixels;
2112 if self.draw_to_back_buffer() {
2113 let row_width = self.can().width;
2114 if let Some(buf) = self.canm().back_buffer.as_mut() {
2115 buf.copy_within(0..move_rows * row_width, pixels * row_width);
2116 }
2117 self.mark_dirty_rect(0, 0, row_width, text_h);
2118 } else {
2119 unsafe {
2120 core::ptr::copy(
2121 self.can().addr,
2122 self.can().addr.add(pixels * self.can().pitch),
2123 move_rows * self.can().pitch,
2124 );
2125 }
2126 }
2127 self.fill_rect(0, 0, text_w, pixels, self.unpack_color(self.bg));
2128 }
2129
2130 pub(crate) fn layout_text_lines(
2132 &self,
2133 text: &str,
2134 wrap: bool,
2135 max_cols: Option<usize>,
2136 ) -> Vec<Vec<char>> {
2137 let mut lines: Vec<Vec<char>> = Vec::new();
2138 let mut current: Vec<char> = Vec::new();
2139 let wrap_cols = max_cols.filter(|&c| c > 0);
2140
2141 for ch in text.chars() {
2142 if ch == '\n' {
2143 lines.push(current);
2144 current = Vec::new();
2145 continue;
2146 }
2147
2148 if wrap {
2149 if let Some(cols) = wrap_cols {
2150 if current.len() >= cols {
2151 lines.push(current);
2152 current = Vec::new();
2153 }
2154 }
2155 }
2156
2157 current.push(ch);
2158 }
2159
2160 lines.push(current);
2161 lines
2162 }
2163
2164 pub fn measure_text(&self, text: &str, max_width: Option<usize>, wrap: bool) -> TextMetrics {
2166 if !self.enabled {
2167 return TextMetrics {
2168 width: 0,
2169 height: 0,
2170 lines: 0,
2171 };
2172 }
2173 let gw = self.font_info.glyph_w;
2174 let gh = self.font_info.glyph_h;
2175 let max_cols = max_width.map(|w| core::cmp::max(1, w / gw));
2176 let lines = self.layout_text_lines(text, wrap, max_cols);
2177
2178 let mut max_line_cols = 0usize;
2179 for line in &lines {
2180 max_line_cols = core::cmp::max(max_line_cols, line.len());
2181 }
2182
2183 TextMetrics {
2184 width: max_line_cols * gw,
2185 height: lines.len() * gh,
2186 lines: lines.len(),
2187 }
2188 }
2189
2190 pub fn draw_text(
2192 &mut self,
2193 pixel_x: usize,
2194 pixel_y: usize,
2195 text: &str,
2196 opts: TextOptions,
2197 ) -> TextMetrics {
2198 self.render_pending_console();
2199 if !self.enabled {
2200 return TextMetrics {
2201 width: 0,
2202 height: 0,
2203 lines: 0,
2204 };
2205 }
2206
2207 let gw = self.font_info.glyph_w;
2208 let gh = self.font_info.glyph_h;
2209 let max_cols = opts.max_width.map(|w| core::cmp::max(1, w / gw));
2210 let lines = self.layout_text_lines(text, opts.wrap, max_cols);
2211 let region_w = opts.max_width.unwrap_or_else(|| {
2212 let mut max_line_cols = 0usize;
2213 for line in &lines {
2214 max_line_cols = core::cmp::max(max_line_cols, line.len());
2215 }
2216 max_line_cols * gw
2217 });
2218
2219 let fg = self.pack_color(opts.fg);
2220 let bg = self.pack_color(opts.bg);
2221 let mut max_line_px = 0usize;
2222
2223 for (line_idx, line) in lines.iter().enumerate() {
2224 let line_px = line.len() * gw;
2225 max_line_px = core::cmp::max(max_line_px, line_px);
2226 let x = match opts.align {
2227 TextAlign::Left => pixel_x,
2228 TextAlign::Center => pixel_x.saturating_add(region_w.saturating_sub(line_px) / 2),
2229 TextAlign::Right => pixel_x.saturating_add(region_w.saturating_sub(line_px)),
2230 };
2231 let y = pixel_y + line_idx * gh;
2232
2233 for (col, ch) in line.iter().enumerate() {
2234 self.draw_glyph_at_pixel(x + col * gw, y, *ch, fg, bg);
2235 }
2236 }
2237
2238 TextMetrics {
2239 width: max_line_px,
2240 height: lines.len() * gh,
2241 lines: lines.len(),
2242 }
2243 }
2244
2245 pub fn draw_strata_stack(
2247 &mut self,
2248 origin_x: usize,
2249 origin_y: usize,
2250 layer_w: usize,
2251 layer_h: usize,
2252 ) {
2253 self.render_pending_console();
2254 if !self.enabled || layer_w == 0 || layer_h == 0 {
2255 return;
2256 }
2257
2258 let palette = [
2260 RgbColor::new(0x24, 0x3B, 0x55),
2261 RgbColor::new(0x2B, 0x54, 0x77),
2262 RgbColor::new(0x2F, 0x74, 0x93),
2263 RgbColor::new(0x3A, 0x93, 0xA8),
2264 RgbColor::new(0x5F, 0xB1, 0xA1),
2265 RgbColor::new(0xA4, 0xCC, 0x94),
2266 ];
2267
2268 let dx = 6usize;
2269 let dy = 5usize;
2270 for (i, color) in palette.iter().enumerate() {
2271 let x = origin_x.saturating_add(i * dx);
2272 let y = origin_y.saturating_add(i * dy);
2273 let w = layer_w.saturating_sub(i * dx);
2274 let h = layer_h.saturating_sub(i * dy);
2275 if w < 8 || h < 8 {
2276 break;
2277 }
2278
2279 self.fill_rect(x, y, w, h, *color);
2280 self.draw_rect(x, y, w, h, RgbColor::new(0x10, 0x16, 0x20));
2281 }
2282 }
2283
2284 pub(crate) fn draw_glyph(&mut self, cx: usize, cy: usize, ch: char) {
2286 let glyph_index = self.glyph_index_for_char(ch);
2287 self.draw_glyph_index_at_pixel(
2288 cx * self.font_info.glyph_w,
2289 cy * self.font_info.glyph_h,
2290 glyph_index,
2291 self.fg,
2292 self.bg,
2293 );
2294 }
2295
2296 pub(crate) fn clear_row(&mut self, row: usize) {
2298 if !self.enabled {
2299 return;
2300 }
2301 self.clear_text_line_pixels(row);
2302 }
2303
2304 pub(crate) fn scroll(&mut self) {
2306 if !self.enabled {
2307 return;
2308 }
2309 self.console_row_head = (self.console_row_head + 1) % self.rows;
2310 self.console_pending_scroll = (self.console_pending_scroll + 1).min(self.rows);
2311 let bottom = ((self.console_row_head + self.rows - 1) % self.rows) * self.cols;
2312 self.console_cells[bottom..bottom + self.cols].fill(SbCell {
2313 ch: ' ',
2314 fg: self.fg,
2315 bg: self.bg,
2316 });
2317 self.console_dirty_cells[bottom..bottom + self.cols].fill(true);
2318 self.console_pending = true;
2319 self.row = self.rows - 1;
2320 }
2321
2322 pub(crate) fn write_char(&mut self, c: char) {
2324 if !self.enabled {
2325 return;
2326 }
2327 let c = normalize_console_char(c);
2328
2329 self.console_pending = true;
2330
2331 self.sb_mirror_char(c);
2333 if self.scroll_offset > 0 {
2335 return;
2336 }
2337 match c {
2340 '\n' => {
2341 self.col = 0;
2342 self.row += 1;
2343 }
2344 '\r' => self.col = 0,
2345 '\t' => self.col = (self.col + 4) & !3,
2346 '\u{8}' => {
2347 if self.col > 0 {
2348 self.col -= 1;
2349 self.queue_console_glyph(' ');
2350 }
2351 }
2352 '\0' => {}
2353 ch => {
2354 self.queue_console_glyph(ch);
2355 self.col += 1;
2356 }
2357 }
2358
2359 if self.col >= self.cols {
2360 self.col = 0;
2361 self.row += 1;
2362 }
2363
2364 if self.row >= self.rows {
2365 self.scroll();
2366 }
2367 }
2368
2369 pub(crate) fn write_bytes(&mut self, s: &str) {
2372 self.begin_viewport_render();
2373 let bytes = s.as_bytes();
2375 let mut i = 0;
2376 while i < bytes.len() {
2377 let b = bytes[i];
2378 if b == 0x1B {
2379 if i + 1 < bytes.len() && bytes[i + 1] == b'[' {
2381 i += 2;
2382 while i < bytes.len() {
2383 let c = bytes[i];
2384 i += 1;
2385 if c >= b'@' && c <= b'~' {
2386 break;
2387 }
2388 }
2389 } else {
2390 i += 1;
2391 }
2392 continue;
2393 }
2394 let ch = b as char;
2396 self.write_char(ch);
2397 i += 1;
2398 }
2399 if self.draw_to_back_buffer() && (self.console_pending || self.can().dirty.len != 0) {
2400 self.canm().present_pending = true;
2401 }
2402 }
2403
2404 pub(crate) fn flush_display(&mut self) {
2406 if !self.enabled {
2407 return;
2408 }
2409 self.request_present();
2410 }
2411
2412 fn queue_console_glyph(&mut self, ch: char) {
2413 if self.row >= self.rows || self.col >= self.cols {
2414 return;
2415 }
2416 let idx = ((self.console_row_head + self.row) % self.rows) * self.cols + self.col;
2417 self.console_cells[idx] = SbCell {
2418 ch,
2419 fg: self.fg,
2420 bg: self.bg,
2421 };
2422 self.console_dirty_cells[idx] = true;
2423 self.console_pending = true;
2424 }
2425
2426 pub(crate) fn render_pending_console(&mut self) {
2429 if !self.enabled || !self.console_pending {
2430 return;
2431 }
2432 self.console_pending = false;
2434 let mouse_visible = self.cursor.mc_visible;
2435 let text_visible = self.cursor.tc_visible;
2436 if mouse_visible {
2437 self.mc_erase_hw();
2438 }
2439 if text_visible {
2440 self.text_cursor_erase_hw();
2441 }
2442 let scroll = core::mem::take(&mut self.console_pending_scroll);
2443 if scroll != 0 {
2444 self.move_text_view_pixels_up(scroll * self.font_info.glyph_h);
2445 }
2446 for row in 0..self.rows {
2447 let start = ((self.console_row_head + row) % self.rows) * self.cols;
2448 for col in 0..self.cols {
2449 let idx = start + col;
2450 if !core::mem::replace(&mut self.console_dirty_cells[idx], false) {
2451 continue;
2452 }
2453 let cell = self.console_cells[idx];
2454 self.draw_glyph_at_pixel(
2455 col * self.font_info.glyph_w,
2456 row * self.font_info.glyph_h,
2457 cell.ch,
2458 cell.fg,
2459 cell.bg,
2460 );
2461 }
2462 }
2463 self.draw_scrollbar_inner();
2464 if text_visible {
2466 self.text_cursor_save_hw();
2467 self.text_cursor_draw_hw();
2468 }
2469 if mouse_visible {
2470 self.mc_save_hw();
2471 self.mc_draw_hw();
2472 }
2473 }
2474
2475 pub(crate) fn sb_mirror_char(&mut self, c: char) {
2483 let cols = self.cols;
2484 let fg = self.fg;
2485 let bg = self.bg;
2486 let rows = self.rows;
2487 self.sb.mirror_char(c, cols, fg, bg, rows);
2488 }
2489
2490 #[inline]
2492 pub(crate) fn sb_trim(&mut self) {
2493 let rows = self.rows;
2494 self.sb.trim(rows);
2495 }
2496
2497 pub(crate) fn draw_scrollbar_inner(&mut self) {
2499 if !self.enabled || self.can().width == 0 {
2500 return;
2501 }
2502 let text_h = self.text_area_height();
2503 if text_h == 0 {
2504 return;
2505 }
2506 let sb_x = self.can().width.saturating_sub(SCROLLBAR_W);
2507 let sb_w = SCROLLBAR_W;
2508 let total = self.sb.rows.len() + 1; let track_color = self.unpack_color(self.fmt.pack_rgb(0x22, 0x28, 0x38));
2510 let thumb_color = self.unpack_color(self.fmt.pack_rgb(0x58, 0x72, 0xA0));
2511 let thumb_hi_color = self.unpack_color(self.fmt.pack_rgb(0x80, 0xA0, 0xC8));
2512
2513 if total <= self.rows {
2514 self.fill_rect(sb_x, 0, sb_w, text_h, track_color);
2516 if sb_w > 2 && text_h > 2 {
2517 self.fill_rect(sb_x + 1, 1, sb_w - 2, text_h - 2, thumb_hi_color);
2518 }
2519 return;
2520 }
2521
2522 let max_offset = total.saturating_sub(self.rows);
2523 let thumb_h = ((text_h * self.rows) / total).max(6);
2524 let avail = text_h.saturating_sub(thumb_h);
2525 let thumb_y = if self.scroll_offset == 0 || avail == 0 {
2527 avail } else {
2529 avail - (avail * self.scroll_offset / max_offset)
2530 };
2531
2532 self.fill_rect(sb_x, 0, sb_w, text_h, track_color);
2534 if thumb_h > 0 && thumb_h < text_h {
2536 self.fill_rect(sb_x, thumb_y, sb_w, thumb_h, thumb_color);
2537 }
2538 }
2539
2540 pub(crate) fn redraw_from_scrollback(&mut self) {
2543 if !self.enabled {
2544 return;
2545 }
2546 self.render_viewport_full();
2547 }
2548
2549 pub(crate) fn redraw_from_scrollback_incremental(&mut self, old_offset: usize) -> bool {
2550 if !self.enabled || self.rows == 0 {
2551 return false;
2552 }
2553 let diff = old_offset.abs_diff(self.scroll_offset);
2554 if diff == 0 || diff >= self.rows {
2555 return false;
2556 }
2557 let pixel_delta = diff.saturating_mul(self.font_info.glyph_h);
2558 if pixel_delta == 0 {
2559 return false;
2560 }
2561
2562 let (prev_draw_to_back, prev_track_dirty) = self.begin_viewport_render();
2563
2564 if self.scroll_offset > old_offset {
2565 self.move_text_view_pixels_down(pixel_delta);
2566 self.redraw_visible_rows(0, diff);
2567 } else {
2568 self.move_text_view_pixels_up(pixel_delta);
2569 self.redraw_visible_rows(self.rows.saturating_sub(diff), diff);
2570 }
2571
2572 self.finalize_live_view_state();
2573 self.draw_scrollbar_inner();
2574 self.end_viewport_render(prev_draw_to_back, prev_track_dirty);
2575 true
2576 }
2577
2578 pub fn scroll_view_up(&mut self, lines: usize) {
2580 if !self.enabled {
2581 return;
2582 }
2583 let total = self.sb.rows.len() + 1;
2584 let max_off = total.saturating_sub(self.rows);
2585 self.set_scroll_offset_and_render((self.scroll_offset + lines).min(max_off));
2586 }
2587
2588 pub fn scroll_view_down(&mut self, lines: usize) {
2590 if !self.enabled {
2591 return;
2592 }
2593 self.set_scroll_offset_and_render(self.scroll_offset.saturating_sub(lines));
2594 }
2595
2596 pub fn scroll_to_live(&mut self) {
2598 if self.scroll_offset == 0 {
2599 return;
2600 }
2601 self.set_scroll_offset_and_render(0);
2602 }
2603
2604 pub fn scrollbar_click(&mut self, px_x: usize, px_y: usize) {
2607 if !self.enabled {
2608 return;
2609 }
2610 let sb_x = self.can().width.saturating_sub(SCROLLBAR_W);
2611 if px_x < sb_x {
2612 return;
2613 }
2614 let text_h = self.text_area_height();
2615 if text_h <= 1 {
2616 return;
2617 }
2618 let total = self.sb.rows.len() + 1;
2619 let max_off = total.saturating_sub(self.rows);
2620 if max_off == 0 {
2621 return;
2622 }
2623 let py = px_y.min(text_h - 1);
2625 let offset = max_off * (text_h - 1 - py) / (text_h - 1);
2626 self.set_scroll_offset_and_render(offset.min(max_off));
2627 }
2628
2629 pub fn scrollbar_drag_to(&mut self, px_y: usize) {
2635 if !self.enabled {
2636 return;
2637 }
2638 let text_h = self.text_area_height();
2639 if text_h <= 1 {
2640 return;
2641 }
2642 let total = self.sb.rows.len() + 1;
2643 let max_off = total.saturating_sub(self.rows);
2644 if max_off == 0 {
2645 return;
2646 }
2647 let py = px_y.min(text_h - 1);
2649 let offset = max_off * (text_h - 1 - py) / (text_h - 1);
2650 self.set_scroll_offset_and_render(offset.min(max_off));
2651 }
2652
2653 pub fn scrollbar_hit_test(&self, px_x: usize, px_y: usize) -> bool {
2655 if !self.enabled {
2656 return false;
2657 }
2658 let sb_x = self.can().width.saturating_sub(SCROLLBAR_W);
2659 px_x >= sb_x && px_y < self.text_area_height()
2660 }
2661}
2662
2663fn normalize_console_char(ch: char) -> char {
2665 match ch {
2666 '\n' | '\r' | '\t' | '\u{8}' => ch,
2667 c if c.is_control() => '\0',
2668 '\u{2500}' | '\u{2501}' | '\u{2504}' | '\u{2505}' | '\u{2013}' | '\u{2014}' => '-',
2670 '\u{2502}' | '\u{2503}' => '|',
2671 '\u{250c}' | '\u{2510}' | '\u{2514}' | '\u{2518}' | '\u{251c}' | '\u{2524}'
2672 | '\u{252c}' | '\u{2534}' | '\u{253c}' => '+',
2673 '\u{00a0}' => ' ',
2674 _ => ch,
2675 }
2676}
2677
2678impl fmt::Write for VgaWriter {
2679 fn write_str(&mut self, s: &str) -> fmt::Result {
2681 if self.enabled {
2682 self.write_bytes(s);
2683 } else {
2684 crate::arch::x86_64::serial::_print(format_args!("{}", s));
2685 }
2686 Ok(())
2687 }
2688}