Skip to main content

entrust_dialog/input/
cursor.rs

1use crate::theme::Theme;
2use ratatui::prelude::Style;
3
4#[derive(Debug, Default, Clone)]
5pub(super) struct Cursor {
6    index: usize,
7    pub(super) state: CursorState,
8    pub(super) mode: CursorMode,
9}
10
11impl Cursor {
12    pub(super) fn set_state(&mut self, state: CursorState) {
13        self.state = state;
14    }
15    fn toggle_state(&mut self) {
16        match self.state {
17            CursorState::On => self.state = CursorState::Off,
18            CursorState::Off => self.state = CursorState::On,
19        }
20    }
21
22    pub(super) fn index(&self) -> usize {
23        self.index
24    }
25
26    pub(crate) fn set_index(&mut self, index: usize) {
27        self.on_move();
28        self.index = index
29    }
30
31    pub(super) fn move_by(&mut self, steps: isize) {
32        self.on_move();
33        if steps.is_positive() {
34            self.index = self.index.saturating_add(steps.unsigned_abs())
35        } else {
36            self.index = self.index.saturating_sub(steps.unsigned_abs())
37        }
38    }
39
40    fn on_move(&mut self) {
41        if self.mode != CursorMode::Hide {
42            self.set_state(CursorState::On);
43        }
44    }
45
46    pub(super) fn set_cursor_mode(&mut self, cursor_mode: CursorMode) {
47        self.mode = cursor_mode;
48        if cursor_mode == CursorMode::Hide {
49            self.set_state(CursorState::Off);
50        }
51    }
52    pub(super) fn tick(&mut self) -> bool {
53        if self.mode == CursorMode::Blink {
54            self.toggle_state();
55            true
56        } else {
57            false
58        }
59    }
60    pub(super) fn current_style(&self, theme: &Theme) -> Style {
61        if self.mode == CursorMode::Hide {
62            Style::default()
63        } else {
64            match self.state {
65                CursorState::On => theme.cursor_on_style,
66                CursorState::Off => theme.cursor_off_style,
67            }
68        }
69    }
70}
71
72#[derive(Debug, Default, Clone, Copy)]
73pub(super) enum CursorState {
74    #[default]
75    On,
76    Off,
77}
78
79#[derive(PartialEq, Debug, Default, Clone, Copy)]
80pub enum CursorMode {
81    #[default]
82    Blink,
83    Hide,
84    Static,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use crate::dialog::Dialog;
91    use crate::input::{InputDialog, Update};
92
93    #[test]
94    fn test_index() {
95        let mut state = InputDialog::default();
96        state.perform_update(Update::InsertChar('1')).unwrap();
97        assert_eq!(vec!['1'], state.content);
98        assert_eq!(1, state.cursor.index());
99
100        state.perform_update(Update::InsertChar('2')).unwrap();
101        assert_eq!(vec!['1', '2'], state.content);
102        assert_eq!(2, state.cursor.index());
103
104        state.perform_update(Update::MoveCursorRight).unwrap();
105        assert_eq!(2, state.cursor.index());
106
107        state.perform_update(Update::MoveCursorLeft).unwrap();
108        state.perform_update(Update::MoveCursorLeft).unwrap();
109        assert_eq!(0, state.cursor.index());
110        state.perform_update(Update::MoveCursorLeft).unwrap();
111        assert_eq!(0, state.cursor.index());
112        state.perform_update(Update::InsertChar('0')).unwrap();
113        assert_eq!(vec!['0', '1', '2'], state.content);
114        assert_eq!(1, state.cursor.index());
115    }
116
117    #[test]
118    fn test_state() {
119        let mut blink_cursor = Cursor {
120            index: 0,
121            state: CursorState::On,
122            mode: CursorMode::Blink,
123        };
124        let theme = Theme::default_ref();
125        assert_eq!(theme.cursor_on_style, blink_cursor.current_style(theme));
126        blink_cursor.tick();
127        assert_eq!(theme.cursor_off_style, blink_cursor.current_style(theme));
128
129        let mut hidden_cursor = Cursor {
130            index: 0,
131            state: CursorState::On,
132            mode: CursorMode::Hide,
133        };
134        assert_eq!(theme.cursor_off_style, hidden_cursor.current_style(theme));
135        hidden_cursor.tick();
136        assert_eq!(theme.cursor_off_style, hidden_cursor.current_style(theme));
137
138        let mut static_cursor = Cursor {
139            index: 0,
140            state: CursorState::On,
141            mode: CursorMode::Static,
142        };
143        assert_eq!(theme.cursor_on_style, static_cursor.current_style(theme));
144        static_cursor.tick();
145        assert_eq!(theme.cursor_on_style, static_cursor.current_style(theme));
146    }
147}