Skip to main content

tui_dispatch_core/
bus.rs

1//! Event bus for dispatching events to registered handlers
2
3use crate::event::{ComponentId, EventContext, EventKind, EventType};
4use crate::keybindings::Keybindings;
5use crate::runtime::EventOutcome;
6use crate::{Action, BindingContext};
7use crossterm::event::{self, KeyEventKind, MouseEventKind};
8use std::collections::{HashMap, HashSet};
9use std::sync::Arc;
10use std::time::Duration;
11use tokio::sync::mpsc;
12use tokio_util::sync::CancellationToken;
13use tracing::{debug, info};
14
15/// Raw event from crossterm before processing
16#[derive(Debug)]
17pub enum RawEvent {
18    Key(crossterm::event::KeyEvent),
19    Mouse(crossterm::event::MouseEvent),
20    Resize(u16, u16),
21}
22
23/// State accessors used by the event bus for routing decisions.
24pub trait EventRoutingState<Id: ComponentId, Ctx: BindingContext> {
25    fn focused(&self) -> Option<Id>;
26    fn modal(&self) -> Option<Id>;
27    fn binding_context(&self, id: Id) -> Ctx;
28    fn default_context(&self) -> Ctx;
29}
30
31/// Where a handler is being routed in the current event flow.
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum RouteTarget<Id: ComponentId> {
34    Modal(Id),
35    Focused(Id),
36    Hovered(Id),
37    Subscriber(Id),
38    Global,
39}
40
41/// Routing plan that determines the order components receive events.
42///
43/// Priority: Modal → Hovered → Focused → Subscribers → Global subscribers
44struct RoutingPlan<Id: ComponentId> {
45    targets: Vec<(RouteTarget<Id>, Id)>,
46    modal_blocks: bool,
47}
48
49impl<Id: ComponentId> RoutingPlan<Id> {
50    /// Build a routing plan for an event.
51    fn build<S, Ctx>(
52        event: &EventKind,
53        state: &S,
54        subscribers: &[Id],
55        global_subscribers: &[Id],
56        hovered: Option<Id>,
57    ) -> Self
58    where
59        S: EventRoutingState<Id, Ctx>,
60        Ctx: BindingContext,
61    {
62        let is_broadcast = event.is_broadcast();
63        let modal = state.modal();
64        let focused = state.focused();
65
66        let mut targets = Vec::with_capacity(8);
67
68        // Modal gets first priority
69        if let Some(id) = modal {
70            targets.push((RouteTarget::Modal(id), id));
71        }
72
73        // Modal blocks non-broadcast events from reaching other components
74        let modal_blocks = modal.is_some() && !is_broadcast;
75
76        if !modal_blocks {
77            // Hovered component (mouse events only)
78            if let Some(id) = hovered {
79                targets.push((RouteTarget::Hovered(id), id));
80            }
81
82            // Focused component
83            if let Some(id) = focused {
84                targets.push((RouteTarget::Focused(id), id));
85            }
86
87            // Type-specific subscribers
88            for &id in subscribers {
89                targets.push((RouteTarget::Subscriber(id), id));
90            }
91        }
92
93        // Global subscribers (for global events, even when modal is active)
94        if event.is_global() {
95            for &id in global_subscribers {
96                targets.push((RouteTarget::Subscriber(id), id));
97            }
98        }
99
100        Self {
101            targets,
102            modal_blocks,
103        }
104    }
105
106    fn iter(&self) -> impl Iterator<Item = (RouteTarget<Id>, Id)> + '_ {
107        self.targets.iter().copied()
108    }
109}
110
111/// Routed event passed to handlers.
112#[derive(Debug, Clone)]
113pub struct RoutedEvent<'a, Id: ComponentId, Ctx: BindingContext> {
114    pub kind: EventKind,
115    pub command: Option<&'a str>,
116    pub binding_ctx: Ctx,
117    pub target: RouteTarget<Id>,
118    pub context: &'a EventContext<Id>,
119}
120
121/// Response returned by an event handler.
122#[derive(Debug, Clone)]
123pub struct HandlerResponse<A> {
124    pub actions: Vec<A>,
125    pub consumed: bool,
126    pub needs_render: bool,
127}
128
129impl<A> HandlerResponse<A> {
130    pub fn ignored() -> Self {
131        Self {
132            actions: Vec::new(),
133            consumed: false,
134            needs_render: false,
135        }
136    }
137
138    pub fn action(action: A) -> Self {
139        Self {
140            actions: vec![action],
141            consumed: true,
142            needs_render: false,
143        }
144    }
145
146    /// Multiple actions, event consumed.
147    pub fn actions<I>(actions: I) -> Self
148    where
149        I: IntoIterator<Item = A>,
150    {
151        Self {
152            actions: actions.into_iter().collect(),
153            consumed: true,
154            needs_render: false,
155        }
156    }
157
158    /// Multiple actions, event NOT consumed (passthrough to other handlers).
159    pub fn actions_passthrough<I>(actions: I) -> Self
160    where
161        I: IntoIterator<Item = A>,
162    {
163        Self {
164            actions: actions.into_iter().collect(),
165            consumed: false,
166            needs_render: false,
167        }
168    }
169
170    pub fn with_render(mut self) -> Self {
171        self.needs_render = true;
172        self
173    }
174
175    pub fn with_consumed(mut self, consumed: bool) -> Self {
176        self.consumed = consumed;
177        self
178    }
179}
180
181/// Trait for event handlers registered with the bus.
182pub trait EventHandler<S, A, Id: ComponentId, Ctx: BindingContext>: 'static {
183    fn handle(&mut self, event: RoutedEvent<'_, Id, Ctx>, state: &S) -> HandlerResponse<A>;
184}
185
186impl<S, A, Id, Ctx, F> EventHandler<S, A, Id, Ctx> for F
187where
188    Id: ComponentId,
189    Ctx: BindingContext,
190    F: for<'a> FnMut(RoutedEvent<'a, Id, Ctx>, &S) -> HandlerResponse<A> + 'static,
191{
192    fn handle(&mut self, event: RoutedEvent<'_, Id, Ctx>, state: &S) -> HandlerResponse<A> {
193        (self)(event, state)
194    }
195}
196
197type HandlerFn<S, A, Id, Ctx> =
198    dyn for<'a, 'ctx> FnMut(RoutedEvent<'ctx, Id, Ctx>, &'a S) -> HandlerResponse<A>;
199
200/// Event bus that manages subscriptions and dispatches events.
201pub struct EventBus<S, A: Action, Id: ComponentId, Ctx: BindingContext> {
202    handlers: HashMap<Id, Box<HandlerFn<S, A, Id, Ctx>>>,
203    global_handlers: Vec<Box<HandlerFn<S, A, Id, Ctx>>>,
204    subscriptions: HashMap<EventType, Vec<Id>>,
205    global_subscribers: Vec<Id>,
206    registration_order: Vec<Id>,
207    context: EventContext<Id>,
208}
209
210/// Default binding context for apps that don't need custom contexts.
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
212pub struct DefaultBindingContext;
213
214impl BindingContext for DefaultBindingContext {
215    fn name(&self) -> &'static str {
216        "default"
217    }
218
219    fn from_name(name: &str) -> Option<Self> {
220        (name == "default").then_some(Self)
221    }
222
223    fn all() -> &'static [Self] {
224        &[Self]
225    }
226}
227
228/// Simplified EventBus for apps that don't need custom binding contexts.
229///
230/// Use this when you don't need context-specific keybindings:
231/// ```ignore
232/// let bus: SimpleEventBus<AppState, AppAction, ComponentId> = SimpleEventBus::new();
233/// ```
234pub type SimpleEventBus<S, A, Id> = EventBus<S, A, Id, DefaultBindingContext>;
235
236impl<S: 'static, A, Id, Ctx> Default for EventBus<S, A, Id, Ctx>
237where
238    A: Action,
239    Id: ComponentId + 'static,
240    Ctx: BindingContext + 'static,
241{
242    fn default() -> Self {
243        Self::new()
244    }
245}
246
247impl<S: 'static, A, Id, Ctx> EventBus<S, A, Id, Ctx>
248where
249    A: Action,
250    Id: ComponentId + 'static,
251    Ctx: BindingContext + 'static,
252{
253    /// Create a new event bus.
254    pub fn new() -> Self {
255        Self {
256            handlers: HashMap::new(),
257            global_handlers: Vec::new(),
258            subscriptions: HashMap::new(),
259            global_subscribers: Vec::new(),
260            registration_order: Vec::new(),
261            context: EventContext::default(),
262        }
263    }
264
265    /// Register a handler for a component ID.
266    pub fn register<F>(&mut self, component: Id, handler: F)
267    where
268        F: for<'a, 'ctx> FnMut(RoutedEvent<'ctx, Id, Ctx>, &'a S) -> HandlerResponse<A> + 'static,
269    {
270        if !self.handlers.contains_key(&component) {
271            self.registration_order.push(component);
272        }
273        self.handlers.insert(component, Box::new(handler));
274    }
275
276    /// Register a component handler that implements [`EventHandler`].
277    pub fn register_handler<H>(&mut self, component: Id, mut handler: H)
278    where
279        H: EventHandler<S, A, Id, Ctx> + 'static,
280    {
281        self.register(component, move |event, state| handler.handle(event, state));
282    }
283
284    /// Register a global handler (not tied to a component).
285    pub fn register_global<F>(&mut self, handler: F)
286    where
287        F: for<'a, 'ctx> FnMut(RoutedEvent<'ctx, Id, Ctx>, &'a S) -> HandlerResponse<A> + 'static,
288    {
289        self.global_handlers.push(Box::new(handler));
290    }
291
292    /// Register a global handler that implements [`EventHandler`].
293    pub fn register_global_handler<H>(&mut self, mut handler: H)
294    where
295        H: EventHandler<S, A, Id, Ctx> + 'static,
296    {
297        self.register_global(move |event, state| handler.handle(event, state));
298    }
299
300    /// Unregister a component handler.
301    pub fn unregister(&mut self, component: Id) -> Option<Box<HandlerFn<S, A, Id, Ctx>>> {
302        self.registration_order.retain(|id| *id != component);
303        self.unsubscribe_all(component);
304        self.handlers.remove(&component)
305    }
306
307    /// Subscribe a component to an event type.
308    pub fn subscribe(&mut self, component: Id, event_type: EventType) {
309        if event_type == EventType::Global {
310            if !self.global_subscribers.contains(&component) {
311                self.global_subscribers.push(component);
312            }
313            return;
314        }
315
316        let entry = self.subscriptions.entry(event_type).or_default();
317        if !entry.contains(&component) {
318            entry.push(component);
319        }
320    }
321
322    /// Subscribe a component to multiple event types.
323    pub fn subscribe_many(&mut self, component: Id, event_types: &[EventType]) {
324        for &event_type in event_types {
325            self.subscribe(component, event_type);
326        }
327    }
328
329    /// Unsubscribe a component from an event type.
330    pub fn unsubscribe(&mut self, component: Id, event_type: EventType) {
331        if event_type == EventType::Global {
332            self.global_subscribers.retain(|id| *id != component);
333            return;
334        }
335
336        if let Some(subscribers) = self.subscriptions.get_mut(&event_type) {
337            subscribers.retain(|id| *id != component);
338        }
339    }
340
341    /// Unsubscribe a component from all event types.
342    pub fn unsubscribe_all(&mut self, component: Id) {
343        self.global_subscribers.retain(|id| *id != component);
344        for subscribers in self.subscriptions.values_mut() {
345            subscribers.retain(|id| *id != component);
346        }
347    }
348
349    /// Get subscribers for an event type.
350    pub fn get_subscribers(&self, event_type: EventType) -> Vec<Id> {
351        if event_type == EventType::Global {
352            return self.global_subscribers.clone();
353        }
354
355        self.subscriptions
356            .get(&event_type)
357            .cloned()
358            .unwrap_or_default()
359    }
360
361    /// Get mutable reference to context.
362    pub fn context_mut(&mut self) -> &mut EventContext<Id> {
363        &mut self.context
364    }
365
366    /// Get reference to context.
367    pub fn context(&self) -> &EventContext<Id> {
368        &self.context
369    }
370
371    /// Route an event through the bus and collect actions.
372    ///
373    /// Routing priority: Modal → Hovered → Focused → Subscribers → Global handlers.
374    /// Modal blocks non-broadcast events from reaching other components.
375    pub fn handle_event(
376        &mut self,
377        event: &EventKind,
378        state: &S,
379        keybindings: &Keybindings<Ctx>,
380    ) -> EventOutcome<A>
381    where
382        S: EventRoutingState<Id, Ctx>,
383    {
384        self.update_context(event);
385
386        let is_broadcast = event.is_broadcast();
387        let is_mouse_event = matches!(event, EventKind::Mouse(_) | EventKind::Scroll { .. });
388
389        // Gather routing inputs
390        let subscribers = self
391            .subscriptions
392            .get(&event.event_type())
393            .cloned()
394            .unwrap_or_default();
395        let global_subscribers = &self.global_subscribers;
396        let hovered = if is_mouse_event {
397            self.mouse_position_from_event(event)
398                .and_then(|(col, row)| self.hit_test(col, row))
399        } else {
400            None
401        };
402
403        // Build routing plan
404        let plan = RoutingPlan::build(event, state, &subscribers, global_subscribers, hovered);
405
406        // Prepare binding context for global handlers
407        let default_binding_ctx = state.default_context();
408        let global_binding_ctx = state
409            .modal()
410            .or_else(|| state.focused())
411            .map(|id| state.binding_context(id))
412            .unwrap_or(default_binding_ctx);
413        let global_command = Self::command_for_context(event, keybindings, global_binding_ctx);
414
415        // Dispatch state
416        let mut actions = Vec::new();
417        let mut needs_render = false;
418        let mut consumed = false;
419        let mut called = HashSet::new();
420
421        // Route to components
422        for (target, id) in plan.iter() {
423            if !called.insert(id) {
424                continue;
425            }
426
427            if let Some(handler) = self.handlers.get_mut(&id) {
428                let binding_ctx = state.binding_context(id);
429                let command = Self::command_for_context(event, keybindings, binding_ctx);
430                let response = Self::call_handler(
431                    handler.as_mut(),
432                    target,
433                    event,
434                    command.as_deref(),
435                    binding_ctx,
436                    &self.context,
437                    state,
438                );
439
440                actions.extend(response.actions);
441                needs_render |= response.needs_render;
442                consumed |= response.consumed;
443
444                if consumed && !is_broadcast {
445                    return EventOutcome {
446                        actions,
447                        needs_render,
448                    };
449                }
450            }
451        }
452
453        // Global handlers (always run last, skip if modal blocks and not global event)
454        let should_run_global = !plan.modal_blocks || event.is_global();
455        if should_run_global {
456            for handler in self.global_handlers.iter_mut() {
457                let response = Self::call_handler(
458                    handler.as_mut(),
459                    RouteTarget::Global,
460                    event,
461                    global_command.as_deref(),
462                    global_binding_ctx,
463                    &self.context,
464                    state,
465                );
466
467                actions.extend(response.actions);
468                needs_render |= response.needs_render;
469                consumed |= response.consumed;
470
471                if consumed && !is_broadcast {
472                    break;
473                }
474            }
475        }
476
477        EventOutcome {
478            actions,
479            needs_render,
480        }
481    }
482
483    fn command_for_context(
484        event: &EventKind,
485        keybindings: &Keybindings<Ctx>,
486        binding_ctx: Ctx,
487    ) -> Option<Arc<str>> {
488        match event {
489            EventKind::Key(key)
490                if matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) =>
491            {
492                keybindings.get_command(*key, binding_ctx).map(Arc::from)
493            }
494            _ => None,
495        }
496    }
497
498    #[allow(clippy::too_many_arguments)]
499    fn call_handler(
500        handler: &mut HandlerFn<S, A, Id, Ctx>,
501        target: RouteTarget<Id>,
502        event: &EventKind,
503        command: Option<&str>,
504        binding_ctx: Ctx,
505        context: &EventContext<Id>,
506        state: &S,
507    ) -> HandlerResponse<A> {
508        let routed = RoutedEvent {
509            kind: event.clone(),
510            command,
511            binding_ctx,
512            target,
513            context,
514        };
515        handler(routed, state)
516    }
517
518    fn update_context(&mut self, event: &EventKind) {
519        match event {
520            EventKind::Key(key) => {
521                self.context.modifiers = key.modifiers;
522            }
523            EventKind::Mouse(mouse) => {
524                self.context.mouse_position = Some((mouse.column, mouse.row));
525                self.context.modifiers = mouse.modifiers;
526            }
527            EventKind::Scroll {
528                column,
529                row,
530                modifiers,
531                ..
532            } => {
533                self.context.mouse_position = Some((*column, *row));
534                self.context.modifiers = *modifiers;
535            }
536            EventKind::Resize(width, height) => {
537                if let Some((column, row)) = self.context.mouse_position {
538                    if column >= *width || row >= *height {
539                        self.context.mouse_position = None;
540                    }
541                }
542            }
543            EventKind::Tick => {}
544        }
545    }
546
547    fn mouse_position_from_event(&self, event: &EventKind) -> Option<(u16, u16)> {
548        match event {
549            EventKind::Mouse(mouse) => Some((mouse.column, mouse.row)),
550            EventKind::Scroll { column, row, .. } => Some((*column, *row)),
551            _ => None,
552        }
553    }
554
555    fn hit_test(&self, column: u16, row: u16) -> Option<Id> {
556        self.registration_order
557            .iter()
558            .rev()
559            .copied()
560            .find(|&id| self.context.point_in_component(id, column, row))
561    }
562}
563
564/// Spawn the event polling task with cancellation support.
565///
566/// This spawns an async task that polls for crossterm events and sends them
567/// through the provided channel. The task can be cancelled using the token.
568///
569/// # Arguments
570/// * `tx` - Channel to send raw events
571/// * `poll_timeout` - Timeout for each poll operation
572/// * `loop_sleep` - Sleep duration between poll cycles
573/// * `cancel_token` - Token to cancel the polling task
574pub fn spawn_event_poller(
575    tx: mpsc::UnboundedSender<RawEvent>,
576    poll_timeout: Duration,
577    loop_sleep: Duration,
578    cancel_token: CancellationToken,
579) -> tokio::task::JoinHandle<()> {
580    tokio::spawn(async move {
581        const MAX_EVENTS_PER_BATCH: usize = 20;
582
583        loop {
584            tokio::select! {
585                _ = cancel_token.cancelled() => {
586                    info!("Event poller cancelled, draining buffer");
587                    // Drain any remaining events from crossterm buffer before exiting
588                    while event::poll(Duration::ZERO).unwrap_or(false) {
589                        let _ = event::read();
590                    }
591                    break;
592                }
593                _ = tokio::time::sleep(loop_sleep) => {
594                    // Process up to MAX_EVENTS_PER_BATCH events per iteration
595                    let mut events_processed = 0;
596                    while events_processed < MAX_EVENTS_PER_BATCH
597                        && event::poll(poll_timeout).unwrap_or(false)
598                    {
599                        events_processed += 1;
600                        if let Ok(evt) = event::read() {
601                            let raw = match evt {
602                                event::Event::Key(key) => Some(RawEvent::Key(key)),
603                                event::Event::Mouse(mouse) => Some(RawEvent::Mouse(mouse)),
604                                event::Event::Resize(w, h) => Some(RawEvent::Resize(w, h)),
605                                _ => None,
606                            };
607                            if let Some(raw) = raw {
608                                if tx.send(raw).is_err() {
609                                    debug!("Event channel closed, stopping poller");
610                                    return;
611                                }
612                            }
613                        }
614                    }
615                }
616            }
617        }
618    })
619}
620
621/// Process a raw event into an EventKind.
622pub fn process_raw_event(raw: RawEvent) -> EventKind {
623    match raw {
624        RawEvent::Key(key) => EventKind::Key(key),
625        RawEvent::Mouse(mouse) => match mouse.kind {
626            MouseEventKind::ScrollDown => EventKind::Scroll {
627                column: mouse.column,
628                row: mouse.row,
629                delta: 1,
630                modifiers: mouse.modifiers,
631            },
632            MouseEventKind::ScrollUp => EventKind::Scroll {
633                column: mouse.column,
634                row: mouse.row,
635                delta: -1,
636                modifiers: mouse.modifiers,
637            },
638            _ => EventKind::Mouse(mouse),
639        },
640        RawEvent::Resize(w, h) => EventKind::Resize(w, h),
641    }
642}
643
644#[cfg(test)]
645mod tests {
646    use super::*;
647    use crate::event::NumericComponentId;
648    use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
649
650    #[derive(Clone, Debug, PartialEq, Eq)]
651    enum TestAction {
652        Log(&'static str),
653    }
654
655    impl Action for TestAction {
656        fn name(&self) -> &'static str {
657            "Log"
658        }
659    }
660
661    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
662    enum TestContext {
663        Default,
664    }
665
666    impl BindingContext for TestContext {
667        fn name(&self) -> &'static str {
668            "default"
669        }
670
671        fn from_name(name: &str) -> Option<Self> {
672            match name {
673                "default" => Some(Self::Default),
674                _ => None,
675            }
676        }
677
678        fn all() -> &'static [Self] {
679            &[Self::Default]
680        }
681    }
682
683    #[derive(Default)]
684    struct TestState {
685        focused: Option<NumericComponentId>,
686        modal: Option<NumericComponentId>,
687    }
688
689    impl EventRoutingState<NumericComponentId, TestContext> for TestState {
690        fn focused(&self) -> Option<NumericComponentId> {
691            self.focused
692        }
693
694        fn modal(&self) -> Option<NumericComponentId> {
695            self.modal
696        }
697
698        fn binding_context(&self, _id: NumericComponentId) -> TestContext {
699            TestContext::Default
700        }
701
702        fn default_context(&self) -> TestContext {
703            TestContext::Default
704        }
705    }
706
707    #[test]
708    fn test_subscribe_unsubscribe() {
709        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
710            EventBus::new();
711
712        let component = NumericComponentId(1);
713        bus.subscribe(component, EventType::Key);
714
715        assert_eq!(bus.get_subscribers(EventType::Key), vec![component]);
716
717        bus.unsubscribe(component, EventType::Key);
718        assert!(bus.get_subscribers(EventType::Key).is_empty());
719    }
720
721    #[test]
722    fn test_subscribe_many() {
723        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
724            EventBus::new();
725
726        let component = NumericComponentId(1);
727        bus.subscribe_many(component, &[EventType::Key, EventType::Mouse]);
728
729        assert_eq!(bus.get_subscribers(EventType::Key), vec![component]);
730        assert_eq!(bus.get_subscribers(EventType::Mouse), vec![component]);
731    }
732
733    #[test]
734    fn test_unsubscribe_all() {
735        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
736            EventBus::new();
737
738        let component = NumericComponentId(1);
739        bus.subscribe_many(
740            component,
741            &[EventType::Key, EventType::Mouse, EventType::Scroll],
742        );
743
744        bus.unsubscribe_all(component);
745
746        assert!(bus.get_subscribers(EventType::Key).is_empty());
747        assert!(bus.get_subscribers(EventType::Mouse).is_empty());
748        assert!(bus.get_subscribers(EventType::Scroll).is_empty());
749    }
750
751    #[test]
752    fn test_handle_event_routes_modal_only() {
753        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
754            EventBus::new();
755
756        let focused = NumericComponentId(1);
757        let modal = NumericComponentId(2);
758        let subscriber = NumericComponentId(3);
759
760        bus.register(focused, |_, _| HandlerResponse {
761            actions: vec![TestAction::Log("focused")],
762            consumed: false,
763            needs_render: false,
764        });
765        bus.register(modal, |_, _| HandlerResponse {
766            actions: vec![TestAction::Log("modal")],
767            consumed: false,
768            needs_render: false,
769        });
770        bus.register(subscriber, |_, _| HandlerResponse {
771            actions: vec![TestAction::Log("subscriber")],
772            consumed: false,
773            needs_render: false,
774        });
775        bus.register_global(|_, _| HandlerResponse {
776            actions: vec![TestAction::Log("global")],
777            consumed: false,
778            needs_render: false,
779        });
780        bus.subscribe(subscriber, EventType::Key);
781
782        let state = TestState {
783            focused: Some(focused),
784            modal: Some(modal),
785        };
786        let keybindings = Keybindings::new();
787
788        let key_event = KeyEvent {
789            code: KeyCode::Char('a'),
790            modifiers: KeyModifiers::NONE,
791            kind: KeyEventKind::Press,
792            state: KeyEventState::empty(),
793        };
794
795        let outcome = bus.handle_event(&EventKind::Key(key_event), &state, &keybindings);
796        assert_eq!(outcome.actions, vec![TestAction::Log("modal")]);
797    }
798
799    #[test]
800    fn test_global_subscribers_only_for_global_events() {
801        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
802            EventBus::new();
803
804        let global_subscriber = NumericComponentId(1);
805        let key_subscriber = NumericComponentId(2);
806
807        bus.register(global_subscriber, |_, _| HandlerResponse {
808            actions: vec![TestAction::Log("global")],
809            consumed: false,
810            needs_render: false,
811        });
812        bus.register(key_subscriber, |_, _| HandlerResponse {
813            actions: vec![TestAction::Log("key")],
814            consumed: false,
815            needs_render: false,
816        });
817        bus.subscribe(global_subscriber, EventType::Global);
818        bus.subscribe(key_subscriber, EventType::Key);
819
820        let state = TestState::default();
821        let keybindings = Keybindings::new();
822
823        let key_event = KeyEvent {
824            code: KeyCode::Char('a'),
825            modifiers: KeyModifiers::NONE,
826            kind: KeyEventKind::Press,
827            state: KeyEventState::empty(),
828        };
829
830        let outcome = bus.handle_event(&EventKind::Key(key_event), &state, &keybindings);
831        assert_eq!(outcome.actions, vec![TestAction::Log("key")]);
832
833        let esc_event = KeyEvent {
834            code: KeyCode::Esc,
835            modifiers: KeyModifiers::NONE,
836            kind: KeyEventKind::Press,
837            state: KeyEventState::empty(),
838        };
839
840        let outcome = bus.handle_event(&EventKind::Key(esc_event), &state, &keybindings);
841        assert_eq!(
842            outcome.actions,
843            vec![TestAction::Log("key"), TestAction::Log("global")]
844        );
845    }
846
847    #[test]
848    fn test_handle_event_consumes() {
849        let mut bus: EventBus<TestState, TestAction, NumericComponentId, TestContext> =
850            EventBus::new();
851
852        let focused = NumericComponentId(1);
853        let modal = NumericComponentId(2);
854
855        bus.register(focused, |_, _| {
856            HandlerResponse::action(TestAction::Log("focused"))
857        });
858        bus.register(modal, |_, _| {
859            HandlerResponse::action(TestAction::Log("modal"))
860        });
861
862        let state = TestState {
863            focused: Some(focused),
864            modal: Some(modal),
865        };
866        let keybindings = Keybindings::new();
867
868        let key_event = KeyEvent {
869            code: KeyCode::Char('a'),
870            modifiers: KeyModifiers::NONE,
871            kind: KeyEventKind::Press,
872            state: KeyEventState::empty(),
873        };
874
875        let outcome = bus.handle_event(&EventKind::Key(key_event), &state, &keybindings);
876        assert_eq!(outcome.actions, vec![TestAction::Log("modal")]);
877    }
878
879    #[test]
880    fn test_process_raw_event_key() {
881        use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
882
883        let key_event = KeyEvent {
884            code: KeyCode::Char('a'),
885            modifiers: KeyModifiers::NONE,
886            kind: KeyEventKind::Press,
887            state: KeyEventState::empty(),
888        };
889
890        let kind = process_raw_event(RawEvent::Key(key_event));
891        assert!(matches!(kind, EventKind::Key(_)));
892    }
893
894    #[test]
895    fn test_process_raw_event_scroll() {
896        use crossterm::event::{MouseEvent, MouseEventKind};
897
898        let scroll_down = MouseEvent {
899            kind: MouseEventKind::ScrollDown,
900            column: 10,
901            row: 20,
902            modifiers: KeyModifiers::NONE,
903        };
904
905        let kind = process_raw_event(RawEvent::Mouse(scroll_down));
906        match kind {
907            EventKind::Scroll {
908                column,
909                row,
910                delta,
911                modifiers,
912            } => {
913                assert_eq!(column, 10);
914                assert_eq!(row, 20);
915                assert_eq!(delta, 1);
916                assert_eq!(modifiers, KeyModifiers::NONE);
917            }
918            _ => panic!("Expected Scroll event"),
919        }
920    }
921
922    #[test]
923    fn test_process_raw_event_resize() {
924        let kind = process_raw_event(RawEvent::Resize(80, 24));
925        assert!(matches!(kind, EventKind::Resize(80, 24)));
926    }
927}