utilities/tables: add x axis scroll support
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>pull/312/head
parent
bcec745c24
commit
ba7a97e90b
|
@ -770,10 +770,10 @@ Start new mail draft in new tab.
|
||||||
.Pq Em m \" default value
|
.Pq Em m \" default value
|
||||||
.It Ic next_account
|
.It Ic next_account
|
||||||
Go to next account.
|
Go to next account.
|
||||||
.Pq Em h \" default value
|
.Pq Em H \" default value
|
||||||
.It Ic prev_account
|
.It Ic prev_account
|
||||||
Go to previous account.
|
Go to previous account.
|
||||||
.Pq Em l \" default value
|
.Pq Em L \" default value
|
||||||
.It Ic next_mailbox
|
.It Ic next_mailbox
|
||||||
Go to next mailbox.
|
Go to next mailbox.
|
||||||
.Pq Em J \" default value
|
.Pq Em J \" default value
|
||||||
|
@ -874,10 +874,10 @@ Mail contact under cursor.
|
||||||
.Pq Em m \" default value
|
.Pq Em m \" default value
|
||||||
.It Ic next_account
|
.It Ic next_account
|
||||||
Go to next account.
|
Go to next account.
|
||||||
.Pq Em h \" default value
|
.Pq Em H \" default value
|
||||||
.It Ic prev_account
|
.It Ic prev_account
|
||||||
Go to previous account.
|
Go to previous account.
|
||||||
.Pq Em l \" default value
|
.Pq Em L \" default value
|
||||||
.It Ic toggle_menu_visibility
|
.It Ic toggle_menu_visibility
|
||||||
Toggle visibility of side menu in mail list.
|
Toggle visibility of side menu in mail list.
|
||||||
.Pq Em ` \" default value
|
.Pq Em ` \" default value
|
||||||
|
|
|
@ -153,10 +153,10 @@ shortcut_key_values! { "listing",
|
||||||
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
scroll_up |> "Scroll up list." |> Key::Char('k'),
|
||||||
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
scroll_down |> "Scroll down list." |> Key::Char('j'),
|
||||||
new_mail |> "Start new mail draft in new tab." |> Key::Char('m'),
|
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_mailbox |> "Go to next mailbox." |> Key::Char('J'),
|
||||||
next_page |> "Go to next page." |> Key::PageDown,
|
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'),
|
prev_mailbox |> "Go to previous mailbox." |> Key::Char('K'),
|
||||||
open_mailbox |> "Open selected mailbox" |> Key::Char('\n'),
|
open_mailbox |> "Open selected mailbox" |> Key::Char('\n'),
|
||||||
toggle_mailbox_collapse |> "Toggle mailbox collapse in menu." |> Key::Char(' '),
|
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'),
|
edit_contact |> "Edit contact under cursor." |> Key::Char('e'),
|
||||||
delete_contact |> "Delete contact under cursor." |> Key::Char('d'),
|
delete_contact |> "Delete contact under cursor." |> Key::Char('d'),
|
||||||
mail_contact |> "Mail contact under cursor." |> Key::Char('m'),
|
mail_contact |> "Mail contact under cursor." |> Key::Char('m'),
|
||||||
next_account |> "Go to next account." |> Key::Char('h'),
|
next_account |> "Go to next account." |> Key::Char('H'),
|
||||||
prev_account |> "Go to previous account." |> Key::Char('l'),
|
prev_account |> "Go to previous account." |> Key::Char('L'),
|
||||||
toggle_menu_visibility |> "Toggle visibility of side menu in mail list." |> Key::Char('`')
|
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'),
|
quit |> "Quit meli." |> Key::Char('q'),
|
||||||
go_to_tab |> "Go to the nth tab" |> Key::Alt('n'),
|
go_to_tab |> "Go to the nth tab" |> Key::Alt('n'),
|
||||||
next_tab |> "Next tab." |> Key::Char('T'),
|
next_tab |> "Next tab." |> Key::Char('T'),
|
||||||
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Right,
|
scroll_right |> "Generic scroll right (catch-all setting)" |> Key::Char('l'),
|
||||||
scroll_left |> "Generic scroll left (catch-all setting)" |> Key::Left,
|
scroll_left |> "Generic scroll left (catch-all setting)" |>Key::Char('h'),
|
||||||
scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Char('k'),
|
scroll_up |> "Generic scroll up (catch-all setting)" |> Key::Char('k'),
|
||||||
scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Char('j'),
|
scroll_down |> "Generic scroll down (catch-all setting)" |> Key::Char('j'),
|
||||||
next_page |> "Go to next page. (catch-all setting)" |> Key::PageDown,
|
next_page |> "Go to next page. (catch-all setting)" |> Key::PageDown,
|
||||||
|
|
|
@ -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::<usize>() * 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 {
|
macro_rules! column_str {
|
||||||
(
|
(
|
||||||
struct $name:ident($($t:ty),+)) => {
|
struct $name:ident($($t:ty),+)) => {
|
||||||
|
@ -1658,6 +1697,52 @@ impl Component for Listing {
|
||||||
self.component.set_movement(PageMovement::Down(amount));
|
self.component.set_movement(PageMovement::Down(amount));
|
||||||
return true;
|
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::<usize>() {
|
||||||
|
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::<usize>() {
|
||||||
|
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)
|
UIEvent::Input(ref key)
|
||||||
if shortcut!(key == shortcuts[Shortcuts::LISTING]["prev_page"]) =>
|
if shortcut!(key == shortcuts[Shortcuts::LISTING]["prev_page"]) =>
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,60 +27,6 @@ use melib::{SortField, SortOrder, TagHash, Threads};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{components::PageMovement, jobs::JoinHandle};
|
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::<usize>() * 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 {
|
macro_rules! row_attr {
|
||||||
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
|
($color_cache:expr, $even: expr, $unseen:expr, $highlighted:expr, $selected:expr $(,)*) => {{
|
||||||
let color_cache = &$color_cache;
|
let color_cache = &$color_cache;
|
||||||
|
|
|
@ -445,7 +445,20 @@ impl ListingTrait for PlainListing {
|
||||||
self.new_cursor_pos.2 = (self.length.saturating_sub(1) / rows) * rows;
|
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::<usize>()
|
||||||
|
.saturating_sub(2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
PageMovement::Left(amount) => {
|
||||||
|
self.data_columns.x_offset = self.data_columns.x_offset.saturating_sub(amount);
|
||||||
|
}
|
||||||
PageMovement::Home => {
|
PageMovement::Home => {
|
||||||
self.new_cursor_pos.2 = 0;
|
self.new_cursor_pos.2 = 0;
|
||||||
}
|
}
|
||||||
|
@ -788,6 +801,18 @@ impl PlainListing {
|
||||||
self.rows.clear();
|
self.rows.clear();
|
||||||
self.length = 0;
|
self.length = 0;
|
||||||
let mut min_width = (0, 0, 0, 0, 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 {
|
for i in iter {
|
||||||
if !context.accounts[&self.cursor_pos.0].contains_key(i) {
|
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);
|
self.rows.row_attr_cache.insert(self.length, row_attr);
|
||||||
|
|
||||||
let entry_strings = self.make_entry_string(&envelope, context);
|
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.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.2 = cmp::max(min_width.2, entry_strings.from.grapheme_width()); /* from */
|
||||||
min_width.3 = cmp::max(
|
min_width.3 = cmp::max(
|
||||||
|
@ -842,6 +889,9 @@ impl PlainListing {
|
||||||
|
|
||||||
self.length += 1;
|
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();
|
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);
|
_ = self.data_columns.columns[2].resize_with_context(min_width.2, self.rows.len(), context);
|
||||||
/* subject column */
|
/* subject column */
|
||||||
_ = self.data_columns.columns[3].resize_with_context(min_width.3, self.rows.len(), context);
|
_ = 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() {
|
let iter = if self.filter_term.is_empty() {
|
||||||
Box::new(self.local_collection.iter().cloned())
|
Box::new(self.local_collection.iter().cloned())
|
||||||
|
|
|
@ -241,38 +241,35 @@ impl<const N: usize> DataColumns<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&mut self,
|
||||||
grid: &mut CellBuffer,
|
grid: &mut CellBuffer,
|
||||||
top_idx: usize,
|
top_idx: usize,
|
||||||
cursor_pos: usize,
|
cursor_pos: usize,
|
||||||
mut bounds: BoundsIterator,
|
mut bounds: BoundsIterator,
|
||||||
) {
|
) {
|
||||||
let mut _relative_x_offset = 0;
|
|
||||||
let mut skip_cols = (0, 0);
|
|
||||||
let mut start_col = 0;
|
let mut start_col = 0;
|
||||||
let total_area = bounds.area();
|
let total_area = bounds.area();
|
||||||
let height = total_area.height();
|
let height = total_area.height();
|
||||||
while _relative_x_offset < self.x_offset && start_col < N {
|
if self.width_accum > 0 && self.x_offset + total_area.width() > self.width_accum {
|
||||||
_relative_x_offset += self.widths[start_col] + 2;
|
self.x_offset = self.width_accum.saturating_sub(total_area.width());
|
||||||
if self.x_offset <= _relative_x_offset {
|
}
|
||||||
skip_cols.0 = start_col;
|
let mut x_offset = self.x_offset;
|
||||||
skip_cols.1 = _relative_x_offset - self.x_offset;
|
for col in 0..N {
|
||||||
_relative_x_offset = self.x_offset;
|
start_col = col;
|
||||||
|
if x_offset == 0 || self.widths[col] > x_offset {
|
||||||
break;
|
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() {
|
if bounds.is_empty() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut column_width = self.widths[col];
|
let column_width = self.widths[col];
|
||||||
if column_width > bounds.width() {
|
if column_width == 0 {
|
||||||
column_width = bounds.width();
|
|
||||||
} else if column_width == 0 {
|
|
||||||
skip_cols.1 = 0;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,11 +279,11 @@ impl<const N: usize> DataColumns<N> {
|
||||||
self.columns[col]
|
self.columns[col]
|
||||||
.area()
|
.area()
|
||||||
.skip_rows(top_idx)
|
.skip_rows(top_idx)
|
||||||
.skip_cols(skip_cols.1)
|
.skip_cols(x_offset)
|
||||||
.take_cols(column_width),
|
.take_cols(column_width - x_offset),
|
||||||
);
|
);
|
||||||
bounds.add_x(column_width + 2);
|
bounds.add_x(column_width - x_offset + 2);
|
||||||
skip_cols.1 = 0;
|
x_offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.theme_config.theme {
|
match self.theme_config.theme {
|
||||||
|
|
Loading…
Reference in New Issue