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