container.rs

  1use pathfinder_geometry::rect::RectF;
  2use serde_json::json;
  3
  4use crate::{
  5    color::ColorU,
  6    geometry::vector::{vec2f, Vector2F},
  7    json::ToJson,
  8    scene::{self, Border, Quad},
  9    AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
 10    SizeConstraint,
 11};
 12
 13pub struct Container {
 14    margin: Margin,
 15    padding: Padding,
 16    background_color: Option<ColorU>,
 17    border: Border,
 18    corner_radius: f32,
 19    shadow: Option<Shadow>,
 20    child: ElementBox,
 21}
 22
 23impl Container {
 24    pub fn new(child: ElementBox) -> Self {
 25        Self {
 26            margin: Margin::default(),
 27            padding: Padding::default(),
 28            background_color: None,
 29            border: Border::default(),
 30            corner_radius: 0.0,
 31            shadow: None,
 32            child,
 33        }
 34    }
 35
 36    pub fn with_margin_top(mut self, margin: f32) -> Self {
 37        self.margin.top = margin;
 38        self
 39    }
 40
 41    pub fn with_margin_left(mut self, margin: f32) -> Self {
 42        self.margin.left = margin;
 43        self
 44    }
 45
 46    pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
 47        self.padding.left = padding;
 48        self.padding.right = padding;
 49        self
 50    }
 51
 52    pub fn with_vertical_padding(mut self, padding: f32) -> Self {
 53        self.padding.top = padding;
 54        self.padding.bottom = padding;
 55        self
 56    }
 57
 58    pub fn with_uniform_padding(mut self, padding: f32) -> Self {
 59        self.padding = Padding {
 60            top: padding,
 61            left: padding,
 62            bottom: padding,
 63            right: padding,
 64        };
 65        self
 66    }
 67
 68    pub fn with_padding_right(mut self, padding: f32) -> Self {
 69        self.padding.right = padding;
 70        self
 71    }
 72
 73    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
 74        self.padding.bottom = padding;
 75        self
 76    }
 77
 78    pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
 79        self.background_color = Some(color.into());
 80        self
 81    }
 82
 83    pub fn with_border(mut self, border: Border) -> Self {
 84        self.border = border;
 85        self
 86    }
 87
 88    pub fn with_corner_radius(mut self, radius: f32) -> Self {
 89        self.corner_radius = radius;
 90        self
 91    }
 92
 93    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
 94        self.shadow = Some(Shadow {
 95            offset,
 96            blur,
 97            color: color.into(),
 98        });
 99        self
