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, Event, EventContext, 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 dispatch_event(
289        &mut self,
290        event: &Event,
291        _: RectF,
292        _: RectF,
293        _: &mut Self::LayoutState,
294        _: &mut Self::PaintState,
295        cx: &mut EventContext,
296    ) -> bool {
297        self.child.dispatch_event(event, cx)
298    }
299
300    fn rect_for_text_range(
301        &self,
302        range_utf16: Range<usize>,
303        _: RectF,
304        _: RectF,
305        _: &Self::LayoutState,
306        _: &Self::PaintState,
307        cx: &MeasurementContext,
308    ) -> Option<RectF> {
309        self.child.rect_for_text_range(range_utf16, cx)
310    }
311
312    fn debug(
313        &self,
314        bounds: RectF,
315        _: &Self::LayoutState,
316        _: &Self::PaintState,
317        cx: &crate::DebugContext,
318    ) -> serde_json::Value {
319        json!({
320            "type": "Container",
321            "bounds": bounds.to_json(),
322            "details": self.style.to_json(),
323            "child": self.child.debug(cx),
324        })
325    }
326}
327
328impl ToJson for ContainerStyle {
329    fn to_json(&self) -> serde_json::Value {
330        json!({
331            "margin": self.margin.to_json(),
332            "padding": self.padding.to_json(),
333            "background_color": self.background_color.to_json(),
334            "border": self.border.to_json(),
335            "corner_radius": self.corner_radius,
336            "shadow": self.shadow.to_json(),
337        })
338    }
339}
340
341#[derive(Clone, Copy, Debug, Default)]
342pub struct Margin {
343    pub top: f32,
344    pub left: f32,
345    pub bottom: f32,
346    pub right: f32,
347}
348
349impl ToJson for Margin {
350    fn to_json(&self) -> serde_json::Value {
351        let mut value = json!({});
352        if self.top > 0. {
353            value["top"] = json!(self.top);
354        }
355        if self.right > 0. {
356            value["right"] = json!(self.right);
357        }
358        if self.bottom > 0. {
359            value["bottom"] = json!(self.bottom);
360        }
361        if self.left > 0. {
362            value["left"] = json!(self.left);
363        }
364        value
365    }
366}
367
368#[derive(Clone, Copy, Debug, Default)]
369pub struct Padding {
370    pub top: f32,
371    pub left: f32,
372    pub bottom: f32,
373    pub right: f32,
374}
375
376impl<'de> Deserialize<'de> for Padding {
377    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
378    where
379        D: serde::Deserializer<'de>,
380    {
381        let spacing = Spacing::deserialize(deserializer)?;
382        Ok(match spacing {
383            Spacing::Uniform(size) => Padding {
384                top: size,
385                left: size,
386                bottom: size,
387                right: size,
388            },
389            Spacing::Specific {
390                top,
391                left,
392                bottom,
393                right,
394            } => Padding {
395                top,
396                left,
397                bottom,
398                right,
399            },
400        })
401    }
402}
403
404impl<'de> Deserialize<'de> for Margin {
405    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
406    where
407        D: serde::Deserializer<'de>,
408    {
409        let spacing = Spacing::deserialize(deserializer)?;
410        Ok(match spacing {
411            Spacing::Uniform(size) => Margin {
412                top: size,
413                left: size,
414                bottom: size,
415                right: size,
416            },
417            Spacing::Specific {
418                top,
419                left,
420                bottom,
421                right,
422            } => Margin {
423                top,
424                left,
425                bottom,
426                right,
427            },
428        })
429    }
430}
431#[derive(Deserialize)]
432#[serde(untagged)]
433enum Spacing {
434    Uniform(f32),
435    Specific {
436        #[serde(default)]
437        top: f32,
438        #[serde(default)]
439        left: f32,
440        #[serde(default)]
441        bottom: f32,
442        #[serde(default)]
443        right: f32,
444    },
445}
446
447impl Padding {
448    pub fn uniform(padding: f32) -> Self {
449        Self {
450            top: padding,
451            left: padding,
452            bottom: padding,
453            right: padding,
454        }
455    }
456}
457
458impl ToJson for Padding {
459    fn to_json(&self) -> serde_json::Value {
460        let mut value = json!({});
461        if self.top > 0. {
462            value["top"] = json!(self.top);
463        }
464        if self.right > 0. {
465            value["right"] = json!(self.right);
466        }
467        if self.bottom > 0. {
468            value["bottom"] = json!(self.bottom);
469        }
470        if self.left > 0. {
471            value["left"] = json!(self.left);
472        }
473        value
474    }
475}
476
477#[derive(Clone, Copy, Debug, Default, Deserialize)]
478pub struct Shadow {
479    #[serde(default, deserialize_with = "deserialize_vec2f")]
480    offset: Vector2F,
481    #[serde(default)]
482    blur: f32,
483    #[serde(default)]
484    color: Color,
485}
486
487impl ToJson for Shadow {
488    fn to_json(&self) -> serde_json::Value {
489        json!({
490            "offset": self.offset.to_json(),
491            "blur": self.blur,
492            "color": self.color.to_json()
493        })
494    }
495}