From 042c68cd95eacc98495daf8a79fc390e3796fb0d Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Fri, 12 Feb 2021 07:21:00 -0600 Subject: [PATCH] draw a table to the UI --- src/mail.rs | 29 ++++++----- src/main.rs | 3 +- src/{ui.rs => ui/mod.rs} | 34 +++++++++++-- src/ui/table.rs | 101 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 21 deletions(-) rename src/{ui.rs => ui/mod.rs} (52%) create mode 100644 src/ui/table.rs diff --git a/src/mail.rs b/src/mail.rs index b15734d..c91eceb 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -4,31 +4,29 @@ use std::sync::Arc; use anyhow::Result; use futures::{ - future::{self, Either, FutureExt}, - pin_mut, select, + future::{self, Either}, + pin_mut, sink::{Sink, SinkExt}, - stream::{Stream, StreamExt, TryStream}, + stream::{Stream, StreamExt}, }; use imap::{ - builders::command::{Command, CommandBuilder}, + builders::command::Command, parser::parse_response, types::{Capability, RequestId, Response, ResponseCode, State, Status}, }; -use tokio::{ - net::TcpStream, - sync::{ - mpsc::{self, Receiver}, - oneshot, - }, -}; +use tokio::{net::TcpStream, sync::mpsc}; use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector}; use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError}; +pub enum MailCommand { + Raw(Command), +} + pub async fn run_mail(server: impl AsRef, port: u16) -> Result<()> { let server = server.as_ref(); let client = TcpStream::connect((server, port)).await?; let codec = LinesCodec::new(); - let mut framed = codec.framed(client); + let framed = codec.framed(client); let mut state = State::NotAuthenticated; let (sink, stream) = framed.split::(); @@ -48,9 +46,8 @@ pub async fn run_mail(server: impl AsRef, port: u16) -> Result<()> { let stream = config.connect(dnsname, stream).await?; let codec = LinesCodec::new(); - let mut framed = codec.framed(stream); + let framed = codec.framed(stream); let (sink, stream) = framed.split::(); - listen_loop(&mut state, sink, stream).await?; } @@ -62,7 +59,7 @@ enum LoopExit { Closed, } -async fn listen_loop(st: &mut State, mut sink: S2, mut stream: S) -> Result> +async fn listen_loop(st: &mut State, sink: S2, mut stream: S) -> Result> where S: Stream> + Unpin, S2: Sink + Unpin, @@ -117,6 +114,7 @@ where .await?; } } + Response::Done { tag, code, .. } => { cmd_mgr.process_done(tag, code)?; } @@ -136,6 +134,7 @@ where Ok(LoopExit::Closed) } +/// A struct in charge of managing multiple in-flight commands. struct CommandManager { tag_idx: usize, in_flight: HashMap) + Send>>, diff --git a/src/main.rs b/src/main.rs index 5fb71f8..6756524 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,7 @@ async fn main() -> Result<()> { let (exit_tx, exit_rx) = oneshot::channel::<()>(); tokio::spawn(mail::run_mail("mzhang.io", 143).unwrap_or_else(report_err)); - - let stdout = std::io::stdout(); + let mut stdout = std::io::stdout(); tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); exit_rx.await?; diff --git a/src/ui.rs b/src/ui/mod.rs similarity index 52% rename from src/ui.rs rename to src/ui/mod.rs index fea0e2b..d31de07 100644 --- a/src/ui.rs +++ b/src/ui/mod.rs @@ -1,3 +1,5 @@ +mod table; + use std::io::Write; use std::time::Duration; @@ -6,29 +8,53 @@ use chrono::Local; use crossterm::{ cursor, event::{self, Event, KeyCode, KeyEvent}, - style, terminal, + style::{self, Color}, + terminal::{self, ClearType}, }; use tokio::time; use crate::ExitSender; -const FRAME: Duration = Duration::from_millis(33); +use self::table::Table; + +const FRAME: Duration = Duration::from_millis(16); + +/// X Y W H +#[derive(Copy, Clone)] +pub struct Rect(u16, u16, u16, u16); pub async fn run_ui(mut w: impl Write, exit: ExitSender) -> Result<()> { execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?; terminal::enable_raw_mode()?; + let mut table = Table::default(); + table.push_row(vec!["ur mom Lol!".to_owned()]); + table.push_row(vec!["hek".to_owned()]); + loop { - execute!(w, cursor::MoveTo(0, 0))?; + queue!( + w, + style::SetBackgroundColor(Color::Reset), + style::SetForegroundColor(Color::Reset), + // terminal::Clear(ClearType::All), + cursor::MoveTo(0, 0), + )?; let now = Local::now(); println!("time {}", now); + let (term_width, term_height) = terminal::size()?; + let bounds = Rect(5, 5, term_width - 10, term_height - 10); + table.draw(&mut w, bounds)?; + w.flush()?; + // approx 60fps time::sleep(FRAME).await; if event::poll(FRAME)? { - match event::read()? { + let event = event::read()?; + table.update(&event); + match event { Event::Key(KeyEvent { code: KeyCode::Char('q'), .. diff --git a/src/ui/table.rs b/src/ui/table.rs new file mode 100644 index 0000000..cf06048 --- /dev/null +++ b/src/ui/table.rs @@ -0,0 +1,101 @@ +use std::io::Write; + +use anyhow::Result; +use crossterm::{ + cursor, + event::{Event, KeyCode, KeyEvent}, + style::{self, Color}, +}; + +use super::Rect; + +#[derive(Default)] +pub struct Table { + selected_row: Option, + rows: Vec>, +} + +impl Table { + pub fn update(&mut self, event: &Event) { + match event { + Event::Key(KeyEvent { code, .. }) => match code { + KeyCode::Char('j') => { + if let Some(selected_row) = &mut self.selected_row { + *selected_row = (self.rows.len() as u16 - 1).min(*selected_row + 1); + } + } + KeyCode::Char('k') => { + if let Some(selected_row) = &mut self.selected_row { + if *selected_row > 0 { + *selected_row = *selected_row - 1; + } + } + } + _ => {} + }, + _ => {} + } + } + + pub fn draw(&self, w: &mut W, rect: Rect) -> Result<()> + where + W: Write, + { + if !self.rows.is_empty() { + let mut columns = Vec::new(); + for row in self.rows.iter() { + for (i, cell) in row.iter().enumerate() { + if columns.len() == 0 || columns.len() - 1 < i { + columns.push(0); + } else { + columns[i] = cell.len().max(columns[i]); + } + } + } + + for (i, row) in self.rows.iter().enumerate() { + queue!(w, cursor::MoveTo(rect.0, rect.1 + i as u16))?; + if let Some(v) = self.selected_row { + if v == i as u16 { + queue!( + w, + style::SetBackgroundColor(Color::White), + style::SetForegroundColor(Color::Black) + )?; + } else { + queue!( + w, + style::SetForegroundColor(Color::White), + style::SetBackgroundColor(Color::Black) + )?; + } + } + let mut s = String::with_capacity(rect.2 as usize); + for (j, cell) in row.iter().enumerate() { + s += &cell; + for _ in 0..columns[j] + 1 { + s += " "; + } + } + for _ in 0..(rect.2 - s.len() as u16) { + s += " "; + } + println!("{}", s); + } + } else { + let msg = "Nothing in this table!"; + let x = rect.0 + (rect.2 - msg.len() as u16) / 2; + let y = rect.1 + rect.3 / 2; + queue!(w, cursor::MoveTo(x, y))?; + println!("{}", msg); + } + Ok(()) + } + + pub fn push_row(&mut self, row: Vec) { + self.rows.push(row); + if let None = self.selected_row { + self.selected_row = Some(0); + } + } +}