1#![allow(dead_code)]
3
4mod action;
5mod animation;
6mod app;
7mod backend;
8mod event;
9mod gallery_scan;
10mod history;
11mod model_info;
12mod session;
13#[cfg(test)]
14pub(crate) mod test_env;
15mod thumbnails;
16mod ui;
17
18use std::io;
19use std::panic;
20
21use anyhow::Result;
22use crossterm::{
23 event::{DisableMouseCapture, EnableMouseCapture},
24 execute,
25 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
26};
27use ratatui::prelude::*;
28
29use app::App;
30
31pub async fn run_tui(host: Option<String>, local: bool) -> Result<()> {
36 let picker = ratatui_image::picker::Picker::from_query_stdio()
39 .unwrap_or_else(|_| ratatui_image::picker::Picker::from_fontsize((8, 16)));
40
41 enable_raw_mode()?;
43 let mut stdout = io::stdout();
44 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
45 let backend = CrosstermBackend::new(stdout);
46 let mut terminal = Terminal::new(backend)?;
47
48 let original_hook = panic::take_hook();
50 panic::set_hook(Box::new(move |panic_info| {
51 let _ = disable_raw_mode();
52 let _ = execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
53 original_hook(panic_info);
54 }));
55
56 let version = mold_core::build_info::version_string();
58 tracing::info!(%version, "starting mold tui");
59 let mut app = App::new(host, local, picker)?;
60 let result = run_event_loop(&mut terminal, &mut app).await;
61
62 app.shutdown();
64
65 disable_raw_mode()?;
67 execute!(
68 terminal.backend_mut(),
69 LeaveAlternateScreen,
70 DisableMouseCapture
71 )?;
72 terminal.show_cursor()?;
73
74 result
75}
76
77async fn run_event_loop(
78 terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
79 app: &mut App,
80) -> Result<()> {
81 let mut last_resource_refresh = std::time::Instant::now();
82 if app.should_poll_remote() {
84 app.spawn_server_status_fetch();
85 } else {
86 app.resource_info.refresh_local();
87 }
88
89 loop {
90 terminal.draw(|frame| ui::render(frame, app))?;
91
92 if crossterm::event::poll(std::time::Duration::from_millis(16))? {
94 let event = crossterm::event::read()?;
95 app.handle_crossterm_event(event);
96 }
97
98 app.process_background_events();
100
101 app.tick_animations();
104
105 if last_resource_refresh.elapsed() >= std::time::Duration::from_secs(2) {
107 if app.should_poll_remote() {
108 app.spawn_server_status_fetch();
109 } else {
110 app.resource_info.refresh_local();
111 }
112 last_resource_refresh = std::time::Instant::now();
113 }
114
115 if app.should_quit {
116 return Ok(());
117 }
118 }
119}