1 /*************************************************************************/
2 /*  split_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 "split_container.h"
31 
32 #include "label.h"
33 #include "margin_container.h"
34 
35 struct _MinSizeCache {
36 
37 	int min_size;
38 	bool will_stretch;
39 	int final_size;
40 };
41 
_getch(int p_idx) const42 Control *SplitContainer::_getch(int p_idx) const {
43 
44 	int idx = 0;
45 
46 	for (int i = 0; i < get_child_count(); i++) {
47 		Control *c = get_child(i)->cast_to<Control>();
48 		if (!c || !c->is_visible())
49 			continue;
50 		if (c->is_set_as_toplevel())
51 			continue;
52 
53 		if (idx == p_idx)
54 			return c;
55 
56 		idx++;
57 	}
58 
59 	return NULL;
60 }
61 
_resort()62 void SplitContainer::_resort() {
63 
64 	/** First pass, determine minimum size AND amount of stretchable elements */
65 
66 	int axis = vertical ? 1 : 0;
67 
68 	bool has_first = _getch(0);
69 	bool has_second = _getch(1);
70 
71 	if (!has_first && !has_second) {
72 		return;
73 	} else if (!(has_first && has_second)) {
74 		if (has_first)
75 			fit_child_in_rect(_getch(0), Rect2(Point2(), get_size()));
76 		else
77 			fit_child_in_rect(_getch(1), Rect2(Point2(), get_size()));
78 
79 		return;
80 	}
81 
82 	Control *first = _getch(0);
83 	Control *second = _getch(1);
84 
85 	bool ratiomode = false;
86 	bool expand_first_mode = false;
87 
88 	if (vertical) {
89 
90 		ratiomode = first->get_v_size_flags() & SIZE_EXPAND && second->get_v_size_flags() & SIZE_EXPAND;
91 		expand_first_mode = first->get_v_size_flags() & SIZE_EXPAND && !(second->get_v_size_flags() & SIZE_EXPAND);
92 	} else {
93 
94 		ratiomode = first->get_h_size_flags() & SIZE_EXPAND && second->get_h_size_flags() & SIZE_EXPAND;
95 		expand_first_mode = first->get_h_size_flags() & SIZE_EXPAND && !(second->get_h_size_flags() & SIZE_EXPAND);
96 	}
97 
98 	int sep = get_constant("separation");
99 	Ref<Texture> g = get_icon("grabber");
100 
101 	if (dragger_visibility == DRAGGER_HIDDEN_COLLAPSED) {
102 		sep = 0;
103 	} else {
104 		sep = MAX(sep, vertical ? g->get_height() : g->get_width());
105 	}
106 
107 	int total = vertical ? get_size().height : get_size().width;
108 
109 	total -= sep;
110 
111 	int minimum = 0;
112 
113 	Size2 ms_first = first->get_combined_minimum_size();
114 	Size2 ms_second = second->get_combined_minimum_size();
115 
116 	if (vertical) {
117 
118 		minimum = ms_first.height + ms_second.height;
119 	} else {
120 
121 		minimum = ms_first.width + ms_second.width;
122 	}
123 
124 	int available = total - minimum;
125 	if (available < 0)
126 		available = 0;
127 
128 	middle_sep = 0;
129 
130 	if (collapsed) {
131 
132 		if (ratiomode) {
133 
134 			middle_sep = ms_first[axis] + available / 2;
135 
136 		} else if (expand_first_mode) {
137 
138 			middle_sep = get_size()[axis] - ms_second[axis] - sep;
139 
140 		} else {
141 
142 			middle_sep = ms_first[axis];
143 		}
144 
145 	} else if (ratiomode) {
146 
147 		if (expand_ofs < -(available / 2))
148 			expand_ofs = -(available / 2);
149 		else if (expand_ofs > (available / 2))
150 			expand_ofs = (available / 2);
151 
152 		middle_sep = ms_first[axis] + available / 2 + expand_ofs;
153 
154 	} else if (expand_first_mode) {
155 
156 		if (expand_ofs > 0)
157 			expand_ofs = 0;
158 
159 		if (expand_ofs < -available)
160 			expand_ofs = -available;
161 
162 		middle_sep = get_size()[axis] - ms_second[axis] - sep + expand_ofs;
163 
164 	} else {
165 
166 		if (expand_ofs < 0)
167 			expand_ofs = 0;
168 
169 		if (expand_ofs > available)
170 			expand_ofs = available;
171 
172 		middle_sep = ms_first[axis] + expand_ofs;
173 	}
174 
175 	if (vertical) {
176 
177 		fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, middle_sep)));
178 		int sofs = middle_sep + sep;
179 		fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs)));
180 
181 	} else {
182 
183 		fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(middle_sep, get_size().height)));
184 		int sofs = middle_sep + sep;
185 		fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height)));
186 	}
187 
188 	update();
189 	_change_notify("split/offset");
190 }
191 
get_minimum_size() const192 Size2 SplitContainer::get_minimum_size() const {
193 
194 	/* Calculate MINIMUM SIZE */
195 
196 	Size2i minimum;
197 	int sep = get_constant("separation");
198 	Ref<Texture> g = get_icon("grabber");
199 	sep = (dragger_visibility != DRAGGER_HIDDEN_COLLAPSED) ? MAX(sep, vertical ? g->get_height() : g->get_width()) : 0;
200 
201 	for (int i = 0; i < 2; i++) {
202 
203 		if (!_getch(i))
204 			break;
205 
206 		if (i == 1) {
207 
208 			if (vertical)
209 				minimum.height += sep;
210 			else
211 				minimum.width += sep;
212 		}
213 
214 		Size2 ms = _getch(i)->get_combined_minimum_size();
215 
216 		if (vertical) {
217 
218 			minimum.height += ms.height;
219 			minimum.width = MAX(minimum.width, ms.width);
220 		} else {
221 
222 			minimum.width += ms.width;
223 			minimum.height = MAX(minimum.height, ms.height);
224 		}
225 	}
226 
227 	return minimum;
228 }
229 
_notification(int p_what)230 void SplitContainer::_notification(int p_what) {
231 
232 	switch (p_what) {
233 
234 		case NOTIFICATION_SORT_CHILDREN: {
235 
236 			_resort();
237 		} break;
238 		case NOTIFICATION_MOUSE_ENTER: {
239 			mouse_inside = true;
240 			update();
241 		} break;
242 		case NOTIFICATION_MOUSE_EXIT: {
243 			mouse_inside = false;
244 			update();
245 		} break;
246 		case NOTIFICATION_DRAW: {
247 
248 			if (!_getch(0) || !_getch(1))
249 				return;
250 
251 			if (collapsed || (!mouse_inside && get_constant("autohide")))
252 				return;
253 			int sep = dragger_visibility != DRAGGER_HIDDEN_COLLAPSED ? get_constant("separation") : 0;
254 			Ref<Texture> tex = get_icon("grabber");
255 			Size2 size = get_size();
256 			if (vertical) {
257 
258 				//draw_style_box( get_stylebox("bg"), Rect2(0,middle_sep,get_size().width,sep));
259 				if (dragger_visibility == DRAGGER_VISIBLE)
260 					draw_texture(tex, Point2i((size.x - tex->get_width()) / 2, middle_sep + (sep - tex->get_height()) / 2));
261 
262 			} else {
263 
264 				//draw_style_box( get_stylebox("bg"), Rect2(middle_sep,0,sep,get_size().height));
265 				if (dragger_visibility == DRAGGER_VISIBLE)
266 					draw_texture(tex, Point2i(middle_sep + (sep - tex->get_width()) / 2, (size.y - tex->get_height()) / 2));
267 			}
268 
269 		} break;
270 	}
271 }
272 
_input_event(const InputEvent & p_event)273 void SplitContainer::_input_event(const InputEvent &p_event) {
274 
275 	if (collapsed || !_getch(0) || !_getch(1) || dragger_visibility != DRAGGER_VISIBLE)
276 		return;
277 
278 	if (p_event.type == InputEvent::MOUSE_BUTTON) {
279 
280 		const InputEventMouseButton &mb = p_event.mouse_button;
281 
282 		if (mb.button_index == BUTTON_LEFT) {
283 
284 			if (mb.pressed) {
285 				int sep = get_constant("separation");
286 
287 				if (vertical) {
288 
289 					if (mb.y > middle_sep && mb.y < middle_sep + sep) {
290 						dragging = true;
291 						drag_from = mb.y;
292 						drag_ofs = expand_ofs;
293 					}
294 				} else {
295 
296 					if (mb.x > middle_sep && mb.x < middle_sep + sep) {
297 						dragging = true;
298 						drag_from = mb.x;
299 						drag_ofs = expand_ofs;
300 					}
301 				}
302 			} else {
303 
304 				dragging = false;
305 			}
306 		}
307 	}
308 
309 	if (p_event.type == InputEvent::MOUSE_MOTION) {
310 
311 		const InputEventMouseMotion &mm = p_event.mouse_motion;
312 
313 		if (dragging) {
314 
315 			expand_ofs = drag_ofs + ((vertical ? mm.y : mm.x) - drag_from);
316 			queue_sort();
317 			emit_signal("dragged", get_split_offset());
318 		}
319 	}
320 }
321 
get_cursor_shape(const Point2 & p_pos) const322 Control::CursorShape SplitContainer::get_cursor_shape(const Point2 &p_pos) const {
323 
324 	if (collapsed)
325 		return Control::get_cursor_shape(p_pos);
326 
327 	if (dragging)
328 		return (vertical ? CURSOR_VSIZE : CURSOR_HSIZE);
329 
330 	int sep = get_constant("separation");
331 
332 	if (vertical) {
333 
334 		if (p_pos.y > middle_sep && p_pos.y < middle_sep + sep) {
335 			return CURSOR_VSIZE;
336 		}
337 	} else {
338 
339 		if (p_pos.x > middle_sep && p_pos.x < middle_sep + sep) {
340 			return CURSOR_HSIZE;
341 		}
342 	}
343 
344 	return Control::get_cursor_shape(p_pos);
345 }
346 
set_split_offset(int p_offset)347 void SplitContainer::set_split_offset(int p_offset) {
348 
349 	if (expand_ofs == p_offset)
350 		return;
351 	expand_ofs = p_offset;
352 	queue_sort();
353 }
354 
get_split_offset() const355 int SplitContainer::get_split_offset() const {
356 
357 	return expand_ofs;
358 }
359 
set_collapsed(bool p_collapsed)360 void SplitContainer::set_collapsed(bool p_collapsed) {
361 
362 	if (collapsed == p_collapsed)
363 		return;
364 	collapsed = p_collapsed;
365 	queue_sort();
366 }
367 
set_dragger_visibility(DraggerVisibility p_visibility)368 void SplitContainer::set_dragger_visibility(DraggerVisibility p_visibility) {
369 
370 	dragger_visibility = p_visibility;
371 	queue_sort();
372 	update();
373 }
374 
get_dragger_visibility() const375 SplitContainer::DraggerVisibility SplitContainer::get_dragger_visibility() const {
376 
377 	return dragger_visibility;
378 }
379 
is_collapsed() const380 bool SplitContainer::is_collapsed() const {
381 
382 	return collapsed;
383 }
384 
_bind_methods()385 void SplitContainer::_bind_methods() {
386 
387 	ObjectTypeDB::bind_method(_MD("_input_event"), &SplitContainer::_input_event);
388 	ObjectTypeDB::bind_method(_MD("set_split_offset", "offset"), &SplitContainer::set_split_offset);
389 	ObjectTypeDB::bind_method(_MD("get_split_offset"), &SplitContainer::get_split_offset);
390 
391 	ObjectTypeDB::bind_method(_MD("set_collapsed", "collapsed"), &SplitContainer::set_collapsed);
392 	ObjectTypeDB::bind_method(_MD("is_collapsed"), &SplitContainer::is_collapsed);
393 
394 	ObjectTypeDB::bind_method(_MD("set_dragger_visibility", "mode"), &SplitContainer::set_dragger_visibility);
395 	ObjectTypeDB::bind_method(_MD("get_dragger_visibility"), &SplitContainer::get_dragger_visibility);
396 
397 	ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
398 
399 	ADD_PROPERTY(PropertyInfo(Variant::INT, "split/offset"), _SCS("set_split_offset"), _SCS("get_split_offset"));
400 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "split/collapsed"), _SCS("set_collapsed"), _SCS("is_collapsed"));
401 	ADD_PROPERTY(PropertyInfo(Variant::INT, "split/dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden & Collapsed"), _SCS("set_dragger_visibility"), _SCS("get_dragger_visibility"));
402 
403 	BIND_CONSTANT(DRAGGER_VISIBLE);
404 	BIND_CONSTANT(DRAGGER_HIDDEN);
405 	BIND_CONSTANT(DRAGGER_HIDDEN_COLLAPSED);
406 }
407 
SplitContainer(bool p_vertical)408 SplitContainer::SplitContainer(bool p_vertical) {
409 
410 	mouse_inside = false;
411 	expand_ofs = 0;
412 	middle_sep = 0;
413 	vertical = p_vertical;
414 	dragging = false;
415 	collapsed = false;
416 	dragger_visibility = DRAGGER_VISIBLE;
417 }
418