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