diff options
author | Emmanuele Bassi <ebassi@gnome.org> | 2019-04-09 15:33:52 +0100 |
---|---|---|
committer | Emmanuele Bassi <ebassi@gnome.org> | 2019-06-27 17:38:02 +0100 |
commit | 9756cb9482b5cef1a831edfdae3b2e8bf7270a21 (patch) | |
tree | efcf8e65840383d53f08b6f843301934274b629c | |
parent | 92f93a603bf893b109746852e3d5a017b635e5cd (diff) | |
download | gtk+-9756cb9482b5cef1a831edfdae3b2e8bf7270a21.tar.gz |
Add GtkConstraintLayout
A layout manager using GtkConstraintSolver to measure and allocate
children.
-rw-r--r-- | gtk/gtk.h | 2 | ||||
-rw-r--r-- | gtk/gtkconstraint.c | 583 | ||||
-rw-r--r-- | gtk/gtkconstraint.h | 85 | ||||
-rw-r--r-- | gtk/gtkconstraintlayout.c | 1108 | ||||
-rw-r--r-- | gtk/gtkconstraintlayout.h | 53 | ||||
-rw-r--r-- | gtk/gtkconstraintprivate.h | 62 | ||||
-rw-r--r-- | gtk/gtkconstraintsolver.c | 31 | ||||
-rw-r--r-- | gtk/gtkconstraintsolverprivate.h | 31 | ||||
-rw-r--r-- | gtk/gtkenums.h | 60 | ||||
-rw-r--r-- | gtk/meson.build | 4 |
10 files changed, 1988 insertions, 31 deletions
@@ -82,6 +82,8 @@ #include <gtk/gtkcolorutils.h> #include <gtk/gtkcombobox.h> #include <gtk/gtkcomboboxtext.h> +#include <gtk/gtkconstraintlayout.h> +#include <gtk/gtkconstraint.h> #include <gtk/gtkcontainer.h> #include <gtk/gtkcssprovider.h> #include <gtk/gtkcustomlayout.h> diff --git a/gtk/gtkconstraint.c b/gtk/gtkconstraint.c new file mode 100644 index 0000000000..d0c152b56d --- /dev/null +++ b/gtk/gtkconstraint.c @@ -0,0 +1,583 @@ +/* gtkconstraint.c: Constraint between two widgets + * 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.1 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/>. + * + * Author: Emmanuele Bassi + */ + +/** + * SECTION:gtkconstraint + * @Title: GtkConstraint + * @Short_description: The description of a constraint + * + * #GtkConstraint describes a constraint between an attribute on a widget + * and another attribute on another widget, expressed as a linear equation + * like: + * + * |[ + * target.attr1 = source.attr2 × multiplier + constant + * ]| + * + * Each #GtkConstraint is part of a system that will be solved by a + * #GtkConstraintLayout in order to allocate and position each child widget. + * + * The source and target widgets, as well as their attributes, of a + * #GtkConstraint instance are immutable after creation. + */ + +#include "config.h" + +#include "gtkconstraintprivate.h" +#include "gtkconstraintsolverprivate.h" +#include "gtkintl.h" +#include "gtktypebuiltins.h" +#include "gtkwidget.h" + +enum { + PROP_TARGET_WIDGET = 1, + PROP_TARGET_ATTRIBUTE, + PROP_RELATION, + PROP_SOURCE_WIDGET, + PROP_SOURCE_ATTRIBUTE, + PROP_MULTIPLIER, + PROP_CONSTANT, + PROP_STRENGTH, + + N_PROPERTIES +}; + +static GParamSpec *obj_props[N_PROPERTIES]; + +G_DEFINE_TYPE (GtkConstraint, gtk_constraint, G_TYPE_OBJECT) + +static void +gtk_constraint_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkConstraint *self = GTK_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_TARGET_WIDGET: + self->target_widget = g_value_get_object (value); + break; + + case PROP_TARGET_ATTRIBUTE: + self->target_attribute = g_value_get_enum (value); + break; + + case PROP_RELATION: + self->relation = g_value_get_enum (value); + break; + + case PROP_SOURCE_WIDGET: + self->source_widget = g_value_get_object (value); + break; + + case PROP_SOURCE_ATTRIBUTE: + self->source_attribute = g_value_get_enum (value); + break; + + case PROP_MULTIPLIER: + self->multiplier = g_value_get_double (value); + break; + + case PROP_CONSTANT: + self->constant = g_value_get_double (value); + break; + + case PROP_STRENGTH: + self->strength = g_value_get_int (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +gtk_constraint_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkConstraint *self = GTK_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_TARGET_WIDGET: + g_value_set_object (value, self->target_widget); + break; + + case PROP_TARGET_ATTRIBUTE: + g_value_set_enum (value, self->target_attribute); + break; + + case PROP_RELATION: + g_value_set_enum (value, self->relation); + break; + + case PROP_SOURCE_WIDGET: + g_value_set_object (value, self->source_widget); + break; + + case PROP_SOURCE_ATTRIBUTE: + g_value_set_enum (value, self->source_attribute); + break; + + case PROP_MULTIPLIER: + g_value_set_double (value, self->multiplier); + break; + + case PROP_CONSTANT: + g_value_set_double (value, self->constant); + break; + + case PROP_STRENGTH: + g_value_set_int (value, self->strength); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +gtk_constraint_finalize (GObject *gobject) +{ + GtkConstraint *self = GTK_CONSTRAINT (gobject); + + gtk_constraint_detach (self); + + G_OBJECT_CLASS (gtk_constraint_parent_class)->finalize (gobject); +} + +static void +gtk_constraint_class_init (GtkConstraintClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gtk_constraint_set_property; + gobject_class->get_property = gtk_constraint_get_property; + gobject_class->finalize = gtk_constraint_finalize; + + /** + * GtkConstraint:target-widget: + * + * The target widget of the constraint. + * + * The constraint will set the #GtkConstraint:target-attribute of the + * target widget using the #GtkConstraint:source-attribute of the source + * widget. + */ + obj_props[PROP_TARGET_WIDGET] = + g_param_spec_object ("target-widget", + P_("Target Widget"), + P_("The target widget of the constraint"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:target-attribute: + * + * The attribute of the #GtkConstraint:target-widget set by the constraint. + */ + obj_props[PROP_TARGET_ATTRIBUTE] = + g_param_spec_enum ("target-attribute", + P_("Target Attribute"), + P_("The attribute of the target widget set by the constraint"), + GTK_TYPE_CONSTRAINT_ATTRIBUTE, + GTK_CONSTRAINT_ATTRIBUTE_NONE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:relation: + * + * The relation order between the terms of the constraint. + */ + obj_props[PROP_RELATION] = + g_param_spec_enum ("relation", + P_("Relation"), + P_("The relation between the source and target attributes"), + GTK_TYPE_CONSTRAINT_RELATION, + GTK_CONSTRAINT_RELATION_EQ, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:source-widget: + * + * The source widget of the constraint. + * + * The constraint will set the #GtkConstraint:target-attribute of the + * target widget using the #GtkConstraint:source-attribute of the source + * widget. + */ + obj_props[PROP_SOURCE_WIDGET] = + g_param_spec_object ("source-widget", + P_("Source Widget"), + P_("The source widget of the constraint"), + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:source-attribute: + * + * The attribute of the #GtkConstraint:source-widget read by the constraint. + */ + obj_props[PROP_SOURCE_ATTRIBUTE] = + g_param_spec_enum ("source-attribute", + P_("Source Attribute"), + P_("The attribute of the source widget set by the constraint"), + GTK_TYPE_CONSTRAINT_ATTRIBUTE, + GTK_CONSTRAINT_ATTRIBUTE_NONE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:multiplier: + * + * The multiplication factor to be applied to the + * #GtkConstraint:source-attribue. + */ + obj_props[PROP_MULTIPLIER] = + g_param_spec_double ("multiplier", + P_("Multiplier"), + P_("The multiplication factor to be applied to the source attribute"), + -G_MAXDOUBLE, G_MAXDOUBLE, 1.0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:constant: + * + * The constant value to be added to the #GtkConstraint:source-attribute. + */ + obj_props[PROP_CONSTANT] = + g_param_spec_double ("constant", + P_("Constant"), + P_("The constant to be added to the source attribute"), + -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + /** + * GtkConstraint:strength: + * + * The strength of the constraint. + * + * The strength can be expressed either using one of the symbolic values + * of the #GtkConstraintStrength enumeration, or any positive integer + * value. + */ + obj_props[PROP_STRENGTH] = + g_param_spec_int ("strength", + P_("Strength"), + P_("The strength of the constraint"), + GTK_CONSTRAINT_STRENGTH_WEAK, G_MAXINT, + GTK_CONSTRAINT_STRENGTH_REQUIRED, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, obj_props); +} + +static void +gtk_constraint_init (GtkConstraint *self) +{ + self->multiplier = 1.0; + self->constant = 0.0; + + self->target_attribute = GTK_CONSTRAINT_ATTRIBUTE_NONE; + self->source_attribute = GTK_CONSTRAINT_ATTRIBUTE_NONE; + self->relation = GTK_CONSTRAINT_RELATION_EQ; + self->strength = GTK_CONSTRAINT_STRENGTH_REQUIRED; +} + +/** + * gtk_constraint_new: + * @target_widget: (nullable): a #GtkWidget + * @target_attribute: the attribute of @target_widget to be set + * @relation: the relation equivalence between @target_attribute and @source_attribute + * @source_widget: (nullable): a #GtkWidget + * @source_attribute: the attribute of @source_widget to be read + * @multiplier: a multiplication factor to be applied to @source_attribute + * @constant: a constant factor to be added to @source_attribute + * @strength: the strength of the constraint + * + * Creates a new #GtkConstraint representing a relation between a layout + * attribute on a source #GtkWidget and a layout attribute on a target + * #GtkWidget. + * + * Returns: the newly created #GtkConstraint + */ +GtkConstraint * +gtk_constraint_new (GtkWidget *target_widget, + GtkConstraintAttribute target_attribute, + GtkConstraintRelation relation, + GtkWidget *source_widget, + GtkConstraintAttribute source_attribute, + double multiplier, + double constant, + int strength) +{ + g_return_val_if_fail (target_widget == NULL || GTK_IS_WIDGET (target_widget), NULL); + g_return_val_if_fail (source_widget == NULL || GTK_IS_WIDGET (source_widget), NULL); + + return g_object_new (GTK_TYPE_CONSTRAINT, + "target-widget", target_widget, + "target-attribute", target_attribute, + "relation", relation, + "source-widget", source_widget, + "source-attribute", source_attribute, + "multiplier", multiplier, + "constant", constant, + "strength", strength, + NULL); +} + +/** + * gtk_constraint_new_constant: + * @target_widget: (nullable): a #GtkWidget + * @target_attribute: the attribute of @target_widget to be set + * @relation: the relation equivalence between @target_attribute and @constant + * @constant: a constant factor to be set on @target_attribute + * @strength: the strength of the constraint + * + * Creates a new #GtkConstraint representing a relation between a layout + * attribute on a target #GtkWidget and a constant value. + * + * Returns: the newly created #GtkConstraint + */ +GtkConstraint * +gtk_constraint_new_constant (GtkWidget *target_widget, + GtkConstraintAttribute target_attribute, + GtkConstraintRelation relation, + double constant, + int strength) +{ + g_return_val_if_fail (target_widget == NULL || GTK_IS_WIDGET (target_widget), NULL); + + return g_object_new (GTK_TYPE_CONSTRAINT, + "target-widget", target_widget, + "target-attribute", target_attribute, + "relation", relation, + "source-attribute", GTK_CONSTRAINT_ATTRIBUTE_NONE, + "constant", constant, + "strength", strength, + NULL); +} + +/** + * gtk_constraint_get_target_widget: + * @constraint: a #GtkConstraint + * + * Retrieves the target widget for the @constraint. + * + * Returns: (transfer none) (nullable): a #GtkWidget + */ +GtkWidget * +gtk_constraint_get_target_widget (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), NULL); + + return constraint->target_widget; +} + +GtkConstraintAttribute +gtk_constraint_get_target_attribute (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_ATTRIBUTE_NONE); + + return constraint->target_attribute; +} + +/** + * gtk_constraint_get_source_widget: + * @constraint: a #GtkConstraint + * + * Retrieves the source widget for the @constraint. + * + * Returns: (transfer none) (nullable): a #GtkWidget + */ +GtkWidget * +gtk_constraint_get_source_widget (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), NULL); + + return constraint->source_widget; +} + +GtkConstraintAttribute +gtk_constraint_get_source_attribute (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_ATTRIBUTE_NONE); + + return constraint->source_attribute; +} + +GtkConstraintRelation +gtk_constraint_get_relation (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_RELATION_EQ); + + return constraint->relation; +} + +double +gtk_constraint_get_multiplier (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), 1.0); + + return constraint->multiplier; +} + +double +gtk_constraint_get_constant (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), 0.0); + + return constraint->constant; +} + +int +gtk_constraint_get_strength (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_STRENGTH_REQUIRED); + + return constraint->strength; +} + +/*< private > + * gtk_constraint_get_weight: + * @constraint: a #GtkConstraint + * + * Computes the weight of the @constraint to be used with + * #GtkConstraintSolver. + * + * Returns: the weight of the constraint + */ +double +gtk_constraint_get_weight (GtkConstraint *constraint) +{ + if (constraint->strength > 0) + return constraint->strength; + + switch (constraint->strength) + { + case GTK_CONSTRAINT_STRENGTH_REQUIRED: + return GTK_CONSTRAINT_WEIGHT_REQUIRED; + + case GTK_CONSTRAINT_STRENGTH_STRONG: + return GTK_CONSTRAINT_WEIGHT_STRONG; + + case GTK_CONSTRAINT_STRENGTH_MEDIUM: + return GTK_CONSTRAINT_WEIGHT_MEDIUM; + + case GTK_CONSTRAINT_STRENGTH_WEAK: + return GTK_CONSTRAINT_WEIGHT_WEAK; + + default: + g_assert_not_reached (); + } + + return 0; +} + +/** + * gtk_constraint_is_required: + * @constraint: a #GtkConstraint + * + * Checks whether the @constraint is a required relation for solving the + * constraint layout. + * + * Returns: %TRUE if the constraint is required + */ +gboolean +gtk_constraint_is_required (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), FALSE); + + return constraint->strength == GTK_CONSTRAINT_STRENGTH_REQUIRED; +} + +/** + * gtk_constraint_is_attached: + * @constraint: a #GtkConstraint + * + * Checks whether the @constraint is attached to a #GtkConstraintLayout, + * and it is contributing to the layout. + * + * Returns: %TRUE if the constraint is attached + */ +gboolean +gtk_constraint_is_attached (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), FALSE); + + return constraint->constraint_ref != NULL; +} + +/** + * gtk_constraint_is_constant: + * @constraint: a #GtkConstraint + * + * Checks whether the @constraint describes a relation between an attribute + * on the #GtkConstraint:target-widget and a constant value. + * + * Returns: %TRUE if the constraint is a constant relation + */ +gboolean +gtk_constraint_is_constant (GtkConstraint *constraint) +{ + g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), FALSE); + + return constraint->source_widget == NULL && + constraint->source_attribute == GTK_CONSTRAINT_ATTRIBUTE_NONE; +} + +void +gtk_constraint_attach (GtkConstraint *constraint, + GtkConstraintSolver *solver, + GtkConstraintRef *ref) +{ + g_return_if_fail (GTK_IS_CONSTRAINT (constraint)); + g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver)); + g_return_if_fail (ref != NULL); + + constraint->constraint_ref = ref; + constraint->solver = solver; +} + +void +gtk_constraint_detach (GtkConstraint *constraint) +{ + g_return_if_fail (GTK_IS_CONSTRAINT (constraint)); + + if (constraint->constraint_ref == NULL) + return; + + gtk_constraint_solver_remove_constraint (constraint->solver, constraint->constraint_ref); + constraint->constraint_ref = NULL; + constraint->solver = NULL; +} diff --git a/gtk/gtkconstraint.h b/gtk/gtkconstraint.h new file mode 100644 index 0000000000..1ca9fd7f5e --- /dev/null +++ b/gtk/gtkconstraint.h @@ -0,0 +1,85 @@ +/* gtkconstraint.h: Constraint between two widgets + * 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.1 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/>. + * + * Author: Emmanuele Bassi + */ + +#pragma once + +#include <gtk/gtktypes.h> +#include <gtk/gtkenums.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CONSTRAINT (gtk_constraint_get_type ()) + +/** + * GtkConstraint: + * + * An object describing the relation between two widget attributes. + * + * All relations are in the form: + * + * |[<!-- language=plain --> + * target.attr_name = source.attr_name × multiplier + constant + * ]| + * + * A #GtkConstraint is immutable once it's created. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkConstraint, gtk_constraint, GTK, CONSTRAINT, GObject) + +GDK_AVAILABLE_IN_ALL +GtkConstraint * gtk_constraint_new (GtkWidget *target_widget, + GtkConstraintAttribute target_attribute, + GtkConstraintRelation relation, + GtkWidget *source_widget, + GtkConstraintAttribute source_attribute, + double multiplier, + double constant, + int strength); +GDK_AVAILABLE_IN_ALL +GtkConstraint * gtk_constraint_new_constant (GtkWidget *target_widget, + GtkConstraintAttribute target_attribute, + GtkConstraintRelation relation, + double constant, + int strength); + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_constraint_get_target_widget (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +GtkConstraintAttribute gtk_constraint_get_target_attribute (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_constraint_get_source_widget (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +GtkConstraintAttribute gtk_constraint_get_source_attribute (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +GtkConstraintRelation gtk_constraint_get_relation (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +double gtk_constraint_get_multiplier (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +double gtk_constraint_get_constant (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +int gtk_constraint_get_strength (GtkConstraint *constraint); + +GDK_AVAILABLE_IN_ALL +gboolean gtk_constraint_is_required (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +gboolean gtk_constraint_is_attached (GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +gboolean gtk_constraint_is_constant (GtkConstraint *constraint); + +G_END_DECLS diff --git a/gtk/gtkconstraintlayout.c b/gtk/gtkconstraintlayout.c new file mode 100644 index 0000000000..6cd6a7c70e --- /dev/null +++ b/gtk/gtkconstraintlayout.c @@ -0,0 +1,1108 @@ +/* gtkconstraintlayout.c: Layout manager using constraints + * 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.1 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/>. + * + * Author: Emmanuele Bassi + */ + +/** + * SECTION: gtkconstraintlayout + * @Title: GtkConstraintLayout + * @Short_description: A layout manager using constraints + * + * GtkConstraintLayout is a layout manager that uses relations between + * widget attributes, expressed via #GtkConstraint instances, to measure + * and allocate widgets. + * + * # How do constraints work + * + * Constraints are objects defining the relationship between attributes + * of a widget; you can read the description of the #GtkConstraint + * class to have a more in depth definition. + * + * By taking multiple constraints and applying them to the children of + * a widget using #GtkConstraintLayout, it's possible to describe complex + * layout policies; each constraint applied to a child or to the parent + * widgets contributes to the full description of the layout, in terms of + * parameters for resolving the value of each attribute. + * + * It is important to note that a layout is defined by the totality of + * constraints; removing a child, or a constraint, from an existing layout + * without changing the remaining constraints may result in an unstable + * or unsolvable layout. + * + * Constraints have an implicit "reading order"; you should start describing + * each edge of each child, as well as their relationship with the parent + * container, from the top left (or top right, in RTL languages), horizontally + * first, and then vertically. + * + * A constraint-based layout with too few constraints can become "unstable", + * that is: have more than one solution. The behavior of an unstable layout + * is undefined. + * + * A constraint-based layout with conflicting constraints may be unsolvable, + * and lead to an unstable layout. + * + */ + +#include "config.h" + +#include "gtkconstraintlayout.h" + +#include "gtkconstraintprivate.h" +#include "gtkconstraintexpressionprivate.h" +#include "gtkconstraintsolverprivate.h" +#include "gtklayoutchild.h" + +#include "gtkdebug.h" +#include "gtkintl.h" +#include "gtkprivate.h" +#include "gtksizerequest.h" +#include "gtkwidgetprivate.h" + +struct _GtkConstraintLayoutChild +{ + GtkLayoutChild parent_instance; + + /* HashTable<static string, Variable>; a hash table of variables, + * one for each attribute; we use these to query and suggest the + * values for the solver. The string is static and does not need + * to be freed. + */ + GHashTable *bound_attributes; + + /* Internal constraints on minimum and natural sizes */ + GtkConstraintRef *width_constraint[2]; + GtkConstraintRef *height_constraint[2]; +}; + +struct _GtkConstraintLayout +{ + GtkLayoutManager parent_instance; + + /* A pointer to the GtkConstraintSolver used by the layout manager; + * we acquire one when the layout manager gets rooted, and release + * it when it gets unrooted. + */ + GtkConstraintSolver *solver; + + /* HashTable<static string, Variable>; a hash table of variables, + * one for each attribute; we use these to query and suggest the + * values for the solver. The string is static and does not need + * to be freed. + */ + GHashTable *bound_attributes; + + /* HashSet<GtkConstraint>; the set of constraints on the + * parent widget, using the public API objects. + */ + GHashTable *constraints; +}; + +G_DEFINE_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK_TYPE_LAYOUT_CHILD) + +static inline GtkConstraintSolver * +gtk_constraint_layout_get_solver (GtkConstraintLayout *self) +{ + GtkWidget *widget; + GtkRoot *root; + + if (self->solver != NULL) + return self->solver; + + widget = gtk_layout_manager_get_widget (GTK_LAYOUT_MANAGER (self)); + if (widget == NULL) + return NULL; + + root = gtk_widget_get_root (widget); + if (root == NULL) + return NULL; + + self->solver = gtk_root_get_constraint_solver (root); + + return self->solver; +} + +static const char * const attribute_names[] = { + [GTK_CONSTRAINT_ATTRIBUTE_NONE] = "none", + [GTK_CONSTRAINT_ATTRIBUTE_LEFT] = "left", + [GTK_CONSTRAINT_ATTRIBUTE_RIGHT] = "right", + [GTK_CONSTRAINT_ATTRIBUTE_TOP] = "top", + [GTK_CONSTRAINT_ATTRIBUTE_BOTTOM] = "bottom", + [GTK_CONSTRAINT_ATTRIBUTE_START] = "start", + [GTK_CONSTRAINT_ATTRIBUTE_END] = "end", + [GTK_CONSTRAINT_ATTRIBUTE_WIDTH] = "width", + [GTK_CONSTRAINT_ATTRIBUTE_HEIGHT] = "height", + [GTK_CONSTRAINT_ATTRIBUTE_CENTER_X] = "center-x", + [GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y] = "center-y", + [GTK_CONSTRAINT_ATTRIBUTE_BASELINE] = "baseline", +}; + +G_GNUC_PURE +static const char * +get_attribute_name (GtkConstraintAttribute attr) +{ + return attribute_names[attr]; +} + +static GtkConstraintVariable * +get_child_attribute (GtkConstraintLayoutChild *self, + GtkConstraintSolver *solver, + GtkWidget *widget, + GtkConstraintAttribute attr) +{ + GtkTextDirection text_dir; + const char *attr_name; + GtkConstraintVariable *res; + + g_assert (attr != GTK_CONSTRAINT_ATTRIBUTE_NONE); + + /* Resolve the start/end attributes depending on the layout's text direction */ + if (attr == GTK_CONSTRAINT_ATTRIBUTE_START) + { + text_dir = gtk_widget_get_direction (widget); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT; + else + attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT; + } + else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END) + { + text_dir = gtk_widget_get_direction (widget); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT; + else + attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT; + } + + attr_name = get_attribute_name (attr); + res = g_hash_table_lookup (self->bound_attributes, attr_name); + if (res != NULL) + return res; + + res = gtk_constraint_solver_create_variable (solver, + gtk_widget_get_name (widget), + attr_name, + 0.0); + g_hash_table_insert (self->bound_attributes, (gpointer) attr_name, res); + + /* Some attributes are really constraints computed from other + * attributes, to avoid creating additional constraints from + * the user's perspective + */ + switch (attr) + { + /* right = left + width */ + case GTK_CONSTRAINT_ATTRIBUTE_RIGHT: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *left, *width; + GtkConstraintExpression *expr; + + left = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + width = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + gtk_constraint_expression_builder_init (&builder, solver); + gtk_constraint_expression_builder_term (&builder, left); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, width); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_MEDIUM); + } + break; + + /* bottom = top + height */ + case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *top, *height; + GtkConstraintExpression *expr; + + top = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP); + height = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + gtk_constraint_expression_builder_init (&builder, solver); + gtk_constraint_expression_builder_term (&builder, top); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, height); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_MEDIUM); + } + break; + + /* centerX = (width / 2.0) + left*/ + case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *left, *width; + GtkConstraintExpression *expr; + + left = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + width = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + gtk_constraint_expression_builder_init (&builder, solver); + gtk_constraint_expression_builder_term (&builder, width); + gtk_constraint_expression_builder_divide_by (&builder); + gtk_constraint_expression_builder_constant (&builder, 2.0); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, left); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* centerY = (height / 2.0) + top */ + case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *top, *height; + GtkConstraintExpression *expr; + + top = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP); + height = get_child_attribute (self, solver, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + gtk_constraint_expression_builder_init (&builder, solver); + gtk_constraint_expression_builder_term (&builder, height); + gtk_constraint_expression_builder_divide_by (&builder); + gtk_constraint_expression_builder_constant (&builder, 2.0); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, top); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* We do not allow negative sizes */ + case GTK_CONSTRAINT_ATTRIBUTE_WIDTH: + case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT: + { + GtkConstraintExpression *expr; + + expr = gtk_constraint_expression_new (0.0); + gtk_constraint_solver_add_constraint (solver, + res, GTK_CONSTRAINT_RELATION_GE, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* These are "pure" attributes */ + case GTK_CONSTRAINT_ATTRIBUTE_NONE: + case GTK_CONSTRAINT_ATTRIBUTE_LEFT: + case GTK_CONSTRAINT_ATTRIBUTE_TOP: + case GTK_CONSTRAINT_ATTRIBUTE_BASELINE: + break; + + /* These attributes must have been resolved to their real names */ + case GTK_CONSTRAINT_ATTRIBUTE_START: + case GTK_CONSTRAINT_ATTRIBUTE_END: + g_assert_not_reached (); + break; + + default: + break; + } + + return res; +} + +static void +gtk_constraint_layout_child_finalize (GObject *gobject) +{ + GtkConstraintLayoutChild *self = GTK_CONSTRAINT_LAYOUT_CHILD (gobject); + GtkLayoutManager *manager; + GtkConstraintSolver *solver; + + manager = gtk_layout_child_get_layout_manager (GTK_LAYOUT_CHILD (self)); + solver = gtk_constraint_layout_get_solver (GTK_CONSTRAINT_LAYOUT (manager)); + if (solver != NULL) + { + if (self->width_constraint[0] != NULL) + gtk_constraint_solver_remove_constraint (solver, self->width_constraint[0]); + if (self->width_constraint[1] != NULL) + gtk_constraint_solver_remove_constraint (solver, self->width_constraint[1]); + if (self->height_constraint[0] != NULL) + gtk_constraint_solver_remove_constraint (solver, self->height_constraint[0]); + if (self->height_constraint[1] != NULL) + gtk_constraint_solver_remove_constraint (solver, self->height_constraint[1]); + } + + g_clear_pointer (&self->bound_attributes, g_hash_table_unref); + + G_OBJECT_CLASS (gtk_constraint_layout_child_parent_class)->finalize (gobject); +} + +static void +gtk_constraint_layout_child_class_init (GtkConstraintLayoutChildClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gtk_constraint_layout_child_finalize; +} + +static void +gtk_constraint_layout_child_init (GtkConstraintLayoutChild *self) +{ + self->bound_attributes = + g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) gtk_constraint_variable_unref); +} + +G_DEFINE_TYPE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER) + +static void +gtk_constraint_layout_finalize (GObject *gobject) +{ + GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (gobject); + + g_clear_pointer (&self->bound_attributes, g_hash_table_unref); + g_clear_pointer (&self->constraints, g_hash_table_unref); + + G_OBJECT_CLASS (gtk_constraint_layout_parent_class)->finalize (gobject); +} + +static GtkConstraintVariable * +get_layout_attribute (GtkConstraintLayout *self, + GtkWidget *widget, + GtkConstraintAttribute attr) +{ + GtkTextDirection text_dir; + const char *attr_name; + GtkConstraintVariable *res; + + /* Resolve the start/end attributes depending on the layout's text direction */ + if (attr == GTK_CONSTRAINT_ATTRIBUTE_START) + { + text_dir = gtk_widget_get_direction (widget); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT; + else + attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT; + } + else if (attr == GTK_CONSTRAINT_ATTRIBUTE_END) + { + text_dir = gtk_widget_get_direction (widget); + if (text_dir == GTK_TEXT_DIR_RTL) + attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT; + else + attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT; + } + + attr_name = get_attribute_name (attr); + res = g_hash_table_lookup (self->bound_attributes, attr_name); + if (res != NULL) + return res; + + res = gtk_constraint_solver_create_variable (self->solver, "super", attr_name, 0.0); + g_hash_table_insert (self->bound_attributes, (gpointer) attr_name, res); + + /* Some attributes are really constraints computed from other + * attributes, to avoid creating additional constraints from + * the user's perspective + */ + switch (attr) + { + /* right = left + width */ + case GTK_CONSTRAINT_ATTRIBUTE_RIGHT: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *left, *width; + GtkConstraintExpression *expr; + + left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + gtk_constraint_expression_builder_init (&builder, self->solver); + gtk_constraint_expression_builder_term (&builder, left); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, width); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (self->solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* bottom = top + height */ + case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *top, *height; + GtkConstraintExpression *expr; + + top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP); + height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + gtk_constraint_expression_builder_init (&builder, self->solver); + gtk_constraint_expression_builder_term (&builder, top); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, height); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (self->solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* centerX = left + (width / 2.0) */ + case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *left, *width; + GtkConstraintExpression *expr; + + left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + gtk_constraint_expression_builder_init (&builder, self->solver); + gtk_constraint_expression_builder_term (&builder, width); + gtk_constraint_expression_builder_divide_by (&builder); + gtk_constraint_expression_builder_constant (&builder, 2.0); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, left); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (self->solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* centerY = top + (height / 2.0) */ + case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y: + { + GtkConstraintExpressionBuilder builder; + GtkConstraintVariable *top, *height; + GtkConstraintExpression *expr; + + top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP); + height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + gtk_constraint_expression_builder_init (&builder, self->solver); + gtk_constraint_expression_builder_term (&builder, height); + gtk_constraint_expression_builder_divide_by (&builder); + gtk_constraint_expression_builder_constant (&builder, 2.0); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, top); + expr = gtk_constraint_expression_builder_finish (&builder); + + gtk_constraint_solver_add_constraint (self->solver, + res, GTK_CONSTRAINT_RELATION_EQ, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* We do not allow negative sizes */ + case GTK_CONSTRAINT_ATTRIBUTE_WIDTH: + case GTK_CONSTRAINT_ATTRIBUTE_HEIGHT: + { + GtkConstraintExpression *expr; + + expr = gtk_constraint_expression_new (0.0); + gtk_constraint_solver_add_constraint (self->solver, + res, GTK_CONSTRAINT_RELATION_GE, expr, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + } + break; + + /* These are "pure" attributes */ + case GTK_CONSTRAINT_ATTRIBUTE_NONE: + case GTK_CONSTRAINT_ATTRIBUTE_LEFT: + case GTK_CONSTRAINT_ATTRIBUTE_TOP: + case GTK_CONSTRAINT_ATTRIBUTE_BASELINE: + break; + + /* These attributes must have been resolved to their real names */ + case GTK_CONSTRAINT_ATTRIBUTE_START: + case GTK_CONSTRAINT_ATTRIBUTE_END: + g_assert_not_reached (); + break; + + default: + break; + } + + return res; +} + +/*< private > + * layout_add_constraint: + * @self: a #GtkConstraintLayout + * @constraint: a #GtkConstraint + * + * Turns a #GtkConstraint into a #GtkConstraintRef inside the + * constraint solver associated to @self. + * + * If @self does not have a #GtkConstraintSolver, because it + * has not been rooted yet, we just store the @constraint instance, + * and we're going to call this function when the layout manager + * gets rooted. + */ +static void +layout_add_constraint (GtkConstraintLayout *self, + GtkConstraint *constraint) +{ + GtkConstraintVariable *target_attr, *source_attr; + GtkConstraintExpressionBuilder builder; + GtkConstraintExpression *expr; + GtkConstraintSolver *solver; + GtkConstraintAttribute attr; + GtkWidget *target_widget, *source_widget; + GtkWidget *layout_widget; + + if (gtk_constraint_is_attached (constraint)) + return; + + /* Once we pass the preconditions, we check if we can turn a GtkConstraint + * into a GtkConstraintRef; if we can't, we keep a reference to the + * constraint object and try later on + */ + layout_widget = gtk_layout_manager_get_widget (GTK_LAYOUT_MANAGER (self)); + if (layout_widget == NULL) + return; + + solver = gtk_constraint_layout_get_solver (self); + if (solver == NULL) + return; + + attr = gtk_constraint_get_target_attribute (constraint); + target_widget = gtk_constraint_get_target_widget (constraint); + if (target_widget == NULL || target_widget == layout_widget) + { + /* A NULL target widget is assumed to be referring to the layout itself */ + target_attr = get_layout_attribute (self, layout_widget, attr); + } + else if (gtk_widget_get_parent (target_widget) == layout_widget) + { + GtkLayoutChild *child_info; + + child_info = gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (self), target_widget); + target_attr = get_child_attribute (GTK_CONSTRAINT_LAYOUT_CHILD (child_info), + solver, + target_widget, + attr); + } + else + { + g_critical ("Unknown target widget '%s'", gtk_widget_get_name (target_widget)); + target_attr = NULL; + } + + if (target_attr == NULL) + return; + + attr = gtk_constraint_get_source_attribute (constraint); + source_widget = gtk_constraint_get_source_widget (constraint); + + /* The constraint is a constant */ + if (attr == GTK_CONSTRAINT_ATTRIBUTE_NONE) + { + source_attr = NULL; + } + else + { + if (source_widget == NULL || source_widget == layout_widget) + { + source_attr = get_layout_attribute (self, layout_widget, attr); + } + else if (gtk_widget_get_parent (source_widget) == layout_widget) + { + GtkLayoutChild *child_info; + + child_info = gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (self), source_widget); + source_attr = get_child_attribute (GTK_CONSTRAINT_LAYOUT_CHILD (child_info), + solver, + source_widget, + attr); + } + else + { + g_critical ("Unknown source widget '%s'", gtk_widget_get_name (source_widget)); + source_attr = NULL; + return; + } + } + + /* Build the expression */ + gtk_constraint_expression_builder_init (&builder, self->solver); + + if (source_attr != NULL) + { + gtk_constraint_expression_builder_term (&builder, source_attr); + gtk_constraint_expression_builder_multiply_by (&builder); + gtk_constraint_expression_builder_constant (&builder, gtk_constraint_get_multiplier (constraint)); + gtk_constraint_expression_builder_plus (&builder); + } + + gtk_constraint_expression_builder_constant (&builder, gtk_constraint_get_constant (constraint)); + expr = gtk_constraint_expression_builder_finish (&builder); + + constraint->solver = solver; + constraint->constraint_ref = + gtk_constraint_solver_add_constraint (self->solver, + target_attr, + gtk_constraint_get_relation (constraint), + expr, + gtk_constraint_get_weight (constraint)); +} + +static void +gtk_constraint_layout_measure (GtkLayoutManager *manager, + GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager); + GtkConstraintVariable *size, *opposite_size; + GtkConstraintSolver *solver; + GtkWidget *child; + int value; + + solver = gtk_constraint_layout_get_solver (self); + if (solver == NULL) + return; + + /* We measure each child in the layout and impose restrictions on the + * minimum and natural size, so we can solve the size of the overall + * layout later on + */ + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + GtkConstraintLayoutChild *child_info; + GtkConstraintVariable *width_var, *height_var; + int min_size = 0, nat_size = 0; + + if (!gtk_widget_should_layout (child)) + continue; + + child_info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child)); + + gtk_widget_measure (child, orientation, for_size, + &min_size, &nat_size, + NULL, NULL); + + if (child_info->width_constraint[0] != NULL) + { + gtk_constraint_solver_remove_constraint (solver, child_info->width_constraint[0]); + gtk_constraint_solver_remove_constraint (solver, child_info->width_constraint[1]); + } + + if (child_info->height_constraint[0] != NULL) + { + gtk_constraint_solver_remove_constraint (solver, child_info->height_constraint[0]); + gtk_constraint_solver_remove_constraint (solver, child_info->height_constraint[1]); + } + + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + width_var = get_child_attribute (child_info, solver, child, + GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + child_info->width_constraint[0] = + gtk_constraint_solver_add_constraint (solver, + width_var, + GTK_CONSTRAINT_RELATION_GE, + gtk_constraint_expression_new (min_size), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + child_info->width_constraint[1] = + gtk_constraint_solver_add_constraint (solver, + width_var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new (nat_size), + GTK_CONSTRAINT_WEIGHT_MEDIUM); + break; + + case GTK_ORIENTATION_VERTICAL: + height_var = get_child_attribute (child_info, solver, child, + GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + child_info->height_constraint[0] = + gtk_constraint_solver_add_constraint (solver, + height_var, + GTK_CONSTRAINT_RELATION_GE, + gtk_constraint_expression_new (min_size), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + child_info->height_constraint[1] = + gtk_constraint_solver_add_constraint (solver, + height_var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new (nat_size), + GTK_CONSTRAINT_WEIGHT_MEDIUM); + break; + + default: + break; + } + } + + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + opposite_size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + break; + + case GTK_ORIENTATION_VERTICAL: + size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + opposite_size = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + break; + + default: + g_assert_not_reached (); + } + + g_assert (size != NULL && opposite_size != NULL); + + /* We impose a temporary value on the size and opposite size of the + * layout, with a low weight to let the solver settle towards the + * natural state of the system. Once we get the value out, we can + * remove these constraints + */ + gtk_constraint_solver_add_edit_variable (solver, size, GTK_CONSTRAINT_WEIGHT_WEAK + 1); + gtk_constraint_solver_add_edit_variable (solver, opposite_size, GTK_CONSTRAINT_WEIGHT_WEAK + 2); + + gtk_constraint_solver_begin_edit (solver); + + gtk_constraint_solver_suggest_value (solver, size, 0.0); + gtk_constraint_solver_suggest_value (solver, opposite_size, for_size >= 0 ? for_size : 0.0); + + gtk_constraint_solver_resolve (solver); + + GTK_NOTE (LAYOUT, + g_print ("layout %p preferred %s size: %.3f (for opposite size: %d)\n", + self, + orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical", + gtk_constraint_variable_get_value (size), + for_size)); + + value = gtk_constraint_variable_get_value (size); + + gtk_constraint_solver_remove_edit_variable (solver, size); + gtk_constraint_solver_remove_edit_variable (solver, opposite_size); + + gtk_constraint_solver_end_edit (solver); + + if (minimum != NULL) + *minimum = value; + + if (natural != NULL) + *natural = value; +} + +static void +gtk_constraint_layout_allocate (GtkLayoutManager *manager, + GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager); + GtkConstraintRef *stay_w, *stay_h, *stay_t, *stay_l; + GtkConstraintSolver *solver; + GtkConstraintVariable *layout_top, *layout_height; + GtkConstraintVariable *layout_left, *layout_width; + GtkWidget *child; + + solver = gtk_constraint_layout_get_solver (self); + if (solver == NULL) + return; + + /* We add required stay constraints to ensure that the layout remains + * within the bounds of the allocation + */ + layout_top = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_TOP); + layout_left = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + layout_width = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + layout_height = get_layout_attribute (self, widget, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + gtk_constraint_variable_set_value (layout_top, 0.0); + stay_t = gtk_constraint_solver_add_stay_variable (solver, + layout_top, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + gtk_constraint_variable_set_value (layout_left, 0.0); + stay_l = gtk_constraint_solver_add_stay_variable (solver, + layout_left, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + gtk_constraint_variable_set_value (layout_width, width); + stay_w = gtk_constraint_solver_add_stay_variable (solver, + layout_width, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + gtk_constraint_variable_set_value (layout_height, height); + stay_h = gtk_constraint_solver_add_stay_variable (solver, + layout_height, + GTK_CONSTRAINT_WEIGHT_REQUIRED); + GTK_NOTE (LAYOUT, + g_print ("Layout [%p]: { .x: %g, .y: %g, .w: %g, .h: %g }\n", + self, + gtk_constraint_variable_get_value (layout_left), + gtk_constraint_variable_get_value (layout_top), + gtk_constraint_variable_get_value (layout_width), + gtk_constraint_variable_get_value (layout_height))); + + /* We reset the constraints on the size of each child, so we are sure the + * layout is up to date + */ + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + GtkConstraintLayoutChild *child_info; + GtkConstraintVariable *width_var, *height_var; + GtkRequisition min_req, nat_req; + + if (!gtk_widget_should_layout (child)) + continue; + + child_info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child)); + + gtk_widget_get_preferred_size (child, &min_req, &nat_req); + + if (child_info->width_constraint[0] != NULL) + { + gtk_constraint_solver_remove_constraint (solver, child_info->width_constraint[0]); + gtk_constraint_solver_remove_constraint (solver, child_info->width_constraint[1]); + } + + if (child_info->height_constraint[0] != NULL) + { + gtk_constraint_solver_remove_constraint (solver, child_info->height_constraint[0]); + gtk_constraint_solver_remove_constraint (solver, child_info->height_constraint[1]); + } + + width_var = get_child_attribute (child_info, solver, child, + GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + + child_info->width_constraint[0] = + gtk_constraint_solver_add_constraint (solver, + width_var, + GTK_CONSTRAINT_RELATION_GE, + gtk_constraint_expression_new (min_req.width), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + child_info->width_constraint[1] = + gtk_constraint_solver_add_constraint (solver, + width_var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new (nat_req.width), + GTK_CONSTRAINT_WEIGHT_MEDIUM); + + height_var = get_child_attribute (child_info, solver, child, + GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + + child_info->height_constraint[0] = + gtk_constraint_solver_add_constraint (solver, + height_var, + GTK_CONSTRAINT_RELATION_GE, + gtk_constraint_expression_new (min_req.height), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + child_info->height_constraint[1] = + gtk_constraint_solver_add_constraint (solver, + height_var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new (nat_req.height), + GTK_CONSTRAINT_WEIGHT_MEDIUM); + } + + for (child = _gtk_widget_get_first_child (widget); + child != NULL; + child = _gtk_widget_get_next_sibling (child)) + { + GtkConstraintVariable *var_top, *var_left, *var_width, *var_height; + GtkConstraintVariable *var_baseline; + GtkConstraintLayoutChild *child_info; + GtkAllocation child_alloc; + int child_baseline = -1; + + if (!gtk_widget_should_layout (child)) + continue; + + child_info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child)); + + /* Retrieve all the values associated with the child */ + var_top = get_child_attribute (child_info, solver, child, GTK_CONSTRAINT_ATTRIBUTE_TOP); + var_left = get_child_attribute (child_info, solver, child, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + var_width = get_child_attribute (child_info, solver, child, GTK_CONSTRAINT_ATTRIBUTE_WIDTH); + var_height = get_child_attribute (child_info, solver, child, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT); + var_baseline = get_child_attribute (child_info, solver, child, GTK_CONSTRAINT_ATTRIBUTE_BASELINE); + + GTK_NOTE (LAYOUT, + g_print ("Allocating child '%s'[%p] with { .x: %g, .y: %g, .w: %g, .h: %g, .b: %g }\n", + gtk_widget_get_name (child), child, + gtk_constraint_variable_get_value (var_left), + gtk_constraint_variable_get_value (var_top), + gtk_constraint_variable_get_value (var_width), + gtk_constraint_variable_get_value (var_height), + gtk_constraint_variable_get_value (var_baseline))); + + child_alloc.x = floor (gtk_constraint_variable_get_value (var_left)); + child_alloc.y = floor (gtk_constraint_variable_get_value (var_top)); + child_alloc.width = ceil (gtk_constraint_variable_get_value (var_width)); + child_alloc.height = ceil (gtk_constraint_variable_get_value (var_height)); + + if (gtk_constraint_variable_get_value (var_baseline) > 0) + child_baseline = floor (gtk_constraint_variable_get_value (var_baseline)); + + gtk_widget_size_allocate (GTK_WIDGET (child), + &child_alloc, + child_baseline); + } + + /* The allocation stay constraints are not needed any more */ + gtk_constraint_solver_remove_constraint (solver, stay_w); + gtk_constraint_solver_remove_constraint (solver, stay_h); + gtk_constraint_solver_remove_constraint (solver, stay_t); + gtk_constraint_solver_remove_constraint (solver, stay_l); +} + +static void +gtk_constraint_layout_root (GtkLayoutManager *manager) +{ + GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager); + GHashTableIter iter; + GtkWidget *widget; + GtkRoot *root; + gpointer key; + + widget = gtk_layout_manager_get_widget (manager); + root = gtk_widget_get_root (widget); + + self->solver = gtk_root_get_constraint_solver (root); + + /* Now that we have a solver, attach all constraints we have */ + g_hash_table_iter_init (&iter, self->constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + GtkConstraint *constraint = key; + + layout_add_constraint (self, constraint); + } +} + +static void +gtk_constraint_layout_unroot (GtkLayoutManager *manager) +{ + GtkConstraintLayout *self = GTK_CONSTRAINT_LAYOUT (manager); + GHashTableIter iter; + gpointer key; + + /* Detach all constraints we're holding, as we're removing the layout + * from the global solver, and they should not contribute to the other + * layouts + */ + g_hash_table_iter_init (&iter, self->constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + GtkConstraint *constraint = key; + + gtk_constraint_detach (constraint); + } + + self->solver = NULL; +} + +static void +gtk_constraint_layout_class_init (GtkConstraintLayoutClass *klass) +{ + GtkLayoutManagerClass *manager_class = GTK_LAYOUT_MANAGER_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gtk_constraint_layout_finalize; + + manager_class->layout_child_type = GTK_TYPE_CONSTRAINT_LAYOUT_CHILD; + manager_class->measure = gtk_constraint_layout_measure; + manager_class->allocate = gtk_constraint_layout_allocate; + manager_class->root = gtk_constraint_layout_root; + manager_class->unroot = gtk_constraint_layout_unroot; +} + +static void +gtk_constraint_layout_init (GtkConstraintLayout *self) +{ + /* The bound variables in the solver */ + self->bound_attributes = + g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, + (GDestroyNotify) gtk_constraint_variable_unref); + + /* The GtkConstraint instances we own */ + self->constraints = + g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) g_object_unref, + NULL); +} + +/** + * gtk_constraint_layout_new: + * + * Creates a new #GtkConstraintLayout layout manager. + * + * Returns: the newly created #GtkConstraintLayout + */ +GtkLayoutManager * +gtk_constraint_layout_new (void) +{ + return g_object_new (GTK_TYPE_CONSTRAINT_LAYOUT, NULL); +} + +/** + * gtk_constraint_layout_add_constraint: + * @manager: a #GtkConstraintLayout + * @constraint: (transfer full): a #GtkConstraint + * + * Adds a #GtkConstraint to the layout manager. + * + * The #GtkConstraint:source-widget and #GtkConstraint:target-widget + * properties of @constraint can be: + * + * - set to %NULL to indicate that the constraint refers to the + * widget using @manager + * - set to the #GtkWidget using @manager + * - set to a child of the #GtkWidget using @manager + * + * The @manager acquires the ownership of @constraint after calling + * this function. + */ +void +gtk_constraint_layout_add_constraint (GtkConstraintLayout *manager, + GtkConstraint *constraint) +{ + g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (manager)); + g_return_if_fail (GTK_IS_CONSTRAINT (constraint)); + g_return_if_fail (!gtk_constraint_is_attached (constraint)); + + layout_add_constraint (manager, constraint); + + g_hash_table_add (manager->constraints, constraint); +} diff --git a/gtk/gtkconstraintlayout.h b/gtk/gtkconstraintlayout.h new file mode 100644 index 0000000000..bd6528da3b --- /dev/null +++ b/gtk/gtkconstraintlayout.h @@ -0,0 +1,53 @@ +/* gtkconstraintlayout.h: Layout manager using constraints + * 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.1 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/>. + * + * Author: Emmanuele Bassi + */ +#pragma once + +#include <gtk/gtklayoutmanager.h> +#include <gtk/gtkconstraint.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_CONSTRAINT_LAYOUT (gtk_constraint_layout_get_type ()) +#define GTK_TYPE_CONSTRAINT_LAYOUT_CHILD (gtk_constraint_layout_child_get_type ()) + +/** + * GtkConstraintLayout: + * + * A layout manager using #GtkConstraint to describe + * relations between widgets. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkConstraintLayout, gtk_constraint_layout, GTK, CONSTRAINT_LAYOUT, GtkLayoutManager) + +GDK_AVAILABLE_IN_ALL +GtkLayoutManager * gtk_constraint_layout_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_constraint_layout_add_constraint (GtkConstraintLayout *manager, + GtkConstraint *constraint); + +/** + * GtkConstraintLayoutChild: + * + * A #GtkLayoutChild in a #GtkConstraintLayout. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK, CONSTRAINT_LAYOUT_CHILD, GtkLayoutChild) + +G_END_DECLS diff --git a/gtk/gtkconstraintprivate.h b/gtk/gtkconstraintprivate.h new file mode 100644 index 0000000000..90f70234a2 --- /dev/null +++ b/gtk/gtkconstraintprivate.h @@ -0,0 +1,62 @@ +/* gtkconstraintprivate.h: Constraint between two widgets + * 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.1 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/>. + * + * Author: Emmanuele Bassi + */ + +#pragma once + +#include "gtkconstraint.h" +#include "gtkconstrainttypesprivate.h" + +G_BEGIN_DECLS + +struct _GtkConstraint +{ + GObject parent_instance; + + GtkConstraintAttribute target_attribute; + GtkConstraintAttribute source_attribute; + + GtkWidget *target_widget; + GtkWidget *source_widget; + + GtkConstraintRelation relation; + + double multiplier; + double constant; + + int strength; + + /* A reference to the real constraint inside the + * GtkConstraintSolver, so we can remove it when + * finalizing the GtkConstraint instance + */ + GtkConstraintRef *constraint_ref; + + GtkConstraintSolver *solver; + + guint active : 1; +}; + +double gtk_constraint_get_weight (GtkConstraint *constraint); + +void gtk_constraint_attach (GtkConstraint *constraint, + GtkConstraintSolver *solver, + GtkConstraintRef *ref); +void gtk_constraint_detach (GtkConstraint *constraint); + +G_END_DECLS diff --git a/gtk/gtkconstraintsolver.c b/gtk/gtkconstraintsolver.c index 411b006957..53262b70af 100644 --- a/gtk/gtkconstraintsolver.c +++ b/gtk/gtkconstraintsolver.c @@ -348,37 +348,6 @@ gtk_constraint_solver_init (GtkConstraintSolver *self) self->auto_solve = TRUE; } -/* Symbolic weight thresholds - * - * Constraint weights live on a continuum, but we use thresholds for simplicity's - * sake, so we don't have to necessarily reason in terms of numeric values. - * - * The public API has a similar approach, where the symbolic constants are negative - * values, and positive values are explicit weights. We map those values into - * numeric values that the GtkConstraintSolver can plug into the linear equations - * tableau. - */ -#define GTK_CONSTRAINT_WEIGHT_REQUIRED (make_weight (1000, 1000, 1000, 1)) -#define GTK_CONSTRAINT_WEIGHT_STRONG (make_weight ( 1, 0, 0, 1)) -#define GTK_CONSTRAINT_WEIGHT_MEDIUM (make_weight ( 0, 1, 0, 1)) -#define GTK_CONSTRAINT_WEIGHT_WEAK (make_weight ( 0, 0, 1, 1)) - -G_GNUC_PURE -static inline double -make_weight (double a, - double b, - double c, - double w) -{ - double res = 0; - - res += CLAMP (a * w, 0, 1000) * 1000000; - res += CLAMP (b * w, 0, 1000) * 1000; - res += CLAMP (c * w, 0, 1000); - - return res; -} - static void gtk_constraint_ref_free (GtkConstraintRef *self) { diff --git a/gtk/gtkconstraintsolverprivate.h b/gtk/gtkconstraintsolverprivate.h index 807b0b08d6..69eed376c7 100644 --- a/gtk/gtkconstraintsolverprivate.h +++ b/gtk/gtkconstraintsolverprivate.h @@ -29,6 +29,37 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (GtkConstraintSolver, gtk_constraint_solver, GTK, CONSTRAINT_SOLVER, GObject) +/* Symbolic weight thresholds + * + * Constraint weights live on a continuum, but we use thresholds for simplicity's + * sake, so we don't have to necessarily reason in terms of numeric values. + * + * The public API has a similar approach, where the symbolic constants are negative + * values, and positive values are explicit weights. We map those values into + * numeric values that the GtkConstraintSolver can plug into the linear equations + * tableau. + */ +#define GTK_CONSTRAINT_WEIGHT_REQUIRED (make_weight (1000, 1000, 1000, 1)) +#define GTK_CONSTRAINT_WEIGHT_STRONG (make_weight ( 1, 0, 0, 1)) +#define GTK_CONSTRAINT_WEIGHT_MEDIUM (make_weight ( 0, 1, 0, 1)) +#define GTK_CONSTRAINT_WEIGHT_WEAK (make_weight ( 0, 0, 1, 1)) + +G_GNUC_PURE +static inline double +make_weight (double a, + double b, + double c, + double w) +{ + double res = 0; + + res += CLAMP (a * w, 0, 1000) * 1000000; + res += CLAMP (b * w, 0, 1000) * 1000; + res += CLAMP (c * w, 0, 1000); + + return res; +} + GtkConstraintSolver * gtk_constraint_solver_new (void); diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index 468adcdc81..e5ea331172 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -1067,4 +1067,64 @@ typedef enum { GTK_CONSTRAINT_RELATION_GE = 1 } GtkConstraintRelation; +/** + * GtkConstraintStrength: + * @GTK_CONSTRAINT_STRENGTH_REQUIRED: The constraint is required towards solving the layout + * @GTK_CONSTRAINT_STRENGTH_STRONG: A strong constraint + * @GTK_CONSTRAINT_STRENGTH_MEDIUM: A medium constraint + * @GTK_CONSTRAINT_STRENGTH_WEAK: A weak constraint + * + * The strength of a constraint, expressed as a symbolic constant. + * + * The strength of a #GtkConstraint can be expressed with any positive + * integer; the values of this enumeration can be used for readability. + */ +typedef enum { + GTK_CONSTRAINT_STRENGTH_REQUIRED = 0, + GTK_CONSTRAINT_STRENGTH_STRONG = -1, + GTK_CONSTRAINT_STRENGTH_MEDIUM = -2, + GTK_CONSTRAINT_STRENGTH_WEAK = -3 +} GtkConstraintStrength; + +/** + * GtkConstraintAttribute: + * @GTK_CONSTRAINT_ATTRIBUTE_NONE: No attribute, used for constant + * relations + * @GTK_CONSTRAINT_ATTRIBUTE_LEFT: The left edge of a widget, regardless of + * text direction + * @GTK_CONSTRAINT_ATTRIBUTE_RIGHT: The right edge of a widget, regardless + * of text direction + * @GTK_CONSTRAINT_ATTRIBUTE_TOP: The top edge of a widget + * @GTK_CONSTRAINT_ATTRIBUTE_BOTTOM: The bottom edge of a widget + * @GTK_CONSTRAINT_ATTRIBUTE_START: The leading edge of a widget, depending + * on text direction; equivalent to %GTK_CONSTRAINT_ATTRIBUTE_LEFT for LTR + * languages, and %GTK_CONSTRAINT_ATTRIBUTE_RIGHT for RTL ones + * @GTK_CONSTRAINT_ATTRIBUTE_END: The trailing edge of a widget, depending + * on text direction; equivalent to %GTK_CONSTRAINT_ATTRIBUTE_RIGHT for LTR + * languages, and %GTK_CONSTRAINT_ATTRIBUTE_LEFT for RTL ones + * @GTK_CONSTRAINT_ATTRIBUTE_WIDTH: The width of a widget + * @GTK_CONSTRAINT_ATTRIBUTE_HEIGHT: The height of a widget + * @GTK_CONSTRAINT_ATTRIBUTE_CENTER_X: The center of a widget, on the + * horizontal axis + * @GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y: The center of a widget, on the + * vertical axis + * @GTK_CONSTRAINT_ATTRIBUTE_BASELINE: The baseline of a widget + * + * The widget attributes that can be used when creating a #GtkConstraint. + */ +typedef enum { + GTK_CONSTRAINT_ATTRIBUTE_NONE, + GTK_CONSTRAINT_ATTRIBUTE_LEFT, + GTK_CONSTRAINT_ATTRIBUTE_RIGHT, + GTK_CONSTRAINT_ATTRIBUTE_TOP, + GTK_CONSTRAINT_ATTRIBUTE_BOTTOM, + GTK_CONSTRAINT_ATTRIBUTE_START, + GTK_CONSTRAINT_ATTRIBUTE_END, + GTK_CONSTRAINT_ATTRIBUTE_WIDTH, + GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, + GTK_CONSTRAINT_ATTRIBUTE_CENTER_X, + GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y, + GTK_CONSTRAINT_ATTRIBUTE_BASELINE +} GtkConstraintAttribute; + #endif /* __GTK_ENUMS_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index b8cef8bb28..a79e3e7ecc 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -202,6 +202,8 @@ gtk_public_sources = files([ 'gtkcombobox.c', 'gtkcomboboxtext.c', 'gtkcomposetable.c', + 'gtkconstraintlayout.c', + 'gtkconstraint.c', 'gtkcontainer.c', 'gtkcssprovider.c', 'gtkdialog.c', @@ -461,6 +463,8 @@ gtk_public_headers = files([ 'gtkcolorutils.h', 'gtkcombobox.h', 'gtkcomboboxtext.h', + 'gtkconstraintlayout.h', + 'gtkconstraint.h', 'gtkcontainer.h', 'gtkcssprovider.h', 'gtkcustomlayout.h', |