diff --git a/doc/classes/SplitContainer.xml b/doc/classes/SplitContainer.xml index a7727755aa4..8f243abb97b 100644 --- a/doc/classes/SplitContainer.xml +++ b/doc/classes/SplitContainer.xml @@ -1,10 +1,10 @@ - A container that splits two child controls horizontally or vertically and provides a grabber for adjusting the split ratio. + A container that arranges child controls horizontally or vertically and provides grabbers for adjusting the split ratios between them. - A container that accepts only two child controls, then arranges them horizontally or vertically and creates a divisor between them. The divisor can be dragged around to change the size relation between the child controls. + A container that arranges child controls horizontally or vertically and creates grabbers between them. The grabbers can be dragged around to change the size relations between the child controls. $DOCS_URL/tutorials/ui/gui_containers.html @@ -12,11 +12,12 @@ + - Clamps the [member split_offset] value to not go outside the currently possible minimal and maximum values. + Clamps the [member split_offsets] values to ensure they are within valid ranges and do not overlap with each other. When overlaps occur, this method prioritizes one split offset (at index [param priority_index]) by clamping any overlapping split offsets to it. - + Returns the drag area [Control]. For example, you can move a pre-configured button into the drag area [Control] so that it rides along with the split bar. Try setting the [Button] anchors to [code]center[/code] prior to the [code]reparent()[/code] call. @@ -27,10 +28,21 @@ [b]Warning:[/b] This is a required internal node, removing and freeing it may cause a crash. + + + + Returns an [Array] of the drag area [Control]s. These are the interactable [Control] nodes between each child. For example, this can be used to add a pre-configured button to a drag area [Control] so that it rides along with the split bar. Try setting the [Button] anchors to [code]center[/code] prior to the [method Node.reparent] call. + [codeblock] + $BarnacleButton.reparent($SplitContainer.get_drag_area_controls()[0]) + [/codeblock] + [b]Note:[/b] The drag area [Control]s are drawn over the [SplitContainer]'s children, so [CanvasItem] draw objects called from a drag area and children added to it will also appear over the [SplitContainer]'s children. Try setting [member Control.mouse_filter] of custom children to [constant Control.MOUSE_FILTER_IGNORE] to prevent blocking the mouse from dragging if desired. + [b]Warning:[/b] These are required internal nodes, removing or freeing them may cause a crash. + + - If [code]true[/code], the dragger will be disabled and the children will be sized as if the [member split_offset] was [code]0[/code]. + If [code]true[/code], the draggers will be disabled and the children will be sized as if all [member split_offsets] were [code]0[/code]. Highlights the drag area [Rect2] so you can see where it is during development. The drag area is gold if [member dragging_enabled] is [code]true[/code], and red if [code]false[/code]. @@ -50,8 +62,13 @@ Enables or disables split dragging. - - The initial offset of the splitting between the two [Control]s, with [code]0[/code] being at the end of the first [Control]. + + The first element of [member split_offsets]. + + + Offsets for each dragger in pixels. Each one is the offset of the split between the [Control] nodes before and after the dragger, with [code]0[/code] being the default position. The default position is based on the [Control] nodes expand flags and minimum sizes. See [member Control.size_flags_horizontal], [member Control.size_flags_vertical], and [member Control.size_flags_stretch_ratio]. + If none of the [Control] nodes before the dragger are expanded, the default position will be at the start of the [SplitContainer]. If none of the [Control] nodes after the dragger are expanded, the default position will be at the end of the [SplitContainer]. If the dragger is in between expanded [Control] nodes, the default position will be in the middle, based on the [member Control.size_flags_stretch_ratio]s and minimum sizes. + [b]Note:[/b] If the split offsets cause [Control] nodes to overlap, the first split will take priority when resolving the positions. If [code]true[/code], a touch-friendly drag handle will be enabled for better usability on smaller screens. Unlike the standard grabber, this drag handle overlaps the [SplitContainer]'s children and does not affect their minimum separation. The standard grabber will no longer be drawn when this option is enabled. @@ -75,7 +92,7 @@ - Emitted when the dragger is dragged by user. + Emitted when any dragger is dragged by user. @@ -104,20 +121,20 @@ The color of the touch dragger when pressed. - Boolean value. If [code]1[/code] ([code]true[/code]), the grabber will hide automatically when it isn't under the cursor. If [code]0[/code] ([code]false[/code]), it's always visible. The [member dragger_visibility] must be [constant DRAGGER_VISIBLE]. + Boolean value. If [code]1[/code] ([code]true[/code]), the grabbers will hide automatically when they aren't under the cursor. If [code]0[/code] ([code]false[/code]), the grabbers are always visible. The [member dragger_visibility] must be [constant DRAGGER_VISIBLE]. - The minimum thickness of the area users can click on to grab the split bar. This ensures that the split bar can still be dragged if [theme_item separation] or [theme_item h_grabber] / [theme_item v_grabber]'s size is too narrow to easily select. + The minimum thickness of the area users can click on to grab a split bar. This ensures that the split bar can still be dragged if [theme_item separation] or [theme_item h_grabber] / [theme_item v_grabber]'s size is too narrow to easily select. - The split bar thickness, i.e., the gap between the two children of the container. This is overridden by the size of the grabber icon if [member dragger_visibility] is set to [constant DRAGGER_VISIBLE], or [constant DRAGGER_HIDDEN], and [theme_item separation] is smaller than the size of the grabber icon in the same axis. + The split bar thickness, i.e., the gap between each child of the container. This is overridden by the size of the grabber icon if [member dragger_visibility] is set to [constant DRAGGER_VISIBLE], or [constant DRAGGER_HIDDEN], and [theme_item separation] is smaller than the size of the grabber icon in the same axis. [b]Note:[/b] To obtain [theme_item separation] values less than the size of the grabber icon, for example a [code]1 px[/code] hairline, set [theme_item h_grabber] or [theme_item v_grabber] to a new [ImageTexture], which effectively sets the grabber icon size to [code]0 px[/code]. - The icon used for the grabber drawn in the middle area. This is only used in [HSplitContainer] and [VSplitContainer]. For [SplitContainer], see [theme_item h_grabber] and [theme_item v_grabber] instead. + The icon used for the grabbers drawn in the separations. This is only used in [HSplitContainer] and [VSplitContainer]. For [SplitContainer], see [theme_item h_grabber] and [theme_item v_grabber] instead. - The icon used for the grabber drawn in the middle area when [member vertical] is [code]false[/code]. + The icon used for the grabbers drawn in the separations when [member vertical] is [code]false[/code]. The icon used for the drag handle when [member touch_dragger_enabled] is [code]true[/code] and [member vertical] is [code]false[/code]. @@ -126,7 +143,7 @@ The icon used for the drag handle when [member touch_dragger_enabled] is [code]true[/code]. This is only used in [HSplitContainer] and [VSplitContainer]. For [SplitContainer], see [theme_item h_touch_dragger] and [theme_item v_touch_dragger] instead. - The icon used for the grabber drawn in the middle area when [member vertical] is [code]true[/code]. + The icon used for the grabbers drawn in the separations when [member vertical] is [code]true[/code]. The icon used for the drag handle when [member touch_dragger_enabled] is [code]true[/code] and [member vertical] is [code]true[/code]. diff --git a/misc/extension_api_validation/4.5-stable.expected b/misc/extension_api_validation/4.5-stable.expected index efc6d535cc9..b3082400b3f 100644 --- a/misc/extension_api_validation/4.5-stable.expected +++ b/misc/extension_api_validation/4.5-stable.expected @@ -106,3 +106,10 @@ GH-112379 Validate extension JSON: Error: Field 'classes/DisplayServer/methods/tts_speak/arguments/5': meta changed value in new API, from "int32" to "int64". `utterance_id` argument changed from `int32` to `int64`. No compatibility method needed. + + +GH-90411 +-------- +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/SplitContainer/methods/clamp_split_offset': arguments + +Optional argument added for index. Compatibility method registered. diff --git a/scene/gui/split_container.compat.inc b/scene/gui/split_container.compat.inc new file mode 100644 index 00000000000..1833290be39 --- /dev/null +++ b/scene/gui/split_container.compat.inc @@ -0,0 +1,41 @@ +/**************************************************************************/ +/* split_container.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void SplitContainer::_clamp_split_offset_compat_90411() { + clamp_split_offset(0); +} + +void SplitContainer::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("clamp_split_offset"), &SplitContainer::_clamp_split_offset_compat_90411); +} + +#endif diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp index e70e5343700..42fb547ed11 100644 --- a/scene/gui/split_container.cpp +++ b/scene/gui/split_container.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "split_container.h" +#include "split_container.compat.inc" #include "scene/gui/texture_rect.h" #include "scene/main/viewport.h" @@ -39,7 +40,7 @@ void SplitContainerDragger::gui_input(const Ref &p_event) { SplitContainer *sc = Object::cast_to(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { + if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) { return; } @@ -48,14 +49,15 @@ void SplitContainerDragger::gui_input(const Ref &p_event) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - sc->_compute_split_offset(true); + // To match the visual position, clamp on the first split. + sc->_update_dragger_positions(0); dragging = true; sc->emit_signal(SNAME("drag_started")); - drag_ofs = sc->split_offset; + start_drag_split_offset = sc->get_split_offset(dragger_index); if (sc->vertical) { - drag_from = get_transform().xform(mb->get_position()).y; + drag_from = (int)get_transform().xform(mb->get_position()).y; } else { - drag_from = get_transform().xform(mb->get_position()).x; + drag_from = (int)get_transform().xform(mb->get_position()).x; } } else { dragging = false; @@ -73,14 +75,16 @@ void SplitContainerDragger::gui_input(const Ref &p_event) { } Vector2i in_parent_pos = get_transform().xform(mm->get_position()); + int new_drag_offset; if (!sc->vertical && is_layout_rtl()) { - sc->split_offset = drag_ofs - (in_parent_pos.x - drag_from); + new_drag_offset = start_drag_split_offset - (in_parent_pos.x - drag_from); } else { - sc->split_offset = drag_ofs + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); + new_drag_offset = start_drag_split_offset + ((sc->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from); } - sc->_compute_split_offset(true); + sc->set_split_offset(new_drag_offset, dragger_index); + sc->_update_dragger_positions(dragger_index); sc->queue_sort(); - sc->emit_signal(SNAME("dragged"), sc->get_split_offset()); + sc->emit_signal(SNAME("dragged"), sc->get_split_offset(dragger_index)); } } @@ -95,34 +99,87 @@ Control::CursorShape SplitContainerDragger::get_cursor_shape(const Point2 &p_pos void SplitContainerDragger::_accessibility_action_inc(const Variant &p_data) { SplitContainer *sc = Object::cast_to(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { + if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) { return; } - sc->split_offset -= 10; - sc->_compute_split_offset(true); - sc->queue_sort(); + sc->set_split_offset(sc->get_split_offset(dragger_index) - 10, dragger_index); + sc->clamp_split_offset(dragger_index); } void SplitContainerDragger::_accessibility_action_dec(const Variant &p_data) { SplitContainer *sc = Object::cast_to(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { + if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) { return; } - sc->split_offset += 10; - sc->_compute_split_offset(true); - sc->queue_sort(); + sc->set_split_offset(sc->get_split_offset(dragger_index) + 10, dragger_index); + sc->clamp_split_offset(dragger_index); } void SplitContainerDragger::_accessibility_action_set_value(const Variant &p_data) { SplitContainer *sc = Object::cast_to(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { + if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) { return; } - sc->split_offset = p_data; - sc->_compute_split_offset(true); - sc->queue_sort(); + sc->set_split_offset(p_data, dragger_index); + sc->clamp_split_offset(dragger_index); +} + +void SplitContainerDragger::_touch_dragger_mouse_exited() { + if (!dragging) { + SplitContainer *sc = Object::cast_to(get_parent()); + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color); + } +} + +void SplitContainerDragger::_touch_dragger_gui_input(const Ref &p_event) { + if (!touch_dragger) { + return; + } + Ref mm = p_event; + Ref mb = p_event; + SplitContainer *sc = Object::cast_to(get_parent()); + + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_pressed_color); + } else { + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color); + } + } + + if (mm.is_valid() && !dragging) { + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_hover_color); + } +} + +void SplitContainerDragger::set_touch_dragger_enabled(bool p_enabled) { + if (p_enabled) { + touch_dragger = memnew(TextureRect); + update_touch_dragger(); + SplitContainer *sc = Object::cast_to(get_parent()); + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color); + touch_dragger->connect(SceneStringName(gui_input), callable_mp(this, &SplitContainerDragger::_touch_dragger_gui_input)); + touch_dragger->connect(SceneStringName(mouse_exited), callable_mp(this, &SplitContainerDragger::_touch_dragger_mouse_exited)); + add_child(touch_dragger, false, Node::INTERNAL_MODE_FRONT); + } else { + if (touch_dragger) { + touch_dragger->queue_free(); + touch_dragger = nullptr; + } + } + queue_redraw(); +} + +void SplitContainerDragger::update_touch_dragger() { + if (!touch_dragger) { + return; + } + SplitContainer *sc = Object::cast_to(get_parent()); + touch_dragger->set_texture(sc->_get_touch_dragger_icon()); + touch_dragger->set_anchors_and_offsets_preset(Control::PRESET_CENTER); + touch_dragger->set_default_cursor_shape(sc->vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); } void SplitContainerDragger::_notification(int p_what) { @@ -134,17 +191,25 @@ void SplitContainerDragger::_notification(int p_what) { DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPLITTER); SplitContainer *sc = Object::cast_to(get_parent()); - if (sc->collapsed || !sc->_get_sortable_child(0) || !sc->_get_sortable_child(1) || !sc->dragging_enabled) { + if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) { return; } - sc->_compute_split_offset(true); - DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->split_offset); + sc->clamp_split_offset(dragger_index); + DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->get_split_offset(dragger_index)); DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_dec)); DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_inc)); DisplayServer::get_singleton()->accessibility_update_add_action(ae, DisplayServer::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &SplitContainerDragger::_accessibility_action_set_value)); } break; + case NOTIFICATION_THEME_CHANGED: { + if (touch_dragger) { + SplitContainer *sc = Object::cast_to(get_parent()); + touch_dragger->set_modulate(sc->theme_cache.touch_dragger_color); + touch_dragger->set_texture(sc->_get_touch_dragger_icon()); + } + } break; + case NOTIFICATION_MOUSE_ENTER: { mouse_inside = true; SplitContainer *sc = Object::cast_to(get_parent()); @@ -152,6 +217,7 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; + case NOTIFICATION_MOUSE_EXIT: { mouse_inside = false; SplitContainer *sc = Object::cast_to(get_parent()); @@ -159,10 +225,24 @@ void SplitContainerDragger::_notification(int p_what) { queue_redraw(); } } break; + + case NOTIFICATION_FOCUS_EXIT: { + if (dragging) { + dragging = false; + queue_redraw(); + } + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (dragging && !is_visible_in_tree()) { + dragging = false; + } + } break; + case NOTIFICATION_DRAW: { SplitContainer *sc = Object::cast_to(get_parent()); draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect); - if (sc->dragger_visibility == sc->DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide) && !sc->touch_dragger_enabled) { + if (sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide) && !sc->touch_dragger_enabled) { Ref tex = sc->_get_grabber_icon(); float available_size = sc->vertical ? (sc->get_size().x - tex->get_size().x) : (sc->get_size().y - tex->get_size().y); if (available_size - sc->drag_area_margin_begin - sc->drag_area_margin_end > 0) { // Draw the grabber only if it fits. @@ -180,23 +260,6 @@ SplitContainerDragger::SplitContainerDragger() { set_focus_mode(FOCUS_ACCESSIBILITY); } -Control *SplitContainer::_get_sortable_child(int p_idx, SortableVisibilityMode p_visibility_mode) const { - int idx = 0; - for (int i = 0; i < get_child_count(false); i++) { - Control *c = as_sortable_control(get_child(i, false), p_visibility_mode); - if (!c) { - continue; - } - - if (idx == p_idx) { - return c; - } - - idx++; - } - return nullptr; -} - Ref SplitContainer::_get_grabber_icon() const { if (is_fixed) { return theme_cache.grabber_icon; @@ -234,126 +297,502 @@ int SplitContainer::_get_separation() const { return MAX(theme_cache.separation, vertical ? g->get_height() : g->get_width()); } -void SplitContainer::_compute_split_offset(bool p_clamp) { - Control *first = _get_sortable_child(0); - Control *second = _get_sortable_child(1); - int axis_index = vertical ? 1 : 0; - int size = get_size()[axis_index]; - int sep = _get_separation(); +Point2i SplitContainer::_get_valid_range(int p_dragger_index) const { + ERR_FAIL_INDEX_V(p_dragger_index, (int)dragger_positions.size(), Point2i()); + const int axis = vertical ? 1 : 0; + const int sep = _get_separation(); - // Compute the wished size. - int wished_size = 0; - int split_offset_with_collapse = 0; - if (!collapsed) { - split_offset_with_collapse = split_offset; + // Sum the minimum sizes on the left and right sides of the dragger. + Point2i position_range = Point2i(0, (int)get_size()[axis]); + position_range.x += sep * p_dragger_index; + position_range.y -= sep * ((int)dragger_positions.size() - p_dragger_index); + + for (int i = 0; i < (int)valid_children.size(); i++) { + Control *child = valid_children[i]; + ERR_FAIL_NULL_V(child, Point2i()); + if (i <= p_dragger_index) { + position_range.x += (int)child->get_combined_minimum_size()[axis]; + } else if (i > p_dragger_index) { + position_range.y -= (int)child->get_combined_minimum_size()[axis]; + } } - bool first_is_expanded = (vertical ? first->get_v_size_flags() : first->get_h_size_flags()) & SIZE_EXPAND; - bool second_is_expanded = (vertical ? second->get_v_size_flags() : second->get_h_size_flags()) & SIZE_EXPAND; + return position_range; +} - if (first_is_expanded && second_is_expanded) { - float ratio = first->get_stretch_ratio() / (first->get_stretch_ratio() + second->get_stretch_ratio()); - wished_size = size * ratio - sep * 0.5 + split_offset_with_collapse; - } else if (first_is_expanded) { - wished_size = size - sep + split_offset_with_collapse; +PackedInt32Array SplitContainer::_get_desired_sizes() const { + ERR_FAIL_COND_V((int)default_dragger_positions.size() != split_offsets.size() || (int)valid_children.size() - 1 != split_offsets.size(), PackedInt32Array()); + PackedInt32Array desired_sizes; + desired_sizes.resize_uninitialized((int)valid_children.size()); + + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + + int desired_start_pos = 0; + for (int i = 0; i < (int)valid_children.size() - 1; i++) { + const int desired_end_pos = default_dragger_positions[i] + split_offsets[i]; + desired_sizes.write[i] = desired_end_pos - desired_start_pos; + desired_start_pos = desired_end_pos + sep; + } + desired_sizes.write[(int)valid_children.size() - 1] = (int)get_size()[axis] - desired_start_pos; + + return desired_sizes; +} + +void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes, int p_priority_index) { + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + const real_t size = get_size()[axis]; + + real_t total_desired_size = 0; + if (!p_desired_sizes.is_empty()) { + ERR_FAIL_COND((int)valid_children.size() != p_desired_sizes.size()); + total_desired_size += sep * (p_desired_sizes.size() - 1); + } + + struct StretchData { + real_t min_size = 0; + real_t stretch_ratio = 0.0; + real_t final_size = 0; + }; + + // First pass, determine the total stretch amount. + real_t stretch_total = 0; + LocalVector stretch_data; + for (int i = 0; i < (int)valid_children.size(); i++) { + Control *child = valid_children[i]; + StretchData sdata; + sdata.min_size = child->get_combined_minimum_size()[axis]; + sdata.final_size = MAX(sdata.min_size, p_desired_sizes.is_empty() ? 0 : p_desired_sizes[i]); + total_desired_size += sdata.final_size; + // Treat the priority child as not expanded, so it doesn't shrink with other expanded children. + if (i != p_priority_index && child->get_stretch_ratio() > 0 && (vertical ? child->get_v_size_flags() : child->get_h_size_flags()).has_flag(SIZE_EXPAND)) { + sdata.stretch_ratio = child->get_stretch_ratio(); + stretch_total += sdata.stretch_ratio; + } + stretch_data.push_back(sdata); + } + + real_t available_space = size - total_desired_size; + + // Grow expanding children. + if (available_space > 0) { + const real_t grow_amount = available_space / stretch_total; + for (StretchData &sdata : stretch_data) { + if (sdata.stretch_ratio <= 0) { + continue; + } + const real_t prev_size = sdata.final_size; + sdata.final_size = prev_size + grow_amount * sdata.stretch_ratio; + const real_t size_diff = prev_size - sdata.final_size; + available_space += size_diff; + } + } + + // Shrink expanding children. + while (available_space < 0) { + real_t shrinkable_stretch_ratio = 0.0; + real_t shrinkable_amount = 0.0; + for (const StretchData &sdata : stretch_data) { + if (sdata.stretch_ratio <= 0 || sdata.final_size <= sdata.min_size) { + continue; + } + shrinkable_stretch_ratio += sdata.stretch_ratio; + shrinkable_amount += sdata.final_size - sdata.min_size; + } + if (shrinkable_stretch_ratio == 0) { + break; + } + + const real_t shrink_amount = MIN(-available_space, shrinkable_amount) / shrinkable_stretch_ratio; + if (Math::is_zero_approx(shrink_amount)) { + break; + } + for (StretchData &sdata : stretch_data) { + if (sdata.stretch_ratio <= 0 || sdata.final_size <= sdata.min_size) { + continue; + } + const real_t prev_size = sdata.final_size; + sdata.final_size = CLAMP(prev_size - shrink_amount * sdata.stretch_ratio, sdata.min_size, sdata.final_size); + const real_t size_diff = prev_size - sdata.final_size; + available_space += size_diff; + } + } + + // Shrink non-expanding children. + while (available_space < 0) { + // Get largest and target sizes. + real_t largest_size = 0; + real_t target_size = 0; + int largest_count = 0; + for (const StretchData &sdata : stretch_data) { + if (sdata.final_size <= sdata.min_size) { + continue; + } + if (sdata.final_size > largest_size) { + target_size = largest_size; + largest_size = sdata.final_size; + largest_count = 1; + } else if (sdata.final_size == largest_size) { + largest_count++; + } else if (sdata.final_size < largest_size) { + target_size = MAX(sdata.final_size, target_size); + } + } + if (largest_size <= 0) { + break; + } + // Don't shrink smaller than needed. + target_size = MAX(target_size, available_space / largest_count); + target_size = MIN(target_size, largest_size + (available_space / largest_count)); + for (StretchData &sdata : stretch_data) { + if (sdata.final_size <= sdata.min_size) { + continue; + } + // Shrink all largest elements. + if (sdata.final_size == largest_size) { + sdata.final_size = CLAMP(target_size, sdata.min_size, sdata.final_size); + const real_t size_diff = largest_size - sdata.final_size; + available_space += size_diff; + } + } + if (Math::is_zero_approx(available_space)) { + break; + } + } + + ERR_FAIL_COND((int)default_dragger_positions.size() != (int)stretch_data.size() - 1); + + // Update the split offsets to match the desired sizes. + split_offsets.resize(MAX(1, (int)default_dragger_positions.size())); + int pos = 0; + real_t error_accumulator = 0.0; + for (int i = 0; i < (int)default_dragger_positions.size(); i++) { + int final_size = (int)stretch_data[i].final_size; + if (final_size == stretch_data[i].final_size) { + error_accumulator += stretch_data[i].final_size - final_size; + if (error_accumulator > 1.0) { + error_accumulator -= 1.0; + final_size += 1; + } + } + pos += final_size; + split_offsets.write[i] = pos - default_dragger_positions[i]; + pos += sep; + } +} + +void SplitContainer::_update_default_dragger_positions() { + if (valid_children.size() <= 1u) { + default_dragger_positions.clear(); + return; + } + default_dragger_positions.resize((int)valid_children.size() - 1); + + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + const int size = (int)get_size()[axis]; + + struct StretchData { + int min_size = 0; + real_t stretch_ratio = 0.0; + int final_size = 0; + bool expand_flag = false; + bool will_stretch = false; + }; + + // First pass, determine the total stretch amount. + real_t stretchable_space = size - sep * ((int)valid_children.size() - 1); + real_t stretch_total = 0; + int expand_count = 0; + LocalVector stretch_data; + for (const Control *child : valid_children) { + StretchData sdata; + sdata.min_size = (int)child->get_combined_minimum_size()[axis]; + sdata.final_size = sdata.min_size; + if ((vertical ? child->get_v_size_flags() : child->get_h_size_flags()).has_flag(SIZE_EXPAND) && child->get_stretch_ratio() > 0) { + sdata.stretch_ratio = child->get_stretch_ratio(); + stretch_total += sdata.stretch_ratio; + sdata.expand_flag = true; + sdata.will_stretch = true; + expand_count++; + } else { + stretchable_space -= sdata.min_size; + } + stretch_data.push_back(sdata); + } + +#ifndef DISABLE_DEPRECATED + if (expand_count == 2 && valid_children.size() == 2u) { + // Special case when there are 2 expanded children, ignore minimum sizes. + const real_t ratio = stretch_data[0].stretch_ratio / (stretch_data[0].stretch_ratio + stretch_data[1].stretch_ratio); + default_dragger_positions[0] = (int)(size * ratio - sep * 0.5); + return; + } +#endif // DISABLE_DEPRECATED + + // Determine final sizes if stretching. + while (stretch_total > 0.0 && stretchable_space > 0.0) { + bool refit_successful = true; + // Keep track of accumulated error in pixels. + float error = 0.0; + for (StretchData &sdata : stretch_data) { + if (!sdata.will_stretch) { + continue; + } + // Check if it reaches its minimum size. + const float desired_stretch_size = sdata.stretch_ratio / stretch_total * stretchable_space; + error += desired_stretch_size - (int)desired_stretch_size; + if (desired_stretch_size < sdata.min_size) { + // Will not be stretched, remove and retry. + stretch_total -= sdata.stretch_ratio; + stretchable_space -= sdata.min_size; + sdata.will_stretch = false; + sdata.final_size = sdata.min_size; + refit_successful = false; + break; + } else { + sdata.final_size = (int)desired_stretch_size; + // Dump accumulated error if one pixel or more. + if (error >= 1.0) { + sdata.final_size += 1; + error -= 1; + } + } + } + if (refit_successful) { + break; + } + } + + // Set the default positions. + int pos = 0; + int expands_seen = 0; + for (int i = 0; i < (int)default_dragger_positions.size(); i++) { + pos += stretch_data[i].final_size; + if (stretch_data[i].expand_flag) { + expands_seen += 1; + } + if (expands_seen == 0) { + // Before all expand flags. + default_dragger_positions[i] = 0; + } else if (expands_seen >= expand_count) { + // After all expand flags. + default_dragger_positions[i] = size - sep; + } else { + default_dragger_positions[i] = pos; + } + pos += sep; + } +} + +void SplitContainer::_update_dragger_positions(int p_clamp_index) { + if (p_clamp_index != -1) { + ERR_FAIL_INDEX(p_clamp_index, (int)dragger_positions.size()); + } + + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + const int size = (int)get_size()[axis]; + + dragger_positions.resize(default_dragger_positions.size()); + + if (split_offsets.size() < (int)default_dragger_positions.size() || split_offsets.is_empty()) { + split_offsets.resize_initialized(MAX(1, (int)default_dragger_positions.size())); + } + + if (collapsed) { + for (int i = 0; i < (int)dragger_positions.size(); i++) { + dragger_positions[i] = default_dragger_positions[i]; + const Point2i valid_range = _get_valid_range(i); + dragger_positions[i] = CLAMP(dragger_positions[i], valid_range.x, valid_range.y); + if (p_clamp_index != -1) { + split_offsets.write[i] = dragger_positions[i] - default_dragger_positions[i]; + } + if (!vertical && is_layout_rtl()) { + dragger_positions[i] = size - dragger_positions[i] - sep; + } + } + return; + } + + // Use split_offsets to find the desired dragger positions. + for (int i = 0; i < (int)dragger_positions.size(); i++) { + // Clamp the desired position to acceptable values. + const Point2i valid_range = _get_valid_range(i); + dragger_positions[i] = CLAMP(default_dragger_positions[i] + split_offsets[i], valid_range.x, valid_range.y); + } + + // Prevent overlaps. + if (p_clamp_index == -1) { + // Check each dragger with the one to the right of it. + for (int i = 0; i < (int)dragger_positions.size() - 1; i++) { + const int check_min_size = (int)valid_children[i + 1]->get_combined_minimum_size()[axis]; + const int push_pos = dragger_positions[i] + sep + check_min_size; + if (dragger_positions[i + 1] < push_pos) { + dragger_positions[i + 1] = push_pos; + const Point2i valid_range = _get_valid_range(i); + dragger_positions[i] = CLAMP(dragger_positions[i], valid_range.x, valid_range.y); + } + } } else { - wished_size = split_offset_with_collapse; - } + // Prioritize the active dragger. + const int dragging_position = dragger_positions[p_clamp_index]; - // Clamp the split offset to acceptable values. - int first_min_size = first->get_combined_minimum_size()[axis_index]; - int second_min_size = second->get_combined_minimum_size()[axis_index]; - computed_split_offset = CLAMP(wished_size, first_min_size, size - sep - second_min_size); + // Push overlapping draggers to the left. + int accumulated_min_size = (int)valid_children[p_clamp_index]->get_combined_minimum_size()[axis]; + for (int i = p_clamp_index - 1; i >= 0; i--) { + const int push_pos = dragging_position - sep * (p_clamp_index - i) - accumulated_min_size; + if (dragger_positions[i] > push_pos) { + dragger_positions[i] = push_pos; + } + accumulated_min_size += (int)valid_children[i]->get_combined_minimum_size()[axis]; + } + + // Push overlapping draggers to the right. + accumulated_min_size = 0; + for (int i = p_clamp_index + 1; i < (int)dragger_positions.size(); i++) { + accumulated_min_size += (int)valid_children[i]->get_combined_minimum_size()[axis]; + const int push_pos = dragging_position + sep * (i - p_clamp_index) + accumulated_min_size; + if (dragger_positions[i] < push_pos) { + dragger_positions[i] = push_pos; + } + } + } // Clamp the split_offset if requested. - if (p_clamp) { - split_offset -= wished_size - computed_split_offset; + if (p_clamp_index != -1) { + for (int i = 0; i < (int)dragger_positions.size(); i++) { + split_offsets.write[i] = dragger_positions[i] - default_dragger_positions[i]; + } + } + + // Invert if rtl. + if (!vertical && is_layout_rtl()) { + for (int i = 0; i < (int)dragger_positions.size(); i++) { + dragger_positions[i] = size - dragger_positions[i] - sep; + } } } void SplitContainer::_resort() { - Control *first = _get_sortable_child(0); - Control *second = _get_sortable_child(1); - - if (!first || !second) { // Only one child. - if (first) { - fit_child_in_rect(first, Rect2(Point2(), get_size())); - } else if (second) { - fit_child_in_rect(second, Rect2(Point2(), get_size())); - } - dragging_area_control->hide(); + if (!is_visible_in_tree()) { return; } - dragging_area_control->set_visible(!collapsed); - if (touch_dragger_enabled) { - touch_dragger->set_visible(dragging_enabled); + if (valid_children.size() < 2u) { + if (valid_children.size() == 1u) { + // Only one valid child. + Control *child = valid_children[0]; + fit_child_in_rect(child, Rect2(Point2(), get_size())); + } + for (SplitContainerDragger *dragger : dragging_area_controls) { + dragger->hide(); + } + return; } - - _compute_split_offset(false); // This recalculates and sets computed_split_offset. - - int sep = _get_separation(); - bool is_rtl = is_layout_rtl(); - - // Move the children. - if (vertical) { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(get_size().width, computed_split_offset))); - int sofs = computed_split_offset + sep; - fit_child_in_rect(second, Rect2(Point2(0, sofs), Size2(get_size().width, get_size().height - sofs))); - } else { - if (is_rtl) { - computed_split_offset = get_size().width - computed_split_offset - sep; - fit_child_in_rect(second, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); - int sofs = computed_split_offset + sep; - fit_child_in_rect(first, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); - } else { - fit_child_in_rect(first, Rect2(Point2(0, 0), Size2(computed_split_offset, get_size().height))); - int sofs = computed_split_offset + sep; - fit_child_in_rect(second, Rect2(Point2(sofs, 0), Size2(get_size().width - sofs, get_size().height))); + for (SplitContainerDragger *dragger : dragging_area_controls) { + dragger->set_visible(!collapsed); + if (touch_dragger_enabled) { + dragger->touch_dragger->set_visible(dragging_enabled); } } - dragging_area_control->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE); + _update_default_dragger_positions(); + _update_dragger_positions(); + + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + const Size2i new_size = get_size(); + const bool rtl = is_layout_rtl(); + + // Move the children. + for (int i = 0; i < (int)valid_children.size(); i++) { + Control *child = valid_children[i]; + int start_pos; + int end_pos; + if (!vertical && rtl) { + start_pos = i >= (int)dragger_positions.size() ? 0 : dragger_positions[i] + sep; + end_pos = i == 0 ? new_size[axis] : dragger_positions[i - 1]; + } else { + start_pos = i == 0 ? 0 : dragger_positions[i - 1] + sep; + end_pos = i >= (int)dragger_positions.size() ? new_size[axis] : dragger_positions[i]; + } + int size = end_pos - start_pos; + + if (vertical) { + fit_child_in_rect(child, Rect2(Point2(0, start_pos), Size2(new_size.width, size))); + } else { + fit_child_in_rect(child, Rect2(Point2(start_pos, 0), Size2(size, new_size.height))); + } + } + + _update_draggers(); + + // Update dragger positions. const int dragger_ctrl_size = MAX(sep, theme_cache.minimum_grab_thickness); - float split_bar_offset = (dragger_ctrl_size - sep) * 0.5; - if (vertical) { - Rect2 split_bar_rect = Rect2(is_rtl ? drag_area_margin_end : drag_area_margin_begin, computed_split_offset, get_size().width - drag_area_margin_begin - drag_area_margin_end, sep); - dragging_area_control->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size)); - dragging_area_control->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size); - } else { - Rect2 split_bar_rect = Rect2(computed_split_offset, drag_area_margin_begin, sep, get_size().height - drag_area_margin_begin - drag_area_margin_end); - dragging_area_control->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (is_rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y)); - dragging_area_control->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (is_rtl ? -1 : 1), 0.0), split_bar_rect.size); + const float split_bar_offset = (dragger_ctrl_size - sep) * 0.5; + + ERR_FAIL_COND(dragging_area_controls.size() != dragger_positions.size()); + for (int i = 0; i < (int)dragger_positions.size(); i++) { + dragging_area_controls[i]->set_mouse_filter(dragging_enabled ? MOUSE_FILTER_STOP : MOUSE_FILTER_IGNORE); + if (vertical) { + const Rect2 split_bar_rect = Rect2(rtl ? drag_area_margin_end : drag_area_margin_begin, dragger_positions[i], new_size.width - drag_area_margin_begin - drag_area_margin_end, sep); + dragging_area_controls[i]->set_rect(Rect2(split_bar_rect.position.x, split_bar_rect.position.y - split_bar_offset + drag_area_offset, split_bar_rect.size.x, dragger_ctrl_size)); + dragging_area_controls[i]->split_bar_rect = Rect2(Vector2(0.0, int(split_bar_offset) - drag_area_offset), split_bar_rect.size); + } else { + const Rect2 split_bar_rect = Rect2(dragger_positions[i], drag_area_margin_begin, sep, new_size.height - drag_area_margin_begin - drag_area_margin_end); + dragging_area_controls[i]->set_rect(Rect2(split_bar_rect.position.x - split_bar_offset + drag_area_offset * (rtl ? -1 : 1), split_bar_rect.position.y, dragger_ctrl_size, split_bar_rect.size.y)); + dragging_area_controls[i]->split_bar_rect = Rect2(Vector2(int(split_bar_offset) - drag_area_offset * (rtl ? -1 : 1), 0.0), split_bar_rect.size); + } + dragging_area_controls[i]->queue_redraw(); } queue_redraw(); - dragging_area_control->queue_redraw(); +} + +void SplitContainer::_update_draggers() { + const int valid_child_count = (int)valid_children.size(); + const int dragger_count = valid_child_count - 1; + const int draggers_size_diff = dragger_count - (int)dragging_area_controls.size(); + + // Add new draggers. + for (int i = 0; i < draggers_size_diff; i++) { + SplitContainerDragger *dragger = memnew(SplitContainerDragger); + dragging_area_controls.push_back(dragger); + add_child(dragger, false, Node::INTERNAL_MODE_BACK); + if (touch_dragger_enabled) { + dragger->set_touch_dragger_enabled(true); + } + } + + // Remove extra draggers. + for (int i = 0; i < -draggers_size_diff; i++) { + const int remove_at = (int)dragging_area_controls.size() - 1; + SplitContainerDragger *dragger = dragging_area_controls[remove_at]; + dragging_area_controls.remove_at(remove_at); + remove_child(dragger); + memdelete(dragger); + } + + // Make sure draggers have the correct index. + for (int i = 0; i < (int)dragging_area_controls.size(); i++) { + dragging_area_controls[i]->dragger_index = i; + } } Size2 SplitContainer::get_minimum_size() const { + const int sep = _get_separation(); + const int axis = vertical ? 1 : 0; + const int other_axis = vertical ? 0 : 1; + Size2i minimum; - int sep = _get_separation(); - for (int i = 0; i < 2; i++) { - Control *child = _get_sortable_child(i, SortableVisibilityMode::VISIBLE); - if (!child) { - break; - } + if (valid_children.size() >= 2u) { + minimum[axis] += sep * ((int)valid_children.size() - 1); + } - if (i == 1) { - if (vertical) { - minimum.height += sep; - } else { - minimum.width += sep; - } - } - - Size2 ms = child->get_combined_minimum_size(); - - if (vertical) { - minimum.height += ms.height; - minimum.width = MAX(minimum.width, ms.width); - } else { - minimum.width += ms.width; - minimum.height = MAX(minimum.height, ms.height); - } + for (const Control *child : valid_children) { + const Size2 min_size = child->get_combined_minimum_size(); + minimum[axis] += (int)min_size[axis]; + minimum[other_axis] = (int)MAX(minimum[other_axis], min_size[other_axis]); } return minimum; @@ -371,36 +810,246 @@ void SplitContainer::_notification(int p_what) { case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { queue_sort(); } break; + case NOTIFICATION_POSTINITIALIZE: { + initialized = true; + } break; case NOTIFICATION_SORT_CHILDREN: { _resort(); } break; case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); - if (touch_dragger) { - touch_dragger->set_modulate(theme_cache.touch_dragger_color); - touch_dragger->set_texture(_get_touch_dragger_icon()); - } + } break; + case NOTIFICATION_PREDELETE: { + valid_children.clear(); + dragging_area_controls.clear(); } break; } } -void SplitContainer::set_split_offset(int p_offset) { - if (split_offset == p_offset) { +void SplitContainer::add_child_notify(Node *p_child) { + Container::add_child_notify(p_child); + + if (p_child->is_internal()) { return; } - split_offset = p_offset; + Control *child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE); + if (!child) { + return; + } + + child->connect(SceneStringName(visibility_changed), callable_mp(this, &SplitContainer::_on_child_visibility_changed).bind(child)); + if (child->is_visible()) { + _add_valid_child(child); + } +} + +void SplitContainer::remove_child_notify(Node *p_child) { + Container::remove_child_notify(p_child); + + if (p_child->is_internal()) { + return; + } + Control *child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE); + if (!child) { + return; + } + + child->disconnect(SceneStringName(visibility_changed), callable_mp(this, &SplitContainer::_on_child_visibility_changed)); + if (child->is_visible()) { + _remove_valid_child(child); + } +} + +void SplitContainer::move_child_notify(Node *p_child) { + Container::move_child_notify(p_child); + + Control *moved_child = as_sortable_control(p_child, SortableVisibilityMode::IGNORE); + const int prev_index = valid_children.find(moved_child); + if (prev_index == -1) { + return; + } + + PackedInt32Array desired_sizes; + if (initialized && !split_offset_pending && valid_children.size() > 2u && split_offsets.size() == (int)default_dragger_positions.size()) { + desired_sizes = _get_desired_sizes(); + } + + valid_children.remove_at(prev_index); + + // Get new index. + int index = 0; + for (int i = 0; i < get_child_count(false); i++) { + Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE); + if (!child) { + continue; + } + if (child == moved_child) { + break; + } + if (valid_children.has(child)) { + index++; + } + } + + valid_children.insert(index, moved_child); + + if (desired_sizes.is_empty()) { + return; + } + + const int prev_desired_size = desired_sizes[prev_index]; + desired_sizes.remove_at(prev_index); + desired_sizes.insert(index, prev_desired_size); + _set_desired_sizes(desired_sizes, index); +} + +void SplitContainer::_on_child_visibility_changed(Control *p_control) { + if (p_control->is_visible()) { + _add_valid_child(p_control); + } else { + _remove_valid_child(p_control); + } +} + +void SplitContainer::_add_valid_child(Control *p_control) { + if (valid_children.has(p_control)) { + return; + } + + // Get index to insert. + bool child_is_valid = false; + int index = 0; + for (int i = 0; i < get_child_count(false); i++) { + Control *child = as_sortable_control(get_child(i, false), SortableVisibilityMode::IGNORE); + if (!child) { + continue; + } + if (child == p_control) { + if (child->is_visible()) { + child_is_valid = true; + } + break; + } + if (valid_children.has(child)) { + index++; + } + } + if (!child_is_valid) { + return; + } + + PackedInt32Array desired_sizes; + if (initialized && can_use_desired_sizes && !split_offset_pending && valid_children.size() >= 2u && split_offsets.size() == (int)default_dragger_positions.size()) { + desired_sizes = _get_desired_sizes(); + } + + valid_children.insert(index, p_control); + if (!initialized) { + // If not initialized, the theme cache isn't ready yet so return early. + return; + } + + _update_default_dragger_positions(); + queue_sort(); + + if (valid_children.size() <= 2u) { + // Already have first dragger. + return; + } + + // Call deferred in case already adding or removing children. + callable_mp(this, &SplitContainer::_update_draggers).call_deferred(); + + if (split_offset_pending && split_offsets.size() == (int)valid_children.size() - 1) { + split_offset_pending = false; + } + if (desired_sizes.is_empty()) { + return; + } + + // Use the child's existing size as it's desired size. + const int axis = vertical ? 1 : 0; + desired_sizes.insert(index, (int)p_control->get_size()[axis]); + _set_desired_sizes(desired_sizes, index); +} + +void SplitContainer::_remove_valid_child(Control *p_control) { + const int index = valid_children.find(p_control); + if (index == -1) { + return; + } + + PackedInt32Array desired_sizes; + if (initialized && !split_offset_pending && valid_children.size() > 2u && split_offsets.size() == (int)default_dragger_positions.size()) { + desired_sizes = _get_desired_sizes(); + } + + valid_children.remove_at(index); + if (!initialized) { + return; + } + // Only use desired sizes to change the split offset after the first time a child is removed. + // This allows adding children to not affect the split offsets when creating. + can_use_desired_sizes = valid_children.size() > 1u; + + _update_default_dragger_positions(); + queue_sort(); + + if (valid_children.size() <= 1u) { + // Don't remove last dragger. + return; + } + + // Call deferred in case already adding or removing children. + callable_mp(this, &SplitContainer::_update_draggers).call_deferred(); + + if (split_offset_pending && split_offsets.size() == (int)valid_children.size() - 2) { + split_offset_pending = false; + } + if (desired_sizes.is_empty()) { + return; + } + + desired_sizes.remove_at(index); + _set_desired_sizes(desired_sizes); +} + +void SplitContainer::set_split_offset(int p_offset, int p_index) { + ERR_FAIL_INDEX(p_index, split_offsets.size()); + if (split_offsets[p_index] == p_offset) { + return; + } + + split_offsets.write[p_index] = p_offset; queue_sort(); } -int SplitContainer::get_split_offset() const { - return split_offset; +int SplitContainer::get_split_offset(int p_index) const { + ERR_FAIL_INDEX_V(p_index, split_offsets.size(), 0); + return split_offsets[p_index]; } -void SplitContainer::clamp_split_offset() { - if (!_get_sortable_child(0) || !_get_sortable_child(1)) { +void SplitContainer::set_split_offsets(const PackedInt32Array &p_offsets) { + if (split_offsets == p_offsets) { return; } - _compute_split_offset(true); + split_offsets = p_offsets; + split_offset_pending = split_offsets.size() > 1 && (int)valid_children.size() - 1 != split_offsets.size(); + queue_sort(); +} + +PackedInt32Array SplitContainer::get_split_offsets() const { + return split_offsets; +} + +void SplitContainer::clamp_split_offset(int p_priority_index) { + ERR_FAIL_INDEX(p_priority_index, split_offsets.size()); + if (valid_children.size() < 2u) { + // Needs at least two children. + return; + } + + _update_dragger_positions(p_priority_index); queue_sort(); } @@ -434,11 +1083,10 @@ void SplitContainer::set_vertical(bool p_vertical) { return; } vertical = p_vertical; - if (touch_dragger) { - touch_dragger->set_texture(_get_touch_dragger_icon()); - touch_dragger->set_anchors_and_offsets_preset(Control::PRESET_CENTER); - touch_dragger->set_default_cursor_shape(vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); + for (SplitContainerDragger *dragger : dragging_area_controls) { + dragger->update_touch_dragger(); } + update_minimum_size(); _resort(); } @@ -452,10 +1100,15 @@ void SplitContainer::set_dragging_enabled(bool p_enabled) { return; } dragging_enabled = p_enabled; - if (!dragging_enabled && dragging_area_control->dragging) { - dragging_area_control->dragging = false; - // queue_redraw() is called by _resort(). - emit_signal(SNAME("drag_ended")); + if (!dragging_enabled) { + bool was_dragging = false; + for (SplitContainerDragger *dragger : dragging_area_controls) { + was_dragging |= dragger->dragging; + dragger->dragging = false; + } + if (was_dragging) { + emit_signal(SNAME("drag_ended")); + } } if (get_viewport()) { get_viewport()->update_mouse_cursor_state(); @@ -529,69 +1182,43 @@ int SplitContainer::get_drag_area_offset() const { void SplitContainer::set_show_drag_area_enabled(bool p_enabled) { show_drag_area = p_enabled; - dragging_area_control->queue_redraw(); + for (SplitContainerDragger *dragger : dragging_area_controls) { + dragger->queue_redraw(); + } } bool SplitContainer::is_show_drag_area_enabled() const { return show_drag_area; } +TypedArray SplitContainer::get_drag_area_controls() { + TypedArray controls; + controls.resize((int)dragging_area_controls.size()); + for (int i = 0; i < (int)dragging_area_controls.size(); i++) { + controls[i] = dragging_area_controls[i]; + } + return controls; +} + void SplitContainer::set_touch_dragger_enabled(bool p_enabled) { if (touch_dragger_enabled == p_enabled) { return; } touch_dragger_enabled = p_enabled; - if (p_enabled) { - touch_dragger = memnew(TextureRect); - touch_dragger->set_texture(_get_touch_dragger_icon()); - touch_dragger->set_anchors_and_offsets_preset(Control::PRESET_CENTER); - touch_dragger->set_default_cursor_shape(vertical ? CURSOR_VSPLIT : CURSOR_HSPLIT); - touch_dragger->set_modulate(theme_cache.touch_dragger_color); - touch_dragger->connect(SceneStringName(gui_input), callable_mp(this, &SplitContainer::_touch_dragger_gui_input)); - touch_dragger->connect(SceneStringName(mouse_exited), callable_mp(this, &SplitContainer::_touch_dragger_mouse_exited)); - dragging_area_control->add_child(touch_dragger, false, Node::INTERNAL_MODE_FRONT); - } else { - if (touch_dragger) { - touch_dragger->queue_free(); - touch_dragger = nullptr; - } + for (SplitContainerDragger *dragger : dragging_area_controls) { + dragger->set_touch_dragger_enabled(p_enabled); } - dragging_area_control->queue_redraw(); } bool SplitContainer::is_touch_dragger_enabled() const { return touch_dragger_enabled; } -void SplitContainer::_touch_dragger_mouse_exited() { - if (!dragging_area_control->dragging) { - touch_dragger->set_modulate(theme_cache.touch_dragger_color); - } -} - -void SplitContainer::_touch_dragger_gui_input(const Ref &p_event) { - if (!touch_dragger_enabled) { - return; - } - Ref mm = p_event; - Ref mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { - if (mb->is_pressed()) { - touch_dragger->set_modulate(theme_cache.touch_dragger_pressed_color); - } else { - touch_dragger->set_modulate(theme_cache.touch_dragger_color); - } - } - - if (mm.is_valid() && !dragging_area_control->dragging) { - touch_dragger->set_modulate(theme_cache.touch_dragger_hover_color); - } -} - void SplitContainer::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::set_split_offset); - ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::get_split_offset); - ClassDB::bind_method(D_METHOD("clamp_split_offset"), &SplitContainer::clamp_split_offset); + ClassDB::bind_method(D_METHOD("set_split_offsets", "offsets"), &SplitContainer::set_split_offsets); + ClassDB::bind_method(D_METHOD("get_split_offsets"), &SplitContainer::get_split_offsets); + + ClassDB::bind_method(D_METHOD("clamp_split_offset", "priority_index"), &SplitContainer::clamp_split_offset, DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_collapsed", "collapsed"), &SplitContainer::set_collapsed); ClassDB::bind_method(D_METHOD("is_collapsed"), &SplitContainer::is_collapsed); @@ -617,7 +1244,7 @@ void SplitContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_drag_area_highlight_in_editor", "drag_area_highlight_in_editor"), &SplitContainer::set_show_drag_area_enabled); ClassDB::bind_method(D_METHOD("is_drag_area_highlight_in_editor_enabled"), &SplitContainer::is_show_drag_area_enabled); - ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control); + ClassDB::bind_method(D_METHOD("get_drag_area_controls"), &SplitContainer::get_drag_area_controls); ClassDB::bind_method(D_METHOD("set_touch_dragger_enabled", "enabled"), &SplitContainer::set_touch_dragger_enabled); ClassDB::bind_method(D_METHOD("is_touch_dragger_enabled"), &SplitContainer::is_touch_dragger_enabled); @@ -626,7 +1253,7 @@ void SplitContainer::_bind_methods() { ADD_SIGNAL(MethodInfo("drag_started")); ADD_SIGNAL(MethodInfo("drag_ended")); - ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offset", "get_split_offset"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "split_offsets", PROPERTY_HINT_NONE, "suffix:px"), "set_split_offsets", "get_split_offsets"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collapsed"), "set_collapsed", "is_collapsed"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "dragging_enabled"), "set_dragging_enabled", "is_dragging_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility"); @@ -656,11 +1283,20 @@ void SplitContainer::_bind_methods() { BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_h, "h_grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SplitContainer, grabber_icon_v, "v_grabber"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_STYLEBOX, SplitContainer, split_bar_background, "split_bar_background"); + +#ifndef DISABLE_DEPRECATED + ClassDB::bind_method(D_METHOD("get_drag_area_control"), &SplitContainer::get_drag_area_control); + ClassDB::bind_method(D_METHOD("set_split_offset", "offset"), &SplitContainer::_set_split_offset_first); + ClassDB::bind_method(D_METHOD("get_split_offset"), &SplitContainer::_get_split_offset_first); + ADD_PROPERTY(PropertyInfo(Variant::INT, "split_offset", PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_NO_EDITOR), "set_split_offset", "get_split_offset"); +#endif // DISABLE_DEPRECATED } SplitContainer::SplitContainer(bool p_vertical) { vertical = p_vertical; + split_offsets.push_back(0); - dragging_area_control = memnew(SplitContainerDragger); - add_child(dragging_area_control, false, Node::INTERNAL_MODE_BACK); + SplitContainerDragger *dragger = memnew(SplitContainerDragger); + dragging_area_controls.push_back(dragger); + add_child(dragger, false, Node::INTERNAL_MODE_BACK); } diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h index b58756c7ec9..72ead3bc1f3 100644 --- a/scene/gui/split_container.h +++ b/scene/gui/split_container.h @@ -37,7 +37,12 @@ class TextureRect; class SplitContainerDragger : public Control { GDCLASS(SplitContainerDragger, Control); friend class SplitContainer; + Rect2 split_bar_rect; + TextureRect *touch_dragger = nullptr; + + void _touch_dragger_mouse_exited(); + void _touch_dragger_gui_input(const Ref &p_event); protected: void _notification(int p_what); @@ -50,12 +55,18 @@ protected: private: bool dragging = false; int drag_from = 0; - int drag_ofs = 0; + int start_drag_split_offset = 0; bool mouse_inside = false; public: + int dragger_index = -1; + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + void set_touch_dragger_enabled(bool p_enabled); + void update_touch_dragger(); + bool is_touch_dragger_enabled() const; + SplitContainerDragger(); }; @@ -71,21 +82,26 @@ public: }; private: - int show_drag_area = false; + bool show_drag_area = false; int drag_area_margin_begin = 0; int drag_area_margin_end = 0; int drag_area_offset = 0; - int split_offset = 0; - int computed_split_offset = 0; + + PackedInt32Array split_offsets; + LocalVector default_dragger_positions; + LocalVector dragger_positions; + LocalVector valid_children; + LocalVector dragging_area_controls; + bool vertical = false; bool collapsed = false; DraggerVisibility dragger_visibility = DRAGGER_VISIBLE; bool dragging_enabled = true; - - SplitContainerDragger *dragging_area_control = nullptr; + bool split_offset_pending = false; + bool can_use_desired_sizes = false; + bool initialized = false; bool touch_dragger_enabled = false; - TextureRect *touch_dragger = nullptr; struct ThemeCache { Color touch_dragger_color; @@ -106,24 +122,44 @@ private: Ref _get_grabber_icon() const; Ref _get_touch_dragger_icon() const; - void _touch_dragger_mouse_exited(); - void _touch_dragger_gui_input(const Ref &p_event); - void _compute_split_offset(bool p_clamp); + Point2i _get_valid_range(int p_dragger_index) const; + + PackedInt32Array _get_desired_sizes() const; + void _set_desired_sizes(const PackedInt32Array &p_desired_sizes, int p_priority_index = -1); + + void _update_default_dragger_positions(); + void _update_dragger_positions(int p_clamp_index = -1); int _get_separation() const; void _resort(); - Control *_get_sortable_child(int p_idx, SortableVisibilityMode p_visibility_mode = SortableVisibilityMode::VISIBLE_IN_TREE) const; + void _update_draggers(); + void _on_child_visibility_changed(Control *p_control); + void _add_valid_child(Control *p_control); + void _remove_valid_child(Control *p_control); protected: bool is_fixed = false; void _notification(int p_what); void _validate_property(PropertyInfo &p_property) const; + + virtual void add_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _clamp_split_offset_compat_90411(); + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + public: - void set_split_offset(int p_offset); - int get_split_offset() const; - void clamp_split_offset(); + void set_split_offset(int p_offset, int p_index = 0); + int get_split_offset(int p_index = 0) const; + + void set_split_offsets(const PackedInt32Array &p_offsets); + PackedInt32Array get_split_offsets() const; + + void clamp_split_offset(int p_priority_index = 0); void set_collapsed(bool p_collapsed); bool is_collapsed() const; @@ -154,11 +190,17 @@ public: void set_show_drag_area_enabled(bool p_enabled); bool is_show_drag_area_enabled() const; - Control *get_drag_area_control() { return dragging_area_control; } + TypedArray get_drag_area_controls(); void set_touch_dragger_enabled(bool p_enabled); bool is_touch_dragger_enabled() const; +#ifndef DISABLE_DEPRECATED + Control *get_drag_area_control() { return dragging_area_controls[0]; } + void _set_split_offset_first(int p_offset) { set_split_offset(p_offset); } + int _get_split_offset_first() const { return get_split_offset(); } +#endif + SplitContainer(bool p_vertical = false); }; diff --git a/tests/scene/test_split_container.h b/tests/scene/test_split_container.h index b2ab1006953..c1d609674f5 100644 --- a/tests/scene/test_split_container.h +++ b/tests/scene/test_split_container.h @@ -37,14 +37,18 @@ namespace TestSplitContainer { -static inline void check_positions(SplitContainer *p_sc, const Vector &p_positions, int p_sep, bool p_horizontal = true) { - // Checks the rects of each split container child. - CHECK(p_sc->get_child_count(false) == p_positions.size() + 1); +#define CHECK_RECTS(m_rects, m_child_rects) \ + CHECK(m_rects.size() == m_child_rects.size()); \ + for (int i = 0; i < (int)m_child_rects.size(); i++) { \ + CHECK_MESSAGE(m_child_rects[i] == m_rects[i], vformat("Child %s is the wrong size. Child rect: %s, expected: %s.", i, m_child_rects[i], m_rects[i])); \ + } + +static inline Vector get_rects_multi(SplitContainer *p_sc, const Vector &p_positions, int p_sep, bool p_horizontal = true) { + // p_positions is the top/left side of the dragger. + Vector rects; int last_pos = 0; - for (int i = 0; i < p_sc->get_child_count(false); i++) { - // Assuming there is no invalid children. - Control *c = Object::cast_to(p_sc->get_child(i, false)); - int pos = i >= p_positions.size() ? p_sc->get_size()[p_horizontal ? 0 : 1] : p_positions[i]; + for (int i = 0; i < (int)p_positions.size() + 1; i++) { + const int pos = i >= (int)p_positions.size() ? p_sc->get_size()[p_horizontal ? 0 : 1] : p_positions[i]; Rect2 rect; if (p_horizontal) { rect.size.y = p_sc->get_size().y; @@ -55,37 +59,67 @@ static inline void check_positions(SplitContainer *p_sc, const Vector &p_po rect.position.y = last_pos; rect.size.y = pos - last_pos; } - CHECK_MESSAGE(c->get_rect() == rect, vformat("Child %s is the wrong size.", i)); + rects.push_back(rect); last_pos = pos + p_sep; } + return rects; } -static inline void check_position(SplitContainer *p_sc, int p_position, int p_sep, bool p_horizontal = true) { - check_positions(p_sc, { p_position }, p_sep, p_horizontal); +static inline Vector get_rects(SplitContainer *p_sc, int p_position, int p_sep, bool p_horizontal = true) { + return get_rects_multi(p_sc, Vector({ p_position }), p_sep, p_horizontal); } -static inline void check_positions_rtl(SplitContainer *p_sc, const Vector &p_positions, int p_sep) { - // Checks the rects of each split container child. Right to left layout. - CHECK(p_sc->get_child_count(false) == p_positions.size() + 1); +static inline Vector get_rects_multi_rtl(SplitContainer *p_sc, const Vector &p_positions, int p_sep) { + Vector rects; int last_pos = p_sc->get_size().x; - for (int i = 0; i < p_sc->get_child_count(false); i++) { - // Assuming there is no invalid children. - Control *c = Object::cast_to(p_sc->get_child(i, false)); - int pos = i >= p_positions.size() ? 0 : p_sc->get_size().x - p_positions[i]; + for (int i = 0; i < (int)p_positions.size() + 1; i++) { + const int pos = i >= (int)p_positions.size() ? 0 : p_sc->get_size().x - p_positions[i]; Rect2 rect; rect.size.y = p_sc->get_size().y; rect.position.x = pos; rect.size.x = last_pos - pos; - CHECK_MESSAGE(c->get_rect() == rect, vformat("Child %s is the wrong size.", i)); + rects.push_back(rect); last_pos = pos - p_sep; } + return rects; } -static inline void check_position_rtl(SplitContainer *p_sc, int p_position, int p_sep) { - check_positions_rtl(p_sc, { p_position }, p_sep); +static inline Vector get_rects_rtl(SplitContainer *p_sc, int p_position, int p_sep) { + return get_rects_multi_rtl(p_sc, Vector({ p_position }), p_sep); } -TEST_CASE("[SceneTree][SplitContainer] Add children") { +static inline Vector get_child_rects(SplitContainer *p_sc) { + Vector rects; + for (int i = 0; i < p_sc->get_child_count(false); i++) { + Control *c = Object::cast_to(p_sc->get_child(i, false)); + if (!c || !c->is_visible()) { + continue; + } + rects.push_back(c->get_rect()); + } + return rects; +} + +static inline void set_size_flags(SplitContainer *p_sc, Vector p_expand_ratios, bool p_horizontal = true) { + for (int i = 0; i < p_sc->get_child_count(false); i++) { + Control *c = Object::cast_to(p_sc->get_child(i, false)); + if (!c || !c->is_visible()) { + continue; + } + float ratio = p_expand_ratios[i]; + if (p_horizontal) { + c->set_h_size_flags(ratio > 0 ? Control::SIZE_EXPAND_FILL : Control::SIZE_FILL); + } else { + c->set_v_size_flags(ratio > 0 ? Control::SIZE_EXPAND_FILL : Control::SIZE_FILL); + } + if (ratio > 0) { + c->set_stretch_ratio(ratio); + } + } + MessageQueue::get_singleton()->flush(); +} + +TEST_CASE("[SceneTree][SplitContainer] Add and remove children") { SplitContainer *split_container = memnew(SplitContainer); split_container->set_size(Size2(500, 500)); SceneTree::get_singleton()->get_root()->add_child(split_container); @@ -164,7 +198,7 @@ TEST_CASE("[SceneTree][SplitContainer] Dragger visibility") { MessageQueue::get_singleton()->flush(); const int sep_constant = split_container->get_theme_constant("separation"); - const Size2i separator_size = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); + const Size2i sep = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); SUBCASE("[SplitContainer] Visibility based on child count") { split_container->remove_child(child_a); @@ -193,16 +227,16 @@ TEST_CASE("[SceneTree][SplitContainer] Dragger visibility") { SUBCASE("[SplitContainer] Set dragger visibility") { split_container->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); // Can't check the visibility since it happens in draw. split_container->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN_COLLAPSED); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, 0); + CHECK_RECTS(get_rects(split_container, 0, 0), get_child_rects(split_container)); split_container->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Not visible when collapsed") { @@ -233,7 +267,7 @@ TEST_CASE("[SceneTree][SplitContainer] Collapsed") { MessageQueue::get_singleton()->flush(); const int sep_constant = split_container->get_theme_constant("separation"); - const Size2i separator_size = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); + const Size2i sep = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); SUBCASE("[SplitContainer] Dragging and cursor") { split_container->set_collapsed(true); @@ -245,11 +279,11 @@ TEST_CASE("[SceneTree][SplitContainer] Collapsed") { // Dragger is disabled, cannot drag. SEND_GUI_MOUSE_BUTTON_EVENT(Point2(1, 1), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 0); SEND_GUI_MOUSE_MOTION_EVENT(Point2(10, 1), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 0); } @@ -258,29 +292,29 @@ TEST_CASE("[SceneTree][SplitContainer] Collapsed") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); split_container->set_collapsed(true); MessageQueue::get_singleton()->flush(); // The split offset is treated as 0 when collapsed. - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 10); } SUBCASE("[SplitContainer] First child expanded") { - int def_pos = split_container->get_size().x - separator_size.x; + int def_pos = split_container->get_size().x - sep.x; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos - 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 10, sep.x), get_child_rects(split_container)); split_container->set_collapsed(true); MessageQueue::get_singleton()->flush(); // The split offset is treated as 0 when collapsed. - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == -10); } @@ -290,30 +324,30 @@ TEST_CASE("[SceneTree][SplitContainer] Collapsed") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); split_container->set_collapsed(true); MessageQueue::get_singleton()->flush(); // The split offset is treated as 0 when collapsed. - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 10); } SUBCASE("[SplitContainer] Both children expanded") { - int def_pos = (split_container->get_size().y - separator_size.y) / 2; + int def_pos = (split_container->get_size().y - sep.y) / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); split_container->set_collapsed(true); MessageQueue::get_singleton()->flush(); // The split offset is treated as 0 when collapsed. - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 10); } @@ -370,7 +404,7 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { MessageQueue::get_singleton()->flush(); const int sep_constant = split_container->get_theme_constant("separation"); - const Size2i separator_size = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); + const Size2i sep = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); SUBCASE("[SplitContainer] Minimum size") { // Minimum size is the sum of both children's minimum sizes and the separator depending on the vertical axis. @@ -379,14 +413,14 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { MessageQueue::get_singleton()->flush(); Size2 min_size = split_container->get_minimum_size(); - CHECK(min_size.x == 200 + separator_size.x); + CHECK(min_size.x == 200 + sep.x); CHECK(min_size.y == 200); split_container->set_vertical(true); MessageQueue::get_singleton()->flush(); min_size = split_container->get_minimum_size(); CHECK(min_size.x == 100); - CHECK(min_size.y == 400 + separator_size.y); + CHECK(min_size.y == 400 + sep.y); } SUBCASE("[SplitContainer] Default position") { @@ -397,54 +431,54 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // No expand flags set. MessageQueue::get_singleton()->flush(); int def_pos = 0; - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); // First expand flags set. child_a->set_v_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_v_size_flags(Control::SIZE_FILL); MessageQueue::get_singleton()->flush(); - def_pos = split_container->get_size().y - separator_size.y; - check_position(split_container, def_pos, separator_size.y, false); + def_pos = split_container->get_size().y - sep.y; + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); // Second expand flags set. child_a->set_v_size_flags(Control::SIZE_FILL); child_b->set_v_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); def_pos = 0; - check_position(split_container, 0, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, 0, sep.y, false), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); // Both expand flags set. child_a->set_v_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_v_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); - def_pos = (split_container->get_size().y - separator_size.y) / 2; - check_position(split_container, def_pos, separator_size.y, false); + def_pos = (split_container->get_size().y - sep.y) / 2; + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); // Unequal stretch ratios. child_a->set_stretch_ratio(2.0); MessageQueue::get_singleton()->flush(); - def_pos = (split_container->get_size().y * 2 / 3) - separator_size.y / 2; - check_position(split_container, def_pos, separator_size.y, false); + def_pos = (split_container->get_size().y * 2 / 3) - sep.y / 2; + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, def_pos, sep.y, false), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Right to left") { @@ -454,65 +488,66 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // No expand flags set. MessageQueue::get_singleton()->flush(); int def_pos = 0; - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); // First expand flags set. child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_FILL); MessageQueue::get_singleton()->flush(); - def_pos = split_container->get_size().y - separator_size.y; - check_position_rtl(split_container, def_pos, separator_size.y); + def_pos = split_container->get_size().y - sep.y; + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); // Second expand flags set. child_a->set_h_size_flags(Control::SIZE_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); def_pos = 0; - check_position_rtl(split_container, 0, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); // Both expand flags set. child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); - def_pos = (split_container->get_size().y - separator_size.y) / 2; - check_position_rtl(split_container, def_pos, separator_size.y); + def_pos = (split_container->get_size().y - sep.y) / 2; + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); // Unequal stretch ratios. child_a->set_stretch_ratio(2.0); MessageQueue::get_singleton()->flush(); - def_pos = (split_container->get_size().y * 2 / 3) - separator_size.y / 2; - check_position_rtl(split_container, def_pos, separator_size.y); + def_pos = (split_container->get_size().y * 2 / 3) - sep.y / 2; + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position_rtl(split_container, def_pos, separator_size.y); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); } SUBCASE("[SplitContainer] No expand flags") { int def_pos = 0; - check_position(split_container, def_pos, separator_size.x); + CHECK(split_container->get_split_offset() == 0); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Minimum sizes affect default position. @@ -520,79 +555,79 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); def_pos = 400; - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Second child with minimum size. child_a->set_custom_minimum_size(Size2(0, 0)); child_b->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - def_pos = split_container->get_size().x - 400 - separator_size.x; - check_position(split_container, def_pos, separator_size.x); + def_pos = split_container->get_size().x - 400 - sep.x; + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Both children with minimum size. child_a->set_custom_minimum_size(Size2(200, 0)); child_b->set_custom_minimum_size(Size2(288, 0)); MessageQueue::get_singleton()->flush(); def_pos = 200; - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] First child expanded") { - const int def_pos = split_container->get_size().x - separator_size.x; + const int def_pos = split_container->get_size().x - sep.x; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Minimum sizes affect default position. // First child with minimum size. child_a->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Second child with minimum size. child_a->set_custom_minimum_size(Size2(0, 0)); child_b->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - int pos = split_container->get_size().x - 400 - separator_size.x; - check_position(split_container, pos, separator_size.x); + int pos = split_container->get_size().x - 400 - sep.x; + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); // Both children with minimum size. child_a->set_custom_minimum_size(Size2(200, 0)); child_b->set_custom_minimum_size(Size2(288, 0)); MessageQueue::get_singleton()->flush(); pos = 200; - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Second child expanded") { @@ -600,12 +635,12 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Minimum sizes affect default position. @@ -613,47 +648,47 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); def_pos = 400; - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Second child with minimum size. child_a->set_custom_minimum_size(Size2(0, 0)); child_b->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - def_pos = 500 - 400 - separator_size.x; - check_position(split_container, def_pos, separator_size.x); + def_pos = 500 - 400 - sep.x; + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Both children with minimum size. child_a->set_custom_minimum_size(Size2(200, 0)); child_b->set_custom_minimum_size(Size2(288, 0)); MessageQueue::get_singleton()->flush(); def_pos = 200; - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == def_pos); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Both children expanded") { - const int def_pos = (split_container->get_size().x - separator_size.x) / 2; + const int def_pos = (split_container->get_size().x - sep.x) / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Minimum sizes affect default position. @@ -661,48 +696,48 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); int pos = 400; - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); // Second child with minimum size. child_a->set_custom_minimum_size(Size2(0, 0)); child_b->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - pos = split_container->get_size().x - 400 - separator_size.x; - check_position(split_container, pos, separator_size.x); + pos = split_container->get_size().x - 400 - sep.x; + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); // Both children with minimum size. child_a->set_custom_minimum_size(Size2(200, 0)); child_b->set_custom_minimum_size(Size2(288, 0)); MessageQueue::get_singleton()->flush(); pos = 200; - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Unequal stretch ratios") { - const int def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + const int def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_a->set_stretch_ratio(2.0); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 0); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Minimum sizes affect default position. @@ -710,33 +745,33 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); int pos = 400; - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); // Second child with minimum size. child_a->set_custom_minimum_size(Size2(0, 0)); child_b->set_custom_minimum_size(Size2(400, 0)); MessageQueue::get_singleton()->flush(); - pos = split_container->get_size().x - 400 - separator_size.x; - check_position(split_container, pos, separator_size.x); + pos = split_container->get_size().x - 400 - sep.x; + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); // Both children with minimum size. child_a->set_custom_minimum_size(Size2(200, 0)); child_b->set_custom_minimum_size(Size2(288, 0)); MessageQueue::get_singleton()->flush(); pos = 200; - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); split_container->clamp_split_offset(); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == pos - def_pos); - check_position(split_container, pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, pos, sep.x), get_child_rects(split_container)); } } @@ -749,13 +784,13 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position_rtl(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects_rtl(split_container, def_pos + 10, sep.y), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position_rtl(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects_rtl(split_container, def_pos, sep.y), get_child_rects(split_container)); } SUBCASE("[SplitContainer] No expand flags") { @@ -765,23 +800,23 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Clamped. split_container->set_split_offset(1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 1000); - check_position(split_container, split_container->get_size().x - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, split_container->get_size().x - sep.x, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] First child expanded") { - int def_pos = split_container->get_size().x - separator_size.x; + int def_pos = split_container->get_size().x - sep.x; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); @@ -789,19 +824,19 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position(split_container, def_pos - 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 10, sep.x), get_child_rects(split_container)); // Clamped. split_container->set_split_offset(-1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -1000); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Second child expanded") { @@ -813,23 +848,23 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Clamped. split_container->set_split_offset(1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 1000); - check_position(split_container, split_container->get_size().x - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, split_container->get_size().x - sep.x, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Both children expanded") { - int def_pos = (split_container->get_size().x - separator_size.x) / 2; + int def_pos = (split_container->get_size().x - sep.x) / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); @@ -838,29 +873,29 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position(split_container, def_pos - 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 10, sep.x), get_child_rects(split_container)); // Clamped positive. split_container->set_split_offset(1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 1000); - check_position(split_container, split_container->get_size().x - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, split_container->get_size().x - sep.x, sep.x), get_child_rects(split_container)); // Clamped negative. split_container->set_split_offset(-1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -1000); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Unequal stretch ratios") { - int def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + int def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_a->set_stretch_ratio(2.0); @@ -870,25 +905,25 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { split_container->set_split_offset(10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 10); - check_position(split_container, def_pos + 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 10, sep.x), get_child_rects(split_container)); // Negative. split_container->set_split_offset(-10); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -10); - check_position(split_container, def_pos - 10, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 10, sep.x), get_child_rects(split_container)); // Clamped positive. split_container->set_split_offset(1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 1000); - check_position(split_container, split_container->get_size().x - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, split_container->get_size().x - sep.x, sep.x), get_child_rects(split_container)); // Clamped negative. split_container->set_split_offset(-1000); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -1000); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); } } @@ -900,16 +935,16 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(10, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 100); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); child_a->set_custom_minimum_size(Size2(50, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 100); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] First child expanded") { - int def_pos = split_container->get_size().x - separator_size.x; + int def_pos = split_container->get_size().x - sep.x; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); @@ -917,12 +952,12 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_b->set_custom_minimum_size(Size2(10, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -100); - check_position(split_container, def_pos - 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); child_b->set_custom_minimum_size(Size2(50, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == -100); - check_position(split_container, def_pos - 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Second child expanded") { @@ -934,16 +969,16 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_custom_minimum_size(Size2(10, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 100); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); child_a->set_custom_minimum_size(Size2(50, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 100); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); } SUBCASE("[SplitContainer] Both children expanded") { - int def_pos = (split_container->get_size().x - separator_size.x) / 2; + int def_pos = (split_container->get_size().x - sep.x) / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); @@ -953,90 +988,345 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_b->set_custom_minimum_size(Size2(10, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 20); - check_position(split_container, def_pos + 20, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); child_a->set_custom_minimum_size(Size2(50, 0)); child_b->set_custom_minimum_size(Size2(50, 0)); MessageQueue::get_singleton()->flush(); CHECK(split_container->get_split_offset() == 20); - check_position(split_container, def_pos + 20, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); } } + SUBCASE("[SplitContainer] Keep split offset when changing visibility") { + SUBCASE("[SplitContainer] No expand flags") { + int def_pos = 0; + split_container->set_split_offset(100); + + child_a->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + + child_a->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] First child expanded") { + int def_pos = split_container->get_size().x - sep.x; + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(-100); + + child_a->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + + child_a->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] Second child expanded") { + int def_pos = 0; + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(100); + + child_a->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + + child_a->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] Both children expanded") { + int def_pos = (split_container->get_size().x - sep.x) / 2; + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(20); + + child_a->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->hide(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + + child_a->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + child_b->show(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + } + } + + SUBCASE("[SplitContainer] Keep split offset when removing children") { + SUBCASE("[SplitContainer] No expand flags") { + int def_pos = 0; + split_container->set_split_offset(100); + + split_container->remove_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->remove_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + + split_container->add_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->add_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] First child expanded") { + int def_pos = split_container->get_size().x - sep.x; + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(-100); + + split_container->remove_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->remove_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + + split_container->add_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->add_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == -100); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] Second child expanded") { + int def_pos = 0; + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(100); + + split_container->remove_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->remove_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + + split_container->add_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->add_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] Both children expanded") { + int def_pos = (split_container->get_size().x - sep.x) / 2; + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + MessageQueue::get_singleton()->flush(); + split_container->set_split_offset(20); + + split_container->remove_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->remove_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + + split_container->add_child(child_a); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects_multi(split_container, Vector(), sep.x), get_child_rects(split_container)); + + split_container->add_child(child_b); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + } + } + + SUBCASE("[SplitContainer] Keep split offset when changing expand flags") { + int def_pos = 0; + split_container->set_split_offset(20); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + def_pos = split_container->get_size().x - sep.x; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); + + child_a->set_h_size_flags(Control::SIZE_FILL); + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + def_pos = 0; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + + child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); + child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); + def_pos = (split_container->get_size().x - sep.x) / 2; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + + child_a->set_h_size_flags(Control::SIZE_FILL); + child_b->set_h_size_flags(Control::SIZE_FILL); + def_pos = 0; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 20); + CHECK_RECTS(get_rects(split_container, def_pos + 20, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] Keep split offset when moving children") { + int def_pos = 0; + split_container->set_split_offset(100); + + split_container->move_child(child_a, 1); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offset() == 100); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); + } + SUBCASE("[SplitContainer] Resize split container") { SUBCASE("[SplitContainer] No expand flags") { int def_pos = 0; // Increase the size. split_container->set_size(Size2(600, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Decrease the size. split_container->set_size(Size2(400, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Change size with a split offset. split_container->set_split_offset(100); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); split_container->set_size(Size2(500, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Change size so that the first child changes size. split_container->set_size(Size2(80, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, 80 - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, 80 - sep.x, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Increase size again. split_container->set_size(Size2(500, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); } SUBCASE("[SplitContainer] First child expanded") { - int def_pos = split_container->get_size().x - separator_size.x; + int def_pos = split_container->get_size().x - sep.x; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); // Increase the size. split_container->set_size(Size2(600, 500)); - def_pos = split_container->get_size().x - separator_size.x; + def_pos = split_container->get_size().x - sep.x; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Decrease the size. split_container->set_size(Size2(400, 500)); - def_pos = split_container->get_size().x - separator_size.x; + def_pos = split_container->get_size().x - sep.x; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Change size with a split offset. split_container->set_split_offset(-100); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos - 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); split_container->set_size(Size2(500, 500)); - def_pos = split_container->get_size().x - separator_size.x; + def_pos = split_container->get_size().x - sep.x; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos - 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == -100); // Change size so that the second child changes size. split_container->set_size(Size2(80, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, 0, separator_size.x); + CHECK_RECTS(get_rects(split_container, 0, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == -100); // Increase size again. split_container->set_size(Size2(500, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos - 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos - 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == -100); } @@ -1048,81 +1338,81 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Increase the size. split_container->set_size(Size2(600, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Decrease the size. split_container->set_size(Size2(400, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Change size with a split offset. split_container->set_split_offset(100); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); split_container->set_size(Size2(500, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Change size so that the first child changes size. split_container->set_size(Size2(80, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, 80 - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, 80 - sep.x, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Increase size again. split_container->set_size(Size2(500, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); } SUBCASE("[SplitContainer] Both children expanded") { - int def_pos = (split_container->get_size().x - separator_size.x) / 2; + int def_pos = (split_container->get_size().x - sep.x) / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); // Increase the size. split_container->set_size(Size2(600, 500)); - def_pos = (split_container->get_size().x - separator_size.x) / 2; + def_pos = (split_container->get_size().x - sep.x) / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Decrease the size. split_container->set_size(Size2(400, 500)); - def_pos = (split_container->get_size().x - separator_size.x) / 2; + def_pos = (split_container->get_size().x - sep.x) / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Change size with a split offset. split_container->set_split_offset(100); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); split_container->set_size(Size2(500, 500)); - def_pos = (split_container->get_size().x - separator_size.x) / 2; + def_pos = (split_container->get_size().x - sep.x) / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Change size so that the second child is minimized. split_container->set_size(Size2(80, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, 80 - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, 80 - sep.x, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Increase size again. split_container->set_size(Size2(500, 500)); - def_pos = (split_container->get_size().x - separator_size.x) / 2; + def_pos = (split_container->get_size().x - sep.x) / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); } SUBCASE("[SplitContainer] Unequal stretch ratios") { - int def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + int def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); child_a->set_stretch_ratio(2.0); @@ -1130,38 +1420,38 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Increase the size. split_container->set_size(Size2(600, 500)); - def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Decrease the size. split_container->set_size(Size2(400, 500)); - def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos, sep.x), get_child_rects(split_container)); // Change size with a split offset. split_container->set_split_offset(100); MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); split_container->set_size(Size2(500, 500)); - def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Change size so that the second child is minimized. split_container->set_size(Size2(80, 500)); MessageQueue::get_singleton()->flush(); - check_position(split_container, 80 - separator_size.x, separator_size.x); + CHECK_RECTS(get_rects(split_container, 80 - sep.x, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); // Increase size again. split_container->set_size(Size2(500, 500)); - def_pos = (split_container->get_size().x * 2 / 3) - separator_size.x / 2; + def_pos = (split_container->get_size().x * 2 / 3) - sep.x / 2; MessageQueue::get_singleton()->flush(); - check_position(split_container, def_pos + 100, separator_size.x); + CHECK_RECTS(get_rects(split_container, def_pos + 100, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == 100); } } @@ -1179,7 +1469,7 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Grab the dragger. SEND_GUI_MOUSE_BUTTON_EVENT(mouse_offset + Point2(0, dragger_pos), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); SIGNAL_CHECK_FALSE("dragged"); @@ -1187,7 +1477,7 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 10; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(0, dragger_pos), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // It is clamped. split_container->clamp_split_offset(); @@ -1199,16 +1489,16 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 400; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(0, dragger_pos), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); ((Array)signal_args[0])[0] = split_container->get_split_offset(); SIGNAL_CHECK("dragged", signal_args); // Moves even when mouse is outside. - dragger_pos = split_container->get_size().y - separator_size.y; + dragger_pos = split_container->get_size().y - sep.y; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(0, 1000), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); ((Array)signal_args[0])[0] = split_container->get_split_offset(); SIGNAL_CHECK("dragged", signal_args); @@ -1217,7 +1507,7 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 100; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(0, dragger_pos), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); ((Array)signal_args[0])[0] = split_container->get_split_offset(); SIGNAL_CHECK("dragged", signal_args); @@ -1225,14 +1515,14 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Release. SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(mouse_offset + Point2(0, dragger_pos), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); SIGNAL_CHECK_FALSE("dragged"); // No longer moves with the mouse. SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(0, 200), MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.y, false); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.y, false), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); SIGNAL_CHECK_FALSE("dragged"); @@ -1247,14 +1537,14 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Grab the dragger. SEND_GUI_MOUSE_BUTTON_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move the dragger. dragger_pos = 10; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // It is clamped. split_container->clamp_split_offset(); @@ -1264,33 +1554,33 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 400; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Moves even when mouse is outside. - dragger_pos = split_container->get_size().x - separator_size.x; + dragger_pos = split_container->get_size().x - sep.x; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(1000, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move back in. dragger_pos = 100; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Release. SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // No longer moves with the mouse. SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(200, 0), MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); } @@ -1298,20 +1588,20 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_a->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); Point2 mouse_offset = Point2(1, 1); - int dragger_pos = split_container->get_size().x - separator_size.x; + int dragger_pos = split_container->get_size().x - sep.x; int split_dragger_ofs = -dragger_pos; // Grab the dragger. SEND_GUI_MOUSE_BUTTON_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move the dragger. dragger_pos -= 10; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // It is clamped. split_container->clamp_split_offset(); @@ -1321,33 +1611,33 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 400; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Moves even when mouse is outside. - dragger_pos = split_container->get_size().x - separator_size.x; + dragger_pos = split_container->get_size().x - sep.x; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(1000, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move back in. dragger_pos = 100; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Release. SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // No longer moves with the mouse. SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(200, 0), MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); } @@ -1361,14 +1651,14 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { // Grab the dragger. SEND_GUI_MOUSE_BUTTON_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move the dragger. dragger_pos = 10; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // It is clamped. split_container->clamp_split_offset(); @@ -1378,33 +1668,33 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 400; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Moves even when mouse is outside. - dragger_pos = split_container->get_size().x - separator_size.x; + dragger_pos = split_container->get_size().x - sep.x; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(1000, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move back in. dragger_pos = 100; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Release. SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // No longer moves with the mouse. SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(200, 0), MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); } @@ -1413,20 +1703,20 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { child_b->set_h_size_flags(Control::SIZE_EXPAND_FILL); MessageQueue::get_singleton()->flush(); Point2 mouse_offset = Point2(1, 1); - int dragger_pos = (split_container->get_size().x - separator_size.x) / 2; + int dragger_pos = (split_container->get_size().x - sep.x) / 2; int split_dragger_ofs = -dragger_pos; // Grab the dragger. SEND_GUI_MOUSE_BUTTON_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move the dragger. dragger_pos += 10; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // It is clamped. split_container->clamp_split_offset(); @@ -1436,33 +1726,33 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { dragger_pos = 400; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Moves even when mouse is outside. - dragger_pos = split_container->get_size().x - separator_size.x; + dragger_pos = split_container->get_size().x - sep.x; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(1000, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Move back in. dragger_pos = 100; SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButtonMask::LEFT, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // Release. SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(mouse_offset + Point2(dragger_pos, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); // No longer moves with the mouse. SEND_GUI_MOUSE_MOTION_EVENT(mouse_offset + Point2(200, 0), MouseButtonMask::NONE, Key::NONE); MessageQueue::get_singleton()->flush(); - check_position(split_container, dragger_pos, separator_size.x); + CHECK_RECTS(get_rects(split_container, dragger_pos, sep.x), get_child_rects(split_container)); CHECK(split_container->get_split_offset() == dragger_pos + split_dragger_ofs); } } @@ -1472,4 +1762,510 @@ TEST_CASE("[SceneTree][SplitContainer] Two children") { memdelete(split_container); } +TEST_CASE("[SceneTree][SplitContainer] More children") { + SplitContainer *split_container = memnew(SplitContainer); + split_container->set_size(Size2(500, 500)); + SceneTree::get_singleton()->get_root()->add_child(split_container); + Control *child_a = memnew(Control); + Control *child_b = memnew(Control); + Control *child_c = memnew(Control); + split_container->add_child(child_a); + split_container->add_child(child_b); + split_container->add_child(child_c); + Size2i min_size = Size2i(10, 10); + child_a->set_custom_minimum_size(min_size); + child_b->set_custom_minimum_size(min_size); + child_c->set_custom_minimum_size(min_size); + MessageQueue::get_singleton()->flush(); + + const int sep_constant = split_container->get_theme_constant("separation"); + const Size2i sep = Size2i(MAX(sep_constant, split_container->get_theme_icon("h_grabber")->get_width()), MAX(sep_constant, split_container->get_theme_icon("v_grabber")->get_height())); + + SUBCASE("[SplitContainer] Duplicate") { + // Make sure dynamically added internal draggers duplicate properly. + SplitContainer *duplicate = (SplitContainer *)split_container->duplicate(); + MessageQueue::get_singleton()->flush(); + CHECK(duplicate->get_child_count(false) == split_container->get_child_count(false)); + CHECK(duplicate->get_child_count(true) == split_container->get_child_count(true)); + memdelete(duplicate); + } + + SUBCASE("[SplitContainer] Default position") { + CHECK(split_container->get_split_offsets() == Vector({ 0, 0 })); + + set_size_flags(split_container, { -1, -1, -1 }); // None expanded. + Vector def_pos = { min_size.x, min_size.x * 2 + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ min_size.x, min_size.x * 2 + sep.x })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { 1, -1, -1 }); // First expanded. + def_pos = { (int)split_container->get_size().x - sep.x * 2 - min_size.x * 2, (int)split_container->get_size().x - sep.x - min_size.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ -min_size.x * 2 - sep.x, -min_size.x })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { -1, 1, -1 }); // Second expanded. + def_pos = { min_size.x, (int)split_container->get_size().x - min_size.x - sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ min_size.x, -min_size.x })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { -1, -1, 1 }); // Third expanded. + def_pos = { min_size.x, min_size.x * 2 + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ min_size.x, min_size.x * 2 + sep.x })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { 1, 1, -1 }); // First and second expanded. + int child_2_expanded_size = ((int)split_container->get_size().x - min_size.x) / 2 - sep.x; + def_pos = { child_2_expanded_size, (int)split_container->get_size().x - min_size.x - sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 0, -min_size.x })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { 1, -1, 1 }); // First and third expanded. + def_pos = { child_2_expanded_size, child_2_expanded_size + min_size.x + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 0, 0 })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { -1, 1, 1 }); // Second and third expanded. + def_pos = { min_size.x, min_size.x + child_2_expanded_size + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ min_size.x, 0 })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { 1, 1, 1 }); // All expanded. + int child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + // Add 1 due to pixel error accumulation. + def_pos = { child_3_expanded_size, child_3_expanded_size * 2 + sep.x + 1 }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 0, 0 })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + + set_size_flags(split_container, { 1, 2, 3 }); // All expanded, different ratios. + int child_6_expanded_size = (split_container->get_size().x - sep.x * 2) / 6; + def_pos = { child_6_expanded_size, child_6_expanded_size * 3 + sep.x + 1 }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->clamp_split_offset(); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 0, 0 })); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + split_container->set_split_offsets({ 0, 0 }); + } + + SUBCASE("[SplitContainer] Set split offset") { + const int expanded_single_size = (int)split_container->get_size().x - min_size.x * 2 - sep.x * 2; + + SUBCASE("[SplitContainer] No expand flags") { + set_size_flags(split_container, { -1, -1, -1 }); // None expanded. + + // First is positive. + split_container->set_split_offsets({ 50, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 50, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { 50, 50 + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Second is positive. + split_container->set_split_offsets({ 0, 50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, 50 }); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, 50 }, sep.x), get_child_rects(split_container)); + + // Both are positive and equal, the first will override since they both start at 0. + split_container->set_split_offsets({ 50, 50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 50, 50 }); + CHECK_RECTS(get_rects_multi(split_container, { 50, 50 + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Both are negative and clamped. + split_container->set_split_offsets({ -50, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -50, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, min_size.x * 2 + sep.x }, sep.x), get_child_rects(split_container)); + + // First positive, second negative. First takes priority. + split_container->set_split_offsets({ 50, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 50, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { 50, 50 + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // First is clamped and pushes second to the end. + split_container->set_split_offsets({ 1000, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 1000, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Second is clamped. + split_container->set_split_offsets({ 0, 1000 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, 1000 }); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Both are clamped positively, first one takes priority. + split_container->set_split_offsets({ 1000, 1000 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 1000, 1000 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] First child expanded") { + set_size_flags(split_container, { 1, -1, -1 }); // First expanded. + + // First is positive and clamped. + split_container->set_split_offsets({ 50, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 50, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Second is positive and clamped. + split_container->set_split_offsets({ 0, 50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, 50 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // First is negative and moves left. + split_container->set_split_offsets({ -50, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -50, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { (int)split_container->get_size().x - 50 - sep.x, (int)split_container->get_size().x - min_size.x - sep.x }, sep.x), get_child_rects(split_container)); + + // Second is negative, but first has priority so it doesn't move. + split_container->set_split_offsets({ 0, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Both are negative and equal, they move left but the second doesn't move as much as wanted. + split_container->set_split_offsets({ -50, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -50, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { (int)split_container->get_size().x - 50 - sep.x, (int)split_container->get_size().x - 50 + min_size.x }, sep.x), get_child_rects(split_container)); + + // Both are negative with space and move left. + split_container->set_split_offsets({ -100, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -100, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { (int)split_container->get_size().x - 100 - sep.x, (int)split_container->get_size().x - 50 - sep.x }, sep.x), get_child_rects(split_container)); + + // First moves all the way left. + split_container->set_split_offsets({ -1000, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -1000, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // Second cannot move all the way left since first takes priority. + split_container->set_split_offsets({ 0, -1000 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, -1000 }); + CHECK_RECTS(get_rects_multi(split_container, { expanded_single_size, expanded_single_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + + // First and second move all the way left. + split_container->set_split_offsets({ -1000, -1000 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -1000, -1000 }); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, min_size.x * 2 + sep.x }, sep.x), get_child_rects(split_container)); + } + + SUBCASE("[SplitContainer] All children expanded") { + set_size_flags(split_container, { 1, 1, 1 }); // All expanded. + const int child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + + // First is moved positive, does not affect second. + split_container->set_split_offsets({ 50, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 50, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size + 50, child_3_expanded_size * 2 + sep.x + 1 }, sep.x), get_child_rects(split_container)); + + // First is moved negative, does not affect second. + split_container->set_split_offsets({ -50, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ -50, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size - 50, child_3_expanded_size * 2 + sep.x + 1 }, sep.x), get_child_rects(split_container)); + + // Second is moved positive, does not affect first. + split_container->set_split_offsets({ 0, 50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, 50 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size, child_3_expanded_size * 2 + 50 + sep.x + 1 }, sep.x), get_child_rects(split_container)); + + // Second is moved negative, does not affect first. + split_container->set_split_offsets({ 0, -50 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, -50 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size, child_3_expanded_size * 2 - 50 + sep.x + 1 }, sep.x), get_child_rects(split_container)); + + // First is moved positive enough to affect second. + split_container->set_split_offsets({ 200, 0 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 200, 0 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size + 200, child_3_expanded_size + 200 + sep.x + min_size.x }, sep.x), get_child_rects(split_container)); + + // Second is moved enough to pass the first, but the first has priority. + split_container->set_split_offsets({ 0, -200 }); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector{ 0, -200 }); + CHECK_RECTS(get_rects_multi(split_container, { child_3_expanded_size, child_3_expanded_size + min_size.x + sep.x }, sep.x), get_child_rects(split_container)); + } + } + + SUBCASE("[SplitContainer] Resize") { + SUBCASE("[SplitContainer] No expand flags") { + Vector def_pos = { min_size.x, min_size.x * 2 + sep.x }; + // Increase the size. + split_container->set_size(Size2(600, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Decrease the size. + split_container->set_size(Size2(400, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Change size with a split offset. + split_container->set_split_offsets({ 50, 100 }); + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { 50, 100 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ 50, 100 }); + + // Change size so that the second child gets clamped and changes size. + split_container->set_size(Size2(100, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { 50, 100 - sep.x - min_size.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ 50, 100 }); + + // Change size so that the first child changes size. + split_container->set_size(Size2(60, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { 60 - min_size.x * 2 - sep.x * 2, 60 - sep.x - min_size.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ 50, 100 }); + + // Increase size again. + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { 50, 100 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ 50, 100 }); + } + + SUBCASE("[SplitContainer] First child expanded") { + set_size_flags(split_container, { 1, -1, -1 }); + Vector def_pos = { (int)split_container->get_size().x - sep.x * 2 - min_size.x, (int)split_container->get_size().x - sep.x }; + // Increase the size. + split_container->set_size(Size2(600, 500)); + def_pos = { (int)split_container->get_size().x - sep.x * 2 - min_size.x * 2, (int)split_container->get_size().x - sep.x - min_size.x }; + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Decrease the size. + split_container->set_size(Size2(400, 500)); + def_pos = { (int)split_container->get_size().x - sep.x * 2 - min_size.x * 2, (int)split_container->get_size().x - sep.x - min_size.x }; + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Change size with a split offset. + split_container->set_split_offsets({ -100, -50 }); + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { (int)split_container->get_size().x - 100 - sep.x, (int)split_container->get_size().x - 50 - sep.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -100, -50 }); + + // Change size so that the first child gets clamped and changes size. + split_container->set_size(Size2(100, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, (int)split_container->get_size().x - 50 - sep.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -100, -50 }); + + // Change size so that the second child changes size. + split_container->set_size(Size2(50, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, min_size.x * 2 + sep.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -100, -50 }); + + // Increase size again. + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { (int)split_container->get_size().x - 100 - sep.x, (int)split_container->get_size().x - 50 - sep.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -100, -50 }); + } + + SUBCASE("[SplitContainer] All children expanded") { + set_size_flags(split_container, { 1, 1, 1 }); + // Increase the size. + split_container->set_size(Size2(600, 500)); + MessageQueue::get_singleton()->flush(); + int child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + Vector def_pos = { child_3_expanded_size, child_3_expanded_size * 2 + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Decrease the size. + split_container->set_size(Size2(400, 500)); + MessageQueue::get_singleton()->flush(); + child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + def_pos = { child_3_expanded_size, child_3_expanded_size * 2 + sep.x }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Change size with a split offset. + split_container->set_split_offsets({ -50, 50 }); + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + def_pos = { child_3_expanded_size, child_3_expanded_size * 2 + sep.x + 1 }; + CHECK_RECTS(get_rects_multi(split_container, { def_pos[0] - 50, def_pos[1] + 50 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -50, 50 }); + + // Change size so that the children get clamped and change sizes. + split_container->set_size(Size2(100, 500)); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { min_size.x, (int)split_container->get_size().x - sep.x - min_size.x }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -50, 50 }); + + // Increase size again. + split_container->set_size(Size2(500, 500)); + MessageQueue::get_singleton()->flush(); + child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + def_pos = { child_3_expanded_size, child_3_expanded_size * 2 + sep.x + 1 }; + CHECK_RECTS(get_rects_multi(split_container, { def_pos[0] - 50, def_pos[1] + 50 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector{ -50, 50 }); + } + } + + SUBCASE("[SplitContainer] Visibility changes") { + set_size_flags(split_container, { -1, -1, -1 }); // None expanded. + split_container->set_split_offsets({ 50, 122 }); + MessageQueue::get_singleton()->flush(); + Vector def_pos = { 50, 122 }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Hide and show the first child. + child_a->set_visible(false); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 60 })); + CHECK_RECTS(get_rects_multi(split_container, { 60 }, sep.x), get_child_rects(split_container)); + + child_a->set_visible(true); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == def_pos); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Hide and show the second child. + child_b->set_visible(false); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 50 })); + CHECK_RECTS(get_rects_multi(split_container, { 50 }, sep.x), get_child_rects(split_container)); + child_b->set_visible(true); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == def_pos); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Hide and show the last child. + child_c->set_visible(false); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == Vector({ 50 })); + CHECK_RECTS(get_rects_multi(split_container, { 50 }, sep.x), get_child_rects(split_container)); + child_c->set_visible(true); + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == def_pos); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + set_size_flags(split_container, { 1, 1, 1 }); // All expanded. + split_container->set_split_offsets({ 50, 60 }); + MessageQueue::get_singleton()->flush(); + int child_3_expanded_size = (split_container->get_size().x - sep.x * 2) / 3; + def_pos = { child_3_expanded_size + 50, child_3_expanded_size * 2 + sep.x + 1 + 60 }; + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + + // Hide and show the first child. + int child_2_expanded_size = (split_container->get_size().x - sep.x) / 2; + child_a->set_visible(false); + MessageQueue::get_singleton()->flush(); + int half_point = (split_container->get_size().x - def_pos[0]) / 2 - sep.x; + int so = child_3_expanded_size + 11 - half_point; // 11 is from 60 - 50 + 1 to get the second child's size. + CHECK_RECTS(get_rects_multi(split_container, { child_2_expanded_size + so }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ so })); + child_a->set_visible(true); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, def_pos, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ 50, 60 })); + + // Hide and show the second child. + child_b->set_visible(false); + MessageQueue::get_singleton()->flush(); + half_point = (split_container->get_size().x - (def_pos[1] - def_pos[0] - sep.x)) / 2 - sep.x + 1; + so = def_pos[0] - half_point; + CHECK_RECTS(get_rects_multi(split_container, { child_2_expanded_size + so }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ so })); + child_b->set_visible(true); + MessageQueue::get_singleton()->flush(); + // There is lost precision due to SplitContainer using ints, so this is off by one. + CHECK_RECTS(get_rects_multi(split_container, { def_pos[0] - 1, def_pos[1] - 1 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ 49, 59 })); + + // Hide and show the last child. + split_container->set_split_offsets({ 50, 60 }); + child_c->set_visible(false); + MessageQueue::get_singleton()->flush(); + half_point = (def_pos[1] - sep.x) / 2 + 1; + so = def_pos[0] - half_point; + CHECK_RECTS(get_rects_multi(split_container, { child_2_expanded_size + so }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ so })); + child_c->set_visible(true); + MessageQueue::get_singleton()->flush(); + CHECK_RECTS(get_rects_multi(split_container, { def_pos[0] - 1, def_pos[1] - 1 }, sep.x), get_child_rects(split_container)); + CHECK(split_container->get_split_offsets() == Vector({ 49, 59 })); + } + + SUBCASE("[SplitContainer] Adjust split offset when moving children") { + split_container->set_split_offsets({ 50, 80 }); + split_container->move_child(child_a, 1); + Vector pos = { 30 - sep.x, 80 }; // 30 = 80 - 50. + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == pos); + CHECK_RECTS(get_rects_multi(split_container, pos, sep.x), get_child_rects(split_container)); + + // Move last child to first. + split_container->set_split_offsets({ 50, 80 }); + split_container->move_child(child_c, 0); + pos = { (int)split_container->get_size().x - 80 - sep.x, (int)split_container->get_size().x - 30 }; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == pos); + CHECK_RECTS(get_rects_multi(split_container, pos, sep.x), get_child_rects(split_container)); + + // Move it back. + split_container->move_child(child_c, 2); + pos = { 50, 80 }; + MessageQueue::get_singleton()->flush(); + CHECK(split_container->get_split_offsets() == pos); + CHECK_RECTS(get_rects_multi(split_container, pos, sep.x), get_child_rects(split_container)); + } + + memdelete(split_container); +} + } // namespace TestSplitContainer