Remove ncurses, add termion
parent
dbda703bcb
commit
ba8508b987
|
@ -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"]
|
||||
|
|
144
src/bin.rs
144
src/bin.rs
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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 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`.
|
|
@ -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::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);
|
||||
}
|
||||
}
|
||||
}
|
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/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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.
|
||||
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);
|
||||
|
Loading…
Reference in New Issue