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