From 6a66afe93eb928e6df93e79d0de386e0867fc3ec Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 24 Mar 2024 15:21:05 +0200 Subject: [PATCH] view: make add contact dialog scrollable on overflow If contact entries in the add contact dialog are too many to fit in the dialog area, show a scrollbar and allow the user to navigate it. Signed-off-by: Manos Pitsidianakis --- meli/src/mail/view.rs | 1 + meli/src/utilities/dialogs.rs | 270 ++++++++++++++++++++++++++++++---- 2 files changed, 242 insertions(+), 29 deletions(-) diff --git a/meli/src/mail/view.rs b/meli/src/mail/view.rs index efed3360..f7d27b95 100644 --- a/meli/src/mail/view.rs +++ b/meli/src/mail/view.rs @@ -356,6 +356,7 @@ impl Component for MailView { fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool { if let Some(ref mut s) = self.contact_selector { + // [ref:FIXME]: contact_selector should not forward navigation events and return true if s.process_event(event, context) { return true; } diff --git a/meli/src/utilities/dialogs.rs b/meli/src/utilities/dialogs.rs index 8f63d4ca..bb77e396 100644 --- a/meli/src/utilities/dialogs.rs +++ b/meli/src/utilities/dialogs.rs @@ -54,10 +54,13 @@ pub struct Selector< theme_default: ThemeAttribute, cursor: SelectorCursor, + scroll_x_cursor: usize, + movement: Option, vertical_alignment: Alignment, horizontal_alignment: Alignment, title: String, - + content: Screen, + initialized: bool, /// If `true`, user has finished their selection done: bool, done_fn: F, @@ -127,6 +130,7 @@ impl Component f * cursor */ self.entries[c].1 = !self.entries[c].1; self.dirty = true; + self.initialized = false; return true; } (UIEvent::Input(Key::Char('\n')), SelectorCursor::Ok) if !self.single_only => { @@ -169,6 +173,7 @@ impl Component f } self.cursor = SelectorCursor::Entry(0); self.dirty = true; + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Entry(c)) @@ -181,6 +186,7 @@ impl Component f } self.cursor = SelectorCursor::Entry(c - 1); self.dirty = true; + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Ok) @@ -190,6 +196,7 @@ impl Component f let c = self.entries.len().saturating_sub(1); self.cursor = SelectorCursor::Entry(c); self.dirty = true; + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Entry(c)) @@ -203,6 +210,7 @@ impl Component f } self.cursor = SelectorCursor::Entry(c + 1); self.dirty = true; + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Entry(_)) @@ -211,6 +219,7 @@ impl Component f { self.cursor = SelectorCursor::Ok; self.set_dirty(true); + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Ok) @@ -218,6 +227,7 @@ impl Component f { self.cursor = SelectorCursor::Cancel; self.set_dirty(true); + self.initialized = false; return true; } (UIEvent::Input(ref key), SelectorCursor::Cancel) @@ -225,15 +235,62 @@ impl Component f { self.cursor = SelectorCursor::Ok; self.set_dirty(true); + self.initialized = false; return true; } (UIEvent::Input(ref key), _) - if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) - || shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) - || shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => + { + self.movement = Some(PageMovement::Left(1)); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => + { + self.movement = Some(PageMovement::Right(1)); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["prev_page"]) => + { + self.movement = Some(PageMovement::PageUp(1)); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["next_page"]) => + { + self.movement = Some(PageMovement::PageDown(1)); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["home_page"]) => + { + self.movement = Some(PageMovement::Home); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["end_page"]) => + { + self.movement = Some(PageMovement::End); + self.set_dirty(true); + self.initialized = false; + return true; + } + (UIEvent::Input(ref key), _) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_up"]) || shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_down"]) => { - return true + return true; } _ => {} } @@ -256,6 +313,7 @@ impl Component f fn set_dirty(&mut self, value: bool) { self.dirty = value; + self.initialized = false; } fn id(&self) -> ComponentId { @@ -456,9 +514,13 @@ impl::new(), + initialized: false, done: false, done_fn, dirty: true, @@ -485,12 +547,11 @@ impl {} + PageMovement::Right(amount) => { + self.scroll_x_cursor = self.scroll_x_cursor.saturating_add(amount); + } + PageMovement::Left(amount) => { + self.scroll_x_cursor = self.scroll_x_cursor.saturating_sub(amount); + } + PageMovement::PageUp(multiplier) => match self.cursor { + SelectorCursor::Unfocused => { + self.cursor = SelectorCursor::Entry(0); + self.initialize(context); + } + SelectorCursor::Entry(c) => { + self.cursor = SelectorCursor::Entry(c.saturating_sub(multiplier * rows)); + self.initialize(context); + } + SelectorCursor::Ok | SelectorCursor::Cancel + if !self.entry_titles.is_empty() => + { + self.cursor = SelectorCursor::Entry( + self.entry_titles.len().saturating_sub(multiplier * rows), + ); + self.initialize(context); + } + SelectorCursor::Ok | SelectorCursor::Cancel => {} + }, + PageMovement::PageDown(multiplier) => match self.cursor { + SelectorCursor::Unfocused => { + self.cursor = SelectorCursor::Entry( + self.entry_titles + .len() + .saturating_sub(1) + .min(multiplier * rows), + ); + self.initialize(context); + } + SelectorCursor::Entry(c) + if c.saturating_add(multiplier * rows) < self.entry_titles.len() + && !self.entry_titles.is_empty() => + { + self.cursor = SelectorCursor::Entry( + self.entry_titles + .len() + .saturating_sub(1) + .min(c.saturating_add(multiplier * rows)), + ); + self.initialize(context); + } + SelectorCursor::Entry(_) => { + self.cursor = SelectorCursor::Ok; + self.initialize(context); + } + SelectorCursor::Ok | SelectorCursor::Cancel => {} + }, + PageMovement::Home if !self.entry_titles.is_empty() => { + self.cursor = SelectorCursor::Entry(0); + self.initialize(context); + } + PageMovement::End + if matches!(self.cursor, SelectorCursor::Ok | SelectorCursor::Cancel) => {} + PageMovement::End + if !matches!(self.cursor, SelectorCursor::Entry(c) if c +1 == self.entry_titles.len()) + && !self.entry_titles.is_empty() => + { + self.cursor = SelectorCursor::Entry(self.entry_titles.len().saturating_sub(1)); + self.initialize(context); + } + PageMovement::Home | PageMovement::End => {} + } + } + let skip_rows = match self.cursor { + SelectorCursor::Unfocused => 0, + SelectorCursor::Entry(e) if e >= rows => e.min(height.saturating_sub(rows)), + SelectorCursor::Entry(_) => 0, + SelectorCursor::Ok | SelectorCursor::Cancel => height.saturating_sub(rows), + }; + + self.scroll_x_cursor = self + .scroll_x_cursor + .min(width.saturating_sub(inner_area.width())); + grid.copy_area( + self.content.grid(), + inner_area, + self.content + .area() + .skip_cols(self.scroll_x_cursor) + .skip_rows(skip_rows), + ); + + if height > dialog_area.height() { + let inner_area = inner_area.skip_rows(1); + ScrollBar::default().set_show_arrows(true).draw( + grid, + inner_area.nth_col(inner_area.width().saturating_sub(1)), + context, + // position + skip_rows, + // visible_rows + inner_area.height(), + // length + height, + ); + } + if width > dialog_area.width() { + let inner_area = inner_area.skip_cols(1); + ScrollBar::default().set_show_arrows(true).draw_horizontal( + grid, + inner_area.nth_row(inner_area.height().saturating_sub(1)), + context, + // position + self.scroll_x_cursor, + // visible_cols + inner_area.width(), + // length + width, + ); + } context.dirty_areas.push_back(dialog_area); self.dirty = false; }