diff --git a/src/components/utilities.rs b/src/components/utilities.rs index ac724f40..d604d8de 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -33,6 +33,9 @@ pub use self::widgets::*; mod layouts; pub use self::layouts::*; +mod dialogs; +pub use self::dialogs::*; + use crate::jobs::JobId; use std::collections::HashSet; @@ -1326,912 +1329,6 @@ impl Component for Tabbed { } } -#[derive(Debug, Copy, PartialEq, Clone)] -enum SelectorCursor { - /// Cursor is at an entry - Entry(usize), - /// Cursor is located on the Ok button - Ok, - /// Cursor is located on the Cancel button - Cancel, -} - -/// Shows a little window with options for user to select. -/// -/// Instantiate with Selector::new(). Set single_only to true if user should only choose one of the -/// 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() -pub struct Selector -{ - /// allow only one selection - single_only: bool, - entries: Vec<(T, bool)>, - pub content: CellBuffer, - - cursor: SelectorCursor, - - /// If true, user has finished their selection - done: bool, - done_fn: F, - dirty: bool, - id: ComponentId, -} - -pub type UIConfirmationDialog = Selector< - bool, - Option Option + 'static + Sync + Send>>, ->; - -pub type UIDialog = Selector< - T, - Option Option + 'static + Sync + Send>>, ->; - -impl fmt::Debug - for Selector -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt("Selector", f) - } -} - -impl fmt::Display - for Selector -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt("Selector", f) - } -} - -impl PartialEq - for Selector -{ - fn eq(&self, other: &Selector) -> bool { - self.entries == other.entries - } -} - -impl Component for UIDialog { - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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); - context.replies.push_back(UIEvent::ComponentKill(self.id)); - } - 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 Selector { - pub fn new( - title: &str, - entries: Vec<(T, String)>, - single_only: bool, - done_fn: F, - context: &Context, - ) -> Selector { - let width = std::cmp::max( - "OK Cancel".len(), - std::cmp::max( - entries - .iter() - .max_by_key(|e| e.1.len()) - .map(|v| v.1.len()) - .unwrap_or(0), - title.len(), - ), - ) + 7; - let height = entries.len() - + 4 - + if single_only { - 0 - } else { - /* Extra room for buttons Okay/Cancel */ - 3 - }; - let mut content = - CellBuffer::new_with_context(width, height, Cell::with_char(' '), context); - let ascii_drawing = context.settings.terminal.ascii_drawing; - write_string_to_grid( - if ascii_drawing { "+-" } else { "┏━" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((0, 0), (width - 1, 0)), - None, - ); - let (x, _) = write_string_to_grid( - title, - &mut content, - Color::Default, - Color::Default, - Attr::DEFAULT, - ((2, 0), (width - 1, 0)), - None, - ); - for i in 1..(width - title.len() - 1) { - write_string_to_grid( - if ascii_drawing { "-" } else { "━" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((x + i, 0), (width - 1, 0)), - None, - ); - } - write_string_to_grid( - if ascii_drawing { "+" } else { "┓" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((width - 1, 0), (width - 1, 0)), - None, - ); - write_string_to_grid( - if ascii_drawing { "+" } else { "┗" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((0, height - 1), (width - 1, height - 1)), - None, - ); - write_string_to_grid( - &if ascii_drawing { - "-".repeat(width - 2) - } else { - "━".repeat(width - 2) - }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((1, height - 1), (width - 2, height - 1)), - None, - ); - write_string_to_grid( - if ascii_drawing { "+" } else { "┛" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((width - 1, height - 1), (width - 1, height - 1)), - None, - ); - for i in 1..height - 1 { - write_string_to_grid( - if ascii_drawing { "|" } else { "┃" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((0, i), (width - 1, i)), - None, - ); - write_string_to_grid( - if ascii_drawing { "|" } else { "┃" }, - &mut content, - Color::Byte(8), - Color::Default, - Attr::DEFAULT, - ((width - 1, i), (width - 1, i)), - None, - ); - } - let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); - if !context.settings.terminal.use_color() { - highlighted_attrs.attrs |= Attr::REVERSE; - } - if single_only { - for (i, e) in entries.iter().enumerate() { - write_string_to_grid( - &e.1, - &mut content, - Color::Default, - if i == 0 { - highlighted_attrs.bg - } else { - Color::Default - }, - if i == 0 { - highlighted_attrs.attrs - } else { - Attr::DEFAULT - }, - ((2, i + 2), (width - 1, i + 2)), - None, - ); - } - } else { - for (i, e) in entries.iter().enumerate() { - write_string_to_grid( - &format!("[ ] {}", e.1), - &mut content, - Color::Default, - Color::Default, - Attr::DEFAULT, - ((2, i + 2), (width - 1, i + 2)), - None, - ); - if i == 0 { - content[(2, i + 2)] - .set_bg(highlighted_attrs.bg) - .set_attrs(highlighted_attrs.attrs); - content[(3, i + 2)] - .set_bg(highlighted_attrs.bg) - .set_attrs(highlighted_attrs.attrs); - content[(4, i + 2)] - .set_bg(highlighted_attrs.bg) - .set_attrs(highlighted_attrs.attrs); - } - } - write_string_to_grid( - "OK Cancel", - &mut content, - Color::Default, - Color::Default, - Attr::BOLD, - ( - ((width - "OK Cancel".len()) / 2, height - 3), - (width - 1, height - 3), - ), - None, - ); - } - let mut identifiers: Vec<(T, bool)> = - entries.into_iter().map(|(id, _)| (id, false)).collect(); - if single_only { - /* set default option */ - identifiers[0].1 = true; - } - - Selector { - single_only, - entries: identifiers, - content, - cursor: SelectorCursor::Entry(0), - done: false, - done_fn, - dirty: true, - id: ComponentId::new_v4(), - } - } - - pub fn is_done(&self) -> bool { - self.done - } - - pub fn collect(self) -> Vec { - self.entries - .into_iter() - .filter(|v| v.1) - .map(|(id, _)| id) - .collect() - } -} - -impl UIDialog { - fn done(&mut self) -> Option { - let Self { - ref mut done_fn, - ref mut entries, - ref id, - .. - } = self; - done_fn.take().and_then(|done_fn| { - debug!(done_fn( - *id, - entries - .iter() - .filter(|v| v.1) - .map(|(id, _)| id) - .cloned() - .collect::>() - .as_slice(), - )) - }) - } -} - -impl UIConfirmationDialog { - fn done(&mut self) -> Option { - let Self { - ref mut done_fn, - ref mut entries, - ref id, - .. - } = self; - done_fn.take().and_then(|done_fn| { - done_fn( - *id, - entries - .iter() - .filter(|v| v.1) - .map(|(id, _)| id) - .cloned() - .any(core::convert::identity), - ) - }) - } -} - #[derive(Debug, Clone, PartialEq)] pub struct RawBuffer { pub buf: CellBuffer, diff --git a/src/components/utilities/dialogs.rs b/src/components/utilities/dialogs.rs new file mode 100644 index 00000000..cb704dc4 --- /dev/null +++ b/src/components/utilities/dialogs.rs @@ -0,0 +1,928 @@ +/* + * meli + * + * Copyright 2020 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +#[derive(Debug, Copy, PartialEq, Clone)] +enum SelectorCursor { + /// Cursor is at an entry + Entry(usize), + /// Cursor is located on the Ok button + Ok, + /// Cursor is located on the Cancel button + Cancel, +} + +/// Shows a little window with options for user to select. +/// +/// Instantiate with Selector::new(). Set single_only to true if user should only choose one of the +/// 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() +pub struct Selector +{ + /// allow only one selection + single_only: bool, + entries: Vec<(T, bool)>, + pub content: CellBuffer, + + cursor: SelectorCursor, + + /// If true, user has finished their selection + done: bool, + done_fn: F, + dirty: bool, + id: ComponentId, +} + +pub type UIConfirmationDialog = Selector< + bool, + Option Option + 'static + Sync + Send>>, +>; + +pub type UIDialog = Selector< + T, + Option Option + 'static + Sync + Send>>, +>; + +impl fmt::Debug + for Selector +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("Selector", f) + } +} + +impl fmt::Display + for Selector +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("Selector", f) + } +} + +impl PartialEq + for Selector +{ + fn eq(&self, other: &Selector) -> bool { + self.entries == other.entries + } +} + +impl Component for UIDialog { + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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); + context.replies.push_back(UIEvent::ComponentKill(self.id)); + } + 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 Selector { + pub fn new( + title: &str, + entries: Vec<(T, String)>, + single_only: bool, + done_fn: F, + context: &Context, + ) -> Selector { + let width = std::cmp::max( + "OK Cancel".len(), + std::cmp::max( + entries + .iter() + .max_by_key(|e| e.1.len()) + .map(|v| v.1.len()) + .unwrap_or(0), + title.len(), + ), + ) + 7; + let height = entries.len() + + 4 + + if single_only { + 0 + } else { + /* Extra room for buttons Okay/Cancel */ + 3 + }; + let mut content = + CellBuffer::new_with_context(width, height, Cell::with_char(' '), context); + let ascii_drawing = context.settings.terminal.ascii_drawing; + write_string_to_grid( + if ascii_drawing { "+-" } else { "┏━" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((0, 0), (width - 1, 0)), + None, + ); + let (x, _) = write_string_to_grid( + title, + &mut content, + Color::Default, + Color::Default, + Attr::DEFAULT, + ((2, 0), (width - 1, 0)), + None, + ); + for i in 1..(width - title.len() - 1) { + write_string_to_grid( + if ascii_drawing { "-" } else { "━" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((x + i, 0), (width - 1, 0)), + None, + ); + } + write_string_to_grid( + if ascii_drawing { "+" } else { "┓" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((width - 1, 0), (width - 1, 0)), + None, + ); + write_string_to_grid( + if ascii_drawing { "+" } else { "┗" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((0, height - 1), (width - 1, height - 1)), + None, + ); + write_string_to_grid( + &if ascii_drawing { + "-".repeat(width - 2) + } else { + "━".repeat(width - 2) + }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((1, height - 1), (width - 2, height - 1)), + None, + ); + write_string_to_grid( + if ascii_drawing { "+" } else { "┛" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((width - 1, height - 1), (width - 1, height - 1)), + None, + ); + for i in 1..height - 1 { + write_string_to_grid( + if ascii_drawing { "|" } else { "┃" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((0, i), (width - 1, i)), + None, + ); + write_string_to_grid( + if ascii_drawing { "|" } else { "┃" }, + &mut content, + Color::Byte(8), + Color::Default, + Attr::DEFAULT, + ((width - 1, i), (width - 1, i)), + None, + ); + } + let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); + if !context.settings.terminal.use_color() { + highlighted_attrs.attrs |= Attr::REVERSE; + } + if single_only { + for (i, e) in entries.iter().enumerate() { + write_string_to_grid( + &e.1, + &mut content, + Color::Default, + if i == 0 { + highlighted_attrs.bg + } else { + Color::Default + }, + if i == 0 { + highlighted_attrs.attrs + } else { + Attr::DEFAULT + }, + ((2, i + 2), (width - 1, i + 2)), + None, + ); + } + } else { + for (i, e) in entries.iter().enumerate() { + write_string_to_grid( + &format!("[ ] {}", e.1), + &mut content, + Color::Default, + Color::Default, + Attr::DEFAULT, + ((2, i + 2), (width - 1, i + 2)), + None, + ); + if i == 0 { + content[(2, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + content[(3, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + content[(4, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } + } + write_string_to_grid( + "OK Cancel", + &mut content, + Color::Default, + Color::Default, + Attr::BOLD, + ( + ((width - "OK Cancel".len()) / 2, height - 3), + (width - 1, height - 3), + ), + None, + ); + } + let mut identifiers: Vec<(T, bool)> = + entries.into_iter().map(|(id, _)| (id, false)).collect(); + if single_only { + /* set default option */ + identifiers[0].1 = true; + } + + Selector { + single_only, + entries: identifiers, + content, + cursor: SelectorCursor::Entry(0), + done: false, + done_fn, + dirty: true, + id: ComponentId::new_v4(), + } + } + + pub fn is_done(&self) -> bool { + self.done + } + + pub fn collect(self) -> Vec { + self.entries + .into_iter() + .filter(|v| v.1) + .map(|(id, _)| id) + .collect() + } +} + +impl UIDialog { + fn done(&mut self) -> Option { + let Self { + ref mut done_fn, + ref mut entries, + ref id, + .. + } = self; + done_fn.take().and_then(|done_fn| { + debug!(done_fn( + *id, + entries + .iter() + .filter(|v| v.1) + .map(|(id, _)| id) + .cloned() + .collect::>() + .as_slice(), + )) + }) + } +} + +impl UIConfirmationDialog { + fn done(&mut self) -> Option { + let Self { + ref mut done_fn, + ref mut entries, + ref id, + .. + } = self; + done_fn.take().and_then(|done_fn| { + done_fn( + *id, + entries + .iter() + .filter(|v| v.1) + .map(|(id, _)| id) + .cloned() + .any(core::convert::identity), + ) + }) + } +}