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