Skip to main content

fission_core/ui/widgets/
container.rs

1use crate::lowering::{LoweringContext, NodeBuilder};
2use crate::ui::traits::Lower;
3use crate::ui::Node;
4use fission_ir::{
5    op::{BoxShadow, Color, Fill, LayoutOp, Op, PaintOp, Stroke},
6    NodeId,
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").into_node())
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<NodeId>,
31    /// The single child widget.
32    pub child: Option<Box<Node>>,
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: Node) -> Self {
97        Self {
98            child: Some(Box::new(child)),
99            ..Default::default()
100        }
101    }
102
103    pub fn id(mut self, id: NodeId) -> Self {
104        self.id = Some(id);
105        self
106    }
107
108    pub fn size(mut self, w: f32, h: f32) -> Self {
109        self.width = Some(w);
110        self.height = Some(h);
111        self
112    }
113
114    pub fn width(mut self, w: f32) -> Self {
115        self.width = Some(w);
116        self
117    }
118
119    pub fn height(mut self, h: f32) -> Self {
120        self.height = Some(h);
121        self
122    }
123
124    pub fn min_width(mut self, w: f32) -> Self {
125        self.min_width = Some(w);
126        self
127    }
128
129    pub fn max_width(mut self, w: f32) -> Self {
130        self.max_width = Some(w);
131        self
132    }
133
134    pub fn min_height(mut self, h: f32) -> Self {
135        self.min_height = Some(h);
136        self
137    }
138
139    pub fn max_height(mut self, h: f32) -> Self {
140        self.max_height = Some(h);
141        self
142    }
143
144    pub fn padding_all(mut self, p: f32) -> Self {
145        self.padding = [p; 4];
146        self
147    }
148
149    pub fn padding(mut self, padding: [f32; 4]) -> Self {
150        self.padding = padding;
151        self
152    }
153
154    pub fn flex_grow(mut self, grow: f32) -> Self {
155        self.flex_grow = grow;
156        self
157    }
158
159    pub fn flex_shrink(mut self, shrink: f32) -> Self {
160        self.flex_shrink = shrink;
161        self
162    }
163
164    pub fn bg(mut self, color: Color) -> Self {
165        self.background_fill = Some(Fill::Solid(color));
166        self.background_color = Some(color);
167        self
168    }
169
170    pub fn bg_fill(mut self, fill: Fill) -> Self {
171        self.background_fill = Some(fill);
172        self.background_color = None;
173        self
174    }
175
176    pub fn border(mut self, color: Color, width: f32) -> Self {
177        self.border_color = Some(color);
178        self.border_width = width;
179        self
180    }
181
182    pub fn border_radius(mut self, radius: f32) -> Self {
183        self.border_radius = radius;
184        self
185    }
186
187    pub fn shadow(mut self, shadow: BoxShadow) -> Self {
188        self.shadow = Some(shadow);
189        self
190    }
191
192    pub fn shadows(mut self, shadows: Vec<BoxShadow>) -> Self {
193        self.shadows = shadows;
194        self
195    }
196
197    pub fn into_node(self) -> Node {
198        Node::Container(self)
199    }
200}
201
202impl Lower for Container {
203    fn lower(&self, cx: &mut LoweringContext) -> NodeId {
204        let id = self.id.unwrap_or_else(|| cx.next_node_id());
205        cx.push_scope(id);
206
207        let mut children_ids = Vec::new();
208
209        // 1. Background Layer (PaintOp -> AbsoluteFill)
210        if self.background_fill.is_some()
211            || self.background_color.is_some()
212            || self.border_color.is_some()
213            || self.shadow.is_some()
214            || !self.shadows.is_empty()
215        {
216            for shadow in &self.shadows {
217                let paint = NodeBuilder::new(
218                    cx.next_node_id(),
219                    Op::Paint(PaintOp::DrawRect {
220                        fill: None,
221                        stroke: None,
222                        corner_radius: self.border_radius,
223                        shadow: Some(*shadow),
224                    }),
225                )
226                .build(cx);
227                children_ids.push(paint);
228            }
229            let paint = NodeBuilder::new(
230                cx.next_node_id(),
231                Op::Paint(PaintOp::DrawRect {
232                    fill: self
233                        .background_fill
234                        .clone()
235                        .or_else(|| self.background_color.map(Fill::Solid)),
236                    stroke: self.border_color.map(|c| Stroke {
237                        fill: Fill::Solid(c),
238                        width: self.border_width,
239                        dash_array: None,
240                        line_cap: fission_ir::op::LineCap::Butt,
241                        line_join: fission_ir::op::LineJoin::Miter,
242                    }),
243                    corner_radius: self.border_radius,
244                    shadow: self.shadow,
245                }),
246            )
247            .build(cx);
248            children_ids.push(paint);
249        }
250
251        // 2. Content Layer
252        if let Some(child) = &self.child {
253            children_ids.push(child.lower(cx));
254        }
255
256        cx.pop_scope();
257
258        let mut layout = NodeBuilder::new(
259            id,
260            Op::Layout(LayoutOp::Box {
261                width: self.width,
262                height: self.height,
263                min_width: self.min_width,
264                max_width: self.max_width,
265                min_height: self.min_height,
266                max_height: self.max_height,
267                padding: self.padding,
268                flex_grow: self.flex_grow,
269                flex_shrink: self.flex_shrink,
270                aspect_ratio: None,
271            }),
272        );
273
274        for cid in children_ids {
275            layout.add_child(cid);
276        }
277
278        layout.build(cx)
279    }
280}