1/**************************************************************************/
2/* grid_container.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "grid_container.h"
32
33#include "core/templates/rb_set.h"
34#include "scene/theme/theme_db.h"
35
36void GridContainer::_notification(int p_what) {
37 switch (p_what) {
38 case NOTIFICATION_SORT_CHILDREN: {
39 RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
40 RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
41 RBSet<int> col_expanded; // Columns which have the SIZE_EXPAND flag set.
42 RBSet<int> row_expanded; // Rows which have the SIZE_EXPAND flag set.
43
44 // Compute the per-column/per-row data.
45 int valid_controls_index = 0;
46 for (int i = 0; i < get_child_count(); i++) {
47 Control *c = Object::cast_to<Control>(get_child(i));
48 if (!c || !c->is_visible_in_tree()) {
49 continue;
50 }
51 if (c->is_set_as_top_level()) {
52 continue;
53 }
54
55 int row = valid_controls_index / columns;
56 int col = valid_controls_index % columns;
57 valid_controls_index++;
58
59 Size2i ms = c->get_combined_minimum_size();
60 if (col_minw.has(col)) {
61 col_minw[col] = MAX(col_minw[col], ms.width);
62 } else {
63 col_minw[col] = ms.width;
64 }
65 if (row_minh.has(row)) {
66 row_minh[row] = MAX(row_minh[row], ms.height);
67 } else {
68 row_minh[row] = ms.height;
69 }
70
71 if (c->get_h_size_flags().has_flag(SIZE_EXPAND)) {
72 col_expanded.insert(col);
73 }
74 if (c->get_v_size_flags().has_flag(SIZE_EXPAND)) {
75 row_expanded.insert(row);
76 }
77 }
78
79 int max_col = MIN(valid_controls_index, columns);
80 int max_row = ceil((float)valid_controls_index / (float)columns);
81
82 // Consider all empty columns expanded.
83 for (int i = valid_controls_index; i < columns; i++) {
84 col_expanded.insert(i);
85 }
86
87 // Evaluate the remaining space for expanded columns/rows.
88 Size2 remaining_space = get_size();
89 for (const KeyValue<int, int> &E : col_minw) {
90 if (!col_expanded.has(E.key)) {
91 remaining_space.width -= E.value;
92 }
93 }
94
95 for (const KeyValue<int, int> &E : row_minh) {
96 if (!row_expanded.has(E.key)) {
97 remaining_space.height -= E.value;
98 }
99 }
100 remaining_space.height -= theme_cache.v_separation * MAX(max_row - 1, 0);
101 remaining_space.width -= theme_cache.h_separation * MAX(max_col - 1, 0);
102
103 bool can_fit = false;
104 while (!can_fit && col_expanded.size() > 0) {
105 // Check if all minwidth constraints are OK if we use the remaining space.
106 can_fit = true;
107 int max_index = col_expanded.front()->get();
108 for (const int &E : col_expanded) {
109 if (col_minw[E] > col_minw[max_index]) {
110 max_index = E;
111 }
112 if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E]) {
113 can_fit = false;
114 }
115 }
116
117 // If not, the column with maximum minwidth is not expanded.
118 if (!can_fit) {
119 col_expanded.erase(max_index);
120 remaining_space.width -= col_minw[max_index];
121 }
122 }
123
124 can_fit = false;
125 while (!can_fit && row_expanded.size() > 0) {
126 // Check if all minheight constraints are OK if we use the remaining space.
127 can_fit = true;
128 int max_index = row_expanded.front()->get();
129 for (const int &E : row_expanded) {
130 if (row_minh[E] > row_minh[max_index]) {
131 max_index = E;
132 }
133 if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E]) {
134 can_fit = false;
135 }
136 }
137
138 // If not, the row with maximum minheight is not expanded.
139 if (!can_fit) {
140 row_expanded.erase(max_index);
141 remaining_space.height -= row_minh[max_index];
142 }
143 }
144
145 // Finally, fit the nodes.
146 int col_remaining_pixel = 0;
147 int col_expand = 0;
148 if (col_expanded.size() > 0) {
149 col_expand = remaining_space.width / col_expanded.size();
150 col_remaining_pixel = remaining_space.width - col_expanded.size() * col_expand;
151 }
152
153 int row_remaining_pixel = 0;
154 int row_expand = 0;
155 if (row_expanded.size() > 0) {
156 row_expand = remaining_space.height / row_expanded.size();
157 row_remaining_pixel = remaining_space.height - row_expanded.size() * row_expand;
158 }
159
160 bool rtl = is_layout_rtl();
161
162 int col_ofs = 0;
163 int row_ofs = 0;
164
165 // Calculate the index of rows and columns that receive the remaining pixel.
166 int col_remaining_pixel_index = 0;
167 for (int i = 0; i < max_col; i++) {
168 if (col_remaining_pixel == 0) {
169 break;
170 }
171 if (col_expanded.has(i)) {
172 col_remaining_pixel_index = i + 1;
173 col_remaining_pixel--;
174 }
175 }
176 int row_remaining_pixel_index = 0;
177 for (int i = 0; i < max_row; i++) {
178 if (row_remaining_pixel == 0) {
179 break;
180 }
181 if (row_expanded.has(i)) {
182 row_remaining_pixel_index = i + 1;
183 row_remaining_pixel--;
184 }
185 }
186
187 valid_controls_index = 0;
188 for (int i = 0; i < get_child_count(); i++) {
189 Control *c = Object::cast_to<Control>(get_child(i));
190 if (!c || !c->is_visible_in_tree()) {
191 continue;
192 }
193 int row = valid_controls_index / columns;
194 int col = valid_controls_index % columns;
195 valid_controls_index++;
196
197 if (col == 0) {
198 if (rtl) {
199 col_ofs = get_size().width;
200 } else {
201 col_ofs = 0;
202 }
203 if (row > 0) {
204 row_ofs += (row_expanded.has(row - 1) ? row_expand : row_minh[row - 1]) + theme_cache.v_separation;
205
206 if (row_expanded.has(row - 1) && row - 1 < row_remaining_pixel_index) {
207 // Apply the remaining pixel of the previous row.
208 row_ofs++;
209 }
210 }
211 }
212
213 Size2 s(col_expanded.has(col) ? col_expand : col_minw[col], row_expanded.has(row) ? row_expand : row_minh[row]);
214
215 // Add the remaining pixel to the expanding columns and rows, starting from left and top.
216 if (col_expanded.has(col) && col < col_remaining_pixel_index) {
217 s.x++;
218 }
219 if (row_expanded.has(row) && row < row_remaining_pixel_index) {
220 s.y++;
221 }
222
223 if (rtl) {
224 Point2 p(col_ofs - s.width, row_ofs);
225 fit_child_in_rect(c, Rect2(p, s));
226 col_ofs -= s.width + theme_cache.h_separation;
227 } else {
228 Point2 p(col_ofs, row_ofs);
229 fit_child_in_rect(c, Rect2(p, s));
230 col_ofs += s.width + theme_cache.h_separation;
231 }
232 }
233 } break;
234
235 case NOTIFICATION_THEME_CHANGED: {
236 update_minimum_size();
237 } break;
238
239 case NOTIFICATION_TRANSLATION_CHANGED:
240 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
241 queue_sort();
242 } break;
243 }
244}
245
246void GridContainer::set_columns(int p_columns) {
247 ERR_FAIL_COND(p_columns < 1);
248
249 if (columns == p_columns) {
250 return;
251 }
252
253 columns = p_columns;
254 queue_sort();
255 update_minimum_size();
256}
257
258int GridContainer::get_columns() const {
259 return columns;
260}
261
262int GridContainer::get_h_separation() const {
263 return theme_cache.h_separation;
264}
265
266void GridContainer::_bind_methods() {
267 ClassDB::bind_method(D_METHOD("set_columns", "columns"), &GridContainer::set_columns);
268 ClassDB::bind_method(D_METHOD("get_columns"), &GridContainer::get_columns);
269
270 ADD_PROPERTY(PropertyInfo(Variant::INT, "columns", PROPERTY_HINT_RANGE, "1,1024,1"), "set_columns", "get_columns");
271
272 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GridContainer, h_separation);
273 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GridContainer, v_separation);
274}
275
276Size2 GridContainer::get_minimum_size() const {
277 RBMap<int, int> col_minw;
278 RBMap<int, int> row_minh;
279
280 int max_row = 0;
281 int max_col = 0;
282
283 int valid_controls_index = 0;
284 for (int i = 0; i < get_child_count(); i++) {
285 Control *c = Object::cast_to<Control>(get_child(i));
286 if (!c || !c->is_visible()) {
287 continue;
288 }
289 int row = valid_controls_index / columns;
290 int col = valid_controls_index % columns;
291 valid_controls_index++;
292
293 Size2i ms = c->get_combined_minimum_size();
294 if (col_minw.has(col)) {
295 col_minw[col] = MAX(col_minw[col], ms.width);
296 } else {
297 col_minw[col] = ms.width;
298 }
299
300 if (row_minh.has(row)) {
301 row_minh[row] = MAX(row_minh[row], ms.height);
302 } else {
303 row_minh[row] = ms.height;
304 }
305 max_col = MAX(col, max_col);
306 max_row = MAX(row, max_row);
307 }
308
309 Size2 ms;
310
311 for (const KeyValue<int, int> &E : col_minw) {
312 ms.width += E.value;
313 }
314
315 for (const KeyValue<int, int> &E : row_minh) {
316 ms.height += E.value;
317 }
318
319 ms.height += theme_cache.v_separation * max_row;
320 ms.width += theme_cache.h_separation * max_col;
321
322 return ms;
323}
324
325GridContainer::GridContainer() {}
326