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