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