Remove ncurses, add termion
parent
dbda703bcb
commit
ba8508b987
|
@ -18,13 +18,14 @@ config = "0.6"
|
||||||
serde_derive = "^1.0.8"
|
serde_derive = "^1.0.8"
|
||||||
serde = "^1.0.8"
|
serde = "^1.0.8"
|
||||||
nom = "3.2.0"
|
nom = "3.2.0"
|
||||||
memmap = "*"
|
memmap = "0.5.2"
|
||||||
base64 = "*"
|
base64 = "*"
|
||||||
crossbeam = "^0.3.0"
|
crossbeam = "^0.3.0"
|
||||||
fnv = "1.0.3"
|
fnv = "1.0.3"
|
||||||
encoding = "0.2.33"
|
encoding = "0.2.33"
|
||||||
bitflags = "1.0"
|
bitflags = "1.0"
|
||||||
notify = "4.0.1"
|
notify = "4.0.1"
|
||||||
|
termion = "1.5.1"
|
||||||
|
|
||||||
[dependencies.ncurses]
|
[dependencies.ncurses]
|
||||||
features = ["wide"]
|
features = ["wide"]
|
||||||
|
|
144
src/bin.rs
144
src/bin.rs
|
@ -20,39 +20,40 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
use ui::index::*;
|
use ui::*;
|
||||||
use ui::ThreadEvent;
|
|
||||||
|
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
|
extern crate termion;
|
||||||
use melib::*;
|
use melib::*;
|
||||||
|
|
||||||
extern crate ncurses;
|
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
|
||||||
|
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::io::{stdout, stdin, };
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
ncurses::setlocale(ncurses::LcCategory::all, "en_US.UTF-8");
|
/* Lock all stdios */
|
||||||
|
let _stdout = stdout();
|
||||||
|
let mut _stdout = _stdout.lock();
|
||||||
|
let stdin = stdin();
|
||||||
|
let stdin = stdin;
|
||||||
|
/*
|
||||||
|
let _stderr = stderr();
|
||||||
|
let mut _stderr = _stderr.lock();
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
let set = Settings::new();
|
let set = Settings::new();
|
||||||
let ui = ui::TUI::initialize();
|
|
||||||
let backends = Backends::new();
|
let backends = Backends::new();
|
||||||
|
|
||||||
let (sender, receiver): (SyncSender<ThreadEvent>, Receiver<ThreadEvent>) =
|
let (sender, receiver): (SyncSender<ThreadEvent>, Receiver<ThreadEvent>) = sync_channel(::std::mem::size_of::<ThreadEvent>());
|
||||||
sync_channel(::std::mem::size_of::<ThreadEvent>());
|
|
||||||
{
|
{
|
||||||
let sender = sender.clone();
|
let sender = sender.clone();
|
||||||
let mut ch = None;
|
thread::Builder::new().name("input-thread".to_string()).spawn(move || {
|
||||||
thread::Builder::new()
|
get_events(stdin, |k| { sender.send(ThreadEvent::Input(k)).unwrap(); })
|
||||||
.name("input-thread".to_string())
|
}).unwrap();
|
||||||
.spawn(move || loop {
|
|
||||||
ch = ncurses::get_wch();
|
|
||||||
if let Some(k) = ch {
|
|
||||||
sender.send(ThreadEvent::Input(k)).unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4");
|
||||||
let mut j = 0;
|
let mut j = 0;
|
||||||
let folder_length = set.accounts["norn"].folders.len();
|
let folder_length = set.accounts["norn"].folders.len();
|
||||||
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone(), backends);
|
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone(), backends);
|
||||||
|
@ -62,63 +63,80 @@ fn main() {
|
||||||
sender.send(ThreadEvent::from(r)).unwrap();
|
sender.send(ThreadEvent::from(r)).unwrap();
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
'main: loop {
|
|
||||||
ncurses::touchwin(ncurses::stdscr());
|
|
||||||
ncurses::mv(0, 0);
|
|
||||||
let mailbox = &mut account[j];
|
|
||||||
let mut index: Box<Window> = match *mailbox.as_ref().unwrap() {
|
|
||||||
Ok(ref v) => Box::new(Index::new(v)),
|
|
||||||
Err(ref v) => Box::new(ErrorWindow::new((*v).clone())),
|
|
||||||
};
|
|
||||||
//eprintln!("{:?}", set);
|
|
||||||
ncurses::refresh();
|
|
||||||
|
|
||||||
index.draw();
|
|
||||||
|
|
||||||
|
|
||||||
|
let mut state = State::new(_stdout);
|
||||||
|
|
||||||
|
let a = Entity {component: Box::new(TextBox::new("a text box".to_string())) };
|
||||||
|
let listing = MailListing::new(Mailbox::new_dummy());
|
||||||
|
let b = Entity { component: Box::new(listing) };
|
||||||
|
let window = Entity { component: Box::new(VSplit::new(a,b,90)) };
|
||||||
|
state.register_entity(window);
|
||||||
|
state.render();
|
||||||
|
'main: loop {
|
||||||
|
let mailbox = &mut account[j];
|
||||||
|
//let mut index: Box<Window> = match *mailbox.as_ref().unwrap() {
|
||||||
|
// Ok(ref v) => Box::new(Index::new(v)),
|
||||||
|
// Err(ref v) => Box::new(ErrorWindow::new((*v).clone())),
|
||||||
|
//};
|
||||||
|
////eprintln!("{:?}", set);
|
||||||
|
match *mailbox.as_ref().unwrap() {
|
||||||
|
Ok(ref v) => {
|
||||||
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox(v.clone()) });
|
||||||
|
},
|
||||||
|
Err(_) => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
//index.draw();
|
||||||
|
//
|
||||||
|
state.render();
|
||||||
|
|
||||||
'inner: loop {
|
'inner: loop {
|
||||||
match receiver.recv().unwrap() {
|
match receiver.recv().unwrap() {
|
||||||
ThreadEvent::Input(k) => match k {
|
ThreadEvent::Input(k) => {
|
||||||
ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)
|
match k {
|
||||||
| ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN) => {
|
key @ Key::Char('j') | key @ Key::Char('k') => {
|
||||||
index.handle_input(k);
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
||||||
continue;
|
state.render();
|
||||||
}
|
},
|
||||||
ncurses::WchResult::Char(k @ 10) => {
|
key @ Key::Up | key @ Key::Down => {
|
||||||
index.handle_input(k as i32);
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
|
||||||
continue;
|
state.render();
|
||||||
}
|
}
|
||||||
ncurses::WchResult::KeyCode(k @ ncurses::KEY_F1) => {
|
Key::Char('\n') => {
|
||||||
if !index.handle_input(k) {
|
// index.handle_input(k);
|
||||||
break 'main;
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))});
|
||||||
|
state.render();
|
||||||
}
|
}
|
||||||
}
|
Key::Char('i') | Key::Esc => {
|
||||||
ncurses::WchResult::Char(113) => {
|
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)});
|
||||||
break 'main;
|
state.render();
|
||||||
}
|
}
|
||||||
ncurses::WchResult::Char(74) => {
|
Key::F(_) => {
|
||||||
if j < folder_length - 1 {
|
// if !index.handle_input(k) {
|
||||||
|
// break 'main;
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
Key::Char('q') | Key::Char('Q') => {
|
||||||
|
break 'main;
|
||||||
|
},
|
||||||
|
Key::Char('J') => if j < folder_length - 1 {
|
||||||
j += 1;
|
j += 1;
|
||||||
break 'inner;
|
break 'inner;
|
||||||
}
|
},
|
||||||
}
|
Key::Char('K') => if j > 0 {
|
||||||
ncurses::WchResult::Char(75) => {
|
|
||||||
if j > 0 {
|
|
||||||
j -= 1;
|
j -= 1;
|
||||||
break 'inner;
|
break 'inner;
|
||||||
}
|
},
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE) => {
|
|
||||||
eprintln!("key_resize");
|
|
||||||
index.redraw();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
},
|
||||||
ThreadEvent::RefreshMailbox { name: n } => {
|
ThreadEvent::RefreshMailbox { name : n } => {
|
||||||
eprintln!("Refresh mailbox {}", n);
|
eprintln!("Refresh mailbox {}", n);
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(ui);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,14 @@ pub struct Mailbox {
|
||||||
|
|
||||||
|
|
||||||
impl Mailbox {
|
impl Mailbox {
|
||||||
|
pub fn new_dummy() -> Self {
|
||||||
|
Mailbox {
|
||||||
|
path: String::default(),
|
||||||
|
collection: Vec::with_capacity(0),
|
||||||
|
threaded_collection: Vec::with_capacity(0),
|
||||||
|
threads: Vec::with_capacity(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>, collection: Result<Vec<Envelope>>) -> Result<Mailbox> {
|
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>, collection: Result<Vec<Envelope>>) -> Result<Mailbox> {
|
||||||
let mut collection: Vec<Envelope> = collection?;
|
let mut collection: Vec<Envelope> = collection?;
|
||||||
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
|
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
|
@ -1,7 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "melt_ui"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
termion = "1.5.1"
|
|
|
@ -1,40 +0,0 @@
|
||||||
extern crate melt_ui;
|
|
||||||
extern crate termion;
|
|
||||||
|
|
||||||
use melt_ui::{State, Entity, BoxPanel, HSplit, VSplit, TextBox, MailListing};
|
|
||||||
use termion::raw::IntoRawMode;
|
|
||||||
|
|
||||||
use std::io::{stdout, stdin, stderr};
|
|
||||||
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
/* Lock all stdios */
|
|
||||||
let _stdout = stdout();
|
|
||||||
let mut _stdout = _stdout.lock();
|
|
||||||
let stdin = stdin();
|
|
||||||
let stdin = stdin.lock();
|
|
||||||
let _stderr = stderr();
|
|
||||||
let mut _stderr = _stderr.lock();
|
|
||||||
let mut s = State::new(_stdout.into_raw_mode().unwrap(), stdin);
|
|
||||||
//s.hello_w();
|
|
||||||
|
|
||||||
// let ent = Entity { width: 30, height: 30, margin_top: 0, margin_left: 0, component: Box::new(BoxPanel{}) };
|
|
||||||
// s.register_entity(ent);
|
|
||||||
|
|
||||||
let a = Entity {component: Box::new(TextBox::new("a text box".to_string())) };
|
|
||||||
//let b = Entity { component: Box::new(TextBox::new("b text box".to_string())) };
|
|
||||||
let l = Entity { component: Box::new(TextBox::new("left text box".to_string())) };
|
|
||||||
let r = Entity { component: Box::new(TextBox::new("right text box".to_string())) };
|
|
||||||
let b = Entity { component: Box::new(VSplit::new(l,r,50)) };
|
|
||||||
let top = Entity { component: Box::new(HSplit::new(a, b, 70)) };
|
|
||||||
//let bottom = Entity { component: Box::new(TextBox::new("hello world.".to_string())) };
|
|
||||||
let bottom = Entity { component: Box::new(MailListing::new(200)) };
|
|
||||||
let ent = Entity { component: Box::new(HSplit::new(top, bottom, 50)) };
|
|
||||||
//
|
|
||||||
s.register_entity(ent);
|
|
||||||
s.render();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,267 +0,0 @@
|
||||||
use std::fmt;
|
|
||||||
use cells::{Color, Cell, CellBuffer, CellAccessor};
|
|
||||||
use position::Pos;
|
|
||||||
|
|
||||||
/// The upper and lower boundary char.
|
|
||||||
const HORZ_BOUNDARY: char = 'β';
|
|
||||||
/// The left and right boundary char.
|
|
||||||
const VERT_BOUNDARY: char = 'β';
|
|
||||||
|
|
||||||
/// The top-left corner
|
|
||||||
const TOP_LEFT_CORNER: char = 'β';
|
|
||||||
/// The top-right corner
|
|
||||||
const TOP_RIGHT_CORNER: char = 'β';
|
|
||||||
/// The bottom-left corner
|
|
||||||
const BOTTOM_LEFT_CORNER: char = 'β';
|
|
||||||
/// The bottom-right corner
|
|
||||||
const BOTTOM_RIGHT_CORNER: char = 'β';
|
|
||||||
|
|
||||||
const LIGHT_VERTICAL_AND_RIGHT: char = 'β';
|
|
||||||
|
|
||||||
const LIGHT_VERTICAL_AND_LEFT: char = 'β€';
|
|
||||||
|
|
||||||
const LIGHT_DOWN_AND_HORIZONTAL: char = 'β¬';
|
|
||||||
|
|
||||||
const LIGHT_UP_AND_HORIZONTAL: char = 'β΄';
|
|
||||||
|
|
||||||
pub struct Entity {
|
|
||||||
//queue: VecDeque,
|
|
||||||
pub component: Box<Component>, // more than one?
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Entity {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "Entity", )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Component {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer);
|
|
||||||
fn process_event(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///A simple box with borders and no content.
|
|
||||||
pub struct BoxPanel {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for BoxPanel {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) {
|
|
||||||
grid[upper_left].set_ch('u');
|
|
||||||
grid[bottom_right].set_ch('b');
|
|
||||||
let width = bottom_right.0 - upper_left.0;
|
|
||||||
let height = bottom_right.1 - upper_left.1;
|
|
||||||
|
|
||||||
grid[upper_left].set_ch('β');
|
|
||||||
grid[(upper_left.0, bottom_right.1)].set_ch(BOTTOM_LEFT_CORNER);
|
|
||||||
grid[(bottom_right.0, upper_left.1)].set_ch('β');
|
|
||||||
grid[bottom_right].set_ch('β');
|
|
||||||
for i in upper_left.1 + 1..bottom_right.1 {
|
|
||||||
grid[(upper_left.0, i)].set_ch('β');
|
|
||||||
grid[(upper_left.0 + width, i)].set_ch('β');
|
|
||||||
}
|
|
||||||
for i in upper_left.0+1..bottom_right.0 {
|
|
||||||
grid[(i, upper_left.1)].set_ch('β');
|
|
||||||
grid[(i, upper_left.1 + height)].set_ch('β');
|
|
||||||
}
|
|
||||||
|
|
||||||
let width = bottom_right.0 - upper_left.0;
|
|
||||||
let height = bottom_right.1 - upper_left.1;
|
|
||||||
}
|
|
||||||
fn process_event(&mut self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A horizontally split in half container.
|
|
||||||
pub struct HSplit {
|
|
||||||
top: Entity,
|
|
||||||
bottom: Entity,
|
|
||||||
ratio: usize, // bottom/whole height * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HSplit {
|
|
||||||
pub fn new(top: Entity, bottom: Entity, ratio: usize) -> Self {
|
|
||||||
HSplit {
|
|
||||||
top: top,
|
|
||||||
bottom: bottom,
|
|
||||||
ratio: ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Component for HSplit {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) {
|
|
||||||
//eprintln!("grid {:?}", grid);
|
|
||||||
grid[upper_left].set_ch('u');
|
|
||||||
let (a,b) = upper_left;
|
|
||||||
grid[(a+1,b)].set_ch('h');
|
|
||||||
let width = bottom_right.0 - upper_left.0;
|
|
||||||
let height = bottom_right.1 - upper_left.1;
|
|
||||||
|
|
||||||
let total_rows = bottom_right.1 - upper_left.1;
|
|
||||||
let bottom_entity_height = (self.ratio*total_rows )/100;
|
|
||||||
let mid = upper_left.1 + total_rows - bottom_entity_height;
|
|
||||||
|
|
||||||
for i in upper_left.0..bottom_right.0+1 {
|
|
||||||
grid[(i, mid)].set_ch('β');
|
|
||||||
}
|
|
||||||
let _ = self.top.component.draw(upper_left, (bottom_right.0, upper_left.1 + mid-1), grid);
|
|
||||||
let _ = self.bottom.component.draw((upper_left.0, upper_left.1 + mid), bottom_right, grid);
|
|
||||||
grid[bottom_right].set_ch('b');
|
|
||||||
}
|
|
||||||
fn process_event(&mut self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A horizontally split in half container.
|
|
||||||
pub struct VSplit {
|
|
||||||
left: Entity,
|
|
||||||
right: Entity,
|
|
||||||
ratio: usize, // right/(container width) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VSplit {
|
|
||||||
pub fn new(left: Entity, right: Entity, ratio: usize) -> Self {
|
|
||||||
VSplit {
|
|
||||||
left: left,
|
|
||||||
right: right,
|
|
||||||
ratio: ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Component for VSplit {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) {
|
|
||||||
|
|
||||||
eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right);
|
|
||||||
let width = bottom_right.0 - upper_left.0;
|
|
||||||
let height = bottom_right.1 - upper_left.1;
|
|
||||||
|
|
||||||
let total_cols = bottom_right.0 - upper_left.0;
|
|
||||||
let right_entity_width = (self.ratio*total_cols )/100;
|
|
||||||
let mid = bottom_right.0 - right_entity_width;
|
|
||||||
eprintln!("total_cols {:?}, right_entity_width: {:?}, mid: {:?}",total_cols, right_entity_width, mid);
|
|
||||||
if (upper_left.1> 1) {
|
|
||||||
let c = grid.get(mid, upper_left.1-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
|
||||||
match c {
|
|
||||||
HORZ_BOUNDARY => {
|
|
||||||
grid[(mid, upper_left.1-1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in upper_left.1..bottom_right.1 {
|
|
||||||
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
|
||||||
}
|
|
||||||
if (bottom_right.1> 1) {
|
|
||||||
let c = grid.get(mid, bottom_right.1-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
|
||||||
match c {
|
|
||||||
HORZ_BOUNDARY => {
|
|
||||||
grid[(mid, bottom_right.1+1)].set_ch(LIGHT_UP_AND_HORIZONTAL);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = self.left.component.draw(upper_left, (mid-1, bottom_right.1), grid);
|
|
||||||
let _ = self.right.component.draw((mid+1, upper_left.1), bottom_right, grid);
|
|
||||||
}
|
|
||||||
fn process_event(&mut self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A box with a text content.
|
|
||||||
pub struct TextBox {
|
|
||||||
content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextBox {
|
|
||||||
pub fn new(s: String) -> Self {
|
|
||||||
TextBox {
|
|
||||||
content: s,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for TextBox {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) {
|
|
||||||
let mut x = upper_left.0;
|
|
||||||
let mut y = upper_left.1;
|
|
||||||
for c in self.content.chars() {
|
|
||||||
grid[(x,y)].set_ch(c);
|
|
||||||
//eprintln!("printed {} in ({}, {})", c, x, y);
|
|
||||||
x += 1;
|
|
||||||
if x == bottom_right.0 + 1 {
|
|
||||||
x = upper_left.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if y == bottom_right.1 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right);
|
|
||||||
let width = bottom_right.0 - upper_left.0;
|
|
||||||
let height = bottom_right.1 - upper_left.1;
|
|
||||||
}
|
|
||||||
fn process_event(&mut self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const max_width: usize = 500;
|
|
||||||
|
|
||||||
pub struct MailListing {
|
|
||||||
cursor_pos: usize,
|
|
||||||
length: usize,
|
|
||||||
// sorting
|
|
||||||
content: CellBuffer,
|
|
||||||
unfocused: bool,
|
|
||||||
// content (2-d vec of bytes) or Cells?
|
|
||||||
// current view on top of content
|
|
||||||
// active or not? for key events
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailListing {
|
|
||||||
pub fn new(length: usize) -> Self {
|
|
||||||
MailListing {
|
|
||||||
cursor_pos: 0,
|
|
||||||
length: length,
|
|
||||||
content: CellBuffer::new(max_width, length+1, Cell::with_char(' ')),
|
|
||||||
unfocused: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for MailListing {
|
|
||||||
fn draw(&mut self, upper_left: Pos, bottom_right: Pos, grid: &mut CellBuffer) {
|
|
||||||
let mut height = 0;
|
|
||||||
let mut stripe = false;
|
|
||||||
for y in upper_left.1..bottom_right.1 {
|
|
||||||
if height == self.length {
|
|
||||||
/* for loop */
|
|
||||||
for _y in y..bottom_right.1 {
|
|
||||||
for x in upper_left.0..bottom_right.0 {
|
|
||||||
grid[(x,y)].set_ch(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for x in upper_left.0..bottom_right.0 {
|
|
||||||
//grid[(x,y)].set_ch(self.content[(x-upper_left.0+1, y-upper_left.1+1)].ch());
|
|
||||||
grid[(x,y)].set_ch(if stripe { 't' } else { 'f'} );
|
|
||||||
grid[(x,y)].set_bg(if stripe { Color::Byte(246) } else {Color::Default });
|
|
||||||
}
|
|
||||||
stripe = if stripe { false } else { true };
|
|
||||||
height +1 ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn process_event(&mut self) {
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
[2J[1;1H[1;1H
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::ops::{Index, IndexMut, Deref, DerefMut};
|
use std::ops::{Index, IndexMut, Deref, DerefMut};
|
||||||
use position::*;
|
use super::position::*;
|
||||||
|
use termion::color::AnsiValue;
|
||||||
|
|
||||||
pub trait CellAccessor: HasSize {
|
pub trait CellAccessor: HasSize {
|
||||||
fn cellvec(&self) -> &Vec<Cell>;
|
fn cellvec(&self) -> &Vec<Cell>;
|
||||||
|
@ -401,9 +402,22 @@ impl Color {
|
||||||
Color::Cyan => 0x06,
|
Color::Cyan => 0x06,
|
||||||
Color::White => 0x07,
|
Color::White => 0x07,
|
||||||
Color::Byte(b) => b,
|
Color::Byte(b) => b,
|
||||||
Color::Default => panic!("Attempted to cast default color to u8"),
|
Color::Default => 0x00,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_termion(&self) -> AnsiValue {
|
||||||
|
match *self {
|
||||||
|
b @ Color::Black | b @ Color::Red | b @ Color::Green | b @ Color::Yellow | b @ Color::Blue | b @ Color::Magenta | b @ Color::Cyan | b @ Color::White | b @ Color::Default =>
|
||||||
|
{
|
||||||
|
AnsiValue(b.as_byte())
|
||||||
|
},
|
||||||
|
Color::Byte(b) => {
|
||||||
|
AnsiValue(b as u8)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The attributes of a `Cell`.
|
/// The attributes of a `Cell`.
|
|
@ -0,0 +1,239 @@
|
||||||
|
use ui::components::*;
|
||||||
|
use ui::cells::*;
|
||||||
|
|
||||||
|
fn make_entry_string(e: &Envelope, idx: usize) -> String {
|
||||||
|
format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject())
|
||||||
|
}
|
||||||
|
|
||||||
|
const max_width: usize = 500;
|
||||||
|
|
||||||
|
pub struct MailListing {
|
||||||
|
cursor_pos: usize,
|
||||||
|
new_cursor_pos: usize,
|
||||||
|
length: usize,
|
||||||
|
// sorting
|
||||||
|
content: CellBuffer,
|
||||||
|
dirty: bool,
|
||||||
|
unfocused: bool,
|
||||||
|
// content (2-d vec of bytes) or Cells?
|
||||||
|
// current view on top of content
|
||||||
|
// active or not? for key events
|
||||||
|
pub mailbox: Mailbox,
|
||||||
|
pager: Option<Pager>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailListing {
|
||||||
|
pub fn new(mailbox: Mailbox) -> Self {
|
||||||
|
let length = mailbox.get_length();
|
||||||
|
|
||||||
|
MailListing {
|
||||||
|
cursor_pos: 0,
|
||||||
|
new_cursor_pos: 0,
|
||||||
|
length: length,
|
||||||
|
content: CellBuffer::new(max_width, length+1, Cell::with_char(' ')),
|
||||||
|
dirty: false,
|
||||||
|
unfocused: false,
|
||||||
|
mailbox: mailbox,
|
||||||
|
pager: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl MailListing {
|
||||||
|
fn draw_list(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
if self.length == 0 {
|
||||||
|
write_string_to_grid(&format!("Folder `{}` is empty.", self.mailbox.path), grid, Color::Default, Color::Default, upper_left, upper_left);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.cursor_pos != self.new_cursor_pos {
|
||||||
|
for idx in [self.cursor_pos, self.new_cursor_pos].iter() {
|
||||||
|
let color = if self.cursor_pos == *idx { if *idx % 2 == 0 { Color::Byte(236) } else {Color::Default } } else { Color::Byte(246) };
|
||||||
|
let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[*idx], *idx), grid, Color::Default, color, set_y(upper_left, get_y(upper_left) + *idx), bottom_right);
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x,get_y(upper_left)+idx)].set_ch(' ');
|
||||||
|
grid[(x,get_y(upper_left)+idx)].set_bg(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
if idx == self.length {
|
||||||
|
for _y in y..get_y(bottom_right) {
|
||||||
|
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||||
|
grid[(x,_y)].set_ch(' ');
|
||||||
|
grid[(x,_y)].set_bg(Color::Default);
|
||||||
|
grid[(x,_y)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Write an entire line for each envelope entry. */
|
||||||
|
|
||||||
|
let color = if self.cursor_pos == idx { Color::Byte(246) } else { if idx % 2 == 0 { Color::Byte(236) } else {Color::Default } };
|
||||||
|
let x = write_string_to_grid(&make_entry_string(&self.mailbox.collection[idx], idx), grid, Color::Default, color, set_y(upper_left, y), bottom_right);
|
||||||
|
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x,y)].set_ch(' ');
|
||||||
|
grid[(x,y)].set_bg(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
idx+=1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_mail_view(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
//Pager
|
||||||
|
let ref mail = self.mailbox.collection[self.cursor_pos];
|
||||||
|
let height = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
let width = get_x(bottom_right) - get_x(upper_left);
|
||||||
|
self.pager = Some(Pager::new(mail, height, width));
|
||||||
|
let pager = self.pager.as_mut().unwrap();
|
||||||
|
pager.dirty = true;
|
||||||
|
pager.draw(grid, upper_left,bottom_right);
|
||||||
|
|
||||||
|
/*
|
||||||
|
let text = mail.get_body().get_text();
|
||||||
|
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||||
|
let lines_length = lines.len();
|
||||||
|
|
||||||
|
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||||
|
grid[(x,y)].set_ch(' ');
|
||||||
|
grid[(x,y)].set_bg(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, l) in lines.iter().enumerate() {
|
||||||
|
write_string_to_grid(l, grid, Color::Default, Color::Default, set_y(upper_left, get_y(upper_left)+i), bottom_right);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
fn redraw_cursor(&mut self, _upper_left: Pos, _bottom_right: Pos, _grid: &mut CellBuffer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for MailListing {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
if !self.unfocused {
|
||||||
|
if !self.dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
/* Draw the entire list */
|
||||||
|
self.draw_list(grid, upper_left,bottom_right);
|
||||||
|
} else {
|
||||||
|
if self.length == 0 && self.dirty {
|
||||||
|
clear_area(grid, upper_left, bottom_right);
|
||||||
|
}
|
||||||
|
/* Render the mail body in a pager, basically copy what HSplit does */
|
||||||
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
/* TODO: ratio in Configuration */
|
||||||
|
let bottom_entity_height = (80*total_rows )/100;
|
||||||
|
let mid = get_y(upper_left) + total_rows - bottom_entity_height;
|
||||||
|
|
||||||
|
if !self.dirty {
|
||||||
|
if let Some(ref mut p) = self.pager {
|
||||||
|
p.draw(grid, (get_x(upper_left), get_y(upper_left) + mid+6), bottom_right);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
self.draw_list(grid, upper_left, (get_x(bottom_right), get_y(upper_left)+ mid -1));
|
||||||
|
if self.length == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for i in get_x(upper_left)..get_x(bottom_right)+1 {
|
||||||
|
grid[(i, mid)].set_ch('β');
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers_height: usize = 6;
|
||||||
|
/* Draw header */
|
||||||
|
{
|
||||||
|
let ref mail = self.mailbox.collection[self.cursor_pos];
|
||||||
|
|
||||||
|
let x = write_string_to_grid(&format!("Date: {}", mail.get_date_as_str()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+1), set_y(upper_left, mid+1));
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x, mid+1)].set_ch(' ');
|
||||||
|
grid[(x, mid+1)].set_bg(Color::Default);
|
||||||
|
grid[(x, mid+1)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
let x = write_string_to_grid(&format!("From: {}", mail.get_from()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+2), set_y(upper_left, mid+2));
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x, mid+2)].set_ch(' ');
|
||||||
|
grid[(x, mid+2)].set_bg(Color::Default);
|
||||||
|
grid[(x, mid+2)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
let x = write_string_to_grid(&format!("To: {}", mail.get_to()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+3), set_y(upper_left, mid+3));
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x, mid+3)].set_ch(' ');
|
||||||
|
grid[(x, mid+3)].set_bg(Color::Default);
|
||||||
|
grid[(x, mid+3)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
let x = write_string_to_grid(&format!("Subject: {}", mail.get_subject()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+4), set_y(upper_left, mid+4));
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x, mid+4)].set_ch(' ');
|
||||||
|
grid[(x, mid+4)].set_bg(Color::Default);
|
||||||
|
grid[(x, mid+4)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
let x = write_string_to_grid(&format!("Message-ID: {}", mail.get_message_id_raw()), grid, Color::Byte(33), Color::Default, set_y(upper_left, mid+5), set_y(upper_left, mid+5));
|
||||||
|
for x in x..get_x(bottom_right)+1 {
|
||||||
|
grid[(x, mid+5)].set_ch(' ');
|
||||||
|
grid[(x, mid+5)].set_bg(Color::Default);
|
||||||
|
grid[(x, mid+5)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw body */
|
||||||
|
self.draw_mail_view(grid, (get_x(upper_left), get_y(upper_left) + mid + headers_height), bottom_right);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Up) => {
|
||||||
|
if self.cursor_pos > 0 {
|
||||||
|
self.new_cursor_pos -= 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Down) => {
|
||||||
|
if self.length > 0 && self.cursor_pos < self.length - 1 {
|
||||||
|
self.new_cursor_pos += 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Char('\n')) if self.unfocused == false => {
|
||||||
|
self.unfocused = true;
|
||||||
|
self.dirty = true;
|
||||||
|
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Esc) if self.unfocused == true => {
|
||||||
|
self.unfocused = false;
|
||||||
|
self.dirty = true;
|
||||||
|
self.pager = None;
|
||||||
|
|
||||||
|
},
|
||||||
|
UIEventType::RefreshMailbox(ref m) => {
|
||||||
|
self.cursor_pos = 0;
|
||||||
|
self.new_cursor_pos = 0;
|
||||||
|
self.mailbox = m.clone();
|
||||||
|
self.length = m.collection.len();
|
||||||
|
self.dirty = true;
|
||||||
|
self.pager = None;
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if let Some(ref mut p) = self.pager {
|
||||||
|
p.process_event(event, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* meli - ui module.
|
||||||
|
*
|
||||||
|
* Copyright 2017-2018 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod utilities;
|
||||||
|
pub mod mail;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub use utilities::*;
|
||||||
|
pub use mail::*;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
|
||||||
|
use super::cells::{Color, CellBuffer};
|
||||||
|
use super::position::Pos;
|
||||||
|
use super::{UIEvent, UIEventType, Key};
|
||||||
|
|
||||||
|
/// The upper and lower boundary char.
|
||||||
|
const HORZ_BOUNDARY: char = 'β';
|
||||||
|
/// The left and right boundary char.
|
||||||
|
const VERT_BOUNDARY: char = 'β';
|
||||||
|
|
||||||
|
/// The top-left corner
|
||||||
|
const TOP_LEFT_CORNER: char = 'β';
|
||||||
|
/// The top-right corner
|
||||||
|
const TOP_RIGHT_CORNER: char = 'β';
|
||||||
|
/// The bottom-left corner
|
||||||
|
const BOTTOM_LEFT_CORNER: char = 'β';
|
||||||
|
/// The bottom-right corner
|
||||||
|
const BOTTOM_RIGHT_CORNER: char = 'β';
|
||||||
|
|
||||||
|
const LIGHT_VERTICAL_AND_RIGHT: char = 'β';
|
||||||
|
|
||||||
|
const LIGHT_VERTICAL_AND_LEFT: char = 'β€';
|
||||||
|
|
||||||
|
const LIGHT_DOWN_AND_HORIZONTAL: char = 'β¬';
|
||||||
|
|
||||||
|
const LIGHT_UP_AND_HORIZONTAL: char = 'β΄';
|
||||||
|
|
||||||
|
pub struct Entity {
|
||||||
|
//queue: VecDeque,
|
||||||
|
pub component: Box<Component>, // more than one?
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entity {
|
||||||
|
pub fn rcv_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
||||||
|
self.component.process_event(&event, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Entity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Entity", )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Component {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos);
|
||||||
|
fn process_event(&mut self, &UIEvent, &mut VecDeque<UIEvent>);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, upper_left: Pos, bottom_right: Pos) -> usize {
|
||||||
|
let (mut x, mut y) = upper_left;
|
||||||
|
for c in s.chars() {
|
||||||
|
eprintln!(" (x,y) = ({},{})", x,y);
|
||||||
|
grid[(x,y)].set_ch(c);
|
||||||
|
grid[(x,y)].set_fg(fg_color);
|
||||||
|
grid[(x,y)].set_bg(bg_color);
|
||||||
|
x += 1;
|
||||||
|
if x == (get_x(bottom_right)) {
|
||||||
|
x = get_x(upper_left);
|
||||||
|
y += 1;
|
||||||
|
if y > (get_y(bottom_right)) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_area(grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
for x in get_x(upper_left)..get_x(bottom_right) {
|
||||||
|
grid[(x,y)].set_ch(' ');
|
||||||
|
grid[(x,y)].set_bg(Color::Default);
|
||||||
|
grid[(x,y)].set_fg(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
use ui::components::*;
|
||||||
|
use ui::cells::*;
|
||||||
|
///A simple box with borders and no content.
|
||||||
|
pub struct BoxPanel {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for BoxPanel {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
grid[upper_left].set_ch('u');
|
||||||
|
grid[bottom_right].set_ch('b');
|
||||||
|
let width = get_x(bottom_right) - get_x(upper_left);
|
||||||
|
let height = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
|
||||||
|
grid[upper_left].set_ch('β');
|
||||||
|
grid[(get_x(upper_left), get_y(bottom_right))].set_ch(BOTTOM_LEFT_CORNER);
|
||||||
|
grid[(get_x(bottom_right), get_y(upper_left))].set_ch('β');
|
||||||
|
grid[bottom_right].set_ch('β');
|
||||||
|
for i in get_y(upper_left) + 1..get_y(bottom_right) {
|
||||||
|
grid[(get_x(upper_left), i)].set_ch('β');
|
||||||
|
grid[(get_x(upper_left) + width, i)].set_ch('β');
|
||||||
|
}
|
||||||
|
for i in get_x(upper_left)+1..get_x(bottom_right) {
|
||||||
|
grid[(i, get_y(upper_left))].set_ch('β');
|
||||||
|
grid[(i, get_y(upper_left) + height)].set_ch('β');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontally split in half container.
|
||||||
|
pub struct HSplit {
|
||||||
|
top: Entity,
|
||||||
|
bottom: Entity,
|
||||||
|
ratio: usize, // bottom/whole height * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HSplit {
|
||||||
|
pub fn new(top: Entity, bottom: Entity, ratio: usize) -> Self {
|
||||||
|
HSplit {
|
||||||
|
top: top,
|
||||||
|
bottom: bottom,
|
||||||
|
ratio: ratio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Component for HSplit {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
grid[upper_left].set_ch('u');
|
||||||
|
let (a,b) = upper_left;
|
||||||
|
grid[(a+1,b)].set_ch('h');
|
||||||
|
|
||||||
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
let bottom_entity_height = (self.ratio*total_rows )/100;
|
||||||
|
let mid = get_y(upper_left) + total_rows - bottom_entity_height;
|
||||||
|
|
||||||
|
for i in get_x(upper_left)..get_x(bottom_right)+1 {
|
||||||
|
grid[(i, mid)].set_ch('β');
|
||||||
|
}
|
||||||
|
let _ = self.top.component.draw(grid, upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1));
|
||||||
|
let _ = self.bottom.component.draw(grid, (get_x(upper_left), get_y(upper_left) + mid), bottom_right);
|
||||||
|
grid[bottom_right].set_ch('b');
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
||||||
|
self.top.rcv_event(event, queue);
|
||||||
|
self.bottom.rcv_event(event, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A horizontally split in half container.
|
||||||
|
pub struct VSplit {
|
||||||
|
left: Entity,
|
||||||
|
right: Entity,
|
||||||
|
ratio: usize, // right/(container width) * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VSplit {
|
||||||
|
pub fn new(left: Entity, right: Entity, ratio: usize) -> Self {
|
||||||
|
VSplit {
|
||||||
|
left: left,
|
||||||
|
right: right,
|
||||||
|
ratio: ratio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Component for VSplit {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
let total_cols = get_x(bottom_right) - get_x(upper_left);
|
||||||
|
let right_entity_width = (self.ratio*total_cols )/100;
|
||||||
|
let mid = get_x(bottom_right) - right_entity_width;
|
||||||
|
|
||||||
|
if get_y(upper_left)> 1 {
|
||||||
|
let c = grid.get(mid, get_y(upper_left)-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
||||||
|
match c {
|
||||||
|
HORZ_BOUNDARY => {
|
||||||
|
grid[(mid, get_y(upper_left)-1)].set_ch(LIGHT_DOWN_AND_HORIZONTAL);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
grid[(mid, i)].set_ch(VERT_BOUNDARY);
|
||||||
|
}
|
||||||
|
if get_y(bottom_right)> 1 {
|
||||||
|
let c = grid.get(mid, get_y(bottom_right)-1).map(|a| a.ch()).unwrap_or_else(|| ' ');
|
||||||
|
match c {
|
||||||
|
HORZ_BOUNDARY => {
|
||||||
|
grid[(mid, get_y(bottom_right)+1)].set_ch(LIGHT_UP_AND_HORIZONTAL);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let _ = self.left.component.draw(grid, upper_left, (mid-1, get_y(bottom_right)));
|
||||||
|
let _ = self.right.component.draw(grid, (mid+1, get_y(upper_left)), bottom_right);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque<UIEvent>) {
|
||||||
|
self.left.rcv_event(event, queue);
|
||||||
|
self.right.rcv_event(event, queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A box with a text content.
|
||||||
|
pub struct TextBox {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextBox {
|
||||||
|
pub fn new(s: String) -> Self {
|
||||||
|
TextBox {
|
||||||
|
content: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for TextBox {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
let mut x = get_x(upper_left);
|
||||||
|
let y = get_y(upper_left);
|
||||||
|
for c in self.content.chars() {
|
||||||
|
grid[(x,y)].set_ch(c);
|
||||||
|
x += 1;
|
||||||
|
if x == get_x(bottom_right) + 1 {
|
||||||
|
x = get_x(upper_left);
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == get_y(bottom_right) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pager {
|
||||||
|
cursor_pos: usize,
|
||||||
|
rows: usize,
|
||||||
|
cols: usize,
|
||||||
|
height: usize,
|
||||||
|
pub dirty: bool,
|
||||||
|
content: CellBuffer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pager {
|
||||||
|
pub fn new(mail: &Envelope, height: usize, width: usize) -> Self {
|
||||||
|
let text = mail.get_body().get_text();
|
||||||
|
let lines: Vec<&str> = text.trim().split('\n').collect();
|
||||||
|
let rows = lines.len();
|
||||||
|
let mut content = CellBuffer::new(width, rows, Cell::with_char(' '));
|
||||||
|
for (i, l) in lines.iter().enumerate() {
|
||||||
|
write_string_to_grid(l, &mut content, Color::Default, Color::Default, (0,i), (width-1, rows-1));
|
||||||
|
}
|
||||||
|
Pager {
|
||||||
|
cursor_pos: 0,
|
||||||
|
rows: rows,
|
||||||
|
height: height,
|
||||||
|
cols: width,
|
||||||
|
dirty: true,
|
||||||
|
content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Pager {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) {
|
||||||
|
if !self.dirty {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clear_area(grid, (get_x(upper_left), get_y(upper_left)-1), bottom_right);
|
||||||
|
self.dirty = false;
|
||||||
|
let mut inner_x = 0;
|
||||||
|
let mut inner_y = self.cursor_pos;
|
||||||
|
|
||||||
|
//if self.cursor_pos < self.rows && self.rows - self.cursor_pos > self.height {
|
||||||
|
for y in get_y(upper_left)..get_y(bottom_right) {
|
||||||
|
'for_x: for x in get_x(upper_left)..get_x(bottom_right) {
|
||||||
|
if inner_x == self.cols {
|
||||||
|
break 'for_x;
|
||||||
|
}
|
||||||
|
grid[(x,y)] = self.content[(inner_x, inner_y)];
|
||||||
|
inner_x += 1;
|
||||||
|
}
|
||||||
|
inner_y += 1;
|
||||||
|
inner_x = 0;
|
||||||
|
if inner_y == self.rows {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque<UIEvent>) {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Char('k')) => {
|
||||||
|
if self.cursor_pos > 0 {
|
||||||
|
self.cursor_pos -= 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Char('j')) => {
|
||||||
|
if self.rows > 0 && self.cursor_pos < self.rows - 1 {
|
||||||
|
self.cursor_pos += 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
527
src/ui/index.rs
527
src/ui/index.rs
|
@ -1,527 +0,0 @@
|
||||||
/*
|
|
||||||
* meli - ui module.
|
|
||||||
*
|
|
||||||
* Copyright 2017 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
use super::pager::Pager;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
extern crate ncurses;
|
|
||||||
|
|
||||||
pub trait Window {
|
|
||||||
fn draw(&mut self) -> ();
|
|
||||||
fn redraw(&mut self) -> ();
|
|
||||||
fn handle_input(&mut self, input: i32) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ErrorWindow {
|
|
||||||
description: String,
|
|
||||||
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window for ErrorWindow {
|
|
||||||
fn draw(&mut self) -> () {
|
|
||||||
ncurses::waddstr(self.win, &self.description);
|
|
||||||
ncurses::wrefresh(self.win);
|
|
||||||
}
|
|
||||||
fn redraw(&mut self) -> () {
|
|
||||||
ncurses::waddstr(self.win, &self.description);
|
|
||||||
ncurses::wrefresh(self.win);
|
|
||||||
}
|
|
||||||
fn handle_input(&mut self, _: i32) -> bool { false }
|
|
||||||
}
|
|
||||||
impl ErrorWindow {
|
|
||||||
pub fn new(err: MeliError) -> Self {
|
|
||||||
/*
|
|
||||||
let mut screen_height = 0;
|
|
||||||
let mut screen_width = 0;
|
|
||||||
/* Get the screen bounds. */
|
|
||||||
ncurses::getmaxyx(ncurses::stdscr(), &mut screen_height, &mut screen_width);
|
|
||||||
// let win = ncurses::newwin( ncurses::LINES(), ncurses::COLS()-30, 0, 30);
|
|
||||||
*/
|
|
||||||
let win = ncurses::newwin(0, 0, 0, 0);
|
|
||||||
ErrorWindow {
|
|
||||||
description: err.description().to_string(),
|
|
||||||
win: win,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Index represents a UI list of mails */
|
|
||||||
pub struct Index {
|
|
||||||
mailbox: Mailbox,
|
|
||||||
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
pad: ncurses::WINDOW,
|
|
||||||
screen_width: i32,
|
|
||||||
screen_height: i32,
|
|
||||||
|
|
||||||
/* threading */
|
|
||||||
threaded: bool,
|
|
||||||
|
|
||||||
cursor_idx: usize,
|
|
||||||
|
|
||||||
pager: Option<Pager>,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Window for Index {
|
|
||||||
fn draw(&mut self) {
|
|
||||||
if self.get_length() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getbegyx(self.win, &mut y, &mut x);
|
|
||||||
|
|
||||||
//ncurses::wclear(self.pad);
|
|
||||||
|
|
||||||
if self.threaded {
|
|
||||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
|
||||||
/* Draw threaded view. */
|
|
||||||
let mut iter = self.mailbox
|
|
||||||
.threaded_collection
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.peekable();
|
|
||||||
/* This is just a desugared for loop so that we can use .peek() */
|
|
||||||
while let Some((idx, i)) = iter.next() {
|
|
||||||
let container = self.mailbox.get_thread(*i);
|
|
||||||
let indentation = container.get_indentation();
|
|
||||||
|
|
||||||
assert_eq!(container.has_message(), true);
|
|
||||||
match iter.peek() {
|
|
||||||
Some(&(_, x))
|
|
||||||
if self.mailbox.get_thread(*x).get_indentation() == indentation =>
|
|
||||||
{
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(true);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if container.has_sibling() {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(true);
|
|
||||||
}
|
|
||||||
let x = &self.mailbox.collection[container.get_message().unwrap()];
|
|
||||||
Index::draw_entry(
|
|
||||||
self.pad,
|
|
||||||
x,
|
|
||||||
idx,
|
|
||||||
indentation,
|
|
||||||
container.has_sibling(),
|
|
||||||
container.has_parent(),
|
|
||||||
idx == self.cursor_idx,
|
|
||||||
container.get_show_subject(),
|
|
||||||
Some(&indentations),
|
|
||||||
);
|
|
||||||
match iter.peek() {
|
|
||||||
Some(&(_, x))
|
|
||||||
if self.mailbox.get_thread(*x).get_indentation() > indentation =>
|
|
||||||
{
|
|
||||||
indentations.push(false);
|
|
||||||
}
|
|
||||||
Some(&(_, x))
|
|
||||||
if self.mailbox.get_thread(*x).get_indentation() < indentation =>
|
|
||||||
{
|
|
||||||
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
|
|
||||||
indentations.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
for (idx, i) in self.mailbox.threaded_collection.iter().enumerate() {
|
|
||||||
let container = self.mailbox.get_thread(*i);
|
|
||||||
|
|
||||||
assert_eq!(container.has_message(), true);
|
|
||||||
if container.has_sibling() {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(true);
|
|
||||||
}
|
|
||||||
let x = &self.mailbox.collection[container.get_message().unwrap()];
|
|
||||||
Index::draw_entry(self.pad, x, idx, container.get_indentation(), container.has_sibling(), idx == self.cursor_idx, container.get_show_subject(), Some(&indentations));
|
|
||||||
if container.has_children() {
|
|
||||||
indentations.push(false);
|
|
||||||
} else {
|
|
||||||
indentations.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} else {
|
|
||||||
for (idx, x) in self.mailbox.collection.as_mut_slice().iter().enumerate() {
|
|
||||||
Index::draw_entry(
|
|
||||||
self.pad,
|
|
||||||
x,
|
|
||||||
idx,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
idx == self.cursor_idx,
|
|
||||||
true,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
|
||||||
let pminrow =
|
|
||||||
(self.cursor_idx as i32).wrapping_div(self.screen_height) * self.screen_height;
|
|
||||||
ncurses::prefresh(
|
|
||||||
self.pad,
|
|
||||||
pminrow,
|
|
||||||
0,
|
|
||||||
y,
|
|
||||||
x,
|
|
||||||
self.screen_height - 1,
|
|
||||||
self.screen_width - 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
fn redraw(&mut self) -> () {
|
|
||||||
ncurses::wnoutrefresh(self.win);
|
|
||||||
ncurses::doupdate();
|
|
||||||
if self.get_length() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* Draw newly highlighted entry */
|
|
||||||
ncurses::wmove(self.pad, self.cursor_idx as i32, 0);
|
|
||||||
let pair = super::COLOR_PAIR_CURSOR;
|
|
||||||
ncurses::wchgat(self.pad, -1, 0, pair);
|
|
||||||
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getbegyx(self.win, &mut y, &mut x);
|
|
||||||
let pminrow =
|
|
||||||
(self.cursor_idx as i32).wrapping_div(self.screen_height) * self.screen_height;
|
|
||||||
ncurses::touchline(self.pad, 1, 1);
|
|
||||||
ncurses::prefresh(
|
|
||||||
self.pad,
|
|
||||||
pminrow,
|
|
||||||
0,
|
|
||||||
y,
|
|
||||||
x,
|
|
||||||
self.screen_height - 1,
|
|
||||||
self.screen_width - 1,
|
|
||||||
);
|
|
||||||
ncurses::wrefresh(self.win);
|
|
||||||
}
|
|
||||||
fn handle_input(&mut self, motion: i32) -> bool {
|
|
||||||
if self.get_length() == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
|
||||||
if self.screen_height == 0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if self.pager.is_some() {
|
|
||||||
match motion {
|
|
||||||
m @ ncurses::KEY_UP |
|
|
||||||
m @ ncurses::KEY_DOWN |
|
|
||||||
m @ ncurses::KEY_NPAGE |
|
|
||||||
m @ ncurses::KEY_PPAGE => {
|
|
||||||
self.pager.as_mut().unwrap().scroll(m);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
ncurses::KEY_F1 => {
|
|
||||||
self.pager = None;
|
|
||||||
self.redraw();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getbegyx(self.win, &mut y, &mut x);
|
|
||||||
let prev_idx = self.cursor_idx;
|
|
||||||
match motion {
|
|
||||||
ncurses::KEY_UP => if self.cursor_idx > 0 {
|
|
||||||
self.cursor_idx -= 1;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
ncurses::KEY_DOWN => if self.cursor_idx < self.get_length() - 1 {
|
|
||||||
self.cursor_idx += 1;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
10 => {
|
|
||||||
self.show_pager();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw newly highlighted entry */
|
|
||||||
ncurses::wmove(self.pad, self.cursor_idx as i32, 0);
|
|
||||||
let pair = super::COLOR_PAIR_CURSOR;
|
|
||||||
ncurses::wchgat(self.pad, -1, 0, pair);
|
|
||||||
/* Draw previous highlighted entry normally */
|
|
||||||
ncurses::wmove(self.pad, prev_idx as i32, 0);
|
|
||||||
{
|
|
||||||
let envelope: &Envelope = if self.threaded {
|
|
||||||
let i = self.mailbox.get_threaded_mail(prev_idx);
|
|
||||||
&self.mailbox.collection[i]
|
|
||||||
} else {
|
|
||||||
&self.mailbox.collection[prev_idx]
|
|
||||||
};
|
|
||||||
let pair = if self.threaded {
|
|
||||||
if prev_idx % 2 == 0 && envelope.is_seen() {
|
|
||||||
super::COLOR_PAIR_THREAD_EVEN
|
|
||||||
} else if prev_idx % 2 == 0 {
|
|
||||||
super::COLOR_PAIR_UNREAD_EVEN
|
|
||||||
} else if envelope.is_seen() {
|
|
||||||
super::COLOR_PAIR_THREAD_ODD
|
|
||||||
} else {
|
|
||||||
super::COLOR_PAIR_UNREAD_ODD
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super::COLOR_PAIR_DEFAULT
|
|
||||||
};
|
|
||||||
|
|
||||||
ncurses::wchgat(self.pad, 32, 0, pair);
|
|
||||||
ncurses::wmove(self.pad, prev_idx as i32, 32);
|
|
||||||
/* If first character in subject column is space, we need to check for indentation
|
|
||||||
* characters and highlight them appropriately */
|
|
||||||
if (ncurses::winch(self.pad) & ncurses::A_CHARTEXT()) == ' ' as u64 {
|
|
||||||
let mut x = 32;
|
|
||||||
loop {
|
|
||||||
match ncurses::mvwinch(self.pad, prev_idx as i32, x) & ncurses::A_CHARTEXT() {
|
|
||||||
32 => {
|
|
||||||
/* ASCII code for space */
|
|
||||||
ncurses::wchgat(self.pad, x, 0, pair);
|
|
||||||
}
|
|
||||||
62 => {
|
|
||||||
/* ASCII code for '>' */
|
|
||||||
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
|
||||||
ncurses::wmove(self.pad, prev_idx as i32, x + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ncurses::wchgat(self.pad, -1, 0, pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate the pad row of the first entry to be displayed in the window */
|
|
||||||
let pminrow =
|
|
||||||
(self.cursor_idx as i32).wrapping_div(self.screen_height) * self.screen_height;
|
|
||||||
let pminrow_prev = (prev_idx as i32).wrapping_div(self.screen_height) * self.screen_height;
|
|
||||||
/*
|
|
||||||
* Refresh window if new page has less rows than window rows, ie
|
|
||||||
* window rows = r
|
|
||||||
* pad rows (total emails) = n
|
|
||||||
* pminrow = i
|
|
||||||
*
|
|
||||||
* β- i
|
|
||||||
* β i+1
|
|
||||||
* β i+2
|
|
||||||
* r β€ ...
|
|
||||||
* β n
|
|
||||||
* β .. β
|
|
||||||
* β i-2 β 'dead' entries (must be cleared)
|
|
||||||
* β i-1 β
|
|
||||||
*/
|
|
||||||
if pminrow != pminrow_prev &&
|
|
||||||
pminrow + self.screen_height > self.get_length() as i32
|
|
||||||
{
|
|
||||||
/* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */
|
|
||||||
let live_entries = self.get_length() as i32 - pminrow;
|
|
||||||
ncurses::wredrawln(self.win, live_entries, self.screen_height);
|
|
||||||
ncurses::wrefresh(self.win);
|
|
||||||
}
|
|
||||||
ncurses::prefresh(
|
|
||||||
self.pad,
|
|
||||||
pminrow,
|
|
||||||
0,
|
|
||||||
y,
|
|
||||||
x,
|
|
||||||
self.screen_height - 1,
|
|
||||||
self.screen_width - 1,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Index {
|
|
||||||
pub fn new(mailbox: &Mailbox) -> Index {
|
|
||||||
let mailbox = mailbox.clone();
|
|
||||||
let mut unread_count = 0;
|
|
||||||
for e in &mailbox.collection {
|
|
||||||
if !e.is_seen() {
|
|
||||||
unread_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eprintln!("unread count {}", unread_count);
|
|
||||||
let mut screen_height = 0;
|
|
||||||
let mut screen_width = 0;
|
|
||||||
/* Get the screen bounds. */
|
|
||||||
ncurses::getmaxyx(ncurses::stdscr(), &mut screen_height, &mut screen_width);
|
|
||||||
// let win = ncurses::newwin( ncurses::LINES(), ncurses::COLS()-30, 0, 30);
|
|
||||||
let win = ncurses::newwin(0, 0, 0, 0);
|
|
||||||
ncurses::getmaxyx(win, &mut screen_height, &mut screen_width);
|
|
||||||
//eprintln!("length is {}\n", length);
|
|
||||||
let mailbox_length = mailbox.get_length();
|
|
||||||
let pad = ncurses::newpad(mailbox_length as i32, 1500);
|
|
||||||
ncurses::wbkgd(
|
|
||||||
pad,
|
|
||||||
' ' as ncurses::chtype |
|
|
||||||
ncurses::COLOR_PAIR(super::COLOR_PAIR_DEFAULT) as ncurses::chtype,
|
|
||||||
);
|
|
||||||
if mailbox_length == 0 {
|
|
||||||
ncurses::printw(&format!("Mailbox {} is empty.\n", mailbox.path));
|
|
||||||
ncurses::refresh();
|
|
||||||
}
|
|
||||||
Index {
|
|
||||||
mailbox: mailbox,
|
|
||||||
win: win,
|
|
||||||
pad: pad,
|
|
||||||
screen_width: 0,
|
|
||||||
screen_height: 0,
|
|
||||||
threaded: true,
|
|
||||||
cursor_idx: 0,
|
|
||||||
pager: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_length(&self) -> usize {
|
|
||||||
if self.threaded {
|
|
||||||
self.mailbox.threaded_collection.len()
|
|
||||||
} else {
|
|
||||||
self.mailbox.get_length()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* draw_entry() doesn't take &mut self because borrow checker complains if it's called from
|
|
||||||
* another method. */
|
|
||||||
fn draw_entry(
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
mail: &Envelope,
|
|
||||||
i: usize,
|
|
||||||
indent: usize,
|
|
||||||
has_sibling: bool,
|
|
||||||
has_parent: bool,
|
|
||||||
highlight: bool,
|
|
||||||
show_subject: bool,
|
|
||||||
indentations: Option<&Vec<bool>>,
|
|
||||||
) {
|
|
||||||
/* TODO: use addchstr */
|
|
||||||
let pair = if highlight {
|
|
||||||
super::COLOR_PAIR_CURSOR
|
|
||||||
} else if i % 2 == 0 && mail.is_seen() {
|
|
||||||
super::COLOR_PAIR_THREAD_EVEN
|
|
||||||
} else if i % 2 == 0 {
|
|
||||||
super::COLOR_PAIR_UNREAD_EVEN
|
|
||||||
} else if mail.is_seen() {
|
|
||||||
super::COLOR_PAIR_THREAD_ODD
|
|
||||||
} else {
|
|
||||||
super::COLOR_PAIR_UNREAD_ODD
|
|
||||||
};
|
|
||||||
let attr = ncurses::COLOR_PAIR(pair);
|
|
||||||
ncurses::wattron(win, attr);
|
|
||||||
|
|
||||||
ncurses::waddstr(win, &format!("{}\t", i));
|
|
||||||
ncurses::waddstr(
|
|
||||||
win,
|
|
||||||
&mail.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
|
|
||||||
);
|
|
||||||
ncurses::waddch(win, '\t' as u64);
|
|
||||||
for i in 0..indent {
|
|
||||||
if indentations.is_some() && indentations.unwrap().len() > i && indentations.unwrap()[i]
|
|
||||||
{
|
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
|
||||||
ncurses::waddstr(win, "β");
|
|
||||||
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
|
||||||
ncurses::wattron(win, attr);
|
|
||||||
} else {
|
|
||||||
ncurses::waddch(win, ' ' as u64);
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
ncurses::waddch(win, ' ' as u64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if indent > 0 {
|
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
|
||||||
if has_sibling && has_parent {
|
|
||||||
ncurses::waddstr(win, "β");
|
|
||||||
} else if has_sibling {
|
|
||||||
ncurses::waddstr(win, "β¬");
|
|
||||||
} else {
|
|
||||||
ncurses::waddstr(win, "β");
|
|
||||||
}
|
|
||||||
ncurses::waddstr(win, "β>");
|
|
||||||
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
|
||||||
}
|
|
||||||
ncurses::wattron(win, attr);
|
|
||||||
if show_subject {
|
|
||||||
ncurses::waddstr(win, &format!("{:.85}", mail.get_subject()));
|
|
||||||
/*
|
|
||||||
if indent == 0 {
|
|
||||||
if mail.get_subject().chars().count() < 85 {
|
|
||||||
for _ in 0..(85 - mail.get_subject().chars().count()) {
|
|
||||||
ncurses::waddstr(win, "β");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ncurses::waddstr(win,"β");
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
let mut screen_height = 0;
|
|
||||||
let mut screen_width = 0;
|
|
||||||
/* Get the screen bounds. */
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getmaxyx(win, &mut screen_height, &mut screen_width);
|
|
||||||
ncurses::getyx(win, &mut y, &mut x);
|
|
||||||
ncurses::waddstr(win, &" ".repeat((screen_width - x) as usize));
|
|
||||||
ncurses::wattroff(win, attr);
|
|
||||||
}
|
|
||||||
fn show_pager(&mut self) {
|
|
||||||
if self.get_length() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
|
||||||
let x: &mut Envelope = if self.threaded {
|
|
||||||
let i = self.mailbox.get_threaded_mail(self.cursor_idx);
|
|
||||||
&mut self.mailbox.collection[i]
|
|
||||||
} else {
|
|
||||||
&mut self.mailbox.collection[self.cursor_idx]
|
|
||||||
};
|
|
||||||
let mut pager = Pager::new(self.win, x);
|
|
||||||
/* TODO: Fix this: */
|
|
||||||
pager.scroll(ncurses::KEY_DOWN);
|
|
||||||
pager.scroll(ncurses::KEY_UP);
|
|
||||||
self.pager = Some(pager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for Index {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
ncurses::delwin(self.pad);
|
|
||||||
ncurses::wclear(self.win);
|
|
||||||
ncurses::delwin(self.win);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,15 @@ extern crate termion;
|
||||||
|
|
||||||
|
|
||||||
use termion::{clear, cursor};
|
use termion::{clear, cursor};
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
use termion::event::{Key as TermionKey, Event as TermionEvent, MouseEvent as TermionMouseEvent};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//use std::env;
|
//use std::env;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use termion::input::TermRead;
|
||||||
|
use std::io::{stdout, stdin, stderr};
|
||||||
//use std::collections::VecDeque;
|
//use std::collections::VecDeque;
|
||||||
//use std::process;
|
//use std::process;
|
||||||
|
|
||||||
|
@ -20,23 +24,33 @@ use position::Pos;
|
||||||
pub use self::components::*;
|
pub use self::components::*;
|
||||||
pub use self::position::*;
|
pub use self::position::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct UIEvent {
|
pub enum UIEventType {
|
||||||
|
Input(Key),
|
||||||
|
RefreshMailbox(String),
|
||||||
|
//Quit?
|
||||||
|
Resize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State<R, W: Write> {
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UIEvent {
|
||||||
|
pub id: u64,
|
||||||
|
pub event_type: UIEventType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct State<W: Write> {
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
|
||||||
grid: CellBuffer,
|
grid: CellBuffer,
|
||||||
stdin: R,
|
pub stdout: termion::raw::RawTerminal<W>,
|
||||||
pub stdout: W,
|
|
||||||
entities: Vec<Entity>,
|
entities: Vec<Entity>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: Read, W: Write> State<R,W> {
|
impl<W: Write> State<W> {
|
||||||
pub fn new(stdout: W, stdin: R) -> Self {
|
pub fn new(stdout: W) -> Self {
|
||||||
let termsize = termion::terminal_size().ok();
|
let termsize = termion::terminal_size().ok();
|
||||||
let termwidth = termsize.map(|(w,_)| w);
|
let termwidth = termsize.map(|(w,_)| w);
|
||||||
let termheight = termsize.map(|(_,h)| h);
|
let termheight = termsize.map(|(_,h)| h);
|
||||||
|
@ -48,8 +62,7 @@ impl<R: Read, W: Write> State<R,W> {
|
||||||
//queue: VecDeque::new();
|
//queue: VecDeque::new();
|
||||||
|
|
||||||
grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')),
|
grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')),
|
||||||
stdin: stdin,
|
stdout: stdout.into_raw_mode().unwrap(),
|
||||||
stdout: stdout,
|
|
||||||
entities: Vec::with_capacity(2),
|
entities: Vec::with_capacity(2),
|
||||||
};
|
};
|
||||||
write!(s.stdout, "{}{}", clear::All, cursor::Goto(1,1)).unwrap();
|
write!(s.stdout, "{}{}", clear::All, cursor::Goto(1,1)).unwrap();
|
||||||
|
@ -99,4 +112,86 @@ impl<R: Read, W: Write> State<R,W> {
|
||||||
pub fn register_entity(&mut self, entity: Entity) {
|
pub fn register_entity(&mut self, entity: Entity) {
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rcv_event(&mut self, event: UIEvent) {
|
||||||
|
/* inform each entity */ for i in 0..self.entities.len() {
|
||||||
|
self.entities[i].rcv_event(&event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_key(k: TermionKey ) -> Key {
|
||||||
|
match k {
|
||||||
|
TermionKey::Backspace => Key::Backspace,
|
||||||
|
TermionKey::Left => Key::Left,
|
||||||
|
TermionKey::Right => Key::Right,
|
||||||
|
TermionKey::Up => Key::Up,
|
||||||
|
TermionKey::Down => Key::Down,
|
||||||
|
TermionKey::Home => Key::Home,
|
||||||
|
TermionKey::End => Key::End,
|
||||||
|
TermionKey::PageUp => Key::PageUp,
|
||||||
|
TermionKey::PageDown => Key::PageDown,
|
||||||
|
TermionKey::Delete => Key::Delete,
|
||||||
|
TermionKey::Insert => Key::Insert,
|
||||||
|
TermionKey::F(u) => Key::F(u),
|
||||||
|
TermionKey::Char(c) => Key::Char(c),
|
||||||
|
TermionKey::Alt(c) => Key::Alt(c),
|
||||||
|
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
||||||
|
TermionKey::Null => Key::Null,
|
||||||
|
TermionKey::Esc => Key::Esc,
|
||||||
|
_ => Key::Char(' '),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Key {
|
||||||
|
/// Backspace.
|
||||||
|
Backspace,
|
||||||
|
/// Left arrow.
|
||||||
|
Left,
|
||||||
|
/// Right arrow.
|
||||||
|
Right,
|
||||||
|
/// Up arrow.
|
||||||
|
Up,
|
||||||
|
/// Down arrow.
|
||||||
|
Down,
|
||||||
|
/// Home key.
|
||||||
|
Home,
|
||||||
|
/// End key.
|
||||||
|
End,
|
||||||
|
/// Page Up key.
|
||||||
|
PageUp,
|
||||||
|
/// Page Down key.
|
||||||
|
PageDown,
|
||||||
|
/// Delete key.
|
||||||
|
Delete,
|
||||||
|
/// Insert key.
|
||||||
|
Insert,
|
||||||
|
/// Function keys.
|
||||||
|
///
|
||||||
|
/// Only function keys 1 through 12 are supported.
|
||||||
|
F(u8),
|
||||||
|
/// Normal character.
|
||||||
|
Char(char),
|
||||||
|
/// Alt modified character.
|
||||||
|
Alt(char),
|
||||||
|
/// Ctrl modified character.
|
||||||
|
///
|
||||||
|
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
|
||||||
|
Ctrl(char),
|
||||||
|
/// Null byte.
|
||||||
|
Null,
|
||||||
|
/// Esc key.
|
||||||
|
Esc,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events<F>(stdin: std::io::Stdin, closure: F) where F: Fn(Key) -> (){
|
||||||
|
let stdin = stdin.lock();
|
||||||
|
for c in stdin.keys() {
|
||||||
|
if let Ok(k) = c {
|
||||||
|
let k = convert_key(k);
|
||||||
|
eprintln!("Received key: {:?}", k);
|
||||||
|
closure(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
262
src/ui/mod.rs
262
src/ui/mod.rs
|
@ -19,12 +19,15 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub mod index;
|
pub mod components;
|
||||||
pub mod pager;
|
pub mod position;
|
||||||
|
pub mod cells;
|
||||||
|
|
||||||
|
extern crate termion;
|
||||||
extern crate ncurses;
|
extern crate ncurses;
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
use melib::*;
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
/* Color pairs; foreground && background. */
|
/* Color pairs; foreground && background. */
|
||||||
/// Default color.
|
/// Default color.
|
||||||
|
@ -44,47 +47,11 @@ pub static COLOR_PAIR_UNREAD_ODD: i16 = 7;
|
||||||
/// Line color for unread even entries in index view.
|
/// Line color for unread even entries in index view.
|
||||||
pub static COLOR_PAIR_UNREAD_EVEN: i16 = 8;
|
pub static COLOR_PAIR_UNREAD_EVEN: i16 = 8;
|
||||||
|
|
||||||
/// Dummy object to provide `ncurses` initialization and destruction.
|
|
||||||
pub struct TUI;
|
|
||||||
|
|
||||||
impl TUI {
|
|
||||||
pub fn initialize() -> Self {
|
|
||||||
/* start ncurses */
|
|
||||||
ncurses::initscr();
|
|
||||||
ncurses::keypad(ncurses::stdscr(), true);
|
|
||||||
ncurses::noecho();
|
|
||||||
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
|
|
||||||
/* Start colors. */
|
|
||||||
|
|
||||||
ncurses::start_color();
|
|
||||||
|
|
||||||
ncurses::init_pair(COLOR_PAIR_DEFAULT, 15, 0);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_CURSOR, 251, 235);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_HEADERS, 33, 0);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_THREAD_INDENT, 5, 0);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_THREAD_ODD, 15, 0);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_THREAD_EVEN, 15, 233);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_UNREAD_ODD, 15, 7);
|
|
||||||
ncurses::init_pair(COLOR_PAIR_UNREAD_EVEN, 15, 8);
|
|
||||||
|
|
||||||
/* Set the window's background color. */
|
|
||||||
ncurses::bkgd(
|
|
||||||
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
|
|
||||||
);
|
|
||||||
TUI {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for TUI {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
ncurses::endwin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
|
/// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads
|
||||||
/// to the main process.
|
/// to the main process.
|
||||||
pub enum ThreadEvent {
|
pub enum ThreadEvent {
|
||||||
/// User input.
|
/// User input.
|
||||||
Input(ncurses::WchResult),
|
Input(Key),
|
||||||
/// A watched folder has been refreshed.
|
/// A watched folder has been refreshed.
|
||||||
RefreshMailbox{ name: String },
|
RefreshMailbox{ name: String },
|
||||||
//Decode { _ }, // For gpg2 signature check
|
//Decode { _ }, // For gpg2 signature check
|
||||||
|
@ -95,3 +62,218 @@ impl From<RefreshEvent> for ThreadEvent {
|
||||||
ThreadEvent::RefreshMailbox { name: event.folder }
|
ThreadEvent::RefreshMailbox { name: event.folder }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use melib::*;
|
||||||
|
|
||||||
|
|
||||||
|
use std;
|
||||||
|
use termion::{clear, style, cursor};
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
use termion::event::{Key as TermionKey, };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
use std::io::{Write, };
|
||||||
|
use termion::input::TermRead;
|
||||||
|
|
||||||
|
use self::cells::*;
|
||||||
|
pub use self::components::*;
|
||||||
|
pub use self::position::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum UIEventType {
|
||||||
|
Input(Key),
|
||||||
|
RefreshMailbox(Mailbox),
|
||||||
|
//Quit?
|
||||||
|
Resize,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UIEvent {
|
||||||
|
pub id: u64,
|
||||||
|
pub event_type: UIEventType,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct State<W: Write> {
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
|
||||||
|
grid: CellBuffer,
|
||||||
|
pub stdout: termion::raw::RawTerminal<W>,
|
||||||
|
entities: Vec<Entity>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> Drop for State<W> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// When done, restore the defaults to avoid messing with the terminal.
|
||||||
|
write!(self.stdout, "{}{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1), cursor::Show).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> State<W> {
|
||||||
|
pub fn new(stdout: W) -> Self {
|
||||||
|
let termsize = termion::terminal_size().ok();
|
||||||
|
let termwidth = termsize.map(|(w,_)| w);
|
||||||
|
let termheight = termsize.map(|(_,h)| h);
|
||||||
|
let width = termwidth.unwrap_or(0) as usize;
|
||||||
|
let height = termheight.unwrap_or(0) as usize;
|
||||||
|
let mut s = State {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
//queue: VecDeque::new();
|
||||||
|
|
||||||
|
grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')),
|
||||||
|
stdout: stdout.into_raw_mode().unwrap(),
|
||||||
|
entities: Vec::with_capacity(2),
|
||||||
|
};
|
||||||
|
write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap();
|
||||||
|
s
|
||||||
|
}
|
||||||
|
pub fn hello_w(&mut self) {
|
||||||
|
write!(self.stdout, "Hey there.").unwrap();
|
||||||
|
}
|
||||||
|
fn update_size(&mut self) {
|
||||||
|
/* update dimensions. TODO: Only do that in size change events. ie SIGWINCH */
|
||||||
|
let termsize = termion::terminal_size().ok();
|
||||||
|
let termwidth = termsize.map(|(w,_)| w);
|
||||||
|
let termheight = termsize.map(|(_,h)| h);
|
||||||
|
self.width = termwidth.unwrap_or(72) as usize;
|
||||||
|
self.height = termheight.unwrap_or(120) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(&mut self) {
|
||||||
|
self.update_size();
|
||||||
|
|
||||||
|
/* draw each entity */ for i in 0..self.entities.len() {
|
||||||
|
self.draw_entity(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only draw dirty areas */
|
||||||
|
for y in 0..self.height {
|
||||||
|
write!(self.stdout, "{}", cursor::Goto(1,y as u16)).unwrap();
|
||||||
|
for x in 0..self.width {
|
||||||
|
let c = self.grid[(x,y)];
|
||||||
|
|
||||||
|
if c.get_bg() != cells::Color::Default {
|
||||||
|
write!(self.stdout, "{}", termion::color::Bg(c.get_bg().as_termion()));
|
||||||
|
}
|
||||||
|
if c.get_fg() != cells::Color::Default {
|
||||||
|
write!(self.stdout, "{}", termion::color::Fg(c.get_fg().as_termion()));
|
||||||
|
}
|
||||||
|
write!(self.stdout, "{}",c.ch()).unwrap();
|
||||||
|
if c.get_bg() != cells::Color::Default {
|
||||||
|
write!(self.stdout, "{}", termion::color::Bg(termion::color::Reset));
|
||||||
|
}
|
||||||
|
if c.get_fg() != cells::Color::Default {
|
||||||
|
write!(self.stdout, "{}", termion::color::Fg(termion::color::Reset));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn draw_entity(&mut self, idx: usize) {
|
||||||
|
let ref mut entity = self.entities[idx];
|
||||||
|
eprintln!("Entity is {:?}", entity);
|
||||||
|
let upper_left = (1,1);
|
||||||
|
let bottom_right = (self.width, self.height);
|
||||||
|
eprintln!("Upper left is {:?} and bottom_right is {:?}", upper_left, bottom_right);
|
||||||
|
|
||||||
|
entity.component.draw(&mut self.grid, upper_left, bottom_right);
|
||||||
|
}
|
||||||
|
pub fn register_entity(&mut self, entity: Entity) {
|
||||||
|
self.entities.push(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rcv_event(&mut self, event: UIEvent) {
|
||||||
|
/* pass a queue for replies */
|
||||||
|
let mut queue : VecDeque<UIEvent> = VecDeque::new();
|
||||||
|
/* inform each entity */ for i in 0..self.entities.len() {
|
||||||
|
self.entities[i].rcv_event(&event, &mut queue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_key(k: TermionKey ) -> Key {
|
||||||
|
match k {
|
||||||
|
TermionKey::Backspace => Key::Backspace,
|
||||||
|
TermionKey::Left => Key::Left,
|
||||||
|
TermionKey::Right => Key::Right,
|
||||||
|
TermionKey::Up => Key::Up,
|
||||||
|
TermionKey::Down => Key::Down,
|
||||||
|
TermionKey::Home => Key::Home,
|
||||||
|
TermionKey::End => Key::End,
|
||||||
|
TermionKey::PageUp => Key::PageUp,
|
||||||
|
TermionKey::PageDown => Key::PageDown,
|
||||||
|
TermionKey::Delete => Key::Delete,
|
||||||
|
TermionKey::Insert => Key::Insert,
|
||||||
|
TermionKey::F(u) => Key::F(u),
|
||||||
|
TermionKey::Char(c) => Key::Char(c),
|
||||||
|
TermionKey::Alt(c) => Key::Alt(c),
|
||||||
|
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
||||||
|
TermionKey::Null => Key::Null,
|
||||||
|
TermionKey::Esc => Key::Esc,
|
||||||
|
_ => Key::Char(' '),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Key {
|
||||||
|
/// Backspace.
|
||||||
|
Backspace,
|
||||||
|
/// Left arrow.
|
||||||
|
Left,
|
||||||
|
/// Right arrow.
|
||||||
|
Right,
|
||||||
|
/// Up arrow.
|
||||||
|
Up,
|
||||||
|
/// Down arrow.
|
||||||
|
Down,
|
||||||
|
/// Home key.
|
||||||
|
Home,
|
||||||
|
/// End key.
|
||||||
|
End,
|
||||||
|
/// Page Up key.
|
||||||
|
PageUp,
|
||||||
|
/// Page Down key.
|
||||||
|
PageDown,
|
||||||
|
/// Delete key.
|
||||||
|
Delete,
|
||||||
|
/// Insert key.
|
||||||
|
Insert,
|
||||||
|
/// Function keys.
|
||||||
|
///
|
||||||
|
/// Only function keys 1 through 12 are supported.
|
||||||
|
F(u8),
|
||||||
|
/// Normal character.
|
||||||
|
Char(char),
|
||||||
|
/// Alt modified character.
|
||||||
|
Alt(char),
|
||||||
|
/// Ctrl modified character.
|
||||||
|
///
|
||||||
|
/// Note that certain keys may not be modifiable with `ctrl`, due to limitations of terminals.
|
||||||
|
Ctrl(char),
|
||||||
|
/// Null byte.
|
||||||
|
Null,
|
||||||
|
/// Esc key.
|
||||||
|
Esc,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_events<F>(stdin: std::io::Stdin, closure: F) where F: Fn(Key) -> (){
|
||||||
|
let stdin = stdin.lock();
|
||||||
|
for c in stdin.keys() {
|
||||||
|
if let Ok(k) = c {
|
||||||
|
let k = convert_key(k);
|
||||||
|
eprintln!("Received key: {:?}", k);
|
||||||
|
closure(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
224
src/ui/pager.rs
224
src/ui/pager.rs
|
@ -1,224 +0,0 @@
|
||||||
/*
|
|
||||||
* meli - ui module.
|
|
||||||
*
|
|
||||||
* Copyright 2017 Manos Pitsidianakis
|
|
||||||
*
|
|
||||||
* This file is part of meli.
|
|
||||||
*
|
|
||||||
* meli is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* meli is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
extern crate ncurses;
|
|
||||||
|
|
||||||
/* Pager represents the part of the UI that shows the mail headers and body for
|
|
||||||
* viewing */
|
|
||||||
pub struct Pager {
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
pad: ncurses::WINDOW,
|
|
||||||
rows: i32,
|
|
||||||
header_height: i32,
|
|
||||||
|
|
||||||
curr_y: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pager {
|
|
||||||
pub fn new(parent: ncurses::WINDOW, entry: &mut Envelope) -> Pager {
|
|
||||||
let mut screen_height = 0;
|
|
||||||
let mut screen_width = 0;
|
|
||||||
ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width);
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getbegyx(parent, &mut y, &mut x);
|
|
||||||
let win = ncurses::subwin(
|
|
||||||
parent,
|
|
||||||
screen_height - (screen_height / 3),
|
|
||||||
screen_width,
|
|
||||||
y + (screen_height / 3),
|
|
||||||
x,
|
|
||||||
);
|
|
||||||
ncurses::wclear(win);
|
|
||||||
//ncurses::touchwin(win);
|
|
||||||
for _ in 1..screen_width + 1 {
|
|
||||||
ncurses::waddstr(win, "β");
|
|
||||||
}
|
|
||||||
let (pad, rows, header_height) = Pager::print_entry(win, entry);
|
|
||||||
ncurses::wbkgd(
|
|
||||||
pad,
|
|
||||||
' ' as ncurses::chtype |
|
|
||||||
ncurses::COLOR_PAIR(super::COLOR_PAIR_DEFAULT) as ncurses::chtype,
|
|
||||||
);
|
|
||||||
ncurses::wrefresh(win);
|
|
||||||
Pager {
|
|
||||||
pad: pad,
|
|
||||||
win: win,
|
|
||||||
rows: rows,
|
|
||||||
header_height: header_height,
|
|
||||||
curr_y: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn scroll(&mut self, motion: i32) {
|
|
||||||
let mut h = 0;
|
|
||||||
let mut w = 0;
|
|
||||||
ncurses::getmaxyx(self.win, &mut h, &mut w);
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
ncurses::getparyx(self.win, &mut y, &mut x);
|
|
||||||
let mut p_x = 0;
|
|
||||||
let mut p_y = 0;
|
|
||||||
ncurses::getbegyx(self.win, &mut p_y, &mut p_x);
|
|
||||||
let pager_size: i32 = h - self.header_height;
|
|
||||||
if pager_size == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
match motion {
|
|
||||||
ncurses::KEY_UP => if self.curr_y > 0 {
|
|
||||||
self.curr_y -= 1;
|
|
||||||
},
|
|
||||||
ncurses::KEY_DOWN => if self.curr_y < self.rows && self.rows - self.curr_y > pager_size
|
|
||||||
{
|
|
||||||
self.curr_y += 1;
|
|
||||||
},
|
|
||||||
ncurses::KEY_NPAGE => {
|
|
||||||
if self.curr_y + h < self.rows && self.rows - self.curr_y - h > pager_size {
|
|
||||||
self.curr_y += pager_size;
|
|
||||||
} else {
|
|
||||||
self.curr_y = if self.rows > h {
|
|
||||||
self.rows - pager_size
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ncurses::KEY_PPAGE => if self.curr_y >= pager_size {
|
|
||||||
self.curr_y -= pager_size;
|
|
||||||
} else {
|
|
||||||
self.curr_y = 0
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* β βββββββββββ β
|
|
||||||
* β β β β
|
|
||||||
* y β β β
|
|
||||||
* β β β β
|
|
||||||
* β xββββββββββ« β β index
|
|
||||||
* β β β β β
|
|
||||||
* h β β β pager β
|
|
||||||
* β ββββββββββw β β
|
|
||||||
*/
|
|
||||||
ncurses::touchwin(self.win);
|
|
||||||
ncurses::prefresh(
|
|
||||||
self.pad,
|
|
||||||
self.curr_y,
|
|
||||||
0,
|
|
||||||
y + self.header_height,
|
|
||||||
p_x + x,
|
|
||||||
y + h - 1,
|
|
||||||
w - 1,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
fn print_entry_headers(win: ncurses::WINDOW, mail: &mut Envelope) -> i32 {
|
|
||||||
let mut i = 0;
|
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
|
||||||
ncurses::waddstr(win, "Date: ");
|
|
||||||
ncurses::waddstr(win, mail.get_date_as_str());
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "From: ");
|
|
||||||
ncurses::waddstr(win, mail.get_from());
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "To: ");
|
|
||||||
ncurses::waddstr(win, mail.get_to());
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "Subject: ");
|
|
||||||
ncurses::waddstr(win, mail.get_subject());
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "Message-ID: ");
|
|
||||||
ncurses::waddstr(
|
|
||||||
win,
|
|
||||||
mail.get_message_id_raw(),
|
|
||||||
//mail.get_message_id(),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "References: ");
|
|
||||||
ncurses::waddstr(win, &format!("{:?}", mail.get_references()));
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::waddstr(win, "In-Reply-To: ");
|
|
||||||
ncurses::waddstr(win, mail.get_in_reply_to_raw());
|
|
||||||
ncurses::waddstr(win, "\n");
|
|
||||||
i += 1;
|
|
||||||
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
|
||||||
/* return number of header lines so we don't overwrite it */
|
|
||||||
i
|
|
||||||
}
|
|
||||||
fn print_entry_content(
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
mail: &mut Envelope,
|
|
||||||
height: i32,
|
|
||||||
) -> (ncurses::WINDOW, i32, i32) {
|
|
||||||
let mut h = 0;
|
|
||||||
let mut w = 0;
|
|
||||||
/* height and width of self.win, the pager window */
|
|
||||||
ncurses::getmaxyx(win, &mut h, &mut w);
|
|
||||||
let mut x = 0;
|
|
||||||
let mut y = 0;
|
|
||||||
/* y,x coordinates of upper left corner of win */
|
|
||||||
ncurses::getparyx(win, &mut y, &mut x);
|
|
||||||
|
|
||||||
let text = mail.get_body().get_text();
|
|
||||||
let lines: Vec<&str> = text.trim().split('\n').collect();
|
|
||||||
let lines_length = lines.len();
|
|
||||||
|
|
||||||
let pad = ncurses::newpad(lines_length as i32, 1024);
|
|
||||||
ncurses::wclear(pad);
|
|
||||||
for l in lines {
|
|
||||||
ncurses::waddstr(pad, &l.replace("%", "%%"));
|
|
||||||
ncurses::waddstr(pad, "\n");
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* β βββββββββββ β
|
|
||||||
* β β β β
|
|
||||||
* y β β β
|
|
||||||
* β β β β
|
|
||||||
* β xββββββββββ« β β index
|
|
||||||
* β β β β β
|
|
||||||
* h β β β pager β
|
|
||||||
* β ββββββββββw β β
|
|
||||||
*/
|
|
||||||
ncurses::pnoutrefresh(pad, 0, 0, y + height, x, y + height - 1, w - 1);
|
|
||||||
(pad, lines_length as i32, height)
|
|
||||||
}
|
|
||||||
fn print_entry(
|
|
||||||
win: ncurses::WINDOW,
|
|
||||||
mail: &mut Envelope,
|
|
||||||
) -> (ncurses::WINDOW, i32, i32) {
|
|
||||||
let header_height = Pager::print_entry_headers(win, mail);
|
|
||||||
Pager::print_entry_content(win, mail, header_height + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Pager {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
ncurses::delwin(self.pad);
|
|
||||||
ncurses::wclear(self.win);
|
|
||||||
ncurses::delwin(self.win);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,25 @@
|
||||||
/// A `(x, y)` position on screen.
|
/// A `(x, y)` position on screen.
|
||||||
pub type Pos = (usize, usize);
|
pub type Pos = (usize, usize);
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_x(p: Pos) -> usize {
|
||||||
|
p.0
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get_y(p: Pos) -> usize {
|
||||||
|
p.1
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_x(p: Pos, new_x: usize) -> Pos {
|
||||||
|
(new_x, p.1)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_y(p: Pos, new_y: usize) -> Pos {
|
||||||
|
(p.0, new_y)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// A `(cols, rows)` size.
|
/// A `(cols, rows)` size.
|
||||||
pub type Size = (usize, usize);
|
pub type Size = (usize, usize);
|
||||||
|
|
Loadingβ¦
Reference in New Issue