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
140pub static LIGHT_STYLE: LazyLock<BuiltInResource<Style>> = LazyLock::new(|| {
142 BuiltInResource::new_no_source(
143 "Light Style",
144 StyleResource::new_ok(
145 uuid!("05141b18-2a27-4fe3-ae6e-7af11c2e7471"),
146 ResourceKind::External,
147 Style::light_style(),
148 ),
149 )
150});
151
152#[derive(Clone, Debug, Reflect, Default)]
157#[reflect(bounds = "T: Reflect + Clone")]
158pub struct StyledProperty<T> {
159 pub property: T,
161 #[reflect(read_only, display_name = "Property Name")]
163 pub name: ImmutableString,
164}
165
166impl<T> From<T> for StyledProperty<T> {
167 fn from(property: T) -> Self {
168 Self {
169 property,
170 name: Default::default(),
171 }
172 }
173}
174
175impl<T: PartialEq> PartialEq for StyledProperty<T> {
176 fn eq(&self, other: &Self) -> bool {
177 self.property == other.property
178 }
179}
180
181impl<T> StyledProperty<T> {
182 pub fn new(property: T, name: impl Into<ImmutableString>) -> Self {
184 Self {
185 property,
186 name: name.into(),
187 }
188 }
189
190 pub fn update(&mut self, style: &StyleResource)
193 where
194 StyleProperty: IntoPrimitive<T>,
195 {
196 if let Some(property) = style.get(self.name.clone()) {
197 self.property = property;
198 }
199 }
200}
201
202impl<T> Deref for StyledProperty<T> {
203 type Target = T;
204
205 fn deref(&self) -> &Self::Target {
206 &self.property
207 }
208}
209
210impl<T> DerefMut for StyledProperty<T> {
211 fn deref_mut(&mut self) -> &mut Self::Target {
212 &mut self.property
213 }
214}
215
216impl<T: Visit> Visit for StyledProperty<T> {
217 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
218 self.property.visit(name, visitor)
219 }
220}
221
222#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
224#[type_uuid(id = "6238f37c-c067-4dd1-be67-6a8bb8853a59")]
225pub struct StylePropertyContainer {
226 pub name: ImmutableString,
228 pub value: StyleProperty,
230}
231
232#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
302#[type_uuid(id = "38a63b49-d765-4c01-8fb5-202cc43d607e")]
303pub struct Style {
304 parent: Option<StyleResource>,
305 properties: Vec<StylePropertyContainer>,
306}
307
308impl Style {
309 pub const BRUSH_DARKEST: &'static str = "Global.Brush.Darkest";
311 pub const BRUSH_DARKER: &'static str = "Global.Brush.Darker";
313 pub const BRUSH_DARK: &'static str = "Global.Brush.Dark";
315 pub const BRUSH_PRIMARY: &'static str = "Global.Brush.Primary";
317 pub const BRUSH_LIGHTER_PRIMARY: &'static str = "Global.Brush.LighterPrimary";
319 pub const BRUSH_LIGHT: &'static str = "Global.Brush.Light";
321 pub const BRUSH_LIGHTER: &'static str = "Global.Brush.Lighter";
323 pub const BRUSH_LIGHTEST: &'static str = "Global.Brush.Lightest";
325 pub const BRUSH_BRIGHT: &'static str = "Global.Brush.Bright";
327 pub const BRUSH_BRIGHTEST: &'static str = "Global.Brush.Brightest";
329 pub const BRUSH_BRIGHT_BLUE: &'static str = "Global.Brush.BrightBlue";
331 pub const BRUSH_DIM_BLUE: &'static str = "Global.Brush.DimBlue";
333 pub const BRUSH_TEXT: &'static str = "Global.Brush.Text";
335 pub const BRUSH_FOREGROUND: &'static str = "Global.Brush.Foreground";
337 pub const BRUSH_INFORMATION: &'static str = "Global.Brush.Information";
339 pub const BRUSH_WARNING: &'static str = "Global.Brush.Warning";
341 pub const BRUSH_ERROR: &'static str = "Global.Brush.Error";
343 pub const BRUSH_OK: &'static str = "Global.Brush.Ok";
345 pub const BRUSH_HIGHLIGHT: &'static str = "Global.Brush.Highlight";
347 pub const FONT_SIZE: &'static str = "Global.Font.Size";
349 pub const BRUSH_OK_NORMAL: &'static str = "Global.Brush.Ok.Normal";
351 pub const BRUSH_OK_PRESSED: &'static str = "Global.Brush.Ok.Pressed";
353 pub const BRUSH_OK_HOVER: &'static str = "Global.Brush.Ok.Hover";
355 pub const BRUSH_CANCEL_NORMAL: &'static str = "Global.Brush.Cancel.Normal";
357 pub const BRUSH_CANCEL_PRESSED: &'static str = "Global.Brush.Cancel.Pressed";
359 pub const BRUSH_CANCEL_HOVER: &'static str = "Global.Brush.Cancel.Hover";
361
362 fn base_style() -> Style {
363 let mut style = Self::default();
364
365 style
366 .set(Self::FONT_SIZE, 14.0f32)
367 .merge(&Button::style())
368 .merge(&CheckBox::style())
369 .merge(&DropdownList::style())
370 .merge(&ToggleButton::style());
371
372 style
373 }
374
375 pub fn dark_style() -> Style {
377 let mut style = Self::base_style();
378 style
379 .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(20)))
380 .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(30)))
381 .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(40)))
382 .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(50)))
383 .set(
384 Self::BRUSH_LIGHTER_PRIMARY,
385 Brush::Solid(Color::repeat_opaque(60)),
386 )
387 .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(70)))
388 .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(85)))
389 .set(
390 Self::BRUSH_LIGHTEST,
391 Brush::Solid(Color::repeat_opaque(100)),
392 )
393 .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(130)))
394 .set(
395 Self::BRUSH_BRIGHTEST,
396 Brush::Solid(Color::repeat_opaque(160)),
397 )
398 .set(
399 Self::BRUSH_BRIGHT_BLUE,
400 Brush::Solid(Color::opaque(80, 118, 178)),
401 )
402 .set(
403 Self::BRUSH_HIGHLIGHT,
404 Brush::Solid(Color::opaque(80, 118, 178)),
405 )
406 .set(
407 Self::BRUSH_DIM_BLUE,
408 Brush::Solid(Color::opaque(66, 99, 149)),
409 )
410 .set(Self::BRUSH_TEXT, Brush::Solid(Color::opaque(190, 190, 190)))
411 .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
412 .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ANTIQUE_WHITE))
413 .set(Self::BRUSH_WARNING, Brush::Solid(Color::GOLD))
414 .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED))
415 .set(Self::BRUSH_OK, Brush::Solid(Color::GREEN))
416 .set(
417 Self::BRUSH_OK_NORMAL,
418 Brush::Solid(Color::opaque(0, 130, 0)),
419 )
420 .set(Self::BRUSH_OK_HOVER, Brush::Solid(Color::opaque(0, 150, 0)))
421 .set(
422 Self::BRUSH_OK_PRESSED,
423 Brush::Solid(Color::opaque(0, 170, 0)),
424 )
425 .set(
426 Self::BRUSH_CANCEL_NORMAL,
427 Brush::Solid(Color::opaque(130, 0, 0)),
428 )
429 .set(
430 Self::BRUSH_CANCEL_HOVER,
431 Brush::Solid(Color::opaque(150, 0, 0)),
432 )
433 .set(
434 Self::BRUSH_CANCEL_PRESSED,
435 Brush::Solid(Color::opaque(170, 0, 0)),
436 );
437 style
438 }
439
440 pub fn light_style() -> Style {
442 let mut style = Self::base_style();
443 style
444 .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(140)))
445 .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(150)))
446 .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(160)))
447 .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(170)))
448 .set(
449 Self::BRUSH_LIGHTER_PRIMARY,
450 Brush::Solid(Color::repeat_opaque(180)),
451 )
452 .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(190)))
453 .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(205)))
454 .set(
455 Self::BRUSH_LIGHTEST,
456 Brush::Solid(Color::repeat_opaque(220)),
457 )
458 .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(40)))
459 .set(
460 Self::BRUSH_BRIGHTEST,
461 Brush::Solid(Color::repeat_opaque(30)),
462 )
463 .set(
464 Self::BRUSH_BRIGHT_BLUE,
465 Brush::Solid(Color::opaque(80, 118, 178)),
466 )
467 .set(
468 Self::BRUSH_HIGHLIGHT,
469 Brush::Solid(Color::opaque(80, 118, 178)),
470 )
471 .set(
472 Self::BRUSH_DIM_BLUE,
473 Brush::Solid(Color::opaque(66, 99, 149)),
474 )
475 .set(Self::BRUSH_TEXT, Brush::Solid(Color::repeat_opaque(0)))
476 .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
477 .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ROYAL_BLUE))
478 .set(
479 Self::BRUSH_WARNING,
480 Brush::Solid(Color::opaque(255, 242, 0)),
481 )
482 .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED));
483 style
484 }
485
486 pub fn with(
498 mut self,
499 name: impl Into<ImmutableString>,
500 property: impl Into<StyleProperty>,
501 ) -> Self {
502 self.set(name, property);
503 self
504 }
505
506 pub fn set_parent(&mut self, parent: Option<StyleResource>) {
509 self.parent = parent;
510 }
511
512 pub fn parent(&self) -> Option<&StyleResource> {
514 self.parent.as_ref()
515 }
516
517 pub fn index_of(&self, name: &ImmutableString) -> Option<usize> {
519 self.properties
520 .binary_search_by(|v| v.name.cached_hash().cmp(&name.cached_hash()))
521 .ok()
522 }
523
524 pub fn contains(&self, name: &ImmutableString) -> bool {
526 self.index_of(name).is_some()
527 }
528
529 pub fn merge(&mut self, other: &Self) -> &mut Self {
532 for other_property in other.properties.iter() {
533 if !self.contains(&other_property.name) {
534 self.set(other_property.name.clone(), other_property.value.clone());
535 }
536 }
537 self
538 }
539
540 pub fn set(
552 &mut self,
553 name: impl Into<ImmutableString>,
554 value: impl Into<StyleProperty>,
555 ) -> &mut Self {
556 let name = name.into();
557 let value = value.into();
558
559 if let Some(existing_index) = self.index_of(&name) {
560 self.properties[existing_index] = StylePropertyContainer { name, value };
561 } else {
562 let index = self
563 .properties
564 .partition_point(|h| h.name.cached_hash() < name.cached_hash());
565 self.properties
566 .insert(index, StylePropertyContainer { name, value });
567 }
568
569 self
570 }
571
572 pub fn get_raw(&self, name: impl Into<ImmutableString>) -> Option<StyleProperty> {
575 let name = name.into();
576 let index = self.index_of(&name)?;
577 if let Some(container) = self.properties.get(index) {
578 return Some(container.value.clone());
579 } else if let Some(parent) = self.parent.as_ref() {
580 let state = parent.state();
581 if let Some(data) = state.data_ref() {
582 return data.get_raw(name);
583 }
584 }
585 None
586 }
587
588 pub fn get<P>(&self, name: impl Into<ImmutableString>) -> Option<P>
592 where
593 StyleProperty: IntoPrimitive<P>,
594 {
595 self.get_raw(name)
596 .and_then(|property| property.into_primitive())
597 }
598
599 pub fn get_or_default<P>(&self, name: impl Into<ImmutableString>) -> P
603 where
604 P: Default,
605 StyleProperty: IntoPrimitive<P>,
606 {
607 self.get_raw(name)
608 .and_then(|property| property.into_primitive())
609 .unwrap_or_default()
610 }
611
612 pub fn get_or<P>(&self, name: impl Into<ImmutableString>, default: P) -> P
614 where
615 StyleProperty: IntoPrimitive<P>,
616 {
617 self.get(name).unwrap_or(default)
618 }
619
620 pub fn property<P>(&self, name: impl Into<ImmutableString>) -> StyledProperty<P>
623 where
624 P: Default,
625 StyleProperty: IntoPrimitive<P>,
626 {
627 let name = name.into();
628 StyledProperty::new(self.get_or_default(name.clone()), name)
629 }
630
631 pub async fn from_file(
633 path: &Path,
634 io: &dyn ResourceIo,
635 resource_manager: ResourceManager,
636 ) -> Result<Self, StyleResourceError> {
637 let bytes = io.load_file(path).await?;
638 let mut visitor = Visitor::load_from_memory(&bytes)?;
639 visitor.blackboard.register(Arc::new(resource_manager));
640 let mut style = Style::default();
641 style.visit("Style", &mut visitor)?;
642 Ok(style)
643 }
644
645 pub fn inner(&self) -> &Vec<StylePropertyContainer> {
649 &self.properties
650 }
651
652 pub fn all_properties(&self) -> Self {
656 let mut properties = self
657 .parent
658 .as_ref()
659 .map(|parent| parent.data_ref().all_properties())
660 .unwrap_or_default();
661 for property in self.properties.iter() {
662 properties.set(property.name.clone(), property.value.clone());
663 }
664 properties
665 }
666}
667
668#[cfg(test)]
669mod test {
670 use crate::brush::Brush;
671 use crate::style::Style;
672 use fyrox_core::color::Color;
673 use fyrox_core::ImmutableString;
674
675 #[test]
676 fn test_style() {
677 let mut style = Style::default();
678 style
679 .set("A", 0.2f32)
680 .set("D", 0.1f32)
681 .set("B", Brush::Solid(Color::WHITE))
682 .set("C", Brush::Solid(Color::WHITE));
683 assert_eq!(style.index_of(&ImmutableString::new("A")), Some(3));
684 assert_eq!(style.index_of(&ImmutableString::new("B")), Some(2));
685 assert_eq!(style.index_of(&ImmutableString::new("C")), Some(1));
686 assert_eq!(style.index_of(&ImmutableString::new("D")), Some(0));
687 }
688}