gtk/gtk/gtkboxlayout.c
António Fernandes 6b59c138b2 boxlayout: Don't always reserve height for baseline alignment
Otherwise an horizontal box may have a larger minumum height than
any of its children even though non of them is baseline-aligned.

Fixes https://gitlab.gnome.org/GNOME/gtk/-/issues/5863
2023-06-02 20:18:56 +01:00

1263 lines
40 KiB
C

/* gtkboxlayout.c: Box layout manager
*
* Copyright 2019 GNOME Foundation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkboxlayout.h"
#include "gtkcsspositionvalueprivate.h"
#include "gtkorientable.h"
#include "gtkprivate.h"
#include "gtksizerequest.h"
#include "gtktypebuiltins.h"
#include "gtkwidgetprivate.h"
#include "gtkcssnodeprivate.h"
/**
* GtkBoxLayout:
*
* `GtkBoxLayout` is a layout manager that arranges children in a single
* row or column.
*
* Whether it is a row or column depends on the value of its
* [property@Gtk.Orientable:orientation] property. Within the other dimension
* all children all allocated the same size. The `GtkBoxLayout` will respect
* the [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign]
* properties of each child widget.
*
* If you want all children to be assigned the same size, you can use
* the [property@Gtk.BoxLayout:homogeneous] property.
*
* If you want to specify the amount of space placed between each child,
* you can use the [property@Gtk.BoxLayout:spacing] property.
*/
struct _GtkBoxLayout
{
GtkLayoutManager parent_instance;
gboolean homogeneous;
guint spacing;
GtkOrientation orientation;
GtkBaselinePosition baseline_position;
int baseline_child;
};
G_DEFINE_TYPE_WITH_CODE (GtkBoxLayout, gtk_box_layout, GTK_TYPE_LAYOUT_MANAGER,
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
enum {
PROP_HOMOGENEOUS = 1,
PROP_SPACING,
PROP_BASELINE_CHILD,
PROP_BASELINE_POSITION,
/* From GtkOrientable */
PROP_ORIENTATION,
N_PROPS = PROP_ORIENTATION
};
static GParamSpec *box_layout_props[N_PROPS];
static void
gtk_box_layout_set_orientation (GtkBoxLayout *self,
GtkOrientation orientation)
{
GtkLayoutManager *layout_manager = GTK_LAYOUT_MANAGER (self);
GtkWidget *widget;
if (self->orientation == orientation)
return;
self->orientation = orientation;
widget = gtk_layout_manager_get_widget (layout_manager);
if (widget != NULL && GTK_IS_ORIENTABLE (widget))
gtk_widget_update_orientation (widget, self->orientation);
gtk_layout_manager_layout_changed (layout_manager);
g_object_notify (G_OBJECT (self), "orientation");
}
static void
gtk_box_layout_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (gobject);
switch (prop_id)
{
case PROP_HOMOGENEOUS:
gtk_box_layout_set_homogeneous (self, g_value_get_boolean (value));
break;
case PROP_SPACING:
gtk_box_layout_set_spacing (self, g_value_get_int (value));
break;
case PROP_BASELINE_CHILD:
gtk_box_layout_set_baseline_child (self, g_value_get_int (value));
break;
case PROP_BASELINE_POSITION:
gtk_box_layout_set_baseline_position (self, g_value_get_enum (value));
break;
case PROP_ORIENTATION:
gtk_box_layout_set_orientation (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
gtk_box_layout_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkBoxLayout *box_layout = GTK_BOX_LAYOUT (gobject);
switch (prop_id)
{
case PROP_HOMOGENEOUS:
g_value_set_boolean (value, box_layout->homogeneous);
break;
case PROP_SPACING:
g_value_set_int (value, box_layout->spacing);
break;
case PROP_BASELINE_CHILD:
g_value_set_int (value, box_layout->baseline_child);
break;
case PROP_BASELINE_POSITION:
g_value_set_enum (value, box_layout->baseline_position);
break;
case PROP_ORIENTATION:
g_value_set_enum (value, box_layout->orientation);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break;
}
}
static void
count_expand_children (GtkWidget *widget,
GtkOrientation orientation,
int *visible_children,
int *expand_children)
{
GtkWidget *child;
*visible_children = *expand_children = 0;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!gtk_widget_should_layout (child))
continue;
*visible_children += 1;
if (gtk_widget_compute_expand (child, orientation))
*expand_children += 1;
}
}
static int
get_spacing (GtkBoxLayout *self,
GtkCssNode *node)
{
GtkCssStyle *style = gtk_css_node_get_style (node);
GtkCssValue *border_spacing;
int css_spacing;
border_spacing = style->size->border_spacing;
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
css_spacing = _gtk_css_position_value_get_x (border_spacing, 100);
else
css_spacing = _gtk_css_position_value_get_y (border_spacing, 100);
return css_spacing + self->spacing;
}
static void
gtk_box_layout_compute_size (GtkBoxLayout *self,
GtkWidget *widget,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkWidget *child;
int n_visible_children = 0;
int required_min = 0, required_nat = 0;
int largest_min = 0, largest_nat = 0;
int spacing = get_spacing (self, gtk_widget_get_css_node (widget));
int child_above_min = 0, child_above_nat = 0;
int above_min = 0, above_nat = 0;
gboolean have_baseline = FALSE;
int pos;
for (child = gtk_widget_get_first_child (widget), pos = 0;
child != NULL;
child = gtk_widget_get_next_sibling (child), pos++)
{
int child_min = 0;
int child_nat = 0;
int child_min_baseline = -1;
int child_nat_baseline = -1;
if (!gtk_widget_should_layout (child))
continue;
gtk_widget_measure (child, self->orientation,
for_size,
&child_min, &child_nat,
&child_min_baseline, &child_nat_baseline);
largest_min = MAX (largest_min, child_min);
largest_nat = MAX (largest_nat, child_nat);
required_min += child_min;
required_nat += child_nat;
if (self->orientation == GTK_ORIENTATION_VERTICAL)
{
if (pos < self->baseline_child)
{
above_min += child_min;
above_nat += child_nat;
}
else if (pos == self->baseline_child)
{
have_baseline = TRUE;
if (child_min_baseline > -1)
{
child_above_min = child_min_baseline;
child_above_nat = child_nat_baseline;
}
else
{
child_above_min = child_min;
child_above_nat = child_nat;
}
}
}
n_visible_children += 1;
}
if (n_visible_children > 0)
{
if (self->homogeneous)
{
required_min = largest_min * n_visible_children;
required_nat = largest_nat * n_visible_children;
above_min = largest_min * MAX (self->baseline_child, 0);
above_nat = largest_nat * MAX (self->baseline_child, 0);
}
required_min += (n_visible_children - 1) * spacing;
required_nat += (n_visible_children - 1) * spacing;
above_min += MAX (self->baseline_child, 0) * spacing;
above_nat += MAX (self->baseline_child, 0) * spacing;
}
*minimum = required_min;
*natural = required_nat;
if (have_baseline)
{
*minimum_baseline = above_min + child_above_min;
*natural_baseline = above_nat + child_above_nat;
}
else
{
*minimum_baseline = -1;
*natural_baseline = -1;
}
}
static void
gtk_box_layout_compute_opposite_size (GtkBoxLayout *self,
GtkWidget *widget,
int *minimum,
int *natural,
int *min_baseline,
int *nat_baseline)
{
GtkWidget *child;
int largest_min = 0, largest_nat = 0;
int largest_min_above = -1, largest_min_below = -1;
int largest_nat_above = -1, largest_nat_below = -1;
gboolean have_baseline = FALSE;
gboolean align_baseline = FALSE;
for (child = gtk_widget_get_first_child (widget);
child != NULL;
child = gtk_widget_get_next_sibling (child))
{
int child_min = 0;
int child_nat = 0;
int child_min_baseline = -1;
int child_nat_baseline = -1;
if (!gtk_widget_should_layout (child))
continue;
gtk_widget_measure (child,
OPPOSITE_ORIENTATION (self->orientation),
-1,
&child_min, &child_nat,
&child_min_baseline, &child_nat_baseline);
largest_min = MAX (largest_min, child_min);
largest_nat = MAX (largest_nat, child_nat);
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
if (child_min_baseline > -1)
{
have_baseline = TRUE;
if (gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE_FILL ||
gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE_CENTER)
align_baseline = TRUE;
largest_min_above = MAX (largest_min_above, child_min_baseline);
largest_min_below = MAX (largest_min_below, child_min - child_min_baseline);
largest_nat_above = MAX (largest_nat_above, child_nat_baseline);
largest_nat_below = MAX (largest_nat_below, child_nat - child_nat_baseline);
}
}
}
if (self->orientation == GTK_ORIENTATION_HORIZONTAL && align_baseline)
{
largest_min = MAX (largest_min, largest_min_above + largest_min_below);
largest_nat = MAX (largest_nat, largest_nat_above + largest_nat_below);
}
*minimum = largest_min;
*natural = largest_nat;
if (have_baseline)
{
*min_baseline = largest_min_above;
*nat_baseline = largest_nat_above;
}
else
{
*min_baseline = -1;
*nat_baseline = -1;
}
}
/* if widgets haven't reached their min opposite size at this
* huge value, things went massively wrong and we need to bail to not
* cause an infinite loop.
*/
#define MAX_ALLOWED_SIZE (1 << 20)
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 (min > MAX_ALLOWED_SIZE)
{
/* sanity check! */
for (i = 0; i < n_sizes; i++)
{
int check_min, check_nat;
gtk_widget_measure (sizes[i].data,
orientation,
MAX_ALLOWED_SIZE,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
gtk_widget_measure (sizes[i].data,
orientation,
-1,
&check_min, &check_nat,
NULL, NULL);
if (check_min < sizes[i].minimum_size)
{
g_critical ("%s %p reports a minimum %s of %u, but minimum %s for %s of %u is %u. Expect overlapping widgets.",
G_OBJECT_TYPE_NAME (sizes[i].data), sizes[i].data,
orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
check_min,
orientation == GTK_ORIENTATION_HORIZONTAL ? "width" : "height",
orientation == GTK_ORIENTATION_HORIZONTAL ? "height" : "width",
MAX_ALLOWED_SIZE, sizes[i].minimum_size);
sizes[i].minimum_size = check_min;
sizes[i].natural_size = check_nat;
}
total_size += sizes[i].minimum_size;
}
return MAX (0, available - total_size);
}
if (max == MAX_ALLOWED_SIZE)
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,
int for_size,
int *minimum,
int *natural,
int *min_baseline,
int *nat_baseline)
{
GtkWidget *child;
int nvis_children;
int nexpand_children;
int computed_minimum = 0, computed_natural = 0;
int computed_minimum_above = 0, computed_natural_above = 0;
int computed_minimum_below = 0, computed_natural_below = 0;
int computed_minimum_baseline = -1, computed_natural_baseline = -1;
GtkRequestedSize *sizes;
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;
int spacing;
gboolean have_baseline = FALSE;
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
if (nvis_children <= 0)
return;
spacing = get_spacing (self, gtk_widget_get_css_node (widget));
sizes = g_newa (GtkRequestedSize, nvis_children);
g_assert ((nvis_children - 1) * spacing <= for_size);
available = for_size - (nvis_children - 1) * spacing;
if (self->homogeneous)
{
size_given_to_child = available / nvis_children;
n_extra_widgets = available % nvis_children;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!gtk_widget_should_layout (child))
continue;
child_size = size_given_to_child;
if (n_extra_widgets)
{
child_size++;
n_extra_widgets--;
}
child_minimum_baseline = child_natural_baseline = -1;
/* Assign the child's position. */
gtk_widget_measure (child,
OPPOSITE_ORIENTATION (self->orientation),
child_size,
&child_minimum, &child_natural,
&child_minimum_baseline, &child_natural_baseline);
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
if (child_minimum_baseline > -1)
{
have_baseline = TRUE;
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
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))
{
if (!gtk_widget_should_layout (child))
continue;
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,
MAX_ALLOWED_SIZE);
/* Bring children up to size first */
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 = available / nexpand_children;
n_extra_widgets = available % nexpand_children;
}
else
{
size_given_to_child = 0;
}
i = 0;
n_inconstant = 0;
for (child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!gtk_widget_should_layout (child))
continue;
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;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
child_minimum_baseline = child_natural_baseline = -1;
/* Assign the child's position. */
gtk_widget_measure (child,
OPPOSITE_ORIENTATION (self->orientation),
child_size,
&child_minimum, &child_natural,
&child_minimum_baseline, &child_natural_baseline);
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
if (child_minimum_baseline > -1)
{
have_baseline = TRUE;
computed_minimum_below = MAX (computed_minimum_below, child_minimum - child_minimum_baseline);
computed_natural_below = MAX (computed_natural_below, child_natural - child_natural_baseline);
computed_minimum_above = MAX (computed_minimum_above, child_minimum_baseline);
computed_natural_above = MAX (computed_natural_above, child_natural_baseline);
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
}
else
{
computed_minimum = MAX (computed_minimum, child_minimum);
computed_natural = MAX (computed_natural, child_natural);
}
}
}
if (have_baseline)
{
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
computed_minimum = MAX (computed_minimum, computed_minimum_below + computed_minimum_above);
computed_natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
switch (self->baseline_position)
{
case GTK_BASELINE_POSITION_TOP:
computed_minimum_baseline = computed_minimum_above;
computed_natural_baseline = computed_natural_above;
break;
case GTK_BASELINE_POSITION_CENTER:
computed_minimum_baseline = computed_minimum_above + MAX((computed_minimum - (computed_minimum_above + computed_minimum_below)) / 2, 0);
computed_natural_baseline = computed_natural_above + MAX((computed_natural - (computed_natural_above + computed_natural_below)) / 2, 0);
break;
case GTK_BASELINE_POSITION_BOTTOM:
computed_minimum_baseline = computed_minimum - computed_minimum_below;
computed_natural_baseline = computed_natural - computed_natural_below;
break;
default:
break;
}
}
}
*minimum = computed_minimum;
*natural = MAX (computed_natural, computed_natural_below + computed_natural_above);
*min_baseline = computed_minimum_baseline;
*nat_baseline = computed_natural_baseline;
}
static void
gtk_box_layout_measure (GtkLayoutManager *layout_manager,
GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *min_baseline,
int *nat_baseline)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
if (self->orientation != orientation)
{
if (for_size < 0)
{
gtk_box_layout_compute_opposite_size (self, widget,
minimum, natural,
min_baseline, nat_baseline);
}
else
{
gtk_box_layout_compute_opposite_size_for_size (self, widget, for_size,
minimum, natural,
min_baseline, nat_baseline);
}
}
else
{
gtk_box_layout_compute_size (self, widget, for_size,
minimum, natural,
min_baseline, nat_baseline);
}
}
static void
gtk_box_layout_allocate (GtkLayoutManager *layout_manager,
GtkWidget *widget,
int width,
int height,
int baseline)
{
GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager);
GtkWidget *child;
int nvis_children;
int nexpand_children;
GtkTextDirection direction;
GtkAllocation child_allocation;
GtkRequestedSize *sizes;
int child_minimum_baseline, child_natural_baseline;
int minimum_above, natural_above;
int minimum_below, natural_below;
gboolean have_baseline;
int extra_space;
int children_minimum_size = 0;
int size_given_to_child;
int n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */
int x = 0, y = 0, i;
int child_size;
int spacing;
count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children);
/* If there is no visible child, simply return. */
if (nvis_children <= 0)
return;
direction = _gtk_widget_get_direction (widget);
sizes = g_newa (GtkRequestedSize, nvis_children);
spacing = get_spacing (self, gtk_widget_get_css_node (widget));
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
extra_space = width - (nvis_children - 1) * spacing;
else
extra_space = height - (nvis_children - 1) * spacing;
have_baseline = FALSE;
minimum_above = natural_above = 0;
minimum_below = natural_below = 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))
{
if (!gtk_widget_should_layout (child))
continue;
gtk_widget_measure (child,
self->orientation,
self->orientation == GTK_ORIENTATION_HORIZONTAL ? height : width,
&sizes[i].minimum_size, &sizes[i].natural_size,
NULL, NULL);
children_minimum_size += sizes[i].minimum_size;
sizes[i].data = child;
i++;
}
if (self->homogeneous)
{
/* We still need to run the above loop to populate the minimum sizes for
* children that aren't going to fill.
*/
size_given_to_child = extra_space / nvis_children;
n_extra_widgets = extra_space % nvis_children;
}
else
{
/* Bring children up to size first */
extra_space -= children_minimum_size;
extra_space = MAX (0, extra_space);
extra_space = gtk_distribute_natural_allocation (extra_space, 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;
}
else
{
size_given_to_child = 0;
}
}
/* Allocate child sizes. */
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!gtk_widget_should_layout (child))
continue;
/* Assign the child's size. */
if (self->homogeneous)
{
child_size = size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
else
{
child_size = sizes[i].minimum_size;
if (gtk_widget_compute_expand (child, self->orientation))
{
child_size += size_given_to_child;
if (n_extra_widgets > 0)
{
child_size++;
n_extra_widgets--;
}
}
}
sizes[i].natural_size = child_size;
if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
(gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE_FILL ||
gtk_widget_get_valign (child) == GTK_ALIGN_BASELINE_CENTER))
{
int child_allocation_width;
int child_minimum_height, child_natural_height;
child_allocation_width = child_size;
child_minimum_baseline = -1;
child_natural_baseline = -1;
gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL,
child_allocation_width,
&child_minimum_height, &child_natural_height,
&child_minimum_baseline, &child_natural_baseline);
if (child_minimum_baseline >= 0)
{
have_baseline = TRUE;
minimum_below = MAX (minimum_below, child_minimum_height - child_minimum_baseline);
natural_below = MAX (natural_below, child_natural_height - child_natural_baseline);
minimum_above = MAX (minimum_above, child_minimum_baseline);
natural_above = MAX (natural_above, child_natural_baseline);
}
}
i++;
}
if (self->orientation == GTK_ORIENTATION_VERTICAL)
baseline = -1;
/* we only calculate our own baseline if we don't get one passed from the parent
* and any of the child widgets explicitly request one */
if (baseline == -1 && have_baseline)
{
/* TODO: This is purely based on the minimum baseline, when things fit we should
use the natural one? */
switch (self->baseline_position)
{
case GTK_BASELINE_POSITION_TOP:
baseline = minimum_above;
break;
case GTK_BASELINE_POSITION_CENTER:
baseline = minimum_above + (height - (minimum_above + minimum_below)) / 2;
break;
case GTK_BASELINE_POSITION_BOTTOM:
baseline = height - minimum_below;
break;
default:
break;
}
}
/* Allocate child positions. */
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.y = 0;
child_allocation.height = height;
x = 0;
}
else
{
child_allocation.x = 0;
child_allocation.width = width;
y = 0;
}
for (i = 0, child = _gtk_widget_get_first_child (widget);
child != NULL;
child = _gtk_widget_get_next_sibling (child))
{
if (!gtk_widget_should_layout (child))
continue;
child_size = sizes[i].natural_size;
/* Assign the child's position. */
if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
{
child_allocation.width = child_size;
child_allocation.x = x;
x += child_size + spacing;
if (direction == GTK_TEXT_DIR_RTL)
child_allocation.x = width - child_allocation.x - child_allocation.width;
}
else /* (self->orientation == GTK_ORIENTATION_VERTICAL) */
{
child_allocation.height = child_size;
child_allocation.y = y;
y += child_size + spacing;
}
gtk_widget_size_allocate (child, &child_allocation, baseline);
i++;
}
}
static void
gtk_box_layout_class_init (GtkBoxLayoutClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkLayoutManagerClass *layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (klass);
gobject_class->set_property = gtk_box_layout_set_property;
gobject_class->get_property = gtk_box_layout_get_property;
layout_manager_class->measure = gtk_box_layout_measure;
layout_manager_class->allocate = gtk_box_layout_allocate;
/**
* GtkBoxLayout:homogeneous: (attributes org.gtk.Property.get=gtk_box_layout_get_homogeneous org.gtk.Property.set=gtk_box_layout_set_homogeneous)
*
* Whether the box layout should distribute the available space
* equally among the children.
*/
box_layout_props[PROP_HOMOGENEOUS] =
g_param_spec_boolean ("homogeneous", NULL, NULL,
FALSE,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkBoxLayout:spacing: (attributes org.gtk.Property.get=gtk_box_layout_get_spacing org.gtk.Property.set=gtk_box_layout_set_spacing)
*
* The space to put between the children.
*/
box_layout_props[PROP_SPACING] =
g_param_spec_int ("spacing", NULL, NULL,
0, G_MAXINT, 0,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkBoxLayout:baseline-child: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_child org.gtk.Property.set=gtk_box_layout_set_baseline_child)
*
* The child that determines the baseline of the box
* in vertical layout.
*
* If the child does baseline positioning, then its baseline
* is lined up with the baseline of the box. If it doesn't, then
* the bottom edge of the child is used.
*
* Since: 4.12
*/
box_layout_props[PROP_BASELINE_CHILD] =
g_param_spec_int ("baseline-child", NULL, NULL,
-1, G_MAXINT, -1,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GtkBoxLayout:baseline-position: (attributes org.gtk.Property.get=gtk_box_layout_get_baseline_position org.gtk.Property.set=gtk_box_layout_set_baseline_position)
*
* The position of the allocated baseline within the extra space
* allocated to each child.
*
* This property is only relevant for horizontal layouts containing
* at least one child with a baseline alignment.
*/
box_layout_props[PROP_BASELINE_POSITION] =
g_param_spec_enum ("baseline-position", NULL, NULL,
GTK_TYPE_BASELINE_POSITION,
GTK_BASELINE_POSITION_CENTER,
GTK_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, box_layout_props);
g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
}
static void
gtk_box_layout_init (GtkBoxLayout *self)
{
self->homogeneous = FALSE;
self->spacing = 0;
self->orientation = GTK_ORIENTATION_HORIZONTAL;
self->baseline_position = GTK_BASELINE_POSITION_CENTER;
self->baseline_child = -1;
}
/**
* gtk_box_layout_new:
* @orientation: the orientation for the new layout
*
* Creates a new `GtkBoxLayout`.
*
* Returns: a new box layout
*/
GtkLayoutManager *
gtk_box_layout_new (GtkOrientation orientation)
{
return g_object_new (GTK_TYPE_BOX_LAYOUT,
"orientation", orientation,
NULL);
}
/**
* gtk_box_layout_set_homogeneous: (attributes org.gtk.Method.set_property=homogeneous)
* @box_layout: a `GtkBoxLayout`
* @homogeneous: %TRUE to set the box layout as homogeneous
*
* Sets whether the box layout will allocate the same
* size to all children.
*/
void
gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout,
gboolean homogeneous)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
homogeneous = !!homogeneous;
if (box_layout->homogeneous == homogeneous)
return;
box_layout->homogeneous = homogeneous;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_HOMOGENEOUS]);
}
/**
* gtk_box_layout_get_homogeneous: (attributes org.gtk.Method.get_property=homogeneous)
* @box_layout: a `GtkBoxLayout`
*
* Returns whether the layout is set to be homogeneous.
*
* Return: %TRUE if the layout is homogeneous
*/
gboolean
gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), FALSE);
return box_layout->homogeneous;
}
/**
* gtk_box_layout_set_spacing: (attributes org.gtk.Method.set_property=spacing)
* @box_layout: a `GtkBoxLayout`
* @spacing: the spacing to apply between children
*
* Sets how much spacing to put between children.
*/
void
gtk_box_layout_set_spacing (GtkBoxLayout *box_layout,
guint spacing)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
if (box_layout->spacing == spacing)
return;
box_layout->spacing = spacing;
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_SPACING]);
}
/**
* gtk_box_layout_get_spacing: (attributes org.gtk.Method.get_property=spacing)
* @box_layout: a `GtkBoxLayout`
*
* Returns the space that @box_layout puts between children.
*
* Returns: the spacing of the layout
*/
guint
gtk_box_layout_get_spacing (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), 0);
return box_layout->spacing;
}
/**
* gtk_box_layout_set_baseline_position: (attributes org.gtk.Method.set_property=baseline-position)
* @box_layout: a `GtkBoxLayout`
* @position: a `GtkBaselinePosition`
*
* Sets the baseline position of a box layout.
*
* The baseline position affects only horizontal boxes with at least one
* baseline aligned child. If there is more vertical space available than
* requested, and the baseline is not allocated by the parent then the
* given @position is used to allocate the baseline within the extra
* space available.
*/
void
gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout,
GtkBaselinePosition position)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
if (box_layout->baseline_position != position)
{
box_layout->baseline_position = position;
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_BASELINE_POSITION]);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
}
}
/**
* gtk_box_layout_get_baseline_position: (attributes org.gtk.Method.get_property=baseline-position)
* @box_layout: a `GtkBoxLayout`
*
* Gets the value set by gtk_box_layout_set_baseline_position().
*
* Returns: the baseline position
*/
GtkBaselinePosition
gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), GTK_BASELINE_POSITION_CENTER);
return box_layout->baseline_position;
}
/**
* gtk_box_layout_set_baseline_child: (attributes org.gtk.Method.set_property=baseline-child)
* @box_layout: a `GtkBoxLayout`
* @child: the child position, or -1
*
* Sets the index of the child that determines the baseline
* in vertical layout.
*
* Since: 4.12
*/
void
gtk_box_layout_set_baseline_child (GtkBoxLayout *box_layout,
int child)
{
g_return_if_fail (GTK_IS_BOX_LAYOUT (box_layout));
g_return_if_fail (child >= -1);
if (box_layout->baseline_child == child)
return;
box_layout->baseline_child = child;
g_object_notify_by_pspec (G_OBJECT (box_layout), box_layout_props[PROP_BASELINE_CHILD]);
gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (box_layout));
}
/**
* gtk_box_layout_get_baseline_child:
* @box_layout: a `GtkBoxLayout`
*
* Gets the value set by gtk_box_layout_set_baseline_child().
*
* Returns: the index of the child that determines the baseline
* in vertical layout, or -1
*
* Since: 4.12
*/
int
gtk_box_layout_get_baseline_child (GtkBoxLayout *box_layout)
{
g_return_val_if_fail (GTK_IS_BOX_LAYOUT (box_layout), -1);
return box_layout->baseline_child;
}