From ba7a97e90b4c474299a7b12fa74b7ea06c1535c8 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 13 Nov 2023 15:52:11 +0200 Subject: [PATCH] utilities/tables: add x axis scroll support Signed-off-by: Manos Pitsidianakis --- meli/docs/meli.conf.5 | 8 +-- meli/src/conf/shortcuts.rs | 12 ++--- meli/src/mail/listing.rs | 85 ++++++++++++++++++++++++++++++++ meli/src/mail/listing/compact.rs | 54 -------------------- meli/src/mail/listing/plain.rs | 56 ++++++++++++++++++++- meli/src/utilities/tables.rs | 37 +++++++------- 6 files changed, 167 insertions(+), 85 deletions(-) diff --git a/meli/docs/meli.conf.5 b/meli/docs/meli.conf.5 index ce3e3bef..e692bcd3 100644 --- a/meli/docs/meli.conf.5 +++ b/meli/docs/meli.conf.5 @@ -770,10 +770,10 @@ Start new mail draft in new tab. .Pq Em m \" default value .It Ic next_account Go to next account. -.Pq Em h \" default value +.Pq Em H \" default value .It Ic prev_account Go to previous account. -.Pq Em l \" default value +.Pq Em L \" default value .It Ic next_mailbox Go to next mailbox. .Pq Em J \" default value @@ -874,10 +874,10 @@ Mail contact under cursor. .Pq Em m \" default value .It Ic next_account Go to next account. -.Pq Em h \" default value +.Pq Em H \" default value .It Ic prev_account Go to previous account. -.Pq Em l \" default value +.Pq Em L \" default value .It Ic toggle_menu_visibility Toggle visibility of side menu in mail list. .Pq Em ` \" default value diff --git a/meli/src/conf/shortcuts.rs b/meli/src/conf/shortcuts.rs index 5c2b95c9..21763144 100644 --- a/meli/src/conf/shortcuts.rs +++ b/meli/src/conf/shortcuts.rs @@ -153,10 +153,10 @@ shortcut_key_values! { "listing", scroll_up |> "Scroll up list." |> Key::Char('k'), scroll_down |> "Scroll down list." |> Key::Char('j'), new_mail |> "Start new mail draft in new tab." |> Key::Char('m'), - next_account |> "Go to next account." |> Key::Char('h'), + next_account |> "Go to next account." |> Key::Char('H'), next_mailbox |> "Go to next mailbox." |> Key::Char('J'), next_page |> "Go to next page." |> Key::PageDown, - prev_account |> "Go to previous account." |> Key::Char('l'), + prev_account |> "Go to previous account." |> Key::Char('L'), prev_mailbox |> "Go to previous mailbox." |> Key::Char('K'), open_mailbox |> "Open selected mailbox" |> Key::Char('\n'), toggle_mailbox_collapse |> "Toggle mailbox collapse in menu." |> Key::Char(' '), @@ -189,8 +189,8 @@ shortcut_key_values! { "contact-list", edit_contact |> "Edit contact under cursor." |> Key::Char('e'), delete_contact |> "Delete contact under cursor." |> Key::Char('d'), mail_contact |> "Mail contact under cursor." |> Key::Char('m'), - next_account |> "Go to next account." |> Key::Char('h'), - prev_account |> "Go to previous account." |> Key::Char('l'), + next_account |> "Go to next account." |> Key::Char('H'), + prev_account |> "Go to previous account." |> Key::Char('L'), toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`') } } @@ -212,8 +212,8 @@ shortcut_key_values! { "general", quit |> "Quit meli." |> Key::Char('q'), go_to_tab |> "Go to the nth tab" |> Key::Alt('n'), next_tab |> "Next tab." |> Key::Char('T'), - scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Right, - scroll_left |> "Generic scroll left (catch-all setting)" |> Key::Left, + scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Char('l'), + scroll_left |> "Generic scroll left (catch-all setting)" |>Key::Char('h'), scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Char('k'), scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Char('j'), next_page |> "Go to next page. (catch-all setting)" |> Key::PageDown, diff --git a/meli/src/mail/listing.rs b/meli/src/mail/listing.rs index 9b6af4be..1867d662 100644 --- a/meli/src/mail/listing.rs +++ b/meli/src/mail/listing.rs @@ -422,6 +422,45 @@ macro_rules! address_list { }}; } +#[macro_export] +macro_rules! digits_of_num { + ($num:expr) => {{ + const GUESS: [usize; 65] = [ + 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, + 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 18, 19, + ]; + const TENS: [usize; 20] = [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000, + ]; + const SIZE_IN_BITS: usize = std::mem::size_of::() * 8; + + let leading_zeros = $num.leading_zeros() as usize; + let base_two_digits: usize = SIZE_IN_BITS - leading_zeros; + let x = GUESS[base_two_digits]; + x + if $num >= TENS[x] { 1 } else { 0 } + }}; +} + macro_rules! column_str { ( struct $name:ident($($t:ty),+)) => { @@ -1658,6 +1697,52 @@ impl Component for Listing { self.component.set_movement(PageMovement::Down(amount)); return true; } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_right"]) => + { + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + self.component.set_modifier_active(false); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + amount + } else { + self.cmd_buf.clear(); + self.component.set_modifier_active(false); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.component.set_movement(PageMovement::Right(amount)); + return true; + } + UIEvent::Input(ref key) + if shortcut!(key == shortcuts[Shortcuts::GENERAL]["scroll_left"]) => + { + let amount = if self.cmd_buf.is_empty() { + 1 + } else if let Ok(amount) = self.cmd_buf.parse::() { + self.cmd_buf.clear(); + self.component.set_modifier_active(false); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + amount + } else { + self.cmd_buf.clear(); + self.component.set_modifier_active(false); + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); + return true; + }; + self.component.set_movement(PageMovement::Left(amount)); + return true; + } UIEvent::Input(ref key) if shortcut!(key == shortcuts[Shortcuts::LISTING]["prev_page"]) => { diff --git a/meli/src/mail/listing/compact.rs b/meli/src/mail/listing/compact.rs index 8da65604..bd607c1f 100644 --- a/meli/src/mail/listing/compact.rs +++ b/meli/src/mail/listing/compact.rs @@ -27,60 +27,6 @@ use melib::{SortField, SortOrder, TagHash, Threads}; use super::*; use crate::{components::PageMovement, jobs::JoinHandle}; -macro_rules! digits_of_num { - ($num:expr) => {{ - const GUESS: [usize; 65] = [ - 1, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, - 8, 9, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, - 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 18, 19, - ]; - const TENS: [usize; 20] = [ - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000, - ]; - const SIZE_IN_BITS: usize = std::mem::size_of::() * 8; - - let leading_zeros = $num.leading_zeros() as usize; - let base_two_digits: usize = SIZE_IN_BITS - leading_zeros; - let x = GUESS[base_two_digits]; - x + if $num >= TENS[x] { 1 } else { 0 } - }}; -} - -macro_rules! address_list { - (($name:expr) as comma_sep_list) => {{ - let mut ret: String = - $name - .into_iter() - .fold(String::new(), |mut s: String, n: &Address| { - s.extend(n.to_string().chars()); - s.push_str(", "); - s - }); - ret.pop(); - ret.pop(); - ret - }}; -} - macro_rules! row_attr { ($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{ let color_cache = &$color_cache; diff --git a/meli/src/mail/listing/plain.rs b/meli/src/mail/listing/plain.rs index f52634e7..f6460297 100644 --- a/meli/src/mail/listing/plain.rs +++ b/meli/src/mail/listing/plain.rs @@ -445,7 +445,20 @@ impl ListingTrait for PlainListing { self.new_cursor_pos.2 = (self.length.saturating_sub(1) / rows) * rows; } } - PageMovement::Right(_) | PageMovement::Left(_) => {} + PageMovement::Right(amount) => { + self.data_columns.x_offset += amount; + self.data_columns.x_offset = self.data_columns.x_offset.min( + self.data_columns + .widths + .iter() + .map(|w| w + 2) + .sum::() + .saturating_sub(2), + ); + } + PageMovement::Left(amount) => { + self.data_columns.x_offset = self.data_columns.x_offset.saturating_sub(amount); + } PageMovement::Home => { self.new_cursor_pos.2 = 0; } @@ -788,6 +801,18 @@ impl PlainListing { self.rows.clear(); self.length = 0; let mut min_width = (0, 0, 0, 0, 0); + #[allow(clippy::type_complexity)] + let mut row_widths: ( + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + SmallVec<[u8; 1024]>, + ) = ( + SmallVec::new(), + SmallVec::new(), + SmallVec::new(), + SmallVec::new(), + ); for i in iter { if !context.accounts[&self.cursor_pos.0].contains_key(i) { @@ -824,6 +849,28 @@ impl PlainListing { self.rows.row_attr_cache.insert(self.length, row_attr); let entry_strings = self.make_entry_string(&envelope, context); + row_widths.1.push( + entry_strings + .date + .grapheme_width() + .try_into() + .unwrap_or(255), + ); + row_widths.2.push( + entry_strings + .from + .grapheme_width() + .try_into() + .unwrap_or(255), + ); + row_widths.3.push( + (entry_strings.flag.grapheme_width() + + entry_strings.subject.grapheme_width() + + 1 + + entry_strings.tags.grapheme_width()) + .try_into() + .unwrap_or(255), + ); min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */ min_width.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */ min_width.3 = cmp::max( @@ -842,6 +889,9 @@ impl PlainListing { self.length += 1; } + row_widths + .0 + .push(digits_of_num!(self.length).try_into().unwrap_or(255)); min_width.0 = self.length.saturating_sub(1).to_string().len(); @@ -868,6 +918,10 @@ impl PlainListing { _ = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context); /* subject column */ _ = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context); + self.data_columns.segment_tree[0] = row_widths.0.into(); + self.data_columns.segment_tree[1] = row_widths.1.into(); + self.data_columns.segment_tree[2] = row_widths.2.into(); + self.data_columns.segment_tree[3] = row_widths.3.into(); let iter = if self.filter_term.is_empty() { Box::new(self.local_collection.iter().cloned()) diff --git a/meli/src/utilities/tables.rs b/meli/src/utilities/tables.rs index 8c3a2725..9f4714fe 100644 --- a/meli/src/utilities/tables.rs +++ b/meli/src/utilities/tables.rs @@ -241,38 +241,35 @@ impl DataColumns { } pub fn draw( - &self, + &mut self, grid: &mut CellBuffer, top_idx: usize, cursor_pos: usize, mut bounds: BoundsIterator, ) { - let mut _relative_x_offset = 0; - let mut skip_cols = (0, 0); let mut start_col = 0; let total_area = bounds.area(); let height = total_area.height(); - while _relative_x_offset < self.x_offset && start_col < N { - _relative_x_offset += self.widths[start_col] + 2; - if self.x_offset <= _relative_x_offset { - skip_cols.0 = start_col; - skip_cols.1 = _relative_x_offset - self.x_offset; - _relative_x_offset = self.x_offset; + if self.width_accum > 0 && self.x_offset + total_area.width() > self.width_accum { + self.x_offset = self.width_accum.saturating_sub(total_area.width()); + } + let mut x_offset = self.x_offset; + for col in 0..N { + start_col = col; + if x_offset == 0 || self.widths[col] > x_offset { break; } - start_col += 1; + x_offset -= self.widths[col]; + x_offset = x_offset.saturating_sub(2); } - for col in skip_cols.0..N { + for col in start_col..N { if bounds.is_empty() { break; } - let mut column_width = self.widths[col]; - if column_width > bounds.width() { - column_width = bounds.width(); - } else if column_width == 0 { - skip_cols.1 = 0; + let column_width = self.widths[col]; + if column_width == 0 { continue; } @@ -282,11 +279,11 @@ impl DataColumns { self.columns[col] .area() .skip_rows(top_idx) - .skip_cols(skip_cols.1) - .take_cols(column_width), + .skip_cols(x_offset) + .take_cols(column_width - x_offset), ); - bounds.add_x(column_width + 2); - skip_cols.1 = 0; + bounds.add_x(column_width - x_offset + 2); + x_offset = 0; } match self.theme_config.theme {