use crossterm::event::{MouseButton as CrosstermMouseButton, MouseEvent, MouseEventKind};
use std::cell::RefCell;
use std::rc::Rc;
#[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)>;
type MouseHandlerRc = Rc<dyn Fn(&Mouse)>;
thread_local! {
static MOUSE_HANDLERS: RefCell<Vec<MouseHandlerRc>> = RefCell::new(Vec::new());
static MOUSE_ENABLED: RefCell<bool> = const { RefCell::new(false) };
}
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);
} else {
MOUSE_HANDLERS.with(|handlers| {
handlers.borrow_mut().push(Rc::new(handler));
});
set_mouse_enabled(true);
}
}
pub fn clear_mouse_handlers() {
MOUSE_HANDLERS.with(|handlers| {
handlers.borrow_mut().clear();
});
}
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);
} else {
MOUSE_HANDLERS.with(|handlers| {
for handler in handlers.borrow().iter() {
handler(&mouse);
}
});
}
}
pub fn is_mouse_enabled() -> bool {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow().is_mouse_enabled()
} else {
MOUSE_ENABLED.with(|enabled| *enabled.borrow())
}
}
pub fn set_mouse_enabled(enabled: bool) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().set_mouse_enabled(enabled);
} else {
MOUSE_ENABLED.with(|e| *e.borrow_mut() = enabled);
}
}
pub fn use_mouse<F>(handler: F)
where
F: Fn(&Mouse) + 'static,
{
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_legacy() {
MOUSE_ENABLED.with(|e| *e.borrow_mut() = false);
assert!(!MOUSE_ENABLED.with(|e| *e.borrow()));
MOUSE_ENABLED.with(|e| *e.borrow_mut() = true);
assert!(MOUSE_ENABLED.with(|e| *e.borrow()));
MOUSE_ENABLED.with(|e| *e.borrow_mut() = false); }
#[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());
}
}