1#![warn(missing_docs)]
22
23pub mod resource;
28
29use crate::{
30 brush::Brush,
31 button::Button,
32 check_box::CheckBox,
33 core::{
34 color::Color, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
35 ImmutableString, Uuid,
36 },
37 dropdown_list::DropdownList,
38 style::resource::{StyleResource, StyleResourceError, StyleResourceExt},
39 toggle::ToggleButton,
40 Thickness,
41};
42use fyrox_resource::untyped::ResourceKind;
43use fyrox_resource::{
44 io::ResourceIo,
45 manager::{BuiltInResource, ResourceManager},
46};
47use fyrox_texture::TextureResource;
48use std::{
49 any::{Any, TypeId},
50 sync::LazyLock,
51};
52use std::{
53 ops::{Deref, DerefMut},
54 path::Path,
55 sync::Arc,
56};
57use strum_macros::{AsRefStr, EnumString, VariantNames};
58
59#[derive(Visit, Reflect, Debug, Clone, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
61#[type_uuid(id = "85b8c1e4-03a2-4a28-acb4-1850d1a29227")]
62pub enum StyleProperty {
63 Number(f32),
65 Thickness(Thickness),
67 Color(Color),
69 Brush(Brush),
71 Texture(TextureResource),
74}
75
76impl Default for StyleProperty {
77 fn default() -> Self {
78 Self::Number(0.0)
79 }
80}
81
82impl StyleProperty {
83 pub fn value_type_id(&self) -> TypeId {
86 match self {
87 StyleProperty::Number(v) => v.type_id(),
88 StyleProperty::Thickness(v) => v.type_id(),
89 StyleProperty::Color(v) => v.type_id(),
90 StyleProperty::Brush(v) => v.type_id(),
91 StyleProperty::Texture(v) => v.type_id(),
92 }
93 }
94}
95
96pub trait IntoPrimitive<T> {
98 fn into_primitive(self) -> Option<T>;
100}
101
102macro_rules! impl_casts {
103 ($ty:ty => $var:ident) => {
104 impl From<$ty> for StyleProperty {
105 fn from(value: $ty) -> Self {
106 Self::$var(value)
107 }
108 }
109
110 impl IntoPrimitive<$ty> for StyleProperty {
111 fn into_primitive(self) -> Option<$ty> {
112 if let StyleProperty::$var(value) = self {
113 Some(value)
114 } else {
115 None
116 }
117 }
118 }
119 };
120}
121
122impl_casts!(f32 => Number);
123impl_casts!(Thickness => Thickness);
124impl_casts!(Color => Color);
125impl_casts!(Brush => Brush);
126impl_casts!(TextureResource => Texture);
127
128pub static DEFAULT_STYLE: LazyLock<BuiltInResource<Style>> = LazyLock::new(|| {
130 BuiltInResource::new_no_source(
131 "__DEFAULT_STYLE__",
132 StyleResource::new_ok(
133 uuid!("1e0716e8-e728-491c-a65b-ca11b15048be"),
134 ResourceKind::External,
135 Style::dark_style(),
136 ),
137 )
138});
139
140#[derive(Clone, Debug, Reflect, Default)]
145#[reflect(bounds = "T: Reflect + Clone")]
146pub struct StyledProperty<T> {
147 pub property: T,
149 #[reflect(read_only, display_name = "Property Name")]
151 pub name: ImmutableString,
152}
153
154impl<T> From<T> for StyledProperty<T> {
155 fn from(property: T) -> Self {
156 Self {
157 property,
158 name: Default::default(),
159 }
160 }
161}
162
163impl<T: PartialEq> PartialEq for StyledProperty<T> {
164 fn eq(&self, other: &Self) -> bool {
165 self.property == other.property
166 }
167}
168
169impl<T> StyledProperty<T> {
170 pub fn new(property: T, name: impl Into<ImmutableString>) -> Self {
172 Self {
173 property,
174 name: name.into(),
175 }
176 }
177
178 pub fn update(&mut self, style: &StyleResource)
181 where
182 StyleProperty: IntoPrimitive<T>,
183 {
184 if let Some(property) = style.get(self.name.clone()) {
185 self.property = property;
186 }
187 }
188}
189
190impl<T> Deref for StyledProperty<T> {
191 type Target = T;
192
193 fn deref(&self) -> &Self::Target {
194 &self.property
195 }
196}
197
198impl<T> DerefMut for StyledProperty<T> {
199 fn deref_mut(&mut self) -> &mut Self::Target {
200 &mut self.property
201 }
202}
203
204impl<T: Visit> Visit for StyledProperty<T> {
205 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
206 self.property.visit(name, visitor)
207 }
208}
209
210#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
212#[type_uuid(id = "6238f37c-c067-4dd1-be67-6a8bb8853a59")]
213pub struct StylePropertyContainer {
214 pub name: ImmutableString,
216 pub value: StyleProperty,
218}
219
220#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
290#[type_uuid(id = "38a63b49-d765-4c01-8fb5-202cc43d607e")]
291pub struct Style {
292 parent: Option<StyleResource>,
293 properties: Vec<StylePropertyContainer>,
294}
295
296impl Style {
297 pub const BRUSH_DARKEST: &'static str = "Global.Brush.Darkest";
299 pub const BRUSH_DARKER: &'static str = "Global.Brush.Darker";
301 pub const BRUSH_DARK: &'static str = "Global.Brush.Dark";
303 pub const BRUSH_PRIMARY: &'static str = "Global.Brush.Primary";
305 pub const BRUSH_LIGHTER_PRIMARY: &'static str = "Global.Brush.LighterPrimary";
307 pub const BRUSH_LIGHT: &'static str = "Global.Brush.Light";
309 pub const BRUSH_LIGHTER: &'static str = "Global.Brush.Lighter";
311 pub const BRUSH_LIGHTEST: &'static str = "Global.Brush.Lightest";
313 pub const BRUSH_BRIGHT: &'static str = "Global.Brush.Bright";
315 pub const BRUSH_BRIGHTEST: &'static str = "Global.Brush.Brightest";
317 pub const BRUSH_BRIGHT_BLUE: &'static str = "Global.Brush.BrightBlue";
319 pub const BRUSH_DIM_BLUE: &'static str = "Global.Brush.DimBlue";
321 pub const BRUSH_TEXT: &'static str = "Global.Brush.Text";
323 pub const BRUSH_FOREGROUND: &'static str = "Global.Brush.Foreground";
325 pub const BRUSH_INFORMATION: &'static str = "Global.Brush.Information";
327 pub const BRUSH_WARNING: &'static str = "Global.Brush.Warning";
329 pub const BRUSH_ERROR: &'static str = "Global.Brush.Error";
331 pub const BRUSH_OK: &'static str = "Global.Brush.Ok";
333 pub const BRUSH_HIGHLIGHT: &'static str = "Global.Brush.Highlight";
335 pub const FONT_SIZE: &'static str = "Global.Font.Size";
337 pub const BRUSH_OK_NORMAL: &'static str = "Global.Brush.Ok.Normal";
339 pub const BRUSH_OK_PRESSED: &'static str = "Global.Brush.Ok.Pressed";
341 pub const BRUSH_OK_HOVER: &'static str = "Global.Brush.Ok.Hover";
343 pub const BRUSH_CANCEL_NORMAL: &'static str = "Global.Brush.Cancel.Normal";
345 pub const BRUSH_CANCEL_PRESSED: &'static str = "Global.Brush.Cancel.Pressed";
347 pub const BRUSH_CANCEL_HOVER: &'static str = "Global.Brush.Cancel.Hover";
349
350 fn base_style() -> Style {
351 let mut style = Self::default();
352
353 style
354 .set(Self::FONT_SIZE, 14.0f32)
355 .merge(&Button::style())
356 .merge(&CheckBox::style())
357 .merge(&DropdownList::style())
358 .merge(&ToggleButton::style());
359
360 style
361 }
362
363 pub fn dark_style() -> Style {
365 let mut style = Self::base_style();
366 style
367 .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(20)))
368 .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(30)))
369 .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(40)))
370 .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(50)))
371 .set(
372 Self::BRUSH_LIGHTER_PRIMARY,
373 Brush::Solid(Color::repeat_opaque(60)),
374 )
375 .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(70)))
376 .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(85)))
377 .set(
378 Self::BRUSH_LIGHTEST,
379 Brush::Solid(Color::repeat_opaque(100)),
380 )
381 .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(130)))
382 .set(
383 Self::BRUSH_BRIGHTEST,
384 Brush::Solid(Color::repeat_opaque(160)),
385 )
386 .set(
387 Self::BRUSH_BRIGHT_BLUE,
388 Brush::Solid(Color::opaque(80, 118, 178)),
389 )
390 .set(
391 Self::BRUSH_HIGHLIGHT,
392 Brush::Solid(Color::opaque(80, 118, 178)),
393 )
394 .set(
395 Self::BRUSH_DIM_BLUE,
396 Brush::Solid(Color::opaque(66, 99, 149)),
397 )
398 .set(Self::BRUSH_TEXT, Brush::Solid(Color::opaque(190, 190, 190)))
399 .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
400 .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ANTIQUE_WHITE))
401 .set(Self::BRUSH_WARNING, Brush::Solid(Color::GOLD))
402 .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED))
403 .set(Self::BRUSH_OK, Brush::Solid(Color::GREEN))
404 .set(
405 Self::BRUSH_OK_NORMAL,
406 Brush::Solid(Color::opaque(0, 130, 0)),
407 )
408 .set(Self::BRUSH_OK_HOVER, Brush::Solid(Color::opaque(0, 150, 0)))
409 .set(
410 Self::BRUSH_OK_PRESSED,
411 Brush::Solid(Color::opaque(0, 170, 0)),
412 )
413 .set(
414 Self::BRUSH_CANCEL_NORMAL,
415 Brush::Solid(Color::opaque(130, 0, 0)),
416 )
417 .set(
418 Self::BRUSH_CANCEL_HOVER,
419 Brush::Solid(Color::opaque(150, 0, 0)),
420 )
421 .set(
422 Self::BRUSH_CANCEL_PRESSED,
423 Brush::Solid(Color::opaque(170, 0, 0)),
424 );
425 style
426 }
427
428 pub fn light_style() -> Style {
430 let mut style = Self::base_style();
431 style
432 .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(140)))
433 .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(150)))
434 .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(160)))
435 .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(170)))
436 .set(
437 Self::BRUSH_LIGHTER_PRIMARY,
438 Brush::Solid(Color::repeat_opaque(180)),
439 )
440 .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(190)))
441 .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(205)))
442 .set(
443 Self::BRUSH_LIGHTEST,
444 Brush::Solid(Color::repeat_opaque(220)),
445 )
446 .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(40)))
447 .set(
448 Self::BRUSH_BRIGHTEST,
449 Brush::Solid(Color::repeat_opaque(30)),
450 )
451 .set(
452 Self::BRUSH_BRIGHT_BLUE,
453 Brush::Solid(Color::opaque(80, 118, 178)),
454 )
455 .set(
456 Self::BRUSH_HIGHLIGHT,
457 Brush::Solid(Color::opaque(80, 118, 178)),
458 )
459 .set(
460 Self::BRUSH_DIM_BLUE,
461 Brush::Solid(Color::opaque(66, 99, 149)),
462 )
463 .set(Self::BRUSH_TEXT, Brush::Solid(Color::repeat_opaque(0)))
464 .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
465 .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ROYAL_BLUE))
466 .set(
467 Self::BRUSH_WARNING,
468 Brush::Solid(Color::opaque(255, 242, 0)),
469 )
470 .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED));
471 style
472 }
473
474 pub fn with(
486 mut self,
487 name: impl Into<ImmutableString>,
488 property: impl Into<StyleProperty>,
489 ) -> Self {
490 self.set(name, property);
491 self
492 }
493
494 pub fn set_parent(&mut self, parent: Option<StyleResource>) {
497 self.parent = parent;
498 }
499
500 pub fn parent(&self) -> Option<&StyleResource> {
502 self.parent.as_ref()
503 }
504
505 pub fn index_of(&self, name: &ImmutableString) -> Option<usize> {
507 self.properties
508 .binary_search_by(|v| v.name.cached_hash().cmp(&name.cached_hash()))
509 .ok()
510 }
511
512 pub fn contains(&self, name: &ImmutableString) -> bool {
514 self.index_of(name).is_some()
515 }
516
517 pub fn merge(&mut self, other: &Self) -> &mut Self {
520 for other_property in other.properties.iter() {
521 if !self.contains(&other_property.name) {
522 self.set(other_property.name.clone(), other_property.value.clone());
523 }
524 }
525 self
526 }
527
528 pub fn set(
540 &mut self,
541 name: impl Into<ImmutableString>,
542 value: impl Into<StyleProperty>,
543 ) -> &mut Self {
544 let name = name.into();
545 let value = value.into();
546
547 if let Some(existing_index) = self.index_of(&name) {
548 self.properties[existing_index] = StylePropertyContainer { name, value };
549 } else {
550 let index = self
551 .properties
552 .partition_point(|h| h.name.cached_hash() < name.cached_hash());
553 self.properties
554 .insert(index, StylePropertyContainer { name, value });
555 }
556
557 self
558 }
559
560 pub fn get_raw(&self, name: impl Into<ImmutableString>) -> Option<StyleProperty> {
563 let name = name.into();
564 let index = self.index_of(&name)?;
565 if let Some(container) = self.properties.get(index) {
566 return Some(container.value.clone());
567 } else if let Some(parent) = self.parent.as_ref() {
568 let state = parent.state();
569 if let Some(data) = state.data_ref() {
570 return data.get_raw(name);
571 }
572 }
573 None
574 }
575
576 pub fn get<P>(&self, name: impl Into<ImmutableString>) -> Option<P>
580 where
581 StyleProperty: IntoPrimitive<P>,
582 {
583 self.get_raw(name)
584 .and_then(|property| property.into_primitive())
585 }
586
587 pub fn get_or_default<P>(&self, name: impl Into<ImmutableString>) -> P
591 where
592 P: Default,
593 StyleProperty: IntoPrimitive<P>,
594 {
595 self.get_raw(name)
596 .and_then(|property| property.into_primitive())
597 .unwrap_or_default()
598 }
599
600 pub fn get_or<P>(&self, name: impl Into<ImmutableString>, default: P) -> P
602 where
603 StyleProperty: IntoPrimitive<P>,
604 {
605 self.get(name).unwrap_or(default)
606 }
607
608 pub fn property<P>(&self, name: impl Into<ImmutableString>) -> StyledProperty<P>
611 where
612 P: Default,
613 StyleProperty: IntoPrimitive<P>,
614 {
615 let name = name.into();
616 StyledProperty::new(self.get_or_default(name.clone()), name)
617 }
618
619 pub async fn from_file(
621 path: &Path,
622 io: &dyn ResourceIo,
623 resource_manager: ResourceManager,
624 ) -> Result<Self, StyleResourceError> {
625 let bytes = io.load_file(path).await?;
626 let mut visitor = Visitor::load_from_memory(&bytes)?;
627 visitor.blackboard.register(Arc::new(resource_manager));
628 let mut style = Style::default();
629 style.visit("Style", &mut visitor)?;
630 Ok(style)
631 }
632
633 pub fn inner(&self) -> &Vec<StylePropertyContainer> {
637 &self.properties
638 }
639
640 pub fn all_properties(&self) -> Self {
644 let mut properties = self
645 .parent
646 .as_ref()
647 .map(|parent| parent.data_ref().all_properties())
648 .unwrap_or_default();
649 for property in self.properties.iter() {
650 properties.set(property.name.clone(), property.value.clone());
651 }
652 properties
653 }
654}
655
656#[cfg(test)]
657mod test {
658 use crate::brush::Brush;
659 use crate::style::Style;
660 use fyrox_core::color::Color;
661 use fyrox_core::ImmutableString;
662
663 #[test]
664 fn test_style() {
665 let mut style = Style::default();
666 style
667 .set("A", 0.2f32)
668 .set("D", 0.1f32)
669 .set("B", Brush::Solid(Color::WHITE))
670 .set("C", Brush::Solid(Color::WHITE));
671 assert_eq!(style.index_of(&ImmutableString::new("A")), Some(3));
672 assert_eq!(style.index_of(&ImmutableString::new("B")), Some(2));
673 assert_eq!(style.index_of(&ImmutableString::new("C")), Some(1));
674 assert_eq!(style.index_of(&ImmutableString::new("D")), Some(0));
675 }
676}