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