feat: Initial support for custom user-defined themes
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
use anyhow::Result;
|
||||
use derivative::Derivative;
|
||||
use ratatui::style::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "theme_tests.rs"]
|
||||
mod theme_tests;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Derivative)]
|
||||
#[derivative(Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Background {
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color_str",
|
||||
serialize_with = "serialize_color_str",
|
||||
default = "default_background_color"
|
||||
)]
|
||||
#[derivative(Default(value = "Some(Color::Rgb(35, 50, 55))"))]
|
||||
pub color: Option<Color>,
|
||||
#[derivative(Default(value = "Some(true)"))]
|
||||
#[serde(default = "default_background_enabled")]
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone, Copy)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Style {
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color_str",
|
||||
serialize_with = "serialize_color_str"
|
||||
)]
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Derivative)]
|
||||
#[derivative(Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct Theme {
|
||||
#[serde(default = "default_background")]
|
||||
#[derivative(Default(
|
||||
value = "Some(Background { color: Some(Color::Rgb(35, 50, 55)), enabled: Some(true) })"
|
||||
))]
|
||||
pub background: Option<Background>,
|
||||
#[serde(default = "default_awaiting_import_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Rgb(255, 170, 66)) })"))]
|
||||
pub awaiting_import: Option<Style>,
|
||||
#[serde(default = "default_indeterminate_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Rgb(255, 170, 66)) })"))]
|
||||
pub indeterminate: Option<Style>,
|
||||
#[serde(default = "default_default_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::White) })"))]
|
||||
pub default: Option<Style>,
|
||||
#[serde(default = "default_downloaded_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Green) })"))]
|
||||
pub downloaded: Option<Style>,
|
||||
#[serde(default = "default_downloading_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Magenta) })"))]
|
||||
pub downloading: Option<Style>,
|
||||
#[serde(default = "default_failure_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Red) })"))]
|
||||
pub failure: Option<Style>,
|
||||
#[serde(default = "default_help_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::LightBlue) })"))]
|
||||
pub help: Option<Style>,
|
||||
#[serde(default = "default_missing_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Red) })"))]
|
||||
pub missing: Option<Style>,
|
||||
#[serde(default = "default_primary_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Cyan) })"))]
|
||||
pub primary: Option<Style>,
|
||||
#[serde(default = "default_secondary_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Yellow) })"))]
|
||||
pub secondary: Option<Style>,
|
||||
#[serde(default = "default_success_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Green) })"))]
|
||||
pub success: Option<Style>,
|
||||
#[serde(default = "default_system_function_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Yellow) })"))]
|
||||
pub system_function: Option<Style>,
|
||||
#[serde(default = "default_unmonitored_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Gray) })"))]
|
||||
pub unmonitored: Option<Style>,
|
||||
#[serde(default = "default_unmonitored_missing_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Yellow) })"))]
|
||||
pub unmonitored_missing: Option<Style>,
|
||||
#[serde(default = "default_unreleased_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::LightCyan) })"))]
|
||||
pub unreleased: Option<Style>,
|
||||
#[serde(default = "default_warning_style")]
|
||||
#[derivative(Default(value = "Some(Style { color: Some(Color::Magenta) })"))]
|
||||
pub warning: Option<Style>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct ThemeDefinition {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub theme: Theme,
|
||||
}
|
||||
|
||||
fn default_background_color() -> Option<Color> {
|
||||
Some(Color::Rgb(35, 50, 55))
|
||||
}
|
||||
|
||||
fn default_background_enabled() -> Option<bool> {
|
||||
Some(true)
|
||||
}
|
||||
|
||||
fn default_background() -> Option<Background> {
|
||||
Some(Background {
|
||||
color: default_background_color(),
|
||||
enabled: Some(true),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_awaiting_import_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Rgb(255, 170, 66)),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_indeterminate_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Rgb(255, 170, 66)),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_default_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::White),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_downloaded_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Green),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_downloading_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Magenta),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_failure_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Red),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_help_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::LightBlue),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_missing_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Red),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_primary_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Cyan),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_secondary_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Yellow),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_success_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Green),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_system_function_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Yellow),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_unmonitored_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Gray),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_unmonitored_missing_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Yellow),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_unreleased_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::LightCyan),
|
||||
})
|
||||
}
|
||||
|
||||
fn default_warning_style() -> Option<Style> {
|
||||
Some(Style {
|
||||
color: Some(Color::Magenta),
|
||||
})
|
||||
}
|
||||
|
||||
fn deserialize_color_str<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: Option<String> = Option::deserialize(deserializer)?;
|
||||
match s {
|
||||
Some(s) => Color::from_str(&s)
|
||||
.map_err(serde::de::Error::custom)
|
||||
.map(Some),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_color_str<S>(color: &Option<Color>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&color.unwrap().to_string())
|
||||
}
|
||||
Reference in New Issue
Block a user