Make Selector widget accept FnOnce

async
Manos Pitsidianakis 2020-02-22 11:47:13 +02:00
parent 499fd59c6e
commit c4c11e4abc
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
6 changed files with 541 additions and 252 deletions

View File

@ -185,24 +185,6 @@ impl Component for ContactManager {
match self.mode {
ViewMode::Discard(ref mut selector) => {
if selector.process_event(event, context) {
if selector.is_done() {
let s = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
ViewMode::Discard(s) => s,
_ => unreachable!(),
};
let key = s.collect()[0] as char;
match key {
'x' => {
context
.replies
.push_back(UIEvent::Action(Tab(Kill(self.parent_id))));
return true;
}
'n' => {}
'y' => {}
_ => {}
}
}
self.set_dirty(true);
return true;
}
@ -309,12 +291,12 @@ impl Component for ContactManager {
('n', "cancel".to_string()),
],
true,
std::sync::Arc::new(move |results: &[char]| match results[0] {
Some(Box::new(move |_, results: &[char]| match results[0] {
'x' => Some(UIEvent::Action(Tab(Kill(parent_id)))),
'n' => None,
'y' => None,
_ => None,
}),
})),
context,
));
self.set_dirty(true);

View File

@ -115,7 +115,7 @@ enum ViewMode {
Edit,
Embed,
SelectRecipients(UIDialog<Address>),
Send(UIDialog<bool>),
Send(UIConfirmationDialog),
}
impl ViewMode {
@ -183,7 +183,6 @@ impl Composer {
.map(|m| m.address)
{
let list_address_string = list_address.to_string();
let id = ret.id;
ret.mode = ViewMode::SelectRecipients(UIDialog::new(
"select recipients",
vec![
@ -194,7 +193,7 @@ impl Composer {
(list_address, list_address_string),
],
false,
std::sync::Arc::new(move |results: &[Address]| {
Some(Box::new(move |id: ComponentId, results: &[Address]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(
@ -205,7 +204,7 @@ impl Composer {
.join(", "),
),
))
}),
})),
context,
));
}
@ -558,99 +557,96 @@ impl Component for Composer {
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
let shortcuts = self.get_shortcuts(context);
match (&mut self.mode, &event) {
match (&mut self.mode, &mut event) {
(ViewMode::Edit, _) => {
if self.pager.process_event(event, context) {
return true;
}
}
(ViewMode::Send(ref selector), UIEvent::FinishedUIDialog(id, result))
if selector.id() == *id =>
{
if let Some(true) = result.downcast_ref::<bool>() {
self.update_draft();
if send_draft(
self.sign_mail,
context,
self.account_cursor,
self.draft.clone(),
SpecialUsageMailbox::Sent,
Flag::SEEN,
) {
context
.replies
.push_back(UIEvent::Action(Tab(Kill(self.id))));
} else {
save_draft(
self.draft.clone().finalise().unwrap().as_bytes(),
context,
SpecialUsageMailbox::Drafts,
Flag::SEEN | Flag::DRAFT,
self.account_cursor,
);
}
}
self.mode = ViewMode::Edit;
return true;
}
(ViewMode::Send(ref mut selector), _) => {
if selector.process_event(event, context) {
if selector.is_done() {
let s = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
ViewMode::Send(s) => s,
_ => unreachable!(),
};
let result: bool = s.collect().get(0).map(|b| *b).unwrap_or(false);
if result {
self.update_draft();
if send_draft(
self.sign_mail,
context,
self.account_cursor,
self.draft.clone(),
SpecialUsageMailbox::Sent,
Flag::SEEN,
) {
context
.replies
.push_back(UIEvent::Action(Tab(Kill(self.id))));
} else {
save_draft(
self.draft.clone().finalise().unwrap().as_bytes(),
context,
SpecialUsageMailbox::Drafts,
Flag::SEEN | Flag::DRAFT,
self.account_cursor,
);
}
}
}
return true;
}
}
(
ViewMode::SelectRecipients(ref selector),
UIEvent::FinishedUIDialog(id, ref mut result),
) if selector.id() == *id => {
if let Some(to_val) = result.downcast_mut::<String>() {
self.draft
.headers_mut()
.insert("To".to_string(), std::mem::replace(to_val, String::new()));
self.update_form();
}
self.mode = ViewMode::Edit;
return true;
}
(ViewMode::SelectRecipients(ref mut selector), _) => {
if selector.process_event(event, context) {
if selector.is_done() {
let s = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
ViewMode::SelectRecipients(s) => s,
_ => unreachable!(),
};
let new_recipients = s.collect();
self.draft.headers_mut().insert(
"To".to_string(),
new_recipients
.into_iter()
.map(|a| a.to_string())
.collect::<Vec<String>>()
.join(", "),
);
self.update_form();
}
return true;
}
}
(ViewMode::Discard(u, ref selector), UIEvent::FinishedUIDialog(id, ref mut result))
if selector.id() == *id =>
{
if let Some(key) = result.downcast_mut::<char>() {
match key {
'x' => {
context.replies.push_back(UIEvent::Action(Tab(Kill(*u))));
return true;
}
'n' => {}
'y' => {
save_draft(
self.draft.clone().finalise().unwrap().as_bytes(),
context,
SpecialUsageMailbox::Drafts,
Flag::SEEN | Flag::DRAFT,
self.account_cursor,
);
context.replies.push_back(UIEvent::Action(Tab(Kill(*u))));
return true;
}
_ => {}
}
}
self.set_dirty(true);
self.mode = ViewMode::Edit;
return true;
}
(ViewMode::Discard(_, ref mut selector), _) => {
if selector.process_event(event, context) {
if selector.is_done() {
let (u, s) = match std::mem::replace(&mut self.mode, ViewMode::Edit) {
ViewMode::Discard(u, s) => (u, s),
_ => unreachable!(),
};
let key = s.collect()[0] as char;
match key {
'x' => {
context.replies.push_back(UIEvent::Action(Tab(Kill(u))));
return true;
}
'n' => {}
'y' => {
save_draft(
self.draft.clone().finalise().unwrap().as_bytes(),
context,
SpecialUsageMailbox::Drafts,
Flag::SEEN | Flag::DRAFT,
self.account_cursor,
);
context.replies.push_back(UIEvent::Action(Tab(Kill(u))));
return true;
}
_ => {}
}
self.set_dirty(true);
}
return true;
}
}
@ -711,15 +707,14 @@ impl Component for Composer {
&& self.mode.is_edit() =>
{
self.update_draft();
let id = self.id;
self.mode = ViewMode::Send(UIDialog::new(
self.mode = ViewMode::Send(UIConfirmationDialog::new(
"send mail?",
vec![(true, "yes".to_string()), (false, "no".to_string())],
/* only one choice */
true,
std::sync::Arc::new(move |results: &[bool]| {
Some(UIEvent::FinishedUIDialog(id, Box::new(results[0])))
}),
Some(Box::new(move |id: ComponentId, result: bool| {
Some(UIEvent::FinishedUIDialog(id, Box::new(result)))
})),
context,
));
return true;
@ -1030,9 +1025,12 @@ impl Component for Composer {
('n', "cancel".to_string()),
],
true,
std::sync::Arc::new(move |results: &[char]| {
Some(UIEvent::FinishedUIDialog(uuid, Box::new(results[0])))
}),
Some(Box::new(move |id: ComponentId, results: &[char]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(results.get(0).map(|c| *c).unwrap_or('n')),
))
})),
context,
),
);
@ -1075,9 +1073,12 @@ impl Component for Composer {
('n', "cancel".to_string()),
],
true,
std::sync::Arc::new(move |results: &[char]| {
Some(UIEvent::FinishedUIDialog(id, Box::new(results[0])))
}),
Some(Box::new(move |id: ComponentId, results: &[char]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(results.get(0).map(|c| *c).unwrap_or('n')),
))
})),
context,
),
);

