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 gen::SchemaGenerator,
17 schema::{InstanceType, Schema, SchemaObject},
18 JsonSchema,
19};
20use serde::Deserialize;
21use serde_json::json;
22
23#[derive(Clone, Copy, Debug, Default, Deserialize)]
24pub struct ContainerStyle {
25 #[serde(default)]
26 pub margin: Margin,
27 #[serde(default)]
28 pub padding: Padding,
29 #[serde(rename = "background")]
30 pub background_color: Option<Color>,
31 #[serde(rename = "overlay")]
32 pub overlay_color: Option<Color>,
33 #[serde(default)]
34 pub border: Border,
35 #[serde(default)]
36 pub corner_radius: f32,
37 #[serde(default)]
38 pub shadow: Option<Shadow>,
39 #[serde(default)]
40 pub cursor: Option<CursorStyle>,
41}
42
43pub struct Container<V: View> {
44 child: AnyElement<V>,
45 style: ContainerStyle,
46}
47
48impl<V: View> Container<V> {
49 pub fn new(child: AnyElement<V>) -> Self {
50 Self {
51 child,
52 style: Default::default(),
53 }
54 }
55
56 pub fn with_style(mut self, style: ContainerStyle) -> Self {
57 self.style = style;
58 self
59 }
60
61 pub fn with_margin_top(mut self, margin: f32) -> Self {
62 self.style.margin.top = margin;
63 self
64 }
65
66 pub fn with_margin_bottom(mut self, margin: f32) -> Self {
67 self.style.margin.bottom = margin;
68 self
69 }
70
71 pub fn with_margin_left(mut self, margin: f32) -> Self {
72 self.style.margin.left = margin;
73 self
74 }
75
76 pub fn with_margin_right(mut self, margin: f32) -> Self {
77 self.style.margin.right = margin;
78 self
79 }
80
81 pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
82 self.style.padding.left = padding;
83 self.style.padding.right = padding;
84 self
85 }
86
87 pub fn with_vertical_padding(mut self, padding: f32) -> Self {
88 self.style.padding.top = padding;
89 self.style.padding.bottom = padding;
90 self
91 }
92
93 pub fn with_uniform_padding(mut self, padding: f32) -> Self {
94 self.style.padding = Padding {
95 top: padding,
96 left: padding,
97 bottom: padding,
98 right: padding,
99 };
100 self
101 }
102
103 pub fn with_padding_left(mut self, padding: f32) -> Self {
104 self.style.padding.left = padding;
105 self
106 }
107
108 pub fn with_padding_right(mut self, padding: f32) -> Self {
109 self.style.padding.right = padding;
110 self
111 }
112
113 pub fn with_padding_top(mut self, padding: f32) -> Self {
114 self.style.padding.top = padding;
115 self
116 }
117
118 pub fn with_padding_bottom(mut self, padding: f32) -> Self {
119 self.style.padding.bottom = padding;
120 self
121 }
122
123 pub fn with_background_color(mut self, color: Color) -> Self {
124 self.style.background_color = Some(color);
125 self
126 }
127
128 pub fn with_overlay_color(mut self, color: Color) -> Self {
129 self.style.overlay_color = Some(color);
130 self
131 }
132
133 pub fn with_border(mut self, border: Border) -> Self {
134 self.style.border = border;
135 self
136 }
137
138 pub fn with_corner_radius(mut self, radius: f32) -> Self {
139 self.style.corner_radius = 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: View> 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 ViewContext<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_radius: self.style.corner_radius,
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_radius: self.style.corner_radius,
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_radius: self.style.corner_radius,
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_radius: self.style.corner_radius,
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_radius: 0.,
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_radius,
335 "shadow": self.shadow.to_json(),
336 })
337 }
338}
339
340impl JsonSchema for ContainerStyle {
341 fn schema_name() -> String {
342 "ContainerStyle".into()
343 }
344
345 fn json_schema(_: &mut SchemaGenerator) -> Schema {
346 let mut schema = SchemaObject::default();
347 schema.instance_type = Some(InstanceType::Integer.into());
348 schema.format = Some("uint".to_owned());
349 Schema::Object(schema)
350 }
351}
352
353#[derive(Clone, Copy, Debug, Default)]
354pub struct Margin {
355 pub top: f32,
356 pub left: f32,
357 pub bottom: f32,
358 pub right: f32,
359}
360
361impl ToJson for Margin {
362 fn to_json(&self) -> serde_json::Value {
363 let mut value = json!({});
364 if self.top > 0. {
365 value["top"] = json!(self.top);
366 }
367 if self.right > 0. {
368 value["right"] = json!(self.right);
369 }
370 if self.bottom > 0. {
371 value["bottom"] = json!(self.bottom);
372 }
373 if self.left > 0. {
374 value["left"] = json!(self.left);
375 }
376 value
377 }
378}
379
380#[derive(Clone, Copy, Debug, Default)]
381pub struct Padding {
382 pub top: f32,
383 pub left: f32,
384 pub bottom: f32,
385 pub right: f32,
386}
387
388impl Padding {
389 pub fn horizontal(padding: f32) -> Self {
390 Self {
391 left: padding,
392 right: padding,
393 ..Default::default()
394 }
395 }
396
397 pub fn vertical(padding: f32) -> Self {
398 Self {
399 top: padding,
400 bottom: padding,
401 ..Default::default()
402 }
403 }
404}
405
406impl<'de> Deserialize<'de> for Padding {
407 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
408 where
409 D: serde::Deserializer<'de>,
410 {
411 let spacing = Spacing::deserialize(deserializer)?;
412 Ok(match spacing {
413 Spacing::Uniform(size) => Padding {
414 top: size,
415 left: size,
416 bottom: size,
417 right: size,
418 },
419 Spacing::Specific {
420 top,
421 left,
422 bottom,
423 right,
424 } => Padding {
425 top,
426 left,
427 bottom,
428 right,
429 },
430 })
431 }
432}
433
434impl<'de> Deserialize<'de> for Margin {
435 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
436 where
437 D: serde::Deserializer<'de>,
438 {
439 let spacing = Spacing::deserialize(deserializer)?;
440 Ok(match spacing {
441 Spacing::Uniform(size) => Margin {
442 top: size,
443 left: size,
444 bottom: size,
445 right: size,
446 },
447 Spacing::Specific {
448 top,
449 left,
450 bottom,
451 right,
452 } => Margin {
453 top,
454 left,
455 bottom,
456 right,
457 },
458 })
459 }
460}
461#[derive(Deserialize)]
462#[serde(untagged)]
463enum Spacing {
464 Uniform(f32),
465 Specific {
466 #[serde(default)]
467 top: f32,
468 #[serde(default)]
469 left: f32,
470 #[serde(default)]
471 bottom: f32,
472 #[serde(default)]
473 right: f32,
474 },
475}
476
477impl Padding {
478 pub fn uniform(padding: f32) -> Self {
479 Self {
480 top: padding,
481 left: padding,
482 bottom: padding,
483 right: padding,
484 }
485 }
486}
487
488impl ToJson for Padding {
489 fn to_json(&self) -> serde_json::Value {
490 let mut value = json!({});
491 if self.top > 0. {
492 value["top"] = json!(self.top);
493 }
494 if self.right > 0. {
495 value["right"] = json!(self.right);
496 }
497 if self.bottom > 0. {
498 value["bottom"] = json!(self.bottom);
499 }
500 if self.left > 0. {
501 value["left"] = json!(self.left);
502 }
503 value
504 }
505}
506
507#[derive(Clone, Copy, Debug, Default, Deserialize)]
508pub struct Shadow {
509 #[serde(default, deserialize_with = "deserialize_vec2f")]
510 offset: Vector2F,
511 #[serde(default)]
512 blur: f32,
513 #[serde(default)]
514 color: Color,
515}
516
517impl ToJson for Shadow {
518 fn to_json(&self) -> serde_json::Value {
519 json!({
520 "offset": self.offset.to_json(),
521 "blur": self.blur,
522 "color": self.color.to_json()
523 })
524 }
525}