diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index bdd0b1de..d8e40c19 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -30,6 +30,12 @@ pub use self::thread::*; mod plain; pub use self::plain::*; +#[derive(Debug, Default, Clone)] +pub(in crate::listing) struct DataColumns { + columns: [CellBuffer; 12], + widths: [usize; 12], // widths of columns calculated in first draw and after size changes +} + #[derive(Debug)] struct AccountMenuEntry { name: String, @@ -40,6 +46,8 @@ struct AccountMenuEntry { trait ListingTrait { fn coordinates(&self) -> (usize, usize, Option); fn set_coordinates(&mut self, _: (usize, usize, Option)); + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context); + fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context); } #[derive(Debug)] @@ -65,6 +73,20 @@ impl ListingTrait for ListingComponent { Threaded(ref mut l) => l.set_coordinates(c), } } + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + match self { + Compact(ref mut l) => l.draw_list(grid, area, context), + Plain(ref mut l) => l.draw_list(grid, area, context), + Threaded(ref mut l) => l.draw_list(grid, area, context), + } + } + fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + match self { + Compact(ref mut l) => l.highlight_line(grid, area, idx, context), + Plain(ref mut l) => l.highlight_line(grid, area, idx, context), + Threaded(ref mut l) => l.highlight_line(grid, area, idx, context), + } + } } #[derive(Debug)] @@ -123,11 +145,11 @@ impl Component for Listing { grid[(mid, i)].set_bg(Color::Default); } } - self.dirty = false; context .dirty_areas .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right)))); } + self.dirty = false; if right_component_width == total_cols { match self.component { Compact(ref mut l) => l.draw(grid, area, context), diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index f29e8ebf..eb3b79d6 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -28,30 +28,6 @@ use std::ops::{Deref, DerefMut}; const MAX_COLS: usize = 500; -#[derive(Debug)] -struct MailboxView { - /// (x, y, z): x is accounts, y is folders, z is index inside a folder. - cursor_pos: (usize, usize, usize), - new_cursor_pos: (usize, usize, usize), - length: usize, - sort: (SortField, SortOrder), - subsort: (SortField, SortOrder), - order: FnvHashMap, - /// Cache current view. - content: CellBuffer, - columns: [CellBuffer; 5], - widths: [usize; 5], // widths of columns calculated in first draw and after size changes - /// If we must redraw on next redraw event - dirty: bool, - /// If `self.view` exists or not. - unfocused: bool, - view: ThreadView, - row_updates: StackVec, - - movement: Option, - id: ComponentId, -} - macro_rules! address_list { (($name:expr) as comma_sep_list) => {{ let mut ret: String = @@ -93,16 +69,359 @@ column_str!(struct FromString(String)); column_str!(struct SubjectString(String)); column_str!(struct FlagString(String)); -impl fmt::Display for MailboxView { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", MailboxView::DESCRIPTION) +/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a +/// `ThreadView`. +#[derive(Debug)] +pub struct CompactListing { + /// (x, y, z): x is accounts, y is folders, z is index inside a folder. + cursor_pos: (usize, usize, usize), + new_cursor_pos: (usize, usize, usize), + length: usize, + sort: (SortField, SortOrder), + subsort: (SortField, SortOrder), + order: FnvHashMap, + /// Cache current view. + data_columns: DataColumns, + /// If we must redraw on next redraw event + dirty: bool, + /// If `self.view` exists or not. + unfocused: bool, + view: ThreadView, + row_updates: StackVec, + + movement: Option, + id: ComponentId, +} + +impl ListingTrait for CompactListing { + fn coordinates(&self) -> (usize, usize, Option) { + (self.cursor_pos.0, self.cursor_pos.1, None) + } + fn set_coordinates(&mut self, coordinates: (usize, usize, Option)) { + self.new_cursor_pos = (coordinates.0, coordinates.1, 0); + self.unfocused = false; + } + fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + let is_seen = { + if self.length == 0 { + return; + } + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = account[self.cursor_pos.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; + let thread_node = threads.root_set(idx); + let thread_node = &threads.thread_nodes()[&thread_node]; + let i = if let Some(i) = thread_node.message() { + i + } else { + let mut iter_ptr = thread_node.children()[0]; + while threads.thread_nodes()[&iter_ptr].message().is_none() { + iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0]; + } + threads.thread_nodes()[&iter_ptr].message().unwrap() + }; + + let root_envelope: &Envelope = &account.get_env(&i); + root_envelope.is_seen() + }; + + let fg_color = if !is_seen { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos.2 == idx { + Color::Byte(246) + } else if !is_seen { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + if !grid.is_empty() { + change_colors(grid, area, fg_color, bg_color); + return; + } + + let (upper_left, bottom_right) = area; + let (mut x, _y) = upper_left; + for i in 0..self.data_columns.columns.len() { + let (width, height) = self.data_columns.columns[i].size(); + if self.data_columns.widths[i] == 0 { + continue; + } + copy_area( + grid, + &self.data_columns.columns[i], + ( + set_x(upper_left, x), + set_x( + bottom_right, + std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])), + ), + ), + ((0, idx), (width.saturating_sub(1), height - 1)), + ); + if i != self.data_columns.columns.len() - 1 { + change_colors( + grid, + ( + set_x( + upper_left, + x + self.data_columns.widths[i].saturating_sub(1), + ), + set_x(bottom_right, x + self.data_columns.widths[i] + 1), + ), + fg_color, + bg_color, + ); + } else { + change_colors( + grid, + ( + set_x( + upper_left, + std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])), + ), + bottom_right, + ), + fg_color, + bg_color, + ); + } + x += self.data_columns.widths[i] + 2; // + SEPARATOR + if x > get_x(bottom_right) { + break; + } + } + } + /// Draw the list of `Envelope`s. + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 + { + self.refresh_mailbox(context); + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + if self.length == 0 { + clear_area(grid, area); + copy_area( + grid, + &self.data_columns.columns[0], + area, + ((0, 0), (MAX_COLS - 1, self.length)), + ); + context.dirty_areas.push_back(area); + return; + } + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + + if let Some(mvm) = self.movement.take() { + match mvm { + PageMovement::PageUp => { + self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(rows); + } + PageMovement::PageDown => { + if self.new_cursor_pos.2 + rows + 1 < self.length { + self.new_cursor_pos.2 += rows; + } else { + self.new_cursor_pos.2 = (self.length / rows) * rows; + } + } + PageMovement::Home => { + self.new_cursor_pos.2 = 0; + } + PageMovement::End => { + self.new_cursor_pos.2 = (self.length / rows) * rows; + } + } + } + + let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); + let page_no = (self.new_cursor_pos.2).wrapping_div(rows); + + let top_idx = page_no * rows; + + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { + if *idx >= self.length { + continue; //bounds check + } + let new_area = ( + set_y(upper_left, get_y(upper_left) + (*idx % rows)), + set_y(bottom_right, get_y(upper_left) + (*idx % rows)), + ); + self.highlight_line(grid, new_area, *idx, context); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + if self.new_cursor_pos.2 >= self.length { + self.new_cursor_pos.2 = self.length - 1; + self.cursor_pos.2 = self.new_cursor_pos.2; + } + + let width = width!(area); + self.data_columns.widths = Default::default(); + self.data_columns.widths[0] = self.data_columns.columns[0].size().0; + self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* date*/ + self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* from */ + self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* flags */ + self.data_columns.widths[4] = self.data_columns.columns[4].size().0; /* subject */ + + let min_col_width = std::cmp::min( + 15, + std::cmp::min(self.data_columns.widths[4], self.data_columns.widths[2]), + ); + if self.data_columns.widths[0] + self.data_columns.widths[1] + 3 * min_col_width + 8 > width + { + let remainder = width + .saturating_sub(self.data_columns.widths[0]) + .saturating_sub(self.data_columns.widths[1]) + - 4; + self.data_columns.widths[2] = remainder / 6; + self.data_columns.widths[4] = (2 * remainder) / 3 - self.data_columns.widths[3]; + } else { + let remainder = width + .saturating_sub(self.data_columns.widths[0]) + .saturating_sub(self.data_columns.widths[1]) + .saturating_sub(8); + if min_col_width + self.data_columns.widths[4] > remainder { + self.data_columns.widths[4] = + remainder - min_col_width - self.data_columns.widths[3]; + self.data_columns.widths[2] = min_col_width; + } + } + clear_area(grid, area); + /* Page_no has changed, so draw new page */ + let mut x = get_x(upper_left); + let mut flag_x = 0; + for i in 0..self.data_columns.columns.len() { + let column_width = self.data_columns.columns[i].size().0; + if i == 3 { + flag_x = x; + } + if self.data_columns.widths[i] == 0 { + continue; + } + copy_area( + grid, + &self.data_columns.columns[i], + ( + set_x(upper_left, x), + set_x( + bottom_right, + std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])), + ), + ), + ( + (0, top_idx), + (column_width.saturating_sub(1), self.length - 1), + ), + ); + x += self.data_columns.widths[i] + 2; // + SEPARATOR + if x > get_x(bottom_right) { + break; + } + } + for r in 0..cmp::min(self.length - top_idx, rows) { + let (fg_color, bg_color) = { + let c = &self.data_columns.columns[0][(0, r + top_idx)]; + (c.fg(), c.bg()) + }; + change_colors( + grid, + ( + pos_inc(upper_left, (0, r)), + (flag_x - 1, get_y(upper_left) + r), + ), + fg_color, + bg_color, + ); + for x in flag_x..(flag_x + 2 + self.data_columns.widths[3]) { + grid[(x, get_y(upper_left) + r)].set_bg(bg_color); + } + change_colors( + grid, + ( + ( + flag_x + 2 + self.data_columns.widths[3], + get_y(upper_left) + r, + ), + (get_x(bottom_right), get_y(upper_left) + r), + ), + fg_color, + bg_color, + ); + } + let temp_copy_because_of_nll = self.cursor_pos.2; // FIXME + self.highlight_line( + grid, + ( + set_y( + upper_left, + get_y(upper_left) + (temp_copy_because_of_nll % rows), + ), + set_y( + bottom_right, + get_y(upper_left) + (temp_copy_because_of_nll % rows), + ), + ), + temp_copy_because_of_nll, + context, + ); + if top_idx + rows > self.length { + clear_area( + grid, + ( + pos_inc(upper_left, (0, self.length - top_idx)), + bottom_right, + ), + ); + } + context.dirty_areas.push_back(area); } } -impl MailboxView { - const DESCRIPTION: &'static str = ""; - /// Helper function to format entry strings for CompactListing */ - /* TODO: Make this configurable */ +impl fmt::Display for CompactListing { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "mail") + } +} + +impl Default for CompactListing { + fn default() -> Self { + CompactListing::new() + } +} + +impl CompactListing { + const DESCRIPTION: &'static str = "compact listing"; + fn new() -> Self { + CompactListing { + cursor_pos: (0, 1, 0), + new_cursor_pos: (0, 0, 0), + length: 0, + sort: (Default::default(), Default::default()), + subsort: (SortField::Date, SortOrder::Desc), + order: FnvHashMap::default(), + row_updates: StackVec::new(), + data_columns: DataColumns::default(), + dirty: true, + unfocused: false, + view: ThreadView::default(), + + movement: None, + id: ComponentId::new_v4(), + } + } fn make_entry_string( e: &Envelope, len: usize, @@ -118,7 +437,7 @@ impl MailboxView { if len > 0 { ( IndexNoString(idx.to_string()), - DateString(MailboxView::format_date(e)), + DateString(CompactListing::format_date(e)), FromString(address_list!((e.from()) as comma_sep_list)), FlagString(format!( "{}{}", @@ -130,7 +449,7 @@ impl MailboxView { } else { ( IndexNoString(idx.to_string()), - DateString(MailboxView::format_date(e)), + DateString(CompactListing::format_date(e)), FromString(address_list!((e.from()) as comma_sep_list)), FlagString(format!( "{}{}", @@ -142,34 +461,7 @@ impl MailboxView { } } - fn new() -> Self { - let content = CellBuffer::new(0, 0, Cell::with_char(' ')); - MailboxView { - cursor_pos: (0, 1, 0), - new_cursor_pos: (0, 0, 0), - length: 0, - sort: (Default::default(), Default::default()), - subsort: (SortField::Date, SortOrder::Desc), - order: FnvHashMap::default(), - row_updates: StackVec::new(), - columns: [ - CellBuffer::default(), - CellBuffer::default(), - CellBuffer::default(), - CellBuffer::default(), - CellBuffer::default(), - ], - widths: [0; 5], - content, - dirty: true, - unfocused: false, - view: ThreadView::default(), - - movement: None, - id: ComponentId::new_v4(), - } - } - /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has + /// Fill the `self.data_columns` `CellBuffers` with the contents of the account folder the user has /// chosen. fn refresh_mailbox(&mut self, context: &mut Context) { self.dirty = true; @@ -201,11 +493,12 @@ impl MailboxView { match context.accounts[self.cursor_pos.0].status(folder_hash) { Ok(_) => {} Err(_) => { - self.content = CellBuffer::new(MAX_COLS, 1, Cell::with_char(' ')); + self.data_columns.columns[0] = + CellBuffer::new("Loading.".len(), 1, Cell::with_char(' ')); self.length = 0; write_string_to_grid( "Loading.", - &mut self.content, + &mut self.data_columns.columns[0], Color::Default, Color::Default, ((0, 0), (MAX_COLS - 1, 0)), @@ -255,7 +548,7 @@ impl MailboxView { } let root_envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&i); - let strings = MailboxView::make_entry_string( + let strings = CompactListing::make_entry_string( root_envelope, thread_node.len(), idx, @@ -271,15 +564,20 @@ impl MailboxView { } /* index column */ - self.columns[0] = CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' ')); + self.data_columns.columns[0] = + CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' ')); /* date column */ - self.columns[1] = CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' ')); + self.data_columns.columns[1] = + CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' ')); /* from column */ - self.columns[2] = CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' ')); + self.data_columns.columns[2] = + CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' ')); /* flags column */ - self.columns[3] = CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' ')); + self.data_columns.columns[3] = + CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' ')); /* subject column */ - self.columns[4] = CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' ')); + self.data_columns.columns[4] = + CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' ')); for ((idx, root_idx), strings) in threads.root_iter().enumerate().zip(rows) { let thread_node = &threads.thread_nodes()[&root_idx]; @@ -317,58 +615,58 @@ impl MailboxView { }; let (x, _) = write_string_to_grid( &strings.0, - &mut self.columns[0], + &mut self.data_columns.columns[0], fg_color, bg_color, ((0, idx), (min_width.0, idx)), false, ); for x in x..min_width.0 { - self.columns[0][(x, idx)].set_bg(bg_color); + self.data_columns.columns[0][(x, idx)].set_bg(bg_color); } let (x, _) = write_string_to_grid( &strings.1, - &mut self.columns[1], + &mut self.data_columns.columns[1], fg_color, bg_color, ((0, idx), (min_width.1, idx)), false, ); for x in x..min_width.1 { - self.columns[1][(x, idx)].set_bg(bg_color); + self.data_columns.columns[1][(x, idx)].set_bg(bg_color); } let (x, _) = write_string_to_grid( &strings.2, - &mut self.columns[2], + &mut self.data_columns.columns[2], fg_color, bg_color, ((0, idx), (min_width.2, idx)), false, ); for x in x..min_width.2 { - self.columns[2][(x, idx)].set_bg(bg_color); + self.data_columns.columns[2][(x, idx)].set_bg(bg_color); } let (x, _) = write_string_to_grid( &strings.3, - &mut self.columns[3], + &mut self.data_columns.columns[3], fg_color, bg_color, ((0, idx), (min_width.3, idx)), false, ); for x in x..min_width.3 { - self.columns[3][(x, idx)].set_bg(bg_color); + self.data_columns.columns[3][(x, idx)].set_bg(bg_color); } let (x, _) = write_string_to_grid( &strings.4, - &mut self.columns[4], + &mut self.data_columns.columns[4], fg_color, bg_color, ((0, idx), (min_width.4, idx)), false, ); for x in x..min_width.4 { - self.columns[4][(x, idx)].set_bg(bg_color); + self.data_columns.columns[4][(x, idx)].set_bg(bg_color); } match ( threads.is_snoozed(root_idx), @@ -377,25 +675,27 @@ impl MailboxView { .has_attachments(), ) { (true, true) => { - self.columns[3][(0, idx)].set_fg(Color::Red); - self.columns[3][(1, idx)].set_fg(Color::Byte(103)); + self.data_columns.columns[3][(0, idx)].set_fg(Color::Red); + self.data_columns.columns[3][(1, idx)].set_fg(Color::Byte(103)); } (true, false) => { - self.columns[3][(0, idx)].set_fg(Color::Red); + self.data_columns.columns[3][(0, idx)].set_fg(Color::Red); } (false, true) => { - self.columns[3][(0, idx)].set_fg(Color::Byte(103)); + self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103)); } (false, false) => {} } self.order.insert(i, idx); } - self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); + let message = format!("Folder `{}` is empty.", mailbox.folder.name()); if self.length == 0 { + self.data_columns.columns[0] = + CellBuffer::new(message.len(), self.length + 1, Cell::with_char(' ')); write_string_to_grid( - &format!("Folder `{}` is empty.", mailbox.folder.name()), - &mut self.content, + &message, + &mut self.data_columns.columns[0], Color::Default, Color::Default, ((0, 0), (MAX_COLS - 1, 0)), @@ -405,295 +705,6 @@ impl MailboxView { } } - fn highlight_line( - &mut self, - grid: Option<&mut CellBuffer>, - area: Area, - idx: usize, - context: &Context, - ) { - let is_seen = { - if self.length == 0 { - return; - } - let account = &context.accounts[self.cursor_pos.0]; - let mailbox = account[self.cursor_pos.1].as_ref().unwrap(); - let threads = &account.collection.threads[&mailbox.folder.hash()]; - let thread_node = threads.root_set(idx); - let thread_node = &threads.thread_nodes()[&thread_node]; - let i = if let Some(i) = thread_node.message() { - i - } else { - let mut iter_ptr = thread_node.children()[0]; - while threads.thread_nodes()[&iter_ptr].message().is_none() { - iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0]; - } - threads.thread_nodes()[&iter_ptr].message().unwrap() - }; - - let root_envelope: &Envelope = &account.get_env(&i); - root_envelope.is_seen() - }; - - let fg_color = if !is_seen { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if !is_seen { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - if idx == self.cursor_pos.2 || grid.is_none() { - if let Some(grid) = grid { - change_colors(grid, area, fg_color, bg_color); - } else { - change_colors(&mut self.content, area, fg_color, bg_color); - } - return; - } - - let (upper_left, bottom_right) = area; - let grid = grid.unwrap(); - let (mut x, _y) = upper_left; - for i in 0..self.columns.len() { - let (width, height) = self.columns[i].size(); - if self.widths[i] == 0 { - continue; - } - copy_area( - grid, - &self.columns[i], - ( - set_x(upper_left, x), - set_x( - bottom_right, - std::cmp::min(get_x(bottom_right), x + (self.widths[i])), - ), - ), - ((0, idx), (width.saturating_sub(1), height - 1)), - ); - if i != self.columns.len() - 1 { - change_colors( - grid, - ( - set_x(upper_left, x + self.widths[i].saturating_sub(1)), - set_x(bottom_right, x + self.widths[i] + 1), - ), - fg_color, - bg_color, - ); - } else { - change_colors( - grid, - ( - set_x( - upper_left, - std::cmp::min(get_x(bottom_right), x + (self.widths[i])), - ), - bottom_right, - ), - fg_color, - bg_color, - ); - } - x += self.widths[i] + 2; // + SEPARATOR - if x > get_x(bottom_right) { - break; - } - } - } - - /// Draw the list of `Envelope`s. - fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 - { - self.refresh_mailbox(context); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - if self.length == 0 { - clear_area(grid, area); - copy_area( - grid, - &self.content, - area, - ((0, 0), (MAX_COLS - 1, self.length)), - ); - context.dirty_areas.push_back(area); - return; - } - let rows = get_y(bottom_right) - get_y(upper_left) + 1; - - if let Some(mvm) = self.movement.take() { - match mvm { - PageMovement::PageUp => { - self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(rows); - } - PageMovement::PageDown => { - if self.new_cursor_pos.2 + rows + 1 < self.length { - self.new_cursor_pos.2 += rows; - } else { - self.new_cursor_pos.2 = (self.length / rows) * rows; - } - } - PageMovement::Home => { - self.new_cursor_pos.2 = 0; - } - PageMovement::End => { - self.new_cursor_pos.2 = (self.length / rows) * rows; - } - } - } - - let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); - let page_no = (self.new_cursor_pos.2).wrapping_div(rows); - - let top_idx = page_no * rows; - - /* If cursor position has changed, remove the highlight from the previous position and - * apply it in the new one. */ - if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { - let old_cursor_pos = self.cursor_pos; - self.cursor_pos = self.new_cursor_pos; - for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { - if *idx >= self.length { - continue; //bounds check - } - let new_area = ( - set_y(upper_left, get_y(upper_left) + (*idx % rows)), - set_y(bottom_right, get_y(upper_left) + (*idx % rows)), - ); - self.highlight_line(Some(grid), new_area, *idx, context); - context.dirty_areas.push_back(new_area); - } - return; - } else if self.cursor_pos != self.new_cursor_pos { - self.cursor_pos = self.new_cursor_pos; - } - if self.new_cursor_pos.2 >= self.length { - self.new_cursor_pos.2 = self.length - 1; - self.cursor_pos.2 = self.new_cursor_pos.2; - } - - let width = width!(area); - self.widths = [ - self.columns[0].size().0, - self.columns[1].size().0, /* date*/ - self.columns[2].size().0, /* from */ - self.columns[3].size().0, /* flags */ - self.columns[4].size().0, /* subject */ - ]; - let min_col_width = std::cmp::min(15, std::cmp::min(self.widths[4], self.widths[2])); - if self.widths[0] + self.widths[1] + 3 * min_col_width + 8 > width { - let remainder = width - .saturating_sub(self.widths[0]) - .saturating_sub(self.widths[1]) - - 4; - self.widths[2] = remainder / 6; - self.widths[4] = (2 * remainder) / 3 - self.widths[3]; - } else { - let remainder = width - .saturating_sub(self.widths[0]) - .saturating_sub(self.widths[1]) - .saturating_sub(8); - if min_col_width + self.widths[4] > remainder { - self.widths[4] = remainder - min_col_width - self.widths[3]; - self.widths[2] = min_col_width; - } - } - clear_area(grid, area); - /* Page_no has changed, so draw new page */ - let mut x = get_x(upper_left); - let mut flag_x = 0; - for i in 0..self.columns.len() { - let column_width = self.columns[i].size().0; - if i == 3 { - flag_x = x; - } - if self.widths[i] == 0 { - continue; - } - copy_area( - grid, - &self.columns[i], - ( - set_x(upper_left, x), - set_x( - bottom_right, - std::cmp::min(get_x(bottom_right), x + (self.widths[i])), - ), - ), - ( - (0, top_idx), - (column_width.saturating_sub(1), self.length - 1), - ), - ); - x += self.widths[i] + 2; // + SEPARATOR - if x > get_x(bottom_right) { - break; - } - } - for r in 0..cmp::min(self.length - top_idx, rows) { - let (fg_color, bg_color) = { - let c = &self.columns[0][(0, r + top_idx)]; - (c.fg(), c.bg()) - }; - change_colors( - grid, - ( - pos_inc(upper_left, (0, r)), - (flag_x - 1, get_y(upper_left) + r), - ), - fg_color, - bg_color, - ); - for x in flag_x..(flag_x + 2 + self.widths[3]) { - grid[(x, get_y(upper_left) + r)].set_bg(bg_color); - } - change_colors( - grid, - ( - (flag_x + 2 + self.widths[3], get_y(upper_left) + r), - (get_x(bottom_right), get_y(upper_left) + r), - ), - fg_color, - bg_color, - ); - } - let temp_copy_because_of_nll = self.cursor_pos.2; // FIXME - self.highlight_line( - Some(grid), - ( - set_y( - upper_left, - get_y(upper_left) + (temp_copy_because_of_nll % rows), - ), - set_y( - bottom_right, - get_y(upper_left) + (temp_copy_because_of_nll % rows), - ), - ), - temp_copy_because_of_nll, - context, - ); - if top_idx + rows > self.length { - clear_area( - grid, - ( - pos_inc(upper_left, (0, self.length - top_idx)), - bottom_right, - ), - ); - } - context.dirty_areas.push_back(area); - } - fn format_date(envelope: &Envelope) -> String { let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); let now: std::time::Duration = std::time::SystemTime::now() @@ -710,7 +721,7 @@ impl MailboxView { } } -impl Component for MailboxView { +impl Component for CompactListing { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.unfocused { if !self.is_dirty() { @@ -726,7 +737,7 @@ impl Component for MailboxView { let top_idx = page_no * rows; if row >= top_idx && row <= top_idx + rows { self.highlight_line( - Some(grid), + grid, ( set_y(upper_left, get_y(upper_left) + (row % rows)), set_y(bottom_right, get_y(upper_left) + (row % rows)), @@ -820,7 +831,12 @@ impl Component for MailboxView { UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => { if let Some(row) = self.order.remove(old_hash) { self.order.insert(*new_hash, row); - self.highlight_line(None, ((0, row), (MAX_COLS - 1, row)), row, context); + self.highlight_line( + &mut CellBuffer::default(), + ((0, row), (MAX_COLS - 1, row)), + row, + context, + ); self.row_updates.push(*new_hash); self.dirty = true; } else { @@ -966,119 +982,3 @@ impl Component for MailboxView { self.id = id; } } - -/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a -/// `ThreadView`. -#[derive(Debug)] -pub struct CompactListing { - views: Vec, - cursor: usize, - dirty: bool, - populated: bool, - id: ComponentId, -} - -impl ListingTrait for CompactListing { - fn coordinates(&self) -> (usize, usize, Option) { - (self.cursor, self.views[self.cursor].cursor_pos.1, None) - } - fn set_coordinates(&mut self, coordinates: (usize, usize, Option)) { - self.views - .get_mut(self.cursor) - .map(|v| v.new_cursor_pos = (coordinates.0, coordinates.1, 0)); - self.views.get_mut(self.cursor).map(|v| v.unfocused = false); - } -} - -impl fmt::Display for CompactListing { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "mail") - } -} - -impl Default for CompactListing { - fn default() -> Self { - CompactListing::new() - } -} - -impl CompactListing { - const DESCRIPTION: &'static str = "compact listing"; - pub fn new() -> Self { - CompactListing { - views: Vec::with_capacity(8), - cursor: 0, - dirty: true, - populated: false, - id: ComponentId::new_v4(), - } - } -} - -impl Component for CompactListing { - fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.populated { - debug!("populating"); - for (idx, a) in context.accounts.iter().enumerate() { - for (fidx, _) in a.iter_mailboxes().enumerate() { - let mut m = MailboxView::new(); - m.new_cursor_pos = (idx, fidx, 0); - self.views.push(m); - } - } - self.populated = true; - } - self.dirty = false; - - if self.views.is_empty() { - return; - } - self.views[self.cursor].draw(grid, area, context); - } - fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { - if self.views.is_empty() { - return false; - } - match *event { - UIEvent::Resize - | UIEvent::MailboxUpdate(_) - | UIEvent::ComponentKill(_) - | UIEvent::StartupCheck(_) - | UIEvent::EnvelopeUpdate(_) - | UIEvent::EnvelopeRename(_, _) - | UIEvent::EnvelopeRemove(_) => { - return self.views[self.cursor].process_event(event, context) - } - _ => return self.views[self.cursor].process_event(event, context), - } - } - - fn is_dirty(&self) -> bool { - if self.views.is_empty() { - return self.dirty; - } - self.dirty || self.views[self.cursor].is_dirty() - } - fn set_dirty(&mut self) { - if self.views.is_empty() { - return; - } - - self.views[self.cursor].set_dirty(); - self.dirty = true; - } - - fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { - if self.views.is_empty() { - return Default::default(); - } - self.views[self.cursor].get_shortcuts(context) - } - - fn id(&self) -> ComponentId { - self.id - } - fn set_id(&mut self, id: ComponentId) { - self.id = id; - } -} diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs index b39c546e..f045ba3a 100644 --- a/ui/src/components/mail/listing/plain.rs +++ b/ui/src/components/mail/listing/plain.rs @@ -93,6 +93,106 @@ impl ListingTrait for PlainListing { fn set_coordinates(&mut self, coordinates: (usize, usize, Option)) { self.new_cursor_pos = (coordinates.0, coordinates.1, 0); } + fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + let account = &context.accounts[self.cursor_pos.0]; + let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos.2 == idx { + Color::Byte(246) + } else if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } + + /// Draw the list of `Envelope`s. + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 + { + self.refresh_mailbox(context); + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + if self.length == 0 { + clear_area(grid, area); + copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); + context.dirty_areas.push_back(area); + return; + } + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + if let Some(mvm) = self.movement.take() { + match mvm { + PageMovement::PageUp => { + self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(rows); + } + PageMovement::PageDown => { + if self.new_cursor_pos.2 + rows + 1 < self.length { + self.new_cursor_pos.2 += rows; + } else { + self.new_cursor_pos.2 = (self.length / rows) * rows; + } + } + PageMovement::Home => { + self.new_cursor_pos.2 = 0; + } + PageMovement::End => { + self.new_cursor_pos.2 = (self.length / rows) * rows; + } + } + } + let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); + let page_no = (self.new_cursor_pos.2).wrapping_div(rows); + + let top_idx = page_no * rows; + + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { + if *idx >= self.length { + continue; //bounds check + } + let new_area = ( + set_y(upper_left, get_y(upper_left) + (*idx % rows)), + set_y(bottom_right, get_y(upper_left) + (*idx % rows)), + ); + self.highlight_line(grid, new_area, *idx, context); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + + /* Page_no has changed, so draw new page */ + copy_area( + grid, + &self.content, + area, + ((0, top_idx), (MAX_COLS - 1, self.length)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), + set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), + ), + self.cursor_pos.2, + context, + ); + context.dirty_areas.push_back(area); + } } impl Default for PlainListing { @@ -334,106 +434,6 @@ impl PlainListing { ); } - fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { - let account = &context.accounts[self.cursor_pos.0]; - let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); - - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors(grid, area, fg_color, bg_color); - } - - /// Draw the list of `Envelope`s. - fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 - { - self.refresh_mailbox(context); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - if self.length == 0 { - clear_area(grid, area); - copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); - context.dirty_areas.push_back(area); - return; - } - let rows = get_y(bottom_right) - get_y(upper_left) + 1; - if let Some(mvm) = self.movement.take() { - match mvm { - PageMovement::PageUp => { - self.new_cursor_pos.2 = self.new_cursor_pos.2.saturating_sub(rows); - } - PageMovement::PageDown => { - if self.new_cursor_pos.2 + rows + 1 < self.length { - self.new_cursor_pos.2 += rows; - } else { - self.new_cursor_pos.2 = (self.length / rows) * rows; - } - } - PageMovement::Home => { - self.new_cursor_pos.2 = 0; - } - PageMovement::End => { - self.new_cursor_pos.2 = (self.length / rows) * rows; - } - } - } - let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); - let page_no = (self.new_cursor_pos.2).wrapping_div(rows); - - let top_idx = page_no * rows; - - /* If cursor position has changed, remove the highlight from the previous position and - * apply it in the new one. */ - if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { - let old_cursor_pos = self.cursor_pos; - self.cursor_pos = self.new_cursor_pos; - for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { - if *idx >= self.length { - continue; //bounds check - } - let new_area = ( - set_y(upper_left, get_y(upper_left) + (*idx % rows)), - set_y(bottom_right, get_y(upper_left) + (*idx % rows)), - ); - self.highlight_line(grid, new_area, *idx, context); - context.dirty_areas.push_back(new_area); - } - return; - } else if self.cursor_pos != self.new_cursor_pos { - self.cursor_pos = self.new_cursor_pos; - } - - /* Page_no has changed, so draw new page */ - copy_area( - grid, - &self.content, - area, - ((0, top_idx), (MAX_COLS - 1, self.length)), - ); - self.highlight_line( - grid, - ( - set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), - set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), - ), - self.cursor_pos.2, - context, - ); - context.dirty_areas.push_back(area); - } fn format_date(envelope: &Envelope) -> String { let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); let now: std::time::Duration = std::time::SystemTime::now() diff --git a/ui/src/components/mail/listing/thread.rs b/ui/src/components/mail/listing/thread.rs index 772361d6..79924806 100644 --- a/ui/src/components/mail/listing/thread.rs +++ b/ui/src/components/mail/listing/thread.rs @@ -59,6 +59,112 @@ impl ListingTrait for ThreadListing { fn set_coordinates(&mut self, coordinates: (usize, usize, Option)) { self.new_cursor_pos = (coordinates.0, coordinates.1, 0); } + fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 + { + self.refresh_mailbox(context); + } + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + if self.length == 0 { + clear_area(grid, area); + copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); + context.dirty_areas.push_back(area); + return; + } + let rows = get_y(bottom_right) - get_y(upper_left) + 1; + let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); + let page_no = (self.new_cursor_pos.2).wrapping_div(rows); + + let top_idx = page_no * rows; + if !self.initialised { + self.initialised = false; + copy_area( + grid, + &self.content, + area, + ((0, top_idx), (MAX_COLS - 1, self.length)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), + set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), + ), + self.cursor_pos.2, + context, + ); + context.dirty_areas.push_back(area); + } + /* If cursor position has changed, remove the highlight from the previous position and + * apply it in the new one. */ + if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { + let old_cursor_pos = self.cursor_pos; + self.cursor_pos = self.new_cursor_pos; + for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { + if *idx >= self.length { + continue; //bounds check + } + let new_area = ( + set_y(upper_left, get_y(upper_left) + (*idx % rows)), + set_y(bottom_right, get_y(upper_left) + (*idx % rows)), + ); + self.highlight_line(grid, new_area, *idx, context); + context.dirty_areas.push_back(new_area); + } + return; + } else if self.cursor_pos != self.new_cursor_pos { + self.cursor_pos = self.new_cursor_pos; + } + + /* Page_no has changed, so draw new page */ + copy_area( + grid, + &self.content, + area, + ((0, top_idx), (MAX_COLS - 1, self.length)), + ); + self.highlight_line( + grid, + ( + set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), + set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), + ), + self.cursor_pos.2, + context, + ); + context.dirty_areas.push_back(area); + } + + fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { + let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] + .as_ref() + .unwrap(); + if mailbox.is_empty() || mailbox.len() <= idx { + return; + } + + if self.locations[idx] != 0 { + let envelope: &Envelope = + &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]); + + let fg_color = if !envelope.is_seen() { + Color::Byte(0) + } else { + Color::Default + }; + let bg_color = if self.cursor_pos.2 == idx { + Color::Byte(246) + } else if !envelope.is_seen() { + Color::Byte(251) + } else if idx % 2 == 0 { + Color::Byte(236) + } else { + Color::Default + }; + change_colors(grid, area, fg_color, bg_color); + } + } } impl Default for ThreadListing { @@ -253,114 +359,6 @@ impl ThreadListing { } } - fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - if mailbox.is_empty() || mailbox.len() <= idx { - return; - } - - if self.locations[idx] != 0 { - let envelope: &Envelope = - &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]); - - let fg_color = if !envelope.is_seen() { - Color::Byte(0) - } else { - Color::Default - }; - let bg_color = if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if !envelope.is_seen() { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - }; - change_colors(grid, area, fg_color, bg_color); - } - } - - /// Draw the list of `Envelope`s. - fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.cursor_pos.1 != self.new_cursor_pos.1 || self.cursor_pos.0 != self.new_cursor_pos.0 - { - self.refresh_mailbox(context); - } - let upper_left = upper_left!(area); - let bottom_right = bottom_right!(area); - if self.length == 0 { - clear_area(grid, area); - copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0))); - context.dirty_areas.push_back(area); - return; - } - let rows = get_y(bottom_right) - get_y(upper_left) + 1; - let prev_page_no = (self.cursor_pos.2).wrapping_div(rows); - let page_no = (self.new_cursor_pos.2).wrapping_div(rows); - - let top_idx = page_no * rows; - if !self.initialised { - self.initialised = false; - copy_area( - grid, - &self.content, - area, - ((0, top_idx), (MAX_COLS - 1, self.length)), - ); - self.highlight_line( - grid, - ( - set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), - set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), - ), - self.cursor_pos.2, - context, - ); - context.dirty_areas.push_back(area); - } - /* If cursor position has changed, remove the highlight from the previous position and - * apply it in the new one. */ - if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no { - let old_cursor_pos = self.cursor_pos; - self.cursor_pos = self.new_cursor_pos; - for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] { - if *idx >= self.length { - continue; //bounds check - } - let new_area = ( - set_y(upper_left, get_y(upper_left) + (*idx % rows)), - set_y(bottom_right, get_y(upper_left) + (*idx % rows)), - ); - self.highlight_line(grid, new_area, *idx, context); - context.dirty_areas.push_back(new_area); - } - return; - } else if self.cursor_pos != self.new_cursor_pos { - self.cursor_pos = self.new_cursor_pos; - } - - /* Page_no has changed, so draw new page */ - copy_area( - grid, - &self.content, - area, - ((0, top_idx), (MAX_COLS - 1, self.length)), - ); - self.highlight_line( - grid, - ( - set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)), - set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)), - ), - self.cursor_pos.2, - context, - ); - context.dirty_areas.push_back(area); - } - fn make_thread_entry( envelope: &Envelope, idx: usize,