use std::io::{self, Stdout};
use std::sync::Arc;
use std::time::Duration;
use crossterm::{
ExecutableCommand,
event::{self, Event, KeyCode, KeyEventKind},
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend};
use tokio::sync::watch;
use super::ui;
use crate::fleet::{Fleet, LogEntry, ShipSnapshot};
pub struct TuiApp {
terminal: Terminal<CrosstermBackend<Stdout>>,
fleet: Arc<Fleet>,
shutdown_rx: watch::Receiver<bool>,
selected_tab: usize,
selected_ship: usize,
log_scroll: u16,
ships_cache: Vec<ShipSnapshot>,
logs_cache: Vec<LogEntry>,
}
impl TuiApp {
pub async fn new(fleet: Arc<Fleet>, shutdown_rx: watch::Receiver<bool>) -> io::Result<Self> {
enable_raw_mode()?;
let mut stdout = io::stdout();
stdout.execute(EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let terminal = Terminal::new(backend)?;
let ships_cache = fleet.ships_snapshot().await;
Ok(Self {
terminal,
fleet,
shutdown_rx,
selected_tab: 0,
selected_ship: 0,
log_scroll: 0,
ships_cache,
logs_cache: vec![],
})
}
pub async fn run(&mut self) -> io::Result<()> {
loop {
if *self.shutdown_rx.borrow() {
break;
}
self.ships_cache = self.fleet.ships_snapshot().await;
if !self.ships_cache.is_empty() && self.selected_ship >= self.ships_cache.len() {
self.selected_ship = self.ships_cache.len() - 1;
}
if self.selected_tab == 1 {
self.logs_cache = self.fleet.ship_logs(self.selected_ship, 100).await;
}
let ships = &self.ships_cache;
let logs = &self.logs_cache;
let selected_tab = self.selected_tab;
let selected_ship = self.selected_ship;
let log_scroll = self.log_scroll;
self.terminal.draw(|frame| {
ui::render(frame, ships, logs, selected_tab, selected_ship, log_scroll);
})?;
if event::poll(Duration::from_millis(250))?
&& let Event::Key(key) = event::read()?
&& key.kind == KeyEventKind::Press
{
match key.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Tab => {
self.selected_tab = (self.selected_tab + 1) % 3;
}
KeyCode::BackTab => {
self.selected_tab = if self.selected_tab == 0 {
2
} else {
self.selected_tab - 1
};
}
KeyCode::Up | KeyCode::Char('k') => {
if self.selected_ship > 0 {
self.selected_ship -= 1;
}
}
KeyCode::Down | KeyCode::Char('j') => {
if self.selected_ship + 1 < self.ships_cache.len() {
self.selected_ship += 1;
}
}
KeyCode::PageUp => {
self.log_scroll = self.log_scroll.saturating_sub(10);
}
KeyCode::PageDown => {
self.log_scroll = self.log_scroll.saturating_add(10);
}
_ => {}
}
}
}
Ok(())
}
}
impl Drop for TuiApp {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = self.terminal.backend_mut().execute(LeaveAlternateScreen);
}
}