1/**************************************************************************/
2/* flow_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 "flow_container.h"
32
33#include "scene/theme/theme_db.h"
34
35struct _LineData {
36 int child_count = 0;
37 int min_line_height = 0;
38 int min_line_length = 0;
39 int stretch_avail = 0;
40 float stretch_ratio_total = 0;
41};
42
43void FlowContainer::_resort() {
44 // Avoid resorting if invisible.
45 if (!is_visible_in_tree()) {
46 return;
47 }
48
49 bool rtl = is_layout_rtl();
50
51 HashMap<Control *, Size2i> children_minsize_cache;
52
53 Vector<_LineData> lines_data;
54
55 Vector2i ofs;
56 int line_height = 0;
57 int line_length = 0;
58 float line_stretch_ratio_total = 0;
59 int current_container_size = vertical ? get_rect().size.y : get_rect().size.x;
60 int children_in_current_line = 0;
61
62 // First pass for line wrapping and minimum size calculation.
63 for (int i = 0; i < get_child_count(); i++) {
64 Control *child = Object::cast_to<Control>(get_child(i));
65 if (!child || !child->is_visible()) {
66 continue;
67 }
68 if (child->is_set_as_top_level()) {
69 continue;
70 }
71
72 Size2i child_msc = child->get_combined_minimum_size();
73
74 if (vertical) { /* VERTICAL */
75 if (children_in_current_line > 0) {
76 ofs.y += theme_cache.v_separation;
77 }
78 if (ofs.y + child_msc.y > current_container_size) {
79 line_length = ofs.y - theme_cache.v_separation;
80 lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
81
82 // Move in new column (vertical line).
83 ofs.x += line_height + theme_cache.h_separation;
84 ofs.y = 0;
85 line_height = 0;
86 line_stretch_ratio_total = 0;
87 children_in_current_line = 0;
88 }
89
90 line_height = MAX(line_height, child_msc.x);
91 if (child->get_v_size_flags().has_flag(SIZE_EXPAND)) {
92 line_stretch_ratio_total += child->get_stretch_ratio();
93 }
94 ofs.y += child_msc.y;
95
96 } else { /* HORIZONTAL */
97 if (children_in_current_line > 0) {
98 ofs.x += theme_cache.h_separation;
99 }
100 if (ofs.x + child_msc.x > current_container_size) {
101 line_length = ofs.x - theme_cache.h_separation;
102 lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
103
104 // Move in new line.
105 ofs.y += line_height + theme_cache.v_separation;
106 ofs.x = 0;
107 line_height = 0;
108 line_stretch_ratio_total = 0;
109 children_in_current_line = 0;
110 }
111
112 line_height = MAX(line_height, child_msc.y);
113 if (child->get_h_size_flags().has_flag(SIZE_EXPAND)) {
114 line_stretch_ratio_total += child->get_stretch_ratio();
115 }
116 ofs.x += child_msc.x;
117 }
118
119 children_minsize_cache[child] = child_msc;
120 children_in_current_line++;
121 }
122 line_length = vertical ? (ofs.y) : (ofs.x);
123 lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total });
124
125 // Second pass for in-line expansion and alignment.
126
127 int current_line_idx = 0;
128 int child_idx_in_line = 0;
129
130 ofs.x = 0;
131 ofs.y = 0;
132
133 for (int i = 0; i < get_child_count(); i++) {
134 Control *child = Object::cast_to<Control>(get_child(i));
135 if (!child || !child->is_visible()) {
136 continue;
137 }
138 if (child->is_set_as_top_level()) {
139 continue;
140 }
141 Size2i child_size = children_minsize_cache[child];
142
143 _LineData line_data = lines_data[current_line_idx];
144 if (child_idx_in_line >= lines_data[current_line_idx].child_count) {
145 current_line_idx++;
146 child_idx_in_line = 0;
147 if (vertical) {
148 ofs.x += line_data.min_line_height + theme_cache.h_separation;
149 ofs.y = 0;
150 } else {
151 ofs.x = 0;
152 ofs.y += line_data.min_line_height + theme_cache.v_separation;
153 }
154 line_data = lines_data[current_line_idx];
155 }
156
157 // The first child of each line adds the offset caused by the alignment,
158 // but only if the line doesn't contain a child that expands.
159 if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
160 int alignment_ofs = 0;
161 switch (alignment) {
162 case ALIGNMENT_CENTER:
163 alignment_ofs = line_data.stretch_avail / 2;
164 break;
165 case ALIGNMENT_END:
166 alignment_ofs = line_data.stretch_avail;
167 break;
168 default:
169 break;
170 }
171
172 if (vertical) { /* VERTICAL */
173 ofs.y += alignment_ofs;
174 } else { /* HORIZONTAL */
175 ofs.x += alignment_ofs;
176 }
177 }
178
179 if (vertical) { /* VERTICAL */
180 if (child->get_h_size_flags().has_flag(SIZE_FILL) || child->get_h_size_flags().has_flag(SIZE_SHRINK_CENTER) || child->get_h_size_flags().has_flag(SIZE_SHRINK_END)) {
181 child_size.width = line_data.min_line_height;
182 }
183
184 if (child->get_v_size_flags().has_flag(SIZE_EXPAND)) {
185 int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
186 child_size.height += stretch;
187 }
188
189 } else { /* HORIZONTAL */
190 if (child->get_v_size_flags().has_flag(SIZE_FILL) || child->get_v_size_flags().has_flag(SIZE_SHRINK_CENTER) || child->get_v_size_flags().has_flag(SIZE_SHRINK_END)) {
191 child_size.height = line_data.min_line_height;
192 }
193
194 if (child->get_h_size_flags().has_flag(SIZE_EXPAND)) {
195 int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
196 child_size.width += stretch;
197 }
198 }
199
200 Rect2 child_rect = Rect2(ofs, child_size);
201 if (rtl) {
202 child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width;
203 }
204
205 fit_child_in_rect(child, child_rect);
206
207 if (vertical) { /* VERTICAL */
208 ofs.y += child_size.height + theme_cache.v_separation;
209 } else { /* HORIZONTAL */
210 ofs.x += child_size.width + theme_cache.h_separation;
211 }
212
213 child_idx_in_line++;
214 }
215 cached_size = (vertical ? ofs.x : ofs.y) + line_height;
216 cached_line_count = lines_data.size();
217}
218
219Size2 FlowContainer::get_minimum_size() const {
220 Size2i minimum;
221
222 for (int i = 0; i < get_child_count(); i++) {
223 Control *c = Object::cast_to<Control>(get_child(i));
224 if (!c) {
225 continue;
226 }
227 if (c->is_set_as_top_level()) {
228 continue;
229 }
230
231 if (!c->is_visible()) {
232 continue;
233 }
234
235 Size2i size = c->get_combined_minimum_size();
236
237 if (vertical) { /* VERTICAL */
238 minimum.height = MAX(minimum.height, size.height);
239 minimum.width = cached_size;
240
241 } else { /* HORIZONTAL */
242 minimum.width = MAX(minimum.width, size.width);
243 minimum.height = cached_size;
244 }
245 }
246
247 return minimum;
248}
249
250Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const {
251 Vector<int> flags;
252 flags.append(SIZE_FILL);
253 if (!vertical) {
254 flags.append(SIZE_EXPAND);
255 }
256 flags.append(SIZE_SHRINK_BEGIN);
257 flags.append(SIZE_SHRINK_CENTER);
258 flags.append(SIZE_SHRINK_END);
259 return flags;
260}
261
262Vector<int> FlowContainer::get_allowed_size_flags_vertical() const {
263 Vector<int> flags;
264 flags.append(SIZE_FILL);
265 if (vertical) {
266 flags.append(SIZE_EXPAND);
267 }
268 flags.append(SIZE_SHRINK_BEGIN);
269 flags.append(SIZE_SHRINK_CENTER);
270 flags.append(SIZE_SHRINK_END);
271 return flags;
272}
273
274void FlowContainer::_notification(int p_what) {
275 switch (p_what) {
276 case NOTIFICATION_SORT_CHILDREN: {
277 _resort();
278 update_minimum_size();
279 } break;
280
281 case NOTIFICATION_THEME_CHANGED: {
282 update_minimum_size();
283 } break;
284
285 case NOTIFICATION_TRANSLATION_CHANGED:
286 case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
287 queue_sort();
288 } break;
289 }
290}
291
292void FlowContainer::_validate_property(PropertyInfo &p_property) const {
293 if (is_fixed && p_property.name == "vertical") {
294 p_property.usage = PROPERTY_USAGE_NONE;
295 }
296}
297
298int FlowContainer::get_line_count() const {
299 return cached_line_count;
300}
301
302void FlowContainer::set_alignment(AlignmentMode p_alignment) {
303 if (alignment == p_alignment) {
304 return;
305 }
306 alignment = p_alignment;
307 _resort();
308}
309
310FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
311 return alignment;
312}
313
314void FlowContainer::set_vertical(bool p_vertical) {
315 ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
316 vertical = p_vertical;
317 update_minimum_size();
318 _resort();
319}
320
321bool FlowContainer::is_vertical() const {
322 return vertical;
323}
324
325FlowContainer::FlowContainer(bool p_vertical) {
326 vertical = p_vertical;
327}
328
329void FlowContainer::_bind_methods() {
330 ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count);
331
332 ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
333 ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
334 ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
335 ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
336
337 BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
338 BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
339 BIND_ENUM_CONSTANT(ALIGNMENT_END);
340
341 ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
342 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
343
344 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, h_separation);
345 BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, v_separation);
346}
347