summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2019-04-09 15:33:52 +0100
committerEmmanuele Bassi <ebassi@gnome.org>2019-06-27 17:38:02 +0100
commit9756cb9482b5cef1a831edfdae3b2e8bf7270a21 (patch)
treeefcf8e65840383d53f08b6f843301934274b629c
parent92f93a603bf893b109746852e3d5a017b635e5cd (diff)
downloadgtk+-9756cb9482b5cef1a831edfdae3b2e8bf7270a21.tar.gz
Add GtkConstraintLayout
A layout manager using GtkConstraintSolver to measure and allocate children.
-rw-r--r--gtk/gtk.h2
-rw-r--r--gtk/gtkconstraint.c583
-rw-r--r--gtk/gtkconstraint.h85
-rw-r--r--gtk/gtkconstraintlayout.c1108
-rw-r--r--gtk/gtkconstraintlayout.h53
-rw-r--r--gtk/gtkconstraintprivate.h62
-rw-r--r--gtk/gtkconstraintsolver.c31
-rw-r--r--gtk/gtkconstraintsolverprivate.h31
-rw-r--r--gtk/gtkenums.h60
-rw-r--r--gtk/meson.build4
10 files changed, 1988 insertions, 31 deletions
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 6691d286c9..ecf4829339 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -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',