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#[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#[derive(Clone, Debug)]
134pub struct StyleAttribute {
135 pub key: StyleAttributeKey,
137 pub value: StyleAttributeValue,
139 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
181pub fn trans(
183 value: impl Into<StyleAttributeValue>,
184 transition: impl Into<StyleTransition>,
185) -> impl StyleAttributeBuilder {
186 (value, transition)
187}
188
189#[derive(Clone, Debug)]
191pub enum StyleAttributeValue {
192 String(String),
194 Enum(String),
196 Unit(Unit),
198 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}