use serde::de::{Deserializer, Error};
use serde::Deserialize;
use std::path::{Path, PathBuf};
use crate::components::Component;
#[derive(Debug, Deserialize)]
pub struct Config {
pub height: u8,
pub position: Option<Position>,
pub background: Option<Background>,
#[serde(
deserialize_with = "deserialize_monitors",
skip_serializing_if = "Vec::is_empty"
)]
pub monitors: Vec<Monitor>,
pub defaults: Option<ComponentSettings>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub left: Vec<Box<Component>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub center: Vec<Box<Component>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub right: Vec<Box<Component>>,
}
fn deserialize_monitors<'a, D>(deserializer: D) -> Result<Vec<Monitor>, D::Error>
where
D: Deserializer<'a>,
{
match Vec::<Monitor>::deserialize(deserializer) {
Ok(monitors) => {
if monitors.is_empty() {
Err(D::Error::custom(String::from(
"at least one monitor is required",
)))
} else {
Ok(monitors)
}
}
err => err,
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct ComponentSettings {
pub foreground: Option<Color>,
pub background: Option<Background>,
pub width: Option<u8>,
pub padding: Option<u8>,
pub offset_x: Option<i8>,
pub offset_y: Option<i8>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fonts: Vec<Font>,
pub border: Option<Border>,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Background {
Image(PathBuf),
Color(Color),
}
impl<'de> Deserialize<'de> for Background {
fn deserialize<D>(deserializer: D) -> Result<Background, D::Error>
where
D: Deserializer<'de>,
{
match String::deserialize(deserializer) {
Ok(text) => {
if text.starts_with('#') {
Color::from_str(&text)
.map_err(D::Error::custom)
.map(Background::Color)
} else {
Path::new(&text)
.canonicalize()
.map_err(D::Error::custom)
.map(Background::Image)
}
}
Err(err) => Err(err),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct Font {
pub description: String,
pub size: u8,
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct Monitor {
pub name: String,
#[serde(default)]
pub fallback_names: Vec<String>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub struct Border {
pub height: u8,
pub color: Color,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
pub enum Position {
Top,
Bottom,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct Color {
r: u8,
g: u8,
b: u8,
a: u8,
}
impl Color {
fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Color { r, g, b, a }
}
fn from_str(string: &str) -> Result<Self, String> {
if !string.starts_with('#') || (string.len() != 7 && string.len() != 9) {
return Err(String::from(
"colors need to follow the format `#RRGGBB` or `#RRGGBBAA`",
));
}
let radix_error =
|_| String::from("hexadecimal color digits need to be within the range 0..=F");
let r = u8::from_str_radix(&string[1..3], 16).map_err(radix_error)?;
let g = u8::from_str_radix(&string[3..5], 16).map_err(radix_error)?;
let b = u8::from_str_radix(&string[5..7], 16).map_err(radix_error)?;
let a = if string.len() == 9 {
u8::from_str_radix(&string[7..9], 16).map_err(radix_error)?
} else {
255
};
Ok(Color::new(r, g, b, a))
}
}
impl ToString for Color {
fn to_string(&self) -> String {
format!("#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Color, D::Error>
where
D: Deserializer<'de>,
{
match String::deserialize(deserializer) {
Ok(color_string) => Color::from_str(&color_string).map_err(D::Error::custom),
Err(err) => Err(err),
}
}
}