Skip to main content

ferroui_controls/primitives/
uniform_grid.rs

1use crate::{ControlImpl, Panel, PanelImpl};
2use ferroui_base::input::InputElementImpl;
3use ferroui_base::interactivity::InteractiveImpl;
4use ferroui_base::layout::{LayoutHelper, Layoutable, LayoutableImpl};
5use ferroui_base::utilities::math_utilities::max;
6use ferroui_base::{
7    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroProperty,
8    Rect, Ref, Size, StyledElementImpl, StyledProperty, VisualImpl,
9};
10use std::cell::Cell;
11
12/// A panel which lays out its children in a grid with all cells having the
13/// same size.
14#[repr(C)]
15pub struct UniformGrid {
16    base: Panel,
17    rows: Cell<i32>,
18    columns: Cell<i32>,
19}
20
21ferro_class!(UniformGrid: Panel);
22ferroui_base::ferro_class_info!(UniformGrid { new: UniformGrid::new });
23ferro_impl_classes!(
24    UniformGrid: StyledElementImpl,
25    VisualImpl,
26    InteractiveImpl,
27    InputElementImpl,
28    ControlImpl,
29    PanelImpl
30);
31
32ferroui_base::ferro_impl_classes!(UniformGrid: FerroObjectImpl);
33
34impl LayoutableImpl for UniformGrid {
35    fn measure_override(this: &Self, available_size: Size) -> Size {
36        this.update_rows_and_columns();
37
38        let rows = this.rows.get() as f64;
39        let columns = this.columns.get() as f64;
40        let mut max_width = 0.0;
41        let mut max_height = 0.0;
42
43        let child_available_size = Size::new(
44            (available_size.width - (columns - 1.0) * this.column_spacing()) / columns,
45            (available_size.height - (rows - 1.0) * this.row_spacing()) / rows,
46        );
47
48        for child in this.children().snapshot().iter() {
49            child.measure(child_available_size);
50
51            if child.desired_size().width > max_width {
52                max_width = child.desired_size().width;
53            }
54
55            if child.desired_size().height > max_height {
56                max_height = child.desired_size().height;
57            }
58        }
59
60        if this.use_layout_rounding() {
61            let scale = LayoutHelper::get_layout_scale(this);
62            max_width = LayoutHelper::round_layout_value(max_width, scale);
63            max_height = LayoutHelper::round_layout_value(max_height, scale);
64        }
65
66        let total_width = max_width * columns + this.column_spacing() * (columns - 1.0);
67        let total_height = max_height * rows + this.row_spacing() * (rows - 1.0);
68
69        Size::new(max(total_width, 0.0), max(total_height, 0.0))
70    }
71
72    fn arrange_override(this: &Self, final_size: Size) -> Size {
73        let mut x = this.first_column();
74        let mut y = 0;
75
76        let column_spacing = this.column_spacing();
77        let row_spacing = this.row_spacing();
78        let rows = this.rows.get();
79        let columns = this.columns.get();
80
81        let mut width = max((final_size.width - (columns - 1) as f64 * column_spacing) / columns as f64, 0.0);
82        let mut height = max((final_size.height - (rows - 1) as f64 * row_spacing) / rows as f64, 0.0);
83
84        // If layout rounding is enabled, round the per-cell unit size to
85        // integral device units.
86        if this.use_layout_rounding() {
87            let scale = LayoutHelper::get_layout_scale(this);
88            width = LayoutHelper::round_layout_value(width, scale);
89            height = LayoutHelper::round_layout_value(height, scale);
90        }
91
92        for child in this.children().snapshot().iter() {
93            if !child.is_visible() {
94                continue;
95            }
96
97            let rect = Rect::new(x as f64 * (width + column_spacing), y as f64 * (height + row_spacing), width, height);
98
99            child.arrange(rect);
100
101            x += 1;
102
103            if x >= columns {
104                x = 0;
105                y += 1;
106            }
107        }
108
109        final_size
110    }
111}
112
113ferroui_base::ferro_properties! { impl UniformGrid {
114    ferro_property!(
115        /// Defines the `Rows` property.
116        pub fn rows_property() -> StyledProperty<i32> {
117            FerroProperty::register::<UniformGrid, _>("Rows", 0)
118        }
119    );
120
121    ferro_property!(
122        /// Defines the `Columns` property.
123        pub fn columns_property() -> StyledProperty<i32> {
124            FerroProperty::register::<UniformGrid, _>("Columns", 0)
125        }
126    );
127
128    ferro_property!(
129        /// Defines the `FirstColumn` property.
130        pub fn first_column_property() -> StyledProperty<i32> {
131            FerroProperty::register::<UniformGrid, _>("FirstColumn", 0)
132        }
133    );
134
135    ferro_property!(
136        /// Defines the `RowSpacing` property.
137        pub fn row_spacing_property() -> StyledProperty<f64> {
138            FerroProperty::register::<UniformGrid, _>("RowSpacing", 0.0)
139        }
140    );
141
142    ferro_property!(
143        /// Defines the `ColumnSpacing` property.
144        pub fn column_spacing_property() -> StyledProperty<f64> {
145            FerroProperty::register::<UniformGrid, _>("ColumnSpacing", 0.0)
146        }
147    );
148} }
149
150impl UniformGrid {
151    fn static_constructor() {
152        Layoutable::affects_measure::<UniformGrid>(&[
153            Self::rows_property().as_property(),
154            Self::columns_property().as_property(),
155            Self::first_column_property().as_property(),
156        ]);
157    }
158
159    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
160    pub fn construct() -> Self {
161        Self { base: Panel::construct(), rows: Cell::new(0), columns: Cell::new(0) }
162    }
163
164    pub fn new() -> Ref<Self> {
165        instantiate(Self::construct())
166    }
167
168    /// The number of rows in each column. A value of zero indicates that the
169    /// number of rows is computed.
170    pub fn rows(&self) -> i32 {
171        self.get_value(Self::rows_property())
172    }
173
174    pub fn set_rows(&self, value: i32) {
175        self.set_value(Self::rows_property(), value)
176    }
177
178    /// The number of columns in each row. A value of zero indicates that the
179    /// number of columns is computed.
180    pub fn columns(&self) -> i32 {
181        self.get_value(Self::columns_property())
182    }
183
184    pub fn set_columns(&self, value: i32) {
185        self.set_value(Self::columns_property(), value)
186    }
187
188    /// The number of columns that are left blank in the first row of the
189    /// grid.
190    pub fn first_column(&self) -> i32 {
191        self.get_value(Self::first_column_property())
192    }
193
194    pub fn set_first_column(&self, value: i32) {
195        self.set_value(Self::first_column_property(), value)
196    }
197
198    /// The spacing between rows.
199    pub fn row_spacing(&self) -> f64 {
200        self.get_value(Self::row_spacing_property())
201    }
202
203    pub fn set_row_spacing(&self, value: f64) {
204        self.set_value(Self::row_spacing_property(), value)
205    }
206
207    /// The spacing between columns.
208    pub fn column_spacing(&self) -> f64 {
209        self.get_value(Self::column_spacing_property())
210    }
211
212    pub fn set_column_spacing(&self, value: f64) {
213        self.set_value(Self::column_spacing_property(), value)
214    }
215
216    fn update_rows_and_columns(&self) {
217        let mut rows = self.rows();
218        let mut columns = self.columns();
219
220        if self.first_column() >= columns {
221            self.set_current_value(Self::first_column_property(), 0);
222        }
223
224        let mut item_count = self.first_column();
225
226        for child in self.children().snapshot().iter() {
227            if child.is_visible() {
228                item_count += 1;
229            }
230        }
231
232        if rows == 0 {
233            if columns == 0 {
234                rows = (item_count as f64).sqrt().ceil() as i32;
235                columns = rows;
236            } else {
237                rows = item_count / columns;
238
239                if item_count % columns != 0 {
240                    rows += 1;
241                }
242            }
243        } else if columns == 0 {
244            columns = item_count / rows;
245
246            if item_count % rows != 0 {
247                columns += 1;
248            }
249        }
250
251        self.rows.set(rows);
252        self.columns.set(columns);
253    }
254}