summaryrefslogtreecommitdiffstats
path: root/ui/src
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-04-07 00:09:10 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-06-10 19:40:43 +0300
commite3d0ad9170a2619a55e3f210cf4d5796ba71cd56 (patch)
treee7a89694a0e77a0569fecf6817bfd06ce15ef35e /ui/src
parent4be1b520891a1dc8037981389c0c4afe46e507fe (diff)
ui: fix paging in ThreadView
Diffstat (limited to 'ui/src')
-rw-r--r--ui/src/components/mail/view/thread.rs377
-rw-r--r--ui/src/terminal/grapheme_clusters.rs8
2 files changed, 177 insertions, 208 deletions
diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs
index f3fd20f3..d04596eb 100644
--- a/ui/src/components/mail/view/thread.rs
+++ b/ui/src/components/mail/view/thread.rs
@@ -30,9 +30,9 @@ struct ThreadEntry {
indentation: usize,
msg_hash: EnvelopeHash,
seen: bool,
-
- hidden: bool,
dirty: bool,
+ hidden: bool,
+ heading: String,
}
#[derive(Debug, Default)]
@@ -42,12 +42,13 @@ pub struct ThreadView {
expanded_pos: usize,
new_expanded_pos: usize,
reversed: bool,
- dirty: bool,
coordinates: (usize, usize, usize),
mailview: MailView,
show_mailview: bool,
entries: Vec<ThreadEntry>,
visible_entries: Vec<Vec<usize>>,
+
+ dirty: bool,
content: CellBuffer,
initiated: bool,
}
@@ -92,6 +93,9 @@ impl StackVec {
fn len(&self) -> usize {
self.len
}
+ fn is_empty(&self) -> bool {
+ self.len == 0
+ }
}
impl Index<usize> for StackVec {
@@ -120,7 +124,6 @@ impl ThreadView {
) -> Self {
let mut view = ThreadView {
reversed: false,
- dirty: true,
initiated: false,
coordinates,
mailview: MailView::default(),
@@ -128,6 +131,7 @@ impl ThreadView {
entries: Vec::new(),
cursor_pos: 1,
new_cursor_pos: 0,
+ dirty: true,
..Default::default()
};
view.initiate(expanded_idx, context);
@@ -138,6 +142,7 @@ impl ThreadView {
if self.entries.is_empty() {
return;
}
+
let old_focused_entry = if self.entries.len() > self.cursor_pos {
Some(self.entries.remove(self.cursor_pos))
} else {
@@ -152,6 +157,7 @@ impl ThreadView {
let expanded_pos = self.expanded_pos;
self.initiate(Some(expanded_pos), context);
+
if let Some(old_focused_entry) = old_focused_entry {
if let Some(new_entry_idx) = self.entries.iter().position(|e| {
e.msg_hash == old_focused_entry.msg_hash
@@ -205,16 +211,14 @@ impl ThreadView {
let height = 2 * self.entries.len() + 1;
let mut width = 0;
- let mut strings: Vec<String> = Vec::with_capacity(self.entries.len());
-
let mut highlight_reply_subjects: Vec<Option<usize>> =
Vec::with_capacity(self.entries.len());
- for e in &self.entries {
+ for e in &mut self.entries {
let envelope: &Envelope = &mailbox.collection[&e.msg_hash];
let thread_node = &threads.thread_nodes()[e.index.1];
let string = if thread_node.show_subject() {
let subject = envelope.subject();
- highlight_reply_subjects.push(Some(subject.len()));
+ highlight_reply_subjects.push(Some(subject.grapheme_width()));
format!(
" {}{} - {} {}",
" ".repeat(e.index.0 * 4),
@@ -231,11 +235,8 @@ impl ThreadView {
envelope.field_from_to_string(),
)
};
- strings.push(string);
- width = cmp::max(
- width,
- e.index.0 * 4 + strings.last().as_ref().unwrap().len() + 2,
- );
+ e.heading = string;
+ width = cmp::max(width, e.index.0 * 4 + e.heading.grapheme_width() + 2);
}
let mut content = CellBuffer::new(width, height, Cell::default());
if self.reversed {
@@ -260,7 +261,7 @@ impl ThreadView {
}
}
write_string_to_grid(
- &strings[y],
+ &e.heading,
&mut content,
if e.seen {
Color::Default
@@ -272,11 +273,14 @@ impl ThreadView {
} else {
Color::Byte(251)
},
- ((e.index.0 * 4 + 1, 2 * y), (width - 1, height - 1)),
+ (
+ (e.index.0 * 4 + 1, 2 * y),
+ (e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1),
+ ),
true,
);
if let Some(len) = highlight_reply_subjects[y] {
- let index = e.index.0 * 4 + 1 + strings[y].len() - len;
+ let index = e.index.0 * 4 + 1 + e.heading.grapheme_width() - len;
let area = ((index, 2 * y), (width - 2, 2 * y));
let fg_color = Color::Byte(33);
let bg_color = Color::Default;
@@ -312,7 +316,7 @@ impl ThreadView {
}
}
write_string_to_grid(
- &strings[y],
+ &e.heading,
&mut content,
if e.seen {
Color::Default
@@ -324,11 +328,14 @@ impl ThreadView {
} else {
Color::Byte(251)
},
- ((e.index.0 * 4 + 1, 2 * y), (width - 1, height - 1)),
- true,
+ (
+ (e.index.0 * 4 + 1, 2 * y),
+ (e.index.0 * 4 + e.heading.grapheme_width() + 1, height - 1),
+ ),
+ false,
);
if let Some(len) = highlight_reply_subjects[y] {
- let index = e.index.0 * 4 + 1 + strings[y].len() - len;
+ let index = e.index.0 * 4 + 1 + e.heading.grapheme_width() - len;
let area = ((index, 2 * y), (width - 2, 2 * y));
let fg_color = Color::Byte(33);
let bg_color = Color::Default;
@@ -363,13 +370,19 @@ impl ThreadView {
indentation: ind,
msg_hash,
seen,
- hidden: false,
dirty: true,
+ hidden: false,
+ heading: String::new(),
}
}
fn highlight_line(&self, grid: &mut CellBuffer, dest_area: Area, src_area: Area, idx: usize) {
- if idx == self.current_pos() {
+ let visibles: Vec<&usize> = self
+ .visible_entries
+ .iter()
+ .flat_map(|ref v| v.iter())
+ .collect();
+ if idx == *visibles[self.cursor_pos] {
let fg_color = Color::Default;
let bg_color = Color::Byte(246);
change_colors(grid, dest_area, fg_color, bg_color);
@@ -380,7 +393,7 @@ impl ThreadView {
copy_area(grid, &self.content, dest_area, src_area);
}
- /// Draw the list
+ /// draw the list
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
@@ -390,7 +403,7 @@ impl ThreadView {
context.dirty_areas.push_back(area);
return;
}
- let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2;
+ let rows = (get_y(bottom_right) - get_y(upper_left)).wrapping_div(2);
let prev_page_no = (self.cursor_pos).wrapping_div(rows);
let page_no = (self.new_cursor_pos).wrapping_div(rows);
@@ -400,48 +413,46 @@ impl ThreadView {
* **line** of an entry in the ThreadView grid. */
let get_entry_area = |idx: usize, entries: &[ThreadEntry]| {
let entries = &entries;
+ let visual_indentation = entries[idx].index.0 * 4;
(
- (entries[idx].index.0 * 4 + 1, 2 * (idx % rows)),
- (width - 1, 2 * (idx % rows)),
+ (visual_indentation, 2 * idx),
+ (
+ visual_indentation + entries[idx].heading.grapheme_width() + 1,
+ 2 * idx,
+ ),
)
};
- if prev_page_no == page_no {
- if self.entries.iter_mut().fold(false, |flag, e| {
- std::mem::replace(&mut e.dirty, false) || flag
- }) {
- let visibles = self.visible_entries();
- let mut visible_entry_counter = 0;
-
- for v in visibles {
- if v.len() == 1 {
- let idx = v[0];
- copy_area(
- grid,
- &self.content,
- (
- pos_inc(upper_left, (0, 2 * visible_entry_counter)),
- bottom_right,
- ),
- (
- (self.entries[idx].index.0 * 4 + 1, 2 * idx),
- (width - 1, 2 * idx + 1),
- ),
- );
- } else {
- copy_area(
- grid,
- &self.content,
- (
- pos_inc(upper_left, (0, 2 * visible_entry_counter)),
- bottom_right,
- ),
- ((0, 2 * v[0]), (width - 1, 2 * v[v.len() - 1] + 1)),
- );
- }
- visible_entry_counter += v.len();
+ if self.dirty || (page_no != prev_page_no) {
+ if page_no != prev_page_no {
+ clear_area(grid, area);
+ }
+ let visibles = self
+ .visible_entries
+ .iter()
+ .flat_map(|v| v.iter())
+ .skip(top_idx)
+ .take(rows);
+ let mut visible_entry_counter = 0;
+
+ for v in visibles {
+ if visible_entry_counter >= rows {
+ break;
}
- context.dirty_areas.push_back(area);
+ let idx = v;
+ copy_area(
+ grid,
+ &self.content,
+ (
+ pos_inc(upper_left, (0, 2 * visible_entry_counter)), // dest_area
+ bottom_right,
+ ),
+ (
+ (0, 2 * idx), //src_area
+ (width - 1, 2 * idx + 1),
+ ),
+ );
+ visible_entry_counter += 1;
}
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
@@ -450,54 +461,63 @@ impl ThreadView {
.iter()
.flat_map(|ref v| v.iter())
.collect();
+ self.cursor_pos = self.new_cursor_pos;
+ let idx = *visibles[self.cursor_pos];
+ let src_area = { get_entry_area(idx, &self.entries) };
+ let visual_indentation = self.entries[idx].indentation * 4;
+ let dest_area = (
+ pos_inc(
+ upper_left,
+ (visual_indentation, 2 * (self.cursor_pos - top_idx)),
+ ),
+ (
+ get_x(upper_left)
+ + visual_indentation
+ + self.entries[idx].heading.grapheme_width()
+ + 1,
+ get_y(upper_left) + 2 * (self.cursor_pos - top_idx),
+ ),
+ );
+
+ self.highlight_line(grid, dest_area, src_area, idx);
+ self.dirty = false;
+ context.dirty_areas.push_back(area);
+ } else {
let old_cursor_pos = self.cursor_pos;
self.cursor_pos = self.new_cursor_pos;
- for &visual_idx in &[old_cursor_pos, self.new_cursor_pos] {
- if std::dbg!(visual_idx >= visibles.len()) {
- continue;
- }
- let idx = *visibles[visual_idx];
- let src_area = get_entry_area(idx, &self.entries);
+ /* If cursor position has changed, remove the highlight from the previous position and
+ * apply it in the new one. */
+ let visibles: Vec<&usize> = self
+ .visible_entries
+ .iter()
+ .flat_map(|ref v| v.iter())
+ .collect();
+ for &idx in &[old_cursor_pos, self.cursor_pos] {
+ let entry_idx = *visibles[idx];
+ let src_area = { get_entry_area(entry_idx, &self.entries) };
+ let visual_indentation = self.entries[entry_idx].indentation * 4;
let dest_area = (
pos_inc(
upper_left,
- (self.entries[idx].indentation * 4 + 1, 2 * visual_idx),
+ (visual_indentation, 2 * (visibles[..idx].len() - top_idx)),
+ ),
+ (
+ get_x(upper_left)
+ + visual_indentation
+ + self.entries[entry_idx].heading.grapheme_width()
+ + 1,
+ get_y(upper_left) + 2 * (visibles[..idx].len() - top_idx),
),
- (get_x(bottom_right), get_y(upper_left) + 2 * visual_idx),
);
- self.highlight_line(grid, dest_area, src_area, idx);
- context.dirty_areas.push_back(dest_area);
+
+ self.highlight_line(grid, dest_area, src_area, entry_idx);
+
+ let (upper_left, bottom_right) = dest_area;
+ context
+ .dirty_areas
+ .push_back((upper_left, (get_x(bottom_right), get_y(upper_left) + 1)));
}
- return;
}
- self.cursor_pos = self.new_cursor_pos;
-
- /* Page_no has changed, so draw new page */
- copy_area(
- grid,
- &self.content,
- area,
- ((0, 2 * top_idx), (width - 1, height - 1)),
- );
- self.highlight_line(
- grid,
- (
- pos_inc(
- upper_left,
- (
- self.entries[self.current_pos()].indentation * 4 + 1,
- 2 * self.current_pos(),
- ),
- ),
- (
- get_x(bottom_right),
- get_y(upper_left) + 2 * self.current_pos(),
- ),
- ),
- get_entry_area(self.current_pos(), &self.entries),
- self.current_pos(),
- );
- context.dirty_areas.push_back(area);
}
fn draw_vert(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
@@ -506,92 +526,51 @@ impl ThreadView {
let bottom_right = bottom_right!(area);
let mid = get_x(upper_left) + self.content.size().0;
- if !self.dirty {
- let upper_left = (mid + 1, get_y(upper_left) + 2);
- if self.show_mailview {
- self.mailview
- .draw(grid, (upper_left, bottom_right), context);
- }
- return;
- }
-
- self.dirty = false;
-
/* First draw the thread subject on the first row */
- let y = {
- let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
- .as_ref()
- .unwrap();
- let threads = &mailbox.collection.threads;
- let thread_node = &threads.thread_nodes()[threads.root_set(self.coordinates.2)];
- let i = if let Some(i) = thread_node.message() {
- i
- } else {
- threads.thread_nodes()[thread_node.children()[0]]
- .message()
- .unwrap()
+ let y = if self.dirty {
+ let y = {
+ let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
+ .as_ref()
+ .unwrap();
+ let threads = &mailbox.collection.threads;
+ let thread_node = &threads.thread_nodes()[threads.root_set(self.coordinates.2)];
+ let i = if let Some(i) = thread_node.message() {
+ i
+ } else {
+ threads.thread_nodes()[thread_node.children()[0]]
+ .message()
+ .unwrap()
+ };
+ let envelope: &Envelope = &mailbox.collection[&i];
+
+ let (x, y) = write_string_to_grid(
+ &envelope.subject(),
+ grid,
+ Color::Byte(33),
+ Color::Default,
+ area,
+ true,
+ );
+ for x in x..=get_x(bottom_right) {
+ grid[(x, y)].set_ch(' ');
+ grid[(x, y)].set_bg(Color::Default);
+ grid[(x, y)].set_fg(Color::Default);
+ }
+ y + 2
};
- let envelope: &Envelope = &mailbox.collection[&i];
-
- let (x, y) = write_string_to_grid(
- &envelope.subject(),
- grid,
- Color::Byte(33),
- Color::Default,
- area,
- true,
- );
- for x in x..=get_x(bottom_right) {
- grid[(x, y)].set_ch(' ');
- grid[(x, y)].set_bg(Color::Default);
- grid[(x, y)].set_fg(Color::Default);
- }
- //context.dirty_areas.push_back(((0,0), set_y(bottom_right, y)));
- y + 2
+ clear_area(grid, (set_y(upper_left, y), set_x(bottom_right, mid)));
+ y
+ } else {
+ get_y(upper_left) + 2
};
-
let (width, height) = self.content.size();
- if height == 0 || height == self.cursor_pos || width == 0 {
+ if height == 0 || width == 0 {
return;
}
-
- /* if this is the first ever draw, there is nothing on the grid to update so populate it
- * first */
- if !self.initiated {
- clear_area(grid, (set_y(upper_left, y - 1), bottom_right));
- let (width, height) = self.content.size();
- if self.show_mailview {
- let area = (set_y(upper_left, y), set_x(bottom_right, mid - 1));
- let upper_left = upper_left!(area);
- let bottom_right = bottom_right!(area);
-
- let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2;
- let page_no = (self.new_cursor_pos).wrapping_div(rows);
- let top_idx = page_no * rows;
-
- copy_area(
- grid,
- &self.content,
- area,
- ((0, 2 * top_idx), (width - 1, height - 1)),
- );
- } else {
- let area = (set_y(upper_left, y), bottom_right);
- let upper_left = upper_left!(area);
- let bottom_right = bottom_right!(area);
-
- let rows = (get_y(bottom_right) - get_y(upper_left) + 1) / 2;
- let page_no = (self.new_cursor_pos).wrapping_div(rows);
- let top_idx = page_no * rows;
- copy_area(
- grid,
- &self.content,
- area,
- ((0, 2 * top_idx), (width - 1, height - 1)),
- );
+ if self.dirty {
+ for x in get_x(upper_left)..=get_x(bottom_right) {
+ set_and_join_box(grid, (x, y - 1), HORZ_BOUNDARY);
}
- context.dirty_areas.push_back(area);
- self.initiated = true;
}
self.draw_list(
@@ -599,11 +578,10 @@ impl ThreadView {
(set_y(upper_left, y), set_x(bottom_right, mid - 1)),
context,
);
- let upper_left = (mid + 1, get_y(upper_left) + y - 1);
- self.mailview
- .draw(grid, (upper_left, bottom_right), context);
- for x in get_x(upper_left)..=get_x(bottom_right) {
- set_and_join_box(grid, (x, y - 1), HORZ_BOUNDARY);
+ {
+ let upper_left = (mid + 1, get_y(upper_left) + y - 1);
+ self.mailview
+ .draw(grid, (upper_left, bottom_right), context);
}
}
fn draw_horz(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
@@ -622,16 +600,6 @@ impl ThreadView {
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
- if !self.dirty {
- if self.show_mailview {
- self.mailview
- .draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
- }
- return;
- }
-
- self.dirty = false;
-
/* First draw the thread subject on the first row */
let y = {
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
@@ -754,6 +722,12 @@ impl ThreadView {
stack.push(e.indentation);
(visies, stack, e.hidden)
}
+ (true, true)
+ if !stack.is_empty() && stack[stack.len() - 1] == e.indentation =>
+ {
+ visies.push(vec![idx]);
+ (visies, stack, e.hidden)
+ }
(true, true) => (visies, stack, e.hidden),
(false, true)
if stack[stack.len() - 1] >= e.indentation
@@ -887,15 +861,13 @@ impl Component for ThreadView {
UIEventType::Input(Key::Up) => {
if self.cursor_pos > 0 {
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(1);
- self.dirty = true;
}
return true;
}
UIEventType::Input(Key::Down) => {
let height = self.visible_entries.iter().flat_map(|v| v.iter()).count();
- if height > 0 && self.cursor_pos + 1 < height {
+ if height > 0 && self.new_cursor_pos + 1 < height {
self.new_cursor_pos += 1;
- self.dirty = true;
}
return true;
}
@@ -912,7 +884,7 @@ impl Component for ThreadView {
UIEventType::Input(Key::Char('p')) => {
self.show_mailview = !self.show_mailview;
self.initiated = false;
- self.set_dirty();
+ self.mailview.set_dirty();
return true;
}
UIEventType::Input(Key::Ctrl('r')) => {
@@ -920,7 +892,7 @@ impl Component for ThreadView {
let expanded_pos = self.expanded_pos;
self.initiate(Some(expanded_pos), context);
self.initiated = false;
- self.set_dirty();
+ self.dirty = true;
return true;
}
UIEventType::Input(Key::Char('h')) => {
@@ -944,7 +916,7 @@ impl Component for ThreadView {
high = mid - 1;
}
}
- return high;
+ return high + 1; //mid
};
self.new_cursor_pos = search_old_cursor_pos(visible_entries, self.cursor_pos);
}
@@ -961,10 +933,11 @@ impl Component for ThreadView {
false
}
fn is_dirty(&self) -> bool {
- self.dirty || self.mailview.is_dirty()
+ (self.cursor_pos != self.new_cursor_pos)
+ || self.dirty
+ || (self.show_mailview && self.mailview.is_dirty())
}
fn set_dirty(&mut self) {
- self.initiated = false;
self.dirty = true;
self.mailview.set_dirty();
}
diff --git a/ui/src/terminal/grapheme_clusters.rs b/ui/src/terminal/grapheme_clusters.rs
index ce1fe79f..69303e19 100644
--- a/ui/src/terminal/grapheme_clusters.rs
+++ b/ui/src/terminal/grapheme_clusters.rs
@@ -27,14 +27,10 @@ pub trait Graphemes: UnicodeSegmentation + CodePointsIter {
UnicodeSegmentation::grapheme_indices(self, true).next_back()
}
- fn grapheme_width(&self) -> i32 {
+ fn grapheme_width(&self) -> usize {
let mut count = 0;
for c in self.code_points() {
- count += if let Some(c) = wcwidth(c) {
- c as i32
- } else {
- -1
- };
+ count += if let Some(c) = wcwidth(c) { c } else { 0 };
}
count