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}