1use crate::arch::x86_64::vga::{self, RgbColor, TextAlign, TextOptions};
2use core::fmt;
3use ratatui::{
4 backend::{Backend, ClearType, WindowSize},
5 buffer::Cell,
6 layout::{Position, Size},
7 style::Color,
8};
9
10#[derive(Debug, Clone, Copy)]
11pub enum BackendError {
12 FramebufferUnavailable,
13}
14
15impl fmt::Display for BackendError {
16 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::FramebufferUnavailable => write!(f, "framebuffer unavailable"),
20 }
21 }
22}
23
24impl core::error::Error for BackendError {}
25
26pub struct Strat9RatatuiBackend {
27 cursor: Position,
28 cursor_visible: bool,
29}
30
31impl Strat9RatatuiBackend {
32 pub fn new() -> Result<Self, BackendError> {
34 if !vga::is_available() {
35 return Err(BackendError::FramebufferUnavailable);
36 }
37 Ok(Self {
38 cursor: Position { x: 0, y: 0 },
39 cursor_visible: false,
40 })
41 }
42
43 fn map_fg_color(color: Color) -> RgbColor {
45 match color {
46 Color::Reset => RgbColor::LIGHT_GREY,
47 Color::Black => RgbColor::new(0x00, 0x00, 0x00),
48 Color::Red => RgbColor::new(0x80, 0x00, 0x00),
49 Color::Green => RgbColor::new(0x00, 0x80, 0x00),
50 Color::Yellow => RgbColor::new(0x80, 0x80, 0x00),
51 Color::Blue => RgbColor::new(0x00, 0x00, 0x80),
52 Color::Magenta => RgbColor::new(0x80, 0x00, 0x80),
53 Color::Cyan => RgbColor::new(0x00, 0x80, 0x80),
54 Color::Gray => RgbColor::new(0xAA, 0xAA, 0xAA),
55 Color::DarkGray => RgbColor::new(0x55, 0x55, 0x55),
56 Color::LightRed => RgbColor::new(0xFF, 0x55, 0x55),
57 Color::LightGreen => RgbColor::new(0x55, 0xFF, 0x55),
58 Color::LightYellow => RgbColor::new(0xFF, 0xFF, 0x55),
59 Color::LightBlue => RgbColor::new(0x55, 0x55, 0xFF),
60 Color::LightMagenta => RgbColor::new(0xFF, 0x55, 0xFF),
61 Color::LightCyan => RgbColor::new(0x55, 0xFF, 0xFF),
62 Color::White => RgbColor::new(0xFF, 0xFF, 0xFF),
63 Color::Rgb(r, g, b) => RgbColor::new(r, g, b),
64 Color::Indexed(idx) => {
65 let basic = match idx & 0x0F {
67 0 => RgbColor::new(0x00, 0x00, 0x00),
68 1 => RgbColor::new(0x80, 0x00, 0x00),
69 2 => RgbColor::new(0x00, 0x80, 0x00),
70 3 => RgbColor::new(0x80, 0x80, 0x00),
71 4 => RgbColor::new(0x00, 0x00, 0x80),
72 5 => RgbColor::new(0x80, 0x00, 0x80),
73 6 => RgbColor::new(0x00, 0x80, 0x80),
74 7 => RgbColor::new(0xAA, 0xAA, 0xAA),
75 8 => RgbColor::new(0x55, 0x55, 0x55),
76 9 => RgbColor::new(0xFF, 0x55, 0x55),
77 10 => RgbColor::new(0x55, 0xFF, 0x55),
78 11 => RgbColor::new(0xFF, 0xFF, 0x55),
79 12 => RgbColor::new(0x55, 0x55, 0xFF),
80 13 => RgbColor::new(0xFF, 0x55, 0xFF),
81 14 => RgbColor::new(0x55, 0xFF, 0xFF),
82 _ => RgbColor::new(0xFF, 0xFF, 0xFF),
83 };
84 if idx < 16 {
85 basic
86 } else {
87 let g = idx;
88 RgbColor::new(g, g, g)
89 }
90 }
91 }
92 }
93
94 fn map_bg_color(color: Color) -> RgbColor {
96 match color {
97 Color::Reset => RgbColor::BLACK,
99 _ => Self::map_fg_color(color),
100 }
101 }
102
103 fn normalize_symbol(symbol: &str) -> char {
105 let ch = symbol.chars().next().unwrap_or(' ');
106 match ch {
107 '│' | '┃' => '|',
109 '─' | '━' => '-',
110 '┌' | '┐' | '└' | '┘' | '├' | '┤' | '┬' | '┴' | '┼' => '+',
111 '█' | '▇' | '▆' | '▅' | '▄' | '▃' | '▂' | '▁' | '░' | '▒' | '▓' => {
113 '#'
114 }
115 c if c.is_ascii_graphic() || c == ' ' => c,
117 _ => '?',
118 }
119 }
120
121 fn draw_cell(&self, x: u16, y: u16, cell: &Cell) {
123 if cell.skip {
124 return;
125 }
126 let cols = vga::text_cols();
127 let rows = vga::text_rows();
128 if x as usize >= cols || y as usize >= rows {
129 return;
130 }
131
132 let (gw, gh) = vga::glyph_size();
133 if gw == 0 || gh == 0 {
134 return;
135 }
136 let px = x as usize * gw;
137 let py = y as usize * gh;
138 let bg = Self::map_bg_color(cell.bg);
139 let fg = Self::map_fg_color(cell.fg);
140
141 vga::fill_rect(px, py, gw, gh, bg);
142
143 let symbol = cell.symbol();
144 let ch = Self::normalize_symbol(symbol);
145 if ch != ' ' {
146 let mut one = [0u8; 4];
147 let text = ch.encode_utf8(&mut one);
148 let opts = TextOptions {
149 fg,
150 bg,
151 align: TextAlign::Left,
152 wrap: false,
153 max_width: Some(gw),
154 };
155 let _ = vga::draw_text(px, py, text, opts);
156 }
157 }
158}
159
160impl Backend for Strat9RatatuiBackend {
161 type Error = BackendError;
162
163 fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
165 where
166 I: Iterator<Item = (u16, u16, &'a Cell)>,
167 {
168 if !vga::is_available() {
169 return Err(BackendError::FramebufferUnavailable);
170 }
171 for (x, y, cell) in content {
172 self.draw_cell(x, y, cell);
173 }
174 Ok(())
175 }
176
177 fn hide_cursor(&mut self) -> Result<(), Self::Error> {
179 self.cursor_visible = false;
180 Ok(())
181 }
182
183 fn show_cursor(&mut self) -> Result<(), Self::Error> {
185 self.cursor_visible = true;
186 Ok(())
187 }
188
189 fn get_cursor_position(&mut self) -> Result<Position, Self::Error> {
191 Ok(self.cursor)
192 }
193
194 fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), Self::Error> {
196 let pos = position.into();
197 self.cursor = pos;
198 vga::set_text_cursor(pos.x as usize, pos.y as usize);
199 Ok(())
200 }
201
202 fn clear(&mut self) -> Result<(), Self::Error> {
204 if !vga::is_available() {
205 return Err(BackendError::FramebufferUnavailable);
206 }
207 vga::fill_rect(0, 0, vga::width(), vga::height(), RgbColor::BLACK);
208 Ok(())
209 }
210
211 fn clear_region(&mut self, clear_type: ClearType) -> Result<(), Self::Error> {
213 match clear_type {
214 ClearType::All => self.clear(),
215 _ => self.clear(),
216 }
217 }
218
219 fn size(&self) -> Result<Size, Self::Error> {
221 if !vga::is_available() {
222 return Err(BackendError::FramebufferUnavailable);
223 }
224 Ok(Size::new(vga::text_cols() as u16, vga::text_rows() as u16))
225 }
226
227 fn window_size(&mut self) -> Result<WindowSize, Self::Error> {
229 if !vga::is_available() {
230 return Err(BackendError::FramebufferUnavailable);
231 }
232 Ok(WindowSize {
233 columns_rows: Size::new(vga::text_cols() as u16, vga::text_rows() as u16),
234 pixels: Size::new(vga::width() as u16, vga::height() as u16),
235 })
236 }
237
238 fn flush(&mut self) -> Result<(), Self::Error> {
240 if !vga::is_available() {
241 return Err(BackendError::FramebufferUnavailable);
242 }
243 Ok(())
244 }
245}