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