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