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}