From 5e8d5dafc195c406375eba8e22dbd751623e4ccb Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 25 Jun 2019 04:53:39 +0000 Subject: Add a constraint-based grid This is implemented as a auxiliary object that generates a bunch of constraints (and variables) when added to a constraint layout. Maybe this could be generalized to a 'constraint set' concept, if we come up with other layouts that we want to reimplement in this way. --- gtk/gtk.h | 1 + gtk/gtkconstraintlayout.c | 233 ++++++++++++++++++++++++++++++++++++++++- gtk/gtkconstraintlayout.h | 5 + gtk/gtkgridconstraint.c | 206 ++++++++++++++++++++++++++++++++++++ gtk/gtkgridconstraint.h | 49 +++++++++ gtk/gtkgridconstraintprivate.h | 47 +++++++++ gtk/meson.build | 1 + 7 files changed, 541 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkgridconstraint.c create mode 100644 gtk/gtkgridconstraint.h create mode 100644 gtk/gtkgridconstraintprivate.h diff --git a/gtk/gtk.h b/gtk/gtk.h index ecf4829339..59f441bd70 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -132,6 +132,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtkconstraintlayout.c b/gtk/gtkconstraintlayout.c index bbdd27746c..f8df73f3ca 100644 --- a/gtk/gtkconstraintlayout.c +++ b/gtk/gtkconstraintlayout.c @@ -62,6 +62,7 @@ #include "gtkconstraintlayout.h" #include "gtkconstraintprivate.h" +#include "gtkgridconstraintprivate.h" #include "gtkconstraintexpressionprivate.h" #include "gtkconstraintsolverprivate.h" #include "gtklayoutchild.h" @@ -105,6 +106,8 @@ struct _GtkConstraintLayout * parent widget, using the public API objects. */ GHashTable *constraints; + + GHashTable *grid_constraints; }; G_DEFINE_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK_TYPE_LAYOUT_CHILD) @@ -361,6 +364,7 @@ gtk_constraint_layout_finalize (GObject *gobject) g_clear_pointer (&self->bound_attributes, g_hash_table_unref); g_clear_pointer (&self->constraints, g_hash_table_unref); + g_clear_pointer (&self->grid_constraints, g_hash_table_unref); G_OBJECT_CLASS (gtk_constraint_layout_parent_class)->finalize (gobject); } @@ -977,6 +981,9 @@ gtk_constraint_layout_allocate (GtkLayoutManager *manager, gtk_constraint_solver_remove_constraint (solver, stay_l); } +static void layout_add_grid_constraint (GtkConstraintLayout *manager, + GtkGridConstraint *constraint); + static void gtk_constraint_layout_root (GtkLayoutManager *manager) { @@ -996,9 +1003,15 @@ gtk_constraint_layout_root (GtkLayoutManager *manager) while (g_hash_table_iter_next (&iter, &key, NULL)) { GtkConstraint *constraint = key; - layout_add_constraint (self, constraint); } + + g_hash_table_iter_init (&iter, self->grid_constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + GtkGridConstraint *constraint = key; + layout_add_grid_constraint (self, constraint); + } } static void @@ -1020,6 +1033,14 @@ gtk_constraint_layout_unroot (GtkLayoutManager *manager) gtk_constraint_detach (constraint); } + g_hash_table_iter_init (&iter, self->grid_constraints); + while (g_hash_table_iter_next (&iter, &key, NULL)) + { + GtkGridConstraint *constraint = key; + + gtk_grid_constraint_detach (constraint); + } + self->solver = NULL; } @@ -1052,6 +1073,10 @@ gtk_constraint_layout_init (GtkConstraintLayout *self) g_hash_table_new_full (NULL, NULL, (GDestroyNotify) g_object_unref, NULL); + self->grid_constraints = + g_hash_table_new_full (NULL, NULL, + (GDestroyNotify) g_object_unref, + NULL); } /** @@ -1121,3 +1146,209 @@ gtk_constraint_layout_remove_constraint (GtkConstraintLayout *manager, gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (manager)); } + +void +gtk_constraint_layout_add_grid_constraint (GtkConstraintLayout *manager, + GtkGridConstraint *constraint) +{ + g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (manager)); + g_return_if_fail (GTK_IS_GRID_CONSTRAINT (constraint)); + g_return_if_fail (!gtk_grid_constraint_is_attached (constraint)); + + layout_add_grid_constraint (manager, constraint); + + g_hash_table_add (manager->grid_constraints, constraint); +} + +static GtkConstraintVariable ** +allocate_variables (GtkConstraintSolver *solver, + const char *name, + int n) +{ + GtkConstraintVariable **vars; + int i; + + vars = g_new (GtkConstraintVariable *, n); + for (i = 0; i < n; i++) + { + char *vname = g_strdup_printf ("%s%d", name, i); + vars[i] = gtk_constraint_solver_create_variable (solver, NULL, vname, 0.0); + } + + return vars; +} + +#if 0 +static void +add_ordering_constraints (GtkConstraintSolver *solver, + GtkConstraintVariable **vars, + int n_vars, + GPtrArray *refs) +{ + int i; + + for (i = 1; i < n_vars; i++) + { + GtkConstraintRef *ref; + + ref = gtk_constraint_solver_add_constraint (solver, + vars[i], + GTK_CONSTRAINT_RELATION_GE, + gtk_constraint_expression_new_from_variable (vars[i - 1]), + GTK_CONSTRAINT_WEIGHT_MEDIUM); + g_ptr_array_add (refs, ref); + } +} +#endif + +static void +add_homogeneous_constraints (GtkConstraintSolver *solver, + GtkConstraintVariable **vars, + int n_vars, + GPtrArray *refs) +{ + int i; + + for (i = 2; i < n_vars; i++) + { + GtkConstraintExpressionBuilder builder; + GtkConstraintRef *ref; + + gtk_constraint_expression_builder_init (&builder, solver); + gtk_constraint_expression_builder_term (&builder, vars[i]); + gtk_constraint_expression_builder_plus (&builder); + gtk_constraint_expression_builder_term (&builder, vars[i - 2]); + gtk_constraint_expression_builder_divide_by (&builder); + gtk_constraint_expression_builder_constant (&builder, 2.0); + + ref = gtk_constraint_solver_add_constraint (solver, + vars[i - 1], + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_builder_finish (&builder), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + g_ptr_array_add (refs, ref); + } +} + +static void +add_child_constraints (GtkConstraintLayout *manager, + GtkConstraintSolver *solver, + GtkGridConstraintChild *child, + GtkConstraintVariable **rows, + GtkConstraintVariable **cols, + GPtrArray *refs) +{ + GtkConstraintLayoutChild *info; + GtkConstraintVariable *var; + GtkConstraintVariable *var1; + GtkConstraintRef *ref; + + info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (manager), child->child)); + + var = get_child_attribute (info, solver, child->child, GTK_CONSTRAINT_ATTRIBUTE_LEFT); + var1 = cols[child->left]; + + ref = gtk_constraint_solver_add_constraint (solver, + var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new_from_variable (var1), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + g_ptr_array_add (refs, ref); + + var = get_child_attribute (info, solver, child->child, GTK_CONSTRAINT_ATTRIBUTE_RIGHT); + var1 = cols[child->right]; + + ref = gtk_constraint_solver_add_constraint (solver, + var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new_from_variable (var1), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + g_ptr_array_add (refs, ref); + + var = get_child_attribute (info, solver, child->child, GTK_CONSTRAINT_ATTRIBUTE_TOP); + var1 = rows[child->top]; + + ref = gtk_constraint_solver_add_constraint (solver, + var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new_from_variable (var1), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + g_ptr_array_add (refs, ref); + + var = get_child_attribute (info, solver, child->child, GTK_CONSTRAINT_ATTRIBUTE_BOTTOM); + var1 = rows[child->bottom]; + + ref = gtk_constraint_solver_add_constraint (solver, + var, + GTK_CONSTRAINT_RELATION_EQ, + gtk_constraint_expression_new_from_variable (var1), + GTK_CONSTRAINT_WEIGHT_REQUIRED); + g_ptr_array_add (refs, ref); +} + +static void +layout_add_grid_constraint (GtkConstraintLayout *manager, + GtkGridConstraint *constraint) +{ + GtkWidget *layout_widget; + GtkConstraintSolver *solver; + GtkConstraintVariable **rows; + GtkConstraintVariable **cols; + int n_rows, n_cols; + GPtrArray *refs; + int i; + + if (gtk_grid_constraint_is_attached (constraint)) + return; + + layout_widget = gtk_layout_manager_get_widget (GTK_LAYOUT_MANAGER (manager)); + if (layout_widget == NULL) + return; + + solver = gtk_constraint_layout_get_solver (manager); + if (solver == NULL) + return; + + gtk_constraint_solver_freeze (solver); + + refs = g_ptr_array_new (); + + n_rows = n_cols = 0; + for (i = 0; i < constraint->children->len; i++) + { + GtkGridConstraintChild *child = g_ptr_array_index (constraint->children, i); + n_rows = MAX (n_rows, child->bottom); + n_cols = MAX (n_cols, child->right); + } + n_rows++; + n_cols++; + + rows = allocate_variables (solver, "row", n_rows); + cols = allocate_variables (solver, "col", n_cols); + +#if 0 + //FIXME for some reason, these 'obvious' constraints + // make things unstable (and they are not really needed) + add_ordering_constraints (solver, rows, n_rows, refs); + add_ordering_constraints (solver, cols, n_cols, refs); +#endif + + if (constraint->row_homogeneous) + add_homogeneous_constraints (solver, rows, n_rows, refs); + if (constraint->column_homogeneous) + add_homogeneous_constraints (solver, cols, n_cols, refs); + + for (i = 0; i < constraint->children->len; i++) + { + GtkGridConstraintChild *child = g_ptr_array_index (constraint->children, i); + add_child_constraints (manager, solver, child, rows, cols, refs); + } + + gtk_grid_constraint_attach (constraint, solver, refs); + + g_free (rows); + g_free (cols); + g_ptr_array_unref (refs); + + gtk_constraint_solver_thaw (solver); +} diff --git a/gtk/gtkconstraintlayout.h b/gtk/gtkconstraintlayout.h index b5eba7b125..9464f3bf51 100644 --- a/gtk/gtkconstraintlayout.h +++ b/gtk/gtkconstraintlayout.h @@ -20,6 +20,7 @@ #include #include +#include G_BEGIN_DECLS @@ -45,6 +46,10 @@ GDK_AVAILABLE_IN_ALL void gtk_constraint_layout_remove_constraint (GtkConstraintLayout *manager, GtkConstraint *constraint); +GDK_AVAILABLE_IN_ALL +void gtk_constraint_layout_add_grid_constraint (GtkConstraintLayout *manager, + GtkGridConstraint *constraint); + /** * GtkConstraintLayoutChild: * diff --git a/gtk/gtkgridconstraint.c b/gtk/gtkgridconstraint.c new file mode 100644 index 0000000000..fe22a686fc --- /dev/null +++ b/gtk/gtkgridconstraint.c @@ -0,0 +1,206 @@ +/* gtkgridconstraint.c: Make a grid with constraints + * Copyright 2019 Red Hat, inc. + * + * 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 . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include "gtkgridconstraint.h" +#include "gtkgridconstraintprivate.h" + +#include "gtkintl.h" +#include "gtktypebuiltins.h" + +enum { + PROP_ROW_HOMOGENEOUS = 1, + PROP_COLUMN_HOMOGENEOUS, + N_PROPERTIES +}; + +static GParamSpec *obj_props[N_PROPERTIES]; + +G_DEFINE_TYPE (GtkGridConstraint, gtk_grid_constraint, G_TYPE_OBJECT) + +static void +gtk_constraint_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkGridConstraint *self = GTK_GRID_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_ROW_HOMOGENEOUS: + self->row_homogeneous = g_value_get_boolean (value); + break; + + case PROP_COLUMN_HOMOGENEOUS: + self->column_homogeneous = g_value_get_boolean (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) +{ + GtkGridConstraint *self = GTK_GRID_CONSTRAINT (gobject); + + switch (prop_id) + { + case PROP_ROW_HOMOGENEOUS: + g_value_set_boolean (value, self->row_homogeneous); + break; + + case PROP_COLUMN_HOMOGENEOUS: + g_value_set_boolean (value, self->column_homogeneous); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +gtk_constraint_finalize (GObject *gobject) +{ + GtkGridConstraint *self = GTK_GRID_CONSTRAINT (gobject); + + gtk_grid_constraint_detach (self); + + g_ptr_array_free (self->children, TRUE); + + G_OBJECT_CLASS (gtk_grid_constraint_parent_class)->finalize (gobject); +} + +static void +gtk_grid_constraint_class_init (GtkGridConstraintClass *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; + + /** + * GtkGridConstraint:row-homogeneous: + * + * Whether to make all rows the same height. + */ + obj_props[PROP_ROW_HOMOGENEOUS] = + g_param_spec_boolean ("row-homogeneous", + P_("Row homogeneous"), + P_("Row homogeneous"), + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * GtkGridConstraint:column-homogeneous: + * + * Whether to make all columns the same width. + */ + obj_props[PROP_COLUMN_HOMOGENEOUS] = + g_param_spec_boolean ("column-homogeneous", + P_("Column homogeneous"), + P_("Column homogeneous"), + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, obj_props); +} + +static void +gtk_grid_constraint_init (GtkGridConstraint *self) +{ + self->children = g_ptr_array_new_with_free_func (g_free); +} + +GtkGridConstraint * +gtk_grid_constraint_new (void) +{ + return g_object_new (GTK_TYPE_GRID_CONSTRAINT, NULL); +} + +void +gtk_grid_constraint_add (GtkGridConstraint *self, + GtkWidget *child, + int left, + int right, + int top, + int bottom) +{ + GtkGridConstraintChild *data; + + g_return_if_fail (GTK_IS_GRID_CONSTRAINT (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + g_return_if_fail (left < right); + g_return_if_fail (top < bottom); + g_return_if_fail (self->refs == NULL); + + data = g_new0 (GtkGridConstraintChild, 1); + + data->child = child; + data->left = left; + data->right = right; + data->top = top; + data->bottom = bottom; + + g_ptr_array_add (self->children, data); +} + +gboolean +gtk_grid_constraint_is_attached (GtkGridConstraint *self) +{ + return self->refs != NULL; +} + +void +gtk_grid_constraint_attach (GtkGridConstraint *self, + GtkConstraintSolver *solver, + GPtrArray *refs) +{ + g_return_if_fail (self->refs == NULL); + + self->solver = solver; + self->refs = g_ptr_array_ref (refs); +} + +void gtk_grid_constraint_detach (GtkGridConstraint *self) +{ + int i; + + if (self->refs == NULL) + return; + + for (i = 0; i < self->refs->len; i++) + { + GtkConstraintRef *ref = g_ptr_array_index (self->refs, i); + gtk_constraint_solver_remove_constraint (self->solver, ref); + } + + g_clear_pointer (&self->refs, g_ptr_array_unref); +} diff --git a/gtk/gtkgridconstraint.h b/gtk/gtkgridconstraint.h new file mode 100644 index 0000000000..fb35f52057 --- /dev/null +++ b/gtk/gtkgridconstraint.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * 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 . + */ + +#ifndef __GTK_GRID_CONSTRAINT_H__ +#define __GTK_GRID_CONSTRAINT_H__ + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_GRID_CONSTRAINT (gtk_grid_constraint_get_type ()) + +/** + * GtkGridConstraint: + * + * An object used for managing constraints for children in + * a constraints layout that are to be arranged in a grid. + */ +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkGridConstraint, gtk_grid_constraint, GTK, GRID_CONSTRAINT, GObject) + +GDK_AVAILABLE_IN_ALL +GtkGridConstraint * gtk_grid_constraint_new (void); + +GDK_AVAILABLE_IN_ALL +void gtk_grid_constraint_add (GtkGridConstraint *self, + GtkWidget *child, + int left, + int right, + int top, + int bottom); + +G_END_DECLS + +#endif /* __GTK_GRID_CONSTRAINT_H__ */ diff --git a/gtk/gtkgridconstraintprivate.h b/gtk/gtkgridconstraintprivate.h new file mode 100644 index 0000000000..6ddd85bb0e --- /dev/null +++ b/gtk/gtkgridconstraintprivate.h @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Red Hat, inc. + * + * 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 . + * + * Author: Matthias Clasen + */ + +#include "gtkgridconstraint.h" +#include "gtkconstraintsolverprivate.h" + +typedef struct { + GtkWidget *child; + int left; + int right; + int top; + int bottom; +} GtkGridConstraintChild; + +struct _GtkGridConstraint { + GObject parent; + + gboolean row_homogeneous; + gboolean column_homogeneous; + + GPtrArray *children; + + GtkConstraintSolver *solver; + GPtrArray *refs; +}; + +gboolean gtk_grid_constraint_is_attached (GtkGridConstraint *constraint); +void gtk_grid_constraint_attach (GtkGridConstraint *constraint, + GtkConstraintSolver *solver, + GPtrArray *refs); +void gtk_grid_constraint_detach (GtkGridConstraint *constraint); diff --git a/gtk/meson.build b/gtk/meson.build index a79e3e7ecc..e0fc1f29e9 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -252,6 +252,7 @@ gtk_public_sources = files([ 'gtkgesturezoom.c', 'gtkglarea.c', 'gtkgrid.c', + 'gtkgridconstraint.c', 'gtkgridlayout.c', 'gtkheaderbar.c', 'gtkicontheme.c', -- cgit v1.2.1