From 3949cecb7531332ea0448f5deedb6d539959b99e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 16 Oct 2020 12:35:51 +0300 Subject: [PATCH] mail/composer: add scrollbars --- src/components/contacts/contact_list.rs | 2 +- src/components/mail/compose.rs | 91 ++++++++++---------- src/components/mail/listing.rs | 2 +- src/components/mail/view.rs | 3 +- src/components/mail/view/thread.rs | 6 +- src/components/utilities.rs | 5 +- src/components/utilities/pager.rs | 89 ++++++++++++++++---- src/components/utilities/widgets.rs | 105 ++++++++++++++++++------ 8 files changed, 206 insertions(+), 97 deletions(-) diff --git a/src/components/contacts/contact_list.rs b/src/components/contacts/contact_list.rs index baeb4394..b316894b 100644 --- a/src/components/contacts/contact_list.rs +++ b/src/components/contacts/contact_list.rs @@ -642,7 +642,7 @@ impl Component for ContactList { let mut draft: Draft = Draft::default(); *draft.headers_mut().get_mut("To").unwrap() = format!("{} <{}>", &card.name(), &card.email()); - let mut composer = Composer::new(account_hash, context); + let mut composer = Composer::with_account(account_hash, context); composer.set_draft(draft); context .replies diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index 4189bc2e..354bbc71 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -98,33 +98,6 @@ pub struct Composer { id: ComponentId, } -impl Default for Composer { - fn default() -> Self { - let mut pager = Pager::default(); - pager.set_reflow(text_processing::Reflow::FormatFlowed); - Composer { - reply_context: None, - account_hash: 0, - - cursor: Cursor::Headers, - - pager, - draft: Draft::default(), - form: FormWidget::default(), - - mode: ViewMode::Edit, - #[cfg(feature = "gpgme")] - gpg_state: gpg::GpgComposeState::new(), - dirty: true, - has_changes: false, - embed_area: ((0, 0), (0, 0)), - embed: None, - initialized: false, - id: ComponentId::new_v4(), - } - } -} - #[derive(Debug)] enum ViewMode { Discard(Uuid, UIDialog), @@ -174,11 +147,32 @@ impl fmt::Display for Composer { impl Composer { const DESCRIPTION: &'static str = "composing"; - pub fn new(account_hash: AccountHash, context: &Context) -> Self { + pub fn new(context: &Context) -> Self { + let mut pager = Pager::new(context); + pager.set_show_scrollbar(true); + Composer { + reply_context: None, + account_hash: 0, + cursor: Cursor::Headers, + pager, + draft: Draft::default(), + form: FormWidget::default(), + mode: ViewMode::Edit, + #[cfg(feature = "gpgme")] + gpg_state: gpg::GpgComposeState::new(), + dirty: true, + has_changes: false, + embed_area: ((0, 0), (0, 0)), + embed: None, + initialized: false, + id: ComponentId::new_v4(), + } + } + + pub fn with_account(account_hash: AccountHash, context: &Context) -> Self { let mut ret = Composer { account_hash, - id: ComponentId::new_v4(), - ..Default::default() + ..Composer::new(context) }; for (h, v) in account_settings!(context[account_hash].composing.default_header_values).iter() @@ -194,8 +188,6 @@ impl Composer { format!("meli {}", option_env!("CARGO_PKG_VERSION").unwrap_or("0.0")), ); } - ret.pager - .set_colors(crate::conf::value(context, "theme_default")); ret } @@ -205,7 +197,7 @@ impl Composer { bytes: &[u8], context: &Context, ) -> Result { - let mut ret = Composer::default(); + let mut ret = Composer::with_account(account_hash, context); let envelope: EnvelopeRef = context.accounts[&account_hash].collection.get_env(env_hash); ret.draft = Draft::edit(&envelope, bytes)?; @@ -220,7 +212,7 @@ impl Composer { context: &mut Context, reply_to_all: bool, ) -> Self { - let mut ret = Composer::new(coordinates.0, context); + let mut ret = Composer::with_account(coordinates.0, context); let account = &context.accounts[&coordinates.0]; let envelope = account.collection.get_env(coordinates.2); let subject = envelope.subject(); @@ -676,8 +668,8 @@ impl Component for Composer { ); let body_area = ( - pos_inc(upper_left, (mid + 1, header_height + 1)), - pos_dec(bottom_right, (mid, 4 + attachments_no)), + pos_inc(upper_left, (mid, header_height + 1)), + pos_dec(bottom_right, (mid, 5 + attachments_no)), ); let (x, y) = write_string_to_grid( @@ -763,13 +755,24 @@ impl Component for Composer { self.embed_area = (upper_left!(header_area), bottom_right!(body_area)); } + if !self.mode.is_edit_attachments() { + self.pager.set_dirty(true); + if self.pager.size().0 > width!(body_area) { + self.pager.set_initialised(false); + } + self.pager.draw(grid, body_area, context); + } + match self.cursor { Cursor::Headers => { change_colors( grid, ( - set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), - bottom_right!(body_area), + pos_dec(upper_left!(body_area), (1, 0)), + pos_dec( + set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), + (1, 0), + ), ), theme_default.fg, theme_default.bg, @@ -779,8 +782,11 @@ impl Component for Composer { change_colors( grid, ( - set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), - bottom_right!(body_area), + pos_dec(upper_left!(body_area), (1, 0)), + pos_dec( + set_y(upper_left!(body_area), get_y(bottom_right!(body_area))), + (1, 0), + ), ), theme_default.fg, Color::Byte(237), @@ -789,11 +795,6 @@ impl Component for Composer { Cursor::Sign | Cursor::Encrypt | Cursor::Attachments => {} } - if !self.mode.is_edit_attachments() { - self.pager.set_dirty(true); - self.pager.draw(grid, body_area, context); - } - match self.mode { ViewMode::Edit | ViewMode::Embed => {} ViewMode::EditAttachments { ref mut widget } => { diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index ba04d17e..0bfa2849 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -1211,7 +1211,7 @@ impl Component for Listing { if shortcut!(k == shortcuts[Listing::DESCRIPTION]["new_mail"]) => { let account_hash = context.accounts[self.cursor_pos.0].hash(); - let composer = Composer::new(account_hash, context); + let composer = Composer::with_account(account_hash, context); context .replies .push_back(UIEvent::Action(Tab(New(Some(Box::new(composer)))))); diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 9f551434..4b702476 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -2143,7 +2143,8 @@ impl Component for MailView { { if let Ok(mailto) = Mailto::try_from(list_post_addr) { let draft: Draft = mailto.into(); - let mut composer = Composer::new(self.coordinates.0, context); + let mut composer = + Composer::with_account(self.coordinates.0, context); composer.set_draft(draft); context.replies.push_back(UIEvent::Action(Tab(New(Some( Box::new(composer), diff --git a/src/components/mail/view/thread.rs b/src/components/mail/view/thread.rs index aee289ef..cc7a0f00 100644 --- a/src/components/mail/view/thread.rs +++ b/src/components/mail/view/thread.rs @@ -570,8 +570,7 @@ impl ThreadView { self.highlight_line(grid, dest_area, src_area, idx); if rows < visibles.len() { - ScrollBar::draw( - ScrollBar::default(), + ScrollBar::default().draw( grid, ( upper_left!(area), @@ -624,8 +623,7 @@ impl ThreadView { self.highlight_line(grid, dest_area, src_area, entry_idx); if rows < visibles.len() { - ScrollBar::draw( - ScrollBar::default(), + ScrollBar::default().draw( grid, ( upper_left!(area), diff --git a/src/components/utilities.rs b/src/components/utilities.rs index 4c6f498c..80f4b3eb 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -316,10 +316,7 @@ impl Component for StatusBar { } let hist_height = std::cmp::min(15, self.auto_complete.suggestions().len()); let hist_area = if height < self.auto_complete.suggestions().len() { - let mut scrollbar = ScrollBar::default(); - scrollbar.set_show_arrows(false); - scrollbar.set_block_character(Some('▌')); - scrollbar.draw( + ScrollBar::default().set_show_arrows(false).draw( grid, ( ( diff --git a/src/components/utilities/pager.rs b/src/components/utilities/pager.rs index d75cd1bc..d499e634 100644 --- a/src/components/utilities/pager.rs +++ b/src/components/utilities/pager.rs @@ -37,6 +37,7 @@ pub struct Pager { colors: ThemeAttribute, initialised: bool, + show_scrollbar: bool, content: CellBuffer, text_lines: (usize, Vec), movement: Option, @@ -51,6 +52,23 @@ impl fmt::Display for Pager { impl Pager { pub const DESCRIPTION: &'static str = "pager"; + pub fn new(context: &Context) -> Self { + let mut ret = Pager::default(); + ret.minimum_width = context.settings.pager.minimum_width; + ret.set_colors(crate::conf::value(context, "theme_default")) + .set_reflow(if context.settings.pager.split_long_lines { + Reflow::All + } else { + Reflow::No + }); + ret + } + + pub fn set_show_scrollbar(&mut self, new_val: bool) -> &mut Self { + self.show_scrollbar = new_val; + self + } + pub fn set_colors(&mut self, new_val: ThemeAttribute) -> &mut Self { self.colors = new_val; self @@ -61,6 +79,11 @@ impl Pager { self } + pub fn set_initialised(&mut self, new_val: bool) -> &mut Self { + self.initialised = new_val; + self + } + pub fn reflow(&self) -> Reflow { self.reflow } @@ -264,13 +287,13 @@ impl Component for Pager { width = self.minimum_width; } - let lines: &[String] = if self.text_lines.0 == width.saturating_sub(2) { + let lines: &[String] = if self.text_lines.0 == width.saturating_sub(4) { &self.text_lines.1 } else { let lines = self .text - .split_lines_reflow(self.reflow, Some(width.saturating_sub(2))); - self.text_lines = (width.saturating_sub(2), lines); + .split_lines_reflow(self.reflow, Some(width.saturating_sub(4))); + self.text_lines = (width.saturating_sub(4), lines); &self.text_lines.1 }; let height = lines.len() + 2; @@ -344,30 +367,36 @@ impl Component for Pager { self.cursor.1 = self.cursor.1.saturating_sub(height * multiplier); } PageMovement::Down(amount) => { - if self.cursor.1 + amount < self.height { + if self.cursor.1 + amount + 1 < self.height { self.cursor.1 += amount; + } else { + self.cursor.1 = self.height.saturating_sub(1); } } PageMovement::PageDown(multiplier) => { - if self.cursor.1 + height * multiplier < self.height { + if self.cursor.1 + height * multiplier + 1 < self.height { self.cursor.1 += height * multiplier; + } else if self.cursor.1 + height * multiplier > self.height { + self.cursor.1 = self.height - 1; + } else { + self.cursor.1 = (self.height / height) * height; } } - PageMovement::Right(multiplier) => { - let offset = width!(area) / 3; - if self.cursor.0 + offset * multiplier < self.content.size().0 { - self.cursor.0 += offset * multiplier; + PageMovement::Right(amount) => { + if self.cursor.0 + amount + 1 < self.width { + self.cursor.0 += amount; + } else { + self.cursor.0 = self.width.saturating_sub(1); } } - PageMovement::Left(multiplier) => { - let offset = width!(area) / 3; - self.cursor.0 = self.cursor.0.saturating_sub(offset * multiplier); + PageMovement::Left(amount) => { + self.cursor.0 = self.cursor.0.saturating_sub(amount); } PageMovement::Home => { self.cursor.1 = 0; } PageMovement::End => { - self.cursor.1 = (self.height / height) * height; + self.cursor.1 = self.height.saturating_sub(1); } } } @@ -397,7 +426,13 @@ impl Component for Pager { clear_area(grid, area, crate::conf::value(context, "theme_default")); let (width, height) = self.content.size(); - let (cols, rows) = (width!(area), height!(area)); + let (mut cols, mut rows) = (width!(area), height!(area)); + if self.show_scrollbar && rows < height { + cols -= 1; + } + if self.show_scrollbar && cols < width { + rows -= 1; + } self.cursor = ( std::cmp::min(width.saturating_sub(cols), self.cursor.0), std::cmp::min(height.saturating_sub(rows), self.cursor.1), @@ -417,6 +452,32 @@ impl Component for Pager { ), ), ); + if self.show_scrollbar && rows + 1 < height { + ScrollBar::default().set_show_arrows(true).draw( + grid, + ( + set_x(upper_left!(area), get_x(bottom_right!(area))), + bottom_right!(area), + ), + context, + self.cursor.1, + rows, + height, + ); + } + if self.show_scrollbar && cols + 1 < width { + ScrollBar::default().set_show_arrows(true).draw_horizontal( + grid, + ( + set_y(upper_left!(area), get_y(bottom_right!(area))), + bottom_right!(area), + ), + context, + self.cursor.0, + cols, + width, + ); + } context.dirty_areas.push_back(area); } fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { diff --git a/src/components/utilities/widgets.rs b/src/components/utilities/widgets.rs index 5ccaf2c6..a46482dc 100644 --- a/src/components/utilities/widgets.rs +++ b/src/components/utilities/widgets.rs @@ -980,19 +980,17 @@ impl AutoComplete { #[derive(Default)] pub struct ScrollBar { - show_arrows: bool, - block_character: Option, + pub show_arrows: bool, } impl ScrollBar { - pub fn set_show_arrows(&mut self, flag: bool) { - self.show_arrows = flag; - } - pub fn set_block_character(&mut self, val: Option) { - self.block_character = val; + pub fn set_show_arrows(&mut self, new_val: bool) -> &mut Self { + self.show_arrows = new_val; + self } + pub fn draw( - self, + &mut self, grid: &mut CellBuffer, area: Area, context: &Context, @@ -1007,17 +1005,17 @@ impl ScrollBar { if height < 3 { return; } - if self.show_arrows { - height -= height; - } clear_area(grid, area, crate::conf::value(context, "theme_default")); let visible_ratio: f32 = (std::cmp::min(visible_rows, length) as f32) / (length as f32); let scrollbar_height = std::cmp::max((visible_ratio * (height as f32)) as usize, 1); + if self.show_arrows { + height -= 3; + } let scrollbar_offset = { let temp = (((pos as f32) / (length as f32)) * (height as f32)) as usize; - if temp + scrollbar_height >= height { - height - scrollbar_height + if scrollbar_height + temp > height { + height.saturating_sub(scrollbar_height) } else { temp } @@ -1025,24 +1023,77 @@ impl ScrollBar { let (mut upper_left, bottom_right) = area; if self.show_arrows { - grid[upper_left].set_ch('▴'); - upper_left = (upper_left.0, upper_left.1 + 1); + grid[upper_left] + .set_ch('▄') + .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg); + upper_left = pos_inc(upper_left, (0, 1)); } - for y in get_y(upper_left)..(get_y(upper_left) + scrollbar_offset) { - grid[set_y(upper_left, y)].set_ch(' '); - } - for y in (get_y(upper_left) + scrollbar_offset) - ..=(get_y(upper_left) + scrollbar_offset + scrollbar_height) - { - grid[set_y(upper_left, y)].set_ch(self.block_character.unwrap_or('█')); - } - for y in (get_y(upper_left) + scrollbar_offset + scrollbar_height + 1)..get_y(bottom_right) - { - grid[set_y(upper_left, y)].set_ch(' '); + upper_left = pos_inc(upper_left, (0, scrollbar_offset)); + for _ in 0..=scrollbar_height { + grid[upper_left].set_bg(crate::conf::value(context, "widgets.options.highlighted").bg); + upper_left = pos_inc(upper_left, (0, 1)); } if self.show_arrows { - grid[set_x(bottom_right, get_x(upper_left))].set_ch('▾'); + grid[pos_dec(bottom_right, (0, 1))] + .set_ch('▀') + .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) + .set_bg(crate::conf::value(context, "theme_default").bg); + } + } + + pub fn draw_horizontal( + &mut self, + grid: &mut CellBuffer, + area: Area, + context: &Context, + pos: usize, + visible_cols: usize, + length: usize, + ) { + if length == 0 { + return; + } + let mut width = width!(area); + if width < 3 { + return; + } + clear_area(grid, area, crate::conf::value(context, "theme_default")); + + let visible_ratio: f32 = (std::cmp::min(visible_cols, length) as f32) / (length as f32); + let scrollbar_width = std::cmp::max((visible_ratio * (width as f32)) as usize, 1); + if self.show_arrows { + width -= 3; + } + let scrollbar_offset = { + let temp = (((pos as f32) / (length as f32)) * (width as f32)) as usize; + if scrollbar_width + temp > width { + width.saturating_sub(scrollbar_width) + } else { + temp + } + }; + let (mut upper_left, bottom_right) = area; + + if self.show_arrows { + grid[upper_left] + .set_ch('▐') + .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg); + upper_left = pos_inc(upper_left, (1, 0)); + } + + upper_left = pos_inc(upper_left, (scrollbar_offset, 0)); + for _ in 0..=scrollbar_width { + grid[upper_left] + .set_ch('█') + .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg); + upper_left = pos_inc(upper_left, (1, 0)); + } + if self.show_arrows { + grid[pos_dec(bottom_right, (1, 0))] + .set_ch('▌') + .set_fg(crate::conf::value(context, "widgets.options.highlighted").bg) + .set_bg(crate::conf::value(context, "theme_default").bg); } } }