mod app;
mod form;
mod view;
pub use app::{App, Outcome};
use std::fs::File;
use std::panic;
use std::time::Duration;
use anyhow::{Context, Result};
use crossterm::event::{self, Event, KeyEventKind};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use ratatui::backend::{Backend, ClearType, CrosstermBackend};
use ratatui::{Terminal, TerminalOptions, Viewport};
use crate::console;
type Screen = Terminal<CrosstermBackend<File>>;
const HEIGHT: u16 = 17;
pub fn run(app: &mut App) -> Result<Outcome> {
let mut screen = enter()?;
let mut top = top_of(&mut screen);
let outcome = event_loop(&mut screen, app, &mut top);
leave(&mut screen, top)?;
outcome
}
fn event_loop(screen: &mut Screen, app: &mut App, top: &mut u16) -> Result<Outcome> {
loop {
screen.draw(|frame| view::draw(app, frame))?;
*top = (*top).min(top_of(screen));
match event::read()? {
Event::Resize(..) => screen.clear()?,
Event::Key(key) if key.kind == KeyEventKind::Press => {
if let Some(outcome) = app.on_key(key)? {
return Ok(outcome);
}
}
_ => {}
}
}
}
fn top_of(screen: &mut Screen) -> u16 {
screen.get_frame().area().y
}
fn enter() -> Result<Screen> {
install_panic_hook();
console::adopt_terminal_as_stdin();
enable_raw_mode().context("failed to put the terminal into raw mode")?;
let mut screen = Terminal::with_options(
CrosstermBackend::new(console::device()?),
TerminalOptions {
viewport: Viewport::Inline(HEIGHT),
},
)
.context("failed to start the terminal backend")?;
screen.clear().context("failed to clear the panel")?;
screen.hide_cursor().ok();
Ok(screen)
}
fn leave(screen: &mut Screen, top: u16) -> Result<()> {
drain_input();
let origin = screen.get_frame().area();
let top = top.min(origin.y);
screen.set_cursor_position((origin.x, top)).ok();
screen
.backend_mut()
.clear_region(ClearType::AfterCursor)
.ok();
screen.show_cursor().ok();
restore();
Ok(())
}
fn drain_input() {
while event::poll(Duration::ZERO).unwrap_or(false) {
if event::read().is_err() {
return;
}
}
}
fn restore() {
let _ = disable_raw_mode();
}
fn install_panic_hook() {
let previous = panic::take_hook();
panic::set_hook(Box::new(move |info| {
restore();
previous(info);
}));
}