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 { column: u16, row: u16, delta: isize },
67 Resize(u16, u16),
69 Tick,
71}
72
73impl EventKind {
74 pub fn event_type(&self) -> EventType {
76 match self {
77 EventKind::Key(_) => EventType::Key,
78 EventKind::Mouse(_) => EventType::Mouse,
79 EventKind::Scroll { .. } => EventType::Scroll,
80 EventKind::Resize(_, _) => EventType::Resize,
81 EventKind::Tick => EventType::Tick,
82 }
83 }
84
85 pub fn is_global(&self) -> bool {
87 match self {
88 EventKind::Key(key) => {
89 use crossterm::event::KeyCode;
90 matches!(key.code, KeyCode::Esc)
91 || (key.modifiers.contains(KeyModifiers::CONTROL)
92 && matches!(key.code, KeyCode::Char('c') | KeyCode::Char('q')))
93 }
94 EventKind::Resize(_, _) => true,
95 _ => false,
96 }
97 }
98}
99
100#[derive(Debug, Clone)]
104pub struct EventContext<C: ComponentId> {
105 pub focused_component: Option<C>,
107 pub mouse_position: Option<(u16, u16)>,
109 pub modifiers: KeyModifiers,
111 pub component_areas: HashMap<C, Rect>,
113 pub is_modal_open: bool,
115 pub active_modal: Option<C>,
117}
118
119impl<C: ComponentId> Default for EventContext<C> {
120 fn default() -> Self {
121 Self {
122 focused_component: None,
123 mouse_position: None,
124 modifiers: KeyModifiers::NONE,
125 component_areas: HashMap::new(),
126 is_modal_open: false,
127 active_modal: None,
128 }
129 }
130}
131
132impl<C: ComponentId> EventContext<C> {
133 pub fn new() -> Self {
135 Self::default()
136 }
137
138 pub fn is_focused(&self, component: C) -> bool {
140 self.focused_component == Some(component)
141 }
142
143 pub fn point_in_component(&self, component: C, x: u16, y: u16) -> bool {
145 self.component_areas
146 .get(&component)
147 .map(|area| {
148 x >= area.x
149 && x < area.x.saturating_add(area.width)
150 && y >= area.y
151 && y < area.y.saturating_add(area.height)
152 })
153 .unwrap_or(false)
154 }
155
156 pub fn component_at(&self, x: u16, y: u16) -> Option<C> {
158 if let Some(modal) = self.active_modal {
159 if self.point_in_component(modal, x, y) {
160 return Some(modal);
161 }
162 }
163
164 for (&id, area) in &self.component_areas {
165 if Some(id) != self.active_modal
166 && x >= area.x
167 && x < area.x.saturating_add(area.width)
168 && y >= area.y
169 && y < area.y.saturating_add(area.height)
170 {
171 return Some(id);
172 }
173 }
174 None
175 }
176
177 pub fn set_component_area(&mut self, component: C, area: Rect) {
179 self.component_areas.insert(component, area);
180 }
181
182 pub fn set_focus(&mut self, component: Option<C>) {
184 self.focused_component = component;
185 }
186
187 pub fn set_modal(&mut self, modal: Option<C>) {
189 self.active_modal = modal;
190 self.is_modal_open = modal.is_some();
191 if let Some(m) = modal {
192 self.focused_component = Some(m);
193 }
194 }
195}
196
197#[derive(Debug, Clone)]
201pub struct Event<C: ComponentId> {
202 pub kind: EventKind,
204 pub context: EventContext<C>,
206}
207
208impl<C: ComponentId> Event<C> {
209 pub fn new(kind: EventKind, context: EventContext<C>) -> Self {
211 Self { kind, context }
212 }
213
214 pub fn event_type(&self) -> EventType {
216 self.kind.event_type()
217 }
218
219 pub fn is_global(&self) -> bool {
221 self.kind.is_global()
222 }
223}