entrust_dialog/input/
cursor.rs1use 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}