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