diff --git a/Cargo.toml b/Cargo.toml index 9d724139..1ddae1d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] diff --git a/src/bin.rs b/src/bin.rs index 976c8b83..eee661e2 100644 --- a/src/bin.rs +++ b/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, Receiver) = - sync_channel(::std::mem::size_of::()); + let (sender, receiver): (SyncSender, Receiver) = sync_channel(::std::mem::size_of::()); { 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 = 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 = 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); } diff --git a/src/mailbox/mod.rs b/src/mailbox/mod.rs index a35cc821..b0129dbf 100644 --- a/src/mailbox/mod.rs +++ b/src/mailbox/mod.rs @@ -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>, collection: Result>) -> Result { let mut collection: Vec = collection?; collection.sort_by(|a, b| a.get_date().cmp(&b.get_date())); diff --git a/src/melt_ui/.gitignore b/src/melt_ui/.gitignore deleted file mode 100644 index 626bd181..00000000 --- a/src/melt_ui/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/target -**/*.rs.bk - -/target -**/*.rs.bk -Cargo.lock diff --git a/src/melt_ui/Cargo.toml b/src/melt_ui/Cargo.toml deleted file mode 100644 index ab719765..00000000 --- a/src/melt_ui/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "melt_ui" -version = "0.1.0" -authors = ["Manos Pitsidianakis "] - -[dependencies] -termion = "1.5.1" diff --git a/src/melt_ui/src/bin/bin.rs b/src/melt_ui/src/bin/bin.rs deleted file mode 100644 index 04b70932..00000000 --- a/src/melt_ui/src/bin/bin.rs +++ /dev/null @@ -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(); - - - -} diff --git a/src/melt_ui/src/components.rs b/src/melt_ui/src/components.rs deleted file mode 100644 index 8eb692a9..00000000 --- a/src/melt_ui/src/components.rs +++ /dev/null @@ -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, // 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!(); - } -} diff --git a/src/melt_ui/src/log2 b/src/melt_ui/src/log2 deleted file mode 100644 index ef910527..00000000 --- a/src/melt_ui/src/log2 +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/melt_ui/src/cells.rs b/src/ui/cells.rs similarity index 95% rename from src/melt_ui/src/cells.rs rename to src/ui/cells.rs index 0f06a3c5..0e7670c4 100644 --- a/src/melt_ui/src/cells.rs +++ b/src/ui/cells.rs @@ -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; @@ -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`. diff --git a/src/ui/components/mail.rs b/src/ui/components/mail.rs new file mode 100644 index 00000000..33e8a5bf --- /dev/null +++ b/src/ui/components/mail.rs @@ -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, + +} + +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) { + 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); + } + } +} diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs new file mode 100644 index 00000000..723cff2e --- /dev/null +++ b/src/ui/components/mod.rs @@ -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 . + */ + +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, // more than one? +} + +impl Entity { + pub fn rcv_event(&mut self, event: &UIEvent, queue: &mut VecDeque) { + 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); +} + + +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); + } + } +} + diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs new file mode 100644 index 00000000..9fea1c19 --- /dev/null +++ b/src/ui/components/utilities.rs @@ -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) { + 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) { + 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) { + 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) { + 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) { + 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; + } + }, + _ => { + }, + } + } +} + diff --git a/src/ui/index.rs b/src/ui/index.rs deleted file mode 100644 index 3ef72269..00000000 --- a/src/ui/index.rs +++ /dev/null @@ -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 . - */ -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, -} - - -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 = 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>, - ) { - /* 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); - } -} diff --git a/src/melt_ui/src/lib.rs b/src/ui/lib.rs similarity index 51% rename from src/melt_ui/src/lib.rs rename to src/ui/lib.rs index 2ca049bf..06a74986 100644 --- a/src/melt_ui/src/lib.rs +++ b/src/ui/lib.rs @@ -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 { + +#[derive(Debug)] +pub struct UIEvent { + pub id: u64, + pub event_type: UIEventType, +} + +pub struct State { width: usize, height: usize, grid: CellBuffer, - stdin: R, - pub stdout: W, + pub stdout: termion::raw::RawTerminal, entities: Vec, } -impl State { - pub fn new(stdout: W, stdin: R) -> Self { +impl State { + 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 State { //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 State { 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(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); + } + } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 2c0b38fb..5d5f6c6d 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -19,12 +19,15 @@ * along with meli. If not, see . */ -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 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 { + width: usize, + height: usize, + + grid: CellBuffer, + pub stdout: termion::raw::RawTerminal, + entities: Vec, + +} + +impl Drop for State { + 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 State { + 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 = 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(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); + } + } +} diff --git a/src/ui/pager.rs b/src/ui/pager.rs deleted file mode 100644 index 8d5d7c12..00000000 --- a/src/ui/pager.rs +++ /dev/null @@ -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 . - */ - -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); - } -} diff --git a/src/melt_ui/src/position.rs b/src/ui/position.rs similarity index 80% rename from src/melt_ui/src/position.rs rename to src/ui/position.rs index 03d2fdfa..387dbe7f 100644 --- a/src/melt_ui/src/position.rs +++ b/src/ui/position.rs @@ -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);