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 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 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 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 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}