ratatui_interact/components/
mouse_pointer.rs1use ratatui::{
30 buffer::Buffer,
31 layout::Rect,
32 style::{Color, Style},
33};
34
35#[derive(Debug, Clone, Default)]
39pub struct MousePointerState {
40 pub enabled: bool,
42 pub position: Option<(u16, u16)>,
44}
45
46impl MousePointerState {
47 pub fn new() -> Self {
51 Self::default()
52 }
53
54 pub fn with_enabled(enabled: bool) -> Self {
56 Self {
57 enabled,
58 position: None,
59 }
60 }
61
62 pub fn set_enabled(&mut self, enabled: bool) {
64 self.enabled = enabled;
65 }
66
67 pub fn toggle(&mut self) {
69 self.enabled = !self.enabled;
70 }
71
72 pub fn update_position(&mut self, col: u16, row: u16) {
74 self.position = Some((col, row));
75 }
76
77 pub fn clear_position(&mut self) {
79 self.position = None;
80 }
81
82 pub fn should_render(&self) -> bool {
86 self.enabled && self.position.is_some()
87 }
88}
89
90#[derive(Debug, Clone)]
92pub struct MousePointerStyle {
93 pub symbol: &'static str,
95 pub fg: Color,
97 pub bg: Option<Color>,
99}
100
101impl Default for MousePointerStyle {
102 fn default() -> Self {
103 Self {
104 symbol: "█",
105 fg: Color::Yellow,
106 bg: None,
107 }
108 }
109}
110
111impl MousePointerStyle {
112 pub fn crosshair() -> Self {
114 Self {
115 symbol: "┼",
116 fg: Color::Cyan,
117 bg: None,
118 }
119 }
120
121 pub fn arrow() -> Self {
123 Self {
124 symbol: "▶",
125 fg: Color::White,
126 bg: None,
127 }
128 }
129
130 pub fn dot() -> Self {
132 Self {
133 symbol: "●",
134 fg: Color::Green,
135 bg: None,
136 }
137 }
138
139 pub fn plus() -> Self {
141 Self {
142 symbol: "+",
143 fg: Color::Magenta,
144 bg: None,
145 }
146 }
147
148 pub fn custom(symbol: &'static str, fg: Color) -> Self {
150 Self {
151 symbol,
152 fg,
153 bg: None,
154 }
155 }
156
157 pub fn symbol(mut self, symbol: &'static str) -> Self {
159 self.symbol = symbol;
160 self
161 }
162
163 pub fn fg(mut self, fg: Color) -> Self {
165 self.fg = fg;
166 self
167 }
168
169 pub fn bg(mut self, bg: Color) -> Self {
171 self.bg = Some(bg);
172 self
173 }
174}
175
176#[derive(Debug, Clone)]
181pub struct MousePointer<'a> {
182 state: &'a MousePointerState,
184 style: MousePointerStyle,
186}
187
188impl<'a> MousePointer<'a> {
189 pub fn new(state: &'a MousePointerState) -> Self {
191 Self {
192 state,
193 style: MousePointerStyle::default(),
194 }
195 }
196
197 pub fn style(mut self, style: MousePointerStyle) -> Self {
199 self.style = style;
200 self
201 }
202
203 pub fn render(self, buf: &mut Buffer) {
208 if !self.state.should_render() {
209 return;
210 }
211
212 let (col, row) = self.state.position.unwrap();
213 self.render_at(buf, col, row);
214 }
215
216 pub fn render_in_area(self, buf: &mut Buffer, area: Rect) {
220 if !self.state.should_render() {
221 return;
222 }
223
224 let (col, row) = self.state.position.unwrap();
225
226 if col >= area.x
228 && col < area.x + area.width
229 && row >= area.y
230 && row < area.y + area.height
231 {
232 self.render_at(buf, col, row);
233 }
234 }
235
236 fn render_at(&self, buf: &mut Buffer, col: u16, row: u16) {
238 let buf_area = buf.area();
239
240 if col >= buf_area.x + buf_area.width || row >= buf_area.y + buf_area.height {
242 return;
243 }
244
245 let mut cell_style = Style::default().fg(self.style.fg);
247 if let Some(bg) = self.style.bg {
248 cell_style = cell_style.bg(bg);
249 }
250
251 buf[(col, row)].set_symbol(self.style.symbol).set_style(cell_style);
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn test_state_default() {
262 let state = MousePointerState::default();
263 assert!(!state.enabled);
264 assert!(state.position.is_none());
265 assert!(!state.should_render());
266 }
267
268 #[test]
269 fn test_state_with_enabled() {
270 let state = MousePointerState::with_enabled(true);
271 assert!(state.enabled);
272 assert!(state.position.is_none());
273 assert!(!state.should_render()); }
275
276 #[test]
277 fn test_state_toggle() {
278 let mut state = MousePointerState::default();
279 assert!(!state.enabled);
280
281 state.toggle();
282 assert!(state.enabled);
283
284 state.toggle();
285 assert!(!state.enabled);
286 }
287
288 #[test]
289 fn test_state_position_update() {
290 let mut state = MousePointerState::default();
291 state.set_enabled(true);
292
293 assert!(state.position.is_none());
294
295 state.update_position(10, 5);
296 assert_eq!(state.position, Some((10, 5)));
297 assert!(state.should_render());
298
299 state.clear_position();
300 assert!(state.position.is_none());
301 assert!(!state.should_render());
302 }
303
304 #[test]
305 fn test_should_render() {
306 let mut state = MousePointerState::default();
307
308 assert!(!state.should_render());
310
311 state.set_enabled(true);
313 assert!(!state.should_render());
314
315 state.update_position(5, 5);
317 assert!(state.should_render());
318
319 state.set_enabled(false);
321 assert!(!state.should_render());
322 }
323
324 #[test]
325 fn test_style_default() {
326 let style = MousePointerStyle::default();
327 assert_eq!(style.symbol, "█");
328 assert_eq!(style.fg, Color::Yellow);
329 assert!(style.bg.is_none());
330 }
331
332 #[test]
333 fn test_style_presets() {
334 let crosshair = MousePointerStyle::crosshair();
335 assert_eq!(crosshair.symbol, "┼");
336 assert_eq!(crosshair.fg, Color::Cyan);
337
338 let arrow = MousePointerStyle::arrow();
339 assert_eq!(arrow.symbol, "▶");
340 assert_eq!(arrow.fg, Color::White);
341
342 let dot = MousePointerStyle::dot();
343 assert_eq!(dot.symbol, "●");
344 assert_eq!(dot.fg, Color::Green);
345
346 let plus = MousePointerStyle::plus();
347 assert_eq!(plus.symbol, "+");
348 assert_eq!(plus.fg, Color::Magenta);
349 }
350
351 #[test]
352 fn test_style_custom() {
353 let custom = MousePointerStyle::custom("X", Color::Red);
354 assert_eq!(custom.symbol, "X");
355 assert_eq!(custom.fg, Color::Red);
356 }
357
358 #[test]
359 fn test_style_builder() {
360 let style = MousePointerStyle::default()
361 .symbol("*")
362 .fg(Color::Blue)
363 .bg(Color::White);
364
365 assert_eq!(style.symbol, "*");
366 assert_eq!(style.fg, Color::Blue);
367 assert_eq!(style.bg, Some(Color::White));
368 }
369
370 #[test]
371 fn test_render_disabled() {
372 let state = MousePointerState::default();
373 let pointer = MousePointer::new(&state);
374
375 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
376 pointer.render(&mut buf);
377
378 for y in 0..10 {
380 for x in 0..10 {
381 assert_eq!(buf[(x, y)].symbol(), " ");
382 }
383 }
384 }
385
386 #[test]
387 fn test_render_enabled() {
388 let mut state = MousePointerState::default();
389 state.set_enabled(true);
390 state.update_position(5, 5);
391
392 let pointer = MousePointer::new(&state);
393
394 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
395 pointer.render(&mut buf);
396
397 assert_eq!(buf[(5, 5)].symbol(), "█");
399 }
400
401 #[test]
402 fn test_render_with_custom_style() {
403 let mut state = MousePointerState::default();
404 state.set_enabled(true);
405 state.update_position(3, 3);
406
407 let pointer = MousePointer::new(&state).style(MousePointerStyle::crosshair());
408
409 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
410 pointer.render(&mut buf);
411
412 assert_eq!(buf[(3, 3)].symbol(), "┼");
413 }
414
415 #[test]
416 fn test_render_out_of_bounds() {
417 let mut state = MousePointerState::default();
418 state.set_enabled(true);
419 state.update_position(100, 100); let pointer = MousePointer::new(&state);
422
423 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
424 pointer.render(&mut buf);
425
426 for y in 0..10 {
428 for x in 0..10 {
429 assert_eq!(buf[(x, y)].symbol(), " ");
430 }
431 }
432 }
433
434 #[test]
435 fn test_render_in_area_inside() {
436 let mut state = MousePointerState::default();
437 state.set_enabled(true);
438 state.update_position(5, 5);
439
440 let pointer = MousePointer::new(&state);
441 let area = Rect::new(0, 0, 10, 10);
442
443 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 20));
444 pointer.render_in_area(&mut buf, area);
445
446 assert_eq!(buf[(5, 5)].symbol(), "█");
447 }
448
449 #[test]
450 fn test_render_in_area_outside() {
451 let mut state = MousePointerState::default();
452 state.set_enabled(true);
453 state.update_position(15, 15); let pointer = MousePointer::new(&state);
456 let area = Rect::new(0, 0, 10, 10);
457
458 let mut buf = Buffer::empty(Rect::new(0, 0, 20, 20));
459 pointer.render_in_area(&mut buf, area);
460
461 assert_eq!(buf[(15, 15)].symbol(), " ");
463 }
464
465 #[test]
466 fn test_render_at_boundary() {
467 let mut state = MousePointerState::default();
468 state.set_enabled(true);
469 state.update_position(9, 9); let pointer = MousePointer::new(&state);
472
473 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 10));
474 pointer.render(&mut buf);
475
476 assert_eq!(buf[(9, 9)].symbol(), "█");
477 }
478}