1use 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#[derive(Debug)]
17pub enum RawEvent {
18 Key(crossterm::event::KeyEvent),
19 Mouse(crossterm::event::MouseEvent),
20 Resize(u16, u16),
21}
22
23pub 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#[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
41struct RoutingPlan<Id: ComponentId> {
45 targets: Vec<(RouteTarget<Id>, Id)>,
46 modal_blocks: bool,
47}
48
49impl<Id: ComponentId> RoutingPlan<Id> {
50 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 if let Some(id) = modal {
70 targets.push((RouteTarget::Modal(id), id));
71 }
72
73 let modal_blocks = modal.is_some() && !is_broadcast;
75
76 if !modal_blocks {
77 if let Some(id) = hovered {
79 targets.push((RouteTarget::Hovered(id), id));
80 }
81
82 if let Some(id) = focused {
84 targets.push((RouteTarget::Focused(id), id));
85 }
86
87 for &id in subscribers {
89 targets.push((RouteTarget::Subscriber(id), id));
90 }
91 }
92
93 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#[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#[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 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 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
181pub 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
200pub 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#[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
228pub 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 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 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 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 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 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 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 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 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 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 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 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 pub fn context_mut(&mut self) -> &mut EventContext<Id> {
363 &mut self.context
364 }
365
366 pub fn context(&self) -> &EventContext<Id> {
368 &self.context
369 }
370
371 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 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 let plan = RoutingPlan::build(event, state, &subscribers, global_subscribers, hovered);
405
406 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 let mut actions = Vec::new();
417 let mut needs_render = false;
418 let mut consumed = false;
419 let mut called = HashSet::new();
420
421 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 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
564pub 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 while event::poll(Duration::ZERO).unwrap_or(false) {
589 let _ = event::read();
590 }
591 break;
592 }
593 _ = tokio::time::sleep(loop_sleep) => {
594 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
621pub 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}