From 7e80ad2a86de587cbbd8fa6a0e80b8dda74fa5fa Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 10 Apr 2019 16:51:15 +0300 Subject: [PATCH] ui: hold live entries for each mailbox in CompactListing --- ui/src/components/mail/listing/compact.rs | 254 ++++++++++++++++------ 1 file changed, 190 insertions(+), 64 deletions(-) diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index deaf70bbc..e885cf092 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -26,10 +26,8 @@ use components::utilities::PageMovement; const MAX_COLS: usize = 500; -/// 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 { +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), @@ -47,28 +45,13 @@ pub struct CompactListing { movement: Option, } -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); - } -} - -impl Default for CompactListing { - fn default() -> Self { - Self::new() - } -} - -impl fmt::Display for CompactListing { +impl fmt::Display for MailboxView { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "mail") + write!(f, "") } } -impl CompactListing { +impl MailboxView { /// Helper function to format entry strings for CompactListing */ /* TODO: Make this configurable */ fn make_entry_string(e: &Envelope, len: usize, idx: usize) -> String { @@ -76,7 +59,7 @@ impl CompactListing { format!( "{} {} {:.85} ({})", idx, - &CompactListing::format_date(e), + &MailboxView::format_date(e), e.subject(), len ) @@ -84,15 +67,15 @@ impl CompactListing { format!( "{} {} {:.85}", idx, - &CompactListing::format_date(e), + &MailboxView::format_date(e), e.subject(), ) } } - pub fn new() -> Self { + fn new() -> Self { let content = CellBuffer::new(0, 0, Cell::with_char(' ')); - CompactListing { + MailboxView { cursor_pos: (0, 1, 0), new_cursor_pos: (0, 0, 0), length: 0, @@ -114,7 +97,6 @@ impl CompactListing { if !(self.cursor_pos.0 == self.new_cursor_pos.0 && self.cursor_pos.1 == self.new_cursor_pos.1) { - //TODO: store cursor_pos in each folder self.cursor_pos.2 = 0; self.new_cursor_pos.2 = 0; } @@ -209,7 +191,7 @@ impl CompactListing { Color::Default }; let (x, _) = write_string_to_grid( - &CompactListing::make_entry_string(root_envelope, thread_node.len(), idx), + &MailboxView::make_entry_string(root_envelope, thread_node.len(), idx), &mut self.content, fg_color, bg_color, @@ -377,7 +359,7 @@ impl CompactListing { } } -impl Component for CompactListing { +impl Component for MailboxView { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if !self.unfocused { if !self.is_dirty() { @@ -438,47 +420,16 @@ impl Component for CompactListing { } UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { let folder_length = context.accounts[self.cursor_pos.0].len(); - let accounts_length = context.accounts.len(); match k { - 'J' if folder_length > 0 => { - if self.new_cursor_pos.1 < folder_length - 1 { - self.new_cursor_pos.1 = self.cursor_pos.1 + 1; - self.refresh_mailbox(context); - } else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 - { - self.new_cursor_pos.0 = self.cursor_pos.0 + 1; - self.new_cursor_pos.1 = 0; - self.refresh_mailbox(context); - } - } - 'K' => { - if self.cursor_pos.1 > 0 { - self.new_cursor_pos.1 = self.cursor_pos.1 - 1; - self.refresh_mailbox(context); - } else if self.cursor_pos.0 > 0 { - self.new_cursor_pos.0 = self.cursor_pos.0 - 1; - self.new_cursor_pos.1 = 0; - self.refresh_mailbox(context); - } - } - _ => {} - } - return true; - } - UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { - let accounts_length = context.accounts.len(); - match k { - 'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => { - self.new_cursor_pos.0 = self.cursor_pos.0 + 1; - self.new_cursor_pos.1 = 0; + 'J' if folder_length > 0 && self.new_cursor_pos.1 < folder_length - 1 => { + self.new_cursor_pos.1 = self.cursor_pos.1 + 1; self.refresh_mailbox(context); } - 'l' if self.cursor_pos.0 > 0 => { - self.new_cursor_pos.0 = self.cursor_pos.0 - 1; - self.new_cursor_pos.1 = 0; + 'K' if self.cursor_pos.1 > 0 => { + self.new_cursor_pos.1 = self.cursor_pos.1 - 1; self.refresh_mailbox(context); } - _ => {} + _ => return false, } return true; } @@ -637,3 +588,178 @@ impl Component for CompactListing { map } } + +/// 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, +} + +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[self.cursor].new_cursor_pos = (coordinates.0, coordinates.1, 0); + } +} + +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 { + pub fn new() -> Self { + CompactListing { + views: Vec::with_capacity(8), + cursor: 0, + dirty: true, + populated: false, + } + } +} + +impl Component for CompactListing { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if !self.populated { + eprintln!("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; + } + + 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.event_type { + UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => { + let folder_length = context.accounts[self.views[self.cursor].cursor_pos.0].len(); + match k { + 'J' if folder_length > 0 => { + if self.cursor < self.views.len() - 1 { + self.cursor += 1; + self.dirty = true; + } + } + 'K' if self.cursor > 0 => { + self.cursor -= 1; + self.dirty = true; + } + _ => return false, + } + self.views[self.cursor].refresh_mailbox(context); + return true; + } + UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => { + let binary_search_account = |entries: &[MailboxView], x: usize| -> Option { + if entries.is_empty() { + return None; + } + + let mut low = 0; + let mut high = entries.len() - 1; + while low < high { + let mid = low + (high - low) / 2; + if x > entries[mid].new_cursor_pos.0 { + low = mid + 1; + } else { + high = mid; + } + } + return Some(low); + }; + match k { + 'h' => { + if let Some(next) = binary_search_account( + &self.views.as_slice()[self.cursor..], + self.views[self.cursor].new_cursor_pos.0 + 1, + ) { + self.cursor += next; + self.dirty = true; + } + } + 'l' if self.views[self.cursor].cursor_pos.0 > 0 => { + if let Some(next) = binary_search_account( + &self.views.as_slice()[..self.cursor], + self.views[self.cursor].new_cursor_pos.0 - 1, + ) { + self.cursor = next; + self.dirty = true; + } + } + _ => return false, + } + self.views[self.cursor].refresh_mailbox(context); + return true; + } + _ => { + 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) -> ShortcutMap { + if self.views.is_empty() { + return Default::default(); + } + let mut map = self.views[self.cursor].get_shortcuts(context); + + let config_map = context.settings.shortcuts.compact_listing.key_values(); + map.insert( + "prev_account", + if let Some(key) = config_map.get("prev_account") { + (*key).clone() + } else { + Key::Char('h') + }, + ); + map.insert( + "next_account", + if let Some(key) = config_map.get("next_account") { + (*key).clone() + } else { + Key::Char('l') + }, + ); + + map + } +}