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 2022-09-07 16:39:15 +03:00
parent 3d92b41075
commit 9dc4d4055c
10 changed files with 523 additions and 162 deletions

View File

@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### 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` 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 - `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 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. - `a484b397` Added configuration setting `library_file_path` to notmuch backend if user wants to specify the library's location manually.

View File

@ -233,10 +233,10 @@ Press
to toggle the sidebars visibility. to toggle the sidebars visibility.
.Pp .Pp
Press Press
.Shortcut Left listing focus_on_menu .Shortcut Left listing focus_right
to switch focus on the sidebar menu. to switch focus on the sidebar menu.
Press Press
.Shortcut Right listing focus_on_list .Shortcut Right listing focus_left
to switch focus on the e-mail list. to switch focus on the e-mail list.
.Pp .Pp
On the e-mail list, press On the e-mail list, press

View File

@ -781,6 +781,14 @@ Decrease sidebar width.
Toggle visibility of side menu in mail list. Toggle visibility of side menu in mail list.
.\" default value .\" default value
.Pq Em ` .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 .It Ic exit_entry
Exit e-mail entry. Exit e-mail entry.
.\" default value .\" default value

View File

@ -61,6 +61,13 @@ pub use self::plain::*;
mod offline; mod offline;
pub use self::offline::*; pub use self::offline::*;
#[derive(Debug, Copy, Clone)]
pub enum Focus {
None,
Entry,
EntryFullscreen,
}
#[derive(Debug, Copy, PartialEq, Clone)] #[derive(Debug, Copy, PartialEq, Clone)]
pub enum Modifier { pub enum Modifier {
SymmetricDifference, SymmetricDifference,
@ -516,6 +523,8 @@ pub trait ListingTrait: Component {
None None
} }
fn set_movement(&mut self, mvm: PageMovement); fn set_movement(&mut self, mvm: PageMovement);
fn focus(&self) -> Focus;
fn set_focus(&mut self, new_value: Focus, context: &mut Context);
} }
#[derive(Debug)] #[derive(Debug)]
@ -655,7 +664,7 @@ impl Component for Listing {
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
let total_cols = get_x(bottom_right) - get_x(upper_left); 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 { if self.focus == ListingFocus::Menu {
(self.ratio * total_cols) / 100 (self.ratio * total_cols) / 100
} else { } else {
@ -925,7 +934,7 @@ impl Component for Listing {
if self.focus == ListingFocus::Mailbox { if self.focus == ListingFocus::Mailbox {
match *event { match *event {
UIEvent::Input(Key::Mouse(MouseEvent::Press(MouseButton::Left, x, _y))) UIEvent::Input(Key::Mouse(MouseEvent::Press(MouseButton::Left, x, _y)))
if self.menu_visibility => if self.is_menu_visible() =>
{ {
match self.menu_width { match self.menu_width {
WidgetWidth::Hold(wx) | WidgetWidth::Set(wx) WidgetWidth::Hold(wx) | WidgetWidth::Set(wx)
@ -942,7 +951,7 @@ impl Component for Listing {
self.set_dirty(true); self.set_dirty(true);
return 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 { match self.menu_width {
WidgetWidth::Hold(ref mut hx) => { WidgetWidth::Hold(ref mut hx) => {
*hx = usize::from(x).saturating_sub(1); *hx = usize::from(x).saturating_sub(1);
@ -952,7 +961,9 @@ impl Component for Listing {
self.set_dirty(true); self.set_dirty(true);
return 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 { match self.menu_width {
WidgetWidth::Hold(_) => { WidgetWidth::Hold(_) => {
self.menu_width = WidgetWidth::Set(usize::from(x).saturating_sub(1)); self.menu_width = WidgetWidth::Set(usize::from(x).saturating_sub(1));
@ -963,8 +974,8 @@ impl Component for Listing {
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
if self.menu_visibility if self.is_menu_visible()
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_on_menu"]) => && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
{ {
self.focus = ListingFocus::Menu; self.focus = ListingFocus::Menu;
if self.show_menu_scrollbar != ShowMenuScrollbar::Never { if self.show_menu_scrollbar != ShowMenuScrollbar::Never {
@ -1337,7 +1348,7 @@ impl Component for Listing {
} else if self.focus == ListingFocus::Menu { } else if self.focus == ListingFocus::Menu {
match *event { match *event {
UIEvent::Input(ref k) 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; self.focus = ListingFocus::Mailbox;
context context
@ -2374,4 +2385,8 @@ impl Listing {
self.get_status(context), self.get_status(context),
))); )));
} }
fn is_menu_visible(&self) -> bool {
!matches!(self.component.focus(), Focus::EntryFullscreen) && self.menu_visibility
}
} }

View File

@ -187,7 +187,7 @@ pub struct CompactListing {
dirty: bool, dirty: bool,
force_draw: bool, force_draw: bool,
/// If `self.view` exists or not. /// If `self.view` exists or not.
unfocused: bool, focus: Focus,
view: ThreadView, view: ThreadView,
row_updates: SmallVec<[ThreadHash; 8]>, row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache, color_cache: ColorCache,
@ -304,7 +304,7 @@ impl MailListingTrait for CompactListing {
if !force && old_cursor_pos == self.new_cursor_pos { if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(context); self.view.update(context);
} else if self.unfocused { } else if self.unfocused() {
let thread = self.get_thread_under_cursor(self.cursor_pos.2); let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.new_cursor_pos, thread, None, context); 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)) { fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0); self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.unfocused = false; self.focus = Focus::None;
self.view = ThreadView::default(); self.view = ThreadView::default();
self.filtered_selection.clear(); self.filtered_selection.clear();
self.filtered_order.clear(); self.filtered_order.clear();
@ -818,7 +818,7 @@ impl ListingTrait for CompactListing {
} }
fn unfocused(&self) -> bool { fn unfocused(&self) -> bool {
self.unfocused !matches!(self.focus, Focus::None)
} }
fn set_modifier_active(&mut self, new_val: bool) { fn set_modifier_active(&mut self, new_val: bool) {
@ -837,6 +837,33 @@ impl ListingTrait for CompactListing {
self.movement = Some(mvm); self.movement = Some(mvm);
self.set_dirty(true); 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 { impl fmt::Display for CompactListing {
@ -863,13 +890,13 @@ impl CompactListing {
filtered_selection: Vec::new(), filtered_selection: Vec::new(),
filtered_order: HashMap::default(), filtered_order: HashMap::default(),
selection: HashMap::default(), selection: HashMap::default(),
focus: Focus::None,
row_updates: SmallVec::new(), row_updates: SmallVec::new(),
data_columns: DataColumns::default(), data_columns: DataColumns::default(),
rows_drawn: SegmentTree::default(), rows_drawn: SegmentTree::default(),
rows: vec![], rows: vec![],
dirty: true, dirty: true,
force_draw: true, force_draw: true,
unfocused: false,
view: ThreadView::default(), view: ThreadView::default(),
color_cache: ColorCache::default(), color_cache: ColorCache::default(),
movement: None, movement: None,
@ -1465,10 +1492,15 @@ impl CompactListing {
impl Component for CompactListing { impl Component for CompactListing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.unfocused { if !self.is_dirty() {
if !self.is_dirty() { return;
return; }
}
if matches!(self.focus, Focus::EntryFullscreen) {
return self.view.draw(grid, area, context);
}
if !self.unfocused() {
let mut area = area; let mut area = area;
if !self.filter_term.is_empty() { if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area; let (upper_left, bottom_right) = area;
@ -1715,40 +1747,81 @@ impl Component for CompactListing {
} }
self.dirty = false; self.dirty = false;
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { 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; return true;
} }
let shortcuts = self.get_shortcuts(context);
if self.length > 0 { if self.length > 0 {
match *event { match *event {
UIEvent::Input(ref k) UIEvent::Input(ref k)
if !self.unfocused if matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) => && (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); let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.cursor_pos, thread, None, context); self.view = ThreadView::new(self.cursor_pos, thread, None, context);
self.unfocused = true; self.set_focus(Focus::Entry, context);
self.dirty = true;
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
if self.unfocused if matches!(self.focus, Focus::Entry)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) => && shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{ {
self.unfocused = false; self.set_focus(Focus::None, context);
self.view return true;
.process_event(&mut UIEvent::VisibilityChange(false), context); }
self.dirty = true; UIEvent::Input(ref k)
/* If self.row_updates is not empty and we exit a thread, the row_update events if matches!(self.focus, Focus::None)
* will be performed but the list will not be drawn. So force a draw in any case. && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
* */ {
self.force_draw = true; 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; return true;
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if !self.unfocused if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) => && shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{ {
if self.modifier_active && self.modifier_command.is_none() { if self.modifier_active && self.modifier_command.is_none() {
@ -1762,7 +1835,7 @@ impl Component for CompactListing {
} }
UIEvent::Action(ref action) => { UIEvent::Action(ref action) => {
match action { match action {
Action::Sort(field, order) if !self.unfocused => { Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order); debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order); self.sort = (*field, *order);
self.sortcmd = true; self.sortcmd = true;
@ -1774,13 +1847,13 @@ impl Component for CompactListing {
} }
return true; return true;
} }
Action::SubSort(field, order) if !self.unfocused => { Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order); debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order); self.subsort = (*field, *order);
// FIXME: perform subsort. // FIXME: perform subsort.
return true; 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 thread = self.get_thread_under_cursor(self.cursor_pos.2);
let account = &mut context.accounts[&self.cursor_pos.0]; let account = &mut context.accounts[&self.cursor_pos.0];
account account
@ -1871,7 +1944,7 @@ impl Component for CompactListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view self.view
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context); .process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context);
} }
@ -1901,7 +1974,7 @@ impl Component for CompactListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context); .process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
} }
@ -1913,7 +1986,7 @@ impl Component for CompactListing {
self.dirty = true; self.dirty = true;
} }
UIEvent::Input(Key::Esc) UIEvent::Input(Key::Esc)
if !self.unfocused if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) => && self.selection.values().cloned().any(std::convert::identity) =>
{ {
for v in self.selection.values_mut() { for v in self.selection.values_mut() {
@ -1922,13 +1995,13 @@ impl Component for CompactListing {
self.dirty = true; self.dirty = true;
return 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_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
self.set_dirty(true); self.set_dirty(true);
return 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( match context.accounts[&self.cursor_pos.0].search(
filter_term, filter_term,
self.sort, self.sort,
@ -1950,7 +2023,7 @@ impl Component for CompactListing {
}; };
self.set_dirty(true); 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( match context.accounts[&self.cursor_pos.0].search(
search_term, search_term,
self.sort, self.sort,
@ -2017,23 +2090,24 @@ impl Component for CompactListing {
} }
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty match self.focus {
|| if self.unfocused { Focus::None => self.dirty,
self.view.is_dirty() Focus::Entry => self.dirty || self.view.is_dirty(),
} else { Focus::EntryFullscreen => self.view.is_dirty(),
false }
}
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
self.dirty = value; self.dirty = value;
if self.unfocused { if self.unfocused() {
self.view.set_dirty(value); self.view.set_dirty(value);
} }
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if self.unfocused { let mut map = if self.unfocused() {
self.view.get_shortcuts(context) self.view.get_shortcuts(context)
} else { } else {
ShortcutMaps::default() ShortcutMaps::default()

View File

@ -114,7 +114,7 @@ pub struct ConversationsListing {
dirty: bool, dirty: bool,
force_draw: bool, force_draw: bool,
/// If `self.view` exists or not. /// If `self.view` exists or not.
unfocused: bool, focus: Focus,
view: ThreadView, view: ThreadView,
row_updates: SmallVec<[ThreadHash; 8]>, row_updates: SmallVec<[ThreadHash; 8]>,
color_cache: ColorCache, 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 if !force && old_cursor_pos == self.new_cursor_pos && old_mailbox_hash == self.cursor_pos.1
{ {
self.view.update(context); self.view.update(context);
} else if self.unfocused { } else if self.unfocused() {
let thread_group = self.get_thread_under_cursor(self.cursor_pos.2); let thread_group = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.new_cursor_pos, thread_group, None, context); 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)) { fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0); self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.unfocused = false; self.focus = Focus::None;
self.view = ThreadView::default(); self.view = ThreadView::default();
self.filtered_selection.clear(); self.filtered_selection.clear();
self.filtered_order.clear(); self.filtered_order.clear();
@ -537,7 +537,7 @@ impl ListingTrait for ConversationsListing {
} }
fn unfocused(&self) -> bool { fn unfocused(&self) -> bool {
self.unfocused !matches!(self.focus, Focus::None)
} }
fn set_modifier_active(&mut self, new_val: bool) { fn set_modifier_active(&mut self, new_val: bool) {
@ -556,6 +556,33 @@ impl ListingTrait for ConversationsListing {
self.movement = Some(mvm); self.movement = Some(mvm);
self.set_dirty(true); 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 { impl fmt::Display for ConversationsListing {
@ -569,7 +596,7 @@ impl ConversationsListing {
//const PADDING_CHAR: char = ' '; //░'; //const PADDING_CHAR: char = ' '; //░';
pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> { pub fn new(coordinates: (AccountHash, MailboxHash)) -> Box<Self> {
Box::new(ConversationsListing { Box::new(Self {
cursor_pos: (coordinates.0, 1, 0), cursor_pos: (coordinates.0, 1, 0),
new_cursor_pos: (coordinates.0, coordinates.1, 0), new_cursor_pos: (coordinates.0, coordinates.1, 0),
length: 0, length: 0,
@ -586,7 +613,7 @@ impl ConversationsListing {
rows: Ok(Vec::with_capacity(1024)), rows: Ok(Vec::with_capacity(1024)),
dirty: true, dirty: true,
force_draw: true, force_draw: true,
unfocused: false, focus: Focus::None,
view: ThreadView::default(), view: ThreadView::default(),
color_cache: ColorCache::default(), color_cache: ColorCache::default(),
movement: None, movement: None,
@ -907,6 +934,11 @@ impl Component for ConversationsListing {
if !self.is_dirty() { if !self.is_dirty() {
return; return;
} }
if matches!(self.focus, Focus::EntryFullscreen) {
return self.view.draw(grid, area, context);
}
let (upper_left, bottom_right) = area; let (upper_left, bottom_right) = area;
{ {
let mut area = area; let mut area = area;
@ -1142,7 +1174,7 @@ impl Component for ConversationsListing {
self.draw_list(grid, area, context); self.draw_list(grid, area, context);
} }
} }
if self.unfocused { if matches!(self.focus, Focus::Entry) {
if self.length == 0 && self.dirty { if self.length == 0 && self.dirty {
clear_area(grid, area, self.color_cache.theme_default); clear_area(grid, area, self.color_cache.theme_default);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -1157,40 +1189,81 @@ impl Component for ConversationsListing {
} }
self.dirty = false; self.dirty = false;
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { 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; return true;
} }
let shortcuts = self.get_shortcuts(context);
if self.length > 0 { if self.length > 0 {
match *event { match *event {
UIEvent::Input(ref k) UIEvent::Input(ref k)
if !self.unfocused if matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) => && (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); let thread = self.get_thread_under_cursor(self.cursor_pos.2);
self.view = ThreadView::new(self.cursor_pos, thread, None, context); self.view = ThreadView::new(self.cursor_pos, thread, None, context);
self.unfocused = true; self.set_focus(Focus::Entry, context);
self.dirty = true;
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
if self.unfocused if !matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) => && shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{ {
self.unfocused = false; self.set_focus(Focus::None, context);
self.view return true;
.process_event(&mut UIEvent::VisibilityChange(false), context); }
self.dirty = true; UIEvent::Input(ref k)
/* If self.row_updates is not empty and we exit a thread, the row_update events if matches!(self.focus, Focus::Entry)
* will be performed but the list will not be drawn. So force a draw in any case. && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"]) =>
* */ {
self.force_draw = true; 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; return true;
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if !self.unfocused if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) => && shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{ {
if self.modifier_active && self.modifier_command.is_none() { if self.modifier_active && self.modifier_command.is_none() {
@ -1221,7 +1294,7 @@ impl Component for ConversationsListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view.process_event( self.view.process_event(
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), &mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
context, context,
@ -1253,13 +1326,13 @@ impl Component for ConversationsListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context); .process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
} }
} }
UIEvent::Action(ref action) => match action { UIEvent::Action(ref action) => match action {
Action::SubSort(field, order) if !self.unfocused => { Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order); debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order); self.subsort = (*field, *order);
// FIXME subsort // FIXME subsort
@ -1271,7 +1344,7 @@ impl Component for ConversationsListing {
//} //}
return true; return true;
} }
Action::Sort(field, order) if !self.unfocused => { Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order); debug!("Sort {:?} , {:?}", field, order);
// FIXME sort // FIXME sort
/* /*
@ -1291,7 +1364,7 @@ impl Component for ConversationsListing {
*/ */
return true; 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 thread = self.get_thread_under_cursor(self.cursor_pos.2);
let account = &mut context.accounts[&self.cursor_pos.0]; let account = &mut context.accounts[&self.cursor_pos.0];
account account
@ -1359,7 +1432,7 @@ impl Component for ConversationsListing {
self.dirty = true; self.dirty = true;
} }
UIEvent::Action(ref action) => match action { 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( match context.accounts[&self.cursor_pos.0].search(
filter_term, filter_term,
self.sort, self.sort,
@ -1385,7 +1458,7 @@ impl Component for ConversationsListing {
_ => {} _ => {}
}, },
UIEvent::Input(Key::Esc) UIEvent::Input(Key::Esc)
if !self.unfocused if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) => && self.selection.values().cloned().any(std::convert::identity) =>
{ {
for (k, v) in self.selection.iter_mut() { for (k, v) in self.selection.iter_mut() {
@ -1398,7 +1471,7 @@ impl Component for ConversationsListing {
return true; return true;
} }
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Char('')) 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.set_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
@ -1432,23 +1505,24 @@ impl Component for ConversationsListing {
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty match self.focus {
|| if self.unfocused { Focus::None => self.dirty,
self.view.is_dirty() Focus::Entry => self.dirty || self.view.is_dirty(),
} else { Focus::EntryFullscreen => self.view.is_dirty(),
false }
}
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
if self.unfocused { if self.unfocused() {
self.view.set_dirty(value); self.view.set_dirty(value);
} }
self.dirty = value; self.dirty = value;
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if self.unfocused { let mut map = if self.unfocused() {
self.view.get_shortcuts(context) self.view.get_shortcuts(context)
} else { } else {
ShortcutMaps::default() ShortcutMaps::default()

View File

@ -84,6 +84,12 @@ impl ListingTrait for OfflineListing {
} }
fn set_movement(&mut self, _: PageMovement) {} 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 { impl fmt::Display for OfflineListing {

View File

@ -146,7 +146,7 @@ pub struct PlainListing {
dirty: bool, dirty: bool,
force_draw: bool, force_draw: bool,
/// If `self.view` exists or not. /// If `self.view` exists or not.
unfocused: bool, focus: Focus,
view: MailView, view: MailView,
row_updates: SmallVec<[EnvelopeHash; 8]>, row_updates: SmallVec<[EnvelopeHash; 8]>,
_row_updates: SmallVec<[ThreadHash; 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); let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
if !force && old_cursor_pos == self.new_cursor_pos { if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(temp, context); self.view.update(temp, context);
} else if self.unfocused { } else if self.unfocused() {
self.view = MailView::new(temp, None, None, context); self.view = MailView::new(temp, None, None, context);
} }
} }
@ -335,7 +335,7 @@ impl ListingTrait for PlainListing {
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) { fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0); self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.unfocused = false; self.focus = Focus::None;
self.view = MailView::default(); self.view = MailView::default();
self.filtered_selection.clear(); self.filtered_selection.clear();
self.filtered_order.clear(); self.filtered_order.clear();
@ -641,13 +641,44 @@ impl ListingTrait for PlainListing {
} }
fn unfocused(&self) -> bool { fn unfocused(&self) -> bool {
self.unfocused !matches!(self.focus, Focus::None)
} }
fn set_movement(&mut self, mvm: PageMovement) { fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm); self.movement = Some(mvm);
self.set_dirty(true); 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 { impl fmt::Display for PlainListing {
@ -680,7 +711,7 @@ impl PlainListing {
data_columns: DataColumns::default(), data_columns: DataColumns::default(),
dirty: true, dirty: true,
force_draw: true, force_draw: true,
unfocused: false, focus: Focus::None,
view: MailView::default(), view: MailView::default(),
color_cache: ColorCache::default(), color_cache: ColorCache::default(),
active_jobs: HashMap::default(), active_jobs: HashMap::default(),
@ -1060,10 +1091,15 @@ impl PlainListing {
impl Component for PlainListing { impl Component for PlainListing {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.unfocused { if !self.is_dirty() {
if !self.is_dirty() { return;
return; }
}
if matches!(self.focus, Focus::EntryFullscreen) {
return self.view.draw(grid, area, context);
}
if matches!(self.focus, Focus::None) {
let mut area = area; let mut area = area;
if !self.filter_term.is_empty() { if !self.filter_term.is_empty() {
let (upper_left, bottom_right) = area; let (upper_left, bottom_right) = area;
@ -1129,48 +1165,79 @@ impl Component for PlainListing {
} }
self.dirty = false; self.dirty = false;
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { 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; return true;
} }
let shortcuts = self.get_shortcuts(context);
if self.length > 0 { if self.length > 0 {
match *event { match *event {
UIEvent::Input(ref k) UIEvent::Input(ref k)
if !self.unfocused if matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"]) => && (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); self.set_focus(Focus::Entry, 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;
return true; return true;
} }
UIEvent::Input(ref k) UIEvent::Input(ref k)
if self.unfocused if !matches!(self.focus, Focus::None)
&& shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) => && shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
{ {
self.unfocused = false; self.set_focus(Focus::None, context);
self.view return true;
.process_event(&mut UIEvent::VisibilityChange(false), context); }
self.dirty = true; UIEvent::Input(ref k)
/* If self.row_updates is not empty and we exit a thread, the row_update events if !matches!(self.focus, Focus::None)
* will be performed but the list will not be drawn. So force a draw in any case. && shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_left"]) =>
* */ {
self.force_draw = true; match self.focus {
Focus::Entry => {
self.set_focus(Focus::None, context);
}
Focus::EntryFullscreen => {
self.set_focus(Focus::Entry, context);
}
Focus::None => {
unreachable!();
}
}
return true; return true;
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
if !self.unfocused if !self.unfocused()
&& shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) => && shortcut!(key == shortcuts[Listing::DESCRIPTION]["select_entry"]) =>
{ {
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context); let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
self.selection.entry(env_hash).and_modify(|e| *e = !*e); self.selection.entry(env_hash).and_modify(|e| *e = !*e);
} }
UIEvent::Action(ref action) => match action { UIEvent::Action(ref action) => match action {
Action::SubSort(field, order) if !self.unfocused => { Action::SubSort(field, order) if !self.unfocused() => {
debug!("SubSort {:?} , {:?}", field, order); debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order); self.subsort = (*field, *order);
//if !self.filtered_selection.is_empty() { //if !self.filtered_selection.is_empty() {
@ -1181,7 +1248,7 @@ impl Component for PlainListing {
//} //}
return true; return true;
} }
Action::Sort(field, order) if !self.unfocused => { Action::Sort(field, order) if !self.unfocused() => {
debug!("Sort {:?} , {:?}", field, order); debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order); self.sort = (*field, *order);
return true; return true;
@ -1189,7 +1256,7 @@ impl Component for PlainListing {
Action::Listing(a @ ListingAction::SetSeen) Action::Listing(a @ ListingAction::SetSeen)
| Action::Listing(a @ ListingAction::SetUnseen) | Action::Listing(a @ ListingAction::SetUnseen)
| Action::Listing(a @ ListingAction::Delete) | Action::Listing(a @ ListingAction::Delete)
if !self.unfocused => if !self.unfocused() =>
{ {
let is_selection_empty = let is_selection_empty =
self.selection.values().cloned().any(std::convert::identity); self.selection.values().cloned().any(std::convert::identity);
@ -1295,7 +1362,7 @@ impl Component for PlainListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view self.view
.process_event(&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), context); .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.row_updates.push(*env_hash);
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
self.view self.view
.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context); .process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
} }
@ -1326,7 +1393,7 @@ impl Component for PlainListing {
self.dirty = true; self.dirty = true;
} }
UIEvent::Input(Key::Esc) UIEvent::Input(Key::Esc)
if !self.unfocused if !self.unfocused()
&& self.selection.values().cloned().any(std::convert::identity) => && self.selection.values().cloned().any(std::convert::identity) =>
{ {
for v in self.selection.values_mut() { for v in self.selection.values_mut() {
@ -1335,13 +1402,13 @@ impl Component for PlainListing {
self.dirty = true; self.dirty = true;
return 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_coordinates((self.new_cursor_pos.0, self.new_cursor_pos.1));
self.set_dirty(true); self.set_dirty(true);
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
return 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( match context.accounts[&self.cursor_pos.0].search(
filter_term, filter_term,
self.sort, self.sort,
@ -1389,23 +1456,23 @@ impl Component for PlainListing {
} }
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty match self.focus {
|| if self.unfocused { Focus::None => self.dirty,
self.view.is_dirty() Focus::Entry | Focus::EntryFullscreen => self.view.is_dirty(),
} else { }
false
}
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
self.dirty = value; self.dirty = value;
if self.unfocused { if self.unfocused() {
self.view.set_dirty(value); self.view.set_dirty(value);
} }
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if self.unfocused { let mut map = if self.unfocused() {
self.view.get_shortcuts(context) self.view.get_shortcuts(context)
} else { } else {
ShortcutMaps::default() ShortcutMaps::default()

View File

@ -128,7 +128,7 @@ pub struct ThreadListing {
/// If we must redraw on next redraw event /// If we must redraw on next redraw event
dirty: bool, dirty: bool,
/// If `self.view` is focused or not. /// If `self.view` is focused or not.
unfocused: bool, focus: Focus,
initialised: bool, initialised: bool,
view: Option<MailView>, view: Option<MailView>,
movement: Option<PageMovement>, movement: Option<PageMovement>,
@ -410,9 +410,10 @@ impl ListingTrait for ThreadListing {
fn coordinates(&self) -> (AccountHash, MailboxHash) { fn coordinates(&self) -> (AccountHash, MailboxHash) {
(self.new_cursor_pos.0, self.new_cursor_pos.1) (self.new_cursor_pos.0, self.new_cursor_pos.1)
} }
fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) { fn set_coordinates(&mut self, coordinates: (AccountHash, MailboxHash)) {
self.new_cursor_pos = (coordinates.0, coordinates.1, 0); self.new_cursor_pos = (coordinates.0, coordinates.1, 0);
self.unfocused = false; self.focus = Focus::None;
self.view = None; self.view = None;
self.order.clear(); self.order.clear();
self.row_updates.clear(); self.row_updates.clear();
@ -741,13 +742,55 @@ impl ListingTrait for ThreadListing {
} }
fn unfocused(&self) -> bool { fn unfocused(&self) -> bool {
self.unfocused !matches!(self.focus, Focus::None)
} }
fn set_movement(&mut self, mvm: PageMovement) { fn set_movement(&mut self, mvm: PageMovement) {
self.movement = Some(mvm); self.movement = Some(mvm);
self.set_dirty(true); 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 { impl fmt::Display for ThreadListing {
@ -772,7 +815,7 @@ impl ThreadListing {
selection: HashMap::default(), selection: HashMap::default(),
order: HashMap::default(), order: HashMap::default(),
dirty: true, dirty: true,
unfocused: false, focus: Focus::None,
view: None, view: None,
initialised: false, initialised: false,
movement: None, movement: None,
@ -1092,10 +1135,17 @@ impl Component for ThreadListing {
} }
} }
*/ */
if !self.unfocused { if !self.is_dirty() {
if !self.is_dirty() { return;
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; self.dirty = false;
/* Draw the entire list */ /* Draw the entire list */
self.draw_list(grid, area, context); self.draw_list(grid, area, context);
@ -1198,12 +1248,38 @@ impl Component for ThreadListing {
self.dirty = false; self.dirty = false;
} }
} }
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { 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 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; return true;
} }
} }
match *event { match *event {
UIEvent::ConfigReload { old_settings: _ } => { UIEvent::ConfigReload { old_settings: _ } => {
self.color_cache = ColorCache { self.color_cache = ColorCache {
@ -1238,18 +1314,43 @@ impl Component for ThreadListing {
} }
self.set_dirty(true); self.set_dirty(true);
} }
UIEvent::Input(Key::Char('\n')) if !self.unfocused => { UIEvent::Input(ref k)
self.unfocused = true; if matches!(self.focus, Focus::None)
self.dirty = true; && (shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_entry"])
|| shortcut!(k == shortcuts[Listing::DESCRIPTION]["focus_right"])) =>
{
self.set_focus(Focus::Entry, context);
return true; return true;
} }
UIEvent::Input(Key::Char('i')) if self.unfocused => { UIEvent::Input(ref k)
self.unfocused = false; if !matches!(self.focus, Focus::None)
if let Some(ref mut s) = self.view { && shortcut!(k == shortcuts[Listing::DESCRIPTION]["exit_entry"]) =>
s.process_event(&mut UIEvent::VisibilityChange(false), context); {
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; return true;
} }
UIEvent::MailboxUpdate((ref idxa, ref idxf)) UIEvent::MailboxUpdate((ref idxa, ref idxf))
@ -1275,7 +1376,7 @@ impl Component for ThreadListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
if let Some(v) = self.view.as_mut() { if let Some(v) = self.view.as_mut() {
v.process_event( v.process_event(
&mut UIEvent::EnvelopeRename(*old_hash, *new_hash), &mut UIEvent::EnvelopeRename(*old_hash, *new_hash),
@ -1301,7 +1402,7 @@ impl Component for ThreadListing {
self.dirty = true; self.dirty = true;
if self.unfocused { if self.unfocused() {
if let Some(v) = self.view.as_mut() { if let Some(v) = self.view.as_mut() {
v.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context); v.process_event(&mut UIEvent::EnvelopeUpdate(*env_hash), context);
} }
@ -1328,7 +1429,7 @@ impl Component for ThreadListing {
self.refresh_mailbox(context, false); self.refresh_mailbox(context, false);
return true; 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( match context.accounts[&self.cursor_pos.0].search(
filter_term, filter_term,
self.sort, self.sort,
@ -1379,20 +1480,36 @@ impl Component for ThreadListing {
} }
false false
} }
fn is_dirty(&self) -> bool { 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) { fn set_dirty(&mut self, value: bool) {
if let Some(p) = self.view.as_mut() { if let Some(p) = self.view.as_mut() {
p.set_dirty(value); p.set_dirty(value);
}; };
self.dirty = value; self.dirty = value;
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
self.view let mut map = if self.unfocused() {
.as_ref() self.view
.map(|p| p.get_shortcuts(context)) .as_ref()
.unwrap_or_default() .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 { fn id(&self) -> ComponentId {

View File

@ -160,8 +160,8 @@ shortcut_key_values! { "listing",
increase_sidebar |> "Increase sidebar width." |> Key::Ctrl('p'), increase_sidebar |> "Increase sidebar width." |> Key::Ctrl('p'),
decrease_sidebar |> "Decrease sidebar width." |> Key::Ctrl('o'), decrease_sidebar |> "Decrease sidebar width." |> Key::Ctrl('o'),
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`'), toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`'),
focus_on_menu |> "Switch focus on sidebar menu." |> Key::Left, focus_left |> "Switch focus on the left." |> Key::Left,
focus_on_list |> "Switch focus on mail list." |> Key::Right, focus_right |> "Switch focus on the right." |> Key::Right,
exit_entry |> "Exit e-mail entry." |> Key::Char('i'), exit_entry |> "Exit e-mail entry." |> Key::Char('i'),
open_entry |> "Open e-mail entry." |> Key::Char('\n') open_entry |> "Open e-mail entry." |> Key::Char('\n')
} }