summaryrefslogtreecommitdiff
path: root/gtk/gtkconstraintexpression.c
diff options
context:
space:
mode:
authorEmmanuele Bassi <ebassi@gnome.org>2019-04-09 14:05:48 +0100
committerEmmanuele Bassi <ebassi@gnome.org>2019-06-30 23:42:44 +0100
commit6b308cd71e1c6d37cd32a3b99d21e729e181aa8d (patch)
treea741e62656b9685662b1775a3d30bfca55ebf54e /gtk/gtkconstraintexpression.c
parent3b6ee32f83882c8c2c736c7bb504a47bf1fdf407 (diff)
downloadgtk+-6b308cd71e1c6d37cd32a3b99d21e729e181aa8d.tar.gz
Add constraint solver
GtkConstraintSolver is an implementation of the Cassowary constraint solving algorithm: http://constraints.cs.washington.edu/cassowary/ The Cassowary method allows to incrementally solve a tableau of linear equations, in the form of: x = y × coefficient + constant with different weights, or strengths, applied to each one. These equations can be used to describe constraints applied to a layout of UI elements, which allows layout managers using the Cassowary method to quickly, and efficiently, lay out widgets in complex relations between themselves and their parent container.
Diffstat (limited to 'gtk/gtkconstraintexpression.c')
-rw-r--r--gtk/gtkconstraintexpression.c1842
1 files changed, 1842 insertions, 0 deletions
diff --git a/gtk/gtkconstraintexpression.c b/gtk/gtkconstraintexpression.c
new file mode 100644
index 0000000000..0e5db96b62
--- /dev/null
+++ b/gtk/gtkconstraintexpression.c
@@ -0,0 +1,1842 @@
+/* gtkconstraintexpression.c: Constraint expressions and variables
+ * Copyright 2019 GNOME Foundation
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * 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
+ */
+
+#include "config.h"
+
+#include "gtkconstraintexpressionprivate.h"
+#include "gtkconstraintsolverprivate.h"
+
+/* {{{ Variables */
+
+typedef enum {
+ GTK_CONSTRAINT_SYMBOL_DUMMY = 'd',
+ GTK_CONSTRAINT_SYMBOL_OBJECTIVE = 'o',
+ GTK_CONSTRAINT_SYMBOL_SLACK = 'S',
+ GTK_CONSTRAINT_SYMBOL_REGULAR = 'v'
+} GtkConstraintSymbolType;
+
+struct _GtkConstraintVariable
+{
+ guint64 _id;
+
+ GtkConstraintSymbolType _type;
+
+ /* Interned strings */
+ const char *name;
+ const char *prefix;
+
+ double value;
+
+ guint is_external : 1;
+ guint is_pivotable : 1;
+ guint is_restricted : 1;
+};
+
+/* Variables are sorted by a monotonic id */
+static guint64 gtk_constraint_variable_next_id;
+
+static void
+gtk_constraint_variable_init (GtkConstraintVariable *variable,
+ const char *name)
+{
+ variable->_id = gtk_constraint_variable_next_id++;
+ variable->name = g_intern_string (name);
+ variable->prefix = NULL;
+ variable->value = 0.0;
+}
+
+/*< private >
+ * gtk_constraint_variable_new_dummy:
+ * @name: the name of the variable
+ *
+ * Allocates and initializes a new #GtkConstraintVariable for a "dummy"
+ * symbol. Dummy symbols are typically used as markers inside a solver,
+ * and will not be factored in the solution when pivoting the tableau
+ * of the constraint equations.
+ *
+ * Only #GtkConstraintSolver should use this function.
+ *
+ * Returns: a newly allocated #GtkConstraintVariable
+ */
+GtkConstraintVariable *
+gtk_constraint_variable_new_dummy (const char *name)
+{
+ GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);
+
+ gtk_constraint_variable_init (res, name);
+
+ res->_type = GTK_CONSTRAINT_SYMBOL_DUMMY;
+ res->is_external = FALSE;
+ res->is_pivotable = FALSE;
+ res->is_restricted = TRUE;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_variable_new_objective:
+ * @name: the name of the variable
+ *
+ * Allocates and initializes a new #GtkConstraintVariable for an objective
+ * symbol. This is the constant value we wish to find as the result of the
+ * simplex optimization.
+ *
+ * Only #GtkConstraintSolver should use this function.
+ *
+ * Returns: a newly allocated #GtkConstraintVariable
+ */
+GtkConstraintVariable *
+gtk_constraint_variable_new_objective (const char *name)
+{
+ GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);
+
+ gtk_constraint_variable_init (res, name);
+
+ res->_type = GTK_CONSTRAINT_SYMBOL_OBJECTIVE;
+ res->is_external = FALSE;
+ res->is_pivotable = FALSE;
+ res->is_restricted = FALSE;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_variable_new_slack:
+ * @name: the name of the variable
+ *
+ * Allocates and initializes a new #GtkConstraintVariable for a "slack"
+ * symbol. Slack variables are introduced inside the tableau to turn
+ * inequalities, like:
+ *
+ * |[
+ * expr ≥ 0
+ * ]|
+ *
+ * Into equalities, like:
+ *
+ * |[
+ * expr - slack = 0
+ * ]|
+ *
+ * Only #GtkConstraintSolver should use this function.
+ *
+ * Returns: a newly allocated #GtkConstraintVariable
+ */
+GtkConstraintVariable *
+gtk_constraint_variable_new_slack (const char *name)
+{
+ GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);
+
+ gtk_constraint_variable_init (res, name);
+
+ res->_type = GTK_CONSTRAINT_SYMBOL_SLACK;
+ res->is_external = FALSE;
+ res->is_pivotable = TRUE;
+ res->is_restricted = TRUE;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_variable_new:
+ * @name: the name of the variable
+ *
+ * Allocates and initializes a new #GtkConstraintVariable for a regular
+ * symbol. All variables introduced by constraints are regular variables.
+ *
+ * Only #GtkConstraintSolver should use this function; a constraint layout
+ * manager should ask the #GtkConstraintSolver to create a variable, using
+ * gtk_constraint_solver_create_variable(), which will insert the variable
+ * in the solver's tableau.
+ *
+ * Returns: a newly allocated #GtkConstraintVariable
+ */
+GtkConstraintVariable *
+gtk_constraint_variable_new (const char *name)
+{
+ GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);
+
+ gtk_constraint_variable_init (res, name);
+
+ res->_type = GTK_CONSTRAINT_SYMBOL_REGULAR;
+ res->is_external = TRUE;
+ res->is_pivotable = FALSE;
+ res->is_restricted = FALSE;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_variable_set_prefix:
+ * @variable: a #GtkConstraintVariable
+ * @prefix: a prefix string
+ *
+ * Sets the prefix to the @variable's name.
+ *
+ * This function is useful when debugging the variable contents.
+ */
+void
+gtk_constraint_variable_set_prefix (GtkConstraintVariable *variable,
+ const char *prefix)
+{
+ g_return_if_fail (variable != NULL);
+
+ variable->prefix = g_intern_string (prefix);
+}
+
+/*< private >
+ * gtk_constraint_variable_ref:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Acquires a reference to @variable.
+ *
+ * Returns: (transfer full): the given #GtkConstraintVariable, with its reference
+ * count increased
+ */
+GtkConstraintVariable *
+gtk_constraint_variable_ref (GtkConstraintVariable *variable)
+{
+ g_return_val_if_fail (variable != NULL, NULL);
+
+ return g_rc_box_acquire (variable);
+}
+
+/*< private >
+ * gtk_constraint_variable_unref:
+ * @variable: (transfer full): a #GtkConstraintVariable
+ *
+ * Releases a reference to @variable.
+ */
+void
+gtk_constraint_variable_unref (GtkConstraintVariable *variable)
+{
+ g_return_if_fail (variable != NULL);
+
+ g_rc_box_release (variable);
+}
+
+/*< private >
+ * gtk_constraint_variable_set_value:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Sets the current value of a #GtkConstraintVariable.
+ */
+void
+gtk_constraint_variable_set_value (GtkConstraintVariable *variable,
+ double value)
+{
+ variable->value = value;
+}
+
+/*< private >
+ * gtk_constraint_variable_get_value:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Retrieves the current value of a #GtkConstraintVariable
+ *
+ * Returns: the value of the variable
+ */
+double
+gtk_constraint_variable_get_value (const GtkConstraintVariable *variable)
+{
+ return variable->value;
+}
+
+/*< private >
+ * gtk_constraint_variable_to_string:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Turns @variable into a string, for debugging purposes.
+ *
+ * Returns: (transfer full): a string with the contents of @variable
+ */
+char *
+gtk_constraint_variable_to_string (const GtkConstraintVariable *variable)
+{
+ GString *buf = g_string_new (NULL);
+
+ if (variable == NULL)
+ g_string_append (buf, "<null>");
+ else
+ {
+ switch (variable->_type)
+ {
+ case GTK_CONSTRAINT_SYMBOL_DUMMY:
+ g_string_append (buf, "(d)");
+ break;
+ case GTK_CONSTRAINT_SYMBOL_OBJECTIVE:
+ g_string_append (buf, "(O)");
+ break;
+ case GTK_CONSTRAINT_SYMBOL_SLACK:
+ g_string_append (buf, "(S)");
+ break;
+ case GTK_CONSTRAINT_SYMBOL_REGULAR:
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_string_append_c (buf, '[');
+
+ if (variable->prefix != NULL)
+ {
+ g_string_append (buf, variable->prefix);
+ g_string_append_c (buf, '.');
+ }
+
+ if (variable->name != NULL)
+ g_string_append (buf, variable->name);
+
+ if (variable->_type == GTK_CONSTRAINT_SYMBOL_REGULAR)
+ {
+ char dbl_buf[G_ASCII_DTOSTR_BUF_SIZE];
+
+ g_ascii_dtostr (dbl_buf, G_ASCII_DTOSTR_BUF_SIZE, variable->value);
+
+ g_string_append_c (buf, ':');
+ g_string_append (buf, dbl_buf);
+ }
+
+ g_string_append_c (buf, ']');
+ }
+
+ return g_string_free (buf, FALSE);
+}
+
+/*< private >
+ * gtk_constraint_variable_is_external:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether the @variable was introduced from outside the solver.
+ *
+ * Returns: %TRUE if the variable is external
+ */
+gboolean
+gtk_constraint_variable_is_external (const GtkConstraintVariable *variable)
+{
+ return variable->is_external;
+}
+
+/*< private >
+ * gtk_constraint_variable_is_pivotable:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether the @variable can be used as a pivot.
+ *
+ * Returns: %TRUE if the variable is pivotable
+ */
+gboolean
+gtk_constraint_variable_is_pivotable (const GtkConstraintVariable *variable)
+{
+ return variable->is_pivotable;
+}
+
+/*< private >
+ * gtk_constraint_variable_is_restricted:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether the @variable's use is restricted.
+ *
+ * Returns: %TRUE if the variable is restricted
+ */
+gboolean
+gtk_constraint_variable_is_restricted (const GtkConstraintVariable *variable)
+{
+ return variable->is_restricted;
+}
+
+/*< private >
+ * gtk_constraint_variable_is_dummy:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether the @variable is a dummy symbol.
+ *
+ * Returns: %TRUE if the variable is a dummy symbol
+ */
+gboolean
+gtk_constraint_variable_is_dummy (const GtkConstraintVariable *variable)
+{
+ return variable->_type == GTK_CONSTRAINT_SYMBOL_DUMMY;
+}
+
+/*< private >
+ * GtkConstraintVariableSet:
+ *
+ * A set of variables.
+ */
+struct _GtkConstraintVariableSet {
+ /* HashSet<Variable>, owns a reference */
+ GHashTable *set;
+
+ /* List<Variable>, used for iterating */
+ GList *ordered_set;
+
+ /* Age of the set, to guard against mutations while iterating */
+ gint64 age;
+};
+
+/*< private >
+ * gtk_constraint_variable_set_free:
+ * @set: a #GtkConstraintVariableSet
+ *
+ * Frees the resources associated to a #GtkConstraintVariableSet/
+ */
+void
+gtk_constraint_variable_set_free (GtkConstraintVariableSet *set)
+{
+ g_return_if_fail (set != NULL);
+
+ g_list_free (set->ordered_set);
+ g_hash_table_unref (set->set);
+
+ g_free (set);
+}
+
+/*< private >
+ * gtk_constraint_variable_set_new:
+ *
+ * Creates a new #GtkConstraintVariableSet.
+ *
+ * Returns: the newly created variable set
+ */
+GtkConstraintVariableSet *
+gtk_constraint_variable_set_new (void)
+{
+ GtkConstraintVariableSet *res = g_new (GtkConstraintVariableSet, 1);
+
+ res->set = g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) gtk_constraint_variable_unref,
+ NULL);
+ res->ordered_set = NULL;
+
+ res->age = 0;
+
+ return res;
+}
+
+static int
+sort_by_variable_id (gconstpointer a,
+ gconstpointer b)
+{
+ const GtkConstraintVariable *va = a, *vb = b;
+
+ if (va == vb)
+ return 0;
+
+ return va->_id - vb->_id;
+}
+
+/*< private >
+ * gtk_constraint_variable_set_add:
+ * @set: a #GtkConstraintVariableSet
+ * @variable: a #GtkConstraintVariable
+ *
+ * Adds @variable to the given @set, if the @variable is not already
+ * in it.
+ *
+ * The @set will acquire a reference on the @variable, and will release
+ * it after calling gtk_constraint_variable_set_remove(), or when the @set
+ * is freed.
+ *
+ * Returns: %TRUE if the variable was added to the set, and %FALSE otherwise
+ */
+gboolean
+gtk_constraint_variable_set_add (GtkConstraintVariableSet *set,
+ GtkConstraintVariable *variable)
+{
+ if (g_hash_table_contains (set->set, variable))
+ return FALSE;
+
+ g_hash_table_add (set->set, gtk_constraint_variable_ref (variable));
+
+ /* This is a tricky bit; the variables in the set must be ordered
+ * not by insertion, but by the incremental id of each variable,
+ * as that's the expected iteration order
+ */
+ set->ordered_set = g_list_insert_sorted (set->ordered_set, variable, sort_by_variable_id);
+
+ set->age += 1;
+
+ return TRUE;
+}
+
+/*< private >
+ * gtk_constraint_variable_set_remove:
+ * @set: a #GtkConstraintVariableSet
+ * @variable: a #GtkConstraintVariable
+ *
+ * Removes @variable from the @set.
+ *
+ * This function will release the reference on @variable held by the @set.
+ *
+ * Returns: %TRUE if the variable was removed from the set, and %FALSE
+ * otherwise
+ */
+gboolean
+gtk_constraint_variable_set_remove (GtkConstraintVariableSet *set,
+ GtkConstraintVariable *variable)
+{
+ if (g_hash_table_contains (set->set, variable))
+ {
+ set->ordered_set = g_list_remove (set->ordered_set, variable);
+ g_hash_table_remove (set->set, variable);
+ set->age += 1;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*< private >
+ * gtk_constraint_variable_set_size:
+ * @set: a #GtkConstraintVariableSet
+ *
+ * Retrieves the size of the @set.
+ *
+ * Returns: the number of variables in the set
+ */
+int
+gtk_constraint_variable_set_size (GtkConstraintVariableSet *set)
+{
+ return g_hash_table_size (set->set);
+}
+
+/*< private >
+ * GtkConstraintVariableSetIter:
+ *
+ * An iterator type for #GtkConstraintVariableSet.
+ */
+/* Keep in sync with GtkConstraintVariableSetIter */
+typedef struct {
+ GtkConstraintVariableSet *set;
+ GList *current;
+ gint64 age;
+} RealVariableSetIter;
+
+#define REAL_VARIABLE_SET_ITER(i) ((RealVariableSetIter *) (i))
+
+/*< private >
+ * gtk_constraint_variable_set_iter_init:
+ * @iter: a #GtkConstraintVariableSetIter
+ * @set: the #GtkConstraintVariableSet to iterate
+ *
+ * Initializes @iter for iterating over @set.
+ */
+void
+gtk_constraint_variable_set_iter_init (GtkConstraintVariableSetIter *iter,
+ GtkConstraintVariableSet *set)
+{
+ RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter);
+
+ g_return_if_fail (iter != NULL);
+ g_return_if_fail (set != NULL);
+
+ riter->set = set;
+ riter->current = NULL;
+ riter->age = set->age;
+}
+
+/*< private >
+ * gtk_constraint_variable_set_iter_next:
+ * @iter: a #GtkConstraintVariableSetIter
+ * @variable_p: (out): the next variable in the set
+ *
+ * Advances the @iter to the next variable in the #GtkConstraintVariableSet.
+ *
+ * Returns: %TRUE if the iterator was advanced, and %FALSE otherwise
+ */
+gboolean
+gtk_constraint_variable_set_iter_next (GtkConstraintVariableSetIter *iter,
+ GtkConstraintVariable **variable_p)
+{
+ RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter);
+
+ g_return_val_if_fail (iter != NULL, FALSE);
+ g_return_val_if_fail (variable_p != NULL, FALSE);
+
+ g_assert (riter->age == riter->set->age);
+
+ if (riter->current == NULL)
+ riter->current = riter->set->ordered_set;
+ else
+ riter->current = riter->current->next;
+
+ if (riter->current != NULL)
+ *variable_p = riter->current->data;
+
+ return riter->current != NULL;
+}
+
+/*< private >
+ * gtk_constraint_variable_pair_new:
+ * @first: a #GtkConstraintVariable
+ * @second: a #GtkConstraintVariable
+ *
+ * Creates a new #GtkConstraintVariablePair, containint @first and @second.
+ *
+ * The #GtkConstraintVariablePair acquires a reference over the two
+ * given #GtkConstraintVariables.
+ *
+ * Returns: a new #GtkConstraintVariablePair
+ */
+GtkConstraintVariablePair *
+gtk_constraint_variable_pair_new (GtkConstraintVariable *first,
+ GtkConstraintVariable *second)
+{
+ GtkConstraintVariablePair *res = g_new (GtkConstraintVariablePair, 1);
+
+ res->first = gtk_constraint_variable_ref (first);
+ res->second = gtk_constraint_variable_ref (second);
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_variable_pair_free:
+ * @pair: a #GtkConstraintVariablePair
+ *
+ * Frees the resources associated by @pair.
+ */
+void
+gtk_constraint_variable_pair_free (GtkConstraintVariablePair *pair)
+{
+ g_clear_pointer (&pair->first, gtk_constraint_variable_unref);
+ g_clear_pointer (&pair->second, gtk_constraint_variable_unref);
+
+ g_free (pair);
+}
+
+/* }}} */
+
+/* {{{ Expressions */
+
+/*< private >
+ * Term:
+ * @variable: a #GtkConstraintVariable
+ * @coefficient: the coefficient applied to the @variable
+ * @next: the next term in the expression
+ * @prev: the previous term in the expression;
+ *
+ * A tuple of (@variable, @coefficient) in an equation.
+ *
+ * The term acquires a reference on the variable.
+ */
+typedef struct _Term Term;
+
+struct _Term {
+ GtkConstraintVariable *variable;
+ double coefficient;
+
+ Term *next;
+ Term *prev;
+};
+
+static Term *
+term_new (GtkConstraintVariable *variable,
+ double coefficient)
+{
+ Term *res = g_new (Term, 1);
+
+ res->variable = gtk_constraint_variable_ref (variable);
+ res->coefficient = coefficient;
+ res->next = res->prev = NULL;
+
+ return res;
+}
+
+static void
+term_free (gpointer data)
+{
+ Term *term = data;
+
+ if (term == NULL)
+ return;
+
+ gtk_constraint_variable_unref (term->variable);
+
+ g_free (term);
+}
+
+struct _GtkConstraintExpression
+{
+ double constant;
+
+ /* HashTable<Variable, Term>; the key is the term's variable,
+ * and the value is owned by the hash table
+ */
+ GHashTable *terms;
+
+ /* List of terms, in insertion order */
+ Term *first_term;
+ Term *last_term;
+
+ /* Used by GtkConstraintExpressionIter to guard against changes
+ * in the expression while iterating
+ */
+ gint64 age;
+};
+
+/*< private >
+ * gtk_constraint_expression_add_term:
+ * @self: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ * @coefficient: a coefficient for @variable
+ *
+ * Adds a new term formed by (@variable, @coefficient) into a
+ * #GtkConstraintExpression.
+ *
+ * The @expression acquires a reference on @variable.
+ */
+static void
+gtk_constraint_expression_add_term (GtkConstraintExpression *self,
+ GtkConstraintVariable *variable,
+ double coefficient)
+{
+ Term *term;
+
+ if (self->terms == NULL)
+ {
+ g_assert (self->first_term == NULL && self->last_term == NULL);
+ self->terms = g_hash_table_new_full (NULL, NULL,
+ NULL,
+ term_free);
+ }
+
+ term = term_new (variable, coefficient);
+
+ g_hash_table_insert (self->terms, term->variable, term);
+
+ if (self->first_term == NULL)
+ self->first_term = term;
+
+ term->prev = self->last_term;
+
+ if (self->last_term != NULL)
+ self->last_term->next = term;
+
+ self->last_term = term;
+
+ /* Increase the age of the expression, so that we can catch
+ * mutations from within an iteration over the terms
+ */
+ self->age += 1;
+}
+
+static void
+gtk_constraint_expression_remove_term (GtkConstraintExpression *self,
+ GtkConstraintVariable *variable)
+{
+ Term *term, *iter;
+
+ if (self->terms == NULL)
+ return;
+
+ term = g_hash_table_lookup (self->terms, variable);
+ if (term == NULL)
+ return;
+
+ /* Keep the variable alive for the duration of the function */
+ gtk_constraint_variable_ref (variable);
+
+ iter = self->first_term;
+ while (iter != NULL)
+ {
+ Term *next = iter->next;
+ Term *prev = iter->prev;
+
+ if (iter == term)
+ {
+ if (prev != NULL)
+ prev->next = next;
+ if (next != NULL)
+ next->prev = prev;
+
+ if (iter == self->first_term)
+ self->first_term = next;
+ if (iter == self->last_term)
+ self->last_term = prev;
+
+ iter->next = NULL;
+ iter->prev = NULL;
+
+ break;
+ }
+
+ iter = next;
+ }
+
+ g_hash_table_remove (self->terms, variable);
+
+ gtk_constraint_variable_unref (variable);
+
+ self->age += 1;
+}
+
+/*< private >
+ * gtk_constraint_expression_new:
+ * @constant: a constant for the expression
+ *
+ * Creates a new #GtkConstraintExpression with the given @constant.
+ *
+ * Returns: (transfer full): the newly created expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_new (double constant)
+{
+ GtkConstraintExpression *res = g_rc_box_new (GtkConstraintExpression);
+
+ res->age = 0;
+ res->terms = NULL;
+ res->first_term = NULL;
+ res->last_term = NULL;
+ res->constant = constant;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_expression_new_from_variable:
+ * @variable: a #GtkConstraintVariable
+ *
+ * Creates a new #GtkConstraintExpression with the given @variable.
+ *
+ * Returns: (transfer full): the newly created expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_new_from_variable (GtkConstraintVariable *variable)
+{
+ GtkConstraintExpression *res = gtk_constraint_expression_new (0.0);
+
+ gtk_constraint_expression_add_term (res, variable, 1.0);
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_expression_ref:
+ * @expression: a #GtkConstraintExpression
+ *
+ * Acquires a reference on @expression.
+ *
+ * Returns: (transfer full): the @expression, with its reference
+ * count increased
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_ref (GtkConstraintExpression *expression)
+{
+ g_return_val_if_fail (expression != NULL, NULL);
+
+ return g_rc_box_acquire (expression);
+}
+
+static void
+gtk_constraint_expression_clear (gpointer data)
+{
+ GtkConstraintExpression *self = data;
+
+ g_clear_pointer (&self->terms, g_hash_table_unref);
+
+ self->age = 0;
+ self->constant = 0.0;
+ self->first_term = NULL;
+ self->last_term = NULL;
+}
+
+/*< private >
+ * gtk_constraint_expression_unref:
+ * @expression: (transfer full): a #GtkConstraintExpression
+ *
+ * Releases a reference on @expression.
+ */
+void
+gtk_constraint_expression_unref (GtkConstraintExpression *expression)
+{
+ g_rc_box_release_full (expression, gtk_constraint_expression_clear);
+}
+
+/*< private >
+ * gtk_constraint_expression_is_constant:
+ * @expression: a #GtkConstraintExpression
+ *
+ * Checks whether @expression is a constant value, with no variable terms.
+ *
+ * Returns: %TRUE if the @expression is a constant
+ */
+gboolean
+gtk_constraint_expression_is_constant (const GtkConstraintExpression *expression)
+{
+ return expression->terms == NULL;
+}
+
+/*< private >
+ * gtk_constraint_expression_set_constant:
+ * @expression: a #GtkConstraintExpression
+ * @constant: the value of the constant
+ *
+ * Sets the value of the constant part of @expression.
+ */
+void
+gtk_constraint_expression_set_constant (GtkConstraintExpression *expression,
+ double constant)
+{
+ g_return_if_fail (expression != NULL);
+
+ expression->constant = constant;
+}
+
+/*< private >
+ * gtk_constraint_expression_get_constant:
+ * @expression: a #GtkConstraintExpression
+ *
+ * Retrieves the constant value of @expression.
+ *
+ * Returns: the constant of @expression
+ */
+double
+gtk_constraint_expression_get_constant (const GtkConstraintExpression *expression)
+{
+ g_return_val_if_fail (expression != NULL, 0.0);
+
+ return expression->constant;
+}
+
+GtkConstraintExpression *
+gtk_constraint_expression_clone (GtkConstraintExpression *expression)
+{
+ GtkConstraintExpression *res;
+ Term *iter;
+
+ res = gtk_constraint_expression_new (expression->constant);
+
+ iter = expression->first_term;
+ while (iter != NULL)
+ {
+ gtk_constraint_expression_add_term (res, iter->variable, iter->coefficient);
+
+ iter = iter->next;
+ }
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_expression_add_variable:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable to add to @expression
+ * @coefficient: the coefficient of @variable
+ * @subject: (nullable): a #GtkConstraintVariable
+ * @solver: (nullable): a #GtkConstraintSolver
+ *
+ * Adds a `(@coefficient × @variable)` term to @expression.
+ *
+ * If @expression already contains a term for @variable, this function will
+ * update its coefficient.
+ *
+ * If @coefficient is 0 and @expression already contains a term for @variable,
+ * the term for @variable will be removed.
+ *
+ * This function will notify @solver if @variable is added or removed from
+ * the @expression.
+ */
+void
+gtk_constraint_expression_add_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable,
+ double coefficient,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver)
+{
+ /* If the expression already contains the variable, update the coefficient */
+ if (expression->terms != NULL)
+ {
+ Term *t = g_hash_table_lookup (expression->terms, variable);
+
+ if (t != NULL)
+ {
+ double new_coefficient = t->coefficient + coefficient;
+
+ /* Setting the coefficient to 0 will remove the variable */
+ if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001))
+ {
+ /* Update the tableau if needed */
+ if (solver != NULL)
+ gtk_constraint_solver_note_removed_variable (solver, variable, subject);
+
+ gtk_constraint_expression_remove_term (expression, variable);
+
+ if (subject != NULL)
+ gtk_constraint_variable_unref (subject);
+ }
+ else
+ {
+ t->coefficient = new_coefficient;
+ }
+
+ return;
+ }
+ }
+
+ /* Otherwise, add the variable if the coefficient is non-zero */
+ if (!G_APPROX_VALUE (coefficient, 0.0, 0.001))
+ {
+ gtk_constraint_expression_add_term (expression, variable, coefficient);
+
+ if (solver != NULL)
+ gtk_constraint_solver_note_added_variable (solver, variable, subject);
+ }
+}
+
+/*< private >
+ * gtk_constraint_expression_remove_variable:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ *
+ * Removes @variable from @expression.
+ */
+void
+gtk_constraint_expression_remove_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable)
+{
+ g_return_if_fail (expression != NULL);
+ g_return_if_fail (variable != NULL);
+
+ gtk_constraint_expression_remove_term (expression, variable);
+}
+
+/*< private >
+ * gtk_constraint_expression_set_variable:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ * @coefficient: a coefficient for @variable
+ *
+ * Sets the @coefficient for @variable inside an @expression.
+ *
+ * If the @expression does not contain a term for @variable, a new
+ * one will be added.
+ */
+void
+gtk_constraint_expression_set_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable,
+ double coefficient)
+{
+ if (expression->terms != NULL)
+ {
+ Term *t = g_hash_table_lookup (expression->terms, variable);
+
+ if (t != NULL)
+ {
+ t->coefficient = coefficient;
+ return;
+ }
+ }
+
+ gtk_constraint_expression_add_term (expression, variable, coefficient);
+}
+
+/*< private >
+ * gtk_constraint_expression_add_expression:
+ * @a_expr: first operand
+ * @b_expr: second operand
+ * @n: the multiplication factor for @b_expr
+ * @subject: (nullable): a #GtkConstraintVariable
+ * @solver: (nullable): a #GtkConstraintSolver
+ *
+ * Adds `(@n × @b_expr)` to @a_expr.
+ *
+ * Typically, this function is used to turn two expressions in the
+ * form:
+ *
+ * |[
+ * a.x + a.width = b.x + b.width
+ * ]|
+ *
+ * into a single expression:
+ *
+ * |[
+ * a.x + a.width - b.x - b.width = 0
+ * ]|
+ *
+ * If @solver is not %NULL, this function will notify a #GtkConstraintSolver
+ * of every variable that was added or removed from @a_expr.
+ */
+void
+gtk_constraint_expression_add_expression (GtkConstraintExpression *a_expr,
+ GtkConstraintExpression *b_expr,
+ double n,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver)
+{
+ Term *iter;
+
+ a_expr->constant += (n * b_expr->constant);
+
+ iter = b_expr->last_term;
+ while (iter != NULL)
+ {
+ Term *next = iter->prev;
+
+ gtk_constraint_expression_add_variable (a_expr,
+ iter->variable, n * iter->coefficient,
+ subject,
+ solver);
+
+ iter = next;
+ }
+}
+
+/*< private >
+ * gtk_constraint_expression_plus_constant:
+ * @expression: a #GtkConstraintExpression
+ * @constant: a constant value
+ *
+ * Adds a @constant value to the @expression.
+ *
+ * This is the equivalent of creating a new #GtkConstraintExpression for
+ * the @constant and calling gtk_constraint_expression_add_expression().
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_plus_constant (GtkConstraintExpression *expression,
+ double constant)
+{
+ GtkConstraintExpression *e;
+
+ e = gtk_constraint_expression_new (constant);
+ gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL);
+ gtk_constraint_expression_unref (e);
+
+ return expression;
+}
+
+/*< private >
+ * gtk_constraint_expression_minus_constant:
+ * @expression: a #GtkConstraintExpression
+ * @constant: a constant value
+ *
+ * Removes a @constant value from the @expression.
+ *
+ * This is the equivalent of creating a new #GtkConstraintExpression for
+ * the inverse of @constant and calling gtk_constraint_expression_add_expression().
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_minus_constant (GtkConstraintExpression *expression,
+ double constant)
+{
+ return gtk_constraint_expression_plus_constant (expression, constant * -1.0);
+}
+
+/*< private >
+ * gtk_constraint_expression_plus_variable:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ *
+ * Adds a @variable to the @expression.
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_plus_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable)
+{
+ GtkConstraintExpression *e;
+
+ e = gtk_constraint_expression_new_from_variable (variable);
+ gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL);
+ gtk_constraint_expression_unref (e);
+
+ return expression;
+}
+
+/*< private >
+ * gtk_constraint_expression_minus_variable:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ *
+ * Subtracts a @variable from the @expression.
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_minus_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable)
+{
+ GtkConstraintExpression *e;
+
+ e = gtk_constraint_expression_new_from_variable (variable);
+ gtk_constraint_expression_add_expression (expression, e, -1.0, NULL, NULL);
+ gtk_constraint_expression_unref (e);
+
+ return expression;
+}
+
+/*< private >
+ * gtk_constraint_expression_multiply_by:
+ * @expression: a #GtkConstraintExpression
+ * @factor: the multiplication factor
+ *
+ * Multiplies the constant part and the coefficient of all terms
+ * in @expression with the given @factor.
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_multiply_by (GtkConstraintExpression *expression,
+ double factor)
+{
+ GHashTableIter iter;
+ gpointer value_p;
+
+ expression->constant *= factor;
+
+ if (expression->terms == NULL)
+ return expression;
+
+ g_hash_table_iter_init (&iter, expression->terms);
+ while (g_hash_table_iter_next (&iter, NULL, &value_p))
+ {
+ Term *t = value_p;
+
+ t->coefficient *= factor;
+ }
+
+ return expression;
+}
+
+/*< private >
+ * gtk_constraint_expression_divide_by:
+ * @expression: a #GtkConstraintExpression
+ * @factor: the division factor
+ *
+ * Divides the constant part and the coefficient of all terms
+ * in @expression by the given @factor.
+ *
+ * Returns: the @expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_divide_by (GtkConstraintExpression *expression,
+ double factor)
+{
+ if (G_APPROX_VALUE (factor, 0.0, 0.001))
+ return expression;
+
+ return gtk_constraint_expression_multiply_by (expression, 1.0 / factor);
+}
+
+/*< private >
+ * gtk_constraint_expression_new_subject:
+ * @expression: a #GtkConstraintExpression
+ * @subject: a #GtkConstraintVariable part of @expression
+ *
+ * Modifies @expression to have a new @subject.
+ *
+ * A #GtkConstraintExpression is a linear expression in the form of
+ * `@expression = 0`. If @expression contains @subject, for instance:
+ *
+ * |[
+ * c + (a × @subject) + (a1 × v1) + … + (an × vn) = 0
+ * ]|
+ *
+ * this function will make @subject the new subject of the expression:
+ *
+ * |[
+ * subject = - (c / a) - ((a1 / a) × v1) - … - ((an / a) × vn) = 0
+ * ]|
+ *
+ * The term @subject is removed from the @expression.
+ *
+ * Returns: the reciprocal of the coefficient of @subject, so we
+ * can use this function in gtk_constraint_expression_change_subject()
+ */
+double
+gtk_constraint_expression_new_subject (GtkConstraintExpression *expression,
+ GtkConstraintVariable *subject)
+{
+ double reciprocal = 1.0;
+ Term *term;
+
+ g_assert (!gtk_constraint_expression_is_constant (expression));
+
+ term = g_hash_table_lookup (expression->terms, subject);
+ g_assert (term != NULL);
+ g_assert (!G_APPROX_VALUE (term->coefficient, 0.0, 0.001));
+
+ reciprocal = 1.0 / term->coefficient;
+
+ gtk_constraint_expression_remove_term (expression, subject);
+ gtk_constraint_expression_multiply_by (expression, -reciprocal);
+
+ return reciprocal;
+}
+
+/*< private >
+ * gtk_constraint_expression_change_subject:
+ * @expression: a #GtkConstraintExpression
+ * @old_subject: the old subject #GtkConstraintVariable of @expression
+ * @new_subject: the new subject #GtkConstraintVariable of @expression
+ *
+ * Turns an @expression in the form of:
+ *
+ * |[
+ * old_subject = c + (a × new_subject) + (a1 × v1) + … + (an × vn)
+ * ]|
+ *
+ * into the form of:
+ *
+ * |[
+ * new_subject = -c / a + old_subject / a - ((a1 / a) × v1) - … - ((an / a) × vn)
+ * ]|
+ *
+ * Which means resolving @expression for @new_subject.
+ */
+void
+gtk_constraint_expression_change_subject (GtkConstraintExpression *expression,
+ GtkConstraintVariable *old_subject,
+ GtkConstraintVariable *new_subject)
+{
+ double reciprocal;
+
+ g_return_if_fail (expression != NULL);
+ g_return_if_fail (old_subject != NULL);
+ g_return_if_fail (new_subject != NULL);
+
+ reciprocal = gtk_constraint_expression_new_subject (expression, new_subject);
+ gtk_constraint_expression_set_variable (expression, old_subject, reciprocal);
+}
+
+/*< private >
+ * gtk_constraint_expression_get_coefficient:
+ * @expression: a #GtkConstraintExpression
+ * @variable: a #GtkConstraintVariable
+ *
+ * Retrieves the coefficient of the term for @variable inside @expression.
+ *
+ * Returns: the coefficient of @variable
+ */
+double
+gtk_constraint_expression_get_coefficient (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable)
+{
+ const Term *term;
+
+ g_return_val_if_fail (expression != NULL, 0.0);
+ g_return_val_if_fail (variable != NULL, 0.0);
+
+ if (expression->terms == NULL)
+ return 0.0;
+
+ term = g_hash_table_lookup (expression->terms, variable);
+ if (term == NULL)
+ return 0.0;
+
+ return term->coefficient;
+}
+
+/*< private >
+ * gtk_constraint_expression_substitute_out:
+ * @expression: a #GtkConstraintExpression
+ * @out_var: the variable to replace
+ * @expr: the expression used to replace @out_var
+ * @subject: (nullable): a #GtkConstraintVariable
+ * @solver: (nullable): a #GtkConstraintSolver
+ *
+ * Replaces every term containing @out_var inside @expression with @expr.
+ *
+ * If @solver is not %NULL, this function will notify the #GtkConstraintSolver
+ * for every variable added to or removed from @expression.
+ */
+void
+gtk_constraint_expression_substitute_out (GtkConstraintExpression *expression,
+ GtkConstraintVariable *out_var,
+ GtkConstraintExpression *expr,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver)
+{
+ double multiplier;
+ Term *iter;
+
+ if (expression->terms == NULL)
+ return;
+
+ multiplier = gtk_constraint_expression_get_coefficient (expression, out_var);
+ gtk_constraint_expression_remove_term (expression, out_var);
+
+ expression->constant = expression->constant + multiplier * expr->constant;
+
+ iter = expr->first_term;
+ while (iter != NULL)
+ {
+ GtkConstraintVariable *clv = iter->variable;
+ double coeff = iter->coefficient;
+ Term *next = iter->next;
+
+ if (expression->terms != NULL &&
+ g_hash_table_contains (expression->terms, clv))
+ {
+ double old_coefficient = gtk_constraint_expression_get_coefficient (expression, clv);
+ double new_coefficient = old_coefficient + multiplier * coeff;
+
+ if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001))
+ {
+ if (solver != NULL)
+ gtk_constraint_solver_note_removed_variable (solver, clv, subject);
+
+ gtk_constraint_expression_remove_term (expression, clv);
+ }
+ else
+ gtk_constraint_expression_set_variable (expression, clv, new_coefficient);
+ }
+ else
+ {
+ gtk_constraint_expression_set_variable (expression, clv, multiplier * coeff);
+
+ if (solver != NULL)
+ gtk_constraint_solver_note_added_variable (solver, clv, subject);
+ }
+
+ iter = next;
+ }
+}
+
+/*< private >
+ * gtk_constraint_expression_get_pivotable_variable:
+ * @expression: a #GtkConstraintExpression
+ *
+ * Retrieves the first #GtkConstraintVariable in @expression that
+ * is marked as pivotable.
+ *
+ * Returns: (transfer none) (nullable): a #GtkConstraintVariable
+ */
+GtkConstraintVariable *
+gtk_constraint_expression_get_pivotable_variable (GtkConstraintExpression *expression)
+{
+ Term *iter;
+
+ if (expression->terms == NULL)
+ {
+ g_critical ("Expression %p is a constant", expression);
+ return NULL;
+ }
+
+ iter = expression->first_term;
+ while (iter != NULL)
+ {
+ Term *next = iter->next;
+
+ if (gtk_constraint_variable_is_pivotable (iter->variable))
+ return iter->variable;
+
+ iter = next;
+ }
+
+ return NULL;
+}
+
+/*< private >
+ * gtk_constraint_expression_to_string:
+ * @expression: a #GtkConstraintExpression
+ *
+ * Creates a string containing @expression.
+ *
+ * This function is only useful for debugging.
+ *
+ * Returns: (transfer full): a string containing the given expression
+ */
+char *
+gtk_constraint_expression_to_string (const GtkConstraintExpression *expression)
+{
+ gboolean needs_plus = FALSE;
+ GString *buf;
+ Term *iter;
+
+ if (expression == NULL)
+ return g_strdup ("<null>");
+
+ buf = g_string_new (NULL);
+
+ if (!G_APPROX_VALUE (expression->constant, 0.0, 0.001))
+ {
+ g_string_append_printf (buf, "%g", expression->constant);
+
+ if (expression->terms != NULL)
+ needs_plus = TRUE;
+ }
+
+ if (expression->terms == NULL)
+ return g_string_free (buf, FALSE);
+
+ iter = expression->first_term;
+ while (iter != NULL)
+ {
+ char *str = gtk_constraint_variable_to_string (iter->variable);
+ Term *next = iter->next;
+
+ if (needs_plus)
+ g_string_append (buf, " + ");
+
+ if (G_APPROX_VALUE (iter->coefficient, 1.0, 0.001))
+ g_string_append_printf (buf, "%s", str);
+ else
+ g_string_append_printf (buf, "(%g * %s)", iter->coefficient, str);
+
+ g_free (str);
+
+ if (!needs_plus)
+ needs_plus = TRUE;
+
+ iter = next;
+ }
+
+ return g_string_free (buf, FALSE);
+}
+
+/* Keep in sync with GtkConstraintExpressionIter */
+typedef struct {
+ GtkConstraintExpression *expression;
+ Term *current;
+ gint64 age;
+} RealExpressionIter;
+
+#define REAL_EXPRESSION_ITER(i) ((RealExpressionIter *) (i))
+
+/*< private >
+ * gtk_constraint_expression_iter_init:
+ * @iter: a #GtkConstraintExpressionIter
+ * @expression: a #GtkConstraintExpression
+ *
+ * Initializes an iterator over @expression.
+ */
+void
+gtk_constraint_expression_iter_init (GtkConstraintExpressionIter *iter,
+ GtkConstraintExpression *expression)
+{
+ RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);
+
+ riter->expression = expression;
+ riter->current = NULL;
+ riter->age = expression->age;
+}
+
+/*< private >
+ * gtk_constraint_expression_iter_next:
+ * @iter: a valid #GtkConstraintExpressionIter
+ * @variable: (out): the variable of the next term
+ * @coefficient: (out): the coefficient of the next term
+ *
+ * Moves the given #GtkConstraintExpressionIter forwards to the next
+ * term in the expression, starting from the first term.
+ *
+ * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator
+ * has reached the end of the terms of the expression
+ */
+gboolean
+gtk_constraint_expression_iter_next (GtkConstraintExpressionIter *iter,
+ GtkConstraintVariable **variable,
+ double *coefficient)
+{
+ RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);
+
+ g_assert (riter->age == riter->expression->age);
+
+ if (riter->current == NULL)
+ riter->current = riter->expression->first_term;
+ else
+ riter->current = riter->current->next;
+
+ if (riter->current != NULL)
+ {
+ *coefficient = riter->current->coefficient;
+ *variable = riter->current->variable;
+ }
+
+ return riter->current != NULL;
+}
+
+/*< private >
+ * gtk_constraint_expression_iter_prev:
+ * @iter: a valid #GtkConstraintExpressionIter
+ * @variable: (out): the variable of the previous term
+ * @coefficient: (out): the coefficient of the previous term
+ *
+ * Moves the given #GtkConstraintExpressionIter backwards to the previous
+ * term in the expression, starting from the last term.
+ *
+ * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator
+ * has reached the beginning of the terms of the expression
+ */
+gboolean
+gtk_constraint_expression_iter_prev (GtkConstraintExpressionIter *iter,
+ GtkConstraintVariable **variable,
+ double *coefficient)
+{
+ RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);
+
+ g_assert (riter->age == riter->expression->age);
+
+ if (riter->current == NULL)
+ riter->current = riter->expression->last_term;
+ else
+ riter->current = riter->current->prev;
+
+ if (riter->current != NULL)
+ {
+ *coefficient = riter->current->coefficient;
+ *variable = riter->current->variable;
+ }
+
+ return riter->current != NULL;
+}
+
+typedef enum {
+ BUILDER_OP_NONE,
+ BUILDER_OP_PLUS,
+ BUILDER_OP_MINUS,
+ BUILDER_OP_MULTIPLY,
+ BUILDER_OP_DIVIDE
+} BuilderOpType;
+
+typedef struct
+{
+ GtkConstraintExpression *expression;
+ GtkConstraintSolver *solver;
+ int op;
+} RealExpressionBuilder;
+
+#define REAL_EXPRESSION_BUILDER(b) ((RealExpressionBuilder *) (b))
+
+/*< private >
+ * gtk_constraint_expression_builder_init:
+ * @builder: a #GtkConstraintExpressionBuilder
+ * @solver: a #GtkConstraintSolver
+ *
+ * Initializes the given #GtkConstraintExpressionBuilder for the
+ * given #GtkConstraintSolver.
+ *
+ * You can use the @builder to construct expressions to be added to the
+ * @solver, in the form of constraints.
+ *
+ * A typical use is:
+ *
+ * |[<!-- language="C" -->
+ * GtkConstraintExpressionBuilder builder;
+ *
+ * // "solver" is set in another part of the code
+ * gtk_constraint_expression_builder_init (&builder, solver);
+ *
+ * // "width" is set in another part of the code
+ * gtk_constraint_expression_builder_term (&builder, width);
+ * gtk_constraint_expression_builder_divide_by (&builder);
+ * gtk_constraint_expression_builder_constant (&builder, 2.0);
+ *
+ * // "left" is set in another part of the code
+ * gtk_constraint_expression_builder_plus (&builder);
+ * gtk_constraint_expression_builder_term (&builder, left);
+ *
+ * // "expr" now contains the following expression:
+ * // width / 2.0 + left
+ * GtkConstraintExpression *expr =
+ * gtk_constraint_expression_builder_finish (&builder);
+ *
+ * // The builder is inert, and can be re-used by calling
+ * // gtk_constraint_expression_builder_init() again.
+ * ]|
+ */
+void
+gtk_constraint_expression_builder_init (GtkConstraintExpressionBuilder *builder,
+ GtkConstraintSolver *solver)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->solver = solver;
+ rbuilder->expression = gtk_constraint_expression_new (0);
+ rbuilder->op = BUILDER_OP_NONE;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_term:
+ * @builder: a #GtkConstraintExpressionBuilder
+ * @term: a #GtkConstraintVariable
+ *
+ * Adds a variable @term to the @builder.
+ */
+void
+gtk_constraint_expression_builder_term (GtkConstraintExpressionBuilder *builder,
+ GtkConstraintVariable *term)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+ GtkConstraintExpression *expr;
+
+ expr = gtk_constraint_expression_new_from_variable (term);
+
+ switch (rbuilder->op)
+ {
+ case BUILDER_OP_NONE:
+ g_clear_pointer (&rbuilder->expression, gtk_constraint_expression_unref);
+ rbuilder->expression = g_steal_pointer (&expr);
+ break;
+
+ case BUILDER_OP_PLUS:
+ gtk_constraint_expression_add_expression (rbuilder->expression,
+ expr, 1.0,
+ NULL,
+ NULL);
+ gtk_constraint_expression_unref (expr);
+ break;
+
+ case BUILDER_OP_MINUS:
+ gtk_constraint_expression_add_expression (rbuilder->expression,
+ expr, -1.0,
+ NULL,
+ NULL);
+ gtk_constraint_expression_unref (expr);
+ break;
+
+ default:
+ break;
+ }
+
+ rbuilder->op = BUILDER_OP_NONE;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_plus:
+ * @builder: a #GtkConstraintExpressionBuilder
+ *
+ * Adds a plus operator to the @builder.
+ */
+void
+gtk_constraint_expression_builder_plus (GtkConstraintExpressionBuilder *builder)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->op = BUILDER_OP_PLUS;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_minus:
+ * @builder: a #GtkConstraintExpressionBuilder
+ *
+ * Adds a minus operator to the @builder.
+ */
+void
+gtk_constraint_expression_builder_minus (GtkConstraintExpressionBuilder *builder)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->op = BUILDER_OP_MINUS;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_divide_by:
+ * @builder: a #GtkConstraintExpressionBuilder
+ *
+ * Adds a division operator to the @builder.
+ */
+void
+gtk_constraint_expression_builder_divide_by (GtkConstraintExpressionBuilder *builder)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->op = BUILDER_OP_DIVIDE;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_multiply_by:
+ * @builder: a #GtkConstraintExpressionBuilder
+ *
+ * Adds a multiplication operator to the @builder.
+ */
+void
+gtk_constraint_expression_builder_multiply_by (GtkConstraintExpressionBuilder *builder)
+
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->op = BUILDER_OP_MULTIPLY;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_constant:
+ * @builder: a #GtkConstraintExpressionBuilder
+ * @value: a constant value
+ *
+ * Adds a constant value to the @builder.
+ */
+void
+gtk_constraint_expression_builder_constant (GtkConstraintExpressionBuilder *builder,
+ double value)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ switch (rbuilder->op)
+ {
+ case BUILDER_OP_NONE:
+ gtk_constraint_expression_set_constant (rbuilder->expression, value);
+ break;
+
+ case BUILDER_OP_PLUS:
+ gtk_constraint_expression_plus_constant (rbuilder->expression, value);
+ break;
+
+ case BUILDER_OP_MINUS:
+ gtk_constraint_expression_minus_constant (rbuilder->expression, value);
+ break;
+
+ case BUILDER_OP_MULTIPLY:
+ gtk_constraint_expression_multiply_by (rbuilder->expression, value);
+ break;
+
+ case BUILDER_OP_DIVIDE:
+ gtk_constraint_expression_divide_by (rbuilder->expression, value);
+ break;
+
+ default:
+ break;
+ }
+
+ rbuilder->op = BUILDER_OP_NONE;
+}
+
+/*< private >
+ * gtk_constraint_expression_builder_finish:
+ * @builder: a #GtkConstraintExpressionBuilder
+ *
+ * Closes the given expression builder, and returns the expression.
+ *
+ * You can only call this function once.
+ *
+ * Returns: (transfer full): the built expression
+ */
+GtkConstraintExpression *
+gtk_constraint_expression_builder_finish (GtkConstraintExpressionBuilder *builder)
+{
+ RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
+
+ rbuilder->solver = NULL;
+ rbuilder->op = BUILDER_OP_NONE;
+
+ return g_steal_pointer (&rbuilder->expression);
+}
+
+/* }}} */