View File

@ -38,7 +38,7 @@ pub use self::envelope::*;
use linkify::{Link, LinkFinder};
use xdg_utils::query_default_app;
#[derive(PartialEq, Debug, Clone)]
#[derive(PartialEq, Debug)]
enum ViewMode {
Normal,
Url,
@ -101,7 +101,7 @@ impl Clone for MailView {
subview: None,
cmd_buf: String::with_capacity(4),
pager: self.pager.clone(),
mode: self.mode.clone(),
mode: ViewMode::Normal,
..*self
}
}
@ -715,34 +715,37 @@ impl Component for MailView {
self.dirty = false;
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
let shortcuts = self.get_shortcuts(context);
match self.mode {
ViewMode::Ansi(ref mut buf) => {
match (&mut self.mode, &mut event) {
(ViewMode::Ansi(ref mut buf), _) => {
if buf.process_event(event, context) {
return true;
}
}
ViewMode::Subview => {
(ViewMode::Subview, _) => {
if let Some(s) = self.subview.as_mut() {
if s.process_event(event, context) {
return true;
}
}
}
ViewMode::ContactSelector(ref mut s) => {
if s.process_event(event, context) {
if s.is_done() {
if let ViewMode::ContactSelector(s) =
std::mem::replace(&mut self.mode, ViewMode::Normal)
{
if let Some(event) = s.done() {
context.replies.push_back(event);
}
} else {
unsafe { std::hint::unreachable_unchecked() }
(ViewMode::ContactSelector(ref s), UIEvent::FinishedUIDialog(id, results))
if *id == s.id() =>
{
if let Some(results) = results.downcast_ref::<Vec<Card>>() {
let account = &mut context.accounts[self.coordinates.0];
{
for card in results.iter() {
account.address_book.add_card(card.clone());
}
}
}
self.mode = ViewMode::Normal;
return true;
}
(ViewMode::ContactSelector(ref mut s), _) => {
if s.process_event(event, context) {
return true;
}
if self.pager.process_event(event, context) {
@ -821,35 +824,18 @@ impl Component for MailView {
entries.push((new_card, format!("{}", addr)));
}
drop(envelope);
let id = self.id;
self.mode = ViewMode::ContactSelector(Selector::new(
"select contacts to add",
entries,
false,
std::sync::Arc::new(move |results: &[Card]| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(results.into_iter().cloned().collect::<Vec<Card>>()),
))
}),
Some(Box::new(move |id: ComponentId, results: &[Card]| {
Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec())))
})),
context,
));
self.dirty = true;
return true;
}
UIEvent::FinishedUIDialog(ref id, ref results)
if self.mode.is_contact_selector() && self.id == *id =>
{
if let Some(results) = results.downcast_ref::<Vec<Card>>() {
let account = &mut context.accounts[self.coordinates.0];
{
for card in results.iter() {
account.address_book.add_card(card.clone());
}
}
}
self.mode = ViewMode::Normal;
}
UIEvent::Input(Key::Esc) | UIEvent::Input(Key::Alt(''))
if self.mode.is_contact_selector() =>
{

View File

@ -1814,8 +1814,8 @@ enum SelectorCursor {
/// options. After passing input events to this component, check Selector::is_done to see if the
/// user has finalised their choices. Collect the choices by consuming the Selector with
/// Selector::collect()
#[derive(Clone)]
pub struct Selector<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Clone + Send> {
pub struct Selector<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send>
{
/// allow only one selection
single_only: bool,
entries: Vec<(T, bool)>,
@ -1830,13 +1830,17 @@ pub struct Selector<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Syn
id: ComponentId,
}
pub type UIConfirmationDialog =
Selector<bool, std::sync::Arc<dyn Fn(bool) -> Option<UIEvent> + 'static + Sync + Send>>;
pub type UIConfirmationDialog = Selector<
bool,
Option<Box<dyn FnOnce(ComponentId, bool) -> Option<UIEvent> + 'static + Sync + Send>>,
>;
pub type UIDialog<T> =
Selector<T, std::sync::Arc<dyn Fn(&[T]) -> Option<UIEvent> + 'static + Sync + Send>>;
pub type UIDialog<T> = Selector<
T,
Option<Box<dyn FnOnce(ComponentId, &[T]) -> Option<UIEvent> + 'static + Sync + Send>>,
>;
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Send> fmt::Debug
impl<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> fmt::Debug
for Selector<T, F>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -1844,7 +1848,7 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Send> fmt::Display
impl<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> fmt::Display
for Selector<T, F>
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -1852,7 +1856,7 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Send> PartialEq
impl<T: 'static + PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> PartialEq
for Selector<T, F>
{
fn eq(&self, other: &Selector<T, F>) -> bool {
@ -1860,9 +1864,7 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Send> Component
for Selector<T, F>
{
impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> Component for UIDialog<T> {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let (width, height) = self.content.size();
copy_area_with_break(grid, &self.content, area, ((0, 0), (width, height)));
@ -1879,6 +1881,9 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* User can only select one entry, so Enter key finalises the selection */
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Entry(c)) if !self.single_only => {
@ -1911,6 +1916,9 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => {
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Esc), _) => {
@ -1918,6 +1926,9 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
e.1 = false;
}
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => {
@ -1925,6 +1936,9 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
e.1 = false;
}
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Up), SelectorCursor::Entry(c)) if c > 0 => {
@ -2138,7 +2152,295 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Send> Selector<T, F> {
impl Component for UIConfirmationDialog {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let (width, height) = self.content.size();
copy_area_with_break(grid, &self.content, area, ((0, 0), (width, height)));
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
let (width, height) = self.content.size();
let shortcuts = self.get_shortcuts(context);
let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() {
highlighted_attrs.attrs |= Attr::Reverse;
}
match (event, self.cursor) {
(UIEvent::Input(Key::Char('\n')), _) if self.single_only => {
/* User can only select one entry, so Enter key finalises the selection */
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Entry(c)) if !self.single_only => {
/* User can select multiple entries, so Enter key toggles the entry under the
* cursor */
self.entries[c].1 = !self.entries[c].1;
if self.entries[c].1 {
write_string_to_grid(
"x",
&mut self.content,
Color::Default,
Color::Default,
Attr::Default,
((3, c + 2), (width - 2, c + 2)),
None,
);
} else {
write_string_to_grid(
" ",
&mut self.content,
Color::Default,
Color::Default,
Attr::Default,
((3, c + 2), (width - 2, c + 2)),
None,
);
}
self.dirty = true;
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => {
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Esc), _) => {
for e in self.entries.iter_mut() {
e.1 = false;
}
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Char('\n')), SelectorCursor::Cancel) if !self.single_only => {
for e in self.entries.iter_mut() {
e.1 = false;
}
self.done = true;
if let Some(event) = self.done() {
context.replies.push_back(event);
}
return true;
}
(UIEvent::Input(Key::Up), SelectorCursor::Entry(c)) if c > 0 => {
if self.single_only {
// Redraw selection
for c in self.content.row_iter(2..(width - 2), c + 2) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(2..(width - 2), c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false;
self.entries[c - 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(2..4, c + 2) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(2..4, c + 1) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
}
self.cursor = SelectorCursor::Entry(c - 1);
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Ok)
| (UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts["general"]["scroll_up"]) =>
{
for c in self.content.row_iter(
((width - "OK Cancel".len()) / 2)..(width - 1),
height - 3,
) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
let c = self.entries.len().saturating_sub(1);
self.cursor = SelectorCursor::Entry(c);
let mut highlighted_attrs =
crate::conf::value(context, "widgets.options.highlighted");
if !context.settings.terminal.use_color() {
highlighted_attrs.attrs |= Attr::Reverse;
}
for c in self.content.row_iter(2..4, c + 2) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
if c < self.entries.len().saturating_sub(1)
&& shortcut!(key == shortcuts["general"]["scroll_down"]) =>
{
if self.single_only {
// Redraw selection
for c in self.content.row_iter(2..(width - 2), c + 2) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(2..(width - 2), c + 3) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.entries[c].1 = false;
self.entries[c + 1].1 = true;
} else {
// Redraw cursor
for c in self.content.row_iter(2..4, c + 2) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(2..4, c + 3) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
}
self.cursor = SelectorCursor::Entry(c + 1);
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Entry(c))
if !self.single_only && shortcut!(key == shortcuts["general"]["scroll_down"]) =>
{
self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(2..4, c + 2) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(
((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1),
height - 3,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Ok)
if shortcut!(key == shortcuts["general"]["scroll_right"]) =>
{
self.cursor = SelectorCursor::Cancel;
for c in self.content.row_iter(
((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1),
height - 3,
) {
self.content[c]
.set_fg(Color::Default)
.set_bg(Color::Default)
.set_attrs(Attr::Default);
}
for c in self.content.row_iter(
((width - "OK Cancel".len()) / 2 + 6)
..((width - "OK Cancel".len()) / 2 + 11),
height - 3,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), SelectorCursor::Cancel)
if shortcut!(key == shortcuts["general"]["scroll_left"]) =>
{
self.cursor = SelectorCursor::Ok;
for c in self.content.row_iter(
((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1),
height - 3,
) {
self.content[c]
.set_fg(highlighted_attrs.fg)
.set_bg(highlighted_attrs.bg)
.set_attrs(highlighted_attrs.attrs);
}
change_colors(
&mut self.content,
(
((width - "OK Cancel".len()) / 2 + 6, height - 3),
((width - "OK Cancel".len()) / 2 + 11, height - 3),
),
Color::Default,
Color::Default,
);
self.dirty = true;
return true;
}
(UIEvent::Input(ref key), _)
if shortcut!(key == shortcuts["general"]["scroll_left"])
|| shortcut!(key == shortcuts["general"]["scroll_right"])
|| shortcut!(key == shortcuts["general"]["scroll_up"])
|| shortcut!(key == shortcuts["general"]["scroll_down"]) =>
{
return true
}
_ => {}
}
false
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = ShortcutMaps::default();
map.insert("general", context.settings.shortcuts.general.key_values());
map
}
fn is_dirty(&self) -> bool {
self.dirty
}
fn set_dirty(&mut self, value: bool) {
self.dirty = value;
}
fn id(&self) -> ComponentId {
self.id
}
fn set_id(&mut self, id: ComponentId) {
self.id = id;
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> Selector<T, F> {
pub fn new(
title: &str,
entries: Vec<(T, String)>,
@ -2349,16 +2651,17 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Clone + Sync + Sen
}
}
impl<T: PartialEq + Debug + Clone + Sync + Send> UIDialog<T> {
pub fn done(self) -> Option<UIEvent> {
impl<T: 'static + PartialEq + Debug + Clone + Sync + Send> UIDialog<T> {
fn done(&mut self) -> Option<UIEvent> {
let Self {
done,
done_fn,
entries,
ref mut done_fn,
ref mut entries,
ref id,
..
} = self;
if done {
(done_fn)(
done_fn.take().and_then(|done_fn| {
debug!(done_fn(
*id,
entries
.iter()
.filter(|v| v.1)
@ -2366,23 +2669,22 @@ impl<T: PartialEq + Debug + Clone + Sync + Send> UIDialog<T> {
.cloned()
.collect::<Vec<_>>()
.as_slice(),
)
} else {
None
}
))
})
}
}
impl UIConfirmationDialog {
pub fn done(self) -> Option<UIEvent> {
fn done(&mut self) -> Option<UIEvent> {
let Self {
done,
done_fn,
entries,
ref mut done_fn,
ref mut entries,
ref id,
..
} = self;
if done {
(done_fn)(
done_fn.take().and_then(|done_fn| {
done_fn(
*id,
entries
.iter()
.filter(|v| v.1)
@ -2390,9 +2692,7 @@ impl UIConfirmationDialog {
.cloned()
.any(core::convert::identity),
)
} else {
None
}
})
}
}

View File

@ -588,7 +588,7 @@ impl Component for FormWidget {
#[derive(Debug, Default)]
pub struct ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send + Sync,
T: 'static + std::fmt::Debug + Default + Send + Sync,
{
buttons: FnvHashMap<String, T>,
layout: Vec<String>,
@ -603,7 +603,7 @@ where
impl<T> fmt::Display for ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send + Sync,
T: 'static + std::fmt::Debug + Default + Send + Sync,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("", f)
@ -612,7 +612,7 @@ where
impl<T> ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send + Sync,
T: 'static + std::fmt::Debug + Default + Send + Sync,
{
pub fn new(init_val: (String, T)) -> ButtonWidget<T> {
ButtonWidget {
@ -642,7 +642,7 @@ where
impl<T> Component for ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send + Sync,
T: 'static + std::fmt::Debug + Default + Send + Sync,
{
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {

View File

@ -190,11 +190,11 @@ pub struct State {
child: Option<ForkType>,
draw_horizontal_segment_fn: fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (),
pub mode: UIMode,
overlay: Vec<Box<dyn Component>>,
components: Vec<Box<dyn Component>>,
pub context: Context,
timer: thread::JoinHandle<()>,
ui_dialogs: Vec<Box<dyn Component>>,
display_messages: SmallVec<[DisplayMessage; 8]>,
display_messages_expiration_start: Option<UnixTimestamp>,
display_messages_active: bool,
@ -248,28 +248,34 @@ impl State {
let rows = termsize.1 as usize;
let work_controller = WorkController::new(sender.clone());
let mut accounts: Vec<Account> = settings
.accounts
.iter()
.enumerate()
.map(|(index, (n, a_s))| {
let sender = sender.clone();
Account::new(
index,
n.to_string(),
a_s.clone(),
&backends,
work_controller.get_context(),
sender.clone(),
NotifyFn::new(Box::new(move |f: FolderHash| {
sender
.send(ThreadEvent::UIEvent(UIEvent::WorkerProgress(f)))
.unwrap();
})),
)
})
.collect::<Result<Vec<Account>>>()?;
accounts.sort_by(|a, b| a.name().cmp(&b.name()));
let accounts: Vec<Account> = {
let mut file_accs = settings
.accounts
.iter()
.collect::<Vec<(&String, &AccountConf)>>();
file_accs.sort_by(|a, b| a.0.cmp(&b.0));
file_accs
.into_iter()
.enumerate()
.map(|(index, (n, a_s))| {
let sender = sender.clone();
Account::new(
index,
n.to_string(),
a_s.clone(),
&backends,
work_controller.get_context(),
sender.clone(),
NotifyFn::new(Box::new(move |f: FolderHash| {
sender
.send(ThreadEvent::UIEvent(UIEvent::WorkerProgress(f)))
.unwrap();
})),
)
})
.collect::<Result<Vec<Account>>>()?
};
let timer = {
let sender = sender.clone();
@ -295,7 +301,7 @@ impl State {
child: None,
mode: UIMode::Normal,
components: Vec::with_capacity(8),
ui_dialogs: Vec::new(),
overlay: Vec::new(),
timer,
draw_rate_limit: RateLimit::new(1, 3),
draw_horizontal_segment_fn: if settings.terminal.use_color() {
@ -494,33 +500,6 @@ impl State {
));
}
}
if !self.ui_dialogs.is_empty() {
let area = center_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
),
(
if self.cols / 3 > 30 {
self.cols / 3
} else {
self.cols
},
if self.rows / 5 > 10 {
self.rows / 5
} else {
self.rows
},
),
);
areas.push(area);
let area = create_box(&mut self.overlay_grid, area);
self.ui_dialogs.get_mut(0).unwrap().draw(
&mut self.overlay_grid,
area,
&mut self.context,
);
}
/* Sort by x_start, ie upper_left corner's x coordinate */
areas.sort_by(|a, b| (a.0).0.partial_cmp(&(b.0).0).unwrap());
@ -663,6 +642,40 @@ impl State {
}
}
}
if !self.overlay.is_empty() {
let area = center_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
),
(
if self.cols / 3 > 30 {
self.cols / 3
} else {
self.cols
},
if self.rows / 5 > 10 {
self.rows / 5
} else {
self.rows
},
),
);
copy_area(&mut self.overlay_grid, &mut self.grid, area, area);
self.overlay
.get_mut(0)
.unwrap()
.draw(&mut self.overlay_grid, area, &mut self.context);
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
(self.draw_horizontal_segment_fn)(
&mut self.overlay_grid,
self.stdout.as_mut().unwrap(),
get_x(upper_left!(area)),
get_x(bottom_right!(area)),
y,
);
}
}
self.flush();
}
@ -858,21 +871,16 @@ impl State {
UIEvent::Command(cmd) => {
if let Ok(action) = parse_command(&cmd.as_bytes()).to_full_result() {
if action.needs_confirmation() {
let action = std::sync::Arc::new(action);
self.ui_dialogs.push(Box::new(UIConfirmationDialog::new(
self.overlay.push(Box::new(UIConfirmationDialog::new(
"You sure?",
vec![(true, "yes".to_string()), (false, "no".to_string())],
true,
std::sync::Arc::new(move |result: bool| {
if result {
Some(UIEvent::FinishedUIDialog(
ComponentId::nil(),
Box::new(action.clone()),
))
} else {
None
}
}),
Some(Box::new(move |id: ComponentId, result: bool| {
Some(UIEvent::FinishedUIDialog(
id,
Box::new(if result { Some(action) } else { None }),
))
})),
&mut self.context,
)));
} else {
@ -950,22 +958,34 @@ impl State {
self.display_messages_pos = self.display_messages.len() - 1;
}
UIEvent::FinishedUIDialog(ref id, ref mut results)
if self.ui_dialogs.get(0).map(|c| c.id()) == Some(*id) =>
if self.overlay.iter().any(|c| c.id() == *id) =>
{
if let Some(ref mut action) = results.downcast_mut::<Action>() {
if let Some(Some(ref mut action)) = results.downcast_mut::<Option<Action>>() {
self.exec_command(std::mem::replace(action, Action::ToggleThreadSnooze));
let pos = self.overlay.iter().position(|c| c.id() == *id).unwrap();
self.overlay.remove(pos);
return;
}
}
UIEvent::GlobalUIDialog(dialog) => {
self.ui_dialogs.push(dialog);
self.overlay.push(dialog);
return;
}
_ => {}
}
let Self {
ref mut components,
ref mut context,
ref mut overlay,
..
} = self;
/* inform each component */
for i in 0..self.components.len() {
self.components[i].process_event(&mut event, &mut self.context);
for c in overlay.iter_mut().chain(components.iter_mut()) {
if c.process_event(&mut event, context) {
break;
}
}
if !self.context.replies.is_empty() {