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 visual appearance of widgets in centralized manner. It can be considered
24//! as a storage for properties, that defines 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 fxhash::FxHashMap;
43use fyrox_resource::{
44    io::ResourceIo,
45    manager::{BuiltInResource, ResourceManager},
46};
47use fyrox_texture::TextureResource;
48use lazy_static::lazy_static;
49use std::{
50    ops::{Deref, DerefMut},
51    path::Path,
52    sync::Arc,
53};
54
55/// A set of potential values for styled properties.
56#[derive(Visit, Reflect, Debug, Clone)]
57pub enum StyleProperty {
58    /// A numeric property.
59    Number(f32),
60    /// A thickness property, that defines width of four sides of a rectangles.
61    Thickness(Thickness),
62    /// A color property.
63    Color(Color),
64    /// A brush property, that defines how to render an arbitrary surface (solid, with gradient, etc.).
65    Brush(Brush),
66    /// A texture property. Could be used together with [`crate::image::Image`] widget or [`crate::nine_patch::NinePatch`]
67    /// widget.
68    Texture(TextureResource),
69}
70
71impl Default for StyleProperty {
72    fn default() -> Self {
73        Self::Number(0.0)
74    }
75}
76
77/// A trait that provides a method that translates [`StyleProperty`] into a specific primitive value.
78pub trait IntoPrimitive<T> {
79    /// Tries to convert self into a primitive value `T`.
80    fn into_primitive(self) -> Option<T>;
81}
82
83macro_rules! impl_casts {
84    ($ty:ty => $var:ident) => {
85        impl From<$ty> for StyleProperty {
86            fn from(value: $ty) -> Self {
87                Self::$var(value)
88            }
89        }
90
91        impl IntoPrimitive<$ty> for StyleProperty {
92            fn into_primitive(self) -> Option<$ty> {
93                if let StyleProperty::$var(value) = self {
94                    Some(value)
95                } else {
96                    None
97                }
98            }
99        }
100    };
101}
102
103impl_casts!(f32 => Number);
104impl_casts!(Thickness => Thickness);
105impl_casts!(Color => Color);
106impl_casts!(Brush => Brush);
107impl_casts!(TextureResource => Texture);
108
109lazy_static! {
110    /// Default style of the library.
111    pub static ref DEFAULT_STYLE: BuiltInResource<Style> = BuiltInResource::new_no_source(
112        StyleResource::new_ok("__DEFAULT_STYLE__".into(), Style::dark_style())
113    );
114}
115
116/// A property, that can bind its value to a style. Why can't we just fetch the actual value from
117/// the style and why do we need to store the value as well? The answer is flexibility. In this
118/// approach style becomes not necessary and the value can be hardcoded. Also, the values of such
119/// properties can be updated individually.
120#[derive(Clone, Debug, Default, Reflect)]
121pub struct StyledProperty<T> {
122    /// Property value.
123    pub property: T,
124    /// Name of the property in a style table.
125    #[reflect(hidden)]
126    pub name: ImmutableString,
127}
128
129impl<T> From<T> for StyledProperty<T> {
130    fn from(property: T) -> Self {
131        Self {
132            property,
133            name: Default::default(),
134        }
135    }
136}
137
138impl<T: PartialEq> PartialEq for StyledProperty<T> {
139    fn eq(&self, other: &Self) -> bool {
140        self.property == other.property
141    }
142}
143
144impl<T> StyledProperty<T> {
145    /// Creates a new styled property with the given value and the name.
146    pub fn new(property: T, name: impl Into<ImmutableString>) -> Self {
147        Self {
148            property,
149            name: name.into(),
150        }
151    }
152
153    /// Tries to sync the property value with its respective value in the given style. This method
154    /// will fail if the given style does not contain the property.
155    pub fn update(&mut self, style: &StyleResource)
156    where
157        StyleProperty: IntoPrimitive<T>,
158    {
159        if let Some(property) = style.get(self.name.clone()) {
160            self.property = property;
161        }
162    }
163}
164
165impl<T> Deref for StyledProperty<T> {
166    type Target = T;
167
168    fn deref(&self) -> &Self::Target {
169        &self.property
170    }
171}
172
173impl<T> DerefMut for StyledProperty<T> {
174    fn deref_mut(&mut self) -> &mut Self::Target {
175        &mut self.property
176    }
177}
178
179impl<T: Visit> Visit for StyledProperty<T> {
180    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
181        self.property.visit(name, visitor)
182    }
183}
184
185/// Style is a simple container for a named properties. Styles can be based off some other style, thus
186/// allowing cascaded styling. Such cascading allows to define some base style with common properties
187/// and then create any amount of derived styles. For example, you can define a style for Button widget
188/// with corner radius, font size, border thickness and then create two derived styles for light and
189/// dark themes that will define colors and brushes. Light or dark theme does not affect all of those
190/// base properties, but has different colors.
191///
192/// Styles can contain only specific types of properties (see [`StyleProperty`] enumeration), any
193/// more complex properties can be built using these primitives.
194///
195/// There are three major ways of widgets styling:
196///
197/// 1) During widget building stage - this way involves [`crate::BuildContext`]'s style field. This
198/// field defines a style for all widgets that will be built with the context.
199/// 2) Message-based style changes - this way is based on [`crate::widget::WidgetMessage::Style`] message
200/// that can be sent to a particular widget (or hierarchy) to force them to update styled properties.
201/// 3) Global style changes - this way is based on [`crate::UserInterface::set_style`] method, that
202/// sends the specified style to all widgets, forcing them to update styled properties.
203///
204/// The most used methods are 1 and 3. The following examples should clarify how to use these
205/// approaches.
206///
207/// ## Examples
208///
209/// The following example shows how to use a style during widget building stage:
210///
211/// ```rust
212/// # use fyrox_ui::{
213/// #     button::{Button, ButtonBuilder},
214/// #     style::{resource::StyleResource, Style},
215/// #     widget::WidgetBuilder,
216/// #     Thickness, UserInterface,
217/// # };
218/// # use fyrox_resource::untyped::ResourceKind;
219/// #
220/// fn build_with_style(ui: &mut UserInterface) {
221///     // The context will use UI style by default. You can override it using `ui.set_style(..)`.
222///     let ctx = &mut ui.build_ctx();
223///
224///     // Create a style resource first and assign it to the build context. All widgets built with
225///     // the context will use this style.
226///     let style = Style::light_style()
227///         .with(Button::CORNER_RADIUS, 6.0f32)
228///         .with(Button::BORDER_THICKNESS, Thickness::uniform(3.0));
229///
230///     ctx.style = StyleResource::new_ok(ResourceKind::Embedded, style);
231///
232///     // The button will have corner radius of 6.0 points and border thickness of 3.0 points on
233///     // each side.
234///     ButtonBuilder::new(WidgetBuilder::new()).build(ctx);
235/// }
236/// ```
237///
238/// To change UI style globally after it was built, use something like this:
239///
240/// ```rust
241/// use fyrox_ui::{
242///     button::Button,
243///     style::{resource::StyleResource, Style},
244///     Thickness, UserInterface,
245/// };
246/// use fyrox_resource::untyped::ResourceKind;
247///
248/// fn apply_style(ui: &mut UserInterface) {
249///     let style = Style::light_style()
250///         .with(Button::CORNER_RADIUS, 3.0f32)
251///         .with(Button::BORDER_THICKNESS, Thickness::uniform(1.0));
252///
253///     ui.set_style(StyleResource::new_ok(ResourceKind::Embedded, style));
254/// }
255/// ```
256#[derive(Visit, Reflect, Default, Debug, TypeUuidProvider)]
257#[type_uuid(id = "38a63b49-d765-4c01-8fb5-202cc43d607e")]
258pub struct Style {
259    parent: Option<StyleResource>,
260    variables: FxHashMap<ImmutableString, StyleProperty>,
261}
262
263impl Style {
264    /// The name of the darkest brush.
265    pub const BRUSH_DARKEST: &'static str = "Global.Brush.Darkest";
266    /// The name of the darker brush.
267    pub const BRUSH_DARKER: &'static str = "Global.Brush.Darker";
268    /// The name of the dark brush.
269    pub const BRUSH_DARK: &'static str = "Global.Brush.Dark";
270    /// The name of the primary brush that is used for the major amount of surface.
271    pub const BRUSH_PRIMARY: &'static str = "Global.Brush.Primary";
272    /// The name of the slightly lighter primary brush.
273    pub const BRUSH_LIGHTER_PRIMARY: &'static str = "Global.Brush.LighterPrimary";
274    /// The name of the light brush.
275    pub const BRUSH_LIGHT: &'static str = "Global.Brush.Light";
276    /// The name of the lighter brush.
277    pub const BRUSH_LIGHTER: &'static str = "Global.Brush.Lighter";
278    /// The name of the lightest brush.
279    pub const BRUSH_LIGHTEST: &'static str = "Global.Brush.Lightest";
280    /// The name of the bright brush.
281    pub const BRUSH_BRIGHT: &'static str = "Global.Brush.Bright";
282    /// The name of the brightest brush.
283    pub const BRUSH_BRIGHTEST: &'static str = "Global.Brush.Brightest";
284    /// The name of the bright blue brush.
285    pub const BRUSH_BRIGHT_BLUE: &'static str = "Global.Brush.BrightBlue";
286    /// The name of the dim blue brush.
287    pub const BRUSH_DIM_BLUE: &'static str = "Global.Brush.DimBlue";
288    /// The name of the text brush.
289    pub const BRUSH_TEXT: &'static str = "Global.Brush.Text";
290    /// The name of the foreground brush.
291    pub const BRUSH_FOREGROUND: &'static str = "Global.Brush.Foreground";
292    /// The name of the information brush.
293    pub const BRUSH_INFORMATION: &'static str = "Global.Brush.Information";
294    /// The name of the warning brush.
295    pub const BRUSH_WARNING: &'static str = "Global.Brush.Warning";
296    /// The name of the error brush.
297    pub const BRUSH_ERROR: &'static str = "Global.Brush.Error";
298    /// The name of the ok brush.
299    pub const BRUSH_OK: &'static str = "Global.Brush.Ok";
300    /// The name of the font size property.
301    pub const FONT_SIZE: &'static str = "Global.Font.Size";
302
303    fn base_style() -> Style {
304        let mut style = Self::default();
305
306        style
307            .set(Self::FONT_SIZE, 14.0f32)
308            .merge(&Button::style())
309            .merge(&CheckBox::style())
310            .merge(&DropdownList::style())
311            .merge(&ToggleButton::style());
312
313        style
314    }
315
316    /// Creates a new dark style.
317    pub fn dark_style() -> Style {
318        let mut style = Self::base_style();
319        style
320            .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(20)))
321            .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(30)))
322            .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(40)))
323            .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(50)))
324            .set(
325                Self::BRUSH_LIGHTER_PRIMARY,
326                Brush::Solid(Color::repeat_opaque(60)),
327            )
328            .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(70)))
329            .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(85)))
330            .set(
331                Self::BRUSH_LIGHTEST,
332                Brush::Solid(Color::repeat_opaque(100)),
333            )
334            .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(130)))
335            .set(
336                Self::BRUSH_BRIGHTEST,
337                Brush::Solid(Color::repeat_opaque(160)),
338            )
339            .set(
340                Self::BRUSH_BRIGHT_BLUE,
341                Brush::Solid(Color::opaque(80, 118, 178)),
342            )
343            .set(
344                Self::BRUSH_DIM_BLUE,
345                Brush::Solid(Color::opaque(66, 99, 149)),
346            )
347            .set(Self::BRUSH_TEXT, Brush::Solid(Color::opaque(220, 220, 220)))
348            .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
349            .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ANTIQUE_WHITE))
350            .set(Self::BRUSH_WARNING, Brush::Solid(Color::GOLD))
351            .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED))
352            .set(Self::BRUSH_OK, Brush::Solid(Color::GREEN));
353        style
354    }
355
356    /// Creates a new light style.
357    pub fn light_style() -> Style {
358        let mut style = Self::base_style();
359        style
360            .set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(140)))
361            .set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(150)))
362            .set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(160)))
363            .set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(170)))
364            .set(
365                Self::BRUSH_LIGHTER_PRIMARY,
366                Brush::Solid(Color::repeat_opaque(180)),
367            )
368            .set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(190)))
369            .set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(205)))
370            .set(
371                Self::BRUSH_LIGHTEST,
372                Brush::Solid(Color::repeat_opaque(220)),
373            )
374            .set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(40)))
375            .set(
376                Self::BRUSH_BRIGHTEST,
377                Brush::Solid(Color::repeat_opaque(30)),
378            )
379            .set(
380                Self::BRUSH_BRIGHT_BLUE,
381                Brush::Solid(Color::opaque(80, 118, 178)),
382            )
383            .set(
384                Self::BRUSH_DIM_BLUE,
385                Brush::Solid(Color::opaque(66, 99, 149)),
386            )
387            .set(Self::BRUSH_TEXT, Brush::Solid(Color::repeat_opaque(0)))
388            .set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
389            .set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ROYAL_BLUE))
390            .set(
391                Self::BRUSH_WARNING,
392                Brush::Solid(Color::opaque(255, 242, 0)),
393            )
394            .set(Self::BRUSH_ERROR, Brush::Solid(Color::RED));
395        style
396    }
397
398    /// The same as [`Self::set`], but takes self as value and essentially allows chained calls in
399    /// builder-like style:
400    ///
401    /// ```rust
402    /// # use fyrox_core::color::Color;
403    /// # use fyrox_ui::brush::Brush;
404    /// # use fyrox_ui::style::Style;
405    /// Style::default()
406    ///     .with("SomeProperty", 0.2f32)
407    ///     .with("SomeOtherProperty", Brush::Solid(Color::WHITE));
408    /// ```
409    pub fn with(
410        mut self,
411        name: impl Into<ImmutableString>,
412        property: impl Into<StyleProperty>,
413    ) -> Self {
414        self.set(name, property);
415        self
416    }
417
418    /// Sets the parent style for this style. Parent style will be used at attempt to fetch properties
419    /// that aren't present in this style.
420    pub fn set_parent(&mut self, parent: Option<StyleResource>) {
421        self.parent = parent;
422    }
423
424    /// Returns parent style of this style.
425    pub fn parent(&self) -> Option<&StyleResource> {
426        self.parent.as_ref()
427    }
428
429    /// Merges current style with some other style. This method does not overwrite existing values,
430    /// instead it only adds missing values from the other style.
431    pub fn merge(&mut self, other: &Self) -> &mut Self {
432        for (k, v) in other.variables.iter() {
433            if !self.variables.contains_key(k) {
434                self.variables.insert(k.clone(), v.clone());
435            }
436        }
437        self
438    }
439
440    /// Registers a new property with the given name and value:
441    ///
442    /// ```rust
443    /// # use fyrox_core::color::Color;
444    /// # use fyrox_ui::brush::Brush;
445    /// # use fyrox_ui::style::Style;
446    /// let mut style = Style::default();
447    /// style
448    ///     .set("SomeProperty", 0.2f32)
449    ///     .set("SomeOtherProperty", Brush::Solid(Color::WHITE));
450    /// ```
451    pub fn set(
452        &mut self,
453        name: impl Into<ImmutableString>,
454        property: impl Into<StyleProperty>,
455    ) -> &mut Self {
456        self.variables.insert(name.into(), property.into());
457        self
458    }
459
460    /// Tries to fetch a property with the given name. If the property is not found, this method will
461    /// try to search in the parent style (the search is recursive).
462    pub fn get_raw(&self, name: impl Into<ImmutableString>) -> Option<StyleProperty> {
463        let name = name.into();
464        if let Some(property) = self.variables.get(&name) {
465            return Some(property.clone());
466        } else if let Some(parent) = self.parent.as_ref() {
467            let state = parent.state();
468            if let Some(data) = state.data_ref() {
469                return data.get_raw(name);
470            }
471        }
472        None
473    }
474
475    /// Tries to fetch a property with the given name and perform type casting to the requested type.
476    /// If the property is not found, this method will try to search in the parent style (the search
477    /// is recursive).
478    pub fn get<P>(&self, name: impl Into<ImmutableString>) -> Option<P>
479    where
480        StyleProperty: IntoPrimitive<P>,
481    {
482        self.get_raw(name)
483            .and_then(|property| property.into_primitive())
484    }
485
486    /// Tries to fetch a property with the given name. If the property is not found, this method will
487    /// try to search in the parent style (the search is recursive). If there's no such property at
488    /// all, this method will return its default value (define by [`Default`] trait).
489    pub fn get_or_default<P>(&self, name: impl Into<ImmutableString>) -> P
490    where
491        P: Default,
492        StyleProperty: IntoPrimitive<P>,
493    {
494        self.get_raw(name)
495            .and_then(|property| property.into_primitive())
496            .unwrap_or_default()
497    }
498
499    /// Tries to fetch a property with the given name or, if not found, returns the given default value.
500    pub fn get_or<P>(&self, name: impl Into<ImmutableString>, default: P) -> P
501    where
502        StyleProperty: IntoPrimitive<P>,
503    {
504        self.get(name).unwrap_or(default)
505    }
506
507    /// Tries to find a property with the given name or takes the default value of the property's type
508    /// and wraps it into [`StyledProperty`], essentially binding the value to the style property.
509    pub fn property<P>(&self, name: impl Into<ImmutableString>) -> StyledProperty<P>
510    where
511        P: Default,
512        StyleProperty: IntoPrimitive<P>,
513    {
514        let name = name.into();
515        StyledProperty::new(self.get_or_default(name.clone()), name)
516    }
517
518    /// Tries to load a style from the given path.
519    pub async fn from_file(
520        path: &Path,
521        io: &dyn ResourceIo,
522        resource_manager: ResourceManager,
523    ) -> Result<Self, StyleResourceError> {
524        let bytes = io.load_file(path).await?;
525        let mut visitor = Visitor::load_from_memory(&bytes)?;
526        visitor.blackboard.register(Arc::new(resource_manager));
527        let mut style = Style::default();
528        style.visit("Style", &mut visitor)?;
529        Ok(style)
530    }
531}