319 lines
8.5 KiB
Rust
319 lines
8.5 KiB
Rust
use std::path::{Path, PathBuf};
|
|
use std::sync::OnceLock;
|
|
|
|
use iced_wgpu::core::Color;
|
|
use serde::Deserialize;
|
|
|
|
const DEFAULT_TOML: &str = include_str!("../../resources/colors.toml");
|
|
|
|
static LOADED: OnceLock<Colors> = OnceLock::new();
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Rgba {
|
|
pub r: f32,
|
|
pub g: f32,
|
|
pub b: f32,
|
|
pub a: f32,
|
|
}
|
|
|
|
impl Rgba {
|
|
pub fn to_color(self) -> Color {
|
|
Color::from_rgba(self.r, self.g, self.b, self.a)
|
|
}
|
|
|
|
pub fn with_alpha(self, a: f32) -> Color {
|
|
Color::from_rgba(self.r, self.g, self.b, a.clamp(0.0, 1.0))
|
|
}
|
|
|
|
pub fn mul_alpha(self, k: f32) -> Color {
|
|
Color::from_rgba(self.r, self.g, self.b, (self.a * k).clamp(0.0, 1.0))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Window {
|
|
pub background: Rgba,
|
|
pub alpha_focused_hovered: f32,
|
|
pub alpha_partial: f32,
|
|
pub alpha_idle: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Panel {
|
|
pub surface: Rgba,
|
|
pub surface_alt: Rgba,
|
|
pub border: Rgba,
|
|
pub text_primary: Rgba,
|
|
pub text_muted: Rgba,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Accent {
|
|
pub base: Rgba,
|
|
pub hover: Rgba,
|
|
pub pressed: Rgba,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Status {
|
|
pub connected: Rgba,
|
|
pub disconnected: Rgba,
|
|
pub warning: Rgba,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct GhostButton {
|
|
pub border_active: f32,
|
|
pub border_hovered: f32,
|
|
pub border_pressed: f32,
|
|
pub border_disabled: f32,
|
|
pub bg_hovered: f32,
|
|
pub bg_pressed: f32,
|
|
pub text_active: f32,
|
|
pub text_hovered: f32,
|
|
pub text_pressed: f32,
|
|
pub text_disabled: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct AccentButton {
|
|
pub bg_active: f32,
|
|
pub bg_hovered: f32,
|
|
pub bg_pressed: f32,
|
|
pub bg_disabled: f32,
|
|
pub border_active: f32,
|
|
pub border_hovered: f32,
|
|
pub border_pressed: f32,
|
|
pub border_disabled: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct Colors {
|
|
pub window: Window,
|
|
pub panel: Panel,
|
|
pub accent: Accent,
|
|
pub status: Status,
|
|
pub ghost: GhostButton,
|
|
pub accent_button: AccentButton,
|
|
}
|
|
|
|
/// Initialise the global palette. Call once during `layers_startup`. Later calls no-op.
|
|
pub fn init(plugin_root: Option<&Path>) {
|
|
let _ = LOADED.set(load(plugin_root));
|
|
}
|
|
|
|
pub fn get() -> &'static Colors {
|
|
LOADED.get_or_init(|| load(None))
|
|
}
|
|
|
|
fn load(plugin_root: Option<&Path>) -> Colors {
|
|
let override_path = plugin_root
|
|
.map(|r| r.join("resources").join("colors.toml"))
|
|
.or_else(|| {
|
|
std::env::var_os("LAYERS_COLORS_TOML").map(PathBuf::from)
|
|
});
|
|
let source = match override_path.as_deref().and_then(read_if_present) {
|
|
Some(s) => s,
|
|
None => DEFAULT_TOML.to_string(),
|
|
};
|
|
parse(&source).unwrap_or_else(|e| {
|
|
tracing::warn!("colors.toml parse failed: {e}; using compiled defaults");
|
|
parse(DEFAULT_TOML).expect("compiled-in default colors must parse")
|
|
})
|
|
}
|
|
|
|
fn read_if_present(p: &Path) -> Option<String> {
|
|
std::fs::read_to_string(p).ok()
|
|
}
|
|
|
|
fn parse(src: &str) -> Result<Colors, String> {
|
|
let raw: Raw = toml::from_str(src).map_err(|e| e.to_string())?;
|
|
Ok(Colors {
|
|
window: Window {
|
|
background: raw.window.background.parse()?,
|
|
alpha_focused_hovered: raw.window.alpha_focused_hovered,
|
|
alpha_partial: raw.window.alpha_partial,
|
|
alpha_idle: raw.window.alpha_idle,
|
|
},
|
|
panel: Panel {
|
|
surface: raw.panel.surface.parse()?,
|
|
surface_alt: raw.panel.surface_alt.parse()?,
|
|
border: raw.panel.border.parse()?,
|
|
text_primary: raw.panel.text_primary.parse()?,
|
|
text_muted: raw.panel.text_muted.parse()?,
|
|
},
|
|
accent: Accent {
|
|
base: raw.accent.base.parse()?,
|
|
hover: raw.accent.hover.parse()?,
|
|
pressed: raw.accent.pressed.parse()?,
|
|
},
|
|
status: Status {
|
|
connected: raw.status.connected.parse()?,
|
|
disconnected: raw.status.disconnected.parse()?,
|
|
warning: raw.status.warning.parse()?,
|
|
},
|
|
ghost: raw.button.ghost,
|
|
accent_button: raw.button.accent,
|
|
})
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Raw {
|
|
window: RawWindow,
|
|
panel: RawPanel,
|
|
accent: RawAccent,
|
|
status: RawStatus,
|
|
button: RawButton,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct RawWindow {
|
|
background: HexColor,
|
|
alpha_focused_hovered: f32,
|
|
alpha_partial: f32,
|
|
alpha_idle: f32,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct RawPanel {
|
|
surface: HexColor,
|
|
surface_alt: HexColor,
|
|
border: HexColor,
|
|
text_primary: HexColor,
|
|
text_muted: HexColor,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct RawAccent {
|
|
base: HexColor,
|
|
hover: HexColor,
|
|
pressed: HexColor,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct RawStatus {
|
|
connected: HexColor,
|
|
disconnected: HexColor,
|
|
warning: HexColor,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct RawButton {
|
|
ghost: GhostButton,
|
|
accent: AccentButton,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for GhostButton {
|
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
#[derive(Deserialize)]
|
|
struct R {
|
|
border_active: f32,
|
|
border_hovered: f32,
|
|
border_pressed: f32,
|
|
border_disabled: f32,
|
|
bg_hovered: f32,
|
|
bg_pressed: f32,
|
|
text_active: f32,
|
|
text_hovered: f32,
|
|
text_pressed: f32,
|
|
text_disabled: f32,
|
|
}
|
|
let r = R::deserialize(d)?;
|
|
Ok(Self {
|
|
border_active: r.border_active,
|
|
border_hovered: r.border_hovered,
|
|
border_pressed: r.border_pressed,
|
|
border_disabled: r.border_disabled,
|
|
bg_hovered: r.bg_hovered,
|
|
bg_pressed: r.bg_pressed,
|
|
text_active: r.text_active,
|
|
text_hovered: r.text_hovered,
|
|
text_pressed: r.text_pressed,
|
|
text_disabled: r.text_disabled,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for AccentButton {
|
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
#[derive(Deserialize)]
|
|
struct R {
|
|
bg_active: f32,
|
|
bg_hovered: f32,
|
|
bg_pressed: f32,
|
|
bg_disabled: f32,
|
|
border_active: f32,
|
|
border_hovered: f32,
|
|
border_pressed: f32,
|
|
border_disabled: f32,
|
|
}
|
|
let r = R::deserialize(d)?;
|
|
Ok(Self {
|
|
bg_active: r.bg_active,
|
|
bg_hovered: r.bg_hovered,
|
|
bg_pressed: r.bg_pressed,
|
|
bg_disabled: r.bg_disabled,
|
|
border_active: r.border_active,
|
|
border_hovered: r.border_hovered,
|
|
border_pressed: r.border_pressed,
|
|
border_disabled: r.border_disabled,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct HexColor(String);
|
|
|
|
impl HexColor {
|
|
fn parse(&self) -> Result<Rgba, String> {
|
|
parse_hex(&self.0)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for HexColor {
|
|
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
let s = String::deserialize(d)?;
|
|
Ok(HexColor(s))
|
|
}
|
|
}
|
|
|
|
fn parse_hex(s: &str) -> Result<Rgba, String> {
|
|
let raw = s.trim_start_matches('#');
|
|
let (r, g, b, a) = match raw.len() {
|
|
3 => (
|
|
dup(u8::from_str_radix(&raw[0..1], 16).map_err(err)?),
|
|
dup(u8::from_str_radix(&raw[1..2], 16).map_err(err)?),
|
|
dup(u8::from_str_radix(&raw[2..3], 16).map_err(err)?),
|
|
255,
|
|
),
|
|
4 => (
|
|
dup(u8::from_str_radix(&raw[0..1], 16).map_err(err)?),
|
|
dup(u8::from_str_radix(&raw[1..2], 16).map_err(err)?),
|
|
dup(u8::from_str_radix(&raw[2..3], 16).map_err(err)?),
|
|
dup(u8::from_str_radix(&raw[3..4], 16).map_err(err)?),
|
|
),
|
|
6 => (
|
|
u8::from_str_radix(&raw[0..2], 16).map_err(err)?,
|
|
u8::from_str_radix(&raw[2..4], 16).map_err(err)?,
|
|
u8::from_str_radix(&raw[4..6], 16).map_err(err)?,
|
|
255,
|
|
),
|
|
8 => (
|
|
u8::from_str_radix(&raw[0..2], 16).map_err(err)?,
|
|
u8::from_str_radix(&raw[2..4], 16).map_err(err)?,
|
|
u8::from_str_radix(&raw[4..6], 16).map_err(err)?,
|
|
u8::from_str_radix(&raw[6..8], 16).map_err(err)?,
|
|
),
|
|
_ => return Err(format!("invalid hex colour: {s:?}")),
|
|
};
|
|
Ok(Rgba {
|
|
r: r as f32 / 255.0,
|
|
g: g as f32 / 255.0,
|
|
b: b as f32 / 255.0,
|
|
a: a as f32 / 255.0,
|
|
})
|
|
}
|
|
|
|
fn dup(n: u8) -> u8 { (n << 4) | n }
|
|
fn err<E: std::fmt::Display>(e: E) -> String { e.to_string() }
|