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