fresh/view/controls/toggle/
input.rs1use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
4
5use super::{FocusState, ToggleLayout, ToggleState};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ToggleEvent {
10 Toggled(bool),
12 Hovered,
14 Left,
16}
17
18impl ToggleState {
19 pub fn handle_mouse(
29 &mut self,
30 event: MouseEvent,
31 layout: &ToggleLayout,
32 ) -> Option<ToggleEvent> {
33 if !self.is_enabled() {
34 return None;
35 }
36
37 let inside = layout.contains(event.column, event.row);
38
39 match event.kind {
40 MouseEventKind::Down(MouseButton::Left) if inside => {
41 self.toggle();
42 Some(ToggleEvent::Toggled(self.checked))
43 }
44 MouseEventKind::Moved => {
45 if inside {
46 if self.focus != FocusState::Focused {
47 self.focus = FocusState::Hovered;
48 }
49 Some(ToggleEvent::Hovered)
50 } else if self.focus == FocusState::Hovered {
51 self.focus = FocusState::Normal;
52 Some(ToggleEvent::Left)
53 } else {
54 None
55 }
56 }
57 _ => None,
58 }
59 }
60
61 pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Option<ToggleEvent> {
67 use crossterm::event::KeyCode;
68
69 if !self.is_enabled() || self.focus != FocusState::Focused {
70 return None;
71 }
72
73 match key.code {
74 KeyCode::Enter | KeyCode::Char(' ') => {
75 self.toggle();
76 Some(ToggleEvent::Toggled(self.checked))
77 }
78 _ => None,
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crossterm::event::{KeyCode, KeyModifiers};
87 use ratatui::layout::Rect;
88
89 fn make_layout() -> ToggleLayout {
90 ToggleLayout {
91 checkbox_area: Rect::new(8, 0, 3, 1),
92 full_area: Rect::new(0, 0, 11, 1),
93 }
94 }
95
96 fn mouse_down(x: u16, y: u16) -> MouseEvent {
97 MouseEvent {
98 kind: MouseEventKind::Down(MouseButton::Left),
99 column: x,
100 row: y,
101 modifiers: crossterm::event::KeyModifiers::empty(),
102 }
103 }
104
105 fn mouse_move(x: u16, y: u16) -> MouseEvent {
106 MouseEvent {
107 kind: MouseEventKind::Moved,
108 column: x,
109 row: y,
110 modifiers: crossterm::event::KeyModifiers::empty(),
111 }
112 }
113
114 #[test]
115 fn test_click_toggles() {
116 let mut state = ToggleState::new(false, "Enable");
117 let layout = make_layout();
118
119 let result = state.handle_mouse(mouse_down(5, 0), &layout);
120 assert_eq!(result, Some(ToggleEvent::Toggled(true)));
121 assert!(state.checked);
122
123 let result = state.handle_mouse(mouse_down(5, 0), &layout);
124 assert_eq!(result, Some(ToggleEvent::Toggled(false)));
125 assert!(!state.checked);
126 }
127
128 #[test]
129 fn test_click_outside_ignored() {
130 let mut state = ToggleState::new(false, "Enable");
131 let layout = make_layout();
132
133 let result = state.handle_mouse(mouse_down(15, 0), &layout);
134 assert!(result.is_none());
135 assert!(!state.checked);
136 }
137
138 #[test]
139 fn test_hover() {
140 let mut state = ToggleState::new(false, "Enable");
141 let layout = make_layout();
142
143 let result = state.handle_mouse(mouse_move(5, 0), &layout);
144 assert_eq!(result, Some(ToggleEvent::Hovered));
145 assert_eq!(state.focus, FocusState::Hovered);
146
147 let result = state.handle_mouse(mouse_move(15, 0), &layout);
148 assert_eq!(result, Some(ToggleEvent::Left));
149 assert_eq!(state.focus, FocusState::Normal);
150 }
151
152 #[test]
153 fn test_disabled_ignores_input() {
154 let mut state = ToggleState::new(false, "Enable").with_focus(FocusState::Disabled);
155 let layout = make_layout();
156
157 let result = state.handle_mouse(mouse_down(5, 0), &layout);
158 assert!(result.is_none());
159 assert!(!state.checked);
160 }
161
162 #[test]
163 fn test_keyboard_activation() {
164 let mut state = ToggleState::new(false, "Enable").with_focus(FocusState::Focused);
165
166 let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
167 let result = state.handle_key(enter);
168 assert_eq!(result, Some(ToggleEvent::Toggled(true)));
169 assert!(state.checked);
170
171 let space = crossterm::event::KeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty());
172 let result = state.handle_key(space);
173 assert_eq!(result, Some(ToggleEvent::Toggled(false)));
174 assert!(!state.checked);
175 }
176
177 #[test]
178 fn test_unfocused_ignores_keyboard() {
179 let mut state = ToggleState::new(false, "Enable"); let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
182 let result = state.handle_key(enter);
183 assert!(result.is_none());
184 assert!(!state.checked);
185 }
186}