Skip to main content

fission_core/ui/widgets/
grid.rs

1use crate::lowering::{LoweringContext, NodeBuilder};
2use crate::ui::traits::Lower;
3use crate::ui::Node;
4use fission_ir::{
5    op::{GridPlacement, GridTrack, LayoutOp, Op},
6    NodeId,
7};
8use serde::{Deserialize, Serialize};
9
10/// A CSS-grid-style layout container.
11///
12/// Define column and row tracks with [`GridTrack`] values (points, fractions,
13/// percentages, or auto) and place children using [`GridItem`].
14///
15/// # Example
16///
17/// ```rust,ignore
18/// Grid {
19///     columns: vec![GridTrack::Fr(1.0), GridTrack::Fr(2.0)],
20///     rows: vec![GridTrack::Points(40.0), GridTrack::Auto],
21///     column_gap: Some(8.0),
22///     row_gap: Some(8.0),
23///     children: vec![
24///         GridItem::new(Text::new("A").into_node()).cell(1, 1).into_node().into(),
25///         GridItem::new(Text::new("B").into_node()).cell(1, 2).into_node().into(),
26///     ],
27///     ..Default::default()
28/// }
29/// ```
30#[derive(Debug, Default, Clone, Serialize, Deserialize)]
31pub struct Grid {
32    /// Explicit node identity.
33    pub id: Option<NodeId>,
34    /// Grid children (typically [`GridItem`] nodes).
35    pub children: Vec<Node>,
36    /// Column track definitions.
37    pub columns: Vec<GridTrack>,
38    /// Row track definitions.
39    pub rows: Vec<GridTrack>,
40    /// Horizontal gap between columns in layout points.
41    pub column_gap: Option<f32>,
42    /// Vertical gap between rows in layout points.
43    pub row_gap: Option<f32>,
44    /// Padding `[left, right, top, bottom]`.
45    pub padding: [f32; 4],
46}
47
48impl Grid {
49    pub fn into_node(self) -> crate::ui::Node {
50        crate::ui::Node::Grid(self)
51    }
52}
53
54impl Lower for Grid {
55    fn lower(&self, cx: &mut LoweringContext) -> NodeId {
56        let id = self.id.unwrap_or_else(|| cx.next_node_id());
57        cx.push_scope(id);
58
59        let mut builder = NodeBuilder::new(
60            id,
61            Op::Layout(LayoutOp::Grid {
62                columns: self.columns.clone(),
63                rows: self.rows.clone(),
64                column_gap: self.column_gap,
65                row_gap: self.row_gap,
66                padding: self.padding,
67            }),
68        );
69
70        for child in &self.children {
71            builder.add_child(child.lower(cx));
72        }
73
74        cx.pop_scope();
75        builder.build(cx)
76    }
77}
78
79/// A child placed within a [`Grid`] at a specific row/column position.
80///
81/// Use [`cell`](GridItem::cell) to set the row and column, and
82/// [`span`](GridItem::span) to span multiple tracks.
83///
84/// # Example
85///
86/// ```rust,ignore
87/// GridItem::new(content)
88///     .cell(2, 1)       // row 2, column 1
89///     .span(1, 2)        // span 1 row, 2 columns
90/// ```
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct GridItem {
93    /// Explicit node identity.
94    pub id: Option<NodeId>,
95    /// The child widget placed in the grid cell.
96    pub child: Box<Node>,
97    /// Starting row (1-indexed line or Auto).
98    pub row_start: GridPlacement,
99    /// Ending row (Auto or Span).
100    pub row_end: GridPlacement,
101    /// Starting column (1-indexed line or Auto).
102    pub col_start: GridPlacement,
103    /// Ending column (Auto or Span).
104    pub col_end: GridPlacement,
105}
106
107impl Default for GridItem {
108    fn default() -> Self {
109        Self {
110            id: None,
111            // Default child: empty Row
112            child: Box::new(Node::Row(crate::ui::Row::default())), 
113            row_start: GridPlacement::Auto,
114            row_end: GridPlacement::Auto,
115            col_start: GridPlacement::Auto,
116            col_end: GridPlacement::Auto,
117        }
118    }
119}
120
121impl GridItem {
122    pub fn new(child: Node) -> Self {
123        Self {
124            child: Box::new(child),
125            ..Default::default()
126        }
127    }
128    
129    pub fn cell(mut self, row: i16, col: i16) -> Self {
130        self.row_start = GridPlacement::Line(row);
131        self.col_start = GridPlacement::Line(col);
132        self
133    }
134    
135    pub fn span(mut self, row_span: u16, col_span: u16) -> Self {
136        self.row_end = GridPlacement::Span(row_span);
137        self.col_end = GridPlacement::Span(col_span);
138        self
139    }
140
141    pub fn into_node(self) -> Node {
142        Node::GridItem(self)
143    }
144}
145
146impl Lower for GridItem {
147    fn lower(&self, cx: &mut LoweringContext) -> NodeId {
148        let id = self.id.unwrap_or_else(|| cx.next_node_id());
149        cx.push_scope(id);
150        
151        let child_id = self.child.lower(cx);
152        
153        cx.pop_scope();
154        
155        let mut builder = NodeBuilder::new(
156            id, 
157            Op::Layout(LayoutOp::GridItem {
158                row_start: self.row_start,
159                row_end: self.row_end,
160                col_start: self.col_start,
161                col_end: self.col_end,
162            })
163        );
164        builder.add_child(child_id);
165        builder.build(cx)
166    }
167}