container.rs

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