pub mod key_translate;
use crossterm::event::{KeyEvent, MouseEvent};
use rdom_core::{Event, EventDetail};
#[derive(Debug, Clone)]
pub struct TuiEvent {
pub event: Event,
}
impl TuiEvent {
pub fn new(event_type: impl Into<String>) -> Self {
Self {
event: Event::new(event_type),
}
}
pub fn keydown(key: KeyEvent) -> Self {
let mut e = Self::new("keydown");
e.event.detail = EventDetail::Keyboard(Box::new(key_translate::translate_key_event(key)));
e
}
pub fn keyup(key: KeyEvent) -> Self {
let mut e = Self::new("keyup");
e.event.detail = EventDetail::Keyboard(Box::new(key_translate::translate_key_event(key)));
e
}
pub fn keypress(key: KeyEvent) -> Self {
let mut e = Self::new("keypress");
e.event.detail = EventDetail::Keyboard(Box::new(key_translate::translate_key_event(key)));
e
}
pub fn click(mouse: MouseEvent) -> Self {
let mut e = Self::new("click");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn mousedown(mouse: MouseEvent) -> Self {
let mut e = Self::new("mousedown");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn mouseup(mouse: MouseEvent) -> Self {
let mut e = Self::new("mouseup");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn mousemove(mouse: MouseEvent) -> Self {
let mut e = Self::new("mousemove");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn mouseover(mouse: MouseEvent) -> Self {
let mut e = Self::new("mouseover");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn mouseout(mouse: MouseEvent) -> Self {
let mut e = Self::new("mouseout");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn contextmenu(mouse: MouseEvent) -> Self {
let mut e = Self::new("contextmenu");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn dblclick(mouse: MouseEvent) -> Self {
let mut e = Self::new("dblclick");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn wheel(mouse: MouseEvent) -> Self {
let mut e = Self::new("wheel");
e.event.detail = EventDetail::Mouse(key_translate::translate_mouse_event(mouse));
e
}
pub fn input(input_type: rdom_core::InputType, data: Option<String>) -> Self {
let mut e = Self::new("input");
e.event.detail = rdom_core::EventDetail::Input(Box::new(rdom_core::InputDetail {
input_type,
data,
is_composing: false,
}));
e
}
pub fn before_input(input_type: rdom_core::InputType, data: Option<String>) -> Self {
let mut e = Self::new("beforeinput");
e.event.detail = rdom_core::EventDetail::Input(Box::new(rdom_core::InputDetail {
input_type,
data,
is_composing: false,
}));
e
}
pub fn copy(text: impl Into<String>) -> Self {
let mut e = Self::new("copy");
e.event.detail = rdom_core::EventDetail::String(text.into());
e
}
pub fn cut(text: impl Into<String>) -> Self {
let mut e = Self::new("cut");
e.event.detail = rdom_core::EventDetail::String(text.into());
e
}
pub fn paste(text: impl Into<String>) -> Self {
let mut e = Self::new("paste");
e.event.detail = rdom_core::EventDetail::String(text.into());
e
}
pub fn focus() -> Self {
let mut e = Self::new("focus");
e.event = e.event.with_bubbles(false);
e
}
pub fn focusin() -> Self {
Self::new("focusin")
}
pub fn blur() -> Self {
let mut e = Self::new("blur");
e.event = e.event.with_bubbles(false);
e
}
pub fn focusout() -> Self {
Self::new("focusout")
}
pub fn close() -> Self {
Self::new("close")
}
pub fn as_event(&self) -> &Event {
&self.event
}
pub fn as_event_mut(&mut self) -> &mut Event {
&mut self.event
}
pub fn into_event(self) -> Event {
self.event
}
pub fn is_key(&self) -> bool {
matches!(self.event.detail, EventDetail::Keyboard(_))
}
pub fn is_mouse(&self) -> bool {
matches!(self.event.detail, EventDetail::Mouse(_))
}
}
pub trait TuiDispatchExt {
fn dispatch_tui_event(
&mut self,
target: rdom_core::NodeId,
tui: &mut TuiEvent,
) -> rdom_core::Result<()>;
}
impl TuiDispatchExt for rdom_core::Dom<crate::TuiExt> {
fn dispatch_tui_event(
&mut self,
target: rdom_core::NodeId,
tui: &mut TuiEvent,
) -> rdom_core::Result<()> {
self.dispatch_event(target, &mut tui.event)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{
KeyCode, KeyEventKind, KeyEventState, KeyModifiers, MouseButton, MouseEventKind,
};
fn key(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
fn mouse_click(col: u16, row: u16) -> MouseEvent {
MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: col,
row,
modifiers: KeyModifiers::empty(),
}
}
#[test]
fn new_plain_event() {
let e = TuiEvent::new("close");
assert_eq!(e.event.event_type, "close");
assert!(!e.is_key());
assert!(!e.is_mouse());
}
#[test]
fn keydown_carries_typed_keyboard_detail() {
let k = key(KeyCode::Enter, KeyModifiers::NONE);
let e = TuiEvent::keydown(k);
assert_eq!(e.event.event_type, "keydown");
assert!(e.is_key());
let kb = e
.event
.detail
.as_keyboard()
.expect("keydown must carry EventDetail::Keyboard");
assert_eq!(kb.key, "Enter");
assert!(!kb.repeat);
assert!(!kb.modifiers.ctrl && !kb.modifiers.shift);
}
#[test]
fn keyup_keypress() {
let k = key(KeyCode::Char('a'), KeyModifiers::NONE);
assert_eq!(TuiEvent::keyup(k).event.event_type, "keyup");
assert_eq!(TuiEvent::keypress(k).event.event_type, "keypress");
}
#[test]
fn click_carries_typed_mouse_detail() {
let m = mouse_click(5, 2);
let e = TuiEvent::click(m);
assert_eq!(e.event.event_type, "click");
assert!(e.is_mouse());
let md = e
.event
.detail
.as_mouse()
.expect("click must carry EventDetail::Mouse");
assert_eq!(md.button, rdom_core::MouseButton::Left);
assert_eq!(md.client_x, 5);
assert_eq!(md.client_y, 2);
}
#[test]
fn mouse_event_variants() {
let m = mouse_click(0, 0);
assert_eq!(TuiEvent::mousedown(m).event.event_type, "mousedown");
assert_eq!(TuiEvent::mouseup(m).event.event_type, "mouseup");
assert_eq!(TuiEvent::mousemove(m).event.event_type, "mousemove");
assert_eq!(TuiEvent::wheel(m).event.event_type, "wheel");
}
#[test]
fn input_carries_typed_detail() {
let e = TuiEvent::input(rdom_core::InputType::InsertText, Some("hello world".into()));
assert_eq!(e.event.event_type, "input");
let i = e.event.detail.as_input().expect("typed Input detail");
assert_eq!(i.input_type, rdom_core::InputType::InsertText);
assert_eq!(i.data.as_deref(), Some("hello world"));
assert!(!i.is_composing);
}
#[test]
fn focus_blur_close_builtins() {
assert_eq!(TuiEvent::focus().event.event_type, "focus");
assert_eq!(TuiEvent::blur().event.event_type, "blur");
assert_eq!(TuiEvent::close().event.event_type, "close");
}
#[test]
fn as_event_mut_allows_stop_propagation() {
let mut e = TuiEvent::new("click");
assert!(!e.event.is_propagation_stopped());
e.as_event_mut().stop_propagation();
assert!(e.event.is_propagation_stopped());
}
#[test]
fn into_event_drops_tui_payload() {
let k = key(KeyCode::Enter, KeyModifiers::empty());
let e = TuiEvent::keydown(k);
let core = e.into_event();
assert_eq!(core.event_type, "keydown");
}
#[test]
fn detail_propagates_through_as_event() {
let e = TuiEvent::input(rdom_core::InputType::InsertText, Some("typed".into()));
assert_eq!(
e.as_event()
.detail
.as_input()
.and_then(|i| i.data.as_deref()),
Some("typed")
);
}
#[test]
fn core_dispatch_works_on_tui_event_inner() {
use crate::prelude::*;
use std::cell::Cell;
use std::rc::Rc;
let mut dom: TuiDom = TuiDom::new();
let btn = dom.create_element("button");
let fired = Rc::new(Cell::new(false));
let f = fired.clone();
dom.add_event_listener(
btn,
"click",
ListenerOptions::default(),
move |_ctx: &mut TuiEventCtx<'_>| {
f.set(true);
},
)
.unwrap();
let mut tui = TuiEvent::click(mouse_click(3, 4));
dom.dispatch_event(btn, tui.as_event_mut()).unwrap();
assert!(fired.get());
}
}