Skip to main content

fission_core/ui/widgets/
grid.rs

1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use crate::ui::Widget;
4use fission_ir::{
5    op::{GridPlacement, GridTrack, LayoutOp, Op},
6    WidgetId,
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")).cell(1, 1).into(),
25///         GridItem::new(Text::new("B")).cell(1, 2).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<WidgetId>,
34    /// Grid children (typically [`GridItem`] nodes).
35    pub children: Vec<Widget>,
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
50impl InternalLower for Grid {
51    fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
52        let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
53        cx.push_scope(id);
54
55        let mut builder = InternalIrBuilder::new(
56            id,
57            Op::Layout(LayoutOp::Grid {
58                columns: self.columns.clone(),
59                rows: self.rows.clone(),
60                column_gap: self.column_gap,
61                row_gap: self.row_gap,
62                padding: self.padding,
63            }),
64        );
65
66        for child in &self.children {
67            builder.add_child(child.lower(cx));
68        }
69
70        cx.pop_scope();
71        builder.build(cx)
72    }
73}
74
75/// A child placed within a [`Grid`] at a specific row/column position.
76///
77/// Use [`cell`](GridItem::cell) to set the row and column, and
78/// [`span`](GridItem::span) to span multiple tracks.
79///
80/// # Example
81///
82/// ```rust,ignore
83/// GridItem::new(content)
84///     .cell(2, 1)       // row 2, column 1
85///     .span(1, 2)        // span 1 row, 2 columns
86/// ```
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct GridItem {
89    /// Explicit node identity.
90    pub id: Option<WidgetId>,
91    /// The child widget placed in the grid cell.
92    pub child: Widget,
93    /// Starting row (1-indexed line or Auto).
94    pub row_start: GridPlacement,
95    /// Ending row (Auto or Span).
96    pub row_end: GridPlacement,
97    /// Starting column (1-indexed line or Auto).
98    pub col_start: GridPlacement,
99    /// Ending column (Auto or Span).
100    pub col_end: GridPlacement,
101}
102
103impl Default for GridItem {
104    fn default() -> Self {
105        Self {
106            id: None,
107            // Default child: empty Row
108            child: crate::ui::Row::default().into(),
109            row_start: GridPlacement::Auto,
110            row_end: GridPlacement::Auto,
111            col_start: GridPlacement::Auto,
112            col_end: GridPlacement::Auto,
113        }
114    }
115}
116
117impl GridItem {
118    pub fn new(child: impl Into<Widget>) -> Self {
119        Self {
120            child: child.into(),
121            ..Default::default()
122        }
123    }
124
125    pub fn cell(mut self, row: i16, col: i16) -> Self {
126        self.row_start = GridPlacement::Line(row);
127        self.col_start = GridPlacement::Line(col);
128        self
129    }
130
131    pub fn span(mut self, row_span: u16, col_span: u16) -> Self {
132        self.row_end = GridPlacement::Span(row_span);
133        self.col_end = GridPlacement::Span(col_span);
134        self
135    }
136}
137
138impl InternalLower for GridItem {
139    fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
140        let id = self.id.map(Into::into).unwrap_or_else(|| cx.next_node_id());
141        cx.push_scope(id);
142
143        let child_id = self.child.lower(cx);
144
145        cx.pop_scope();
146
147        let mut builder = InternalIrBuilder::new(
148            id,
149            Op::Layout(LayoutOp::GridItem {
150                row_start: self.row_start,
151                row_end: self.row_end,
152                col_start: self.col_start,
153                col_end: self.col_end,
154            }),
155        );
156        builder.add_child(child_id);
157        builder.build(cx)
158    }
159}