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 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<str>, 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::<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 codec = LinesCodec::new();
|
||||
let mut framed = codec.framed(stream);
|
||||
let framed = codec.framed(stream);
|
||||
let (sink, stream) = framed.split::<String>();
|
||||
|
||||
listen_loop(&mut state, sink, stream).await?;
|
||||
}
|
||||
|
||||
|
@ -62,7 +59,7 @@ enum LoopExit<S, S2> {
|
|||
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
|
||||
S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
||||
S2: Sink<String> + 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<S> {
|
||||
tag_idx: usize,
|
||||
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::<()>();
|
||||
|
||||
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?;
|
||||
|
|
|
@ -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'),
|
||||
..
|
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