tui_dispatch_core/
event.rs1use crossterm::event::{KeyEvent, KeyModifiers, MouseEvent};
4use ratatui::layout::Rect;
5use std::collections::HashMap;
6use std::fmt::Debug;
7use std::hash::Hash;
8
9pub trait ComponentId: Clone + Copy + Eq + Hash + Debug {
24 fn name(&self) -> &'static str;
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
33pub struct NumericComponentId(pub u32);
34
35impl ComponentId for NumericComponentId {
36 fn name(&self) -> &'static str {
37 "component"
38 }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum EventType {
44 Key,
46 Mouse,
48 Scroll,
50 Resize,
52 Tick,
54 Global,
56}
57
58#[derive(Debug, Clone)]
60pub enum EventKind {
61 Key(KeyEvent),
63 Mouse(MouseEvent),
65 Scroll {
67 column: u16,
68 row: u16,
69 delta: isize,
70 modifiers: KeyModifiers,
71 },
72 Resize(u16, u16),
74 Tick,
76}
77
78impl EventKind {
79 pub fn event_type(&self) -> EventType {
81 match self {
82 EventKind::Key(_) => EventType::Key,
83 EventKind::Mouse(_) => EventType::Mouse,
84 EventKind::Scroll { .. } => EventType::Scroll,
85 EventKind::Resize(_, _) => EventType::Resize,
86 EventKind::Tick => EventType::Tick,
87 }
88 }
89
90 pub fn is_global(&self) -> bool {
92 match self {
93 EventKind::Key(key) => {
94 use crossterm::event::KeyCode;
95 matches!(key.code, KeyCode::Esc)
96 || (key.modifiers.contains(KeyModifiers::CONTROL)
97 && matches!(key.code, KeyCode::Char('c') | KeyCode::Char('q')))
98 }
99 EventKind::Resize(_, _) => true,
100 _ => false,
101 }
102 }
103
104 pub fn is_broadcast(&self) -> bool {
106 matches!(self, EventKind::Resize(..) | EventKind::Tick)
107 }
108}
109
110#[derive(Debug, Clone)]
114pub struct EventContext<C: ComponentId> {
115 pub mouse_position: Option<(u16, u16)>,
117 pub modifiers: KeyModifiers,
119 pub component_areas: HashMap<C, Rect>,
121}
122
123impl<C: ComponentId> Default for EventContext<C> {
124 fn default() -> Self {
125 Self {
126 mouse_position: None,
127 modifiers: KeyModifiers::NONE,
128 component_areas: HashMap::new(),
129 }
130 }
131}
132
133impl<C: ComponentId> EventContext<C> {
134 pub fn new() -> Self {
136 Self::default()
137 }
138
139 pub fn point_in_component(&self, component: C, x: u16, y: u16) -> bool {
141 self.component_areas
142 .get(&component)
143 .map(|area| {
144 x >= area.x
145 && x < area.x.saturating_add(area.width)
146 && y >= area.y
147 && y < area.y.saturating_add(area.height)
148 })
149 .unwrap_or(false)
150 }
151
152 pub fn component_at(&self, x: u16, y: u16) -> Option<C> {
158 self.component_areas
159 .iter()
160 .find(|(_, area)| {
161 x >= area.x
162 && x < area.x.saturating_add(area.width)
163 && y >= area.y
164 && y < area.y.saturating_add(area.height)
165 })
166 .map(|(id, _)| *id)
167 }
168
169 pub fn set_component_area(&mut self, component: C, area: Rect) {
171 self.component_areas.insert(component, area);
172 }
173}
174
175#[derive(Debug, Clone)]
179pub struct Event<C: ComponentId> {
180 pub kind: EventKind,
182 pub context: EventContext<C>,
184}
185
186impl<C: ComponentId> Event<C> {
187 pub fn new(kind: EventKind, context: EventContext<C>) -> Self {
189 Self { kind, context }
190 }
191
192 pub fn event_type(&self) -> EventType {
194 self.kind.event_type()
195 }
196
197 pub fn is_global(&self) -> bool {
199 self.kind.is_global()
200 }
201}