draw a table to the UI
This commit is contained in:
parent
c94eb56e53
commit
042c68cd95
4 changed files with 146 additions and 21 deletions
29
src/mail.rs
29
src/mail.rs
|
@ -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>>,
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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
101
src/ui/table.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue