use crossterm::event::{MouseButton as CrosstermMouseButton, MouseEvent, MouseEventKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
}
impl From<CrosstermMouseButton> for MouseButton {
fn from(btn: CrosstermMouseButton) -> Self {
match btn {
CrosstermMouseButton::Left => MouseButton::Left,
CrosstermMouseButton::Right => MouseButton::Right,
CrosstermMouseButton::Middle => MouseButton::Middle,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseAction {
Press(MouseButton),
Release(MouseButton),
Drag(MouseButton),
Move,
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
}
#[derive(Debug, Clone)]
pub struct Mouse {
pub x: u16,
pub y: u16,
pub action: MouseAction,
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
}
impl Mouse {
pub fn from_event(event: &MouseEvent) -> Self {
let action = match event.kind {
MouseEventKind::Down(btn) => MouseAction::Press(btn.into()),
MouseEventKind::Up(btn) => MouseAction::Release(btn.into()),
MouseEventKind::Drag(btn) => MouseAction::Drag(btn.into()),
MouseEventKind::Moved => MouseAction::Move,
MouseEventKind::ScrollUp => MouseAction::ScrollUp,
MouseEventKind::ScrollDown => MouseAction::ScrollDown,
MouseEventKind::ScrollLeft => MouseAction::ScrollLeft,
MouseEventKind::ScrollRight => MouseAction::ScrollRight,
};
Self {
x: event.column,
y: event.row,
action,
ctrl: event
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL),
shift: event
.modifiers
.contains(crossterm::event::KeyModifiers::SHIFT),
alt: event
.modifiers
.contains(crossterm::event::KeyModifiers::ALT),
}
}
pub fn is_click(&self) -> bool {
matches!(self.action, MouseAction::Press(_))
}
pub fn is_left_click(&self) -> bool {
matches!(self.action, MouseAction::Press(MouseButton::Left))
}
pub fn is_right_click(&self) -> bool {
matches!(self.action, MouseAction::Press(MouseButton::Right))
}
pub fn is_scroll(&self) -> bool {
matches!(
self.action,
MouseAction::ScrollUp
| MouseAction::ScrollDown
| MouseAction::ScrollLeft
| MouseAction::ScrollRight
)
}
pub fn scroll_delta(&self) -> (i8, i8) {
match self.action {
MouseAction::ScrollUp => (0, -1),
MouseAction::ScrollDown => (0, 1),
MouseAction::ScrollLeft => (-1, 0),
MouseAction::ScrollRight => (1, 0),
_ => (0, 0),
}
}
}
pub type MouseHandler = Box<dyn Fn(&Mouse)>;
pub fn register_mouse_handler<F>(handler: F)
where
F: Fn(&Mouse) + 'static,
{
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().register_mouse_handler(handler);
}
}
pub fn clear_mouse_handlers() {}
pub fn dispatch_mouse_event(event: &MouseEvent) {
let mouse = Mouse::from_event(event);
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow().dispatch_mouse(&mouse);
}
}
pub fn is_mouse_enabled() -> bool {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow().is_mouse_enabled()
} else {
false
}
}
pub fn set_mouse_enabled(enabled: bool) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().set_mouse_enabled(enabled);
}
}
pub fn use_mouse<F>(handler: F)
where
F: Fn(&Mouse) + 'static,
{
if let Some(ctx) = crate::hooks::context::current_context() {
ctx.borrow_mut().use_hook(|| ());
}
register_mouse_handler(handler);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mouse_action_from_event() {
let _left = MouseButton::Left;
let _right = MouseButton::Right;
let _action = MouseAction::Press(MouseButton::Left);
}
#[test]
fn test_mouse_is_click() {
let mouse = Mouse {
x: 10,
y: 5,
action: MouseAction::Press(MouseButton::Left),
ctrl: false,
shift: false,
alt: false,
};
assert!(mouse.is_click());
assert!(mouse.is_left_click());
assert!(!mouse.is_right_click());
}
#[test]
fn test_mouse_scroll_delta() {
let scroll_up = Mouse {
x: 0,
y: 0,
action: MouseAction::ScrollUp,
ctrl: false,
shift: false,
alt: false,
};
assert_eq!(scroll_up.scroll_delta(), (0, -1));
let scroll_down = Mouse {
x: 0,
y: 0,
action: MouseAction::ScrollDown,
ctrl: false,
shift: false,
alt: false,
};
assert_eq!(scroll_down.scroll_delta(), (0, 1));
}
#[test]
fn test_mouse_enabled_default() {
crate::runtime::set_current_runtime(None);
assert!(!is_mouse_enabled());
}
#[test]
fn test_mouse_with_runtime() {
use crate::runtime::{RuntimeContext, with_runtime};
use std::cell::RefCell;
use std::rc::Rc;
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
let clicked = Rc::new(RefCell::new(false));
let clicked_clone = clicked.clone();
with_runtime(ctx.clone(), || {
use_mouse(move |mouse| {
if mouse.is_left_click() {
*clicked_clone.borrow_mut() = true;
}
});
});
let mouse = Mouse {
x: 10,
y: 5,
action: MouseAction::Press(MouseButton::Left),
ctrl: false,
shift: false,
alt: false,
};
ctx.borrow().dispatch_mouse(&mouse);
assert!(*clicked.borrow());
}
#[test]
#[should_panic(expected = "Hook order violation")]
fn test_use_mouse_participates_in_hook_order() {
use crate::hooks::context::{HookContext, with_hooks};
use crate::hooks::use_signal;
use std::cell::RefCell;
use std::rc::Rc;
let ctx = Rc::new(RefCell::new(HookContext::new()));
with_hooks(ctx.clone(), || {
use_mouse(|_| {});
let _state = use_signal(|| 1usize);
});
with_hooks(ctx, || {
let _state = use_signal(|| 1usize);
});
}
}