1use crate::anim::Slot;
11use crate::color::Color;
12use crate::spec::Sizing;
13use crate::value::Value;
14
15#[derive(Clone, Copy, Debug, Default, PartialEq)]
18pub struct Slots {
19 pub width: Option<Sizing>,
22 pub height: Option<Sizing>,
23 pub bg: Option<Color>,
24 pub radius: Option<f32>,
26 pub opacity: Option<f32>,
28}
29
30impl Slots {
31 pub fn width(mut self, width: Sizing) -> Self {
32 self.width = Some(width);
33 self
34 }
35
36 pub fn height(mut self, height: Sizing) -> Self {
37 self.height = Some(height);
38 self
39 }
40
41 pub fn bg(mut self, bg: Color) -> Self {
42 self.bg = Some(bg);
43 self
44 }
45
46 pub fn radius(mut self, radius: f32) -> Self {
47 self.radius = Some(radius);
48 self
49 }
50
51 pub fn opacity(mut self, opacity: f32) -> Self {
52 self.opacity = Some(opacity.clamp(0.0, 1.0));
53 self
54 }
55
56 pub(crate) fn parse_field(
66 &mut self,
67 name: &str,
68 v: &Value,
69 refs: Option<&mut crate::tokens::NameRefs<'_>>,
70 ) -> Result<bool, String> {
71 let num = |what: &str| {
72 v.as_float()
73 .map(|n| n as f32)
74 .ok_or_else(|| format!("{what} must be a number"))
75 };
76 if let Some(refs) = refs {
77 let hit = match name {
78 "width" => refs
79 .length_ref(v)
80 .map(|px| self.width = px.map(Sizing::Fixed)),
81 "height" => refs
82 .length_ref(v)
83 .map(|px| self.height = px.map(Sizing::Fixed)),
84 "bg" => refs.color_ref(v).map(|c| self.bg = c),
85 "radius" => refs.length_ref(v).map(|r| self.radius = r),
86 _ => None,
87 };
88 if hit.is_some() {
89 return Ok(true);
90 }
91 }
92 match name {
93 "width" => self.width = kept(sizing_value(v))?,
96 "height" => self.height = kept(sizing_value(v))?,
97 "bg" => self.bg = Some(color_value(v)?),
98 "radius" => self.radius = Some(num("radius")?),
99 "opacity" => self.opacity = Some(num("opacity")?.clamp(0.0, 1.0)),
100 _ => return Ok(false),
101 }
102 Ok(true)
103 }
104
105 #[inline]
111 pub(crate) fn lanes(&self, slot: Slot) -> Option<[f32; 4]> {
112 match slot {
113 Slot::Width => self.width.and_then(Sizing::amount).map(one),
114 Slot::Height => self.height.and_then(Sizing::amount).map(one),
115 Slot::Bg => self.bg.map(Color::lanes),
116 Slot::Radius => self.radius.map(|r| [r; 4]),
117 Slot::Opacity => self.opacity.map(one),
118 Slot::Border | Slot::Pos | Slot::Shadow | Slot::ShadowColor => None,
119 }
120 }
121}
122
123#[inline]
125pub(crate) fn one(v: f32) -> [f32; 4] {
126 [v, 0.0, 0.0, 0.0]
127}
128
129macro_rules! slot_builders {
133 ($ty:ty) => {
134 impl $ty {
135 #[inline]
137 pub fn width(mut self, width: impl Into<crate::spec::Sizing>) -> Self {
138 self.slots = self.slots.width(width.into());
139 self
140 }
141
142 #[inline]
144 pub fn height(mut self, height: impl Into<crate::spec::Sizing>) -> Self {
145 self.slots = self.slots.height(height.into());
146 self
147 }
148
149 #[inline]
151 pub fn grow_width(self) -> Self {
152 self.width(crate::spec::Sizing::GROW)
153 }
154
155 #[inline]
157 pub fn grow_height(self) -> Self {
158 self.height(crate::spec::Sizing::GROW)
159 }
160
161 pub fn bg(mut self, bg: crate::color::Color) -> Self {
162 self.slots = self.slots.bg(bg);
163 self
164 }
165
166 pub fn radius(mut self, radius: f32) -> Self {
167 self.slots = self.slots.radius(radius);
168 self
169 }
170
171 pub fn opacity(mut self, opacity: f32) -> Self {
172 self.slots = self.slots.opacity(opacity);
173 self
174 }
175 }
176
177 impl std::ops::Deref for $ty {
178 type Target = crate::slots::Slots;
179 fn deref(&self) -> &crate::slots::Slots {
180 &self.slots
181 }
182 }
183
184 impl std::ops::DerefMut for $ty {
185 fn deref_mut(&mut self) -> &mut crate::slots::Slots {
186 &mut self.slots
187 }
188 }
189 };
190}
191pub(crate) use slot_builders;
192
193fn kept<T>(r: Result<T, String>) -> Result<Option<T>, String> {
196 match r {
197 Ok(v) => Ok(Some(v)),
198 Err(e) if crate::calc::is_full(&e) => Ok(None),
199 Err(e) => Err(e),
200 }
201}
202
203pub(crate) fn sizing_value(v: &Value) -> Result<Sizing, String> {
205 match v {
206 Value::Int(_) | Value::Float(_) => Ok(Sizing::Fixed(v.as_float().unwrap_or(0.0) as f32)),
207 Value::Str(s) => crate::schema::sizing_str(s),
208 Value::Map(_) => {
209 if let Some(g) = v.get_float("grow") {
210 Ok(Sizing::Grow(g as f32))
211 } else if let Some(p) = v.get_float("percent") {
212 Ok(Sizing::Percent(p as f32 / 100.0))
216 } else if let Some(p) = v.get_float("pct") {
217 Ok(Sizing::Percent(p as f32 / 100.0))
219 } else {
220 crate::calc::sizing_value(v).map_err(|e| {
222 format!("sizing object needs grow, percent or a size expression: {e}")
223 })
224 }
225 }
226 _ => Err("bad sizing (fit | grow | number | \"N%\" | a size expression)".into()),
227 }
228}
229
230pub(crate) fn color_value(v: &Value) -> Result<Color, String> {
232 match v {
233 Value::Int(n) => Ok(crate::schema::color_num(*n as u32)),
234 Value::Float(n) => Ok(crate::schema::color_num(*n as u32)),
235 Value::Str(s) => crate::schema::color_hex_str(s),
236 _ => Err("color must be a 0xRRGGBBAA number or \"#hex\" string".into()),
237 }
238}