Skip to main content

fyrox_ui/style/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21#![warn(missing_docs)]
22
23//! Style allows to change the visual appearance of widgets in a centralized manner. It can be considered
24//! as a storage for properties that define visual appearance. See [`Style`] docs for more info
25//! and usage examples.
26
27pub 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/// A set of potential values for styled properties.
60#[derive(Visit, Reflect, Debug, Clone, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
61#[type_uuid(id = "85b8c1e4-03a2-4a28-acb4-1850d1a29227")]
62pub enum StyleProperty {
63    /// A numeric property.
64    Number(f32),
65    /// A thickness property that defines the width of four sides of a rectangles.
66    Thickness(Thickness),
67    /// A color property.
68    Color(Color),
69    /// A brush property, that defines how to render an arbitrary surface (solid, with gradient, etc.).
70    Brush(Brush),
71    /// A texture property. Could be used together with [`crate::image::Image`] widget or [`crate::nine_patch::NinePatch`]
72    /// widget.
73    Texture(TextureResource),
74}
75
76impl Default for StyleProperty {
77    fn default() -> Self {
78        Self::Number(0.0)
79    }
80}
81
82impl StyleProperty {
83    /// Returns type id of the actual value stored in the property. The set of potential types is
84    /// finite (see [`StyleProperty`] declaration).
85    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
96/// A trait that provides a method that translates [`StyleProperty`] into a specific primitive value.
97pub trait IntoPrimitive<T> {
98    /// Tries to convert self into a primitive value `T`.
99    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
128/// Default style of the library.
129pub 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/// Light style of the library.
141pub 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/// A property, that can bind its value to a style. Why can't we just fetch the actual value from
153/// the style and why do we need to store the value as well? The answer is flexibility. In this
154/// approach, style becomes not necessary and the value can be hardcoded. Also, the values of such
155/// properties can be updated individually.
156#[derive(Clone, Debug, Reflect, Default)]
157#[reflect(bounds = "T: Reflect + Clone")]
158pub struct StyledProperty<T> {
159    /// Property value.
160    pub property: T,
161    /// Name of the property in a style table.
162    #[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    /// Creates a new styled property with the given value and the name.
183    pub fn new(property: T, name: impl Into<ImmutableString>) -> Self {
184        Self {
185            property,
186            name: name.into(),
187        }
188    }
189
190    /// Tries to sync the property value with its respective value in the given style. This method
191    /// will fail if the given style does not contain the property.
192    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/// Named style property container.
223#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
224#[type_uuid(id = "6238f37c-c067-4dd1-be67-6a8bb8853a59")]
225pub struct StylePropertyContainer {
226    /// Name of the property.
227    pub name: ImmutableString,
228    /// The actual value of the property.
229    pub value: StyleProperty,
230}
231
232/// Style is a simple container for a named properties. Styles can be based on some other styles, thus
233/// allowing cascaded styling. Such cascading allows to define some base style with common properties
234/// and then create any number of derived styles. For example, you can define a style for Button widget
235/// with corner radius, font size, border thickness and then create two derived styles for light and
236/// dark themes that will define colors and brushes. Light or dark theme does not affect all of those
237/// base properties, but has different colors.
238///
239/// Styles can contain only specific types of properties (see [`StyleProperty`] enumeration), any
240/// more complex properties can be built using these primitives.
241///
242/// There are three major ways of widget styling:
243///
244/// 1) During widget building stage - this way involves [`crate::BuildContext`]'s style field. This
245/// field defines a style for all widgets that will be built with the context.
246/// 2) Message-based style changes - this way is based on [`crate::widget::WidgetMessage::Style`] message
247/// that can be sent to a particular widget (or hierarchy) to force them to update styled properties.
248/// 3) Global style changes - this way is based on [`crate::UserInterface::set_style`] method, that
249/// sends the specified style to all widgets, forcing them to update styled properties.
250///
251/// The most used methods are 1 and 3. The following examples should clarify how to use these
252/// approaches.
253///
254/// ## Examples
255///
256/// The following example shows how to use a style during widget building stage:
257///
258/// ```rust
259/// # use fyrox_ui::{
260/// #     button::{Button, ButtonBuilder},
261/// #     style::{resource::StyleResource, Style},
262/// #     widget::WidgetBuilder,
263/// #     Thickness, UserInterface,
264/// # };
265/// #
266/// fn build_with_style(ui: &mut UserInterface) {
267///     // The context will use UI style by default. You can override it using `ui.set_style(..)`.
268///     let ctx = &mut ui.build_ctx();
269///
270///     // Create a style resource first and assign it to the build context. All widgets built with
271///     // the context will use this style.
272///     let style = Style::light_style()
273///         .with(Button::CORNER_RADIUS, 6.0f32)
274///         .with(Button::BORDER_THICKNESS, Thickness::uniform(3.0));
275///
276///     ctx.style = StyleResource::new_embedded(style);
277///
278///     // The button will have a corner radius of 6.0 points and border thickness of 3.0 points on
279///     // each side.
280///     ButtonBuilder::new(WidgetBuilder::new()).build(ctx);
281/// }
282/// ```
283///
284/// To change UI style globally after it was built, use something like this:
285///
286/// ```rust
287/// use fyrox_ui::{
288///     button::Button,
289///     style::{resource::StyleResource, Style},
290///     Thickness, UserInterface,
291/// };
292///
293/// fn apply_style(ui: &mut UserInterface) {
294///     let style = Style::light_style()
295///         .with(Button::CORNER_RADIUS, 3.0f32)
296///         .with(Button::BORDER_THICKNESS, Thickness::uniform(1.0));
297///
298///     ui.set_style(StyleResource::new_embedded(style));
299/// }
300/// ```
301#[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    /// The name of the darkest brush.
310    pub const BRUSH_DARKEST: &'static str = "Global.Brush.Darkest";
311    /// The name of the darker brush.
312    pub const BRUSH_DARKER: &'static str = "Global.Brush.Darker";
313    /// The name of the dark brush.
314    pub const BRUSH_DARK: &'static str = "Global.Brush.Dark";
315    /// The name of the primary brush that is used for the major amount of surface.
316    pub const BRUSH_PRIMARY: &'static str = "Global.Brush.Primary";
317    /// The name of the slightly lighter primary brush.
318    pub const BRUSH_LIGHTER_PRIMARY: &'static str = "Global.Brush.LighterPrimary";
319    /// The name of the light brush.
320    pub const BRUSH_LIGHT: &'static str = "Global.Brush.Light";
321    /// The name of the lighter brush.
322    pub const BRUSH_LIGHTER: &'static str = "Global.Brush.Lighter";
323    /// The name of the lightest brush.
324    pub const BRUSH_LIGHTEST: &'static str = "Global.Brush.Lightest";
325    /// The name of the bright brush.
326    pub const BRUSH_BRIGHT: &'static str = "Global.Brush.Bright";
327    /// The name of the brightest brush.
328    pub const BRUSH_BRIGHTEST: &'static str = "Global.Brush.Brightest";
329    /// The name of the bright blue brush.
330    pub const BRUSH_BRIGHT_BLUE: &'static str = "Global.Brush.BrightBlue";
331    /// The name of the dim blue brush.
332    pub const BRUSH_DIM_BLUE: &'static str = "Global.Brush.DimBlue";
333    /// The name of the text brush.
334    pub const BRUSH_TEXT: &'static str = "Global.Brush.Text";
335    /// The name of the foreground brush.
336    pub const BRUSH_FOREGROUND: &'static str = "Global.Brush.Foreground";
337    /// The name of the information brush.
338    pub const BRUSH_INFORMATION: &'static str = "Global.Brush.Information";
339    /// The name of the warning brush.
340    pub const BRUSH_WARNING: &'static str = "Global.Brush.Warning";
341    /// The name of the error brush.
342    pub const BRUSH_ERROR: &'static str = "Global.Brush.Error";
343    /// The name of the ok brush.
344    pub const BRUSH_OK: &'static str = "Global.Brush.Ok";
345    /// The name of the highlight brush used to highlight widgets with keyboard focus.
346    pub const BRUSH_HIGHLIGHT: &'static str = "Global.Brush.Highlight";
347    /// The name of the font size property.
348    pub const FONT_SIZE: &'static str = "Global.Font.Size";
349    /// The name of the normal state brush of the `ok` context action.
350    pub const BRUSH_OK_NORMAL: &'static str = "Global.Brush.Ok.Normal";
351    /// The name of the pressed state brush of the `ok` context action.
352    pub const BRUSH_OK_PRESSED: &'static str = "Global.Brush.Ok.Pressed";
353    /// The name of the hover state brush of the `ok` context action.
354    pub const BRUSH_OK_HOVER: &'static str = "Global.Brush.Ok.Hover";
355    /// The name of the normal state brush of the `cancel` context action.
356    pub const BRUSH_CANCEL_NORMAL: &'static str = "Global.Brush.Cancel.Normal";
357    /// The name of the pressed state brush of the `cancel` context action.
358    pub const BRUSH_CANCEL_PRESSED: &'static str = "Global.Brush.Cancel.Pressed";
359    /// The name of the hover state brush of the `cancel` context action.
360    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    /// Creates a new dark style.
376    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    /// Creates a new light style.
441    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    /// The same as [`Self::set`], but takes self as value and essentially allows chained calls in
487    /// builder-like style:
488    ///
489    /// ```rust
490    /// # use fyrox_core::color::Color;
491    /// # use fyrox_ui::brush::Brush;
492    /// # use fyrox_ui::style::Style;
493    /// Style::default()
494    ///     .with("SomeProperty", 0.2f32)
495    ///     .with("SomeOtherProperty", Brush::Solid(Color::WHITE));
496    /// ```
497    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    /// Sets the parent style for this style. Parent style will be used in an attempt to fetch properties
507    /// that aren't present in this style.
508    pub fn set_parent(&mut self, parent: Option<StyleResource>) {
509        self.parent = parent;
510    }
511
512    /// Returns parent style of this style.
513    pub fn parent(&self) -> Option<&StyleResource> {
514        self.parent.as_ref()
515    }
516
517    /// Returns an index of the variable with the given name.
518    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    /// Checks if there's a variable with the given name.
525    pub fn contains(&self, name: &ImmutableString) -> bool {
526        self.index_of(name).is_some()
527    }
528
529    /// Merges current style with some other style. This method does not overwrite existing values,
530    /// instead it only adds missing values from the other style.
531    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    /// Registers a new property with the given name and value:
541    ///
542    /// ```rust
543    /// # use fyrox_core::color::Color;
544    /// # use fyrox_ui::brush::Brush;
545    /// # use fyrox_ui::style::Style;
546    /// let mut style = Style::default();
547    /// style
548    ///     .set("SomeProperty", 0.2f32)
549    ///     .set("SomeOtherProperty", Brush::Solid(Color::WHITE));
550    /// ```
551    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    /// Tries to fetch a property with the given name. If the property is not found, this method will
573    /// try to search in the parent style (the search is recursive).
574    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    /// Tries to fetch a property with the given name and perform type casting to the requested type.
589    /// If the property is not found, this method will try to search in the parent style (the search
590    /// is recursive).
591    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    /// Tries to fetch a property with the given name. If the property is not found, this method will
600    /// try to search in the parent style (the search is recursive). If there's no such property at
601    /// all, this method will return its default value (define by [`Default`] trait).
602    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    /// Tries to fetch a property with the given name or, if not found, returns the given default value.
613    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    /// Tries to find a property with the given name or takes the default value of the property's type
621    /// and wraps it into [`StyledProperty`], essentially binding the value to the style property.
622    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    /// Tries to load a style from the given path.
632    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    /// Returns an immutable reference to the internal container with the style properties.
646    /// Keep in mind that the returned container contains only the properties of the current
647    /// style! Properties of the parent style(s) should be obtained separately.
648    pub fn inner(&self) -> &Vec<StylePropertyContainer> {
649        &self.properties
650    }
651
652    /// Collects all the properties in the current and ancestor style chain. Returns a hash map with
653    /// all property values with their names. Basically, this method merges all the styles with their
654    /// ancestor style chain.
655    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}