summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-09-07 16:39:15 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-09-07 16:39:15 +0300
commit9dc4d4055cb2f854e835748315677bf4a2db2012 (patch)
tree6c266af1e4a979be7ee42389c7eb65edc9635ed4
parent3d92b41075fc16214675cf141acd9c89fb6f5c49 (diff)
downloadmeli-9dc4d4055cb2f854e835748315677bf4a2db2012.zip
listing: add focus_{left,right} shortcuts to switch focus
This allows you to make the mail entry column occupy the whole screen if you press focus_right (Right key) twice.
-rw-r--r--CHANGELOG.md2
-rw-r--r--docs/meli.74
-rw-r--r--docs/meli.conf.58
-rw-r--r--src/components/mail/listing.rs29
-rw-r--r--src/components/mail/listing/compact.rs158
-rw-r--r--src/components/mail/listing/conversations.rs152
-rw-r--r--src/components/mail/listing/offline.rs6
-rw-r--r--src/components/mail/listing/plain.rs155
-rw-r--r--src/components/mail/listing/thread.rs167
-rw-r--r--src/conf/shortcuts.rs4
10 files changed, 523 insertions, 162 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e224e688..32bb9929 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- Added shortcuts for focusing to sidebar menu and back to the e-mail view (`focus_left` and `focus_right`)
- `f76f4ea3` A new manual page, `meli.7` which contains a general tutorial for using meli.
-- `f76f4ea3` Added shortcuts for focusing to sidebar menu and back to the e-mail view (`focus_on_menu` and `focus_on_list`)
- `cbe593cf` add configurable header preample suffix and prefix for editing
- `a484b397` Added instructions and information to error shown when libnotmuch could not be found.
- `a484b397` Added configuration setting `library_file_path` to notmuch backend if user wants to specify the library's location manually.
diff --git a/docs/meli.7 b/docs/meli.7
index 02b665ee..0567926e 100644
--- a/docs/meli.7
+++ b/docs/meli.7
@@ -233,10 +233,10 @@ Press
to toggle the sidebars visibility.
.Pp
Press
-.Shortcut Left listing focus_on_menu
+.Shortcut Left listing focus_right
to switch focus on the sidebar menu.
Press
-.Shortcut Right listing focus_on_list
+.Shortcut Right listing focus_left
to switch focus on the e-mail list.
.Pp
On the e-mail list, press
diff --git a/docs/meli.conf.5 b/docs/meli.conf.5
index b8b5eef9..163e9722 100644
--- a/docs/meli.conf.5
+++ b/docs/meli.conf.5
@@ -781,6 +781,14 @@ Decrease sidebar width.
Toggle visibility of side menu in mail list.
.\" default value
.Pq Em `
+.It Ic focus_left
+Switch focus on the left.
+.\" default value
+.Pq Em Left
+.It Ic focus_right
+Switch focus on the right.
+.\" default value
+.Pq Em Right
.It Ic exit_entry
Exit e-mail entry.
.\" default value
diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs
index b00bfdcb..a4143616 100644
--- a/src/components/mail/listing.rs
+++ b/src/components/mail/listing.rs
@@ -61,6 +61,13 @@ pub use self::plain::*;
mod offline;
pub use self::offline::*;
+#[derive(Debug, Copy, Clone)]
+pub enum Focus {
+ None,
+ Entry,
+ EntryFullscreen,
+}
+
#[derive(Debug, Copy, PartialEq, Clone)]
pub enum Modifier {
SymmetricDifference,
@@ -516,6 +523,8 @@ pub trait ListingTrait: Component {
None
}
fn set_movement(&mut self, mvm: PageMovement);
+ fn focus(&self) -> Focus;
+ fn set_focus(&mut self, new_value: Focus, context: &mut Context);
}
#[derive(Debug)]
@@ -655,7 +664,7 @@ impl Component for Listing {
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 {
+ let right_component_width = if self.is_menu_visible() {
if self.focus == ListingFocus::Menu {
(self.ratio * total_cols) / 100
} else {
@@ -925,7 +934,7 @@ impl Component for Listing {
if self.focus == ListingFocus::Mailbox {
match *event {
UIEvent::Input(Key::Mouse(MouseEvent::Press(MouseButton::Left, x, _y)))
- if self.menu_visibility =>
+ if self.is_menu_visible() =>
{
match self.menu_width {
WidgetWidth::Hold(wx) | WidgetWidth::Set(wx)
@@ -942,7 +951,7 @@ impl Component for Listing {
self.set_dirty(true);
return true;
}
- UIEvent::Input(Key::Mouse(MouseEvent::Hold(x, _y))) if self.menu_visibility => {
+ UIEvent::Input(Key::Mouse(MouseEvent::Hold(x, _y))) if self.is_menu_visible() => {
match self.menu_width {
WidgetWidth::Hold(ref mut hx) => {
*hx = usize::from(x).saturating_sub(1);
@@ -952,7 +961,9 @@ impl Component for Listing {
self.set_dirty(true);
return true;
}
- UIEvent::Input(Key::Mouse(MouseEvent::Release(x, _y))) if self.menu_visibility => {
+ UIEvent::Input(Key::Mouse(MouseEvent::Release(x, _y)))
+ if self.is_menu_visible() =>
+ {
match self.menu_width {
WidgetWidth::Hold(_) => {
self.menu_width = WidgetWidth::Set(usize::from(x).saturating_sub(1));
@@ -963,8 +974,8 @@ impl Component for Listing {
return true;
}
UIEvent::Input(ref k)
- if self.menu_visibility
- && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_on_menu"]) =>
+ if self.is_menu_visible()
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
{
self.focus = ListingFocus::Menu;
if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
@@ -1337,7 +1348,7 @@ impl Component for Listing {
} else if self.focus == ListingFocus::Menu {
match *event {
UIEvent::Input(ref k)
- if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_on_list"]) =>
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
{
self.focus = ListingFocus::Mailbox;
context
@@ -2374,4 +2385,8 @@ impl Listing {
self.get_status(context),
)));
}
+
+ fn is_menu_visible(&self) -> bool {
+ !matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility
+ }
}
diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs
index fd09a080..bf051ced 100644
--- a/src/components/mail/listing/compact.rs
+++ b/src/components/mail/listing/compact.rs
@@ -187,7 +187,7 @@ pub struct CompactListing {
dirty: bool,
force_draw: bool,
/// If `self.view` exists or not.
- unfocused: bool,
+ focus: Focus,
view: ThreadView,
row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache,
@@ -304,7 +304,7 @@ impl MailListingTrait for CompactListing {
if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(context);
- } else if self.unfocused {
+ } else if self.unfocused() {
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.new_cursor_pos, thread, None, context);
@@ -490,7 +490,7 @@ impl ListingTrait for CompactListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
- self.unfocused = false;
+ self.focus = Focus::None;
self.view = ThreadView::default();
self.filtered_selection.clear();
self.filtered_order.clear();
@@ -818,7 +818,7 @@ impl ListingTrait for CompactListing {
}
fn unfocused(&self) -> bool {
- self.unfocused
+ !matches!(self.focus, Focus::None)
}
fn set_modifier_active(&mut self, new_val: bool) {
@@ -837,6 +837,33 @@ impl ListingTrait for CompactListing {
self.movement = Some(mvm);
self.set_dirty(true);
}
+
+ fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
+ match new_value {
+ Focus::None => {
+ self.view
+ .process_event(&mut UIEvent::VisibilityChange(false), context);
+ self.dirty = true;
+ /* If self.row_updates is not empty and we exit a thread, the row_update events
+ * will be performed but the list will not be drawn. So force a draw in any case.
+ * */
+ self.force_draw = true;
+ }
+ Focus::Entry => {
+ self.force_draw = true;
+ self.dirty = true;
+ self.view.set_dirty(true);
+ }
+ Focus::EntryFullscreen => {
+ self.view.set_dirty(true);
+ }
+ }
+ self.focus = new_value;
+ }
+
+ fn focus(&self) -> Focus {
+ self.focus
+ }
}
impl fmt::Display for CompactListing {
@@ -863,13 +890,13 @@ impl CompactListing {
filtered_selection: Vec::new(),
filtered_order: HashMap::default(),
selection: HashMap::default(),
+ focus: Focus::None,
row_updates: SmallVec::new(),
data_columns: DataColumns::default(),
rows_drawn: SegmentTree::default(),
rows: vec![],
dirty: true,
force_draw: true,
- unfocused: false,
view: ThreadView::default(),
color_cache: ColorCache::default(),
movement: None,
@@ -1465,10 +1492,15 @@ impl CompactListing {
impl Component for CompactListing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
- if !self.unfocused {
- if !self.is_dirty() {
- return;
- }
+ if !self.is_dirty() {
+ return;
+ }
+
+ if matches!(self.focus, Focus::EntryFullscreen) {
+ return self.view.draw(grid, area, context);
+ }
+
+ if !self.unfocused() {
let mut area = area;
if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area;
@@ -1715,40 +1747,81 @@ impl Component for CompactListing {
}
self.dirty = false;
}
+
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
- if self.unfocused && self.view.process_event(event, context) {
+ let shortcuts = self.get_shortcuts(context);
+
+ match (&event, self.focus) {
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::EntryFullscreen)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::Entry, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ _ => {}
+ }
+
+ if self.unfocused() && self.view.process_event(event, context) {
return true;
}
- let shortcuts = self.get_shortcuts(context);
if self.length > 0 {
match *event {
UIEvent::Input(ref k)
- if !self.unfocused
- && shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) =>
+ if matches!(self.focus, Focus::None)
+ && (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
+ || shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.cursor_pos, thread, None, context);
- self.unfocused = true;
- self.dirty = true;
+ self.set_focus(Focus::Entry, context);
return true;
}
UIEvent::Input(ref k)
- if self.unfocused
+ if matches!(self.focus, Focus::Entry)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{
- self.unfocused = false;
- self.view
- .process_event(&mut UIEvent::VisibilityChange(false), context);
- self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
- * will be performed but the list will not be drawn. So force a draw in any case.
- * */
- self.force_draw = true;
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::Entry, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ match self.focus {
+ Focus::Entry => {
+ self.set_focus(Focus::None, context);
+ }
+ Focus::EntryFullscreen => {
+ self.set_focus(Focus::Entry, context);
+ }
+ Focus::None => {
+ unreachable!();
+ }
+ }
return true;
}
UIEvent::Input(ref key)
- if !self.unfocused
+ if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{
if self.modifier_active && self.modifier_command.is_none() {
@@ -1762,7 +1835,7 @@ impl Component for CompactListing {
}
UIEvent::Action(ref action) => {
match action {
- Action::Sort(field, order) if !self.unfocused => {
+ Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order);
self.sortcmd = true;
@@ -1774,13 +1847,13 @@ impl Component for CompactListing {
}
return true;
}
- Action::SubSort(field, order) if !self.unfocused => {
+ Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order);
// FIXME: perform subsort.
return true;
}
- Action::Listing(ToggleThreadSnooze) if !self.unfocused => {
+ Action::Listing(ToggleThreadSnooze) if !self.unfocused() => {
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
let account = &mut context.accounts[&self.cursor_pos.0];
account
@@ -1871,7 +1944,7 @@ impl Component for CompactListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
}
@@ -1901,7 +1974,7 @@ impl Component for CompactListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
}
@@ -1913,7 +1986,7 @@ impl Component for CompactListing {
self.dirty = true;
}
UIEvent::Input(Key::Esc)
- if !self.unfocused
+ if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) =>
{
for v in self.selection.values_mut() {
@@ -1922,13 +1995,13 @@ impl Component for CompactListing {
self.dirty = true;
return true;
}
- UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
+ UIEvent::Input(Key::Esc) if !self.unfocused() && !self.filter_term.is_empty() => {
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.refresh_mailbox(context, false);
self.set_dirty(true);
return true;
}
- UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
+ UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused() => {
match context.accounts[&self.cursor_pos.0].search(
filter_term,
self.sort,
@@ -1950,7 +2023,7 @@ impl Component for CompactListing {
};
self.set_dirty(true);
}
- UIEvent::Action(Action::Listing(Select(ref search_term))) if !self.unfocused => {
+ UIEvent::Action(Action::Listing(Select(ref search_term))) if !self.unfocused() => {
match context.accounts[&self.cursor_pos.0].search(
search_term,
self.sort,
@@ -2017,23 +2090,24 @@ impl Component for CompactListing {
}
false
}
+
fn is_dirty(&self) -> bool {
- self.dirty
- || if self.unfocused {
- self.view.is_dirty()
- } else {
- false
- }
+ match self.focus {
+ Focus::None => self.dirty,
+ Focus::Entry => self.dirty || self.view.is_dirty(),
+ Focus::EntryFullscreen => self.view.is_dirty(),
+ }
}
+
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
- if self.unfocused {
+ if self.unfocused() {
self.view.set_dirty(value);
}
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
- let mut map = if self.unfocused {
+ let mut map = if self.unfocused() {
self.view.get_shortcuts(context)
} else {
ShortcutMaps::default()
diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs
index 95c56c4e..0252f709 100644
--- a/src/components/mail/listing/conversations.rs
+++ b/src/components/mail/listing/conversations.rs
@@ -114,7 +114,7 @@ pub struct ConversationsListing {
dirty: bool,
force_draw: bool,
/// If `self.view` exists or not.
- unfocused: bool,
+ focus: Focus,
view: ThreadView,
row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache,
@@ -215,7 +215,7 @@ impl MailListingTrait for ConversationsListing {
if !force && old_cursor_pos == self.new_cursor_pos && old_mailbox_hash == self.cursor_pos.1
{
self.view.update(context);
- } else if self.unfocused {
+ } else if self.unfocused() {
let thread_group = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context);
@@ -352,7 +352,7 @@ impl ListingTrait for ConversationsListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
- self.unfocused = false;
+ self.focus = Focus::None;
self.view = ThreadView::default();
self.filtered_selection.clear();
self.filtered_order.clear();
@@ -537,7 +537,7 @@ impl ListingTrait for ConversationsListing {
}
fn unfocused(&self) -> bool {
- self.unfocused
+ !matches!(self.focus, Focus::None)
}
fn set_modifier_active(&mut self, new_val: bool) {
@@ -556,6 +556,33 @@ impl ListingTrait for ConversationsListing {
self.movement = Some(mvm);
self.set_dirty(true);
}
+
+ fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
+ match new_value {
+ Focus::None => {
+ self.view
+ .process_event(&mut UIEvent::VisibilityChange(false), context);
+ self.dirty = true;
+ /* If self.row_updates is not empty and we exit a thread, the row_update events
+ * will be performed but the list will not be drawn. So force a draw in any case.
+ * */
+ self.force_draw = true;
+ }
+ Focus::Entry => {
+ self.force_draw = true;
+ self.dirty = true;
+ self.view.set_dirty(true);
+ }
+ Focus::EntryFullscreen => {
+ self.view.set_dirty(true);
+ }
+ }
+ self.focus = new_value;
+ }
+
+ fn focus(&self) -> Focus {
+ self.focus
+ }
}
impl fmt::Display for ConversationsListing {
@@ -569,7 +596,7 @@ impl ConversationsListing {
//const PADDING_CHAR: char = ' '; //░';
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
- Box::new(ConversationsListing {
+ Box::new(Self {
cursor_pos: (coordinates.0, 1, 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0,
@@ -586,7 +613,7 @@ impl ConversationsListing {
rows: Ok(Vec::with_capacity(1024)),
dirty: true,
force_draw: true,
- unfocused: false,
+ focus: Focus::None,
view: ThreadView::default(),
color_cache: ColorCache::default(),
movement: None,
@@ -907,6 +934,11 @@ impl Component for ConversationsListing {
if !self.is_dirty() {
return;
}
+
+ if matches!(self.focus, Focus::EntryFullscreen) {
+ return self.view.draw(grid, area, context);
+ }
+
let (upper_left, bottom_right) = area;
{
let mut area = area;
@@ -1142,7 +1174,7 @@ impl Component for ConversationsListing {
self.draw_list(grid, area, context);
}
}
- if self.unfocused {
+ if matches!(self.focus, Focus::Entry) {
if self.length == 0 && self.dirty {
clear_area(grid, area, self.color_cache.theme_default);
context.dirty_areas.push_back(area);
@@ -1157,40 +1189,81 @@ impl Component for ConversationsListing {
}
self.dirty = false;
}
+
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
- if self.unfocused && self.view.process_event(event, context) {
+ let shortcuts = self.get_shortcuts(context);
+
+ match (&event, self.focus) {
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::EntryFullscreen)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::Entry, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ _ => {}
+ }
+
+ if self.unfocused() && self.view.process_event(event, context) {
return true;
}
- let shortcuts = self.get_shortcuts(context);
if self.length > 0 {
match *event {
UIEvent::Input(ref k)
- if !self.unfocused
- && shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) =>
+ if matches!(self.focus, Focus::None)
+ && (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
+ || shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.cursor_pos, thread, None, context);
- self.unfocused = true;
- self.dirty = true;
+ self.set_focus(Focus::Entry, context);
return true;
}
UIEvent::Input(ref k)
- if self.unfocused
+ if !matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{
- self.unfocused = false;
- self.view
- .process_event(&mut UIEvent::VisibilityChange(false), context);
- self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
- * will be performed but the list will not be drawn. So force a draw in any case.
- * */
- self.force_draw = true;
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if matches!(self.focus, Focus::Entry)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ match self.focus {
+ Focus::Entry => {
+ self.set_focus(Focus::None, context);
+ }
+ Focus::EntryFullscreen => {
+ self.set_focus(Focus::Entry, context);
+ }
+ Focus::None => {
+ unreachable!();
+ }
+ }
return true;
}
UIEvent::Input(ref key)
- if !self.unfocused
+ if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{
if self.modifier_active && self.modifier_command.is_none() {
@@ -1221,7 +1294,7 @@ impl Component for ConversationsListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view.process_event(
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
context,
@@ -1253,13 +1326,13 @@ impl Component for ConversationsListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
}
}
UIEvent::Action(ref action) => match action {
- Action::SubSort(field, order) if !self.unfocused => {
+ Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order);
// FIXME subsort
@@ -1271,7 +1344,7 @@ impl Component for ConversationsListing {
//}
return true;
}
- Action::Sort(field, order) if !self.unfocused => {
+ Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order);
// FIXME sort
/*
@@ -1291,7 +1364,7 @@ impl Component for ConversationsListing {
*/
return true;
}
- Action::Listing(ToggleThreadSnooze) if !self.unfocused => {
+ Action::Listing(ToggleThreadSnooze) if !self.unfocused() => {
let thread = self.get_thread_under_cursor(self.cursor_pos.2);
let account = &mut context.accounts[&self.cursor_pos.0];
account
@@ -1359,7 +1432,7 @@ impl Component for ConversationsListing {
self.dirty = true;
}
UIEvent::Action(ref action) => match action {
- Action::Listing(Search(ref filter_term)) if !self.unfocused => {
+ Action::Listing(Search(ref filter_term)) if !self.unfocused() => {
match context.accounts[&self.cursor_pos.0].search(
filter_term,
self.sort,
@@ -1385,7 +1458,7 @@ impl Component for ConversationsListing {
_ => {}
},
UIEvent::Input(Key::Esc)
- if !self.unfocused
+ if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) =>
{
for (k, v) in self.selection.iter_mut() {
@@ -1398,7 +1471,7 @@ impl Component for ConversationsListing {
return true;
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Char(''))
- if !self.unfocused && !&self.filter_term.is_empty() =>
+ if !self.unfocused() && !&self.filter_term.is_empty() =>
{
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.refresh_mailbox(context, false);
@@ -1432,23 +1505,24 @@ impl Component for ConversationsListing {
false
}
+
fn is_dirty(&self) -> bool {
- self.dirty
- || if self.unfocused {
- self.view.is_dirty()
- } else {
- false
- }
+ match self.focus {
+ Focus::None => self.dirty,
+ Focus::Entry => self.dirty || self.view.is_dirty(),
+ Focus::EntryFullscreen => self.view.is_dirty(),
+ }
}
+
fn set_dirty(&mut self, value: bool) {
- if self.unfocused {
+ if self.unfocused() {
self.view.set_dirty(value);
}
self.dirty = value;
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
- let mut map = if self.unfocused {
+ let mut map = if self.unfocused() {
self.view.get_shortcuts(context)
} else {
ShortcutMaps::default()
diff --git a/src/components/mail/listing/offline.rs b/src/components/mail/listing/offline.rs
index 2c04702b..940f5ae9 100644
--- a/src/components/mail/listing/offline.rs
+++ b/src/components/mail/listing/offline.rs
@@ -84,6 +84,12 @@ impl ListingTrait for OfflineListing {
}
fn set_movement(&mut self, _: PageMovement) {}
+
+ fn focus(&self) -> Focus {
+ Focus::None
+ }
+
+ fn set_focus(&mut self, _new_value: Focus, _context: &mut Context) {}
}
impl fmt::Display for OfflineListing {
diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs
index 0228fa64..c4073f11 100644
--- a/src/components/mail/listing/plain.rs
+++ b/src/components/mail/listing/plain.rs
@@ -146,7 +146,7 @@ pub struct PlainListing {
dirty: bool,
force_draw: bool,
/// If `self.view` exists or not.
- unfocused: bool,
+ focus: Focus,
view: MailView,
row_updates: SmallVec<[EnvelopeHash; 8]>,
_row_updates: SmallVec<[ThreadHash; 8]>,
@@ -296,7 +296,7 @@ impl MailListingTrait for PlainListing {
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(temp, context);
- } else if self.unfocused {
+ } else if self.unfocused() {
self.view = MailView::new(temp, None, None, context);
}
}
@@ -335,7 +335,7 @@ impl ListingTrait for PlainListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
- self.unfocused = false;
+ self.focus = Focus::None;
self.view = MailView::default();
self.filtered_selection.clear();
self.filtered_order.clear();
@@ -641,13 +641,44 @@ impl ListingTrait for PlainListing {
}
fn unfocused(&self) -> bool {
- self.unfocused
+ !matches!(self.focus, Focus::None)
}
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
}
+
+ fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
+ match new_value {
+ Focus::None => {
+ self.view
+ .process_event(&mut UIEvent::VisibilityChange(false), context);
+ self.dirty = true;
+ /* If self.row_updates is not empty and we exit a thread, the row_update events
+ * will be performed but the list will not be drawn. So force a draw in any case.
+ * */
+ self.force_draw = true;
+ }
+ Focus::Entry => {
+ let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
+ let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
+ self.view = MailView::new(temp, None, None, context);
+ self.force_draw = true;
+ self.dirty = true;
+ self.view.set_dirty(true);
+ }
+ Focus::EntryFullscreen => {
+ self.dirty = true;
+ self.view.set_dirty(true);
+ }
+ }
+ self.focus = new_value;
+ }
+
+ fn focus(&self) -> Focus {
+ self.focus
+ }
}
impl fmt::Display for PlainListing {
@@ -680,7 +711,7 @@ impl PlainListing {
data_columns: DataColumns::default(),
dirty: true,
force_draw: true,
- unfocused: false,
+ focus: Focus::None,
view: MailView::default(),
color_cache: ColorCache::default(),
active_jobs: HashMap::default(),
@@ -1060,10 +1091,15 @@ impl PlainListing {
impl Component for PlainListing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
- if !self.unfocused {
- if !self.is_dirty() {
- return;
- }
+ if !self.is_dirty() {
+ return;
+ }
+
+ if matches!(self.focus, Focus::EntryFullscreen) {
+ return self.view.draw(grid, area, context);
+ }
+
+ if matches!(self.focus, Focus::None) {
let mut area = area;
if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area;
@@ -1129,48 +1165,79 @@ impl Component for PlainListing {
}
self.dirty = false;
}
+
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
- if self.unfocused && self.view.process_event(event, context) {
+ let shortcuts = self.get_shortcuts(context);
+
+ match (&event, self.focus) {
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::EntryFullscreen)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::Entry, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ _ => {}
+ }
+
+ if self.unfocused() && self.view.process_event(event, context) {
return true;
}
- let shortcuts = self.get_shortcuts(context);
if self.length > 0 {
match *event {
UIEvent::Input(ref k)
- if !self.unfocused
- && shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) =>
+ if matches!(self.focus, Focus::None)
+ && (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
+ || shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
- let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
- let temp = (self.cursor_pos.0, self.cursor_pos.1, env_hash);
- self.view = MailView::new(temp, None, None, context);
- self.unfocused = true;
- self.dirty = true;
+ self.set_focus(Focus::Entry, context);
return true;
}
UIEvent::Input(ref k)
- if self.unfocused
+ if !matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{
- self.unfocused = false;
- self.view
- .process_event(&mut UIEvent::VisibilityChange(false), context);
- self.dirty = true;
- /* If self.row_updates is not empty and we exit a thread, the row_update events
- * will be performed but the list will not be drawn. So force a draw in any case.
- * */
- self.force_draw = true;
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ match self.focus {
+ Focus::Entry => {
+ self.set_focus(Focus::None, context);
+ }
+ Focus::EntryFullscreen => {
+ self.set_focus(Focus::Entry, context);
+ }
+ Focus::None => {
+ unreachable!();
+ }
+ }
return true;
}
UIEvent::Input(ref key)
- if !self.unfocused
+ if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
self.selection.entry(env_hash).and_modify(|e| *e = !*e);
}
UIEvent::Action(ref action) => match action {
- Action::SubSort(field, order) if !self.unfocused => {
+ Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order);
//if !self.filtered_selection.is_empty() {
@@ -1181,7 +1248,7 @@ impl Component for PlainListing {
//}
return true;
}
- Action::Sort(field, order) if !self.unfocused => {
+ Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order);
return true;
@@ -1189,7 +1256,7 @@ impl Component for PlainListing {
Action::Listing(a @ ListingAction::SetSeen)
| Action::Listing(a @ ListingAction::SetUnseen)
| Action::Listing(a @ ListingAction::Delete)
- if !self.unfocused =>
+ if !self.unfocused() =>
{
let is_selection_empty =
self.selection.values().cloned().any(std::convert::identity);
@@ -1295,7 +1362,7 @@ impl Component for PlainListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
}
@@ -1314,7 +1381,7 @@ impl Component for PlainListing {
self.row_updates.push(*env_hash);
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
}
@@ -1326,7 +1393,7 @@ impl Component for PlainListing {
self.dirty = true;
}
UIEvent::Input(Key::Esc)
- if !self.unfocused
+ if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) =>
{
for v in self.selection.values_mut() {
@@ -1335,13 +1402,13 @@ impl Component for PlainListing {
self.dirty = true;
return true;
}
- UIEvent::Input(Key::Esc) if !self.unfocused && !self.filter_term.is_empty() => {
+ UIEvent::Input(Key::Esc) if !self.unfocused() && !self.filter_term.is_empty() => {
self.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.set_dirty(true);
self.refresh_mailbox(context, false);
return true;
}
- UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
+ UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused() => {
match context.accounts[&self.cursor_pos.0].search(
filter_term,
self.sort,
@@ -1389,23 +1456,23 @@ impl Component for PlainListing {
}
false
}
+
fn is_dirty(&self) -> bool {
- self.dirty
- || if self.unfocused {
- self.view.is_dirty()
- } else {
- false
- }
+ match self.focus {
+ Focus::None => self.dirty,
+ Focus::Entry | Focus::EntryFullscreen => self.view.is_dirty(),
+ }
}
+
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
- if self.unfocused {
+ if self.unfocused() {
self.view.set_dirty(value);
}
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
- let mut map = if self.unfocused {
+ let mut map = if self.unfocused() {
self.view.get_shortcuts(context)
} else {
ShortcutMaps::default()
diff --git a/src/components/mail/listing/thread.rs b/src/components/mail/listing/thread.rs
index 029a0408..396e272a 100644
--- a/src/components/mail/listing/thread.rs
+++ b/src/components/mail/listing/thread.rs
@@ -128,7 +128,7 @@ pub struct ThreadListing {
/// If we must redraw on next redraw event
dirty: bool,
/// If `self.view` is focused or not.
- unfocused: bool,
+ focus: Focus,
initialised: bool,
view: Option<MailView>,
movement: Option<PageMovement>,
@@ -410,9 +410,10 @@ impl ListingTrait for ThreadListing {
fn coordinates(&self) -> (AccountHash, MailboxHash) {
(self.new_cursor_pos.0, self.new_cursor_pos.1)
}
+
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
- self.unfocused = false;
+ self.focus = Focus::None;
self.view = None;
self.order.clear();
self.row_updates.clear();
@@ -741,13 +742,55 @@ impl ListingTrait for ThreadListing {
}
fn unfocused(&self) -> bool {
- self.unfocused
+ !matches!(self.focus, Focus::None)
}
fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm);
self.set_dirty(true);
}
+
+ fn set_focus(&mut self, new_value: Focus, context: &mut Context) {
+ match new_value {
+ Focus::None => {
+ self.view = None;
+ self.dirty = true;
+ /* If self.row_updates is not empty and we exit a thread, the row_update events
+ * will be performed but the list will not be drawn. So force a draw in any case.
+ * */
+ // self.force_draw = true;
+ }
+ Focus::Entry => {
+ // self.force_draw = true;
+ self.dirty = true;
+ let coordinates = (
+ self.cursor_pos.0,
+ self.cursor_pos.1,
+ self.get_env_under_cursor(self.cursor_pos.2, context),
+ );
+
+ if let Some(ref mut v) = self.view {
+ v.update(coordinates, context);
+ } else {
+ self.view = Some(MailView::new(coordinates, None, None, context));
+ }
+
+ if let Some(ref mut s) = self.view {
+ s.set_dirty(true);
+ }
+ }
+ Focus::EntryFullscreen => {
+ if let Some(ref mut s) = self.view {
+ s.set_dirty(true);
+ }
+ }
+ }
+ self.focus = new_value;
+ }
+
+ fn focus(&self) -> Focus {
+ self.focus
+ }
}
impl fmt::Display for ThreadListing {
@@ -772,7 +815,7 @@ impl ThreadListing {
selection: HashMap::default(),
order: HashMap::default(),
dirty: true,
- unfocused: false,
+ focus: Focus::None,
view: None,
initialised: false,
movement: None,
@@ -1092,10 +1135,17 @@ impl Component for ThreadListing {
}
}
*/
- if !self.unfocused {
- if !self.is_dirty() {
- return;
+ if !self.is_dirty() {
+ return;
+ }
+
+ if matches!(self.focus, Focus::EntryFullscreen) {
+ if let Some(v) = self.view.as_mut() {
+ return v.draw(grid, area, context);
}
+ }
+
+ if !self.unfocused() {
self.dirty = false;
/* Draw the entire list */
self.draw_list(grid, area, context);
@@ -1198,12 +1248,38 @@ impl Component for ThreadListing {
self.dirty = false;
}
}
+
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
+ let shortcuts = self.get_shortcuts(context);
+
+ match (&event, self.focus) {
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::EntryFullscreen)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::Entry, context);
+ return true;
+ }
+ (UIEvent::Input(ref k), Focus::Entry)
+ if shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ _ => {}
+ }
+
if let Some(ref mut v) = self.view {
- if v.process_event(event, context) {
+ if !matches!(self.focus, Focus::None) && v.process_event(event, context) {
return true;
}
}
+
match *event {
UIEvent::ConfigReload { old_settings: _ } => {
self.color_cache = ColorCache {
@@ -1238,18 +1314,43 @@ impl Component for ThreadListing {
}
self.set_dirty(true);
}
- UIEvent::Input(Key::Char('\n')) if !self.unfocused => {
- self.unfocused = true;
- self.dirty = true;
+ UIEvent::Input(ref k)
+ if matches!(self.focus, Focus::None)
+ && (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
+ || shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
+ {
+ self.set_focus(Focus::Entry, context);
return true;
}
- UIEvent::Input(Key::Char('i')) if self.unfocused => {
- self.unfocused = false;
- if let Some(ref mut s) = self.view {
- s.process_event(&mut UIEvent::VisibilityChange(false), context);
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
+ {
+ self.set_focus(Focus::None, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::Entry)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
+ {
+ self.set_focus(Focus::EntryFullscreen, context);
+ return true;
+ }
+ UIEvent::Input(ref k)
+ if !matches!(self.focus, Focus::None)
+ && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
+ {
+ match self.focus {
+ Focus::Entry => {
+ self.set_focus(Focus::None, context);
+ }
+ Focus::EntryFullscreen => {
+ self.set_focus(Focus::Entry, context);
+ }
+ Focus::None => {
+ unreachable!();
+ }
}
- self.dirty = true;
- self.view = None;
return true;
}
UIEvent::MailboxUpdate((ref idxa, ref idxf))
@@ -1275,7 +1376,7 @@ impl Component for ThreadListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
if let Some(v) = self.view.as_mut() {
v.process_event(
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
@@ -1301,7 +1402,7 @@ impl Component for ThreadListing {
self.dirty = true;
- if self.unfocused {
+ if self.unfocused() {
if let Some(v) = self.view.as_mut() {
v.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
}
@@ -1328,7 +1429,7 @@ impl Component for ThreadListing {
self.refresh_mailbox(context, false);
return true;
}
- Action::Listing(Search(ref filter_term)) if !self.unfocused => {
+ Action::Listing(Search(ref filter_term)) if !self.unfocused() => {
match context.accounts[&self.cursor_pos.0].search(
filter_term,
self.sort,
@@ -1379,20 +1480,36 @@ impl Component for ThreadListing {
}
false
}
+
fn is_dirty(&self) -> bool {
- self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
+ match self.focus {
+ Focus::None => self.dirty,
+ Focus::Entry => self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false),
+ Focus::EntryFullscreen => self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false),
+ }
}
+
fn set_dirty(&mut self, value: bool) {
if let Some(p) = self.view.as_mut() {
p.set_dirty(value);
};
self.dirty = value;
}
+
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
- self.view
- .as_ref()
- .map(|p| p.get_shortcuts(context))
- .unwrap_or_default()
+ let mut map = if self.unfocused() {
+ self.view
+ .as_ref()
+ .map(|p| p.get_shortcuts(context))
+ .unwrap_or_default()
+ } else {
+ ShortcutMaps::default()
+ };
+
+ let config_map = context.settings.shortcuts.listing.key_values();
+ map.insert(Listing::DESCRIPTION, config_map);
+
+ map
}
fn id(&self) -> ComponentId {
diff --git a/src/conf/shortcuts.rs b/src/conf/shortcuts.rs
index 63e63420..45e7cbcf 100644
--- a/src/conf/shortcuts.rs
+++ b/src/conf/shortcuts.rs
@@ -160,8 +160,8 @@ shortcut_key_values! { "listing",
increase_sidebar |> "Increase sidebar width." |> Key::Ctrl('p'),
decrease_sidebar |> "Decrease sidebar width." |> Key::Ctrl('o'),
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`'),
- focus_on_menu |> "Switch focus on sidebar menu." |> Key::Left,
- focus_on_list |> "Switch focus on mail list." |> Key::Right,
+ focus_left |> "Switch focus on the left." |> Key::Left,
+ focus_right |> "Switch focus on the right." |> Key::Right,
exit_entry |> "Exit e-mail entry." |> Key::Char('i'),
open_entry |> "Open e-mail entry." |> Key::Char('\n')
}