fresh/view/controls/button/
input.rs1use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
4
5use super::{ButtonLayout, ButtonState, FocusState};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ButtonEvent {
10 Clicked,
12 Hovered,
14 Left,
16}
17
18impl ButtonState {
19 pub fn handle_mouse(
29 &mut self,
30 event: MouseEvent,
31 layout: &ButtonLayout,
32 ) -> Option<ButtonEvent> {
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.pressed = true;
42 None }
44 MouseEventKind::Up(MouseButton::Left) => {
45 let was_pressed = self.pressed;
46 self.pressed = false;
47
48 if inside && was_pressed {
49 Some(ButtonEvent::Clicked)
50 } else {
51 None
52 }
53 }
54 MouseEventKind::Moved => {
55 if inside {
56 if self.focus != FocusState::Focused {
57 self.focus = FocusState::Hovered;
58 }
59 Some(ButtonEvent::Hovered)
60 } else if self.focus == FocusState::Hovered {
61 self.focus = FocusState::Normal;
62 Some(ButtonEvent::Left)
63 } else {
64 None
65 }
66 }
67 _ => None,
68 }
69 }
70
71 pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Option<ButtonEvent> {
77 use crossterm::event::KeyCode;
78
79 if !self.is_enabled() || self.focus != FocusState::Focused {
80 return None;
81 }
82
83 match key.code {
84 KeyCode::Enter | KeyCode::Char(' ') => Some(ButtonEvent::Clicked),
85 _ => None,
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crossterm::event::{KeyCode, KeyModifiers};
94 use ratatui::layout::Rect;
95
96 fn make_layout() -> ButtonLayout {
97 ButtonLayout {
98 button_area: Rect::new(0, 0, 10, 1),
99 }
100 }
101
102 fn mouse_down(x: u16, y: u16) -> MouseEvent {
103 MouseEvent {
104 kind: MouseEventKind::Down(MouseButton::Left),
105 column: x,
106 row: y,
107 modifiers: crossterm::event::KeyModifiers::empty(),
108 }
109 }
110
111 fn mouse_up(x: u16, y: u16) -> MouseEvent {
112 MouseEvent {
113 kind: MouseEventKind::Up(MouseButton::Left),
114 column: x,
115 row: y,
116 modifiers: crossterm::event::KeyModifiers::empty(),
117 }
118 }
119
120 fn mouse_move(x: u16, y: u16) -> MouseEvent {
121 MouseEvent {
122 kind: MouseEventKind::Moved,
123 column: x,
124 row: y,
125 modifiers: crossterm::event::KeyModifiers::empty(),
126 }
127 }
128
129 #[test]
130 fn test_click_inside_button() {
131 let mut state = ButtonState::new("Test");
132 let layout = make_layout();
133
134 let result = state.handle_mouse(mouse_down(5, 0), &layout);
136 assert!(result.is_none());
137 assert!(state.pressed);
138
139 let result = state.handle_mouse(mouse_up(5, 0), &layout);
141 assert_eq!(result, Some(ButtonEvent::Clicked));
142 assert!(!state.pressed);
143 }
144
145 #[test]
146 fn test_click_outside_button() {
147 let mut state = ButtonState::new("Test");
148 let layout = make_layout();
149
150 state.handle_mouse(mouse_down(5, 0), &layout);
152 assert!(state.pressed);
153
154 let result = state.handle_mouse(mouse_up(15, 0), &layout);
156 assert!(result.is_none());
157 assert!(!state.pressed);
158 }
159
160 #[test]
161 fn test_hover() {
162 let mut state = ButtonState::new("Test");
163 let layout = make_layout();
164
165 let result = state.handle_mouse(mouse_move(5, 0), &layout);
167 assert_eq!(result, Some(ButtonEvent::Hovered));
168 assert_eq!(state.focus, FocusState::Hovered);
169
170 let result = state.handle_mouse(mouse_move(15, 0), &layout);
172 assert_eq!(result, Some(ButtonEvent::Left));
173 assert_eq!(state.focus, FocusState::Normal);
174 }
175
176 #[test]
177 fn test_disabled_button_ignores_input() {
178 let mut state = ButtonState::new("Test").with_focus(FocusState::Disabled);
179 let layout = make_layout();
180
181 let result = state.handle_mouse(mouse_down(5, 0), &layout);
182 assert!(result.is_none());
183 assert!(!state.pressed);
184 }
185
186 #[test]
187 fn test_keyboard_activation() {
188 let mut state = ButtonState::new("Test").with_focus(FocusState::Focused);
189
190 let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
191 let result = state.handle_key(enter);
192 assert_eq!(result, Some(ButtonEvent::Clicked));
193
194 let space = crossterm::event::KeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty());
195 let result = state.handle_key(space);
196 assert_eq!(result, Some(ButtonEvent::Clicked));
197 }
198
199 #[test]
200 fn test_unfocused_button_ignores_keyboard() {
201 let mut state = ButtonState::new("Test"); let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
204 let result = state.handle_key(enter);
205 assert!(result.is_none());
206 }
207}