lib: Introduce typed inline and fullscreen terminal

This commit is contained in:
Erik Kundt
2024-09-26 14:16:13 +02:00
parent a147d2359d
commit 066e7502a4
3 changed files with 81 additions and 36 deletions
+70 -22
View File
@@ -2,17 +2,85 @@ use std::io::{self, Write};
use std::thread;
use std::time::Instant;
use ratatui::termion::screen::{AlternateScreen, IntoAlternateScreen};
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};
use ratatui::prelude::*;
use ratatui::{prelude::*, CompletedFrame};
use ratatui::{TerminalOptions, Viewport};
use tokio::sync::mpsc::{self};
use super::event::Event;
pub type Backend = TermionBackendExt<RawTerminal<io::Stdout>>;
pub type Backend<S> = TermionBackendExt<S>;
pub type InlineTerminal = ratatui::terminal::Terminal<Backend<RawTerminal<io::Stdout>>>;
pub type FullscreenTerminal =
ratatui::terminal::Terminal<Backend<AlternateScreen<RawTerminal<io::Stdout>>>>;
pub enum Terminal {
Inline(InlineTerminal),
Fullscreen(FullscreenTerminal),
}
impl Terminal {
pub fn restore(&mut self) -> io::Result<()> {
match self {
Terminal::Fullscreen(inner) => {
inner.clear()?;
}
Terminal::Inline(inner) => {
// TODO(erikli): Check if still needed.
let size = inner.get_frame().size();
inner.set_cursor(size.x, size.y)?;
}
}
Ok(())
}
pub fn draw<F>(&mut self, f: F) -> io::Result<CompletedFrame>
where
F: FnOnce(&mut Frame),
{
match self {
Terminal::Inline(inner) => inner.draw(f),
Terminal::Fullscreen(inner) => inner.draw(f),
}
}
}
impl TryFrom<Viewport> for Terminal {
type Error = anyhow::Error;
fn try_from(viewport: Viewport) -> Result<Self, Self::Error> {
match viewport {
Viewport::Fullscreen => {
let stdout = io::stdout().into_raw_mode()?.into_alternate_screen()?;
let options = TerminalOptions { viewport };
let mut terminal = ratatui::terminal::Terminal::with_options(
TermionBackendExt::new(stdout),
options,
)?;
terminal.clear()?;
Ok(Terminal::Fullscreen(terminal))
}
_ => {
let stdout = io::stdout().into_raw_mode()?;
let options = TerminalOptions { viewport };
let terminal = ratatui::terminal::Terminal::with_options(
TermionBackendExt::new(stdout),
options,
)?;
Ok(Terminal::Inline(terminal))
}
}
}
}
/// FIXME Remove workaround after a new `ratatui` version with
/// https://github.com/ratatui-org/ratatui/pull/981/ included was released.
@@ -93,26 +161,6 @@ impl<W: Write> ratatui::backend::Backend for TermionBackendExt<W> {
}
}
/// Setup a `Terminal` with inline viewport using the `termion` backend.
pub fn setup(viewport: Viewport) -> anyhow::Result<Terminal<Backend>> {
let is_fullscreen = viewport == Viewport::Fullscreen;
let stdout = io::stdout().into_raw_mode()?;
let options = TerminalOptions { viewport };
let mut terminal = Terminal::with_options(TermionBackendExt::new(stdout), options)?;
if is_fullscreen {
terminal.clear()?;
}
Ok(terminal)
}
/// Restore the `Terminal` on quit.
pub fn restore(terminal: &mut Terminal<Backend>) -> anyhow::Result<()> {
terminal.clear()?;
Ok(())
}
/// Spawn one thread that polls `stdin` for new user input and another thread
/// that polls UNIX signals, e.g. `SIGWINCH` when the terminal window size is
/// being changed.
+6 -9
View File
@@ -21,6 +21,7 @@ use crate::event::Event;
use crate::store::Update;
use crate::task::Interrupted;
use crate::terminal;
use crate::terminal::Terminal;
use crate::ui::theme::Theme;
use crate::ui::{Column, ToRow};
@@ -53,20 +54,19 @@ impl Frontend {
{
let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
let mut terminal = terminal::setup(viewport)?;
let mut terminal = Terminal::try_from(viewport)?;
let mut events_rx = terminal::events();
let mut state = state_rx.recv().await.unwrap();
let mut ctx = Context::default().with_sender(state_tx);
let mut events_rx = terminal::events();
let result: anyhow::Result<Interrupted<P>> = loop {
tokio::select! {
// Tick to terminate the select every N milliseconds
_ = ticker.tick() => (),
// Handle input events
Some(event) = events_rx.recv() => {
log::info!("Received event: {:?}", event);
match event {
Event::Key(key) => ctx.store_input(key),
Event::Resize => (),
@@ -79,9 +79,7 @@ impl Frontend {
// Catch and handle interrupt signal to gracefully shutdown
Ok(interrupted) = interrupt_rx.recv() => {
log::info!("Received interrupt: {:?}", interrupted);
let size = terminal.get_frame().size();
let _ = terminal.set_cursor(size.x, size.y);
terminal.restore()?;
break Ok(interrupted);
}
@@ -96,8 +94,7 @@ impl Frontend {
ctx.clear_inputs();
};
terminal::restore(&mut terminal)?;
terminal.restore()?;
result
}
+5 -5
View File
@@ -11,6 +11,7 @@ use crate::event::Event;
use crate::store::Update;
use crate::task::Interrupted;
use crate::terminal;
use crate::terminal::Terminal;
use crate::ui::rm::widget::RenderProps;
use crate::ui::rm::widget::Widget;
@@ -53,7 +54,7 @@ impl Frontend {
{
let mut ticker = tokio::time::interval(RENDERING_TICK_RATE);
let mut terminal = terminal::setup(viewport)?;
let mut terminal = Terminal::try_from(viewport)?;
let mut events_rx = terminal::events();
let mut root = {
@@ -67,6 +68,7 @@ impl Frontend {
tokio::select! {
// Tick to terminate the select every N milliseconds
_ = ticker.tick() => (),
// Handle input events
Some(event) = events_rx.recv() => match event {
Event::Key(key) => root.handle_event(key),
Event::Resize => (),
@@ -77,16 +79,14 @@ impl Frontend {
},
// Catch and handle interrupt signal to gracefully shutdown
Ok(interrupted) = interrupt_rx.recv() => {
let size = terminal.get_frame().size();
let _ = terminal.set_cursor(size.x, size.y);
terminal.restore()?;
break Ok(interrupted);
}
}
terminal.draw(|frame| root.render(RenderProps::from(frame.size()), frame))?;
};
terminal::restore(&mut terminal)?;
terminal.restore()?;
result
}