gonna just plop tui.rs in here

This commit is contained in:
Michael Zhang 2021-03-01 02:20:21 -06:00
parent b3b137b86a
commit 09eafd0f3f
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
10 changed files with 315 additions and 144 deletions

85
Cargo.lock generated
View file

@ -279,6 +279,12 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
[[package]]
name = "cassowary"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cast5" name = "cast5"
version = "0.8.0" version = "0.8.0"
@ -434,31 +440,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "crossterm"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c"
dependencies = [
"bitflags 1.2.1",
"crossterm_winapi",
"lazy_static",
"libc",
"mio",
"parking_lot",
"signal-hook",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "3.0.2" version = "3.0.2"
@ -1189,6 +1170,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]] [[package]]
name = "objc" name = "objc"
version = "0.2.7" version = "0.2.7"
@ -1250,7 +1237,6 @@ dependencies = [
"async-trait", "async-trait",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"chrono", "chrono",
"crossterm",
"fern", "fern",
"format-bytes", "format-bytes",
"futures", "futures",
@ -1265,11 +1251,13 @@ dependencies = [
"rustls-connector", "rustls-connector",
"serde", "serde",
"structopt", "structopt",
"termion",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-stream", "tokio-stream",
"tokio-util", "tokio-util",
"toml", "toml",
"tui",
"webpki-roots", "webpki-roots",
"xdg", "xdg",
] ]
@ -1634,6 +1622,15 @@ dependencies = [
"bitflags 1.2.1", "bitflags 1.2.1",
] ]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall 0.2.5",
]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.3.5" version = "0.3.5"
@ -1899,17 +1896,6 @@ dependencies = [
"opaque-debug 0.3.0", "opaque-debug 0.3.0",
] ]
[[package]]
name = "signal-hook"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
dependencies = [
"libc",
"mio",
"signal-hook-registry",
]
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.3.0" version = "1.3.0"
@ -2087,6 +2073,18 @@ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall 0.2.5",
"redox_termios",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -2215,6 +2213,19 @@ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
] ]
[[package]]
name = "tui"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
dependencies = [
"bitflags 1.2.1",
"cassowary",
"termion",
"unicode-segmentation",
"unicode-width",
]
[[package]] [[package]]
name = "twofish" name = "twofish"
version = "0.4.0" version = "0.4.0"

View file

@ -12,11 +12,11 @@ license = "GPL-3.0-or-later"
members = ["imap", "smtp"] members = ["imap", "smtp"]
[dependencies] [dependencies]
# crossterm = "0.19.0"
anyhow = "1.0.38" anyhow = "1.0.38"
async-trait = "0.1.42" async-trait = "0.1.42"
cfg-if = "1.0.0" cfg-if = "1.0.0"
chrono = "0.4.19" chrono = "0.4.19"
crossterm = "0.19.0"
fern = { version = "0.6.0", features = ["colored"] } fern = { version = "0.6.0", features = ["colored"] }
format-bytes = "0.2.0" format-bytes = "0.2.0"
futures = "0.3.13" futures = "0.3.13"
@ -34,8 +34,11 @@ tokio-rustls = "0.22.0"
tokio-stream = { version = "0.1.3", features = ["sync"] } tokio-stream = { version = "0.1.3", features = ["sync"] }
tokio-util = { version = "0.6.3", features = ["full"] } tokio-util = { version = "0.6.3", features = ["full"] }
toml = "0.5.8" toml = "0.5.8"
# tui = { version = "0.14.0", default-features = false, features = ["crossterm"] }
webpki-roots = "0.21.0" webpki-roots = "0.21.0"
xdg = "2.2.0" xdg = "2.2.0"
tui = "0.14.0"
termion = "1.5.6"
[dependencies.panorama-imap] [dependencies.panorama-imap]
path = "imap" path = "imap"

View file

@ -8,8 +8,8 @@
#[macro_use] #[macro_use]
extern crate anyhow; extern crate anyhow;
#[macro_use] // #[macro_use]
extern crate crossterm; // extern crate crossterm;
#[macro_use] #[macro_use]
extern crate format_bytes; extern crate format_bytes;
#[macro_use] #[macro_use]

95
src/ui/events.rs Normal file
View file

