1use std::io;
2
3use crossterm::cursor::SetCursorStyle;
4use ratatui::{
5 buffer::Buffer,
6 layout::{Position, Rect, Size},
7};
8
9use crate::config::Bindings;
10
11#[derive(Default, Clone, Copy)]
16enum CursorState {
17 #[default]
18 Inactive,
19 Movement,
20 Selection,
21}
22
23impl CursorState {
24 fn is_active(&self) -> bool {
25 !matches!(self, Self::Inactive)
26 }
27
28 pub fn is_selecting(&self) -> bool {
29 matches!(self, Self::Selection)
30 }
31
32 fn toggle_selection(&mut self) {
36 match self {
37 Self::Inactive => (),
38 Self::Movement => {
39 *self = Self::Selection;
40 }
41 Self::Selection => {
42 *self = Self::Movement;
43 }
44 }
45 }
46
47 fn set_active(&mut self) {
48 *self = Self::Movement;
49 }
50}
51
52#[derive(Default, Clone, Copy)]
54pub enum CursorDirection {
55 #[default]
56 Down,
57 Up,
58 Left,
59 Right,
60}
61
62impl CursorDirection {
63 fn go_from(&self, x: u16, y: u16) -> Position {
64 let mut x = x;
65 let mut y = y;
66 match self {
67 CursorDirection::Down => {
68 y = y.saturating_add(1);
69 }
70 CursorDirection::Up => {
71 y = y.saturating_sub(1);
72 }
73 CursorDirection::Left => {
74 x = x.saturating_sub(1);
75 }
76 CursorDirection::Right => {
77 x = x.saturating_add(1);
78 }
79 }
80 Position::new(x, y)
81 }
82}
83
84#[derive(Default, Clone)]
98pub struct Cursor {
99 state: CursorState,
100 cursor: Option<Position>,
101 origin: Option<Position>,
102 rect: Option<Rect>,
103 pub is_dragging: bool,
105 pub leave_bind: String,
107 pub enter_bind: String,
109 pub copy_bind: String,
111}
112
113impl Cursor {
114 pub fn new(binds: &Bindings) -> Self {
116 let reversed = binds.keybind_reversed();
117 let leave_bind = reversed.get("ResetMode").cloned().unwrap_or_default();
118 let enter_bind = reversed.get("Cursor").cloned().unwrap_or_default();
119 let copy_bind = reversed.get("CopyPaste").cloned().unwrap_or_default();
120 Self {
121 state: CursorState::default(),
122 cursor: None,
123 origin: None,
124 rect: None,
125 is_dragging: false,
126 leave_bind,
127 enter_bind,
128 copy_bind,
129 }
130 }
131
132 pub fn rect(&self) -> Option<Rect> {
134 self.rect
135 }
136
137 pub fn cursor(&self) -> Option<Position> {
139 self.cursor
140 }
141
142 pub fn is_active(&self) -> bool {
144 self.state.is_active()
145 }
146
147 pub fn is_selecting(&self) -> bool {
149 self.state.is_selecting()
150 }
151
152 pub fn reset(&mut self) {
155 self.state = CursorState::default();
156 self.cursor = None;
157 self.origin = None;
158 self.rect = None;
159 self.is_dragging = false;
160 }
161
162 pub fn toggle(&mut self, position: Position) {
164 if self.state.is_active() {
165 self.toggle_selection();
166 } else {
167 self.start_cursor(position);
168 }
169 }
170
171 fn start_cursor(&mut self, position: Position) {
174 let _ = crossterm::execute!(io::stdout(), SetCursorStyle::SteadyBlock);
175 self.state.set_active();
176 self.cursor = Some(position);
177 self.origin = Some(position);
178 self.rect = None;
179 }
180
181 fn toggle_selection(&mut self) {
184 if !self.state.is_active() {
185 return;
186 }
187 self.state.toggle_selection();
188 if self.state.is_selecting() {
189 self.origin = self.cursor;
190 } else {
191 self.clear_selection();
192 }
193 }
194
195 fn clear_selection(&mut self) {
197 self.rect = None;
198 }
199
200 pub fn move_cursor(&mut self, direction: CursorDirection, Size { width, height }: Size) {
204 let Some(Position { x, y }) = self.cursor else {
205 return;
206 };
207 let new_pos = direction
208 .go_from(x, y)
209 .clamp(Position::ORIGIN, Position::new(width, height));
210 self.cursor = Some(new_pos);
211 }
212
213 pub fn move_cursor_to(&mut self, position: Position) {
216 if !self.state.is_active() {
217 return;
218 }
219 self.cursor = Some(position);
220 }
221
222 pub fn move_origin_to(&mut self, position: Position) {
225 if !self.state.is_active() {
226 return;
227 }
228 self.origin = Some(position);
229 }
230
231 pub fn extend_selection(&mut self) {
234 if !self.state.is_selecting() {
235 return;
236 }
237 let start = self.origin.expect("Should be set");
238 let end = self.cursor.expect("Should be set");
239 let x = start.x.min(end.x);
240 let y = start.y.min(end.y);
241 let width = u16::abs_diff(start.x, end.x) + 1;
242 let height = u16::abs_diff(start.y, end.y) + 1;
243 self.rect = Some(Rect::new(x, y, width, height));
244 }
245
246 pub fn mouse_drag(&mut self, row: u16, col: u16) {
251 let pos = Position::new(col, row);
252 self.move_cursor_to(pos);
253 if self.is_dragging {
254 self.extend_selection();
255 } else {
256 self.move_origin_to(pos);
257 self.is_dragging = true;
258 }
259 }
260
261 pub fn stop_drag(&mut self) {
263 self.is_dragging = false;
264 }
265}
266
267pub fn read_rect_from_buffer(rect: &Rect, buffer: &Buffer) -> String {
269 let mut content = String::new();
270 for y in rect.y..rect.y + rect.height {
271 for x in rect.x..rect.x + rect.width {
272 let Some(cell) = buffer.cell((x, y)) else {
273 continue;
275 };
276 content.push_str(cell.symbol());
277 }
278 content.push('\n')
279 }
280 content
281}