use core::fmt; use spin::{Lazy, Mutex}; use x86_64::instructions::interrupts; use super::framebuffer::{colour::Colour, display::FRAMEBUFFER_WRITER}; use crate::resources::font::{FONT_SPLEEN_8X16, Font}; static FONT_WIDTH: u32 = 8; static FONT_HEIGHT: u32 = 16; pub static WRITER: Lazy> = Lazy::new(|| Mutex::new(Writer::new())); pub fn screensize_chars() -> (u32, u32) { let writer = WRITER.lock(); (writer.screen_width, writer.screen_height) } pub struct Writer { font: &'static Font, /// Measured in chars not pixels. screen_width: u32, /// Measured in chars not pixels. screen_height: u32, /// 16 pixels tall. text_line: u32, /// 8 pixels wide. text_col: u32, fg_color: Colour, bg_color: Colour, } impl Default for Writer { fn default() -> Self { Self::new() } } impl Writer { pub fn new() -> Self { FRAMEBUFFER_WRITER.lock().as_mut().map_or_else( || { panic!("Framebuffer writer not initialized."); }, |writer| Self { font: &FONT_SPLEEN_8X16, screen_width: writer.width() / 8, screen_height: writer.height() / 16, text_line: 0, text_col: 0, fg_color: Colour::White, bg_color: Colour::Black, }, ) } pub const fn set_font(&mut self, font: &'static Font) { self.font = font; } /// This is sent when the user types a backspace. const BACKSPACE: u8 = 8; pub fn write_glyph(&mut self, c: u8) { if c == b'\n' { self.newline(); return; } else if c == Self::BACKSPACE { self.backspace(); return; } // Get the character data from the font array. -- each byte is a row of pixels let data: &[u8] = self.font.glyph_for(c as u16); if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() { for (row, line) in data.iter().enumerate().take(16) { for col in 0..8 { let pixel_x: u32 = self.text_col * FONT_WIDTH + col; let pixel_y: u32 = self.text_line * FONT_HEIGHT + row as u32; if line & (0x80 >> col) != 0 { // Write the foreground color writer.write_pixel(pixel_x as usize, pixel_y as usize, self.fg_color); } else { // Write the background color writer.write_pixel(pixel_x as usize, pixel_y as usize, self.bg_color); } } } } // Go to next position if self.text_col + 1 >= self.screen_width { self.newline(); } else { self.text_col += 1; } } pub const fn dimensions(&self) -> (u32, u32) { (self.screen_width, self.screen_height) } pub const fn next_char(&mut self) { self.text_col += 1; } pub const fn newline(&mut self) { self.text_col = 0; if self.text_line + 1 >= self.screen_height { self.text_line = 0; } else { self.text_line += 1; } } /// Handles the backspace character. TODO: Implement VT-100 style terminal control /// codes alongside a shell. Not simple. pub fn backspace(&mut self) { if self.text_col > 0 { self.text_col -= 1; // Blank out the previous char. self.write_glyph(b' '); self.text_col -= 1; } } pub fn write_string(&mut self, s: &str) { for c in s.chars() { self.write_glyph(c as u8); } } pub const fn set_colour(&mut self, fg: Colour, bg: Colour) { self.fg_color = fg; self.bg_color = bg; } pub const fn reset_colour(&mut self) { self.fg_color = Colour::White; self.bg_color = Colour::Black; } } impl core::fmt::Write for Writer { fn write_str(&mut self, s: &str) -> core::fmt::Result { self.write_string(s); Ok(()) } } fn write(args: fmt::Arguments, fg: Colour, bg: Colour) { use core::fmt::Write; interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); writer.set_colour(fg, bg); writer.write_fmt(args).unwrap(); writer.reset_colour(); }); } pub fn _print(args: fmt::Arguments) { x86_64::instructions::interrupts::without_interrupts(|| { write(args, Colour::White, Colour::Black); }) } pub fn _print_err(args: fmt::Arguments) { x86_64::instructions::interrupts::without_interrupts(|| { write(args, Colour::Red, Colour::Black); }) } pub fn _print_log(args: fmt::Arguments) { x86_64::instructions::interrupts::without_interrupts(|| { write(args, Colour::Yellow, Colour::Black); }) } pub fn clear_screen() { interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); writer.text_line = 0; writer.text_col = 0; if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() { writer.clear(); } }); } pub fn reset_cursor() { interrupts::without_interrupts(|| { let mut writer = WRITER.lock(); writer.text_line = 0; writer.text_col = 0; }); } #[macro_export] macro_rules! println_log { () => ($crate::print_log!("\n")); ($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*))); } #[macro_export] macro_rules! print_log { ($($arg:tt)*) => ($crate::_print_log(format_args!($($arg)*))); } #[macro_export] macro_rules! println { () => ($crate::print!("\n")); ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); } #[macro_export] macro_rules! print { ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*))); } #[macro_export] macro_rules! printlnerr { () => ($crate::printerr!("\n")); ($($arg:tt)*) => ($crate::printerr!("{}\n", format_args!($($arg)*))); } #[macro_export] macro_rules! printerr { ($($arg:tt)*) => ($crate::_print_err(format_args!($($arg)*))); }