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(hit_bounds) = quad_bounds.intersection(visible_bounds) {
216 if let Some(style) = self.style.cursor {
217 cx.scene.push_cursor_region(CursorRegion {
218 bounds: hit_bounds,
219 style,
220 });
221 }
222 }
223
224 let child_origin =
225 quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
226
227 if self.style.border.overlay {
228 cx.scene.push_quad(Quad {
229 bounds: quad_bounds,
230 background: self.style.background_color,
231 border: Default::default(),
232 corner_radius: self.style.corner_radius,
233 });
234
235 self.child.paint(child_origin, visible_bounds, cx);
236
237 cx.scene.push_layer(None);
238 cx.scene.push_quad(Quad {
239 bounds: quad_bounds,
240 background: Default::default(),
241 border: self.style.border,
242 corner_radius: self.style.corner_radius,
243 });
244 cx.scene.pop_layer();
245 } else {
246 cx.scene.push_quad(Quad {
247 bounds: quad_bounds,
248 background: self.style.background_color,
249 border: self.style.border,
250 corner_radius: self.style.corner_radius,
251 });
252
253 let child_origin = child_origin
254 + vec2f(
255 self.style.border.left_width(),
256 self.style.border.top_width(),
257 );
258 self.child.paint(child_origin, visible_bounds, cx);
259 }
260 }
261
262 fn dispatch_event(
263 &mut self,
264 event: &Event,
265 _: RectF,
266 _: RectF,
267 _: &mut Self::LayoutState,
268 _: &mut Self::PaintState,
269 cx: &mut EventContext,
270 ) -> bool {
271 self.child.dispatch_event(event, cx)
272 }
273
274 fn debug(
275 &self,
276 bounds: RectF,
277 _: &Self::LayoutState,
278 _: &Self::PaintState,
279 cx: &crate::DebugContext,
280 ) -> serde_json::Value {
281 json!({
282 "type": "Container",
283 "bounds": bounds.to_json(),
284 "details": self.style.to_json(),
285 "child": self.child.debug(cx),
286 })
287 }
288}
289
290impl ToJson for ContainerStyle {
291 fn to_json(&self) -> serde_json::Value {
292 json!({
293 "margin": self.margin.to_json(),
294 "padding": self.padding.to_json(),
295 "background_color": self.background_color.to_json(),
296 "border": self.border.to_json(),
297 "corner_radius": self.corner_radius,
298 "shadow": self.shadow.to_json(),
299 })
300 }
301}
302
303#[derive(Clone, Copy, Debug, Default)]
304pub struct Margin {
305 pub top: f32,
306 pub left: f32,
307 pub bottom: f32,
308 pub right: f32,
309}
310
311impl ToJson for Margin {
312 fn to_json(&self) -> serde_json::Value {
313 let mut value = json!({});
314 if self.top > 0. {
315 value["top"] = json!(self.top);
316 }
317 if self.right > 0. {
318 value["right"] = json!(self.right);
319 }
320 if self.bottom > 0. {
321 value["bottom"] = json!(self.bottom);
322 }
323 if self.left > 0. {
324 value["left"] = json!(self.left);
325 }
326 value
327 }
328}
329
330#[derive(Clone, Copy, Debug, Default)]
331pub struct Padding {
332 pub top: f32,
333 pub left: f32,
334 pub bottom: f32,
335 pub right: f32,
336}
337
338impl<'de> Deserialize<'de> for Padding {
339 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
340 where
341 D: serde::Deserializer<'de>,
342 {
343 let spacing = Spacing::deserialize(deserializer)?;
344 Ok(match spacing {
345 Spacing::Uniform(size) => Padding {
346 top: size,
347 left: size,
348 bottom: size,
349 right: size,
350 },
351 Spacing::Specific {
352 top,
353 left,
354 bottom,
355 right,
356 } => Padding {
357 top,
358 left,
359 bottom,
360 right,
361 },
362 })
363 }
364}
365
366impl<'de> Deserialize<'de> for Margin {
367 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
368 where
369 D: serde::Deserializer<'de>,
370 {
371 let spacing = Spacing::deserialize(deserializer)?;
372 Ok(match spacing {
373 Spacing::Uniform(size) => Margin {
374 top: size,
375 left: size,
376 bottom: size,
377 right: size,
378 },
379 Spacing::Specific {
380 top,
381 left,
382 bottom,
383 right,
384 } => Margin {
385 top,
386 left,
387 bottom,
388 right,
389 },
390 })
391 }
392}
393#[derive(Deserialize)]
394#[serde(untagged)]
395enum Spacing {
396 Uniform(f32),
397 Specific {
398 #[serde(default)]
399 top: f32,
400 #[serde(default)]
401 left: f32,
402 #[serde(default)]
403 bottom: f32,
404 #[serde(default)]
405 right: f32,
406 },
407}
408
409impl Padding {
410 pub fn uniform(padding: f32) -> Self {
411 Self {
412 top: padding,
413 left: padding,
414 bottom: padding,
415 right: padding,
416 }
417 }
418}
419
420impl ToJson for Padding {
421 fn to_json(&self) -> serde_json::Value {
422 let mut value = json!({});
423 if self.top > 0. {
424 value["top"] = json!(self.top);
425 }
426 if self.right > 0. {
427 value["right"] = json!(self.right);
428 }
429 if self.bottom > 0. {
430 value["bottom"] = json!(self.bottom);
431 }
432 if self.left > 0. {
433 value["left"] = json!(self.left);
434 }
435 value
436 }
437}
438
439#[derive(Clone, Copy, Debug, Default, Deserialize)]
440pub struct Shadow {
441 #[serde(default, deserialize_with = "deserialize_vec2f")]
442 offset: Vector2F,
443 #[serde(default)]
444 blur: f32,
445 #[serde(default)]
446 color: Color,
447}
448
449impl ToJson for Shadow {
450 fn to_json(&self) -> serde_json::Value {
451 json!({
452 "offset": self.offset.to_json(),
453 "blur": self.blur,
454 "color": self.color.to_json()
455 })
456 }
457}