Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/scene/gui/box_container.cpp
9896 views
1
/**************************************************************************/
2
/* box_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 "box_container.h"
32
33
#include "scene/gui/label.h"
34
#include "scene/gui/margin_container.h"
35
#include "scene/theme/theme_db.h"
36
37
struct _MinSizeCache {
38
int min_size = 0;
39
bool will_stretch = false;
40
int final_size = 0;
41
};
42
43
void BoxContainer::_resort() {
44
/** First pass, determine minimum size AND amount of stretchable elements */
45
46
Size2i new_size = get_size();
47
48
bool rtl = is_layout_rtl();
49
50
bool first = true;
51
int children_count = 0;
52
int stretch_min = 0;
53
int stretch_avail = 0;
54
float stretch_ratio_total = 0.0;
55
HashMap<Control *, _MinSizeCache> min_size_cache;
56
57
for (int i = 0; i < get_child_count(); i++) {
58
Control *c = as_sortable_control(get_child(i));
59
if (!c) {
60
continue;
61
}
62
63
Size2i size = c->get_combined_minimum_size();
64
_MinSizeCache msc;
65
66
if (vertical) { /* VERTICAL */
67
stretch_min += size.height;
68
msc.min_size = size.height;
69
msc.will_stretch = c->get_v_size_flags().has_flag(SIZE_EXPAND);
70
71
} else { /* HORIZONTAL */
72
stretch_min += size.width;
73
msc.min_size = size.width;
74
msc.will_stretch = c->get_h_size_flags().has_flag(SIZE_EXPAND);
75
}
76
77
if (msc.will_stretch) {
78
stretch_avail += msc.min_size;
79
stretch_ratio_total += c->get_stretch_ratio();
80
}
81
msc.final_size = msc.min_size;
82
min_size_cache[c] = msc;
83
children_count++;
84
}
85
86
if (children_count == 0) {
87
return;
88
}
89
90
int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * theme_cache.separation;
91
int stretch_diff = stretch_max - stretch_min;
92
if (stretch_diff < 0) {
93
//avoid negative stretch space
94
stretch_diff = 0;
95
}
96
97
stretch_avail += stretch_diff; //available stretch space.
98
/** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
99
elements exist */
100
101
bool has_stretched = false;
102
while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist
103
104
has_stretched = true;
105
bool refit_successful = true; //assume refit-test will go well
106
float error = 0.0; // Keep track of accumulated error in pixels
107
108
for (int i = 0; i < get_child_count(); i++) {
109
Control *c = as_sortable_control(get_child(i));
110
if (!c) {
111
continue;
112
}
113
114
ERR_FAIL_COND(!min_size_cache.has(c));
115
_MinSizeCache &msc = min_size_cache[c];
116
117
if (msc.will_stretch) { //wants to stretch
118
//let's see if it can really stretch
119
float final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
120
// Add leftover fractional pixels to error accumulator
121
error += final_pixel_size - (int)final_pixel_size;
122
if (final_pixel_size < msc.min_size) {
123
//if available stretching area is too small for widget,
124
//then remove it from stretching area
125
msc.will_stretch = false;
126
stretch_ratio_total -= c->get_stretch_ratio();
127
refit_successful = false;
128
stretch_avail -= msc.min_size;
129
msc.final_size = msc.min_size;
130
break;
131
} else {
132
msc.final_size = final_pixel_size;
133
// Dump accumulated error if one pixel or more
134
if (error >= 1) {
135
msc.final_size += 1;
136
error -= 1;
137
}
138
}
139
}
140
}
141
142
if (refit_successful) { //uf refit went well, break
143
break;
144
}
145
}
146
147
/** Final pass, draw and stretch elements **/
148
149
int ofs = 0;
150
if (!has_stretched) {
151
if (!vertical) {
152
switch (alignment) {
153
case ALIGNMENT_BEGIN:
154
if (rtl) {
155
ofs = stretch_diff;
156
}
157
break;
158
case ALIGNMENT_CENTER:
159
ofs = stretch_diff / 2;
160
break;
161
case ALIGNMENT_END:
162
if (!rtl) {
163
ofs = stretch_diff;
164
}
165
break;
166
}
167
} else {
168
switch (alignment) {
169
case ALIGNMENT_BEGIN:
170
break;
171
case ALIGNMENT_CENTER:
172
ofs = stretch_diff / 2;
173
break;
174
case ALIGNMENT_END:
175
ofs = stretch_diff;
176
break;
177
}
178
}
179
}
180
181
first = true;
182
int idx = 0;
183
184
int start;
185
int end;
186
int delta;
187
if (!rtl || vertical) {
188
start = 0;
189
end = get_child_count();
190
delta = +1;
191
} else {
192
start = get_child_count() - 1;
193
end = -1;
194
delta = -1;
195
}
196
197
for (int i = start; i != end; i += delta) {
198
Control *c = as_sortable_control(get_child(i));
199
if (!c) {
200
continue;
201
}
202
203
_MinSizeCache &msc = min_size_cache[c];
204
205
if (first) {
206
first = false;
207
} else {
208
ofs += theme_cache.separation;
209
}
210
211
int from = ofs;
212
int to = ofs + msc.final_size;
213
214
if (msc.will_stretch && idx == children_count - 1) {
215
//adjust so the last one always fits perfect
216
//compensating for numerical imprecision
217
218
to = vertical ? new_size.height : new_size.width;
219
}
220
221
int size = to - from;
222
223
Rect2 rect;
224
225
if (vertical) {
226
rect = Rect2(0, from, new_size.width, size);
227
} else {
228
rect = Rect2(from, 0, size, new_size.height);
229
}
230
231
fit_child_in_rect(c, rect);
232
233
ofs = to;
234
idx++;
235
}
236
}
237
238
Size2 BoxContainer::get_minimum_size() const {
239
/* Calculate MINIMUM SIZE */
240
241
Size2i minimum;
242
243
bool first = true;
244
245
for (int i = 0; i < get_child_count(); i++) {
246
Control *c = as_sortable_control(get_child(i), SortableVisibilityMode::VISIBLE);
247
if (!c) {
248
continue;
249
}
250
251
Size2i size = c->get_combined_minimum_size();
252
253
if (vertical) { /* VERTICAL */
254
255
if (size.width > minimum.width) {
256
minimum.width = size.width;
257
}
258
259
minimum.height += size.height + (first ? 0 : theme_cache.separation);
260
261
} else { /* HORIZONTAL */
262
263
if (size.height > minimum.height) {
264
minimum.height = size.height;
265
}
266
267
minimum.width += size.width + (first ? 0 : theme_cache.separation);
268
}
269
270
first = false;
271
}
272
273
return minimum;
274
}
275
276
void BoxContainer::_notification(int p_what) {
277
switch (p_what) {
278
case NOTIFICATION_SORT_CHILDREN: {
279
_resort();
280
} break;
281
282
case NOTIFICATION_THEME_CHANGED: {
283
update_minimum_size();
284
} break;
285
286
case NOTIFICATION_TRANSLATION_CHANGED:
287
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
288
queue_sort();
289
} break;
290
}
291
}
292
293
void BoxContainer::_validate_property(PropertyInfo &p_property) const {
294
if (is_fixed && p_property.name == "vertical") {
295
p_property.usage = PROPERTY_USAGE_NONE;
296
}
297
}
298
299
void BoxContainer::set_alignment(AlignmentMode p_alignment) {
300
if (alignment == p_alignment) {
301
return;
302
}
303
alignment = p_alignment;
304
_resort();
305
}
306
307
BoxContainer::AlignmentMode BoxContainer::get_alignment() const {
308
return alignment;
309
}
310
311
void BoxContainer::set_vertical(bool p_vertical) {
312
ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + ".");
313
vertical = p_vertical;
314
update_minimum_size();
315
_resort();
316
}
317
318
bool BoxContainer::is_vertical() const {
319
return vertical;
320
}
321
322
Control *BoxContainer::add_spacer(bool p_begin) {
323
Control *c = memnew(Control);
324
c->set_mouse_filter(MOUSE_FILTER_PASS); //allow spacer to pass mouse events
325
326
if (vertical) {
327
c->set_v_size_flags(SIZE_EXPAND_FILL);
328
} else {
329
c->set_h_size_flags(SIZE_EXPAND_FILL);
330
}
331
332
add_child(c);
333
if (p_begin) {
334
move_child(c, 0);
335
}
336
337
return c;
338
}
339
340
Vector<int> BoxContainer::get_allowed_size_flags_horizontal() const {
341
Vector<int> flags;
342
flags.append(SIZE_FILL);
343
if (!vertical) {
344
flags.append(SIZE_EXPAND);
345
}
346
flags.append(SIZE_SHRINK_BEGIN);
347
flags.append(SIZE_SHRINK_CENTER);
348
flags.append(SIZE_SHRINK_END);
349
return flags;
350
}
351
352
Vector<int> BoxContainer::get_allowed_size_flags_vertical() const {
353
Vector<int> flags;
354
flags.append(SIZE_FILL);
355
if (vertical) {
356
flags.append(SIZE_EXPAND);
357
}
358
flags.append(SIZE_SHRINK_BEGIN);
359
flags.append(SIZE_SHRINK_CENTER);
360
flags.append(SIZE_SHRINK_END);
361
return flags;
362
}
363
364
BoxContainer::BoxContainer(bool p_vertical) {
365
vertical = p_vertical;
366
}
367
368
void BoxContainer::_bind_methods() {
369
ClassDB::bind_method(D_METHOD("add_spacer", "begin"), &BoxContainer::add_spacer);
370
ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &BoxContainer::set_alignment);
371
ClassDB::bind_method(D_METHOD("get_alignment"), &BoxContainer::get_alignment);
372
ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &BoxContainer::set_vertical);
373
ClassDB::bind_method(D_METHOD("is_vertical"), &BoxContainer::is_vertical);
374
375
BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN);
376
BIND_ENUM_CONSTANT(ALIGNMENT_CENTER);
377
BIND_ENUM_CONSTANT(ALIGNMENT_END);
378
379
ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment");
380
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
381
382
BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, BoxContainer, separation);
383
}
384
385
MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) {
386
Label *l = memnew(Label);
387
l->set_theme_type_variation("HeaderSmall");
388
l->set_text(p_label);
389
add_child(l);
390
MarginContainer *mc = memnew(MarginContainer);
391
mc->add_theme_constant_override("margin_left", 0);
392
mc->add_child(p_control, true);
393
add_child(mc);
394
if (p_expand) {
395
mc->set_v_size_flags(SIZE_EXPAND_FILL);
396
}
397
p_control->set_accessibility_name(p_label);
398
399
return mc;
400
}
401
402