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