Skip to main content

fission_core/ui/widgets/
container.rs

1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use crate::ui::Widget;
4use fission_ir::{
5    op::{BoxShadow, Color, Fill, LayoutOp, Op, PaintOp, Stroke},
6    WidgetId,
7};
8use serde::{Deserialize, Serialize};
9
10/// The universal wrapper widget: background fill, border, padding, size
11/// constraints, and box shadow on a single child.
12///
13/// `Container` is the workhorse of layout composition. Use it whenever you
14/// need to add visual decoration or spacing around a child widget.
15///
16/// # Example
17///
18/// ```rust,ignore
19/// Container::new(Text::new("Card body"))
20///     .bg(theme.tokens.colors.surface)
21///     .border(theme.tokens.colors.border, 1.0)
22///     .border_radius(8.0)
23///     .padding_all(16.0)
24///     .width(320.0)
25///     .flex_grow(1.0)
26/// ```
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Container {
29    /// Explicit node identity.
30    pub id: Option<WidgetId>,
31    /// The single child widget.
32    pub child: Option<Widget>,
33
34    // -- Layout constraints --
35    /// Fixed width in layout points.
36    pub width: Option<f32>,
37    /// Fixed height in layout points.
38    pub height: Option<f32>,
39    /// Minimum width constraint.
40    pub min_width: Option<f32>,
41    /// Maximum width constraint.
42    pub max_width: Option<f32>,
43    /// Minimum height constraint.
44    pub min_height: Option<f32>,
45    /// Maximum height constraint.
46    pub max_height: Option<f32>,
47    /// Padding `[left, right, top, bottom]`.
48    pub padding: [f32; 4],
49    /// Flex grow factor (how much extra space this container absorbs).
50    pub flex_grow: f32,
51    /// Flex shrink factor (how much this container shrinks when space is tight).
52    pub flex_shrink: f32,
53
54    // -- Visual style --
55    /// Background fill.
56    pub background_fill: Option<Fill>,
57    /// Legacy background fill colour.
58    pub background_color: Option<Color>,
59    /// Border stroke colour.
60    pub border_color: Option<Color>,
61    /// Border stroke width in layout points.
62    pub border_width: f32,
63    /// Corner radius for rounded corners.
64    pub border_radius: f32,
65    /// Optional drop shadow.
66    pub shadow: Option<BoxShadow>,
67    /// Additional shadows drawn behind the container in order.
68    pub shadows: Vec<BoxShadow>,
69}
70
71impl Default for Container {
72    fn default() -> Self {
73        Self {
74            id: None,
75            child: None,
76            width: None,
77            height: None,
78            min_width: None,
79            max_width: None,
80            min_height: None,
81            max_height: None,
82            padding: [0.0; 4],
83            flex_grow: 0.0,
84            flex_shrink: 1.0,
85            background_fill: None,
86            background_color: None,
87            border_color: None,
88            border_width: 0.0,
89            border_radius: 0.0,
90            shadow: None,
91            shadows: Vec::new(),
92        }
93    }
94}
95impl Container {
96    pub fn new(child: impl Into<Widget>) -> Self {
97        Self {
98            child: Some(child.into()),
99            ..Default::default()
100        }
101    }
102
103    pub fn size(mut self, w: f32, h: f32) -> Self {
104        self.width = Some(w);
105        self.height = Some(h);
106        self
107    }
108
109    pub fn width(mut self, w: f32) -> Self {
110        self.width = Some(w);
111        self
112    }
113
114    pub fn height(mut self, h: f32) -> Self {
115        self.height = Some(h);
116        self
117    }
118
119    pub fn min_width(mut self, w: f32) -> Self {
120        self.min_width = Some(w);
121        self
122    }
123
124    pub fn max_width(mut self, w: f32) -> Self {
125        self.max_width = Some(w);
126        self
127    }
128
129    pub fn min_height(mut self, h: f32) -> Self {
130        self.min_height = Some(h);
131        self
132    }
133
134    pub fn max_height(mut self, h: f32) -> Self {
135        self.max_height = Some(h);
136        self
137    }
138
139    pub fn padding_all(mut self, p: f32) -> Self {
140        self.padding = [p; 4];
141        self
142    }
143
144    pub fn padding(mut self, padding: [f32; 4]) -> Self {
145        self.padding = padding;
146        self
147    }
148
149    pub fn flex_grow(mut self, grow: f32) -> Self {
150        self.flex_grow = grow;
151        self
152    }
153
154    pub fn flex_shrink(mut self, shrink: f32) -> Self {
155        self.flex_shrink = shrink;
156        self
157    }
158
159    pub fn bg(mut self, color: Color) -> Self {
160        self.background_fill = Some(Fill::Solid(color));
161        self.background_color = Some(color);
162        self
163    }
164
165    pub fn bg_fill(mut self, fill: Fill) -> Self {
166        self.background_fill = Some(fill);
167        self.background_color = None;
168        self
169    }
170
171    pub fn border(mut self, color: Color, width: f32) -> Self {
172        self.border_color = Some(color);
173        self.border_width = width;
174        self
175    }
176
177    pub fn border_radius(mut self, radius: f32) -> Self {
178        self.border_radius = radius;
179        self
180    }
181
182    pub fn shadow(mut self, shadow: BoxShadow) -> Self {
183        self.shadow = Some(shadow);
184        self
185    }
186
187    pub fn shadows(mut self, shadows: Vec<BoxShadow>) -> Self {
188        self.shadows = shadows;
189        self
190    }
191}
192
193impl InternalLower for Container {
194    fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
195        let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
196        cx.push_scope(id);
197
198        let mut children_ids = Vec::new();
199
200        // 1. Background Layer (PaintOp -> AbsoluteFill)
201        if self.background_fill.is_some()
202            || self.background_color.is_some()
203            || self.border_color.is_some()
204            || self.shadow.is_some()
205            || !self.shadows.is_empty()
206        {
207            for shadow in &self.shadows {
208                let paint = InternalIrBuilder::new(
209                    cx.next_node_id(),
210                    Op::Paint(PaintOp::DrawRect {
211                        fill: None,
212                        stroke: None,
213                        corner_radius: self.border_radius,
214                        shadow: Some(*shadow),
215                    }),
216                )
217                .build(cx);
218                children_ids.push(paint);
219            }
220            let paint = InternalIrBuilder::new(
221                cx.next_node_id(),
222                Op::Paint(PaintOp::DrawRect {
223                    fill: self
224                        .background_fill
225                        .clone()
226                        .or_else(|| self.background_color.map(Fill::Solid)),
227                    stroke: self.border_color.map(|c| Stroke {
228                        fill: Fill::Solid(c),
229                        width: self.border_width,
230                        dash_array: None,
231                        line_cap: fission_ir::op::LineCap::Butt,
232                        line_join: fission_ir::op::LineJoin::Miter,
233                    }),
234                    corner_radius: self.border_radius,
235                    shadow: self.shadow,
236                }),
237            )
238            .build(cx);
239            children_ids.push(paint);
240        }
241
242        // 2. Content Layer
243        if let Some(child) = &self.child {
244            children_ids.push(child.lower(cx));
245        }
246
247        cx.pop_scope();
248
249        let mut layout = InternalIrBuilder::new(
250            id,
251            Op::Layout(LayoutOp::Box {
252                width: self.width,
253                height: self.height,
254                min_width: self.min_width,
255                max_width: self.max_width,
256                min_height: self.min_height,
257                max_height: self.max_height,
258                padding: self.padding,
259                flex_grow: self.flex_grow,
260                flex_shrink: self.flex_shrink,
261                aspect_ratio: None,
262            }),
263        );
264
265        for cid in children_ids {
266            layout.add_child(cid);
267        }
268
269        layout.build(cx)
270    }
271}