sickle_ui_scaffold/
ui_style.rs

1pub mod attribute;
2pub mod builder;
3pub mod generated;
4pub mod manual;
5
6use bevy::{ecs::system::EntityCommands, prelude::*, utils::HashSet};
7
8use sickle_math::lerp::Lerp;
9
10use attribute::AnimatedVals;
11use generated::LockableStyleAttribute;
12
13pub mod prelude {
14    pub use super::{
15        attribute::{AnimatedVals, InteractiveVals},
16        builder::StyleBuilder,
17        generated::*,
18        manual::*,
19        *,
20    };
21}
22
23pub struct UiStyle<'a> {
24    commands: EntityCommands<'a>,
25}
26
27impl UiStyle<'_> {
28    /// Returns the Entity that is the target of all styling commands
29    pub fn id(&self) -> Entity {
30        self.commands.id()
31    }
32
33    /// Returns the underlying EntityCommands via reborrow
34    pub fn entity_commands(&mut self) -> EntityCommands {
35        self.commands.reborrow()
36    }
37}
38
39pub trait UiStyleExt {
40    /// Styling commands for UI Nodes
41    ///
42    /// `sickle_ui` exposes functions for all standard bevy styleable attributes.
43    /// Manual extension can be done for custom styling needs via extension traits:
44    ///
45    /// ```rust
46    /// pub trait SetMyPropExt {
47    ///     fn my_prop(&mut self, value: f32) -> &mut Self;
48    /// }
49    ///
50    /// impl SetMyPropExt for UiStyle<'_> {
51    ///     fn my_prop(&mut self, value: f32) -> &mut Self {
52    ///         // SetMyProp is assumed to be an EntityCommand
53    ///         // Alternatively a closure can be supplied as per a standard bevy command
54    ///         // NOTE: All built-in commands structs are public and can be re-used in extensions
55    ///         self.entity_commands().add(SetMyProp {
56    ///             value
57    ///         });
58    ///         self
59    ///     }
60    /// }
61    /// ```
62    fn style(&mut self, entity: Entity) -> UiStyle;
63}
64
65impl UiStyleExt for Commands<'_, '_> {
66    fn style(&mut self, entity: Entity) -> UiStyle {
67        UiStyle {
68            commands: self.entity(entity),
69        }
70    }
71}
72
73pub struct UiStyleUnchecked<'a> {
74    commands: EntityCommands<'a>,
75}
76
77impl UiStyleUnchecked<'_> {
78    /// Returns the Entity that is the target of all styling commands
79    pub fn id(&self) -> Entity {
80        self.commands.id()
81    }
82
83    /// Returns the underlying EntityCommands via reborrow
84    pub fn entity_commands(&mut self) -> EntityCommands {
85        self.commands.reborrow()
86    }
87}
88
89pub trait UiStyleUncheckedExt {
90    /// Same as [`UiStyleExt::style`], except styling calls will bypass attribute locks
91    fn style_unchecked(&mut self, entity: Entity) -> UiStyleUnchecked;
92}
93
94impl UiStyleUncheckedExt for Commands<'_, '_> {
95    fn style_unchecked(&mut self, entity: Entity) -> UiStyleUnchecked {
96        UiStyleUnchecked {
97            commands: self.entity(entity),
98        }
99    }
100}
101
102pub trait LogicalEq<Rhs: ?Sized = Self> {
103    fn logical_eq(&self, other: &Rhs) -> bool;
104
105    fn logical_ne(&self, other: &Rhs) -> bool {
106        !self.logical_eq(other)
107    }
108}
109
110/// A set of attributes that should be protected against styling via [`UiStyleExt::style`] commands.
111///
112/// Used by widgets to protect attributes that are controlled by logic and should not be styled by end users.
113#[derive(Component, Debug, Default, Reflect)]
114pub struct LockedStyleAttributes(HashSet<LockableStyleAttribute>);
115
116impl LockedStyleAttributes {
117    /// Creates a new empty set
118    pub fn new() -> Self {
119        Self(HashSet::<LockableStyleAttribute>::new())
120    }
121
122    /// Creates a new set from the provided set of [`LockableStyleAttribute`]s
123    pub fn lock(attributes: impl Into<HashSet<LockableStyleAttribute>>) -> Self {
124        Self(attributes.into())
125    }
126
127    /// Creates a new set from the provided list of [`LockableStyleAttribute`]s
128    pub fn from_vec(attributes: Vec<LockableStyleAttribute>) -> Self {
129        let mut set = HashSet::<LockableStyleAttribute>::with_capacity(attributes.len());
130        for attribute in attributes.iter() {
131            if !set.contains(attribute) {
132                set.insert(*attribute);
133            }
134        }
135
136        Self(set)
137    }
138
139    /// Checks whether the set contains the attribute
140    pub fn contains(&self, attr: LockableStyleAttribute) -> bool {
141        self.0.contains(&attr)
142    }
143}
144
145impl From<LockableStyleAttribute> for HashSet<LockableStyleAttribute> {
146    fn from(value: LockableStyleAttribute) -> Self {
147        let mut set = HashSet::<LockableStyleAttribute>::new();
148        set.insert(value);
149        set
150    }
151}
152
153/// Dummy stylable attribute used for tracking state changes
154///
155/// This can be used in animated themes to provide discretized states to interop with logic
156#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
157pub enum TrackedStyleState {
158    #[default]
159    None,
160    Transitioning,
161    Enter,
162    Idle,
163    Hover,
164    Pressed,
165    Released,
166    Canceled,
167}
168
169impl Lerp for TrackedStyleState {
170    fn lerp(&self, to: Self, t: f32) -> Self {
171        if t == 0. {
172            *self
173        } else if t == 1. {
174            to
175        } else {
176            Self::Transitioning
177        }
178    }
179}
180
181impl TrackedStyleState {
182    pub fn default_vals() -> AnimatedVals<Self> {
183        AnimatedVals {
184            idle: Self::Idle,
185            hover: Self::Hover.into(),
186            press: Self::Pressed.into(),
187            cancel: Self::Canceled.into(),
188            enter_from: Self::Enter.into(),
189            ..default()
190        }
191    }
192}