ailloli_ui_terminal_core/
cursor.rs

1//! Terminal cursor position, visibility, and presentation shape.
2
3use serde::{Deserialize, Serialize};
4
5use crate::size::TerminalSize;
6
7/// Requested cursor rendering shape.
8///
9/// # Examples
10///
11/// ```
12/// use ailloli_ui_terminal_core::TerminalCursorShape;
13/// assert_ne!(TerminalCursorShape::Block, TerminalCursorShape::Bar);
14/// ```
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub enum TerminalCursorShape {
17    /// Full-cell block.
18    Block,
19    /// Horizontal underline.
20    Underline,
21    /// Vertical insertion bar.
22    Bar,
23}
24
25/// Zero-based cursor position and presentation state.
26///
27/// Public fields and deserialization may place the cursor out of bounds; call
28/// [`Self::clamp_to`] before indexing a screen.
29///
30/// # Examples
31///
32/// ```
33/// use ailloli_ui_terminal_core::{TerminalCursor, TerminalCursorShape};
34/// let cursor = TerminalCursor::new();
35/// assert_eq!((cursor.row, cursor.col, cursor.visible, cursor.shape), (0, 0, true, TerminalCursorShape::Block));
36/// ```
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub struct TerminalCursor {
39    /// Zero-based screen row.
40    pub row: usize,
41    /// Zero-based screen column.
42    pub col: usize,
43    /// Whether the renderer should show the cursor.
44    pub visible: bool,
45    /// Requested renderer shape.
46    pub shape: TerminalCursorShape,
47}
48
49impl TerminalCursor {
50    /// Creates a visible block cursor at row zero, column zero.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use ailloli_ui_terminal_core::TerminalCursor;
56    /// assert_eq!((TerminalCursor::new().row, TerminalCursor::new().col), (0, 0));
57    /// ```
58    pub const fn new() -> Self {
59        Self {
60            row: 0,
61            col: 0,
62            visible: true,
63            shape: TerminalCursorShape::Block,
64        }
65    }
66
67    /// Clamps row and column into a terminal size.
68    ///
69    /// Zero dimensions are first clamped to one, so the result is always a
70    /// valid `(row, col)` for [`TerminalSize::clamped`]. Visibility and shape
71    /// are unchanged.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use ailloli_ui_terminal_core::{TerminalCursor, TerminalSize};
77    /// let mut cursor = TerminalCursor { row: 9, col: 9, ..TerminalCursor::new() };
78    /// cursor.clamp_to(TerminalSize { rows: 2, cols: 3 });
79    /// assert_eq!((cursor.row, cursor.col), (1, 2));
80    /// ```
81    pub fn clamp_to(&mut self, size: TerminalSize) {
82        let size = size.clamped();
83        self.row = self.row.min(size.rows - 1);
84        self.col = self.col.min(size.cols - 1);
85    }
86}
87
88impl Default for TerminalCursor {
89    fn default() -> Self {
90        Self::new()
91    }
92}