Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/gui/flow_container.cpp
9903 views
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/gui/texture_rect.h"
34
#include "scene/theme/theme_db.h"
35
36
struct _LineData {
37
int child_count = 0;
38
int min_line_height = 0;
39
int min_line_length = 0;
40
int stretch_avail = 0;
41
float stretch_ratio_total = 0;
42
bool is_filled = false;
43
};
44
45
void FlowContainer::_resort() {
46
// Avoid resorting if invisible.
47
if (!is_visible_in_tree()) {
48
return;
49
}
50
51
bool rtl = is_layout_rtl();
52
53
HashMap<Control *, Size2i> children_minsize_cache;
54
55
Vector<_LineData> lines_data;
56
57
Vector2i ofs;
58
int line_height = 0;
59
int line_length = 0;
60
float line_stretch_ratio_total = 0;
61
int current_container_size = vertical ? get_size().y : get_size().x;
62
int children_in_current_line = 0;
63
Control *last_child = nullptr;
64
65
// First pass for line wrapping and minimum size calculation.
66
for (int i = 0; i < get_child_count(); i++) {
67
Control *child = as_sortable_control(get_child(i));
68
if (!child) {
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, true });
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, true });
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
last_child = child;
120
children_minsize_cache[child] = child_msc;
121
children_in_current_line++;
122
}
123
line_length = vertical ? (ofs.y) : (ofs.x);
124
bool is_filled = false;
125
if (last_child != nullptr) {
126
is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false);
127
}
128
lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled });
129
130
// Second pass for in-line expansion and alignment.
131
132
int current_line_idx = 0;
133
int child_idx_in_line = 0;
134
135
ofs.x = 0;
136
ofs.y = 0;
137
138
for (int i = 0; i < get_child_count(); i++) {
139
Control *child = as_sortable_control(get_child(i));
140
if (!child) {
141
continue;
142
}
143
Size2i child_size = children_minsize_cache[child];
144
145
_LineData line_data = lines_data[current_line_idx];
146
if (child_idx_in_line >= lines_data[current_line_idx].child_count) {
147
current_line_idx++;
148
child_idx_in_line = 0;
149
if (vertical) {
150
ofs.x += line_data.min_line_height + theme_cache.h_separation;
151
ofs.y = 0;
152
} else {
153
ofs.x = 0;
154
ofs.y += line_data.min_line_height + theme_cache.v_separation;
155
}
156
line_data = lines_data[current_line_idx];
157
}
158
159
// The first child of each line adds the offset caused by the alignment,
160
// but only if the line doesn't contain a child that expands.
161
if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) {
162
int alignment_ofs = 0;
163
bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled;
164
float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0;
165
switch (alignment) {
166
case ALIGNMENT_BEGIN: {
167
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) {
168
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
169
alignment_ofs = line_data.stretch_avail - prior_stretch_avail;
170
} else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) {
171
alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5;
172
}
173
}
174
} break;
175
case ALIGNMENT_CENTER: {
176
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) {
177
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) {
178
alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5);
179
} else { // Is LAST_WRAP_ALIGNMENT_BEGIN
180
alignment_ofs = prior_stretch_avail * 0.5;
181
}
182
} else {
183
alignment_ofs = line_data.stretch_avail * 0.5;
184
}
185
} break;
186
case ALIGNMENT_END: {
187
if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) {
188
if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) {
189
alignment_ofs = prior_stretch_avail;
190
} else { // Is LAST_WRAP_ALIGNMENT_CENTER
191
alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5;
192
}
193
} else {
194
alignment_ofs = line_data.stretch_avail;
195
}
196
} break;
197
default:
198
break;
199
}
200
if (vertical) { /* VERTICAL */
201
ofs.y += alignment_ofs;
202
} else { /* HORIZONTAL */
203
ofs.x += alignment_ofs;
204
}
205
}
206
207
bool is_unsupported_texture_rect = false;
208
if (lines_data.size() > 1) {
209
TextureRect *trect = Object::cast_to<TextureRect>(child);
210
if (trect) {
211
TextureRect::ExpandMode mode = trect->get_expand_mode();
212
if (mode == TextureRect::EXPAND_FIT_WIDTH || mode == TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL ||
213
mode == TextureRect::EXPAND_FIT_HEIGHT || mode == TextureRect::EXPAND_FIT_HEIGHT_PROPORTIONAL) {
214
is_unsupported_texture_rect = true;
215
}
216
}
217
}
218
219
if (is_unsupported_texture_rect) {
220
// Temporary fix for editor crash. Changing size of TextureRect with EXPAND_FIT_* ExpandModes can lead to infinite loop if child items are moved between lines.
221
WARN_PRINT_ONCE("TextureRects with Fit Expand Modes are currently not supported inside FlowContainers with multiple lines");
222
child_size = child->get_size();
223
} else if (vertical) { /* VERTICAL */
224
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)) {
225
child_size.width = line_data.min_line_height;
226
}
227
228
if (child->get_v_size_flags().has_flag(SIZE_EXPAND)) {
229
int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
230
child_size.height += stretch;
231
}
232
233
} else { /* HORIZONTAL */
234
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)) {
235
child_size.height = line_data.min_line_height;
236
}
237
238
if (child->get_h_size_flags().has_flag(SIZE_EXPAND)) {
239
int stretch = line_data.stretch_avail * child->get_stretch_ratio() / line_data.stretch_ratio_total;
240
child_size.width += stretch;
241
}
242
}
243
244
Rect2 child_rect = Rect2(ofs, child_size);
245
if (reverse_fill && !vertical) {
246
child_rect.position.y = get_rect().size.y - child_rect.position.y - child_rect.size.height;
247
}
248
if ((rtl && !vertical) || ((rtl != reverse_fill) && vertical)) {
249
child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width;
250
}
251
252
fit_child_in_rect(child, child_rect);
253
254
if (vertical) { /* VERTICAL */
255
ofs.y += child_size.height + theme_cache.v_separation;
256
} else { /* HORIZONTAL */
257
ofs.x += child_size.width + theme_cache.h_separation;
258
}
259
260
child_idx_in_line++;
261
}
262
cached_size = (vertical ? ofs.x : ofs.y) + line_height;
263
cached_line_count = lines_data.size();
264
cached_line_max_child_count = lines_data.size() > 0 ? lines_data[0].child_count : 0;
265
}
266
267
Size2 FlowContainer::get_minimum_size() const {
268
Size2i minimum;
269
270
for (int i = 0; i < get_child_count(); i++) {
271
Control *c = as_sortable_control(get_child(i), SortableVisibilityMode::VISIBLE);
272
if (!c) {
273
continue;
274
}
275
276
Size2i size = c->get_combined_minimum_size();
277
278
if (vertical) { /* VERTICAL */
279
minimum.height = MAX(minimum.height, size.height);
280
minimum.width = cached_size;
281
282
} else { /* HORIZONTAL */
283
minimum.width = MAX(minimum.width, size.width);
284
minimum.height = cached_size;
285
}
286
}
287
288
return minimum;
289
}
290
291
Vector<int> FlowContainer::get_allowed_size_flags_horizontal() const {
292
Vector<int> flags;
293
flags.append(SIZE_FILL);
294
if (!vertical) {
295
flags.append(SIZE_EXPAND);
296
}
297
flags.append(SIZE_SHRINK_BEGIN);
298
flags.append(SIZE_SHRINK_CENTER);
299
flags.append(SIZE_SHRINK_END);
300
return flags;
301
}
302
303
Vector<int> FlowContainer::get_allowed_size_flags_vertical() const {
304
Vector<int> flags;
305
flags.append(SIZE_FILL);
306
if (vertical) {
307
flags.append(SIZE_EXPAND);
308
}
309
flags.append(SIZE_SHRINK_BEGIN);
310
flags.append(SIZE_SHRINK_CENTER);
311
flags.append(SIZE_SHRINK_END);
312
return flags;
313
}
314
315
void FlowContainer::_notification(int p_what) {
316
switch (p_what) {
317
case NOTIFICATION_SORT_CHILDREN: {
318
_resort();
319
update_minimum_size();
320
} break;
321
322
case NOTIFICATION_THEME_CHANGED: {
323
update_minimum_size();
324
} break;
325
326
case NOTIFICATION_TRANSLATION_CHANGED:
327
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
328
queue_sort();
329
} break;
330
}
331
}
332
333
void FlowContainer::_validate_property(PropertyInfo &p_property) const {
334
if (is_fixed && p_property.name == "vertical") {
335
p_property.usage = PROPERTY_USAGE_NONE;
336
}
337
}
338
339
int FlowContainer::get_line_count() const {
340
return cached_line_count;
341
}
342
343
int FlowContainer::get_line_max_child_count() const {
344
return cached_line_max_child_count;
345
}
346
347
void FlowContainer::set_alignment(AlignmentMode p_alignment) {
348
if (alignment == p_alignment) {
349
return;
350
}
351
alignment = p_alignment;
352
_resort();
353
}
354
355
FlowContainer::AlignmentMode FlowContainer::get_alignment() const {
356
return alignment;
357
}
358
359
void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) {
360
if (last_wrap_alignment == p_last_wrap_alignment) {
361
return;
362
}
363
last_wrap_alignment = p_last_wrap_alignment;
364
_resort();
365
}
366
367
FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const {
368
return last_wrap_alignment;
369
}
370
371
void FlowContainer::set_vertical(bool p_vertical) {
372
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
373
vertical = p_vertical;
374
update_minimum_size();
375
_resort();
376
}
377
378
bool FlowContainer::is_vertical() const {
379
return vertical;
380
}
381
382
void FlowContainer::set_reverse_fill(bool p_reverse_fill) {
383
if (reverse_fill == p_reverse_fill) {
384
return;
385
}
386
reverse_fill = p_reverse_fill;
387
_resort();
388
}
389
390
bool FlowContainer::is_reverse_fill() const {
391
return reverse_fill;
392
}
393
394
FlowContainer::FlowContainer(bool p_vertical) {
395
vertical = p_vertical;
396
}
397
398
void FlowContainer::_bind_methods() {
399
ClassDB::bind_method(D_METHOD("get_line_count"), &FlowContainer::get_line_count);
400
401
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment);
402
ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment);
403
ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment);
404
ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment);
405
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical);
406
ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical);
407
ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill);
408
ClassDB::bind_method(D_METHOD("is_reverse_fill"), &FlowContainer::is_reverse_fill);
409
410
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
411
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
412
BIND_ENUM_CONSTANT(ALIGNMENT_END);
413
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT);
414
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN);
415
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER);
416
BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END);
417
418
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
419
ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment");
420
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
421
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill");
422
423
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, h_separation);
424
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, FlowContainer, v_separation);
425
}
426
427