diff --git a/meli.1 b/meli.1 index 206dce08f..bf113a3fd 100644 --- a/meli.1 +++ b/meli.1 @@ -402,6 +402,10 @@ catchall for general errors Specifies the editor to use .It Ev MELI_CONFIG Override the configuration file +.It Ev NO_COLOR +When present (regardless of its value), prevents the addition of ANSI color. The configuration value +.Ic use_color +overrides this. .El .Sh FILES .Nm diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index 2e58a9f5f..5d9f217bd 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -1050,49 +1050,38 @@ impl Listing { if idx == lines_len { break; } - let ( - fg_color, - bg_color, - index_fg_color, - index_bg_color, - unread_count_fg, - unread_count_bg, - ) = if must_highlight_account { + let (att, index_att, unread_count_att) = if must_highlight_account { if self.cursor_pos.1 == idx { - ( - crate::conf::value(context, "mail.sidebar_highlighted").fg, - crate::conf::value(context, "mail.sidebar_highlighted").bg, - crate::conf::value(context, "mail.sidebar_highlighted_index").fg, - crate::conf::value(context, "mail.sidebar_highlighted_index").bg, - crate::conf::value(context, "mail.sidebar_highlighted_unread_count").fg, - crate::conf::value(context, "mail.sidebar_highlighted_unread_count").bg, - ) + let mut ret = ( + crate::conf::value(context, "mail.sidebar_highlighted"), + crate::conf::value(context, "mail.sidebar_highlighted_index"), + crate::conf::value(context, "mail.sidebar_highlighted_unread_count"), + ); + + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + ret.0.attrs |= Attr::Reverse; + ret.1.attrs |= Attr::Reverse; + ret.2.attrs |= Attr::Reverse; + } + ret } else { ( - crate::conf::value(context, "mail.sidebar_highlighted_account").fg, - crate::conf::value(context, "mail.sidebar_highlighted_account").bg, - crate::conf::value(context, "mail.sidebar_highlighted_account_index").fg, - crate::conf::value(context, "mail.sidebar_highlighted_account_index").bg, + crate::conf::value(context, "mail.sidebar_highlighted_account"), + crate::conf::value(context, "mail.sidebar_highlighted_account_index"), crate::conf::value( context, "mail.sidebar_highlighted_account_unread_count", - ) - .fg, - crate::conf::value( - context, - "mail.sidebar_highlighted_account_unread_count", - ) - .bg, + ), ) } } else { ( - crate::conf::value(context, "mail.sidebar").fg, - crate::conf::value(context, "mail.sidebar").bg, - crate::conf::value(context, "mail.sidebar_index").fg, - crate::conf::value(context, "mail.sidebar_index").bg, - crate::conf::value(context, "mail.sidebar_unread_count").fg, - crate::conf::value(context, "mail.sidebar_unread_count").bg, + crate::conf::value(context, "mail.sidebar"), + crate::conf::value(context, "mail.sidebar_index"), + crate::conf::value(context, "mail.sidebar_unread_count"), ) }; @@ -1117,27 +1106,27 @@ impl Listing { let (x, _) = write_string_to_grid( &format!("{:>width$}", inc, width = total_folder_no_digits), grid, - index_fg_color, - index_bg_color, - Attr::Default, + index_att.fg, + index_att.bg, + index_att.attrs, (set_y(upper_left, y), bottom_right), None, ); let (x, _) = write_string_to_grid( &" ".repeat(depth + 1), grid, - fg_color, - bg_color, - Attr::Default, + att.fg, + att.bg, + att.attrs, ((x, y), bottom_right), None, ); let (x, _) = write_string_to_grid( entries[&folder_idx].name(), grid, - fg_color, - bg_color, - Attr::Default, + att.fg, + att.bg, + att.attrs, ((x, y), bottom_right), None, ); @@ -1156,13 +1145,14 @@ impl Listing { let (x, _) = write_string_to_grid( &count_string, grid, - unread_count_fg, - unread_count_bg, - if count.unwrap_or(0) > 0 { - Attr::Bold - } else { - Attr::Default - }, + unread_count_att.fg, + unread_count_att.bg, + unread_count_att.attrs + | if count.unwrap_or(0) > 0 { + Attr::Bold + } else { + Attr::Default + }, ( ( /* Hide part of folder name if need be to fit the message count */ @@ -1173,7 +1163,9 @@ impl Listing { ), None, ); - change_colors(grid, ((x, y), set_y(bottom_right, y)), fg_color, bg_color); + for c in grid.row_iter(x..(get_x(bottom_right) + 1), y) { + grid[c].set_fg(att.fg).set_bg(att.bg).set_attrs(att.attrs); + } idx += 1; } if idx == 0 { diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 2dea9198d..f62191c62 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -146,15 +146,32 @@ impl ListingTrait for CompactListing { } else { self.color_cache.odd.bg }; + let attrs = if self.cursor_pos.2 == idx { + self.color_cache.highlighted.attrs + } else if self.selection[&thread_hash] { + self.color_cache.selected.attrs + } else if thread.unseen() > 0 { + self.color_cache.unseen.attrs + } else if idx % 2 == 0 { + self.color_cache.even.attrs + } else { + self.color_cache.odd.attrs + }; let (upper_left, bottom_right) = area; - change_colors(grid, area, fg_color, bg_color); let x = get_x(upper_left) + self.data_columns.widths[0] + self.data_columns.widths[1] + self.data_columns.widths[2] + 3 * 2; + for c in grid.row_iter( + get_x(upper_left)..(get_x(bottom_right) + 1), + get_y(upper_left), + ) { + grid[c].set_fg(fg_color).set_bg(bg_color).set_attrs(attrs); + } + copy_area( grid, &self.data_columns.columns[3], @@ -165,7 +182,7 @@ impl ListingTrait for CompactListing { ), ); for c in grid.row_iter(x..(self.data_columns.widths[3] + x), get_y(upper_left)) { - grid[c].set_bg(bg_color); + grid[c].set_bg(bg_color).set_attrs(attrs); } return; } @@ -641,6 +658,12 @@ impl CompactListing { thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"), ..self.color_cache }; + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + self.color_cache.highlighted.attrs |= Attr::Reverse; + } // Get mailbox as a reference. // diff --git a/ui/src/components/mail/listing/conversations.rs b/ui/src/components/mail/listing/conversations.rs index 5fe9b2255..20519b5f4 100644 --- a/ui/src/components/mail/listing/conversations.rs +++ b/ui/src/components/mail/listing/conversations.rs @@ -119,6 +119,15 @@ impl ListingTrait for ConversationsListing { } else { self.color_cache.theme_default.bg }; + let attrs = if self.cursor_pos.2 == idx { + self.color_cache.highlighted.attrs + } else if self.selection[&thread_hash] { + self.color_cache.selected.attrs + } else if thread.unseen() > 0 { + self.color_cache.unseen.attrs + } else { + self.color_cache.theme_default.attrs + }; copy_area( grid, @@ -134,26 +143,38 @@ impl ListingTrait for ConversationsListing { let (x, y) = upper_left; if self.cursor_pos.2 == idx || self.selection[&thread_hash] { for x in x..=get_x(bottom_right) { - grid[(x, y)].set_fg(fg_color); - grid[(x, y)].set_bg(bg_color); + grid[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); - grid[(x, y + 1)].set_fg(fg_color); - grid[(x, y + 1)].set_bg(bg_color); + grid[(x, y + 1)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); - grid[(x, y + 2)].set_fg(padding_fg); - grid[(x, y + 2)].set_bg(bg_color); + grid[(x, y + 2)] + .set_fg(padding_fg) + .set_bg(bg_color) + .set_attrs(attrs); } } else if width < width!(area) { /* fill any remaining columns, if our view is wider than self.content */ for x in (x + width)..=get_x(bottom_right) { - grid[(x, y)].set_fg(fg_color); - grid[(x, y)].set_bg(bg_color); + grid[(x, y)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); - grid[(x, y + 1)].set_fg(fg_color); - grid[(x, y + 1)].set_bg(bg_color); + grid[(x, y + 1)] + .set_fg(fg_color) + .set_bg(bg_color) + .set_attrs(attrs); - grid[(x, y + 2)].set_fg(padding_fg); - grid[(x, y + 2)].set_bg(bg_color); + grid[(x, y + 2)] + .set_fg(padding_fg) + .set_bg(bg_color) + .set_attrs(attrs); } } return; @@ -570,6 +591,12 @@ impl ConversationsListing { ..self.color_cache }; + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + self.color_cache.highlighted.attrs |= Attr::Reverse; + } // Get mailbox as a reference. // match context.accounts[self.cursor_pos.0].status(self.folder_hash) { diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs index 8c75bc09f..2f8c6bce4 100644 --- a/ui/src/components/mail/listing/plain.rs +++ b/ui/src/components/mail/listing/plain.rs @@ -73,6 +73,7 @@ pub struct PlainListing { view: MailView, row_updates: SmallVec<[EnvelopeHash; 8]>, _row_updates: SmallVec<[ThreadHash; 8]>, + color_cache: ColorCache, movement: Option, id: ComponentId, @@ -125,37 +126,51 @@ impl ListingTrait for PlainListing { let account = &context.accounts[self.cursor_pos.0]; let envelope: EnvelopeRef = account.collection.get_env(i); - let fg_color = self.data_columns.columns[0][(0, idx)].fg(); - let bg_color = if context.settings.terminal.theme == "light" { - if self.cursor_pos.2 == idx { - Color::Byte(244) - } else if self.selection[&i] { - Color::Byte(210) - } else if !envelope.is_seen() { - Color::Byte(251) - } else { - self.data_columns.columns[0][(0, idx)].bg() - } + let fg_color = if !envelope.is_seen() { + self.color_cache.unseen.fg + } else if self.cursor_pos.2 == idx { + self.color_cache.highlighted.fg + } else if idx % 2 == 0 { + self.color_cache.even.fg } else { - if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if self.selection[&i] { - Color::Byte(210) - } else if !envelope.is_seen() { - Color::Byte(251) - } else { - self.data_columns.columns[0][(0, idx)].bg() - } + self.color_cache.odd.fg + }; + let bg_color = if self.cursor_pos.2 == idx { + self.color_cache.highlighted.bg + } else if self.selection[&i] { + self.color_cache.selected.bg + } else if !envelope.is_seen() { + self.color_cache.unseen.bg + } else if idx % 2 == 0 { + self.color_cache.even.bg + } else { + self.color_cache.odd.bg + }; + let attrs = if self.cursor_pos.2 == idx { + self.color_cache.highlighted.attrs + } else if self.selection[&i] { + self.color_cache.selected.attrs + } else if !envelope.is_seen() { + self.color_cache.unseen.attrs + } else if idx % 2 == 0 { + self.color_cache.even.attrs + } else { + self.color_cache.odd.attrs }; let (upper_left, bottom_right) = area; - change_colors(grid, area, fg_color, bg_color); let x = get_x(upper_left) + self.data_columns.widths[0] + self.data_columns.widths[1] + self.data_columns.widths[2] + 3 * 2; + for c in grid.row_iter( + get_x(upper_left)..(get_x(bottom_right) + 1), + get_y(upper_left), + ) { + grid[c].set_fg(fg_color).set_bg(bg_color).set_attrs(attrs); + } copy_area( grid, &self.data_columns.columns[3], @@ -166,7 +181,7 @@ impl ListingTrait for PlainListing { ), ); for c in grid.row_iter(x..(x + self.data_columns.widths[3]), get_y(upper_left)) { - grid[c].set_bg(bg_color); + grid[c].set_bg(bg_color).set_attrs(attrs); } return; } @@ -495,6 +510,7 @@ impl PlainListing { force_draw: true, unfocused: false, view: MailView::default(), + color_cache: ColorCache::default(), movement: None, id: ComponentId::new_v4(), @@ -571,6 +587,23 @@ impl PlainListing { return; }; + self.color_cache = ColorCache { + unseen: crate::conf::value(context, "mail.listing.plain.unseen"), + highlighted: crate::conf::value(context, "mail.listing.plain.highlighted"), + even: crate::conf::value(context, "mail.listing.plain.even"), + odd: crate::conf::value(context, "mail.listing.plain.odd"), + selected: crate::conf::value(context, "mail.listing.plain.selected"), + attachment_flag: crate::conf::value(context, "mail.listing.attachment_flag"), + thread_snooze_flag: crate::conf::value(context, "mail.listing.thread_snooze_flag"), + ..self.color_cache + }; + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + self.color_cache.highlighted.attrs |= Attr::Reverse; + } + // Get mailbox as a reference. // match context.accounts[self.cursor_pos.0].status(self.folder_hash) { diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index f19891a1c..d6e818f64 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -764,7 +764,13 @@ impl StatusBar { } } fn draw_status_bar(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { - let attribute = crate::conf::value(context, "status.bar"); + let mut attribute = crate::conf::value(context, "status.bar"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + attribute.attrs |= Attr::Reverse; + } let (x, y) = write_string_to_grid( &self.status, grid, @@ -774,7 +780,7 @@ impl StatusBar { area, None, ); - for c in grid.row_iter_from(x.., y) { + for c in grid.row_iter(x..(get_x(bottom_right!(area)) + 1), y) { grid[c] .set_ch(' ') .set_fg(attribute.fg) @@ -789,7 +795,7 @@ impl StatusBar { get_x(bottom_right!(area)), ) { - grid[(x, y)].set_attrs(Attr::Bold); + grid[(x, y)].set_attrs(attribute.attrs | Attr::Bold); } } let noto_colors = crate::conf::value(context, "status.notification"); @@ -1369,21 +1375,29 @@ impl Tabbed { clear_area(grid, area); return; } + let mut tab_focused_attribute = crate::conf::value(context, "tab.focused"); + let tab_unfocused_attribute = crate::conf::value(context, "tab.unfocused"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + tab_focused_attribute.attrs |= Attr::Reverse; + } let mut x = get_x(upper_left); let y: usize = get_y(upper_left); for (idx, c) in self.children.iter().enumerate() { - let (fg, bg) = if idx == self.cursor_pos { - (Color::Default, Color::Default) + let ThemeAttribute { fg, bg, attrs } = if idx == self.cursor_pos { + tab_focused_attribute } else { - (Color::Byte(15), Color::Byte(8)) + tab_unfocused_attribute }; let (x_, _y_) = write_string_to_grid( &format!(" {} ", c), grid, fg, bg, - Attr::Default, + attrs, (set_x(upper_left, x), bottom_right!(area)), None, ); @@ -1408,9 +1422,11 @@ impl Tabbed { } if self.cursor_pos == self.children.len() - 1 { - cslice[(y * cols) + x].set_ch('▍'); - cslice[(y * cols) + x].set_fg(Color::Byte(8)); - cslice[(y * cols) + x].set_bg(Color::Default); + cslice[(y * cols) + x] + .set_ch('▍') + .set_fg(tab_unfocused_attribute.bg) + .set_bg(tab_unfocused_attribute.fg) + .set_attrs(tab_unfocused_attribute.attrs); } context.dirty_areas.push_back(area); @@ -1838,6 +1854,13 @@ impl Component for Selector { fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool { let (width, height) = self.content.size(); let shortcuts = self.get_shortcuts(context); + let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + highlighted_attrs.attrs |= Attr::Reverse; + } match (event, self.cursor) { (UIEvent::Input(Key::Char('\n')), _) if self.single_only => { /* User can only select one entry, so Enter key finalises the selection */ @@ -1893,34 +1916,34 @@ impl Component for Selector { (UIEvent::Input(Key::Up), SelectorCursor::Entry(c)) if c > 0 => { if self.single_only { // Redraw selection - change_colors( - &mut self.content, - ((2, c + 2), (width - 2, c + 2)), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ((2, c + 1), (width - 2, c + 1)), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter(2..(width - 2), c + 2) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter(2..(width - 2), c + 1) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } self.entries[c].1 = false; self.entries[c - 1].1 = true; } else { // Redraw cursor - change_colors( - &mut self.content, - ((2, c + 2), (4, c + 2)), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ((2, c + 1), (4, c + 1)), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter(2..4, c + 2) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter(2..4, c + 1) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } } self.cursor = SelectorCursor::Entry(c - 1); self.dirty = true; @@ -1930,23 +1953,31 @@ impl Component for Selector { | (UIEvent::Input(ref key), SelectorCursor::Cancel) if shortcut!(key == shortcuts["general"]["scroll_up"]) => { - change_colors( - &mut self.content, - ( - ((width - "OK Cancel".len()) / 2, height - 3), - (width - 1, height - 3), - ), - Color::Default, - Color::Default, - ); + for c in self.content.row_iter( + ((width - "OK Cancel".len()) / 2)..(width - 1), + height - 3, + ) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } let c = self.entries.len().saturating_sub(1); self.cursor = SelectorCursor::Entry(c); - change_colors( - &mut self.content, - ((2, c + 2), (4, c + 2)), - Color::Default, - Color::Byte(8), - ); + let mut highlighted_attrs = + crate::conf::value(context, "widgets.options.highlighted"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + highlighted_attrs.attrs |= Attr::Reverse; + } + for c in self.content.row_iter(2..4, c + 2) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } self.dirty = true; return true; } @@ -1956,34 +1987,34 @@ impl Component for Selector { { if self.single_only { // Redraw selection - change_colors( - &mut self.content, - ((2, c + 2), (width - 2, c + 2)), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ((2, c + 3), (width - 2, c + 3)), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter(2..(width - 2), c + 2) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter(2..(width - 2), c + 3) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } self.entries[c].1 = false; self.entries[c + 1].1 = true; } else { // Redraw cursor - change_colors( - &mut self.content, - ((2, c + 2), (4, c + 2)), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ((2, c + 3), (4, c + 3)), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter(2..4, c + 2) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter(2..4, c + 3) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } } self.cursor = SelectorCursor::Entry(c + 1); self.dirty = true; @@ -1993,21 +2024,21 @@ impl Component for Selector { if !self.single_only && shortcut!(key == shortcuts["general"]["scroll_down"]) => { self.cursor = SelectorCursor::Ok; - change_colors( - &mut self.content, - ((2, c + 2), (4, c + 2)), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ( - ((width - "OK Cancel".len()) / 2, height - 3), - ((width - "OK Cancel".len()) / 2 + 1, height - 3), - ), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter(2..4, c + 2) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter( + ((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1), + height - 3, + ) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } self.dirty = true; return true; } @@ -2015,24 +2046,25 @@ impl Component for Selector { if shortcut!(key == shortcuts["general"]["scroll_right"]) => { self.cursor = SelectorCursor::Cancel; - change_colors( - &mut self.content, - ( - ((width - "OK Cancel".len()) / 2, height - 3), - ((width - "OK Cancel".len()) / 2 + 1, height - 3), - ), - Color::Default, - Color::Default, - ); - change_colors( - &mut self.content, - ( - ((width - "OK Cancel".len()) / 2 + 6, height - 3), - ((width - "OK Cancel".len()) / 2 + 11, height - 3), - ), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter( + ((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1), + height - 3, + ) { + self.content[c] + .set_fg(Color::Default) + .set_bg(Color::Default) + .set_attrs(Attr::Default); + } + for c in self.content.row_iter( + ((width - "OK Cancel".len()) / 2 + 6) + ..((width - "OK Cancel".len()) / 2 + 11), + height - 3, + ) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } self.dirty = true; return true; } @@ -2040,15 +2072,15 @@ impl Component for Selector { if shortcut!(key == shortcuts["general"]["scroll_left"]) => { self.cursor = SelectorCursor::Ok; - change_colors( - &mut self.content, - ( - ((width - "OK Cancel".len()) / 2, height - 3), - ((width - "OK Cancel".len()) / 2 + 1, height - 3), - ), - Color::Default, - Color::Byte(8), - ); + for c in self.content.row_iter( + ((width - "OK Cancel".len()) / 2)..((width - "OK Cancel".len()) / 2 + 1), + height - 3, + ) { + self.content[c] + .set_fg(highlighted_attrs.fg) + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + } change_colors( &mut self.content, ( @@ -2213,6 +2245,13 @@ impl Selector { None, ); } + let mut highlighted_attrs = crate::conf::value(context, "widgets.options.highlighted"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + highlighted_attrs.attrs |= Attr::Reverse; + } if single_only { for (i, e) in entries.iter().enumerate() { write_string_to_grid( @@ -2220,11 +2259,15 @@ impl Selector { &mut content, Color::Default, if i == 0 { - Color::Byte(8) + highlighted_attrs.bg } else { Color::Default }, - Attr::Default, + if i == 0 { + highlighted_attrs.attrs + } else { + Attr::Default + }, ((2, i + 2), (width - 1, i + 2)), None, ); @@ -2241,9 +2284,15 @@ impl Selector { None, ); if i == 0 { - content[(2, i + 2)].set_bg(Color::Byte(8)); - content[(3, i + 2)].set_bg(Color::Byte(8)); - content[(4, i + 2)].set_bg(Color::Byte(8)); + content[(2, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + content[(3, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); + content[(4, i + 2)] + .set_bg(highlighted_attrs.bg) + .set_attrs(highlighted_attrs.attrs); } } write_string_to_grid( diff --git a/ui/src/components/utilities/widgets.rs b/ui/src/components/utilities/widgets.rs index 13c2a3d0f..9b6355c9c 100644 --- a/ui/src/components/utilities/widgets.rs +++ b/ui/src/components/utilities/widgets.rs @@ -375,15 +375,16 @@ impl Component for FormWidget { ), ); + let label_attrs = crate::conf::value(context, "widgets.form.label"); for (i, k) in self.layout.iter().enumerate() { let v = self.fields.get_mut(k).unwrap(); /* Write field label */ write_string_to_grid( k.as_str(), grid, - Color::Default, - Color::Default, - Attr::Bold, + label_attrs.fg, + label_attrs.bg, + label_attrs.attrs, ( pos_inc(upper_left, (1, i)), set_y(bottom_right, i + get_y(upper_left)), @@ -403,15 +404,25 @@ impl Component for FormWidget { /* Highlight if necessary */ if i == self.cursor { if self.focus == FormFocus::Fields { - change_colors( - grid, - ( - pos_inc(upper_left, (0, i)), - set_y(bottom_right, i + get_y(upper_left)), - ), - Color::Default, - Color::Byte(246), - ); + let mut field_attrs = + crate::conf::value(context, "widgets.form.highlighted"); + if std::env::var("NO_COLOR").is_ok() + && (context.settings.terminal.use_color.is_false() + || context.settings.terminal.use_color.is_internal()) + { + field_attrs.attrs |= Attr::Reverse; + } + for row in grid.bounds_iter(( + pos_inc(upper_left, (0, i)), + set_y(bottom_right, i + get_y(upper_left)), + )) { + for c in row { + grid[c] + .set_fg(field_attrs.fg) + .set_bg(field_attrs.bg) + .set_attrs(field_attrs.attrs); + } + } } if self.focus == FormFocus::TextInput { v.draw_cursor( diff --git a/ui/src/conf/terminal.rs b/ui/src/conf/terminal.rs index 94aac1d32..b434ca04d 100644 --- a/ui/src/conf/terminal.rs +++ b/ui/src/conf/terminal.rs @@ -21,6 +21,7 @@ use super::deserializers::non_empty_string; use super::Theme; +use super::ToggleFlag; /// Settings for terminal display #[derive(Debug, Deserialize, Clone, Serialize)] @@ -30,6 +31,7 @@ pub struct TerminalSettings { pub theme: String, pub themes: Theme, pub ascii_drawing: bool, + pub use_color: ToggleFlag, #[serde(deserialize_with = "non_empty_string")] pub window_title: Option, } @@ -40,6 +42,7 @@ impl Default for TerminalSettings { theme: "dark".to_string(), themes: Theme::default(), ascii_drawing: false, + use_color: ToggleFlag::InternalVal(true), window_title: Some("meli".to_string()), } } diff --git a/ui/src/conf/themes.rs b/ui/src/conf/themes.rs index f896dd0ae..6bf2c29bd 100644 --- a/ui/src/conf/themes.rs +++ b/ui/src/conf/themes.rs @@ -149,6 +149,10 @@ const DEFAULT_KEYS: &'static [&'static str] = &[ "tab.focused", "tab.unfocused", "tab.bar", + "widgets.form.label", + "widgets.form.field", + "widgets.form.highlighted", + "widgets.options.highlighted", "mail.sidebar", "mail.sidebar_unread_count", "mail.sidebar_index", @@ -166,6 +170,8 @@ const DEFAULT_KEYS: &'static [&'static str] = &[ "mail.listing.plain.even", "mail.listing.plain.odd", "mail.listing.plain.unseen", + "mail.listing.plain.selected", + "mail.listing.plain.highlighted", "mail.listing.conversations", "mail.listing.conversations.subject", "mail.listing.conversations.from", @@ -216,6 +222,12 @@ impl From for ThemeValue { } } +impl From for ThemeValue { + fn from(from: Attr) -> Self { + ThemeValue::Value(from) + } +} + impl Default for ThemeValue { fn default() -> Self { ThemeValue::Value(Color::Default) @@ -440,8 +452,16 @@ impl Default for Theme { add!("status.notification", dark = { fg: Color::Byte(219), bg: Color::Byte(88) }, light = { fg: Color::Byte(219), bg: Color::Byte(88) }); add!("tab.focused"); - add!("tab.unfocused"); + add!("tab.unfocused", dark = { fg: Color::Byte(15), bg: Color::Byte(8), }, light = { fg: Color::Byte(15), bg: Color::Byte(8), }); add!("tab.bar"); + add!( + "widgets.form.label", + dark = { attrs: Attr::Bold }, + light = { attrs: Attr::Bold } + ); + add!("widgets.form.field"); + add!("widgets.form.highlighted", light = { bg: Color::Byte(246) }, dark = { bg: Color::Byte(246) }); + add!("widgets.options.highlighted", light = { bg: Color::Byte(8) }, dark = { bg: Color::Byte(8) }); /* Mail Sidebar */ @@ -598,13 +618,45 @@ impl Default for Theme { } ); - add!("mail.listing.plain.even"); + /* PlainListing */ + add!("mail.listing.plain.even", + dark = { + bg: Color::Byte(236) + }, + light = { + bg: Color::Byte(252) + } + ); add!("mail.listing.plain.odd"); - add!("mail.listing.plain.unseen"); - add!("mail.listing.conversations.subject"); - add!("mail.listing.conversations.from"); - add!("mail.listing.conversations.date"); - add!("mail.listing.conversations.unseen_padding"); + add!( + "mail.listing.plain.unseen", + dark = { + fg: Color::Byte(0), + bg: Color::Byte(251) + + }, + light = { + fg: Color::Byte(0), + bg: Color::Byte(251) + } + ); + add!("mail.listing.plain.selected", + dark = { + bg: Color::Byte(210) + }, + light = { + bg: Color::Byte(210) + } + ); + add!( + "mail.listing.plain.highlighted", + dark = { + bg: Color::Byte(246) + }, + light = { + bg: Color::Byte(244) + } + ); add!( "mail.view.headers", diff --git a/ui/src/state.rs b/ui/src/state.rs index 43303ab82..6ecf8f203 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -179,6 +179,7 @@ pub struct State { draw_rate_limit: RateLimit, stdout: Option, child: Option, + draw_horizontal_segment_fn: fn(&mut State, usize, usize, usize) -> (), pub mode: UIMode, components: Vec>, pub context: Context, @@ -274,6 +275,14 @@ impl State { components: Vec::with_capacity(1), timer, draw_rate_limit: RateLimit::new(1, 3), + draw_horizontal_segment_fn: if env::var("NO_COLOR").is_ok() + && (settings.terminal.use_color.is_false() + || settings.terminal.use_color.is_internal()) + { + State::draw_horizontal_segment_no_color + } else { + State::draw_horizontal_segment + }, context: Context { accounts, @@ -457,18 +466,18 @@ impl State { continue; } if let Some((x_start, x_end)) = segment.take() { - self.draw_horizontal_segment(x_start, x_end, y); + (self.draw_horizontal_segment_fn)(self, x_start, x_end, y); } match segment { ref mut s @ None => { *s = Some((*x_start, *x_end)); } ref mut s @ Some(_) if s.unwrap().1 < *x_start => { - self.draw_horizontal_segment(s.unwrap().0, s.unwrap().1, y); + (self.draw_horizontal_segment_fn)(self, s.unwrap().0, s.unwrap().1, y); *s = Some((*x_start, *x_end)); } ref mut s @ Some(_) if s.unwrap().1 < *x_end => { - self.draw_horizontal_segment(s.unwrap().0, s.unwrap().1, y); + (self.draw_horizontal_segment_fn)(self, s.unwrap().0, s.unwrap().1, y); *s = Some((s.unwrap().1, *x_end)); } Some((_, ref mut x)) => { @@ -477,7 +486,7 @@ impl State { } } if let Some((x_start, x_end)) = segment { - self.draw_horizontal_segment(x_start, x_end, y); + (self.draw_horizontal_segment_fn)(self, x_start, x_end, y); } } self.flush(); @@ -521,6 +530,33 @@ impl State { } } + fn draw_horizontal_segment_no_color(&mut self, x_start: usize, x_end: usize, y: usize) { + write!( + self.stdout(), + "{}", + cursor::Goto(x_start as u16 + 1, (y + 1) as u16) + ) + .unwrap(); + let mut current_attrs = Attr::Default; + let Self { + ref grid, + ref mut stdout, + .. + } = self; + let stdout = stdout.as_mut().unwrap(); + write!(stdout, "\x1B[m").unwrap(); + for x in x_start..=x_end { + let c = &grid[(x, y)]; + if c.attrs() != current_attrs { + c.attrs().write(current_attrs, stdout).unwrap(); + current_attrs = c.attrs(); + } + if !c.empty() { + write!(stdout, "{}", c.ch()).unwrap(); + } + } + } + /// Draw the entire screen from scratch. pub fn render(&mut self) { self.update_size(); diff --git a/ui/src/terminal/cells.rs b/ui/src/terminal/cells.rs index 394441bb0..875f610a0 100644 --- a/ui/src/terminal/cells.rs +++ b/ui/src/terminal/cells.rs @@ -366,19 +366,6 @@ impl CellBuffer { RowIterator { row, col: 0..0 } } } - - /// row_iter() but with `RangeFrom`. - /// See `RowIterator` documentation. - pub fn row_iter_from(&self, bounds: std::ops::RangeFrom, row: usize) -> RowIterator { - if row < self.rows { - RowIterator { - row, - col: std::cmp::min(self.cols.saturating_sub(1), bounds.start)..self.cols, - } - } else { - RowIterator { row, col: 0..0 } - } - } } impl Deref for CellBuffer { @@ -1407,6 +1394,24 @@ pub enum Attr { BoldReverseUnderline = 0b111, } +impl core::ops::BitOr for Attr { + type Output = Attr; + + fn bitor(self, rhs: Self) -> Self::Output { + match self as u8 | rhs as u8 { + 0b000 => Attr::Default, + 0b001 => Attr::Bold, + 0b100 => Attr::Underline, + 0b011 => Attr::BoldUnderline, + 0b010 => Attr::Reverse, + 0b101 => Attr::BoldReverse, + 0b110 => Attr::UnderlineReverse, + 0b111 => Attr::BoldReverseUnderline, + _ => unsafe { std::hint::unreachable_unchecked() }, + } + } +} + impl core::ops::BitAnd for Attr { type Output = bool; @@ -1415,6 +1420,23 @@ impl core::ops::BitAnd for Attr { } } +impl core::ops::BitOrAssign for Attr { + fn bitor_assign(&mut self, rhs: Attr) { + use Attr::*; + *self = match *self as u8 | rhs as u8 { + 0b000 => Default, + 0b001 => Bold, + 0b100 => Underline, + 0b011 => BoldUnderline, + 0b010 => Reverse, + 0b101 => BoldReverse, + 0b110 => UnderlineReverse, + 0b111 => BoldReverseUnderline, + _ => unsafe { std::hint::unreachable_unchecked() }, + }; + } +} + impl Default for Attr { fn default() -> Self { Attr::Default