1 /*************************************************************************/
2 /*  box_container.cpp                                                    */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
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 #include "box_container.h"
31 #include "label.h"
32 #include "margin_container.h"
33 
34 struct _MinSizeCache {
35 
36 	int min_size;
37 	bool will_stretch;
38 	int final_size;
39 };
40 
_resort()41 void BoxContainer::_resort() {
42 
43 	/** First pass, determine minimum size AND amount of stretchable elements */
44 
45 	Size2i new_size = get_size();
46 
47 	int sep = get_constant("separation"); //,vertical?"VBoxContainer":"HBoxContainer");
48 
49 	bool first = true;
50 	int children_count = 0;
51 	int stretch_min = 0;
52 	int stretch_avail = 0;
53 	float stretch_ratio_total = 0;
54 	Map<Control *, _MinSizeCache> min_size_cache;
55 
56 	for (int i = 0; i < get_child_count(); i++) {
57 		Control *c = get_child(i)->cast_to<Control>();
58 		if (!c || !c->is_visible())
59 			continue;
60 		if (c->is_set_as_toplevel())
61 			continue;
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() & 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() & 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 	int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * sep;
90 	int stretch_diff = stretch_max - stretch_min;
91 	if (stretch_diff < 0) {
92 		//avoid negative stretch space
93 		stretch_max = stretch_min;
94 		stretch_diff = 0;
95 	}
96 
97 	stretch_avail += stretch_diff; //available stretch space.
98 	/** Second, pass sucessively 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, dont 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 
107 		for (int i = 0; i < get_child_count(); i++) {
108 
109 			Control *c = get_child(i)->cast_to<Control>();
110 			if (!c || !c->is_visible())
111 				continue;
112 			if (c->is_set_as_toplevel())
113 				continue;
114 
115 			ERR_FAIL_COND(!min_size_cache.has(c));
116 			_MinSizeCache &msc = min_size_cache[c];
117 
118 			if (msc.will_stretch) { //wants to stretch
119 				//let's see if it can really stretch
120 
121 				int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
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 				}
134 			}
135 		}
136 
137 		if (refit_successful) //uf refit went well, break
138 			break;
139 	}
140 
141 	/** Final pass, draw and stretch elements **/
142 
143 	int ofs = 0;
144 	if (!has_stretched) {
145 		switch (align) {
146 			case ALIGN_BEGIN:
147 				break;
148 			case ALIGN_CENTER:
149 				ofs = stretch_diff / 2;
150 				break;
151 			case ALIGN_END:
152 				ofs = stretch_diff;
153 				break;
154 		}
155 	}
156 
157 	first = true;
158 	int idx = 0;
159 
160 	for (int i = 0; i < get_child_count(); i++) {
161 
162 		Control *c = get_child(i)->cast_to<Control>();
163 		if (!c || !c->is_visible())
164 			continue;
165 		if (c->is_set_as_toplevel())
166 			continue;
167 
168 		_MinSizeCache &msc = min_size_cache[c];
169 
170 		if (first)
171 			first = false;
172 		else
173 			ofs += sep;
174 
175 		int from = ofs;
176 		int to = ofs + msc.final_size;
177 
178 		if (msc.will_stretch && idx == children_count - 1) {
179 			//adjust so the last one always fits perfect
180 			//compensating for numerical imprecision
181 
182 			to = vertical ? new_size.height : new_size.width;
183 		}
184 
185 		int size = to - from;
186 
187 		Rect2 rect;
188 
189 		if (vertical) {
190 
191 			rect = Rect2(0, from, new_size.width, size);
192 		} else {
193 
194 			rect = Rect2(from, 0, size, new_size.height);
195 		}
196 
197 		fit_child_in_rect(c, rect);
198 
199 		ofs = to;
200 		idx++;
201 	}
202 }
203 
get_minimum_size() const204 Size2 BoxContainer::get_minimum_size() const {
205 
206 	/* Calculate MINIMUM SIZE */
207 
208 	Size2i minimum;
209 	int sep = get_constant("separation"); //,vertical?"VBoxContainer":"HBoxContainer");
210 
211 	bool first = true;
212 
213 	for (int i = 0; i < get_child_count(); i++) {
214 		Control *c = get_child(i)->cast_to<Control>();
215 		if (!c)
216 			continue;
217 		if (c->is_set_as_toplevel())
218 			continue;
219 
220 		if (c->is_hidden()) {
221 			continue;
222 		}
223 
224 		Size2i size = c->get_combined_minimum_size();
225 
226 		if (vertical) { /* VERTICAL */
227 
228 			if (size.width > minimum.width) {
229 				minimum.width = size.width;
230 			}
231 
232 			minimum.height += size.height + (first ? 0 : sep);
233 
234 		} else { /* HORIZONTAL */
235 
236 			if (size.height > minimum.height) {
237 				minimum.height = size.height;
238 			}
239 
240 			minimum.width += size.width + (first ? 0 : sep);
241 		}
242 
243 		first = false;
244 	}
245 
246 	return minimum;
247 }
248 
_notification(int p_what)249 void BoxContainer::_notification(int p_what) {
250 
251 	switch (p_what) {
252 
253 		case NOTIFICATION_SORT_CHILDREN: {
254 
255 			_resort();
256 		} break;
257 	}
258 }
259 
set_alignment(AlignMode p_align)260 void BoxContainer::set_alignment(AlignMode p_align) {
261 	align = p_align;
262 	_resort();
263 }
264 
get_alignment() const265 BoxContainer::AlignMode BoxContainer::get_alignment() const {
266 	return align;
267 }
268 
add_spacer(bool p_begin)269 void BoxContainer::add_spacer(bool p_begin) {
270 
271 	Control *c = memnew(Control);
272 	c->set_stop_mouse(false);
273 	if (vertical)
274 		c->set_v_size_flags(SIZE_EXPAND_FILL);
275 	else
276 		c->set_h_size_flags(SIZE_EXPAND_FILL);
277 
278 	add_child(c);
279 	if (p_begin)
280 		move_child(c, 0);
281 }
282 
BoxContainer(bool p_vertical)283 BoxContainer::BoxContainer(bool p_vertical) {
284 
285 	vertical = p_vertical;
286 	align = ALIGN_BEGIN;
287 	//	set_ignore_mouse(true);
288 	set_stop_mouse(false);
289 }
290 
_bind_methods()291 void BoxContainer::_bind_methods() {
292 
293 	ObjectTypeDB::bind_method(_MD("add_spacer", "begin"), &BoxContainer::add_spacer);
294 	ObjectTypeDB::bind_method(_MD("get_alignment"), &BoxContainer::get_alignment);
295 	ObjectTypeDB::bind_method(_MD("set_alignment", "alignment"), &BoxContainer::set_alignment);
296 
297 	BIND_CONSTANT(ALIGN_BEGIN);
298 	BIND_CONSTANT(ALIGN_CENTER);
299 	BIND_CONSTANT(ALIGN_END);
300 
301 	ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), _SCS("set_alignment"), _SCS("get_alignment"));
302 }
303 
add_margin_child(const String & p_label,Control * p_control,bool p_expand)304 MarginContainer *VBoxContainer::add_margin_child(const String &p_label, Control *p_control, bool p_expand) {
305 
306 	Label *l = memnew(Label);
307 	l->set_text(p_label);
308 	add_child(l);
309 	MarginContainer *mc = memnew(MarginContainer);
310 	mc->add_child(p_control);
311 	add_child(mc);
312 	if (p_expand)
313 		mc->set_v_size_flags(SIZE_EXPAND_FILL);
314 
315 	return mc;
316 }
317