ori_core/style/
attribute.rs

1use std::fmt::Display;
2
3use ori_graphics::{Color, TextAlign};
4use smallvec::SmallVec;
5use smol_str::SmolStr;
6
7use crate::{ReadSignal, StyleTransition, Unit};
8
9/// A collection of [`StyleAttribute`]s.
10#[derive(Clone, Debug, Default)]
11pub struct StyleAttributes {
12    attributes: SmallVec<[StyleAttribute; 8]>,
13}
14
15impl StyleAttributes {
16    pub const fn new() -> Self {
17        Self {
18            attributes: SmallVec::new_const(),
19        }
20    }
21
22    pub fn len(&self) -> usize {
23        self.attributes.len()
24    }
25
26    pub fn is_empty(&self) -> bool {
27        self.attributes.is_empty()
28    }
29
30    pub fn clear(&mut self) {
31        self.attributes.clear();
32    }
33
34    pub fn add(&mut self, attribute: StyleAttribute) {
35        self.attributes.push(attribute);
36    }
37
38    pub fn extend(&mut self, attributes: impl IntoIterator<Item = StyleAttribute>) {
39        self.attributes.extend(attributes);
40    }
41
42    pub fn get(&self, name: &str) -> Option<&StyleAttribute> {
43        for attribute in self.attributes.iter() {
44            if attribute.key == name {
45                return Some(&attribute);
46            }
47        }
48
49        None
50    }
51
52    pub fn get_value<T: FromStyleAttribute>(&self, name: &str) -> Option<T> {
53        for attribute in self.attributes.iter().rev() {
54            if attribute.key != name {
55                continue;
56            }
57
58            if let Some(value) = T::from_attribute(attribute.value.clone()) {
59                return Some(value);
60            } else {
61                tracing::warn!(
62                    "Invalid attribute value for attribute '{}': {:?}, expected '{}'.",
63                    name,
64                    attribute.value,
65                    std::any::type_name::<T>(),
66                );
67            }
68        }
69
70        None
71    }
72
73    pub fn get_value_transition<T: FromStyleAttribute>(
74        &self,
75        name: &str,
76    ) -> Option<(T, Option<StyleTransition>)> {
77        for attribute in self.attributes.iter().rev() {
78            if attribute.key != name {
79                continue;
80            }
81
82            if let Some(value) = T::from_attribute(attribute.value.clone()) {
83                return Some((value, attribute.transition));
84            } else {
85                tracing::warn!(
86                    "Invalid attribute value for attribute '{}': {:?}, expected '{}'.",
87                    name,
88                    attribute.value,
89                    std::any::type_name::<T>(),
90                );
91            }
92        }
93
94        None
95    }
96
97    pub fn iter(&self) -> impl Iterator<Item = &StyleAttribute> {
98        self.attributes.iter()
99    }
100}
101
102impl IntoIterator for StyleAttributes {
103    type Item = StyleAttribute;
104    type IntoIter = smallvec::IntoIter<[Self::Item; 8]>;
105
106    fn into_iter(self) -> Self::IntoIter {
107        self.attributes.into_iter()
108    }
109}
110
111impl<'a> IntoIterator for &'a StyleAttributes {
112    type Item = &'a StyleAttribute;
113    type IntoIter = std::slice::Iter<'a, StyleAttribute>;
114
115    fn into_iter(self) -> Self::IntoIter {
116        self.attributes.iter()
117    }
118}
119
120impl FromIterator<StyleAttribute> for StyleAttributes {
121    fn from_iter<T: IntoIterator<Item = StyleAttribute>>(iter: T) -> Self {
122        Self {
123            attributes: iter.into_iter().collect(),
124        }
125    }
126}
127
128pub type StyleAttributeKey = SmolStr;
129
130/// A [`Style`](super::Style) attribute.
131///
132/// An attribute is a name and a value.
133#[derive(Clone, Debug)]
134pub struct StyleAttribute {
135    /// The attribute key.
136    pub key: StyleAttributeKey,
137    /// The attribute value.
138    pub value: StyleAttributeValue,
139    /// The transition to use when animating the attribute.
140    pub transition: Option<StyleTransition>,
141}
142
143impl StyleAttribute {
144    pub fn new(key: impl Into<SmolStr>, value: impl Into<StyleAttributeValue>) -> Self {
145        Self {
146            key: key.into(),
147            value: value.into(),
148            transition: None,
149        }
150    }
151
152    pub fn with_transition(
153        key: impl Into<SmolStr>,
154        value: impl Into<StyleAttributeValue>,
155        transition: impl Into<StyleTransition>,
156    ) -> Self {
157        Self {
158            key: key.into(),
159            value: value.into(),
160            transition: Some(transition.into()),
161        }
162    }
163}
164
165pub trait StyleAttributeBuilder {
166    fn attribute(self, key: impl Into<StyleAttributeKey>) -> StyleAttribute;
167}
168
169impl<T: Into<StyleAttributeValue>> StyleAttributeBuilder for T {
170    fn attribute(self, key: impl Into<StyleAttributeKey>) -> StyleAttribute {
171        StyleAttribute::new(key, self)
172    }
173}
174
175impl<T: Into<StyleAttributeValue>, U: Into<StyleTransition>> StyleAttributeBuilder for (T, U) {
176    fn attribute(self, key: impl Into<StyleAttributeKey>) -> StyleAttribute {
177        StyleAttribute::with_transition(key, self.0, self.1)
178    }
179}
180
181/// An ease of use function to create an [`StyleAttributeBuilder`] with a transition.
182pub fn trans(
183    value: impl Into<StyleAttributeValue>,
184    transition: impl Into<StyleTransition>,
185) -> impl StyleAttributeBuilder {
186    (value, transition)
187}
188
189/// A [`Style`](super::Style) attribute value.
190#[derive(Clone, Debug)]
191pub enum StyleAttributeValue {
192    /// A string value, eg. `"hello"`.
193    String(String),
194    /// An enum value, eg. `red` or `space-between`.
195    Enum(String),
196    /// A length value, eg. `10px` or `10pt`.
197    Unit(Unit),
198    /// A color value, eg. `#ff0000`.
199    Color(Color),
200}
201
202impl StyleAttributeValue {
203    pub fn is_none(&self) -> bool {
204        matches!(self, Self::Enum(value) if value == "none")
205    }
206}
207
208impl Display for StyleAttributeValue {
209    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        match self {
211            Self::String(value) => write!(f, "\"{}\"", value),
212            Self::Enum(value) => write!(f, "{}", value),
213            Self::Unit(value) => write!(f, "{}", value),
214            Self::Color(value) => write!(f, "{}", value),
215        }
216    }
217}
218
219impl From<String> for StyleAttributeValue {
220    fn from(value: String) -> Self {
221        Self::String(value)
222    }
223}
224
225impl From<&str> for StyleAttributeValue {
226    fn from(value: &str) -> Self {
227        Self::String(value.to_string())
228    }
229}
230
231macro_rules! num_impl {
232    ($($t:ty),*) => {
233        $(
234            impl From<$t> for StyleAttributeValue {
235                fn from(value: $t) -> Self {
236                    Self::Unit(Unit::Px(value as f32))
237                }
238            }
239        )*
240    };
241}
242
243num_impl!(f32, f64, u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
244
245impl From<Unit> for StyleAttributeValue {
246    fn from(value: Unit) -> Self {
247        Self::Unit(value)
248    }
249}
250
251impl From<Color> for StyleAttributeValue {
252    fn from(value: Color) -> Self {
253        Self::Color(value)
254    }
255}
256
257impl<T> From<&ReadSignal<T>> for StyleAttributeValue
258where
259    T: Into<StyleAttributeValue> + Clone,
260{
261    fn from(value: &ReadSignal<T>) -> Self {
262        value.cloned().into()
263    }
264}
265
266impl<T: StyleAttributeEnum> From<T> for StyleAttributeValue {
267    fn from(value: T) -> Self {
268        Self::Enum(String::from(value.to_str()))
269    }
270}
271
272pub trait FromStyleAttribute: Sized {
273    fn from_attribute(value: StyleAttributeValue) -> Option<Self>;
274}
275
276impl<T: StyleAttributeEnum> FromStyleAttribute for T {
277    fn from_attribute(value: StyleAttributeValue) -> Option<Self> {
278        match value {
279            StyleAttributeValue::Enum(value) => T::from_str(&value),
280            _ => None,
281        }
282    }
283}
284
285impl<T: FromStyleAttribute> FromStyleAttribute for Option<T> {
286    fn from_attribute(value: StyleAttributeValue) -> Option<Self> {
287        if value.is_none() {
288            return Some(None);
289        }
290
291        T::from_attribute(value).map(Some)
292    }
293}
294
295impl FromStyleAttribute for String {
296    fn from_attribute(value: StyleAttributeValue) -> Option<Self> {
297        match value {
298            StyleAttributeValue::String(value) => Some(value),
299            _ => None,
300        }
301    }
302}
303
304impl FromStyleAttribute for Unit {
305    fn from_attribute(value: StyleAttributeValue) -> Option<Self> {
306        match value {
307            StyleAttributeValue::Unit(value) => Some(value),
308            _ => None,
309        }
310    }
311}
312
313impl FromStyleAttribute for Color {
314    fn from_attribute(value: StyleAttributeValue) -> Option<Self> {
315        match value {
316            StyleAttributeValue::Color(value) => Some(value),
317            _ => None,
318        }
319    }
320}
321
322pub trait StyleAttributeEnum: Sized {
323    fn from_str(s: &str) -> Option<Self>;
324    fn to_str(&self) -> &str;
325}
326
327impl StyleAttributeEnum for bool {
328    fn from_str(s: &str) -> Option<Self> {
329        match s {
330            "true" => Some(true),
331            "false" => Some(false),
332            _ => None,
333        }
334    }
335
336    fn to_str(&self) -> &str {
337        if *self {
338            "true"
339        } else {
340            "false"
341        }
342    }
343}
344
345impl StyleAttributeEnum for TextAlign {
346    fn from_str(s: &str) -> Option<Self> {
347        match s {
348            "left" | "start" => Some(Self::Start),
349            "center" => Some(Self::Center),
350            "right" | "end" => Some(Self::End),
351            _ => None,
352        }
353    }
354
355    fn to_str(&self) -> &str {
356        match self {
357            Self::Start => "start",
358            Self::Center => "center",
359            Self::End => "end",
360        }
361    }
362}