Skip to main content

nuhxboard_types/
style.rs

1use bitflags::bitflags;
2use schemars::JsonSchema;
3use serde::{
4    de::Deserializer,
5    ser::{SerializeSeq, Serializer},
6    Deserialize, Serialize,
7};
8use std::{
9    collections::{HashMap, HashSet},
10    sync::{LazyLock, RwLock},
11};
12
13#[derive(Serialize, Deserialize, Debug, JsonSchema)]
14#[serde(rename_all = "PascalCase")]
15pub struct Style {
16    /// Background color of the window. Will be overridden by background image if present.
17    pub background_color: NohRgb,
18    pub background_image_file_name: Option<String>,
19    pub default_key_style: DefaultKeyStyle,
20    pub default_mouse_speed_indicator_style: MouseSpeedIndicatorStyle,
21    #[serde(with = "CustomMap")]
22    pub element_styles: HashMap<u32, ElementStyle>,
23}
24
25// This allows `HashMap<u32, ElementStyle>` to be serialized as a list of `{Key: u32, Value: ElementStyle}`
26struct CustomMap;
27impl CustomMap {
28    pub fn serialize<S>(map: &HashMap<u32, ElementStyle>, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let mut seq = serializer.serialize_seq(Some(map.len()))?;
33        for (key, value) in map {
34            seq.serialize_element(&KeyValue {
35                key: *key,
36                value: value.clone(),
37            })?;
38        }
39        seq.end()
40    }
41
42    pub fn deserialize<'de, D>(deserializer: D) -> Result<HashMap<u32, ElementStyle>, D::Error>
43    where
44        D: Deserializer<'de>,
45    {
46        let vec = Vec::<KeyValue>::deserialize(deserializer)?;
47        let mut map = HashMap::new();
48        for item in vec {
49            map.insert(item.key, item.value);
50        }
51        Ok(map)
52    }
53}
54
55impl JsonSchema for CustomMap {
56    fn schema_name() -> std::borrow::Cow<'static, str> {
57        "CustomMap".into()
58    }
59
60    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
61        generator.subschema_for::<Vec<KeyValue>>()
62    }
63}
64
65#[derive(Serialize, Deserialize, JsonSchema)]
66#[serde(rename_all = "PascalCase")]
67struct KeyValue {
68    key: u32,
69    value: ElementStyle,
70}
71
72#[derive(Serialize, Deserialize, Debug, Clone, Copy, JsonSchema)]
73#[serde(rename_all = "PascalCase")]
74pub struct NohRgb {
75    pub red: f32,
76    pub green: f32,
77    pub blue: f32,
78}
79
80impl NohRgb {
81    pub const BLACK: NohRgb = NohRgb {
82        red: 0.0,
83        green: 0.0,
84        blue: 0.0,
85    };
86
87    pub const WHITE: NohRgb = NohRgb {
88        red: 255.0,
89        green: 255.0,
90        blue: 255.0,
91    };
92
93    pub const DEFAULT_GRAY: NohRgb = NohRgb {
94        red: 100.0,
95        green: 100.0,
96        blue: 100.0,
97    };
98}
99
100impl From<NohRgb> for iced::Color {
101    fn from(val: NohRgb) -> Self {
102        iced::Color::from_rgba(val.red / 255.0, val.green / 255.0, val.blue / 255.0, 1.0)
103    }
104}
105
106impl From<iced::Color> for NohRgb {
107    fn from(val: iced::Color) -> Self {
108        NohRgb {
109            red: val.r * 255.0,
110            green: val.g * 255.0,
111            blue: val.b * 255.0,
112        }
113    }
114}
115
116impl From<NohRgb> for colorgrad::Color {
117    fn from(value: NohRgb) -> Self {
118        colorgrad::Color::new(
119            value.red / 255.0,
120            value.green / 255.0,
121            value.blue / 255.0,
122            1.0,
123        )
124    }
125}
126
127#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
128#[serde(rename_all = "PascalCase")]
129pub struct DefaultKeyStyle {
130    pub loose: KeySubStyle,
131    pub pressed: KeySubStyle,
132}
133
134#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
135#[serde(rename_all = "PascalCase")]
136pub struct KeyStyle {
137    pub loose: Option<KeySubStyle>,
138    pub pressed: Option<KeySubStyle>,
139}
140
141impl From<DefaultKeyStyle> for KeyStyle {
142    fn from(val: DefaultKeyStyle) -> Self {
143        Self {
144            loose: Some(val.loose),
145            pressed: Some(val.pressed),
146        }
147    }
148}
149
150#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
151#[serde(rename_all = "PascalCase")]
152pub struct KeySubStyle {
153    pub background: NohRgb,
154    pub text: NohRgb,
155    pub outline: NohRgb,
156    pub show_outline: bool,
157    /// Outline thickness in pixels.
158    pub outline_width: u32,
159    pub font: Font,
160    pub background_image_file_name: Option<String>,
161}
162
163#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
164#[serde(rename_all = "PascalCase")]
165pub struct Font {
166    pub font_family: String,
167    /// Font size in pixels.
168    pub size: f32,
169    pub style: FontStyle,
170}
171
172impl From<FontStyle> for iced::font::Weight {
173    fn from(val: FontStyle) -> Self {
174        if val.contains(FontStyle::BOLD) {
175            iced::font::Weight::Bold
176        } else {
177            iced::font::Weight::Normal
178        }
179    }
180}
181
182impl From<FontStyle> for iced::font::Style {
183    fn from(val: FontStyle) -> Self {
184        if val.contains(FontStyle::ITALIC) {
185            iced::font::Style::Italic
186        } else {
187            iced::font::Style::Normal
188        }
189    }
190}
191
192impl Font {
193    pub fn as_iced(&self, store: &LazyLock<RwLock<HashSet<&'static str>>>) -> Option<iced::Font> {
194        Some(iced::Font {
195            family: iced::font::Family::Name(store.read().unwrap().get(self.font_family.as_str())?),
196            weight: self.style.into(),
197            style: self.style.into(),
198            stretch: iced::font::Stretch::Normal,
199        })
200    }
201}
202
203bitflags! {
204    #[derive(Debug, Clone, Copy)]
205    pub struct FontStyle: u8 {
206        const BOLD = 0b0001;
207        const ITALIC = 0b0010;
208        const UNDERLINE = 0b0100;
209        const STRIKETHROUGH = 0b1000;
210    }
211}
212
213impl Serialize for FontStyle {
214    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
215    where
216        S: Serializer,
217    {
218        serializer.serialize_u8(self.bits())
219    }
220}
221
222impl<'de> Deserialize<'de> for FontStyle {
223    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
224    where
225        D: Deserializer<'de>,
226    {
227        let bits = u8::deserialize(deserializer)?;
228        FontStyle::from_bits(bits).ok_or_else(|| serde::de::Error::custom("Extraneous bits set"))
229    }
230}
231
232impl JsonSchema for FontStyle {
233    fn schema_name() -> std::borrow::Cow<'static, str> {
234        "FontStyle".into()
235    }
236
237    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
238        generator.subschema_for::<u8>()
239    }
240}
241
242#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
243#[serde(rename_all = "PascalCase")]
244pub struct MouseSpeedIndicatorStyle {
245    pub inner_color: NohRgb,
246    pub outer_color: NohRgb,
247    pub outline_width: u32,
248}
249
250#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
251#[serde(tag = "__type")]
252pub enum ElementStyle {
253    KeyStyle(KeyStyle),
254    MouseSpeedIndicatorStyle(MouseSpeedIndicatorStyle),
255}
256
257impl ElementStyle {
258    pub fn as_key_style(&self) -> Option<&KeyStyle> {
259        match self {
260            ElementStyle::KeyStyle(key_style) => Some(key_style),
261            _ => None,
262        }
263    }
264
265    pub fn as_mouse_speed_indicator_style(&self) -> Option<&MouseSpeedIndicatorStyle> {
266        match self {
267            ElementStyle::MouseSpeedIndicatorStyle(mouse_speed_indicator_style) => {
268                Some(mouse_speed_indicator_style)
269            }
270            _ => None,
271        }
272    }
273}
274
275impl Default for Style {
276    fn default() -> Self {
277        Style {
278            background_color: NohRgb {
279                red: 0.0,
280                green: 0.0,
281                blue: 100.0,
282            },
283            background_image_file_name: None,
284            default_key_style: DefaultKeyStyle {
285                loose: KeySubStyle {
286                    background: NohRgb::DEFAULT_GRAY,
287                    text: NohRgb::BLACK,
288                    outline: NohRgb {
289                        red: 0.0,
290                        green: 255.0,
291                        blue: 0.0,
292                    },
293                    show_outline: false,
294                    outline_width: 1,
295                    font: Font::default(),
296                    background_image_file_name: None,
297                },
298                pressed: KeySubStyle {
299                    background: NohRgb::WHITE,
300                    text: NohRgb::BLACK,
301                    outline: NohRgb {
302                        red: 0.0,
303                        green: 255.0,
304                        blue: 0.0,
305                    },
306                    show_outline: false,
307                    outline_width: 1,
308                    font: Font::default(),
309                    background_image_file_name: None,
310                },
311            },
312            default_mouse_speed_indicator_style: MouseSpeedIndicatorStyle {
313                inner_color: NohRgb::DEFAULT_GRAY,
314                outer_color: NohRgb::WHITE,
315                outline_width: 1,
316            },
317            element_styles: HashMap::new(),
318        }
319    }
320}
321
322impl Default for Font {
323    fn default() -> Self {
324        Self {
325            font_family: "Courier New".into(),
326            size: 10.0,
327            style: FontStyle::empty(),
328        }
329    }
330}