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_uniform_padding(mut self, padding: f32) -> Self {
 47        self.padding = Padding {
 48            top: padding,
 49            left: padding,
 50            bottom: padding,
 51            right: padding,
 52        };
 53        self
 54    }
 55
 56    pub fn with_padding_right(mut self, padding: f32) -> Self {
 57        self.padding.right = padding;
 58        self
 59    }
 60
 61    pub fn with_padding_bottom(mut self, padding: f32) -> Self {
 62        self.padding.bottom = padding;
 63        self
 64    }
 65
 66    pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
 67        self.background_color = Some(color.into());
 68        self
 69    }
 70
 71    pub fn with_border(mut self, border: Border) -> Self {
 72        self.border = border;
 73        self
 74    }
 75
 76    pub fn with_corner_radius(mut self, radius: f32) -> Self {
 77        self.corner_radius = radius;
 78        self
 79    }
 80
 81    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
 82        self.shadow = Some(Shadow {
 83            offset,
 84            blur,
 85            color: color.into(),
 86        });
 87        self
 88    }
 89
 90    fn margin_size(&self) -> Vector2F {
 91        vec2f(
 92            self.margin.left + self.margin.right,
 93            self.margin.top + self.margin.bottom,
 94        )
 95    }
 96
 97    fn padding_size(&self) -> Vector2F {
 98        vec2f(
 99            self.padding.left + self.padding.right,
100            self.padding.top + self.padding.bottom,
101        )
102    }
103
104    fn border_size(&self) -> Vector2F {
105        let mut x = 0.0;
106        if self.border.left {
107            x += self.border.width;
108        }
109        if self.border.right {
110            x += self.border.width;
111        }
112
113        let mut y = 0.0;
114        if self.border.top {
115            y += self.border.width;
116        }
117        if self.border.bottom {
118            y += self.border.width;
119        }
120
121        vec2f(x, y)
122    }
123}
124
125impl Element for Container {
126    type LayoutState = ();
127    type PaintState = ();
128
129    fn layout(
130        &mut self,
131        constraint: SizeConstraint,
132        ctx: &mut LayoutContext,
133    ) -> (Vector2F, Self::LayoutState) {
134        let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
135        let child_constraint = SizeConstraint {
136            min: (constraint.min - size_buffer).max(Vector2F::zero()),
137            max: (constraint.max - size_buffer).max(Vector2F::zero()),
138        };
139        let child_size = self.child.layout(child_constraint, ctx);
140        (child_size + size_buffer, ())
141    }
142
143    fn after_layout(
144        &mut self,
145        _: Vector2F,
146        _: &mut Self::LayoutState,
147        ctx: &mut AfterLayoutContext,
148    ) {
149        self.child.after_layout(ctx);
150    }
151
152    fn paint(
153        &mut self,
154        bounds: RectF,
155        _: &mut Self::LayoutState,
156        ctx: &mut PaintContext,
157    ) -> Self::PaintState {
158        let quad_bounds = RectF::from_points(
159            bounds.origin() + vec2f(self.margin.left, self.margin.top),
160            bounds.lower_right() - vec2f(self.margin.right, self.margin.bottom),
161        );
162
163        if let Some(shadow) = self.shadow.as_ref() {
164            ctx.scene.push_shadow(scene::Shadow {
165                bounds: quad_bounds + shadow.offset,
166                corner_radius: self.corner_radius,
167                sigma: shadow.blur,
168                color: shadow.color,
169            });
170        }
171        ctx.scene.push_quad(Quad {
172            bounds: quad_bounds,
173            background: self.background_color,
174            border: self.border,
175            corner_radius: self.corner_radius,
176        });
177
178        let child_origin = quad_bounds.origin()
179            + vec2f(self.padding.left, self.padding.top)
180            + vec2f(self.border.left_width(), self.border.top_width());
181        self.child.paint(child_origin, ctx);
182    }
183
184    fn dispatch_event(
185        &mut self,
186        event: &Event,
187        _: RectF,
188        _: &mut Self::LayoutState,
189        _: &mut Self::PaintState,
190        ctx: &mut EventContext,
191    ) -> bool {
192        self.child.dispatch_event(event, ctx)
193    }
194
195    fn debug(
196        &self,
197        bounds: RectF,
198        _: &Self::LayoutState,
199        _: &Self::PaintState,
200        ctx: &crate::DebugContext,
201    ) -> serde_json::Value {
202        json!({
203            "type": "Container",
204            "bounds": bounds.to_json(),
205            "details": {
206                "margin": self.margin.to_json(),
207                "padding": self.padding.to_json(),
208                "background_color": self.background_color.to_json(),
209                "border": self.border.to_json(),
210                "corner_radius": self.corner_radius,
211                "shadow": self.shadow.to_json(),
212            },
213            "child": self.child.debug(ctx),
214        })
215    }
216}
217
218#[derive(Default)]
219pub struct Margin {
220    top: f32,
221    left: f32,
222    bottom: f32,
223    right: f32,
224}
225
226impl ToJson for Margin {
227    fn to_json(&self) -> serde_json::Value {
228        let mut value = json!({});
229        if self.top > 0. {
230            value["top"] = json!(self.top);
231        }
232        if self.right > 0. {
233            value["right"] = json!(self.right);
234        }
235        if self.bottom > 0. {
236            value["bottom"] = json!(self.bottom);
237        }
238        if self.left > 0. {
239            value["left"] = json!(self.left);
240        }
241        value
242    }
243}
244
245#[derive(Default)]
246pub struct Padding {
247    top: f32,
248    left: f32,
249    bottom: f32,
250    right: f32,
251}
252
253impl ToJson for Padding {
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(Default)]
273pub struct Shadow {
274    offset: Vector2F,
275    blur: f32,
276    color: ColorU,
277}
278
279impl ToJson for Shadow {
280    fn to_json(&self) -> serde_json::Value {
281        json!({
282            "offset": self.offset.to_json(),
283            "blur": self.blur,
284            "color": self.color.to_json()
285        })
286    }
287}