100    }
101
102    fn margin_size(&self) -> Vector2F {
103        vec2f(
104            self.margin.left + self.margin.right,
105            self.margin.top + self.margin.bottom,
106        )
107    }
108
109    fn padding_size(&self) -> Vector2F {
110        vec2f(
111            self.padding.left + self.padding.right,
112            self.padding.top + self.padding.bottom,
113        )
114    }
115
116    fn border_size(&self) -> Vector2F {
117        let mut x = 0.0;
118        if self.border.left {
119            x += self.border.width;
120        }
121        if self.border.right {
122            x += self.border.width;
123        }
124
125        let mut y = 0.0;
126        if self.border.top {
127            y += self.border.width;
128        }
129        if self.border.bottom {
130            y += self.border.width;
131        }
132
133        vec2f(x, y)
134    }
135}
136
137impl Element for Container {
138    type LayoutState = ();
139    type PaintState = ();
140
141    fn layout(
142        &mut self,
143        constraint: SizeConstraint,
144        cx: &mut LayoutContext,
145    ) -> (Vector2F, Self::LayoutState) {
146        let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
147        let child_constraint = SizeConstraint {
148            min: (constraint.min - size_buffer).max(Vector2F::zero()),
149            max: (constraint.max - size_buffer).max(Vector2F::zero()),
150        };
151        let child_size = self.child.layout(child_constraint, cx);
152        (child_size + size_buffer, ())
153    }
154
155    fn after_layout(
156        &mut self,
157        _: Vector2F,
158        _: &mut Self::LayoutState,
159        cx: &mut AfterLayoutContext,
160    ) {
161        self.child.after_layout(cx);
162    }
163
164    fn paint(
165        &mut self,
166        bounds: RectF,
167        _: &mut Self::LayoutState,
168        cx: &mut PaintContext,
169    ) -> Self::PaintState {
170        let quad_bounds = RectF::from_points(
171            bounds.origin() + vec2f(self.margin.left, self.margin.top),
172            bounds.lower_right() - vec2f(self.margin.right, self.margin.bottom),
173        );
174
175        if let Some(shadow) = self.shadow.as_ref() {
176            cx.scene.push_shadow(scene::Shadow {
177                bounds: quad_bounds + shadow.offset,
178                corner_radius: self.corner_radius,
179                sigma: shadow.blur,
180                color: shadow.color,
181            });
182        }
183        cx.scene.push_quad(Quad {
184            bounds: quad_bounds,
185            background: self.background_color,
186            border: self.border,
187            corner_radius: self.corner_radius,
188        });
189
190        let child_origin = quad_bounds.origin()
191            + vec2f(self.padding.left, self.padding.top)
192            + vec2f(self.border.left_width(), self.border.top_width());
193        self.child.paint(child_origin, cx);
194    }
195
196    fn dispatch_event(
197        &mut self,
198        event: &Event,
199        _: RectF,
200        _: &mut Self::LayoutState,
201        _: &mut Self::PaintState,
202        cx: &mut EventContext,
203    ) -> bool {
204        self.child.dispatch_event(event, cx)
205    }
206
207    fn debug(
208        &self,
209        bounds: RectF,
210        _: &Self::LayoutState,
211        _: &Self::PaintState,
212        cx: &crate::DebugContext,
213    ) -> serde_json::Value {
214        json!({
215            "type": "Container",
216            "bounds": bounds.to_json(),
217            "details": {
218                "margin": self.margin.to_json(),
219                "padding": self.padding.to_json(),
220                "background_color": self.background_color.to_json(),
221                "border": self.border.to_json(),
222                "corner_radius": self.corner_radius,
223                "shadow": self.shadow.to_json(),
224            },
225            "child": self.child.debug(cx),
226        })
227    }
228}
229
230#[derive(Default)]
231pub struct Margin {
232    top: f32,
233    left: f32,
234    bottom: f32,
235    right: f32,
236}
237
238impl ToJson for Margin {
239    fn to_json(&self) -> serde_json::Value {
240        let mut value = json!({});
241        if self.top > 0. {
242            value["top"] = json!(self.top);
243        }
244        if self.right > 0. {
245            value["right"] = json!(self.right);
246        }
247        if self.bottom > 0. {
248            value["bottom"] = json!(self.bottom);
249        }
250        if self.left > 0. {
251            value["left"] = json!(self.left);
252        }
253        value
254    }
255}
256
257#[derive(Default)]
258pub struct Padding {
259    top: f32,
260    left: f32,
261    bottom: f32,
262    right: f32,
263}
264
265impl ToJson for Padding {
266    fn to_json(&self) -> serde_json::Value {
267        let mut value = json!({});
268        if self.top > 0. {
269            value["top"] = json!(self.top);
270        }
271        if self.right > 0. {
272            value["right"] = json!(self.right);
273        }
274        if self.bottom > 0. {
275            value["bottom"] = json!(self.bottom);
276        }
277        if self.left > 0. {
278            value["left"] = json!(self.left);
279        }
280        value
281    }
282}
283
284#[derive(Default)]
285pub struct Shadow {
286    offset: Vector2F,
287    blur: f32,
288    color: ColorU,
289}
290
291impl ToJson for Shadow {
292    fn to_json(&self) -> serde_json::Value {
293        json!({
294            "offset": self.offset.to_json(),
295            "blur": self.blur,
296            "color": self.color.to_json()
297        })
298    }
299}