ui: fix paging in ThreadView

embed
Manos Pitsidianakis 2019-04-07 00:09:10 +03:00
parent 4be1b52089
commit e3d0ad9170
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 177 additions and 208 deletions

View File

@ -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;
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 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();
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(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);
}
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(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 * self.current_pos(),
),
),
get_entry_area(self.current_pos(), &self.entries),
self.current_pos(),
);
context.dirty_areas.push_back(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)));
}
}
}
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();
}

View File

@ -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