use super::app_event_handlers::handle_app_event;
use super::event::{AppActionEvent, AppEvent, LastAppEvent};
use crate::Result;
use crate::event::Rx;
use crate::exec::ExecutorTx;
use crate::model::ModelManager;
use crate::support::time::now_micro;
use crate::tui::core::app_state::process_app_state;
use crate::tui::core::{PingTimerTx, start_ping_timer};
use crate::tui::{AppState, AppTx, ExitTx, MainView};
use ratatui::DefaultTerminal;
use tokio::task::JoinHandle;
use tracing::error;
pub fn run_ui_loop(
mut terminal: DefaultTerminal,
mm: ModelManager,
executor_tx: ExecutorTx,
app_rx: Rx<AppEvent>,
app_tx: AppTx,
exit_tx: ExitTx,
) -> Result<JoinHandle<()>> {
let mut app_state = AppState::new(mm, LastAppEvent::default())?;
let ping_tx: PingTimerTx = start_ping_timer(app_tx.clone())?;
let handle = tokio::spawn(async move {
loop {
process_app_state(&mut app_state);
if let Some(action_event) = app_state.take_action_event_to_send() {
let _ = app_tx.send(action_event).await;
}
let _ = terminal_draw(&mut terminal, &mut app_state);
if app_state.should_redraw() {
app_state.core_mut().do_redraw = false;
let _ = app_tx.send(AppEvent::DoRedraw).await;
}
let app_event = {
let mut last_refresh = None;
let evt = loop {
match app_rx.try_recv() {
Ok(Some(r)) => {
if r.is_refresh_event() {
last_refresh = Some(r);
continue;
} else {
break Some(r);
}
}
Ok(None) => {
break last_refresh;
}
Err(err) => {
error!("UI LOOP ERROR.\nCause: {err}");
continue;
}
}
};
match evt {
Some(evt) => evt,
None => {
if app_state.should_be_pinged() {
let _ = ping_tx.send(now_micro()).await;
}
match app_rx.recv().await {
Ok(evt) => evt,
Err(err) => {
error!("UI LOOP ERROR.\nCause: {err}");
continue;
}
}
}
}
};
if let AppEvent::Action(AppActionEvent::Quit) = &app_event {
let _ = terminal.clear();
let _ = exit_tx.send(()).await;
break;
}
let _ = handle_app_event(
&mut terminal,
app_state.mm(),
&executor_tx,
&app_tx,
&exit_tx,
&app_event,
)
.await;
app_state.core_mut().last_app_event = app_event.into();
}
});
Ok(handle)
}
fn terminal_draw(terminal: &mut DefaultTerminal, app_state: &mut AppState) -> Result<()> {
terminal.draw(|frame| {
let area = frame.area();
frame.render_stateful_widget(MainView, area, app_state);
})?;
Ok(())
}