pub(crate) mod mouse;
use std::time::{Duration, Instant};
use crossterm::event::{Event as CtEvent, MouseEvent};
use rdom_core::NodeId;
use crate::TuiDom;
const MULTI_CLICK_THRESHOLD: Duration = Duration::from_millis(500);
const MULTI_CLICK_TOLERANCE: u16 = 2;
#[derive(Debug, Clone, Copy)]
pub(super) struct ClickRecord {
pub(super) at: Instant,
pub(super) column: u16,
pub(super) row: u16,
pub(super) count: u8,
}
#[derive(Debug, Default)]
pub struct Router {
pub(super) down_target: Option<NodeId>,
pub(super) hover_target: Option<NodeId>,
pub(crate) selection_drag: Option<crate::render::inline::InlineFlow>,
pub(super) last_click: Option<ClickRecord>,
pub(crate) scrollbar_drag: Option<crate::runtime::scrollbar::ScrollbarDrag>,
pub(super) pending_redraw: bool,
}
impl Router {
pub fn new() -> Self {
Self::default()
}
pub fn route(&mut self, dom: &mut TuiDom, event: CtEvent) -> RouteOutcome {
if crate::runtime::trace::enabled() {
match &event {
CtEvent::Mouse(m) => crate::rdom_trace!(
"Router::route Mouse {{ kind: {:?}, col: {}, row: {}, mods: {:?} }} \
capture={:?} hover_target={:?} dom.hovered={:?} sel_drag={:?} scb_drag={}",
m.kind,
m.column,
m.row,
m.modifiers,
dom.pointer_capture(),
self.hover_target,
dom.hovered(),
self.selection_drag.is_some(),
self.scrollbar_drag.is_some(),
),
CtEvent::FocusGained => {
crate::rdom_trace!("Router::route FocusGained")
}
CtEvent::FocusLost => {
crate::rdom_trace!("Router::route FocusLost")
}
CtEvent::Resize(w, h) => {
crate::rdom_trace!("Router::route Resize {w}x{h}")
}
CtEvent::Key(k) => crate::rdom_trace!("Router::route Key {k:?}"),
CtEvent::Paste(_) => crate::rdom_trace!("Router::route Paste"),
}
}
match event {
CtEvent::Mouse(m) => {
self.pending_redraw = false;
let mut outcome = mouse::route_mouse(self, dom, m);
outcome.redraw_requested |= self.pending_redraw;
outcome
}
_ => RouteOutcome::default(),
}
}
pub fn reset(&mut self) {
self.down_target = None;
self.hover_target = None;
self.selection_drag = None;
self.last_click = None;
self.scrollbar_drag = None;
}
pub(super) fn register_click(&mut self, mouse: &MouseEvent) -> u8 {
let now = Instant::now();
let count = match self.last_click {
Some(prev)
if prev.count < 3
&& now.duration_since(prev.at) <= MULTI_CLICK_THRESHOLD
&& abs_diff_u16(mouse.column, prev.column) <= MULTI_CLICK_TOLERANCE
&& abs_diff_u16(mouse.row, prev.row) <= MULTI_CLICK_TOLERANCE =>
{
prev.count + 1
}
_ => 1,
};
self.last_click = Some(ClickRecord {
at: now,
column: mouse.column,
row: mouse.row,
count,
});
count
}
pub fn hover_target(&self) -> Option<NodeId> {
self.hover_target
}
pub fn down_target(&self) -> Option<NodeId> {
self.down_target
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct RouteOutcome {
pub redraw_requested: bool,
pub quit_requested: bool,
}
impl RouteOutcome {
pub fn merge(&mut self, other: RouteOutcome) {
self.redraw_requested |= other.redraw_requested;
self.quit_requested |= other.quit_requested;
}
}
#[inline]
fn abs_diff_u16(a: u16, b: u16) -> u16 {
a.abs_diff(b)
}