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_dirty: bool,
57    pub mc_save: [u32; CURSOR_W * CURSOR_H],
58
59    // -- Text cursor --------------------------------------------------------
60    pub tc_col: usize,
61    pub tc_row: usize,
62    pub tc_w: usize,
63    pub tc_h: usize,
64    pub tc_visible: bool,
65    pub tc_dirty: bool,
66    pub tc_color: u32,
67    pub tc_save: [u32; TEXT_CURSOR_MAX_DIM * TEXT_CURSOR_MAX_DIM],
68}
69
70impl CursorManager {
71    /// Create a new cursor manager with all cursors hidden.
72    pub const fn new() -> Self {
73        Self {
74            mc_x: 0,
75            mc_y: 0,
76            mc_visible: false,
77            mc_dirty: false,
78            mc_save: [0u32; CURSOR_W * CURSOR_H],
79            tc_col: 0,
80            tc_row: 0,
81            tc_w: 0,
82            tc_h: 0,
83            tc_visible: false,
84            tc_dirty: false,
85            tc_color: 0,
86            tc_save: [0u32; TEXT_CURSOR_MAX_DIM * TEXT_CURSOR_MAX_DIM],
87        }
88    }
89
90    // ========================================================================
91    // Mouse cursor
92    // ========================================================================
93
94    /// Save the pixels under the mouse cursor into `mc_save`.
95    /// Sets `mc_dirty = true` so `present()` will redraw.
96    pub fn mc_save_hw(&mut self, canvas: &CanvasBuffer) {
97        self.mc_dirty = true;
98        let x = self.mc_x;
99        let y = self.mc_y;
100        for cy in 0..CURSOR_H {
101            for cx in 0..CURSOR_W {
102                let px = x + cx as i32;
103                let py = y + cy as i32;
104                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
105                    self.mc_save[cy * CURSOR_W + cx] = 0;
106                    continue;
107                }
108                self.mc_save[cy * CURSOR_W + cx] = canvas.read_hw_pixel(px as usize, py as usize);
109            }
110        }
111    }
112
113    /// Draw the mouse cursor sprite onto the hardware framebuffer.
114    pub fn mc_draw_hw(&mut self, canvas: &mut CanvasBuffer, fmt: &PixelFormat) {
115        let x = self.mc_x;
116        let y = self.mc_y;
117        let black = fmt.pack_rgb(RgbColor::BLACK.r, RgbColor::BLACK.g, RgbColor::BLACK.b);
118        let white = fmt.pack_rgb(RgbColor::WHITE.r, RgbColor::WHITE.g, RgbColor::WHITE.b);
119        for cy in 0..CURSOR_H {
120            for cx in 0..CURSOR_W {
121                let p = CURSOR_PIXELS[cy * CURSOR_W + cx];
122                if p == 0 {
123                    continue;
124                }
125                let px = x + cx as i32;
126                let py = y + cy as i32;
127                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
128                    continue;
129                }
130                let color = if p == 1 { black } else { white };
131                canvas.write_hw_pixel(px as usize, py as usize, color);
132            }
133        }
134    }
135
136    /// Erase the mouse cursor by restoring the saved pixels.
137    /// Sets `mc_dirty = true` so the next `present()` flushes the change.
138    pub fn mc_erase_hw(&mut self, canvas: &mut CanvasBuffer) {
139        self.mc_dirty = true;
140        let x = self.mc_x;
141        let y = self.mc_y;
142        for cy in 0..CURSOR_H {
143            for cx in 0..CURSOR_W {
144                if CURSOR_PIXELS[cy * CURSOR_W + cx] == 0 {
145                    continue;
146                }
147                let px = x + cx as i32;
148                let py = y + cy as i32;
149                if px < 0 || py < 0 || px as usize >= canvas.width || py as usize >= canvas.height {
150                    continue;
151                }
152                canvas.write_hw_pixel(px as usize, py as usize, self.mc_save[cy * CURSOR_W + cx]);
153            }
154        }
155    }
156
157    /// Update mouse cursor position (save old area, redraw at new position).
158    pub fn update_mouse_cursor(
159        &mut self,
160        x: i32,
161        y: i32,
162        canvas: &mut CanvasBuffer,
163        fmt: &PixelFormat,
164    ) -> bool {
165        let moved = self.mc_visible && (self.mc_x != x || self.mc_y != y);
166        let needs_redraw = !self.mc_visible || moved;
167
168        if moved {
169            self.mc_erase_hw(canvas);
170        }
171        if needs_redraw {
172            self.mc_x = x;
173            self.mc_y = y;
174            self.mc_save_hw(canvas);
175            self.mc_draw_hw(canvas, fmt);
176            self.mc_visible = true;
177        }
178        needs_redraw
179    }
180
181    /// Hide the mouse cursor.
182    pub fn hide_mouse_cursor(&mut self, canvas: &mut CanvasBuffer) {
183        if self.mc_visible {
184            self.mc_erase_hw(canvas);
185            self.mc_visible = false;
186        }
187    }
188
189    // ========================================================================
190    // Text cursor
191    // ========================================================================
192
193    /// Calculate the pixel rectangle for the text cursor at `(col, row)`.
194    pub fn text_cursor_rect(
195        &self,
196        col: usize,
197        row: usize,
198        glyph_w: usize,
199        glyph_h: usize,
200    ) -> (usize, usize, usize, usize) {
201        let tw = glyph_w.min(TEXT_CURSOR_MAX_DIM);
202        let th = glyph_h.min(TEXT_CURSOR_MAX_DIM);
203        (col * glyph_w, row * glyph_h, tw, th)
204    }
205
206    /// Save the pixels under the text cursor.
207    pub fn text_cursor_save_hw(
208        &mut self,
209        col: usize,
210        row: usize,
211        glyph_w: usize,
212        glyph_h: usize,
213        canvas: &CanvasBuffer,
214    ) {
215        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
216        for dy in 0..th {
217            for dx in 0..tw {
218                let px = tx + dx;
219                let py = ty + dy;
220                if px < canvas.width && py < canvas.height {
221                    self.tc_save[dy * tw + dx] = canvas.read_hw_pixel(px, py);
222                }
223            }
224        }
225    }
226
227    /// Draw the text cursor (inverted-colour block) on the hardware framebuffer.
228    /// Resets `tc_dirty = false` after drawing.
229    pub fn text_cursor_draw_hw(
230        &mut self,
231        col: usize,
232        row: usize,
233        glyph_w: usize,
234        glyph_h: usize,
235        color: u32,
236        canvas: &mut CanvasBuffer,
237    ) {
238        self.tc_dirty = false;
239        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
240        for dy in 0..th {
241            for dx in 0..tw {
242                let px = tx + dx;
243                let py = ty + dy;
244                if px < canvas.width && py < canvas.height {
245                    canvas.write_hw_pixel(px, py, color);
246                }
247            }
248        }
249    }
250
251    /// Erase the text cursor by restoring the saved pixels.
252    /// Sets `tc_dirty = true` so the next `present()` flushes.
253    pub fn text_cursor_erase_hw(
254        &mut self,
255        col: usize,
256        row: usize,
257        glyph_w: usize,
258        glyph_h: usize,
259        canvas: &mut CanvasBuffer,
260    ) {
261        let (tx, ty, tw, th) = self.text_cursor_rect(col, row, glyph_w, glyph_h);
262        for dy in 0..th {
263            for dx in 0..tw {
264                let px = tx + dx;
265                let py = ty + dy;
266                if px < canvas.width && py < canvas.height {
267                    canvas.write_hw_pixel(px, py, self.tc_save[dy * tw + dx]);
268                }
269            }
270        }
271    }
272
273    /// Draw the text cursor as a solid-colour overlay at the given position.
274    pub fn draw_text_cursor_overlay(
275        &mut self,
276        col: usize,
277        row: usize,
278        glyph_w: usize,
279        glyph_h: usize,
280        color: u32,
281        canvas: &mut CanvasBuffer,
282    ) {
283        self.text_cursor_draw_hw(col, row, glyph_w, glyph_h, color, canvas);
284    }
285
286    /// Hide the text cursor.
287    pub fn hide_text_cursor(
288        &mut self,
289        col: usize,
290        row: usize,
291        glyph_w: usize,
292        glyph_h: usize,
293        canvas: &mut CanvasBuffer,
294    ) {
295        if self.tc_visible {
296            self.text_cursor_erase_hw(col, row, glyph_w, glyph_h, canvas);
297            self.tc_visible = false;
298        }
299    }
300}