ui: distinct shortcut maps with description

put shortcuts in different maps according to their source component
instead of bagging them all in the same one, and then print each
shortcut section on its own in the shortcut window
embed
Manos Pitsidianakis 2019-05-10 22:00:56 +03:00
parent 5a564dee63
commit bf35894a18
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
10 changed files with 211 additions and 138 deletions

View File

@ -78,6 +78,7 @@ const _DOUBLE_UP_AND_RIGHT: char = '╚';
type ComponentId = Uuid;
pub type ShortcutMap = FnvHashMap<&'static str, Key>;
pub type ShortcutMaps = FnvHashMap<String, ShortcutMap>;
/// Types implementing this Trait can draw on the terminal and receive events.
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
@ -96,7 +97,7 @@ pub trait Component: Display + Debug + Send {
fn set_id(&mut self, _id: ComponentId) {}
fn id(&self) -> ComponentId;
fn get_shortcuts(&self, _context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, _context: &Context) -> ShortcutMaps {
Default::default()
}
}

View File

@ -35,11 +35,12 @@ impl Default for ContactList {
impl fmt::Display for ContactList {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "contacts")
write!(f, "{}", ContactList::DESCRIPTION)
}
}
impl ContactList {
const DESCRIPTION: &'static str = "contact list";
pub fn new() -> Self {
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
ContactList {
@ -227,7 +228,7 @@ impl Component for ContactList {
return true;
}
}
let shortcuts = self.get_shortcuts(context);
let shortcuts = &self.get_shortcuts(context)[Self::DESCRIPTION];
match *event {
UIEvent::Input(ref key) if *key == shortcuts["create_contact"] => {
let mut manager = ContactManager::default();
@ -300,7 +301,7 @@ impl Component for ContactList {
fn kill(&mut self, uuid: Uuid) {
self.mode = ViewMode::Close(uuid);
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = self
.view
.as_ref()
@ -308,8 +309,16 @@ impl Component for ContactList {
.unwrap_or_default();
let config_map = context.settings.shortcuts.contact_list.key_values();
map.insert("create_contact", (*config_map["create_contact"]).clone());
map.insert("edit_contact", (*config_map["edit_contact"]).clone());
map.insert(
self.to_string(),
[
("create_contact", (*config_map["create_contact"]).clone()),
("edit_contact", (*config_map["edit_contact"]).clone()),
]
.iter()
.cloned()
.collect(),
);
map
}

View File

@ -114,6 +114,7 @@ impl fmt::Display for Composer {
}
impl Composer {
const DESCRIPTION: &'static str = "compose";
pub fn new(account_cursor: usize) -> Self {
Composer {
account_cursor,
@ -650,7 +651,7 @@ impl Component for Composer {
self.mode = ViewMode::Discard(uuid);
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if self.mode.is_overview() {
self.pager.get_shortcuts(context)
} else {
@ -659,17 +660,18 @@ impl Component for Composer {
if let Some((_, ref view)) = self.reply_context {
map.extend(view.get_shortcuts(context));
map.remove("reply");
}
let mut our_map: ShortcutMap = Default::default();
if self.mode.is_overview() {
map.insert("Switch to edit mode.", Key::Char('o'));
map.insert("Deliver draft to mailer.", Key::Char('s'));
our_map.insert("Switch to edit mode.", Key::Char('o'));
our_map.insert("Deliver draft to mailer.", Key::Char('s'));
}
if self.mode.is_edit() {
map.insert("Switch to overview", Key::Char('v'));
our_map.insert("Switch to overview", Key::Char('v'));
}
map.insert("Edit in $EDITOR", Key::Char('e'));
our_map.insert("Edit in $EDITOR", Key::Char('e'));
map.insert(Composer::DESCRIPTION.to_string(), our_map);
map
}

View File

@ -158,7 +158,7 @@ impl Component for Listing {
return true;
}
let shortcuts = self.get_shortcuts(context);
let shortcuts = &self.get_shortcuts(context)[Listing::DESCRIPTION];
match *event {
UIEvent::Input(ref k)
if k == shortcuts["next_folder"] || k == shortcuts["prev_folder"] =>
@ -347,7 +347,7 @@ impl Component for Listing {
}
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = match self.component {
Compact(ref l) => l.get_shortcuts(context),
Plain(ref l) => l.get_shortcuts(context),
@ -355,46 +355,54 @@ impl Component for Listing {
};
let config_map = context.settings.shortcuts.listing.key_values();
map.insert(
"new_mail",
if let Some(key) = config_map.get("new_mail") {
(*key).clone()
} else {
Key::Char('m')
},
Listing::DESCRIPTION.to_string(),
[
(
"new_mail",
if let Some(key) = config_map.get("new_mail") {
(*key).clone()
} else {
Key::Char('m')
},
),
(
"prev_folder",
if let Some(key) = config_map.get("prev_folder") {
(*key).clone()
} else {
Key::Char('K')
},
),
(
"next_folder",
if let Some(key) = config_map.get("next_folder") {
(*key).clone()
} else {
Key::Char('J')
},
),
(
"prev_account",
if let Some(key) = config_map.get("prev_account") {
(*key).clone()
} else {
Key::Char('l')
},
),
(
"next_account",
if let Some(key) = config_map.get("next_account") {
(*key).clone()
} else {
Key::Char('h')
},
),
("toggle-menu-visibility", Key::Char('`')),
]
.iter()
.cloned()
.collect(),
);
map.insert(
"prev_folder",
if let Some(key) = config_map.get("prev_folder") {
(*key).clone()
} else {
Key::Char('K')
},
);
map.insert(
"next_folder",
if let Some(key) = config_map.get("next_folder") {
(*key).clone()
} else {
Key::Char('J')
},
);
map.insert(
"prev_account",
if let Some(key) = config_map.get("prev_account") {
(*key).clone()
} else {
Key::Char('l')
},
);
map.insert(
"next_account",
if let Some(key) = config_map.get("next_account") {
(*key).clone()
} else {
Key::Char('h')
},
);
map.insert("toggle-menu-visibility", Key::Char('`'));
map
}
@ -426,6 +434,7 @@ impl From<IndexStyle> for ListingComponent {
}
impl Listing {
const DESCRIPTION: &'static str = "listing";
pub fn new(accounts: &[Account]) -> Self {
let accounts = accounts
.iter()

View File

@ -97,6 +97,7 @@ impl fmt::Display for MailboxView {
}
impl MailboxView {
const DESCRIPTION: &'static str = "";
/// Helper function to format entry strings for CompactListing */
/* TODO: Make this configurable */
fn make_entry_string(
@ -546,7 +547,7 @@ impl Component for MailboxView {
return true;
}
let shortcuts = self.get_shortcuts(context);
let shortcuts = &self.get_shortcuts(context)[CompactListing::DESCRIPTION];
match *event {
UIEvent::Input(Key::Up) => {
if self.cursor_pos.2 > 0 {
@ -661,45 +662,53 @@ impl Component for MailboxView {
self.dirty = true;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if self.unfocused {
self.view.get_shortcuts(context)
} else {
ShortcutMap::default()
ShortcutMaps::default()
};
let config_map = context.settings.shortcuts.compact_listing.key_values();
map.insert(
"open_thread",
if let Some(key) = config_map.get("open_thread") {
(*key).clone()
} else {
Key::Char('\n')
},
);
map.insert(
"prev_page",
if let Some(key) = config_map.get("prev_page") {
(*key).clone()
} else {
Key::PageUp
},
);
map.insert(
"next_page",
if let Some(key) = config_map.get("next_page") {
(*key).clone()
} else {
Key::PageDown
},
);
map.insert(
"exit_thread",
if let Some(key) = config_map.get("exit_thread") {
(*key).clone()
} else {
Key::Char('i')
},
CompactListing::DESCRIPTION.to_string(),
[
(
"open_thread",
if let Some(key) = config_map.get("open_thread") {
(*key).clone()
} else {
Key::Char('\n')
},
),
(
"prev_page",
if let Some(key) = config_map.get("prev_page") {
(*key).clone()
} else {
Key::PageUp
},
),
(
"next_page",
if let Some(key) = config_map.get("next_page") {
(*key).clone()
} else {
Key::PageDown
},
),
(
"exit_thread",
if let Some(key) = config_map.get("exit_thread") {
(*key).clone()
} else {
Key::Char('i')
},
),
]
.iter()
.cloned()
.collect(),
);
map
@ -747,6 +756,7 @@ impl Default for CompactListing {
}
impl CompactListing {
const DESCRIPTION: &'static str = "compact listing";
pub fn new() -> Self {
CompactListing {
views: Vec::with_capacity(8),
@ -811,7 +821,7 @@ impl Component for CompactListing {
self.dirty = true;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
if self.views.is_empty() {
return Default::default();
}

View File

@ -667,7 +667,7 @@ impl Component for ThreadListing {
};
self.dirty = true;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
self.view
.as_ref()
.map(|p| p.get_shortcuts(context))

View File

@ -75,11 +75,12 @@ pub struct MailView {
impl fmt::Display for MailView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display subject/info
write!(f, "view mail")
write!(f, "{}", MailView::DESCRIPTION)
}
}
impl MailView {
const DESCRIPTION: &'static str = "mail";
pub fn new(
coordinates: (usize, usize, EnvelopeHash),
pager: Option<Pager>,
@ -752,28 +753,30 @@ impl Component for MailView {
_ => {}
}
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if let Some(ref sbv) = self.subview {
sbv.get_shortcuts(context)
} else if let Some(ref pgr) = self.pager {
pgr.get_shortcuts(context)
} else {
FnvHashMap::with_capacity_and_hasher(4, Default::default())
Default::default()
};
map.insert("add_addresses_to_contacts", Key::Char('c'));
map.insert("view_raw_source", Key::Alt('r'));
let mut our_map = FnvHashMap::with_capacity_and_hasher(4, Default::default());
our_map.insert("add_addresses_to_contacts", Key::Char('c'));
our_map.insert("view_raw_source", Key::Alt('r'));
if self.mode.is_attachment() || self.mode == ViewMode::Subview || self.mode == ViewMode::Raw
{
map.insert("return_to_normal_view", Key::Char('r'));
our_map.insert("return_to_normal_view", Key::Char('r'));
}
map.insert("open_attachment", Key::Char('a'));
our_map.insert("open_attachment", Key::Char('a'));
if self.mode == ViewMode::Url {
map.insert("go_to_url", Key::Char('g'));
our_map.insert("go_to_url", Key::Char('g'));
}
if self.mode == ViewMode::Normal || self.mode == ViewMode::Url {
map.insert("toggle_url_mode", Key::Char('u'));
our_map.insert("toggle_url_mode", Key::Char('u'));
}
map.insert(MailView::DESCRIPTION.to_string(), our_map);
map
}

View File

@ -55,6 +55,7 @@ pub struct ThreadView {
}
impl ThreadView {
const DESCRIPTION: &'static str = "thread view";
/*
* coordinates: (account index, mailbox index, root set thread_node index)
* expanded_idx: optional position of expanded entry when we render the threadview. Default
@ -956,13 +957,21 @@ impl Component for ThreadView {
self.dirty = true;
self.mailview.set_dirty();
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = self.mailview.get_shortcuts(context);
map.insert("reply", Key::Char('R'));
map.insert("reverse thread order", Key::Ctrl('r'));
map.insert("toggle_mailview", Key::Char('p'));
map.insert("toggle_subthread visibility", Key::Char('h'));
map.insert(
ThreadView::DESCRIPTION.to_string(),
[
("reply", Key::Char('R')),
("reverse thread order", Key::Ctrl('r')),
("toggle_mailview", Key::Char('p')),
("toggle_subthread visibility", Key::Char('h')),
]
.iter()
.cloned()
.collect(),
);
map
}

View File

@ -108,7 +108,7 @@ impl Component for HSplit {
self.bottom.set_dirty();
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut top_map = self.top.get_shortcuts(context);
top_map.extend(self.bottom.get_shortcuts(context).into_iter());
top_map
@ -248,7 +248,7 @@ impl Component for VSplit {
self.right.set_dirty();
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut right_map = self.right.get_shortcuts(context);
right_map.extend(self.left.get_shortcuts(context).into_iter());
right_map
@ -287,11 +287,12 @@ pub struct Pager {
impl fmt::Display for Pager {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display info
write!(f, "pager")
write!(f, "{}", Pager::DESCRIPTION)
}
}
impl Pager {
const DESCRIPTION: &'static str = "pager";
pub fn update_from_str(&mut self, text: &str, width: Option<usize>) {
let lines: Vec<&str> = if let Some(width) = width {
word_break_string(text, width)
@ -501,7 +502,7 @@ impl Component for Pager {
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
let shortcuts = self.get_shortcuts(context);
let shortcuts = &self.get_shortcuts(context)[Self::DESCRIPTION];
match *event {
UIEvent::Input(ref key) if *key == shortcuts["scroll_up"] => {
if self.cursor_pos > 0 {
@ -544,16 +545,24 @@ impl Component for Pager {
fn set_dirty(&mut self) {
self.dirty = true;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
let mut map = FnvHashMap::with_capacity_and_hasher(4, Default::default());
let config_map = context.settings.shortcuts.pager.key_values();
map.insert("scroll_up", (*config_map["scroll_up"]).clone());
map.insert("scroll_down", (*config_map["scroll_down"]).clone());
map.insert("page_up", (*config_map["page_up"]).clone());
map.insert("page_down", (*config_map["page_down"]).clone());
map
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let config_map: FnvHashMap<&'static str, &Key> =
context.settings.shortcuts.pager.key_values();
[(
Pager::DESCRIPTION.to_string(),
[
("scroll_up", (*config_map["scroll_up"]).clone()),
("scroll_down", (*config_map["scroll_down"]).clone()),
("page_up", (*config_map["page_up"]).clone()),
("page_down", (*config_map["page_down"]).clone()),
]
.iter()
.cloned()
.collect::<ShortcutMap>(),
)]
.iter()
.cloned()
.collect()
}
fn id(&self) -> ComponentId {
@ -773,7 +782,7 @@ impl Component for StatusBar {
self.dirty = true;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
self.container.get_shortcuts(context)
}
@ -979,16 +988,16 @@ impl Component for Tabbed {
clear_area(grid, area);
create_box(grid, area);
let mut idx = 0;
// TODO: print into a pager
for (idx, (k, v)) in self.children[self.cursor_pos]
for (desc, shortcuts) in self.children[self.cursor_pos]
.get_shortcuts(context)
.into_iter()
.enumerate()
{
let (x, y) = write_string_to_grid(
&k,
write_string_to_grid(
&desc,
grid,
Color::Byte(29),
Color::Default,
Color::Default,
(
pos_inc(upper_left!(area), (2, 1 + idx)),
@ -999,20 +1008,41 @@ impl Component for Tabbed {
),
false,
);
write_string_to_grid(
&format!("{}", v),
grid,
Color::Default,
Color::Default,
(
(x + 2, y),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
idx += 2;
let mut shortcuts = shortcuts.into_iter().collect::<Vec<_>>();
shortcuts.sort_unstable_by_key(|(ref k, _)| *k);
for (k, v) in shortcuts {
let (x, y) = write_string_to_grid(
&k,
grid,
Color::Byte(29),
Color::Default,
(
pos_inc(upper_left!(area), (2, 1 + idx)),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
),
false,
);
false,
);
write_string_to_grid(
&format!("{}", v),
grid,
Color::Default,
Color::Default,
(
(x + 2, y),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
false,
);
idx += 1;
}
idx += 1;
}
context.dirty_areas.push_back(area);
}

View File

@ -39,9 +39,9 @@ macro_rules! shortcut_key_values {
}
/// Returns a hashmap of all shortcuts and their values
pub fn key_values(&self) -> FnvHashMap<&'static str, &Key> {
let mut map: FnvHashMap<&'static str, &Key> = Default::default();
$(map.insert(stringify!($fname),&(self.$fname));)*
map
[
$((stringify!($fname),&(self.$fname)),)*
].iter().cloned().collect()
}
}
}