Skip to main content

strat9_kernel/shell/commands/top/
ratatui_backend.rs

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    /// Performs the fmt operation.
17    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    /// Creates a new instance.
33    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    /// Maps fg color.
44    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                // ANSI 16-color fallback + grayscale for higher indices.
66                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    /// Maps bg color.
95    fn map_bg_color(color: Color) -> RgbColor {
96        match color {
97            // For background, Reset should stay dark to match console expectations.
98            Color::Reset => RgbColor::BLACK,
99            _ => Self::map_fg_color(color),
100        }
101    }
102
103    /// Performs the normalize symbol operation.
104    fn normalize_symbol(symbol: &str) -> char {
105        let ch = symbol.chars().next().unwrap_or(' ');
106        match ch {
107            // Box drawing fallback
108            '│' | '┃' => '|',
109            '─' | '━' => '-',
110            '┌' | '┐' | '└' | '┘' | '├' | '┤' | '┬' | '┴' | '┼' => '+',
111            // Block/shade fallback (used by gauges/progress)
112            '█' | '▇' | '▆' | '▅' | '▄' | '▃' | '▂' | '▁' | '░' | '▒' | '▓' => {
113                '#'
114            }
115            // Keep printable ASCII and Latin-1 letters/numbers as-is.
116            c if c.is_ascii_graphic() || c == ' ' => c,
117            _ => '?',
118        }
119    }
120
121    /// Performs the draw cell operation.
122    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    /// Performs the draw operation.
164    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    /// Performs the hide cursor operation.
178    fn hide_cursor(&mut self) -> Result<(), Self::Error> {
179        self.cursor_visible = false;
180        Ok(())
181    }
182
183    /// Performs the show cursor operation.
184    fn show_cursor(&mut self) -> Result<(), Self::Error> {
185        self.cursor_visible = true;
186        Ok(())
187    }
188
189    /// Returns cursor position.
190    fn get_cursor_position(&mut self) -> Result<Position, Self::Error> {
191        Ok(self.cursor)
192    }
193
194    /// Sets cursor position.
195    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    /// Performs the clear operation.
203    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    /// Performs the clear region operation.
212    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    /// Performs the size operation.
220    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    /// Performs the window size operation.
228    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    /// Performs the flush operation.
239    fn flush(&mut self) -> Result<(), Self::Error> {
240        if !vga::is_available() {
241            return Err(BackendError::FramebufferUnavailable);
242        }
243        Ok(())
244    }
245}