1use 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#[derive(Debug)]
16pub enum RawEvent {
17 Key(crossterm::event::KeyEvent),
18 Mouse(crossterm::event::MouseEvent),
19 Resize(u16, u16),
20}
21
22pub 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#[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#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct EventOutcome<A> {
43 pub actions: Vec<A>,
45 pub needs_render: bool,
47}
48
49impl<A> EventOutcome<A> {
50 pub fn ignored() -> Self {
52 Self {
53 actions: Vec::new(),
54 needs_render: false,
55 }
56 }
57
58 pub fn needs_render() -> Self {
60 Self {
61 actions: Vec::new(),
62 needs_render: true,
63 }
64 }
65
66 pub fn action(action: A) -> Self {
68 Self {
69 actions: vec![action],
70 needs_render: false,
71 }
72 }
73
74 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 pub fn with_render(mut self) -> Self {
87 self.needs_render = true;
88 self
89 }
90
91 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
133struct RoutingPlan<Id: ComponentId> {
137 targets: Vec<(RouteTarget<Id>, Id)>,
138 modal_blocks: bool,
139}
140
141impl<Id: ComponentId> RoutingPlan<Id> {
142 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 if let Some(id) = modal {
173 targets.push((RouteTarget::Modal(id), id));
174 }
175
176 let modal_blocks = modal.is_some() && !is_broadcast;
178
179 if !modal_blocks {
180 if let Some(id) = hovered {
182 targets.push((RouteTarget::Hovered(id), id));
183 }
184
185 if let Some(id) = focused {
187 targets.push((RouteTarget::Focused(id), id));
188 }
189
190 for &id in subscribers {
192 targets.push((RouteTarget::Subscriber(id), id));
193 }
194 }
195
196 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#[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#[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 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 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
284pub 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
303pub 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#[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
332pub 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 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 pub fn with_global_key_policy(mut self, policy: GlobalKeyPolicy) -> Self {
383 self.global_key_policy = policy;
384 self
385 }
386
387 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 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 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 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 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 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 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 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 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 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 pub fn context_mut(&mut self) -> &mut EventContext<Id> {
485 &mut self.context
486 }
487
488 pub fn context(&self) -> &EventContext<Id> {
490 &self.context
491 }
492
493 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 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 let is_global = self.global_key_policy.is_global(event);
535
536 let plan = RoutingPlan::build(
538 event,
539 is_global,
540 state,
541 subscribers,
542 global_subscribers,
543 hovered,
544 );
545
546 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 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 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 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
702pub 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 while event::poll(Duration::ZERO).unwrap_or(false) {
728 let _ = event::read();
729 }
730 break;
731 }
732 _ = tokio::time::sleep(loop_sleep) => {
733 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
761pub 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}