1use std::io;
7use std::time::Duration;
8
9use ratatui::backend::Backend;
10use ratatui::layout::Rect;
11use ratatui::{Frame, Terminal};
12use tokio::sync::mpsc;
13use tokio_util::sync::CancellationToken;
14
15use crate::bus::{process_raw_event, spawn_event_poller, EventBus, EventRoutingState, RawEvent};
16use crate::effect::{DispatchResult, EffectStore, EffectStoreWithMiddleware};
17use crate::event::{ComponentId, EventContext, EventKind};
18use crate::keybindings::Keybindings;
19use crate::store::{Middleware, Reducer, Store, StoreWithMiddleware};
20use crate::{Action, BindingContext};
21
22#[cfg(feature = "subscriptions")]
23use crate::subscriptions::Subscriptions;
24#[cfg(feature = "tasks")]
25use crate::tasks::TaskManager;
26
27#[derive(Debug, Clone, Copy)]
29pub struct PollerConfig {
30 pub poll_timeout: Duration,
32 pub loop_sleep: Duration,
34}
35
36impl Default for PollerConfig {
37 fn default() -> Self {
38 Self {
39 poll_timeout: Duration::from_millis(10),
40 loop_sleep: Duration::from_millis(16),
41 }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct EventOutcome<A> {
48 pub actions: Vec<A>,
50 pub needs_render: bool,
52}
53
54#[derive(Debug, Clone, Copy, Default)]
56pub struct RenderContext {
57 pub debug_enabled: bool,
59}
60
61impl RenderContext {
62 pub fn is_focused(self) -> bool {
64 !self.debug_enabled
65 }
66}
67
68impl<A> EventOutcome<A> {
69 pub fn ignored() -> Self {
71 Self {
72 actions: Vec::new(),
73 needs_render: false,
74 }
75 }
76
77 pub fn needs_render() -> Self {
79 Self {
80 actions: Vec::new(),
81 needs_render: true,
82 }
83 }
84
85 pub fn action(action: A) -> Self {
87 Self {
88 actions: vec![action],
89 needs_render: false,
90 }
91 }
92
93 pub fn actions<I>(actions: I) -> Self
95 where
96 I: IntoIterator<Item = A>,
97 {
98 Self {
99 actions: actions.into_iter().collect(),
100 needs_render: false,
101 }
102 }
103
104 pub fn with_render(mut self) -> Self {
106 self.needs_render = true;
107 self
108 }
109}
110
111impl<A> Default for EventOutcome<A> {
112 fn default() -> Self {
113 Self::ignored()
114 }
115}
116
117impl<A> From<A> for EventOutcome<A> {
118 fn from(action: A) -> Self {
119 Self::action(action)
120 }
121}
122
123impl<A> From<Vec<A>> for EventOutcome<A> {
124 fn from(actions: Vec<A>) -> Self {
125 Self {
126 actions,
127 needs_render: false,
128 }
129 }
130}
131
132impl<A> From<Option<A>> for EventOutcome<A> {
133 fn from(action: Option<A>) -> Self {
134 match action {
135 Some(action) => Self::action(action),
136 None => Self::ignored(),
137 }
138 }
139}
140
141impl<A> EventOutcome<A> {
142 pub fn from_actions(iter: impl IntoIterator<Item = A>) -> Self {
147 Self {
148 actions: iter.into_iter().collect(),
149 needs_render: false,
150 }
151 }
152}
153
154#[cfg(feature = "debug")]
155pub trait DebugAdapter<S, A>: 'static {
156 fn render(
157 &mut self,
158 frame: &mut Frame,
159 state: &S,
160 render_ctx: RenderContext,
161 render_fn: &mut dyn FnMut(&mut Frame, Rect, &S, RenderContext),
162 );
163
164 fn handle_event(
165 &mut self,
166 event: &EventKind,
167 state: &S,
168 action_tx: &mpsc::UnboundedSender<A>,
169 ) -> Option<bool>;
170
171 fn log_action(&mut self, action: &A);
172 fn is_enabled(&self) -> bool;
173}
174
175#[cfg(feature = "debug")]
176pub trait DebugHooks<A>: Sized {
177 #[cfg(feature = "tasks")]
178 fn with_task_manager(self, _tasks: &TaskManager<A>) -> Self {
179 self
180 }
181
182 #[cfg(feature = "subscriptions")]
183 fn with_subscriptions(self, _subscriptions: &Subscriptions<A>) -> Self {
184 self
185 }
186}
187
188pub trait DispatchStore<S, A: Action> {
190 fn dispatch(&mut self, action: A) -> bool;
192 fn state(&self) -> &S;
194}
195
196impl<S, A: Action> DispatchStore<S, A> for Store<S, A> {
197 fn dispatch(&mut self, action: A) -> bool {
198 Store::dispatch(self, action)
199 }
200
201 fn state(&self) -> &S {
202 Store::state(self)
203 }
204}
205
206impl<S, A: Action, M: Middleware<A>> DispatchStore<S, A> for StoreWithMiddleware<S, A, M> {
207 fn dispatch(&mut self, action: A) -> bool {
208 StoreWithMiddleware::dispatch(self, action)
209 }
210
211 fn state(&self) -> &S {
212 StoreWithMiddleware::state(self)
213 }
214}
215
216pub trait EffectStoreLike<S, A: Action, E> {
218 fn dispatch(&mut self, action: A) -> DispatchResult<E>;
220 fn state(&self) -> &S;
222}
223
224impl<S, A: Action, E> EffectStoreLike<S, A, E> for EffectStore<S, A, E> {
225 fn dispatch(&mut self, action: A) -> DispatchResult<E> {
226 EffectStore::dispatch(self, action)
227 }
228
229 fn state(&self) -> &S {
230 EffectStore::state(self)
231 }
232}
233
234impl<S, A: Action, E, M: Middleware<A>> EffectStoreLike<S, A, E>
235 for EffectStoreWithMiddleware<S, A, E, M>
236{
237 fn dispatch(&mut self, action: A) -> DispatchResult<E> {
238 EffectStoreWithMiddleware::dispatch(self, action)
239 }
240
241 fn state(&self) -> &S {
242 EffectStoreWithMiddleware::state(self)
243 }
244}
245
246pub struct DispatchRuntime<S, A: Action, St: DispatchStore<S, A> = Store<S, A>> {
248 store: St,
249 action_tx: mpsc::UnboundedSender<A>,
250 action_rx: mpsc::UnboundedReceiver<A>,
251 poller_config: PollerConfig,
252 #[cfg(feature = "debug")]
253 debug: Option<Box<dyn DebugAdapter<S, A>>>,
254 should_render: bool,
255 _state: std::marker::PhantomData<S>,
256}
257
258impl<S: 'static, A: Action> DispatchRuntime<S, A, Store<S, A>> {
259 pub fn new(state: S, reducer: Reducer<S, A>) -> Self {
261 Self::from_store(Store::new(state, reducer))
262 }
263}
264
265impl<S: 'static, A: Action, St: DispatchStore<S, A>> DispatchRuntime<S, A, St> {
266 pub fn from_store(store: St) -> Self {
268 let (action_tx, action_rx) = mpsc::unbounded_channel();
269 Self {
270 store,
271 action_tx,
272 action_rx,
273 poller_config: PollerConfig::default(),
274 #[cfg(feature = "debug")]
275 debug: None,
276 should_render: true,
277 _state: std::marker::PhantomData,
278 }
279 }
280
281 #[cfg(feature = "debug")]
283 pub fn with_debug<D>(mut self, debug: D) -> Self
284 where
285 D: DebugAdapter<S, A>,
286 {
287 self.debug = Some(Box::new(debug));
288 self
289 }
290
291 pub fn with_event_poller(mut self, config: PollerConfig) -> Self {
293 self.poller_config = config;
294 self
295 }
296
297 pub fn enqueue(&self, action: A) {
299 let _ = self.action_tx.send(action);
300 }
301
302 pub fn action_tx(&self) -> mpsc::UnboundedSender<A> {
304 self.action_tx.clone()
305 }
306
307 pub fn state(&self) -> &S {
309 self.store.state()
310 }
311
312 pub async fn run<B, FRender, FEvent, FQuit, R>(
314 &mut self,
315 terminal: &mut Terminal<B>,
316 mut render: FRender,
317 mut map_event: FEvent,
318 mut should_quit: FQuit,
319 ) -> io::Result<()>
320 where
321 B: Backend,
322 FRender: FnMut(&mut Frame, Rect, &S, RenderContext),
323 FEvent: FnMut(&EventKind, &S) -> R,
324 R: Into<EventOutcome<A>>,
325 FQuit: FnMut(&A) -> bool,
326 {
327 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
328 let cancel_token = CancellationToken::new();
329 let _handle = spawn_event_poller(
330 event_tx,
331 self.poller_config.poll_timeout,
332 self.poller_config.loop_sleep,
333 cancel_token.clone(),
334 );
335
336 loop {
337 if self.should_render {
338 let state = self.store.state();
339 let render_ctx = RenderContext {
340 debug_enabled: {
341 #[cfg(feature = "debug")]
342 {
343 self.debug
344 .as_ref()
345 .map(|debug| debug.is_enabled())
346 .unwrap_or(false)
347 }
348 #[cfg(not(feature = "debug"))]
349 {
350 false
351 }
352 },
353 };
354 terminal.draw(|frame| {
355 #[cfg(feature = "debug")]
356 if let Some(debug) = self.debug.as_mut() {
357 let mut render_fn =
358 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
359 render(f, area, state, ctx);
360 };
361 debug.render(frame, state, render_ctx, &mut render_fn);
362 } else {
363 render(frame, frame.area(), state, render_ctx);
364 }
365
366 #[cfg(not(feature = "debug"))]
367 {
368 render(frame, frame.area(), state, render_ctx);
369 }
370 })?;
371 self.should_render = false;
372 }
373
374 tokio::select! {
375 Some(raw_event) = event_rx.recv() => {
376 let event = process_raw_event(raw_event);
377
378 #[cfg(feature = "debug")]
379 if let Some(debug) = self.debug.as_mut() {
380 if let Some(needs_render) =
381 debug.handle_event(&event, self.store.state(), &self.action_tx)
382 {
383 self.should_render = needs_render;
384 continue;
385 }
386 }
387
388 let outcome: EventOutcome<A> = map_event(&event, self.store.state()).into();
389 if outcome.needs_render {
390 self.should_render = true;
391 }
392 for action in outcome.actions {
393 let _ = self.action_tx.send(action);
394 }
395 }
396
397 Some(action) = self.action_rx.recv() => {
398 if should_quit(&action) {
399 break;
400 }
401
402 #[cfg(feature = "debug")]
403 if let Some(debug) = self.debug.as_mut() {
404 debug.log_action(&action);
405 }
406
407 self.should_render = self.store.dispatch(action);
408 }
409
410 else => {
411 break;
412 }
413 }
414 }
415
416 cancel_token.cancel();
417 Ok(())
418 }
419
420 pub async fn run_with_bus<B, FRender, FQuit, Id, Ctx>(
422 &mut self,
423 terminal: &mut Terminal<B>,
424 bus: &mut EventBus<S, A, Id, Ctx>,
425 keybindings: &Keybindings<Ctx>,
426 mut render: FRender,
427 mut should_quit: FQuit,
428 ) -> io::Result<()>
429 where
430 B: Backend,
431 Id: ComponentId + 'static,
432 Ctx: BindingContext + 'static,
433 S: EventRoutingState<Id, Ctx>,
434 FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
435 FQuit: FnMut(&A) -> bool,
436 {
437 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
438 let cancel_token = CancellationToken::new();
439 let _handle = spawn_event_poller(
440 event_tx,
441 self.poller_config.poll_timeout,
442 self.poller_config.loop_sleep,
443 cancel_token.clone(),
444 );
445
446 loop {
447 if self.should_render {
448 let state = self.store.state();
449 let render_ctx = RenderContext {
450 debug_enabled: {
451 #[cfg(feature = "debug")]
452 {
453 self.debug
454 .as_ref()
455 .map(|debug| debug.is_enabled())
456 .unwrap_or(false)
457 }
458 #[cfg(not(feature = "debug"))]
459 {
460 false
461 }
462 },
463 };
464 terminal.draw(|frame| {
465 #[cfg(feature = "debug")]
466 if let Some(debug) = self.debug.as_mut() {
467 let mut render_fn =
468 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
469 render(f, area, state, ctx, bus.context_mut());
470 };
471 debug.render(frame, state, render_ctx, &mut render_fn);
472 } else {
473 render(frame, frame.area(), state, render_ctx, bus.context_mut());
474 }
475
476 #[cfg(not(feature = "debug"))]
477 {
478 render(frame, frame.area(), state, render_ctx, bus.context_mut());
479 }
480 })?;
481 self.should_render = false;
482 }
483
484 tokio::select! {
485 Some(raw_event) = event_rx.recv() => {
486 let event = process_raw_event(raw_event);
487
488 #[cfg(feature = "debug")]
489 if let Some(debug) = self.debug.as_mut() {
490 if let Some(needs_render) =
491 debug.handle_event(&event, self.store.state(), &self.action_tx)
492 {
493 self.should_render = needs_render;
494 continue;
495 }
496 }
497
498 let outcome = bus.handle_event(&event, self.store.state(), keybindings);
499 if outcome.needs_render {
500 self.should_render = true;
501 }
502 for action in outcome.actions {
503 let _ = self.action_tx.send(action);
504 }
505 }
506
507 Some(action) = self.action_rx.recv() => {
508 if should_quit(&action) {
509 break;
510 }
511
512 #[cfg(feature = "debug")]
513 if let Some(debug) = self.debug.as_mut() {
514 debug.log_action(&action);
515 }
516
517 self.should_render = self.store.dispatch(action);
518 }
519
520 else => {
521 break;
522 }
523 }
524 }
525
526 cancel_token.cancel();
527 Ok(())
528 }
529}
530
531pub struct EffectContext<'a, A: Action> {
533 action_tx: &'a mpsc::UnboundedSender<A>,
534 #[cfg(feature = "tasks")]
535 tasks: &'a mut TaskManager<A>,
536 #[cfg(feature = "subscriptions")]
537 subscriptions: &'a mut Subscriptions<A>,
538}
539
540impl<'a, A: Action> EffectContext<'a, A> {
541 pub fn emit(&self, action: A) {
543 let _ = self.action_tx.send(action);
544 }
545
546 pub fn action_tx(&self) -> &mpsc::UnboundedSender<A> {
548 self.action_tx
549 }
550
551 #[cfg(feature = "tasks")]
553 pub fn tasks(&mut self) -> &mut TaskManager<A> {
554 self.tasks
555 }
556
557 #[cfg(feature = "subscriptions")]
559 pub fn subscriptions(&mut self) -> &mut Subscriptions<A> {
560 self.subscriptions
561 }
562}
563
564pub struct EffectRuntime<S, A: Action, E, St: EffectStoreLike<S, A, E> = EffectStore<S, A, E>> {
566 store: St,
567 action_tx: mpsc::UnboundedSender<A>,
568 action_rx: mpsc::UnboundedReceiver<A>,
569 poller_config: PollerConfig,
570 #[cfg(feature = "debug")]
571 debug: Option<Box<dyn DebugAdapter<S, A>>>,
572 should_render: bool,
573 #[cfg(feature = "tasks")]
574 tasks: TaskManager<A>,
575 #[cfg(feature = "subscriptions")]
576 subscriptions: Subscriptions<A>,
577 action_broadcast: tokio::sync::broadcast::Sender<String>,
579 _state: std::marker::PhantomData<S>,
580 _effect: std::marker::PhantomData<E>,
581}
582
583impl<S: 'static, A: Action, E> EffectRuntime<S, A, E, EffectStore<S, A, E>> {
584 pub fn new(state: S, reducer: crate::effect::EffectReducer<S, A, E>) -> Self {
586 Self::from_store(EffectStore::new(state, reducer))
587 }
588}
589
590impl<S: 'static, A: Action, E, St: EffectStoreLike<S, A, E>> EffectRuntime<S, A, E, St> {
591 pub fn from_store(store: St) -> Self {
593 let (action_tx, action_rx) = mpsc::unbounded_channel();
594 let (action_broadcast, _) = tokio::sync::broadcast::channel(64);
595
596 #[cfg(feature = "tasks")]
597 let tasks = TaskManager::new(action_tx.clone());
598 #[cfg(feature = "subscriptions")]
599 let subscriptions = Subscriptions::new(action_tx.clone());
600
601 Self {
602 store,
603 action_tx,
604 action_rx,
605 poller_config: PollerConfig::default(),
606 #[cfg(feature = "debug")]
607 debug: None,
608 should_render: true,
609 #[cfg(feature = "tasks")]
610 tasks,
611 #[cfg(feature = "subscriptions")]
612 subscriptions,
613 action_broadcast,
614 _state: std::marker::PhantomData,
615 _effect: std::marker::PhantomData,
616 }
617 }
618
619 #[cfg(feature = "debug")]
621 pub fn with_debug<D>(mut self, debug: D) -> Self
622 where
623 D: DebugAdapter<S, A> + DebugHooks<A>,
624 {
625 let debug = {
626 let debug = debug;
627 #[cfg(feature = "tasks")]
628 let debug = debug.with_task_manager(&self.tasks);
629 #[cfg(feature = "subscriptions")]
630 let debug = debug.with_subscriptions(&self.subscriptions);
631 debug
632 };
633 self.debug = Some(Box::new(debug));
634 self
635 }
636
637 pub fn with_event_poller(mut self, config: PollerConfig) -> Self {
639 self.poller_config = config;
640 self
641 }
642
643 pub fn subscribe_actions(&self) -> tokio::sync::broadcast::Receiver<String> {
648 self.action_broadcast.subscribe()
649 }
650
651 pub fn enqueue(&self, action: A) {
653 let _ = self.action_tx.send(action);
654 }
655
656 pub fn action_tx(&self) -> mpsc::UnboundedSender<A> {
658 self.action_tx.clone()
659 }
660
661 pub fn state(&self) -> &S {
663 self.store.state()
664 }
665
666 #[cfg(feature = "tasks")]
668 pub fn tasks(&mut self) -> &mut TaskManager<A> {
669 &mut self.tasks
670 }
671
672 #[cfg(feature = "subscriptions")]
674 pub fn subscriptions(&mut self) -> &mut Subscriptions<A> {
675 &mut self.subscriptions
676 }
677
678 #[cfg(all(feature = "tasks", feature = "subscriptions"))]
679 fn effect_context(&mut self) -> EffectContext<'_, A> {
680 EffectContext {
681 action_tx: &self.action_tx,
682 tasks: &mut self.tasks,
683 subscriptions: &mut self.subscriptions,
684 }
685 }
686
687 #[cfg(all(feature = "tasks", not(feature = "subscriptions")))]
688 fn effect_context(&mut self) -> EffectContext<'_, A> {
689 EffectContext {
690 action_tx: &self.action_tx,
691 tasks: &mut self.tasks,
692 }
693 }
694
695 #[cfg(all(not(feature = "tasks"), feature = "subscriptions"))]
696 fn effect_context(&mut self) -> EffectContext<'_, A> {
697 EffectContext {
698 action_tx: &self.action_tx,
699 subscriptions: &mut self.subscriptions,
700 }
701 }
702
703 #[cfg(all(not(feature = "tasks"), not(feature = "subscriptions")))]
704 fn effect_context(&mut self) -> EffectContext<'_, A> {
705 EffectContext {
706 action_tx: &self.action_tx,
707 }
708 }
709
710 pub async fn run<B, FRender, FEvent, FQuit, FEffect, R>(
712 &mut self,
713 terminal: &mut Terminal<B>,
714 mut render: FRender,
715 mut map_event: FEvent,
716 mut should_quit: FQuit,
717 mut handle_effect: FEffect,
718 ) -> io::Result<()>
719 where
720 B: Backend,
721 FRender: FnMut(&mut Frame, Rect, &S, RenderContext),
722 FEvent: FnMut(&EventKind, &S) -> R,
723 R: Into<EventOutcome<A>>,
724 FQuit: FnMut(&A) -> bool,
725 FEffect: FnMut(E, &mut EffectContext<A>),
726 {
727 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
728 let cancel_token = CancellationToken::new();
729 let _handle = spawn_event_poller(
730 event_tx,
731 self.poller_config.poll_timeout,
732 self.poller_config.loop_sleep,
733 cancel_token.clone(),
734 );
735
736 loop {
737 if self.should_render {
738 let state = self.store.state();
739 let render_ctx = RenderContext {
740 debug_enabled: {
741 #[cfg(feature = "debug")]
742 {
743 self.debug
744 .as_ref()
745 .map(|debug| debug.is_enabled())
746 .unwrap_or(false)
747 }
748 #[cfg(not(feature = "debug"))]
749 {
750 false
751 }
752 },
753 };
754 terminal.draw(|frame| {
755 #[cfg(feature = "debug")]
756 if let Some(debug) = self.debug.as_mut() {
757 let mut render_fn =
758 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
759 render(f, area, state, ctx);
760 };
761 debug.render(frame, state, render_ctx, &mut render_fn);
762 } else {
763 render(frame, frame.area(), state, render_ctx);
764 }
765
766 #[cfg(not(feature = "debug"))]
767 {
768 render(frame, frame.area(), state, render_ctx);
769 }
770 })?;
771 self.should_render = false;
772 }
773
774 tokio::select! {
775 Some(raw_event) = event_rx.recv() => {
776 let event = process_raw_event(raw_event);
777
778 #[cfg(feature = "debug")]
779 if let Some(debug) = self.debug.as_mut() {
780 if let Some(needs_render) =
781 debug.handle_event(&event, self.store.state(), &self.action_tx)
782 {
783 self.should_render = needs_render;
784 continue;
785 }
786 }
787
788 let outcome: EventOutcome<A> = map_event(&event, self.store.state()).into();
789 if outcome.needs_render {
790 self.should_render = true;
791 }
792 for action in outcome.actions {
793 let _ = self.action_tx.send(action);
794 }
795 }
796
797 Some(action) = self.action_rx.recv() => {
798 if should_quit(&action) {
799 break;
800 }
801
802 #[cfg(feature = "debug")]
803 if let Some(debug) = self.debug.as_mut() {
804 debug.log_action(&action);
805 }
806
807 let _ = self.action_broadcast.send(action.name().to_string());
809
810 let result = self.store.dispatch(action);
811 if result.has_effects() {
812 let mut ctx = self.effect_context();
813 for effect in result.effects {
814 handle_effect(effect, &mut ctx);
815 }
816 }
817 self.should_render = result.changed;
818 }
819
820 else => {
821 break;
822 }
823 }
824 }
825
826 cancel_token.cancel();
827 #[cfg(feature = "subscriptions")]
828 self.subscriptions.cancel_all();
829 #[cfg(feature = "tasks")]
830 self.tasks.cancel_all();
831
832 Ok(())
833 }
834
835 pub async fn run_with_bus<B, FRender, FQuit, FEffect, Id, Ctx>(
837 &mut self,
838 terminal: &mut Terminal<B>,
839 bus: &mut EventBus<S, A, Id, Ctx>,
840 keybindings: &Keybindings<Ctx>,
841 mut render: FRender,
842 mut should_quit: FQuit,
843 mut handle_effect: FEffect,
844 ) -> io::Result<()>
845 where
846 B: Backend,
847 Id: ComponentId + 'static,
848 Ctx: BindingContext + 'static,
849 S: EventRoutingState<Id, Ctx>,
850 FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
851 FQuit: FnMut(&A) -> bool,
852 FEffect: FnMut(E, &mut EffectContext<A>),
853 {
854 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
855 let cancel_token = CancellationToken::new();
856 let _handle = spawn_event_poller(
857 event_tx,
858 self.poller_config.poll_timeout,
859 self.poller_config.loop_sleep,
860 cancel_token.clone(),
861 );
862
863 loop {
864 if self.should_render {
865 let state = self.store.state();
866 let render_ctx = RenderContext {
867 debug_enabled: {
868 #[cfg(feature = "debug")]
869 {
870 self.debug
871 .as_ref()
872 .map(|debug| debug.is_enabled())
873 .unwrap_or(false)
874 }
875 #[cfg(not(feature = "debug"))]
876 {
877 false
878 }
879 },
880 };
881 terminal.draw(|frame| {
882 #[cfg(feature = "debug")]
883 if let Some(debug) = self.debug.as_mut() {
884 let mut render_fn =
885 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
886 render(f, area, state, ctx, bus.context_mut());
887 };
888 debug.render(frame, state, render_ctx, &mut render_fn);
889 } else {
890 render(frame, frame.area(), state, render_ctx, bus.context_mut());
891 }
892
893 #[cfg(not(feature = "debug"))]
894 {
895 render(frame, frame.area(), state, render_ctx, bus.context_mut());
896 }
897 })?;
898 self.should_render = false;
899 }
900
901 tokio::select! {
902 Some(raw_event) = event_rx.recv() => {
903 let event = process_raw_event(raw_event);
904
905 #[cfg(feature = "debug")]
906 if let Some(debug) = self.debug.as_mut() {
907 if let Some(needs_render) =
908 debug.handle_event(&event, self.store.state(), &self.action_tx)
909 {
910 self.should_render = needs_render;
911 continue;
912 }
913 }
914
915 let outcome = bus.handle_event(&event, self.store.state(), keybindings);
916 if outcome.needs_render {
917 self.should_render = true;
918 }
919 for action in outcome.actions {
920 let _ = self.action_tx.send(action);
921 }
922 }
923
924 Some(action) = self.action_rx.recv() => {
925 if should_quit(&action) {
926 break;
927 }
928
929 #[cfg(feature = "debug")]
930 if let Some(debug) = self.debug.as_mut() {
931 debug.log_action(&action);
932 }
933
934 let _ = self.action_broadcast.send(action.name().to_string());
936
937 let result = self.store.dispatch(action);
938 if result.has_effects() {
939 let mut ctx = self.effect_context();
940 for effect in result.effects {
941 handle_effect(effect, &mut ctx);
942 }
943 }
944 self.should_render = result.changed;
945 }
946
947 else => {
948 break;
949 }
950 }
951 }
952
953 cancel_token.cancel();
954 #[cfg(feature = "subscriptions")]
955 self.subscriptions.cancel_all();
956 #[cfg(feature = "tasks")]
957 self.tasks.cancel_all();
958
959 Ok(())
960 }
961}