use std::fmt::Display; use std::io::{self, Stdout, Write}; use termion::event::Key; use termion::input::TermRead; use termion::raw::{IntoRawMode, RawTerminal}; use termion::{clear, cursor}; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::Mutex; use tokio::time::Instant; use self::body::{Body, SigninPage}; use self::frame::Frame; use self::theme::{Color, Theme}; pub mod body; pub mod frame; pub mod theme; type Result = std::result::Result; pub struct Screen { screen: Mutex>, events_ch: Receiver, body: Body, last_interrupt: Option, theme: Theme, } #[derive(Debug)] enum KeyEvent { Key(Key), Interrupt, } #[derive(Debug)] pub enum Event { Key(Key), } impl From for Event { fn from(value: KeyEvent) -> Self { match value { KeyEvent::Key(key) => Event::Key(key), _ => panic!( "FIXME: this event should be handled outside of children" ), } } } impl Display for Event { fn fmt( &self, f: &mut std::fmt::Formatter<'_>, ) -> std::fmt::Result { match self { Event::Key(key) => { write!( f, "{}", format!("{:#?}", key) ) } } } } impl From for KeyEvent { fn from(value: Key) -> Self { KeyEvent::Key(value) } } pub trait Page { fn init( &mut self, env: Environment, screen: &mut RawTerminal, ) -> Result<()>; fn receive_event( &mut self, env: Environment, screen: &mut RawTerminal, event: Event, ) -> Result<()>; } #[derive(Debug, Clone, Copy)] pub struct Environment { pub theme: Theme, pub frame: Frame, } impl Into for Environment { fn into(self) -> String { self.frame.frame_str(self.theme.primary()) } } impl Environment { fn initial_must(theme: Theme) -> Self { let w: u16 = std::env::var("FRAME_WIDTH") .map(|f| f.parse().unwrap_or(1)) .unwrap_or(1); Self { theme, frame: Frame::from_terminal_size(theme.colors.frame, w) .expect("could not get terminal size"), } } #[inline(always)] pub fn primary_frame(&self) -> String { self.frame.frame_str(self.theme.primary()) } } impl Screen { pub fn new(theme: Theme) -> Result { let screen = Mutex::new(io::stdout().into_raw_mode()?); let (send, recv) = mpsc::channel::(16); tokio::spawn(async { Screen::input_loop(send).await }); let body = Body::Signin(SigninPage::default()); // let body = Body::Echo; Ok(Self { screen, body, theme, events_ch: recv, last_interrupt: None, }) } pub async fn start(mut self) -> Result<()> { let env = Environment::initial_must(self.theme); { let mut scr = self.screen.lock().await; self.body.init(env, &mut scr)?; } while let Some(ev) = self.events_ch.recv().await { let mut scr = self.screen.lock().await; match ev { KeyEvent::Interrupt => { if let Some(last) = self.last_interrupt { if last.elapsed().as_millis() < 500 { self.events_ch.close(); break; } } self.last_interrupt = Some(Instant::now()); } KeyEvent::Key(key) => self.body.receive_event( env, &mut scr, Event::Key(key), )?, } } // Cleanup let mut scr = self.screen.lock().await; write!( scr, "{color}{clear}{show_cursor}{move_to_start}", color = Into::::into("#000000").bg_string(), move_to_start = cursor::Goto(1, 1), clear = clear::All, show_cursor = cursor::Show, )?; scr.flush(); Ok(()) } async fn input_loop(snd: Sender) { let input = io::stdin(); for key in input.keys() { let event = match key.unwrap() { Key::Ctrl(sub) => match sub { 'c' => KeyEvent::Interrupt, key => KeyEvent::Key(Key::Ctrl(key)), }, key => key.into(), }; snd.send(event).await.unwrap(); } } }