feat: updated engine

This commit is contained in:
Sara Gerretsen 2026-07-10 17:04:34 +02:00
parent cbe99774ff
commit f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions

View file

@ -31,9 +31,119 @@
#include "split_container.h"
#include "split_container.compat.inc"
#include "core/config/engine.h"
#include "core/input/input.h"
#include "core/object/callable_mp.h"
#include "core/object/class_db.h"
#include "scene/gui/texture_rect.h"
#include "scene/main/viewport.h"
#include "scene/theme/theme_db.h"
#include "servers/display/accessibility_server.h"
void SplitContainerMultiDragger::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER: {
if (!dragging && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
split_container->show_grabber_icon(dragger_index);
}
} break;
case NOTIFICATION_MOUSE_EXIT: {
if (!dragging && !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
split_container->show_grabber_icon(-1);
}
} break;
}
}
void SplitContainerMultiDragger::gui_input(const Ref<InputEvent> &p_event) {
SplitContainer *parent_sc = Object::cast_to<SplitContainer>(get_parent()->get_parent());
ERR_FAIL_NULL(parent_sc);
if (parent_sc->collapsed || parent_sc->valid_children.size() < 2u || !parent_sc->dragging_enabled) {
return;
}
if (split_container->collapsed || split_container->valid_children.size() < 2u || !split_container->dragging_enabled || !split_container->is_visible_in_tree()) {
return;
}
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
if (mb->is_pressed()) {
// To match the visual position, clamp on the first split.
split_container->_update_dragger_positions(0);
dragging = true;
split_container->show_grabber_icon(dragger_index);
split_container->emit_signal(SNAME("drag_started"));
const int axis = split_container->vertical ? 1 : 0;
start_drag_split_offset = split_container->get_split_offset(dragger_index);
drag_from = (int)get_transform().xform(mb->get_position())[axis];
} else {
stop_dragging();
}
// Don't accept to allow the event to go to the parent dragger.
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (!dragging) {
return;
}
Vector2i in_parent_pos = get_transform().xform(mm->get_position());
int new_drag_offset;
if (!split_container->vertical && is_layout_rtl()) {
new_drag_offset = start_drag_split_offset - (in_parent_pos.x - drag_from);
} else {
new_drag_offset = start_drag_split_offset + ((split_container->vertical ? in_parent_pos.y : in_parent_pos.x) - drag_from);
}
split_container->set_split_offset(new_drag_offset, dragger_index);
split_container->_update_dragger_positions(dragger_index);
split_container->queue_sort();
split_container->emit_signal(SNAME("dragged"), split_container->get_split_offset(dragger_index));
}
}
Control::CursorShape SplitContainerMultiDragger::get_cursor_shape(const Point2 &p_pos) const {
SplitContainer *parent_sc = Object::cast_to<SplitContainer>(get_parent()->get_parent());
ERR_FAIL_NULL_V(parent_sc, CURSOR_ARROW);
if (dragging || (!parent_sc->collapsed && parent_sc->dragging_enabled)) {
return CURSOR_DRAG;
}
return Control::get_cursor_shape(p_pos);
}
void SplitContainerMultiDragger::update_position() {
if (dragging || is_queued_for_deletion()) {
return;
}
SplitContainer *parent_sc = Object::cast_to<SplitContainer>(get_parent()->get_parent());
ERR_FAIL_NULL(parent_sc);
ERR_FAIL_INDEX(dragger_index, (int)split_container->dragging_area_controls.size());
const int axis = parent_sc->vertical ? 1 : 0;
const int cross_axis = parent_sc->vertical ? 0 : 1;
Control *control = split_container->dragging_area_controls[dragger_index];
Point2 pos;
pos[axis] = get_parent_control()->get_global_position()[axis];
pos[cross_axis] = control->get_global_position()[cross_axis];
set_global_position(pos);
}
void SplitContainerMultiDragger::stop_dragging() {
if (!dragging) {
return;
}
dragging = false;
split_container->show_grabber_icon(-1);
update_position();
split_container->emit_signal(SNAME("drag_ended"));
}
SplitContainerMultiDragger::SplitContainerMultiDragger() {
set_mouse_filter(MOUSE_FILTER_PASS);
}
void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@ -73,9 +183,7 @@ void SplitContainerDragger::gui_input(const Ref<InputEvent> &p_event) {
drag_from = (int)get_transform().xform(mb->get_position()).x;
}
} else {
dragging = false;
queue_redraw();
sc->emit_signal(SNAME("drag_ended"));
stop_dragging();
}
accept_event();
}
@ -194,6 +302,28 @@ void SplitContainerDragger::update_touch_dragger() {
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);
touch_dragger->set_visible(sc->dragging_enabled);
}
void SplitContainerDragger::stop_dragging() {
ERR_THREAD_GUARD;
if (!dragging) {
return;
}
dragging = false;
queue_redraw();
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
sc->emit_signal(SNAME("drag_ended"));
for (Node *child : iterate_children()) {
SplitContainerMultiDragger *dragger = Object::cast_to<SplitContainerMultiDragger>(child);
if (dragger) {
dragger->stop_dragging();
}
}
}
String SplitContainerDragger::_get_accessibility_name() const {
return RTR("Drag to resize");
}
void SplitContainerDragger::_notification(int p_what) {
@ -205,19 +335,18 @@ void SplitContainerDragger::_notification(int p_what) {
RID ae = get_accessibility_element();
ERR_FAIL_COND(ae.is_null());
DisplayServer::get_singleton()->accessibility_update_set_role(ae, DisplayServer::AccessibilityRole::ROLE_SPLITTER);
DisplayServer::get_singleton()->accessibility_update_set_name(ae, RTR("Drag to resize"));
AccessibilityServer::get_singleton()->update_set_role(ae, AccessibilityServerEnums::AccessibilityRole::ROLE_SPLITTER);
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
if (sc->collapsed || sc->valid_children.size() < 2u || !sc->dragging_enabled) {
return;
}
sc->clamp_split_offset(dragger_index);
DisplayServer::get_singleton()->accessibility_update_set_num_value(ae, sc->get_split_offset(dragger_index));
AccessibilityServer::get_singleton()->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));
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_DECREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_dec));
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_INCREMENT, callable_mp(this, &SplitContainerDragger::_accessibility_action_inc));
AccessibilityServer::get_singleton()->update_add_action(ae, AccessibilityServerEnums::AccessibilityAction::ACTION_SET_VALUE, callable_mp(this, &SplitContainerDragger::_accessibility_action_set_value));
} break;
case NOTIFICATION_THEME_CHANGED: {
@ -245,22 +374,19 @@ void SplitContainerDragger::_notification(int p_what) {
} break;
case NOTIFICATION_FOCUS_EXIT: {
if (dragging) {
dragging = false;
queue_redraw();
}
stop_dragging();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (dragging && !is_visible_in_tree()) {
dragging = false;
stop_dragging();
}
} break;
case NOTIFICATION_DRAW: {
SplitContainer *sc = Object::cast_to<SplitContainer>(get_parent());
draw_style_box(sc->theme_cache.split_bar_background, split_bar_rect);
if (sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide) && !sc->touch_dragger_enabled) {
if (!sc->touch_dragger_enabled && sc->dragger_visibility == SplitContainer::DRAGGER_VISIBLE && (dragging || mouse_inside || !sc->theme_cache.autohide || sc->force_show_grabber_icon == dragger_index)) {
Ref<Texture2D> 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.
@ -325,15 +451,43 @@ Point2i SplitContainer::_get_valid_range(int p_dragger_index) const {
position_range.x += sep * p_dragger_index;
position_range.y -= sep * ((int)dragger_positions.size() - p_dragger_index);
int max_left = sep * p_dragger_index;
int max_right = sep * ((int)dragger_positions.size() - p_dragger_index);
bool has_left_max = true;
bool has_right_max = true;
for (int i = 0; i < (int)valid_children.size(); i++) {
Control *child = valid_children[i];
ERR_FAIL_NULL_V(child, Point2i());
int max_size = (int)child->get_combined_maximum_size()[axis];
if (i <= p_dragger_index) {
position_range.x += (int)child->get_combined_minimum_size()[axis];
position_range.x += (int)child->get_bound_minimum_size()[axis];
if (has_left_max) {
if (max_size >= 0) {
max_left += max_size;
} else {
has_left_max = false;
}
}
} else if (i > p_dragger_index) {
position_range.y -= (int)child->get_combined_minimum_size()[axis];
position_range.y -= (int)child->get_bound_minimum_size()[axis];
if (has_right_max) {
if (max_size >= 0) {
max_right += max_size;
} else {
has_right_max = false;
}
}
}
}
if (has_left_max) {
position_range.y = MIN(position_range.y, max_left);
}
if (has_right_max) {
position_range.x = MAX(position_range.x, (int)get_size()[axis] - max_right);
}
return position_range;
}
@ -360,6 +514,8 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
const int sep = _get_separation();
const int axis = vertical ? 1 : 0;
const real_t size = get_size()[axis];
const Size2 combined_max_size = get_inner_combined_maximum_size();
const bool propagating_max_size = is_propagating_maximum_size() && combined_max_size[axis] >= 0;
real_t total_desired_size = 0;
if (!p_desired_sizes.is_empty()) {
@ -369,6 +525,7 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
struct StretchData {
real_t min_size = 0;
real_t max_size = -1;
real_t stretch_ratio = 0.0;
real_t final_size = 0;
bool priority = false;
@ -380,7 +537,8 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
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.min_size = child->get_bound_minimum_size()[axis];
sdata.max_size = child->get_combined_maximum_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;
sdata.priority = i == p_priority_index;
@ -392,19 +550,42 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
stretch_data.push_back(sdata);
}
real_t available_space = size - total_desired_size;
const real_t stretch_max = (propagating_max_size && stretch_total > 0.0) ? combined_max_size[axis] : size;
real_t available_space = stretch_max - total_desired_size;
// Grow expanding children.
if (available_space > 0) {
const real_t grow_amount = available_space / stretch_total;
while (available_space > 0) {
real_t growable_stretch_ratio = 0.0;
for (const StretchData &sdata : stretch_data) {
if (sdata.stretch_ratio <= 0) {
continue;
}
if (sdata.max_size >= 0 && sdata.final_size >= sdata.max_size) {
continue;
}
growable_stretch_ratio += sdata.stretch_ratio;
}
if (growable_stretch_ratio <= 0.0) {
break;
}
const real_t grow_amount = available_space / growable_stretch_ratio;
const real_t prev_available_space = available_space;
for (StretchData &sdata : stretch_data) {
if (sdata.stretch_ratio <= 0) {
continue;
}
if (sdata.max_size >= 0 && sdata.final_size >= sdata.max_size) {
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;
const real_t target_size = prev_size + grow_amount * sdata.stretch_ratio;
sdata.final_size = sdata.max_size >= 0 ? MIN(target_size, sdata.max_size) : target_size;
const real_t size_diff = sdata.final_size - prev_size;
available_space -= size_diff;
}
if (Math::is_zero_approx(available_space) || Math::is_equal_approx(available_space, prev_available_space)) {
break;
}
}
@ -493,6 +674,21 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
}
}
if (stretch_total > 0.0) {
real_t desired_axis_size = sep * (MAX((int)stretch_data.size() - 1, 0));
for (const StretchData &sdata : stretch_data) {
desired_axis_size += sdata.final_size;
}
const bool should_resize_for_growth = desired_axis_size > size;
const bool should_resize_for_propagating_max_shrink = propagating_max_size && desired_axis_size < size;
if (should_resize_for_growth || should_resize_for_propagating_max_shrink) {
Size2 new_size = get_size();
new_size[axis] = desired_axis_size;
set_size(new_size);
_update_default_dragger_positions();
}
}
ERR_FAIL_COND((int)default_dragger_positions.size() != (int)stretch_data.size() - 1);
// Update the split offsets to match the desired sizes.
@ -527,6 +723,7 @@ void SplitContainer::_update_default_dragger_positions() {
struct StretchData {
int min_size = 0;
int max_size = -1;
real_t stretch_ratio = 0.0;
int final_size = 0;
bool expand_flag = false;
@ -540,7 +737,8 @@ void SplitContainer::_update_default_dragger_positions() {
LocalVector<StretchData> stretch_data;
for (const Control *child : valid_children) {
StretchData sdata;
sdata.min_size = (int)child->get_combined_minimum_size()[axis];
sdata.min_size = (int)child->get_bound_minimum_size()[axis];
sdata.max_size = (int)child->get_combined_maximum_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();
@ -583,6 +781,14 @@ void SplitContainer::_update_default_dragger_positions() {
sdata.final_size = sdata.min_size;
refit_successful = false;
break;
} else if (sdata.max_size >= 0 && desired_stretch_size > sdata.max_size) {
// Will not be stretched, remove and retry.
stretch_total -= sdata.stretch_ratio;
stretchable_space -= sdata.max_size;
sdata.will_stretch = false;
sdata.final_size = sdata.max_size;
refit_successful = false;
break;
} else {
sdata.final_size = (int)desired_stretch_size;
// Dump accumulated error if one pixel or more.
@ -659,7 +865,7 @@ void SplitContainer::_update_dragger_positions(int p_clamp_index) {
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 check_min_size = (int)valid_children[i + 1]->get_bound_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;
@ -671,23 +877,39 @@ void SplitContainer::_update_dragger_positions(int p_clamp_index) {
// Prioritize the active dragger.
const int dragging_position = dragger_positions[p_clamp_index];
// Push overlapping draggers to the left.
int accumulated_min_size = (int)valid_children[p_clamp_index]->get_combined_minimum_size()[axis];
// Propagate constraints to the left.
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;
const int right_dragger_position = i == p_clamp_index - 1 ? dragging_position : dragger_positions[i + 1];
const int min_size = (int)valid_children[i + 1]->get_bound_minimum_size()[axis];
const int max_position = right_dragger_position - sep - min_size;
if (dragger_positions[i] > max_position) {
dragger_positions[i] = max_position;
}
const int max_size = (int)valid_children[i + 1]->get_combined_maximum_size()[axis];
if (max_size >= 0) {
const int min_position = right_dragger_position - sep - max_size;
if (dragger_positions[i] < min_position) {
dragger_positions[i] = min_position;
}
}
accumulated_min_size += (int)valid_children[i]->get_combined_minimum_size()[axis];
}
// Push overlapping draggers to the right.
accumulated_min_size = 0;
// Propagate constraints to the right.
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;
const int left_dragger_position = i == p_clamp_index + 1 ? dragging_position : dragger_positions[i - 1];
const int min_size = (int)valid_children[i]->get_bound_minimum_size()[axis];
const int min_position = left_dragger_position + sep + min_size;
if (dragger_positions[i] < min_position) {
dragger_positions[i] = min_position;
}
const int max_size = (int)valid_children[i]->get_combined_maximum_size()[axis];
if (max_size >= 0) {
const int max_position = left_dragger_position + sep + max_size;
if (dragger_positions[i] > max_position) {
dragger_positions[i] = max_position;
}
}
}
}
@ -720,13 +942,14 @@ void SplitContainer::_resort() {
for (SplitContainerDragger *dragger : dragging_area_controls) {
dragger->hide();
}
if (drag_nested_intersections) {
// Ancestors may need to remove multi draggers.
_update_nested_ancestors();
}
return;
}
for (SplitContainerDragger *dragger : dragging_area_controls) {
dragger->set_visible(!collapsed);
if (touch_dragger_enabled) {
dragger->touch_dragger->set_visible(dragging_enabled);
}
}
_update_default_dragger_positions();
@ -779,9 +1002,23 @@ void SplitContainer::_resort() {
dragging_area_controls[i]->queue_redraw();
}
queue_redraw();
if (drag_nested_intersections) {
_update_nested_ancestors();
// Update all multi dragger positions in case size changed.
for (SplitContainerDragger *dragger : dragging_area_controls) {
for (Node *child : dragger->iterate_children()) {
SplitContainerMultiDragger *multi_dragger = Object::cast_to<SplitContainerMultiDragger>(child);
if (multi_dragger) {
multi_dragger->update_position();
}
}
}
}
}
void SplitContainer::_update_draggers() {
ERR_THREAD_GUARD;
if (!is_visible_in_tree()) {
return;
}
@ -789,6 +1026,24 @@ void SplitContainer::_update_draggers() {
const int dragger_count = MAX(valid_child_count - 1, 1);
const int draggers_size_diff = dragger_count - (int)dragging_area_controls.size();
if (draggers_size_diff != 0) {
LocalVector<SplitContainer *> to_update;
for (SplitContainerDragger *dragger : dragging_area_controls) {
for (Node *child : dragger->iterate_children()) {
SplitContainerMultiDragger *multi_dragger = Object::cast_to<SplitContainerMultiDragger>(child);
if (multi_dragger == nullptr || multi_dragger->is_queued_for_deletion()) {
continue;
}
to_update.push_back(multi_dragger->split_container);
}
}
_remove_nested_descendent(nullptr);
// Existing descendents may not resort, so need to update them after draggers update.
for (SplitContainer *to_update_sc : to_update) {
callable_mp(to_update_sc, &SplitContainer::_update_nested_ancestors).call_deferred(false);
}
}
// Add new draggers.
for (int i = 0; i < draggers_size_diff; i++) {
SplitContainerDragger *dragger = memnew(SplitContainerDragger);
@ -803,6 +1058,7 @@ void SplitContainer::_update_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];
dragger->stop_dragging();
dragging_area_controls.remove_at(remove_at);
// replace_by removes all children, so make sure it is a child before removing.
if (dragger->get_parent() == this) {
@ -817,7 +1073,7 @@ void SplitContainer::_update_draggers() {
}
}
Size2 SplitContainer::get_minimum_size() const {
Size2 SplitContainer::_get_minimum_size(bool p_use_desired_sizes) const {
const int sep = _get_separation();
const int axis = vertical ? 1 : 0;
const int other_axis = vertical ? 0 : 1;
@ -829,7 +1085,7 @@ Size2 SplitContainer::get_minimum_size() const {
}
for (const Control *child : valid_children) {
const Size2 min_size = child->get_combined_minimum_size();
const Size2 min_size = p_use_desired_sizes ? child->get_bound_desired_size() : child->get_bound_minimum_size();
minimum[axis] += (int)min_size[axis];
minimum[other_axis] = (int)MAX(minimum[other_axis], min_size[other_axis]);
}
@ -837,6 +1093,14 @@ Size2 SplitContainer::get_minimum_size() const {
return minimum;
}
Size2 SplitContainer::get_minimum_size() const {
return _get_minimum_size(false);
}
Size2 SplitContainer::get_desired_size() const {
return _get_minimum_size(true);
}
void SplitContainer::_validate_property(PropertyInfo &p_property) const {
if (is_fixed && p_property.name == "vertical") {
p_property.usage = PROPERTY_USAGE_NONE;
@ -858,9 +1122,17 @@ void SplitContainer::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
update_minimum_size();
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible_in_tree()) {
_update_nested_ancestors();
}
} break;
case NOTIFICATION_PREDELETE: {
valid_children.clear();
dragging_area_controls.clear();
if (drag_nested_intersections) {
_remove_nested_descendent(nullptr);
}
} break;
}
}
@ -1053,6 +1325,178 @@ void SplitContainer::_remove_valid_child(Control *p_control) {
_set_desired_sizes(desired_sizes);
}
void SplitContainer::_remove_nested_descendent(SplitContainer *p_nested_sc) {
for (SplitContainerDragger *dragger : dragging_area_controls) {
for (int i = dragger->get_child_count() - 1; i >= 0; i--) {
SplitContainerMultiDragger *multi_dragger = Object::cast_to<SplitContainerMultiDragger>(dragger->get_child(i));
if (multi_dragger == nullptr || multi_dragger->is_queued_for_deletion()) {
continue;
}
if (p_nested_sc != nullptr && multi_dragger->split_container != p_nested_sc) {
continue;
}
dragger->stop_dragging();
SplitContainer *nested_sc = multi_dragger->split_container;
if (nested_sc->is_connected(SceneStringName(tree_exiting), callable_mp(this, &SplitContainer::_remove_nested_descendent))) {
nested_sc->disconnect(SceneStringName(tree_exiting), callable_mp(this, &SplitContainer::_remove_nested_descendent).bind(nested_sc));
}
multi_dragger->queue_free();
}
}
}
void SplitContainer::_update_nested_descendent(SplitContainer *p_nested_sc, Control *p_direct_child) {
ERR_THREAD_GUARD;
// Remove multi draggers if the descendent cannot drag or is not orthogonal. If any of these change, the descendent will call this update again.
if (!p_nested_sc->is_dragging_enabled() || !p_nested_sc->is_dragging_nested_intersections() || p_nested_sc->is_collapsed() || !p_nested_sc->is_visible_in_tree() || p_nested_sc->is_vertical() == vertical || p_nested_sc->valid_children.size() < 2u) {
_remove_nested_descendent(p_nested_sc);
return;
}
if (dragging_area_controls.size() != valid_children.size() - 1) {
_update_draggers();
}
// Make sure the nested SplitContainer is adjacent.
const int child_index = valid_children.find(p_direct_child);
if (child_index < 0) {
return;
}
const int axis = vertical ? 1 : 0;
const float max_delta_pos = theme_cache.minimum_grab_thickness;
const float start_pos = p_direct_child->get_global_rect().get_position()[axis];
const float end_pos = p_direct_child->get_global_rect().get_end()[axis];
const float child_start_pos = p_nested_sc->get_global_rect().get_position()[axis];
const float child_end_pos = p_nested_sc->get_global_rect().get_end()[axis];
bool nested_to_start = Math::abs(start_pos - child_start_pos) <= max_delta_pos;
bool nested_to_end = Math::abs(end_pos - child_end_pos) <= max_delta_pos;
if (is_layout_rtl() && !is_vertical()) {
SWAP(nested_to_start, nested_to_end);
}
LocalVector<SplitContainerDragger *> connected_draggers;
if (nested_to_start && child_index > 0) {
connected_draggers.push_back(dragging_area_controls[child_index - 1]);
}
if (nested_to_end && child_index < (int)dragging_area_controls.size()) {
connected_draggers.push_back(dragging_area_controls[child_index]);
}
if (connected_draggers.is_empty()) {
_remove_nested_descendent(p_nested_sc);
return;
}
bool needs_rebuild = false;
for (SplitContainerDragger *dragger : connected_draggers) {
int needed = (int)p_nested_sc->valid_children.size() - 1;
for (Node *child : dragger->iterate_children()) {
SplitContainerMultiDragger *multi_dragger = Object::cast_to<SplitContainerMultiDragger>(child);
if (multi_dragger == nullptr || multi_dragger->is_queued_for_deletion()) {
continue;
}
if (multi_dragger->split_container != p_nested_sc) {
continue;
}
needed--;
if (needed < 0) {
break;
}
}
if (needed != 0) {
needs_rebuild = true;
break;
}
}
if (!needs_rebuild) {
// Only update the positions.
for (SplitContainerDragger *dragger : dragging_area_controls) {
for (Node *child : dragger->iterate_children()) {
SplitContainerMultiDragger *multi_dragger = Object::cast_to<SplitContainerMultiDragger>(child);
if (multi_dragger) {
multi_dragger->update_position();
}
}
}
return;
}
_remove_nested_descendent(p_nested_sc);
// Add new multi draggers.
const int sep = _get_separation();
const int drag_thickness = MAX(sep, theme_cache.minimum_grab_thickness);
for (SplitContainerDragger *dragger : connected_draggers) {
for (int i = 0; i < (int)p_nested_sc->dragging_area_controls.size(); i++) {
SplitContainerMultiDragger *new_dragger = memnew(SplitContainerMultiDragger);
new_dragger->dragger_index = i;
new_dragger->split_container = p_nested_sc;
new_dragger->set_size(Size2(drag_thickness, drag_thickness));
dragger->add_child(new_dragger, false, INTERNAL_MODE_FRONT);
if (!p_nested_sc->is_connected(SceneStringName(tree_exiting), callable_mp(this, &SplitContainer::_remove_nested_descendent))) {
p_nested_sc->connect(SceneStringName(tree_exiting), callable_mp(this, &SplitContainer::_remove_nested_descendent).bind(p_nested_sc));
}
callable_mp(new_dragger, &SplitContainerMultiDragger::update_position).call_deferred();
}
}
}
void SplitContainer::_update_nested_ancestors(bool p_remove) {
if (!is_inside_tree()) {
return;
}
if (!p_remove && !drag_nested_intersections) {
return;
}
Control *last_ancestor = this;
Control *ancestor = get_parent_control();
while (ancestor) {
SplitContainer *ancestor_split_container = Object::cast_to<SplitContainer>(ancestor);
if (ancestor_split_container && ancestor_split_container->is_dragging_nested_intersections()) {
if (p_remove) {
ancestor_split_container->_remove_nested_descendent(this);
} else {
ancestor_split_container->_update_nested_descendent(this, last_ancestor);
}
}
if (ancestor->is_part_of_edited_scene() != is_part_of_edited_scene()) {
break;
}
if (ancestor->is_set_as_top_level()) {
break;
}
last_ancestor = ancestor;
ancestor = ancestor->get_parent_control();
}
}
void SplitContainer::_update_all_nested_descendents(Control *p_control, Control *p_first_child) {
ERR_THREAD_GUARD;
for (Node *child_node : p_control->iterate_children<false>()) {
Control *child_control = Object::cast_to<Control>(child_node);
if (!child_control || child_control->is_set_as_top_level()) {
break;
}
if (child_control->is_part_of_edited_scene() != is_part_of_edited_scene()) {
break;
}
if (p_first_child == nullptr) {
p_first_child = child_control;
}
SplitContainer *control_sc = Object::cast_to<SplitContainer>(child_control);
if (control_sc) {
_update_nested_descendent(control_sc, p_first_child);
}
_update_all_nested_descendents(child_control, p_first_child);
}
}
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) {
@ -1140,13 +1584,13 @@ void SplitContainer::set_dragging_enabled(bool p_enabled) {
}
dragging_enabled = p_enabled;
if (!dragging_enabled) {
bool was_dragging = false;
for (SplitContainerDragger *dragger : dragging_area_controls) {
was_dragging |= dragger->dragging;
dragger->dragging = false;
dragger->stop_dragging();
}
if (was_dragging) {
emit_signal(SNAME("drag_ended"));
}
if (touch_dragger_enabled) {
for (SplitContainerDragger *dragger : dragging_area_controls) {
dragger->update_touch_dragger();
}
}
if (get_viewport()) {
@ -1253,6 +1697,36 @@ bool SplitContainer::is_touch_dragger_enabled() const {
return touch_dragger_enabled;
}
void SplitContainer::show_grabber_icon(int p_index) {
if (force_show_grabber_icon == p_index) {
return;
}
force_show_grabber_icon = p_index;
for (SplitContainerDragger *dragger : dragging_area_controls) {
dragger->queue_redraw();
}
}
void SplitContainer::set_drag_nested_intersections(bool p_enabled) {
if (drag_nested_intersections == p_enabled) {
return;
}
drag_nested_intersections = p_enabled;
if (!is_inside_tree()) {
return;
}
_update_nested_ancestors(!drag_nested_intersections);
if (drag_nested_intersections) {
_update_all_nested_descendents(this);
} else {
_remove_nested_descendent(nullptr);
}
}
bool SplitContainer::is_dragging_nested_intersections() const {
return drag_nested_intersections;
}
void SplitContainer::_bind_methods() {
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);
@ -1288,6 +1762,9 @@ void SplitContainer::_bind_methods() {
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);
ClassDB::bind_method(D_METHOD("set_drag_nested_intersections", "enabled"), &SplitContainer::set_drag_nested_intersections);
ClassDB::bind_method(D_METHOD("is_dragging_nested_intersections"), &SplitContainer::is_dragging_nested_intersections);
ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::INT, "offset")));
ADD_SIGNAL(MethodInfo("drag_started"));
ADD_SIGNAL(MethodInfo("drag_ended"));
@ -1298,6 +1775,7 @@ void SplitContainer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "dragger_visibility", PROPERTY_HINT_ENUM, "Visible,Hidden,Hidden and Collapsed"), "set_dragger_visibility", "get_dragger_visibility");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "touch_dragger_enabled"), "set_touch_dragger_enabled", "is_touch_dragger_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "drag_nested_intersections"), "set_drag_nested_intersections", "is_dragging_nested_intersections");
ADD_GROUP("Drag Area", "drag_area_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_area_margin_begin", PROPERTY_HINT_NONE, "suffix:px"), "set_drag_area_margin_begin", "get_drag_area_margin_begin");