Make Selector widget accept FnOnce
parent
499fd59c6e
commit
c4c11e4abc
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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() =>
|
||||
{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
156
src/state.rs
156
src/state.rs
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue