use std::io;
use ratatui::backend::Backend;
use ratatui::layout::Rect;
use ratatui::{Frame, Terminal};
use crate::bus::{EventBus, EventRoutingState};
use crate::event::{ComponentId, EventContext};
use crate::keybindings::Keybindings;
use crate::store::NoEffect;
use crate::{Action, BindingContext};
use super::core::{draw_frame, EffectContext, RenderContext, Runtime, RuntimeStore};
#[doc(hidden)]
pub struct EventBusRouting<S, A, Id, Ctx>
where
A: Action,
Id: ComponentId + 'static,
Ctx: BindingContext + 'static,
S: EventRoutingState<Id, Ctx>,
{
pub(crate) bus: EventBus<S, A, Id, Ctx>,
pub(crate) keybindings: Keybindings<Ctx>,
}
impl<S, A, E, Routing, St> Runtime<S, A, E, Routing, St>
where
S: 'static,
A: Action,
St: RuntimeStore<S, A, E>,
{
pub fn with_event_bus<Id, Ctx>(
self,
bus: EventBus<S, A, Id, Ctx>,
keybindings: Keybindings<Ctx>,
) -> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
where
Id: ComponentId + 'static,
Ctx: BindingContext + 'static,
S: EventRoutingState<Id, Ctx>,
{
Runtime {
store: self.store,
shell: self.shell,
routing: EventBusRouting { bus, keybindings },
#[cfg(feature = "tasks")]
tasks: self.tasks,
#[cfg(feature = "subscriptions")]
subscriptions: self.subscriptions,
action_broadcast: self.action_broadcast,
_effect: std::marker::PhantomData,
}
}
}
impl<S, A, E, Id, Ctx, St> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
where
S: 'static,
A: Action,
Id: ComponentId + 'static,
Ctx: BindingContext + 'static,
S: EventRoutingState<Id, Ctx>,
St: RuntimeStore<S, A, E>,
{
pub fn bus(&self) -> &EventBus<S, A, Id, Ctx> {
&self.routing.bus
}
pub fn bus_mut(&mut self) -> &mut EventBus<S, A, Id, Ctx> {
&mut self.routing.bus
}
pub fn keybindings(&self) -> &Keybindings<Ctx> {
&self.routing.keybindings
}
pub fn keybindings_mut(&mut self) -> &mut Keybindings<Ctx> {
&mut self.routing.keybindings
}
}
impl<S, A, Id, Ctx, St> Runtime<S, A, NoEffect, EventBusRouting<S, A, Id, Ctx>, St>
where
S: 'static,
A: Action,
Id: ComponentId + 'static,
Ctx: BindingContext + 'static,
S: EventRoutingState<Id, Ctx>,
St: RuntimeStore<S, A, NoEffect>,
{
pub async fn run<B, FRender, FQuit>(
&mut self,
terminal: &mut Terminal<B>,
render: FRender,
should_quit: FQuit,
) -> io::Result<()>
where
B: Backend,
FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
FQuit: FnMut(&A) -> bool,
{
self.run_with_hooks(terminal, render, should_quit, |_, _| {})
.await
}
pub async fn run_with_hooks<B, FRender, FQuit, FAfter>(
&mut self,
terminal: &mut Terminal<B>,
mut render: FRender,
mut should_quit: FQuit,
mut after_render: FAfter,
) -> io::Result<()>
where
B: Backend,
FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
FQuit: FnMut(&A) -> bool,
FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
{
let (mut event_rx, cancel_token) = self.shell.spawn_poller();
loop {
if self.shell.should_render {
let bus = &mut self.routing.bus;
draw_frame(
&mut self.shell,
self.store.state(),
terminal,
|f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
)?;
after_render(&mut self.routing.bus, self.store.state());
}
tokio::select! {
Some(raw_event) = event_rx.recv() => {
let bus = &mut self.routing.bus;
let kb = &self.routing.keybindings;
self.shell.process_event(
raw_event,
self.store.state(),
|event, state| bus.handle_event(event, state, kb),
);
}
Some(action) = self.shell.action_rx.recv() => {
if should_quit(&action) {
break;
}
self.shell.debug_log_action(&action);
if self.dispatch_action(action) {
break;
}
}
else => { break; }
}
}
self.cleanup(cancel_token);
Ok(())
}
}
impl<S, A, E, Id, Ctx, St> Runtime<S, A, E, EventBusRouting<S, A, Id, Ctx>, St>
where
S: 'static,
A: Action,
Id: ComponentId + 'static,
Ctx: BindingContext + 'static,
S: EventRoutingState<Id, Ctx>,
St: RuntimeStore<S, A, E>,
{
pub async fn run_with_effects<B, FRender, FQuit, FEffect>(
&mut self,
terminal: &mut Terminal<B>,
render: FRender,
should_quit: FQuit,
handle_effect: FEffect,
) -> io::Result<()>
where
B: Backend,
FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
FQuit: FnMut(&A) -> bool,
FEffect: FnMut(E, &mut EffectContext<A>),
{
self.run_with_effect_hooks(terminal, render, should_quit, handle_effect, |_, _| {})
.await
}
pub async fn run_with_effect_hooks<B, FRender, FQuit, FEffect, FAfter>(
&mut self,
terminal: &mut Terminal<B>,
mut render: FRender,
mut should_quit: FQuit,
mut handle_effect: FEffect,
mut after_render: FAfter,
) -> io::Result<()>
where
B: Backend,
FRender: FnMut(&mut Frame, Rect, &S, RenderContext, &mut EventContext<Id>),
FQuit: FnMut(&A) -> bool,
FEffect: FnMut(E, &mut EffectContext<A>),
FAfter: FnMut(&mut EventBus<S, A, Id, Ctx>, &S),
{
let (mut event_rx, cancel_token) = self.shell.spawn_poller();
loop {
if self.shell.should_render {
let bus = &mut self.routing.bus;
draw_frame(
&mut self.shell,
self.store.state(),
terminal,
|f, area, s, ctx| render(f, area, s, ctx, bus.context_mut()),
)?;
after_render(&mut self.routing.bus, self.store.state());
}
tokio::select! {
Some(raw_event) = event_rx.recv() => {
let bus = &mut self.routing.bus;
let kb = &self.routing.keybindings;
self.shell.process_event(
raw_event,
self.store.state(),
|event, state| bus.handle_event(event, state, kb),
);
}
Some(action) = self.shell.action_rx.recv() => {
if should_quit(&action) {
break;
}
self.shell.debug_log_action(&action);
if self.dispatch_and_handle_effects(action, &mut handle_effect) {
break;
}
}
else => { break; }
}
}
self.cleanup(cancel_token);
Ok(())
}
}