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