From d089eb49dcf3d52fb188e6b911076d5829ddf75f Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 14 Jul 2018 15:04:42 +0300 Subject: [PATCH] Add scrolling, only redraw dirty areas --- src/bin.rs | 26 ++--- src/conf/pager.rs | 13 ++- src/ui/components/mail.rs | 164 +++++++++++++++++++++------ src/ui/components/mod.rs | 32 ++++-- src/ui/components/utilities.rs | 185 ++++++++++++++++++++++++------- src/ui/lib.rs | 197 --------------------------------- src/ui/mod.rs | 74 +++++++++---- src/ui/position.rs | 5 + 8 files changed, 374 insertions(+), 322 deletions(-) delete mode 100644 src/ui/lib.rs diff --git a/src/bin.rs b/src/bin.rs index 5fb45fc9e..2a25de037 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -141,11 +141,12 @@ fn main() { let mut state = State::new(_stdout, set); - let a = Entity {component: Box::new(AccountMenu::new(&account)) }; + let menu = Entity {component: Box::new(AccountMenu::new(&account)) }; 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); + let window = Entity { component: Box::new(VSplit::new(menu,b,90)) }; + let status_bar = Entity { component: Box::new(StatusBar::new(window)) }; + state.register_entity(status_bar); state.render(); 'main: loop { @@ -157,7 +158,7 @@ fn main() { Err(_) => {}, }; - state.render(); + state.redraw(); 'inner: loop { match receiver.recv().unwrap() { @@ -165,19 +166,19 @@ fn main() { match k { key @ Key::Char('j') | key @ Key::Char('k') => { state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); - state.render(); + state.redraw(); }, key @ Key::Up | key @ Key::Down => { state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); - state.render(); + state.redraw(); } Key::Char('\n') => { state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Char('\n'))}); - state.render(); + state.redraw(); } Key::Char('i') | Key::Esc => { state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(Key::Esc)}); - state.render(); + state.redraw(); } Key::F(_) => { }, @@ -210,15 +211,6 @@ fn main() { eprintln!("got go cmd with {:?}", v); }, } - /* - if let Some((inst, 'g')) = cmd_queue.front() { - eprintln!("g at front"); - if (Instant::now - inst >= Duration::from_millis(300)) { - - } - - } - */ } } } diff --git a/src/conf/pager.rs b/src/conf/pager.rs index 9f3e16ea0..ec32918f3 100644 --- a/src/conf/pager.rs +++ b/src/conf/pager.rs @@ -6,10 +6,19 @@ fn zero_val () -> usize { 0 } +/// Settings for the pager function. #[derive(Debug, Deserialize, Default)] pub struct PagerSettings { #[serde(default = "zero_val")] - pager_context: usize, + /// Number of context lines when going to next page. + /// Default: 0 + pub pager_context: usize, + #[serde(default = "false_val")] - pager_stop: bool, + /// Stop at the end instead of displaying next mail. + /// Default: false + pub pager_stop: bool, + /// Always show headers when scrolling. + /// Default: false + pub headers_sticky: bool, } diff --git a/src/ui/components/mail.rs b/src/ui/components/mail.rs index 05623c112..e73026a1a 100644 --- a/src/ui/components/mail.rs +++ b/src/ui/components/mail.rs @@ -5,7 +5,7 @@ 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_cols: usize = 500; +const MAX_COLS: usize = 500; /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a /// `Pager`. @@ -34,8 +34,8 @@ impl MailListing { cursor_pos: 0, new_cursor_pos: 0, length: length, - content: CellBuffer::new(MAX_cols, length+1, Cell::with_char(' ')), - dirty: false, + content: CellBuffer::new(MAX_COLS, length+1, Cell::with_char(' ')), + dirty: true, unfocused: false, mailbox: mailbox, pager: None, @@ -46,10 +46,19 @@ impl MailListing { impl MailListing { /// Draw only the list of `Envelope`s. - fn draw_list(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); if self.length == 0 { - clear_area(grid, upper_left, bottom_right); - write_string_to_grid(&format!("Folder `{}` is empty.", self.mailbox.folder.get_name()), grid, Color::Default, Color::Default, upper_left, upper_left); + clear_area(grid, area); + let new_area = (upper_left, set_x(upper_left, get_x(bottom_right))); + write_string_to_grid(&format!("Folder `{}` is empty.", + self.mailbox.folder.get_name()), + grid, + Color::Default, + Color::Default, + new_area); + context.dirty_areas.push_back(area); return; } let rows = get_y(bottom_right) - get_y(upper_left) + 1; @@ -64,12 +73,35 @@ impl MailListing { if *idx >= self.length { continue; //bounds check } - 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 % rows)), bottom_right); + let envelope: &Envelope = &self.mailbox.collection[*idx]; + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos == *idx { + if !envelope.is_seen() { + Color::Byte(252) + } else if *idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + } + } else { + Color::Byte(246) + }; + let new_area = (set_y(upper_left, get_y(upper_left)+(*idx % rows)), bottom_right); + let x = write_string_to_grid(&make_entry_string(envelope, *idx), + grid, + fg_color, + bg_color, + new_area); for x in x..=get_x(bottom_right) { grid[(x,get_y(upper_left)+(*idx % rows))].set_ch(' '); - grid[(x,get_y(upper_left)+(*idx % rows))].set_bg(color); + grid[(x,get_y(upper_left)+(*idx % rows))].set_bg(bg_color); } + context.dirty_areas.push_back(new_area); } self.cursor_pos = self.new_cursor_pos; return; @@ -77,28 +109,42 @@ impl MailListing { self.cursor_pos = self.new_cursor_pos; } + context.dirty_areas.push_back(area); let mut idx = page_no*rows; for y in get_y(upper_left)..=get_y(bottom_right) { if idx >= self.length { - clear_area(grid, set_y(upper_left, y), bottom_right); + clear_area(grid, + (set_y(upper_left, y), bottom_right)); break; } /* Write an entire line for each envelope entry. */ + let envelope: &Envelope = &self.mailbox.collection[idx]; - let color = if self.cursor_pos == idx { + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos == idx { Color::Byte(246) } else { - if idx % 2 == 0 { - Color::Byte(236) + if !envelope.is_seen() { + Color::Byte(251) + } 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); + let x = write_string_to_grid(&make_entry_string(envelope, idx), + grid, + fg_color, + bg_color, + (set_y(upper_left, y), bottom_right)); for x in x..=get_x(bottom_right) { grid[(x,y)].set_ch(' '); - grid[(x,y)].set_bg(color); + grid[(x,y)].set_bg(bg_color); } idx+=1; @@ -106,30 +152,39 @@ impl MailListing { } /// Create a pager for the `Envelope` currently under the cursor. - fn draw_mail_view(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let ref mail = self.mailbox.collection[self.cursor_pos]; let rows = get_y(bottom_right) - get_y(upper_left); let cols = get_x(bottom_right) - get_x(upper_left); self.pager = Some(Pager::new(mail, rows, cols)); - let pager = self.pager.as_mut().unwrap(); - pager.draw(grid, upper_left,bottom_right); + self.pager.as_mut().map(|p| p.draw(grid, area, context)); } } impl Component for MailListing { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.unfocused { if !self.dirty { return; } self.dirty = false; /* Draw the entire list */ - self.draw_list(grid, upper_left,bottom_right); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + self.draw_list(grid, + area, + context); } else { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); if self.length == 0 && self.dirty { - clear_area(grid, upper_left, bottom_right); + clear_area(grid, area); + context.dirty_areas.push_back(area); } /* Render the mail body in a pager, basically copy what HSplit does */ let total_rows = get_y(bottom_right) - get_y(upper_left); @@ -139,12 +194,16 @@ impl Component for MailListing { 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); + p.draw(grid, + ((get_x(upper_left), get_y(upper_left) + mid+6), bottom_right), + context); } return; } self.dirty = false; - self.draw_list(grid, upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-3)); + self.draw_list(grid, + (upper_left, (get_x(bottom_right), get_y(upper_left)+ mid-1)), + context); if self.length == 0 { return; } @@ -166,44 +225,68 @@ impl Component for MailListing { { 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)); + eprintln!("writing headers {} {}", mail.get_date_as_str(), mail.get_subject()); + 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(bottom_right, mid+1))); for x in x..get_x(bottom_right) { 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)); + 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(bottom_right, mid+2))); for x in x..get_x(bottom_right) { 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)); + 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(bottom_right, mid+3))); for x in x..get_x(bottom_right) { 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)); + 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(bottom_right, mid+4))); for x in x..get_x(bottom_right) { 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)); + 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(bottom_right, mid+5))); for x in x..get_x(bottom_right) { grid[(x, mid+5)].set_ch(' '); grid[(x, mid+5)].set_bg(Color::Default); grid[(x, mid+5)].set_fg(Color::Default); } } + context.dirty_areas.push_back((set_y(upper_left, mid), set_y(bottom_right, mid+5))); /* Draw body */ - self.draw_mail_view(grid, (get_x(upper_left), get_y(upper_left) + mid + headers_rows), bottom_right); + self.draw_mail_view(grid, + ((get_x(upper_left), get_y(upper_left) + mid + headers_rows), bottom_right), + context); } } - fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque) { + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { match event.event_type { UIEventType::Input(Key::Up) => { if self.cursor_pos > 0 { @@ -240,9 +323,12 @@ impl Component for MailListing { }, } if let Some(ref mut p) = self.pager { - p.process_event(event, queue); + p.process_event(event, context); } } + fn is_dirty(&self) -> bool { + self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) + } } #[derive(Debug)] @@ -274,10 +360,12 @@ impl AccountMenu { } impl Component for AccountMenu { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !(self.dirty) { return; } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); self.dirty = false; let mut parents: Vec> = vec!(None; self.entries.len()); @@ -340,11 +428,16 @@ impl Component for AccountMenu { } else { format!("{}", lines[idx]) }; - write_string_to_grid(&s, grid, Color::Byte(30), Color::Default, set_y(upper_left, y), bottom_right); + write_string_to_grid(&s, + grid, + Color::Byte(30), + Color::Default, + (set_y(upper_left, y), bottom_right)); idx += 1; } + context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque) { + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { match event.event_type { UIEventType::RefreshMailbox(ref m) => { self.highlight_folder(m); @@ -353,4 +446,7 @@ impl Component for AccountMenu { }, } } + fn is_dirty(&self) -> bool { + self.dirty + } } diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index 3d195aa89..f97c6bada 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -21,8 +21,8 @@ pub mod utilities; pub mod mail; -use super::*; +use super::*; pub use utilities::*; pub use mail::*; @@ -30,7 +30,7 @@ use std::fmt; use super::cells::{Color, CellBuffer}; -use super::position::Pos; +use super::position::{Area, }; use super::{UIEvent, UIEventType, Key}; /// The upper and lower boundary char. @@ -58,14 +58,14 @@ const LIGHT_UP_AND_HORIZONTAL: char = '┴'; /// `Entity` is a container for Components. Totally useless now so if it is not useful in the /// future (ie hold some information, id or state) it should be removed. pub struct Entity { - //queue: VecDeque, + //context: VecDeque, pub component: Box, // more than one? } impl Entity { /// Pass events to child component. - pub fn rcv_event(&mut self, event: &UIEvent, queue: &mut VecDeque) { - self.component.process_event(&event, queue); + pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) { + self.component.process_event(&event, context); } } @@ -79,22 +79,30 @@ impl fmt::Debug for Entity { /// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its /// fields (eg self.dirty = false) and act upon that in their `draw` implementation. pub trait Component { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos); - fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque); + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context); + fn process_event(&mut self, event: &UIEvent, context: &mut Context); + fn is_dirty(&self) -> bool { + true + } } -fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, upper_left: Pos, bottom_right: Pos) -> usize { +fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_color: Color, area: Area) -> usize { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); let (mut x, mut y) = upper_left; + if y > (get_y(bottom_right)) || x > get_x(bottom_right) { + return 0; + } for c in s.chars() { 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)) { + if x == (get_x(bottom_right))+1 { x = get_x(upper_left); y += 1; - if y == (get_y(bottom_right)) { + if y == (get_y(bottom_right))+1 { return x; } } @@ -102,7 +110,9 @@ fn write_string_to_grid(s: &str, grid: &mut CellBuffer, fg_color: Color, bg_colo x } -fn clear_area(grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { +fn clear_area(grid: &mut CellBuffer, area: Area) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); 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(' '); diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs index a8551fa33..d68151db9 100644 --- a/src/ui/components/utilities.rs +++ b/src/ui/components/utilities.rs @@ -5,7 +5,9 @@ pub struct BoxPanel { } impl Component for BoxPanel { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); grid[upper_left].set_ch('u'); grid[bottom_right].set_ch('b'); let width = get_x(bottom_right) - get_x(upper_left); @@ -24,7 +26,7 @@ impl Component for BoxPanel { grid[(i, get_y(upper_left) + height)].set_ch('─'); } } - fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque) { + fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { return; } } @@ -48,7 +50,9 @@ impl HSplit { impl Component for HSplit { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); 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; @@ -56,13 +60,20 @@ impl Component for HSplit { for i in get_x(upper_left)..=get_x(bottom_right) { 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); + let _ = self.top.component.draw(grid, + (upper_left, (get_x(bottom_right), get_y(upper_left) + mid-1)), + context); + let _ = self.bottom.component.draw(grid, + ((get_x(upper_left), get_y(upper_left) + mid), bottom_right), + context); 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); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + self.top.rcv_event(event, context); + self.bottom.rcv_event(event, context); + } + fn is_dirty(&self) -> bool { + self.top.component.is_dirty() || self.bottom.component.is_dirty() } } @@ -86,7 +97,9 @@ impl VSplit { impl Component for VSplit { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); 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; @@ -101,7 +114,7 @@ impl Component for VSplit { } } - for i in get_y(upper_left)..get_y(bottom_right) { + for i in get_y(upper_left)..=get_y(bottom_right) { grid[(mid, i)].set_ch(VERT_BOUNDARY); } if get_y(bottom_right)> 1 { @@ -113,12 +126,19 @@ impl Component for VSplit { _ => {}, } } - 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); + let _ = self.left.component.draw(grid, + (upper_left, (mid-1, get_y(bottom_right))), + context); + let _ = self.right.component.draw(grid, + ((mid+1, get_y(upper_left)), bottom_right), + context); } - fn process_event(&mut self, event: &UIEvent, queue: &mut VecDeque) { - self.left.rcv_event(event, queue); - self.right.rcv_event(event, queue); + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + self.left.rcv_event(event, context); + self.right.rcv_event(event, context); + } + fn is_dirty(&self) -> bool { + self.left.component.is_dirty() || self.right.component.is_dirty() } } @@ -136,7 +156,9 @@ impl TextBox { } impl Component for TextBox { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); let mut x = get_x(upper_left); let y = get_y(upper_left); for c in self.content.chars() { @@ -151,7 +173,7 @@ impl Component for TextBox { } } } - fn process_event(&mut self, _event: &UIEvent, _queue: &mut VecDeque) { + fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) { return; } } @@ -161,27 +183,31 @@ impl Component for TextBox { /// current view of the text. It is responsible for scrolling etc. pub struct Pager { cursor_pos: usize, + lines_no: usize, rows: usize, cols: usize, - height: usize, - pub dirty: bool, + dirty: bool, content: CellBuffer, } impl Pager { - pub fn new(mail: &Envelope, height: usize, width: usize) -> Self { + pub fn new(mail: &Envelope, rows: usize, cols: 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(' ')); + let lines_no = lines.len(); + let mut content = CellBuffer::new(cols, 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)); + write_string_to_grid(l, + &mut content, + Color::Default, + Color::Default, + ((0, i), (cols-1, rows-1))); } Pager { cursor_pos: 0, + lines_no: lines_no, rows: rows, - height: height, - cols: width, + cols: cols, dirty: true, content: content, } @@ -189,32 +215,44 @@ impl Pager { } impl Component for Pager { - fn draw(&mut self, grid: &mut CellBuffer, upper_left: Pos, bottom_right: Pos) { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.dirty { return; } - clear_area(grid, (get_x(upper_left), get_y(upper_left)-1), bottom_right); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + clear_area(grid, + ((get_x(upper_left), get_y(upper_left)-1), bottom_right)); + context.dirty_areas.push_back(((get_x(upper_left), get_y(upper_left)-1), bottom_right)); + + if self.lines_no == 0 { + return; + } + + let pager_context = context.settings.pager.pager_context; + let rows = get_y(bottom_right) - get_y(upper_left); + let page_length = rows / self.lines_no; 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; + 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; + } + } + context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &UIEvent, _queue: &mut VecDeque) { + fn process_event(&mut self, event: &UIEvent, _context: &mut Context) { match event.event_type { UIEventType::Input(Key::Char('k')) => { if self.cursor_pos > 0 { @@ -232,5 +270,70 @@ impl Component for Pager { }, } } + fn is_dirty(&self) -> bool { + self.dirty + } } +/// Status bar. +pub struct StatusBar { + container: Entity, + position: usize, + status: String, + dirty: bool, +} + +impl StatusBar { + pub fn new(container: Entity) -> Self { + StatusBar { + container: container, + position: 0, + status: String::with_capacity(250), + dirty: true, + } + } + fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.dirty { + return; + } + self.dirty = false; + clear_area(grid, area); + let x = write_string_to_grid(&self.status, + grid, + Color::Byte(36), + Color::Default, + area); + context.dirty_areas.push_back(area); + } +} + + +impl Component for StatusBar { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let total_rows = get_y(bottom_right) - get_y(upper_left); + if total_rows == 0 { + return; + } + + let _ = self.container.component.draw(grid, + (upper_left, (get_x(bottom_right), get_y(bottom_right)-1)), + context); + self.draw_status_bar(grid, (set_y(upper_left, get_y(bottom_right)), bottom_right), context); + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) { + self.container.rcv_event(event, context); + match event.event_type { + UIEventType::RefreshMailbox(ref m) => { + self.status = format!("Mailbox: {}, Messages: {}, New: {}", m.folder.get_name(), m.collection.len(), m.collection.iter().filter(|e| !e.is_seen()).count()); + self.dirty = true; + + }, + _ => {}, + } + } + fn is_dirty(&self) -> bool { + self.dirty || self.container.component.is_dirty() + } +} diff --git a/src/ui/lib.rs b/src/ui/lib.rs deleted file mode 100644 index 06a749867..000000000 --- a/src/ui/lib.rs +++ /dev/null @@ -1,197 +0,0 @@ -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; - - -mod components; -mod position; -mod cells; -use cells::{Cell, CellBuffer}; -use position::Pos; - -pub use self::components::*; -pub use self::position::*; - -#[derive(Debug)] -pub enum UIEventType { - Input(Key), - RefreshMailbox(String), - //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 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, "{}{}", 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); - } - - for y in 1..self.height { - write!(self.stdout, "{}", cursor::Goto(1,y as u16)).unwrap(); - for x in 1..self.width { - let c = self.grid[(x,y)]; - if c.get_bg() == cells::Color::Default { - write!(self.stdout, "{}",c.ch()).unwrap(); - } else { - write!(self.stdout, "{}{}{}", termion::color::Bg(termion::color::LightBlack),c.ch(),termion::color::Bg(termion::color::Reset)).unwrap(); - } - - } - } - } - 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(upper_left, bottom_right, &mut self.grid); - } - 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 cc6a4eacc..ba8fd2754 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -19,8 +19,9 @@ * along with meli. If not, see . */ -pub mod components; +#[macro_use] pub mod position; +pub mod components; pub mod cells; extern crate termion; @@ -28,6 +29,7 @@ extern crate ncurses; extern crate melib; use std::collections::VecDeque; +pub use self::position::*; /* Color pairs; foreground && background. */ /// Default color. @@ -87,7 +89,6 @@ use termion::input::TermRead; use self::cells::*; pub use self::components::*; -pub use self::position::*; #[derive(Debug)] pub enum UIEventType { @@ -105,6 +106,14 @@ pub struct UIEvent { pub event_type: UIEventType, } +pub struct Context { + settings: Settings, + queue: VecDeque, + /// Areas of the screen that must be redrawn in the next render + dirty_areas: VecDeque, + +} + pub struct State { cols: usize, rows: usize, @@ -112,8 +121,7 @@ pub struct State { grid: CellBuffer, stdout: termion::raw::RawTerminal, entities: Vec, - settings: Settings, - + context: Context, } impl Drop for State { @@ -133,12 +141,15 @@ impl State { let mut s = State { cols: cols, rows: rows, - //queue: VecDeque::new(); - grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), stdout: stdout.into_raw_mode().unwrap(), entities: Vec::with_capacity(1), - settings: settings, + + context: Context { + settings: settings, + queue: VecDeque::with_capacity(5), + dirty_areas: VecDeque::with_capacity(5), + }, }; write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap(); s @@ -157,17 +168,23 @@ impl State { self.grid.resize(self.cols, self.rows, Cell::with_char(' ')); } - pub fn render(&mut self) { - self.update_size(); - - /* draw each entity */ for i in 0..self.entities.len() { + pub fn redraw(&mut self) { + for i in 0..self.entities.len() { self.draw_entity(i); } + let areas: Vec = self.context.dirty_areas.drain(0..).collect(); + /* draw each entity */ + for a in areas { + self.draw_area(a); + } + } + fn draw_area(&mut self, area: Area) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); - /* Only draw dirty areas */ - for y in 0..self.rows { - write!(self.stdout, "{}", cursor::Goto(1,(y+1) as u16)).unwrap(); - for x in 0..self.cols { + for y in get_y(upper_left)..=get_y(bottom_right) { + write!(self.stdout, "{}", cursor::Goto(get_x(upper_left) as u16 + 1,(y+1) as u16)).unwrap(); + for x in get_x(upper_left)..=get_x(bottom_right) { let c = self.grid[(x,y)]; if c.get_bg() != cells::Color::Default { @@ -188,22 +205,39 @@ impl State { } self.stdout.flush().unwrap(); } + pub fn render(&mut self) { + self.update_size(); + + /* draw each entity */ + for i in 0..self.entities.len() { + self.draw_entity(i); + } + let cols = self.cols; + let rows = self.rows; + + /* Only draw dirty areas */ + self.draw_area(((0, 0), (cols-1, rows-1))); + return; + } pub fn draw_entity(&mut self, idx: usize) { - let ref mut entity = self.entities[idx]; + let entity = &mut self.entities[idx]; let upper_left = (0,0); let bottom_right = (self.cols-1, self.rows-1); - entity.component.draw(&mut self.grid, upper_left, bottom_right); + if entity.component.is_dirty() { + entity.component.draw(&mut self.grid, + (upper_left, bottom_right), + &mut self.context); + } } 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(); + /* pass Context */ /* inform each entity */ for i in 0..self.entities.len() { - self.entities[i].rcv_event(&event, &mut queue); + self.entities[i].rcv_event(&event, &mut self.context); } } } diff --git a/src/ui/position.rs b/src/ui/position.rs index 387dbe7f5..e3ed49503 100644 --- a/src/ui/position.rs +++ b/src/ui/position.rs @@ -18,7 +18,12 @@ pub fn set_y(p: Pos, new_y: usize) -> Pos { (p.0, new_y) } +pub type Area = (Pos, Pos); +#[macro_export] +macro_rules! upper_left { ($a:expr) => ( $a.0 ) } +#[macro_export] +macro_rules! bottom_right { ($a:expr) => ( $a.1 ) } /// A `(cols, rows)` size. pub type Size = (usize, usize);