Skip to content

Commit

Permalink
feat(event): add event handling for mouse events (#79)
Browse files Browse the repository at this point in the history
crossterm only, as termion does not support mouse events

fixes #64

Co-authored-by: veeso <christian.visintin@veeso.dev>
  • Loading branch information
hasezoey and veeso authored Oct 13, 2024
1 parent 0197f65 commit 3df4628
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 7 deletions.
83 changes: 82 additions & 1 deletion src/core/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ where
{
/// A keyboard event
Keyboard(KeyEvent),
/// A Mouse event
Mouse(MouseEvent),
/// This event is raised after the terminal window is resized
WindowResize(u16, u16),
/// Window focus gained
Expand Down Expand Up @@ -45,6 +47,14 @@ where
}
}

pub(crate) fn is_mouse(&self) -> Option<&MouseEvent> {
if let Event::Mouse(m) = self {
Some(m)
} else {
None
}
}

pub(crate) fn is_window_resize(&self) -> bool {
matches!(self, Self::WindowResize(_, _))
}
Expand Down Expand Up @@ -234,6 +244,66 @@ pub enum MediaKeyCode {
MuteVolume,
}

/// A keyboard event
#[derive(Debug, Eq, PartialEq, Copy, Clone, PartialOrd, Hash)]
#[cfg_attr(
feature = "serialize",
derive(Deserialize, Serialize),
serde(tag = "type")
)]
pub struct MouseEvent {
/// The kind of mouse event that was caused
pub kind: MouseEventKind,
/// The key modifiers active when the event occurred
pub modifiers: KeyModifiers,
/// The column that the event occurred on
pub column: u16,
/// The row that the event occurred on
pub row: u16,
}

/// A Mouse event
#[derive(Debug, Eq, PartialEq, Copy, Clone, PartialOrd, Hash)]
#[cfg_attr(
feature = "serialize",
derive(Deserialize, Serialize),
serde(tag = "type", content = "args")
)]
pub enum MouseEventKind {
/// Pressed mouse button. Contains the button that was pressed
Down(MouseButton),
/// Released mouse button. Contains the button that was released
Up(MouseButton),
/// Moved the mouse cursor while pressing the contained mouse button
Drag(MouseButton),
/// Moved / Hover changed without pressing any buttons
Moved,
/// Scrolled mouse wheel downwards
ScrollDown,
/// Scrolled mouse wheel upwards
ScrollUp,
/// Scrolled mouse wheel left
ScrollLeft,
/// Scrolled mouse wheel right
ScrollRight,
}

/// A keyboard event
#[derive(Debug, Eq, PartialEq, Copy, Clone, PartialOrd, Hash)]
#[cfg_attr(
feature = "serialize",
derive(Deserialize, Serialize),
serde(tag = "type", content = "args")
)]
pub enum MouseButton {
/// Left mouse button.
Left,
/// Right mouse button.
Right,
/// Middle mouse button.
Middle,
}

#[cfg(test)]
mod test {

Expand Down Expand Up @@ -262,7 +332,7 @@ mod test {
assert!(e.is_keyboard().is_some());
assert_eq!(e.is_window_resize(), false);
assert_eq!(e.is_tick(), false);
assert_eq!(e.is_tick(), false);
assert_eq!(e.is_mouse().is_some(), false);
assert!(e.is_user().is_none());
let e: Event<MockEvent> = Event::WindowResize(0, 24);
assert!(e.is_window_resize());
Expand All @@ -271,6 +341,17 @@ mod test {
assert!(e.is_tick());
let e: Event<MockEvent> = Event::User(MockEvent::Bar);
assert_eq!(e.is_user().unwrap(), &MockEvent::Bar);

let e: Event<MockEvent> = Event::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0,
row: 0,
});
assert!(e.is_mouse().is_some());
assert_eq!(e.is_keyboard().is_some(), false);
assert_eq!(e.is_tick(), false);
assert_eq!(e.is_window_resize(), false);
}

// -- serde
Expand Down
93 changes: 91 additions & 2 deletions src/core/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
//! This module defines the model for the Subscriptions
use std::hash::Hash;
use std::ops::Range;

use crate::event::KeyEvent;
use crate::event::{KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use crate::{AttrValue, Attribute, Event, State};

/// Public type to define a subscription.
Expand Down Expand Up @@ -91,6 +92,25 @@ where
}
}

/// A event clause for [`MouseEvent`]s
#[derive(Debug, PartialEq, Eq)]
pub struct MouseEventClause {
/// The kind of mouse event that was caused
pub kind: MouseEventKind,
/// The key modifiers active when the event occurred
pub modifiers: KeyModifiers,
/// The column that the event occurred on
pub column: Range<u16>,
/// The row that the event occurred on
pub row: Range<u16>,
}

impl MouseEventClause {
fn is_in_range(&self, ev: &MouseEvent) -> bool {
self.column.contains(&ev.column) && self.row.contains(&ev.row)
}
}

#[derive(Debug, PartialEq, Eq)]

/// An event clause indicates on which kind of event the event must be forwarded to the `target` component.
Expand All @@ -102,6 +122,8 @@ where
Any,
/// Check whether a certain key has been pressed
Keyboard(KeyEvent),
/// Check whether a certain key has been pressed
Mouse(MouseEventClause),
/// Check whether window has been resized
WindowResize,
/// The event will be forwarded on a tick
Expand All @@ -121,6 +143,7 @@ where
///
/// - Any: Forward, no matter what kind of event
/// - Keyboard: everything must match
/// - Mouse: everything must match, column and row need to be within range
/// - WindowResize: matches only event type, not sizes
/// - Tick: matches tick event
/// - None: matches None event
Expand All @@ -129,6 +152,7 @@ where
match self {
EventClause::Any => true,
EventClause::Keyboard(k) => Some(k) == ev.is_keyboard(),
EventClause::Mouse(m) => ev.is_mouse().map(|ev| m.is_in_range(ev)).unwrap_or(false),
EventClause::WindowResize => ev.is_window_resize(),
EventClause::Tick => ev.is_tick(),
EventClause::User(u) => Some(u) == ev.is_user(),
Expand Down Expand Up @@ -296,7 +320,7 @@ mod test {

use super::*;
use crate::command::Cmd;
use crate::event::Key;
use crate::event::{Key, KeyModifiers, MouseEventKind};
use crate::mock::{MockComponentId, MockEvent, MockFooInput};
use crate::{MockComponent, StateValue};

Expand Down Expand Up @@ -389,6 +413,71 @@ mod test {
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter)).forward(&Event::Tick),
false
);
assert_eq!(
EventClause::<MockEvent>::Keyboard(KeyEvent::from(Key::Enter)).forward(&Event::Mouse(
MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0,
row: 0
}
)),
false
);
}

#[test]
fn event_clause_mouse_should_forward() {
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0,
row: 0
})),
true
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Mouse(MouseEvent {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 20,
row: 20
})),
false
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Keyboard(KeyEvent::from(Key::Backspace))),
false
);
assert_eq!(
EventClause::<MockEvent>::Mouse(MouseEventClause {
kind: MouseEventKind::Moved,
modifiers: KeyModifiers::NONE,
column: 0..10,
row: 0..10
})
.forward(&Event::Tick),
false
);
}

#[test]
Expand Down
Loading

0 comments on commit 3df4628

Please sign in to comment.