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