use crate::fs;
use de::deserialize_color_hex_string;
use serde::Deserialize;
use std::cmp::Ordering;
pub async fn load_user_themes() -> Vec<Theme> {
log::debug!("loading user themes");
fs::load_user_themes().await
}
#[derive(Debug, Clone, Deserialize)]
pub struct Theme {
pub name: String,
pub palette: ColorPalette,
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct BaseColors {
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub background: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub foreground: iced_native::Color,
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct NormalColors {
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub primary: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub secondary: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub surface: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub error: iced_native::Color,
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct BrightColors {
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub primary: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub secondary: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub surface: iced_native::Color,
#[serde(deserialize_with = "deserialize_color_hex_string")]
pub error: iced_native::Color,
}
#[derive(Debug, Clone, Copy, Deserialize)]
pub struct ColorPalette {
pub base: BaseColors,
pub normal: NormalColors,
pub bright: BrightColors,
}
impl Theme {
pub fn dark() -> Theme {
Theme {
name: "Dark".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#111111").unwrap(),
foreground: hex_to_color("#1C1C1C").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#3f2b56").unwrap(),
secondary: hex_to_color("#4a3c1c").unwrap(),
surface: hex_to_color("#828282").unwrap(),
error: hex_to_color("#992B2B").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#BA84FC").unwrap(),
secondary: hex_to_color("#ffd03c").unwrap(),
surface: hex_to_color("#E0E0E0").unwrap(),
error: hex_to_color("#C13047").unwrap(),
},
},
}
}
pub fn light() -> Theme {
Theme {
name: "Light".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#ffffff").unwrap(),
foreground: hex_to_color("#F5F5F5").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#DFDBFF").unwrap(),
secondary: hex_to_color("#F9D659").unwrap(),
surface: hex_to_color("#828282").unwrap(),
error: hex_to_color("#992B2B").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#9580ff").unwrap(),
secondary: hex_to_color("#EAA326").unwrap(),
surface: hex_to_color("#000000").unwrap(),
error: hex_to_color("#C13047").unwrap(),
},
},
}
}
pub fn alliance() -> Theme {
Theme {
name: "Alliance".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#011930").unwrap(),
foreground: hex_to_color("#022242").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#3e3523").unwrap(),
secondary: hex_to_color("#3e3523").unwrap(),
surface: hex_to_color("#7F8387").unwrap(),
error: hex_to_color("#5b3a5e").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#ac8a1b").unwrap(),
secondary: hex_to_color("#ac8a1b").unwrap(),
surface: hex_to_color("#B4B9BF").unwrap(),
error: hex_to_color("#953e43").unwrap(),
},
},
}
}
pub fn horde() -> Theme {
Theme {
name: "Horde".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#161313").unwrap(),
foreground: hex_to_color("#211C1C").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#331E1F").unwrap(),
secondary: hex_to_color("#542A18").unwrap(),
surface: hex_to_color("#5E5B5A").unwrap(),
error: hex_to_color("#44282a").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#953e43").unwrap(),
secondary: hex_to_color("#e27342").unwrap(),
surface: hex_to_color("#9B9897").unwrap(),
error: hex_to_color("#953e43").unwrap(),
},
},
}
}
pub fn ayu() -> Theme {
Theme {
name: "Ayu".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#1f2430").unwrap(),
foreground: hex_to_color("#232834").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#987a47").unwrap(),
secondary: hex_to_color("#315e6b").unwrap(),
surface: hex_to_color("#60697a").unwrap(),
error: hex_to_color("#712a34").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#ffcc66").unwrap(),
secondary: hex_to_color("#5ccfe6").unwrap(),
surface: hex_to_color("#cbccc6").unwrap(),
error: hex_to_color("#ff3333").unwrap(),
},
},
}
}
pub fn dracula() -> Theme {
Theme {
name: "Dracula".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#282a36").unwrap(),
foreground: hex_to_color("#353746").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#483e61").unwrap(),
secondary: hex_to_color("#386e50").unwrap(),
surface: hex_to_color("#a2a4a3").unwrap(),
error: hex_to_color("#A13034").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#bd94f9").unwrap(),
secondary: hex_to_color("#49eb7a").unwrap(),
surface: hex_to_color("#f4f8f3").unwrap(),
error: hex_to_color("#ff7ac6").unwrap(),
},
},
}
}
pub fn forest_night() -> Theme {
Theme {
name: "Forest Night".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#323d43").unwrap(),
foreground: hex_to_color("#3c474d").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#505a60").unwrap(),
secondary: hex_to_color("#465258").unwrap(),
surface: hex_to_color("#999f93").unwrap(),
error: hex_to_color("#74484c").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#a7c080").unwrap(),
secondary: hex_to_color("#83b6af").unwrap(),
surface: hex_to_color("#d8caac").unwrap(),
error: hex_to_color("#e68183").unwrap(),
},
},
}
}
pub fn gruvbox() -> Theme {
Theme {
name: "Gruvbox".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#282828").unwrap(),
foreground: hex_to_color("#3c3836").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#63612f").unwrap(),
secondary: hex_to_color("#695133").unwrap(),
surface: hex_to_color("#928374").unwrap(),
error: hex_to_color("#81302e").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#98971a").unwrap(),
secondary: hex_to_color("#d79921").unwrap(),
surface: hex_to_color("#ebdbb2").unwrap(),
error: hex_to_color("#cc241d").unwrap(),
},
},
}
}
pub fn nord() -> Theme {
Theme {
name: "Nord".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#2e3440").unwrap(),
foreground: hex_to_color("#3b4252").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#485b60").unwrap(),
secondary: hex_to_color("#425066").unwrap(),
surface: hex_to_color("#9196a1").unwrap(),
error: hex_to_color("#894f5a").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#8fbcbb").unwrap(),
secondary: hex_to_color("#5e81ac").unwrap(),
surface: hex_to_color("#eceff4").unwrap(),
error: hex_to_color("#bf616a").unwrap(),
},
},
}
}
pub fn outrun() -> Theme {
Theme {
name: "Outrun".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#0d0821").unwrap(),
foreground: hex_to_color("#110A2B").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#330442").unwrap(),
secondary: hex_to_color("#6e3e2e").unwrap(),
surface: hex_to_color("#484e81").unwrap(),
error: hex_to_color("#671a30").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#ff00ff").unwrap(),
secondary: hex_to_color("#ff963a").unwrap(),
surface: hex_to_color("#757dc8").unwrap(),
error: hex_to_color("#db2c3e").unwrap(),
},
},
}
}
pub fn solarized_dark() -> Theme {
Theme {
name: "Solarized Dark".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#012b36").unwrap(),
foreground: hex_to_color("#093642").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#1A615B").unwrap(),
secondary: hex_to_color("#523F09").unwrap(),
surface: hex_to_color("#63797e").unwrap(),
error: hex_to_color("#b80f15").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#2aa096").unwrap(),
secondary: hex_to_color("#a37f12").unwrap(),
surface: hex_to_color("#93a1a1").unwrap(),
error: hex_to_color("#EE2F36").unwrap(),
},
},
}
}
pub fn solarized_light() -> Theme {
Theme {
name: "Solarized Light".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#fdf6e3").unwrap(),
foreground: hex_to_color("#eee8d5").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#BCCCC3").unwrap(),
secondary: hex_to_color("#ccbd9e").unwrap(),
surface: hex_to_color("#95a3a2").unwrap(),
error: hex_to_color("#b80f15").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#2aa096").unwrap(),
secondary: hex_to_color("#a37f12").unwrap(),
surface: hex_to_color("#4C5D63").unwrap(),
error: hex_to_color("#EE2F36").unwrap(),
},
},
}
}
pub fn sort() -> Theme {
Theme {
name: "Sort".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#000000").unwrap(),
foreground: hex_to_color("#101010").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#525252").unwrap(),
secondary: hex_to_color("#525252").unwrap(),
surface: hex_to_color("#525252").unwrap(),
error: hex_to_color("#525252").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#A3A3A3").unwrap(),
secondary: hex_to_color("#A3A3A3").unwrap(),
surface: hex_to_color("#A3A3A3").unwrap(),
error: hex_to_color("#A3A3A3").unwrap(),
},
},
}
}
pub fn ferra() -> Theme {
Theme {
name: "Ferra".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#211f22").unwrap(),
foreground: hex_to_color("#2b292d").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#664A50").unwrap(),
secondary: hex_to_color("#855859").unwrap(),
surface: hex_to_color("#816961").unwrap(),
error: hex_to_color("#713f47").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#b4838d").unwrap(),
secondary: hex_to_color("#e5989b").unwrap(),
surface: hex_to_color("#fecdb2").unwrap(),
error: hex_to_color("#e06b75").unwrap(),
},
},
}
}
pub fn one_dark() -> Theme {
Theme {
name: "One Dark".to_string(),
palette: ColorPalette {
base: BaseColors {
background: hex_to_color("#282c34").unwrap(),
foreground: hex_to_color("#2c323c").unwrap(),
},
normal: NormalColors {
primary: hex_to_color("#385c7c").unwrap(),
secondary: hex_to_color("#654473").unwrap(),
surface: hex_to_color("#5b626e").unwrap(),
error: hex_to_color("#713f47").unwrap(),
},
bright: BrightColors {
primary: hex_to_color("#61afef").unwrap(),
secondary: hex_to_color("#c679dd").unwrap(),
surface: hex_to_color("#a6adba").unwrap(),
error: hex_to_color("#e06b75").unwrap(),
},
},
}
}
}
fn hex_to_color(hex: &str) -> Option<iced_native::Color> {
if hex.len() == 7 {
let hash = &hex[0..1];
let r = u8::from_str_radix(&hex[1..3], 16);
let g = u8::from_str_radix(&hex[3..5], 16);
let b = u8::from_str_radix(&hex[5..7], 16);
return match (hash, r, g, b) {
("#", Ok(r), Ok(g), Ok(b)) => Some(iced_native::Color {
r: r as f32 / 255.0,
g: g as f32 / 255.0,
b: b as f32 / 255.0,
a: 1.0,
}),
_ => None,
};
}
None
}
impl PartialEq for Theme {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl PartialOrd for Theme {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.name.cmp(&other.name))
}
}
impl Eq for Theme {}
impl Ord for Theme {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
struct Color(iced_native::Color);
mod de {
use super::{hex_to_color, Color};
use serde::de::{self, Error, Unexpected, Visitor};
use std::fmt;
pub(crate) fn deserialize_color_hex_string<'de, D>(
deserializer: D,
) -> Result<iced_native::Color, D::Error>
where
D: de::Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a hex string in the format of '#09ACDF'")
}
#[allow(clippy::unnecessary_unwrap)]
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
if let Some(color) = hex_to_color(s) {
return Ok(Color(color));
}
Err(de::Error::invalid_value(Unexpected::Str(s), &self))
}
}
deserializer.deserialize_any(ColorVisitor).map(|c| c.0)
}
}
#[cfg(test)]
mod tests {
use super::{de::deserialize_color_hex_string, Theme};
use serde::de::value::{Error, StrDeserializer};
use serde::de::IntoDeserializer;
#[test]
fn test_hex_color_deser() {
let colors = [
"AABBCC", "AABBCG", "#AABBCG", "#AABB091", "#AABBCC", "#AABB09",
];
for (idx, color_str) in colors.iter().enumerate() {
let deserializer: StrDeserializer<Error> = color_str.into_deserializer();
let color = deserialize_color_hex_string(deserializer);
if idx < 4 {
assert!(color.is_err());
} else {
assert!(color.is_ok());
}
}
}
#[test]
fn test_theme_yml_deser() {
let theme_str = "---
name: Test
palette:
base:
background: '#484793'
foreground: '#484793'
normal:
primary: '#484793'
secondary: '#484793'
surface: '#484793'
error: '#484793'
bright:
primary: '#484793'
secondary: '#484793'
surface: '#484793'
error: '#484793'
";
serde_yaml::from_str::<Theme>(theme_str).unwrap();
}
}