@ -0,0 +1,95 @@
use std::io;
use std::sync::mpsc;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread;
use std::time::Duration;
use termion::event::Key;
use termion::input::TermRead;
pub enum Event<I> {
Input(I),
Tick,
}
/// A small event handler that wrap termion input and tick events. Each event
/// type is handled in its own thread and returned to a common `Receiver`
pub struct Events {
rx: mpsc::Receiver<Event<Key>>,
input_handle: thread::JoinHandle<()>,
ignore_exit_key: Arc<AtomicBool>,
tick_handle: thread::JoinHandle<()>,
}
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub exit_key: Key,
pub tick_rate: Duration,
}
impl Default for Config {
fn default() -> Config {
Config {
exit_key: Key::Char('q'),
tick_rate: Duration::from_millis(250),
}
}
}
impl Events {
pub fn new() -> Events {
Events::with_config(Config::default())
}
pub fn with_config(config: Config) -> Events {
let (tx, rx) = mpsc::channel();
let ignore_exit_key = Arc::new(AtomicBool::new(false));
let input_handle = {
let tx = tx.clone();
let ignore_exit_key = ignore_exit_key.clone();
thread::spawn(move || {
let stdin = io::stdin();
for evt in stdin.keys() {
if let Ok(key) = evt {
if let Err(err) = tx.send(Event::Input(key)) {
eprintln!("{}", err);
return;
}
if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
return;
}
}
}
})
};
let tick_handle = {
thread::spawn(move || loop {
if tx.send(Event::Tick).is_err() {
break;
}
thread::sleep(config.tick_rate);
})
};
Events {
rx,
ignore_exit_key,
input_handle,
tick_handle,
}
}
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
pub fn disable_exit_key(&mut self) {
self.ignore_exit_key.store(true, Ordering::Relaxed);
}
pub fn enable_exit_key(&mut self) {
self.ignore_exit_key.store(false, Ordering::Relaxed);
}
}

View file

@ -1,121 +1,62 @@
//! UI module //! UI library
mod drawbuf; mod events;
mod table;
mod tabs;
mod widget;
use std::fmt::Debug; use std::io::Stdout;
use std::io::{Stdout, Write};
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use chrono::Local; use termion::{event::Key, screen::AlternateScreen};
use crossterm::{ use tokio::sync::mpsc;
cursor::{self, MoveTo}, use tui::{
event::{self, Event, KeyCode, KeyEvent}, backend::TermionBackend,
style::{self, Color, SetBackgroundColor, SetForegroundColor}, layout::{Constraint, Direction, Layout},
terminal::{self, Clear, ClearType}, widgets::{Block, Borders, Widget},
Terminal,
}; };
use tokio::time;
use crate::ExitSender; use self::events::{Config, Event, Events};
use self::drawbuf::DrawBuf; /// Main entrypoint for the UI
use self::table::Table; pub async fn run_ui(stdout: Stdout, exit_tx: mpsc::Sender<()>) -> Result<()> {
use self::tabs::Tabs; let stdout = AlternateScreen::from(stdout);
use self::widget::Widget; let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
const FRAME_DURATION: Duration = Duration::from_millis(20); let events = Events::with_config(Config {
tick_rate: Duration::from_millis(17),
/// Type alias for the screen object we're drawing to ..Config::default()
pub type Screen = Stdout; });
/// Rectangle
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub w: u16,
pub h: u16,
}
impl Rect {
/// Construct a new rectangle from (x, y) and (w, h)
pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self {
Rect { x, y, w, h }
}
}
/// UI entrypoint.
pub async fn run_ui(mut w: Stdout, exit: ExitSender) -> Result<()> {
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?;
let (term_width, term_height) = terminal::size()?;
let bounds = Rect::new(0, 0, term_width, term_height);
let mut drawbuf = DrawBuf::new(bounds);
let mut table = Table::default();
table.push_row(vec!["ur mom Lol!".to_owned()]);
table.push_row(vec!["hek".to_owned()]);
let mut tabs = Tabs::new();
tabs.add_tab("Mail", table);
loop { loop {
queue!( terminal.draw(|f| {
w, let chunks = Layout::default()
SetBackgroundColor(Color::Reset), .direction(Direction::Vertical)
SetForegroundColor(Color::Reset), .margin(1)
Clear(ClearType::All), .constraints(
MoveTo(0, 0), [
)?; Constraint::Percentage(10),
Constraint::Percentage(80),
Constraint::Percentage(10),
]
.as_ref(),
)
.split(f.size());
let block = Block::default().title("Block").borders(Borders::ALL);
f.render_widget(block, chunks[0]);
let block = Block::default().title("Block 2").borders(Borders::ALL);
f.render_widget(block, chunks[1]);
})?;
// let now = Local::now(); if let Event::Input(input) = events.next()? {
// println!("time {}", now); match input {
Key::Char('q') => {
let (term_width, term_height) = terminal::size()?;
let bounds = Rect::new(5, 5, term_width - 10, term_height - 10);
// table.draw(&mut w, bounds)?;
// tabs.draw(&mut w, bounds)?;
drawbuf.draw(&mut w)?;
w.flush()?;
// approx 60fps
time::sleep(FRAME_DURATION).await;
// check to see if there's even an event this frame. otherwise, just keep going
let event = if event::poll(FRAME_DURATION)? {
let event = event::read()?;
// table.update(&event);
if let Event::Key(KeyEvent {
code: KeyCode::Char('q'),
..
}) = event
{
break; break;
} }
_ => {}
Some(event) }
} else { }
None
};
tabs.update(event);
} }
execute!(
w,
style::ResetColor,
cursor::Show,
terminal::LeaveAlternateScreen
)?;
terminal::disable_raw_mode()?;
exit.send(()).await?;
debug!("sent exit");
Ok(()) Ok(())
} }

