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