Remove ncurses, add termion

embed
Manos Pitsidianakis 2018-07-11 17:07:51 +03:00
parent dbda703bcb
commit ba8508b987
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
17 changed files with 1037 additions and 1187 deletions

View File

@ -18,13 +18,14 @@ config = "0.6"
serde_derive = "^1.0.8"
serde = "^1.0.8"
nom = "3.2.0"
memmap = "*"
memmap = "0.5.2"
base64 = "*"
crossbeam = "^0.3.0"
fnv = "1.0.3"
encoding = "0.2.33"
bitflags = "1.0"
notify = "4.0.1"
termion = "1.5.1"
[dependencies.ncurses]
features = ["wide"]

View File

@ -20,39 +20,40 @@
*/
mod ui;
use ui::index::*;
use ui::ThreadEvent;
use ui::*;
extern crate melib;
extern crate termion;
use melib::*;
extern crate ncurses;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use std::thread;
use std::io::{stdout, stdin, };
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 ui = ui::TUI::initialize();
let backends = Backends::new();
let (sender, receiver): (SyncSender<ThreadEvent>, Receiver<ThreadEvent>) =
sync_channel(::std::mem::size_of::<ThreadEvent>());
let (sender, receiver): (SyncSender<ThreadEvent>, Receiver<ThreadEvent>) = sync_channel(::std::mem::size_of::<ThreadEvent>());
{
let sender = sender.clone();
let mut ch = None;
thread::Builder::new()
.name("input-thread".to_string())
.spawn(move || loop {
ch = ncurses::get_wch();
if let Some(k) = ch {
sender.send(ThreadEvent::Input(k)).unwrap();
}
})
.unwrap();
thread::Builder::new().name("input-thread".to_string()).spawn(move || {
get_events(stdin, |k| { sender.send(ThreadEvent::Input(k)).unwrap(); })
}).unwrap();
}
//let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4");
let mut j = 0;
let folder_length = set.accounts["norn"].folders.len();
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();
})));
}
'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 {
match receiver.recv().unwrap() {
ThreadEvent::Input(k) => match k {
ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)
| ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN) => {
index.handle_input(k);
continue;
}
ncurses::WchResult::Char(k @ 10) => {
index.handle_input(k as i32);
continue;
}
ncurses::WchResult::KeyCode(k @ ncurses::KEY_F1) => {
if !index.handle_input(k) {
break 'main;
ThreadEvent::Input(k) => {
match k {
key @ Key::Char('j') | key @ Key::Char('k') => {
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
state.render();
},
key @ Key::Up | key @ Key::Down => {
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)});
state.render();
}
Key::Char('\n') => {
// index.handle_input(k);
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))});
state.render();
}
}
ncurses::WchResult::Char(113) => {
break 'main;
}
ncurses::WchResult::Char(74) => {
if j < folder_length - 1 {
Key::Char('i') | Key::Esc => {
state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)});
state.render();
}
Key::F(_) => {
// 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;
break 'inner;
}
}
ncurses::WchResult::Char(75) => {
if j > 0 {
},
Key::Char('K') => if j > 0 {
j -= 1;
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);
}
},
}
}
}
drop(ui);
}

View File

@ -44,6 +44,14 @@ pub struct 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> {
let mut collection: Vec<Envelope> = collection?;
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));

View File

@ -1,6 +0,0 @@
/target
**/*.rs.bk
/target
**/*.rs.bk
Cargo.lock

View File

@ -1,7 +0,0 @@
[package]
name = "melt_ui"
version = "0.1.0"
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
[dependencies]
termion = "1.5.1"

View File

@ -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();
}

View File

@ -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!();
}
}

View File

@ -1 +0,0 @@


View File

@ -1,5 +1,6 @@
use std::ops::{Index, IndexMut, Deref, DerefMut};
use position::*;
use super::position::*;
use termion::color::AnsiValue;
pub trait CellAccessor: HasSize {
fn cellvec(&self) -> &Vec<Cell>;
@ -401,9 +402,22 @@ impl Color {
Color::Cyan => 0x06,
Color::White => 0x07,
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`.

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
},
_ => {
},
}
}
}

View File

@ -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);
}
}

View File

@ -2,11 +2,15 @@ extern crate termion;
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::io::{Read, Write};
use termion::input::TermRead;
use std::io::{stdout, stdin, stderr};
//use std::collections::VecDeque;
//use std::process;
@ -20,23 +24,33 @@ use position::Pos;
pub use self::components::*;
pub use self::position::*;
pub struct UIEvent {
#[derive(Debug)]
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,
height: usize,
grid: CellBuffer,
stdin: R,
pub stdout: W,
pub stdout: termion::raw::RawTerminal<W>,
entities: Vec<Entity>,
}
impl<R: Read, W: Write> State<R,W> {
pub fn new(stdout: W, stdin: R) -> Self {
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);
@ -48,8 +62,7 @@ impl<R: Read, W: Write> State<R,W> {
//queue: VecDeque::new();
grid: CellBuffer::new(width+1, height+1, Cell::with_char(' ')),
stdin: stdin,
stdout: stdout,
stdout: stdout.into_raw_mode().unwrap(),
entities: Vec::with_capacity(2),
};
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) {
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);
}
}
}

View File

@ -19,12 +19,15 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
pub mod index;
pub mod pager;
pub mod components;
pub mod position;
pub mod cells;
extern crate termion;
extern crate ncurses;
extern crate melib;
use melib::*;
use std::collections::VecDeque;
/* Color pairs; foreground && background. */
/// Default color.
@ -44,47 +47,11 @@ pub static COLOR_PAIR_UNREAD_ODD: i16 = 7;
/// Line color for unread even entries in index view.
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
/// to the main process.
pub enum ThreadEvent {
/// User input.
Input(ncurses::WchResult),
Input(Key),
/// A watched folder has been refreshed.
RefreshMailbox{ name: String },
//Decode { _ }, // For gpg2 signature check
@ -95,3 +62,218 @@ impl From<RefreshEvent> for ThreadEvent {
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);
}
}
}

View File

@ -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);
}
}

View File

@ -1,5 +1,25 @@
/// A `(x, y)` position on screen.
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.
pub type Size = (usize, usize);