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 | |
35 | struct _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 | |
43 | void 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 | |
219 | Size2 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 | |
250 | Vector<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 | |
262 | Vector<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 | |
274 | void 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 | |
292 | void 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 | |
298 | int FlowContainer::get_line_count() const { |
299 | return cached_line_count; |
300 | } |
301 | |
302 | void FlowContainer::set_alignment(AlignmentMode p_alignment) { |
303 | if (alignment == p_alignment) { |
304 | return; |
305 | } |
306 | alignment = p_alignment; |
307 | _resort(); |
308 | } |
309 | |
310 | FlowContainer::AlignmentMode FlowContainer::get_alignment() const { |
311 | return alignment; |
312 | } |
313 | |
314 | void 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 | |
321 | bool FlowContainer::is_vertical() const { |
322 | return vertical; |
323 | } |
324 | |
325 | FlowContainer::FlowContainer(bool p_vertical) { |
326 | vertical = p_vertical; |
327 | } |
328 | |
329 | void 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 | |