1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-30 18:30:54 +00:00

Merge pull request #113648 from kitbdev/fix-multisplit-incorrect-shrinking

Fix SplitContainer incorrect child shrink logic
This commit is contained in:
Rémi Verschelde
2025-12-18 14:24:15 +01:00
2 changed files with 104 additions and 8 deletions

View File

@@ -353,6 +353,7 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
real_t min_size = 0;
real_t stretch_ratio = 0.0;
real_t final_size = 0;
bool priority = false;
};
// First pass, determine the total stretch amount.
@@ -364,6 +365,7 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
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;
sdata.priority = i == p_priority_index;
// 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();
@@ -419,13 +421,14 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
}
// Shrink non-expanding children.
bool skip_priority_child = true;
while (available_space < 0) {
// Get largest and target sizes.
// Get largest and target sizes. The target size is the second largest size.
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) {
if (sdata.final_size <= sdata.min_size || (skip_priority_child && sdata.priority)) {
continue;
}
if (sdata.final_size > largest_size) {
@@ -434,18 +437,24 @@ void SplitContainer::_set_desired_sizes(const PackedInt32Array &p_desired_sizes,
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);
} else if (sdata.final_size < largest_size && sdata.final_size > target_size) {
target_size = sdata.final_size;
}
}
if (largest_size <= 0) {
break;
if (skip_priority_child) {
// Retry with priority child.
skip_priority_child = false;
continue;
} else {
// No more children to shrink.
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));
target_size = MAX(target_size, largest_size + available_space / largest_count);
for (StretchData &sdata : stretch_data) {
if (sdata.final_size <= sdata.min_size) {
if (sdata.final_size <= sdata.min_size || (skip_priority_child && sdata.priority)) {
continue;
}
// Shrink all largest elements.

View File

@@ -2265,6 +2265,93 @@ TEST_CASE("[SceneTree][SplitContainer] More children") {
CHECK_RECTS(get_rects_multi(split_container, pos, sep.x), get_child_rects(split_container));
}
SUBCASE("[SplitContainer] Showing child with not enough space shrinks the largest child first") {
set_size_flags(split_container, { -1, -1, -1 }); // None expanded.
// Second child is largest.
child_a->set_visible(false);
Vector<int> pos = { 360 };
split_container->set_split_offsets(pos);
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));
child_a->set_size(Vector2(100, 100));
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));
child_a->set_visible(true);
pos = { 100, 360 };
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));
// Last child is largest.
child_a->set_visible(false);
pos = { 60 };
split_container->set_split_offsets(pos);
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));
child_a->set_size(Vector2(100, 100));
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));
child_a->set_visible(true);
pos = { 100, 160 + sep.x };
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));
// Both visible children are the same size.
child_a->set_visible(false);
pos = { (int)split_container->get_size().x / 2 - sep.x / 2 };
split_container->set_split_offsets(pos);
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));
CHECK(child_b->get_size().x == child_c->get_size().x);
child_a->set_size(Vector2(100, 100));
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));
child_a->set_visible(true);
pos = { 100, (int)split_container->get_size().x / 2 + 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));
CHECK(child_b->get_size().x == child_c->get_size().x);
// Second child is slightly larger than the last child.
child_a->set_visible(false);
pos = { (int)split_container->get_size().x / 2 - sep.x / 2 + 20 };
split_container->set_split_offsets(pos);
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));
child_a->set_size(Vector2(100, 100));
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));
child_a->set_visible(true);
pos = { 100, (int)split_container->get_size().x / 2 + 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));
CHECK(child_b->get_size().x == child_c->get_size().x);
}
memdelete(split_container);
}