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