Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 64 additions & 41 deletions scene/gui/box_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,26 @@

struct _MinSizeCache {
int min_size = 0;
int desired_size = 0;
int max_size = -1;
real_t stretch_ratio = 0;
bool will_stretch = false;
int final_size = 0;
};

void BoxContainer::_resort() {
/** First pass, determine minimum size AND amount of stretchable elements */

Size2i new_size = get_size();
Size2 combined_max_size = get_combined_maximum_size();
Size2i combined_max_size = get_combined_maximum_size();
bool propagating_max_size = vertical ? is_propagating_maximum_size() && combined_max_size.height >= 0 : is_propagating_maximum_size() && combined_max_size.width >= 0;

bool rtl = is_layout_rtl();

bool first = true;
int children_count = 0;
int stretch_min = 0;
int stretch_avail = 0;
int combined_min = 0;
int stretch_space = 0;
float stretch_ratio_total = 0.0;
int desired_extra_space = 0;
HashMap<Control *, _MinSizeCache> min_size_cache;

for (int i = 0; i < get_child_count(); i++) {
Expand All @@ -68,30 +69,31 @@ void BoxContainer::_resort() {
c->set_parent_maximum_size_cache(combined_max_size);
}
Size2i min_size = c->get_bound_minimum_size().ceil();
Size2i max_size = c->get_combined_maximum_size();
Size2i desired_size = c->get_bound_desired_size().ceil();
Size2i max_size = c->get_combined_maximum_size().floor();
_MinSizeCache msc;

if (vertical) { /* VERTICAL */
stretch_min += min_size.height;
msc.min_size = min_size.height;
msc.desired_size = desired_size.height;
msc.max_size = max_size.height;
msc.will_stretch = c->get_v_size_flags().has_flag(SIZE_EXPAND);

} else { /* HORIZONTAL */
stretch_min += min_size.width;
msc.min_size = min_size.width;
msc.desired_size = desired_size.width;
msc.max_size = max_size.width;
msc.will_stretch = c->get_h_size_flags().has_flag(SIZE_EXPAND);
}

if (msc.max_size >= 0 && msc.max_size < msc.min_size) {
msc.max_size = msc.min_size;
}

if (msc.will_stretch) {
stretch_avail += msc.min_size;
stretch_space += msc.min_size;
stretch_ratio_total += c->get_stretch_ratio();
}

combined_min += msc.min_size;
desired_extra_space += msc.desired_size - msc.min_size;

msc.final_size = msc.min_size;
min_size_cache[c] = msc;
children_count++;
Expand All @@ -101,27 +103,50 @@ void BoxContainer::_resort() {
return;
}

int stretch_max = (vertical ? new_size.height : new_size.width) - (children_count - 1) * theme_cache.separation;
int stretch_diff = stretch_max - stretch_min;
int max_space = (vertical ? new_size.height : new_size.width);
if (propagating_max_size) {
max_space = MIN(max_space, vertical ? combined_max_size.height : combined_max_size.width);
}
max_space -= theme_cache.separation * (children_count - 1);
int stretch_diff = max_space - combined_min;
if (stretch_diff < 0) {
//avoid negative stretch space
stretch_diff = 0;
}

stretch_avail += stretch_diff; //available stretch space.
/** Second, pass successively to discard elements that can't be stretched, this will run while stretchable
elements exist */
stretch_space += stretch_diff; //available stretch space.

if (propagating_max_size) {
if (vertical) {
stretch_avail = MIN(stretch_avail, combined_max_size.height);
} else {
stretch_avail = MIN(stretch_avail, combined_max_size.width);
// First, allocate extra space to Controls which have a desired size larger than their minimum size, up to their desired size, in proportion to how much extra space they want.
if (stretch_space > 0 && desired_extra_space > 0) {
real_t space_available_ratio = MIN(real_t(stretch_space) / real_t(desired_extra_space), 1.0);

for (Node *child : iterate_children()) {
Control *c = as_sortable_control(child, SortableVisibilityMode::VISIBLE);
if (!c) {
continue;
}

_MinSizeCache &msc = min_size_cache[c];

if (msc.desired_size > msc.min_size) {
int desired_size_increase = floor((msc.desired_size - msc.min_size) * space_available_ratio);

if (msc.will_stretch) {
// Increase the minimum size to reflect the desired size allocation, so that the SIZE_EXPAND allocation does not shrink them back down.
msc.min_size += desired_size_increase;
} else {
// If the Control isn't stretchable
stretch_space -= desired_size_increase;
}

msc.final_size += desired_size_increase;
}
}
}

while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist
bool refit_successful = true; //assume refit-test will go well
// Second, allocate stretch space to Controls with the SIZE_EXPAND flag, in proportion to their stretch ratio.
while (stretch_ratio_total > 0) {
bool refit_successful = true;
float error = 0.0; // Keep track of accumulated error in pixels

for (int i = 0; i < get_child_count(); i++) {
Expand All @@ -133,37 +158,35 @@ void BoxContainer::_resort() {
ERR_FAIL_COND(!min_size_cache.has(c));
_MinSizeCache &msc = min_size_cache[c];

if (msc.will_stretch) { //wants to stretch
//let's see if it can really stretch
if (msc.will_stretch) {
float stretch_ratio = c->get_stretch_ratio();
float final_pixel_size = stretch_avail * stretch_ratio / stretch_ratio_total;
// Add leftover fractional pixels to error accumulator
float final_pixel_size = stretch_space * stretch_ratio / stretch_ratio_total;

// Add leftover fractional pixels to error accumulator and dump if greater than 1.
error += final_pixel_size - (int)final_pixel_size;
if (error >= 1) {
final_pixel_size += 1;
error -= 1;
}

if (final_pixel_size < msc.min_size) {
//if available stretching area is too small for widget,
//then remove it from stretching area
// If stretching would make the Control smaller than its minimum size, cap it and redistribute its unused share.
msc.will_stretch = false;
stretch_ratio_total -= stretch_ratio;
refit_successful = false;
stretch_avail -= msc.min_size;
stretch_space -= msc.min_size;
msc.final_size = msc.min_size;
break;
} else if (msc.max_size >= 0 && final_pixel_size > msc.max_size) {
// If stretching would exceed the Control's maximum size,
// cap it and redistribute its unused share.
// If stretching would exceed the Control's maximum size, cap it and redistribute its unused share.
msc.will_stretch = false;
stretch_ratio_total -= stretch_ratio;
refit_successful = false;
stretch_avail -= msc.max_size;
stretch_space -= msc.max_size;
msc.final_size = msc.max_size;
break;
} else {
msc.final_size = final_pixel_size;
// Dump accumulated error if one pixel or more
if (error >= 1 && (msc.max_size < 0 || msc.final_size < msc.max_size)) {
msc.final_size += 1;
error -= 1;
}
}
}
}
Expand All @@ -176,7 +199,7 @@ void BoxContainer::_resort() {
/** Final pass, draw and stretch elements **/

int ofs = 0;
int final_stretch_diff = stretch_max - stretch_min;
int final_stretch_diff = max_space - combined_min;
for (int i = 0; i < get_child_count(); i++) {
Control *c = as_sortable_control(get_child(i));
if (!c) {
Expand Down
5 changes: 5 additions & 0 deletions scene/gui/control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2095,6 +2095,11 @@ void Control::grow_to_desired_size() {
}
}

bool Control::is_expanded_by_desired_size() const {
ERR_READ_THREAD_GUARD_V(false);
return data.expanded_by_desired_size;
}

void Control::add_child_notify(Node *p_child) {
CanvasItem::add_child_notify(p_child);

Expand Down
1 change: 1 addition & 0 deletions scene/gui/control.h
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ class Control : public CanvasItem {
void update_desired_size();

void grow_to_desired_size();
bool is_expanded_by_desired_size() const;

void set_block_maximum_size_adjust(bool p_block);
void set_block_minimum_size_adjust(bool p_block);
Expand Down
12 changes: 11 additions & 1 deletion scene/gui/flow_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ void FlowContainer::_resort() {
continue;
}

Size2i child_msc = child->get_bound_minimum_size();
// Since we are in a FlowContainer, children will always have up to the full width/height available to them, so we can use the desired size as the minimum size for layout purposes.
Size2i child_msc = child->get_bound_desired_size();
Size2i child_max_size = child->get_combined_maximum_size();

if (vertical) { /* VERTICAL */
Expand Down Expand Up @@ -332,6 +333,15 @@ void FlowContainer::_resort() {
child_rect.position.x = get_rect().size.x - child_rect.position.x - child_rect.size.width;
}

// Ensure that the child does not exceed the container's size in the flow direction.
// This will only ever apply in the case of having a single child in a line that is larger than the container's minimum size, i.e. a child with a desired size greater than its own minimum size.
// This will result in the child being given a size between its minimum size and its desired size, which is the expected behavior.
if (vertical) {
child_rect.size.height = MIN(child_rect.size.height, current_container_size - ofs.y);
} else {
child_rect.size.width = MIN(child_rect.size.width, current_container_size - ofs.x);
}

fit_child_in_rect(child, child_rect);

if (vertical) { /* VERTICAL */
Expand Down
60 changes: 58 additions & 2 deletions scene/gui/grid_container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
void GridContainer::_resort() {
RBMap<int, int> col_minw; // Max of min_width of all controls in each col (indexed by col).
RBMap<int, int> row_minh; // Max of min_height of all controls in each row (indexed by row).
RBMap<int, int> col_desiredw; // Max of desired_width of all controls in each col (indexed by col).
RBMap<int, int> row_desiredh; // Max of desired_height of all controls in each row (indexed by row).
RBMap<int, int> col_maxw; // Min positive max_width of all controls in each col (indexed by col).
RBMap<int, int> row_maxh; // Min positive max_height of all controls in each row (indexed by row).
RBMap<int, int> col_fixed_size; // Final fixed width for non-expanded columns.
Expand All @@ -63,8 +65,9 @@ void GridContainer::_resort() {
if (is_propagating_maximum_size()) {
c->set_parent_maximum_size_cache(combined_max_size);
}
Size2i ms = c->get_bound_minimum_size();
Size2 max_size = c->get_combined_maximum_size();
Size2i ms = c->get_bound_minimum_size().ceil();
Size2i desired_size = c->get_bound_desired_size().ceil();
Size2i max_size = c->get_combined_maximum_size().floor();
if (col_minw.has(col)) {
col_minw[col] = MAX(col_minw[col], ms.width);
} else {
Expand All @@ -76,6 +79,17 @@ void GridContainer::_resort() {
row_minh[row] = ms.height;
}

if (col_desiredw.has(col)) {
col_desiredw[col] = MAX(col_desiredw[col], desired_size.width);
} else {
col_desiredw[col] = desired_size.width;
}
if (row_desiredh.has(row)) {
row_desiredh[row] = MAX(row_desiredh[row], desired_size.height);
} else {
row_desiredh[row] = desired_size.height;
}

int max_width = int(max_size.width) >= 0 ? int(max_size.width) : (combined_max_size.width >= 0 ? combined_max_size.width : size.width);
if (col_maxw.has(col)) {
col_maxw[col] = MAX(col_maxw[col], max_width);
Expand Down Expand Up @@ -134,6 +148,48 @@ void GridContainer::_resort() {
remaining_space.height -= theme_cache.v_separation * MAX(max_row - 1, 0);
remaining_space.width -= theme_cache.h_separation * MAX(max_col - 1, 0);

// Distribute the remaining space to cols/rows which have a desired size larger than their minimum size, up to their desired size, in proportion to how much extra space they want.
int total_desired_extra_space = 0;
for (const KeyValue<int, int> &E : col_desiredw) {
int desired_extra_space = E.value - col_minw[E.key];
if (desired_extra_space > 0) {
total_desired_extra_space += desired_extra_space;
}
}
if (remaining_space.width > 0 && total_desired_extra_space > 0) {
real_t space_available_ratio = MIN(real_t(remaining_space.width) / real_t(total_desired_extra_space), 1.0);
for (const KeyValue<int, int> &E : col_desiredw) {
int desired_extra_space = E.value - col_minw[E.key];
if (desired_extra_space > 0) {
int desired_size_increase = floor(desired_extra_space * space_available_ratio);
col_minw[E.key] += desired_size_increase;
if (!col_expanded.has(E.key)) {
remaining_space.width -= desired_size_increase;
}
}
}
}
total_desired_extra_space = 0;
for (const KeyValue<int, int> &E : row_desiredh) {
int desired_extra_space = E.value - row_minh[E.key];
if (desired_extra_space > 0) {
total_desired_extra_space += desired_extra_space;
}
}
if (remaining_space.height > 0 && total_desired_extra_space > 0) {
real_t space_available_ratio = MIN(real_t(remaining_space.height) / real_t(total_desired_extra_space), 1.0);
for (const KeyValue<int, int> &E : row_desiredh) {
int desired_extra_space = E.value - row_minh[E.key];
if (desired_extra_space > 0) {
int desired_size_increase = floor(desired_extra_space * space_available_ratio);
row_minh[E.key] += desired_size_increase;
if (!row_expanded.has(E.key)) {
remaining_space.height -= desired_size_increase;
}
}
}
}

bool can_fit = false;
while (!can_fit && col_expanded.size() > 0) {
// Check if all minwidth constraints are OK if we use the remaining space.
Expand Down
6 changes: 5 additions & 1 deletion scene/gui/label.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ void Label::_shape() const {
if (maximum_width <= 0) {
maximum_width = 1;
}
width = maximum_width;
if (width > 0 && !is_expanded_by_desired_size()) {
width = MIN(width, maximum_width);
} else {
width = maximum_width;
}
}

if (text_dirty) {
Expand Down
Loading