1use 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#[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 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 pub fn bus(&self) -> &EventBus<S, A, Id, Ctx> {
73 &self.routing.bus
74 }
75
76 pub fn bus_mut(&mut self) -> &mut EventBus<S, A, Id, Ctx> {
78 &mut self.routing.bus
79 }
80
81 pub fn keybindings(&self) -> &Keybindings<Ctx> {
83 &self.routing.keybindings
84 }
85
86 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 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 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 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 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}