From 76c46739447023368db4c3e6d008b52ff4b249e0 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 9 Nov 2021 03:28:29 +0100 Subject: [PATCH] boxlayout: Fix broken min-size-for-opposite-size Assume a vbox with 2 wrapping labels saying Hello World Hi Ho being measured for their minimum width for 3 rows of text. This should be layouted like Hello World Hi Ho and measured accordingly. However, previously this was layouted as Hello World Hi Ho with 1.5 lines being assigned to both labels. That will obviously not compute the above wrapping which clearly results in a smaller min width. A reftest testing exactly this was included. --- gtk/gtkboxlayout.c | 161 +++++++++++++----- testsuite/reftests/meson.build | 2 + ...apping-labels-where-one-should-wrap.ref.ui | 27 +++ ...2-wrapping-labels-where-one-should-wrap.ui | 30 ++++ 4 files changed, 178 insertions(+), 42 deletions(-) create mode 100644 testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ref.ui create mode 100644 testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ui diff --git a/gtk/gtkboxlayout.c b/gtk/gtkboxlayout.c index 44394059d4..e79c5e202a 100644 --- a/gtk/gtkboxlayout.c +++ b/gtk/gtkboxlayout.c @@ -288,6 +288,64 @@ gtk_box_layout_compute_opposite_size (GtkBoxLayout *self, *natural = largest_nat; } +static int +distribute_remaining_size (GtkRequestedSize *sizes, + gsize n_sizes, + GtkOrientation orientation, + int available, + int min, + int max) +{ + int total_size = 0; + gsize i; + + if (n_sizes == 0) + return available; + + for (i = 0; i < n_sizes; i++) + { + gtk_widget_measure (sizes[i].data, + orientation, + min, + &sizes[i].minimum_size, &sizes[i].natural_size, + NULL, NULL); + total_size += sizes[i].minimum_size; + } + + if (total_size <= available) + return available - total_size; + + /* total_size > available happens when we last ran for values too big, + * rerun for the correct value min == max in that case */ + while (min < max || total_size > available) + { + int test; + + if (max == G_MAXINT) + test = min * 2; + else + test = (min + max) / 2; + + total_size = 0; + for (i = 0; i < n_sizes; i++) + { + gtk_widget_measure (sizes[i].data, + orientation, + test, + &sizes[i].minimum_size, &sizes[i].natural_size, + NULL, NULL); + total_size += sizes[i].minimum_size; + } + + if (total_size > available) + min = test + 1; + else + max = test; + } + + return available - total_size; +} + static void gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, GtkWidget *widget, @@ -305,8 +363,7 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, int computed_minimum_below = 0, computed_natural_below = 0; int computed_minimum_baseline = -1, computed_natural_baseline = -1; GtkRequestedSize *sizes; - int extra_space, size_given_to_child, i; - int children_minimum_size = 0; + int available, size_given_to_child, i; int child_size, child_minimum, child_natural; int child_minimum_baseline, child_natural_baseline; int n_extra_widgets = 0; @@ -321,12 +378,12 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, spacing = get_spacing (self, gtk_widget_get_css_node (widget)); sizes = g_newa (GtkRequestedSize, nvis_children); g_assert ((nvis_children - 1) * spacing <= for_size); - extra_space = for_size - (nvis_children - 1) * spacing; + available = for_size - (nvis_children - 1) * spacing; if (self->homogeneous) { - size_given_to_child = extra_space / nvis_children; - n_extra_widgets = extra_space % nvis_children; + size_given_to_child = available / nvis_children; + n_extra_widgets = available % nvis_children; for (child = _gtk_widget_get_first_child (widget); child != NULL; @@ -364,70 +421,90 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, computed_natural = MAX (computed_natural, child_natural); } } - } else { + int min_size = 0, child_min_size; + int n_inconstant = 0; + /* Retrieve desired size for visible children */ for (i = 0, child = _gtk_widget_get_first_child (widget); child != NULL; child = _gtk_widget_get_next_sibling (child)) { - int min_opposite, nat_for_min; - if (!gtk_widget_should_layout (child)) continue; - gtk_widget_measure (child, - self->orientation, - -1, - &sizes[i].minimum_size, &sizes[i].natural_size, - NULL, NULL); - /* Don't just use the natural size as the max size, - * the natural size is the ideal size, not necessarily - * the maximum size. - * Also check the nat size for opposite min size. - */ - gtk_widget_measure (child, - OPPOSITE_ORIENTATION (self->orientation), - -1, - &min_opposite, NULL, - NULL, NULL); - gtk_widget_measure (child, - self->orientation, - min_opposite, - NULL, &nat_for_min, - NULL, NULL); - sizes[i].natural_size = MAX (sizes[i].natural_size, nat_for_min); - sizes[i].data = child; - - children_minimum_size += sizes[i].minimum_size; - i += 1; + if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_CONSTANT_SIZE) + { + gtk_widget_measure (child, + self->orientation, + -1, + &sizes[i].minimum_size, &sizes[i].natural_size, + NULL, NULL); + sizes[i].data = child; + g_assert (available >= sizes[i].minimum_size); + available -= sizes[i].minimum_size; + i++; + } + else + { + gtk_widget_measure (child, + OPPOSITE_ORIENTATION (self->orientation), + -1, + &child_min_size, NULL, + NULL, NULL); + min_size = MAX (min_size, child_min_size); + n_inconstant++; + sizes[nvis_children - n_inconstant].data = child; + } } + available = distribute_remaining_size (sizes + nvis_children - n_inconstant, + n_inconstant, + self->orientation, + available, + min_size, + G_MAXINT); + /* Bring children up to size first */ - g_assert (children_minimum_size <= extra_space); - extra_space -= children_minimum_size; - extra_space = gtk_distribute_natural_allocation (extra_space, nvis_children, sizes); + available = gtk_distribute_natural_allocation (available, nvis_children, sizes); /* Calculate space which hasn't distributed yet, * and is available for expanding children. */ if (nexpand_children > 0) { - size_given_to_child = extra_space / nexpand_children; - n_extra_widgets = extra_space % nexpand_children; + size_given_to_child = available / nexpand_children; + n_extra_widgets = available % nexpand_children; } else { size_given_to_child = 0; } - for (i = 0; i < nvis_children; i++) + i = 0; + n_inconstant = 0; + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) { - child_size = sizes[i].minimum_size; + if (!gtk_widget_should_layout (child)) + continue; - if (gtk_widget_compute_expand (sizes[i].data, self->orientation)) + if (sizes[i].data == child) + { + child_size = sizes[i].minimum_size; + i++; + } + else + { + n_inconstant++; + g_assert (sizes[nvis_children - n_inconstant].data == child); + child_size = sizes[nvis_children - n_inconstant].minimum_size; + } + + if (gtk_widget_compute_expand (child, self->orientation)) { child_size += size_given_to_child; @@ -440,7 +517,7 @@ gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, child_minimum_baseline = child_natural_baseline = -1; /* Assign the child's position. */ - gtk_widget_measure (sizes[i].data, + gtk_widget_measure (child, OPPOSITE_ORIENTATION (self->orientation), child_size, &child_minimum, &child_natural, diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build index 9f40c0f3a0..83a06c9c80 100644 --- a/testsuite/reftests/meson.build +++ b/testsuite/reftests/meson.build @@ -506,6 +506,8 @@ testdata = [ 'unresolvable.css', 'unresolvable.ref.ui', 'unresolvable.ui', + 'vbox-with-2-wrapping-labels-where-one-should-wrap.ref.ui', + 'vbox-with-2-wrapping-labels-where-one-should-wrap.ui', 'vbox-with-max-width-chars-label.ref.ui', 'vbox-with-max-width-chars-label.ui', 'window-border-width.ref.ui', diff --git a/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ref.ui b/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ref.ui new file mode 100644 index 0000000000..ec575c07d9 --- /dev/null +++ b/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ref.ui @@ -0,0 +1,27 @@ + + + + 0 + + + + + 1 + + + Hello +World + + + + + Hi Ho + + + + + + + + + diff --git a/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ui b/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ui new file mode 100644 index 0000000000..f398308496 --- /dev/null +++ b/testsuite/reftests/vbox-with-2-wrapping-labels-where-one-should-wrap.ui @@ -0,0 +1,30 @@ + + + + 0 + + + + + 1 + + + Hello World + 1 + 1 + + + + + Hi Ho + 1 + 1 + + + + + + + + +