textiler_core/theme/sx/
sx_value.rs

1use std::fmt::{Debug, Formatter};
2use std::str::FromStr;
3use std::sync::{Arc, Mutex};
4
5use crate::theme::sx::sx_value_parsing::{parse_sx_value, ParseSxValueError};
6use crate::theme::{Color, Theme, PALETTE_SELECTOR_REGEX};
7use crate::Sx;
8
9/// An sx value
10#[derive(Debug, PartialEq, Clone)]
11pub enum SxValue {
12    Integer(i32),
13    Float(f32),
14    Percent(f32),
15    FloatDimension {
16        value: f32,
17        unit: String,
18    },
19    Dimension {
20        value: i32,
21        unit: String,
22    },
23    CssLiteral(String),
24    String(String),
25    Color(Color),
26    ThemeToken {
27        palette: String,
28        selector: String,
29    },
30    ClassVar {
31        class: String,
32        var: String,
33        fallback: Option<Box<SxValue>>,
34    },
35    Callback(FnSxValue),
36    Nested(Sx),
37}
38
39impl SxValue {
40    pub fn var(class: &str, var: &str, fallback: impl Into<Option<SxValue>>) -> Self {
41        Self::ClassVar {
42            class: class.to_string(),
43            var: var.to_string(),
44            fallback: fallback.into().map(|fallback| Box::new(fallback)),
45        }
46    }
47
48    pub fn to_css(self) -> Option<String> {
49        Some(match self {
50            SxValue::Integer(i) => {
51                format!("{i}")
52            }
53            SxValue::Float(f) => {
54                format!("{f}")
55            }
56            SxValue::Percent(p) => {
57                format!("{}%", p * 100.0)
58            }
59            SxValue::FloatDimension { value, unit } => {
60                format!("{value}{unit}")
61            }
62            SxValue::Dimension { value, unit } => {
63                format!("{value}{unit}")
64            }
65            SxValue::CssLiteral(lit) => {
66                format!("{lit}")
67            }
68            SxValue::String(s) => {
69                format!("\"{s}\"")
70            }
71            SxValue::Color(c) => c.to_string(),
72            _other => return None,
73        })
74    }
75}
76
77impl From<i32> for SxValue {
78    fn from(value: i32) -> Self {
79        Self::Integer(value)
80    }
81}
82
83impl From<f32> for SxValue {
84    fn from(value: f32) -> Self {
85        Self::Float(value)
86    }
87}
88
89impl From<&str> for SxValue {
90    fn from(quoted_str: &str) -> Self {
91        if let Some(matched) = PALETTE_SELECTOR_REGEX.captures(quoted_str) {
92            let palette = matched["palette"].to_string();
93            let selector = matched["selector"].to_string();
94
95            SxValue::ThemeToken { palette, selector }
96        } else if quoted_str.contains(char::is_whitespace) {
97            SxValue::CssLiteral(quoted_str.to_string())
98        } else {
99            quoted_str.parse().unwrap()
100        }
101    }
102}
103
104impl From<Sx> for SxValue {
105    fn from(value: Sx) -> Self {
106        Self::Nested(value)
107    }
108}
109
110impl FromStr for SxValue {
111    type Err = ParseSxValueError;
112
113    fn from_str(s: &str) -> Result<Self, Self::Err> {
114        parse_sx_value(s)
115    }
116}
117
118/// An sx value derived from a function
119#[derive(Clone)]
120pub struct FnSxValue {
121    id: u64,
122    callback: Arc<Mutex<dyn Fn(&Theme) -> SxValue + Send>>,
123}
124
125impl PartialEq for FnSxValue {
126    fn eq(&self, other: &Self) -> bool {
127        self.id == other.id
128    }
129}
130
131impl FnSxValue {
132    pub fn new<R, F: Fn(&Theme) -> R + Send + 'static>(callback: F) -> Self
133    where
134        R: Into<SxValue>,
135    {
136        Self {
137            id: rand::random(),
138            callback: Arc::new(Mutex::new(move |theme: &Theme| (callback)(theme).into())),
139        }
140    }
141
142    pub fn apply(&self, theme: &Theme) -> SxValue {
143        let callback = self.callback.lock().expect("callback is poisoned");
144        (callback)(theme)
145    }
146}
147
148impl Debug for FnSxValue {
149    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150        write!(f, "(&Theme) => SxValue")
151    }
152}