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, 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.clone();
 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_left(mut self, margin: f32) -> Self {
 56        self.style.margin.left = margin;
 57        self
 58    }
 59
 60    pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
 61        self.style.padding.left = padding;
 62        self.style.padding.right = padding;
 63        self
 64    }
 65
 66    pub fn with_vertical_padding(mut self, padding: f32) -> Self {
 67        self.style.padding.top = padding;
 68        self.style.padding.bottom = padding;
 69        self
 70    }
 71
 72    pub fn with_uniform_padding(mut self, padding: f32) -> Self {
 73        self.style.padding = Padding {
 74            top: padding,
 75            left: padding,
 76            bottom: padding,
 77            right: padding,
 78        };
 79        self
 80    }
 81
 82    pub fn with_padding_right(mut self, padding: f32) -> Self {
 83        self.style.padding.right = padding;
 84        self
 85    }
 86
 87    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
 88        self.style.padding.bottom = padding;
 89        self
 90    }
 91
 92    pub fn with_background_color(mut self, color: Color) -> Self {
 93        self.style.background_color = Some(color);
 94        self
 95    }
 96
 97    pub fn with_border(mut self, border: Border) -> Self {
 98        self.style.border = border;
 99        self
100    }
101
102    pub fn with_corner_radius(mut self, radius: f32) -> Self {
103        self.style.corner_radius = radius;
104        self
105    }
106
107    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
108        self.style.shadow = Some(Shadow {
109            offset,
110            blur,
111            color,
112        });
113        self
114    }
115
116    fn margin_size(&self) -> Vector2F {
117        vec2f(
118            self.style.margin.left + self.style.margin.right,
119            self.style.margin.top + self.style.margin.bottom,
120        )
121    }
122
123    fn padding_size(&self) -> Vector2F {
124        vec2f(
125            self.style.padding.left + self.style.padding.right,
126            self.style.padding.top + self.style.padding.bottom,
127        )
128    }
129
130    fn border_size(&self) -> Vector2F {
131        let mut x = 0.0;
132        if self.style.border.left {
133            x += self.style.border.width;
134        }
135        if self.style.border.right {
136            x += self.style.border.width;
137        }
138
139        let mut y = 0.0;
140        if self.style.border.top {
141            y += self.style.border.width;
142        }
143        if self.style.border.bottom {
144            y += self.style.border.width;
145        }
146
147        vec2f(x, y)
148    }
149}
150
151impl Element for Container {
152    type LayoutState = ();
153    type PaintState = ();
154
155    fn layout(
156        &mut self,
157        constraint: SizeConstraint,
158        cx: &mut LayoutContext,
159    ) -> (Vector2F, Self::LayoutState) {
160        let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
161        let child_constraint = SizeConstraint {
162            min: (constraint.min - size_buffer).max(Vector2F::zero()),
163            max: (constraint.max - size_buffer).max(Vector2F::zero()),
164        };
165        let child_size = self.child.layout(child_constraint, cx);
166        (child_size + size_buffer, ())
167    }
168
169    fn paint(
170        &mut self,
171        bounds: RectF,
172        visible_bounds: RectF,
173        _: &mut Self::LayoutState,
174        cx: &mut PaintContext,
175    ) -> Self::PaintState {
176        let quad_bounds = RectF::from_points(
177            bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
178            bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
179        );
180
181        if let Some(shadow) = self.style.shadow.as_ref() {
182            cx.scene.push_shadow(scene::Shadow {
183                bounds: quad_bounds + shadow.offset,
184                corner_radius: self.style.corner_radius,
185                sigma: shadow.blur,
186                color: shadow.color,
187            });
188        }
189        cx.scene.push_quad(Quad {
190            bounds: quad_bounds,
191            background: self.style.background_color,
192            border: self.style.border,
193            corner_radius: self.style.corner_radius,
194        });
195
196        let child_origin = quad_bounds.origin()
197            + vec2f(self.style.padding.left, self.style.padding.top)
198            + vec2f(
199                self.style.border.left_width(),
200                self.style.border.top_width(),
201            );
202        self.child.paint(child_origin, visible_bounds, cx);
203    }
204
205    fn dispatch_event(
206        &mut self,
207        event: &Event,
208        _: RectF,
209        _: &mut Self::LayoutState,
210        _: &mut Self::PaintState,
211        cx: &mut EventContext,
212    ) -> bool {
213        self.child.dispatch_event(event, cx)
214    }
215
216    fn debug(
217        &self,
218        bounds: RectF,
219        _: &Self::LayoutState,
220        _: &Self::PaintState,
221        cx: &crate::DebugContext,
222    ) -> serde_json::Value {
223        json!({
224            "type": "Container",
225            "bounds": bounds.to_json(),
226            "details": self.style.to_json(),
227            "child": self.child.debug(cx),
228        })
229    }
230}
231
232impl ToJson for ContainerStyle {
233    fn to_json(&self) -> serde_json::Value {
234        json!({
235            "margin": self.margin.to_json(),
236            "padding": self.padding.to_json(),
237            "background_color": self.background_color.to_json(),
238            "border": self.border.to_json(),
239            "corner_radius": self.corner_radius,
240            "shadow": self.shadow.to_json(),
241        })
242    }
243}
244
245#[derive(Clone, Debug, Default)]
246pub struct Margin {
247    pub top: f32,
248    pub left: f32,
249    pub bottom: f32,
250    pub right: f32,
251}
252
253impl ToJson for Margin {
254    fn to_json(&self) -> serde_json::Value {
255        let mut value = json!({});
256        if self.top > 0. {
257            value["top"] = json!(self.top);
258        }
259        if self.right > 0. {
260            value["right"] = json!(self.right);
261        }
262        if self.bottom > 0. {
263            value["bottom"] = json!(self.bottom);
264        }
265        if self.left > 0. {
266            value["left"] = json!(self.left);
267        }
268        value
269    }
270}
271
272#[derive(Clone, Debug, Default)]
273pub struct Padding {
274    pub top: f32,
275    pub left: f32,
276    pub bottom: f32,
277    pub right: f32,
278}
279
280impl<'de> Deserialize<'de> for Padding {
281    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
282    where
283        D: serde::Deserializer<'de>,
284    {
285        let spacing = Spacing::deserialize(deserializer)?;
286        Ok(match spacing {
287            Spacing::Uniform(size) => Padding {
288                top: size,
289                left: size,
290                bottom: size,
291                right: size,
292            },
293            Spacing::Specific {
294                top,
295                left,
296                bottom,
297                right,
298            } => Padding {
299                top,
300                left,
301                bottom,
302                right,
303            },
304        })
305    }
306}
307
308impl<'de> Deserialize<'de> for Margin {
309    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310    where
311        D: serde::Deserializer<'de>,
312    {
313        let spacing = Spacing::deserialize(deserializer)?;
314        Ok(match spacing {
315            Spacing::Uniform(size) => Margin {
316                top: size,
317                left: size,
318                bottom: size,
319                right: size,
320            },
321            Spacing::Specific {
322                top,
323                left,
324                bottom,
325                right,
326            } => Margin {
327                top,
328                left,
329                bottom,
330                right,
331            },
332        })
333    }
334}
335#[derive(Deserialize)]
336#[serde(untagged)]
337enum Spacing {
338    Uniform(f32),
339    Specific {
340        #[serde(default)]
341        top: f32,
342        #[serde(default)]
343        left: f32,
344        #[serde(default)]
345        bottom: f32,
346        #[serde(default)]
347        right: f32,
348    },
349}
350
351impl ToJson for Padding {
352    fn to_json(&self) -> serde_json::Value {
353        let mut value = json!({});
354        if self.top > 0. {
355            value["top"] = json!(self.top);
356        }
357        if self.right > 0. {
358            value["right"] = json!(self.right);
359        }
360        if self.bottom > 0. {
361            value["bottom"] = json!(self.bottom);
362        }
363        if self.left > 0. {
364            value["left"] = json!(self.left);
365        }
366        value
367    }
368}
369
370#[derive(Clone, Debug, Default, Deserialize)]
371pub struct Shadow {
372    #[serde(default, deserialize_with = "deserialize_vec2f")]
373    offset: Vector2F,
374    #[serde(default)]
375    blur: f32,
376    #[serde(default)]
377    color: Color,
378}
379
380impl ToJson for Shadow {
381    fn to_json(&self) -> serde_json::Value {
382        json!({
383            "offset": self.offset.to_json(),
384            "blur": self.blur,
385            "color": self.color.to_json()
386        })
387    }
388}