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