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 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
25struct 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 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 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}