Skip to main content

strat9_kernel/arch/x86_64/vga/
cursor.rs

1//! Cursor manager for the framebuffer terminal.
2//!
3//! Owns the mouse-cursor and text-cursor state (position, visibility, saved
4//! pixels) and provides methods to save/restore/draw them directly to the
5//! hardware framebuffer via [`CanvasBuffer`].
6
7use super::types::PixelFormat;
8use crate::framebuffer::{CanvasBuffer, RgbColor};
9
10// ---------------------------------------------------------------------------
11// Constants
12// ---------------------------------------------------------------------------
13
14/// Mouse-cursor sprite width (pixels).
15pub(crate) const CURSOR_W: usize = 12;
16/// Mouse-cursor sprite height (pixels).
17pub(crate) const CURSOR_H: usize = 16;
18/// Maximum text-cursor width or height (pixels).
19pub(crate) const TEXT_CURSOR_MAX_DIM: usize = 32;
20
21/// Mouse cursor sprite: 0=transparent, 1=black outline, 2=white fill.
22#[rustfmt::skip]
23pub(crate) const CURSOR_PIXELS: [u8; CURSOR_W * CURSOR_H] = [
24    1,0,0,0,0,0,0,0,0,0,0,0,
25    1,1,0,0,0,0,0,0,0,0,0,0,
26    1,2,1,0,0,0,0,0,0,0,0,0,
27    1,2,2,1,0,0,0,0,0,0,0,0,
28    1,2,2,2,1,0,0,0,0,0,0,0,
29    1,2,2,2,2,1,0,0,0,0,0,0,
30    1,2,2,2,2,2,1,0,0,0,0,0,
31    1,2,2,2,2,2,2,1,0,0,0,0,
32    1,2,2,2,2,2,2,2,1,0,0,0,
33    1,2,2,2,2,2,1,1,0,0,0,0,
34    1,2,2,2,1,0,0,0,0,0,0,0,
35    1,2,1,1,2,2,1,0,0,0,0,0,
36    1,1,0,0,1,2,2,1,0,0,0,0,
37    0,0,0,0,0,1,2,1,0,0,0,0,
38    0,0,0,0,0,0,1,1,0,0,0,0,
39    0,0,0,0,0,0,0,0,0,0,0,0,
40];
41
42// ---------------------------------------------------------------------------
43// CursorManager
44// ---------------------------------------------------------------------------
45
46/// Holds all cursor-related state for the framebuffer terminal.
47///
48/// **Mouse cursor** – a small arrow sprite that can be saved/restored/drawn.
49/// **Text cursor** – a blinking underline / block that follows the text input
50/// position.
51pub(crate) struct CursorManager {
52    // -- Mouse cursor -------------------------------------------------------
53    pub mc_x: i32,
54    pub mc_y: i32,
55    pub mc_visible: bool,
56    pub mc_save: [u32; CURSOR_W * CURSOR_H],
57
58    // -- Text cursor --------------------------------------------------------
59    pub tc_col: usize,
60    pub tc_row: usize,
61    pub tc_w: usize,
62    pub tc_h: usize,
63    pub tc_visible: bool,
64    pub tc_color: u32,
65    pub tc_save: [u32; TEXT_CURSOR_MAX_DIM * TEXT_CURSOR_MAX_DIM],
66}
67
68impl CursorManager {
69    /// Create a new cursor manager with all cursors hidden.
70    pub const fn new() -> Self {
71        Self {
72            mc_x: 0,
73            mc_y: 0,
74            mc_visible: false,
75            mc_save: [0u32; CURSOR_W * CURSOR_H],
76            tc_col: 0,
77            tc_row: 0,
78            tc_w: 0,
79            tc_h: 0,
80            tc_visible: false,
81            tc_color: 0,
82            tc_save: [0u32; TEXT_CURSOR_MAX_DIM * TEXT_CURSOR_MAX_DIM],
83        }
84    }
85
86    // ========================================================================
87    // Mouse cursor
88    // ========================================================================
89
90    /// Save the pixels under the mouse cursor into `mc_save`.
91    pub fn mc_save_hw(&mut self, canvas: &CanvasBuffer) {
92        let x = self.mc_x;
93        let y = self.mc_y;
94        for cy in 0..CURSOR_H {
95            for cx in 0..CURSOR_W {
96                let px = x + cx as i32;
97                let py = y + cy as i32;
98                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
99                    self.mc_save[cy * CURSOR_W + cx] = 0;
100                    continue;
101                }
102                self.mc_save[cy * CURSOR_W + cx] = canvas.read_hw_pixel(px as usize, py as usize);
103            }
104        }
105    }
106
107    /// Draw the mouse cursor sprite onto the hardware framebuffer.
108    pub fn mc_draw_hw(&mut self, canvas: &mut CanvasBuffer, fmt: &PixelFormat) {
109        let x = self.mc_x;
110        let y = self.mc_y;
111        let black = fmt.pack_rgb(RgbColor::BLACK.r, RgbColor::BLACK.g, RgbColor::BLACK.b);
112        let white = fmt.pack_rgb(RgbColor::WHITE.r, RgbColor::WHITE.g, RgbColor::WHITE.b);
113        for cy in 0..CURSOR_H {
114            for cx in 0..CURSOR_W {
115                let p = CURSOR_PIXELS[cy * CURSOR_W + cx];
116                if p == 0 {
117                    continue;
118                }
119                let px = x + cx as i32;
120                let py = y + cy as i32;
121                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
122                    continue;
123                }
124                let color = if p == 1 { black } else { white };
125                canvas.write_hw_pixel(px as usize, py as usize, color);
126            }
127        }
128    }
129
130    /// Erase the mouse cursor by restoring the saved pixels.
131    pub fn mc_erase_hw(&mut self, canvas: &mut CanvasBuffer) {
132        let x = self.mc_x;
133        let y = self.mc_y;
134        for cy in 0..CURSOR_H {
135            for cx in 0..CURSOR_W {
136                if CURSOR_PIXELS[cy * CURSOR_W + cx] == 0 {
137                    continue;
138                }
139                let px = x + cx as i32;
140                let py = y + cy as i32;
141                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
142                    continue;
143                }
144                canvas.write_hw_pixel(px as usize, py as usize, self.mc_save[cy * CURSOR_W + cx]);
145            }
146        }
147    }
148
149    /// Update mouse cursor position (save old area, redraw at new position).
150    pub fn update_mouse_cursor(
151        &mut self,
152        x: i32,
153        y: i32,
154        canvas: &mut CanvasBuffer,
155        fmt: &PixelFormat,
156    ) -> bool {
157        let moved = self.mc_visible && (self.mc_x != x || self.mc_y != y);
158        let needs_redraw = !self.mc_visible || moved;
159
160        if moved {
161            self.mc_erase_hw(canvas);
162        }
163        if needs_redraw {
164            self.mc_x = x;
165            self.mc_y = y;
166            self.mc_save_hw(canvas);
167            self.mc_draw_hw(canvas, fmt);
168            self.mc_visible = true;
169        }
170        needs_redraw
171    }
172
173    /// Hide the mouse cursor.
174    pub fn hide_mouse_cursor(&mut self, canvas: &mut CanvasBuffer) {
175        if self.mc_visible {
176            self.mc_erase_hw(canvas);
177            self.mc_visible = false;
178        }
179    }
180
181    // ========================================================================
182    // Text cursor
183    // ========================================================================
184
185    /// Calculate the pixel rectangle for the text cursor at `(col, row)`.
186    pub fn text_cursor_rect(
187        &self,
188        col: usize,
189        row: usize,
190        glyph_w: usize,
191        glyph_h: usize,
192    ) -> (usize, usize, usize, usize) {
193        let tw = glyph_w.min(TEXT_CURSOR_MAX_DIM);
194        let th = glyph_h.min(TEXT_CURSOR_MAX_DIM);
195        (col * glyph_w, row * glyph_h, tw, th)
196    }
197
198    /// Save the pixels under the text cursor.
199    pub fn text_cursor_save_hw(
200        &mut self,
201        col: usize,
202        row: usize,
203        glyph_w: usize,
204        glyph_h: usize,
205        canvas: &CanvasBuffer,
206    ) {
207        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
208        for dy in 0..th {
209            for dx in 0..tw {
210                let px = tx + dx;
211                let py = ty + dy;
212                if px < canvas.width && py < canvas.height {
213                    self.tc_save[dy * tw + dx] = canvas.read_hw_pixel(px, py);
214                }
215            }
216        }
217    }
218
219    /// Draw the text cursor (inverted-colour block) on the hardware framebuffer.
220    pub fn text_cursor_draw_hw(
221        &mut self,
222        col: usize,
223        row: usize,
224        glyph_w: usize,
225        glyph_h: usize,
226        color: u32,
227        canvas: &mut CanvasBuffer,
228    ) {
229        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
230        for dy in 0..th {
231            for dx in 0..tw {
232                let px = tx + dx;
233                let py = ty + dy;
234                if px < canvas.width && py < canvas.height {
235                    canvas.write_hw_pixel(px, py, color);
236                }
237            }
238        }
239    }
240
241    /// Erase the text cursor by restoring the saved pixels.
242    pub fn text_cursor_erase_hw(
243        &mut self,
244        col: usize,
245        row: usize,
246        glyph_w: usize,
247        glyph_h: usize,
248        canvas: &mut CanvasBuffer,
249    ) {
250        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
251        for dy in 0..th {
252            for dx in 0..tw {
253                let px = tx + dx;
254                let py = ty + dy;
255                if px < canvas.width && py < canvas.height {
256                    canvas.write_hw_pixel(px, py, self.tc_save[dy * tw + dx]);
257                }
258            }
259        }
260    }
261
262    /// Draw the text cursor as a solid-colour overlay at the given position.
263    pub fn draw_text_cursor_overlay(
264        &mut self,
265        col: usize,
266        row: usize,
267        glyph_w: usize,
268        glyph_h: usize,
269        color: u32,
270        canvas: &mut CanvasBuffer,
271    ) {
272        self.text_cursor_draw_hw(col, row, glyph_w, glyph_h, color, canvas);
273    }
274
275    /// Hide the text cursor.
276    pub fn hide_text_cursor(
277        &mut self,
278        col: usize,
279        row: usize,
280        glyph_w: usize,
281        glyph_h: usize,
282        canvas: &mut CanvasBuffer,
283    ) {
284        if self.tc_visible {
285            self.text_cursor_erase_hw(col, row, glyph_w, glyph_h, canvas);
286            self.tc_visible = false;
287        }
288    }
289}