chart_js_wrapper/
common.rs

1use std::fmt;
2use std::fmt::Write;
3use sailfish::RenderError;
4use sailfish::runtime::{Buffer, Render};
5use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
6use serde::de::Visitor;
7
8/// Newtype for percentage values, serialized as "{value}%"
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Percent(pub f32);
11
12impl Serialize for Percent {
13    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
14    where S: Serializer {
15        let s = format!("{}%", self.0);
16        serializer.serialize_str(&s)
17    }
18}
19
20impl<'de> Deserialize<'de> for Percent {
21    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
22    where D: Deserializer<'de> {
23        let s = String::deserialize(deserializer)?;
24        let trimmed = s.trim_end_matches('%');
25        trimmed.parse::<f32>()
26            .map(Percent)
27            .map_err(de::Error::custom)
28    }
29}
30
31
32/// Newtype for percentage values, serialized as "{value}px"
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct Pixels(pub usize);
35
36impl Serialize for Pixels {
37    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
38    where S: Serializer {
39        let s = format!("{}px", self.0);
40        serializer.serialize_str(&s)
41    }
42}
43
44impl<'de> Deserialize<'de> for Pixels {
45    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46    where D: Deserializer<'de> {
47        let s = String::deserialize(deserializer)?;
48        let trimmed = s.trim_end_matches("px");
49        trimmed.parse::<usize>()
50            .map(Pixels)
51            .map_err(de::Error::custom)
52    }
53}
54
55
56#[derive(Serialize, Deserialize, Debug, Clone)]
57#[serde(untagged)]
58pub enum Size{
59    Percent(Percent),
60    Pixel(Pixels)
61}
62
63
64impl Render for Size{
65    fn render(&self, b: &mut Buffer) -> Result<(), RenderError> {
66        b.write_str(&self.to_string())?;
67        Ok(())
68    }
69}
70
71impl Size{
72    pub fn to_string(&self) -> String{
73        match self{
74            Size::Percent(p) => format!("{}%", p.0),
75            Size::Pixel(p) => format!("{}px", p.0)
76        }
77    }
78
79    pub fn percent(f: f32) -> Self{
80        Size::Percent(Percent(f))
81    }
82
83    pub fn pixels(f: usize) -> Self{
84        Size::Pixel(Pixels(f))
85    }
86
87}
88
89#[derive(Debug,Clone, PartialEq)]
90pub struct Rgb(pub u8,pub u8,pub u8);
91
92impl Serialize for Rgb {
93    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
94    where
95        S: Serializer,
96    {
97        let s = format!("rgb({}, {}, {})", self.0, self.1, self.2);
98        serializer.serialize_str(&s)
99    }
100}
101
102impl<'de> Deserialize<'de> for Rgb {
103    fn deserialize<D>(deserializer: D) -> Result<Rgb, D::Error>
104    where
105        D: Deserializer<'de>,
106    {
107        struct RgbVisitor;
108
109        impl<'de> Visitor<'de> for RgbVisitor {
110            type Value = Rgb;
111
112            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
113                formatter.write_str(r#"a string in the format "rgb(r, g, b)""#)
114            }
115
116            fn visit_str<E>(self, v: &str) -> Result<Rgb, E>
117            where
118                E: de::Error,
119            {
120                let v = v.trim();
121                if !v.starts_with("rgb(") || !v.ends_with(')') {
122                    return Err(E::custom("invalid format"));
123                }
124
125                let content = &v[4..v.len() - 1]; // strip "rgb(" and ")"
126                let parts: Vec<&str> = content.split(',').map(str::trim).collect();
127                if parts.len() != 3 {
128                    return Err(E::custom("expected three components"));
129                }
130
131                let r = parts[0].parse::<u8>().map_err(E::custom)?;
132                let g = parts[1].parse::<u8>().map_err(E::custom)?;
133                let b = parts[2].parse::<u8>().map_err(E::custom)?;
134
135                Ok(Rgb(r, g, b))
136            }
137        }
138
139        deserializer.deserialize_str(RgbVisitor)
140    }
141}
142
143#[derive(Serialize, Deserialize, Debug, Clone)]
144pub struct Padding {
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub top: Option<f32>,
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub bottom: Option<f32>,
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub left: Option<f32>,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub right: Option<f32>
153}