121
src/ui_custom/mod.rs Normal file
View file

@ -0,0 +1,121 @@
//! UI module
mod drawbuf;
mod table;
mod tabs;
mod widget;
use std::fmt::Debug;
use std::io::{Stdout, Write};
use std::time::Duration;
use anyhow::Result;
use chrono::Local;
use crossterm::{
cursor::{self, MoveTo},
event::{self, Event, KeyCode, KeyEvent},
style::{self, Color, SetBackgroundColor, SetForegroundColor},
terminal::{self, Clear, ClearType},
};
use tokio::time;
use crate::ExitSender;
use self::drawbuf::DrawBuf;
use self::table::Table;
use self::tabs::Tabs;
use self::widget::Widget;
const FRAME_DURATION: Duration = Duration::from_millis(20);
/// Type alias for the screen object we're drawing to
pub type Screen = Stdout;
/// Rectangle
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub w: u16,
pub h: u16,
}
impl Rect {
/// Construct a new rectangle from (x, y) and (w, h)
pub fn new(x: u16, y: u16, w: u16, h: u16) -> Self {
Rect { x, y, w, h }
}
}
/// UI entrypoint.
pub async fn run_ui(mut w: Stdout, exit: ExitSender) -> Result<()> {
execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?;
let (term_width, term_height) = terminal::size()?;
let bounds = Rect::new(0, 0, term_width, term_height);
let mut drawbuf = DrawBuf::new(bounds);
let mut table = Table::default();
table.push_row(vec!["ur mom Lol!".to_owned()]);
table.push_row(vec!["hek".to_owned()]);
let mut tabs = Tabs::new();
tabs.add_tab("Mail", table);
loop {
queue!(
w,
SetBackgroundColor(Color::Reset),
SetForegroundColor(Color::Reset),
Clear(ClearType::All),
MoveTo(0, 0),
)?;
// let now = Local::now();
// println!("time {}", now);
let (term_width, term_height) = terminal::size()?;
let bounds = Rect::new(5, 5, term_width - 10, term_height - 10);
// table.draw(&mut w, bounds)?;
// tabs.draw(&mut w, bounds)?;
drawbuf.draw(&mut w)?;
w.flush()?;
// approx 60fps
time::sleep(FRAME_DURATION).await;
// check to see if there's even an event this frame. otherwise, just keep going
let event = if event::poll(FRAME_DURATION)? {
let event = event::read()?;
// table.update(&event);
if let Event::Key(KeyEvent {
code: KeyCode::Char('q'),
..
}) = event
{
break;
}
Some(event)
} else {
None
};
tabs.update(event);
}
execute!(
w,
style::ResetColor,
cursor::Show,
terminal::LeaveAlternateScreen
)?;
terminal::disable_raw_mode()?;
exit.send(()).await?;
debug!("sent exit");
Ok(())
}