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