use super::Color; use enum_map::{enum_map, Enum, EnumMap}; #[cfg(feature = "toml")] use log::warn; use std::ops::{Index, IndexMut}; use std::str::FromStr; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Color configuration for the application. /// /// Assign each color role an actual color. /// /// It implements `Index` and `IndexMut` to access and modify this mapping: /// /// It also implements [`Extend`] to update a batch of colors at /// once. /// /// # Example /// /// ```rust /// # use cursive::theme::Palette; /// use cursive::theme::{BaseColor::*, Color::*, PaletteColor::*}; /// /// let mut palette = Palette::default(); /// /// assert_eq!(palette[Background], Dark(Blue)); /// palette[Shadow] = Light(Red); /// assert_eq!(palette[Shadow], Light(Red)); /// /// let colors = vec![(Shadow, Dark(Green)), (Primary, Light(Blue))]; /// palette.extend(colors); /// assert_eq!(palette[Shadow], Dark(Green)); /// assert_eq!(palette[Primary], Light(Blue)); /// ``` #[derive(PartialEq, Eq, Clone, Debug)] pub struct Palette { basic: EnumMap, custom: HashMap, } /// A node in the palette tree. /// /// This describes a value attached to a custom keyword in the palette. /// /// This can either be a color, or a nested namespace with its own mapping. #[derive(PartialEq, Eq, Clone, Debug)] pub enum PaletteNode { /// A single color. Color(Color), /// A group of values bundled in the same namespace. /// /// Namespaces can be merged in the palette with `Palette::merge`. Namespace(HashMap), } // Basic usage: only use basic colors impl Index for Palette { type Output = Color; fn index(&self, palette_color: PaletteColor) -> &Color { &self.basic[palette_color] } } // We can alter existing color if needed (but why?...) impl IndexMut for Palette { fn index_mut(&mut self, palette_color: PaletteColor) -> &mut Color { &mut self.basic[palette_color] } } impl Palette { /// Returns a custom color from this palette. /// /// Returns `None` if the given key was not found. pub fn custom<'a>(&'a self, key: &str) -> Option<&'a Color> { self.custom.get(key).and_then(|node| { if let PaletteNode::Color(ref color) = *node { Some(color) } else { None } }) } /// Returns a new palette where the given namespace has been merged. /// /// All values in the namespace will override previous values. pub fn merge(&self, namespace: &str) -> Palette { let mut result = self.clone(); if let Some(&PaletteNode::Namespace(ref palette)) = self.custom.get(namespace) { // Merge `result` and `palette` for (key, value) in palette.iter() { match *value { PaletteNode::Color(color) => result.set_color(key, color), PaletteNode::Namespace(ref map) => { result.add_namespace(key, map.clone()) } } } } result } /// Sets the color for the given key. /// /// This will update either the basic palette or the custom values. pub fn set_color(&mut self, key: &str, color: Color) { if self.set_basic_color(key, color).is_err() { self.custom .insert(key.to_string(), PaletteNode::Color(color)); } } /// Sets a basic color from its name. /// /// Returns `Err(())` if `key` is not a known `PaletteColor`. pub fn set_basic_color( &mut self, key: &str, color: Color, ) -> Result<(), ()> { PaletteColor::from_str(key).map(|c| self.basic[c] = color) } /// Adds a color namespace to this palette. pub fn add_namespace( &mut self, key: &str, namespace: HashMap, ) { self.custom .insert(key.to_string(), PaletteNode::Namespace(namespace)); } } impl Extend<(PaletteColor, Color)> for Palette { fn extend(&mut self, iter: T) where T: IntoIterator, { for (k, v) in iter { (*self)[k] = v; } } } /// Returns the default palette for a cursive application. /// /// * `Background` => `Dark(Blue)` /// * `Shadow` => `Dark(Black)` /// * `View` => `Dark(White)` /// * `Primary` => `Dark(Black)` /// * `Secondary` => `Dark(Blue)` /// * `Tertiary` => `Light(White)` /// * `TitlePrimary` => `Dark(Red)` /// * `TitleSecondary` => `Dark(Yellow)` /// * `Highlight` => `Dark(Red)` /// * `HighlightInactive` => `Dark(Blue)` /// * `HighlightText` => `Dark(White)` impl Default for Palette { fn default() -> Palette { use self::PaletteColor::*; use crate::theme::BaseColor::*; use crate::theme::Color::*; Palette { basic: enum_map! { Background => Dark(Blue), Shadow => Dark(Black), View => Dark(White), Primary => Dark(Black), Secondary => Dark(Blue), Tertiary => Light(White), TitlePrimary => Dark(Red), TitleSecondary => Light(Blue), Highlight => Dark(Red), HighlightInactive => Dark(Blue), HighlightText => Dark(White), }, custom: HashMap::default(), } } } // Iterate over a toml #[cfg(feature = "toml")] fn iterate_toml<'a>( table: &'a toml::value::Table, ) -> impl Iterator + 'a { table.iter().flat_map(|(key, value)| { let node = match value { toml::Value::Table(table) => { // This should define a new namespace // Treat basic colors as simple string. // We'll convert them back in the merge method. let map = iterate_toml(table) .map(|(key, value)| (key.to_string(), value)) .collect(); // Should we only return something if it's non-empty? Some(PaletteNode::Namespace(map)) } toml::Value::Array(colors) => { // This should be a list of colors - just pick the first valid one. colors .iter() .flat_map(toml::Value::as_str) .flat_map(Color::parse) .map(PaletteNode::Color) .next() } toml::Value::String(color) => { // This describe a new color - easy! Color::parse(color).map(PaletteNode::Color) } other => { // Other - error? warn!( "Found unexpected value in theme: {} = {:?}", key, other ); None } }; node.map(|node| (key.as_str(), node)) }) } /// Fills `palette` with the colors from the given `table`. #[cfg(feature = "toml")] pub(crate) fn load_toml(palette: &mut Palette, table: &toml::value::Table) { // TODO: use serde for that? // Problem: toml-rs doesn't do well with Enums... for (key, value) in iterate_toml(table) { match value { PaletteNode::Color(color) => palette.set_color(key, color), PaletteNode::Namespace(map) => palette.add_namespace(key, map), } } } /// Color entry in a palette. /// /// Each `PaletteColor` is used for a specific role in a default application. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Enum)] pub enum PaletteColor { /// Color used for the application background. Background, /// Color used for View shadows. Shadow, /// Color used for View backgrounds. View, /// Primary color used for the text. Primary, /// Secondary color used for the text. Secondary, /// Tertiary color used for the text. Tertiary, /// Primary color used for title text. TitlePrimary, /// Secondary color used for title text. TitleSecondary, /// Color used for highlighting text. Highlight, /// Color used for highlighting inactive text. HighlightInactive, /// Color used for highlighted text HighlightText, } impl PaletteColor { /// Given a palette, resolve `self` to a concrete color. pub fn resolve(self, palette: &Palette) -> Color { palette[self] } } impl FromStr for PaletteColor { type Err = (); fn from_str(s: &str) -> Result { use PaletteColor::*; Ok(match s { "Background" | "background" => Background, "Shadow" | "shadow" => Shadow, "View" | "view" => View, "Primary" | "primary" => Primary, "Secondary" | "secondary" => Secondary, "Tertiary" | "tertiary" => Tertiary, "TitlePrimary" | "title_primary" => TitlePrimary, "TitleSecondary" | "title_secondary" => TitleSecondary, "Highlight" | "highlight" => Highlight, "HighlightInactive" | "highlight_inactive" => HighlightInactive, "HighlightText" | "highlight_text" => HighlightText, _ => return Err(()), }) } }