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}
37
38impl Slots {
39 pub fn width(mut self, width: Sizing) -> Self {
40 self.width = Some(width);
41 self
42 }
43
44 pub fn height(mut self, height: Sizing) -> Self {
45 self.height = Some(height);
46 self
47 }
48
49 pub fn bg(mut self, bg: Color) -> Self {
50 self.bg = Some(bg);
51 self
52 }
53
54 pub fn radius(mut self, radius: f32) -> Self {
55 self.radius = Some(radius);
56 self
57 }
58
59 pub fn opacity(mut self, opacity: f32) -> Self {
60 self.opacity = Some(opacity.clamp(0.0, 1.0));
61 self
62 }
63
64 pub(crate) fn parse_field(
73 &mut self,
74 name: &str,
75 v: &Value,
76 refs: Option<&mut crate::tokens::NameRefs<'_>>,
77 ) -> Result<bool, String> {
78 let num = |what: &str| {
79 v.as_float()
80 .map(|n| n as f32)
81 .ok_or_else(|| format!("{what} must be a number"))
82 };
83 if let Some(refs) = refs {
84 let hit = match name {
85 "width" => refs
86 .length_ref(v)
87 .map(|px| self.width = px.map(Sizing::Fixed)),
88 "height" => refs
89 .length_ref(v)
90 .map(|px| self.height = px.map(Sizing::Fixed)),
91 "bg" => refs.color_ref(v).map(|c| self.bg = c),
92 "radius" => refs.length_ref(v).map(|r| self.radius = r),
93 _ => None,
94 };
95 if hit.is_some() {
96 return Ok(true);
97 }
98 }
99 match name {
100 "width" => self.width = kept(sizing_value(v))?,
103 "height" => self.height = kept(sizing_value(v))?,
104 "bg" => self.bg = Some(color_value(v)?),
105 "radius" => self.radius = Some(num("radius")?),
106 "opacity" => self.opacity = Some(num("opacity")?.clamp(0.0, 1.0)),
107 _ => return Ok(false),
108 }
109 Ok(true)
110 }
111
112 #[inline]
118 pub(crate) fn lanes(&self, slot: Slot) -> Option<[f32; 4]> {
119 match slot {
120 Slot::Width => self.width.and_then(Sizing::amount).map(one),
121 Slot::Height => self.height.and_then(Sizing::amount).map(one),
122 Slot::Bg => self.bg.map(Color::lanes),
123 Slot::Radius => self.radius.map(|r| [r; 4]),
124 Slot::Opacity => self.opacity.map(one),
125 Slot::Border | Slot::Pos | Slot::Shadow | Slot::ShadowColor => None,
126 }
127 }
128}
129
130#[inline]
132pub(crate) fn one(v: f32) -> [f32; 4] {
133 [v, 0.0, 0.0, 0.0]
134}
135
136macro_rules! slot_builders {
140 ($ty:ty) => {
141 impl $ty {
142 #[inline]
144 pub fn width(mut self, width: impl Into<crate::spec::Sizing>) -> Self {
145 self.slots = self.slots.width(width.into());
146 self
147 }
148
149 #[inline]
151 pub fn height(mut self, height: impl Into<crate::spec::Sizing>) -> Self {
152 self.slots = self.slots.height(height.into());
153 self
154 }
155
156 #[inline]
158 pub fn grow_width(self) -> Self {
159 self.width(crate::spec::Sizing::GROW)
160 }
161
162 #[inline]
164 pub fn grow_height(self) -> Self {
165 self.height(crate::spec::Sizing::GROW)
166 }
167
168 pub fn bg(mut self, bg: crate::color::Color) -> Self {
169 self.slots = self.slots.bg(bg);
170 self
171 }
172
173 pub fn radius(mut self, radius: f32) -> Self {
174 self.slots = self.slots.radius(radius);
175 self
176 }
177
178 pub fn opacity(mut self, opacity: f32) -> Self {
179 self.slots = self.slots.opacity(opacity);
180 self
181 }
182 }
183
184 impl std::ops::Deref for $ty {
185 type Target = crate::slots::Slots;
186 fn deref(&self) -> &crate::slots::Slots {
187 &self.slots
188 }
189 }
190
191 impl std::ops::DerefMut for $ty {
192 fn deref_mut(&mut self) -> &mut crate::slots::Slots {
193 &mut self.slots
194 }
195 }
196 };
197}
198pub(crate) use slot_builders;
199
200fn kept<T>(r: Result<T, String>) -> Result<Option<T>, String> {
203 match r {
204 Ok(v) => Ok(Some(v)),
205 Err(e) if crate::calc::is_full(&e) => Ok(None),
206 Err(e) => Err(e),
207 }
208}
209
210pub(crate) fn sizing_value(v: &Value) -> Result<Sizing, String> {
212 match v {
213 Value::Int(_) | Value::Float(_) => Ok(Sizing::Fixed(v.as_float().unwrap_or(0.0) as f32)),
214 Value::Str(s) => crate::schema::sizing_str(s),
215 Value::Map(_) => {
216 if let Some(g) = v.get_float("grow") {
217 Ok(Sizing::Grow(g as f32))
218 } else if let Some(p) = v.get_float("percent") {
219 Ok(Sizing::Percent(p as f32 / 100.0))
222 } else if let Some(p) = v.get_float("pct") {
223 Ok(Sizing::Percent(p as f32 / 100.0))
225 } else {
226 crate::calc::sizing_value(v).map_err(|e| {
228 format!("sizing object needs grow, percent or a size expression: {e}")
229 })
230 }
231 }
232 _ => Err("bad sizing (fit | grow | number | \"N%\" | a size expression)".into()),
233 }
234}
235
236pub(crate) fn color_value(v: &Value) -> Result<Color, String> {
238 match v {
239 Value::Int(n) => Ok(crate::schema::color_num(*n as u32)),
240 Value::Float(n) => Ok(crate::schema::color_num(*n as u32)),
241 Value::Str(s) => crate::schema::color_hex_str(s),
242 _ => Err("color must be a 0xRRGGBBAA number or \"#hex\" string".into()),
243 }
244}