Skip to main content

romm_cli/tui/
mod.rs

1//! Terminal UI module.
2//!
3//! This module contains all ratatui / crossterm code and is responsible
4//! purely for presentation and interaction. It talks to the rest of the
5//! application through:
6//! - `RommClient` (HTTP / data access),
7//! - `core::cache::RomCache` (disk-backed ROM cache), and
8//! - `core::download::DownloadManager` (background ROM downloads).
9//!
10//! Keeping those \"service\" types UI-agnostic makes it easy to add other
11//! frontends (e.g. a GUI) reusing the same core logic.
12
13pub mod app;
14pub mod openapi;
15pub mod screens;
16pub mod utils;
17
18use anyhow::{anyhow, Result};
19
20use crate::client::RommClient;
21use crate::config::Config;
22
23use self::app::App;
24use self::openapi::EndpointRegistry;
25
26/// Launch the interactive TUI.
27pub async fn run(client: RommClient, config: Config) -> Result<()> {
28    let openapi_path =
29        std::env::var("ROMM_OPENAPI_PATH").unwrap_or_else(|_| "openapi.json".to_string());
30
31    let registry = if std::path::Path::new(&openapi_path).exists() {
32        EndpointRegistry::from_file(&openapi_path)?
33    } else {
34        return Err(anyhow!(
35            "OpenAPI file not found at {}. Set ROMM_OPENAPI_PATH or provide openapi.json.",
36            openapi_path
37        ));
38    };
39
40    // Ensure terminal is cleaned up if a panic occurs.
41    let original_hook = std::panic::take_hook();
42    std::panic::set_hook(Box::new(move |panic| {
43        let _ = crossterm::terminal::disable_raw_mode();
44        let _ = crossterm::execute!(
45            std::io::stdout(),
46            crossterm::terminal::LeaveAlternateScreen,
47            crossterm::event::DisableMouseCapture
48        );
49        original_hook(panic);
50    }));
51
52    let mut app = App::new(client, config, registry);
53    app.run().await
54}