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