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