use std::sync::Arc;
use schemars::JsonSchema;
use serde::de::{self, Deserializer};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::form::{Presets, presets};
use crate::Color;
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(remote = "Self", default, rename_all = "camelCase", deny_unknown_fields)]
#[schemars(transform = with_base)]
pub struct Theme {
pub background: Color,
pub surface: Color,
pub grid: Color,
pub vertex: Color,
pub vertex_radius: f32,
pub edge: Option<Color>,
pub edge_width: f32,
pub line: Color,
pub line_width: f32,
pub area: Color,
pub shading: f32,
pub text: Color,
pub font: Arc<str>,
pub font_size: f32,
pub playhead: Color,
pub missing: Color,
pub hover: Color,
pub selected: Option<Color>,
pub glow: f32,
pub dim: f32,
pub hover_scale: f32,
pub dash: f32,
pub flow: Flow,
pub edge_spacing: f32,
pub lane_bow: f32,
pub arrow: f32,
pub halo: f32,
pub group_padding: f32,
pub shadow: Shadow,
pub atmosphere: Color,
pub night: f32,
}
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[schemars(transform = merged)]
pub struct Flow {
pub color: Color,
pub spacing: f32,
pub length: f32,
pub width: f32,
pub units: bool,
}
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[schemars(transform = merged)]
pub struct Shadow {
pub color: Color,
pub light: [f32; 3],
pub softness: f32,
pub lift: f32,
}
impl Theme {
pub fn dark() -> Theme {
Theme {
background: Color::hex("#0f141a").expect("hex"),
surface: Color::hex("#18212b").expect("hex"),
grid: Color::hex("#ffffff").expect("hex").alpha(0.07),
vertex: Color::hex("#7cc4ff").expect("hex"),
vertex_radius: 4.0,
edge: None,
edge_width: 1.5,
line: Color::hex("#7cc4ff").expect("hex"),
line_width: 1.5,
area: Color::hex("#2b3d50").expect("hex"),
shading: 0.6,
text: Color::hex("#a8b2c2").expect("hex"),
font: "ui-monospace, Menlo, Consolas, monospace".into(),
font_size: 11.0,
playhead: Color::hex("#ffe5d4").expect("hex").alpha(0.8),
missing: Color::hex("#6b7480").expect("hex"),
hover: Color::hex("#ffffff").expect("hex").alpha(0.6),
selected: Some(Color::hex("#ffb000").expect("hex").alpha(0.9)),
glow: 6.0,
dim: 0.35,
hover_scale: 1.25,
dash: 8.0,
flow: Flow {
color: Color::hex("#ffffff").expect("hex").alpha(0.85),
spacing: 64.0,
length: 24.0,
width: 4.0,
units: false,
},
edge_spacing: 4.0,
lane_bow: 0.12,
arrow: 7.0,
halo: 2.0,
group_padding: 16.0,
shadow: Shadow { color: Color::BLACK.alpha(0.45), light: [-0.35, 0.45, 1.0], softness: 0.15, lift: 3.0 },
atmosphere: Color::hex("#5aa9ff").expect("hex").alpha(0.55),
night: 0.3,
}
}
pub fn light() -> Theme {
Theme {
background: Color::hex("#f7f8fa").expect("hex"),
surface: Color::hex("#e8ecf0").expect("hex"),
grid: Color::hex("#000000").expect("hex").alpha(0.08),
vertex: Color::hex("#1f6feb").expect("hex"),
line: Color::hex("#1f6feb").expect("hex"),
area: Color::hex("#c5d2df").expect("hex"),
text: Color::hex("#4a5361").expect("hex"),
playhead: Color::hex("#e36209").expect("hex").alpha(0.8),
missing: Color::hex("#9aa3ad").expect("hex"),
hover: Color::hex("#000000").expect("hex").alpha(0.35),
selected: Some(Color::hex("#e36209").expect("hex").alpha(0.9)),
flow: Flow { color: Color::hex("#ffffff").expect("hex").alpha(0.9), ..Theme::dark().flow },
shadow: Shadow { color: Color::BLACK.alpha(0.22), ..Theme::dark().shadow },
atmosphere: Color::hex("#6cb4ff").expect("hex").alpha(0.45),
night: 0.55,
..Theme::dark()
}
}
}
impl Default for Theme {
fn default() -> Theme {
Theme::dark()
}
}
impl Presets for Theme {
const NAMES: &'static [&'static str] = &["dark", "light"];
fn preset(name: &str) -> Option<Theme> {
match name {
"dark" => Some(Theme::dark()),
"light" => Some(Theme::light()),
_ => None,
}
}
fn options<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Theme, D::Error> {
Theme::deserialize(deserializer)
}
}
impl<'de> Deserialize<'de> for Theme {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Theme, D::Error> {
let named = |name: &str| {
Theme::preset(name).ok_or_else(|| de::Error::custom(format!("no theme {name:?}: dark or light")))
};
let mut options = match Value::deserialize(deserializer)? {
Value::String(name) => return named(&name),
Value::Object(options) => options,
_ => return Err(de::Error::custom("a theme: \"dark\", \"light\", or options")),
};
let base = match options.remove("base") {
None => Theme::dark(),
Some(Value::String(name)) => named(&name)?,
Some(_) => return Err(de::Error::custom("base names a theme: dark or light")),
};
let mut theme = serde_json::to_value(base).map_err(de::Error::custom)?;
over(&mut theme, Value::Object(options));
Theme::deserialize(theme).map_err(de::Error::custom)
}
}
impl Serialize for Theme {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Theme::serialize(self, serializer)
}
}
fn over(base: &mut Value, options: Value) {
match (base, options) {
(Value::Object(base), Value::Object(options)) => {
for (key, value) in options {
over(base.entry(key).or_insert(Value::Null), value);
}
}
(slot, value) => *slot = value,
}
}
fn merged(schema: &mut schemars::Schema) {
schema.remove("required");
}
fn with_base(schema: &mut schemars::Schema) {
if let Some(properties) = schema.get_mut("properties").and_then(Value::as_object_mut) {
let base = serde_json::json!({ "description": "The theme these options change; dark unless set.", "enum": Theme::NAMES });
properties.insert("base".into(), base);
}
presets::<Theme>(schema);
}