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}