code_mesh_tui/
events.rs

1use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent};
2use futures_util::stream::Stream;
3use std::pin::Pin;
4use std::task::{Context, Poll};
5use tokio::sync::mpsc;
6
7/// Application events
8#[derive(Debug, Clone)]
9pub enum AppEvent {
10    /// Terminal input event
11    Input(InputEvent),
12    /// Application tick for animations/updates
13    Tick,
14    /// Application should quit
15    Quit,
16    /// Resize terminal
17    Resize(u16, u16),
18    /// Custom application event
19    Custom(String),
20}
21
22/// Input events from the terminal
23#[derive(Debug, Clone)]
24pub enum InputEvent {
25    /// Key press event
26    Key(KeyEvent),
27    /// Mouse event
28    Mouse(MouseEvent),
29    /// Focus gained
30    FocusGained,
31    /// Focus lost
32    FocusLost,
33    /// Paste event
34    Paste(String),
35}
36
37/// Event handler for managing terminal and application events
38pub struct EventHandler {
39    event_rx: mpsc::UnboundedReceiver<AppEvent>,
40    _event_tx: mpsc::UnboundedSender<AppEvent>,
41}
42
43impl EventHandler {
44    /// Create a new event handler
45    pub fn new() -> Self {
46        let (event_tx, event_rx) = mpsc::unbounded_channel();
47        
48        // Spawn terminal event listener
49        let tx = event_tx.clone();
50        tokio::spawn(async move {
51            let mut event_stream = crossterm::event::EventStream::new();
52            
53            loop {
54                use futures_util::StreamExt;
55                
56                if let Some(Ok(event)) = event_stream.next().await {
57                    let app_event = match event {
58                        CrosstermEvent::Key(key) => {
59                            // Handle quit shortcut early
60                            if key.code == crossterm::event::KeyCode::Char('c')
61                                && key.modifiers.contains(crossterm::event::KeyModifiers::CONTROL)
62                            {
63                                AppEvent::Quit
64                            } else {
65                                AppEvent::Input(InputEvent::Key(key))
66                            }
67                        }
68                        CrosstermEvent::Mouse(mouse) => AppEvent::Input(InputEvent::Mouse(mouse)),
69                        CrosstermEvent::Resize(width, height) => AppEvent::Resize(width, height),
70                        CrosstermEvent::FocusGained => AppEvent::Input(InputEvent::FocusGained),
71                        CrosstermEvent::FocusLost => AppEvent::Input(InputEvent::FocusLost),
72                        CrosstermEvent::Paste(data) => AppEvent::Input(InputEvent::Paste(data)),
73                    };
74                    
75                    if tx.send(app_event).is_err() {
76                        break;
77                    }
78                }
79            }
80        });
81        
82        Self {
83            event_rx,
84            _event_tx: event_tx,
85        }
86    }
87    
88    /// Receive the next event
89    pub async fn next(&mut self) -> Option<AppEvent> {
90        self.event_rx.recv().await
91    }
92    
93    /// Try to receive an event without blocking
94    pub fn try_next(&mut self) -> Option<AppEvent> {
95        self.event_rx.try_recv().ok()
96    }
97}
98
99impl Stream for EventHandler {
100    type Item = AppEvent;
101    
102    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
103        self.event_rx.poll_recv(cx)
104    }
105}
106
107/// Key binding handler for mapping keys to actions
108#[derive(Debug, Clone)]
109pub struct KeybindHandler {
110    bindings: std::collections::HashMap<String, String>,
111    leader_key: Option<String>,
112    leader_sequence: bool,
113}
114
115impl KeybindHandler {
116    /// Create a new keybind handler
117    pub fn new() -> Self {
118        Self {
119            bindings: std::collections::HashMap::new(),
120            leader_key: None,
121            leader_sequence: false,
122        }
123    }
124    
125    /// Set the leader key
126    pub fn set_leader_key(&mut self, key: String) {
127        self.leader_key = Some(key);
128    }
129    
130    /// Add a key binding
131    pub fn bind(&mut self, key: String, action: String) {
132        self.bindings.insert(key, action);
133    }
134    
135    /// Handle a key event and return the bound action if any
136    pub fn handle_key(&mut self, key: &KeyEvent) -> Option<String> {
137        let key_string = self.key_to_string(key);
138        
139        // Check if this is the leader key
140        if let Some(leader) = &self.leader_key {
141            if key_string == *leader && !self.leader_sequence {
142                self.leader_sequence = true;
143                return None;
144            }
145        }
146        
147        // If we're in a leader sequence, check for leader bindings
148        if self.leader_sequence {
149            self.leader_sequence = false;
150            let leader_binding = format!("leader+{}", key_string);
151            return self.bindings.get(&leader_binding).cloned();
152        }
153        
154        // Check for direct bindings
155        self.bindings.get(&key_string).cloned()
156    }
157    
158    /// Convert a key event to a string representation
159    fn key_to_string(&self, key: &KeyEvent) -> String {
160        use crossterm::event::{KeyCode, KeyModifiers};
161        
162        let mut parts = Vec::new();
163        
164        if key.modifiers.contains(KeyModifiers::CONTROL) {
165            parts.push("ctrl");
166        }
167        if key.modifiers.contains(KeyModifiers::ALT) {
168            parts.push("alt");
169        }
170        if key.modifiers.contains(KeyModifiers::SHIFT) {
171            parts.push("shift");
172        }
173        
174        let key_part = match key.code {
175            KeyCode::Char(c) => c.to_string(),
176            KeyCode::Enter => "enter".to_string(),
177            KeyCode::Tab => "tab".to_string(),
178            KeyCode::Backspace => "backspace".to_string(),
179            KeyCode::Delete => "delete".to_string(),
180            KeyCode::Insert => "insert".to_string(),
181            KeyCode::Home => "home".to_string(),
182            KeyCode::End => "end".to_string(),
183            KeyCode::PageUp => "pageup".to_string(),
184            KeyCode::PageDown => "pagedown".to_string(),
185            KeyCode::Up => "up".to_string(),
186            KeyCode::Down => "down".to_string(),
187            KeyCode::Left => "left".to_string(),
188            KeyCode::Right => "right".to_string(),
189            KeyCode::Esc => "esc".to_string(),
190            KeyCode::F(n) => format!("f{}", n),
191            _ => "unknown".to_string(),
192        };
193        
194        parts.push(&key_part);
195        parts.join("+")
196    }
197}
198
199impl Default for KeybindHandler {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205/// Mouse handler for processing mouse events
206#[derive(Debug, Clone)]
207pub struct MouseHandler {
208    last_position: (u16, u16),
209    drag_state: Option<DragState>,
210}
211
212#[derive(Debug, Clone)]
213struct DragState {
214    start_position: (u16, u16),
215    current_position: (u16, u16),
216}
217
218impl MouseHandler {
219    /// Create a new mouse handler
220    pub fn new() -> Self {
221        Self {
222            last_position: (0, 0),
223            drag_state: None,
224        }
225    }
226    
227    /// Handle a mouse event
228    pub fn handle_mouse(&mut self, event: &MouseEvent) -> MouseAction {
229        use crossterm::event::{MouseButton, MouseEventKind};
230        
231        match event.kind {
232            MouseEventKind::Down(button) => {
233                self.last_position = (event.column, event.row);
234                match button {
235                    MouseButton::Left => {
236                        self.drag_state = Some(DragState {
237                            start_position: (event.column, event.row),
238                            current_position: (event.column, event.row),
239                        });
240                        MouseAction::LeftClick(event.column, event.row)
241                    }
242                    MouseButton::Right => MouseAction::RightClick(event.column, event.row),
243                    MouseButton::Middle => MouseAction::MiddleClick(event.column, event.row),
244                }
245            }
246            MouseEventKind::Up(MouseButton::Left) => {
247                if let Some(drag) = self.drag_state.take() {
248                    if drag.start_position != drag.current_position {
249                        MouseAction::DragEnd(drag.start_position, drag.current_position)
250                    } else {
251                        MouseAction::LeftClick(event.column, event.row)
252                    }
253                } else {
254                    MouseAction::LeftClick(event.column, event.row)
255                }
256            }
257            MouseEventKind::Up(_) => MouseAction::None,
258            MouseEventKind::Drag(MouseButton::Left) => {
259                if let Some(ref mut drag) = self.drag_state {
260                    drag.current_position = (event.column, event.row);
261                    MouseAction::Drag(drag.start_position, drag.current_position)
262                } else {
263                    MouseAction::None
264                }
265            }
266            MouseEventKind::Moved => {
267                self.last_position = (event.column, event.row);
268                MouseAction::Move(event.column, event.row)
269            }
270            MouseEventKind::ScrollDown => MouseAction::ScrollDown(event.column, event.row),
271            MouseEventKind::ScrollUp => MouseAction::ScrollUp(event.column, event.row),
272            MouseEventKind::ScrollLeft => MouseAction::ScrollLeft(event.column, event.row),
273            MouseEventKind::ScrollRight => MouseAction::ScrollRight(event.column, event.row),
274            _ => MouseAction::None,
275        }
276    }
277}
278
279impl Default for MouseHandler {
280    fn default() -> Self {
281        Self::new()
282    }
283}
284
285/// Mouse actions that can be performed
286#[derive(Debug, Clone)]
287pub enum MouseAction {
288    None,
289    LeftClick(u16, u16),
290    RightClick(u16, u16),
291    MiddleClick(u16, u16),
292    Move(u16, u16),
293    Drag((u16, u16), (u16, u16)), // start, current
294    DragEnd((u16, u16), (u16, u16)), // start, end
295    ScrollUp(u16, u16),
296    ScrollDown(u16, u16),
297    ScrollLeft(u16, u16),
298    ScrollRight(u16, u16),
299}
300
301/// Event dispatcher for routing events to components
302pub struct EventDispatcher {
303    handlers: Vec<Box<dyn EventConsumer>>,
304}
305
306impl EventDispatcher {
307    /// Create a new event dispatcher
308    pub fn new() -> Self {
309        Self {
310            handlers: Vec::new(),
311        }
312    }
313    
314    /// Add an event handler
315    pub fn add_handler(&mut self, handler: Box<dyn EventConsumer>) {
316        self.handlers.push(handler);
317    }
318    
319    /// Dispatch an event to all handlers
320    pub fn dispatch(&mut self, event: &AppEvent) -> bool {
321        for handler in &mut self.handlers {
322            if handler.handle_event(event) {
323                return true; // Event was consumed
324            }
325        }
326        false
327    }
328}
329
330impl Default for EventDispatcher {
331    fn default() -> Self {
332        Self::new()
333    }
334}
335
336/// Trait for components that can consume events
337pub trait EventConsumer {
338    /// Handle an event, returning true if the event was consumed
339    fn handle_event(&mut self, event: &AppEvent) -> bool;
340}
341
342#[cfg(test)]
343mod tests {
344    use super::*;
345    use crossterm::event::{KeyCode, KeyModifiers};
346
347    #[test]
348    fn test_key_to_string() {
349        let handler = KeybindHandler::new();
350        
351        let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
352        assert_eq!(handler.key_to_string(&key), "a");
353        
354        let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::CONTROL);
355        assert_eq!(handler.key_to_string(&key), "ctrl+a");
356        
357        let key = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
358        assert_eq!(handler.key_to_string(&key), "enter");
359    }
360    
361    #[test]
362    fn test_keybind_handler() {
363        let mut handler = KeybindHandler::new();
364        handler.bind("q".to_string(), "quit".to_string());
365        handler.bind("ctrl+c".to_string(), "interrupt".to_string());
366        
367        let key = KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE);
368        assert_eq!(handler.handle_key(&key), Some("quit".to_string()));
369        
370        let key = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
371        assert_eq!(handler.handle_key(&key), Some("interrupt".to_string()));
372    }
373    
374    #[test]
375    fn test_leader_sequence() {
376        let mut handler = KeybindHandler::new();
377        handler.set_leader_key("ctrl+x".to_string());
378        handler.bind("leader+s".to_string(), "save".to_string());
379        
380        // First press the leader key
381        let leader_key = KeyEvent::new(KeyCode::Char('x'), KeyModifiers::CONTROL);
382        assert_eq!(handler.handle_key(&leader_key), None);
383        
384        // Then press the bound key
385        let bound_key = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::NONE);
386        assert_eq!(handler.handle_key(&bound_key), Some("save".to_string()));
387    }
388}