Skip to main content

tui_dispatch_core/runtime/
bus.rs

1//! Event-bus routing mode for [`Runtime`](super::Runtime).
2
3use std::io;
4
5use ratatui::backend::Backend;
6use ratatui::layout::Rect;
7use ratatui::{Frame, Terminal};
8
9use crate::bus::{EventBus, EventRoutingState};
10use crate::event::{ComponentId, EventContext};
11use crate::keybindings::Keybindings;
12use crate::store::NoEffect;
13use crate::{Action, BindingContext};
14
15use super::core::{draw_frame, EffectContext, RenderContext, Runtime, RuntimeStore};
16
17/// Runtime routing mode that routes raw events through an [`EventBus`].
18#[doc(hidden)]
19pub struct EventBusRouting<S, A, Id, Ctx>
20where
21    A: Action,
22    Id: ComponentId + 'static,
23    Ctx: BindingContext + 'static,
24    S: EventRoutingState<Id, Ctx>,
25{
26    pub(crate) bus: EventBus<S, A, Id, Ctx>,
27    pub(crate) keybindings: Keybindings<Ctx>,
28}
29
30impl<S, A, E, Routing, St> Runtime<S, A, E, Routing, St>
31where
32    S: 'static,
33    A: Action,
34    St: RuntimeStore<S, A, E>,
35{
36    /// Pair this runtime with an [`EventBus`] + [`Keybindings`] so raw events
37    /// are routed through the bus before actions are dispatched.
38    pub fn with_event_bus<Id, Ctx>(
39        self,
40        bus: EventBus<S, A, Id, Ctx>,
41        keybindings: Keybindings<Ctx>,
42    ) -> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
43    where
44        Id: ComponentId + 'static,
45        Ctx: BindingContext + 'static,
46        S: EventRoutingState<Id, Ctx>,
47    {
48        Runtime {
49            store: self.store,
50            shell: self.shell,
51            routing: EventBusRouting { bus, keybindings },
52            #[cfg(feature = "tasks")]
53            tasks: self.tasks,
54            #[cfg(feature = "subscriptions")]
55            subscriptions: self.subscriptions,
56            action_broadcast: self.action_broadcast,
57            _effect: std::marker::PhantomData,
58        }
59    }
60}
61
62impl<S, A, E, Id, Ctx, St> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
63where
64    S: 'static,
65    A: Action,
66    Id: ComponentId + 'static,
67    Ctx: BindingContext + 'static,
68    S: EventRoutingState<Id, Ctx>,
69    St: RuntimeStore<S, A, E>,
70{
71    /// Access the event bus.
72    pub fn bus(&self) -> &EventBus<S, A, Id, Ctx> {
73        &self.routing.bus
74    }
75
76    /// Access the event bus mutably.
77    pub fn bus_mut(&mut self) -> &mut EventBus<S, A, Id, Ctx> {
78        &mut self.routing.bus
79    }
80
81    /// Access the keybindings.
82    pub fn keybindings(&self) -> &Keybindings<Ctx> {
83        &self.routing.keybindings
84    }
85
86    /// Access the keybindings mutably.
87    pub fn keybindings_mut(&mut self) -> &mut Keybindings<Ctx> {
88        &mut self.routing.keybindings
89    }
90}
91
92impl<S, A, Id, Ctx, St> Runtime<S, A, NoEffect, EventBusRouting<S, A, Id, Ctx>, St>
93where
94    S: 'static,
95    A: Action,
96    Id: ComponentId + 'static,
97    Ctx: BindingContext + 'static,
98    S: EventRoutingState<Id, Ctx>,
99    St: RuntimeStore<S, A, NoEffect>,
100{
101    /// Run the event/action loop until quit, routing raw events through the
102    /// bus + keybindings.
103    pub async fn run<B, FRender, FQuit>(
104        &mut self,
105        terminal: &mut Terminal<B>,
106        render: FRender,
107        should_quit: FQuit,
108    ) -> io::Result<()>
109    where
110        B: Backend,
111        B::Error: Send + Sync + 'static,
112        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
113        FQuit: FnMut(&A) -> bool,
114    {
115        self.run_with_hooks(terminal, render, should_quit, |_, _| {})
116            .await
117    }
118
119    /// Run the event/action loop with a post-render hook invoked after each
120    /// frame is drawn.
121    pub async fn run_with_hooks<B, FRender, FQuit, FAfter>(
122        &mut self,
123        terminal: &mut Terminal<B>,
124        mut render: FRender,
125        mut should_quit: FQuit,
126        mut after_render: FAfter,
127    ) -> io::Result<()>
128    where
129        B: Backend,
130        B::Error: Send + Sync + 'static,
131        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
132        FQuit: FnMut(&A) -> bool,
133        FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
134    {
135        let (mut event_rx, cancel_token) = self.shell.spawn_poller();
136
137        loop {
138            if self.shell.should_render {
139                let bus = &mut self.routing.bus;
140                draw_frame(
141                    &mut self.shell,
142                    self.store.state(),
143                    terminal,
144                    |f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
145                )?;
146                after_render(&mut self.routing.bus, self.store.state());
147            }
148
149            tokio::select! {
150                Some(raw_event) = event_rx.recv() => {
151                    let bus = &mut self.routing.bus;
152                    let kb = &self.routing.keybindings;
153                    self.shell.process_event(
154                        raw_event,
155                        self.store.state(),
156                        |event, state| bus.handle_event(event, state, kb),
157                    );
158                }
159
160                Some(action) = self.shell.action_rx.recv() => {
161                    if should_quit(&action) {
162                        break;
163                    }
164                    self.shell.debug_log_action(&action);
165                    if self.dispatch_action(action) {
166                        break;
167                    }
168                }
169
170                else => { break; }
171            }
172        }
173
174        self.cleanup(cancel_token);
175        Ok(())
176    }
177}
178
179impl<S, A, E, Id, Ctx, St> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
180where
181    S: 'static,
182    A: Action,
183    Id: ComponentId + 'static,
184    Ctx: BindingContext + 'static,
185    S: EventRoutingState<Id, Ctx>,
186    St: RuntimeStore<S, A, E>,
187{
188    /// Run the event/action loop until quit, routing raw events through the
189    /// bus + keybindings and handling emitted effects at the run boundary.
190    pub async fn run_with_effects<B, FRender, FQuit, FEffect>(
191        &mut self,
192        terminal: &mut Terminal<B>,
193        render: FRender,
194        should_quit: FQuit,
195        handle_effect: FEffect,
196    ) -> io::Result<()>
197    where
198        B: Backend,
199        B::Error: Send + Sync + 'static,
200        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
201        FQuit: FnMut(&A) -> bool,
202        FEffect: FnMut(E, &mut EffectContext<A>),
203    {
204        self.run_with_effect_hooks(terminal, render, should_quit, handle_effect, |_, _| {})
205            .await
206    }
207
208    /// Run the event/action loop with effects and a post-render hook invoked
209    /// after each frame is drawn.
210    pub async fn run_with_effect_hooks<B, FRender, FQuit, FEffect, FAfter>(
211        &mut self,
212        terminal: &mut Terminal<B>,
213        mut render: FRender,
214        mut should_quit: FQuit,
215        mut handle_effect: FEffect,
216        mut after_render: FAfter,
217    ) -> io::Result<()>
218    where
219        B: Backend,
220        B::Error: Send + Sync + 'static,
221        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
222        FQuit: FnMut(&A) -> bool,
223        FEffect: FnMut(E, &mut EffectContext<A>),
224        FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
225    {
226        let (mut event_rx, cancel_token) = self.shell.spawn_poller();
227
228        loop {
229            if self.shell.should_render {
230                let bus = &mut self.routing.bus;
231                draw_frame(
232                    &mut self.shell,
233                    self.store.state(),
234                    terminal,
235                    |f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
236                )?;
237                after_render(&mut self.routing.bus, self.store.state());
238            }
239
240            tokio::select! {
241                Some(raw_event) = event_rx.recv() => {
242                    let bus = &mut self.routing.bus;
243                    let kb = &self.routing.keybindings;
244                    self.shell.process_event(
245                        raw_event,
246                        self.store.state(),
247                        |event, state| bus.handle_event(event, state, kb),
248                    );
249                }
250
251                Some(action) = self.shell.action_rx.recv() => {
252                    if should_quit(&action) {
253                        break;
254                    }
255                    self.shell.debug_log_action(&action);
256                    if self.dispatch_and_handle_effects(action, &mut handle_effect) {
257                        break;
258                    }
259                }
260
261                else => { break; }
262            }
263        }
264
265        self.cleanup(cancel_token);
266        Ok(())
267    }
268}