#![warn(missing_docs)]
pub mod resource;
use crate::{
brush::Brush,
button::Button,
check_box::CheckBox,
core::{
color::Color, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
ImmutableString, Uuid,
},
dropdown_list::DropdownList,
style::resource::{StyleResource, StyleResourceError, StyleResourceExt},
toggle::ToggleButton,
Thickness,
};
use fyrox_resource::untyped::ResourceKind;
use fyrox_resource::{
io::ResourceIo,
manager::{BuiltInResource, ResourceManager},
};
use fyrox_texture::TextureResource;
use std::{
any::{Any, TypeId},
sync::LazyLock,
};
use std::{
ops::{Deref, DerefMut},
path::Path,
sync::Arc,
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
#[derive(Visit, Reflect, Debug, Clone, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
#[type_uuid(id = "85b8c1e4-03a2-4a28-acb4-1850d1a29227")]
pub enum StyleProperty {
Number(f32),
Thickness(Thickness),
Color(Color),
Brush(Brush),
Texture(TextureResource),
}
impl Default for StyleProperty {
fn default() -> Self {
Self::Number(0.0)
}
}
impl StyleProperty {
pub fn value_type_id(&self) -> TypeId {
match self {
StyleProperty::Number(v) => v.type_id(),
StyleProperty::Thickness(v) => v.type_id(),
StyleProperty::Color(v) => v.type_id(),
StyleProperty::Brush(v) => v.type_id(),
StyleProperty::Texture(v) => v.type_id(),
}
}
}
pub trait IntoPrimitive<T> {
fn into_primitive(self) -> Option<T>;
}
macro_rules! impl_casts {
($ty:ty => $var:ident) => {
impl From<$ty> for StyleProperty {
fn from(value: $ty) -> Self {
Self::$var(value)
}
}
impl IntoPrimitive<$ty> for StyleProperty {
fn into_primitive(self) -> Option<$ty> {
if let StyleProperty::$var(value) = self {
Some(value)
} else {
None
}
}
}
};
}
impl_casts!(f32 => Number);
impl_casts!(Thickness => Thickness);
impl_casts!(Color => Color);
impl_casts!(Brush => Brush);
impl_casts!(TextureResource => Texture);
pub static DEFAULT_STYLE: LazyLock<BuiltInResource<Style>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Default Style",
StyleResource::new_ok(
uuid!("1e0716e8-e728-491c-a65b-ca11b15048be"),
ResourceKind::External,
Style::dark_style(),
),
)
});
pub static LIGHT_STYLE: LazyLock<BuiltInResource<Style>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Light Style",
StyleResource::new_ok(
uuid!("05141b18-2a27-4fe3-ae6e-7af11c2e7471"),
ResourceKind::External,
Style::light_style(),
),
)
});
#[derive(Clone, Debug, Reflect, Default)]
#[reflect(bounds = "T: Reflect + Clone")]
pub struct StyledProperty<T> {
pub property: T,
#[reflect(read_only, display_name = "Property Name")]
pub name: ImmutableString,
}
impl<T> From<T> for StyledProperty<T> {
fn from(property: T) -> Self {
Self {
property,
name: Default::default(),
}
}
}
impl<T: PartialEq> PartialEq for StyledProperty<T> {
fn eq(&self, other: &Self) -> bool {
self.property == other.property
}
}
impl<T> StyledProperty<T> {
pub fn new(property: T, name: impl Into<ImmutableString>) -> Self {
Self {
property,
name: name.into(),
}
}
pub fn update(&mut self, style: &StyleResource)
where
StyleProperty: IntoPrimitive<T>,
{
if let Some(property) = style.get(self.name.clone()) {
self.property = property;
}
}
}
impl<T> Deref for StyledProperty<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.property
}
}
impl<T> DerefMut for StyledProperty<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.property
}
}
impl<T: Visit> Visit for StyledProperty<T> {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
self.property.visit(name, visitor)
}
}
#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
#[type_uuid(id = "6238f37c-c067-4dd1-be67-6a8bb8853a59")]
pub struct StylePropertyContainer {
pub name: ImmutableString,
pub value: StyleProperty,
}
#[derive(Visit, Reflect, Clone, Default, Debug, TypeUuidProvider)]
#[type_uuid(id = "38a63b49-d765-4c01-8fb5-202cc43d607e")]
pub struct Style {
parent: Option<StyleResource>,
properties: Vec<StylePropertyContainer>,
}
impl Style {
pub const BRUSH_DARKEST: &'static str = "Global.Brush.Darkest";
pub const BRUSH_DARKER: &'static str = "Global.Brush.Darker";
pub const BRUSH_DARK: &'static str = "Global.Brush.Dark";
pub const BRUSH_PRIMARY: &'static str = "Global.Brush.Primary";
pub const BRUSH_LIGHTER_PRIMARY: &'static str = "Global.Brush.LighterPrimary";
pub const BRUSH_LIGHT: &'static str = "Global.Brush.Light";
pub const BRUSH_LIGHTER: &'static str = "Global.Brush.Lighter";
pub const BRUSH_LIGHTEST: &'static str = "Global.Brush.Lightest";
pub const BRUSH_BRIGHT: &'static str = "Global.Brush.Bright";
pub const BRUSH_BRIGHTEST: &'static str = "Global.Brush.Brightest";
pub const BRUSH_BRIGHT_BLUE: &'static str = "Global.Brush.BrightBlue";
pub const BRUSH_DIM_BLUE: &'static str = "Global.Brush.DimBlue";
pub const BRUSH_TEXT: &'static str = "Global.Brush.Text";
pub const BRUSH_FOREGROUND: &'static str = "Global.Brush.Foreground";
pub const BRUSH_INFORMATION: &'static str = "Global.Brush.Information";
pub const BRUSH_WARNING: &'static str = "Global.Brush.Warning";
pub const BRUSH_ERROR: &'static str = "Global.Brush.Error";
pub const BRUSH_OK: &'static str = "Global.Brush.Ok";
pub const BRUSH_HIGHLIGHT: &'static str = "Global.Brush.Highlight";
pub const FONT_SIZE: &'static str = "Global.Font.Size";
pub const BRUSH_OK_NORMAL: &'static str = "Global.Brush.Ok.Normal";
pub const BRUSH_OK_PRESSED: &'static str = "Global.Brush.Ok.Pressed";
pub const BRUSH_OK_HOVER: &'static str = "Global.Brush.Ok.Hover";
pub const BRUSH_CANCEL_NORMAL: &'static str = "Global.Brush.Cancel.Normal";
pub const BRUSH_CANCEL_PRESSED: &'static str = "Global.Brush.Cancel.Pressed";
pub const BRUSH_CANCEL_HOVER: &'static str = "Global.Brush.Cancel.Hover";
fn base_style() -> Style {
let mut style = Self::default();
style
.set(Self::FONT_SIZE, 14.0f32)
.merge(&Button::style())
.merge(&CheckBox::style())
.merge(&DropdownList::style())
.merge(&ToggleButton::style());
style
}
pub fn dark_style() -> Style {
let mut style = Self::base_style();
style
.set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(20)))
.set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(30)))
.set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(40)))
.set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(50)))
.set(
Self::BRUSH_LIGHTER_PRIMARY,
Brush::Solid(Color::repeat_opaque(60)),
)
.set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(70)))
.set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(85)))
.set(
Self::BRUSH_LIGHTEST,
Brush::Solid(Color::repeat_opaque(100)),
)
.set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(130)))
.set(
Self::BRUSH_BRIGHTEST,
Brush::Solid(Color::repeat_opaque(160)),
)
.set(
Self::BRUSH_BRIGHT_BLUE,
Brush::Solid(Color::opaque(80, 118, 178)),
)
.set(
Self::BRUSH_HIGHLIGHT,
Brush::Solid(Color::opaque(80, 118, 178)),
)
.set(
Self::BRUSH_DIM_BLUE,
Brush::Solid(Color::opaque(66, 99, 149)),
)
.set(Self::BRUSH_TEXT, Brush::Solid(Color::opaque(190, 190, 190)))
.set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
.set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ANTIQUE_WHITE))
.set(Self::BRUSH_WARNING, Brush::Solid(Color::GOLD))
.set(Self::BRUSH_ERROR, Brush::Solid(Color::RED))
.set(Self::BRUSH_OK, Brush::Solid(Color::GREEN))
.set(
Self::BRUSH_OK_NORMAL,
Brush::Solid(Color::opaque(0, 130, 0)),
)
.set(Self::BRUSH_OK_HOVER, Brush::Solid(Color::opaque(0, 150, 0)))
.set(
Self::BRUSH_OK_PRESSED,
Brush::Solid(Color::opaque(0, 170, 0)),
)
.set(
Self::BRUSH_CANCEL_NORMAL,
Brush::Solid(Color::opaque(130, 0, 0)),
)
.set(
Self::BRUSH_CANCEL_HOVER,
Brush::Solid(Color::opaque(150, 0, 0)),
)
.set(
Self::BRUSH_CANCEL_PRESSED,
Brush::Solid(Color::opaque(170, 0, 0)),
);
style
}
pub fn light_style() -> Style {
let mut style = Self::base_style();
style
.set(Self::BRUSH_DARKEST, Brush::Solid(Color::repeat_opaque(140)))
.set(Self::BRUSH_DARKER, Brush::Solid(Color::repeat_opaque(150)))
.set(Self::BRUSH_DARK, Brush::Solid(Color::repeat_opaque(160)))
.set(Self::BRUSH_PRIMARY, Brush::Solid(Color::repeat_opaque(170)))
.set(
Self::BRUSH_LIGHTER_PRIMARY,
Brush::Solid(Color::repeat_opaque(180)),
)
.set(Self::BRUSH_LIGHT, Brush::Solid(Color::repeat_opaque(190)))
.set(Self::BRUSH_LIGHTER, Brush::Solid(Color::repeat_opaque(205)))
.set(
Self::BRUSH_LIGHTEST,
Brush::Solid(Color::repeat_opaque(220)),
)
.set(Self::BRUSH_BRIGHT, Brush::Solid(Color::repeat_opaque(40)))
.set(
Self::BRUSH_BRIGHTEST,
Brush::Solid(Color::repeat_opaque(30)),
)
.set(
Self::BRUSH_BRIGHT_BLUE,
Brush::Solid(Color::opaque(80, 118, 178)),
)
.set(
Self::BRUSH_HIGHLIGHT,
Brush::Solid(Color::opaque(80, 118, 178)),
)
.set(
Self::BRUSH_DIM_BLUE,
Brush::Solid(Color::opaque(66, 99, 149)),
)
.set(Self::BRUSH_TEXT, Brush::Solid(Color::repeat_opaque(0)))
.set(Self::BRUSH_FOREGROUND, Brush::Solid(Color::WHITE))
.set(Self::BRUSH_INFORMATION, Brush::Solid(Color::ROYAL_BLUE))
.set(
Self::BRUSH_WARNING,
Brush::Solid(Color::opaque(255, 242, 0)),
)
.set(Self::BRUSH_ERROR, Brush::Solid(Color::RED));
style
}
pub fn with(
mut self,
name: impl Into<ImmutableString>,
property: impl Into<StyleProperty>,
) -> Self {
self.set(name, property);
self
}
pub fn set_parent(&mut self, parent: Option<StyleResource>) {
self.parent = parent;
}
pub fn parent(&self) -> Option<&StyleResource> {
self.parent.as_ref()
}
pub fn index_of(&self, name: &ImmutableString) -> Option<usize> {
self.properties
.binary_search_by(|v| v.name.cached_hash().cmp(&name.cached_hash()))
.ok()
}
pub fn contains(&self, name: &ImmutableString) -> bool {
self.index_of(name).is_some()
}
pub fn merge(&mut self, other: &Self) -> &mut Self {
for other_property in other.properties.iter() {
if !self.contains(&other_property.name) {
self.set(other_property.name.clone(), other_property.value.clone());
}
}
self
}
pub fn set(
&mut self,
name: impl Into<ImmutableString>,
value: impl Into<StyleProperty>,
) -> &mut Self {
let name = name.into();
let value = value.into();
if let Some(existing_index) = self.index_of(&name) {
self.properties[existing_index] = StylePropertyContainer { name, value };
} else {
let index = self
.properties
.partition_point(|h| h.name.cached_hash() < name.cached_hash());
self.properties
.insert(index, StylePropertyContainer { name, value });
}
self
}
pub fn get_raw(&self, name: impl Into<ImmutableString>) -> Option<StyleProperty> {
let name = name.into();
let index = self.index_of(&name)?;
if let Some(container) = self.properties.get(index) {
return Some(container.value.clone());
} else if let Some(parent) = self.parent.as_ref() {
let state = parent.state();
if let Some(data) = state.data_ref() {
return data.get_raw(name);
}
}
None
}
pub fn get<P>(&self, name: impl Into<ImmutableString>) -> Option<P>
where
StyleProperty: IntoPrimitive<P>,
{
self.get_raw(name)
.and_then(|property| property.into_primitive())
}
pub fn get_or_default<P>(&self, name: impl Into<ImmutableString>) -> P
where
P: Default,
StyleProperty: IntoPrimitive<P>,
{
self.get_raw(name)
.and_then(|property| property.into_primitive())
.unwrap_or_default()
}
pub fn get_or<P>(&self, name: impl Into<ImmutableString>, default: P) -> P
where
StyleProperty: IntoPrimitive<P>,
{
self.get(name).unwrap_or(default)
}
pub fn property<P>(&self, name: impl Into<ImmutableString>) -> StyledProperty<P>
where
P: Default,
StyleProperty: IntoPrimitive<P>,
{
let name = name.into();
StyledProperty::new(self.get_or_default(name.clone()), name)
}
pub async fn from_file(
path: &Path,
io: &dyn ResourceIo,
resource_manager: ResourceManager,
) -> Result<Self, StyleResourceError> {
let bytes = io.load_file(path).await?;
let mut visitor = Visitor::load_from_memory(&bytes)?;
visitor.blackboard.register(Arc::new(resource_manager));
let mut style = Style::default();
style.visit("Style", &mut visitor)?;
Ok(style)
}
pub fn inner(&self) -> &Vec<StylePropertyContainer> {
&self.properties
}
pub fn all_properties(&self) -> Self {
let mut properties = self
.parent
.as_ref()
.map(|parent| parent.data_ref().all_properties())
.unwrap_or_default();
for property in self.properties.iter() {
properties.set(property.name.clone(), property.value.clone());
}
properties
}
}
#[cfg(test)]
mod test {
use crate::brush::Brush;
use crate::style::Style;
use fyrox_core::color::Color;
use fyrox_core::ImmutableString;
#[test]
fn test_style() {
let mut style = Style::default();
style
.set("A", 0.2f32)
.set("D", 0.1f32)
.set("B", Brush::Solid(Color::WHITE))
.set("C", Brush::Solid(Color::WHITE));
assert_eq!(style.index_of(&ImmutableString::new("A")), Some(3));
assert_eq!(style.index_of(&ImmutableString::new("B")), Some(2));
assert_eq!(style.index_of(&ImmutableString::new("C")), Some(1));
assert_eq!(style.index_of(&ImmutableString::new("D")), Some(0));
}
}