diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 4d8f1eeacb..fa6062a708 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -7184,3 +7184,21 @@ gtk_layout_child_get_child_widget GTK_TYPE_LAYOUT_CHILD gtk_layout_child_get_type + +
+gtkboxlayout +GtkBoxLayout +GtkBoxLayoutClass + +gtk_box_layout_new +gtk_box_layout_set_homogeneous +gtk_box_layout_get_homogeneous +gtk_box_layout_set_spacing +gtk_box_layout_get_spacing +gtk_box_layout_set_baseline_position +gtk_box_layout_get_baseline_position + + +GTK_TYPE_BOX_LAYOUT +gtk_box_layout_get_type +
diff --git a/gtk/gtk.h b/gtk/gtk.h index 9bc2e57423..e7118344ab 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkboxlayout.c b/gtk/gtkboxlayout.c new file mode 100644 index 0000000000..99167163b3 --- /dev/null +++ b/gtk/gtkboxlayout.c @@ -0,0 +1,877 @@ +/* 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 . + */ + +#include "config.h" + +#include "gtkboxlayout.h" + +#include "gtkcsspositionvalueprivate.h" +#include "gtkintl.h" +#include "gtkorientableprivate.h" +#include "gtkprivate.h" +#include "gtksizerequest.h" +#include "gtkstylecontextprivate.h" +#include "gtktypebuiltins.h" +#include "gtkwidgetprivate.h" + +/** + * SECTION:gtkboxlayout + * @Title: GtkBoxLayout + * @Short_description: Layout manager for placing all children in a single row or column + * + * A GtkBoxLayout is a layout manager that arranges the children of any + * widget using it into a single row or column, depending on the value + * of its #GtkOrientable:orientation property. Within the other dimension + * all children all allocated the same size. The GtkBoxLayout will respect + * the #GtkWidget:halign and #GtkWidget:valign properties of each child + * widget. + * + * If you want all children to be assigned the same size, you can use + * the #GtkBoxLayout:homogeneous property. + * + * If you want to specify the amount of space placed between each child, + * you can use the #GtkBoxLayout:spacing property. + */ + +struct _GtkBoxLayout +{ + GtkLayoutManager parent_instance; + + gboolean homogeneous; + guint spacing; + GtkOrientation orientation; + GtkBaselinePosition baseline_position; +}; + +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_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_orientable_set_style_classes (GTK_ORIENTABLE (widget)); + + gtk_layout_manager_layout_changed (layout_manager); +} + +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_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_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, + gint *visible_children, + gint *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_get_visible (child)) + { + *visible_children += 1; + + if (gtk_widget_compute_expand (child, orientation)) + *expand_children += 1; + } + } +} + +static gint +get_spacing (GtkBoxLayout *self, + GtkStyleContext *style_context) +{ + GtkCssValue *border_spacing; + gint css_spacing; + + border_spacing = _gtk_style_context_peek_property (style_context, GTK_CSS_PROPERTY_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 GtkSizeRequestMode +gtk_box_layout_get_request_mode (GtkLayoutManager *layout_manager, + GtkWidget *widget) +{ + GtkBoxLayout *self = GTK_BOX_LAYOUT (layout_manager); + + return self->orientation == GTK_ORIENTATION_HORIZONTAL + ? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT + : GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +gtk_box_layout_compute_size (GtkBoxLayout *self, + GtkWidget *widget, + int for_size, + int *minimum, + int *natural) +{ + 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_style_context (widget)); + + 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; + + if (!_gtk_widget_get_visible (child)) + continue; + + gtk_widget_measure (child, self->orientation, + for_size, + &child_min, &child_nat, + NULL, NULL); + + largest_min = MAX (largest_min, child_min); + largest_nat = MAX (largest_nat, child_nat); + + required_min += child_min; + required_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; + } + + required_min += (n_visible_children - 1) * spacing; + required_nat += (n_visible_children - 1) * spacing; + } + + if (minimum != NULL) + *minimum = required_min; + if (natural != NULL) + *natural = required_nat; +} + +static void +gtk_box_layout_compute_opposite_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 extra_space, size_given_to_child, i; + int children_minimum_size = 0; + int child_size, child_minimum, child_natural; + int child_minimum_baseline, child_natural_baseline; + int n_extra_widgets = 0; + int spacing; + gboolean have_baseline; + + count_expand_children (widget, self->orientation, &nvis_children, &nexpand_children); + + if (nvis_children <= 0) + return; + + spacing = get_spacing (self, _gtk_widget_get_style_context (widget)); + sizes = g_newa (GtkRequestedSize, nvis_children); + extra_space = MAX (0, for_size - (nvis_children - 1) * spacing); + + /* 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_get_visible (child)) + { + gtk_widget_measure (child, + self->orientation, + -1, + &sizes[i].minimum_size, &sizes[i].natural_size, + NULL, NULL); + + children_minimum_size += sizes[i].minimum_size; + i += 1; + } + } + + 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; + } + } + + have_baseline = FALSE; + for (i = 0, child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + /* If widget is not visible, skip it. */ + if (!_gtk_widget_get_visible (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--; + } + } + } + + 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 (child_minimum_baseline >= 0) + { + 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); + } + i += 1; + } + + if (have_baseline) + { + 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; + } + } + + if (minimum != NULL) + *minimum = computed_minimum; + if (natural != NULL) + *natural = MAX (computed_natural, computed_natural_below + computed_natural_above); + + if (min_baseline != NULL) + *min_baseline = computed_minimum_baseline; + if (nat_baseline != NULL) + *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) + { + gtk_box_layout_compute_opposite_size (self, widget, for_size, + minimum, natural, + min_baseline, nat_baseline); + } + else + { + gtk_box_layout_compute_size (self, widget, for_size, + minimum, natural); + } +} + +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; + gint nvis_children; + gint nexpand_children; + GtkTextDirection direction; + GtkAllocation child_allocation; + GtkRequestedSize *sizes; + gint child_minimum_baseline, child_natural_baseline; + gint minimum_above, natural_above; + gint minimum_below, natural_below; + gboolean have_baseline; + gint extra_space; + gint children_minimum_size = 0; + gint size_given_to_child; + gint n_extra_widgets = 0; /* Number of widgets that receive 1 extra px */ + gint x = 0, y = 0, i; + gint child_size; + gint 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_style_context (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_get_visible (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 widget is not visible, skip it. */ + if (!_gtk_widget_get_visible (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) + { + 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 widget is not visible, skip it. */ + if (!_gtk_widget_get_visible (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->get_request_mode = gtk_box_layout_get_request_mode; + layout_manager_class->measure = gtk_box_layout_measure; + layout_manager_class->allocate = gtk_box_layout_allocate; + + /** + * GtkBoxLayout:homogeneous: + * + * Whether the box layout should distribute the available space + * homogeneously among the children of the widget using it as a + * layout manager. + */ + box_layout_props[PROP_HOMOGENEOUS] = + g_param_spec_boolean ("homogeneous", + P_("Homogeneous"), + P_("Distribute space homogeneously"), + FALSE, + GTK_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkBoxLayout:spacing: + * + * The space between each child of the widget using the box + * layout as its layout manager. + */ + box_layout_props[PROP_SPACING] = + g_param_spec_int ("spacing", + P_("Spacing"), + P_("Spacing between widgets"), + 0, G_MAXINT, 0, + GTK_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkBoxLayout:baseline-position: + * + * The position of the allocated baseline within the extra space + * allocated to each child of the widget using a box layout + * manager. + * + * 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", + P_("Baseline position"), + P_("The position of the baseline aligned widgets if extra space is available"), + 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; +} + +GtkLayoutManager * +gtk_box_layout_new (GtkOrientation orientation) +{ + return g_object_new (GTK_TYPE_BOX_LAYOUT, + "orientation", orientation, + NULL); +} + +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]); +} + +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; +} + +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]); +} + +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: + * @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: + * @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; +} diff --git a/gtk/gtkboxlayout.h b/gtk/gtkboxlayout.h new file mode 100644 index 0000000000..8d394219b7 --- /dev/null +++ b/gtk/gtkboxlayout.h @@ -0,0 +1,54 @@ +/* gtkboxlayout.h: 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 . + */ + +#pragma once + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_BOX_LAYOUT (gtk_box_layout_get_type()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkBoxLayout, gtk_box_layout, GTK, BOX_LAYOUT, GtkLayoutManager) + +GDK_AVAILABLE_IN_ALL +GtkLayoutManager * gtk_box_layout_new (GtkOrientation orientation); + +GDK_AVAILABLE_IN_ALL +void gtk_box_layout_set_homogeneous (GtkBoxLayout *box_layout, + gboolean homogeneous); +GDK_AVAILABLE_IN_ALL +gboolean gtk_box_layout_get_homogeneous (GtkBoxLayout *box_layout); +GDK_AVAILABLE_IN_ALL +void gtk_box_layout_set_spacing (GtkBoxLayout *box_layout, + guint spacing); +GDK_AVAILABLE_IN_ALL +guint gtk_box_layout_get_spacing (GtkBoxLayout *box_layout); +GDK_AVAILABLE_IN_ALL +void gtk_box_layout_set_baseline_position (GtkBoxLayout *box_layout, + GtkBaselinePosition position); +GDK_AVAILABLE_IN_ALL +GtkBaselinePosition gtk_box_layout_get_baseline_position (GtkBoxLayout *box_layout); + +G_END_DECLS diff --git a/gtk/meson.build b/gtk/meson.build index 88f78c4f4a..f6d869a6c3 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -165,6 +165,7 @@ gtk_public_sources = files([ 'gtkbin.c', 'gtkbindings.c', 'gtkborder.c', + 'gtkboxlayout.c', 'gtkbox.c', 'gtkbuildable.c', 'gtkbuilder.c',