From 4ee5447cf9d07fa911589632fd13016445cf49ca Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 25 Feb 2019 11:11:56 +0200 Subject: [PATCH] Add FormWidget/ButtonWidget and use them in Contacts --- src/bin.rs | 19 +- ui/src/components.rs | 12 +- ui/src/components/contacts.rs | 108 ++++---- ui/src/components/contacts/contact_list.rs | 33 ++- ui/src/components/mail.rs | 2 +- ui/src/components/mail/accounts/contacts.rs | 2 +- ui/src/components/mail/compose.rs | 1 - ui/src/components/mail/listing/compact.rs | 2 +- ui/src/components/mail/listing/thread.rs | 4 +- ui/src/components/mail/view.rs | 29 +- ui/src/components/utilities.rs | 8 +- ui/src/components/utilities/widgets.rs | 277 ++++++++++++++++++++ ui/src/conf/accounts.rs | 2 +- ui/src/state.rs | 7 +- ui/src/types.rs | 3 + ui/src/types/cells.rs | 16 +- ui/src/types/keys.rs | 2 +- ui/src/types/position.rs | 4 + 18 files changed, 426 insertions(+), 105 deletions(-) create mode 100644 ui/src/components/utilities/widgets.rs diff --git a/src/bin.rs b/src/bin.rs index c6fe391a..fb0071d1 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -66,7 +66,7 @@ fn main() { let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts))); let listing = listing::Listing::from(IndexStyle::Compact); let b = Entity::from(Box::new(listing)); - let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context)), Box::new(ContactManager::default())])); + let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context)), Box::new(ContactList::default())])); let window = Entity::from(tabs); let status_bar = Entity::from(Box::new(StatusBar::new(window))); @@ -123,6 +123,19 @@ fn main() { }, } }, + UIMode::Insert => { + match k { + Key::Char('\n') | Key::Esc => { + state.mode = UIMode::Normal; + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::ChangeMode(UIMode::Normal)}); + state.redraw(); + }, + k => { + state.rcv_event(UIEvent { id: 0, event_type: UIEventType::InsertInput(k)}); + state.redraw(); + }, + } + } UIMode::Execute => { match k { Key::Char('\n') | Key::Esc => { @@ -147,7 +160,9 @@ fn main() { }, ThreadEvent::UIEvent(UIEventType::ChangeMode(f)) => { state.mode = f; - break 'inner; // `goto` 'reap loop, and wait on child. + if f == UIMode::Fork { + break 'inner; // `goto` 'reap loop, and wait on child. + } } ThreadEvent::UIEvent(UIEventType::StartupCheck) => { let mut render_flag = false; diff --git a/ui/src/components.rs b/ui/src/components.rs index a3460e62..1ede7049 100644 --- a/ui/src/components.rs +++ b/ui/src/components.rs @@ -91,7 +91,7 @@ impl From> for Entity { let id = Uuid::new_v4(); kind.set_id(id); Entity { - id: id, + id, component: kind, } } @@ -105,7 +105,7 @@ where let id = Uuid::new_v4(); kind.set_id(id); Entity { - id: id, + id, component: kind, } } @@ -151,8 +151,8 @@ pub trait Component: Display + Debug { true } fn set_dirty(&mut self); - fn kill(&mut self, id: EntityId) {} - fn set_id(&mut self, id: EntityId) {} + fn kill(&mut self, _id: EntityId) {} + fn set_id(&mut self, _id: EntityId) {} } /* @@ -218,7 +218,7 @@ fn ch_to_bin(ch: char) -> Option { } } -#[allow(never_loop)] +#[allow(clippy::never_loop)] fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 { let (x, y) = idx; let mut bin_set = 0b1010; @@ -309,7 +309,7 @@ fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 { bin_set } -#[allow(never_loop)] +#[allow(clippy::never_loop)] fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 { let (x, y) = idx; let mut bin_set = 0b0101; diff --git a/ui/src/components/contacts.rs b/ui/src/components/contacts.rs index 4b50ae56..9d18d7c6 100644 --- a/ui/src/components/contacts.rs +++ b/ui/src/components/contacts.rs @@ -25,28 +25,6 @@ mod contact_list; pub use self::contact_list::*; -macro_rules! write_field { - ($title:expr, $value:expr, $target_grid:expr, $fg_color:expr, $bg_color:expr, $width:expr, $y:expr) => {{ - let (x, y) = write_string_to_grid( - $title, - &mut $target_grid, - $fg_color, - $bg_color, - ((1, $y + 2), ($width - 1, $y + 2)), - false, - ); - write_string_to_grid( - &$value, - &mut $target_grid, - Color::Default, - Color::Default, - ((x, y), ($width - 1, y)), - false, - ); - y - }} -} - #[derive(Debug)] enum ViewMode { ReadOnly, @@ -60,6 +38,7 @@ pub struct ContactManager { id: Uuid, pub card: Card, mode: ViewMode, + form: FormWidget, content: CellBuffer, dirty: bool, initialized: bool, @@ -71,6 +50,7 @@ impl Default for ContactManager { id: Uuid::nil(), card: Card::new(), mode: ViewMode::Read, + form: FormWidget::default(), content: CellBuffer::new(200, 100, Cell::with_char(' ')), dirty: true, initialized: false, @@ -112,27 +92,16 @@ impl ContactManager { ((x, 0), (width, 0)), false, ); - for x in 0..width { - set_and_join_box(&mut self.content, (x, 2), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 4), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 6), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 8), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 10), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 12), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 14), HORZ_BOUNDARY); - set_and_join_box(&mut self.content, (x, 16), HORZ_BOUNDARY); - } - for y in 0..height { - set_and_join_box(&mut self.content, (width - 1, y), VERT_BOUNDARY); - } - let mut y = write_field!("First Name: ", self.card.firstname(), self.content, Color::Byte(250), Color::Default, width, 1); - y = write_field!("Last Name: ", self.card.lastname(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("Additional Name: ", self.card.additionalname(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("Name Prefix: ", self.card.name_prefix(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("Name Suffix: ", self.card.name_suffix(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("E-mail: ", self.card.email(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("url: ", self.card.url(), self.content, Color::Byte(250), Color::Default, width, y); - y = write_field!("key: ", self.card.key(), self.content, Color::Byte(250), Color::Default, width, y); + self.form = FormWidget::new("Save".into()); + self.form.add_button(("Cancel".into(), false)); + self.form.push(("First Name".into(), self.card.firstname().to_string())); + self.form.push(("Last Name".into(), self.card.lastname().to_string())); + self.form.push(("Additional Name".into(), self.card.additionalname().to_string())); + self.form.push(("Name Prefix".into(), self.card.name_prefix().to_string())); + self.form.push(("Name Suffix".into(), self.card.name_suffix().to_string())); + self.form.push(("E-mail".into(), self.card.email().to_string())); + self.form.push(("url".into(), self.card.url().to_string())); + self.form.push(("key".into(), self.card.key().to_string())); } } @@ -144,31 +113,62 @@ impl Component for ContactManager { } clear_area(grid, area); let (width, height) = self.content.size(); - copy_area(grid, &self.content, area, ((0, 0), (width - 1, height -1))); + copy_area(grid, &self.content, area, ((0, 0), (width - 1, 0))); + + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + self.form.draw(grid, (set_y(upper_left, get_y(upper_left) + 1), bottom_right), context); context.dirty_areas.push_back(area); } fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { - match event.event_type { - UIEventType::Input(Key::Char('\n')) => { - context.replies.push_back(UIEvent { - id: 0, - event_type: UIEventType::EntityKill(self.id), - }); - return true; - }, - _ => {}, + if self.form.process_event(event, context) { + match self.form.buttons_result() { + None => {}, + Some(true) => { + eprintln!("fields: {:?}", std::mem::replace(&mut self.form, FormWidget::default()).collect()); + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::StatusEvent(StatusEvent::DisplayMessage("Saved.".into())), + }); + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::EntityKill(self.id), + }); + }, + Some(false) => { + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::EntityKill(self.id), + }); + + }, + } + return true; } + /* + match event.event_type { + UIEventType::Input(Key::Char('\n')) => { + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::EntityKill(self.id), + }); + return true; + }, + _ => {}, + } + */ false } fn is_dirty(&self) -> bool { - self.dirty + self.dirty | self.form.is_dirty() } fn set_dirty(&mut self) { self.dirty = true; self.initialized = false; + self.form.set_dirty(); } fn set_id(&mut self, uuid: Uuid) { diff --git a/ui/src/components/contacts/contact_list.rs b/ui/src/components/contacts/contact_list.rs index f1717d37..614eed3d 100644 --- a/ui/src/components/contacts/contact_list.rs +++ b/ui/src/components/contacts/contact_list.rs @@ -19,7 +19,6 @@ pub struct ContactList { id_positions: Vec, mode: ViewMode, - initialized: bool, dirty: bool, view: Option, } @@ -47,7 +46,6 @@ impl ContactList { id_positions: Vec::new(), mode: ViewMode::List, content, - initialized: false, dirty: true, view: None, } @@ -57,7 +55,7 @@ impl ContactList { let account = &mut context.accounts[self.account_pos]; let book = &mut account.address_book; self.content.resize(MAX_COLS, book.len(), Cell::with_char(' ')); - eprintln!("{:?}", book); + eprintln!("initialize {:?}", book); self.id_positions.clear(); if self.id_positions.capacity() < book.len() { @@ -81,24 +79,33 @@ impl ContactList { impl Component for ContactList { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - if !self.initialized { - self.initialize(context); - self.initialized = true; - } - if let Some(mgr) = self.view.as_mut() { mgr.draw(grid, area, context); - self.dirty = false; return; } if self.dirty { + self.initialize(context); clear_area(grid, area); copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, self.content.size().1 - 1))); context.dirty_areas.push_back(area); self.dirty = false; } + + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + /* Reset previously highlighted line */ + let fg_color = Color::Default; + let bg_color = Color::Default; + change_colors(grid, (pos_inc(upper_left, (0, self.cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.cursor_pos)), fg_color, bg_color); + + /* Highlight current line */ + let bg_color = Color::Byte(246); + change_colors(grid, (pos_inc(upper_left, (0, self.new_cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos)), fg_color, bg_color); + self.cursor_pos = self.new_cursor_pos; } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { if let Some(ref mut v) = self.view { if v.process_event(event, context) { @@ -112,14 +119,10 @@ impl Component for ContactList { let card = book[&self.id_positions[self.cursor_pos]].clone(); let mut manager = ContactManager::default(); manager.card = card; - - - let entity = Entity::from(Box::new(manager)); self.mode = ViewMode::View(*entity.id()); self.view = Some(entity); - self.set_dirty(); return true; }, @@ -134,9 +137,11 @@ impl Component for ContactList { } false } + fn is_dirty(&self) -> bool { - self.dirty + self.dirty || self.view.as_ref().map(|v| v.is_dirty()).unwrap_or(false) } + fn set_dirty(&mut self) { if let Some(p) = self.view.as_mut() { p.set_dirty(); diff --git a/ui/src/components/mail.rs b/ui/src/components/mail.rs index e2d32bef..144683c2 100644 --- a/ui/src/components/mail.rs +++ b/ui/src/components/mail.rs @@ -133,7 +133,7 @@ impl AccountMenu { inc: &mut usize, index: usize, //account index context: &mut Context, - ) -> () { + ) { let len = s.len(); match context.accounts[index].status(root) { Ok(_) => {} diff --git a/ui/src/components/mail/accounts/contacts.rs b/ui/src/components/mail/accounts/contacts.rs index e914a095..ccd5d4be 100644 --- a/ui/src/components/mail/accounts/contacts.rs +++ b/ui/src/components/mail/accounts/contacts.rs @@ -45,7 +45,7 @@ impl Component for ContactsPanel { copy_area(grid, &self.content, area, ((0, 0), (width - 1, height - 1))); context.dirty_areas.push_back(area); } - fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) -> bool { false } fn is_dirty(&self) -> bool { diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index 854a90e4..c541a842 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -20,7 +20,6 @@ */ use super::*; -use std::dbg; use melib::Draft; use std::str::FromStr; diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index c21dda0e..3c5123e9 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -209,7 +209,7 @@ impl CompactListing { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - if mailbox.len() == 0 { + if mailbox.is_empty() { return; } let threads = &mailbox.collection.threads; diff --git a/ui/src/components/mail/listing/thread.rs b/ui/src/components/mail/listing/thread.rs index 9dcc457f..25fd52c9 100644 --- a/ui/src/components/mail/listing/thread.rs +++ b/ui/src/components/mail/listing/thread.rs @@ -224,7 +224,7 @@ impl ThreadListing { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - if mailbox.len() == 0 { + if mailbox.is_empty() { return; } if self.locations[idx] != 0 { @@ -255,7 +255,7 @@ impl ThreadListing { let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] .as_ref() .unwrap(); - if mailbox.len() == 0 || mailbox.len() <= idx { + if mailbox.is_empty() || mailbox.len() <= idx { return; } diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index a3a7c55c..0113aee2 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -391,22 +391,19 @@ impl Component for MailView { eprintln!("{:?}", new_card); - */ - match self.mode { - ViewMode::ContactSelector(_) => { - if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) { - for c in s.collect() { - let mut new_card: Card = Card::new(); - let email = String::from_utf8(c).unwrap(); - new_card.set_email(&email); - new_card.set_firstname(""); - context.accounts[self.coordinates.0].address_book.add_card(new_card); - } - //eprintln!("{:?}", s.collect()); +*/ + if let ViewMode::ContactSelector(_) = self.mode { + if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) { + for c in s.collect() { + let mut new_card: Card = Card::new(); + let email = String::from_utf8(c).unwrap(); + new_card.set_email(&email); + new_card.set_firstname(""); + context.accounts[self.coordinates.0].address_book.add_card(new_card); } - return true; - }, - _ => {}, + //eprintln!("{:?}", s.collect()); + } + return true; } let accounts = &context.accounts; @@ -604,7 +601,7 @@ impl Component for MailView { true } fn is_dirty(&self) -> bool { - self.dirty || true + self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) || self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false) } diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 5a7cd315..daa3c2f6 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -23,6 +23,10 @@ */ use super::*; +mod widgets; + +pub use self::widgets::*; + /// A horizontally split in half container. #[derive(Debug)] pub struct HSplit { @@ -212,7 +216,7 @@ impl fmt::Display for Pager { } impl Pager { - pub fn update_from_str(&mut self, text: &str) -> () { + pub fn update_from_str(&mut self, text: &str) { let lines: Vec<&str> = text.trim().split('\n').collect(); let height = lines.len() + 1; let width = lines.iter().map(|l| l.len()).max().unwrap_or(0); @@ -673,7 +677,7 @@ impl Progress { } } - pub fn add_work(&mut self, n: usize) -> () { + pub fn add_work(&mut self, n: usize) { if self.finished >= self.total_work { return; } diff --git a/ui/src/components/utilities/widgets.rs b/ui/src/components/utilities/widgets.rs new file mode 100644 index 00000000..c2d1556f --- /dev/null +++ b/ui/src/components/utilities/widgets.rs @@ -0,0 +1,277 @@ +use super::*; +use fnv::FnvHashMap; + +#[derive(Debug, PartialEq)] +enum FormFocus { + Fields, + Buttons, + TextInput, +} + +impl Default for FormFocus { + fn default() -> FormFocus { + FormFocus::Fields + } +} + +#[derive(Debug, Default)] +pub struct FormWidget { + fields: FnvHashMap, + cursors: Vec, + layout: Vec, + buttons: ButtonWidget, + + field_name_max_length: usize, + cursor: usize, + focus: FormFocus, + dirty: bool, +} + +impl fmt::Display for FormWidget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("", f) + } +} + +impl FormWidget { + pub fn new(action: String) -> FormWidget { + FormWidget { + buttons: ButtonWidget::new((action, true)), + focus: FormFocus::Fields, + ..Default::default() + } + } + + pub fn add_button(&mut self, val: (String, bool)) { + self.buttons.push(val); + } + + pub fn push(&mut self, value: (String, String)) { + self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len()); + self.layout.push(value.0.clone()); + self.fields.insert(value.0, value.1); + self.cursors.push(0); + } + + pub fn insert(&mut self, index: usize, value: (String, String)) { + self.layout.insert(index, value.0.clone()); + self.fields.insert(value.0, value.1); + self.cursors.insert(index, 0); + } + + pub fn collect(self) -> Option> { + if let Some(true) = self.buttons_result() { + Some(self.fields) + } else { + None + } + } + pub fn buttons_result(&self) -> Option { + self.buttons.result + } +} + +impl Component for FormWidget { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + for (i, k) in self.layout.iter().enumerate() { + let v = &self.fields[k]; + write_string_to_grid( + k.as_str(), + grid, + Color::Default, + Color::Default, + (pos_inc(upper_left, (1, i * 2)), set_y(bottom_right, i * 2 + get_y(upper_left))), + false, + ); + write_string_to_grid( + v.as_str(), + grid, + Color::Default, + Color::Default, + (pos_inc(upper_left, (self.field_name_max_length + 3, i * 2)), set_y(bottom_right, i * 2 + get_y(upper_left))), + false, + ); + if i == self.cursor { + if self.focus == FormFocus::Fields { + change_colors(grid, (pos_inc(upper_left, (0, i * 2)), set_y(bottom_right, i * 2 + get_y(upper_left))), Color::Default, Color::Byte(246)); + } + if self.focus == FormFocus::TextInput { + change_colors(grid, + (pos_inc(upper_left, (self.field_name_max_length + 3 + self.cursors[i], i * 2)), + (get_x(upper_left) + self.field_name_max_length + 3 + self.cursors[i], i * 2 + get_y(upper_left))), + Color::Default, Color::Byte(248)); + } + } + } + let length = self.layout.len(); + self.buttons.draw(grid, + (pos_inc(upper_left, (1, length * 2 + 3)), set_y(bottom_right, length * 2 + 3 + get_y(upper_left))), + context); + self.dirty = false; + context.dirty_areas.push_back(area); + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + if self.focus == FormFocus::Buttons { + if self.buttons.process_event(event, context) { + return true; + } + } + + match event.event_type { + UIEventType::Input(Key::Up) => { + self.cursor = self.cursor.saturating_sub(1); + }, + UIEventType::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => { + self.cursor += 1; + }, + UIEventType::Input(Key::Down) => { + self.focus = FormFocus::Buttons; + }, + UIEventType::Input(Key::Char('\n')) if self.focus == FormFocus::Fields => { + self.focus = FormFocus::TextInput; + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::ChangeMode(UIMode::Insert), + }); + }, + UIEventType::Input(Key::Up) if self.focus == FormFocus::Buttons => { + self.focus = FormFocus::Fields; + }, + UIEventType::InsertInput(Key::Right) if self.focus == FormFocus::TextInput => { + if self.cursors[self.cursor] < self.fields[&self.layout[self.cursor]].len().saturating_sub(1) { + self.cursors[self.cursor] += 1; + } + }, + UIEventType::InsertInput(Key::Left) if self.focus == FormFocus::TextInput => { + if self.cursors[self.cursor] == 0 { + self.focus = FormFocus::Fields; + context.replies.push_back(UIEvent { + id: 0, + event_type: UIEventType::ChangeMode(UIMode::Normal), + }); + } else { + self.cursors[self.cursor] = self.cursors[self.cursor] - 1; + } + }, + UIEventType::ChangeMode(UIMode::Normal) if self.focus == FormFocus::TextInput => { + self.focus = FormFocus::Fields; + }, + UIEventType::InsertInput(Key::Char(k)) if self.focus == FormFocus::TextInput => { + let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); + field.insert(self.cursors[self.cursor], k); + self.cursors[self.cursor] += 1; + }, + UIEventType::InsertInput(Key::Backspace) if self.focus == FormFocus::TextInput => { + let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); + match self.cursors[self.cursor] { + i if i == 0 => {}, + i if i == field.len() => { + field.pop(); + self.cursors[self.cursor] -= 1; + }, + _ => { + field.remove(self.cursors[self.cursor]); + self.cursors[self.cursor] -= 1; + } + } + }, + _ => { + return false; + } + } + self.set_dirty(); + true + } + fn is_dirty(&self) -> bool { + self.dirty + } + fn set_dirty(&mut self) { + self.dirty = true; + } +} + + +#[derive(Debug, Default)] +pub struct ButtonWidget where T: std::fmt::Debug + Default{ + buttons: FnvHashMap, + layout: Vec, + + result: Option, + cursor: usize, +} + +impl fmt::Display for ButtonWidget where T: std::fmt::Debug + Default { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Display::fmt("", f) + } +} + +impl ButtonWidget where T: std::fmt::Debug + Default { + pub fn new(init_val: (String, T)) -> ButtonWidget { + ButtonWidget { + layout: vec![init_val.0.clone()], + buttons: vec![init_val].into_iter().collect(), + result: None, + cursor: 0, + } + } + + pub fn push(&mut self, value: (String, T)) { + self.layout.push(value.0.clone()); + self.buttons.insert(value.0, value.1); + } + + pub fn is_resolved(&self) -> bool { + self.result.is_some() + } +} + + +impl Component for ButtonWidget where T: std::fmt::Debug + Default { + fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + + let mut len = 0; + for (i, k) in self.layout.iter().enumerate() { + let cur_len = k.len(); + let (x, y) = write_string_to_grid( + k.as_str(), + grid, + Color::Default, + if i == self.cursor { Color::Byte(246) } else { Color::Default }, + (pos_inc(upper_left, (len, 0)), pos_inc(upper_left, (cur_len + len, 0))), + false, + ); + len += cur_len + 3; + } + } + fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool { + match event.event_type { + UIEventType::Input(Key::Char('\n')) => { + self.result = Some(self.buttons.remove(&self.layout[self.cursor]).unwrap_or_default()); + return true; + }, + UIEventType::Input(Key::Left) => { + self.cursor = self.cursor.saturating_sub(1); + return true; + }, + UIEventType::Input(Key::Right) if self.cursor < self.layout.len().saturating_sub(1) => { + self.cursor += 1; + return true; + }, + _ => {} + } + + false + } + fn is_dirty(&self) -> bool { + true + } + fn set_dirty(&mut self) {} +} + + diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index da7ac043..0c82b437 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -190,7 +190,7 @@ impl Account { } None } - pub fn watch(&self, r: RefreshEventConsumer) -> () { + pub fn watch(&self, r: RefreshEventConsumer) { self.backend.watch(r).unwrap(); } /* This doesn't represent the number of correctly parsed mailboxes though */ diff --git a/ui/src/state.rs b/ui/src/state.rs index b9a6ef6f..73187339 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -473,7 +473,12 @@ impl State { self.flush(); } return; - } + }, + UIEventType::ChangeMode(m) => { + self.context + .sender + .send(ThreadEvent::UIEvent(UIEventType::ChangeMode(m))); + }, _ => {} } /* inform each entity */ diff --git a/ui/src/types.rs b/ui/src/types.rs index 19065b49..aaacec11 100644 --- a/ui/src/types.rs +++ b/ui/src/types.rs @@ -77,6 +77,7 @@ pub enum ForkType { pub enum UIEventType { Input(Key), ExInput(Key), + InsertInput(Key), RefreshMailbox((usize, usize)), //Quit? Resize, @@ -113,6 +114,7 @@ pub struct UIEvent { #[derive(Debug, PartialEq, Copy, Clone)] pub enum UIMode { Normal, + Insert, Execute, Fork, } @@ -124,6 +126,7 @@ impl fmt::Display for UIMode { "{}", match *self { UIMode::Normal => "NORMAL", + UIMode::Insert => "INSERT", UIMode::Execute => "EX", UIMode::Fork => "FORK", } diff --git a/ui/src/types/cells.rs b/ui/src/types/cells.rs index 3903b220..31dba436 100644 --- a/ui/src/types/cells.rs +++ b/ui/src/types/cells.rs @@ -658,6 +658,18 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, /// Change foreground and background colors in an `Area` pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) { + let bounds = grid.size(); + let upper_left = upper_left!(area); + let bottom_right = bottom_right!(area); + let (x, y) = upper_left; + if y > (get_y(bottom_right)) + || x > get_x(bottom_right) + || y >= get_y(bounds) + || x >= get_x(bounds) + { + eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area); + return; + } if !is_valid_area!(area) { eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area); return; @@ -685,8 +697,8 @@ pub fn write_string_to_grid( let (mut x, mut y) = upper_left; if y > (get_y(bottom_right)) || x > get_x(bottom_right) - || y > get_y(bounds) - || x > get_x(bounds) + || y >= get_y(bounds) + || x >= get_x(bounds) { eprintln!(" Invalid area with string {} and area {:?}", s, area); return (x, y); diff --git a/ui/src/types/keys.rs b/ui/src/types/keys.rs index e54ebfc9..722548b1 100644 --- a/ui/src/types/keys.rs +++ b/ui/src/types/keys.rs @@ -119,7 +119,7 @@ pub fn get_events( mut closure: impl FnMut(Key), mut exit: impl FnMut(), rx: &chan::Receiver, -) -> () { +) { let mut input_mode = InputMode::Normal; let mut paste_buf = String::with_capacity(256); for c in stdin.events() { diff --git a/ui/src/types/position.rs b/ui/src/types/position.rs index 61ebb79a..0dbf48b8 100644 --- a/ui/src/types/position.rs +++ b/ui/src/types/position.rs @@ -44,6 +44,10 @@ pub fn set_x(p: Pos, new_x: usize) -> Pos { pub fn set_y(p: Pos, new_y: usize) -> Pos { (p.0, new_y) } +#[inline(always)] +pub fn pos_inc(p: Pos, inc: (usize, usize)) -> Pos { + (p.0 + inc.0, p.1 + inc.1) +} /// An `Area` consists of two points: the upper left and bottom right corners. ///