1 use super::Color;
2 use enum_map::{enum_map, Enum, EnumMap};
3 #[cfg(feature = "toml")]
4 use log::warn;
5 
6 use std::ops::{Index, IndexMut};
7 use std::str::FromStr;
8 
9 // Use AHash instead of the slower SipHash
10 type HashMap<K, V> = std::collections::HashMap<K, V, ahash::RandomState>;
11 
12 /// Error parsing a color.
13 #[derive(Debug)]
14 pub struct NoSuchColor;
15 
16 impl std::fmt::Display for NoSuchColor {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result17     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18         write!(f, "Could not parse the given color")
19     }
20 }
21 
22 impl std::error::Error for NoSuchColor {}
23 
24 /// Color configuration for the application.
25 ///
26 /// Assign each color role an actual color.
27 ///
28 /// It implements `Index` and `IndexMut` to access and modify this mapping:
29 ///
30 /// It also implements [`Extend`] to update a batch of colors at
31 /// once.
32 ///
33 /// # Example
34 ///
35 /// ```rust
36 /// # use cursive_core::theme::Palette;
37 /// use cursive_core::theme::{BaseColor::*, Color::*, PaletteColor::*};
38 ///
39 /// let mut palette = Palette::default();
40 ///
41 /// assert_eq!(palette[Background], Dark(Blue));
42 /// palette[Shadow] = Light(Red);
43 /// assert_eq!(palette[Shadow], Light(Red));
44 ///
45 /// let colors = vec![(Shadow, Dark(Green)), (Primary, Light(Blue))];
46 /// palette.extend(colors);
47 /// assert_eq!(palette[Shadow], Dark(Green));
48 /// assert_eq!(palette[Primary], Light(Blue));
49 /// ```
50 #[derive(PartialEq, Eq, Clone, Debug)]
51 pub struct Palette {
52     basic: EnumMap<PaletteColor, Color>,
53     custom: HashMap<String, PaletteNode>,
54 }
55 
56 /// A node in the palette tree.
57 ///
58 /// This describes a value attached to a custom keyword in the palette.
59 ///
60 /// This can either be a color, or a nested namespace with its own mapping.
61 #[derive(PartialEq, Eq, Clone, Debug)]
62 pub enum PaletteNode {
63     /// A single color.
64     Color(Color),
65     /// A group of values bundled in the same namespace.
66     ///
67     /// Namespaces can be merged in the palette with `Palette::merge`.
68     Namespace(HashMap<String, PaletteNode>),
69 }
70 
71 // Basic usage: only use basic colors
72 impl Index<PaletteColor> for Palette {
73     type Output = Color;
74 
index(&self, palette_color: PaletteColor) -> &Color75     fn index(&self, palette_color: PaletteColor) -> &Color {
76         &self.basic[palette_color]
77     }
78 }
79 
80 // We can alter existing color if needed (but why?...)
81 impl IndexMut<PaletteColor> for Palette {
index_mut(&mut self, palette_color: PaletteColor) -> &mut Color82     fn index_mut(&mut self, palette_color: PaletteColor) -> &mut Color {
83         &mut self.basic[palette_color]
84     }
85 }
86 
87 impl Palette {
88     /// Returns a custom color from this palette.
89     ///
90     /// Returns `None` if the given key was not found.
custom<'a>(&'a self, key: &str) -> Option<&'a Color>91     pub fn custom<'a>(&'a self, key: &str) -> Option<&'a Color> {
92         self.custom.get(key).and_then(|node| {
93             if let PaletteNode::Color(ref color) = *node {
94                 Some(color)
95             } else {
96                 None
97             }
98         })
99     }
100 
101     /// Returns a new palette where the given namespace has been merged.
102     ///
103     /// All values in the namespace will override previous values.
merge(&self, namespace: &str) -> Palette104     pub fn merge(&self, namespace: &str) -> Palette {
105         let mut result = self.clone();
106 
107         if let Some(&PaletteNode::Namespace(ref palette)) =
108             self.custom.get(namespace)
109         {
110             // Merge `result` and `palette`
111             for (key, value) in palette.iter() {
112                 match *value {
113                     PaletteNode::Color(color) => result.set_color(key, color),
114                     PaletteNode::Namespace(ref map) => {
115                         result.add_namespace(key, map.clone())
116                     }
117                 }
118             }
119         }
120 
121         result
122     }
123 
124     /// Sets the color for the given key.
125     ///
126     /// This will update either the basic palette or the custom values.
set_color(&mut self, key: &str, color: Color)127     pub fn set_color(&mut self, key: &str, color: Color) {
128         if self.set_basic_color(key, color).is_err() {
129             self.custom
130                 .insert(key.to_string(), PaletteNode::Color(color));
131         }
132     }
133 
134     /// Sets a basic color from its name.
135     ///
136     /// Returns `Err(())` if `key` is not a known `PaletteColor`.
set_basic_color( &mut self, key: &str, color: Color, ) -> Result<(), NoSuchColor>137     pub fn set_basic_color(
138         &mut self,
139         key: &str,
140         color: Color,
141     ) -> Result<(), NoSuchColor> {
142         PaletteColor::from_str(key).map(|c| self.basic[c] = color)
143     }
144 
145     /// Adds a color namespace to this palette.
add_namespace( &mut self, key: &str, namespace: HashMap<String, PaletteNode>, )146     pub fn add_namespace(
147         &mut self,
148         key: &str,
149         namespace: HashMap<String, PaletteNode>,
150     ) {
151         self.custom
152             .insert(key.to_string(), PaletteNode::Namespace(namespace));
153     }
154 }
155 
156 impl Extend<(PaletteColor, Color)> for Palette {
extend<T>(&mut self, iter: T) where T: IntoIterator<Item = (PaletteColor, Color)>,157     fn extend<T>(&mut self, iter: T)
158     where
159         T: IntoIterator<Item = (PaletteColor, Color)>,
160     {
161         for (k, v) in iter {
162             (*self)[k] = v;
163         }
164     }
165 }
166 
167 /// Returns the default palette for a cursive application.
168 ///
169 /// * `Background` => `Dark(Blue)`
170 /// * `Shadow` => `Dark(Black)`
171 /// * `View` => `Dark(White)`
172 /// * `Primary` => `Dark(Black)`
173 /// * `Secondary` => `Dark(Blue)`
174 /// * `Tertiary` => `Light(White)`
175 /// * `TitlePrimary` => `Dark(Red)`
176 /// * `TitleSecondary` => `Dark(Yellow)`
177 /// * `Highlight` => `Dark(Red)`
178 /// * `HighlightInactive` => `Dark(Blue)`
179 /// * `HighlightText` => `Dark(White)`
180 impl Default for Palette {
default() -> Palette181     fn default() -> Palette {
182         use self::PaletteColor::*;
183         use crate::theme::BaseColor::*;
184         use crate::theme::Color::*;
185 
186         Palette {
187             basic: enum_map! {
188                 Background => Dark(Blue),
189                 Shadow => Dark(Black),
190                 View => Dark(White),
191                 Primary => Dark(Black),
192                 Secondary => Dark(Blue),
193                 Tertiary => Light(White),
194                 TitlePrimary => Dark(Red),
195                 TitleSecondary => Light(Blue),
196                 Highlight => Dark(Red),
197                 HighlightInactive => Dark(Blue),
198                 HighlightText => Dark(White),
199             },
200             custom: HashMap::default(),
201         }
202     }
203 }
204 
205 // Iterate over a toml
206 #[cfg(feature = "toml")]
iterate_toml( table: &toml::value::Table, ) -> impl Iterator<Item = (&str, PaletteNode)>207 fn iterate_toml(
208     table: &toml::value::Table,
209 ) -> impl Iterator<Item = (&str, PaletteNode)> {
210     table.iter().flat_map(|(key, value)| {
211         let node = match value {
212             toml::Value::Table(table) => {
213                 // This should define a new namespace
214                 // Treat basic colors as simple string.
215                 // We'll convert them back in the merge method.
216                 let map = iterate_toml(table)
217                     .map(|(key, value)| (key.to_string(), value))
218                     .collect();
219                 // Should we only return something if it's non-empty?
220                 Some(PaletteNode::Namespace(map))
221             }
222             toml::Value::Array(colors) => {
223                 // This should be a list of colors - just pick the first valid one.
224                 colors
225                     .iter()
226                     .flat_map(toml::Value::as_str)
227                     .flat_map(Color::parse)
228                     .map(PaletteNode::Color)
229                     .next()
230             }
231             toml::Value::String(color) => {
232                 // This describe a new color - easy!
233                 Color::parse(color).map(PaletteNode::Color)
234             }
235             other => {
236                 // Other - error?
237                 warn!(
238                     "Found unexpected value in theme: {} = {:?}",
239                     key, other
240                 );
241                 None
242             }
243         };
244 
245         node.map(|node| (key.as_str(), node))
246     })
247 }
248 
249 /// Fills `palette` with the colors from the given `table`.
250 #[cfg(feature = "toml")]
load_toml(palette: &mut Palette, table: &toml::value::Table)251 pub(crate) fn load_toml(palette: &mut Palette, table: &toml::value::Table) {
252     // TODO: use serde for that?
253     // Problem: toml-rs doesn't do well with Enums...
254 
255     for (key, value) in iterate_toml(table) {
256         match value {
257             PaletteNode::Color(color) => palette.set_color(key, color),
258             PaletteNode::Namespace(map) => palette.add_namespace(key, map),
259         }
260     }
261 }
262 
263 /// Color entry in a palette.
264 ///
265 /// Each `PaletteColor` is used for a specific role in a default application.
266 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Enum)]
267 pub enum PaletteColor {
268     /// Color used for the application background.
269     Background,
270     /// Color used for View shadows.
271     Shadow,
272     /// Color used for View backgrounds.
273     View,
274     /// Primary color used for the text.
275     Primary,
276     /// Secondary color used for the text.
277     Secondary,
278     /// Tertiary color used for the text.
279     Tertiary,
280     /// Primary color used for title text.
281     TitlePrimary,
282     /// Secondary color used for title text.
283     TitleSecondary,
284     /// Color used for highlighting text.
285     Highlight,
286     /// Color used for highlighting inactive text.
287     HighlightInactive,
288     /// Color used for highlighted text
289     HighlightText,
290 }
291 
292 impl PaletteColor {
293     /// Given a palette, resolve `self` to a concrete color.
resolve(self, palette: &Palette) -> Color294     pub fn resolve(self, palette: &Palette) -> Color {
295         palette[self]
296     }
297 }
298 
299 impl FromStr for PaletteColor {
300     type Err = NoSuchColor;
301 
from_str(s: &str) -> Result<Self, NoSuchColor>302     fn from_str(s: &str) -> Result<Self, NoSuchColor> {
303         use PaletteColor::*;
304 
305         Ok(match s {
306             "Background" | "background" => Background,
307             "Shadow" | "shadow" => Shadow,
308             "View" | "view" => View,
309             "Primary" | "primary" => Primary,
310             "Secondary" | "secondary" => Secondary,
311             "Tertiary" | "tertiary" => Tertiary,
312             "TitlePrimary" | "title_primary" => TitlePrimary,
313             "TitleSecondary" | "title_secondary" => TitleSecondary,
314             "Highlight" | "highlight" => Highlight,
315             "HighlightInactive" | "highlight_inactive" => HighlightInactive,
316             "HighlightText" | "highlight_text" => HighlightText,
317             _ => return Err(NoSuchColor),
318         })
319     }
320 }
321