Browse Source

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.
137-memory-eating
Manos Pitsidianakis 5 months ago
parent
commit
9dc4d4055c
  1. 2
      CHANGELOG.md
  2. 4
      docs/meli.7
  3. 8
      docs/meli.conf.5
  4. 29
      src/components/mail/listing.rs
  5. 158
      src/components/mail/listing/compact.rs
  6. 152
      src/components/mail/listing/conversations.rs
  7. 6
      src/components/mail/listing/offline.rs
  8. 155
      src/components/mail/listing/plain.rs
  9. 167
      src/components/mail/listing/thread.rs
  10. 4
      src/conf/shortcuts.rs

2
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.

4
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

8
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

29
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
}
}

158
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()

152
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()

6
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 {

155
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()

167
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 {