From a9425be61e6d27cd25642aa9828e199e36b0698a Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 26 Oct 2019 15:58:56 +0300 Subject: [PATCH] ui/contacts: add side-menu, remove accounts tab - Rename accounts tab to status tab - add side menu to contacts tab to switch between accounts --- src/bin.rs | 5 +- ui/src/components/contacts/contact_list.rs | 256 +++++++++++++++--- ui/src/components/mail.rs | 4 +- .../mail/{accounts.rs => status.rs} | 128 +-------- ui/src/conf/shortcuts.rs | 5 +- 5 files changed, 246 insertions(+), 152 deletions(-) rename ui/src/components/mail/{accounts.rs => status.rs} (60%) diff --git a/src/bin.rs b/src/bin.rs index 8131d6e8..66770f5f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -201,11 +201,10 @@ fn main() -> std::result::Result<(), std::io::Error> { let signal_recvr = notify(signals, sender)?; - /* Register some reasonably useful interfaces */ let window = Box::new(Tabbed::new(vec![ Box::new(listing::Listing::new(&state.context.accounts)), - Box::new(AccountsPanel::new(&state.context)), - Box::new(ContactList::default()), + Box::new(ContactList::new(&state.context)), + Box::new(StatusPanel::new()), ])); let status_bar = Box::new(StatusBar::new(window)); diff --git a/ui/src/components/contacts/contact_list.rs b/ui/src/components/contacts/contact_list.rs index 070616c3..5ee9a730 100644 --- a/ui/src/components/contacts/contact_list.rs +++ b/ui/src/components/contacts/contact_list.rs @@ -11,8 +11,16 @@ enum ViewMode { View(ComponentId), } +#[derive(Debug)] +struct AccountMenuEntry { + name: String, + // Index in the config account vector. + index: usize, +} + #[derive(Debug)] pub struct ContactList { + accounts: Vec, cursor_pos: usize, new_cursor_pos: usize, account_pos: usize, @@ -24,17 +32,14 @@ pub struct ContactList { mode: ViewMode, dirty: bool, + show_divider: bool, + menu_visibility: bool, movement: Option, view: Option, + ratio: usize, // right/(container width) * 100 id: ComponentId, } -impl Default for ContactList { - fn default() -> Self { - Self::new() - } -} - impl fmt::Display for ContactList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", ContactList::DESCRIPTION) @@ -43,8 +48,18 @@ impl fmt::Display for ContactList { impl ContactList { const DESCRIPTION: &'static str = "contact list"; - pub fn new() -> Self { + pub fn new(context: &Context) -> Self { + let accounts = context + .accounts + .iter() + .enumerate() + .map(|(i, a)| AccountMenuEntry { + name: a.name().to_string(), + index: i, + }) + .collect(); ContactList { + accounts, cursor_pos: 0, new_cursor_pos: 0, length: 0, @@ -56,14 +71,17 @@ impl ContactList { dirty: true, movement: None, view: None, + ratio: 90, + show_divider: false, + menu_visibility: true, id: ComponentId::new_v4(), } } - pub fn for_account(pos: usize) -> Self { + pub fn for_account(pos: usize, context: &Context) -> Self { ContactList { account_pos: pos, - ..Self::new() + ..Self::new(context) } } @@ -204,23 +222,109 @@ impl ContactList { }; change_colors(grid, area, fg_color, bg_color); } -} -impl Component for ContactList { - fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if let Some(mgr) = self.view.as_mut() { - mgr.draw(grid, area, context); - return; - } - - if !self.dirty { + fn draw_menu(&mut self, grid: &mut CellBuffer, mut area: Area, context: &mut Context) { + if !self.is_dirty() { return; } + clear_area(grid, area); + /* visually divide menu and listing */ + area = (area.0, pos_dec(area.1, (1, 0))); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); self.dirty = false; - if !self.initialized { - self.initialize(context); + let mut y = get_y(upper_left); + for a in &self.accounts { + self.print_account(grid, (set_y(upper_left, y), bottom_right), &a, context); + y += 1; } + context.dirty_areas.push_back(area); + } + /* + * Print a single account in the menu area. + */ + fn print_account( + &self, + grid: &mut CellBuffer, + area: Area, + a: &AccountMenuEntry, + context: &mut Context, + ) { + if !is_valid_area!(area) { + debug!("BUG: invalid area in print_account"); + } + + let width = width!(area); + let must_highlight_account: bool = self.account_pos == a.index; + let (fg_color, bg_color) = if must_highlight_account { + if self.account_pos == a.index { + (Color::Byte(233), Color::Byte(15)) + } else { + (Color::Byte(15), Color::Byte(233)) + } + } else { + (Color::Default, Color::Default) + }; + + let s = format!(" [{}]", context.accounts[a.index].address_book.len()); + + if a.name.grapheme_len() + s.len() > width + 1 { + /* Print account name */ + write_string_to_grid(&a.name, grid, fg_color, bg_color, Attr::Bold, area, false); + write_string_to_grid( + &s, + grid, + fg_color, + bg_color, + Attr::Bold, + ( + pos_dec( + (get_x(bottom_right!(area)), get_y(upper_left!(area))), + (s.len() - 1, 0), + ), + bottom_right!(area), + ), + false, + ); + write_string_to_grid( + "…", + grid, + fg_color, + bg_color, + Attr::Bold, + ( + pos_dec( + (get_x(bottom_right!(area)), get_y(upper_left!(area))), + (s.len() - 1, 0), + ), + bottom_right!(area), + ), + false, + ); + } else { + /* Print account name */ + + write_string_to_grid(&a.name, grid, fg_color, bg_color, Attr::Bold, area, false); + write_string_to_grid( + &s, + grid, + fg_color, + bg_color, + Attr::Bold, + ( + pos_dec( + (get_x(bottom_right!(area)), get_y(upper_left!(area))), + (s.len() - 1, 0), + ), + bottom_right!(area), + ), + false, + ); + } + } + + 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); @@ -371,6 +475,60 @@ impl Component for ContactList { ); context.dirty_areas.push_back(area); } +} + +impl Component for ContactList { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + if let Some(mgr) = self.view.as_mut() { + mgr.draw(grid, area, context); + return; + } + + if !self.dirty { + return; + } + if !self.initialized { + self.initialize(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_component_width = if self.menu_visibility { + (self.ratio * total_cols) / 100 + } else { + total_cols + }; + let mid = get_x(bottom_right) - right_component_width; + if self.dirty && mid != get_x(upper_left) { + if self.show_divider { + for i in get_y(upper_left)..=get_y(bottom_right) { + grid[(mid, i)].set_ch(VERT_BOUNDARY); + grid[(mid, i)].set_fg(Color::Default); + grid[(mid, i)].set_bg(Color::Default); + } + } else { + for i in get_y(upper_left)..=get_y(bottom_right) { + grid[(mid, i)].set_fg(Color::Default); + grid[(mid, i)].set_bg(Color::Default); + } + } + context + .dirty_areas + .push_back(((mid, get_y(upper_left)), (mid, get_y(bottom_right)))); + } + + if right_component_width == total_cols { + self.draw_list(grid, area, context); + } else if right_component_width == 0 { + self.draw_menu(grid, area, context); + } else { + self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context); + self.draw_list(grid, (set_x(upper_left, mid + 1), bottom_right), context); + } + self.dirty = false; + } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { if let Some(ref mut v) = self.view { @@ -409,24 +567,53 @@ impl Component for ContactList { return true; } - UIEvent::Input(Key::Char('n')) if self.view.is_none() => { - let card = Card::new(); - let mut manager = ContactManager::default(); - manager.set_parent_id(self.id); - manager.card = card; - manager.account_pos = self.account_pos; - - self.mode = ViewMode::View(manager.id()); - self.view = Some(manager); + UIEvent::Input(ref key) if *key == shortcuts["next_account"] && self.view.is_none() => { + if self.accounts.is_empty() { + return true; + } + if self.account_pos < self.accounts.len() - 1 { + self.account_pos += 1; + self.set_dirty(); + self.initialized = false; + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( + self.get_status(context).unwrap(), + ))); + } return true; } - UIEvent::Input(Key::Up) => { + UIEvent::Input(ref key) if *key == shortcuts["prev_account"] && self.view.is_none() => { + if self.accounts.is_empty() { + return true; + } + if self.account_pos > 0 { + self.account_pos -= 1; + self.set_dirty(); + self.initialized = false; + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus( + self.get_status(context).unwrap(), + ))); + } + return true; + } + UIEvent::Input(ref k) + if k == shortcuts["toggle_menu_visibility"] && self.view.is_none() => + { + self.menu_visibility = !self.menu_visibility; + self.set_dirty(); + } + UIEvent::Input(Key::Up) if self.view.is_none() => { self.set_dirty(); self.new_cursor_pos = self.cursor_pos.saturating_sub(1); return true; } - UIEvent::Input(Key::Down) if self.cursor_pos < self.length.saturating_sub(1) => { + UIEvent::Input(Key::Down) + if self.cursor_pos < self.length.saturating_sub(1) && self.view.is_none() => + { self.set_dirty(); self.new_cursor_pos += 1; return true; @@ -496,4 +683,11 @@ impl Component for ContactList { .map(|p| p.can_quit_cleanly(context)) .unwrap_or(true) } + + fn get_status(&self, context: &Context) -> Option { + Some(format!( + "{} entries", + context.accounts[self.account_pos].address_book.len() + )) + } } diff --git a/ui/src/components/mail.rs b/ui/src/components/mail.rs index 657ae664..deb73163 100644 --- a/ui/src/components/mail.rs +++ b/ui/src/components/mail.rs @@ -35,8 +35,8 @@ pub use self::compose::*; pub mod pgp; -mod accounts; -pub use self::accounts::*; +mod status; +pub use self::status::*; fn get_display_name(context: &Context, idx: usize) -> String { let settings = context.accounts[idx].runtime_settings.account(); diff --git a/ui/src/components/mail/accounts.rs b/ui/src/components/mail/status.rs similarity index 60% rename from ui/src/components/mail/accounts.rs rename to ui/src/components/mail/status.rs index 9f93a0f9..7489839e 100644 --- a/ui/src/components/mail/accounts.rs +++ b/ui/src/components/mail/status.rs @@ -1,5 +1,5 @@ /* - * meli - accounts module. + * meli - status tab module. * * Copyright 2019 Manos Pitsidianakis * @@ -23,25 +23,23 @@ use super::*; use std::fmt; #[derive(Debug)] -pub struct AccountsPanel { +pub struct StatusPanel { cursor: (usize, usize), - account_cursor: usize, content: CellBuffer, dirty: bool, id: ComponentId, } -impl fmt::Display for AccountsPanel { +impl fmt::Display for StatusPanel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "accounts") + write!(f, "status") } } -impl Component for AccountsPanel { +impl Component for StatusPanel { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if self.dirty { - self.initialize(context); - self.dirty = false; + if !self.dirty { + return; } let (width, height) = self.content.size(); { @@ -145,22 +143,11 @@ impl Component for AccountsPanel { ), ), ); + self.dirty = false; context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { + fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool { match *event { - UIEvent::Input(Key::Char('k')) => { - self.account_cursor = self.account_cursor.saturating_sub(1); - self.dirty = true; - return true; - } - UIEvent::Input(Key::Char('j')) => { - if self.account_cursor + 1 < context.accounts.len() { - self.account_cursor += 1; - self.dirty = true; - } - return true; - } UIEvent::Input(Key::Left) => { self.cursor.0 = self.cursor.0.saturating_sub(1); self.dirty = true; @@ -181,14 +168,6 @@ impl Component for AccountsPanel { self.dirty = true; return true; } - UIEvent::Input(Key::Char('\n')) => { - context - .replies - .push_back(UIEvent::Action(Tab(New(Some(Box::new( - ContactList::for_account(self.account_cursor), - )))))); - return true; - } UIEvent::MailboxUpdate(_) => { self.dirty = true; } @@ -212,96 +191,15 @@ impl Component for AccountsPanel { } } -impl AccountsPanel { - pub fn new(context: &Context) -> AccountsPanel { - let content = CellBuffer::new(120, 40 + context.accounts.len() * 20, Cell::default()); +impl StatusPanel { + pub fn new() -> StatusPanel { + let content = CellBuffer::new(120, 40, Cell::default()); - AccountsPanel { + StatusPanel { cursor: (0, 0), - account_cursor: 0, content, dirty: true, id: ComponentId::new_v4(), } } - fn initialize(&mut self, context: &Context) { - write_string_to_grid( - "Accounts", - &mut self.content, - Color::Default, - Color::Default, - Attr::Default, - ((2, 10), (120 - 1, 10)), - true, - ); - - for (i, a) in context.accounts.iter().enumerate() { - for x in 2..(120 - 1) { - set_and_join_box(&mut self.content, (x, 12 + i * 10), HORZ_BOUNDARY); - } - //create_box(&mut self.content, ((2, 5 + i * 10), (120 - 1, 15 + i * 10))); - let (x, y) = write_string_to_grid( - a.name(), - &mut self.content, - Color::Default, - Color::Default, - Attr::Bold, - ((3, 12 + i * 10), (120 - 2, 12 + i * 10)), - true, - ); - write_string_to_grid( - " ▒██▒ ", - &mut self.content, - Color::Byte(32), - Color::Default, - Attr::Default, - ((x, y), (120 - 2, 12 + i * 10)), - true, - ); - write_string_to_grid( - &a.runtime_settings.account().identity, - &mut self.content, - Color::Default, - Color::Default, - Attr::Default, - ((4, y + 2), (120 - 2, y + 2)), - true, - ); - if i == self.account_cursor { - for h in 1..8 { - self.content[(2, h + y + 1)].set_ch('*'); - } - } else { - for h in 1..8 { - self.content[(2, h + y + 1)].set_ch(' '); - } - } - write_string_to_grid( - &format!( - "Messages total {}, unseen {}", - a.collection.len(), - a.collection - .envelopes - .values() - .filter(|e| !e.is_seen()) - .count() - ), - &mut self.content, - Color::Default, - Color::Default, - Attr::Default, - ((5, y + 3), (120 - 2, y + 3)), - true, - ); - write_string_to_grid( - &format!("Contacts total {}", a.address_book.len()), - &mut self.content, - Color::Default, - Color::Default, - Attr::Default, - ((5, y + 4), (120 - 2, y + 4)), - true, - ); - } - } } diff --git a/ui/src/conf/shortcuts.rs b/ui/src/conf/shortcuts.rs index 735921c7..075065b1 100644 --- a/ui/src/conf/shortcuts.rs +++ b/ui/src/conf/shortcuts.rs @@ -84,7 +84,10 @@ shortcut_key_values! { "contact-list", /// Shortcut listing for the contact list view pub struct ContactListShortcuts { create_contact: Key |> "Create new contact." |> Key::Char('c'), - edit_contact: Key |> "Edit contact under cursor." |> Key::Char('e') + edit_contact: Key |> "Edit contact under cursor." |> Key::Char('e'), + toggle_menu_visibility: Key |> "Toggle visibility of side menu in mail list." |> Key::Char('`'), + prev_account: Key |> "Go to previous account." |> Key::Char('l'), + next_account: Key |> "Go to next account." |> Key::Char('h') } }