Add box drawing in threadview with fluid layout

embed
Manos Pitsidianakis 2018-08-25 02:23:40 +03:00
parent a4ac421bca
commit c5992b707d
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
9 changed files with 791 additions and 343 deletions

View File

@ -80,8 +80,18 @@ impl Backends {
}
pub struct RefreshEvent {
pub hash: u64,
pub folder: String,
hash: u64,
folder: String,
}
impl RefreshEvent {
pub fn hash(&self) -> u64 {
self.hash
}
pub fn folder(&self) -> &str {
self.folder.as_str()
}
}
/// A `RefreshEventConsumer` is a boxed closure that must be used to consume a `RefreshEvent` and

15
sample-config 100644
View File

@ -0,0 +1,15 @@
[accounts.account-name]
root_folder = "/path/to/root/folder"
sent_folder = "/path/to/sent/folder" # optional
format = "Maildir"
threaded = true
[accounts.account-name.folders]
"Inbox" = "inbox"
"Sent" = "sent"
"Drafts" = "drafts"
"foobar-devel" = "devbar-fooel"
#[pager]
#pager_ratio = 80
#filter = "/usr/bin/pygmentize"

View File

@ -69,10 +69,10 @@ impl Component for Composer {
if self.dirty {
for i in get_y(upper_left)..=get_y(bottom_right) {
grid[(mid, i)].set_ch(VERT_BOUNDARY);
set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
grid[(mid, i)].set_fg(Color::Default);
grid[(mid, i)].set_bg(Color::Default);
grid[(mid + 80, i)].set_ch(VERT_BOUNDARY);
set_and_join_box(grid, (mid + 80, i), VERT_BOUNDARY);
grid[(mid + 80, i)].set_fg(Color::Default);
grid[(mid + 80, i)].set_bg(Color::Default);
}
@ -84,7 +84,7 @@ impl Component for Composer {
if self.dirty {
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + 79 {
grid[(i, header_height)].set_ch(HORZ_BOUNDARY);
set_and_join_box(grid, (i, header_height), HORZ_BOUNDARY);
grid[(i, header_height)].set_fg(Color::Default);
grid[(i, header_height)].set_bg(Color::Default);
}
@ -151,6 +151,7 @@ impl Component for Composer {
fn is_dirty(&self) -> bool {
self.dirty || self.pager.is_dirty()
}
fn set_dirty(&mut self) {
self.dirty = true;
self.pager.set_dirty();

View File

@ -175,35 +175,46 @@ impl CompactListing {
}
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let threads = &mailbox.threads;
let container = threads.root_set()[idx];
let container = &threads.containers()[container];
let i = if let Some(i) = container.message() {
i
} else {
threads.containers()[container.first_child().unwrap()]
.message()
.unwrap()
};
let root_envelope: &Envelope = &mailbox.collection[i];
let fg_color = if !root_envelope.is_seen() {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if self.cursor_pos.2 == idx {
Color::Byte(246)
} else if !root_envelope.is_seen() {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
Color::Default
};
change_colors(grid, area, fg_color, bg_color);
if idx == self.cursor_pos.2 {
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let threads = &mailbox.threads;
let container = threads.root_set()[idx];
let container = &threads.containers()[container];
let i = if let Some(i) = container.message() {
i
} else {
threads.containers()[container.first_child().unwrap()]
.message()
.unwrap()
};
let root_envelope: &Envelope = &mailbox.collection[i];
let fg_color = if !root_envelope.is_seen() {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if self.cursor_pos.2 == idx {
Color::Byte(246)
} else if !root_envelope.is_seen() {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
} else {
Color::Default
};
change_colors(grid, area, fg_color, bg_color);
return;
}
let (width, height) = self.content.size();
copy_area(
grid,
&self.content,
area,
((0, idx), (width - 1, height - 1)),
);
}
/// Draw the list of `Envelope`s.

View File

@ -22,12 +22,11 @@
use super::*;
use std::cmp;
#[derive(Debug)]
#[derive(Debug, Clone)]
struct ThreadEntry {
index: (usize, usize, usize),
/// (indentation, container index, line number in listing)
indentation: usize,
content: CellBuffer,
msg_idx: usize,
}
@ -40,6 +39,7 @@ pub struct ThreadView {
dirty: bool,
coordinates: (usize, usize, usize),
mailview: MailView,
show_mailview: bool,
entries: Vec<ThreadEntry>,
content: CellBuffer,
initiated: bool,
@ -47,6 +47,7 @@ pub struct ThreadView {
impl ThreadView {
pub fn new(coordinates: (usize, usize, usize), context: &Context) -> Self {
/* stack to push thread messages in order in order to pop and print them later */
let mut stack: Vec<(usize, usize)> = Vec::with_capacity(32);
let mailbox = &context.accounts[coordinates.0][coordinates.1]
.as_ref()
@ -64,6 +65,7 @@ impl ThreadView {
initiated: false,
coordinates,
mailview: MailView::default(),
show_mailview: true,
entries: Vec::new(),
cursor_pos: 1,
new_cursor_pos: 0,
@ -85,58 +87,88 @@ impl ThreadView {
stack.push((ind + 1, i));
}
}
view.new_expanded_pos = view.entries.len() - 1;
view.new_expanded_pos = view.entries.len().saturating_sub(1);
view.expanded_pos = view.new_expanded_pos + 1;
let height = 2 * view.entries.len();
let height = 2 * view.entries.len() + 1;
let mut width = 0;
let mut strings: Vec<String> = Vec::with_capacity(view.entries.len());
let mut highlight_reply_subjects: Vec<Option<usize>> =
Vec::with_capacity(view.entries.len());
for e in &view.entries {
let envelope: &Envelope = &mailbox.collection[e.msg_idx];
strings.push(format!(
" {}{} - {}",
" ".repeat(e.index.0 * 4),
envelope.date_as_str(),
envelope.field_from_to_string()
));
let container = &threads.containers()[e.index.1];
let string = if container.show_subject() {
let subject = envelope.subject();
highlight_reply_subjects.push(Some(subject.len()));
format!(
" {}{} - {} {}",
" ".repeat(e.index.0 * 4),
envelope.date_as_str(),
envelope.field_from_to_string(),
envelope.subject(),
)
} else {
highlight_reply_subjects.push(None);
format!(
" {}{} - {}",
" ".repeat(e.index.0 * 4),
envelope.date_as_str(),
envelope.field_from_to_string(),
)
};
strings.push(string);
width = cmp::max(
width,
e.index.0 + strings.last().as_ref().unwrap().len() + 1,
e.index.0 * 4 + strings.last().as_ref().unwrap().len() + 2,
);
}
let mut content = CellBuffer::new(width, height, Cell::default());
for (y, e) in view.entries.iter().enumerate() {
/* Box character drawing stuff */
if y > 0 && content.get_mut(e.index.0 * 4, 2 * y - 1).is_some() {
let index = (e.index.0 * 4, 2 * y - 1);
if content[index].ch() == ' ' {
let mut ctr = 1;
while content[(e.index.0 * 4 + ctr, 2 * y - 1)].ch() == ' ' {
content[(e.index.0 * 4 + ctr, 2 * y - 1)].set_ch(HORZ_BOUNDARY);
set_and_join_box(
&mut content,
(e.index.0 * 4 + ctr, 2 * y - 1),
HORZ_BOUNDARY,
);
ctr += 1;
}
content[index].set_ch(_TOP_LEFT_CORNER);
} else {
content[index].set_ch(LIGHT_DOWN_AND_HORIZONTAL);
};
set_and_join_box(&mut content, index, HORZ_BOUNDARY);
}
}
write_string_to_grid(
&strings[y],
&mut content,
Color::Default,
Color::Default,
((e.index.0 + 1, 2 * y), (width - 1, height - 1)),
((e.index.0 * 4 + 1, 2 * y), (width - 1, height - 1)),
true,
);
content[(e.index.0 * 4, 2 * y)].set_ch(VERT_BOUNDARY);
for i in (e.index.0 * 4)..width {
content[(i, 2 * y + 1)].set_ch(HORZ_BOUNDARY);
if let Some(len) = highlight_reply_subjects[y] {
let index = e.index.0 * 4 + 1 + strings[y].len() - len;
let area = ((index, 2 * y), (width - 2, 2 * y));
let fg_color = Color::Byte(33);
let bg_color = Color::Default;
change_colors(&mut content, area, fg_color, bg_color);
}
content[(e.index.0 * 4, 2 * y + 1)].set_ch(LIGHT_UP_AND_HORIZONTAL);
set_and_join_box(&mut content, (e.index.0 * 4, 2 * y), VERT_BOUNDARY);
set_and_join_box(&mut content, (e.index.0 * 4, 2 * y + 1), VERT_BOUNDARY);
for i in ((e.index.0 * 4) + 1)..width - 1 {
set_and_join_box(&mut content, (i, 2 * y + 1), HORZ_BOUNDARY);
}
set_and_join_box(&mut content, (width - 1, 2 * y + 1), VERT_BOUNDARY);
}
for y in 0..height - 1 {
set_and_join_box(&mut content, (width - 1, y), VERT_BOUNDARY);
}
//view.mailview = MailView::new((view.coordinates.0, view.coordinates.1, view.entries[view.expanded_pos].msg_idx), None, None);
view.content = content;
view.new_cursor_pos = view.new_expanded_pos;
view
@ -155,74 +187,32 @@ impl ThreadView {
.message()
.unwrap()
};
let envelope: &Envelope = &mailbox.collection[msg_idx];
let op = context.accounts[self.coordinates.0]
.backend
.operation(envelope.hash());
let body = envelope.body(op);
let mut body_text: String = " \n".repeat(6);
body_text.push_str(&String::from_utf8_lossy(&decode_rec(&body, None)));
let mut buf = CellBuffer::from(body_text.as_str()).split_newlines();
let date = format!("Date: {}\n", envelope.date_as_str());
let from = format!("From: {}\n", envelope.field_from_to_string());
let message_id = &format!("Message-ID: <{}>\n\n", envelope.message_id_raw());
let mut width = [date.len(), from.len(), message_id.len(), buf.size().0]
.iter()
.cloned()
.max()
.unwrap_or(1);
let height = buf.size().1;
if width > buf.size().0 {
buf.resize(width, height, Cell::default());
width -= 1;
} else {
width = buf.size().0 - 1;
}
write_string_to_grid(
&date,
&mut buf,
Color::Byte(33),
Color::Default,
((ind, 0), (width, height)),
true,
);
write_string_to_grid(
&from,
&mut buf,
Color::Byte(33),
Color::Default,
((ind, 1), (width, height)),
true,
);
write_string_to_grid(
&message_id,
&mut buf,
Color::Byte(33),
Color::Default,
((ind, 2), (width, height)),
true,
);
ThreadEntry {
index: (ind, idx, order),
indentation: ind,
content: buf,
msg_idx,
}
}
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize) {
let fg_color = Color::Default;
let bg_color = if self.cursor_pos == idx {
Color::Byte(246)
} else {
Color::Default
};
change_colors(grid, area, fg_color, bg_color);
if idx == self.cursor_pos {
let fg_color = Color::Default;
let bg_color = Color::Byte(246);
change_colors(grid, area, fg_color, bg_color);
return;
}
let (width, height) = self.content.size();
copy_area(
grid,
&self.content,
area,
(
(self.entries[idx].index.0 * 4 + 1, 2 * idx),
(width - 1, height - 1),
),
);
}
/// Draw the list
@ -232,7 +222,6 @@ impl ThreadView {
let (width, height) = self.content.size();
if height == 0 {
clear_area(grid, area);
copy_area(grid, &self.content, area, ((0, 0), (width - 1, 0)));
context.dirty_areas.push_back(area);
return;
}
@ -242,23 +231,38 @@ impl ThreadView {
let top_idx = page_no * rows;
/* This closure (written for code clarity, should be inlined by the compiler) returns the
* **line** of an entry in the grid. */
let entries = &self.entries;
let get_entry_area = |idx: usize| {
(
(
entries[idx].index.0 * 4 + 1 + get_x(upper_left),
get_y(upper_left) + 2 * (idx % rows),
),
(
cmp::min(get_x(upper_left) + width - 2, get_x(bottom_right)),
get_y(upper_left) + 2 * (idx % rows),
),
)
};
/* If cursor position has changed, remove the highlight from the previous position and
* apply it in the new one. */
if 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, self.new_cursor_pos] {
let new_area = (
set_y(upper_left, get_y(upper_left) + 2 * (idx % rows)),
set_y(bottom_right, get_y(upper_left) + 2 * (idx % rows)),
);
if idx >= self.entries.len() {
continue;
}
let new_area = get_entry_area(idx);
self.highlight_line(grid, new_area, idx);
context.dirty_areas.push_back(new_area);
}
return;
} else if self.cursor_pos != self.new_cursor_pos {
self.cursor_pos = self.new_cursor_pos;
}
self.cursor_pos = self.new_cursor_pos;
/* Page_no has changed, so draw new page */
copy_area(
@ -267,53 +271,28 @@ impl ThreadView {
area,
((0, 2 * top_idx), (width - 1, height - 1)),
);
self.highlight_line(
grid,
(
set_y(upper_left, get_y(upper_left) + 2 * (self.cursor_pos % rows)),
set_y(
bottom_right,
get_y(upper_left) + 2 * (self.cursor_pos % rows),
),
),
self.cursor_pos,
);
self.highlight_line(grid, get_entry_area(self.cursor_pos), self.cursor_pos);
context.dirty_areas.push_back(area);
}
}
impl fmt::Display for ThreadView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display subject/info
write!(f, "view thread")
}
}
impl Component for ThreadView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
fn draw_vert(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_rows = get_y(bottom_right) - get_y(upper_left);
let pager_ratio = context.runtime_settings.pager.pager_ratio;
let bottom_entity_rows = (pager_ratio * total_rows) / 100;
if bottom_entity_rows > total_rows {
clear_area(grid, area);
context.dirty_areas.push_back(area);
return;
}
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
let mid = get_x(upper_left) + self.content.size().0;
if !self.dirty {
self.mailview
.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
let upper_left = (mid + 1, get_y(upper_left) + 1);
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()
@ -351,27 +330,197 @@ impl Component for ThreadView {
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();
copy_area(
grid,
&self.content,
(set_y(upper_left, y), set_y(bottom_right, mid - 1)),
((0, 0), (width - 1, height - 1)),
);
for x in get_x(upper_left)..=get_x(bottom_right) {
grid[(x, mid)].set_ch(HORZ_BOUNDARY);
}
if let Some(cell) = grid.get_mut(get_x(upper_left).saturating_sub(1), mid) {
if cell.ch() == VERT_BOUNDARY {
cell.set_ch(LIGHT_VERTICAL_AND_RIGHT);
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)),
);
for y in get_y(upper_left)..=get_y(bottom_right) {
set_and_join_box(grid, (mid, y), VERT_BOUNDARY);
}
} 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)),
);
}
context.dirty_areas.push_back(area);
self.initiated = true;
}
if self.show_mailview {
self.draw_list(
grid,
(set_y(upper_left, y), set_x(bottom_right, mid - 1)),
context,
);
let upper_left = (mid + 1, get_y(upper_left) + 1);
self.mailview
.draw(grid, (upper_left, bottom_right), context);
} else {
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
}
}
fn draw_horz(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let total_rows = height!(area);
let pager_ratio = context.runtime_settings.pager.pager_ratio;
let bottom_entity_rows = (pager_ratio * total_rows) / 100;
if bottom_entity_rows > total_rows {
clear_area(grid, area);
context.dirty_areas.push_back(area);
return;
}
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 = &mut context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.threads;
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
let i = if let Some(i) = container.message() {
i
} else {
threads.containers()[container.first_child().unwrap()]
.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);
}
//context.dirty_areas.push_back(((0,0), set_y(bottom_right, y)));
y + 2
};
let (width, height) = self.content.size();
if height == 0 || height == self.cursor_pos || 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_y(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)),
);
for x in get_x(upper_left)..=get_x(bottom_right) {
set_and_join_box(grid, (x, mid), HORZ_BOUNDARY);
}
} 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)),
);
}
context.dirty_areas.push_back(area);
self.initiated = true;
}
if self.show_mailview {
self.draw_list(
grid,
(set_y(upper_left, y), set_y(bottom_right, mid - 1)),
context,
);
self.mailview
.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
} else {
self.draw_list(grid, (set_y(upper_left, y), bottom_right), context);
}
}
}
impl fmt::Display for ThreadView {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display subject/info
write!(f, "view thread")
}
}
impl Component for ThreadView {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
let total_rows = height!(area);
let total_cols = width!(area);
if total_rows < 24 || total_cols < 80 {
return;
}
/* If user has selected another mail to view, change to it */
if self.new_expanded_pos != self.expanded_pos {
self.expanded_pos = self.new_expanded_pos;
self.mailview = MailView::new(
@ -384,13 +533,12 @@ impl Component for ThreadView {
None,
);
}
self.draw_list(
grid,
(set_y(upper_left, y), set_y(bottom_right, mid - 1)),
context,
);
self.mailview
.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
if total_cols >= self.content.size().0 + 74 {
self.draw_vert(grid, area, context);
} else {
self.draw_horz(grid, area, context);
}
}
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
if self.mailview.process_event(event, context) {
@ -399,7 +547,7 @@ impl Component for ThreadView {
match event.event_type {
UIEventType::Input(Key::Up) => {
if self.cursor_pos > 0 {
self.new_cursor_pos -= 1;
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(1);
self.dirty = true;
}
return true;
@ -414,7 +562,15 @@ impl Component for ThreadView {
}
UIEventType::Input(Key::Char('\n')) => {
self.new_expanded_pos = self.cursor_pos;
self.dirty = true;
self.show_mailview = true;
self.initiated = false;
self.set_dirty();
return true;
}
UIEventType::Input(Key::Char('p')) => {
self.show_mailview = !self.show_mailview;
self.initiated = false;
self.set_dirty();
return true;
}
UIEventType::Resize => {

View File

@ -103,165 +103,6 @@ pub trait Component: Display + Debug {
fn set_dirty(&mut self);
}
// TODO: word break.
pub fn copy_area_with_break(
grid_dest: &mut CellBuffer,
grid_src: &CellBuffer,
dest: Area,
src: Area,
) -> Pos {
if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return upper_left!(dest);
}
let mut ret = bottom_right!(dest);
let mut src_x = get_x(upper_left!(src));
let mut src_y = get_y(upper_left!(src));
'y_: for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
'x_: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
if grid_src[(src_x, src_y)].ch() == '\n' {
src_y += 1;
src_x = 0;
if src_y >= get_y(bottom_right!(src)) {
ret.1 = y;
break 'y_;
}
continue 'y_;
}
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
src_x += 1;
if src_x >= get_x(bottom_right!(src)) {
src_y += 1;
src_x = 0;
if src_y >= get_y(bottom_right!(src)) {
//clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest)));
ret.1 = y;
break 'y_;
}
break 'x_;
}
}
}
ret
}
/// Copy a source `Area` to a destination.
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) -> Pos {
if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return upper_left!(dest);
}
let mut ret = bottom_right!(dest);
let mut src_x = get_x(upper_left!(src));
let mut src_y = get_y(upper_left!(src));
let (cols, rows) = grid_src.size();
if src_x >= cols || src_y >= rows {
eprintln!("DEBUG: src area outside of grid_src in copy_area",);
return upper_left!(dest);
}
for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
if src_x >= get_x(bottom_right!(src)) {
break 'for_x;
}
src_x += 1;
}
src_x = get_x(upper_left!(src));
if src_y >= get_y(bottom_right!(src)) {
clear_area(
grid_dest,
((get_x(upper_left!(dest)), y), bottom_right!(dest)),
);
ret.1 = y;
break;
}
src_y += 1;
}
ret
}
/// Change foreground and background colors in an `Area`
pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) {
if !is_valid_area!(area) {
eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area);
return;
}
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
for x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) {
grid[(x, y)].set_fg(fg_color);
grid[(x, y)].set_bg(bg_color);
}
}
}
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
fn write_string_to_grid(
s: &str,
grid: &mut CellBuffer,
fg_color: Color,
bg_color: Color,
area: Area,
line_break: bool,
) -> Pos {
let bounds = grid.size();
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let (mut x, mut y) = upper_left;
if y > (get_y(bottom_right))
|| x > get_x(bottom_right)
|| y > get_y(bounds)
|| x > get_x(bounds)
{
eprintln!(" Invalid area with string {} and area {:?}", s, area);
return (x, y);
}
'char: for c in s.chars() {
grid[(x, y)].set_ch(c);
grid[(x, y)].set_fg(fg_color);
grid[(x, y)].set_bg(bg_color);
x += 1;
if x == (get_x(bottom_right)) + 1 || x > get_x(bounds) {
x = get_x(upper_left);
y += 1;
if y > (get_y(bottom_right)) || y > get_y(bounds) {
return (x, y - 1);
}
if !line_break {
break 'char;
}
}
}
(x, y)
}
/// Completely clear an `Area` with an empty char and the terminal's default colors.
fn clear_area(grid: &mut CellBuffer, area: Area) {
if !is_valid_area!(area) {
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
for y in get_y(upper_left)..=get_y(bottom_right) {
for x in get_x(upper_left)..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
}
}
fn new_draft(_context: &mut Context) -> Vec<u8> {
// TODO: Generate proper message-id https://www.jwz.org/doc/mid.html
let mut v = String::with_capacity(500);
@ -271,3 +112,255 @@ fn new_draft(_context: &mut Context) -> Vec<u8> {
v.push_str("Message-Id: \n\n");
v.into_bytes()
}
fn bin_to_ch(b: u32) -> char {
match b {
0b0001 => '╶',
0b0010 => '╵',
0b0011 => '└',
0b0100 => '╴',
0b0101 => '─',
0b0110 => '┘',
0b0111 => '┴',
0b1000 => '╷',
0b1001 => '┌',
0b1010 => '│',
0b1011 => '├',
0b1100 => '┐',
0b1101 => '┬',
0b1110 => '┤',
0b1111 => '┼',
x => unreachable!(format!("unreachable bin_to_ch(x), x = {:b}", x)),
}
}
fn ch_to_bin(ch: char) -> Option<u32> {
match ch {
'└' => Some(0b0011),
'─' => Some(0b0101),
'┘' => Some(0b0110),
'┴' => Some(0b0111),
'┌' => Some(0b1001),
'│' => Some(0b1010),
'├' => Some(0b1011),
'┐' => Some(0b1100),
'┬' => Some(0b1101),
'┤' => Some(0b1110),
'┼' => Some(0b1111),
'╷' => Some(0b1000),
'╵' => Some(0b0010),
'╴' => Some(0b0100),
'╶' => Some(0b0001),
_ => None,
}
}
#[allow(never_loop)]
fn set_and_join_vert(grid: &mut CellBuffer, idx: Pos) -> u32 {
let (x, y) = idx;
let mut bin_set = 0b1010;
/* Check left side
*
* 1
* -> 2 0
* 3
*/
loop {
if x > 0 {
if let Some(cell) = grid.get_mut(x - 1, y) {
if let Some(adj) = ch_to_bin(cell.ch()) {
if (adj & 0b0001) > 0 {
bin_set |= 0b0100;
break;
} else if adj == 0b0100 {
cell.set_ch(bin_to_ch(0b0101));
bin_set |= 0b0100;
break;
}
}
}
}
bin_set &= 0b1011;
break;
}
/* Check right side
*
* 1
* 2 0 <-
* 3
*/
loop {
if let Some(cell) = grid.get_mut(x + 1, y) {
if let Some(adj) = ch_to_bin(cell.ch()) {
if (adj & 0b0100) > 0 {
bin_set |= 0b0001;
break;
}
}
}
bin_set &= 0b1110;
break;
}
/* Set upper side
*
* 1 <-
* 2 0
* 3
*/
loop {
if y > 0 {
if let Some(cell) = grid.get_mut(x, y - 1) {
if let Some(adj) = ch_to_bin(cell.ch()) {
cell.set_ch(bin_to_ch(adj | 0b1000));
break;
}
}
}
bin_set &= 0b1101;
break;
}
/* Set bottom side
*
* 1
* 2 0
* 3 <-
*/
loop {
if let Some(cell) = grid.get_mut(x, y + 1) {
if let Some(adj) = ch_to_bin(cell.ch()) {
cell.set_ch(bin_to_ch(adj | 0b0010));
break;
}
}
bin_set &= 0b0111;
break;
}
if bin_set == 0 {
bin_set = 0b1010;
}
bin_set
}
#[allow(never_loop)]
fn set_and_join_horz(grid: &mut CellBuffer, idx: Pos) -> u32 {
let (x, y) = idx;
let mut bin_set = 0b0101;
/* Check upper side
*
* 1 <-
* 2 0
* 3
*/
loop {
if y > 0 {
if let Some(cell) = grid.get_mut(x, y - 1) {
if let Some(adj) = ch_to_bin(cell.ch()) {
if (adj & 0b1000) > 0 {
bin_set |= 0b0010;
break;
} else if adj == 0b0010 {
bin_set |= 0b0010;
cell.set_ch(bin_to_ch(0b1010));
break;
}
}
}
}
bin_set &= 0b1101;
break;
}
/* Check bottom side
*
* 1
* 2 0
* 3 <-
*/
loop {
if let Some(cell) = grid.get_mut(x, y + 1) {
if let Some(adj) = ch_to_bin(cell.ch()) {
if (adj & 0b0010) > 0 {
bin_set |= 0b1000;
break;
} else if adj == 0b1000 {
cell.set_ch(bin_to_ch(0b1010));
break;
}
}
}
bin_set &= 0b0111;
break;
}
/* Set left side
*
* 1
* -> 2 0
* 3
*/
loop {
if x > 0 {
if let Some(cell) = grid.get_mut(x - 1, y) {
if let Some(adj) = ch_to_bin(cell.ch()) {
cell.set_ch(bin_to_ch(adj | 0b0001));
break;
}
}
}
bin_set &= 0b1011;
break;
}
/* Set right side
*
* 1
* 2 0 <-
* 3
*/
loop {
if let Some(cell) = grid.get_mut(x + 1, y) {
if let Some(adj) = ch_to_bin(cell.ch()) {
cell.set_ch(bin_to_ch(adj | 0b0100));
break;
}
}
bin_set &= 0b1110;
break;
}
if bin_set == 0 {
bin_set = 0b0101;
}
bin_set
}
fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) {
/* Connected sides:
*
* 1
* 2 c 0
* 3
*
* #3210
* 0b____
*/
let bin_set = match ch {
'│' => set_and_join_vert(grid, idx),
'─' => set_and_join_horz(grid, idx),
_ => unreachable!(),
};
grid[idx].set_ch(bin_to_ch(bin_set));
}

View File

@ -197,7 +197,7 @@ pub struct Pager {
cursor_pos: usize,
max_cursor_pos: Option<usize>,
height: usize,
width: usize,
dirty: bool,
content: CellBuffer,
@ -332,6 +332,9 @@ impl Component for Pager {
self.cursor_pos = self.cursor_pos.saturating_sub(height);
}
PagerMovement::PageDown => {
/* This might "overflow" beyond the max_cursor_pos boundary if it's not yet
* set. TODO: Rework the page up/down stuff
*/
if self.cursor_pos + 2 * height + 1 < self.height {
self.cursor_pos += height;
} else {
@ -349,12 +352,12 @@ impl Component for Pager {
Some(max) if max <= self.cursor_pos => {
self.cursor_pos -= 1;
return;
},
}
Some(max) if max >= height => {
self.cursor_pos = 0;
return;
}
_ => {},
_ => {}
}
clear_area(grid, area);

View File

@ -551,3 +551,162 @@ pub enum Attr {
UnderlineReverse = 0b110,
BoldReverseUnderline = 0b111,
}
// TODO: word break.
pub fn copy_area_with_break(
grid_dest: &mut CellBuffer,
grid_src: &CellBuffer,
dest: Area,
src: Area,
) -> Pos {
if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return upper_left!(dest);
}
let mut ret = bottom_right!(dest);
let mut src_x = get_x(upper_left!(src));
let mut src_y = get_y(upper_left!(src));
'y_: for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
'x_: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
if grid_src[(src_x, src_y)].ch() == '\n' {
src_y += 1;
src_x = 0;
if src_y >= get_y(bottom_right!(src)) {
ret.1 = y;
break 'y_;
}
continue 'y_;
}
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
src_x += 1;
if src_x >= get_x(bottom_right!(src)) {
src_y += 1;
src_x = 0;
if src_y >= get_y(bottom_right!(src)) {
//clear_area(grid_dest, ((get_x(upper_left!(dest)), y), bottom_right!(dest)));
ret.1 = y;
break 'y_;
}
break 'x_;
}
}
}
ret
}
/// Copy a source `Area` to a destination.
pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area, src: Area) -> Pos {
if !is_valid_area!(dest) || !is_valid_area!(src) {
eprintln!(
"BUG: Invalid areas in copy_area:\n src: {:?}\n dest: {:?}",
src, dest
);
return upper_left!(dest);
}
let mut ret = bottom_right!(dest);
let mut src_x = get_x(upper_left!(src));
let mut src_y = get_y(upper_left!(src));
let (cols, rows) = grid_src.size();
if src_x >= cols || src_y >= rows {
eprintln!("DEBUG: src area outside of grid_src in copy_area",);
return upper_left!(dest);
}
for y in get_y(upper_left!(dest))..=get_y(bottom_right!(dest)) {
'for_x: for x in get_x(upper_left!(dest))..=get_x(bottom_right!(dest)) {
grid_dest[(x, y)] = grid_src[(src_x, src_y)];
if src_x >= get_x(bottom_right!(src)) {
break 'for_x;
}
src_x += 1;
}
src_x = get_x(upper_left!(src));
if src_y >= get_y(bottom_right!(src)) {
clear_area(
grid_dest,
((get_x(upper_left!(dest)), y), bottom_right!(dest)),
);
ret.1 = y;
break;
}
src_y += 1;
}
ret
}
/// Change foreground and background colors in an `Area`
pub fn change_colors(grid: &mut CellBuffer, area: Area, fg_color: Color, bg_color: Color) {
if !is_valid_area!(area) {
eprintln!("BUG: Invalid area in change_colors:\n area: {:?}", area);
return;
}
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
for x in get_x(upper_left!(area))..=get_x(bottom_right!(area)) {
grid[(x, y)].set_fg(fg_color);
grid[(x, y)].set_bg(bg_color);
}
}
}
/// Write an `&str` to a `CellBuffer` in a specified `Area` with the passed colors.
pub fn write_string_to_grid(
s: &str,
grid: &mut CellBuffer,
fg_color: Color,
bg_color: Color,
area: Area,
line_break: bool,
) -> Pos {
let bounds = grid.size();
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let (mut x, mut y) = upper_left;
if y > (get_y(bottom_right))
|| x > get_x(bottom_right)
|| y > get_y(bounds)
|| x > get_x(bounds)
{
eprintln!(" Invalid area with string {} and area {:?}", s, area);
return (x, y);
}
'char: for c in s.chars() {
grid[(x, y)].set_ch(c);
grid[(x, y)].set_fg(fg_color);
grid[(x, y)].set_bg(bg_color);
x += 1;
if x == (get_x(bottom_right)) + 1 || x > get_x(bounds) {
x = get_x(upper_left);
y += 1;
if y > (get_y(bottom_right)) || y > get_y(bounds) {
return (x, y - 1);
}
if !line_break {
break 'char;
}
}
}
(x, y)
}
/// Completely clear an `Area` with an empty char and the terminal's default colors.
pub fn clear_area(grid: &mut CellBuffer, area: Area) {
if !is_valid_area!(area) {
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
for y in get_y(upper_left)..=get_y(bottom_right) {
for x in get_x(upper_left)..=get_x(bottom_right) {
grid[(x, y)].set_ch(' ');
grid[(x, y)].set_bg(Color::Default);
grid[(x, y)].set_fg(Color::Default);
}
}
}

View File

@ -58,7 +58,7 @@ pub enum ThreadEvent {
impl From<RefreshEvent> for ThreadEvent {
fn from(event: RefreshEvent) -> Self {
ThreadEvent::RefreshMailbox { hash: event.hash }
ThreadEvent::RefreshMailbox { hash: event.hash() }
}
}