theater_cli/tui/
mod.rs

1pub mod app;
2pub mod components;
3pub mod event_explorer;
4pub mod events;
5pub mod ui;
6
7use crossterm::{
8    execute,
9    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
10};
11use ratatui::{backend::CrosstermBackend, Terminal};
12use std::io;
13use tokio::sync::mpsc;
14use tracing::debug;
15
16use app::TuiApp;
17use events::{handle_input, InputEvent};
18use theater_server::ManagementResponse;
19use ui::render_ui;
20
21pub async fn run_tui(
22    actor_id: String,
23    manifest_path: String,
24    mut response_rx: mpsc::UnboundedReceiver<ManagementResponse>,
25) -> Result<(), Box<dyn std::error::Error>> {
26    debug!("Starting TUI for actor: {}", actor_id);
27
28    // Setup terminal
29    enable_raw_mode()?;
30    let mut stdout = io::stdout();
31    execute!(stdout, EnterAlternateScreen)?;
32    let backend = CrosstermBackend::new(stdout);
33    let mut terminal = Terminal::new(backend)?;
34
35    // Create app state
36    let mut app = TuiApp::new(actor_id, manifest_path);
37
38    // Main TUI loop
39    let result = run_tui_loop(&mut terminal, &mut app, &mut response_rx).await;
40
41    // Cleanup terminal
42    disable_raw_mode()?;
43    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
44    terminal.show_cursor()?;
45
46    result
47}
48
49async fn run_tui_loop(
50    terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
51    app: &mut TuiApp,
52    response_rx: &mut mpsc::UnboundedReceiver<ManagementResponse>,
53) -> Result<(), Box<dyn std::error::Error>> {
54    loop {
55        // Render the UI
56        terminal.draw(|f| render_ui(f, app))?;
57
58        // Handle input events
59        match handle_input(app)? {
60            InputEvent::Quit => {
61                app.quit();
62                break;
63            }
64            InputEvent::TogglePause => {
65                app.toggle_pause();
66                debug!("Toggled pause state: paused={}", app.paused);
67            }
68            InputEvent::ToggleAutoScroll => {
69                app.toggle_auto_scroll();
70                debug!("Toggled auto-scroll: enabled={}", app.auto_scroll);
71            }
72            InputEvent::ClearEvents => {
73                app.reset_events();
74                debug!("Cleared event history");
75            }
76            InputEvent::None => {}
77        }
78
79        // Handle incoming management responses (non-blocking)
80        while let Ok(response) = response_rx.try_recv() {
81            debug!("TUI received management response: {:?}", response);
82            app.handle_management_response(response);
83        }
84
85        // Check if we should quit
86        if app.should_quit {
87            debug!("TUI quitting");
88            break;
89        }
90
91        // Small delay to prevent excessive CPU usage
92        tokio::time::sleep(tokio::time::Duration::from_millis(16)).await; // ~60 FPS
93    }
94
95    Ok(())
96}