draw a table to the UI

This commit is contained in:
Michael Zhang 2021-02-12 07:21:00 -06:00
parent c94eb56e53
commit 042c68cd95
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
4 changed files with 146 additions and 21 deletions

View file

@ -4,31 +4,29 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use futures::{ use futures::{
future::{self, Either, FutureExt}, future::{self, Either},
pin_mut, select, pin_mut,
sink::{Sink, SinkExt}, sink::{Sink, SinkExt},
stream::{Stream, StreamExt, TryStream}, stream::{Stream, StreamExt},
}; };
use imap::{ use imap::{
builders::command::{Command, CommandBuilder}, builders::command::Command,
parser::parse_response, parser::parse_response,
types::{Capability, RequestId, Response, ResponseCode, State, Status}, types::{Capability, RequestId, Response, ResponseCode, State, Status},
}; };
use tokio::{ use tokio::{net::TcpStream, sync::mpsc};
net::TcpStream,
sync::{
mpsc::{self, Receiver},
oneshot,
},
};
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector}; use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError}; use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
pub enum MailCommand {
Raw(Command),
}
pub async fn run_mail(server: impl AsRef<str>, port: u16) -> Result<()> { pub async fn run_mail(server: impl AsRef<str>, port: u16) -> Result<()> {
let server = server.as_ref(); let server = server.as_ref();
let client = TcpStream::connect((server, port)).await?; let client = TcpStream::connect((server, port)).await?;
let codec = LinesCodec::new(); let codec = LinesCodec::new();
let mut framed = codec.framed(client); let framed = codec.framed(client);
let mut state = State::NotAuthenticated; let mut state = State::NotAuthenticated;
let (sink, stream) = framed.split::<String>(); let (sink, stream) = framed.split::<String>();
@ -48,9 +46,8 @@ pub async fn run_mail(server: impl AsRef<str>, port: u16) -> Result<()> {
let stream = config.connect(dnsname, stream).await?; let stream = config.connect(dnsname, stream).await?;
let codec = LinesCodec::new(); let codec = LinesCodec::new();
let mut framed = codec.framed(stream); let framed = codec.framed(stream);
let (sink, stream) = framed.split::<String>(); let (sink, stream) = framed.split::<String>();
listen_loop(&mut state, sink, stream).await?; listen_loop(&mut state, sink, stream).await?;
} }
@ -62,7 +59,7 @@ enum LoopExit<S, S2> {
Closed, Closed,
} }
async fn listen_loop<S, S2>(st: &mut State, mut sink: S2, mut stream: S) -> Result<LoopExit<S, S2>> async fn listen_loop<S, S2>(st: &mut State, sink: S2, mut stream: S) -> Result<LoopExit<S, S2>>
where where
S: Stream<Item = Result<String, LinesCodecError>> + Unpin, S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
S2: Sink<String> + Unpin, S2: Sink<String> + Unpin,
@ -117,6 +114,7 @@ where
.await?; .await?;
} }
} }
Response::Done { tag, code, .. } => { Response::Done { tag, code, .. } => {
cmd_mgr.process_done(tag, code)?; cmd_mgr.process_done(tag, code)?;
} }
@ -136,6 +134,7 @@ where
Ok(LoopExit::Closed) Ok(LoopExit::Closed)
} }
/// A struct in charge of managing multiple in-flight commands.
struct CommandManager<S> { struct CommandManager<S> {
tag_idx: usize, tag_idx: usize,
in_flight: HashMap<String, Box<dyn Fn(Option<ResponseCode>) + Send>>, in_flight: HashMap<String, Box<dyn Fn(Option<ResponseCode>) + Send>>,

View file

@ -21,8 +21,7 @@ async fn main() -> Result<()> {
let (exit_tx, exit_rx) = oneshot::channel::<()>(); let (exit_tx, exit_rx) = oneshot::channel::<()>();
tokio::spawn(mail::run_mail("mzhang.io", 143).unwrap_or_else(report_err)); tokio::spawn(mail::run_mail("mzhang.io", 143).unwrap_or_else(report_err));
let mut stdout = std::io::stdout();
let stdout = std::io::stdout();
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
exit_rx.await?; exit_rx.await?;

View file

@ -1,3 +1,5 @@
mod table;
use std::io::Write; use std::io::Write;
use std::time::Duration; use std::time::Duration;
@ -6,29 +8,53 @@ use chrono::Local;
use crossterm::{ use crossterm::{
cursor, cursor,
event::{self, Event, KeyCode, KeyEvent}, event::{self, Event, KeyCode, KeyEvent},
style, terminal, style::{self, Color},
terminal::{self, ClearType},
}; };
use tokio::time; use tokio::time;
use crate::ExitSender; 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<()> { pub async fn run_ui(mut w: impl Write, exit: ExitSender) -> Result<()> {
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?; execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?; 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 { 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(); let now = Local::now();
println!("time {}", 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 // approx 60fps
time::sleep(FRAME).await; time::sleep(FRAME).await;
if event::poll(FRAME)? { if event::poll(FRAME)? {
match event::read()? { let event = event::read()?;
table.update(&event);
match event {
Event::Key(KeyEvent { Event::Key(KeyEvent {
code: KeyCode::Char('q'), code: KeyCode::Char('q'),
.. ..

101
src/ui/table.rs Normal file
View file

@ -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<u16>,
rows: Vec<Vec<String>>,
}
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<W>(&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<String>) {
self.rows.push(row);
if let None = self.selected_row {
self.selected_row = Some(0);
}
}
}