1use crate::anim::Slot;
19use crate::color::Color;
20use crate::spec::Sizing;
21use crate::value::Value;
22
23#[derive(Clone, Copy, Debug, Default, PartialEq)]
26pub struct Slots {
27 pub width: Option<Sizing>,
30 pub height: Option<Sizing>,
31 pub bg: Option<Color>,
32 pub radius: Option<f32>,
34 pub opacity: Option<f32>,
36 pub rotate: Option<f32>,
38 pub scale: Option<f32>,
40}
41
42impl Slots {
43 pub fn width(mut self, width: Sizing) -> Self {
44 self.width = Some(width);
45 self
46 }
47
48 pub fn height(mut self, height: Sizing) -> Self {
49 self.height = Some(height);
50 self
51 }
52
53 pub fn bg(mut self, bg: Color) -> Self {
54 self.bg = Some(bg);
55 self
56 }
57
58 pub fn radius(mut self, radius: f32) -> Self {
59 self.radius = Some(radius);
60 self
61 }
62
63 pub fn opacity(mut self, opacity: f32) -> Self {
64 self.opacity = Some(opacity.clamp(0.0, 1.0));
65 self
66 }
67
68 pub fn rotate(mut self, turns: f32) -> Self {
69 self.rotate = Some(turns);
70 self
71 }
72
73 pub fn scale(mut self, scale: f32) -> Self {
74 self.scale = Some(scale);
75 self
76 }
77
78 pub(crate) fn parse_field(
87 &mut self,
88 name: &str,
89 v: &Value,
90 refs: Option<&mut crate::tokens::NameRefs<'_>>,
91 ) -> Result<bool, String> {
92 let num = |what: &str| {
93 v.as_float()
94 .map(|n| n as f32)
95 .ok_or_else(|| format!("{what} must be a number"))
96 };
97 if let Some(refs) = refs {
98 let hit = match name {
99 "width" => refs
100 .length_ref(v)
101 .map(|px| self.width = px.map(Sizing::Fixed)),
102 "height" => refs
103 .length_ref(v)
104 .map(|px| self.height = px.map(Sizing::Fixed)),
105 "bg" => refs.color_ref(v).map(|c| self.bg = c),
106 "radius" => refs.length_ref(v).map(|r| self.radius = r),
107 _ => None,
108 };
109 if hit.is_some() {
110 return Ok(true);
111 }
112 }
113 match name {
114 "width" => self.width = kept(sizing_value(v))?,
117 "height" => self.height = kept(sizing_value(v))?,
118 "bg" => self.bg = Some(color_value(v)?),
119 "radius" => self.radius = Some(num("radius")?),
120 "opacity" => self.opacity = Some(num("opacity")?.clamp(0.0, 1.0)),
121 "rotate" => self.rotate = Some(num("rotate")?),
122 "scale" => self.scale = Some(num("scale")?),
123 _ => return Ok(false),
124 }
125 Ok(true)
126 }
127
128 #[inline]
134 pub(crate) fn lanes(&self, slot: Slot) -> Option<[f32; 4]> {
135 match slot {
136 Slot::Width => self.width.and_then(Sizing::amount).map(one),
137 Slot::Height => self.height.and_then(Sizing::amount).map(one),
138 Slot::Bg => self.bg.map(Color::lanes),
139 Slot::Radius => self.radius.map(|r| [r; 4]),
140 Slot::Opacity => self.opacity.map(one),
141 Slot::Transform => None,
144 Slot::Border | Slot::Pos | Slot::Shadow | Slot::ShadowColor => None,
145 }
146 }
147
148 #[inline]
153 pub(crate) fn transform_lanes(&self, base: [f32; 4]) -> Option<[f32; 4]> {
154 if self.rotate.is_none() && self.scale.is_none() {
155 return None;
156 }
157 let pick = |v: Option<f32>, b: f32| v.filter(|v| v.is_finite()).unwrap_or(b);
159 Some([
160 pick(self.rotate, base[0]),
161 pick(self.scale, base[1]),
162 0.0,
163 0.0,
164 ])
165 }
166}
167
168#[inline]
170pub(crate) fn one(v: f32) -> [f32; 4] {
171 [v, 0.0, 0.0, 0.0]
172}
173
174macro_rules! slot_builders {
178 ($ty:ty) => {
179 impl $ty {
180 #[inline]
182 pub fn width(mut self, width: impl Into<crate::spec::Sizing>) -> Self {
183 self.slots = self.slots.width(width.into());
184 self
185 }
186
187 #[inline]
189 pub fn height(mut self, height: impl Into<crate::spec::Sizing>) -> Self {
190 self.slots = self.slots.height(height.into());
191 self
192 }
193
194 #[inline]
196 pub fn grow_width(self) -> Self {
197 self.width(crate::spec::Sizing::GROW)
198 }
199
200 #[inline]
202 pub fn grow_height(self) -> Self {
203 self.height(crate::spec::Sizing::GROW)
204 }
205
206 pub fn bg(mut self, bg: crate::color::Color) -> Self {
207 self.slots = self.slots.bg(bg);
208 self
209 }
210
211 pub fn radius(mut self, radius: f32) -> Self {
212 self.slots = self.slots.radius(radius);
213 self
214 }
215
216 pub fn rotate(mut self, turns: f32) -> Self {
218 self.slots = self.slots.rotate(turns);
219 self
220 }
221
222 pub fn scale(mut self, scale: f32) -> Self {
224 self.slots = self.slots.scale(scale);
225 self
226 }
227
228 pub fn opacity(mut self, opacity: f32) -> Self {
229 self.slots = self.slots.opacity(opacity);
230 self
231 }
232 }
233
234 impl std::ops::Deref for $ty {
235 type Target = crate::slots::Slots;
236 fn deref(&self) -> &crate::slots::Slots {
237 &self.slots
238 }
239 }
240
241 impl std::ops::DerefMut for $ty {
242 fn deref_mut(&mut self) -> &mut crate::slots::Slots {
243 &mut self.slots
244 }
245 }
246 };
247}
248pub(crate) use slot_builders;
249
250fn kept<T>(r: Result<T, String>) -> Result<Option<T>, String> {
253 match r {
254 Ok(v) => Ok(Some(v)),
255 Err(e) if crate::calc::is_full(&e) => Ok(None),
256 Err(e) => Err(e),
257 }
258}
259
260pub(crate) fn sizing_value(v: &Value) -> Result<Sizing, String> {
262 match v {
263 Value::Int(_) | Value::Float(_) => Ok(Sizing::Fixed(v.as_float().unwrap_or(0.0) as f32)),
264 Value::Str(s) => crate::schema::sizing_str(s),
265 Value::Map(_) => {
266 if let Some(g) = v.get_float("grow") {
267 Ok(Sizing::Grow(g as f32))
268 } else if let Some(p) = v.get_float("percent") {
269 Ok(Sizing::Percent(p as f32 / 100.0))
272 } else if let Some(p) = v.get_float("pct") {
273 Ok(Sizing::Percent(p as f32 / 100.0))
275 } else {
276 crate::calc::sizing_value(v).map_err(|e| {
278 format!("sizing object needs grow, percent or a size expression: {e}")
279 })
280 }
281 }
282 _ => Err("bad sizing (fit | grow | number | \"N%\" | a size expression)".into()),
283 }
284}
285
286pub(crate) fn color_value(v: &Value) -> Result<Color, String> {
288 match v {
289 Value::Int(n) => Ok(crate::schema::color_num(*n as u32)),
290 Value::Float(n) => Ok(crate::schema::color_num(*n as u32)),
291 Value::Str(s) => crate::schema::color_hex_str(s),
292 _ => Err("color must be a 0xRRGGBBAA number or \"#hex\" string".into()),
293 }
294}