rustic_rs/commands/
tui.rs

1//! `tui` subcommand
2mod diff;
3mod ls;
4mod progress;
5mod restore;
6mod snapshots;
7pub mod summary;
8mod tree;
9mod widgets;
10
11pub use diff::Diff;
12pub use ls::Ls;
13pub use snapshots::Snapshots;
14
15use std::io;
16use std::sync::{Arc, RwLock};
17
18use anyhow::Result;
19use crossterm::event::{KeyEvent, KeyModifiers};
20use crossterm::{
21    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
22    execute,
23    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
24};
25use progress::TuiProgressBars;
26use ratatui::prelude::*;
27use scopeguard::defer;
28use widgets::{Draw, ProcessEvent};
29
30pub trait TuiResult {
31    fn exit(&self) -> bool;
32}
33
34impl TuiResult for bool {
35    fn exit(&self) -> bool {
36        *self
37    }
38}
39
40pub fn run(f: impl FnOnce(TuiProgressBars) -> Result<()>) -> Result<()> {
41    // setup terminal
42    let terminal = init_terminal()?;
43    let terminal = Arc::new(RwLock::new(terminal));
44
45    // restore terminal (even when leaving through ?, early return, or panic)
46    defer! {
47        reset_terminal().unwrap();
48    }
49
50    let progress = TuiProgressBars { terminal };
51
52    if let Err(err) = f(progress) {
53        println!("{err:?}");
54    }
55
56    Ok(())
57}
58
59/// Initializes the terminal.
60fn init_terminal() -> Result<Terminal<CrosstermBackend<io::Stdout>>> {
61    execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
62    enable_raw_mode()?;
63
64    let backend = CrosstermBackend::new(io::stdout());
65
66    let mut terminal = Terminal::new(backend)?;
67    terminal.hide_cursor()?;
68
69    Ok(terminal)
70}
71
72/// Resets the terminal.
73fn reset_terminal() -> Result<()> {
74    disable_raw_mode()?;
75    execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
76    Ok(())
77}
78
79pub fn run_app<T: TuiResult, A: Draw + ProcessEvent<Result = Result<T>>, B: Backend>(
80    terminal: Arc<RwLock<Terminal<B>>>,
81    mut app: A,
82) -> Result<()> {
83    loop {
84        _ = terminal.write().unwrap().draw(|f| ui(f, &mut app))?;
85        let event = event::read()?;
86
87        if let Event::Key(KeyEvent {
88            code: KeyCode::Char('c'),
89            modifiers: KeyModifiers::CONTROL,
90            kind: KeyEventKind::Press,
91            ..
92        }) = event
93        {
94            return Ok(());
95        }
96        if app.input(event)?.exit() {
97            return Ok(());
98        }
99    }
100}
101
102fn ui<A: Draw>(f: &mut Frame<'_>, app: &mut A) {
103    let area = f.area();
104    app.draw(area, f);
105}