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        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
112        FQuit: FnMut(&A) -> bool,
113    {
114        self.run_with_hooks(terminal, render, should_quit, |_, _| {})
115            .await
116    }
117
118    /// Run the event/action loop with a post-render hook invoked after each
119    /// frame is drawn.
120    pub async fn run_with_hooks<B, FRender, FQuit, FAfter>(
121        &mut self,
122        terminal: &mut Terminal<B>,
123        mut render: FRender,
124        mut should_quit: FQuit,
125        mut after_render: FAfter,
126    ) -> io::Result<()>
127    where
128        B: Backend,
129        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
130        FQuit: FnMut(&A) -> bool,
131        FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
132    {
133        let (mut event_rx, cancel_token) = self.shell.spawn_poller();
134
135        loop {
136            if self.shell.should_render {
137                let bus = &mut self.routing.bus;
138                draw_frame(
139                    &mut self.shell,
140                    self.store.state(),
141                    terminal,
142                    |f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
143                )?;
144                after_render(&mut self.routing.bus, self.store.state());
145            }
146
147            tokio::select! {
148                Some(raw_event) = event_rx.recv() => {
149                    let bus = &mut self.routing.bus;
150                    let kb = &self.routing.keybindings;
151                    self.shell.process_event(
152                        raw_event,
153                        self.store.state(),
154                        |event, state| bus.handle_event(event, state, kb),
155                    );
156                }
157
158                Some(action) = self.shell.action_rx.recv() => {
159                    if should_quit(&action) {
160                        break;
161                    }
162                    self.shell.debug_log_action(&action);
163                    if self.dispatch_action(action) {
164                        break;
165                    }
166                }
167
168                else => { break; }
169            }
170        }
171
172        self.cleanup(cancel_token);
173        Ok(())
174    }
175}
176
177impl<S, A, E, Id, Ctx, St> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
178where
179    S: 'static,
180    A: Action,
181    Id: ComponentId + 'static,
182    Ctx: BindingContext + 'static,
183    S: EventRoutingState<Id, Ctx>,
184    St: RuntimeStore<S, A, E>,
185{
186    /// Run the event/action loop until quit, routing raw events through the
187    /// bus + keybindings and handling emitted effects at the run boundary.
188    pub async fn run_with_effects<B, FRender, FQuit, FEffect>(
189        &mut self,
190        terminal: &mut Terminal<B>,
191        render: FRender,
192        should_quit: FQuit,
193        handle_effect: FEffect,
194    ) -> io::Result<()>
195    where
196        B: Backend,
197        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
198        FQuit: FnMut(&A) -> bool,
199        FEffect: FnMut(E, &mut EffectContext<A>),
200    {
201        self.run_with_effect_hooks(terminal, render, should_quit, handle_effect, |_, _| {})
202            .await
203    }
204
205    /// Run the event/action loop with effects and a post-render hook invoked
206    /// after each frame is drawn.
207    pub async fn run_with_effect_hooks<B, FRender, FQuit, FEffect, FAfter>(
208        &mut self,
209        terminal: &mut Terminal<B>,
210        mut render: FRender,
211        mut should_quit: FQuit,
212        mut handle_effect: FEffect,
213        mut after_render: FAfter,
214    ) -> io::Result<()>
215    where
216        B: Backend,
217        FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
218        FQuit: FnMut(&A) -> bool,
219        FEffect: FnMut(E, &mut EffectContext<A>),
220        FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
221    {
222        let (mut event_rx, cancel_token) = self.shell.spawn_poller();
223
224        loop {
225            if self.shell.should_render {
226                let bus = &mut self.routing.bus;
227                draw_frame(
228                    &mut self.shell,
229                    self.store.state(),
230                    terminal,
231                    |f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
232                )?;
233                after_render(&mut self.routing.bus, self.store.state());
234            }
235
236            tokio::select! {
237                Some(raw_event) = event_rx.recv() => {
238                    let bus = &mut self.routing.bus;
239                    let kb = &self.routing.keybindings;
240                    self.shell.process_event(
241                        raw_event,
242                        self.store.state(),
243                        |event, state| bus.handle_event(event, state, kb),
244                    );
245                }
246
247                Some(action) = self.shell.action_rx.recv() => {
248                    if should_quit(&action) {
249                        break;
250                    }
251                    self.shell.debug_log_action(&action);
252                    if self.dispatch_and_handle_effects(action, &mut handle_effect) {
253                        break;
254                    }
255                }
256
257                else => { break; }
258            }
259        }
260
261        self.cleanup(cancel_token);
262        Ok(())
263    }
264}