diff --git a/meli.conf.5 b/meli.conf.5 index 0c24bd5e8..3ffd95696 100644 --- a/meli.conf.5 +++ b/meli.conf.5 @@ -317,6 +317,11 @@ auto verify signed e-mail according to RFC3156 (optional) select between these themes: light / dark .\" default value .Pq Em dark +.It Cm ascii_drawing Ar boolean +(optional) if true, box drawing will be done with ascii characters. +.\" default value +.Pq Em false +.El .Sh SEE ALSO .Xr meli 1 .Sh CONFORMING TO diff --git a/ui/src/components.rs b/ui/src/components.rs index f19bcf998..dd3febb75 100644 --- a/ui/src/components.rs +++ b/ui/src/components.rs @@ -90,7 +90,7 @@ pub trait Component: Display + Debug + Send { fn is_visible(&self) -> bool { true } - fn can_quit_cleanly(&mut self) -> bool { + fn can_quit_cleanly(&mut self, _context: &Context) -> bool { true } fn set_dirty(&mut self); @@ -374,6 +374,17 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) { * 0b____ */ + if grid.ascii_drawing { + grid[idx].set_ch(match ch { + '│' => '|', + '─' => '-', + _ => unreachable!(), + }); + + grid[idx].set_fg(Color::Byte(240)); + return; + } + let bin_set = match ch { '│' => set_and_join_vert(grid, idx), '─' => set_and_join_horz(grid, idx), @@ -391,19 +402,21 @@ pub fn create_box(grid: &mut CellBuffer, area: Area) { let upper_left = upper_left!(area); let bottom_right = bottom_right!(area); - for x in get_x(upper_left)..get_x(bottom_right) { - grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); - grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); - grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); - } + if !grid.ascii_drawing { + for x in get_x(upper_left)..get_x(bottom_right) { + grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY); + grid[(x, get_y(bottom_right))].set_fg(Color::Byte(240)); + } - for y in get_y(upper_left)..get_y(bottom_right) { - grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); - grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); - grid[(get_x(bottom_right), y)].set_fg(Color::Byte(240)); + for y in get_y(upper_left)..get_y(bottom_right) { + grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY); + grid[(get_x(bottom_right), y)].set_fg(Color::Byte(240)); + } + set_and_join_box(grid, upper_left, HORZ_BOUNDARY); + set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); + set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); + set_and_join_box(grid, bottom_right, VERT_BOUNDARY); } - set_and_join_box(grid, upper_left, HORZ_BOUNDARY); - set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY); - set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY); - set_and_join_box(grid, bottom_right, VERT_BOUNDARY); } diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index b9bfc7395..254979946 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -171,6 +171,7 @@ impl Composer { (list_address, list_address_string), ], false, + context, )); } } @@ -826,7 +827,7 @@ impl Component for Composer { } } - fn kill(&mut self, uuid: Uuid, _context: &mut Context) { + fn kill(&mut self, uuid: Uuid, context: &mut Context) { self.mode = ViewMode::Discard( uuid, Selector::new( @@ -837,6 +838,7 @@ impl Component for Composer { ('n', "cancel".to_string()), ], true, + context, ), ); } @@ -873,7 +875,7 @@ impl Component for Composer { self.id = id; } - fn can_quit_cleanly(&mut self) -> bool { + fn can_quit_cleanly(&mut self, context: &Context) -> bool { /* Play it safe and ask user for confirmation */ self.mode = ViewMode::Discard( self.id, @@ -885,6 +887,7 @@ impl Component for Composer { ('n', "cancel".to_string()), ], true, + context, ), ); self.set_dirty(); diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index e486009dd..82bf07868 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -405,7 +405,8 @@ impl ListingTrait for CompactListing { ), ERROR, ); - self.data_columns.columns[0] = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.data_columns.columns[0] = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); write_string_to_grid( &message, &mut self.data_columns.columns[0], @@ -422,7 +423,8 @@ impl ListingTrait for CompactListing { } else { self.length = 0; let message = format!("No results for `{}`.", filter_term); - self.data_columns.columns[0] = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.data_columns.columns[0] = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); write_string_to_grid( &message, &mut self.data_columns.columns[0], @@ -530,7 +532,7 @@ impl CompactListing { Err(_) => { let message: String = context.accounts[self.cursor_pos.0][folder_hash].to_string(); self.data_columns.columns[0] = - CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); self.length = 0; write_string_to_grid( message.as_str(), @@ -610,19 +612,19 @@ impl CompactListing { /* index column */ self.data_columns.columns[0] = - CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context); /* date column */ self.data_columns.columns[1] = - CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context); /* from column */ self.data_columns.columns[2] = - CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context); /* flags column */ self.data_columns.columns[3] = - CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context); /* subject column */ self.data_columns.columns[4] = - CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context); for ((idx, root_idx), strings) in threads.root_iter().enumerate().zip(rows) { let thread_node = &threads.thread_nodes()[&root_idx]; @@ -750,8 +752,12 @@ impl CompactListing { if self.length == 0 { let mailbox = &account[self.cursor_pos.1]; let message = mailbox.to_string(); - self.data_columns.columns[0] = - CellBuffer::new(message.len(), self.length + 1, Cell::with_char(' ')); + self.data_columns.columns[0] = CellBuffer::new_with_context( + message.len(), + self.length + 1, + Cell::with_char(' '), + context, + ); write_string_to_grid( &message, &mut self.data_columns.columns[0], @@ -803,19 +809,19 @@ impl CompactListing { /* index column */ self.data_columns.columns[0] = - CellBuffer::new(min_width.0, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.0, rows.len(), Cell::with_char(' '), context); /* date column */ self.data_columns.columns[1] = - CellBuffer::new(min_width.1, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.1, rows.len(), Cell::with_char(' '), context); /* from column */ self.data_columns.columns[2] = - CellBuffer::new(min_width.2, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.2, rows.len(), Cell::with_char(' '), context); /* flags column */ self.data_columns.columns[3] = - CellBuffer::new(min_width.3, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.3, rows.len(), Cell::with_char(' '), context); /* subject column */ self.data_columns.columns[4] = - CellBuffer::new(min_width.4, rows.len(), Cell::with_char(' ')); + CellBuffer::new_with_context(min_width.4, rows.len(), Cell::with_char(' '), context); for ((idx, thread_hash), strings) in self.filtered_selection.iter().enumerate().zip(rows) { let i = threads.thread_nodes()[thread_hash].message().unwrap(); diff --git a/ui/src/components/mail/listing/conversations.rs b/ui/src/components/mail/listing/conversations.rs index 7289f0816..4010907ae 100644 --- a/ui/src/components/mail/listing/conversations.rs +++ b/ui/src/components/mail/listing/conversations.rs @@ -448,7 +448,8 @@ impl ListingTrait for ConversationsListing { if let Some(error) = error { self.length = 0; let message = format!("Error: {}", error.to_string()); - self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); write_string_to_grid( &message, &mut self.content, @@ -465,7 +466,8 @@ impl ListingTrait for ConversationsListing { } else { self.length = 0; let message = format!("No results for `{}`.", filter_term); - self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); write_string_to_grid( &message, &mut self.content, @@ -576,7 +578,8 @@ impl ConversationsListing { Ok(()) => {} Err(_) => { let message: String = context.accounts[self.cursor_pos.0][folder_hash].to_string(); - self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); self.length = 0; write_string_to_grid( message.as_str(), @@ -653,7 +656,8 @@ impl ConversationsListing { selection.retain(|e, _| order.contains_key(e)); let width = std::cmp::min(MAX_COLS, max_entry_columns); - self.content = CellBuffer::new(width, 4 * rows.len(), Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(width, 4 * rows.len(), Cell::with_char(' '), context); let padding_fg = if context.settings.terminal.theme == "light" { Color::Byte(254) @@ -749,7 +753,8 @@ impl ConversationsListing { if self.length == 0 { let mailbox = &account[self.cursor_pos.1]; let message = mailbox.to_string(); - self.content = CellBuffer::new(message.len(), 1, Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(message.len(), 1, Cell::with_char(' '), context); write_string_to_grid( &message, &mut self.content, diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 6845e28ae..1a6dbc574 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -706,6 +706,7 @@ impl Component for MailView { "select contacts to add", entries, false, + context, )); self.dirty = true; return true; diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index f84ad2072..a5100e667 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -222,7 +222,7 @@ impl ThreadView { 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()); + let mut content = CellBuffer::new_with_context(width, height, Cell::default(), context); if self.reversed { for (y, e) in self.entries.iter().rev().enumerate() { /* Box character drawing stuff */ diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 9c744fb16..a065ea2ac 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -305,7 +305,9 @@ impl Pager { let height = lines.len() + 1; let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0)); + let ascii_drawing = self.content.ascii_drawing; let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); + content.set_ascii_drawing(ascii_drawing); Pager::print_string(&mut content, lines); self.text = text.to_string(); self.content = content; @@ -361,7 +363,11 @@ impl Pager { let height = lines.len() + 1; let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0)); - let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); + let mut content = if let Some(context) = context { + CellBuffer::new_with_context(width, height, Cell::with_char(' '), context) + } else { + CellBuffer::new(width, height, Cell::with_char(' ')) + }; //interpret_format_flowed(&text); Pager::print_string(&mut content, lines); content @@ -479,7 +485,8 @@ impl Component for Pager { let height = lines.len() + 1; self.width = width; self.height = height; - self.content = CellBuffer::new(width, height, Cell::with_char(' ')); + self.content = + CellBuffer::new_with_context(width, height, Cell::with_char(' '), context); Pager::print_string(&mut self.content, lines); } if self.cursor_pos + height >= self.height { @@ -1067,8 +1074,8 @@ impl Component for StatusBar { self.id = id; } - fn can_quit_cleanly(&mut self) -> bool { - self.container.can_quit_cleanly() + fn can_quit_cleanly(&mut self, context: &Context) -> bool { + self.container.can_quit_cleanly(context) } } @@ -1335,7 +1342,8 @@ impl Component for Tabbed { ), ); } - self.help_content = CellBuffer::new(max_width, max_length + 2, Cell::default()); + self.help_content = + CellBuffer::new_with_context(max_width, max_length + 2, Cell::default(), context); let (width, height) = self.help_content.size(); let (cols, rows) = (width!(area), height!(area)); if cols == 0 || rows == 0 { @@ -1556,9 +1564,9 @@ impl Component for Tabbed { self.id = id; } - fn can_quit_cleanly(&mut self) -> bool { + fn can_quit_cleanly(&mut self, context: &Context) -> bool { for (i, c) in self.children.iter_mut().enumerate() { - if !c.can_quit_cleanly() { + if !c.can_quit_cleanly(context) { self.cursor_pos = i; self.set_dirty(); return false; @@ -1843,7 +1851,12 @@ impl Component for Selector { } impl Selector { - pub fn new(title: &str, entries: Vec<(T, String)>, single_only: bool) -> Selector { + pub fn new( + title: &str, + entries: Vec<(T, String)>, + single_only: bool, + context: &Context, + ) -> Selector { let width = std::cmp::max( "OK Cancel".len(), std::cmp::max( @@ -1863,9 +1876,11 @@ impl Selector { /* Extra room for buttons Okay/Cancel */ 3 }; - let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); + let mut content = + CellBuffer::new_with_context(width, height, Cell::with_char(' '), context); + let ascii_drawing = context.settings.terminal.ascii_drawing; write_string_to_grid( - "┏━", + if ascii_drawing { "+-" } else { "┏━" }, &mut content, Color::Byte(8), Color::Default, @@ -1884,7 +1899,7 @@ impl Selector { ); for i in 1..(width - title.len() - 1) { write_string_to_grid( - "━", + if ascii_drawing { "-" } else { "━" }, &mut content, Color::Byte(8), Color::Default, @@ -1894,7 +1909,7 @@ impl Selector { ); } write_string_to_grid( - "┓", + if ascii_drawing { "+" } else { "┓" }, &mut content, Color::Byte(8), Color::Default, @@ -1903,7 +1918,7 @@ impl Selector { false, ); write_string_to_grid( - "┗", + if ascii_drawing { "+" } else { "┗" }, &mut content, Color::Byte(8), Color::Default, @@ -1912,7 +1927,11 @@ impl Selector { false, ); write_string_to_grid( - &"━".repeat(width - 2), + &if ascii_drawing { + "-".repeat(width - 2) + } else { + "━".repeat(width - 2) + }, &mut content, Color::Byte(8), Color::Default, @@ -1921,7 +1940,7 @@ impl Selector { false, ); write_string_to_grid( - "┛", + if ascii_drawing { "+" } else { "┛" }, &mut content, Color::Byte(8), Color::Default, @@ -1931,7 +1950,7 @@ impl Selector { ); for i in 1..height - 1 { write_string_to_grid( - "┃", + if ascii_drawing { "|" } else { "┃" }, &mut content, Color::Byte(8), Color::Default, @@ -1940,7 +1959,7 @@ impl Selector { false, ); write_string_to_grid( - "┃", + if ascii_drawing { "|" } else { "┃" }, &mut content, Color::Byte(8), Color::Default, diff --git a/ui/src/conf/terminal.rs b/ui/src/conf/terminal.rs index 2861cb50b..2e6887c20 100644 --- a/ui/src/conf/terminal.rs +++ b/ui/src/conf/terminal.rs @@ -25,12 +25,15 @@ pub struct TerminalSettings { #[serde(default)] /// light, dark pub theme: String, + #[serde(default)] + pub ascii_drawing: bool, } impl Default for TerminalSettings { fn default() -> Self { TerminalSettings { theme: "dark".to_string(), + ascii_drawing: false, } } } diff --git a/ui/src/state.rs b/ui/src/state.rs index c924669f9..574463668 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -235,6 +235,10 @@ impl State { }, threads: FnvHashMap::with_capacity_and_hasher(1, Default::default()), }; + if s.context.settings.terminal.ascii_drawing { + s.grid.set_ascii_drawing(true); + } + for a in s.context.accounts.iter_mut() { for worker in a.workers.values_mut() { if let Some(worker) = worker.as_mut() { @@ -513,7 +517,12 @@ impl State { } pub fn can_quit_cleanly(&mut self) -> bool { - self.components.iter_mut().all(|c| c.can_quit_cleanly()) + let State { + ref mut components, + ref context, + .. + } = self; + components.iter_mut().all(|c| c.can_quit_cleanly(context)) } pub fn register_component(&mut self, component: Box) { diff --git a/ui/src/terminal/cells.rs b/ui/src/terminal/cells.rs index 0db2f079a..ef7773bf2 100644 --- a/ui/src/terminal/cells.rs +++ b/ui/src/terminal/cells.rs @@ -25,6 +25,7 @@ */ use super::position::*; +use crate::state::Context; use text_processing::wcwidth; use std::convert::From; @@ -105,6 +106,7 @@ pub struct CellBuffer { cols: usize, rows: usize, buf: Vec, + pub ascii_drawing: bool, } impl fmt::Debug for CellBuffer { @@ -137,9 +139,23 @@ impl CellBuffer { cols, rows, buf: vec![cell; cols * rows], + ascii_drawing: false, } } + pub fn new_with_context(cols: usize, rows: usize, cell: Cell, context: &Context) -> CellBuffer { + CellBuffer { + cols, + rows, + buf: vec![cell; cols * rows], + ascii_drawing: context.settings.terminal.ascii_drawing, + } + } + + pub fn set_ascii_drawing(&mut self, new_val: bool) { + self.ascii_drawing = new_val; + } + /// Resizes `CellBuffer` to the given number of rows and columns, using the given `Cell` as /// a blank. pub fn resize(&mut self, newcols: usize, newrows: usize, blank: Cell) {