1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use crate::ui::Widget;
4use fission_ir::{
5 op::{
6 BackdropFilter, BoxAlignment, BoxGridPlacement, BoxPosition, BoxShadow, BoxStyle, Color,
7 Fill, GridPlacement, LayoutOp, Length, Op, OrderedLayoutUnit, Overflow, PaintOp, Stroke,
8 },
9 CompositeStyle, WidgetId,
10};
11use serde::{Deserialize, Serialize};
12
13use super::split_box_margin;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct Container {
34 pub id: Option<WidgetId>,
36 pub child: Option<Widget>,
38
39 pub width: Option<f32>,
42 pub height: Option<f32>,
44 pub min_width: Option<f32>,
46 pub max_width: Option<f32>,
48 pub min_height: Option<f32>,
50 pub max_height: Option<f32>,
52 pub padding: [f32; 4],
54 pub flex_grow: f32,
56 pub flex_shrink: f32,
58 #[serde(default)]
60 pub box_style: BoxStyle,
61 #[serde(default)]
63 pub margin: [f32; 4],
64
65 pub background_fill: Option<Fill>,
68 pub background_color: Option<Color>,
70 pub border_color: Option<Color>,
72 pub border_width: f32,
74 pub border_radius: f32,
76 pub shadow: Option<BoxShadow>,
78 pub shadows: Vec<BoxShadow>,
80 pub backdrop_filter: Option<BackdropFilter>,
82}
83
84impl Default for Container {
85 fn default() -> Self {
86 Self {
87 id: None,
88 child: None,
89 width: None,
90 height: None,
91 min_width: None,
92 max_width: None,
93 min_height: None,
94 max_height: None,
95 padding: [0.0; 4],
96 flex_grow: 0.0,
97 flex_shrink: 1.0,
98 box_style: BoxStyle::default(),
99 margin: [0.0; 4],
100 background_fill: None,
101 background_color: None,
102 border_color: None,
103 border_width: 0.0,
104 border_radius: 0.0,
105 shadow: None,
106 shadows: Vec::new(),
107 backdrop_filter: None,
108 }
109 }
110}
111impl Container {
112 pub fn new(child: impl Into<Widget>) -> Self {
113 let mut container = Self {
114 child: Some(child.into()),
115 ..Default::default()
116 };
117 container.box_style.alignment = BoxAlignment::Stretch;
118 container
119 }
120
121 pub fn size(mut self, w: f32, h: f32) -> Self {
122 self.width = Some(w);
123 self.height = Some(h);
124 self
125 }
126
127 pub fn width(mut self, w: f32) -> Self {
128 self.width = Some(w);
129 self
130 }
131
132 pub fn width_length(mut self, width: Length) -> Self {
134 self.box_style.width = Some(width);
135 self
136 }
137
138 pub fn height_length(mut self, height: Length) -> Self {
140 self.box_style.height = Some(height);
141 self
142 }
143
144 pub fn min_width_length(mut self, width: Length) -> Self {
146 self.box_style.min_width = Some(width);
147 self
148 }
149
150 pub fn max_width_length(mut self, width: Length) -> Self {
152 self.box_style.max_width = Some(width);
153 self
154 }
155
156 pub fn min_height_length(mut self, height: Length) -> Self {
158 self.box_style.min_height = Some(height);
159 self
160 }
161
162 pub fn max_height_length(mut self, height: Length) -> Self {
164 self.box_style.max_height = Some(height);
165 self
166 }
167
168 pub fn height(mut self, h: f32) -> Self {
169 self.height = Some(h);
170 self
171 }
172
173 pub fn min_width(mut self, w: f32) -> Self {
174 self.min_width = Some(w);
175 self
176 }
177
178 pub fn max_width(mut self, w: f32) -> Self {
179 self.max_width = Some(w);
180 self
181 }
182
183 pub fn min_height(mut self, h: f32) -> Self {
184 self.min_height = Some(h);
185 self
186 }
187
188 pub fn max_height(mut self, h: f32) -> Self {
189 self.max_height = Some(h);
190 self
191 }
192
193 pub fn padding_all(mut self, p: f32) -> Self {
194 self.padding = [p; 4];
195 self
196 }
197
198 pub fn padding(mut self, padding: [f32; 4]) -> Self {
199 self.padding = padding;
200 self
201 }
202
203 pub fn padding_lengths(mut self, padding: [Length; 4]) -> Self {
205 self.box_style.padding = Some(padding);
206 self
207 }
208
209 pub fn margin_all(mut self, margin: f32) -> Self {
211 self.margin = [margin; 4];
212 self
213 }
214
215 pub fn margin(mut self, margin: [f32; 4]) -> Self {
217 self.margin = margin;
218 self
219 }
220
221 pub fn margin_lengths(mut self, margin: [Length; 4]) -> Self {
223 self.box_style.margin = Some(margin);
224 self
225 }
226
227 pub fn align_child(mut self, alignment: BoxAlignment) -> Self {
229 self.box_style.alignment = alignment;
230 self
231 }
232
233 pub fn aspect_ratio(mut self, ratio: f32) -> Self {
235 self.box_style.aspect_ratio = Some(OrderedLayoutUnit(ratio.max(0.0)));
236 self
237 }
238
239 pub fn positioned(
241 mut self,
242 left: Option<f32>,
243 top: Option<f32>,
244 right: Option<f32>,
245 bottom: Option<f32>,
246 ) -> Self {
247 self.box_style.position = Some(BoxPosition {
248 left: left.map(Length::Points),
249 top: top.map(Length::Points),
250 right: right.map(Length::Points),
251 bottom: bottom.map(Length::Points),
252 });
253 self
254 }
255
256 pub fn positioned_lengths(
258 mut self,
259 left: Option<Length>,
260 top: Option<Length>,
261 right: Option<Length>,
262 bottom: Option<Length>,
263 ) -> Self {
264 self.box_style.position = Some(BoxPosition {
265 left,
266 top,
267 right,
268 bottom,
269 });
270 self
271 }
272
273 pub fn grid_cell(mut self, row: i16, column: i16) -> Self {
275 self.box_style.grid = Some(BoxGridPlacement {
276 row_start: GridPlacement::Line(row),
277 col_start: GridPlacement::Line(column),
278 ..Default::default()
279 });
280 self
281 }
282
283 pub fn grid_span(mut self, rows: u16, columns: u16) -> Self {
285 let placement = self.box_style.grid.get_or_insert_default();
286 placement.row_end = GridPlacement::Span(rows.max(1));
287 placement.col_end = GridPlacement::Span(columns.max(1));
288 self
289 }
290
291 pub fn clip_overflow(mut self, clip: bool) -> Self {
293 self.box_style.overflow = if clip {
294 Overflow::Clip
295 } else {
296 Overflow::Visible
297 };
298 self
299 }
300
301 pub fn flex_grow(mut self, grow: f32) -> Self {
302 self.flex_grow = grow;
303 self.box_style.flex_grow = Some(OrderedLayoutUnit(grow));
304 self
305 }
306
307 pub fn flex_shrink(mut self, shrink: f32) -> Self {
308 self.flex_shrink = shrink;
309 self.box_style.flex_shrink = Some(OrderedLayoutUnit(shrink));
310 self
311 }
312
313 pub fn bg(mut self, color: Color) -> Self {
314 self.background_fill = Some(Fill::Solid(color));
315 self.background_color = Some(color);
316 self
317 }
318
319 pub fn bg_fill(mut self, fill: Fill) -> Self {
320 self.background_fill = Some(fill);
321 self.background_color = None;
322 self
323 }
324
325 pub fn border(mut self, color: Color, width: f32) -> Self {
326 self.border_color = Some(color);
327 self.border_width = width;
328 self
329 }
330
331 pub fn border_radius(mut self, radius: f32) -> Self {
332 self.border_radius = radius;
333 self
334 }
335
336 pub fn shadow(mut self, shadow: BoxShadow) -> Self {
337 self.shadow = Some(shadow);
338 self
339 }
340
341 pub fn shadows(mut self, shadows: Vec<BoxShadow>) -> Self {
342 self.shadows = shadows;
343 self
344 }
345
346 pub fn backdrop_blur(mut self, sigma: f32) -> Self {
348 self.backdrop_filter = Some(BackdropFilter::Blur(sigma.max(0.0)));
349 self
350 }
351}
352
353impl InternalLower for Container {
354 fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
355 let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
356 cx.push_scope(id);
357
358 let mut children_ids = Vec::new();
359
360 if let Some(filter) = self.backdrop_filter {
361 let paint = InternalIrBuilder::new(
362 cx.next_node_id(),
363 Op::Paint(PaintOp::BackdropFilter {
364 filter,
365 corner_radius: self.border_radius,
366 }),
367 )
368 .build(cx);
369 children_ids.push(paint);
370 }
371
372 if self.background_fill.is_some()
374 || self.background_color.is_some()
375 || self.border_color.is_some()
376 || self.shadow.is_some()
377 || !self.shadows.is_empty()
378 {
379 for shadow in &self.shadows {
380 let paint = InternalIrBuilder::new(
381 cx.next_node_id(),
382 Op::Paint(PaintOp::DrawRect {
383 fill: None,
384 stroke: None,
385 corner_radius: self.border_radius,
386 shadow: Some(*shadow),
387 }),
388 )
389 .build(cx);
390 children_ids.push(paint);
391 }
392 let paint = InternalIrBuilder::new(
393 cx.next_node_id(),
394 Op::Paint(PaintOp::DrawRect {
395 fill: self
396 .background_fill
397 .clone()
398 .or_else(|| self.background_color.map(Fill::Solid)),
399 stroke: self.border_color.map(|c| Stroke {
400 fill: Fill::Solid(c),
401 width: self.border_width,
402 dash_array: None,
403 line_cap: fission_ir::op::LineCap::Butt,
404 line_join: fission_ir::op::LineJoin::Miter,
405 }),
406 corner_radius: self.border_radius,
407 shadow: self.shadow,
408 }),
409 )
410 .build(cx);
411 children_ids.push(paint);
412 }
413
414 if let Some(child) = &self.child {
416 children_ids.push(child.lower(cx));
417 }
418
419 cx.pop_scope();
420
421 let mut style = self.box_style.clone();
422 style.width = style.width.or(self.width.map(Length::Points));
423 style.height = style.height.or(self.height.map(Length::Points));
424 style.min_width = style.min_width.or(self.min_width.map(Length::Points));
425 style.max_width = style.max_width.or(self.max_width.map(Length::Points));
426 style.min_height = style.min_height.or(self.min_height.map(Length::Points));
427 style.max_height = style.max_height.or(self.max_height.map(Length::Points));
428 style.padding = style
429 .padding
430 .or_else(|| (self.padding != [0.0; 4]).then(|| self.padding.map(Length::Points)));
431 style.margin = style
432 .margin
433 .or_else(|| (self.margin != [0.0; 4]).then(|| self.margin.map(Length::Points)));
434 let margin_style = split_box_margin(&mut style);
435 let position = style.position.take();
436 let grid = style.grid.take();
437 let flex_grow = style
438 .flex_grow
439 .map(|value| value.0)
440 .unwrap_or(self.flex_grow);
441 let flex_shrink = style
442 .flex_shrink
443 .map(|value| value.0)
444 .unwrap_or(self.flex_shrink);
445
446 let mut layout = InternalIrBuilder::new(
447 id,
448 Op::Layout(LayoutOp::StyledBox {
449 style: style.clone(),
450 flex_grow,
451 flex_shrink,
452 }),
453 )
454 .composite(CompositeStyle {
455 clip_to_bounds: style.overflow == Overflow::Clip,
456 ..Default::default()
457 });
458
459 for cid in children_ids {
460 layout.add_child(cid);
461 }
462
463 let mut result = layout.build(cx);
464 if let Some(margin_style) = margin_style {
465 let mut outer = InternalIrBuilder::new(
466 cx.next_node_id(),
467 Op::Layout(LayoutOp::StyledBox {
468 style: margin_style,
469 flex_grow,
470 flex_shrink,
471 }),
472 );
473 outer.add_child(result);
474 result = outer.build(cx);
475 }
476 if let Some(position) = position {
477 let mut outer = InternalIrBuilder::new(
478 cx.next_node_id(),
479 Op::Layout(LayoutOp::PositionedLengths {
480 left: position.left,
481 top: position.top,
482 right: position.right,
483 bottom: position.bottom,
484 width: None,
485 height: None,
486 }),
487 );
488 outer.add_child(result);
489 result = outer.build(cx);
490 }
491 if let Some(grid) = grid {
492 let mut outer = InternalIrBuilder::new(
493 cx.next_node_id(),
494 Op::Layout(LayoutOp::GridItem {
495 row_start: grid.row_start,
496 row_end: grid.row_end,
497 col_start: grid.col_start,
498 col_end: grid.col_end,
499 }),
500 );
501 outer.add_child(result);
502 result = outer.build(cx);
503 }
504 result
505 }
506}