container.rs

  1use pathfinder_geometry::rect::RectF;
  2use serde::Deserialize;
  3use serde_json::json;
  4
  5use crate::{
  6    color::Color,
  7    geometry::{
  8        deserialize_vec2f,
  9        vector::{vec2f, Vector2F},
 10    },
 11    json::ToJson,
 12    scene::{self, Border, Quad},
 13    Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
 14};
 15
 16#[derive(Clone, Copy, Debug, Default, Deserialize)]
 17pub struct ContainerStyle {
 18    #[serde(default)]
 19    pub margin: Margin,
 20    #[serde(default)]
 21    pub padding: Padding,
 22    #[serde(rename = "background")]
 23    pub background_color: Option<Color>,
 24    #[serde(default)]
 25    pub border: Border,
 26    #[serde(default)]
 27    pub corner_radius: f32,
 28    #[serde(default)]
 29    pub shadow: Option<Shadow>,
 30}
 31
 32pub struct Container {
 33    child: ElementBox,
 34    style: ContainerStyle,
 35}
 36
 37impl Container {
 38    pub fn new(child: ElementBox) -> Self {
 39        Self {
 40            child,
 41            style: Default::default(),
 42        }
 43    }
 44
 45    pub fn with_style(mut self, style: ContainerStyle) -> Self {
 46        self.style = style;
 47        self
 48    }
 49
 50    pub fn with_margin_top(mut self, margin: f32) -> Self {
 51        self.style.margin.top = margin;
 52        self
 53    }
 54
 55    pub fn with_margin_bottom(mut self, margin: f32) -> Self {
 56        self.style.margin.bottom = margin;
 57        self
 58    }
 59
 60    pub fn with_margin_left(mut self, margin: f32) -> Self {
 61        self.style.margin.left = margin;
 62        self
 63    }
 64
 65    pub fn with_margin_right(mut self, margin: f32) -> Self {
 66        self.style.margin.right = margin;
 67        self
 68    }
 69
 70    pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
 71        self.style.padding.left = padding;
 72        self.style.padding.right = padding;
 73        self
 74    }
 75
 76    pub fn with_vertical_padding(mut self, padding: f32) -> Self {
 77        self.style.padding.top = padding;
 78        self.style.padding.bottom = padding;
 79        self
 80    }
 81
 82    pub fn with_uniform_padding(mut self, padding: f32) -> Self {
 83        self.style.padding = Padding {
 84            top: padding,
 85            left: padding,
 86            bottom: padding,
 87            right: padding,
 88        };
 89        self
 90    }
 91
 92    pub fn with_padding_left(mut self, padding: f32) -> Self {
 93        self.style.padding.left = padding;
 94        self
 95    }
 96
 97    pub fn with_padding_right(mut self, padding: f32) -> Self {
 98        self.style.padding.right = padding;
 99        self
100    }
101
102    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
103        self.style.padding.bottom = padding;
104        self
105    }
106
107    pub fn with_background_color(mut self, color: Color) -> Self {
108        self.style.background_color = Some(color);
109        self
110    }
111
112    pub fn with_border(mut self, border: Border) -> Self {
113        self.style.border = border;
114        self
115    }
116
117    pub fn with_corner_radius(mut self, radius: f32) -> Self {
118        self.style.corner_radius = radius;
119        self
120    }
121
122    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
123        self.style.shadow = Some(Shadow {
124            offset,
125            blur,
126            color,
127        });
128        self
129    }
130
131    fn margin_size(&self) -> Vector2F {
132        vec2f(
133            self.style.margin.left + self.style.margin.right,
134            self.style.margin.top + self.style.margin.bottom,
135        )
136    }
137
138    fn padding_size(&self) -> Vector2F {
139        vec2f(
140            self.style.padding.left + self.style.padding.right,
141            self.style.padding.top + self.style.padding.bottom,
142        )
143    }
144
145    fn border_size(&self) -> Vector2F {
146        let mut x = 0.0;
147        if self.style.border.left {
148            x += self.style.border.width;
149        }
150        if self.style.border.right {
151            x += self.style.border.width;
152        }
153
154        let mut y = 0.0;
155        if self.style.border.top {
156            y += self.style.border.width;
157        }
158        if self.style.border.bottom {
159            y += self.style.border.width;
160        }
161
162        vec2f(x, y)
163    }
164}
165
166impl Element for Container {
167    type LayoutState = ();
168    type PaintState = ();
169
170    fn layout(
171        &mut self,
172        constraint: SizeConstraint,
173        cx: &mut LayoutContext,
174    ) -> (Vector2F, Self::LayoutState) {
175        let mut size_buffer = self.margin_size() + self.padding_size();
176        if !self.style.border.overlay {
177            size_buffer += self.border_size();
178        }
179        let child_constraint = SizeConstraint {
180            min: (constraint.min - size_buffer).max(Vector2F::zero()),
181            max: (constraint.max - size_buffer).max(Vector2F::zero()),
182        };
183        let child_size = self.child.layout(child_constraint, cx);
184        (child_size + size_buffer, ())
185    }
186
187    fn paint(
188        &mut self,
189        bounds: RectF,
190        visible_bounds: RectF,
191        _: &mut Self::LayoutState,
192        cx: &mut PaintContext,
193    ) -> Self::PaintState {
194        let quad_bounds = RectF::from_points(
195            bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
196            bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
197        );
198
199        if let Some(shadow) = self.style.shadow.as_ref() {
200            cx.scene.push_shadow(scene::Shadow {
201                bounds: quad_bounds + shadow.offset,
202                corner_radius: self.style.corner_radius,
203                sigma: shadow.blur,
204                color: shadow.color,
205            });
206        }
207
208        let child_origin =
209            quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
210
211        if self.style.border.overlay {
212            cx.scene.push_quad(Quad {
213                bounds: quad_bounds,
214                background: self.style.background_color,
215                border: Default::default(),
216                corner_radius: self.style.corner_radius,
217            });
218
219            self.child.paint(child_origin, visible_bounds, cx);
220
221            cx.scene.push_layer(None);
222            cx.scene.push_quad(Quad {
223                bounds: quad_bounds,
224                background: Default::default(),
225                border: self.style.border,
226                corner_radius: self.style.corner_radius,
227            });
228            cx.scene.pop_layer();
229        } else {
230            cx.scene.push_quad(Quad {
231                bounds: quad_bounds,
232                background: self.style.background_color,
233                border: self.style.border,
234                corner_radius: self.style.corner_radius,
235            });
236
237            let child_origin = child_origin
238                + vec2f(
239                    self.style.border.left_width(),
240                    self.style.border.top_width(),
241                );
242            self.child.paint(child_origin, visible_bounds, cx);
243        }
244    }
245
246    fn dispatch_event(
247        &mut self,
248        event: &Event,
249        _: RectF,
250        _: RectF,
251        _: &mut Self::LayoutState,
252        _: &mut Self::PaintState,
253        cx: &mut EventContext,
254    ) -> bool {
255        self.child.dispatch_event(event, cx)
256    }
257
258    fn debug(
259        &self,
260        bounds: RectF,
261        _: &Self::LayoutState,
262        _: &Self::PaintState,
263        cx: &crate::DebugContext,
264    ) -> serde_json::Value {
265        json!({
266            "type": "Container",
267            "bounds": bounds.to_json(),
268            "details": self.style.to_json(),
269            "child": self.child.debug(cx),
270        })
271    }
272}
273
274impl ToJson for ContainerStyle {
275    fn to_json(&self) -> serde_json::Value {
276        json!({
277            "margin": self.margin.to_json(),
278            "padding": self.padding.to_json(),
279            "background_color": self.background_color.to_json(),
280            "border": self.border.to_json(),
281            "corner_radius": self.corner_radius,
282            "shadow": self.shadow.to_json(),
283        })
284    }
285}
286
287#[derive(Clone, Copy, Debug, Default)]
288pub struct Margin {
289    pub top: f32,
290    pub left: f32,
291    pub bottom: f32,
292    pub right: f32,
293}
294
295impl ToJson for Margin {
296    fn to_json(&self) -> serde_json::Value {
297        let mut value = json!({});
298        if self.top > 0. {
299            value["top"] = json!(self.top);
300        }
301        if self.right > 0. {
302            value["right"] = json!(self.right);
303        }
304        if self.bottom > 0. {
305            value["bottom"] = json!(self.bottom);
306        }
307        if self.left > 0. {
308            value["left"] = json!(self.left);
309        }
310        value
311    }
312}
313
314#[derive(Clone, Copy, Debug, Default)]
315pub struct Padding {
316    pub top: f32,
317    pub left: f32,
318    pub bottom: f32,
319    pub right: f32,
320}
321
322impl<'de> Deserialize<'de> for Padding {
323    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
324    where
325        D: serde::Deserializer<'de>,
326    {
327        let spacing = Spacing::deserialize(deserializer)?;
328        Ok(match spacing {
329            Spacing::Uniform(size) => Padding {
330                top: size,
331                left: size,
332                bottom: size,
333                right: size,
334            },
335            Spacing::Specific {
336                top,
337                left,
338                bottom,
339                right,
340            } => Padding {
341                top,
342                left,
343                bottom,
344                right,
345            },
346        })
347    }
348}
349
350impl<'de> Deserialize<'de> for Margin {
351    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
352    where
353        D: serde::Deserializer<'de>,
354    {
355        let spacing = Spacing::deserialize(deserializer)?;
356        Ok(match spacing {
357            Spacing::Uniform(size) => Margin {
358                top: size,
359                left: size,
360                bottom: size,
361                right: size,
362            },
363            Spacing::Specific {
364                top,
365                left,
366                bottom,
367                right,
368            } => Margin {
369                top,
370                left,
371                bottom,
372                right,
373            },
374        })
375    }
376}
377#[derive(Deserialize)]
378#[serde(untagged)]
379enum Spacing {
380    Uniform(f32),
381    Specific {
382        #[serde(default)]
383        top: f32,
384        #[serde(default)]
385        left: f32,
386        #[serde(default)]
387        bottom: f32,
388        #[serde(default)]
389        right: f32,
390    },
391}
392
393impl Padding {
394    pub fn uniform(padding: f32) -> Self {
395        Self {
396            top: padding,
397            left: padding,
398            bottom: padding,
399            right: padding,
400        }
401    }
402}
403
404impl ToJson for Padding {
405    fn to_json(&self) -> serde_json::Value {
406        let mut value = json!({});
407        if self.top > 0. {
408            value["top"] = json!(self.top);
409        }
410        if self.right > 0. {
411            value["right"] = json!(self.right);
412        }
413        if self.bottom > 0. {
414            value["bottom"] = json!(self.bottom);
415        }
416        if self.left > 0. {
417            value["left"] = json!(self.left);
418        }
419        value
420    }
421}
422
423#[derive(Clone, Copy, Debug, Default, Deserialize)]
424pub struct Shadow {
425    #[serde(default, deserialize_with = "deserialize_vec2f")]
426    offset: Vector2F,
427    #[serde(default)]
428    blur: f32,
429    #[serde(default)]
430    color: Color,
431}
432
433impl ToJson for Shadow {
434    fn to_json(&self) -> serde_json::Value {
435        json!({
436            "offset": self.offset.to_json(),
437            "blur": self.blur,
438            "color": self.color.to_json()
439        })
440    }
441}