ui/contacts: add side-menu, remove accounts tab

- Rename accounts tab to status tab
- add side menu to contacts tab to switch between accounts
embed
Manos Pitsidianakis 2019-10-26 15:58:56 +03:00
parent ce11447a1a
commit a9425be61e
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
5 changed files with 246 additions and 152 deletions

View File

@ -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));

View File

@ -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<AccountMenuEntry>,
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<PageMovement>,
view: Option<ContactManager>,
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<String> {
Some(format!(
"{} entries",
context.accounts[self.account_pos].address_book.len()
))
}
}

View File

@ -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();

View File

@ -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,
);
}
}
}

View File

@ -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')
}
}