use futures::{
FutureExt,
future::{Either, select},
};
use std::io::{self};
use crate::{
ElementKey,
component::{ComponentHelperExt, InstantiatedComponent},
context::{ContextStack, SystemContext},
element::ElementRepr,
props::AnyProps,
terminal::{CrossTerminal, Terminal, TerminalImpl, UpdaterTerminal},
};
use super::ComponentDrawer;
struct RestoreGuard;
impl Drop for RestoreGuard {
fn drop(&mut self) {
ratatui::restore();
}
}
fn should_quit_on_ctrl_c(system_context: &SystemContext, event: &crossterm::event::Event) -> bool {
system_context.auto_quit_on_ctrl_c() && CrossTerminal::received_ctrl_c(event.clone())
}
#[doc(hidden)]
pub struct Tree<'a> {
root_component: InstantiatedComponent,
props: AnyProps<'a>,
system_context: SystemContext,
}
impl<'a> Tree<'a> {
pub(crate) fn new(mut props: AnyProps<'a>, helper: Box<dyn ComponentHelperExt>) -> Self {
Tree {
root_component: InstantiatedComponent::new(
ElementKey::user("_root_tree_"),
props.borrow(),
helper,
),
props,
system_context: SystemContext::new(),
}
}
pub(crate) fn update_once(&mut self, terminal: &mut dyn UpdaterTerminal) {
self.system_context.input.begin_frame();
let mut component_context_stack = ContextStack::root(&mut self.system_context);
self.root_component
.update(terminal, &mut component_context_stack, self.props.borrow());
}
pub(crate) fn draw_root(&mut self, drawer: &mut ComponentDrawer) {
self.root_component.draw(drawer);
}
fn render(&mut self, terminal: &mut Terminal) -> io::Result<()> {
self.update_once(terminal);
terminal.draw(|frame| {
let area = frame.area();
let mut drawer = ComponentDrawer::new(frame, area);
self.draw_root(&mut drawer);
})?;
Ok(())
}
async fn render_loop(&mut self, terminal: &mut Terminal) -> io::Result<()> {
loop {
self.render(terminal)?;
if self.system_context.should_exit() {
break;
}
match select(
self.root_component.wait().boxed_local(),
terminal.next_event().boxed_local(),
)
.await
{
Either::Left(((), _)) => continue,
Either::Right((Some(event), _)) => {
if should_quit_on_ctrl_c(&self.system_context, &event) {
break;
}
self.system_context.input.dispatch(event);
continue;
}
Either::Right((None, _)) => break,
}
}
Ok(())
}
}
pub(crate) async fn render_loop<E: ElementRepr>(
mut element: E,
mut terminal: Terminal,
) -> io::Result<()> {
let helper = element.helper();
let mut tree = Tree::new(element.props_mut(), helper);
let _restore_guard = RestoreGuard;
tree.render_loop(&mut terminal).await
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
fn ctrl_c_event() -> Event {
Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL))
}
#[test]
fn ctrl_c_quits_by_default() {
assert!(should_quit_on_ctrl_c(
&SystemContext::new(),
&ctrl_c_event()
));
}
#[test]
fn ctrl_c_is_dispatched_when_auto_quit_is_disabled() {
let mut system_context = SystemContext::new();
system_context.set_auto_quit_on_ctrl_c(false);
assert!(!should_quit_on_ctrl_c(&system_context, &ctrl_c_event()));
}
#[test]
fn regular_key_does_not_quit() {
let event = Event::Key(KeyEvent::new(KeyCode::Char('c'), KeyModifiers::NONE));
assert!(!should_quit_on_ctrl_c(&SystemContext::new(), &event));
}
}