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