diff --git a/melib/src/error.rs b/melib/src/error.rs index 49464e81..b66fdb63 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -173,3 +173,10 @@ impl From<&str> for MeliError { MeliError::new(kind.to_string()) } } + +impl From for MeliError { + #[inline] + fn from(kind: String) -> MeliError { + MeliError::new(kind) + } +} diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 9c5ea6d3..15cae3f8 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -126,34 +126,24 @@ impl ListingTrait for CompactListing { let thread = threads.thread_ref(thread_hash); let fg_color = if thread.unseen() > 0 { - Color::Byte(0) + crate::conf::color(context, "mail.listing.compact.unseen_fg") + } else if self.cursor_pos.2 == idx { + crate::conf::color(context, "mail.listing.compact.highlighted_fg") + } else if idx % 2 == 0 { + crate::conf::color(context, "mail.listing.compact.even_fg") } else { - Color::Default + crate::conf::color(context, "mail.listing.compact.odd_fg") }; - let bg_color = if context.settings.terminal.theme == "light" { - if self.cursor_pos.2 == idx { - Color::Byte(244) - } else if self.selection[&thread_hash] { - Color::Byte(210) - } else if thread.unseen() > 0 { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(252) - } else { - Color::Default - } + let bg_color = if self.cursor_pos.2 == idx { + crate::conf::color(context, "mail.listing.compact.highlighted_bg") + } else if self.selection[&thread_hash] { + crate::conf::color(context, "mail.listing.compact.selected_bg") + } else if thread.unseen() > 0 { + crate::conf::color(context, "mail.listing.compact.unseen_bg") + } else if idx % 2 == 0 { + crate::conf::color(context, "mail.listing.compact.even_bg") } else { - if self.cursor_pos.2 == idx { - Color::Byte(246) - } else if self.selection[&thread_hash] { - Color::Byte(210) - } else if thread.unseen() > 0 { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - } + crate::conf::color(context, "mail.listing.compact.odd_bg") }; let (upper_left, bottom_right) = area; @@ -356,7 +346,10 @@ impl ListingTrait for CompactListing { let c = &self.data_columns.columns[0][(0, r + top_idx)]; if self.selection[&thread_hash] { - (c.fg(), Color::Byte(210)) + ( + c.fg(), + crate::conf::color(context, "mail.listing.compact.selected_bg"), + ) } else { (c.fg(), c.bg()) } @@ -810,27 +803,21 @@ impl CompactListing { panic!(); } let thread = threads.thread_ref(thread); - let fg_color = if thread.unseen() > 0 { - Color::Byte(0) + let (fg_color, bg_color) = if thread.unseen() > 0 { + ( + crate::conf::color(context, "mail.listing.compact.unseen_fg"), + crate::conf::color(context, "mail.listing.compact.unseen_bg"), + ) + } else if idx % 2 == 0 { + ( + crate::conf::color(context, "mail.listing.compact.even_fg"), + crate::conf::color(context, "mail.listing.compact.even_bg"), + ) } else { - Color::Default - }; - let bg_color = if context.settings.terminal.theme == "light" { - if thread.unseen() > 0 { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(252) - } else { - Color::Default - } - } else { - if thread.unseen() > 0 { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - } + ( + crate::conf::color(context, "mail.listing.compact.odd_fg"), + crate::conf::color(context, "mail.listing.compact.odd_bg"), + ) }; let (x, _) = write_string_to_grid( &idx.to_string(), @@ -921,14 +908,26 @@ impl CompactListing { } match (thread.snoozed(), thread.has_attachments()) { (true, true) => { - self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103)); - self.data_columns.columns[3][(2, idx)].set_fg(Color::Red); + self.data_columns.columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.attachment_flag_fg", + )); + self.data_columns.columns[3][(2, idx)].set_fg(crate::conf::color( + context, + "mail.listing.thread_snooze_flag_fg", + )); } (true, false) => { - self.data_columns.columns[3][(0, idx)].set_fg(Color::Red); + self.data_columns.columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.thread_snooze_flag_fg", + )); } (false, true) => { - self.data_columns.columns[3][(0, idx)].set_fg(Color::Byte(103)); + self.data_columns.columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.attachment_flag_fg", + )); } (false, false) => {} } @@ -981,30 +980,24 @@ impl CompactListing { * arrive */ return; } - let envelope: EnvelopeRef = account.collection.get_env(env_hash); - let fg_color = if thread.unseen() > 0 { - Color::Byte(0) - } else { - Color::Default - }; let idx = self.order[&thread_hash]; - let bg_color = if context.settings.terminal.theme == "light" { - if thread.unseen() > 0 { - Color::Byte(251) - } else if idx % 2 == 0 { - Color::Byte(252) - } else { - Color::Default - } + let (fg_color, bg_color) = if thread.unseen() > 0 { + ( + crate::conf::color(context, "mail.listing.compact.unseen_fg"), + crate::conf::color(context, "mail.listing.compact.unseen_bg"), + ) + } else if idx % 2 == 0 { + ( + crate::conf::color(context, "mail.listing.compact.even_fg"), + crate::conf::color(context, "mail.listing.compact.even_bg"), + ) } else { - if thread.unseen() > 0 { - Color::Byte(253) - } else if idx % 2 == 0 { - Color::Byte(236) - } else { - Color::Default - } + ( + crate::conf::color(context, "mail.listing.compact.odd_fg"), + crate::conf::color(context, "mail.listing.compact.odd_bg"), + ) }; + let envelope: EnvelopeRef = account.collection.get_env(env_hash); let strings = self.make_entry_string(&envelope, context, threads, thread_hash); drop(envelope); let columns = &mut self.data_columns.columns; @@ -1108,14 +1101,26 @@ impl CompactListing { } match (thread.snoozed(), thread.has_attachments()) { (true, true) => { - columns[3][(0, idx)].set_fg(Color::Byte(103)); - columns[3][(2, idx)].set_fg(Color::Red); + columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.attachment_flag_fg", + )); + columns[3][(2, idx)].set_fg(crate::conf::color( + context, + "mail.listing.thread_snooze_flag_fg", + )); } (true, false) => { - columns[3][(0, idx)].set_fg(Color::Red); + columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.thread_snooze_flag_fg", + )); } (false, true) => { - columns[3][(0, idx)].set_fg(Color::Byte(103)); + columns[3][(0, idx)].set_fg(crate::conf::color( + context, + "mail.listing.attachment_flag_fg", + )); } (false, false) => {} } diff --git a/ui/src/conf.rs b/ui/src/conf.rs index 06dbfc9f..288dd84d 100644 --- a/ui/src/conf.rs +++ b/ui/src/conf.rs @@ -33,6 +33,8 @@ pub mod tags; pub mod shortcuts; mod listing; pub mod terminal; +mod themes; +pub use themes::*; pub mod accounts; pub use self::accounts::Account; @@ -77,6 +79,7 @@ pub struct MailUIConf { pub identity: Option, pub index_style: Option, pub tags: Option, + pub theme: Option, } #[serde(default)] @@ -330,9 +333,26 @@ impl FileSettings { } } - FileSettings::validate(config_path.to_str().unwrap())?; - let s = pp::pp(config_path.to_str().unwrap()).unwrap(); - let s: FileSettings = toml::from_str(&s).unwrap(); + let path = config_path + .to_str() + .expect("Configuration file path was not valid UTF-8"); + FileSettings::validate(path)?; + let mut s: FileSettings = toml::from_str(&pp::pp(path)?).map_err(|err| err.to_string())?; + let Theme { + light: default_light, + dark: default_dark, + } = Theme::default(); + for (k, v) in default_light.into_iter() { + if !s.terminal.themes.light.contains_key(&k) { + s.terminal.themes.light.insert(k, v); + } + } + for (k, v) in default_dark.into_iter() { + if !s.terminal.themes.dark.contains_key(&k) { + s.terminal.themes.dark.insert(k, v); + } + } + Ok(s) } diff --git a/ui/src/conf/terminal.rs b/ui/src/conf/terminal.rs index 0cbefa5e..94aac1d3 100644 --- a/ui/src/conf/terminal.rs +++ b/ui/src/conf/terminal.rs @@ -20,6 +20,7 @@ */ use super::deserializers::non_empty_string; +use super::Theme; /// Settings for terminal display #[derive(Debug, Deserialize, Clone, Serialize)] @@ -27,6 +28,7 @@ use super::deserializers::non_empty_string; pub struct TerminalSettings { /// light, dark pub theme: String, + pub themes: Theme, pub ascii_drawing: bool, #[serde(deserialize_with = "non_empty_string")] pub window_title: Option, @@ -36,6 +38,7 @@ impl Default for TerminalSettings { fn default() -> Self { TerminalSettings { theme: "dark".to_string(), + themes: Theme::default(), ascii_drawing: false, window_title: Some("meli".to_string()), } diff --git a/ui/src/conf/themes.rs b/ui/src/conf/themes.rs new file mode 100644 index 00000000..a157bede --- /dev/null +++ b/ui/src/conf/themes.rs @@ -0,0 +1,212 @@ +/* + * meli - themes conf module + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use crate::terminal::Color; +use crate::Context; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; + +#[inline(always)] +pub fn color(context: &Context, key: &'static str) -> Color { + (match context.settings.terminal.theme.as_str() { + "light" => &context.settings.terminal.themes.light, + "dark" | _ => &context.settings.terminal.themes.dark, + })[key] +} + +const DEFAULT_KEYS: &'static [&'static str] = &[ + "general.background", + "general.foreground", + "general.status_bar_fg", + "general.status_bar_bg", + "general.tab_focused_fg", + "general.tab_focused_bg", + "general.tab_unfocused_fg", + "general.tab_unfocused_bg", + "general.tab_bar_bg", + "mail.sidebar_fg", + "mail.sidebar_bg", + "mail.sidebar_unread_count_fg", + "mail.sidebar_unread_count_bg", + "mail.sidebar_index_fg", + "mail.sidebar_index_bg", + "mail.sidebar_highlighted_fg", + "mail.sidebar_highlighted_bg", + "mail.sidebar_highlighted_unread_count_fg", + "mail.sidebar_highlighted_unread_count_bg", + "mail.sidebar_highlighted_index_fg", + "mail.sidebar_highlighted_index_bg", + "mail.listing.compact.even_fg", + "mail.listing.compact.even_bg", + "mail.listing.compact.odd_fg", + "mail.listing.compact.odd_bg", + "mail.listing.compact.unseen_fg", + "mail.listing.compact.unseen_fg", + "mail.listing.compact.selected_fg", + "mail.listing.compact.selected_bg", + "mail.listing.plain.even_fg", + "mail.listing.plain.even_bg", + "mail.listing.plain.odd_fg", + "mail.listing.plain.odd_bg", + "mail.listing.plain.unseen_fg", + "mail.listing.plain.unseen_bg", + "mail.listing.conversations.subject_fg", + "mail.listing.conversations.subject_bg", + "mail.listing.conversations.from_fg", + "mail.listing.conversations.from_bg", + "mail.listing.conversations.date_fg", + "mail.listing.conversations.date_bg", + "mail.listing.conversations.padding", + "mail.listing.conversations.unseen_fg", + "mail.listing.conversations.unseen_bg", + "mail.listing.conversations.unseen_padding", + "mail.view.headers_fg", + "mail.view.headers_bg", + "mail.view.body_fg", + "mail.view.body_bg", + "mail.listing.attachment_flag_fg", + "mail.listing.attachment_flag_bg", + "mail.listing.thread_snooze_flag_fg", + "mail.listing.thread_snooze_flag_bg", +]; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Theme { + #[serde(default)] + pub light: HashMap, Color>, + #[serde(default)] + pub dark: HashMap, Color>, +} + +impl Theme { + pub fn validate(&self) -> Option { + let hash_set: HashSet<&'static str> = DEFAULT_KEYS.into_iter().map(|k| *k).collect(); + let keys: Vec<&'_ str> = self + .light + .keys() + .chain(self.dark.keys()) + .filter_map(|k| { + if !hash_set.contains(&k.as_ref()) { + Some(k.as_ref()) + } else { + None + } + }) + .collect(); + if keys.is_empty() { + None + } else { + Some(format!("Unrecognized theme keywords: {}", keys.join(", "))) + } + } +} + +impl Default for Theme { + fn default() -> Theme { + let mut light = HashMap::default(); + let mut dark = HashMap::default(); + + light.insert("general.background".into(), Color::Default); + light.insert("general.foreground".into(), Color::Default); + + dark.insert("general.background".into(), Color::Default); + dark.insert("general.foreground".into(), Color::Default); + /* + "general.status_bar_fg", + "general.status_bar_bg", + "general.tab_focused_fg", + "general.tab_focused_bg", + "general.tab_unfocused_fg", + "general.tab_unfocused_bg", + "general.tab_bar_bg", + "mail.sidebar_fg", + "mail.sidebar_bg", + "mail.sidebar_unread_count_fg", + "mail.sidebar_unread_count_bg", + "mail.sidebar_index_fg", + "mail.sidebar_index_bg", + "mail.sidebar_highlighted_fg", + "mail.sidebar_highlighted_bg", + "mail.sidebar_highlighted_unread_count_fg", + "mail.sidebar_highlighted_unread_count_bg", + "mail.sidebar_highlighted_index_fg", + "mail.sidebar_highlighted_index_bg", + */ + light.insert("mail.listing.compact.even_fg".into(), Color::Default); + light.insert("mail.listing.compact.even_bg".into(), Color::Byte(252)); + light.insert("mail.listing.compact.odd_fg".into(), Color::Default); + light.insert("mail.listing.compact.odd_bg".into(), Color::Default); + light.insert("mail.listing.compact.unseen_fg".into(), Color::Byte(0)); + light.insert("mail.listing.compact.unseen_bg".into(), Color::Byte(251)); + light.insert("mail.listing.compact.selected_fg".into(), Color::Default); + light.insert("mail.listing.compact.selected_bg".into(), Color::Byte(210)); + light.insert("mail.listing.compact.highlighted_fg".into(), Color::Default); + light.insert( + "mail.listing.compact.highlighted_bg".into(), + Color::Byte(244), + ); + + dark.insert("mail.listing.compact.even_fg".into(), Color::Default); + dark.insert("mail.listing.compact.even_bg".into(), Color::Byte(236)); + dark.insert("mail.listing.compact.odd_fg".into(), Color::Default); + dark.insert("mail.listing.compact.odd_bg".into(), Color::Default); + dark.insert("mail.listing.compact.unseen_fg".into(), Color::Byte(0)); + dark.insert("mail.listing.compact.unseen_bg".into(), Color::Byte(251)); + dark.insert("mail.listing.compact.selected_fg".into(), Color::Default); + dark.insert("mail.listing.compact.selected_bg".into(), Color::Byte(210)); + dark.insert("mail.listing.compact.highlighted_fg".into(), Color::Default); + dark.insert( + "mail.listing.compact.highlighted_bg".into(), + Color::Byte(246), + ); + /* + "mail.listing.plain.even_fg", + "mail.listing.plain.even_bg", + "mail.listing.plain.odd_fg", + "mail.listing.plain.odd_bg", + "mail.listing.plain.unseen_fg", + "mail.listing.plain.unseen_bg", + "mail.listing.conversations.subject_fg", + "mail.listing.conversations.subject_bg", + "mail.listing.conversations.from_fg", + "mail.listing.conversations.from_bg", + "mail.listing.conversations.date_fg", + "mail.listing.conversations.date_bg", + "mail.listing.conversations.padding", + "mail.listing.conversations.unseen_fg", + "mail.listing.conversations.unseen_bg", + "mail.listing.conversations.unseen_padding", + "mail.view.headers_fg", + "mail.view.headers_bg", + "mail.view.body_fg", + "mail.view.body_bg", + */ + light.insert("mail.listing.attachment_flag_fg".into(), Color::Byte(103)); + light.insert("mail.listing.attachment_flag_bg".into(), Color::Default); + light.insert("mail.listing.thread_snooze_flag_fg".into(), Color::Red); + light.insert("mail.listing.thread_snooze_flag_bg".into(), Color::Default); + dark.insert("mail.listing.attachment_flag_fg".into(), Color::Byte(103)); + dark.insert("mail.listing.attachment_flag_bg".into(), Color::Default); + dark.insert("mail.listing.thread_snooze_flag_fg".into(), Color::Red); + dark.insert("mail.listing.thread_snooze_flag_bg".into(), Color::Default); + + Theme { light, dark } + } +}