Skip to main content

fission_core/ui/widgets/
column.rs

1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use crate::Widget;
4use fission_ir::op::{AlignItems, FlexWrap, JustifyContent};
5use fission_ir::{FlexDirection, LayoutOp, Op, Semantics, WidgetId};
6use serde::{Deserialize, Serialize};
7
8/// A vertical flex container that lays out children in a column.
9///
10/// Children are arranged top-to-bottom. Use `align_items` to control
11/// cross-axis (horizontal) alignment and `justify_content` for main-axis
12/// (vertical) distribution.
13///
14/// # Example
15///
16/// ```rust,ignore
17/// Column {
18///     children: vec![
19///         Text::new("Title").size(24.0).into(),
20///         Text::new("Subtitle").size(14.0).into(),
21///     ],
22///     gap: Some(4.0),
23///     align_items: AlignItems::Stretch,
24///     ..Default::default()
25/// }
26/// ```
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Column {
29    /// Explicit node identity.
30    pub id: Option<WidgetId>,
31    /// The child widgets laid out top-to-bottom.
32    pub children: Vec<Widget>,
33    /// Custom semantics for accessibility.
34    pub semantics: Option<Semantics>,
35    /// Flex grow factor.
36    pub flex_grow: f32,
37    /// Flex shrink factor.
38    pub flex_shrink: f32,
39    /// Spacing between children in layout points.
40    pub gap: Option<f32>,
41    /// Whether children wrap when they overflow.
42    pub wrap: FlexWrap,
43    /// Cross-axis (horizontal) alignment (default: `Stretch`).
44    pub align_items: AlignItems,
45    /// Main-axis (vertical) distribution (default: `Start`).
46    pub justify_content: JustifyContent,
47}
48
49impl Default for Column {
50    fn default() -> Self {
51        Self {
52            id: None,
53            children: Vec::new(),
54            gap: None,
55            flex_grow: 0.0,
56            flex_shrink: 1.0,
57            semantics: None,
58            wrap: FlexWrap::NoWrap,
59            align_items: AlignItems::Stretch,
60            justify_content: JustifyContent::Start,
61        }
62    }
63}
64
65impl Column {
66    pub fn children(mut self, children: Vec<Widget>) -> Self {
67        self.children = children;
68        self
69    }
70
71    pub fn flex_grow(mut self, flex_grow: f32) -> Self {
72        self.flex_grow = flex_grow;
73        self
74    }
75
76    pub fn gap(mut self, gap: Option<f32>) -> Self {
77        self.gap = gap;
78        self
79    }
80
81    pub fn align_items(mut self, align: AlignItems) -> Self {
82        self.align_items = align;
83        self
84    }
85
86    pub fn justify_content(mut self, justify: JustifyContent) -> Self {
87        self.justify_content = justify;
88        self
89    }
90}
91
92impl InternalLower for Column {
93    fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
94        let layout_id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
95
96        cx.push_scope(layout_id);
97
98        let mut builder = InternalIrBuilder::new(
99            layout_id,
100            Op::Layout(LayoutOp::Flex {
101                direction: FlexDirection::Column,
102                wrap: self.wrap,
103                flex_grow: self.flex_grow,
104                flex_shrink: self.flex_shrink,
105                padding: [0.0; 4],
106                gap: self.gap,
107                align_items: self.align_items,
108                justify_content: self.justify_content,
109            }),
110        );
111        for child in &self.children {
112            builder.add_child(child.lower(cx));
113        }
114
115        cx.pop_scope();
116
117        let layout_id = builder.build(cx);
118
119        if let Some(s) = &self.semantics {
120            let mut semantics_builder =
121                InternalIrBuilder::new(cx.next_node_id(), Op::Semantics(s.clone()));
122            semantics_builder.add_child(layout_id);
123            return semantics_builder.build(cx);
124        }
125
126        layout_id
127    }
128}