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 colour, 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
36    /// Fixed width in layout points.
37    pub width: Option<f32>,
38    /// Fixed height in layout points.
39    pub height: Option<f32>,
40    /// Minimum width constraint.
41    pub min_width: Option<f32>,
42    /// Maximum width constraint.
43    pub max_width: Option<f32>,
44    /// Minimum height constraint.
45    pub min_height: Option<f32>,
46    /// Maximum height constraint.
47    pub max_height: Option<f32>,
48    /// Padding `[left, right, top, bottom]`.
49    pub padding: [f32; 4],
50    /// Flex grow factor (how much extra space this container absorbs).
51    pub flex_grow: f32,
52    /// Flex shrink factor (how much this container shrinks when space is tight).
53    pub flex_shrink: f32,
54
55    // -- Visual style --
56
57    /// 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}
68
69impl Default for Container {
70    fn default() -> Self {
71        Self {
72            id: None,
73            child: None,
74            width: None,
75            height: None,
76            min_width: None,
77            max_width: None,
78            min_height: None,
79            max_height: None,
80            padding: [0.0; 4],
81            flex_grow: 0.0,
82            flex_shrink: 1.0,
83            background_color: None,
84            border_color: None,
85            border_width: 0.0,
86            border_radius: 0.0,
87            shadow: None,
88        }
89    }
90}
91impl Container {
92    pub fn new(child: Node) -> Self {
93        Self {
94            child: Some(Box::new(child)),
95            ..Default::default()
96        }
97    }
98
99    pub fn id(mut self, id: NodeId) -> Self {
100        self.id = Some(id);
101        self
102    }
103    
104    pub fn size(mut self, w: f32, h: f32) -> Self {
105        self.width = Some(w);
106        self.height = Some(h);
107        self
108    }
109
110    pub fn width(mut self, w: f32) -> Self {
111        self.width = Some(w);
112        self
113    }
114
115    pub fn height(mut self, h: f32) -> Self {
116        self.height = Some(h);
117        self
118    }
119
120    pub fn min_width(mut self, w: f32) -> Self {
121        self.min_width = Some(w);
122        self
123    }
124
125    pub fn max_width(mut self, w: f32) -> Self {
126        self.max_width = Some(w);
127        self
128    }
129
130    pub fn min_height(mut self, h: f32) -> Self {
131        self.min_height = Some(h);
132        self
133    }
134
135    pub fn max_height(mut self, h: f32) -> Self {
136        self.max_height = Some(h);
137        self
138    }
139    
140    pub fn padding_all(mut self, p: f32) -> Self {
141        self.padding = [p; 4];
142        self
143    }
144
145    pub fn flex_grow(mut self, grow: f32) -> Self {
146        self.flex_grow = grow;
147        self
148    }
149
150    pub fn flex_shrink(mut self, shrink: f32) -> Self {
151        self.flex_shrink = shrink;
152        self
153    }
154    
155    pub fn bg(mut self, color: Color) -> Self {
156        self.background_color = Some(color);
157        self
158    }
159
160    pub fn border(mut self, color: Color, width: f32) -> Self {
161        self.border_color = Some(color);
162        self.border_width = width;
163        self
164    }
165
166    pub fn border_radius(mut self, radius: f32) -> Self {
167        self.border_radius = radius;
168        self
169    }
170
171    pub fn shadow(mut self, shadow: BoxShadow) -> Self {
172        self.shadow = Some(shadow);
173        self
174    }
175
176    pub fn into_node(self) -> Node {
177        Node::Container(self)
178    }
179}
180
181impl Lower for Container {
182    fn lower(&self, cx: &mut LoweringContext) -> NodeId {
183        let id = self.id.unwrap_or_else(|| cx.next_node_id());
184        cx.push_scope(id);
185
186        let mut children_ids = Vec::new();
187
188        // 1. Background Layer (PaintOp -> AbsoluteFill)
189        if self.background_color.is_some() || self.border_color.is_some() || self.shadow.is_some() {
190             let paint = NodeBuilder::new(cx.next_node_id(), Op::Paint(PaintOp::DrawRect {
191                 fill: self.background_color.map(|c| Fill { color: c }),
192                 stroke: self.border_color.map(|c| Stroke { color: c, width: self.border_width }),
193                 corner_radius: self.border_radius,
194                 shadow: self.shadow,
195             })).build(cx);
196             children_ids.push(paint);
197        }
198
199        // 2. Content Layer
200        if let Some(child) = &self.child {
201            children_ids.push(child.lower(cx));
202        }
203        
204        cx.pop_scope();
205
206        let mut layout = NodeBuilder::new(id, Op::Layout(LayoutOp::Box {
207            width: self.width,
208            height: self.height,
209            min_width: self.min_width,
210            max_width: self.max_width,
211            min_height: self.min_height,
212            max_height: self.max_height,
213            padding: self.padding,
214            flex_grow: self.flex_grow,
215            flex_shrink: self.flex_shrink,
216            aspect_ratio: None,
217        }));
218        
219        for cid in children_ids {
220            layout.add_child(cid);
221        }
222        
223        layout.build(cx)
224    }
225}