use crate::internal::InternalLower;
use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
use crate::ui::Widget;
use fission_ir::{
op::{
BackdropFilter, BoxAlignment, BoxGridPlacement, BoxPosition, BoxShadow, BoxStyle, Color,
Fill, GridPlacement, LayoutOp, Length, Op, OrderedLayoutUnit, Overflow, PaintOp, Stroke,
},
CompositeStyle, WidgetId,
};
use serde::{Deserialize, Serialize};
use super::split_box_margin;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Container {
pub id: Option<WidgetId>,
pub child: Option<Widget>,
pub width: Option<f32>,
pub height: Option<f32>,
pub min_width: Option<f32>,
pub max_width: Option<f32>,
pub min_height: Option<f32>,
pub max_height: Option<f32>,
pub padding: [f32; 4],
pub flex_grow: f32,
pub flex_shrink: f32,
#[serde(default)]
pub box_style: BoxStyle,
#[serde(default)]
pub margin: [f32; 4],
pub background_fill: Option<Fill>,
pub background_color: Option<Color>,
pub border_color: Option<Color>,
pub border_width: f32,
pub border_radius: f32,
pub shadow: Option<BoxShadow>,
pub shadows: Vec<BoxShadow>,
pub backdrop_filter: Option<BackdropFilter>,
}
impl Default for Container {
fn default() -> Self {
Self {
id: None,
child: None,
width: None,
height: None,
min_width: None,
max_width: None,
min_height: None,
max_height: None,
padding: [0.0; 4],
flex_grow: 0.0,
flex_shrink: 1.0,
box_style: BoxStyle::default(),
margin: [0.0; 4],
background_fill: None,
background_color: None,
border_color: None,
border_width: 0.0,
border_radius: 0.0,
shadow: None,
shadows: Vec::new(),
backdrop_filter: None,
}
}
}
impl Container {
pub fn new(child: impl Into<Widget>) -> Self {
let mut container = Self {
child: Some(child.into()),
..Default::default()
};
container.box_style.alignment = BoxAlignment::Stretch;
container
}
pub fn size(mut self, w: f32, h: f32) -> Self {
self.width = Some(w);
self.height = Some(h);
self
}
pub fn width(mut self, w: f32) -> Self {
self.width = Some(w);
self
}
pub fn width_length(mut self, width: Length) -> Self {
self.box_style.width = Some(width);
self
}
pub fn height_length(mut self, height: Length) -> Self {
self.box_style.height = Some(height);
self
}
pub fn min_width_length(mut self, width: Length) -> Self {
self.box_style.min_width = Some(width);
self
}
pub fn max_width_length(mut self, width: Length) -> Self {
self.box_style.max_width = Some(width);
self
}
pub fn min_height_length(mut self, height: Length) -> Self {
self.box_style.min_height = Some(height);
self
}
pub fn max_height_length(mut self, height: Length) -> Self {
self.box_style.max_height = Some(height);
self
}
pub fn height(mut self, h: f32) -> Self {
self.height = Some(h);
self
}
pub fn min_width(mut self, w: f32) -> Self {
self.min_width = Some(w);
self
}
pub fn max_width(mut self, w: f32) -> Self {
self.max_width = Some(w);
self
}
pub fn min_height(mut self, h: f32) -> Self {
self.min_height = Some(h);
self
}
pub fn max_height(mut self, h: f32) -> Self {
self.max_height = Some(h);
self
}
pub fn padding_all(mut self, p: f32) -> Self {
self.padding = [p; 4];
self
}
pub fn padding(mut self, padding: [f32; 4]) -> Self {
self.padding = padding;
self
}
pub fn padding_lengths(mut self, padding: [Length; 4]) -> Self {
self.box_style.padding = Some(padding);
self
}
pub fn margin_all(mut self, margin: f32) -> Self {
self.margin = [margin; 4];
self
}
pub fn margin(mut self, margin: [f32; 4]) -> Self {
self.margin = margin;
self
}
pub fn margin_lengths(mut self, margin: [Length; 4]) -> Self {
self.box_style.margin = Some(margin);
self
}
pub fn align_child(mut self, alignment: BoxAlignment) -> Self {
self.box_style.alignment = alignment;
self
}
pub fn aspect_ratio(mut self, ratio: f32) -> Self {
self.box_style.aspect_ratio = Some(OrderedLayoutUnit(ratio.max(0.0)));
self
}
pub fn positioned(
mut self,
left: Option<f32>,
top: Option<f32>,
right: Option<f32>,
bottom: Option<f32>,
) -> Self {
self.box_style.position = Some(BoxPosition {
left: left.map(Length::Points),
top: top.map(Length::Points),
right: right.map(Length::Points),
bottom: bottom.map(Length::Points),
});
self
}
pub fn positioned_lengths(
mut self,
left: Option<Length>,
top: Option<Length>,
right: Option<Length>,
bottom: Option<Length>,
) -> Self {
self.box_style.position = Some(BoxPosition {
left,
top,
right,
bottom,
});
self
}
pub fn grid_cell(mut self, row: i16, column: i16) -> Self {
self.box_style.grid = Some(BoxGridPlacement {
row_start: GridPlacement::Line(row),
col_start: GridPlacement::Line(column),
..Default::default()
});
self
}
pub fn grid_span(mut self, rows: u16, columns: u16) -> Self {
let placement = self.box_style.grid.get_or_insert_default();
placement.row_end = GridPlacement::Span(rows.max(1));
placement.col_end = GridPlacement::Span(columns.max(1));
self
}
pub fn clip_overflow(mut self, clip: bool) -> Self {
self.box_style.overflow = if clip {
Overflow::Clip
} else {
Overflow::Visible
};
self
}
pub fn flex_grow(mut self, grow: f32) -> Self {
self.flex_grow = grow;
self.box_style.flex_grow = Some(OrderedLayoutUnit(grow));
self
}
pub fn flex_shrink(mut self, shrink: f32) -> Self {
self.flex_shrink = shrink;
self.box_style.flex_shrink = Some(OrderedLayoutUnit(shrink));
self
}
pub fn bg(mut self, color: Color) -> Self {
self.background_fill = Some(Fill::Solid(color));
self.background_color = Some(color);
self
}
pub fn bg_fill(mut self, fill: Fill) -> Self {
self.background_fill = Some(fill);
self.background_color = None;
self
}
pub fn border(mut self, color: Color, width: f32) -> Self {
self.border_color = Some(color);
self.border_width = width;
self
}
pub fn border_radius(mut self, radius: f32) -> Self {
self.border_radius = radius;
self
}
pub fn shadow(mut self, shadow: BoxShadow) -> Self {
self.shadow = Some(shadow);
self
}
pub fn shadows(mut self, shadows: Vec<BoxShadow>) -> Self {
self.shadows = shadows;
self
}
pub fn backdrop_blur(mut self, sigma: f32) -> Self {
self.backdrop_filter = Some(BackdropFilter::Blur(sigma.max(0.0)));
self
}
}
impl InternalLower for Container {
fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
cx.push_scope(id);
let mut children_ids = Vec::new();
if let Some(filter) = self.backdrop_filter {
let paint = InternalIrBuilder::new(
cx.next_node_id(),
Op::Paint(PaintOp::BackdropFilter {
filter,
corner_radius: self.border_radius,
}),
)
.build(cx);
children_ids.push(paint);
}
if self.background_fill.is_some()
|| self.background_color.is_some()
|| self.border_color.is_some()
|| self.shadow.is_some()
|| !self.shadows.is_empty()
{
for shadow in &self.shadows {
let paint = InternalIrBuilder::new(
cx.next_node_id(),
Op::Paint(PaintOp::DrawRect {
fill: None,
stroke: None,
corner_radius: self.border_radius,
shadow: Some(*shadow),
}),
)
.build(cx);
children_ids.push(paint);
}
let paint = InternalIrBuilder::new(
cx.next_node_id(),
Op::Paint(PaintOp::DrawRect {
fill: self
.background_fill
.clone()
.or_else(|| self.background_color.map(Fill::Solid)),
stroke: self.border_color.map(|c| Stroke {
fill: Fill::Solid(c),
width: self.border_width,
dash_array: None,
line_cap: fission_ir::op::LineCap::Butt,
line_join: fission_ir::op::LineJoin::Miter,
}),
corner_radius: self.border_radius,
shadow: self.shadow,
}),
)
.build(cx);
children_ids.push(paint);
}
if let Some(child) = &self.child {
children_ids.push(child.lower(cx));
}
cx.pop_scope();
let mut style = self.box_style.clone();
style.width = style.width.or(self.width.map(Length::Points));
style.height = style.height.or(self.height.map(Length::Points));
style.min_width = style.min_width.or(self.min_width.map(Length::Points));
style.max_width = style.max_width.or(self.max_width.map(Length::Points));
style.min_height = style.min_height.or(self.min_height.map(Length::Points));
style.max_height = style.max_height.or(self.max_height.map(Length::Points));
style.padding = style
.padding
.or_else(|| (self.padding != [0.0; 4]).then(|| self.padding.map(Length::Points)));
style.margin = style
.margin
.or_else(|| (self.margin != [0.0; 4]).then(|| self.margin.map(Length::Points)));
let margin_style = split_box_margin(&mut style);
let position = style.position.take();
let grid = style.grid.take();
let flex_grow = style
.flex_grow
.map(|value| value.0)
.unwrap_or(self.flex_grow);
let flex_shrink = style
.flex_shrink
.map(|value| value.0)
.unwrap_or(self.flex_shrink);
let mut layout = InternalIrBuilder::new(
id,
Op::Layout(LayoutOp::StyledBox {
style: style.clone(),
flex_grow,
flex_shrink,
}),
)
.composite(CompositeStyle {
clip_to_bounds: style.overflow == Overflow::Clip,
..Default::default()
});
for cid in children_ids {
layout.add_child(cid);
}
let mut result = layout.build(cx);
if let Some(margin_style) = margin_style {
let mut outer = InternalIrBuilder::new(
cx.next_node_id(),
Op::Layout(LayoutOp::StyledBox {
style: margin_style,
flex_grow,
flex_shrink,
}),
);
outer.add_child(result);
result = outer.build(cx);
}
if let Some(position) = position {
let mut outer = InternalIrBuilder::new(
cx.next_node_id(),
Op::Layout(LayoutOp::PositionedLengths {
left: position.left,
top: position.top,
right: position.right,
bottom: position.bottom,
width: None,
height: None,
}),
);
outer.add_child(result);
result = outer.build(cx);
}
if let Some(grid) = grid {
let mut outer = InternalIrBuilder::new(
cx.next_node_id(),
Op::Layout(LayoutOp::GridItem {
row_start: grid.row_start,
row_end: grid.row_end,
col_start: grid.col_start,
col_end: grid.col_end,
}),
);
outer.add_child(result);
result = outer.build(cx);
}
result
}
}