summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2022-11-24 16:43:53 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2022-11-24 19:58:06 +0200
commit104352e5950598f4a659bd593d587910af8adc12 (patch)
tree14201aa906312b52edfa96813d1f4268efe1cf01
parentbd22f986f0c06f6dae535733d484aa89f610ed46 (diff)
downloadmeli-104352e5950598f4a659bd593d587910af8adc12.zip
Add table UI widget
-rw-r--r--src/components/contacts/contact_list.rs2
-rw-r--r--src/components/mail/listing.rs13
-rw-r--r--src/components/mail/listing/compact.rs352
-rw-r--r--src/components/mail/listing/conversations.rs15
-rw-r--r--src/components/mail/listing/plain.rs231
-rw-r--r--src/components/mail/listing/thread.rs310
-rw-r--r--src/components/mail/view/thread.rs10
-rw-r--r--src/components/utilities.rs13
-rw-r--r--src/components/utilities/pager.rs2
-rw-r--r--src/components/utilities/tables.rs318
-rw-r--r--src/terminal/cells.rs89
-rw-r--r--src/terminal/position.rs11
12 files changed, 705 insertions, 661 deletions
diff --git a/src/components/contacts/contact_list.rs b/src/components/contacts/contact_list.rs
index 52684cd9..8fc89e2b 100644
--- a/src/components/contacts/contact_list.rs
+++ b/src/components/contacts/contact_list.rs
@@ -46,7 +46,7 @@ pub struct ContactList {
new_cursor_pos: usize,
account_pos: usize,
length: usize,
- data_columns: DataColumns,
+ data_columns: DataColumns<4>,
initialized: bool,
theme_default: ThemeAttribute,
highlight_theme: ThemeAttribute,
diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs
index acba0dc7..6e75a44b 100644
--- a/src/components/mail/listing.rs
+++ b/src/components/mail/listing.rs
@@ -58,6 +58,7 @@ pub struct RowsState<T> {
pub entries: Vec<(T, EntryStrings)>,
pub all_threads: HashSet<ThreadHash>,
pub all_envelopes: HashSet<EnvelopeHash>,
+ pub row_attr_cache: HashMap<usize, ThemeAttribute>,
}
impl<T> RowsState<T> {
@@ -72,6 +73,7 @@ impl<T> RowsState<T> {
self.entries.clear();
self.all_threads.clear();
self.all_envelopes.clear();
+ self.row_attr_cache.clear();
}
#[inline(always)]
@@ -232,13 +234,6 @@ impl Default for Modifier {
}
}
-#[derive(Debug, Default, Clone)]
-pub struct DataColumns {
- pub columns: Box<[CellBuffer; 12]>,
- pub widths: [usize; 12], // widths of columns calculated in first draw and after size changes
- pub segment_tree: Box<[SegmentTree; 12]>,
-}
-
#[derive(Debug, Default)]
/// Save theme colors to avoid looking them up again and again from settings
struct ColorCache {
@@ -255,8 +250,6 @@ struct ColorCache {
odd_unseen: ThemeAttribute,
odd_highlighted: ThemeAttribute,
odd_selected: ThemeAttribute,
- attachment_flag: ThemeAttribute,
- thread_snooze_flag: ThemeAttribute,
tag_default: ThemeAttribute,
/* Conversations */
@@ -2078,7 +2071,7 @@ impl Listing {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
- pos_inc(upper_left!(area), (width!(area), 0)),
+ pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
bottom_right!(area),
),
context,
diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs
index 131ec097..b09f4986 100644
--- a/src/components/mail/listing/compact.rs
+++ b/src/components/mail/listing/compact.rs
@@ -168,7 +168,7 @@ pub struct CompactListing {
sortcmd: bool,
subsort: (SortField, SortOrder),
/// Cache current view.
- data_columns: DataColumns,
+ data_columns: DataColumns<4>,
rows_drawn: SegmentTree,
rows: RowsState<(ThreadHash, EnvelopeHash)>,
@@ -222,7 +222,7 @@ impl MailListingTrait for CompactListing {
} else {
if let Some(env_hashes) = self
.get_thread_under_cursor(self.cursor_pos.2)
- .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|v| v.clone()))
+ .and_then(|thread| self.rows.thread_to_env.get(&thread).cloned())
{
cursor_iter = Some(env_hashes.into_iter());
} else {
@@ -261,8 +261,6 @@ impl MailListingTrait for CompactListing {
odd_highlighted: crate::conf::value(context, "mail.listing.compact.odd_highlighted"),
even: crate::conf::value(context, "mail.listing.compact.even"),
odd: crate::conf::value(context, "mail.listing.compact.odd"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
@@ -394,6 +392,14 @@ impl MailListingTrait for CompactListing {
continue;
}
}
+ let row_attr = row_attr!(
+ self.color_cache,
+ self.length % 2 == 0,
+ threads.thread_ref(thread).unseen() > 0,
+ false,
+ false
+ );
+ self.rows.row_attr_cache.insert(self.length, row_attr);
let entry_strings = self.make_entry_string(&root_envelope, context, &threads, thread);
row_widths
@@ -451,6 +457,21 @@ impl MailListingTrait for CompactListing {
min_width.0 = self.length.saturating_sub(1).to_string().len();
+ self.data_columns.elasticities[0].set_rigid();
+ self.data_columns.elasticities[1].set_rigid();
+ self.data_columns.elasticities[2].set_grow(5, Some(35));
+ self.data_columns.elasticities[3].set_rigid();
+ self.data_columns
+ .cursor_config
+ .set_handle(true)
+ .set_even_odd_theme(
+ self.color_cache.even_highlighted,
+ self.color_cache.odd_highlighted,
+ );
+ self.data_columns
+ .theme_config
+ .set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
+
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
@@ -629,26 +650,27 @@ impl ListingTrait for CompactListing {
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
- self.draw_rows(
- context,
- top_idx,
- cmp::min(self.length.saturating_sub(1), top_idx + rows - 1),
- );
+ let end_idx = cmp::min(self.length.saturating_sub(1), top_idx + rows - 1);
+ self.draw_rows(context, top_idx, end_idx);
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
- for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
- if *idx >= self.length {
+ for &(idx, highlight) in &[(old_cursor_pos.2, false), (self.new_cursor_pos.2, true)] {
+ if idx >= self.length {
continue; //bounds check
}
- let new_area = (
- set_y(upper_left, get_y(upper_left) + (*idx % rows)),
- set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
- );
- self.highlight_line(grid, new_area, *idx, context);
+ let new_area = nth_row_area(area, idx % rows);
+ self.data_columns
+ .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
+ if highlight {
+ let row_attr = row_attr!(self.color_cache, idx % 2 == 0, false, true, false);
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ } else if let Some(row_attr) = self.rows.row_attr_cache.get(&idx) {
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ }
context.dirty_areas.push_back(new_area);
}
if !self.force_draw {
@@ -662,115 +684,37 @@ impl ListingTrait for CompactListing {
self.cursor_pos.2 = self.new_cursor_pos.2;
}
- let width = width!(area);
- self.data_columns.widths = Default::default();
- self.data_columns.widths[0] = self.data_columns.columns[0].size().0;
- self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* date*/
- self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* from */
- self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* subject */
-
- let min_col_width = std::cmp::min(
- 15,
- std::cmp::min(self.data_columns.widths[3], self.data_columns.widths[2]),
- );
- if self.data_columns.widths[0] + self.data_columns.widths[1] + 2 * min_col_width + 4 > width
- {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(2 * 2);
- self.data_columns.widths[2] = remainder / 6;
- } else {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(3 * 2);
- if min_col_width + self.data_columns.widths[3] > remainder {
- self.data_columns.widths[2] = min_col_width;
- }
- }
- for i in 0..3 {
- /* Set column widths to their maximum value width in the range
- * [top_idx, top_idx + rows]. By using a segment tree the query is O(logn), which is
- * great!
- */
- self.data_columns.widths[i] =
- self.data_columns.segment_tree[i].get_max(top_idx, top_idx + rows - 1) as usize;
- }
- if self.data_columns.widths.iter().sum::<usize>() > width {
- let diff = self.data_columns.widths.iter().sum::<usize>() - width;
- if self.data_columns.widths[2] > 2 * diff {
- self.data_columns.widths[2] -= diff;
- } else {
- self.data_columns.widths[2] = std::cmp::max(
- 15,
- self.data_columns.widths[2].saturating_sub((2 * diff) / 3),
- );
- }
- }
- clear_area(grid, area, self.color_cache.theme_default);
/* Page_no has changed, so draw new page */
- let mut x = get_x(upper_left);
- let mut flag_x = 0;
- for i in 0..4 {
- let column_width = self.data_columns.widths[i];
- if i == 3 {
- flag_x = x;
- }
- if column_width == 0 {
- continue;
- }
- copy_area(
- grid,
- &self.data_columns.columns[i],
- (set_x(upper_left, x), bottom_right),
- (
- (0, top_idx),
- (column_width.saturating_sub(1), self.length - 1),
- ),
- );
- x += column_width + 2; // + SEPARATOR
- if x > get_x(bottom_right) {
- break;
- }
- }
-
- let account = &context.accounts[&self.cursor_pos.0];
- let threads = account.collection.get_threads(self.cursor_pos.1);
- for r in 0..cmp::min(self.length - top_idx, rows) {
- if let Some(thread_hash) = self.get_thread_under_cursor(r + top_idx) {
- let row_attr = row_attr!(
- self.color_cache,
- (r + top_idx) % 2 == 0,
- threads.thread_ref(thread_hash).unseen() > 0,
- self.cursor_pos.2 == (r + top_idx),
- self.rows.is_thread_selected(thread_hash)
- );
- change_colors(
- grid,
- (
- pos_inc(upper_left, (0, r)),
- (flag_x.saturating_sub(1), get_y(upper_left) + r),
- ),
- row_attr.fg,
- row_attr.bg,
- );
- for x in flag_x..get_x(bottom_right) {
- grid[(x, get_y(upper_left) + r)].set_bg(row_attr.bg);
- }
+ _ = self
+ .data_columns
+ .recalc_widths((width!(area), height!(area)), top_idx);
+ clear_area(grid, area, self.color_cache.theme_default);
+ /* copy table columns */
+ self.data_columns
+ .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
+ /* apply each row colors separately */
+ for i in top_idx..(top_idx + height!(area)) {
+ if let Some(row_attr) = self.rows.row_attr_cache.get(&i) {
+ change_colors(grid, nth_row_area(area, i % rows), row_attr.fg, row_attr.bg);
}
}
- self.highlight_line(
+ /* highlight cursor */
+ let row_attr = row_attr!(
+ self.color_cache,
+ self.cursor_pos.2 % 2 == 0,
+ false,
+ true,
+ false
+ );
+ change_colors(
grid,
- (
- set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- ),
- self.cursor_pos.2,
- context,
+ nth_row_area(area, self.cursor_pos.2 % rows),
+ row_attr.fg,
+ row_attr.bg,
);
+ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length {
clear_area(
grid,
@@ -1050,43 +994,6 @@ impl CompactListing {
fn update_line(&mut self, context: &Context, env_hash: EnvelopeHash) {
let account = &context.accounts[&self.cursor_pos.0];
- let selected_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .selected_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_SELECTED_FLAG)
- .grapheme_width();
- let thread_snoozed_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .thread_snoozed_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_SNOOZED_FLAG)
- .grapheme_width();
- let unseen_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .unseen_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_UNSEEN_FLAG)
- .grapheme_width();
- let attachment_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .attachment_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_ATTACHMENT_FLAG)
- .grapheme_width();
-
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
@@ -1102,8 +1009,9 @@ impl CompactListing {
idx % 2 == 0,
thread.unseen() > 0,
false,
- false,
+ self.rows.is_thread_selected(thread_hash)
);
+ self.rows.row_attr_cache.insert(idx, row_attr);
let strings = self.make_entry_string(&envelope, context, &threads, thread_hash);
drop(envelope);
let columns = &mut self.data_columns.columns;
@@ -1204,25 +1112,6 @@ impl CompactListing {
for c in columns[3].row_iter(x..min_width.3, idx) {
columns[3][c].set_ch(' ').set_bg(row_attr.bg);
}
- /* Set fg color for flags */
- let mut x = 0;
- if self.rows.selection.get(&env_hash).cloned().unwrap_or(false) {
- x += selected_flag_len;
- }
- if thread.snoozed() {
- for x in x..(x + thread_snoozed_flag_len) {
- columns[3][(x, idx)].set_fg(self.color_cache.thread_snooze_flag.fg);
- }
- x += thread_snoozed_flag_len;
- }
- if thread.unseen() > 0 {
- x += unseen_flag_len;
- }
- if thread.has_attachments() {
- for x in x..(x + attachment_flag_len) {
- columns[3][(x, idx)].set_fg(self.color_cache.attachment_flag.fg);
- }
- }
*self.rows.entries.get_mut(idx).unwrap() = ((thread_hash, env_hash), strings);
self.rows_drawn.update(idx, 1);
}
@@ -1246,48 +1135,8 @@ impl CompactListing {
self.data_columns.columns[2].size().0,
self.data_columns.columns[3].size().0,
);
- let account = &context.accounts[&self.cursor_pos.0];
-
- let threads = account.collection.get_threads(self.cursor_pos.1);
- let selected_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .selected_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_SELECTED_FLAG)
- .grapheme_width();
- let thread_snoozed_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .thread_snoozed_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_SNOOZED_FLAG)
- .grapheme_width();
- let unseen_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .unseen_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_UNSEEN_FLAG)
- .grapheme_width();
- let attachment_flag_len = mailbox_settings!(
- context[self.cursor_pos.0][&self.cursor_pos.1]
- .listing
- .attachment_flag
- )
- .as_ref()
- .map(|s| s.as_str())
- .unwrap_or(super::DEFAULT_ATTACHMENT_FLAG)
- .grapheme_width();
-
- for (idx, ((thread_hash, root_env_hash), strings)) in self
+ for (idx, ((_thread_hash, root_env_hash), strings)) in self
.rows
.entries
.iter()
@@ -1306,14 +1155,7 @@ impl CompactListing {
panic!();
}
- let thread = threads.thread_ref(*thread_hash);
- let row_attr = row_attr!(
- self.color_cache,
- idx % 2 == 0,
- thread.unseen() > 0,
- self.cursor_pos.2 == idx,
- self.rows.selection[root_env_hash]
- );
+ let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = write_string_to_grid(
&idx.to_string(),
&mut self.data_columns.columns[0],
@@ -1428,33 +1270,6 @@ impl CompactListing {
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
}
- /* Set fg color for flags */
- let mut x = 0;
- if self
- .rows
- .selection
- .get(root_env_hash)
- .cloned()
- .unwrap_or(false)
- {
- x += selected_flag_len;
- }
- if thread.snoozed() {
- for x in x..(x + thread_snoozed_flag_len) {
- self.data_columns.columns[3][(x, idx)]
- .set_fg(self.color_cache.thread_snooze_flag.fg);
- }
- x += thread_snoozed_flag_len;
- }
- if thread.unseen() > 0 {
- x += unseen_flag_len;
- }
- if thread.has_attachments() {
- for x in x..(x + attachment_flag_len) {
- self.data_columns.columns[3][(x, idx)]
- .set_fg(self.color_cache.attachment_flag.fg);
- }
- }
}
}
@@ -1726,19 +1541,23 @@ impl Component for CompactListing {
}
if !self.rows.row_updates.is_empty() {
- while let Some(row) = self.rows.row_updates.pop() {
- self.update_line(context, row);
- let row: usize = self.rows.env_order[&row];
+ while let Some(env_hash) = self.rows.row_updates.pop() {
+ self.update_line(context, env_hash);
+ let row: usize = self.rows.env_order[&env_hash];
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
if row >= top_idx && row < top_idx + rows {
- let area = (
- set_y(upper_left, get_y(upper_left) + (row % rows)),
- set_y(bottom_right, get_y(upper_left) + (row % rows)),
+ let new_area = nth_row_area(area, row % rows);
+ self.data_columns.draw(
+ grid,
+ row,
+ self.cursor_pos.2,
+ grid.bounds_iter(new_area),
);
- self.highlight_line(grid, area, row, context);
- context.dirty_areas.push_back(area);
+ let row_attr = self.rows.row_attr_cache[&row];
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ context.dirty_areas.push_back(new_area);
}
}
if self.force_draw {
@@ -1842,11 +1661,11 @@ impl Component for CompactListing {
{
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
- } else {
- if let Some(thread_hash) = self.get_thread_under_cursor(self.cursor_pos.2) {
- self.rows
- .update_selection_with_thread(thread_hash, |e| *e = !*e);
- }
+ } else if let Some(thread_hash) =
+ self.get_thread_under_cursor(self.cursor_pos.2)
+ {
+ self.rows
+ .update_selection_with_thread(thread_hash, |e| *e = !*e);
}
return true;
}
@@ -1916,11 +1735,6 @@ impl Component for CompactListing {
),
even: crate::conf::value(context, "mail.listing.compact.even"),
odd: crate::conf::value(context, "mail.listing.compact.odd"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(
- context,
- "mail.listing.thread_snooze_flag",
- ),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs
index 2b05a7e1..c2d396cc 100644
--- a/src/components/mail/listing/conversations.rs
+++ b/src/components/mail/listing/conversations.rs
@@ -151,7 +151,7 @@ impl MailListingTrait for ConversationsListing {
} else {
if let Some(env_hashes) = self
.get_thread_under_cursor(self.cursor_pos.2)
- .and_then(|thread| self.rows.thread_to_env.get(&thread).map(|v| v.clone()))
+ .and_then(|thread| self.rows.thread_to_env.get(&thread).cloned())
{
cursor_iter = Some(env_hashes.into_iter());
} else {
@@ -187,8 +187,6 @@ impl MailListingTrait for ConversationsListing {
selected: crate::conf::value(context, "mail.listing.conversations.selected"),
unseen: crate::conf::value(context, "mail.listing.conversations.unseen"),
highlighted: crate::conf::value(context, "mail.listing.conversations.highlighted"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
..self.color_cache
};
@@ -1306,10 +1304,8 @@ impl Component for ConversationsListing {
{
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
- } else {
- if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
- self.rows.update_selection_with_thread(thread, |e| *e = !*e);
- }
+ } else if let Some(thread) = self.get_thread_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_thread(thread, |e| *e = !*e);
}
return true;
}
@@ -1440,11 +1436,6 @@ impl Component for ConversationsListing {
context,
"mail.listing.conversations.highlighted",
),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(
- context,
- "mail.listing.thread_snooze_flag",
- ),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
..self.color_cache
};
diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs
index c4296a4c..c54294c3 100644
--- a/src/components/mail/listing/plain.rs
+++ b/src/components/mail/listing/plain.rs
@@ -130,7 +130,7 @@ pub struct PlainListing {
subsort: (SortField, SortOrder),
rows: RowsState<(ThreadHash, EnvelopeHash)>,
/// Cache current view.
- data_columns: DataColumns,
+ data_columns: DataColumns<4>,
#[allow(clippy::type_complexity)]
search_job: Option<(String, JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>)>,
@@ -167,9 +167,9 @@ impl MailListingTrait for PlainListing {
.values()
.cloned()
.any(std::convert::identity);
- dbg!(is_selection_empty);
if is_selection_empty {
- return dbg!(self.get_env_under_cursor(self.cursor_pos.2))
+ return self
+ .get_env_under_cursor(self.cursor_pos.2)
.into_iter()
.collect::<_>();
}
@@ -205,8 +205,6 @@ impl MailListingTrait for PlainListing {
odd_highlighted: crate::conf::value(context, "mail.listing.plain.odd_highlighted"),
even_selected: crate::conf::value(context, "mail.listing.plain.even_selected"),
odd_selected: crate::conf::value(context, "mail.listing.plain.odd_selected"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
@@ -448,15 +446,19 @@ impl ListingTrait for PlainListing {
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
- for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
- if *idx >= self.length {
+ for &(idx, highlight) in &[(old_cursor_pos.2, false), (self.new_cursor_pos.2, true)] {
+ if idx >= self.length {
continue; //bounds check
}
- let new_area = (
- set_y(upper_left, get_y(upper_left) + (*idx % rows)),
- set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
- );
- self.highlight_line(grid, new_area, *idx, context);
+ let new_area = nth_row_area(area, idx % rows);
+ self.data_columns
+ .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
+ if highlight {
+ let row_attr = row_attr!(self.color_cache, idx % 2 == 0, false, true, false);
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ } else if let Some(row_attr) = self.rows.row_attr_cache.get(&idx) {
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ }
context.dirty_areas.push_back(new_area);
}
return;
@@ -468,107 +470,37 @@ impl ListingTrait for PlainListing {
self.cursor_pos.2 = self.new_cursor_pos.2;
}
- let width = width!(area);
- self.data_columns.widths = Default::default();
- self.data_columns.widths[0] = self.data_columns.columns[0].size().0;
- self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* date*/
- self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* from */
- self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* subject */
-
- let min_col_width = std::cmp::min(
- 15,
- std::cmp::min(self.data_columns.widths[3], self.data_columns.widths[2]),
- );
- if self.data_columns.widths[0] + self.data_columns.widths[1] + 2 * min_col_width + 4 > width
- {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(2 * 2);
- self.data_columns.widths[2] = remainder / 6;
- } else {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(3 * 2);
- if min_col_width + self.data_columns.widths[3] > remainder {
- self.data_columns.widths[2] = min_col_width;
- }
- }
- clear_area(grid, area, self.color_cache.theme_default);
/* Page_no has changed, so draw new page */
- let mut x = get_x(upper_left);
- let mut flag_x = 0;
- for i in 0..4 {
- let column_width = self.data_columns.widths[i];
- if i == 3 {
- flag_x = x;
- }
- if column_width == 0 {
- continue;
- }
- copy_area(
- grid,
- &self.data_columns.columns[i],
- (set_x(upper_left, x), bottom_right),
- (
- (0, top_idx),
- (column_width.saturating_sub(1), self.length - 1),
- ),
- );
- x += column_width + 2; // + SEPARATOR
- if x > get_x(bottom_right) {
- break;
- }
- }
- for r in 0..cmp::min(self.length - top_idx, rows) {
- let (fg_color, bg_color) = {
- let c = &self.data_columns.columns[0][(0, r + top_idx)];
- (c.fg(), c.bg())
- };
- change_colors(
- grid,
- (
- pos_inc(upper_left, (0, r)),
- (flag_x.saturating_sub(1), get_y(upper_left) + r),
- ),
- fg_color,
- bg_color,
- );
- for c in grid.row_iter(
- flag_x
- ..std::cmp::min(
- get_x(bottom_right),
- flag_x + 2 + self.data_columns.widths[3],
- ),
- get_y(upper_left) + r,
- ) {
- grid[c].set_bg(bg_color);
+ _ = self
+ .data_columns
+ .recalc_widths((width!(area), height!(area)), top_idx);
+ clear_area(grid, area, self.color_cache.theme_default);
+ /* copy table columns */
+ self.data_columns
+ .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
+ /* apply each row colors separately */
+ for i in top_idx..(top_idx + height!(area)) {
+ if let Some(row_attr) = self.rows.row_attr_cache.get(&i) {
+ change_colors(grid, nth_row_area(area, i % rows), row_attr.fg, row_attr.bg);
}
- change_colors(
- grid,
- (
- (
- flag_x + 2 + self.data_columns.widths[3],
- get_y(upper_left) + r,
- ),
- (get_x(bottom_right), get_y(upper_left) + r),
- ),
- fg_color,
- bg_color,
- );
}
- self.highlight_line(
+ /* highlight cursor */
+ let row_attr = row_attr!(
+ self.color_cache,
+ self.cursor_pos.2 % 2 == 0,
+ false,
+ true,
+ false
+ );
+ change_colors(
grid,
- (
- set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- ),
- self.cursor_pos.2,
- context,
+ nth_row_area(area, self.cursor_pos.2 % rows),
+ row_attr.fg,
+ row_attr.bg,
);
+ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length {
clear_area(
grid,
@@ -845,6 +777,14 @@ impl PlainListing {
continue;
}
}
+ let row_attr = row_attr!(
+ self.color_cache,
+ self.length % 2 == 0,
+ !envelope.is_seen(),
+ false,
+ false
+ );
+ self.rows.row_attr_cache.insert(self.length, row_attr);
let entry_strings = self.make_entry_string(&envelope, context);
min_width.1 = cmp::max(min_width.1, entry_strings.date.grapheme_width()); /* date */
@@ -868,6 +808,21 @@ impl PlainListing {
min_width.0 = self.length.saturating_sub(1).to_string().len();
+ self.data_columns.elasticities[0].set_rigid();
+ self.data_columns.elasticities[1].set_rigid();
+ self.data_columns.elasticities[2].set_grow(5, Some(35));
+ self.data_columns.elasticities[3].set_rigid();
+ self.data_columns
+ .cursor_config
+ .set_handle(true)
+ .set_even_odd_theme(
+ self.color_cache.even_highlighted,
+ self.color_cache.odd_highlighted,
+ );
+ self.data_columns
+ .theme_config
+ .set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
+
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
@@ -903,14 +858,7 @@ impl PlainListing {
panic!();
}
- let envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0].collection.get_env(i);
- let row_attr = row_attr!(
- self.color_cache,
- idx % 2 == 0,
- !envelope.is_seen(),
- false,
- false
- );
+ let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = write_string_to_grid(
&idx.to_string(),
@@ -998,17 +946,6 @@ impl PlainListing {
for c in columns[3].row_iter(x..min_width.3, idx) {
columns[3][c].set_bg(row_attr.bg).set_attrs(row_attr.attrs);
}
- /* Set fg color for flags */
- let mut x = 0;
- if self.rows.selection.get(&i).cloned().unwrap_or(false) {
- x += 1;
- }
- if !envelope.is_seen() {
- x += 1;
- }
- if envelope.has_attachments() {
- columns[3][(x, idx)].set_fg(self.color_cache.attachment_flag.fg);
- }
}
if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status();
@@ -1091,19 +1028,34 @@ impl Component for PlainListing {
if !self.rows.row_updates.is_empty() {
let (upper_left, bottom_right) = area;
- while let Some(row) = self.rows.row_updates.pop() {
- let row: usize = self.rows.env_order[&row];
+ while let Some(env_hash) = self.rows.row_updates.pop() {
+ let row: usize = self.rows.env_order[&env_hash];
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
if row >= top_idx && row <= top_idx + rows {
- let area = (
- set_y(upper_left, get_y(upper_left) + (row % rows)),
- set_y(bottom_right, get_y(upper_left) + (row % rows)),
+ let new_area = nth_row_area(area, row % rows);
+ self.data_columns.draw(
+ grid,
+ row,
+ self.cursor_pos.2,
+ grid.bounds_iter(new_area),
);
- self.highlight_line(grid, area, row, context);
- context.dirty_areas.push_back(area);
+ let envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0]
+ .collection
+ .get_env(env_hash);
+ let row_attr = row_attr!(
+ self.color_cache,
+ row % 2 == 0,
+ !envelope.is_seen(),
+ false,
+ self.rows.selection[&env_hash]
+ );
+ self.rows.row_attr_cache.insert(row, row_attr);
+
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ context.dirty_areas.push_back(new_area);
}
}
if self.force_draw {
@@ -1196,10 +1148,8 @@ impl Component for PlainListing {
{
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
- } else {
- if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
- self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
- }
+ } else if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
}
return true;
}
@@ -1243,11 +1193,6 @@ impl Component for PlainListing {
),
even_selected: crate::conf::value(context, "mail.listing.plain.even_selected"),
odd_selected: crate::conf::value(context, "mail.listing.plain.odd_selected"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(
- context,
- "mail.listing.thread_snooze_flag",
- ),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
@@ -1332,9 +1277,7 @@ impl Component for PlainListing {
.cloned()
.any(std::convert::identity) =>
{
- for v in self.rows.selection.values_mut() {
- *v = false;
- }
+ self.rows.clear_selection();
self.dirty = true;
return true;
}
diff --git a/src/components/mail/listing/thread.rs b/src/components/mail/listing/thread.rs
index 6129730d..2194aece 100644
--- a/src/components/mail/listing/thread.rs
+++ b/src/components/mail/listing/thread.rs
@@ -121,9 +121,9 @@ pub struct ThreadListing {
crate::jobs::JoinHandle<Result<SmallVec<[EnvelopeHash; 512]>>>,
)>,
- data_columns: DataColumns,
+ data_columns: DataColumns<5>,
rows_drawn: SegmentTree,
- rows: RowsState<(bool, bool, ThreadHash, EnvelopeHash)>,
+ rows: RowsState<(ThreadHash, EnvelopeHash)>,
/// If we must redraw on next redraw event
dirty: bool,
/// If `self.view` is focused or not.
@@ -189,8 +189,6 @@ impl MailListingTrait for ThreadListing {
odd_highlighted: crate::conf::value(context, "mail.listing.plain.odd_highlighted"),
even: crate::conf::value(context, "mail.listing.plain.even"),
odd: crate::conf::value(context, "mail.listing.plain.odd"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
@@ -358,15 +356,18 @@ impl MailListingTrait for ThreadListing {
); /* tags + subject */
self.rows.insert_thread(
threads.envelope_to_thread[&env_hash],
- (
- envelope.is_seen(),
- envelope.has_attachments(),
- threads.envelope_to_thread[&env_hash],
- env_hash,
- ),
+ (threads.envelope_to_thread[&env_hash], env_hash),
smallvec::smallvec![env_hash],
entry_strings,
);
+ let row_attr = row_attr!(
+ self.color_cache,
+ idx % 2 == 0,
+ !envelope.is_seen(),
+ false,
+ false,
+ );
+ self.rows.row_attr_cache.insert(idx, row_attr);
idx += 1;
} else {
continue;
@@ -389,6 +390,23 @@ impl MailListingTrait for ThreadListing {
}
}
min_width.0 = idx.saturating_sub(1).to_string().len();
+
+ self.data_columns.elasticities[0].set_rigid();
+ self.data_columns.elasticities[1].set_rigid();
+ self.data_columns.elasticities[2].set_grow(5, Some(35));
+ self.data_columns.elasticities[3].set_rigid();
+ self.data_columns.elasticities[4].set_rigid();
+ self.data_columns
+ .cursor_config
+ .set_handle(true)
+ .set_even_odd_theme(
+ self.color_cache.even_highlighted,
+ self.color_cache.odd_highlighted,
+ );
+ self.data_columns
+ .theme_config
+ .set_even_odd_theme(self.color_cache.even, self.color_cache.odd);
+
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, self.rows.len(), None, context);
@@ -496,41 +514,24 @@ impl ListingTrait for ThreadListing {
cmp::min(self.length.saturating_sub(1), top_idx + rows - 1),
);
- /*
- if !self.initialised {
- self.initialised = false;
- copy_area(
- grid,
- &self.content,
- area,
- ((0, top_idx), (MAX_COLS - 1, self.length)),
- );
- self.highlight_line(
- grid,
- (
- set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- ),
- self.cursor_pos.2,
- context,
- );
- context.dirty_areas.push_back(area);
- }
- */
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
- for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
- if *idx >= self.length {
+ for &(idx, highlight) in &[(old_cursor_pos.2, false), (self.new_cursor_pos.2, true)] {
+ if idx >= self.length {
continue; //bounds check
}
- let new_area = (
- set_y(upper_left, get_y(upper_left) + (*idx % rows)),
- set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
- );
- self.highlight_line(grid, new_area, *idx, context);
+ let new_area = nth_row_area(area, idx % rows);
+ self.data_columns
+ .draw(grid, idx, self.cursor_pos.2, grid.bounds_iter(new_area));
+ if highlight {
+ let row_attr = row_attr!(self.color_cache, idx % 2 == 0, false, true, false);
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ } else if let Some(row_attr) = self.rows.row_attr_cache.get(&idx) {
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
+ }
context.dirty_areas.push_back(new_area);
}
return;
@@ -544,148 +545,45 @@ impl ListingTrait for ThreadListing {
self.cursor_pos.2 = self.new_cursor_pos.2;
}
- let width = width!(area);
- self.data_columns.widths = Default::default();
- self.data_columns.widths[0] = self.data_columns.columns[0].size().0;
- self.data_columns.widths[1] = self.data_columns.columns[1].size().0; /* date*/
- self.data_columns.widths[2] = self.data_columns.columns[2].size().0; /* from */
- self.data_columns.widths[3] = self.data_columns.columns[3].size().0; /* flags */
- self.data_columns.widths[4] = self.data_columns.columns[4].size().0; /* subject */
-
- let min_col_width = std::cmp::min(
- 15,
- std::cmp::min(self.data_columns.widths[4], self.data_columns.widths[2]),
- );
- if self.data_columns.widths[0] + self.data_columns.widths[1] + 3 * min_col_width + 8 > width
- {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(4);
- self.data_columns.widths[2] = remainder / 6;
- self.data_columns.widths[4] =
- ((2 * remainder) / 3).saturating_sub(self.data_columns.widths[3]);
- } else {
- let remainder = width
- .saturating_sub(self.data_columns.widths[0])
- .saturating_sub(self.data_columns.widths[1])
- .saturating_sub(8);
- if min_col_width + self.data_columns.widths[4] > remainder {
- self.data_columns.widths[4] =
- remainder.saturating_sub(min_col_width + self.data_columns.widths[3]);
- self.data_columns.widths[2] = min_col_width;
- }
- }
- for &i in &[2, 4] {
- /* Set From and Subject column widths to their maximum value width in the range
- * [top_idx, top_idx + rows]. By using a segment tree the query is O(logn), which is
- * great!
- */
- self.data_columns.widths[i] =
- self.data_columns.segment_tree[i].get_max(top_idx, top_idx + rows) as usize;
- }
- if self.data_columns.widths.iter().sum::<usize>() > width {
- let diff = self.data_columns.widths.iter().sum::<usize>() - width;
- if self.data_columns.widths[2] > 2 * diff {
- self.data_columns.widths[2] -= diff;
- } else {
- self.data_columns.widths[2] = std::cmp::max(
- 15,
- self.data_columns.widths[2].saturating_sub((2 * diff) / 3),
- );
- self.data_columns.widths[4] = std::cmp::max(
- 15,
- self.data_columns.widths[4].saturating_sub(diff / 3 + diff % 3),
- );
- }
- }
+ _ = self
+ .data_columns
+ .recalc_widths((width!(area), height!(area)), top_idx);
clear_area(grid, area, self.color_cache.theme_default);
/* Page_no has changed, so draw new page */
- let mut x = get_x(upper_left);
- let mut flag_x = 0;
- for i in 0..self.data_columns.columns.len() {
- let column_width = self.data_columns.columns[i].size().0;
- if i == 3 {
- flag_x = x;
- }
- if self.data_columns.widths[i] == 0 {
- continue;
- }
- copy_area(
- grid,
- &self.data_columns.columns[i],
- (
- set_x(upper_left, x),
- set_x(
- bottom_right,
- std::cmp::min(get_x(bottom_right), x + (self.data_columns.widths[i])),
- ),
- ),
- (
- (0, top_idx),
- (column_width.saturating_sub(1), self.length - 1),
- ),
- );
- x += self.data_columns.widths[i] + 2; // + SEPARATOR
- if x > get_x(bottom_right) {
- break;
- }
- }
+ self.data_columns
+ .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
- for r in 0..cmp::min(self.length - top_idx, rows) {
- let (fg_color, bg_color) = {
- let c = &self.data_columns.columns[0][(0, r + top_idx)];
- if let Some(env_hash) = self.get_env_under_cursor(r + top_idx) {
- if self.rows.selection[&env_hash] {
- (c.fg(), self.color_cache.selected.bg)
- } else {
- (c.fg(), c.bg())
- }
- } else {
- (c.fg(), c.bg())
- }
- };
- change_colors(
- grid,
- (
- pos_inc(upper_left, (0, r)),
- (flag_x.saturating_sub(1), get_y(upper_left) + r),
- ),
- fg_color,
- bg_color,
- );
- for x in flag_x
- ..std::cmp::min(
- get_x(bottom_right),
- flag_x + 2 + self.data_columns.widths[3],
- )
- {
- grid[(x, get_y(upper_left) + r)].set_bg(bg_color);
+ /* Page_no has changed, so draw new page */
+ _ = self
+ .data_columns
+ .recalc_widths((width!(area), height!(area)), top_idx);
+ clear_area(grid, area, self.color_cache.theme_default);
+ /* copy table columns */
+ self.data_columns
+ .draw(grid, top_idx, self.cursor_pos.2, grid.bounds_iter(area));
+ /* apply each row colors separately */
+ for i in top_idx..(top_idx + height!(area)) {
+ if let Some(row_attr) = self.rows.row_attr_cache.get(&i) {
+ change_colors(grid, nth_row_area(area, i % rows), row_attr.fg, row_attr.bg);
}
- change_colors(
- grid,
- (
- (
- flag_x + 2 + self.data_columns.widths[3],
- get_y(upper_left) + r,
- ),
- (get_x(bottom_right), get_y(upper_left) + r),
- ),
- fg_color,
- bg_color,
- );
}
- self.highlight_line(
+ /* highlight cursor */
+ let row_attr = row_attr!(
+ self.color_cache,
+ self.cursor_pos.2 % 2 == 0,
+ false,
+ true,
+ false
+ );
+ change_colors(
grid,
- (
- set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- ),
- self.cursor_pos.2,
- context,
+ nth_row_area(area, self.cursor_pos.2 % rows),
+ row_attr.fg,
+ row_attr.bg,
);
+ /* clear gap if available height is more than count of entries */
if top_idx + rows > self.length {
clear_area(
grid,
@@ -697,24 +595,6 @@ impl ListingTrait for ThreadListing {
);
}
context.dirty_areas.push_back(area);
- /*
- copy_area(
- grid,
- &self.content,
- area,
- ((0, top_idx), (MAX_COLS - 1, self.length)),
- );
- self.highlight_line(
- grid,
- (
- set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
- ),
- self.cursor_pos.2,
- context,
- );
- context.dirty_areas.push_back(area);
- */
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
@@ -994,7 +874,7 @@ impl ThreadListing {
self.data_columns.columns[4].size().0,
);
- for (idx, ((is_seen, has_attachments, _thread_hash, env_hash), strings)) in self
+ for (idx, ((_thread_hash, env_hash), strings)) in self
.rows
.entries
.iter()
@@ -1013,13 +893,7 @@ impl ThreadListing {
panic!();
}
- let row_attr = row_attr!(
- self.color_cache,
- idx % 2 == 0,
- !*is_seen,
- self.cursor_pos.2 == idx,
- self.rows.selection[&env_hash],
- );
+ let row_attr = self.rows.row_attr_cache[&idx];
let (x, _) = write_string_to_grid(
&idx.to_string(),
&mut self.data_columns.columns[0],
@@ -1139,9 +1013,6 @@ impl ThreadListing {
.set_bg(row_attr.bg)
.set_attrs(row_attr.attrs);
}
- if *has_attachments {
- self.data_columns.columns[3][(0, idx)].set_fg(self.color_cache.attachment_flag.fg);
- }
}
}
}
@@ -1315,15 +1186,29 @@ impl Component for ThreadListing {
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
let top_idx = page_no * rows;
- while let Some(row) = self.rows.row_updates.pop() {
- let row: usize = self.rows.env_order[&row];
+ while let Some(env_hash) = self.rows.row_updates.pop() {
+ let row: usize = self.rows.env_order[&env_hash];
if row >= top_idx && row <= top_idx + rows {
- let new_area = (
- set_y(upper_left, get_y(upper_left) + (row % rows)),
- set_y(bottom_right, get_y(upper_left) + (row % rows)),
+ let new_area = nth_row_area(area, row % rows);
+ self.data_columns.draw(
+ grid,
+ row,
+ self.cursor_pos.2,
+ grid.bounds_iter(new_area),
+ );
+ let envelope: EnvelopeRef = context.accounts[&self.cursor_pos.0]
+ .collection
+ .get_env(env_hash);
+ let row_attr = row_attr!(
+ self.color_cache,
+ row % 2 == 0,
+ !envelope.is_seen(),
+ false,
+ self.rows.selection[&env_hash]
);
- self.highlight_line(grid, new_area, row, context);
+ self.rows.row_attr_cache.insert(row, row_attr);
+ change_colors(grid, new_area, row_attr.fg, row_attr.bg);
context.dirty_areas.push_back(new_area);
}
}
@@ -1486,11 +1371,6 @@ impl Component for ThreadListing {
),
even: crate::conf::value(context, "mail.listing.plain.even"),
odd: crate::conf::value(context, "mail.listing.plain.odd"),
- attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"),
- thread_snooze_flag: crate::conf::value(
- context,
- "mail.listing.thread_snooze_flag",
- ),
tag_default: crate::conf::value(context, "mail.listing.tag_default"),
theme_default: crate::conf::value(context, "theme_default"),
..self.color_cache
@@ -1559,7 +1439,7 @@ impl Component for ThreadListing {
}
self.rows.rename_env(*old_hash, *new_hash);
if let Some(&row) = self.rows.env_order.get(new_hash) {
- (self.rows.entries[row].0).3 = *new_hash;
+ (self.rows.entries[row].0).1 = *new_hash;
}
self.dirty = true;
@@ -1623,10 +1503,8 @@ impl Component for ThreadListing {
{
if self.modifier_active && self.modifier_command.is_none() {
self.modifier_command = Some(Modifier::default());
- } else {
- if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
- self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
- }
+ } else if let Some(env_hash) = self.get_env_under_cursor(self.cursor_pos.2) {
+ self.rows.update_selection_with_env(env_hash, |e| *e = !*e);
}
return true;
}
diff --git a/src/components/mail/view/thread.rs b/src/components/mail/view/thread.rs
index 34001386..63642893 100644
--- a/src/components/mail/view/thread.rs
+++ b/src/components/mail/view/thread.rs
@@ -564,7 +564,10 @@ impl ThreadView {
if rows < visibles.len() {
ScrollBar::default().set_show_arrows(true).draw(
grid,
- (pos_inc(upper_left!(area), (width!(area), 0)), bottom_right),
+ (
+ pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
+ bottom_right,
+ ),
context,
2 * self.cursor_pos,
rows,
@@ -618,7 +621,10 @@ impl ThreadView {
if rows < visibles.len() {
ScrollBar::default().set_show_arrows(true).draw(
grid,
- (pos_inc(upper_left!(area), (width!(area), 0)), bottom_right),
+ (
+ pos_inc(upper_left!(area), (width!(area).saturating_sub(1), 0)),
+ bottom_right,
+ ),
context,
2 * self.cursor_pos,
rows,
diff --git a/src/components/utilities.rs b/src/components/utilities.rs
index d1ad816a..55508cf4 100644
--- a/src/components/utilities.rs
+++ b/src/components/utilities.rs
@@ -36,6 +36,9 @@ pub use self::layouts::*;
mod dialogs;
pub use self::dialogs::*;
+mod tables;
+pub use self::tables::*;
+
use crate::jobs::JobId;
use std::collections::HashSet;
@@ -1048,7 +1051,10 @@ impl Component for Tabbed {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
- pos_inc(upper_left!(inner_area), (width!(inner_area), 0)),
+ pos_inc(
+ upper_left!(inner_area),
+ (width!(inner_area).saturating_sub(1), 0),
+ ),
bottom_right!(inner_area),
),
context,
@@ -1300,7 +1306,10 @@ impl Component for Tabbed {
ScrollBar::default().set_show_arrows(true).draw(
grid,
(
- pos_inc(upper_left!(inner_area), (width!(inner_area), 0)),
+ pos_inc(
+ upper_left!(inner_area),
+ (width!(inner_area).saturating_sub(1), 0),
+ ),
bottom_right!(inner_area),
),
context,
diff --git a/src/components/utilities/pager.rs b/src/components/utilities/pager.rs
index 9f24543e..546cd1a6 100644
--- a/src/components/utilities/pager.rs
+++ b/src/components/utilities/pager.rs
@@ -340,7 +340,7 @@ impl Pager {
.text_lines
.iter()
.skip(self.cursor.1)
- .take(height!(area) + 1)
+ .take(height!(area))
{
write_string_to_grid(
l,
diff --git a/src/components/utilities/tables.rs b/src/components/utilities/tables.rs
new file mode 100644
index 00000000..25ce5492
--- /dev/null
+++ b/src/components/utilities/tables.rs
@@ -0,0 +1,318 @@
+/*
+ * meli
+ *
+ * Copyright 2017-2022 Manos Pitsidianakis
+ *
+ * This file is part of meli.
+ *
+ * meli is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * meli is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with meli. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*! UI components to display tabular lists. */
+use super::*;
+use crate::segment_tree::SegmentTree;
+use std::mem::MaybeUninit;
+
+#[derive(Debug, Default, Copy, Clone)]
+pub enum ColumnElasticity {
+ #[default]
+ Rigid,
+ Grow {
+ min: usize,
+ max: Option<usize>,
+ },
+}
+
+impl ColumnElasticity {
+ pub fn set_rigid(&mut self) {
+ *self = Self::Rigid;
+ }
+ pub fn set_grow(&mut self, min: usize, max: Option<usize>) {
+ *self = Self::Grow { min, max };
+ }
+}
+
+/*#[derive(Debug, Default, Clone)]
+pub enum TableRowFormat {
+ #[default]
+ None,
+ Fill(FormatTag),
+ Range {
+ start: usize,
+ end: usize,
+ val: FormatTag,
+ },
+}
+
+impl TableRowFormat {
+ pub const SELECTED: u8 = 0;
+ pub const UNREAD: u8 = 1;
+}
+*/
+
+#[derive(Debug, Default, Clone)]
+pub struct TableThemeConfig {
+ pub theme: TableTheme,
+ //pub row_formats: HashMap<usize, SmallVec<[(u8, TableRowFormat); 6]>>,
+}
+
+impl TableThemeConfig {
+ pub fn set_single_theme(&mut self, value: ThemeAttribute) -> &mut Self {
+ self.theme = TableTheme::Single(value);
+ self
+ }
+
+ pub fn set_even_odd_theme(&mut self, even: ThemeAttribute, odd: ThemeAttribute) -> &mut Self {
+ self.theme = TableTheme::EvenOdd { even, odd };
+ self
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum TableTheme {
+ Single(ThemeAttribute),
+ EvenOdd {
+ even: ThemeAttribute,
+ odd: ThemeAttribute,
+ },
+}
+
+impl Default for TableTheme {
+ fn default() -> Self {
+ Self::Single(ThemeAttribute::default())
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct TableCursorConfig {
+ pub handle: bool,
+ pub theme: TableTheme,
+}
+
+impl TableCursorConfig {
+ pub fn set_handle(&mut self, value: bool) -> &mut Self {
+ self.handle = value;
+ self
+ }
+
+ pub fn set_single_theme(&mut self, value: ThemeAttribute) -> &mut Self {
+ self.theme = TableTheme::Single(value);
+ self
+ }
+
+ pub fn set_even_odd_theme(&mut self, even: ThemeAttribute, odd: ThemeAttribute) -> &mut Self {
+ self.theme = TableTheme::EvenOdd { even, odd };
+ self
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct DataColumns<const N: usize> {
+ pub cursor_config: TableCursorConfig,
+ pub theme_config: TableThemeConfig,
+ pub columns: Box<[CellBuffer; N]>,
+ /// widths of columns calculated in first draw and after size changes
+ pub widths: [usize; N],
+ pub elasticities: [ColumnElasticity; N],
+ pub x_offset: usize,
+ pub width_accum: usize,
+ pub segment_tree: Box<[SegmentTree; N]>,
+}
+
+// Workaround because Default derive doesn't work for const generic array lengths yet.
+impl<const N: usize> Default for DataColumns<N> {
+ fn default() -> Self {
+ fn init_array<T, const N: usize>(cl: impl Fn() -> T) -> [T; N] {
+ // https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element
+ let mut data: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
+ for elem in &mut data[..] {
+ elem.write(cl());
+ }
+ let ptr = &data as *const [MaybeUninit<T>; N];
+ std::mem::forget(data);
+ unsafe { (ptr as *const [T; N]).read() }
+ }
+ Self {
+ cursor_config: TableCursorConfig::default(),
+ theme_config: TableThemeConfig::default(),
+ columns: Box::new(init_array(CellBuffer::default)),
+ widths: [0_usize; N],
+ elasticities: [ColumnElasticity::default(); N],
+ x_offset: 0,
+ width_accum: 0,
+ segment_tree: Box::new(init_array(SegmentTree::default)),
+ }
+ }
+}
+
+impl<const N: usize> DataColumns<N> {
+ pub fn recalc_widths(
+ &mut self,
+ (screen_width, screen_height): (usize, usize),
+ top_idx: usize,
+ ) -> usize {
+ let mut width_accum = 0;
+ let mut growees = 0;
+ let mut growees_max = 0;
+ let grow_minmax = None;
+ for i in 0..N {
+ if screen_height == 0 {
+ self.widths[i] = 0;
+ continue;
+ }
+ self.widths[i] =
+ self.segment_tree[i].get_max(top_idx, top_idx + screen_height - 1) as usize;
+ if self.widths[i] == 0 {
+ self.widths[i] = self.columns[i].cols;
+ }
+ match self.elasticities[i] {
+ ColumnElasticity::Rigid => {}
+ ColumnElasticity::Grow {
+ min,
+ max: Some(max),
+ } => {
+ self.widths[i] = std::cmp::max(min, std::cmp::min(max, self.widths[i]));
+ growees += 1;
+ }
+ ColumnElasticity::Grow { min, max: None } => {
+ self.widths[i] = std::cmp::max(min, self.widths[i]);
+ growees += 1;
+ growees_max += 1;
+ }
+ }
+ width_accum += self.widths[i];
+ }
+ // add column gaps
+ width_accum += 2 * N.saturating_sub(1);
+ debug_assert!(growees >= growees_max);
+ debug_assert!(grow_minmax.is_none() || growees_max > 0);
+ if width_accum >= screen_width || screen_height == 0 || screen_width == 0 || growees == 0 {
+ self.width_accum = width_accum;
+ return width_accum;
+ }
+ let distribute = screen_width - width_accum;
+ let maxmins = growees_max * grow_minmax.unwrap_or(0);
+ let part = if maxmins != 0 && growees_max < growees {
+ distribute.saturating_sub(maxmins) / (growees - growees_max)
+ } else if maxmins != 0 {
+ distribute.saturating_sub(maxmins) / growees_max + grow_minmax.unwrap_or(0)
+ } else {
+ distribute / growees
+ };
+
+ for i in 0..N {
+ match self.elasticities[i] {
+ ColumnElasticity::Rigid => {}
+ ColumnElasticity::Grow {
+ min: _,
+ max: Some(_),
+ } => {}
+ ColumnElasticity::Grow { min: _, max: None } => {
+ self.widths[i] += part;
+ width_accum += part;
+ }
+ }
+ }
+ self.width_accum = width_accum;
+ width_accum
+ }
+
+ pub fn draw(
+ &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 (width, height) = (width!(total_area), height!(total_area));
+ 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;
+ break;
+ }
+ start_col += 1;
+ }
+
+ for col in skip_cols.0..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;
+ continue;
+ }
+
+ let mut column_area = bounds.add_x(column_width + 2);
+ copy_area(
+ grid,
+ &self.columns[col],
+ column_area.area(),
+ (
+ (skip_cols.1, top_idx),
+ (
+ column_width.saturating_sub(1),
+ self.columns[col].rows.saturating_sub(1),
+ ),
+ ),
+ );
+ let gap_area = column_area.add_x(column_width);
+ match self.theme_config.theme {
+ TableTheme::Single(row_attr) => {
+ change_colors(grid, gap_area.area(), row_attr.fg, row_attr.bg);
+ }
+ TableTheme::EvenOdd { even, odd } => {
+ change_colors(grid, gap_area.area(), even.fg, even.bg);
+ let mut top_idx = top_idx;
+ for row in gap_area {
+ if top_idx % 2 != 0 {
+ change_colors(grid, row.area(), odd.fg, odd.bg);
+ }
+ top_idx += 1;
+ }
+ }
+ };
+
+ skip_cols.1 = 0;
+ }
+ if self.cursor_config.handle && (top_idx..(top_idx + height)).contains(&cursor_pos) {
+ let offset = cursor_pos - top_idx;
+ let row_attr = match self.cursor_config.theme {
+ TableTheme::Single(attr) => attr,
+ TableTheme::EvenOdd { even, odd: _ } if cursor_pos % 2 == 0 => even,
+ TableTheme::EvenOdd { even: _, odd } => odd,
+ };
+
+ change_colors(
+ grid,
+ (
+ pos_inc(upper_left!(total_area), (0, offset)),
+ pos_inc(upper_left!(total_area), (width, offset)),
+ ),
+ row_attr.fg,
+ row_attr.bg,
+ );
+ }
+ }
+}
diff --git a/src/terminal/cells.rs b/src/terminal/cells.rs
index 914cbe56..20f239e8 100644
--- a/src/terminal/cells.rs
+++ b/src/terminal/cells.rs
@@ -26,6 +26,7 @@
use super::{position::*, Color};
use crate::state::Context;
+use crate::ThemeAttribute;
use melib::text_processing::wcwidth;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@@ -57,9 +58,9 @@ pub struct ScrollRegion {
/// index, `Cellbuffer[y][x]`, corresponds to a column within a row and thus the x-axis.
#[derive(Clone, PartialEq, Eq)]
pub struct CellBuffer {
- cols: usize,
- rows: usize,
- buf: Vec<Cell>,
+ pub cols: usize,
+ pub rows: usize,
+ pub buf: Vec<Cell>,
pub default_cell: Cell,
/// ASCII-only flag.
pub ascii_drawing: bool,
@@ -357,6 +358,8 @@ impl CellBuffer {
/// See `BoundsIterator` documentation.
pub fn bounds_iter(&self, area: Area) -> BoundsIterator {
BoundsIterator {
+ width: width!(area),
+ height: height!(area),
rows: std::cmp::min(self.rows.saturating_sub(1), get_y(upper_left!(area)))
..(std::cmp::min(self.rows, get_y(bottom_right!(area)) + 1)),
cols: (
@@ -373,9 +376,14 @@ impl CellBuffer {
row,
col: std::cmp::min(self.cols.saturating_sub(1), bounds.start)
..(std::cmp::min(self.cols, bounds.end)),
+ _width: bounds.len(),
}
} else {
- RowIterator { row, col: 0..0 }
+ RowIterator {
+ row,
+ col: 0..0,
+ _width: 0,
+ }
}
}
@@ -1247,8 +1255,10 @@ pub fn clear_area(grid: &mut CellBuffer, area: Area, attributes: crate::conf::Th
/// grid[c].set_ch('w');
/// }
/// ```
+#[derive(Debug)]
pub struct RowIterator {
row: usize,
+ _width: usize,
col: std::ops::Range<usize>,
}
@@ -1263,17 +1273,64 @@ pub struct RowIterator {
/// }
/// }
/// ```
+#[derive(Clone, Debug)]
pub struct BoundsIterator {
rows: std::ops::Range<usize>,
+ pub width: usize,
+ pub height: usize,
cols: (usize, usize),
}
+impl BoundsIterator {
+ const EMPTY: Self = BoundsIterator {
+ rows: 0..0,
+ width: 0,
+ height: 0,
+ cols: (0, 0),
+ };
+
+ pub fn area(&self) -> Area {
+ (
+ (self.cols.0, self.rows.start),
+ (
+ std::cmp::max(self.cols.0, self.cols.1.saturating_sub(1)),
+ std::cmp::max(self.rows.start, self.rows.end.saturating_sub(1)),
+ ),
+ )
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.width == 0 || self.height == 0 || self.rows.len() == 0
+ }
+
+ pub fn add_x(&mut self, x: usize) -> Self {
+ if x == 0 {
+ return Self::EMPTY;
+ }
+
+ let ret = Self {
+ rows: self.rows.clone(),
+ width: self.width.saturating_sub(x),
+ height: self.height,
+ cols: self.cols,
+ };
+ if self.cols.0 + x < self.cols.1 && self.width > x {
+ self.cols.0 += x;
+ self.width -= x;
+ return ret;
+ }
+ *self = Self::EMPTY;
+ ret
+ }
+}
+
impl Iterator for BoundsIterator {
type Item = RowIterator;
fn next(&mut self) -> Option<Self::Item> {
if let Some(next_row) = self.rows.next() {
Some(RowIterator {
row: next_row,
+ _width: self.width,
col: self.cols.0..self.cols.1,
})
} else {
@@ -1305,6 +1362,10 @@ impl RowIterator {
self
}
}
+
+ pub fn area(&self) -> Area {
+ ((self.col.start, self.row), (self.col.end, self.row))
+ }
}
pub use boundaries::create_box;
@@ -1739,6 +1800,26 @@ impl core::cmp::PartialOrd for FormatTag {
}
}
+impl From<ThemeAttribute> for FormatTag {
+ fn from(val: ThemeAttribute) -> Self {
+ let ThemeAttribute { fg, bg, attrs, .. } = val;
+ Self {
+ fg: Some(fg),
+ bg: Some(bg),
+ attrs: Some(attrs),
+ priority: 0,
+ }
+ }
+}
+
+impl FormatTag {
+ #[inline(always)]
+ pub fn set_priority(mut self, new_val: u8) -> Self {
+ self.priority = new_val;
+ self
+ }
+}
+
#[derive(Debug, Copy, Hash, Clone, PartialEq, Eq)]
pub enum WidgetWidth {
Unset,
diff --git a/src/terminal/position.rs b/src/terminal/position.rs
index 9c93a0b0..8f62da86 100644
--- a/src/terminal/position.rs
+++ b/src/terminal/position.rs
@@ -76,6 +76,7 @@ macro_rules! height {
($a:expr) => {
($crate::get_y($crate::bottom_right!($a)))
.saturating_sub($crate::get_y($crate::upper_left!($a)))
+ + 1
};
}
@@ -93,6 +94,7 @@ macro_rules! width {
($a:expr) => {
($crate::get_x($crate::bottom_right!($a)))
.saturating_sub($crate::get_x($crate::upper_left!($a)))
+ + 1
};
}
@@ -252,3 +254,12 @@ pub fn place_in_area(area: Area, (width, height): (usize, usize), upper: bool, l
),
)
}
+
+#[inline(always)]
+/// Get `n`th row of `area` or its last one.
+pub fn nth_row_area(area: Area, n: usize) -> Area {
+ let (upper_left, bottom_right) = area;
+ let (_, max_y) = bottom_right;
+ let y = std::cmp::min(max_y, get_y(upper_left) + n);
+ (set_y(upper_left, y), set_y(bottom_right, y))
+}