use crossterm::event::{MouseButton, MouseEvent, MouseEventKind};
use super::{ButtonLayout, ButtonState, FocusState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonEvent {
Clicked,
Hovered,
Left,
}
impl ButtonState {
pub fn handle_mouse(
&mut self,
event: MouseEvent,
layout: &ButtonLayout,
) -> Option<ButtonEvent> {
if !self.is_enabled() {
return None;
}
let inside = layout.contains(event.column, event.row);
match event.kind {
MouseEventKind::Down(MouseButton::Left) if inside => {
self.pressed = true;
None }
MouseEventKind::Up(MouseButton::Left) => {
let was_pressed = self.pressed;
self.pressed = false;
if inside && was_pressed {
Some(ButtonEvent::Clicked)
} else {
None
}
}
MouseEventKind::Moved => {
if inside {
if self.focus != FocusState::Focused {
self.focus = FocusState::Hovered;
}
Some(ButtonEvent::Hovered)
} else if self.focus == FocusState::Hovered {
self.focus = FocusState::Normal;
Some(ButtonEvent::Left)
} else {
None
}
}
_ => None,
}
}
pub fn handle_key(&mut self, key: crossterm::event::KeyEvent) -> Option<ButtonEvent> {
use crossterm::event::KeyCode;
if !self.is_enabled() || self.focus != FocusState::Focused {
return None;
}
match key.code {
KeyCode::Enter | KeyCode::Char(' ') => Some(ButtonEvent::Clicked),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::layout::Rect;
fn make_layout() -> ButtonLayout {
ButtonLayout {
button_area: Rect::new(0, 0, 10, 1),
}
}
fn mouse_down(x: u16, y: u16) -> MouseEvent {
MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: x,
row: y,
modifiers: crossterm::event::KeyModifiers::empty(),
}
}
fn mouse_up(x: u16, y: u16) -> MouseEvent {
MouseEvent {
kind: MouseEventKind::Up(MouseButton::Left),
column: x,
row: y,
modifiers: crossterm::event::KeyModifiers::empty(),
}
}
fn mouse_move(x: u16, y: u16) -> MouseEvent {
MouseEvent {
kind: MouseEventKind::Moved,
column: x,
row: y,
modifiers: crossterm::event::KeyModifiers::empty(),
}
}
#[test]
fn test_click_inside_button() {
let mut state = ButtonState::new("Test");
let layout = make_layout();
let result = state.handle_mouse(mouse_down(5, 0), &layout);
assert!(result.is_none());
assert!(state.pressed);
let result = state.handle_mouse(mouse_up(5, 0), &layout);
assert_eq!(result, Some(ButtonEvent::Clicked));
assert!(!state.pressed);
}
#[test]
fn test_click_outside_button() {
let mut state = ButtonState::new("Test");
let layout = make_layout();
state.handle_mouse(mouse_down(5, 0), &layout);
assert!(state.pressed);
let result = state.handle_mouse(mouse_up(15, 0), &layout);
assert!(result.is_none());
assert!(!state.pressed);
}
#[test]
fn test_hover() {
let mut state = ButtonState::new("Test");
let layout = make_layout();
let result = state.handle_mouse(mouse_move(5, 0), &layout);
assert_eq!(result, Some(ButtonEvent::Hovered));
assert_eq!(state.focus, FocusState::Hovered);
let result = state.handle_mouse(mouse_move(15, 0), &layout);
assert_eq!(result, Some(ButtonEvent::Left));
assert_eq!(state.focus, FocusState::Normal);
}
#[test]
fn test_disabled_button_ignores_input() {
let mut state = ButtonState::new("Test").with_focus(FocusState::Disabled);
let layout = make_layout();
let result = state.handle_mouse(mouse_down(5, 0), &layout);
assert!(result.is_none());
assert!(!state.pressed);
}
#[test]
fn test_keyboard_activation() {
let mut state = ButtonState::new("Test").with_focus(FocusState::Focused);
let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
let result = state.handle_key(enter);
assert_eq!(result, Some(ButtonEvent::Clicked));
let space = crossterm::event::KeyEvent::new(KeyCode::Char(' '), KeyModifiers::empty());
let result = state.handle_key(space);
assert_eq!(result, Some(ButtonEvent::Clicked));
}
#[test]
fn test_unfocused_button_ignores_keyboard() {
let mut state = ButtonState::new("Test");
let enter = crossterm::event::KeyEvent::new(KeyCode::Enter, KeyModifiers::empty());
let result = state.handle_key(enter);
assert!(result.is_none());
}
}