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