use crate::sub_app::SubApp;
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use eyre::Result;
use ratatui::Frame;
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Constraint, Direction, Layout};
use std::io::Stdout;
struct RawModeGuard;
impl RawModeGuard {
fn enter() -> eyre::Result<Self> {
enable_raw_mode()?;
Ok(Self)
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
}
}
struct AlternateScreenGuard;
impl AlternateScreenGuard {
fn enter() -> eyre::Result<Self> {
execute!(std::io::stdout(), EnterAlternateScreen)?;
Ok(Self)
}
}
impl Drop for AlternateScreenGuard {
fn drop(&mut self) {
let _ = execute!(std::io::stdout(), LeaveAlternateScreen);
}
}
struct TerminalModeGuard {
_raw: RawModeGuard,
_alt: AlternateScreenGuard,
}
impl TerminalModeGuard {
fn enter() -> eyre::Result<(Self, Terminal<CrosstermBackend<Stdout>>)> {
let raw = RawModeGuard::enter()?;
let alt = AlternateScreenGuard::enter()?;
let stdout = std::io::stdout();
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
Ok((
Self {
_raw: raw,
_alt: alt,
},
terminal,
))
}
}
pub struct App<E, V, S>
where
V: SubApp<E, S>,
{
_terminal_guard: TerminalModeGuard,
terminal: Terminal<CrosstermBackend<Stdout>>,
sub_apps: Vec<V>,
_phantom: std::marker::PhantomData<(E, S)>,
}
impl<E, V, S> App<E, V, S>
where
V: SubApp<E, S>,
{
pub fn new(sub_apps: Vec<V>) -> Result<Self> {
let (_terminal_guard, terminal) = TerminalModeGuard::enter()?;
Ok(Self {
_terminal_guard,
sub_apps,
_phantom: std::marker::PhantomData,
terminal,
})
}
pub fn handle_input(&mut self, event: &mut E, state: &mut S) {
for sub_app in &mut self.sub_apps {
sub_app.handle_input(event, state);
}
}
pub fn draw(&mut self, state: &mut S) -> Result<()> {
let sub_apps = &self.sub_apps;
self.terminal
.draw(|frame| render_app::<V, E, S>(sub_apps, state, frame))?;
Ok(())
}
}
fn render_app<V, E, S>(sub_apps: &[V], state: &mut S, frame: &mut Frame)
where
V: SubApp<E, S>,
{
let area = frame.area();
let constraints: Vec<Constraint> = sub_apps.iter().map(|v| v.constraints()).collect();
let areas = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(area);
for (sub_app, area) in sub_apps.iter().zip(areas.iter().copied()) {
sub_app.render(frame, area, state);
}
}