Skip to main content

strat9_kernel/shell/commands/top/
ratatui_backend.rs

1use crate::arch::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        #[allow(deprecated)]
124        if cell.skip {
125            return;
126        }
127        let cols = vga::text_cols();
128        let rows = vga::text_rows();
129        if x as usize >= cols || y as usize >= rows {
130            return;
131        }
132
133        let (gw, gh) = vga::glyph_size();
134        if gw == 0 || gh == 0 {
135            return;
136        }
137        let px = x as usize * gw;
138        let py = y as usize * gh;
139        let bg = Self::map_bg_color(cell.bg);
140        let fg = Self::map_fg_color(cell.fg);
141
142        vga::fill_rect(px, py, gw, gh, bg);
143
144        let symbol = cell.symbol();
145        let ch = Self::normalize_symbol(symbol);
146        if ch != ' ' {
147            let mut one = [0u8; 4];
148            let text = ch.encode_utf8(&mut one);
149            let opts = TextOptions {
150                fg,
151                bg,
152                align: TextAlign::Left,
153                wrap: false,
154                max_width: Some(gw),
155            };
156            let _ = vga::draw_text(px, py, text, opts);
157        }
158    }
159}
160
161impl Backend for Strat9RatatuiBackend {
162    type Error = BackendError;
163
164    /// Performs the draw operation.
165    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
166    where
167        I: Iterator<Item = (u16, u16, &'a Cell)>,
168    {
169        if !vga::is_available() {
170            return Err(BackendError::FramebufferUnavailable);
171        }
172        for (x, y, cell) in content {
173            self.draw_cell(x, y, cell);
174        }
175        Ok(())
176    }
177
178    /// Performs the hide cursor operation.
179    fn hide_cursor(&mut self) -> Result<(), Self::Error> {
180        self.cursor_visible = false;
181        Ok(())
182    }
183
184    /// Performs the show cursor operation.
185    fn show_cursor(&mut self) -> Result<(), Self::Error> {
186        self.cursor_visible = true;
187        Ok(())
188    }
189
190    /// Returns cursor position.
191    fn get_cursor_position(&mut self) -> Result<Position, Self::Error> {
192        Ok(self.cursor)
193    }
194
195    /// Sets cursor position.
196    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), Self::Error> {
197        let pos = position.into();
198        self.cursor = pos;
199        vga::set_text_cursor(pos.x as usize, pos.y as usize);
200        Ok(())
201    }
202
203    /// Performs the clear operation.
204    fn clear(&mut self) -> Result<(), Self::Error> {
205        if !vga::is_available() {
206            return Err(BackendError::FramebufferUnavailable);
207        }
208        vga::fill_rect(0, 0, vga::width(), vga::height(), RgbColor::BLACK);
209        Ok(())
210    }
211
212    /// Performs the clear region operation.
213    fn clear_region(&mut self, clear_type: ClearType) -> Result<(), Self::Error> {
214        match clear_type {
215            ClearType::All => self.clear(),
216            _ => self.clear(),
217        }
218    }
219
220    /// Performs the size operation.
221    fn size(&self) -> Result<Size, Self::Error> {
222        if !vga::is_available() {
223            return Err(BackendError::FramebufferUnavailable);
224        }
225        Ok(Size::new(vga::text_cols() as u16, vga::text_rows() as u16))
226    }
227
228    /// Performs the window size operation.
229    fn window_size(&mut self) -> Result<WindowSize, Self::Error> {
230        if !vga::is_available() {
231            return Err(BackendError::FramebufferUnavailable);
232        }
233        Ok(WindowSize {
234            columns_rows: Size::new(vga::text_cols() as u16, vga::text_rows() as u16),
235            pixels: Size::new(vga::width() as u16, vga::height() as u16),
236        })
237    }
238
239    /// Performs the flush operation.
240    fn flush(&mut self) -> Result<(), Self::Error> {
241        if !vga::is_available() {
242            return Err(BackendError::FramebufferUnavailable);
243        }
244        Ok(())
245    }
246}