textiler_core/theme/sx/
sx_value.rs1use 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#[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#[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}