gonna just plop tui.rs in here
This commit is contained in:
parent
b3b137b86a
commit
09eafd0f3f
10 changed files with 315 additions and 144 deletions
85
Cargo.lock
generated
85
Cargo.lock
generated
|
@ -279,6 +279,12 @@ version = "1.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "cast5"
|
||||
version = "0.8.0"
|
||||
|
@ -434,31 +440,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.0.2"
|
||||
|
@ -1189,6 +1170,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
|
@ -1250,7 +1237,6 @@ dependencies = [
|
|||
"async-trait",
|
||||
"cfg-if 1.0.0",
|
||||
"chrono",
|
||||
"crossterm",
|
||||
"fern",
|
||||
"format-bytes",
|
||||
"futures",
|
||||
|
@ -1265,11 +1251,13 @@ dependencies = [
|
|||
"rustls-connector",
|
||||
"serde",
|
||||
"structopt",
|
||||
"termion",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"tui",
|
||||
"webpki-roots",
|
||||
"xdg",
|
||||
]
|
||||
|
@ -1634,6 +1622,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "redox_users"
|
||||
version = "0.3.5"
|
||||
|
@ -1899,17 +1896,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.3.0"
|
||||
|
@ -2087,6 +2073,18 @@ dependencies = [
|
|||
"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]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
|
@ -2215,6 +2213,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "twofish"
|
||||
version = "0.4.0"
|
||||
|
|
|
@ -12,11 +12,11 @@ license = "GPL-3.0-or-later"
|
|||
members = ["imap", "smtp"]
|
||||
|
||||
[dependencies]
|
||||
# crossterm = "0.19.0"
|
||||
anyhow = "1.0.38"
|
||||
async-trait = "0.1.42"
|
||||
cfg-if = "1.0.0"
|
||||
chrono = "0.4.19"
|
||||
crossterm = "0.19.0"
|
||||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
format-bytes = "0.2.0"
|
||||
futures = "0.3.13"
|
||||
|
@ -34,8 +34,11 @@ tokio-rustls = "0.22.0"
|
|||
tokio-stream = { version = "0.1.3", features = ["sync"] }
|
||||
tokio-util = { version = "0.6.3", features = ["full"] }
|
||||
toml = "0.5.8"
|
||||
# tui = { version = "0.14.0", default-features = false, features = ["crossterm"] }
|
||||
webpki-roots = "0.21.0"
|
||||
xdg = "2.2.0"
|
||||
tui = "0.14.0"
|
||||
termion = "1.5.6"
|
||||
|
||||
[dependencies.panorama-imap]
|
||||
path = "imap"
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate crossterm;
|
||||
// #[macro_use]
|
||||
// extern crate crossterm;
|
||||
#[macro_use]
|
||||
extern crate format_bytes;
|
||||
#[macro_use]
|
||||
|
|
95
src/ui/events.rs
Normal file
95
src/ui/events.rs
Normal 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);
|
||||
}
|
||||
}
|
147
src/ui/mod.rs
147
src/ui/mod.rs
|
@ -1,121 +1,62 @@
|
|||
//! UI module
|
||||
//! UI library
|
||||
|
||||
mod drawbuf;
|
||||
mod table;
|
||||
mod tabs;
|
||||
mod widget;
|
||||
mod events;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::io::{Stdout, Write};
|
||||
use std::io::Stdout;
|
||||
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 termion::{event::Key, screen::AlternateScreen};
|
||||
use tokio::sync::mpsc;
|
||||
use tui::{
|
||||
backend::TermionBackend,
|
||||
layout::{Constraint, Direction, Layout},
|
||||
widgets::{Block, Borders, Widget},
|
||||
Terminal,
|
||||
};
|
||||
use tokio::time;
|
||||
|
||||
use crate::ExitSender;
|
||||
use self::events::{Config, Event, Events};
|
||||
|
||||
use self::drawbuf::DrawBuf;
|
||||
use self::table::Table;
|
||||
use self::tabs::Tabs;
|
||||
use self::widget::Widget;
|
||||
/// Main entrypoint for the UI
|
||||
pub async fn run_ui(stdout: Stdout, exit_tx: mpsc::Sender<()>) -> Result<()> {
|
||||
let stdout = AlternateScreen::from(stdout);
|
||||
let backend = TermionBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
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);
|
||||
let events = Events::with_config(Config {
|
||||
tick_rate: Duration::from_millis(17),
|
||||
..Config::default()
|
||||
});
|
||||
|
||||
loop {
|
||||
queue!(
|
||||
w,
|
||||
SetBackgroundColor(Color::Reset),
|
||||
SetForegroundColor(Color::Reset),
|
||||
Clear(ClearType::All),
|
||||
MoveTo(0, 0),
|
||||
)?;
|
||||
terminal.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints(
|
||||
[
|
||||
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();
|
||||
// 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
|
||||
{
|
||||
if let Event::Input(input) = events.next()? {
|
||||
match input {
|
||||
Key::Char('q') => {
|
||||
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(())
|
||||
}
|
||||
|
|
121
src/ui_custom/mod.rs
Normal file
121
src/ui_custom/mod.rs
Normal 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(())
|
||||
}
|
Loading…
Reference in a new issue