| 341 |
341 |
|
api: MnwApiClient,
|
| 342 |
342 |
|
staging_dir: PathBuf,
|
| 343 |
343 |
|
) -> anyhow::Result<AppHandle> {
|
|
344 |
+ |
let mut writer = writer;
|
|
345 |
+ |
// Enter alternate screen, hide cursor, enable raw-like mode
|
|
346 |
+ |
use std::io::Write;
|
|
347 |
+ |
let _ = writer.write_all(b"\x1b[?1049h\x1b[?25l\x1b[2J\x1b[H");
|
|
348 |
+ |
let _ = writer.flush();
|
| 344 |
349 |
|
let backend = CrosstermBackend::new(writer);
|
| 345 |
|
- |
let mut terminal = Terminal::new(backend)?;
|
| 346 |
|
- |
terminal.resize(ratatui::layout::Rect::new(0, 0, cols, rows))?;
|
|
350 |
+ |
let options = ratatui::TerminalOptions {
|
|
351 |
+ |
viewport: ratatui::Viewport::Fixed(ratatui::layout::Rect::new(0, 0, cols, rows)),
|
|
352 |
+ |
};
|
|
353 |
+ |
let mut terminal = Terminal::with_options(backend, options)?;
|
| 347 |
354 |
|
|
| 348 |
355 |
|
let (tx, mut rx) = mpsc::channel::<AppEvent>(64);
|
| 349 |
356 |
|
let handle = AppHandle { tx: tx.clone() };
|
| 361 |
368 |
|
let mut screen = Screen::Home;
|
| 362 |
369 |
|
let staging_dir = staging_dir;
|
| 363 |
370 |
|
|
|
371 |
+ |
/// Write escape codes to leave alternate screen and restore cursor.
|
|
372 |
+ |
fn cleanup(terminal: &mut Terminal<CrosstermBackend<TerminalHandle>>) {
|
|
373 |
+ |
use std::io::Write;
|
|
374 |
+ |
let be = terminal.backend_mut();
|
|
375 |
+ |
let _ = be.write_all(b"\x1b[?25h\x1b[?1049l");
|
|
376 |
+ |
let _ = be.flush();
|
|
377 |
+ |
}
|
|
378 |
+ |
|
| 364 |
379 |
|
// Initial render (loading state)
|
| 365 |
380 |
|
if let Err(e) = terminal.draw(|frame| home::render(frame, &app)) {
|
| 366 |
|
- |
tracing::error!(error = ?e, "initial render failed");
|
|
381 |
+ |
tracing::error!(error = ?e, "TUI: initial render failed");
|
|
382 |
+ |
cleanup(&mut terminal);
|
| 367 |
383 |
|
return;
|
| 368 |
384 |
|
}
|
| 369 |
385 |
|
|
| 380 |
396 |
|
)
|
| 381 |
397 |
|
{
|
| 382 |
398 |
|
tracing::info!(user = %app.user.username, "user quit");
|
|
399 |
+ |
cleanup(&mut terminal);
|
| 383 |
400 |
|
let _ = session_handle.close(channel_id).await;
|
| 384 |
401 |
|
return;
|
| 385 |
402 |
|
}
|
| 640 |
657 |
|
Screen::Settings => settings::render(frame, &app),
|
| 641 |
658 |
|
}) {
|
| 642 |
659 |
|
tracing::error!(error = ?e, "render failed");
|
|
660 |
+ |
cleanup(&mut terminal);
|
| 643 |
661 |
|
return;
|
| 644 |
662 |
|
}
|
| 645 |
663 |
|
}
|
|
664 |
+ |
// Event loop ended (channel dropped)
|
|
665 |
+ |
cleanup(&mut terminal);
|
| 646 |
666 |
|
});
|
| 647 |
667 |
|
|
| 648 |
668 |
|
Ok(handle)
|