// mod handlers; pub mod messages; mod model; use std::sync::mpsc::Sender; use actix::{Actor, ActorContext as _, Context}; pub use model::*; use ratatui::{ crossterm::event::{self, KeyCode, KeyEventKind}, DefaultTerminal, }; #[derive(Debug)] pub struct Tui { terminal: Option, signal_shutdown: Sender<()>, pub state: State, } impl Actor for Tui { type Context = Context; fn started(&mut self, _ctx: &mut Self::Context) { self.terminal.replace(ratatui::init()); } fn stopped(&mut self, _ctx: &mut Self::Context) { self.terminal.take(); ratatui::restore(); } } impl Tui { pub fn new(signal_shutdown: Sender<()>) -> Self { Self { terminal: None, signal_shutdown, state: State::initial(), } } pub const fn state(&self) -> &State { &self.state } fn draw(&mut self) -> std::io::Result<()> { let t = self.terminal.take(); if let Some(mut terminal) = t { terminal.draw(|frame| { frame.render_widget(self.state(), frame.area()); })?; self.terminal = Some(terminal); } else { eprintln!("No terminal setup"); } Ok(()) } fn handle_input(&self, ctx: &mut ::Context) -> std::io::Result<()> { if event::poll(std::time::Duration::from_millis(16))? { if let event::Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { match key.code { KeyCode::Char('q') => { ctx.stop(); if let Err(err) = self.signal_shutdown.send(()) { tracing::error!(?err, "Failed to signal shutdown"); } } KeyCode::Esc => { // } _ => (), } } } } Ok(()) } }