1use crate::{
2 color::Color,
3 math::RectOffset,
4 text::{
5 atlas::{Atlas, SpriteKey},
6 Font,
7 },
8 texture::Image,
9 ui::ElementState,
10 Error,
11};
12
13use std::{
14 collections::HashMap,
15 sync::{Arc, Mutex},
16};
17
18pub struct StyleBuilder {
19 atlas: Arc<Mutex<Atlas>>,
20 font: Arc<Mutex<Font>>,
21 font_size: u16,
22 text_color: Color,
23 text_color_hovered: Color,
24 text_color_clicked: Color,
25 background_margin: Option<RectOffset>,
26 margin: Option<RectOffset>,
27 background: Option<Image>,
28 background_hovered: Option<Image>,
29 background_clicked: Option<Image>,
30 color: Color,
31 color_inactive: Option<Color>,
32 color_hovered: Color,
33 color_selected: Color,
34 color_selected_hovered: Color,
35 color_clicked: Color,
36 reverse_background_z: bool,
37}
38
39impl StyleBuilder {
40 pub(crate) fn new(default_font: Arc<Mutex<Font>>, atlas: Arc<Mutex<Atlas>>) -> StyleBuilder {
41 StyleBuilder {
42 atlas,
43 font: default_font,
44 font_size: 16,
45 text_color: Color::from_rgba(0, 0, 0, 255),
46 text_color_hovered: Color::from_rgba(0, 0, 0, 255),
47 text_color_clicked: Color::from_rgba(0, 0, 0, 255),
48 color: Color::from_rgba(255, 255, 255, 255),
49 color_hovered: Color::from_rgba(255, 255, 255, 255),
50 color_clicked: Color::from_rgba(255, 255, 255, 255),
51 color_selected: Color::from_rgba(255, 255, 255, 255),
52 color_selected_hovered: Color::from_rgba(255, 255, 255, 255),
53 color_inactive: None,
54 background: None,
55 background_margin: None,
56 margin: None,
57 background_hovered: None,
58 background_clicked: None,
59 reverse_background_z: false,
60 }
61 }
62
63 pub fn with_font(self, font: &Font) -> Result<StyleBuilder, Error> {
64 let mut font = font.clone();
65 font.set_atlas(self.atlas.clone());
66 font.set_characters(Arc::new(Mutex::new(HashMap::new())));
67 Ok(StyleBuilder {
68 font: Arc::new(Mutex::new(font)),
69 ..self
70 })
71 }
72
73 pub fn font(self, ttf_bytes: &[u8]) -> Result<StyleBuilder, Error> {
74 let font = Font::load_from_bytes(self.atlas.clone(), ttf_bytes)?;
75
76 Ok(StyleBuilder {
77 font: Arc::new(Mutex::new(font)),
78 ..self
79 })
80 }
81
82 pub fn background(self, background: Image) -> StyleBuilder {
83 StyleBuilder {
84 background: Some(background),
85 ..self
86 }
87 }
88
89 pub fn margin(self, margin: RectOffset) -> StyleBuilder {
90 StyleBuilder {
91 margin: Some(margin),
92 ..self
93 }
94 }
95
96 pub fn background_margin(self, margin: RectOffset) -> StyleBuilder {
97 StyleBuilder {
98 background_margin: Some(margin),
99 ..self
100 }
101 }
102
103 pub fn background_hovered(self, background_hovered: Image) -> StyleBuilder {
104 StyleBuilder {
105 background_hovered: Some(background_hovered),
106 ..self
107 }
108 }
109
110 pub fn background_clicked(self, background_clicked: Image) -> StyleBuilder {
111 StyleBuilder {
112 background_clicked: Some(background_clicked),
113 ..self
114 }
115 }
116
117 pub fn text_color(self, color: Color) -> StyleBuilder {
118 StyleBuilder {
119 text_color: color,
120 ..self
121 }
122 }
123
124 pub fn text_color_hovered(self, color_hovered: Color) -> StyleBuilder {
125 StyleBuilder {
126 text_color_hovered: color_hovered,
127 ..self
128 }
129 }
130
131 pub fn text_color_clicked(self, color_clicked: Color) -> StyleBuilder {
132 StyleBuilder {
133 text_color_clicked: color_clicked,
134 ..self
135 }
136 }
137
138 pub fn font_size(self, font_size: u16) -> StyleBuilder {
139 StyleBuilder { font_size, ..self }
140 }
141
142 pub fn color(self, color: Color) -> StyleBuilder {
143 StyleBuilder { color, ..self }
144 }
145
146 pub fn color_hovered(self, color_hovered: Color) -> StyleBuilder {
147 StyleBuilder {
148 color_hovered,
149 ..self
150 }
151 }
152
153 pub fn color_clicked(self, color_clicked: Color) -> StyleBuilder {
154 StyleBuilder {
155 color_clicked,
156 ..self
157 }
158 }
159
160 pub fn color_selected(self, color_selected: Color) -> StyleBuilder {
161 StyleBuilder {
162 color_selected,
163 ..self
164 }
165 }
166
167 pub fn color_selected_hovered(self, color_selected_hovered: Color) -> StyleBuilder {
168 StyleBuilder {
169 color_selected_hovered,
170 ..self
171 }
172 }
173
174 pub fn color_inactive(self, color_inactive: Color) -> StyleBuilder {
175 StyleBuilder {
176 color_inactive: Some(color_inactive),
177 ..self
178 }
179 }
180
181 pub fn reverse_background_z(self, reverse_background_z: bool) -> StyleBuilder {
182 StyleBuilder {
183 reverse_background_z,
184 ..self
185 }
186 }
187
188 pub fn build(self) -> Style {
189 let mut atlas = self.atlas.lock().unwrap();
190
191 let background = self.background.map(|image| {
192 let id = atlas.new_unique_id();
193 atlas.cache_sprite(id, image);
194 id
195 });
196
197 let background_hovered = self.background_hovered.map(|image| {
198 let id = atlas.new_unique_id();
199 atlas.cache_sprite(id, image);
200 id
201 });
202
203 let background_clicked = self.background_clicked.map(|image| {
204 let id = atlas.new_unique_id();
205 atlas.cache_sprite(id, image);
206 id
207 });
208
209 Style {
210 background_margin: self.background_margin,
211 margin: self.margin,
212 background,
213 background_hovered,
214 background_clicked,
215 color: self.color,
216 color_hovered: self.color_hovered,
217 color_clicked: self.color_clicked,
218 color_inactive: self.color_inactive,
219 color_selected: self.color_selected,
220 color_selected_hovered: self.color_selected_hovered,
221 font: self.font,
222 text_color: self.text_color,
223 text_color_hovered: self.text_color_hovered,
224 text_color_clicked: self.text_color_clicked,
225 font_size: self.font_size,
226 reverse_background_z: self.reverse_background_z,
227 }
228 }
229}
230
231#[derive(Debug, Clone)]
232pub struct Style {
233 pub(crate) background: Option<SpriteKey>,
234 pub(crate) background_hovered: Option<SpriteKey>,
235 pub(crate) background_clicked: Option<SpriteKey>,
236 pub(crate) color: Color,
237 pub(crate) color_inactive: Option<Color>,
238 pub(crate) color_hovered: Color,
239 pub(crate) color_clicked: Color,
240 pub(crate) color_selected: Color,
241 pub(crate) color_selected_hovered: Color,
242 pub(crate) background_margin: Option<RectOffset>,
247 pub(crate) margin: Option<RectOffset>,
252 pub(crate) font: Arc<Mutex<Font>>,
253 pub(crate) text_color: Color,
254 pub(crate) text_color_hovered: Color,
255 pub(crate) text_color_clicked: Color,
256 pub(crate) font_size: u16,
257 pub(crate) reverse_background_z: bool,
258}
259
260impl Style {
261 fn default(font: Arc<Mutex<Font>>) -> Style {
262 Style {
263 background: None,
264 background_margin: None,
265 margin: None,
266 background_hovered: None,
267 background_clicked: None,
268 font,
269 text_color: Color::from_rgba(0, 0, 0, 255),
270 text_color_hovered: Color::from_rgba(0, 0, 0, 255),
271 text_color_clicked: Color::from_rgba(0, 0, 0, 255),
272 font_size: 16,
273 color: Color::from_rgba(255, 255, 255, 255),
274 color_hovered: Color::from_rgba(255, 255, 255, 255),
275 color_clicked: Color::from_rgba(255, 255, 255, 255),
276 color_selected: Color::from_rgba(255, 255, 255, 255),
277 color_selected_hovered: Color::from_rgba(255, 255, 255, 255),
278 color_inactive: None,
279 reverse_background_z: false,
280 }
281 }
282
283 pub(crate) fn border_margin(&self) -> RectOffset {
284 let background_offset = self.background_margin.unwrap_or_default();
285 let background = self.margin.unwrap_or_default();
286
287 RectOffset {
288 left: background_offset.left + background.left,
289 right: background_offset.right + background.right,
290 top: background_offset.top + background.top,
291 bottom: background_offset.bottom + background.bottom,
292 }
293 }
294
295 pub(crate) fn text_color(&self, element_state: ElementState) -> Color {
296 let ElementState {
297 focused,
298 hovered,
299 clicked,
300 ..
301 } = element_state;
302
303 if clicked {
304 self.text_color_clicked
305 } else if hovered {
306 self.text_color_hovered
307 } else if focused {
308 self.text_color
309 } else {
310 Color::new(
311 self.text_color.r * 0.6,
312 self.text_color.g * 0.6,
313 self.text_color.b * 0.6,
314 self.text_color.a * 0.6,
315 )
316 }
317 }
318
319 pub(crate) fn color(&self, element_state: ElementState) -> Color {
320 let ElementState {
321 clicked,
322 hovered,
323 focused,
324 selected,
325 } = element_state;
326
327 if focused == false {
328 return self.color_inactive.unwrap_or(Color::from_rgba(
329 (self.color.r * 255.) as u8,
330 (self.color.g * 255.) as u8,
331 (self.color.b * 255.) as u8,
332 (self.color.a * 255. * 0.8) as u8,
333 ));
334 }
335 if clicked {
336 return self.color_clicked;
337 }
338 if selected && hovered {
339 return self.color_selected_hovered;
340 }
341
342 if selected {
343 return self.color_selected;
344 }
345 if hovered {
346 return self.color_hovered;
347 }
348
349 self.color
350 }
351
352 pub(crate) const fn background_sprite(&self, element_state: ElementState) -> Option<SpriteKey> {
353 let ElementState {
354 clicked, hovered, ..
355 } = element_state;
356
357 if clicked && self.background_clicked.is_some() {
358 return self.background_clicked;
359 }
360
361 if hovered && self.background_hovered.is_some() {
362 return self.background_hovered;
363 }
364
365 self.background
366 }
367}
368
369#[derive(Debug, Clone)]
370pub struct Skin {
371 pub label_style: Style,
372 pub button_style: Style,
373 pub tabbar_style: Style,
374 pub combobox_style: Style,
375 pub window_style: Style,
376 pub editbox_style: Style,
377 pub window_titlebar_style: Style,
378 pub scrollbar_style: Style,
379 pub scrollbar_handle_style: Style,
380 pub checkbox_style: Style,
381 pub group_style: Style,
382
383 pub margin: f32,
384 pub title_height: f32,
385
386 pub scroll_width: f32,
387 pub scroll_multiplier: f32,
388}
389
390impl Skin {
391 pub(crate) fn new(atlas: Arc<Mutex<Atlas>>, default_font: Arc<Mutex<Font>>) -> Self {
392 Skin {
393 label_style: Style {
394 margin: Some(RectOffset::new(2., 2., 2., 2.)),
395 text_color: Color::from_rgba(0, 0, 0, 255),
396 color_inactive: Some(Color::from_rgba(0, 0, 0, 128)),
397 ..Style::default(default_font.clone())
398 },
399 button_style: Style {
400 margin: Some(RectOffset::new(2., 2., 2., 2.)),
401 color: Color::from_rgba(204, 204, 204, 235),
402 color_clicked: Color::from_rgba(187, 187, 187, 255),
403 color_hovered: Color::from_rgba(170, 170, 170, 235),
404 text_color: Color::from_rgba(0, 0, 0, 255),
405 ..Style::default(default_font.clone())
406 },
407 combobox_style: StyleBuilder::new(default_font.clone(), atlas.clone())
408 .background_margin(RectOffset::new(1., 14., 1., 1.))
409 .color_inactive(Color::from_rgba(238, 238, 238, 128))
410 .text_color(Color::from_rgba(0, 0, 0, 255))
411 .color(Color::from_rgba(220, 220, 220, 255))
412 .background(Image {
413 width: 16,
414 height: 30,
415 bytes: include_bytes!("combobox.img").to_vec(),
416 })
417 .build(),
418 tabbar_style: Style {
419 margin: Some(RectOffset::new(2., 2., 2., 2.)),
420 color: Color::from_rgba(220, 220, 220, 235),
421 color_clicked: Color::from_rgba(187, 187, 187, 235),
422 color_hovered: Color::from_rgba(170, 170, 170, 235),
423 color_selected_hovered: Color::from_rgba(180, 180, 180, 235),
424 color_selected: Color::from_rgba(204, 204, 204, 235),
425 text_color: Color::from_rgba(0, 0, 0, 255),
426 ..Style::default(default_font.clone())
427 },
428 window_style: StyleBuilder::new(default_font.clone(), atlas.clone())
429 .background_margin(RectOffset::new(1., 1., 1., 1.))
430 .color_inactive(Color::from_rgba(238, 238, 238, 128))
431 .text_color(Color::from_rgba(0, 0, 0, 255))
432 .background(Image {
433 width: 3,
434 height: 3,
435 bytes: vec![
436 68, 68, 68, 255, 68, 68, 68, 255, 68, 68, 68, 255, 68, 68, 68, 255, 238,
437 238, 238, 255, 68, 68, 68, 255, 68, 68, 68, 255, 68, 68, 68, 255, 68, 68,
438 68, 255,
439 ],
440 })
441 .build(),
442 window_titlebar_style: Style {
443 color: Color::from_rgba(68, 68, 68, 255),
444 color_inactive: Some(Color::from_rgba(102, 102, 102, 127)),
445 text_color: Color::from_rgba(0, 0, 0, 255),
446 ..Style::default(default_font.clone())
447 },
448 scrollbar_style: Style {
449 color: Color::from_rgba(68, 68, 68, 255),
450 ..Style::default(default_font.clone())
451 },
452 editbox_style: Style {
453 text_color: Color::from_rgba(0, 0, 0, 255),
454 color_selected: Color::from_rgba(200, 200, 200, 255),
455 ..Style::default(default_font.clone())
456 },
457
458 scrollbar_handle_style: Style {
459 color: Color::from_rgba(204, 204, 204, 235),
460 color_inactive: Some(Color::from_rgba(204, 204, 204, 128)),
461 color_hovered: Color::from_rgba(180, 180, 180, 235),
462 color_clicked: Color::from_rgba(170, 170, 170, 235),
463 ..Style::default(default_font.clone())
464 },
465 checkbox_style: Style {
466 text_color: Color::from_rgba(0, 0, 0, 255),
467 font_size: 16,
468 color: Color::from_rgba(200, 200, 200, 255),
469 color_hovered: Color::from_rgba(210, 210, 210, 255),
470 color_clicked: Color::from_rgba(150, 150, 150, 255),
471 color_selected: Color::from_rgba(128, 128, 128, 255),
472 color_selected_hovered: Color::from_rgba(140, 140, 140, 255),
473 ..Style::default(default_font.clone())
474 },
475 group_style: Style {
476 color: Color::from_rgba(34, 34, 34, 68),
477 color_hovered: Color::from_rgba(34, 153, 34, 68),
478 color_selected: Color::from_rgba(34, 34, 255, 255),
479 color_selected_hovered: Color::from_rgba(55, 55, 55, 68),
480 ..Style::default(default_font.clone())
481 },
482
483 margin: 2.0,
484 title_height: 14.0,
485 scroll_width: 10.0,
486 scroll_multiplier: 3.,
487 }
488 }
489}