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, ViewContext,
 14};
 15use schemars::JsonSchema;
 16use serde::Deserialize;
 17use serde_json::json;
 18
 19#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
 20pub struct ContainerStyle {
 21    #[serde(default)]
 22    pub margin: Margin,
 23    #[serde(default)]
 24    pub padding: Padding,
 25    #[serde(rename = "background")]
 26    pub background_color: Option<Color>,
 27    #[serde(rename = "overlay")]
 28    pub overlay_color: Option<Color>,
 29    #[serde(default)]
 30    pub border: Border,
 31    #[serde(default)]
 32    #[serde(alias = "corner_radius")]
 33    pub corner_radii: CornerRadii,
 34    #[serde(default)]
 35    pub shadow: Option<Shadow>,
 36    #[serde(default)]
 37    pub cursor: Option<CursorStyle>,
 38}
 39
 40pub struct Container<V> {
 41    child: AnyElement<V>,
 42    style: ContainerStyle,
 43}
 44
 45impl<V> 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_radii.top_left = radius;
137        self.style.corner_radii.top_right = radius;
138        self.style.corner_radii.bottom_right = radius;
139        self.style.corner_radii.bottom_left = 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: 'static> 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 PaintContext<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_radii: self.style.corner_radii,
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_radii: self.style.corner_radii.into(),
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_radii: self.style.corner_radii.into(),
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_radii: self.style.corner_radii.into(),
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_radii: self.style.corner_radii.into(),
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_radii,
335            "shadow": self.shadow.to_json(),
336        })
337    }
338}
339
340#[derive(Clone, Copy, Debug, Default, JsonSchema)]
341pub struct Margin {
342    pub top: f32,
343    pub bottom: f32,
344    pub left: f32,
345    pub right: f32,
346}
347
348impl ToJson for Margin {
349    fn to_json(&self) -> serde_json::Value {
350        let mut value = json!({});
351        if self.top > 0. {
352            value["top"] = json!(self.top);
353        }
354        if self.right > 0. {
355            value["right"] = json!(self.right);
356        }
357        if self.bottom > 0. {
358            value["bottom"] = json!(self.bottom);
359        }
360        if self.left > 0. {
361            value["left"] = json!(self.left);
362        }
363        value
364    }
365}
366
367#[derive(Clone, Copy, Debug, Default, JsonSchema)]
368pub struct Padding {
369    pub top: f32,
370    pub left: f32,
371    pub bottom: f32,
372    pub right: f32,
373}
374
375impl Padding {
376    pub fn horizontal(padding: f32) -> Self {
377        Self {
378            left: padding,
379            right: padding,
380            ..Default::default()
381        }
382    }
383
384    pub fn vertical(padding: f32) -> Self {
385        Self {
386            top: padding,
387            bottom: padding,
388            ..Default::default()
389        }
390    }
391}
392
393impl<'de> Deserialize<'de> for Padding {
394    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
395    where
396        D: serde::Deserializer<'de>,
397    {
398        let spacing = Spacing::deserialize(deserializer)?;
399        Ok(match spacing {
400            Spacing::Uniform(size) => Padding {
401                top: size,
402                left: size,
403                bottom: size,
404                right: size,
405            },
406            Spacing::Specific {
407                top,
408                left,
409                bottom,
410                right,
411            } => Padding {
412                top,
413                left,
414                bottom,
415                right,
416            },
417        })
418    }
419}
420
421impl<'de> Deserialize<'de> for Margin {
422    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
423    where
424        D: serde::Deserializer<'de>,
425    {
426        let spacing = Spacing::deserialize(deserializer)?;
427        Ok(match spacing {
428            Spacing::Uniform(size) => Margin {
429                top: size,
430                left: size,
431                bottom: size,
432                right: size,
433            },
434            Spacing::Specific {
435                top,
436                left,
437                bottom,
438                right,
439            } => Margin {
440                top,
441                left,
442                bottom,
443                right,
444            },
445        })
446    }
447}
448#[derive(Deserialize)]
449#[serde(untagged)]
450enum Spacing {
451    Uniform(f32),
452    Specific {
453        #[serde(default)]
454        top: f32,
455        #[serde(default)]
456        left: f32,
457        #[serde(default)]
458        bottom: f32,
459        #[serde(default)]
460        right: f32,
461    },
462}
463
464impl Padding {
465    pub fn uniform(padding: f32) -> Self {
466        Self {
467            top: padding,
468            left: padding,
469            bottom: padding,
470            right: padding,
471        }
472    }
473}
474
475impl ToJson for Padding {
476    fn to_json(&self) -> serde_json::Value {
477        let mut value = json!({});
478        if self.top > 0. {
479            value["top"] = json!(self.top);
480        }
481        if self.right > 0. {
482            value["right"] = json!(self.right);
483        }
484        if self.bottom > 0. {
485            value["bottom"] = json!(self.bottom);
486        }
487        if self.left > 0. {
488            value["left"] = json!(self.left);
489        }
490        value
491    }
492}
493
494#[derive(Clone, Copy, Debug, Default, Deserialize, JsonSchema)]
495pub struct Shadow {
496    #[serde(default, deserialize_with = "deserialize_vec2f")]
497    #[schemars(with = "Vec::<f32>")]
498    offset: Vector2F,
499    #[serde(default)]
500    blur: f32,
501    #[serde(default)]
502    color: Color,
503}
504
505impl ToJson for Shadow {
506    fn to_json(&self) -> serde_json::Value {
507        json!({
508            "offset": self.offset.to_json(),
509            "blur": self.blur,
510            "color": self.color.to_json()
511        })
512    }
513}