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 mut size_buffer = self.margin_size() + self.padding_size();
171 if !self.style.border.overlay {
172 size_buffer += self.border_size();
173 }
174 let child_constraint = SizeConstraint {
175 min: (constraint.min - size_buffer).max(Vector2F::zero()),
176 max: (constraint.max - size_buffer).max(Vector2F::zero()),
177 };
178 let child_size = self.child.layout(child_constraint, cx);
179 (child_size + size_buffer, ())
180 }
181
182 fn paint(
183 &mut self,
184 bounds: RectF,
185 visible_bounds: RectF,
186 _: &mut Self::LayoutState,
187 cx: &mut PaintContext,
188 ) -> Self::PaintState {
189 let quad_bounds = RectF::from_points(
190 bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
191 bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
192 );
193
194 if let Some(shadow) = self.style.shadow.as_ref() {
195 cx.scene.push_shadow(scene::Shadow {
196 bounds: quad_bounds + shadow.offset,
197 corner_radius: self.style.corner_radius,
198 sigma: shadow.blur,
199 color: shadow.color,
200 });
201 }
202
203 let child_origin =
204 quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
205
206 if self.style.border.overlay {
207 cx.scene.push_quad(Quad {
208 bounds: quad_bounds,
209 background: self.style.background_color,
210 border: Default::default(),
211 corner_radius: self.style.corner_radius,
212 });
213
214 self.child.paint(child_origin, visible_bounds, cx);
215
216 cx.scene.push_layer(None);
217 cx.scene.push_quad(Quad {
218 bounds: quad_bounds,
219 background: Default::default(),
220 border: self.style.border,
221 corner_radius: self.style.corner_radius,
222 });
223 cx.scene.pop_layer();
224 } else {
225 cx.scene.push_quad(Quad {
226 bounds: quad_bounds,
227 background: self.style.background_color,
228 border: self.style.border,
229 corner_radius: self.style.corner_radius,
230 });
231
232 let child_origin = child_origin
233 + vec2f(
234 self.style.border.left_width(),
235 self.style.border.top_width(),
236 );
237 self.child.paint(child_origin, visible_bounds, cx);
238 }
239 }
240
241 fn dispatch_event(
242 &mut self,
243 event: &Event,
244 _: RectF,
245 _: &mut Self::LayoutState,
246 _: &mut Self::PaintState,
247 cx: &mut EventContext,
248 ) -> bool {
249 self.child.dispatch_event(event, cx)
250 }
251
252 fn debug(
253 &self,
254 bounds: RectF,
255 _: &Self::LayoutState,
256 _: &Self::PaintState,
257 cx: &crate::DebugContext,
258 ) -> serde_json::Value {
259 json!({
260 "type": "Container",
261 "bounds": bounds.to_json(),
262 "details": self.style.to_json(),
263 "child": self.child.debug(cx),
264 })
265 }
266}
267
268impl ToJson for ContainerStyle {
269 fn to_json(&self) -> serde_json::Value {
270 json!({
271 "margin": self.margin.to_json(),
272 "padding": self.padding.to_json(),
273 "background_color": self.background_color.to_json(),
274 "border": self.border.to_json(),
275 "corner_radius": self.corner_radius,
276 "shadow": self.shadow.to_json(),
277 })
278 }
279}
280
281#[derive(Clone, Copy, Debug, Default)]
282pub struct Margin {
283 pub top: f32,
284 pub left: f32,
285 pub bottom: f32,
286 pub right: f32,
287}
288
289impl ToJson for Margin {
290 fn to_json(&self) -> serde_json::Value {
291 let mut value = json!({});
292 if self.top > 0. {
293 value["top"] = json!(self.top);
294 }
295 if self.right > 0. {
296 value["right"] = json!(self.right);
297 }
298 if self.bottom > 0. {
299 value["bottom"] = json!(self.bottom);
300 }
301 if self.left > 0. {
302 value["left"] = json!(self.left);
303 }
304 value
305 }
306}
307
308#[derive(Clone, Copy, Debug, Default)]
309pub struct Padding {
310 pub top: f32,
311 pub left: f32,
312 pub bottom: f32,
313 pub right: f32,
314}
315
316impl<'de> Deserialize<'de> for Padding {
317 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318 where
319 D: serde::Deserializer<'de>,
320 {
321 let spacing = Spacing::deserialize(deserializer)?;
322 Ok(match spacing {
323 Spacing::Uniform(size) => Padding {
324 top: size,
325 left: size,
326 bottom: size,
327 right: size,
328 },
329 Spacing::Specific {
330 top,
331 left,
332 bottom,
333 right,
334 } => Padding {
335 top,
336 left,
337 bottom,
338 right,
339 },
340 })
341 }
342}
343
344impl<'de> Deserialize<'de> for Margin {
345 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
346 where
347 D: serde::Deserializer<'de>,
348 {
349 let spacing = Spacing::deserialize(deserializer)?;
350 Ok(match spacing {
351 Spacing::Uniform(size) => Margin {
352 top: size,
353 left: size,
354 bottom: size,
355 right: size,
356 },
357 Spacing::Specific {
358 top,
359 left,
360 bottom,
361 right,
362 } => Margin {
363 top,
364 left,
365 bottom,
366 right,
367 },
368 })
369 }
370}
371#[derive(Deserialize)]
372#[serde(untagged)]
373enum Spacing {
374 Uniform(f32),
375 Specific {
376 #[serde(default)]
377 top: f32,
378 #[serde(default)]
379 left: f32,
380 #[serde(default)]
381 bottom: f32,
382 #[serde(default)]
383 right: f32,
384 },
385}
386
387impl Padding {
388 pub fn uniform(padding: f32) -> Self {
389 Self {
390 top: padding,
391 left: padding,
392 bottom: padding,
393 right: padding,
394 }
395 }
396}
397
398impl ToJson for Padding {
399 fn to_json(&self) -> serde_json::Value {
400 let mut value = json!({});
401 if self.top > 0. {
402 value["top"] = json!(self.top);
403 }
404 if self.right > 0. {
405 value["right"] = json!(self.right);
406 }
407 if self.bottom > 0. {
408 value["bottom"] = json!(self.bottom);
409 }
410 if self.left > 0. {
411 value["left"] = json!(self.left);
412 }
413 value
414 }
415}
416
417#[derive(Clone, Copy, Debug, Default, Deserialize)]
418pub struct Shadow {
419 #[serde(default, deserialize_with = "deserialize_vec2f")]
420 offset: Vector2F,
421 #[serde(default)]
422 blur: f32,
423 #[serde(default)]
424 color: Color,
425}
426
427impl ToJson for Shadow {
428 fn to_json(&self) -> serde_json::Value {
429 json!({
430 "offset": self.offset.to_json(),
431 "blur": self.blur,
432 "color": self.color.to_json()
433 })
434 }
435}