use std::io::stdout; use std::io::Write; use anyhow::{bail, Result}; use crossterm::{ cursor::{self, MoveTo}, event::{Event, EventStream, KeyCode, KeyModifiers}, terminal::{self, disable_raw_mode, enable_raw_mode}, ExecutableCommand, QueueableCommand, }; use futures::StreamExt; use tokio::sync::broadcast::Sender; use crate::message::Message; pub(crate) struct Ui { sender: Sender, } impl Ui { pub(crate) fn new(sender: Sender) -> Self { Self { sender } } pub(crate) async fn main_loop(self) -> Result<()> { let mut reader = EventStream::new(); loop { enable_raw_mode()?; stdout().execute(cursor::Hide)?; if let Some(Ok(Event::Key(key_event))) = reader.next().await { let should_exit = self.handle_key_event(key_event)?; if should_exit { return Ok(()); }; } } } fn handle_key_event(&self, key_event: crossterm::event::KeyEvent) -> Result { if Self::is_char_q(key_event) { self.prepare_for_program_quit()?; return Ok(true); }; if Self::is_ctrl_c(key_event) { self.prepare_for_program_quit()?; self.sender.send(Message::CtrlC)?; bail!(""); } if Self::is_space(key_event) { self.sender.send(Message::TogglePause)?; } Ok(false) } fn prepare_for_program_quit(&self) -> Result<()> { self.sender.send(Message::Quit)?; disable_raw_mode()?; stdout().execute(cursor::Show)?; Ok(()) } fn is_char_q(key_event: crossterm::event::KeyEvent) -> bool { key_event.code == KeyCode::Char('q') } fn is_space(key_event: crossterm::event::KeyEvent) -> bool { key_event.code == KeyCode::Char(' ') } fn is_ctrl_c(key_event: crossterm::event::KeyEvent) -> bool { key_event.code == KeyCode::Char('c') && key_event.modifiers.contains(KeyModifiers::CONTROL) } } pub(crate) fn clear_terminal() -> Result<()> { let mut stdout = stdout(); stdout.queue(terminal::Clear(terminal::ClearType::All))?; stdout.queue(terminal::Clear(terminal::ClearType::Purge))?; stdout.queue(MoveTo(0, 0))?; stdout.flush()?; Ok(()) }