summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2019-07-02 14:01:01 +0000
committerMatthias Clasen <mclasen@redhat.com>2019-07-02 14:01:01 +0000
commitb2f15a622db1c74f86ef0e10ebbc0cb8a8bc5f91 (patch)
treeff70b2a1e25b92a78c0f448d62653eb6b4e5428b
parent33bd7051f24862f80cb8149a79090df350b28855 (diff)
parent04aaf02881acee3e603835ac1062f6525de096c8 (diff)
downloadgtk+-b2f15a622db1c74f86ef0e10ebbc0cb8a8bc5f91.tar.gz
Merge branch 'wip/ebassi/constraint-layout' into 'master'
Add constraint-based layout manager Closes #1090 See merge request GNOME/gtk!973
-rw-r--r--demos/constraint-editor/constraint-editor-application.c94
-rw-r--r--demos/constraint-editor/constraint-editor-application.h28
-rw-r--r--demos/constraint-editor/constraint-editor-window.c354
-rw-r--r--demos/constraint-editor/constraint-editor-window.h34
-rw-r--r--demos/constraint-editor/constraint-editor-window.ui68
-rw-r--r--demos/constraint-editor/constraint-editor.c583
-rw-r--r--demos/constraint-editor/constraint-editor.css12
-rw-r--r--demos/constraint-editor/constraint-editor.gresource.xml9
-rw-r--r--demos/constraint-editor/constraint-editor.h29
-rw-r--r--demos/constraint-editor/constraint-editor.ui180
-rw-r--r--demos/constraint-editor/constraint-view-child.c93
-rw-r--r--demos/constraint-editor/constraint-view-child.h44
-rw-r--r--demos/constraint-editor/constraint-view.c378
-rw-r--r--demos/constraint-editor/constraint-view.h44
-rw-r--r--demos/constraint-editor/guide-editor.c387
-rw-r--r--demos/constraint-editor/guide-editor.h28
-rw-r--r--demos/constraint-editor/guide-editor.ui188
-rw-r--r--demos/constraint-editor/main.c28
-rw-r--r--demos/constraint-editor/meson.build19
-rw-r--r--demos/gtk-demo/constraints.c289
-rw-r--r--demos/gtk-demo/constraints2.c245
-rw-r--r--demos/gtk-demo/constraints3.c165
-rw-r--r--demos/gtk-demo/demo.gresource.xml3
-rw-r--r--demos/gtk-demo/meson.build3
-rw-r--r--demos/meson.build1
-rw-r--r--docs/reference/gtk/gtk4-docs.xml3
-rw-r--r--docs/reference/gtk/gtk4-sections.txt80
-rw-r--r--docs/reference/gtk/gtk4.types.in4
-rw-r--r--docs/reference/gtk/meson.build7
-rw-r--r--gtk/gtk.h2
-rw-r--r--gtk/gtkconstraint.c614
-rw-r--r--gtk/gtkconstraint.h100
-rw-r--r--gtk/gtkconstraintexpression.c1833
-rw-r--r--gtk/gtkconstraintexpressionprivate.h279
-rw-r--r--gtk/gtkconstraintguide.c694
-rw-r--r--gtk/gtkconstraintguide.h83
-rw-r--r--gtk/gtkconstraintguideprivate.h38
-rw-r--r--gtk/gtkconstraintlayout.c2118
-rw-r--r--gtk/gtkconstraintlayout.h88
-rw-r--r--gtk/gtkconstraintlayoutprivate.h37
-rw-r--r--gtk/gtkconstraintprivate.h60
-rw-r--r--gtk/gtkconstraintsolver.c2247
-rw-r--r--gtk/gtkconstraintsolverprivate.h117
-rw-r--r--gtk/gtkconstrainttypesprivate.h50
-rw-r--r--gtk/gtkconstraintvflparser.c1227
-rw-r--r--gtk/gtkconstraintvflparserprivate.h76
-rw-r--r--gtk/gtkdebug.h3
-rw-r--r--gtk/gtkenums.h94
-rw-r--r--gtk/gtklayoutmanager.c60
-rw-r--r--gtk/gtklayoutmanager.h7
-rw-r--r--gtk/gtklayoutmanagerprivate.h3
-rw-r--r--gtk/gtkmain.c3
-rw-r--r--gtk/gtkroot.c19
-rw-r--r--gtk/gtkroot.h14
-rw-r--r--gtk/gtkrootprivate.h20
-rw-r--r--gtk/gtkwidget.c16
-rw-r--r--gtk/gtkwindow.c16
-rw-r--r--gtk/meson.build9
-rw-r--r--testsuite/gtk/constraint-solver.c380
-rw-r--r--testsuite/gtk/meson.build5
60 files changed, 13696 insertions, 16 deletions
diff --git a/demos/constraint-editor/constraint-editor-application.c b/demos/constraint-editor/constraint-editor-application.c
new file mode 100644
index 0000000000..09b5d64270
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-application.c
@@ -0,0 +1,94 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor-application.h"
+#include "constraint-editor-window.h"
+
+struct _ConstraintEditorApplication
+{
+ GtkApplication parent_instance;
+};
+
+G_DEFINE_TYPE(ConstraintEditorApplication, constraint_editor_application, GTK_TYPE_APPLICATION);
+
+static void
+constraint_editor_application_init (ConstraintEditorApplication *app)
+{
+}
+
+static void
+quit_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer data)
+{
+ g_application_quit (G_APPLICATION (data));
+}
+
+static GActionEntry app_entries[] =
+{
+ { "quit", quit_activated, NULL, NULL, NULL }
+};
+
+static void
+constraint_editor_application_startup (GApplication *app)
+{
+ const char *quit_accels[2] = { "<Ctrl>Q", NULL };
+ GtkCssProvider *provider;
+
+ G_APPLICATION_CLASS (constraint_editor_application_parent_class)->startup (app);
+
+ g_action_map_add_action_entries (G_ACTION_MAP (app),
+ app_entries, G_N_ELEMENTS (app_entries),
+ app);
+ gtk_application_set_accels_for_action (GTK_APPLICATION (app), "app.quit", quit_accels);
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gtk/gtk4/constraint-editor/constraint-editor.css");
+ gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+static void
+constraint_editor_application_activate (GApplication *app)
+{
+ ConstraintEditorWindow *win;
+
+ win = constraint_editor_window_new (CONSTRAINT_EDITOR_APPLICATION (app));
+ gtk_window_present (GTK_WINDOW (win));
+}
+
+static void
+constraint_editor_application_class_init (ConstraintEditorApplicationClass *class)
+{
+ GApplicationClass *application_class = G_APPLICATION_CLASS (class);
+
+ application_class->startup = constraint_editor_application_startup;
+ application_class->activate = constraint_editor_application_activate;
+}
+
+ConstraintEditorApplication *
+constraint_editor_application_new (void)
+{
+ return g_object_new (CONSTRAINT_EDITOR_APPLICATION_TYPE,
+ "application-id", "org.gtk.gtk4.ConstraintEditor",
+ NULL);
+}
diff --git a/demos/constraint-editor/constraint-editor-application.h b/demos/constraint-editor/constraint-editor-application.h
new file mode 100644
index 0000000000..c7d9fd3048
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-application.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_EDITOR_APPLICATION_TYPE (constraint_editor_application_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditorApplication, constraint_editor_application, CONSTRAINT, EDITOR_APPLICATION, GtkApplication)
+
+ConstraintEditorApplication *constraint_editor_application_new (void);
diff --git a/demos/constraint-editor/constraint-editor-window.c b/demos/constraint-editor/constraint-editor-window.c
new file mode 100644
index 0000000000..a8fa33464d
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.c
@@ -0,0 +1,354 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor-window.h"
+#include "constraint-view.h"
+#include "constraint-editor.h"
+#include "guide-editor.h"
+
+struct _ConstraintEditorWindow
+{
+ GtkApplicationWindow parent_instance;
+
+ GtkWidget *paned;
+ GtkWidget *view;
+ GtkWidget *list;
+};
+
+G_DEFINE_TYPE(ConstraintEditorWindow, constraint_editor_window, GTK_TYPE_APPLICATION_WINDOW);
+
+gboolean
+constraint_editor_window_load (ConstraintEditorWindow *self,
+ GFile *file)
+{
+ GBytes *bytes;
+
+ bytes = g_file_load_bytes (file, NULL, NULL, NULL);
+ if (bytes == NULL)
+ return FALSE;
+
+ if (!g_utf8_validate (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL))
+ {
+ g_bytes_unref (bytes);
+ return FALSE;
+ }
+
+#if 0
+
+ gtk_text_buffer_get_end_iter (self->text_buffer, &end);
+ gtk_text_buffer_insert (self->text_buffer,
+ &end,
+ g_bytes_get_data (bytes, NULL),
+ g_bytes_get_size (bytes));
+#endif
+
+ g_bytes_unref (bytes);
+
+ return TRUE;
+}
+
+static void
+constraint_editor_window_finalize (GObject *object)
+{
+ //ConstraintEditorWindow *self = (ConstraintEditorWindow *)object;
+
+ G_OBJECT_CLASS (constraint_editor_window_parent_class)->finalize (object);
+}
+
+static int child_counter;
+static int guide_counter;
+
+static void
+add_child (ConstraintEditorWindow *win)
+{
+ char *name;
+
+ child_counter++;
+ name = g_strdup_printf ("Child %d", child_counter);
+ constraint_view_add_child (CONSTRAINT_VIEW (win->view), name);
+ g_free (name);
+}
+
+static void
+add_guide (ConstraintEditorWindow *win)
+{
+ char *name;
+ GtkConstraintGuide *guide;
+
+ guide_counter++;
+ name = g_strdup_printf ("Guide %d", guide_counter);
+ guide = g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+ g_object_set_data_full (G_OBJECT (guide), "name", name, g_free);
+
+ constraint_view_add_guide (CONSTRAINT_VIEW (win->view), guide);
+}
+
+static void
+constraint_editor_done (ConstraintEditor *editor,
+ GtkConstraint *constraint,
+ ConstraintEditorWindow *win)
+{
+ GtkConstraint *old_constraint;
+
+ g_object_get (editor, "constraint", &old_constraint, NULL);
+
+ if (old_constraint)
+ constraint_view_remove_constraint (CONSTRAINT_VIEW (win->view), old_constraint);
+
+ constraint_view_add_constraint (CONSTRAINT_VIEW (win->view), constraint);
+
+ g_clear_object (&old_constraint);
+
+ gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW));
+}
+
+static void
+edit_constraint (ConstraintEditorWindow *win,
+ GtkConstraint *constraint)
+{
+ GtkWidget *window;
+ ConstraintEditor *editor;
+ GListModel *model;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ if (constraint)
+ gtk_window_set_title (GTK_WINDOW (window), "Edit Constraint");
+ else
+ gtk_window_set_title (GTK_WINDOW (window), "Create Constraint");
+
+ model = constraint_view_get_model (CONSTRAINT_VIEW (win->view));
+
+ editor = constraint_editor_new (model, constraint);
+
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor));
+
+ g_signal_connect (editor, "done", G_CALLBACK (constraint_editor_done), win);
+
+ gtk_widget_show (window);
+}
+
+static void
+add_constraint (ConstraintEditorWindow *win)
+{
+ edit_constraint (win, NULL);
+}
+
+static void
+guide_editor_done (GuideEditor *editor,
+ GtkConstraintGuide *guide,
+ ConstraintEditorWindow *win)
+{
+ constraint_view_guide_changed (CONSTRAINT_VIEW (win->view), guide);
+ gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (editor), GTK_TYPE_WINDOW));
+}
+
+static void
+edit_guide (ConstraintEditorWindow *win,
+ GtkConstraintGuide *guide)
+{
+ GtkWidget *window;
+ GuideEditor *editor;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+ gtk_window_set_transient_for (GTK_WINDOW (window), GTK_WINDOW (win));
+ gtk_window_set_title (GTK_WINDOW (window), "Edit Guide");
+
+ editor = guide_editor_new (guide);
+ gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor));
+
+ g_signal_connect (editor, "done", G_CALLBACK (guide_editor_done), win);
+ gtk_widget_show (window);
+}
+
+static void
+row_activated (GtkListBox *list,
+ GtkListBoxRow *row,
+ ConstraintEditorWindow *win)
+{
+ GObject *item;
+
+ item = G_OBJECT (g_object_get_data (G_OBJECT (row), "item"));
+
+ if (GTK_IS_CONSTRAINT (item))
+ edit_constraint (win, GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ edit_guide (win, GTK_CONSTRAINT_GUIDE (item));
+}
+
+static void
+constraint_editor_window_class_init (ConstraintEditorWindowClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ g_type_ensure (CONSTRAINT_VIEW_TYPE);
+
+ object_class->finalize = constraint_editor_window_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gtk/gtk4/constraint-editor/constraint-editor-window.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, paned);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, view);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditorWindow, list);
+
+ gtk_widget_class_bind_template_callback (widget_class, add_child);
+ gtk_widget_class_bind_template_callback (widget_class, add_guide);
+ gtk_widget_class_bind_template_callback (widget_class, add_constraint);
+ gtk_widget_class_bind_template_callback (widget_class, row_activated);
+}
+
+static void
+row_edit (GtkButton *button,
+ ConstraintEditorWindow *win)
+{
+ GtkWidget *row;
+ GObject *item;
+
+ row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
+ item = (GObject *)g_object_get_data (G_OBJECT (row), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ edit_constraint (win, GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ edit_guide (win, GTK_CONSTRAINT_GUIDE (item));
+}
+
+static void
+mark_constraints_invalid (ConstraintEditorWindow *win,
+ gpointer removed)
+{
+ GtkWidget *child;
+ GObject *item;
+
+ for (child = gtk_widget_get_first_child (win->list);
+ child;
+ child = gtk_widget_get_next_sibling (child))
+ {
+ item = (GObject *)g_object_get_data (G_OBJECT (child), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ {
+ GtkConstraint *constraint = GTK_CONSTRAINT (item);
+
+ if (gtk_constraint_get_target (constraint) == (GtkConstraintTarget *)removed ||
+ gtk_constraint_get_source (constraint) == (GtkConstraintTarget *)removed)
+ {
+ GtkWidget *button;
+ button = (GtkWidget *)g_object_get_data (G_OBJECT (child), "edit");
+ gtk_button_set_icon_name (GTK_BUTTON (button), "dialog-warning-symbolic");
+ gtk_widget_set_tooltip_text (button, "Constraint is invalid");
+ }
+ }
+ }
+}
+
+static void
+row_delete (GtkButton *button,
+ ConstraintEditorWindow *win)
+{
+ GtkWidget *row;
+ GObject *item;
+
+ row = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_LIST_BOX_ROW);
+ item = (GObject *)g_object_get_data (G_OBJECT (row), "item");
+ if (GTK_IS_CONSTRAINT (item))
+ constraint_view_remove_constraint (CONSTRAINT_VIEW (win->view),
+ GTK_CONSTRAINT (item));
+ else if (GTK_IS_CONSTRAINT_GUIDE (item))
+ {
+ mark_constraints_invalid (win, item);
+ constraint_view_remove_guide (CONSTRAINT_VIEW (win->view),
+ GTK_CONSTRAINT_GUIDE (item));
+ }
+ else if (GTK_IS_WIDGET (item))
+ {
+ mark_constraints_invalid (win, item);
+ constraint_view_remove_child (CONSTRAINT_VIEW (win->view),
+ GTK_WIDGET (item));
+ }
+}
+
+static GtkWidget *
+create_widget_func (gpointer item,
+ gpointer user_data)
+{
+ ConstraintEditorWindow *win = user_data;
+ const char *name;
+ GtkWidget *row, *box, *label, *button;
+
+ name = (const char *)g_object_get_data (G_OBJECT (item), "name");
+
+ row = gtk_list_box_row_new ();
+ g_object_set_data (G_OBJECT (row), "item", item);
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ label = gtk_label_new (name);
+ g_object_set (label,
+ "margin", 10,
+ NULL);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_set_hexpand (label, TRUE);
+ gtk_container_add (GTK_CONTAINER (row), box);
+ gtk_container_add (GTK_CONTAINER (box), label);
+
+ if (GTK_IS_CONSTRAINT (item) || GTK_IS_CONSTRAINT_GUIDE (item))
+ {
+ button = gtk_button_new_from_icon_name ("document-edit-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_edit), win);
+ g_object_set_data (G_OBJECT (row), "edit", button);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ }
+ else if (GTK_IS_WIDGET (item))
+ {
+ button = gtk_button_new_from_icon_name ("edit-delete-symbolic");
+ gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
+ g_signal_connect (button, "clicked", G_CALLBACK (row_delete), win);
+ gtk_container_add (GTK_CONTAINER (box), button);
+ }
+
+ return row;
+}
+
+static void
+constraint_editor_window_init (ConstraintEditorWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_bind_model (GTK_LIST_BOX (self->list),
+ constraint_view_get_model (CONSTRAINT_VIEW (self->view)),
+ create_widget_func,
+ self,
+ NULL);
+}
+
+ConstraintEditorWindow *
+constraint_editor_window_new (ConstraintEditorApplication *application)
+{
+ return g_object_new (CONSTRAINT_EDITOR_WINDOW_TYPE,
+ "application", application,
+ NULL);
+}
diff --git a/demos/constraint-editor/constraint-editor-window.h b/demos/constraint-editor/constraint-editor-window.h
new file mode 100644
index 0000000000..9096c6e71d
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#include "constraint-editor-application.h"
+
+
+#define CONSTRAINT_EDITOR_WINDOW_TYPE (constraint_editor_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditorWindow, constraint_editor_window, CONSTRAINT, EDITOR_WINDOW, GtkApplicationWindow)
+
+ConstraintEditorWindow * constraint_editor_window_new (ConstraintEditorApplication *application);
+
+gboolean constraint_editor_window_load (ConstraintEditorWindow *self,
+ GFile *file);
diff --git a/demos/constraint-editor/constraint-editor-window.ui b/demos/constraint-editor/constraint-editor-window.ui
new file mode 100644
index 0000000000..c2c9a009ec
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor-window.ui
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ConstraintEditorWindow" parent="GtkApplicationWindow">
+ <style>
+ <class name="devel"/>
+ </style>
+ <property name="title" translatable="yes">GTK Constraint Editor</property>
+ <property name="default-width">1024</property>
+ <property name="default-height">768</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header">
+ <property name="title" translatable="yes">GTK Constraint Editor</property>
+ <property name="show-title-buttons">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Child</property>
+ <signal name="clicked" handler="add_child" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Guide</property>
+ <signal name="clicked" handler="add_guide" swapped="yes"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="label">Add Constraint</property>
+ <signal name="clicked" handler="add_constraint" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="hscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">automatic</property>
+ <property name="vexpand">1</property>
+ <child>
+ <object class="GtkListBox" id="list">
+ <property name="show-separators">1</property>
+ <property name="selection-mode">none</property>
+ <signal name="row-activated" handler="row_activated"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="ConstraintView" id="view">
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/constraint-editor.c b/demos/constraint-editor/constraint-editor.c
new file mode 100644
index 0000000000..5a85b35ecf
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.c
@@ -0,0 +1,583 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "constraint-editor.h"
+
+struct _ConstraintEditor
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *grid;
+ GtkWidget *name;
+ GtkWidget *target;
+ GtkWidget *target_attr;
+ GtkWidget *relation;
+ GtkWidget *source;
+ GtkWidget *source_attr;
+ GtkWidget *multiplier;
+ GtkWidget *constant;
+ GtkWidget *strength;
+ GtkWidget *preview;
+ GtkWidget *button;
+
+ GtkConstraint *constraint;
+ GListModel *model;
+
+ gboolean constructed;
+};
+
+enum {
+ PROP_MODEL = 1,
+ PROP_CONSTRAINT,
+ LAST_PROP
+};
+
+static GParamSpec *pspecs[LAST_PROP];
+
+enum {
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(ConstraintEditor, constraint_editor, GTK_TYPE_WIDGET);
+
+static const char *
+get_target_name (GtkConstraintTarget *target)
+{
+ if (target == NULL)
+ return "super";
+ else
+ return (const char *)g_object_get_data (G_OBJECT (target), "name");
+}
+
+static void
+constraint_target_combo (GListModel *model,
+ GtkWidget *combo,
+ gboolean is_source)
+{
+ int i;
+
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "super", "Super");
+
+ if (model)
+ {
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ GObject *item = g_list_model_get_object (model, i);
+ const char *name;
+
+ if (GTK_IS_CONSTRAINT (item))
+ continue;
+
+ name = get_target_name (GTK_CONSTRAINT_TARGET (item));
+
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), name, name);
+ g_object_unref (item);
+ }
+ }
+}
+
+static void
+constraint_attribute_combo (GtkWidget *combo,
+ gboolean is_source)
+{
+ if (is_source)
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "none", "None");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "left", "Left");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "right", "Right");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "top", "Top");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "bottom", "Bottom");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "start", "Start");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "end", "End");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "width", "Width");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "height", "Height");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "center-x", "Center X");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "center-y", "Center Y");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "baseline", "Baseline");
+}
+
+static void
+constraint_relation_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "le", "≤");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "eq", "=");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "ge", "≥");
+}
+
+static void
+constraint_strength_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "weak", "Weak");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "medium", "Medium");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "strong", "Strong");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "required", "Required");
+}
+
+static gpointer
+get_target (GListModel *model,
+ const char *id)
+{
+ int i;
+
+ if (strcmp ("super", id) == 0)
+ return NULL;
+
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ GObject *item = g_list_model_get_object (model, i);
+ const char *name;
+ if (GTK_IS_CONSTRAINT (item))
+ continue;
+ name = (const char *)g_object_get_data (item, "name");
+ g_object_unref (item);
+ if (strcmp (name, id) == 0)
+ return item;
+ }
+
+ return NULL;
+}
+
+static GtkConstraintAttribute
+get_target_attr (const char *id)
+{
+ GtkConstraintAttribute attr;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_ATTRIBUTE);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ attr = value->value;
+ g_type_class_unref (class);
+
+ return attr;
+}
+
+static const char *
+get_attr_nick (GtkConstraintAttribute attr)
+{
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_ATTRIBUTE);
+ GEnumValue *value = g_enum_get_value (class, attr);
+ const char *nick = value->value_nick;
+ g_type_class_unref (class);
+
+ return nick;
+}
+
+static GtkConstraintRelation
+get_relation (const char *id)
+{
+ GtkConstraintRelation relation;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_RELATION);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ relation = value->value;
+ g_type_class_unref (class);
+
+ return relation;
+}
+
+static const char *
+get_relation_nick (GtkConstraintRelation relation)
+{
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_RELATION);
+ GEnumValue *value = g_enum_get_value (class, relation);
+ const char *nick = value->value_nick;
+ g_type_class_unref (class);
+
+ return nick;
+}
+
+static GtkConstraintStrength
+get_strength (const char *id)
+{
+ GtkConstraintStrength strength;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ strength = value->value;
+ g_type_class_unref (class);
+
+ return strength;
+}
+
+static const char *
+get_strength_nick (GtkConstraintStrength strength)
+{
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value (class, strength);
+ const char *nick = value->value_nick;
+ g_type_class_unref (class);
+
+ return nick;
+}
+
+static void
+create_constraint (GtkButton *button,
+ ConstraintEditor *editor)
+{
+ const char *id;
+ gpointer target;
+ GtkConstraintAttribute target_attr;
+ gpointer source;
+ GtkConstraintAttribute source_attr;
+ GtkConstraintRelation relation;
+ double multiplier;
+ double constant;
+ int strength;
+ GtkConstraint *constraint;
+ const char *name;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target));
+ target = get_target (editor->model, id);
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target_attr));
+ target_attr = get_target_attr (id);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source));
+ source = get_target (editor->model, id);
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ source_attr = get_target_attr (id);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->relation));
+ relation = get_relation (id);
+
+ multiplier = g_ascii_strtod (gtk_editable_get_text (GTK_EDITABLE (editor->multiplier)), NULL);
+
+ constant = g_ascii_strtod (gtk_editable_get_text (GTK_EDITABLE (editor->constant)), NULL);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->strength));
+ strength = get_strength (id);
+
+ name = gtk_editable_get_text (GTK_EDITABLE (editor->name));
+
+ constraint = gtk_constraint_new (target, target_attr,
+ relation,
+ source, source_attr,
+ multiplier,
+ constant,
+ strength);
+ g_object_set_data_full (G_OBJECT (constraint), "name", g_strdup (name), g_free);
+ g_signal_emit (editor, signals[DONE], 0, constraint);
+ g_object_unref (constraint);
+}
+
+static void
+source_attr_changed (ConstraintEditor *editor)
+{
+ const char *id;
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ if (strcmp (id, "none") == 0)
+ {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (editor->source), -1);
+ gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), "");
+ gtk_widget_set_sensitive (editor->source, FALSE);
+ gtk_widget_set_sensitive (editor->multiplier, FALSE);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (editor->source, TRUE);
+ gtk_widget_set_sensitive (editor->multiplier, TRUE);
+ }
+}
+
+static void
+update_preview (ConstraintEditor *editor)
+{
+ GString *str;
+ const char *name;
+ const char *attr;
+ char *relation;
+ const char *multiplier;
+ const char *constant;
+ double c, m;
+
+ if (!editor->constructed)
+ return;
+
+ str = g_string_new ("");
+
+ name = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target));
+ attr = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target_attr));
+ relation = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (editor->relation));
+
+ if (name == NULL)
+ name = "[ ]";
+
+ g_string_append_printf (str, "%s.%s %s ", name, attr, relation);
+ g_free (relation);
+
+ constant = gtk_editable_get_text (GTK_EDITABLE (editor->constant));
+ c = g_ascii_strtod (constant, NULL);
+
+ attr = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source_attr));
+ if (strcmp (attr, "none") != 0)
+ {
+ name = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source));
+ multiplier = gtk_editable_get_text (GTK_EDITABLE (editor->multiplier));
+ m = g_ascii_strtod (multiplier, NULL);
+
+ if (name == NULL)
+ name = "[ ]";
+
+ g_string_append_printf (str, "%s.%s", name, attr);
+
+ if (m != 1.0)
+ g_string_append_printf (str, " × %g", m);
+
+ if (c > 0.0)
+ g_string_append_printf (str, " + %g", c);
+ else if (c < 0.0)
+ g_string_append_printf (str, " - %g", -c);
+ }
+ else
+ g_string_append_printf (str, "%g", c);
+
+ gtk_label_set_label (GTK_LABEL (editor->preview), str->str);
+
+ g_string_free (str, TRUE);
+}
+
+static void
+update_button (ConstraintEditor *editor)
+{
+ if (gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->target)) != NULL &&
+ gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->source)) != NULL)
+ gtk_widget_set_sensitive (editor->button, TRUE);
+ else
+ gtk_widget_set_sensitive (editor->button, FALSE);
+}
+
+static void
+constraint_editor_init (ConstraintEditor *editor)
+{
+ gtk_widget_init_template (GTK_WIDGET (editor));
+}
+
+static int constraint_counter;
+
+static void
+constraint_editor_constructed (GObject *object)
+{
+ ConstraintEditor *editor = CONSTRAINT_EDITOR (object);
+
+ constraint_target_combo (editor->model, editor->target, FALSE);
+ constraint_attribute_combo (editor->target_attr, FALSE);
+ constraint_relation_combo (editor->relation);
+ constraint_target_combo (editor->model, editor->source, TRUE);
+ constraint_attribute_combo (editor->source_attr, TRUE);
+
+ constraint_strength_combo (editor->strength);
+
+ if (editor->constraint)
+ {
+ GtkConstraintTarget *target;
+ GtkConstraintAttribute attr;
+ GtkConstraintRelation relation;
+ GtkConstraintStrength strength;
+ const char *nick;
+ char *val;
+ double multiplier;
+ double constant;
+
+ nick = (char *)g_object_get_data (G_OBJECT (editor->constraint), "name");
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), nick);
+
+ target = gtk_constraint_get_target (editor->constraint);
+ nick = get_target_name (target);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target), nick);
+
+ attr = gtk_constraint_get_target_attribute (editor->constraint);
+ nick = get_attr_nick (attr);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target_attr), nick);
+
+ target = gtk_constraint_get_source (editor->constraint);
+ nick = get_target_name (target);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source), nick);
+
+ attr = gtk_constraint_get_source_attribute (editor->constraint);
+ nick = get_attr_nick (attr);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source_attr), nick);
+
+ relation = gtk_constraint_get_relation (editor->constraint);
+ nick = get_relation_nick (relation);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->relation), nick);
+
+ multiplier = gtk_constraint_get_multiplier (editor->constraint);
+ val = g_strdup_printf ("%g", multiplier);
+ gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), val);
+ g_free (val);
+
+ constant = gtk_constraint_get_constant (editor->constraint);
+ val = g_strdup_printf ("%g", constant);
+ gtk_editable_set_text (GTK_EDITABLE (editor->constant), val);
+ g_free (val);
+
+ strength = gtk_constraint_get_strength (editor->constraint);
+ nick = get_strength_nick (strength);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), nick);
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Apply");
+ }
+ else
+ {
+ char *name;
+
+ constraint_counter++;
+ name = g_strdup_printf ("Constraint %d", constraint_counter);
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), name);
+ g_free (name);
+
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->target_attr), "left");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->source_attr), "left");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->relation), "eq");
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), "required");
+
+ gtk_editable_set_text (GTK_EDITABLE (editor->multiplier), "1.0");
+ gtk_editable_set_text (GTK_EDITABLE (editor->constant), "0.0");
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Create");
+ }
+
+ editor->constructed = TRUE;
+ update_preview (editor);
+ update_button (editor);
+}
+
+static void
+constraint_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ConstraintEditor *self = CONSTRAINT_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_MODEL:
+ self->model = g_value_dup_object (value);
+ break;
+
+ case PROP_CONSTRAINT:
+ self->constraint = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+constraint_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ConstraintEditor *self = CONSTRAINT_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_MODEL:
+ g_value_set_object (value, self->model);
+ break;
+
+ case PROP_CONSTRAINT:
+ g_value_set_object (value, self->constraint);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+constraint_editor_dispose (GObject *object)
+{
+ ConstraintEditor *self = (ConstraintEditor *)object;
+
+ g_clear_pointer (&self->grid, gtk_widget_unparent);
+ g_clear_object (&self->model);
+ g_clear_object (&self->constraint);
+
+ G_OBJECT_CLASS (constraint_editor_parent_class)->dispose (object);
+}
+
+static void
+constraint_editor_class_init (ConstraintEditorClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->constructed = constraint_editor_constructed;
+ object_class->dispose = constraint_editor_dispose;
+ object_class->set_property = constraint_editor_set_property;
+ object_class->get_property = constraint_editor_get_property;
+
+ pspecs[PROP_CONSTRAINT] =
+ g_param_spec_object ("constraint", "constraint", "constraint",
+ GTK_TYPE_CONSTRAINT,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY);
+
+ pspecs[PROP_MODEL] =
+ g_param_spec_object ("model", "model", "model",
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, pspecs);
+
+ signals[DONE] =
+ g_signal_new ("done",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_CONSTRAINT);
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gtk/gtk4/constraint-editor/constraint-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, grid);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, name);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, target);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, target_attr);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, relation);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, source);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, source_attr);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, multiplier);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, constant);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, strength);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, preview);
+ gtk_widget_class_bind_template_child (widget_class, ConstraintEditor, button);
+
+ gtk_widget_class_bind_template_callback (widget_class, update_preview);
+ gtk_widget_class_bind_template_callback (widget_class, update_button);
+ gtk_widget_class_bind_template_callback (widget_class, create_constraint);
+ gtk_widget_class_bind_template_callback (widget_class, source_attr_changed);
+}
+
+ConstraintEditor *
+constraint_editor_new (GListModel *model,
+ GtkConstraint *constraint)
+{
+ return g_object_new (CONSTRAINT_EDITOR_TYPE,
+ "model", model,
+ "constraint", constraint,
+ NULL);
+}
diff --git a/demos/constraint-editor/constraint-editor.css b/demos/constraint-editor/constraint-editor.css
new file mode 100644
index 0000000000..c9538bf268
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.css
@@ -0,0 +1,12 @@
+constraintview {
+ background: black;
+ color: white;
+}
+
+constraintview .child {
+ background: red;
+}
+
+constraintview .guide {
+ background: blue;
+}
diff --git a/demos/constraint-editor/constraint-editor.gresource.xml b/demos/constraint-editor/constraint-editor.gresource.xml
new file mode 100644
index 0000000000..e57964b1a7
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gtk/gtk4/constraint-editor">
+ <file preprocess="xml-stripblanks">constraint-editor-window.ui</file>
+ <file preprocess="xml-stripblanks">constraint-editor.ui</file>
+ <file preprocess="xml-stripblanks">guide-editor.ui</file>
+ <file>constraint-editor.css</file>
+ </gresource>
+</gresources>
diff --git a/demos/constraint-editor/constraint-editor.h b/demos/constraint-editor/constraint-editor.h
new file mode 100644
index 0000000000..c5940e254b
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.h
@@ -0,0 +1,29 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_EDITOR_TYPE (constraint_editor_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintEditor, constraint_editor, CONSTRAINT, EDITOR, GtkWidget)
+
+ConstraintEditor * constraint_editor_new (GListModel *model,
+ GtkConstraint *constraint);
diff --git a/demos/constraint-editor/constraint-editor.ui b/demos/constraint-editor/constraint-editor.ui
new file mode 100644
index 0000000000..604757659a
--- /dev/null
+++ b/demos/constraint-editor/constraint-editor.ui
@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ConstraintEditor" parent="GtkWidget">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="margin">20</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Name</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Target</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="target">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="update_button" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="target_attr">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Relation</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="relation">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Source</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="source">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="update_button" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="source_attr">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <signal name="changed" handler="source_attr_changed" swapped="yes"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Multiplier</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="multiplier">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Constant</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="constant">
+ <signal name="changed" handler="update_preview" swapped="yes"/>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Strength</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">6</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="strength">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">6</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="preview">
+ <property name="xalign">0</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">7</property>
+ <property name="column-span">2</property>
+ </layout>
+ <attributes>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="label">Create</property>
+ <signal name="clicked" handler="create_constraint"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">8</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/constraint-view-child.c b/demos/constraint-editor/constraint-view-child.c
new file mode 100644
index 0000000000..272054a387
--- /dev/null
+++ b/demos/constraint-editor/constraint-view-child.c
@@ -0,0 +1,93 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "constraint-view-child.h"
+
+struct _ConstraintViewChild
+{
+ GObject parent_instance;
+
+ char *name;
+};
+
+enum {
+ PROP_NAME = 1,
+ LAST_PROP
+};
+
+static GParamSpec props[LAST_PROP];
+
+G_DEFINE_TYPE (ConstraintViewChild, constraint_view_child, G_TYPE_OBJECT)
+
+static void
+constraint_view_child_init (ConstraintViewChild *child)
+{
+}
+
+static void
+constraint_view_child_finalize (GObject *object)
+{
+ ConstraintViewChild *child = CONSTRAINT_VIEW_CHILD (object);
+
+ g_free (child->name);
+
+ G_OBJECT_CLASS (constraint_view_child_parent_class)->finalize (object);
+}
+
+static void
+constraint_view_child_set_property (GObject *object,
+
+static void
+constraint_view_child_class_init (ConstraintViewChildClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = constraint_view_child_finalize;
+ object_class->get_property = constraint_view_child_get_property;
+ object_class->set_property = constraint_view_child_set_property;
+
+ props[PROP_NAME] =
+ g_param_spec_string ("name", "name", "name",
+ NULL,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+}
+
+#define CONSTRAINT_VIEW_CHILD_TYPE (constraint_view_get_type ())
+
+G_DECLARE_TYPE (ConstraintViewChild, constraint_view_child, CONSTRAINT, VIEW_CHILD, GObject)
+
+#define CONSTRAINT_VIEW_WIDGET_TYPE (constraint_view_widget_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewWidget, constraint_view_widget, CONSTRAINT, VIEW_WIDGET, ConstraintViewChild)
+
+ConstraintViewWidget * constraint_view_widget_new (void);
+
+#define CONSTRAINT_VIEW_GUIDE_TYPE (constraint_view_guide_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewGuide, constraint_view_guide, CONSTRAINT, VIEW_GUIDE, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_guide_new (void);
+
+#define CONSTRAINT_VIEW_CONSTRAINT_TYPE (constraint_view_constraint_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewConstraint, constraint_view_constraint, CONSTRAINT, VIEW_CONSTRAINT, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_constraint_new (void);
diff --git a/demos/constraint-editor/constraint-view-child.h b/demos/constraint-editor/constraint-view-child.h
new file mode 100644
index 0000000000..6f2120ef0b
--- /dev/null
+++ b/demos/constraint-editor/constraint-view-child.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_VIEW_CHILD_TYPE (constraint_view_get_type ())
+
+G_DECLARE_TYPE (ConstraintViewChild, constraint_view_child, CONSTRAINT, VIEW_CHILD, GObject)
+
+#define CONSTRAINT_VIEW_WIDGET_TYPE (constraint_view_widget_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewWidget, constraint_view_widget, CONSTRAINT, VIEW_WIDGET, ConstraintViewChild)
+
+ConstraintViewWidget * constraint_view_widget_new (void);
+
+#define CONSTRAINT_VIEW_GUIDE_TYPE (constraint_view_guide_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewGuide, constraint_view_guide, CONSTRAINT, VIEW_GUIDE, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_guide_new (void);
+
+#define CONSTRAINT_VIEW_CONSTRAINT_TYPE (constraint_view_constraint_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintViewConstraint, constraint_view_constraint, CONSTRAINT, VIEW_CONSTRAINT, ConstraintViewChild)
+
+ConstraintViewGuide * constraint_view_constraint_new (void);
diff --git a/demos/constraint-editor/constraint-view.c b/demos/constraint-editor/constraint-view.c
new file mode 100644
index 0000000000..7a4b094df5
--- /dev/null
+++ b/demos/constraint-editor/constraint-view.c
@@ -0,0 +1,378 @@
+/* Copyright (C) 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 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/>.
+ */
+
+#include <gtk/gtk.h>
+#include "constraint-view.h"
+
+struct _ConstraintView
+{
+ GtkWidget parent;
+
+ GListStore *store;
+
+ GtkWidget *drag_widget;
+};
+
+G_DEFINE_TYPE (ConstraintView, constraint_view, GTK_TYPE_WIDGET);
+
+static void
+constraint_view_dispose (GObject *object)
+{
+ ConstraintView *view = CONSTRAINT_VIEW (object);
+ GtkWidget *child;
+
+ while ((child = gtk_widget_get_first_child (GTK_WIDGET (view))) != NULL)
+ gtk_widget_unparent (child);
+
+ g_clear_object (&view->store);
+
+ G_OBJECT_CLASS (constraint_view_parent_class)->dispose (object);
+}
+
+static void
+constraint_view_class_init (ConstraintViewClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = constraint_view_dispose;
+
+ gtk_widget_class_set_css_name (widget_class, "constraintview");
+}
+
+static void
+update_weak_position (ConstraintView *self,
+ GtkWidget *child,
+ double x,
+ double y)
+{
+ GtkLayoutManager *manager;
+ GtkConstraint *constraint;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
+ constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), "x-constraint");
+ if (constraint)
+ {
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (child), "x-constraint", NULL);
+ }
+ constraint = gtk_constraint_new_constant (child,
+ GTK_CONSTRAINT_ATTRIBUTE_CENTER_X,
+ GTK_CONSTRAINT_RELATION_EQ,
+ x,
+ GTK_CONSTRAINT_STRENGTH_WEAK);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (child), "x-constraint", constraint);
+
+ constraint = (GtkConstraint *)g_object_get_data (G_OBJECT (child), "y-constraint");
+ if (constraint)
+ {
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (child), "y-constraint", NULL);
+ }
+ constraint = gtk_constraint_new_constant (child,
+ GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y,
+ GTK_CONSTRAINT_RELATION_EQ,
+ y,
+ GTK_CONSTRAINT_STRENGTH_WEAK);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (child), "y-constraint", constraint);
+}
+
+static void
+drag_begin (GtkGestureDrag *drag,
+ double start_x,
+ double start_y,
+ ConstraintView *self)
+{
+ GtkWidget *widget;
+
+ widget = gtk_widget_pick (GTK_WIDGET (self), start_x, start_y, GTK_PICK_DEFAULT);
+
+ if (GTK_IS_LABEL (widget))
+ {
+ widget = gtk_widget_get_ancestor (widget, GTK_TYPE_FRAME);
+ if (widget &&
+ gtk_widget_get_parent (widget) == (GtkWidget *)self)
+ {
+ self->drag_widget = widget;
+ }
+ }
+}
+
+static void
+drag_update (GtkGestureDrag *drag,
+ double offset_x,
+ double offset_y,
+ ConstraintView *self)
+{
+ double x, y;
+
+ if (!self->drag_widget)
+ return;
+
+ gtk_gesture_drag_get_start_point (drag, &x, &y);
+ update_weak_position (self, self->drag_widget, x + offset_x, y + offset_y);
+}
+
+static void
+drag_end (GtkGestureDrag *drag,
+ double offset_x,
+ double offset_y,
+ ConstraintView *self)
+{
+ self->drag_widget = NULL;
+}
+
+static void
+constraint_view_init (ConstraintView *self)
+{
+ GtkEventController *controller;
+
+ gtk_widget_set_layout_manager (GTK_WIDGET (self),
+ gtk_constraint_layout_new ());
+
+ self->store = g_list_store_new (G_TYPE_OBJECT);
+
+ controller = (GtkEventController *)gtk_gesture_drag_new ();
+ g_signal_connect (controller, "drag-begin", G_CALLBACK (drag_begin), self);
+ g_signal_connect (controller, "drag-update", G_CALLBACK (drag_update), self);
+ g_signal_connect (controller, "drag-end", G_CALLBACK (drag_end), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), controller);
+}
+
+ConstraintView *
+constraint_view_new (void)
+{
+ return g_object_new (CONSTRAINT_VIEW_TYPE, NULL);
+}
+
+void
+constraint_view_add_child (ConstraintView *view,
+ const char *name)
+{
+ GtkWidget *frame;
+ GtkWidget *label;
+
+ label = gtk_label_new (name);
+ frame = gtk_frame_new (NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (frame), "child");
+ g_object_set_data_full (G_OBJECT (frame), "name", g_strdup (name), g_free);
+ gtk_container_add (GTK_CONTAINER (frame), label);
+ gtk_widget_set_parent (frame, GTK_WIDGET (view));
+
+ update_weak_position (view, frame, 100, 100);
+
+ g_list_store_append (view->store, frame);
+}
+
+void
+constraint_view_remove_child (ConstraintView *view,
+ GtkWidget *child)
+{
+ int i;
+
+ gtk_widget_unparent (child);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)child)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_add_guide (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkLayoutManager *manager;
+ GtkWidget *frame;
+ GtkWidget *label;
+ const char *name;
+ GtkConstraint *constraint;
+
+ name = (const char *)g_object_get_data (G_OBJECT (guide), "name");
+
+ label = gtk_label_new (name);
+ frame = gtk_frame_new (NULL);
+ gtk_style_context_add_class (gtk_widget_get_style_context (frame), "guide");
+ g_object_set_data_full (G_OBJECT (frame), "name", g_strdup (name), g_free);
+ gtk_container_add (GTK_CONTAINER (frame), label);
+ gtk_widget_insert_after (frame, GTK_WIDGET (view), NULL);
+
+ g_object_set_data (G_OBJECT (guide), "frame", frame);
+ g_object_set_data (G_OBJECT (guide), "label", label);
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_add_guide (GTK_CONSTRAINT_LAYOUT (manager),
+ g_object_ref (guide));
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_LEFT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_LEFT,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "left-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "top-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "width-constraint", constraint);
+
+ constraint = gtk_constraint_new (frame,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ 1.0, 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ g_object_set_data (G_OBJECT (guide), "height-constraint", constraint);
+
+ update_weak_position (view, frame, 150, 150);
+
+ g_list_store_append (view->store, guide);
+}
+
+void
+constraint_view_guide_changed (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkWidget *label;
+ const char *name;
+ int i;
+
+ name = (const char *)g_object_get_data (G_OBJECT (guide), "name");
+ label = (GtkWidget *)g_object_get_data (G_OBJECT (guide), "label");
+ gtk_label_set_label (GTK_LABEL (label), name);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)guide)
+ {
+ g_list_model_items_changed (G_LIST_MODEL (view->store), i, 1, 1);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_remove_guide (ConstraintView *view,
+ GtkConstraintGuide *guide)
+{
+ GtkLayoutManager *manager;
+ GtkWidget *frame;
+ GtkConstraint *constraint;
+ int i;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "left-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "top-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "width-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ constraint = (GtkConstraint*)g_object_get_data (G_OBJECT (guide), "height-constraint");
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+
+ frame = (GtkWidget *)g_object_get_data (G_OBJECT (guide), "frame");
+ gtk_widget_unparent (frame);
+
+ gtk_constraint_layout_remove_guide (GTK_CONSTRAINT_LAYOUT (manager),
+ guide);
+
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)guide)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+void
+constraint_view_add_constraint (ConstraintView *view,
+ GtkConstraint *constraint)
+{
+ GtkLayoutManager *manager;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_add_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ g_object_ref (constraint));
+
+ g_list_store_append (view->store, constraint);
+}
+
+void
+constraint_view_remove_constraint (ConstraintView *view,
+ GtkConstraint *constraint)
+{
+ GtkLayoutManager *manager;
+ int i;
+
+ manager = gtk_widget_get_layout_manager (GTK_WIDGET (view));
+ gtk_constraint_layout_remove_constraint (GTK_CONSTRAINT_LAYOUT (manager),
+ constraint);
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (view->store)); i++)
+ {
+ if (g_list_model_get_item (G_LIST_MODEL (view->store), i) == (GObject*)constraint)
+ {
+ g_list_store_remove (view->store, i);
+ break;
+ }
+ }
+}
+
+GListModel *
+constraint_view_get_model (ConstraintView *view)
+{
+ return G_LIST_MODEL (view->store);
+}
diff --git a/demos/constraint-editor/constraint-view.h b/demos/constraint-editor/constraint-view.h
new file mode 100644
index 0000000000..40e80c8c43
--- /dev/null
+++ b/demos/constraint-editor/constraint-view.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define CONSTRAINT_VIEW_TYPE (constraint_view_get_type ())
+
+G_DECLARE_FINAL_TYPE (ConstraintView, constraint_view, CONSTRAINT, VIEW, GtkWidget)
+
+ConstraintView * constraint_view_new (void);
+
+void constraint_view_add_child (ConstraintView *view,
+ const char *name);
+void constraint_view_remove_child (ConstraintView *view,
+ GtkWidget *child);
+void constraint_view_add_guide (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_remove_guide (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_guide_changed (ConstraintView *view,
+ GtkConstraintGuide *guide);
+void constraint_view_add_constraint (ConstraintView *view,
+ GtkConstraint *constraint);
+void constraint_view_remove_constraint (ConstraintView *view,
+ GtkConstraint *constraint);
+GListModel * constraint_view_get_model (ConstraintView *view);
diff --git a/demos/constraint-editor/guide-editor.c b/demos/constraint-editor/guide-editor.c
new file mode 100644
index 0000000000..39bd08a157
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.c
@@ -0,0 +1,387 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "guide-editor.h"
+
+struct _GuideEditor
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *grid;
+ GtkWidget *name;
+ GtkWidget *min_width;
+ GtkWidget *min_height;
+ GtkWidget *nat_width;
+ GtkWidget *nat_height;
+ GtkWidget *max_width;
+ GtkWidget *max_height;
+ GtkWidget *strength;
+ GtkWidget *button;
+
+ GtkConstraintGuide *guide;
+
+ gboolean constructed;
+};
+
+enum {
+ PROP_GUIDE = 1,
+ LAST_PROP
+};
+
+static GParamSpec *pspecs[LAST_PROP];
+
+enum {
+ DONE,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE(GuideEditor, guide_editor, GTK_TYPE_WIDGET);
+
+static void
+guide_strength_combo (GtkWidget *combo)
+{
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "weak", "Weak");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "medium", "Medium");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "strong", "Strong");
+ gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), "required", "Required");
+}
+
+static GtkConstraintStrength
+get_strength (const char *id)
+{
+ GtkConstraintStrength strength;
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value_by_nick (class, id);
+ strength = value->value;
+ g_type_class_unref (class);
+
+ return strength;
+}
+
+const char *
+get_strength_nick (GtkConstraintStrength strength)
+{
+ GEnumClass *class = g_type_class_ref (GTK_TYPE_CONSTRAINT_STRENGTH);
+ GEnumValue *value = g_enum_get_value (class, strength);
+ const char *nick = value->value_nick;
+ g_type_class_unref (class);
+
+ return nick;
+}
+
+static void
+create_guide (GtkButton *button,
+ GuideEditor *editor)
+{
+ const char *id;
+ int strength;
+ const char *name;
+ int w, h;
+ GtkConstraintGuide *guide;
+
+ if (editor->guide)
+ guide = g_object_ref (editor->guide);
+ else
+ guide = gtk_constraint_guide_new ();
+
+ name = gtk_editable_get_text (GTK_EDITABLE (editor->name));
+ g_object_set_data_full (G_OBJECT (guide), "name", g_strdup (name), g_free);
+
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->min_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->min_height));
+ gtk_constraint_guide_set_min_size (guide, w, h);
+
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->nat_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->nat_height));
+ gtk_constraint_guide_set_nat_size (guide, w, h);
+
+ w = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->max_width));
+ h = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (editor->max_height));
+ gtk_constraint_guide_set_max_size (guide, w, h);
+
+ id = gtk_combo_box_get_active_id (GTK_COMBO_BOX (editor->strength));
+ strength = get_strength (id);
+ gtk_constraint_guide_set_strength (guide, strength);
+
+ g_signal_emit (editor, signals[DONE], 0, guide);
+ g_object_unref (guide);
+}
+
+static void
+guide_editor_init (GuideEditor *editor)
+{
+ gtk_widget_init_template (GTK_WIDGET (editor));
+}
+
+static int guide_counter;
+
+static int
+min_input (GtkSpinButton *spin_button,
+ double *new_val)
+{
+ if (strcmp (gtk_editable_get_text (GTK_EDITABLE (spin_button)), "") == 0)
+ {
+ *new_val = 0.0;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int
+max_input (GtkSpinButton *spin_button,
+ double *new_val)
+{
+ if (strcmp (gtk_editable_get_text (GTK_EDITABLE (spin_button)), "") == 0)
+ {
+ *new_val = G_MAXINT;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+min_output (GtkSpinButton *spin_button)
+{
+ GtkAdjustment *adjustment;
+ double value;
+ GtkWidget *box, *text;
+
+ adjustment = gtk_spin_button_get_adjustment (spin_button);
+ value = gtk_adjustment_get_value (adjustment);
+
+ box = gtk_widget_get_first_child (GTK_WIDGET (spin_button));
+ text = gtk_widget_get_first_child (box);
+
+ if (value == 0.0)
+ {
+ gtk_editable_set_text (GTK_EDITABLE (spin_button), "");
+ gtk_text_set_placeholder_text (GTK_TEXT (text), "unset");
+ return TRUE;
+ }
+ else
+ {
+ gtk_text_set_placeholder_text (GTK_TEXT (text), "");
+ return FALSE;
+ }
+}
+
+static gboolean
+max_output (GtkSpinButton *spin_button)
+{
+ GtkAdjustment *adjustment;
+ double value;
+ GtkWidget *box, *text;
+
+ adjustment = gtk_spin_button_get_adjustment (spin_button);
+ value = gtk_adjustment_get_value (adjustment);
+
+ box = gtk_widget_get_first_child (GTK_WIDGET (spin_button));
+ text = gtk_widget_get_first_child (box);
+
+ if (value == (double)G_MAXINT)
+ {
+ gtk_editable_set_text (GTK_EDITABLE (spin_button), "");
+ gtk_text_set_placeholder_text (GTK_TEXT (text), "unset");
+ return TRUE;
+ }
+ else
+ {
+ gtk_text_set_placeholder_text (GTK_TEXT (text), "");
+ return FALSE;
+ }
+}
+
+static void
+guide_editor_constructed (GObject *object)
+{
+ GuideEditor *editor = GUIDE_EDITOR (object);
+
+ guide_strength_combo (editor->strength);
+
+ g_signal_connect (editor->min_width, "input", G_CALLBACK (min_input), NULL);
+ g_signal_connect (editor->min_width, "output", G_CALLBACK (min_output), NULL);
+
+ g_signal_connect (editor->min_height, "input", G_CALLBACK (min_input), NULL);
+ g_signal_connect (editor->min_height, "output", G_CALLBACK (min_output), NULL);
+
+ g_signal_connect (editor->max_width, "input", G_CALLBACK (max_input), NULL);
+ g_signal_connect (editor->max_width, "output", G_CALLBACK (max_output), NULL);
+
+ g_signal_connect (editor->max_height, "input", G_CALLBACK (max_input), NULL);
+ g_signal_connect (editor->max_height, "output", G_CALLBACK (max_output), NULL);
+
+ if (editor->guide)
+ {
+ GtkConstraintStrength strength;
+ const char *nick;
+ int w, h;
+
+ nick = (char *)g_object_get_data (G_OBJECT (editor->guide), "name");
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), nick);
+
+ gtk_constraint_guide_get_min_size (editor->guide, &w, &h);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_height), h);
+
+ gtk_constraint_guide_get_nat_size (editor->guide, &w, &h);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_height), h);
+
+ gtk_constraint_guide_get_max_size (editor->guide, &w, &h);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_width), w);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_height), h);
+
+ strength = gtk_constraint_guide_get_strength (editor->guide);
+ nick = get_strength_nick (strength);
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), nick);
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Apply");
+ }
+ else
+ {
+ char *name;
+
+ guide_counter++;
+ name = g_strdup_printf ("Guide %d", guide_counter);
+ gtk_editable_set_text (GTK_EDITABLE (editor->name), name);
+ g_free (name);
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_width), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->min_height), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_width), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_height), 0.0);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_width), G_MAXINT);
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->max_height), G_MAXINT);
+
+ gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), "medium");
+
+ gtk_button_set_label (GTK_BUTTON (editor->button), "Create");
+ }
+
+ editor->constructed = TRUE;
+}
+
+static void
+guide_editor_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GuideEditor *self = GUIDE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GUIDE:
+ self->guide = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+guide_editor_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GuideEditor *self = GUIDE_EDITOR (object);
+
+ switch (property_id)
+ {
+ case PROP_GUIDE:
+ g_value_set_object (value, self->guide);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+guide_editor_dispose (GObject *object)
+{
+ GuideEditor *self = (GuideEditor *)object;
+
+ g_clear_pointer (&self->grid, gtk_widget_unparent);
+ g_clear_object (&self->guide);
+
+ G_OBJECT_CLASS (guide_editor_parent_class)->dispose (object);
+}
+
+static void
+guide_editor_class_init (GuideEditorClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+
+ object_class->constructed = guide_editor_constructed;
+ object_class->dispose = guide_editor_dispose;
+ object_class->set_property = guide_editor_set_property;
+ object_class->get_property = guide_editor_get_property;
+
+ pspecs[PROP_GUIDE] =
+ g_param_spec_object ("guide", "guide", "guide",
+ GTK_TYPE_CONSTRAINT_GUIDE,
+ G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, pspecs);
+
+ signals[DONE] =
+ g_signal_new ("done",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_CONSTRAINT_GUIDE);
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gtk/gtk4/constraint-editor/guide-editor.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, grid);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, name);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, min_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, min_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, nat_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, nat_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, max_width);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, max_height);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, strength);
+ gtk_widget_class_bind_template_child (widget_class, GuideEditor, button);
+
+ gtk_widget_class_bind_template_callback (widget_class, create_guide);
+}
+
+GuideEditor *
+guide_editor_new (GtkConstraintGuide *guide)
+{
+ return g_object_new (GUIDE_EDITOR_TYPE,
+ "guide", guide,
+ NULL);
+}
diff --git a/demos/constraint-editor/guide-editor.h b/demos/constraint-editor/guide-editor.h
new file mode 100644
index 0000000000..d11cb4f3db
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+#define GUIDE_EDITOR_TYPE (guide_editor_get_type ())
+
+G_DECLARE_FINAL_TYPE (GuideEditor, guide_editor, GUIDE, EDITOR, GtkWidget)
+
+GuideEditor * guide_editor_new (GtkConstraintGuide *guide);
diff --git a/demos/constraint-editor/guide-editor.ui b/demos/constraint-editor/guide-editor.ui
new file mode 100644
index 0000000000..a788de10bf
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.ui
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <object class="GtkAdjustment" id="min_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="min_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="nat_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="nat_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="max_width_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <object class="GtkAdjustment" id="max_height_adj">
+ <property name="lower">0</property>
+ <property name="upper">2147483647</property>
+ <property name="step-increment">1</property>
+ <property name="page-increment">10</property>
+ <property name="page-size">0</property>
+ </object>
+ <template class="GuideEditor" parent="GtkWidget">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="margin">20</property>
+ <property name="row-spacing">10</property>
+ <property name="column-spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Name</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="name">
+ <property name="max-width-chars">20</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Min Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="min_width">
+ <property name="adjustment">min_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="min_height">
+ <property name="adjustment">min_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Nat Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="nat_width">
+ <property name="adjustment">nat_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="nat_height">
+ <property name="adjustment">nat_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Max Size</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_width">
+ <property name="adjustment">max_width_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="max_height">
+ <property name="adjustment">max_height_adj</property>
+ <property name="max-width-chars">5</property>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Strength</property>
+ <layout>
+ <property name="left-attach">0</property>
+ <property name="top-attach">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="strength">
+ <layout>
+ <property name="left-attach">1</property>
+ <property name="top-attach">4</property>
+ <property name="column-span">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="label">Create</property>
+ <signal name="clicked" handler="create_guide"/>
+ <layout>
+ <property name="left-attach">2</property>
+ <property name="top-attach">5</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/demos/constraint-editor/main.c b/demos/constraint-editor/main.c
new file mode 100644
index 0000000000..771774c08f
--- /dev/null
+++ b/demos/constraint-editor/main.c
@@ -0,0 +1,28 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <constraint-editor-application.h>
+
+int
+main (int argc, char *argv[])
+{
+ return g_application_run (G_APPLICATION (constraint_editor_application_new ()), argc, argv);
+}
diff --git a/demos/constraint-editor/meson.build b/demos/constraint-editor/meson.build
new file mode 100644
index 0000000000..af66846437
--- /dev/null
+++ b/demos/constraint-editor/meson.build
@@ -0,0 +1,19 @@
+constraint_editor_sources = [
+ 'main.c',
+ 'constraint-editor-application.c',
+ 'constraint-editor-window.c',
+ 'constraint-view.c',
+ 'constraint-editor.c',
+ 'guide-editor.c',
+]
+
+constraint_editor_resources = gnome.compile_resources('constraint_editor_resources',
+ 'constraint-editor.gresource.xml',
+ source_dir: '.')
+
+executable('gtk4-constraint-editor',
+ constraint_editor_sources, constraint_editor_resources,
+ dependencies: libgtk_dep,
+ include_directories: confinc,
+ gui_app: true,
+ install: false)
diff --git a/demos/gtk-demo/constraints.c b/demos/gtk-demo/constraints.c
new file mode 100644
index 0000000000..1b6b00b2f5
--- /dev/null
+++ b/demos/gtk-demo/constraints.c
@@ -0,0 +1,289 @@
+/* Constraints/Simple
+ *
+ * GtkConstraintLayout provides a layout manager that uses relations
+ * between widgets (also known as "constraints") to compute the position
+ * and size of each child.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE (SimpleGrid, simple_grid, SIMPLE, GRID, GtkWidget)
+
+struct _SimpleGrid
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *button1, *button2;
+ GtkWidget *button3;
+};
+
+G_DEFINE_TYPE (SimpleGrid, simple_grid, GTK_TYPE_WIDGET)
+
+static void
+simple_grid_destroy (GtkWidget *widget)
+{
+ SimpleGrid *self = SIMPLE_GRID (widget);
+
+ g_clear_pointer (&self->button1, gtk_widget_destroy);
+ g_clear_pointer (&self->button2, gtk_widget_destroy);
+ g_clear_pointer (&self->button3, gtk_widget_destroy);
+
+ GTK_WIDGET_CLASS (simple_grid_parent_class)->destroy (widget);
+}
+
+static void
+simple_grid_class_init (SimpleGridClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->destroy = simple_grid_destroy;
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CONSTRAINT_LAYOUT);
+}
+
+/* Layout:
+ *
+ * +-------------------------------------+
+ * | +-----------++-------++-----------+ |
+ * | | Child 1 || Space || Child 2 | |
+ * | +-----------++-------++-----------+ |
+ * | +---------------------------------+ |
+ * | | Child 3 | |
+ * | +---------------------------------+ |
+ * +-------------------------------------+
+ *
+ * Constraints:
+ *
+ * super.start = child1.start - 8
+ * child1.width = child2.width
+ * child1.end = space.start
+ * space.end = child2.start
+ * child2.end = super.end - 8
+ * super.start = child3.start - 8
+ * child3.end = super.end - 8
+ * super.top = child1.top - 8
+ * super.top = child2.top - 8
+ * child1.bottom = child3.top - 12
+ * child2.bottom = child3.top - 12
+ * child3.height = child1.height
+ * child3.height = child2.height
+ * child3.bottom = super.bottom - 8
+ *
+ * To add some flexibility, we make the space
+ * stretchable:
+ *
+ * space.width >= 10
+ * space.width = 100
+ * space.width <= 200
+ */
+static void
+build_constraints (SimpleGrid *self,
+ GtkConstraintLayout *manager)
+{
+ GtkConstraintGuide *guide;
+
+ guide = gtk_constraint_guide_new ();
+ gtk_constraint_guide_set_name (guide, "space");
+ gtk_constraint_guide_set_min_size (guide, 10, 10);
+ gtk_constraint_guide_set_nat_size (guide, 100, 10);
+ gtk_constraint_guide_set_max_size (guide, 200, 20);
+ gtk_constraint_guide_set_strength (guide, GTK_CONSTRAINT_STRENGTH_STRONG);
+ gtk_constraint_layout_add_guide (manager, guide);
+
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new_constant (GTK_CONSTRAINT_TARGET (self->button1),
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_RELATION_LE,
+ 200.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ guide,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (guide,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0,
+ -12.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0,
+ -12.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button1,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ self->button2,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (self->button3,
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ GTK_CONSTRAINT_RELATION_EQ,
+ NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+}
+
+static void
+simple_grid_init (SimpleGrid *self)
+{
+ GtkWidget *widget = GTK_WIDGET (self);
+
+ self->button1 = gtk_button_new_with_label ("Child 1");
+ gtk_widget_set_parent (self->button1, widget);
+ gtk_widget_set_name (self->button1, "button1");
+
+ self->button2 = gtk_button_new_with_label ("Child 2");
+ gtk_widget_set_parent (self->button2, widget);
+ gtk_widget_set_name (self->button2, "button2");
+
+ self->button3 = gtk_button_new_with_label ("Child 3");
+ gtk_widget_set_parent (self->button3, widget);
+ gtk_widget_set_name (self->button3, "button3");
+
+ GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
+ build_constraints (self, GTK_CONSTRAINT_LAYOUT (manager));
+}
+
+GtkWidget *
+do_constraints (GtkWidget *do_widget)
+{
+ static GtkWidget *window;
+
+ if (!window)
+ {
+ GtkWidget *header, *box, *grid, *button;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
+
+ header = gtk_header_bar_new ();
+ gtk_header_bar_set_title (GTK_HEADER_BAR (header), "Constraints");
+ gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), FALSE);
+ gtk_window_set_titlebar (GTK_WINDOW (window), header);
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ grid = g_object_new (simple_grid_get_type (), NULL);
+ gtk_widget_set_hexpand (grid, TRUE);
+ gtk_widget_set_vexpand (grid, TRUE);
+ gtk_container_add (GTK_CONTAINER (box), grid);
+
+ button = gtk_button_new_with_label ("Close");
+ gtk_container_add (GTK_CONTAINER (box), button);
+ gtk_widget_set_hexpand (grid, TRUE);
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gtk_widget_destroy), window);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show (window);
+ else
+ gtk_widget_destroy (window);
+
+ return window;
+}
diff --git a/demos/gtk-demo/constraints2.c b/demos/gtk-demo/constraints2.c
new file mode 100644
index 0000000000..7ec7e09520
--- /dev/null
+++ b/demos/gtk-demo/constraints2.c
@@ -0,0 +1,245 @@
+/* Constraints/Interactive
+ *
+ * Demonstrate how constraints can be updates during
+ * user interaction.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE (InteractiveGrid, interactive_grid, INTERACTIVE, GRID, GtkWidget)
+
+struct _InteractiveGrid
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *button1, *button2;
+ GtkWidget *button3;
+ GtkConstraintGuide *guide;
+ GtkConstraint *constraint;
+};
+
+G_DEFINE_TYPE (InteractiveGrid, interactive_grid, GTK_TYPE_WIDGET)
+
+static void
+interactive_grid_destroy (GtkWidget *widget)
+{
+ InteractiveGrid *self = INTERACTIVE_GRID (widget);
+
+ g_clear_pointer (&self->button1, gtk_widget_destroy);
+ g_clear_pointer (&self->button2, gtk_widget_destroy);
+ g_clear_pointer (&self->button3, gtk_widget_destroy);
+
+ GTK_WIDGET_CLASS (interactive_grid_parent_class)->destroy (widget);
+}
+
+static void
+interactive_grid_class_init (InteractiveGridClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->destroy = interactive_grid_destroy;
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CONSTRAINT_LAYOUT);
+}
+
+static void
+build_constraints (InteractiveGrid *self,
+ GtkConstraintLayout *manager)
+{
+ self->guide = g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+ gtk_constraint_layout_add_guide (manager, self->guide);
+
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new_constant (GTK_CONSTRAINT_TARGET (self->guide),
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_RELATION_EQ,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->button1),
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button1),
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->guide),
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button2),
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->guide),
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button2),
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->button3),
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button3),
+ GTK_CONSTRAINT_ATTRIBUTE_END,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->guide),
+ GTK_CONSTRAINT_ATTRIBUTE_START,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->button1),
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button2),
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->button1),
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button3),
+ GTK_CONSTRAINT_ATTRIBUTE_TOP,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_TARGET (self->button2),
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ 1.0,
+ 0.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+ gtk_constraint_layout_add_constraint (manager,
+ gtk_constraint_new (GTK_CONSTRAINT_TARGET (self->button3),
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ GTK_CONSTRAINT_RELATION_EQ,
+ NULL,
+ GTK_CONSTRAINT_ATTRIBUTE_BOTTOM,
+ 1.0,
+ -8.0,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED));
+}
+
+static void
+drag_cb (GtkGestureDrag *drag,
+ double offset_x,
+ double offset_y,
+ InteractiveGrid *self)
+{
+ GtkConstraintLayout *layout = GTK_CONSTRAINT_LAYOUT (gtk_widget_get_layout_manager (GTK_WIDGET (self)));
+ double x, y;
+
+ if (self->constraint)
+ {
+ gtk_constraint_layout_remove_constraint (layout, self->constraint);
+ g_clear_object (&self->constraint);
+ }
+
+ gtk_gesture_drag_get_start_point (drag, &x, &y);
+ self->constraint = gtk_constraint_new_constant (GTK_CONSTRAINT_TARGET (self->guide),
+ GTK_CONSTRAINT_ATTRIBUTE_LEFT,
+ GTK_CONSTRAINT_RELATION_EQ,
+ x + offset_x,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_layout_add_constraint (layout, g_object_ref (self->constraint));
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+interactive_grid_init (InteractiveGrid *self)
+{
+ GtkWidget *widget = GTK_WIDGET (self);
+ GtkGesture *drag;
+
+ self->button1 = gtk_button_new_with_label ("Child 1");
+ gtk_widget_set_parent (self->button1, widget);
+ gtk_widget_set_name (self->button1, "button1");
+
+ self->button2 = gtk_button_new_with_label ("Child 2");
+ gtk_widget_set_parent (self->button2, widget);
+ gtk_widget_set_name (self->button2, "button2");
+
+ self->button3 = gtk_button_new_with_label ("Child 3");
+ gtk_widget_set_parent (self->button3, widget);
+ gtk_widget_set_name (self->button3, "button3");
+
+ GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
+ build_constraints (self, GTK_CONSTRAINT_LAYOUT (manager));
+
+ drag = gtk_gesture_drag_new ();
+ g_signal_connect (drag, "drag-update", G_CALLBACK (drag_cb), self);
+ gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drag));
+}
+
+GtkWidget *
+do_constraints2 (GtkWidget *do_widget)
+{
+ static GtkWidget *window;
+
+ if (!window)
+ {
+ GtkWidget *header, *box, *grid, *button;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
+
+ header = gtk_header_bar_new ();
+ gtk_header_bar_set_title (GTK_HEADER_BAR (header), "Constraints");
+ gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), FALSE);
+ gtk_window_set_titlebar (GTK_WINDOW (window), header);
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ grid = g_object_new (interactive_grid_get_type (), NULL);
+ gtk_widget_set_hexpand (grid, TRUE);
+ gtk_widget_set_vexpand (grid, TRUE);
+ gtk_container_add (GTK_CONTAINER (box), grid);
+
+ button = gtk_button_new_with_label ("Close");
+ gtk_container_add (GTK_CONTAINER (box), button);
+ gtk_widget_set_hexpand (grid, TRUE);
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gtk_widget_destroy), window);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show (window);
+ else
+ gtk_widget_destroy (window);
+
+ return window;
+}
diff --git a/demos/gtk-demo/constraints3.c b/demos/gtk-demo/constraints3.c
new file mode 100644
index 0000000000..648f38125b
--- /dev/null
+++ b/demos/gtk-demo/constraints3.c
@@ -0,0 +1,165 @@
+/* Constraints/VFL
+ *
+ * GtkConstraintLayout allows defining constraints using a
+ * compact syntax called Visual Format Language, or VFL.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE (VflGrid, vfl_grid, VFL, GRID, GtkWidget)
+
+struct _VflGrid
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *button1, *button2;
+ GtkWidget *button3;
+};
+
+G_DEFINE_TYPE (VflGrid, vfl_grid, GTK_TYPE_WIDGET)
+
+static void
+vfl_grid_destroy (GtkWidget *widget)
+{
+ VflGrid *self = VFL_GRID (widget);
+
+ g_clear_pointer (&self->button1, gtk_widget_destroy);
+ g_clear_pointer (&self->button2, gtk_widget_destroy);
+ g_clear_pointer (&self->button3, gtk_widget_destroy);
+
+ GTK_WIDGET_CLASS (vfl_grid_parent_class)->destroy (widget);
+}
+
+static void
+vfl_grid_class_init (VflGridClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ widget_class->destroy = vfl_grid_destroy;
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_CONSTRAINT_LAYOUT);
+}
+
+/* Layout:
+ *
+ * +-----------------------------+
+ * | +-----------+ +-----------+ |
+ * | | Child 1 | | Child 2 | |
+ * | +-----------+ +-----------+ |
+ * | +-------------------------+ |
+ * | | Child 3 | |
+ * | +-------------------------+ |
+ * +-----------------------------+
+ *
+ * Constraints:
+ *
+ * super.start = child1.start - 8
+ * child1.width = child2.width
+ * child1.end = child2.start - 12
+ * child2.end = super.end - 8
+ * super.start = child3.start - 8
+ * child3.end = super.end - 8
+ * super.top = child1.top - 8
+ * super.top = child2.top - 8
+ * child1.bottom = child3.top - 12
+ * child2.bottom = child3.top - 12
+ * child3.height = child1.height
+ * child3.height = child2.height
+ * child3.bottom = super.bottom - 8
+ *
+ * Visual format:
+ *
+ * H:|-8-[view1(==view2)-12-[view2]-8-|
+ * H:|-8-[view3]-8-|
+ * V:|-8-[view1]-12-[view3(==view1)]-8-|
+ * V:|-8-[view2]-12-[view3(==view2)]-8-|
+ */
+static void
+build_constraints (VflGrid *self,
+ GtkConstraintLayout *manager)
+{
+ const char * const vfl[] = {
+ "H:|-[button1(==button2)]-12-[button2]-|",
+ "H:|-[button3]-|",
+ "V:|-[button1]-12-[button3(==button1)]-|",
+ "V:|-[button2]-12-[button3(==button2)]-|",
+ };
+ GError *error = NULL;
+
+ gtk_constraint_layout_add_constraints_from_description (manager, vfl, G_N_ELEMENTS (vfl),
+ 8, 8,
+ &error,
+ "button1", self->button1,
+ "button2", self->button2,
+ "button3", self->button3,
+ NULL);
+ if (error != NULL)
+ {
+ g_printerr ("VFL parsing error:\n%s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+vfl_grid_init (VflGrid *self)
+{
+ GtkWidget *widget = GTK_WIDGET (self);
+
+ self->button1 = gtk_button_new_with_label ("Child 1");
+ gtk_widget_set_parent (self->button1, widget);
+ gtk_widget_set_name (self->button1, "button1");
+
+ self->button2 = gtk_button_new_with_label ("Child 2");
+ gtk_widget_set_parent (self->button2, widget);
+ gtk_widget_set_name (self->button2, "button2");
+
+ self->button3 = gtk_button_new_with_label ("Child 3");
+ gtk_widget_set_parent (self->button3, widget);
+ gtk_widget_set_name (self->button3, "button3");
+
+ GtkLayoutManager *manager = gtk_widget_get_layout_manager (GTK_WIDGET (self));
+ build_constraints (self, GTK_CONSTRAINT_LAYOUT (manager));
+}
+
+GtkWidget *
+do_constraints3 (GtkWidget *do_widget)
+{
+ static GtkWidget *window;
+
+ if (!window)
+ {
+ GtkWidget *header, *box, *grid, *button;
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_display (GTK_WINDOW (window), gtk_widget_get_display (do_widget));
+
+ header = gtk_header_bar_new ();
+ gtk_header_bar_set_title (GTK_HEADER_BAR (header), "Constraints");
+ gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), FALSE);
+ gtk_window_set_titlebar (GTK_WINDOW (window), header);
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ grid = g_object_new (vfl_grid_get_type (), NULL);
+ gtk_widget_set_hexpand (grid, TRUE);
+ gtk_widget_set_vexpand (grid, TRUE);
+ gtk_container_add (GTK_CONTAINER (box), grid);
+
+ button = gtk_button_new_with_label ("Close");
+ gtk_container_add (GTK_CONTAINER (box), button);
+ gtk_widget_set_hexpand (grid, TRUE);
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (gtk_widget_destroy), window);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show (window);
+ else
+ gtk_widget_destroy (window);
+
+ return window;
+}
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index ca8558dc29..6b6205b6b7 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -150,6 +150,9 @@
<file>clipboard.c</file>
<file>colorsel.c</file>
<file>combobox.c</file>
+ <file>constraints.c</file>
+ <file>constraints2.c</file>
+ <file>constraints3.c</file>
<file>css_accordion.c</file>
<file>css_basics.c</file>
<file>css_blendmodes.c</file>
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 87e5ae4f85..b6f52cb25c 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -8,6 +8,9 @@ demos = files([
'clipboard.c',
'colorsel.c',
'combobox.c',
+ 'constraints.c',
+ 'constraints2.c',
+ 'constraints3.c',
'css_accordion.c',
'css_basics.c',
'css_blendmodes.c',
diff --git a/demos/meson.build b/demos/meson.build
index 6f1905ddb6..c9d7081b40 100644
--- a/demos/meson.build
+++ b/demos/meson.build
@@ -1,3 +1,4 @@
+subdir('constraint-editor')
subdir('gtk-demo')
subdir('icon-browser')
subdir('node-editor')
diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml
index e574d082ee..d220270a81 100644
--- a/docs/reference/gtk/gtk4-docs.xml
+++ b/docs/reference/gtk/gtk4-docs.xml
@@ -109,6 +109,9 @@
<xi:include href="xml/gtkcustomlayout.xml" />
<xi:include href="xml/gtkfixedlayout.xml" />
<xi:include href="xml/gtkgridlayout.xml" />
+ <xi:include href="xml/gtkconstraintlayout.xml" />
+ <xi:include href="xml/gtkconstraint.xml" />
+ <xi:include href="xml/gtkconstraintguide.xml" />
</chapter>
<chapter id="DisplayWidgets">
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index a9076529c2..ac9ea19fa9 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -7287,3 +7287,83 @@ gtk_grid_layout_get_type
GTK_TYPE_GRID_LAYOUT_CHILD
gtk_grid_layout_child_get_type
</SECTION>
+
+<SECTION>
+<FILE>gtkconstraint</FILE>
+GtkConstraint
+GtkConstraintTarget
+
+gtk_constraint_new
+gtk_constraint_new_constant
+gtk_constraint_get_target
+GtkConstraintAttribute
+gtk_constraint_get_target_attribute
+GtkConstraintRelation
+gtk_constraint_get_relation
+gtk_constraint_get_source
+gtk_constraint_get_source_attribute
+gtk_constraint_get_multiplier
+gtk_constraint_get_constant
+GtkConstraintStrength
+gtk_constraint_get_strength
+gtk_constraint_is_required
+gtk_constraint_is_attached
+gtk_constraint_is_constant
+
+<SUBSECTION Standard>
+GTK_TYPE_CONSTRAINT
+gtk_constraint_get_type
+GTK_TYPE_CONSTRAINT_TARGET
+gtk_constraint_target_get_type
+</SECTION>
+
+<SECTION>
+<FILE>gtkconstraintlayout</FILE>
+GtkConstraintLayout
+GtkConstraintLayoutChild
+GtkConstraintVflParserError
+
+gtk_constraint_layout_new
+
+<SUBSECTION Constraints>
+gtk_constraint_layout_add_constraint
+gtk_constraint_layout_remove_constraint
+gtk_constraint_layout_remove_all_constraints
+
+<SUBSECTION Guides>
+gtk_constraint_layout_add_guide
+gtk_constraint_layout_remove_guide
+
+<SUBSECTION VFL>
+gtk_constraint_layout_add_constraints_from_description
+gtk_constraint_layout_add_constraints_from_descriptionv
+
+<SUBSECTION Standard>
+GTK_TYPE_CONSTRAINT_LAYOUT
+gtk_constraint_layout_get_type
+GTK_TYPE_CONSTRAINT_LAYOUT_CHILD
+gtk_constraint_layout_child_get_type
+GTK_CONSTRAINT_VFL_PARSER_ERROR
+gtk_constraint_vfl_parser_error_quark
+</SECTION>
+
+<SECTION>
+<FILE>gtkconstraintguide</FILE>
+GtkConstraintGuide
+
+gtk_constraint_guide_new
+gtk_constraint_guide_set_name
+gtk_constraint_guide_get_name
+gtk_constraint_guide_set_strength
+gtk_constraint_guide_get_strength
+gtk_constraint_guide_set_min_size
+gtk_constraint_guide_get_min_size
+gtk_constraint_guide_set_nat_size
+gtk_constraint_guide_get_nat_size
+gtk_constraint_guide_set_max_size
+gtk_constraint_guide_get_max_size
+
+<SUBSECTION Standard>
+GTK_TYPE_CONSTRAINT_GUIDE
+gtk_constraint_guide_get_tyoe
+</SECTION>
diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in
index 8073f3ad20..a03901d769 100644
--- a/docs/reference/gtk/gtk4.types.in
+++ b/docs/reference/gtk/gtk4.types.in
@@ -49,6 +49,10 @@ gtk_color_chooser_dialog_get_type
gtk_color_chooser_widget_get_type
gtk_combo_box_get_type
gtk_combo_box_text_get_type
+gtk_constraint_get_type
+gtk_constraint_guide_get_type
+gtk_constraint_layout_get_type
+gtk_constraint_target_get_type
gtk_container_get_type
gtk_css_provider_get_type
gtk_dialog_get_type
diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build
index 1243d12c95..c9f1e9c2c9 100644
--- a/docs/reference/gtk/meson.build
+++ b/docs/reference/gtk/meson.build
@@ -23,6 +23,13 @@ private_headers = [
'gtkcolorswatchprivate.h',
'gtkcomboboxprivate.h',
'gtkcontainerprivate.h',
+ 'gtkconstraintexpressionprivate.h',
+ 'gtkconstraintguideprivate.h',
+ 'gtkconstraintlayoutprivate.h',
+ 'gtkconstraintprivate.h',
+ 'gtkconstraintsolverprivate.h',
+ 'gtkconstrainttypesprivate.h',
+ 'gtkconstraintvflparserprivate.h',
'gtkcssanimatedstyleprivate.h',
'gtkcssanimationprivate.h',
'gtkcssarrayvalueprivate.h',
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..4ddaa5ac1b
--- /dev/null
+++ b/gtk/gtkconstraint.c
@@ -0,0 +1,614 @@
+/* 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 = 1,
+ PROP_TARGET_ATTRIBUTE,
+ PROP_RELATION,
+ PROP_SOURCE,
+ 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:
+ self->target = 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:
+ self->source = 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:
+ g_value_set_object (value, self->target);
+ 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:
+ g_value_set_object (value, self->source);
+ 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:
+ *
+ * The target of the constraint.
+ *
+ * The constraint will set the #GtkConstraint:target-attribute of the
+ * target using the #GtkConstraint:source-attribute of the source
+ * widget.
+ */
+ obj_props[PROP_TARGET] =
+ g_param_spec_object ("target",
+ P_("Target"),
+ P_("The target of the constraint"),
+ GTK_TYPE_CONSTRAINT_TARGET,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY);
+ /**
+ * GtkConstraint:target-attribute:
+ *
+ * The attribute of the #GtkConstraint:target set by the constraint.
+ */
+ obj_props[PROP_TARGET_ATTRIBUTE] =
+ g_param_spec_enum ("target-attribute",
+ P_("Target Attribute"),
+ P_("The attribute of the target 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 order relation 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:
+ *
+ * The source of the constraint.
+ *
+ * The constraint will set the #GtkConstraint:target-attribute of the
+ * target using the #GtkConstraint:source-attribute of the source.
+ */
+ obj_props[PROP_SOURCE] =
+ g_param_spec_object ("source",
+ P_("Source"),
+ P_("The source of the constraint"),
+ GTK_TYPE_CONSTRAINT_TARGET,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_CONSTRUCT_ONLY);
+ /**
+ * GtkConstraint:source-attribute:
+ *
+ * The attribute of the #GtkConstraint:source 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"),
+ 0, GTK_CONSTRAINT_STRENGTH_REQUIRED,
+ 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: (nullable) (type GtkConstraintTarget): a #GtkConstraintTarget
+ * @target_attribute: the attribute of @target to be set
+ * @relation: the relation equivalence between @target_attribute and @source_attribute
+ * @source: (nullable) (type GtkConstraintTarget): a #GtkConstraintTarget
+ * @source_attribute: the attribute of @source 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 and a layout attribute on a target.
+ *
+ * Returns: the newly created #GtkConstraint
+ */
+GtkConstraint *
+gtk_constraint_new (gpointer target,
+ GtkConstraintAttribute target_attribute,
+ GtkConstraintRelation relation,
+ gpointer source,
+ GtkConstraintAttribute source_attribute,
+ double multiplier,
+ double constant,
+ int strength)
+{
+ g_return_val_if_fail (target == NULL || GTK_IS_CONSTRAINT_TARGET (target), NULL);
+ g_return_val_if_fail (source == NULL || GTK_IS_CONSTRAINT_TARGET (source), NULL);
+
+ return g_object_new (GTK_TYPE_CONSTRAINT,
+ "target", target,
+ "target-attribute", target_attribute,
+ "relation", relation,
+ "source", source,
+ "source-attribute", source_attribute,
+ "multiplier", multiplier,
+ "constant", constant,
+ "strength", strength,
+ NULL);
+}
+
+/**
+ * gtk_constraint_new_constant:
+ * @target: (nullable) (type GtkConstraintTarget): a #GtkConstraintTarget
+ * @target_attribute: the attribute of @target 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 and a constant value.
+ *
+ * Returns: the newly created #GtkConstraint
+ */
+GtkConstraint *
+gtk_constraint_new_constant (gpointer target,
+ GtkConstraintAttribute target_attribute,
+ GtkConstraintRelation relation,
+ double constant,
+ int strength)
+{
+ g_return_val_if_fail (target == NULL || GTK_IS_CONSTRAINT_TARGET (target), NULL);
+
+ return g_object_new (GTK_TYPE_CONSTRAINT,
+ "target", target,
+ "target-attribute", target_attribute,
+ "relation", relation,
+ "source-attribute", GTK_CONSTRAINT_ATTRIBUTE_NONE,
+ "constant", constant,
+ "strength", strength,
+ NULL);
+}
+
+/**
+ * gtk_constraint_get_target:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the #GtkConstraintTarget used as the target for @constraint.
+ *
+ * If the #GtkConstraint:target property is set to %NULL, the @constraint
+ * will use the #GtkConstraintLayout's widget.
+ *
+ * Returns: (transfer none) (nullable): a #GtkConstraintTarget
+ */
+GtkConstraintTarget *
+gtk_constraint_get_target (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), NULL);
+
+ return constraint->target;
+}
+
+/**
+ * gtk_constraint_get_target_attribute:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the attribute of the target to be set by the @constraint.
+ *
+ * Returns: the target's attribute
+ */
+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:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the #GtkConstraintTarget used as the source for @constraint.
+ *
+ * If the #GtkConstraint:source property is set to %NULL, the @constraint
+ * will use the #GtkConstraintLayout's widget.
+ *
+ * Returns: (transfer none) (nullable): a #GtkConstraintTarget
+ */
+GtkConstraintTarget *
+gtk_constraint_get_source (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), NULL);
+
+ return constraint->source;
+}
+
+/**
+ * gtk_constraint_get_source_attribute:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the attribute of the source to be read by the @constraint.
+ *
+ * Returns: the target's attribute
+ */
+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;
+}
+
+/**
+ * gtk_constraint_get_relation:
+ * @constraint: a #GtkConstraint
+ *
+ * The order relation between the terms of the @constraint.
+ *
+ * Returns: a #GtkConstraintRelation value
+ */
+GtkConstraintRelation
+gtk_constraint_get_relation (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_RELATION_EQ);
+
+ return constraint->relation;
+}
+
+/**
+ * gtk_constraint_get_multiplier:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the multiplication factor applied to the source
+ * attribute's value.
+ *
+ * Returns: a multiplication factor
+ */
+double
+gtk_constraint_get_multiplier (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), 1.0);
+
+ return constraint->multiplier;
+}
+
+/**
+ * gtk_constraint_get_constant:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the constant factor added to the source attributes' value.
+ *
+ * Returns: a constant factor
+ */
+double
+gtk_constraint_get_constant (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), 0.0);
+
+ return constraint->constant;
+}
+
+/**
+ * gtk_constraint_get_strength:
+ * @constraint: a #GtkConstraint
+ *
+ * Retrieves the strength of the constraint.
+ *
+ * Returns: the strength of the constraint
+ */
+int
+gtk_constraint_get_strength (GtkConstraint *constraint)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT (constraint), GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ return constraint->strength;
+}
+
+/**
+ * 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 == 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;
+}
+
+typedef struct _GtkConstraintTargetInterface GtkConstraintTargetInterface;
+
+struct _GtkConstraintTargetInterface
+{
+ GTypeInterface g_iface;
+};
+
+G_DEFINE_INTERFACE (GtkConstraintTarget, gtk_constraint_target, G_TYPE_OBJECT)
+
+static void
+gtk_constraint_target_default_init (GtkConstraintTargetInterface *iface)
+{
+}
diff --git a/gtk/gtkconstraint.h b/gtk/gtkconstraint.h
new file mode 100644
index 0000000000..2f14e66999
--- /dev/null
+++ b/gtk/gtkconstraint.h
@@ -0,0 +1,100 @@
+/* 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
+
+typedef struct _GtkConstraintTarget GtkConstraintTarget;
+
+#define GTK_TYPE_CONSTRAINT_TARGET (gtk_constraint_target_get_type ())
+
+/**
+ * GtkConstraintTarget:
+ *
+ * The GtkConstraintTarget interface is implemented by objects that
+ * can be used as source or target in #GtkConstraints. Besides
+ * #GtkWidget, it is also implemented by #GtkConstraintGuide.
+ */
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_INTERFACE (GtkConstraintTarget, gtk_constraint_target, GTK, CONSTRAINT_TARGET, GObject)
+
+#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 (gpointer target,
+ GtkConstraintAttribute target_attribute,
+ GtkConstraintRelation relation,
+ gpointer source,
+ GtkConstraintAttribute source_attribute,
+ double multiplier,
+ double constant,
+ int strength);
+GDK_AVAILABLE_IN_ALL
+GtkConstraint * gtk_constraint_new_constant (gpointer target,
+ GtkConstraintAttribute target_attribute,
+ GtkConstraintRelation relation,
+ double constant,
+ int strength);
+
+GDK_AVAILABLE_IN_ALL
+GtkConstraintTarget * gtk_constraint_get_target (GtkConstraint *constraint);
+GDK_AVAILABLE_IN_ALL
+GtkConstraintAttribute gtk_constraint_get_target_attribute (GtkConstraint *constraint);
+GDK_AVAILABLE_IN_ALL
+GtkConstraintTarget * gtk_constraint_get_source (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/gtkconstraintexpression.c b/gtk/gtkconstraintexpression.c
new file mode 100644
index 0000000000..f3a0a37f79
--- /dev/null
+++ b/gtk/gtkconstraintexpression.c
@@ -0,0 +1,1833 @@
+/* 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 *prefix,
+ const char *name)
+{
+ variable->_id = gtk_constraint_variable_next_id++;
+
+ variable->prefix = g_intern_string (prefix);
+ 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, NULL, 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, NULL, 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, NULL, 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:
+ * @prefix: (nullable): an optional prefix string for @name
+ * @name: (nullable): an optional name for 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 *prefix,
+ const char *name)
+{
+ GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);
+
+ gtk_constraint_variable_init (res, prefix, 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_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 {
+ /* List<Variable>, owns a reference */
+ GSequence *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_sequence_free (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_sequence_new ((GDestroyNotify) gtk_constraint_variable_unref);
+
+ res->age = 0;
+
+ return res;
+}
+
+static int
+sort_by_variable_id (gconstpointer a,
+ gconstpointer b,
+ gpointer data)
+{
+ 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)
+{
+ GSequenceIter *iter;
+
+ iter = g_sequence_search (set->set, variable, sort_by_variable_id, NULL);
+ if (!g_sequence_iter_is_end (iter))
+ {
+ GtkConstraintVariable *v = g_sequence_get (iter);
+ if (v->_id == variable->_id)
+ return FALSE;
+ }
+
+ g_sequence_insert_before (iter, gtk_constraint_variable_ref (variable));
+
+ 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)
+{
+ GSequenceIter *iter;
+
+ iter = g_sequence_lookup (set->set, variable, sort_by_variable_id, NULL);
+ if (iter != NULL)
+ {
+ g_sequence_remove (iter);
+ 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_sequence_get_length (set->set);
+}
+
+gboolean
+gtk_constraint_variable_set_is_empty (GtkConstraintVariableSet *set)
+{
+ return g_sequence_is_empty (set->set);
+}
+
+gboolean
+gtk_constraint_variable_set_is_singleton (GtkConstraintVariableSet *set)
+{
+ return g_sequence_iter_next (g_sequence_get_begin_iter (set->set)) == g_sequence_get_end_iter (set->set);
+}
+
+/*< private >
+ * GtkConstraintVariableSetIter:
+ *
+ * An iterator type for #GtkConstraintVariableSet.
+ */
+/* Keep in sync with GtkConstraintVariableSetIter */
+typedef struct {
+ GtkConstraintVariableSet *set;
+ GSequenceIter *iter;
+ 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->iter = g_sequence_get_begin_iter (set->set);
+ 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 (g_sequence_iter_is_end (riter->iter))
+ return FALSE;
+
+ *variable_p = g_sequence_get (riter->iter);
+ riter->iter = g_sequence_iter_next (riter->iter);
+
+ return TRUE;
+}
+
+/*< 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);
+ }
+ 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);
+}
+
+/* }}} */
diff --git a/gtk/gtkconstraintexpressionprivate.h b/gtk/gtkconstraintexpressionprivate.h
new file mode 100644
index 0000000000..942915ad38
--- /dev/null
+++ b/gtk/gtkconstraintexpressionprivate.h
@@ -0,0 +1,279 @@
+/* gtkconstraintequationprivate.h: 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
+ */
+
+#pragma once
+
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+GtkConstraintVariable *
+gtk_constraint_variable_new (const char *prefix,
+ const char *name);
+
+GtkConstraintVariable *
+gtk_constraint_variable_new_dummy (const char *name);
+
+GtkConstraintVariable *
+gtk_constraint_variable_new_objective (const char *name);
+
+GtkConstraintVariable *
+gtk_constraint_variable_new_slack (const char *name);
+
+GtkConstraintVariable *
+gtk_constraint_variable_ref (GtkConstraintVariable *variable);
+
+void
+gtk_constraint_variable_unref (GtkConstraintVariable *variable);
+
+void
+gtk_constraint_variable_set_value (GtkConstraintVariable *variable,
+ double value);
+
+double
+gtk_constraint_variable_get_value (const GtkConstraintVariable *variable);
+
+char *
+gtk_constraint_variable_to_string (const GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_is_external (const GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_is_pivotable (const GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_is_restricted (const GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_is_dummy (const GtkConstraintVariable *variable);
+
+typedef struct {
+ GtkConstraintVariable *first;
+ GtkConstraintVariable *second;
+} GtkConstraintVariablePair;
+
+GtkConstraintVariablePair *
+gtk_constraint_variable_pair_new (GtkConstraintVariable *first,
+ GtkConstraintVariable *second);
+
+void
+gtk_constraint_variable_pair_free (GtkConstraintVariablePair *pair);
+
+typedef struct _GtkConstraintVariableSet GtkConstraintVariableSet;
+
+GtkConstraintVariableSet *
+gtk_constraint_variable_set_new (void);
+
+void
+gtk_constraint_variable_set_free (GtkConstraintVariableSet *set);
+
+gboolean
+gtk_constraint_variable_set_add (GtkConstraintVariableSet *set,
+ GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_set_remove (GtkConstraintVariableSet *set,
+ GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_variable_set_is_empty (GtkConstraintVariableSet *set);
+
+gboolean
+gtk_constraint_variable_set_is_singleton (GtkConstraintVariableSet *set);
+
+int
+gtk_constraint_variable_set_size (GtkConstraintVariableSet *set);
+
+typedef struct {
+ /*< private >*/
+ gpointer dummy1;
+ gpointer dummy2;
+ gint64 dummy3;
+} GtkConstraintVariableSetIter;
+
+void
+gtk_constraint_variable_set_iter_init (GtkConstraintVariableSetIter *iter,
+ GtkConstraintVariableSet *set);
+
+gboolean
+gtk_constraint_variable_set_iter_next (GtkConstraintVariableSetIter *iter,
+ GtkConstraintVariable **variable_p);
+
+GtkConstraintExpression *
+gtk_constraint_expression_new (double constant);
+
+GtkConstraintExpression *
+gtk_constraint_expression_new_from_variable (GtkConstraintVariable *variable);
+
+GtkConstraintExpression *
+gtk_constraint_expression_ref (GtkConstraintExpression *expression);
+
+void
+gtk_constraint_expression_unref (GtkConstraintExpression *expression);
+
+GtkConstraintExpression *
+gtk_constraint_expression_clone (GtkConstraintExpression *expression);
+
+void
+gtk_constraint_expression_set_constant (GtkConstraintExpression *expression,
+ double constant);
+double
+gtk_constraint_expression_get_constant (const GtkConstraintExpression *expression);
+
+gboolean
+gtk_constraint_expression_is_constant (const GtkConstraintExpression *expression);
+
+void
+gtk_constraint_expression_add_expression (GtkConstraintExpression *a_expr,
+ GtkConstraintExpression *b_expr,
+ double n,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver);
+
+void
+gtk_constraint_expression_add_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable,
+ double coefficient,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver);
+
+void
+gtk_constraint_expression_remove_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable);
+
+void
+gtk_constraint_expression_set_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable,
+ double coefficient);
+
+double
+gtk_constraint_expression_get_coefficient (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable);
+
+char *
+gtk_constraint_expression_to_string (const GtkConstraintExpression *expression);
+
+double
+gtk_constraint_expression_new_subject (GtkConstraintExpression *expression,
+ GtkConstraintVariable *subject);
+
+void
+gtk_constraint_expression_change_subject (GtkConstraintExpression *expression,
+ GtkConstraintVariable *old_subject,
+ GtkConstraintVariable *new_subject);
+
+void
+gtk_constraint_expression_substitute_out (GtkConstraintExpression *expression,
+ GtkConstraintVariable *out_var,
+ GtkConstraintExpression *expr,
+ GtkConstraintVariable *subject,
+ GtkConstraintSolver *solver);
+
+GtkConstraintVariable *
+gtk_constraint_expression_get_pivotable_variable (GtkConstraintExpression *expression);
+
+GtkConstraintExpression *
+gtk_constraint_expression_plus_constant (GtkConstraintExpression *expression,
+ double constant);
+
+GtkConstraintExpression *
+gtk_constraint_expression_minus_constant (GtkConstraintExpression *expression,
+ double constant);
+
+GtkConstraintExpression *
+gtk_constraint_expression_plus_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable);
+
+GtkConstraintExpression *
+gtk_constraint_expression_minus_variable (GtkConstraintExpression *expression,
+ GtkConstraintVariable *variable);
+
+GtkConstraintExpression *
+gtk_constraint_expression_multiply_by (GtkConstraintExpression *expression,
+ double factor);
+
+GtkConstraintExpression *
+gtk_constraint_expression_divide_by (GtkConstraintExpression *expression,
+ double factor);
+
+struct _GtkConstraintExpressionBuilder
+{
+ /*< private >*/
+ gpointer dummy1;
+ gpointer dummy2;
+ int dummy3;
+};
+
+void
+gtk_constraint_expression_builder_init (GtkConstraintExpressionBuilder *builder,
+ GtkConstraintSolver *solver);
+
+void
+gtk_constraint_expression_builder_term (GtkConstraintExpressionBuilder *builder,
+ GtkConstraintVariable *term);
+
+void
+gtk_constraint_expression_builder_plus (GtkConstraintExpressionBuilder *builder);
+
+void
+gtk_constraint_expression_builder_minus (GtkConstraintExpressionBuilder *builder);
+
+void
+gtk_constraint_expression_builder_divide_by (GtkConstraintExpressionBuilder *builder);
+
+void
+gtk_constraint_expression_builder_multiply_by (GtkConstraintExpressionBuilder *builder);
+
+void
+gtk_constraint_expression_builder_constant (GtkConstraintExpressionBuilder *builder,
+ double value);
+
+GtkConstraintExpression *
+gtk_constraint_expression_builder_finish (GtkConstraintExpressionBuilder *builder) G_GNUC_WARN_UNUSED_RESULT;
+
+/*< private >
+ * GtkConstraintExpressionIter:
+ *
+ * An iterator object for terms inside a #GtkConstraintExpression.
+ */
+typedef struct {
+ /*< private >*/
+ gpointer dummy1;
+ gpointer dummy2;
+ gint64 dummy3;
+} GtkConstraintExpressionIter;
+
+void
+gtk_constraint_expression_iter_init (GtkConstraintExpressionIter *iter,
+ GtkConstraintExpression *equation);
+
+gboolean
+gtk_constraint_expression_iter_next (GtkConstraintExpressionIter *iter,
+ GtkConstraintVariable **variable,
+ double *coefficient);
+
+gboolean
+gtk_constraint_expression_iter_prev (GtkConstraintExpressionIter *iter,
+ GtkConstraintVariable **variable,
+ double *coefficient);
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintguide.c b/gtk/gtkconstraintguide.c
new file mode 100644
index 0000000000..de7bb03499
--- /dev/null
+++ b/gtk/gtkconstraintguide.c
@@ -0,0 +1,694 @@
+/* gtkconstraintguide.c: Flexible space for 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+/**
+ * SECTION:gtkconstraintguide
+ * @Title: GtkConstraintGuide
+ * @Short_description: An invisible constraint target
+ *
+ * A #GtkConstraintGuide is an invisible layout element that can be
+ * used by widgets inside a #GtkConstraintLayout as a source or a target
+ * of a #GtkConstraint. Guides can be used like guidelines or as
+ * flexible space.
+ *
+ * Unlike a #GtkWidget, a #GtkConstraintGuide will not be drawn.
+ */
+
+#include "config.h"
+
+#include "gtkconstraintguide.h"
+
+#include "gtkconstraintguideprivate.h"
+#include "gtkconstraintlayoutprivate.h"
+#include "gtkconstraintexpressionprivate.h"
+#include "gtkconstraintsolverprivate.h"
+
+#include "gtkdebug.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+
+typedef enum {
+ MIN_WIDTH,
+ MIN_HEIGHT,
+ NAT_WIDTH,
+ NAT_HEIGHT,
+ MAX_WIDTH,
+ MAX_HEIGHT,
+ LAST_VALUE
+} GuideValue;
+
+struct _GtkConstraintGuide
+{
+ GObject parent_instance;
+
+ char *name;
+
+ int strength;
+
+ int values[LAST_VALUE];
+
+ GtkConstraintLayout *layout;
+
+ /* 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;
+
+ GtkConstraintRef *constraints[LAST_VALUE];
+};
+
+
+struct _GtkConstraintGuideClass {
+ GObjectClass parent_class;
+};
+
+enum {
+ PROP_MIN_WIDTH = 1,
+ PROP_MIN_HEIGHT,
+ PROP_NAT_WIDTH,
+ PROP_NAT_HEIGHT,
+ PROP_MAX_WIDTH,
+ PROP_MAX_HEIGHT,
+ PROP_STRENGTH,
+ PROP_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *guide_props[LAST_PROP];
+
+static void
+gtk_constraint_guide_constraint_target_iface_init (GtkConstraintTargetInterface *iface)
+{
+}
+
+G_DEFINE_TYPE_WITH_CODE (GtkConstraintGuide, gtk_constraint_guide, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_CONSTRAINT_TARGET,
+ gtk_constraint_guide_constraint_target_iface_init))
+
+static void
+gtk_constraint_guide_init (GtkConstraintGuide *guide)
+{
+ guide->strength = GTK_CONSTRAINT_STRENGTH_MEDIUM;
+
+ guide->values[MIN_WIDTH] = 0;
+ guide->values[MIN_HEIGHT] = 0;
+ guide->values[NAT_WIDTH] = 0;
+ guide->values[NAT_HEIGHT] = 0;
+ guide->values[MAX_WIDTH] = G_MAXINT;
+ guide->values[MAX_HEIGHT] = G_MAXINT;
+
+ guide->bound_attributes =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL,
+ (GDestroyNotify) gtk_constraint_variable_unref);
+}
+
+static void
+gtk_constraint_guide_update_constraint (GtkConstraintGuide *guide,
+ GuideValue index)
+{
+ GtkConstraintSolver *solver;
+ GtkConstraintVariable *var;
+
+ if (!guide->layout)
+ return;
+
+ solver = gtk_constraint_layout_get_solver (guide->layout);
+ if (!solver)
+ return;
+
+ if (guide->constraints[index] != NULL)
+ {
+ gtk_constraint_solver_remove_constraint (solver, guide->constraints[index]);
+ guide->constraints[index] = NULL;
+ }
+
+ if (index == MIN_WIDTH || index == NAT_WIDTH || index == MAX_WIDTH)
+ var = gtk_constraint_layout_get_attribute (guide->layout, GTK_CONSTRAINT_ATTRIBUTE_WIDTH, "guide", NULL, guide->bound_attributes);
+ else
+ var = gtk_constraint_layout_get_attribute (guide->layout, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, "guide", NULL, guide->bound_attributes);
+
+ /* We always install min-size constraints,
+ * but we avoid nat-size constraints if min == max
+ * and we avoid max-size constraints if max == G_MAXINT
+ */
+ if (index == MIN_WIDTH || index == MIN_HEIGHT)
+ {
+ guide->constraints[index] =
+ gtk_constraint_solver_add_constraint (solver,
+ var,
+ GTK_CONSTRAINT_RELATION_GE,
+ gtk_constraint_expression_new (guide->values[index]),
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ }
+ else if ((index == NAT_WIDTH && guide->values[MIN_WIDTH] != guide->values[MAX_WIDTH]) ||
+ (index == NAT_HEIGHT && guide->values[MIN_HEIGHT] != guide->values[MAX_HEIGHT]))
+ {
+ gtk_constraint_variable_set_value (var, guide->values[index]);
+ guide->constraints[index] =
+ gtk_constraint_solver_add_stay_variable (solver,
+ var,
+ guide->strength);
+ }
+ else if ((index == MAX_WIDTH || index == MAX_HEIGHT) &&
+ guide->values[index] < G_MAXINT)
+ {
+ guide->constraints[index] =
+ gtk_constraint_solver_add_constraint (solver,
+ var,
+ GTK_CONSTRAINT_RELATION_LE,
+ gtk_constraint_expression_new (guide->values[index]),
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ }
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (guide->layout));
+}
+
+void
+gtk_constraint_guide_update (GtkConstraintGuide *guide)
+{
+ int i;
+
+ for (i = 0; i < LAST_VALUE; i++)
+ gtk_constraint_guide_update_constraint (guide, i);
+}
+
+void
+gtk_constraint_guide_detach (GtkConstraintGuide *guide)
+{
+ GtkConstraintSolver *solver;
+ int i;
+
+ if (!guide->layout)
+ return;
+
+ solver = gtk_constraint_layout_get_solver (guide->layout);
+ if (!solver)
+ return;
+
+ for (i = 0; i < LAST_VALUE; i++)
+ {
+ if (guide->constraints[i])
+ {
+ gtk_constraint_solver_remove_constraint (solver, guide->constraints[i]);
+ guide->constraints[i] = NULL;
+ }
+ }
+
+ g_hash_table_remove_all (guide->bound_attributes);
+}
+
+GtkConstraintVariable *
+gtk_constraint_guide_get_attribute (GtkConstraintGuide *guide,
+ GtkConstraintAttribute attr)
+{
+ GtkLayoutManager *manager = GTK_LAYOUT_MANAGER (guide->layout);
+ GtkWidget *widget = gtk_layout_manager_get_widget (manager);
+
+ return gtk_constraint_layout_get_attribute (guide->layout, attr, "guide", widget, guide->bound_attributes);
+}
+
+GtkConstraintLayout *
+gtk_constraint_guide_get_layout (GtkConstraintGuide *guide)
+{
+ return guide->layout;
+}
+
+void
+gtk_constraint_guide_set_layout (GtkConstraintGuide *guide,
+ GtkConstraintLayout *layout)
+{
+ guide->layout = layout;
+}
+
+static void
+gtk_constraint_guide_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (gobject);
+ int val;
+ GuideValue index;
+
+ switch (prop_id)
+ {
+ case PROP_MIN_WIDTH:
+ case PROP_MIN_HEIGHT:
+ case PROP_NAT_WIDTH:
+ case PROP_NAT_HEIGHT:
+ case PROP_MAX_WIDTH:
+ case PROP_MAX_HEIGHT:
+ val = g_value_get_int (value);
+ index = prop_id - 1;
+ if (self->values[index] != val)
+ {
+ self->values[index] = val;
+ g_object_notify_by_pspec (gobject, pspec);
+
+ gtk_constraint_guide_update_constraint (self, index);
+ if (index == MIN_WIDTH || index == MAX_WIDTH)
+ gtk_constraint_guide_update_constraint (self, NAT_WIDTH);
+ if (index == MIN_HEIGHT || index == MAX_HEIGHT)
+ gtk_constraint_guide_update_constraint (self, NAT_HEIGHT);
+ }
+ break;
+
+ case PROP_STRENGTH:
+ gtk_constraint_guide_set_strength (self, g_value_get_enum (value));
+ break;
+
+ case PROP_NAME:
+ gtk_constraint_guide_set_name (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_constraint_guide_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (gobject);
+
+ switch (prop_id)
+ {
+ case PROP_MIN_WIDTH:
+ case PROP_MIN_HEIGHT:
+ case PROP_NAT_WIDTH:
+ case PROP_NAT_HEIGHT:
+ case PROP_MAX_WIDTH:
+ case PROP_MAX_HEIGHT:
+ g_value_set_int (value, self->values[prop_id - 1]);
+ break;
+
+ case PROP_STRENGTH:
+ g_value_set_enum (value, self->strength);
+ break;
+
+ case PROP_NAME:
+ g_value_set_string (value, self->name);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_constraint_guide_finalize (GObject *object)
+{
+ GtkConstraintGuide *self = GTK_CONSTRAINT_GUIDE (object);
+
+ g_free (self->name);
+
+ g_clear_pointer (&self->bound_attributes, g_hash_table_unref);
+
+ G_OBJECT_CLASS (gtk_constraint_guide_parent_class)->finalize (object);
+}
+
+static void
+gtk_constraint_guide_class_init (GtkConstraintGuideClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->finalize = gtk_constraint_guide_finalize;
+ object_class->set_property = gtk_constraint_guide_set_property;
+ object_class->get_property = gtk_constraint_guide_get_property;
+
+ /**
+ * GtkConstraintGuide:min-width:
+ *
+ * The minimum width of the guide.
+ */
+ guide_props[PROP_MIN_WIDTH] =
+ g_param_spec_int ("min-width",
+ "Minimum width",
+ "Minimum width",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:min-height:
+ *
+ * The minimum height of the guide.
+ */
+ guide_props[PROP_MIN_HEIGHT] =
+ g_param_spec_int ("min-height",
+ "Minimum height",
+ "Minimum height",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:nat-width:
+ *
+ * The preferred, or natural, width of the guide.
+ */
+ guide_props[PROP_NAT_WIDTH] =
+ g_param_spec_int ("nat-width",
+ "Natural width",
+ "Natural width",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:nat-height:
+ *
+ * The preferred, or natural, height of the guide.
+ */
+ guide_props[PROP_NAT_HEIGHT] =
+ g_param_spec_int ("nat-height",
+ "Natural height",
+ "Natural height",
+ 0, G_MAXINT, 0,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:max-width:
+ *
+ * The maximum width of the guide.
+ */
+ guide_props[PROP_MAX_WIDTH] =
+ g_param_spec_int ("max-width",
+ "Maximum width",
+ "Maximum width",
+ 0, G_MAXINT, G_MAXINT,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:max-height:
+ *
+ * The maximum height of the guide.
+ */
+ guide_props[PROP_MAX_HEIGHT] =
+ g_param_spec_int ("max-height",
+ "Maximum height",
+ "Maximum height",
+ 0, G_MAXINT, G_MAXINT,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:strength:
+ *
+ * The #GtkConstraintStrength to be used for the constraint on
+ * the natural size of the guide.
+ */
+ guide_props[PROP_STRENGTH] =
+ g_param_spec_enum ("strength",
+ "Strength",
+ "The strength to use for natural size",
+ GTK_TYPE_CONSTRAINT_STRENGTH,
+ GTK_CONSTRAINT_STRENGTH_MEDIUM,
+ G_PARAM_READWRITE|
+ G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * GtkConstraintGuide:name:
+ *
+ * A name that identifies the #GtkConstraintGuide, for debugging.
+ */
+ guide_props[PROP_NAME] =
+ g_param_spec_string ("name",
+ "Name",
+ "A name to use in debug message",
+ NULL,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, LAST_PROP, guide_props);
+}
+
+/**
+ * gtk_constraint_guide_new:
+ *
+ * Creates a new #GtkConstraintGuide object.
+ *
+ * Return: a new #GtkConstraintGuide object.
+ */
+GtkConstraintGuide *
+gtk_constraint_guide_new (void)
+{
+ return g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+}
+
+/**
+ * gtk_constraint_guide_set_min_size:
+ * @guide: a #GtkConstraintGuide object
+ * @width: the new minimum width, or -1 to not change it
+ * @height: the new minimum height, or -1 to not change it
+ *
+ * Sets the minimum size of @guide.
+ *
+ * If @guide is attached to a #GtkConstraintLayout,
+ * the constraints will be updated to reflect the new size.
+ */
+void
+gtk_constraint_guide_set_min_size (GtkConstraintGuide *guide,
+ int width,
+ int height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+ g_return_if_fail (width >= -1);
+ g_return_if_fail (height >= -1);
+
+ g_object_freeze_notify (G_OBJECT (guide));
+
+ if (width != -1)
+ g_object_set (guide, "min-width", width, NULL);
+
+ if (height != -1)
+ g_object_set (guide, "min-height", height, NULL);
+
+ g_object_thaw_notify (G_OBJECT (guide));
+}
+
+/**
+ * gtk_constraint_guide_get_min_size:
+ * @guide: a #GtkContraintGuide object
+ * @width: (allow-none): return location for the minimum width,
+ * or %NULL
+ * @height: (allow-none): return location for the minimum height,
+ * or %NULL
+ *
+ * Gets the minimum size of @guide.
+ */
+void
+gtk_constraint_guide_get_min_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+
+ if (width)
+ *width = guide->values[MIN_WIDTH];
+ if (height)
+ *height = guide->values[MIN_HEIGHT];
+}
+
+/**
+ * gtk_constraint_guide_set_nat_size:
+ * @guide: a #GtkConstraintGuide object
+ * @width: the new natural width, or -1 to not change it
+ * @height: the new natural height, or -1 to not change it
+ *
+ * Sets the natural size of @guide.
+ *
+ * If @guide is attached to a #GtkConstraintLayout,
+ * the constraints will be updated to reflect the new size.
+ */
+void
+gtk_constraint_guide_set_nat_size (GtkConstraintGuide *guide,
+ int width,
+ int height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+ g_return_if_fail (width >= -1);
+ g_return_if_fail (height >= -1);
+
+ g_object_freeze_notify (G_OBJECT (guide));
+
+ if (width != -1)
+ g_object_set (guide, "nat-width", width, NULL);
+
+ if (height != -1)
+ g_object_set (guide, "nat-height", height, NULL);
+
+ g_object_thaw_notify (G_OBJECT (guide));
+}
+
+/**
+ * gtk_constraint_guide_get_nat_size:
+ * @guide: a #GtkContraintGuide object
+ * @width: (allow-none): return location for the natural width,
+ * or %NULL
+ * @height: (allow-none): return location for the natural height,
+ * or %NULL
+ *
+ * Gets the natural size of @guide.
+ */
+void
+gtk_constraint_guide_get_nat_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+
+ if (width)
+ *width = guide->values[NAT_WIDTH];
+ if (height)
+ *height = guide->values[NAT_HEIGHT];
+}
+
+/**
+ * gtk_constraint_guide_set_max_size:
+ * @guide: a #GtkConstraintGuide object
+ * @width: the new maximum width, or -1 to not change it
+ * @height: the new maximum height, or -1 to not change it
+ *
+ * Sets the maximum size of @guide.
+ *
+ * If @guide is attached to a #GtkConstraintLayout,
+ * the constraints will be updated to reflect the new size.
+ */
+void
+gtk_constraint_guide_set_max_size (GtkConstraintGuide *guide,
+ int width,
+ int height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+ g_return_if_fail (width >= -1);
+ g_return_if_fail (height >= -1);
+
+ g_object_freeze_notify (G_OBJECT (guide));
+
+ if (width != -1)
+ g_object_set (guide, "max-width", width, NULL);
+
+ if (height != -1)
+ g_object_set (guide, "max-height", height, NULL);
+
+ g_object_thaw_notify (G_OBJECT (guide));
+}
+
+/**
+ * gtk_constraint_guide_get_max_size:
+ * @guide: a #GtkContraintGuide object
+ * @width: (allow-none): return location for the maximum width,
+ * or %NULL
+ * @height: (allow-none): return location for the maximum height,
+ * or %NULL
+ *
+ * Gets the maximum size of @guide.
+ */
+void
+gtk_constraint_guide_get_max_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+
+ if (width)
+ *width = guide->values[MAX_WIDTH];
+ if (height)
+ *height = guide->values[MAX_HEIGHT];
+}
+
+/**
+ * gtk_constraint_guide_get_name:
+ * @guide: a #GtkConstraintGuide
+ *
+ * Retrieves the name set using gtk_constraint_guide_set_name().
+ *
+ * Returns: (transfer none) (nullable): the name of the guide
+ */
+const char *
+gtk_constraint_guide_get_name (GtkConstraintGuide *guide)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide), NULL);
+
+ return guide->name;
+}
+
+/**
+ * gtk_constraint_guide_set_name:
+ * @guide: a #GtkConstraintGuide
+ * @name: (nullable): a name for the @guide
+ *
+ * Sets a name for the given #GtkConstraintGuide.
+ *
+ * The name is useful for debugging purposes.
+ */
+void
+gtk_constraint_guide_set_name (GtkConstraintGuide *guide,
+ const char *name)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+
+ g_free (guide->name);
+ guide->name = g_strdup (name);
+ g_object_notify_by_pspec (G_OBJECT (guide), guide_props[PROP_NAME]);
+}
+
+/**
+ * gtk_constraint_guide_get_strength:
+ * @guide: a #GtkConstraintGuide
+ *
+ * Retrieves the strength set using gtk_constraint_guide_set_strength().
+ *
+ * Returns: the strength of the constraint on the natural size
+ */
+GtkConstraintStrength
+gtk_constraint_guide_get_strength (GtkConstraintGuide *guide)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide),
+ GTK_CONSTRAINT_STRENGTH_MEDIUM);
+
+ return guide->strength;
+}
+
+/**
+ * gtk_constraint_guide_set_strength:
+ * @guide: a #GtkConstraintGuide
+ * @strength: the strength of the constraint
+ *
+ * Sets the strength of the constraint on the natural size of the
+ * given #GtkConstraintGuide.
+ */
+void
+gtk_constraint_guide_set_strength (GtkConstraintGuide *guide,
+ GtkConstraintStrength strength)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+
+ if (guide->strength == strength)
+ return;
+
+ guide->strength = strength;
+ g_object_notify_by_pspec (G_OBJECT (guide), guide_props[PROP_STRENGTH]);
+ gtk_constraint_guide_update_constraint (guide, NAT_WIDTH);
+ gtk_constraint_guide_update_constraint (guide, NAT_HEIGHT);
+}
diff --git a/gtk/gtkconstraintguide.h b/gtk/gtkconstraintguide.h
new file mode 100644
index 0000000000..dbd7235009
--- /dev/null
+++ b/gtk/gtkconstraintguide.h
@@ -0,0 +1,83 @@
+/* gtkconstraintguide.h: Flexible space for 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#pragma once
+
+#include <gtk/gtktypes.h>
+#include <gtk/gtkenums.h>
+#include <gtk/gtktypebuiltins.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CONSTRAINT_GUIDE (gtk_constraint_guide_get_type ())
+
+/**
+ * GtkConstraintGuide:
+ *
+ * An object that can be added to a #GtkConstraintLayout and be
+ * used in constraints like a widget, without being drawn.
+ *
+ * Guides have a minimum, maximum and natural size. Depending
+ * on the constraints that are applied, they can act like a
+ * guideline that widgets can be aligned to, or like 'flexible space'.
+ */
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkConstraintGuide, gtk_constraint_guide, GTK, CONSTRAINT_GUIDE, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkConstraintGuide * gtk_constraint_guide_new (void);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_set_min_size (GtkConstraintGuide *guide,
+ int width,
+ int height);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_get_min_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_set_nat_size (GtkConstraintGuide *guide,
+ int width,
+ int height);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_get_nat_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_set_max_size (GtkConstraintGuide *guide,
+ int width,
+ int height);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_get_max_size (GtkConstraintGuide *guide,
+ int *width,
+ int *height);
+
+GDK_AVAILABLE_IN_ALL
+GtkConstraintStrength gtk_constraint_guide_get_strength (GtkConstraintGuide *guide);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_set_strength (GtkConstraintGuide *guide,
+ GtkConstraintStrength strength);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_guide_set_name (GtkConstraintGuide *guide,
+ const char *name);
+GDK_AVAILABLE_IN_ALL
+const char * gtk_constraint_guide_get_name (GtkConstraintGuide *guide);
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintguideprivate.h b/gtk/gtkconstraintguideprivate.h
new file mode 100644
index 0000000000..69c7e5f2c4
--- /dev/null
+++ b/gtk/gtkconstraintguideprivate.h
@@ -0,0 +1,38 @@
+/* gtkconstraintguideprivate.h: Constraint between two widgets
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#pragma once
+
+#include "gtkconstraintguide.h"
+#include "gtkconstraintlayout.h"
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+void gtk_constraint_guide_update (GtkConstraintGuide *guide);
+void gtk_constraint_guide_detach (GtkConstraintGuide *guide);
+
+GtkConstraintVariable *gtk_constraint_guide_get_attribute (GtkConstraintGuide *guide,
+ GtkConstraintAttribute attr);
+
+GtkConstraintLayout *gtk_constraint_guide_get_layout (GtkConstraintGuide *guide);
+void gtk_constraint_guide_set_layout (GtkConstraintGuide *guide,
+ GtkConstraintLayout *layout);
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintlayout.c b/gtk/gtkconstraintlayout.c
new file mode 100644
index 0000000000..4623798b5e
--- /dev/null
+++ b/gtk/gtkconstraintlayout.c
@@ -0,0 +1,2118 @@
+/* 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. You can use the #GtkConstraint:strength
+ * property of #GtkConstraint to "nudge" the layout towards a solution.
+ *
+ * # GtkConstraintLayout as GtkBuildable
+ *
+ * GtkConstraintLayout implements the #GtkBuildable interface and has a
+ * custom "constraints" element which allows describing constraints in a
+ * GtkBuilder UI file.
+ *
+ * An example of a UI definition fragment specifying a constraint:
+ *
+ * |[
+ * <object class="GtkConstraintLayout">
+ * <constraints>
+ * <constraint target="button" target-attribute="start"
+ * relation="eq"
+ * source="super" source-attribute="start"
+ * constant="12"
+ * strength="required" />
+ * <constraint target="button" target-attribute="width"
+ * relation="ge"
+ * constant="250"
+ * strength="strong" />
+ * </constraints>
+ * </object>
+ * ]|
+ *
+ * The definition above will add two constraints to the GtkConstraintLayout:
+ *
+ * - a required constraint between the leading edge of "button" and
+ * the leading edge of the widget using the constraint layout, plus
+ * 12 pixels
+ * - a strong, constant constraint making the width of "button" greater
+ * than, or equal to 250 pixels
+ *
+ * The "target" and "target-attribute" attributes are required.
+ *
+ * The "source" and "source-attribute" attributes of the "constraint"
+ * element are optional; if they are not specified, the constraint is
+ * assumed to be a constant.
+ *
+ * The "relation" attribute is optional; if not specified, the constraint
+ * is assumed to be an equality.
+ *
+ * The "strength" attribute is optional; if not specified, the constraint
+ * is assumed to be required.
+ *
+ * The "source" and "target" attributes can be set to "super" to indicate
+ * that the constraint target is the widget using the GtkConstraintLayout.
+ *
+ * Additionally, the "constraints" element can also contain a description
+ * of the #GtkConstraintGuides used by the layout:
+ *
+ * |[
+ * <constraints>
+ * <guide min-width="100" max-width="500" name="hspace"/>
+ * <guide min-height="64" nat-height="128" name="vspace" strength="strong"/>
+ * </constraints>
+ * ]|
+ *
+ * The "guide" element has the following optional attributes:
+ *
+ * - "min-width", "nat-width", and "max-width", describe the minimum,
+ * natural, and maximum width of the guide, respectively
+ * - "min-height", "nat-height", and "max-height", describe the minimum,
+ * natural, and maximum height of the guide, respectively
+ * - "strength" describes the strength of the constraint on the natural
+ * size of the guide; if not specified, the constraint is assumed to
+ * have a medium strength
+ * - "name" describes a name for the guide, useful when debugging
+ *
+ * # Using the Visual Format Language
+ *
+ * Complex constraints can be described using a compact syntax called VFL,
+ * or *Visual Format Language*.
+ *
+ * The Visual Format Language describes all the constraints on a row or
+ * column, typically starting from the leading edge towards the trailing
+ * one. Each element of the layout is composed by "views", which identify
+ * a #GtkConstraintTarget.
+ *
+ * For instance:
+ *
+ * |[
+ * [button]-[textField]
+ * ]|
+ *
+ * Describes a constraint that binds the trailing edge of "button" to the
+ * leading edge of "textField", leaving a default space between the two.
+ *
+ * Using VFL is also possible to specify predicates that describe constraints
+ * on attributes like width and height:
+ *
+ * |[
+ * // Width must be greater than, or equal to 50
+ * [button(>=50)]
+ *
+ * // Width of button1 must be equal to width of button2
+ * [button1(==button2)]
+ * ]|
+ *
+ * The default orientation for a VFL description is horizontal, unless
+ * otherwise specified:
+ *
+ * |[
+ * // horizontal orientation, default attribute: width
+ * H:[button(>=150)]
+ *
+ * // vertical orientation, default attribute: height
+ * V:[button1(==button2)]
+ * ]|
+ *
+ * It's also possible to specify multiple predicates, as well as their
+ * strength:
+ *
+ * |[
+ * // minimum width of button must be 150
+ * // natural width of button can be 250
+ * [button(>=150@required, ==250@medium)]
+ * ]|
+ *
+ * Finally, it's also possible to use simple arithmetic operators:
+ *
+ * |[
+ * // width of button1 must be equal to width of button2
+ * // divided by 2 plus 12
+ * [button1(button2 / 2 + 12)]
+ * ]|
+ */
+
+#include "config.h"
+
+#include "gtkconstraintlayout.h"
+#include "gtkconstraintlayoutprivate.h"
+
+#include "gtkconstraintprivate.h"
+#include "gtkconstraintexpressionprivate.h"
+#include "gtkconstraintguideprivate.h"
+#include "gtkconstraintsolverprivate.h"
+#include "gtkconstraintvflparserprivate.h"
+
+#include "gtkbuildable.h"
+#include "gtkbuilderprivate.h"
+#include "gtkdebug.h"
+#include "gtklayoutchild.h"
+#include "gtkintl.h"
+#include "gtkprivate.h"
+#include "gtksizerequest.h"
+#include "gtkwidgetprivate.h"
+
+#include <string.h>
+#include <errno.h>
+
+enum {
+ MIN_WIDTH,
+ MIN_HEIGHT,
+ NAT_WIDTH,
+ NAT_HEIGHT,
+ LAST_VALUE
+};
+
+struct _GtkConstraintLayoutChild
+{
+ GtkLayoutChild parent_instance;
+
+ int values[LAST_VALUE];
+ GtkConstraintRef *constraints[LAST_VALUE];
+
+ /* 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;
+};
+
+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;
+
+ /* HashSet<GtkConstraintGuide> */
+ GHashTable *guides;
+};
+
+G_DEFINE_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK_TYPE_LAYOUT_CHILD)
+
+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 GtkConstraintAttribute
+resolve_direction (GtkConstraintAttribute attr,
+ GtkWidget *widget)
+{
+ GtkTextDirection text_dir;
+
+ /* Resolve the start/end attributes depending on the layout's text direction */
+
+ if (widget)
+ text_dir = gtk_widget_get_direction (widget);
+ else
+ text_dir = GTK_TEXT_DIR_LTR;
+
+ if (attr == GTK_CONSTRAINT_ATTRIBUTE_START)
+ {
+ 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)
+ {
+ if (text_dir == GTK_TEXT_DIR_RTL)
+ attr = GTK_CONSTRAINT_ATTRIBUTE_LEFT;
+ else
+ attr = GTK_CONSTRAINT_ATTRIBUTE_RIGHT;
+ }
+
+ return attr;
+}
+
+GtkConstraintVariable *
+gtk_constraint_layout_get_attribute (GtkConstraintLayout *layout,
+ GtkConstraintAttribute attr,
+ const char *prefix,
+ GtkWidget *widget,
+ GHashTable *bound_attributes)
+{
+ const char *attr_name;
+ GtkConstraintVariable *res;
+ GtkConstraintSolver *solver = layout->solver;
+
+ attr = resolve_direction (attr, widget);
+
+ attr_name = get_attribute_name (attr);
+ res = g_hash_table_lookup (bound_attributes, attr_name);
+ if (res != NULL)
+ return res;
+
+ res = gtk_constraint_solver_create_variable (solver, prefix, attr_name, 0.0);
+ g_hash_table_insert (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 = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
+ width = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
+
+ 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_STRENGTH_REQUIRED);
+ }
+ break;
+
+ /* bottom = top + height */
+ case GTK_CONSTRAINT_ATTRIBUTE_BOTTOM:
+ {
+ GtkConstraintExpressionBuilder builder;
+ GtkConstraintVariable *top, *height;
+ GtkConstraintExpression *expr;
+
+ top = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
+ height = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
+
+ 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_STRENGTH_REQUIRED);
+ }
+ break;
+
+ /* centerX = (width / 2.0) + left*/
+ case GTK_CONSTRAINT_ATTRIBUTE_CENTER_X:
+ {
+ GtkConstraintExpressionBuilder builder;
+ GtkConstraintVariable *left, *width;
+ GtkConstraintExpression *expr;
+
+ left = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_LEFT, prefix, widget, bound_attributes);
+ width = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_WIDTH, prefix, widget, bound_attributes);
+
+ 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_STRENGTH_REQUIRED);
+ }
+ break;
+
+ /* centerY = (height / 2.0) + top */
+ case GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y:
+ {
+ GtkConstraintExpressionBuilder builder;
+ GtkConstraintVariable *top, *height;
+ GtkConstraintExpression *expr;
+
+ top = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_TOP, prefix, widget, bound_attributes);
+ height = gtk_constraint_layout_get_attribute (layout, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT, prefix, widget, bound_attributes);
+
+ 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_STRENGTH_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_STRENGTH_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 GtkConstraintVariable *
+get_child_attribute (GtkConstraintLayout *layout,
+ GtkWidget *widget,
+ GtkConstraintAttribute attr)
+{
+ GtkConstraintLayoutChild *child_info;
+ const char *prefix = gtk_widget_get_name (widget);
+
+ child_info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (GTK_LAYOUT_MANAGER (layout), widget));
+
+ return gtk_constraint_layout_get_attribute (layout, attr, prefix, widget, child_info->bound_attributes);
+}
+
+static void
+gtk_constraint_layout_child_finalize (GObject *gobject)
+{
+ GtkConstraintLayoutChild *self = GTK_CONSTRAINT_LAYOUT_CHILD (gobject);
+
+ 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);
+}
+
+static void gtk_buildable_interface_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkConstraintLayout, gtk_constraint_layout, GTK_TYPE_LAYOUT_MANAGER,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_buildable_interface_init))
+
+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_clear_pointer (&self->guides, 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_STRENGTH_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_STRENGTH_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_STRENGTH_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_STRENGTH_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_STRENGTH_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;
+ GtkConstraintTarget *target, *source;
+ 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 = gtk_constraint_get_target (constraint);
+ if (target == NULL || target == GTK_CONSTRAINT_TARGET (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_IS_WIDGET (target) &&
+ gtk_widget_get_parent (GTK_WIDGET (target)) == layout_widget)
+ {
+ target_attr = get_child_attribute (self, GTK_WIDGET (target), attr);
+ }
+ else if (GTK_IS_CONSTRAINT_GUIDE (target))
+ {
+ GtkConstraintGuide *guide;
+
+ guide = (GtkConstraintGuide*)g_hash_table_lookup (self->guides, target);
+ target_attr = gtk_constraint_guide_get_attribute (guide, attr);
+ }
+ else
+ {
+ g_critical ("Unknown target widget '%p'", target);
+ target_attr = NULL;
+ }
+
+ if (target_attr == NULL)
+ return;
+
+ attr = gtk_constraint_get_source_attribute (constraint);
+ source = gtk_constraint_get_source (constraint);
+
+ /* The constraint is a constant */
+ if (attr == GTK_CONSTRAINT_ATTRIBUTE_NONE)
+ {
+ source_attr = NULL;
+ }
+ else
+ {
+ if (source == NULL || source == GTK_CONSTRAINT_TARGET (layout_widget))
+ {
+ source_attr = get_layout_attribute (self, layout_widget, attr);
+ }
+ else if (GTK_IS_WIDGET (source) &&
+ gtk_widget_get_parent (GTK_WIDGET (source)) == layout_widget)
+ {
+ source_attr = get_child_attribute (self, GTK_WIDGET (source), attr);
+ }
+ else if (GTK_IS_CONSTRAINT_GUIDE (source))
+ {
+ GtkConstraintGuide *guide;
+
+ guide = (GtkConstraintGuide*)g_hash_table_lookup (self->guides, source);
+ source_attr = gtk_constraint_guide_get_attribute (guide, attr);
+ }
+ else
+ {
+ g_critical ("Unknown source widget '%p'", source);
+ 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_strength (constraint));
+}
+
+static void
+update_child_constraint (GtkConstraintLayout *self,
+ GtkConstraintLayoutChild *child_info,
+ GtkWidget *child,
+ int index,
+ int value)
+{
+
+ GtkConstraintVariable *var;
+ int attr[LAST_VALUE] = {
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT,
+ GTK_CONSTRAINT_ATTRIBUTE_WIDTH,
+ GTK_CONSTRAINT_ATTRIBUTE_HEIGHT
+ };
+ int relation[LAST_VALUE] = {
+ GTK_CONSTRAINT_RELATION_GE,
+ GTK_CONSTRAINT_RELATION_GE,
+ GTK_CONSTRAINT_RELATION_EQ,
+ GTK_CONSTRAINT_RELATION_EQ
+ };
+
+ if (child_info->values[index] != value)
+ {
+ child_info->values[index] = value;
+
+ if (child_info->constraints[index])
+ gtk_constraint_solver_remove_constraint (self->solver,
+ child_info->constraints[index]);
+
+ var = get_child_attribute (self, child, attr[index]);
+
+ if (relation[index] == GTK_CONSTRAINT_RELATION_EQ)
+ {
+ gtk_constraint_variable_set_value (var, value);
+ child_info->constraints[index] =
+ gtk_constraint_solver_add_stay_variable (self->solver,
+ var,
+ GTK_CONSTRAINT_STRENGTH_MEDIUM);
+ }
+ else
+ {
+ child_info->constraints[index] =
+ gtk_constraint_solver_add_constraint (self->solver,
+ var,
+ relation[index],
+ gtk_constraint_expression_new (value),
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ }
+ }
+}
+
+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 min_value;
+ int nat_value;
+
+ solver = gtk_constraint_layout_get_solver (self);
+ if (solver == NULL)
+ return;
+
+ gtk_constraint_solver_freeze (solver);
+
+ /* 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 *info;
+ GtkRequisition min_req, nat_req;
+
+ if (!gtk_widget_should_layout (child))
+ continue;
+
+ gtk_widget_get_preferred_size (child, &min_req, &nat_req);
+
+ info = GTK_CONSTRAINT_LAYOUT_CHILD (gtk_layout_manager_get_layout_child (manager, child));
+
+ update_child_constraint (self, info, child, MIN_WIDTH, min_req.width);
+ update_child_constraint (self, info, child, MIN_HEIGHT, min_req.height);
+ update_child_constraint (self, info, child, NAT_WIDTH, nat_req.width);
+ update_child_constraint (self, info, child, NAT_HEIGHT, nat_req.height);
+ }
+
+ gtk_constraint_solver_thaw (solver);
+
+ 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);
+
+ nat_value = gtk_constraint_variable_get_value (size);
+
+ /* 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_STRENGTH_STRONG * 2);
+ if (for_size > 0)
+ gtk_constraint_solver_add_edit_variable (solver, opposite_size, GTK_CONSTRAINT_STRENGTH_STRONG * 2);
+ gtk_constraint_solver_begin_edit (solver);
+ gtk_constraint_solver_suggest_value (solver, size, 0.0);
+ if (for_size > 0)
+ gtk_constraint_solver_suggest_value (solver, opposite_size, for_size);
+ gtk_constraint_solver_resolve (solver);
+
+ min_value = gtk_constraint_variable_get_value (size);
+
+ gtk_constraint_solver_remove_edit_variable (solver, size);
+ if (for_size > 0)
+ gtk_constraint_solver_remove_edit_variable (solver, opposite_size);
+ gtk_constraint_solver_end_edit (solver);
+
+ GTK_NOTE (LAYOUT,
+ g_print ("layout %p %s size: min %d nat %d (for opposite size: %d)\n",
+ self,
+ orientation == GTK_ORIENTATION_HORIZONTAL ? "horizontal" : "vertical",
+ min_value, nat_value,
+ for_size));
+
+ if (minimum != NULL)
+ *minimum = min_value;
+
+ if (natural != NULL)
+ *natural = nat_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_STRENGTH_REQUIRED);
+ gtk_constraint_variable_set_value (layout_left, 0.0);
+ stay_l = gtk_constraint_solver_add_stay_variable (solver,
+ layout_left,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_variable_set_value (layout_width, width);
+ stay_w = gtk_constraint_solver_add_stay_variable (solver,
+ layout_width,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_variable_set_value (layout_height, height);
+ stay_h = gtk_constraint_solver_add_stay_variable (solver,
+ layout_height,
+ GTK_CONSTRAINT_STRENGTH_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)));
+
+ 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;
+ GtkAllocation child_alloc;
+ int child_baseline = -1;
+
+ if (!gtk_widget_should_layout (child))
+ continue;
+
+ /* Retrieve all the values associated with the child */
+ var_top = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_TOP);
+ var_left = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
+ var_width = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
+ var_height = get_child_attribute (self, child, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
+ var_baseline = get_child_attribute (self, 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);
+ }
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (LAYOUT))
+ {
+ GHashTableIter iter;
+ gpointer key;
+ g_hash_table_iter_init (&iter, self->guides);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ GtkConstraintGuide *guide = key;
+ GtkConstraintVariable *var_top, *var_left, *var_width, *var_height;
+ var_top = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_TOP);
+ var_left = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_LEFT);
+ var_width = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_WIDTH);
+ var_height = gtk_constraint_guide_get_attribute (guide, GTK_CONSTRAINT_ATTRIBUTE_HEIGHT);
+ g_print ("Allocating guide '%s'[%p] with { .x: %g .y: %g .w: %g .h: %g }\n",
+ gtk_constraint_guide_get_name (guide), guide,
+ 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));
+ }
+ }
+#endif
+
+ /* 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);
+ }
+
+ g_hash_table_iter_init (&iter, self->guides);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ GtkConstraintGuide *guide = key;
+ gtk_constraint_guide_update (guide);
+ }
+}
+
+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);
+ }
+
+ g_hash_table_iter_init (&iter, self->guides);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ GtkConstraintGuide *guide = key;
+ gtk_constraint_guide_detach (guide);
+ }
+
+ 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);
+
+ self->guides =
+ g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) g_object_unref,
+ NULL);
+}
+
+typedef struct {
+ GtkConstraintLayout *layout;
+ GtkBuilder *builder;
+ GList *constraints;
+ GList *guides;
+} ConstraintsParserData;
+
+typedef struct {
+ char *source_name;
+ char *source_attr;
+ char *target_name;
+ char *target_attr;
+ char *relation;
+ char *strength;
+ double constant;
+ double multiplier;
+} ConstraintData;
+
+typedef struct {
+ char *name;
+ char *strength;
+ struct {
+ int min, nat, max;
+ } sizes[2];
+} GuideData;
+
+static void
+constraint_data_free (gpointer _data)
+{
+ ConstraintData *data = _data;
+
+ g_free (data->source_name);
+ g_free (data->source_attr);
+ g_free (data->target_name);
+ g_free (data->target_attr);
+ g_free (data->relation);
+ g_free (data->strength);
+
+ g_free (data);
+}
+
+static void
+guide_data_free (gpointer _data)
+{
+ GuideData *data = _data;
+
+ g_free (data->name);
+ g_free (data->strength);
+
+ g_free (data);
+}
+
+static void
+parse_double (const char *string,
+ double *value_p,
+ double default_value)
+{
+ double value;
+ char *endptr;
+ int saved_errno;
+
+ if (string == NULL || string[0] == '\0')
+ {
+ *value_p = default_value;
+ return;
+ }
+
+ saved_errno = errno;
+ errno = 0;
+ value = g_ascii_strtod (string, &endptr);
+ if (errno == 0 && endptr != string)
+ *value_p = value;
+ else
+ *value_p = default_value;
+
+ errno = saved_errno;
+}
+
+static void
+parse_int (const char *string,
+ int *value_p,
+ int default_value)
+{
+ gint64 value;
+ char *endptr;
+ int saved_errno;
+
+ if (string == NULL || string[0] == '\0')
+ {
+ *value_p = default_value;
+ return;
+ }
+
+ saved_errno = errno;
+ errno = 0;
+ value = g_ascii_strtoll (string, &endptr, 10);
+ if (errno == 0 && endptr != string)
+ *value_p = (int) value;
+ else
+ *value_p = default_value;
+
+ errno = saved_errno;
+}
+
+static GtkConstraint *
+constraint_data_to_constraint (const ConstraintData *data,
+ GtkBuilder *builder,
+ GError **error)
+{
+ gpointer source, target;
+ int source_attr, target_attr;
+ int relation, strength;
+ gboolean res;
+
+ if (g_strcmp0 (data->source_name, "super") == 0)
+ source = NULL;
+ else if (data->source_name == NULL)
+ {
+ if (data->source_attr != NULL)
+ {
+ g_set_error (error, GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Constraints without 'source' must also not "
+ "have a 'source-attribute' attribute");
+ return NULL;
+ }
+
+ source = NULL;
+ }
+ else
+ source = gtk_builder_get_object (builder, data->source_name);
+
+ if (g_strcmp0 (data->target_name, "super") == 0)
+ target = NULL;
+ else
+ {
+ target = gtk_builder_get_object (builder, data->target_name);
+
+ if (target == NULL)
+ {
+ g_set_error (error, GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_VALUE,
+ "Unable to find target '%s' for constraint",
+ data->target_name);
+ return NULL;
+ }
+ }
+
+ if (data->source_attr != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
+ data->source_attr,
+ &source_attr,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
+
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_ATTRIBUTE,
+ data->target_attr,
+ &target_attr,
+ error);
+ if (!res)
+ return NULL;
+
+ if (data->relation != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_RELATION,
+ data->relation,
+ &relation,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ if (data->strength != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_STRENGTH,
+ data->strength,
+ &strength,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ if (source != NULL && source_attr != GTK_CONSTRAINT_ATTRIBUTE_NONE)
+ return gtk_constraint_new (target, target_attr,
+ relation,
+ source, source_attr,
+ data->multiplier,
+ data->constant,
+ strength);
+
+ return gtk_constraint_new_constant (target, target_attr,
+ relation,
+ data->constant,
+ strength);
+}
+
+static GtkConstraintGuide *
+guide_data_to_guide (const GuideData *data,
+ GtkBuilder *builder,
+ GError **error)
+{
+ int strength;
+ gboolean res;
+
+ if (data->strength != NULL)
+ {
+ res = _gtk_builder_enum_from_string (GTK_TYPE_CONSTRAINT_STRENGTH,
+ data->strength,
+ &strength,
+ error);
+ if (!res)
+ return NULL;
+ }
+ else
+ strength = GTK_CONSTRAINT_STRENGTH_MEDIUM;
+
+ return g_object_new (GTK_TYPE_CONSTRAINT_GUIDE,
+ "min-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].min,
+ "nat-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].nat,
+ "max-width", data->sizes[GTK_ORIENTATION_HORIZONTAL].max,
+ "min-height", data->sizes[GTK_ORIENTATION_VERTICAL].min,
+ "nat-height", data->sizes[GTK_ORIENTATION_VERTICAL].nat,
+ "max-height", data->sizes[GTK_ORIENTATION_VERTICAL].max,
+ "strength", strength,
+ "name", data->name,
+ NULL);
+}
+
+static void
+constraints_start_element (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ ConstraintsParserData *data = user_data;
+
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ if (!_gtk_builder_check_parent (data->builder, context, "object", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
+ G_MARKUP_COLLECT_INVALID, NULL, NULL,
+ G_MARKUP_COLLECT_INVALID))
+ _gtk_builder_prefix_error (data->builder, context, error);
+ }
+ else if (strcmp (element_name, "constraint") == 0)
+ {
+ const char *target_name, *target_attribute;
+ const char *relation_str = NULL;
+ const char *source_name = NULL, *source_attribute = NULL;
+ const char *multiplier_str = NULL, *constant_str = NULL;
+ const char *strength_str = NULL;
+ ConstraintData *cdata;
+
+ if (!_gtk_builder_check_parent (data->builder, context, "constraints", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
+ G_MARKUP_COLLECT_STRING, "target", &target_name,
+ G_MARKUP_COLLECT_STRING, "target-attribute", &target_attribute,
+ G_MARKUP_COLLECT_STRING, "relation", &relation_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source", &source_name,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "source-attribute", &source_attribute,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "multiplier", &multiplier_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "constant", &constant_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength", &strength_str,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ cdata = g_new0 (ConstraintData, 1);
+ cdata->target_name = g_strdup (target_name);
+ cdata->target_attr = g_strdup (target_attribute);
+ cdata->relation = g_strdup (relation_str);
+ cdata->source_name = g_strdup (source_name);
+ cdata->source_attr = g_strdup (source_attribute);
+ parse_double (multiplier_str, &cdata->multiplier, 1.0);
+ parse_double (constant_str, &cdata->constant, 0.0);
+ cdata->strength = g_strdup (strength_str);
+
+ data->constraints = g_list_prepend (data->constraints, cdata);
+ }
+ else if (strcmp (element_name, "guide") == 0)
+ {
+ const char *min_width, *nat_width, *max_width;
+ const char *min_height, *nat_height, *max_height;
+ const char *strength_str;
+ const char *name;
+ GuideData *gdata;
+
+ if (!_gtk_builder_check_parent (data->builder, context, "constraints", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, attr_names, attr_values, error,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min-width", &min_width,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-width", &nat_width,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-width", &max_width,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min-height", &min_height,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "nat-height", &nat_height,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max-height", &max_height,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "strength", &strength_str,
+ G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ gdata = g_new0 (GuideData, 1);
+ parse_int (min_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].min), 0);
+ parse_int (nat_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].nat), 0);
+ parse_int (max_width, &(gdata->sizes[GTK_ORIENTATION_HORIZONTAL].max), G_MAXINT);
+ parse_int (min_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].min), 0);
+ parse_int (nat_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].nat), 0);
+ parse_int (max_height, &(gdata->sizes[GTK_ORIENTATION_VERTICAL].max), G_MAXINT);
+ gdata->name = g_strdup (name);
+ gdata->strength = g_strdup (strength_str);
+
+ data->guides = g_list_prepend (data->guides, gdata);
+ }
+ else
+ {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "GtkConstraintLayout", element_name,
+ error);
+ }
+}
+
+static void
+constraints_end_element (GMarkupParseContext *context,
+ const char *element_name,
+ gpointer user_data,
+ GError **error)
+{
+}
+
+static const GMarkupParser constraints_parser = {
+ constraints_start_element,
+ constraints_end_element,
+ NULL,
+};
+
+static gboolean
+gtk_constraint_layout_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ GMarkupParser *parser,
+ gpointer *parser_data)
+{
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ ConstraintsParserData *data = g_new (ConstraintsParserData, 1);
+
+ data->layout = g_object_ref (GTK_CONSTRAINT_LAYOUT (buildable));
+ data->builder = builder;
+ data->constraints = NULL;
+ data->guides = NULL;
+
+ *parser = constraints_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gtk_constraint_layout_custom_tag_end (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ gpointer data)
+{
+}
+
+static void
+gtk_constraint_layout_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const char *element_name,
+ gpointer user_data)
+{
+ ConstraintsParserData *data = user_data;
+
+ if (strcmp (element_name, "constraints") == 0)
+ {
+ GList *l;
+
+ data->guides = g_list_reverse (data->guides);
+ for (l = data->guides; l != NULL; l = l->next)
+ {
+ const GuideData *gdata = l->data;
+ GtkConstraintGuide *g;
+ GError *error = NULL;
+
+ g = guide_data_to_guide (gdata, builder, &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to parse guide definition: %s",
+ error->message);
+ g_error_free (error);
+ continue;
+ }
+
+ gtk_constraint_layout_add_guide (data->layout, g);
+ }
+
+ data->constraints = g_list_reverse (data->constraints);
+ for (l = data->constraints; l != NULL; l = l->next)
+ {
+ const ConstraintData *cdata = l->data;
+ GtkConstraint *c;
+ GError *error = NULL;
+
+ c = constraint_data_to_constraint (cdata, builder, &error);
+ if (error != NULL)
+ {
+ g_critical ("Unable to parse constraint definition '%s.%s [%s] %s.%s * %g + %g': %s",
+ cdata->target_name, cdata->target_attr,
+ cdata->relation,
+ cdata->source_name, cdata->source_attr,
+ cdata->multiplier,
+ cdata->constant,
+ error->message);
+ g_error_free (error);
+ continue;
+ }
+
+ gtk_constraint_layout_add_constraint (data->layout, c);
+ }
+
+ g_list_free_full (data->constraints, constraint_data_free);
+ g_list_free_full (data->guides, guide_data_free);
+ g_object_unref (data->layout);
+ g_free (data);
+ }
+}
+
+static void
+gtk_buildable_interface_init (GtkBuildableIface *iface)
+{
+ iface->custom_tag_start = gtk_constraint_layout_custom_tag_start;
+ iface->custom_tag_end = gtk_constraint_layout_custom_tag_end;
+ iface->custom_finished = gtk_constraint_layout_custom_finished;
+}
+
+/**
+ * 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:
+ * @layout: a #GtkConstraintLayout
+ * @constraint: (transfer full): a #GtkConstraint
+ *
+ * Adds a #GtkConstraint to the layout manager.
+ *
+ * The #GtkConstraint:source and #GtkConstraint:target
+ * properties of @constraint can be:
+ *
+ * - set to %NULL to indicate that the constraint refers to the
+ * widget using @layout
+ * - set to the #GtkWidget using @layout
+ * - set to a child of the #GtkWidget using @layout
+ * - set to a guide that is part of @layout
+ *
+ * The @layout acquires the ownership of @constraint after calling
+ * this function.
+ */
+void
+gtk_constraint_layout_add_constraint (GtkConstraintLayout *layout,
+ GtkConstraint *constraint)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
+ g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
+ g_return_if_fail (!gtk_constraint_is_attached (constraint));
+
+ layout_add_constraint (layout, constraint);
+
+ g_hash_table_add (layout->constraints, constraint);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+}
+
+/**
+ * gtk_constraint_layout_remove_constraint:
+ * @layout: a #GtkConstraintLayout
+ * @constraint: a #GtkConstraint
+ *
+ * Removes @constraint from the layout manager,
+ * so that it no longer influences the layout.
+ */
+void
+gtk_constraint_layout_remove_constraint (GtkConstraintLayout *layout,
+ GtkConstraint *constraint)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
+ g_return_if_fail (GTK_IS_CONSTRAINT (constraint));
+ g_return_if_fail (gtk_constraint_is_attached (constraint));
+
+ gtk_constraint_detach (constraint);
+ g_hash_table_remove (layout->constraints, constraint);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+}
+
+/**
+ * gtk_constraint_layout_remove_all_constraints:
+ * @layout: a #GtkConstraintLayout
+ *
+ * Removes all constraints from the layout manager.
+ */
+void
+gtk_constraint_layout_remove_all_constraints (GtkConstraintLayout *layout)
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
+
+ g_hash_table_iter_init (&iter, layout->constraints);
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ {
+ GtkConstraint *constraint = key;
+
+ gtk_constraint_detach (constraint);
+ g_hash_table_iter_remove (&iter);
+ }
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+}
+
+/**
+ * gtk_constraint_layout_add_guide:
+ * @layout: a #GtkConstraintLayout
+ * @guide: (transfer full): a #GtkConstraintGuide object
+ *
+ * Adds a guide to @layout. A guide can be used as
+ * the source or target of constraints, like a widget,
+ * but it is not visible.
+ *
+ * The @layout acquires the ownership of @guide after calling
+ * this function.
+ */
+void
+gtk_constraint_layout_add_guide (GtkConstraintLayout *layout,
+ GtkConstraintGuide *guide)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+ g_return_if_fail (gtk_constraint_guide_get_layout (guide) == NULL);
+
+ gtk_constraint_guide_set_layout (guide, layout);
+ g_hash_table_add (layout->guides, guide);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+}
+
+/**
+ * gtk_constraint_layout_remove_guide:
+ * @layout: a #GtkConstraintManager
+ * @guide: a #GtkConstraintGuide object
+ *
+ * Removes @guide from the layout manager,
+ * so that it no longer influences the layout.
+ */
+void
+gtk_constraint_layout_remove_guide (GtkConstraintLayout *layout,
+ GtkConstraintGuide *guide)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout));
+ g_return_if_fail (GTK_IS_CONSTRAINT_GUIDE (guide));
+ g_return_if_fail (gtk_constraint_guide_get_layout (guide) == layout);
+
+ gtk_constraint_guide_detach (guide);
+
+ gtk_constraint_guide_set_layout (guide, NULL);
+ g_hash_table_remove (layout->guides, guide);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+}
+
+static GtkConstraintAttribute
+attribute_from_name (const char *name)
+{
+ if (name == NULL || *name == '\0')
+ return GTK_CONSTRAINT_ATTRIBUTE_NONE;
+
+ /* We sadly need to special case these two because the name does
+ * not match the VFL grammar rules
+ */
+ if (strcmp (name, "centerX") == 0)
+ return GTK_CONSTRAINT_ATTRIBUTE_CENTER_X;
+
+ if (strcmp (name, "centerY") == 0)
+ return GTK_CONSTRAINT_ATTRIBUTE_CENTER_Y;
+
+ for (int i = 0; i < G_N_ELEMENTS (attribute_names); i++)
+ {
+ if (strcmp (attribute_names[i], name) == 0)
+ return i;
+ }
+
+ return GTK_CONSTRAINT_ATTRIBUTE_NONE;
+}
+
+GQuark
+gtk_constraint_vfl_parser_error_quark (void)
+{
+ return g_quark_from_static_string ("gtk-constraint-vfl-parser-error-quark");
+}
+
+/**
+ * gtk_constraint_layout_add_constraints_from_descriptionv: (rename-to gtk_constraint_layout_add_constraints_from_description)
+ * @layout: a #GtkConstraintLayout
+ * @lines: (array length=n_lines): an array of Visual Format Language lines
+ * defining a set of constraints
+ * @n_lines: the number of lines
+ * @hspacing: default horizontal spacing value, or -1 for the fallback value
+ * @vspacing: default vertical spacing value, or -1 for the fallback value
+ * @views: (element-type utf8 Gtk.ConstraintTarget): a dictionary of [ name, target ]
+ * pairs; the `name` keys map to the view names in the VFL lines, while
+ * the `target` values map to children of the widget using a #GtkConstraintLayout, or guides
+ * @error: return location for a #GError
+ *
+ * Creates a list of constraints from a formal description using a compact
+ * description syntax called VFL, or "Visual Format Language".
+ *
+ * The Visual Format Language is based on Apple's AutoLayout [VFL](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html).
+ *
+ * The @views dictionary is used to match #GtkConstraintTargets to the symbolic
+ * view name inside the VFL.
+ *
+ * The VFL grammar is:
+ *
+ * |[<!-- language="plain" -->
+ * <visualFormatString> = (<orientation>)?
+ * (<superview><connection>)?
+ * <view>(<connection><view>)*
+ * (<connection><superview>)?
+ * <orientation> = 'H' | 'V'
+ * <superview> = '|'
+ * <connection> = '' | '-' <predicateList> '-' | '-'
+ * <predicateList> = <simplePredicate> | <predicateListWithParens>
+ * <simplePredicate> = <metricName> | <positiveNumber>
+ * <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')'
+ * <predicate> = (<relation>)? <objectOfPredicate> (<operatorList>)? ('@' <priority>)?
+ * <relation> = '==' | '<=' | '>='
+ * <objectOfPredicate> = <constant> | <viewName> | ('.' <attributeName>)?
+ * <priority> = <positiveNumber> | 'required' | 'strong' | 'medium' | 'weak'
+ * <constant> = <number>
+ * <operatorList> = (<multiplyOperator>)? (<addOperator>)?
+ * <multiplyOperator> = [ '*' | '/' ] <positiveNumber>
+ * <addOperator> = [ '+' | '-' ] <positiveNumber>
+ * <viewName> = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier
+ * <metricName> = [A-Za-z_]([A-Za-z0-9_]*) // A C identifier
+ * <attributeName> = 'top' | 'bottom' | 'left' | 'right' | 'width' | 'height' |
+ * 'start' | 'end' | 'centerX' | 'centerY' | 'baseline'
+ * <positiveNumber> // A positive real number parseable by g_ascii_strtod()
+ * <number> // A real number parseable by g_ascii_strtod()
+ * ]|
+ *
+ * **Note**: The VFL grammar used by GTK is slightly different than the one
+ * defined by Apple, as it can use symbolic values for the constraint's
+ * strength instead of numeric values; additionally, GTK allows adding
+ * simple arithmetic operations inside predicates.
+ *
+ * Examples of VFL descriptions are:
+ *
+ * |[<!-- language="plain" -->
+ * // Default spacing
+ * [button]-[textField]
+ *
+ * // Width constraint
+ * [button(>=50)]
+ *
+ * // Connection to super view
+ * |-50-[purpleBox]-50-|
+ *
+ * // Vertical layout
+ * V:[topField]-10-[bottomField]
+ *
+ * // Flush views
+ * [maroonView][blueView]
+ *
+ * // Priority
+ * [button(100@strong)]
+ *
+ * // Equal widths
+ * [button1(==button2)]
+ *
+ * // Multiple predicates
+ * [flexibleButton(>=70,<=100)]
+ *
+ * // A complete line of layout
+ * |-[find]-[findNext]-[findField(>=20)]-|
+ *
+ * // Operators
+ * [button1(button2 / 3 + 50)]
+ *
+ * // Named attributes
+ * [button1(==button2.height)]
+ * ]|
+ *
+ * Returns: (transfer container) (element-type GtkConstraint): the list of
+ * #GtkConstraints that were added to the layout
+ */
+GList *
+gtk_constraint_layout_add_constraints_from_descriptionv (GtkConstraintLayout *layout,
+ const char * const lines[],
+ gsize n_lines,
+ int hspacing,
+ int vspacing,
+ GHashTable *views,
+ GError **error)
+{
+ GtkConstraintVflParser *parser;
+ GList *res = NULL;
+
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
+ g_return_val_if_fail (lines != NULL, NULL);
+ g_return_val_if_fail (views != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ parser = gtk_constraint_vfl_parser_new ();
+ gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
+ gtk_constraint_vfl_parser_set_views (parser, views);
+
+ for (gsize i = 0; i < n_lines; i++)
+ {
+ const char *line = lines[i];
+ GError *internal_error = NULL;
+
+ gtk_constraint_vfl_parser_parse_line (parser, line, -1, &internal_error);
+ if (internal_error != NULL)
+ {
+ int offset = gtk_constraint_vfl_parser_get_error_offset (parser);
+ int range = gtk_constraint_vfl_parser_get_error_range (parser);
+ char *squiggly = NULL;
+
+ if (range > 0)
+ {
+ squiggly = g_new (char, range + 1);
+
+ for (int r = 0; r < range; i++)
+ squiggly[r] = '~';
+
+ squiggly[range] = '\0';
+ }
+
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ internal_error->code,
+ "%" G_GSIZE_FORMAT ":%d: %s\n"
+ "%s\n"
+ "%*s^%s",
+ i, offset + 1,
+ internal_error->message,
+ line,
+ offset, " ", squiggly != NULL ? squiggly : "");
+
+ g_free (squiggly);
+ g_error_free (internal_error);
+ gtk_constraint_vfl_parser_free (parser);
+ return res;
+ }
+
+ int n_constraints = 0;
+ GtkConstraintVfl *constraints = gtk_constraint_vfl_parser_get_constraints (parser, &n_constraints);
+ for (int j = 0; j < n_constraints; j++)
+ {
+ const GtkConstraintVfl *c = &constraints[j];
+ gpointer source, target;
+ GtkConstraintAttribute source_attr, target_attr;
+
+ target = g_hash_table_lookup (views, c->view1);
+ target_attr = attribute_from_name (c->attr1);
+
+ if (c->view2 != NULL)
+ source = g_hash_table_lookup (views, c->view2);
+ else
+ source = NULL;
+
+ if (c->attr2 != NULL)
+ source_attr = attribute_from_name (c->attr2);
+ else
+ source_attr = GTK_CONSTRAINT_ATTRIBUTE_NONE;
+
+ GtkConstraint *constraint =
+ gtk_constraint_new (target, target_attr,
+ c->relation,
+ source, source_attr,
+ c->multiplier,
+ c->constant,
+ c->strength);
+
+ layout_add_constraint (layout, constraint);
+ g_hash_table_add (layout->constraints, constraint);
+
+ res = g_list_prepend (res, constraint);
+ }
+
+ g_free (constraints);
+ }
+
+ gtk_constraint_vfl_parser_free (parser);
+
+ gtk_layout_manager_layout_changed (GTK_LAYOUT_MANAGER (layout));
+
+ return res;
+}
+
+/**
+ * gtk_constraint_layout_add_constraints_from_description:
+ * @layout: a #GtkConstraintLayout
+ * @lines: (array length=n_lines): an array of Visual Format Language lines
+ * defining a set of constraints
+ * @n_lines: the number of lines
+ * @hspacing: default horizontal spacing value, or -1 for the fallback value
+ * @vspacing: default vertical spacing value, or -1 for the fallback value
+ * @error: return location for a #GError
+ * @first_view: the name of a view in the VFL description, followed by the
+ * #GtkConstraintTarget to which it maps
+ * @...: a %NULL-terminated list of view names and #GtkConstraintTargets
+ *
+ * Creates a list of constraints they formal description using a compact
+ * description syntax called VFL, or "Visual Format Language".
+ *
+ * This function is a convenience wrapper around
+ * gtk_constraint_layout_add_constraints_from_descriptionv(), using
+ * variadic arguments to populate the view/target map.
+ *
+ * Returns: (transfer container) (element-type GtkConstraint): the list of
+ * #GtkConstraints that were added to the layout
+ */
+GList *
+gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *layout,
+ const char * const lines[],
+ gsize n_lines,
+ int hspacing,
+ int vspacing,
+ GError **error,
+ const char *first_view,
+ ...)
+{
+ GtkConstraintVflParser *parser;
+ GHashTable *views;
+ const char *view;
+ GList *res;
+ va_list args;
+
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_LAYOUT (layout), NULL);
+ g_return_val_if_fail (lines != NULL, NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ g_return_val_if_fail (first_view != NULL, NULL);
+
+ parser = gtk_constraint_vfl_parser_new ();
+ gtk_constraint_vfl_parser_set_default_spacing (parser, hspacing, vspacing);
+
+ views = g_hash_table_new (g_str_hash, g_str_equal);
+
+ va_start (args, first_view);
+
+ view = first_view;
+ while (view != NULL)
+ {
+ GtkConstraintTarget *target = va_arg (args, GtkConstraintTarget *);
+
+ if (target == NULL)
+ break;
+
+ g_hash_table_insert (views, (gpointer) view, target);
+
+ view = va_arg (args, const char *);
+ }
+
+ va_end (args);
+
+ res =
+ gtk_constraint_layout_add_constraints_from_descriptionv (layout, lines, n_lines,
+ hspacing, vspacing,
+ views,
+ error);
+
+ g_hash_table_unref (views);
+
+ return res;
+}
diff --git a/gtk/gtkconstraintlayout.h b/gtk/gtkconstraintlayout.h
new file mode 100644
index 0000000000..90d5e84926
--- /dev/null
+++ b/gtk/gtkconstraintlayout.h
@@ -0,0 +1,88 @@
+/* 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>
+#include <gtk/gtkconstraintguide.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 ())
+#define GTK_CONSTRAINT_VFL_PARSER_ERROR (gtk_constraint_vfl_parser_error_quark ())
+
+/**
+ * GtkConstraintLayoutChild:
+ *
+ * A #GtkLayoutChild in a #GtkConstraintLayout.
+ */
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (GtkConstraintLayoutChild, gtk_constraint_layout_child, GTK, CONSTRAINT_LAYOUT_CHILD, GtkLayoutChild)
+
+/**
+ * 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
+GQuark gtk_constraint_vfl_parser_error_quark (void);
+
+GDK_AVAILABLE_IN_ALL
+GtkLayoutManager * gtk_constraint_layout_new (void);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_layout_add_constraint (GtkConstraintLayout *layout,
+ GtkConstraint *constraint);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_layout_remove_constraint (GtkConstraintLayout *layout,
+ GtkConstraint *constraint);
+
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_layout_add_guide (GtkConstraintLayout *layout,
+ GtkConstraintGuide *guide);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_layout_remove_guide (GtkConstraintLayout *layout,
+ GtkConstraintGuide *guide);
+GDK_AVAILABLE_IN_ALL
+void gtk_constraint_layout_remove_all_constraints (GtkConstraintLayout *layout);
+
+GDK_AVAILABLE_IN_ALL
+GList * gtk_constraint_layout_add_constraints_from_description (GtkConstraintLayout *layout,
+ const char * const lines[],
+ gsize n_lines,
+ int hspacing,
+ int vspacing,
+ GError **error,
+ const char *first_view,
+ ...) G_GNUC_NULL_TERMINATED;
+GDK_AVAILABLE_IN_ALL
+GList * gtk_constraint_layout_add_constraints_from_descriptionv (GtkConstraintLayout *layout,
+ const char * const lines[],
+ gsize n_lines,
+ int hspacing,
+ int vspacing,
+ GHashTable *views,
+ GError **error);
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintlayoutprivate.h b/gtk/gtkconstraintlayoutprivate.h
new file mode 100644
index 0000000000..0d591911fd
--- /dev/null
+++ b/gtk/gtkconstraintlayoutprivate.h
@@ -0,0 +1,37 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#pragma once
+
+#include "gtkconstraintlayout.h"
+#include "gtkconstraintsolverprivate.h"
+
+G_BEGIN_DECLS
+
+GtkConstraintSolver *
+gtk_constraint_layout_get_solver (GtkConstraintLayout *layout);
+
+GtkConstraintVariable *
+gtk_constraint_layout_get_attribute (GtkConstraintLayout *layout,
+ GtkConstraintAttribute attr,
+ const char *prefix,
+ GtkWidget *widget,
+ GHashTable *bound_attributes);
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintprivate.h b/gtk/gtkconstraintprivate.h
new file mode 100644
index 0000000000..d90b313f42
--- /dev/null
+++ b/gtk/gtkconstraintprivate.h
@@ -0,0 +1,60 @@
+/* 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;
+
+ GtkConstraintTarget *target;
+ GtkConstraintTarget *source;
+
+ 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;
+};
+
+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
new file mode 100644
index 0000000000..b7097024c5
--- /dev/null
+++ b/gtk/gtkconstraintsolver.c
@@ -0,0 +1,2247 @@
+/* gtkconstraintsolver.c: Constraint solver based on the Cassowary method
+ * 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
+ */
+
+/*< private >
+ * SECTION: gtkconstraintsolver
+ * @Title: GtkConstraintSolver
+ * @Short_description: An incremental solver for a tableau of linear equations
+ *
+ * GtkConstraintSolver is an object that encodes constraints into a tableau
+ * of linear equations and solves them, using an incremental optimization
+ * algorithm known as the "Cassowary Linear Arithmetic Constraint Solving
+ * Algorithm" (Badros, Borning & Stuckey 2001).
+ *
+ * Each constraint is expressed as a linear equation, whose terms are variables
+ * containing widget attributes like the width, height, or position; the simplex
+ * solver takes all the constraints and incrementally optimizes the tableau to
+ * replace known terms; additionally, the algorithm will try to assign a value
+ * to all remaining variables in order to satisfy the various constraints.
+ *
+ * Each constraint is given a "strength", which determines whether satisfying
+ * the constraint is required in order to solve the tableau or not.
+ *
+ * A typical example of GtkConstraintSolver use is solving the following
+ * system of constraints:
+ *
+ * - [required] right = left + 10
+ * - [required] right ≤ 100
+ * - [required] middle = left + right / 2
+ * - [required] left ≥ 0
+ *
+ * Once we create a GtkConstraintSolver instance, we need to create the
+ * various variables and expressions that represent the values we want to
+ * compute and the constraints we wish to impose on the solutions:
+ *
+ * |[
+ * GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+ *
+ * // Our variables
+ * GtkConstraintVariable *left =
+ * gtk_constraint_solver_create_variable (solver, NULL, "left", 0.0);
+ * GtkConstraintVariable *middle =
+ * gtk_constraint_solver_create_variable (solver, NULL, "middle", 0.0);
+ * GtkConstraintVariable *right =
+ * gtk_constraint_solver_create_variable (solver, NULL, "right", 0.0);
+ *
+ * // Our constraints
+ * GtkConstraintExpressionBuilder builder;
+ * GtkConstraintExpression *e;
+ *
+ * // right = left + 10
+ * gtk_constraint_expression_builder_init (&builder, solver);
+ * gtk_constraint_expression_builder_term (&builder, left);
+ * gtk_constraint_expression_builder_plus (&builder);
+ * gtk_constraint_expression_builder_constant (&builder, 10.0);
+ * e = gtk_constraint_expression_builder_finish (&builder);
+ * gtk_constraint_solver_add_constraint (solver,
+ * right, GTK_CONSTRAINT_RELATION_EQ, e,
+ * GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ *
+ * // right ≤ 100
+ * gtk_constraint_expression_builder_constant (&builder, 100.0);
+ * e = gtk_constraint_expression_builder_finish (&builder);
+ * gtk_constraint_solver_add_constraint (solver,
+ * right, GTK_CONSTRAINT_RELATION_LE, e,
+ * GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ *
+ * // middle = (left + right) / 2
+ * gtk_constraint_expression_builder_term (&builder, left);
+ * gtk_constraint_expression_builder_plus (&builder)
+ * gtk_constraint_expression_builder_term (&builder, right);
+ * gtk_constraint_expression_builder_divide_by (&builder);
+ * gtk_constraint_expression_builder_constant (&builder, 2.0);
+ * e = gtk_constraint_expression_builder_finish (&builder);
+ * gtk_constraint_solver_add_constraint (solver
+ * middle, GTK_CONSTRAINT_RELATION_EQ, e,
+ * GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ *
+ * // left ≥ 0
+ * gtk_constraint_expression_builder_constant (&builder, 0.0);
+ * e = gtk_constraint_expression_builder_finish (&builder);
+ * gtk_constraint_solver_add_constraint (solver,
+ * left, GTK_CONSTRAINT_RELATION_GE, e,
+ * GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ * ]|
+ *
+ * Now that we have all our constraints in place, suppose we wish to find
+ * the values of `left` and `right` if we specify the value of `middle`. In
+ * order to do that, we need to add an additional "stay" constraint, i.e.
+ * a constraint that tells the solver to optimize for the solution that keeps
+ * the variable in place:
+ *
+ * |[
+ * // Set the value first
+ * gtk_constraint_variable_set_value (middle, 45.0);
+ * // and then add the stay constraint, with a weak strength
+ * gtk_constraint_solver_add_stay_variable (solver, middle, GTK_CONSTRAINT_STRENGTH_WEAK);
+ * ]|
+ *
+ * GtkConstraintSolver incrementally solves the system every time a constraint
+ * is added or removed, so it's possible to query the values of the variables
+ * immediately afterward:
+ *
+ * |[
+ * double left_val = gtk_constraint_variable_get_value (left);
+ * double right_val = gtk_constraint_variable_get_value (right);
+ * double middle_val = gtk_constraint_variable_get_value (middle);
+ *
+ * // These are the values computed by the solver:
+ * g_assert_cmpfloat_with_epsilon (left_val, 40.0, 0.001);
+ * g_assert_cmpfloat_with_epsilon (middle_val, 45.0, 0.001);
+ * g_assert_cmpfloat_with_epsilon (right_val, 50.0, 0.001);
+ * ]|
+ *
+ * As you can see:
+ *
+ * - the middle value hasn't changed
+ * - the left value is ≥ 0
+ * - the right value is ≤ 100
+ * - the right value is left + 10
+ * - the middle value is (left + right) / 2.0
+ *
+ * For more information about the Cassowary constraint solving algorithm and
+ * toolkit, see the following papers:
+ *
+ * - Badros G & Borning A, 1998, 'Cassowary Linear Arithmetic Constraint
+ * Solving Algorithm: Interface and Implementation', Technical Report
+ * UW-CSE-98-06-04, June 1998 (revised July 1999)
+ * https://constraints.cs.washington.edu/cassowary/cassowary-tr.pdf
+ * - Badros G, Borning A & Stuckey P, 2001, 'Cassowary Linear Arithmetic
+ * Constraint Solving Algorithm', ACM Transactions on Computer-Human
+ * Interaction, vol. 8 no. 4, December 2001, pages 267-306
+ * https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf
+ *
+ * The following implementation is based on these projects:
+ *
+ * - the original [C++ implementation](https://sourceforge.net/projects/cassowary/)
+ * - the JavaScript port [Cassowary.js](https://github.com/slightlyoff/cassowary.js)
+ * - the Python port [Cassowary](https://github.com/pybee/cassowary)
+ */
+
+#include "config.h"
+
+#include "gtkconstraintsolverprivate.h"
+#include "gtkconstraintexpressionprivate.h"
+
+#include "gtkdebug.h"
+
+#include <glib.h>
+#include <string.h>
+#include <math.h>
+#include <float.h>
+
+struct _GtkConstraintRef
+{
+ /* The constraint's normal form inside the solver:
+ *
+ * x - (y × coefficient + constant) = 0
+ *
+ * We only use equalities, and replace inequalities with slack
+ * variables.
+ */
+ GtkConstraintExpression *expression;
+
+ /* A constraint variable, only used by stay and edit constraints */
+ GtkConstraintVariable *variable;
+
+ /* The original relation used when creating the constraint */
+ GtkConstraintRelation relation;
+
+ /* The strength of the constraint; this value is used to strengthen
+ * or weaken a constraint weight in the tableau when coming to a
+ * solution
+ */
+ int strength;
+
+ GtkConstraintSolver *solver;
+
+ guint is_edit : 1;
+ guint is_stay : 1;
+};
+
+typedef struct {
+ GtkConstraintRef *constraint;
+
+ GtkConstraintVariable *eplus;
+ GtkConstraintVariable *eminus;
+
+ double prev_constant;
+} EditInfo;
+
+typedef struct {
+ GtkConstraintRef *constraint;
+} StayInfo;
+
+struct _GtkConstraintSolver
+{
+ GObject parent_instance;
+
+ /* HashTable<Variable, VariableSet>; owns keys and values */
+ GHashTable *columns;
+ /* HashTable<Variable, Expression>; owns keys and values */
+ GHashTable *rows;
+
+ /* Set<Variable>; does not own keys */
+ GHashTable *external_rows;
+ /* Set<Variable>; does not own keys */
+ GHashTable *external_parametric_vars;
+
+ /* Vec<Variable> */
+ GPtrArray *infeasible_rows;
+ /* Vec<VariablePair>; owns the pair */
+ GPtrArray *stay_error_vars;
+
+ /* HashTable<Constraint, VariableSet>; owns the set */
+ GHashTable *error_vars;
+ /* HashTable<Constraint, Variable> */
+ GHashTable *marker_vars;
+
+ /* HashTable<Variable, EditInfo>; does not own keys, but owns values */
+ GHashTable *edit_var_map;
+ /* HashTable<Variable, StayInfo>; does not own keys, but owns values */
+ GHashTable *stay_var_map;
+
+ GtkConstraintVariable *objective;
+
+ /* Set<Constraint>; owns the key */
+ GHashTable *constraints;
+
+ /* Counters */
+ int var_counter;
+ int slack_counter;
+ int artificial_counter;
+ int dummy_counter;
+ int optimize_count;
+ int freeze_count;
+
+ /* Bitfields; keep at the end */
+ guint auto_solve : 1;
+ guint needs_solving : 1;
+ guint in_edit_phase : 1;
+};
+
+static void gtk_constraint_ref_free (GtkConstraintRef *ref);
+static void edit_info_free (gpointer data);
+
+G_DEFINE_TYPE (GtkConstraintSolver, gtk_constraint_solver, G_TYPE_OBJECT)
+
+static void
+gtk_constraint_solver_finalize (GObject *gobject)
+{
+ GtkConstraintSolver *self = GTK_CONSTRAINT_SOLVER (gobject);
+
+ g_hash_table_remove_all (self->constraints);
+ g_clear_pointer (&self->constraints, g_hash_table_unref);
+
+ g_clear_pointer (&self->stay_error_vars, g_ptr_array_unref);
+ g_clear_pointer (&self->infeasible_rows, g_ptr_array_unref);
+
+ g_clear_pointer (&self->external_rows, g_hash_table_unref);
+ g_clear_pointer (&self->external_parametric_vars, g_hash_table_unref);
+ g_clear_pointer (&self->error_vars, g_hash_table_unref);
+ g_clear_pointer (&self->marker_vars, g_hash_table_unref);
+ g_clear_pointer (&self->edit_var_map, g_hash_table_unref);
+ g_clear_pointer (&self->stay_var_map, g_hash_table_unref);
+
+ g_clear_pointer (&self->rows, g_hash_table_unref);
+ g_clear_pointer (&self->columns, g_hash_table_unref);
+
+ G_OBJECT_CLASS (gtk_constraint_solver_parent_class)->finalize (gobject);
+}
+
+static void
+gtk_constraint_solver_class_init (GtkConstraintSolverClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gtk_constraint_solver_finalize;
+}
+
+static void
+gtk_constraint_solver_init (GtkConstraintSolver *self)
+{
+ self->columns =
+ g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) gtk_constraint_variable_unref,
+ (GDestroyNotify) gtk_constraint_variable_set_free);
+
+ self->rows =
+ g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) gtk_constraint_variable_unref,
+ (GDestroyNotify) gtk_constraint_expression_unref);
+
+ self->external_rows = g_hash_table_new (NULL, NULL);
+
+ self->external_parametric_vars = g_hash_table_new (NULL, NULL);
+
+ self->infeasible_rows = g_ptr_array_new ();
+
+ self->stay_error_vars =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_constraint_variable_pair_free);
+
+ self->error_vars =
+ g_hash_table_new_full (NULL, NULL,
+ NULL,
+ (GDestroyNotify) gtk_constraint_variable_set_free);
+
+ self->marker_vars = g_hash_table_new (NULL, NULL);
+
+ self->edit_var_map = g_hash_table_new_full (NULL, NULL,
+ NULL,
+ edit_info_free);
+
+ self->stay_var_map = g_hash_table_new_full (NULL, NULL,
+ NULL,
+ g_free);
+
+ /* The rows table owns the objective variable */
+ self->objective = gtk_constraint_variable_new_objective ("Z");
+ g_hash_table_insert (self->rows,
+ self->objective,
+ gtk_constraint_expression_new (0.0));
+
+ self->constraints =
+ g_hash_table_new_full (NULL, NULL,
+ (GDestroyNotify) gtk_constraint_ref_free,
+ NULL);
+
+ self->slack_counter = 0;
+ self->dummy_counter = 0;
+ self->artificial_counter = 0;
+ self->freeze_count = 0;
+
+ self->needs_solving = FALSE;
+ self->auto_solve = TRUE;
+}
+
+static void
+gtk_constraint_ref_free (GtkConstraintRef *self)
+{
+ gtk_constraint_solver_remove_constraint (self->solver, self);
+
+ gtk_constraint_expression_unref (self->expression);
+
+ if (self->is_edit || self->is_stay)
+ {
+ g_assert (self->variable != NULL);
+ gtk_constraint_variable_unref (self->variable);
+ }
+
+ g_free (self);
+}
+
+static gboolean
+gtk_constraint_ref_is_inequality (const GtkConstraintRef *self)
+{
+ return self->relation != GTK_CONSTRAINT_RELATION_EQ;
+}
+
+static gboolean
+gtk_constraint_ref_is_required (const GtkConstraintRef *self)
+{
+ return self->strength == GTK_CONSTRAINT_STRENGTH_REQUIRED;
+}
+
+static const char *relations[] = {
+ "<=",
+ "==",
+ ">=",
+};
+
+static const char *
+relation_to_string (GtkConstraintRelation r)
+{
+ return relations[r + 1];
+}
+
+static const char *
+strength_to_string (int s)
+{
+ if (s >= GTK_CONSTRAINT_STRENGTH_STRONG)
+ return "strong";
+
+ if (s >= GTK_CONSTRAINT_STRENGTH_MEDIUM)
+ return "medium";
+
+ return "weak";
+}
+
+static char *
+gtk_constraint_ref_to_string (const GtkConstraintRef *self)
+{
+ GString *buf = g_string_new (NULL);
+ char *str;
+
+ if (self->is_stay)
+ g_string_append (buf, "[stay]");
+ else if (self->is_edit)
+ g_string_append (buf, "[edit]");
+
+ str = gtk_constraint_expression_to_string (self->expression);
+ g_string_append (buf, str);
+ g_free (str);
+
+ g_string_append_c (buf, ' ');
+ g_string_append (buf, relation_to_string (self->relation));
+ g_string_append (buf, " 0.0");
+
+ if (gtk_constraint_ref_is_required (self))
+ g_string_append (buf, " [strength:required]");
+ else
+ g_string_append_printf (buf, " [strength:%d (%s)]",
+ self->strength,
+ strength_to_string (self->strength));
+
+ return g_string_free (buf, FALSE);
+}
+
+static GtkConstraintVariableSet *
+gtk_constraint_solver_get_column_set (GtkConstraintSolver *self,
+ GtkConstraintVariable *param_var)
+{
+ return g_hash_table_lookup (self->columns, param_var);
+}
+
+static gboolean
+gtk_constraint_solver_column_has_key (GtkConstraintSolver *self,
+ GtkConstraintVariable *subject)
+{
+ return g_hash_table_contains (self->columns, subject);
+}
+
+static void
+gtk_constraint_solver_insert_column_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *param_var,
+ GtkConstraintVariable *row_var)
+{
+ GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, param_var);
+
+ if (cset == NULL)
+ {
+ cset = gtk_constraint_variable_set_new ();
+ g_hash_table_insert (self->columns, gtk_constraint_variable_ref (param_var), cset);
+ }
+
+ if (row_var != NULL)
+ gtk_constraint_variable_set_add (cset, row_var);
+}
+
+static void
+gtk_constraint_solver_insert_error_variable (GtkConstraintSolver *self,
+ GtkConstraintRef *constraint,
+ GtkConstraintVariable *variable)
+{
+ GtkConstraintVariableSet *cset = g_hash_table_lookup (self->error_vars, constraint);
+
+ if (cset == NULL)
+ {
+ cset = gtk_constraint_variable_set_new ();
+ g_hash_table_insert (self->error_vars, constraint, cset);
+ }
+
+ gtk_constraint_variable_set_add (cset, variable);
+}
+
+static void
+gtk_constraint_solver_reset_stay_constants (GtkConstraintSolver *self)
+{
+ int i;
+
+ for (i = 0; i < self->stay_error_vars->len; i++)
+ {
+ GtkConstraintVariablePair *pair = g_ptr_array_index (self->stay_error_vars, i);
+ GtkConstraintExpression *expression;
+
+ expression = g_hash_table_lookup (self->rows, pair->first);
+
+ if (expression == NULL)
+ expression = g_hash_table_lookup (self->rows, pair->second);
+
+ if (expression != NULL)
+ gtk_constraint_expression_set_constant (expression, 0.0);
+ }
+}
+
+static void
+gtk_constraint_solver_set_external_variables (GtkConstraintSolver *self)
+{
+ GHashTableIter iter;
+ gpointer key_p;
+
+ g_hash_table_iter_init (&iter, self->external_parametric_vars);
+ while (g_hash_table_iter_next (&iter, &key_p, NULL))
+ {
+ GtkConstraintVariable *variable = key_p;
+
+ if (g_hash_table_contains (self->rows, variable))
+ continue;
+
+ gtk_constraint_variable_set_value (variable, 0.0);
+ }
+
+ g_hash_table_iter_init (&iter, self->external_rows);
+ while (g_hash_table_iter_next (&iter, &key_p, NULL))
+ {
+ GtkConstraintVariable *variable = key_p;
+ GtkConstraintExpression *expression;
+ double constant;
+
+ expression = g_hash_table_lookup (self->rows, variable);
+ constant = gtk_constraint_expression_get_constant (expression);
+
+ gtk_constraint_variable_set_value (variable, constant);
+ }
+
+ self->needs_solving = FALSE;
+}
+
+static void
+gtk_constraint_solver_add_row (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintExpression *expression)
+{
+ GtkConstraintExpressionIter iter;
+ GtkConstraintVariable *t_v;
+ double t_c;
+
+ g_hash_table_insert (self->rows,
+ gtk_constraint_variable_ref (variable),
+ gtk_constraint_expression_ref (expression));
+
+ gtk_constraint_expression_iter_init (&iter, expression);
+ while (gtk_constraint_expression_iter_next (&iter, &t_v, &t_c))
+ {
+ gtk_constraint_solver_insert_column_variable (self, t_v, variable);
+
+ if (gtk_constraint_variable_is_external (t_v))
+ g_hash_table_add (self->external_parametric_vars, t_v);
+ }
+
+ if (gtk_constraint_variable_is_external (variable))
+ g_hash_table_add (self->external_rows, variable);
+}
+
+static void
+gtk_constraint_solver_remove_column (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable)
+{
+ GtkConstraintVariable *v;
+ GtkConstraintVariableSetIter iter;
+ GtkConstraintVariableSet *cset;
+
+ /* Take a reference on the variable, as we're going to remove it
+ * from various maps and we want to guarantee the pointer is
+ * valid until we leave this function
+ */
+ gtk_constraint_variable_ref (variable);
+
+ cset = g_hash_table_lookup (self->columns, variable);
+ if (cset == NULL)
+ goto out;
+
+ gtk_constraint_variable_set_iter_init (&iter, cset);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
+
+ gtk_constraint_expression_remove_variable (e, variable);
+ }
+
+ g_hash_table_remove (self->columns, variable);
+
+out:
+ if (gtk_constraint_variable_is_external (variable))
+ {
+ g_hash_table_remove (self->external_rows, variable);
+ g_hash_table_remove (self->external_parametric_vars, variable);
+ }
+
+ gtk_constraint_variable_unref (variable);
+}
+
+static GtkConstraintExpression *
+gtk_constraint_solver_remove_row (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ gboolean free_res)
+{
+ GtkConstraintExpression *e;
+ GtkConstraintExpressionIter iter;
+ GtkConstraintVariable *t_v;
+ double t_c;
+
+ e = g_hash_table_lookup (self->rows, variable);
+ g_assert (e != NULL);
+
+ gtk_constraint_expression_ref (e);
+
+ gtk_constraint_expression_iter_init (&iter, e);
+ while (gtk_constraint_expression_iter_next (&iter, &t_v, &t_c))
+ {
+ GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, t_v);
+
+ if (cset != NULL)
+ gtk_constraint_variable_set_remove (cset, variable);
+ }
+
+ g_ptr_array_remove (self->infeasible_rows, variable);
+
+ if (gtk_constraint_variable_is_external (variable))
+ g_hash_table_remove (self->external_rows, variable);
+
+ g_hash_table_remove (self->rows, variable);
+
+ if (free_res)
+ {
+ gtk_constraint_expression_unref (e);
+ return NULL;
+ }
+
+ return e;
+}
+
+/*< private >
+ * gtk_constraint_solver_substitute_out:
+ * @self: a #GtkConstraintSolver
+ * @old_variable: a #GtkConstraintVariable
+ * @expression: a #GtkConstraintExpression
+ *
+ * Replaces @old_variable in every row of the tableau with @expression.
+ */
+static void
+gtk_constraint_solver_substitute_out (GtkConstraintSolver *self,
+ GtkConstraintVariable *old_variable,
+ GtkConstraintExpression *expression)
+{
+ GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, old_variable);
+ if (cset != NULL)
+ {
+ GtkConstraintVariableSetIter iter;
+ GtkConstraintVariable *v;
+
+ gtk_constraint_variable_set_iter_init (&iter, cset);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ GtkConstraintExpression *row = g_hash_table_lookup (self->rows, v);
+
+ gtk_constraint_expression_substitute_out (row, old_variable, expression, v, self);
+
+ if (gtk_constraint_variable_is_restricted (v) &&
+ gtk_constraint_expression_get_constant (row) < 0)
+ g_ptr_array_add (self->infeasible_rows, v);
+ }
+ }
+
+ if (gtk_constraint_variable_is_external (old_variable))
+ {
+ g_hash_table_add (self->external_rows, old_variable);
+ g_hash_table_remove (self->external_parametric_vars, old_variable);
+ }
+
+ g_hash_table_remove (self->columns, old_variable);
+}
+
+/*< private >
+ * gtk_constraint_solver_pivot:
+ * @self: a #GtkConstraintSolver
+ * @entry_var: a #GtkConstraintVariable
+ * @exit_var: a #GtkConstraintVariable
+ *
+ * Pivots the #GtkConstraintSolver.
+ *
+ * This function will move @entry_var into the basis of the tableau,
+ * making it a basic variable; and move @exit_var out of the basis of
+ * the tableau, making it a parametric variable.
+ */
+static void
+gtk_constraint_solver_pivot (GtkConstraintSolver *self,
+ GtkConstraintVariable *entry_var,
+ GtkConstraintVariable *exit_var)
+{
+ GtkConstraintExpression *expr;
+
+ if (entry_var != NULL)
+ gtk_constraint_variable_ref (entry_var);
+ else
+ g_critical ("INTERNAL: invalid entry variable during pivot");
+
+ if (exit_var != NULL)
+ gtk_constraint_variable_ref (exit_var);
+ else
+ g_critical ("INTERNAL: invalid exit variable during pivot");
+
+ /* We keep a reference to the expression */
+ expr = gtk_constraint_solver_remove_row (self, exit_var, FALSE);
+
+ gtk_constraint_expression_change_subject (expr, exit_var, entry_var);
+ gtk_constraint_solver_substitute_out (self, entry_var, expr);
+
+ if (gtk_constraint_variable_is_external (entry_var))
+ g_hash_table_remove (self->external_parametric_vars, entry_var);
+
+ gtk_constraint_solver_add_row (self, entry_var, expr);
+
+ gtk_constraint_variable_unref (entry_var);
+ gtk_constraint_variable_unref (exit_var);
+ gtk_constraint_expression_unref (expr);
+}
+
+static void
+gtk_constraint_solver_optimize (GtkConstraintSolver *self,
+ GtkConstraintVariable *z)
+{
+ GtkConstraintVariable *entry = NULL, *exit = NULL;
+ GtkConstraintExpression *z_row = g_hash_table_lookup (self->rows, z);
+
+#ifdef G_ENABLE_DEBUG
+ gint64 start_time = g_get_monotonic_time ();
+#endif
+
+ g_assert (z_row != NULL);
+
+ self->optimize_count += 1;
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (CONSTRAINTS))
+ {
+ char *str = gtk_constraint_variable_to_string (z);
+ g_message ("optimize: %s", str);
+ g_free (str);
+ }
+#endif
+
+ while (TRUE)
+ {
+ GtkConstraintVariableSet *column_vars;
+ GtkConstraintVariableSetIter viter;
+ GtkConstraintExpressionIter eiter;
+ GtkConstraintVariable *t_v, *v;
+ double t_c;
+ double objective_coefficient = 0.0;
+ double min_ratio;
+ double r;
+
+ gtk_constraint_expression_iter_init (&eiter, z_row);
+ while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
+ {
+ if (gtk_constraint_variable_is_pivotable (t_v) && t_c < objective_coefficient)
+ {
+ entry = t_v;
+ objective_coefficient = t_c;
+ break;
+ }
+ }
+
+ if (objective_coefficient >= -1e-8)
+ break;
+
+ min_ratio = DBL_MAX;
+ r = 0;
+
+ column_vars = gtk_constraint_solver_get_column_set (self, entry);
+ gtk_constraint_variable_set_iter_init (&viter, column_vars);
+ while (gtk_constraint_variable_set_iter_next (&viter, &v))
+ {
+ if (gtk_constraint_variable_is_pivotable (v))
+ {
+ GtkConstraintExpression *expr = g_hash_table_lookup (self->rows, v);
+ double coeff = gtk_constraint_expression_get_coefficient (expr, entry);
+
+ if (coeff < 0.0)
+ {
+ double constant = gtk_constraint_expression_get_constant (expr);
+
+ r = -1.0 * constant / coeff;
+ if (r < min_ratio)
+ {
+ min_ratio = r;
+ exit = v;
+ }
+ }
+ }
+ }
+
+ if (min_ratio == DBL_MAX)
+ {
+ GTK_NOTE (CONSTRAINTS, g_message ("Unbounded objective variable during optimization"));
+ break;
+ }
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (CONSTRAINTS))
+ {
+ char *entry_s = gtk_constraint_variable_to_string (entry);
+ char *exit_s = gtk_constraint_variable_to_string (exit);
+ g_message ("pivot(entry: %s, exit: %s)", entry_s, exit_s);
+ g_free (entry_s);
+ g_free (exit_s);
+ }
+#endif
+
+ gtk_constraint_solver_pivot (self, entry, exit);
+ }
+
+ GTK_NOTE (CONSTRAINTS,
+ g_message ("solver.optimize.time := %.3f ms (pass: %d)",
+ (float) (g_get_monotonic_time () - start_time) / 1000.f,
+ self->optimize_count));
+}
+
+/*< private >
+ * gtk_constraint_solver_new_expression:
+ * @self: a #GtkConstraintSolver
+ * @constraint: a #GtkConstraintRef
+ * @eplus_p: (out) (optional): the positive error variable
+ * @eminus_p: (out) (optional): the negative error variable
+ * @prev_constant_p: the constant part of the @constraint's expression
+ *
+ * Creates a new expression for the @constraint, replacing
+ * any basic variable with their expressions, and normalizing
+ * the terms to avoid a negative constant.
+ *
+ * If the @constraint is not required, this function will add
+ * error variables with the appropriate weight to the tableau.
+ *
+ * Returns: (transfer full): the new expression for the constraint
+ */
+static GtkConstraintExpression *
+gtk_constraint_solver_new_expression (GtkConstraintSolver *self,
+ GtkConstraintRef *constraint,
+ GtkConstraintVariable **eplus_p,
+ GtkConstraintVariable **eminus_p,
+ double *prev_constant_p)
+{
+ GtkConstraintExpression *cn_expr = constraint->expression;
+ GtkConstraintExpression *expr;
+ GtkConstraintExpressionIter eiter;
+ GtkConstraintVariable *t_v;
+ double t_c;
+
+ if (eplus_p != NULL)
+ *eplus_p = NULL;
+ if (eminus_p != NULL)
+ *eminus_p = NULL;
+ if (prev_constant_p != NULL)
+ *prev_constant_p = 0.0;
+
+ expr = gtk_constraint_expression_new (gtk_constraint_expression_get_constant (cn_expr));
+
+ gtk_constraint_expression_iter_init (&eiter, cn_expr);
+ while (gtk_constraint_expression_iter_next (&eiter, &t_v, &t_c))
+ {
+ GtkConstraintExpression *e = g_hash_table_lookup (self->rows, t_v);
+
+ if (e == NULL)
+ gtk_constraint_expression_add_variable (expr, t_v, t_c, NULL, self);
+ else
+ gtk_constraint_expression_add_expression (expr, e, t_c, NULL, self);
+ }
+
+ if (gtk_constraint_ref_is_inequality (constraint))
+ {
+ GtkConstraintVariable *slack_var;
+
+ /* If the constraint is an inequality, we add a slack variable to
+ * turn it into an equality, e.g. from
+ *
+ * expr ≥ 0
+ *
+ * to
+ *
+ * expr - slack = 0
+ *
+ * Additionally, if the constraint is not required we add an
+ * error variable with the weight of the constraint:
+ *
+ * expr - slack + error = 0
+ */
+ self->slack_counter += 1;
+
+ slack_var = gtk_constraint_variable_new_slack ("s");
+ gtk_constraint_expression_set_variable (expr, slack_var, -1.0);
+ gtk_constraint_variable_unref (slack_var);
+
+ g_hash_table_insert (self->marker_vars, constraint, slack_var);
+
+ if (!gtk_constraint_ref_is_required (constraint))
+ {
+ GtkConstraintExpression *z_row;
+ GtkConstraintVariable *eminus;
+
+ self->slack_counter += 1;
+
+ eminus = gtk_constraint_variable_new_slack ("em");
+ gtk_constraint_expression_set_variable (expr, eminus, 1.0);
+ gtk_constraint_variable_unref (eminus);
+
+ z_row = g_hash_table_lookup (self->rows, self->objective);
+ gtk_constraint_expression_set_variable (z_row, eminus, constraint->strength);
+
+ gtk_constraint_solver_insert_error_variable (self, constraint, eminus);
+ gtk_constraint_solver_note_added_variable (self, eminus, self->objective);
+ gtk_constraint_variable_unref (eminus);
+ }
+ }
+ else
+ {
+ GtkConstraintVariable *dummy_var;
+
+ if (gtk_constraint_ref_is_required (constraint))
+ {
+ /* If the constraint is required, we use a dummy marker variable;
+ * the dummy won't be allowed to enter the basis of the tableau
+ * when pivoting.
+ */
+ self->dummy_counter += 1;
+
+ dummy_var = gtk_constraint_variable_new_dummy ("dummy");
+
+ if (eplus_p != NULL)
+ *eplus_p = dummy_var;
+ if (eminus_p != NULL)
+ *eminus_p = dummy_var;
+ if (prev_constant_p != NULL)
+ *prev_constant_p = gtk_constraint_expression_get_constant (cn_expr);
+
+ gtk_constraint_expression_set_variable (expr, dummy_var, 1.0);
+ g_hash_table_insert (self->marker_vars, constraint, dummy_var);
+
+ gtk_constraint_variable_unref (dummy_var);
+ }
+ else
+ {
+ GtkConstraintVariable *eplus, *eminus;
+ GtkConstraintExpression *z_row;
+
+ /* Since the constraint is a non-required equality, we need to
+ * add error variables around it, i.e. turn it from:
+ *
+ * expr = 0
+ *
+ * to:
+ *
+ * expr - eplus + eminus = 0
+ */
+ self->slack_counter += 1;
+
+ eplus = gtk_constraint_variable_new_slack ("ep");
+ eminus = gtk_constraint_variable_new_slack ("em");
+
+ gtk_constraint_expression_set_variable (expr, eplus, -1.0);
+ gtk_constraint_expression_set_variable (expr, eminus, 1.0);
+
+ g_hash_table_insert (self->marker_vars, constraint, eplus);
+
+ z_row = g_hash_table_lookup (self->rows, self->objective);
+
+ gtk_constraint_expression_set_variable (z_row, eplus, constraint->strength);
+ gtk_constraint_expression_set_variable (z_row, eminus, constraint->strength);
+ gtk_constraint_solver_note_added_variable (self, eplus, self->objective);
+ gtk_constraint_solver_note_added_variable (self, eminus, self->objective);
+
+ gtk_constraint_solver_insert_error_variable (self, constraint, eplus);
+ gtk_constraint_solver_insert_error_variable (self, constraint, eminus);
+
+ if (constraint->is_stay)
+ {
+ g_ptr_array_add (self->stay_error_vars, gtk_constraint_variable_pair_new (eplus, eminus));
+ }
+ else if (constraint->is_edit)
+ {
+ if (eplus_p != NULL)
+ *eplus_p = eplus;
+ if (eminus_p != NULL)
+ *eminus_p = eminus;
+ if (prev_constant_p != NULL)
+ *prev_constant_p = gtk_constraint_expression_get_constant (cn_expr);
+ }
+
+ gtk_constraint_variable_unref (eplus);
+ gtk_constraint_variable_unref (eminus);
+ }
+ }
+
+ if (gtk_constraint_expression_get_constant (expr) < 0.0)
+ gtk_constraint_expression_multiply_by (expr, -1.0);
+
+ return expr;
+}
+
+static void
+gtk_constraint_solver_dual_optimize (GtkConstraintSolver *self)
+{
+ GtkConstraintExpression *z_row = g_hash_table_lookup (self->rows, self->objective);
+#ifdef G_ENABLE_DEBUG
+ gint64 start_time = g_get_monotonic_time ();
+#endif
+
+ /* We iterate until we don't have any more infeasible rows; the pivot()
+ * at the end of the loop iteration may add or remove infeasible rows
+ * as well
+ */
+ while (self->infeasible_rows->len != 0)
+ {
+ GtkConstraintVariable *entry_var, *exit_var, *t_v;
+ GtkConstraintExpressionIter eiter;
+ GtkConstraintExpression *expr;
+ double ratio, t_c;
+
+ /* Pop the last element of the array */
+ exit_var = g_ptr_array_index (self->infeasible_rows, self->infeasible_rows->len - 1);
+ g_ptr_array_set_size (self->infeasible_rows, self->infeasible_rows->len - 1);
+
+ expr = g_hash_table_lookup (self->rows, exit_var);
+ if (expr == NULL)
+ continue;
+
+ if (gtk_constraint_expression_get_constant (expr) >= 0.0)
+ continue;
+
+ ratio = DBL_MAX;
+ entry_var = NULL;
+
+ gtk_constraint_expression_iter_init (&eiter, expr);
+ while (gtk_constraint_expression_iter_next (&eiter, &t_v, &t_c))
+ {
+ if (t_c > 0.0 && gtk_constraint_variable_is_pivotable (t_v))
+ {
+ double zc = gtk_constraint_expression_get_coefficient (z_row, t_v);
+ double r = zc / t_c;
+
+ if (r < ratio)
+ {
+ entry_var = t_v;
+ ratio = r;
+ }
+ }
+ }
+
+ if (ratio == DBL_MAX)
+ g_critical ("INTERNAL: ratio == DBL_MAX in dual_optimize");
+
+ gtk_constraint_solver_pivot (self, entry_var, exit_var);
+ }
+
+ GTK_NOTE (CONSTRAINTS,
+ g_message ("dual_optimize.time := %.3f ms",
+ (float) (g_get_monotonic_time () - start_time) / 1000.f));
+}
+
+static void
+gtk_constraint_solver_delta_edit_constant (GtkConstraintSolver *self,
+ double delta,
+ GtkConstraintVariable *plus_error_var,
+ GtkConstraintVariable *minus_error_var)
+{
+ GtkConstraintExpression *plus_expr, *minus_expr;
+ GtkConstraintVariable *basic_var;
+ GtkConstraintVariableSet *column_set;
+ GtkConstraintVariableSetIter iter;
+
+ plus_expr = g_hash_table_lookup (self->rows, plus_error_var);
+ if (plus_expr != NULL)
+ {
+ double new_constant = gtk_constraint_expression_get_constant (plus_expr) + delta;
+
+ gtk_constraint_expression_set_constant (plus_expr, new_constant);
+
+ if (new_constant < 0.0)
+ g_ptr_array_add (self->infeasible_rows, plus_error_var);
+
+ return;
+ }
+
+ minus_expr = g_hash_table_lookup (self->rows, minus_error_var);
+ if (minus_expr != NULL)
+ {
+ double new_constant = gtk_constraint_expression_get_constant (minus_expr) - delta;
+
+ gtk_constraint_expression_set_constant (minus_expr, new_constant);
+
+ if (new_constant < 0.0)
+ g_ptr_array_add (self->infeasible_rows, minus_error_var);
+
+ return;
+ }
+
+ column_set = g_hash_table_lookup (self->columns, minus_error_var);
+ if (column_set == NULL)
+ {
+ g_critical ("INTERNAL: Columns are unset during delta edit");
+ return;
+ }
+
+ gtk_constraint_variable_set_iter_init (&iter, column_set);
+ while (gtk_constraint_variable_set_iter_next (&iter, &basic_var))
+ {
+ GtkConstraintExpression *expr;
+ double c, new_constant;
+
+ expr = g_hash_table_lookup (self->rows, basic_var);
+ c = gtk_constraint_expression_get_coefficient (expr, minus_error_var);
+
+ new_constant = gtk_constraint_expression_get_constant (expr) + (c * delta);
+ gtk_constraint_expression_set_constant (expr, new_constant);
+
+ if (gtk_constraint_variable_is_restricted (basic_var) && new_constant < 0.0)
+ g_ptr_array_add (self->infeasible_rows, basic_var);
+ }
+}
+
+static GtkConstraintVariable *
+gtk_constraint_solver_choose_subject (GtkConstraintSolver *self,
+ GtkConstraintExpression *expression)
+{
+ GtkConstraintExpressionIter eiter;
+ GtkConstraintVariable *subject = NULL;
+ GtkConstraintVariable *retval = NULL;
+ GtkConstraintVariable *t_v;
+ gboolean found_unrestricted = FALSE;
+ gboolean found_new_restricted = FALSE;
+ gboolean retval_found = FALSE;
+ double coeff = 0.0;
+ double t_c;
+
+ gtk_constraint_expression_iter_init (&eiter, expression);
+ while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
+ {
+ if (found_unrestricted)
+ {
+ if (!gtk_constraint_variable_is_restricted (t_v))
+ {
+ if (!g_hash_table_contains (self->columns, t_v))
+ {
+ retval_found = TRUE;
+ retval = t_v;
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (gtk_constraint_variable_is_restricted (t_v))
+ {
+ if (!found_new_restricted &&
+ !gtk_constraint_variable_is_dummy (t_v) &&
+ t_c < 0.0)
+ {
+ GtkConstraintVariableSet *cset = g_hash_table_lookup (self->columns, t_v);
+
+ if (cset == NULL ||
+ (gtk_constraint_variable_set_is_singleton (cset) &&
+ g_hash_table_contains (self->columns, self->objective)))
+ {
+ subject = t_v;
+ found_new_restricted = TRUE;
+ }
+ }
+ }
+ else
+ {
+ subject = t_v;
+ found_unrestricted = TRUE;
+ }
+ }
+ }
+
+ if (retval_found)
+ return retval;
+
+ if (subject != NULL)
+ return subject;
+
+ gtk_constraint_expression_iter_init (&eiter, expression);
+ while (gtk_constraint_expression_iter_prev (&eiter, &t_v, &t_c))
+ {
+ if (!gtk_constraint_variable_is_dummy (t_v))
+ return NULL;
+
+ if (!g_hash_table_contains (self->columns, t_v))
+ {
+ subject = t_v;
+ coeff = t_c;
+ }
+ }
+
+ if (!G_APPROX_VALUE (gtk_constraint_expression_get_constant (expression), 0.0, 0.001))
+ {
+ GTK_NOTE (CONSTRAINTS,
+ g_message ("Unable to satisfy required constraint (choose_subject)"));
+ return NULL;
+ }
+
+ if (coeff > 0)
+ gtk_constraint_expression_multiply_by (expression, -1.0);
+
+ return subject;
+}
+
+static gboolean
+gtk_constraint_solver_try_adding_directly (GtkConstraintSolver *self,
+ GtkConstraintExpression *expression)
+{
+ GtkConstraintVariable *subject;
+
+ subject = gtk_constraint_solver_choose_subject (self, expression);
+ if (subject == NULL)
+ return FALSE;
+
+ gtk_constraint_variable_ref (subject);
+
+ gtk_constraint_expression_new_subject (expression, subject);
+ if (gtk_constraint_solver_column_has_key (self, subject))
+ gtk_constraint_solver_substitute_out (self, subject, expression);
+
+ gtk_constraint_solver_add_row (self, subject, expression);
+
+ gtk_constraint_variable_unref (subject);
+
+ return TRUE;
+}
+
+static void
+gtk_constraint_solver_add_with_artificial_variable (GtkConstraintSolver *self,
+ GtkConstraintExpression *expression)
+{
+ GtkConstraintVariable *av, *az;
+ GtkConstraintExpression *az_row;
+ GtkConstraintExpression *az_tableau_row;
+ GtkConstraintExpression *e;
+
+ av = gtk_constraint_variable_new_slack ("a");
+ self->artificial_counter += 1;
+
+ az = gtk_constraint_variable_new_objective ("az");
+
+ az_row = gtk_constraint_expression_clone (expression);
+
+ gtk_constraint_solver_add_row (self, az, az_row);
+ gtk_constraint_solver_add_row (self, av, expression);
+
+ gtk_constraint_expression_unref (az_row);
+ gtk_constraint_variable_unref (av);
+ gtk_constraint_variable_unref (az);
+
+ gtk_constraint_solver_optimize (self, az);
+
+ az_tableau_row = g_hash_table_lookup (self->rows, az);
+ if (!G_APPROX_VALUE (gtk_constraint_expression_get_constant (az_tableau_row), 0.0, 0.001))
+ {
+ gtk_constraint_solver_remove_column (self, av);
+ gtk_constraint_solver_remove_row (self, az, TRUE);
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (CONSTRAINTS))
+ {
+ char *str = gtk_constraint_expression_to_string (expression);
+ g_message ("Unable to satisfy a required constraint (add): %s", str);
+ g_free (str);
+ }
+#endif
+
+ return;
+ }
+
+ e = g_hash_table_lookup (self->rows, av);
+ if (e != NULL)
+ {
+ GtkConstraintVariable *entry_var;
+
+ if (gtk_constraint_expression_is_constant (e))
+ {
+ gtk_constraint_solver_remove_row (self, av, TRUE);
+ gtk_constraint_solver_remove_row (self, az, TRUE);
+ return;
+ }
+
+ entry_var = gtk_constraint_expression_get_pivotable_variable (e);
+ if (entry_var == NULL)
+ return;
+
+ gtk_constraint_solver_pivot (self, entry_var, av);
+ }
+
+ g_assert (!g_hash_table_contains (self->rows, av));
+
+ gtk_constraint_solver_remove_column (self, av);
+ gtk_constraint_solver_remove_row (self, az, TRUE);
+}
+
+static void
+gtk_constraint_solver_add_constraint_internal (GtkConstraintSolver *self,
+ GtkConstraintRef *constraint)
+{
+ GtkConstraintExpression *expr;
+ GtkConstraintVariable *eplus;
+ GtkConstraintVariable *eminus;
+ double prev_constant;
+
+ expr = gtk_constraint_solver_new_expression (self, constraint,
+ &eplus,
+ &eminus,
+ &prev_constant);
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (CONSTRAINTS))
+ {
+ char *expr_s = gtk_constraint_expression_to_string (expr);
+ char *ref_s = gtk_constraint_ref_to_string (constraint);
+ g_message ("Adding constraint '%s' (normalized expression: '%s')", ref_s, expr_s);
+ g_free (ref_s);
+ g_free (expr_s);
+ }
+#endif
+
+ if (constraint->is_stay)
+ {
+ StayInfo *si = g_new (StayInfo, 1);
+
+ si->constraint = constraint;
+
+ g_hash_table_insert (self->stay_var_map, constraint->variable, si);
+ }
+ else if (constraint->is_edit)
+ {
+ EditInfo *ei = g_new (EditInfo, 1);
+
+ ei->constraint = constraint;
+ ei->eplus = eplus;
+ ei->eminus = eminus;
+ ei->prev_constant = prev_constant;
+
+ g_hash_table_insert (self->edit_var_map, constraint->variable, ei);
+ }
+
+ if (!gtk_constraint_solver_try_adding_directly (self, expr))
+ gtk_constraint_solver_add_with_artificial_variable (self, expr);
+
+ gtk_constraint_expression_unref (expr);
+
+ self->needs_solving = TRUE;
+
+ if (self->auto_solve)
+ {
+ gtk_constraint_solver_optimize (self, self->objective);
+ gtk_constraint_solver_set_external_variables (self);
+ }
+
+ constraint->solver = self;
+
+ g_hash_table_add (self->constraints, constraint);
+}
+
+/*< private >
+ * gtk_constraint_solver_new:
+ *
+ * Creates a new #GtkConstraintSolver instance.
+ *
+ * Returns: the newly created #GtkConstraintSolver
+ */
+GtkConstraintSolver *
+gtk_constraint_solver_new (void)
+{
+ return g_object_new (GTK_TYPE_CONSTRAINT_SOLVER, NULL);
+}
+
+/*< private >
+ * gtk_constraint_solver_freeze:
+ * @solver: a #GtkConstraintSolver
+ *
+ * Freezes the solver; any constraint addition or removal will not
+ * be automatically solved until gtk_constraint_solver_thaw() is
+ * called.
+ */
+void
+gtk_constraint_solver_freeze (GtkConstraintSolver *solver)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
+
+ solver->freeze_count += 1;
+
+ if (solver->freeze_count > 0)
+ solver->auto_solve = FALSE;
+}
+
+/*< private >
+ * gtk_constraint_solver_thaw:
+ * @solver: a #GtkConstraintSolver
+ *
+ * Thaws a frozen #GtkConstraintSolver.
+ */
+void
+gtk_constraint_solver_thaw (GtkConstraintSolver *solver)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
+ g_return_if_fail (solver->freeze_count > 0);
+
+ solver->freeze_count -= 1;
+
+ if (solver->freeze_count == 0)
+ {
+ solver->auto_solve = TRUE;
+ gtk_constraint_solver_resolve (solver);
+ }
+}
+
+/*< private >
+ * gtk_constraint_solver_note_added_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: a #GtkConstraintVariable
+ * @subject: a #GtkConstraintVariable
+ *
+ * Adds a new @variable into the tableau of a #GtkConstraintSolver.
+ *
+ * This function is typically called by #GtkConstraintExpression, and
+ * should never be directly called.
+ */
+void
+gtk_constraint_solver_note_added_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintVariable *subject)
+{
+ if (subject != NULL)
+ gtk_constraint_solver_insert_column_variable (self, variable, subject);
+}
+
+/*< private >
+ * gtk_constraint_solver_note_removed_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: a #GtkConstraintVariable
+ * @subject: a #GtkConstraintVariable
+ *
+ * Removes a @variable from the tableau of a #GtkConstraintSolver.
+ *
+ * This function is typically called by #GtkConstraintExpression, and
+ * should never be directly called.
+ */
+void
+gtk_constraint_solver_note_removed_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintVariable *subject)
+{
+ GtkConstraintVariableSet *set;
+
+ set = g_hash_table_lookup (self->columns, variable);
+ if (set != NULL && subject != NULL)
+ gtk_constraint_variable_set_remove (set, subject);
+}
+
+/*< private >
+ * gtk_constraint_solver_create_variable:
+ * @self: a #GtkConstraintSolver
+ * @prefix: (nullable): the prefix of the variable
+ * @name: (nullable): the name of the variable
+ * @value: the initial value of the variable
+ *
+ * Creates a new variable inside the @solver.
+ *
+ * Returns: (transfer full): the newly created variable
+ */
+GtkConstraintVariable *
+gtk_constraint_solver_create_variable (GtkConstraintSolver *self,
+ const char *prefix,
+ const char *name,
+ double value)
+{
+ GtkConstraintVariable *res;
+
+ res = gtk_constraint_variable_new (prefix, name);
+ gtk_constraint_variable_set_value (res, value);
+
+ self->var_counter++;
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_solver_resolve:
+ * @solver: a #GtkConstraintSolver
+ *
+ * Resolves the constraints currently stored in @solver.
+ */
+void
+gtk_constraint_solver_resolve (GtkConstraintSolver *solver)
+{
+#ifdef G_ENABLE_DEBUG
+ gint64 start_time = g_get_monotonic_time ();
+#endif
+
+ g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
+
+ gtk_constraint_solver_dual_optimize (solver);
+ gtk_constraint_solver_set_external_variables (solver);
+
+ g_ptr_array_set_size (solver->infeasible_rows, 0);
+
+ gtk_constraint_solver_reset_stay_constants (solver);
+
+ GTK_NOTE (CONSTRAINTS,
+ g_message ("resolve.time := %.3f ms",
+ (float) (g_get_monotonic_time () - start_time) / 1000.f));
+
+ solver->needs_solving = FALSE;
+}
+
+/*< private >
+ * gtk_constraint_solver_add_constraint:
+ * @self: a #GtkConstraintSolver
+ * @variable: the subject of the constraint
+ * @relation: the relation of the constraint
+ * @expression: the expression of the constraint
+ * @strength: the strength of the constraint
+ *
+ * Adds a new constraint in the form of:
+ *
+ * |[
+ * variable relation expression (strength)
+ * |]
+ *
+ * into the #GtkConstraintSolver.
+ *
+ * Returns: (transfer none): a reference to the newly created
+ * constraint; you can use the reference to remove the
+ * constraint from the solver
+ */
+GtkConstraintRef *
+gtk_constraint_solver_add_constraint (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintRelation relation,
+ GtkConstraintExpression *expression,
+ int strength)
+{
+ GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
+
+ res->solver = self;
+ res->strength = strength;
+ res->is_edit = FALSE;
+ res->is_stay = FALSE;
+ res->relation = relation;
+
+ if (expression == NULL)
+ res->expression = gtk_constraint_expression_new_from_variable (variable);
+ else
+ {
+ res->expression = expression;
+
+ if (variable != NULL)
+ {
+ switch (res->relation)
+ {
+ case GTK_CONSTRAINT_RELATION_EQ:
+ gtk_constraint_expression_add_variable (res->expression,
+ variable, -1.0,
+ NULL,
+ self);
+ break;
+
+ case GTK_CONSTRAINT_RELATION_LE:
+ gtk_constraint_expression_add_variable (res->expression,
+ variable, -1.0,
+ NULL,
+ self);
+ break;
+
+ case GTK_CONSTRAINT_RELATION_GE:
+ gtk_constraint_expression_multiply_by (res->expression, -1.0);
+ gtk_constraint_expression_add_variable (res->expression,
+ variable, 1.0,
+ NULL,
+ self);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+ }
+ }
+
+ gtk_constraint_solver_add_constraint_internal (self, res);
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_solver_add_stay_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: a stay #GtkConstraintVariable
+ * @strength: the strength of the constraint
+ *
+ * Adds a constraint on a stay @variable with the given @strength.
+ *
+ * A stay variable is an "anchor" in the system: a variable that is
+ * supposed to stay at the same value.
+ *
+ * Returns: (transfer none): a reference to the newly created
+ * constraint; you can use the reference to remove the
+ * constraint from the solver
+ */
+GtkConstraintRef *
+gtk_constraint_solver_add_stay_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ int strength)
+{
+ GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
+
+ res->solver = self;
+ res->variable = gtk_constraint_variable_ref (variable);
+ res->relation = GTK_CONSTRAINT_RELATION_EQ;
+ res->strength = strength;
+ res->is_stay = TRUE;
+ res->is_edit = FALSE;
+
+ res->expression = gtk_constraint_expression_new (gtk_constraint_variable_get_value (res->variable));
+ gtk_constraint_expression_add_variable (res->expression,
+ res->variable, -1.0,
+ NULL,
+ self);
+
+#ifdef G_ENABLE_DEBUG
+ if (GTK_DEBUG_CHECK (CONSTRAINTS))
+ {
+ char *str = gtk_constraint_expression_to_string (res->expression);
+ g_message ("Adding stay variable: %s", str);
+ g_free (str);
+ }
+#endif
+
+ gtk_constraint_solver_add_constraint_internal (self, res);
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_solver_remove_stay_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: a stay variable
+ *
+ * Removes the stay constraint associated to @variable.
+ *
+ * This is a convenience function for gtk_constraint_solver_remove_constraint().
+ */
+void
+gtk_constraint_solver_remove_stay_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable)
+{
+ StayInfo *si = g_hash_table_lookup (self->stay_var_map, variable);
+
+ if (si == NULL)
+ {
+ char *str = gtk_constraint_variable_to_string (variable);
+
+ g_critical ("Unknown stay variable '%s'", str);
+
+ g_free (str);
+
+ return;
+ }
+
+ gtk_constraint_solver_remove_constraint (self, si->constraint);
+}
+
+/*< private >
+ * gtk_constraint_solver_add_edit_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: an edit variable
+ * @strength: the strength of the constraint
+ *
+ * Adds an editable constraint to the @solver.
+ *
+ * Editable constraints can be used to suggest values to a
+ * #GtkConstraintSolver inside an edit phase, for instance: if
+ * you want to change the value of a variable without necessarily
+ * insert a new constraint every time.
+ *
+ * See also: gtk_constraint_solver_suggest_value()
+ *
+ * Returns: (transfer none): a reference to the newly added constraint
+ */
+GtkConstraintRef *
+gtk_constraint_solver_add_edit_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ int strength)
+{
+ GtkConstraintRef *res = g_new0 (GtkConstraintRef, 1);
+
+ res->solver = self;
+ res->variable = gtk_constraint_variable_ref (variable);
+ res->relation = GTK_CONSTRAINT_RELATION_EQ;
+ res->strength = strength;
+ res->is_stay = FALSE;
+ res->is_edit = TRUE;
+
+ res->expression = gtk_constraint_expression_new (gtk_constraint_variable_get_value (variable));
+ gtk_constraint_expression_add_variable (res->expression,
+ variable, -1.0,
+ NULL,
+ self);
+
+ gtk_constraint_solver_add_constraint_internal (self, res);
+
+ return res;
+}
+
+/*< private >
+ * gtk_constraint_solver_remove_edit_variable:
+ * @self: a #GtkConstraintSolver
+ * @variable: an edit variable
+ *
+ * Removes the edit constraint associated to @variable.
+ *
+ * This is a convenience function around gtk_constraint_solver_remove_constraint().
+ */
+void
+gtk_constraint_solver_remove_edit_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable)
+{
+ EditInfo *ei = g_hash_table_lookup (self->edit_var_map, variable);
+
+ if (ei == NULL)
+ {
+ char *str = gtk_constraint_variable_to_string (variable);
+
+ g_critical ("Unknown edit variable '%s'", str);
+
+ g_free (str);
+
+ return;
+ }
+
+ gtk_constraint_solver_remove_constraint (self, ei->constraint);
+}
+
+/*< private >
+ * gtk_constraint_solver_remove_constraint:
+ * @self: a #GtkConstraintSolver
+ * @constraint: a constraint reference
+ *
+ * Removes a @constraint from the @solver.
+ */
+void
+gtk_constraint_solver_remove_constraint (GtkConstraintSolver *self,
+ GtkConstraintRef *constraint)
+{
+ GtkConstraintExpression *z_row;
+ GtkConstraintVariable *marker;
+ GtkConstraintVariableSet *error_vars;
+ GtkConstraintVariableSetIter iter;
+
+ if (!g_hash_table_contains (self->constraints, constraint))
+ return;
+
+ self->needs_solving = TRUE;
+
+ gtk_constraint_solver_reset_stay_constants (self);
+
+ z_row = g_hash_table_lookup (self->rows, self->objective);
+ error_vars = g_hash_table_lookup (self->error_vars, constraint);
+
+ if (error_vars != NULL)
+ {
+ GtkConstraintVariable *v;
+
+ gtk_constraint_variable_set_iter_init (&iter, error_vars);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
+
+ if (e == NULL)
+ {
+ gtk_constraint_expression_add_variable (z_row,
+ v,
+ constraint->strength,
+ self->objective,
+ self);
+ }
+ else
+ {
+ gtk_constraint_expression_add_expression (z_row,
+ e,
+ constraint->strength,
+ self->objective,
+ self);
+ }
+ }
+ }
+
+ marker = g_hash_table_lookup (self->marker_vars, constraint);
+ if (marker == NULL)
+ {
+ g_critical ("Constraint %p not found", constraint);
+ return;
+ }
+
+ g_hash_table_remove (self->marker_vars, constraint);
+
+ if (g_hash_table_lookup (self->rows, marker) == NULL)
+ {
+ GtkConstraintVariableSet *set = g_hash_table_lookup (self->columns, marker);
+ GtkConstraintVariable *exit_var = NULL;
+ GtkConstraintVariable *v;
+ double min_ratio = 0;
+
+ if (set == NULL)
+ goto no_columns;
+
+ gtk_constraint_variable_set_iter_init (&iter, set);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ if (gtk_constraint_variable_is_restricted (v))
+ {
+ GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
+ double coeff = gtk_constraint_expression_get_coefficient (e, marker);
+
+ if (coeff < 0.0)
+ {
+ double r = -gtk_constraint_expression_get_constant (e) / coeff;
+
+ if (exit_var == NULL ||
+ r < min_ratio ||
+ G_APPROX_VALUE (r, min_ratio, 0.0001))
+ {
+ min_ratio = r;
+ exit_var = v;
+ }
+ }
+ }
+ }
+
+ if (exit_var == NULL)
+ {
+ gtk_constraint_variable_set_iter_init (&iter, set);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ if (gtk_constraint_variable_is_restricted (v))
+ {
+ GtkConstraintExpression *e = g_hash_table_lookup (self->rows, v);
+ double coeff = gtk_constraint_expression_get_coefficient (e, marker);
+ double r = 0.0;
+
+ if (!G_APPROX_VALUE (coeff, 0.0, 0.0001))
+ r = gtk_constraint_expression_get_constant (e) / coeff;
+
+ if (exit_var == NULL || r < min_ratio)
+ {
+ min_ratio = r;
+ exit_var = v;
+ }
+ }
+ }
+ }
+
+ if (exit_var == NULL)
+ {
+ if (gtk_constraint_variable_set_is_empty (set))
+ gtk_constraint_solver_remove_column (self, marker);
+ else
+ {
+ gtk_constraint_variable_set_iter_init (&iter, set);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ if (v != self->objective)
+ {
+ exit_var = v;
+ break;
+ }
+ }
+ }
+ }
+
+ if (exit_var != NULL)
+ gtk_constraint_solver_pivot (self, marker, exit_var);
+ }
+
+no_columns:
+ if (g_hash_table_lookup (self->rows, marker) != NULL)
+ gtk_constraint_solver_remove_row (self, marker, TRUE);
+ else
+ gtk_constraint_variable_unref (marker);
+
+ if (error_vars != NULL)
+ {
+ GtkConstraintVariable *v;
+
+ gtk_constraint_variable_set_iter_init (&iter, error_vars);
+ while (gtk_constraint_variable_set_iter_next (&iter, &v))
+ {
+ if (v != marker)
+ gtk_constraint_solver_remove_column (self, v);
+ }
+ }
+
+ if (constraint->is_stay)
+ {
+ if (error_vars != NULL)
+ {
+ GPtrArray *remaining =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) gtk_constraint_variable_pair_free);
+
+ int i = 0;
+
+ for (i = 0; i < self->stay_error_vars->len; i++)
+ {
+ GtkConstraintVariablePair *pair = g_ptr_array_index (self->stay_error_vars, i);
+ gboolean found = FALSE;
+
+ if (gtk_constraint_variable_set_remove (error_vars, pair->first))
+ found = TRUE;
+
+ if (gtk_constraint_variable_set_remove (error_vars, pair->second))
+ found = FALSE;
+
+ if (!found)
+ g_ptr_array_add (remaining, gtk_constraint_variable_pair_new (pair->first, pair->second));
+ }
+
+ g_clear_pointer (&self->stay_error_vars, g_ptr_array_unref);
+ self->stay_error_vars = remaining;
+ }
+
+ g_hash_table_remove (self->stay_var_map, constraint->variable);
+ }
+ else if (constraint->is_edit)
+ {
+ EditInfo *ei = g_hash_table_lookup (self->edit_var_map, constraint->variable);
+
+ gtk_constraint_solver_remove_column (self, ei->eminus);
+
+ g_hash_table_remove (self->edit_var_map, constraint->variable);
+ }
+
+ if (error_vars != NULL)
+ g_hash_table_remove (self->error_vars, constraint);
+
+ if (self->auto_solve)
+ {
+ gtk_constraint_solver_optimize (self, self->objective);
+ gtk_constraint_solver_set_external_variables (self);
+ }
+
+ g_hash_table_remove (self->constraints, constraint);
+}
+
+/*< private >
+ * gtk_constraint_solver_suggest_value:
+ * @self: a #GtkConstraintSolver
+ * @variable: a #GtkConstraintVariable
+ * @value: the suggested value for @variable
+ *
+ * Suggests a new @value for an edit @variable.
+ *
+ * The @variable must be an edit variable, and the solver must be
+ * in an edit phase.
+ */
+void
+gtk_constraint_solver_suggest_value (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ double value)
+{
+ EditInfo *ei = g_hash_table_lookup (self->edit_var_map, variable);
+ double delta;
+ if (ei == NULL)
+ {
+ g_critical ("Suggesting value '%g' but variable %p is not editable",
+ value, variable);
+ return;
+ }
+
+ if (!self->in_edit_phase)
+ {
+ g_critical ("Suggesting value '%g' for variable '%p' but solver is "
+ "not in an edit phase",
+ value, variable);
+ return;
+ }
+
+ delta = value - ei->prev_constant;
+ ei->prev_constant = value;
+
+ gtk_constraint_solver_delta_edit_constant (self, delta, ei->eplus, ei->eminus);
+}
+
+/*< private >
+ * gtk_constraint_solver_has_stay_variable:
+ * @solver: a #GtkConstraintSolver
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether @variable is a stay variable.
+ *
+ * Returns: %TRUE if the variable is a stay variable
+ */
+gboolean
+gtk_constraint_solver_has_stay_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver), FALSE);
+ g_return_val_if_fail (variable != NULL, FALSE);
+
+ return g_hash_table_contains (solver->stay_var_map, variable);
+}
+
+/*< private >
+ * gtk_constraint_solver_has_edit_variable:
+ * @solver: a #GtkConstraintSolver
+ * @variable: a #GtkConstraintVariable
+ *
+ * Checks whether @variable is an edit variable.
+ *
+ * Returns: %TRUE if the variable is an edit variable
+ */
+gboolean
+gtk_constraint_solver_has_edit_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable)
+{
+ g_return_val_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver), FALSE);
+ g_return_val_if_fail (variable != NULL, FALSE);
+
+ return g_hash_table_contains (solver->edit_var_map, variable);
+}
+
+/*< private >
+ * gtk_constraint_solver_begin_edit:
+ * @solver: a #GtkConstraintSolver
+ *
+ * Begins the edit phase for a constraint system.
+ *
+ * Typically, you need to add new edit constraints for a variable to the
+ * system, using gtk_constraint_solver_add_edit_variable(); then you
+ * call this function and suggest values for the edit variables, using
+ * gtk_constraint_solver_suggest_value(). After you suggested a value
+ * for all the variables you need to edit, you will need to call
+ * gtk_constraint_solver_resolve() to solve the system, and get the value
+ * of the various variables that you're interested in.
+ *
+ * Once you completed the edit phase, call gtk_constraint_solver_end_edit()
+ * to remove all the edit variables.
+ */
+void
+gtk_constraint_solver_begin_edit (GtkConstraintSolver *solver)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
+
+ if (g_hash_table_size (solver->edit_var_map) == 0)
+ {
+ g_critical ("Solver %p does not have editable variables.", solver);
+ return;
+ }
+
+ g_ptr_array_set_size (solver->infeasible_rows, 0);
+ gtk_constraint_solver_reset_stay_constants (solver);
+
+ solver->in_edit_phase = TRUE;
+}
+
+static void
+edit_info_free (gpointer data)
+{
+ g_free (data);
+}
+
+/*< private >
+ * gtk_constraint_solver_end_edit:
+ * @solver: a #GtkConstraintSolver
+ *
+ * Ends the edit phase for a constraint system, and clears
+ * all the edit variables introduced.
+ */
+void
+gtk_constraint_solver_end_edit (GtkConstraintSolver *solver)
+{
+ solver->in_edit_phase = FALSE;
+
+ gtk_constraint_solver_resolve (solver);
+
+ g_hash_table_remove_all (solver->edit_var_map);
+}
+
+void
+gtk_constraint_solver_clear (GtkConstraintSolver *solver)
+{
+ g_return_if_fail (GTK_IS_CONSTRAINT_SOLVER (solver));
+
+ g_hash_table_remove_all (solver->constraints);
+ g_hash_table_remove_all (solver->external_rows);
+ g_hash_table_remove_all (solver->external_parametric_vars);
+ g_hash_table_remove_all (solver->error_vars);
+ g_hash_table_remove_all (solver->marker_vars);
+ g_hash_table_remove_all (solver->edit_var_map);
+ g_hash_table_remove_all (solver->stay_var_map);
+
+ g_ptr_array_set_size (solver->infeasible_rows, 0);
+ g_ptr_array_set_size (solver->stay_error_vars, 0);
+
+ g_hash_table_remove_all (solver->rows);
+ g_hash_table_remove_all (solver->columns);
+
+ /* The rows table owns the objective variable */
+ solver->objective = gtk_constraint_variable_new_objective ("Z");
+ g_hash_table_insert (solver->rows,
+ solver->objective,
+ gtk_constraint_expression_new (0.0));
+
+ solver->slack_counter = 0;
+ solver->dummy_counter = 0;
+ solver->artificial_counter = 0;
+ solver->freeze_count = 0;
+
+ solver->needs_solving = FALSE;
+ solver->auto_solve = TRUE;
+}
+
+char *
+gtk_constraint_solver_to_string (GtkConstraintSolver *solver)
+{
+ GString *buf = g_string_new (NULL);
+
+ g_string_append (buf, "Tableau info:\n");
+ g_string_append_printf (buf, "Rows: %d (= %d constraints)\n",
+ g_hash_table_size (solver->rows),
+ g_hash_table_size (solver->rows) - 1);
+ g_string_append_printf (buf, "Columns: %d\n",
+ g_hash_table_size (solver->columns));
+ g_string_append_printf (buf, "Infeasible rows: %d\n",
+ solver->infeasible_rows->len);
+ g_string_append_printf (buf, "External basic variables: %d\n",
+ g_hash_table_size (solver->external_rows));
+ g_string_append_printf (buf, "External parametric variables: %d\n",
+ g_hash_table_size (solver->external_parametric_vars));
+
+ g_string_append (buf, "Constraints:");
+ if (g_hash_table_size (solver->constraints) == 0)
+ g_string_append (buf, " <empty>\n");
+ else
+ {
+ GHashTableIter iter;
+ gpointer key_p;
+
+ g_string_append (buf, "\n");
+
+ g_hash_table_iter_init (&iter, solver->constraints);
+ while (g_hash_table_iter_next (&iter, &key_p, NULL))
+ {
+ char *ref = gtk_constraint_ref_to_string (key_p);
+
+ g_string_append_printf (buf, " %s\n", ref);
+
+ g_free (ref);
+ }
+ }
+
+ g_string_append (buf, "Stay error vars:");
+ if (solver->stay_error_vars->len == 0)
+ g_string_append (buf, " <empty>\n");
+ else
+ {
+ g_string_append (buf, "\n");
+
+ for (int i = 0; i < solver->stay_error_vars->len; i++)
+ {
+ const GtkConstraintVariablePair *pair = g_ptr_array_index (solver->stay_error_vars, i);
+ char *first_s = gtk_constraint_variable_to_string (pair->first);
+ char *second_s = gtk_constraint_variable_to_string (pair->second);
+
+ g_string_append_printf (buf, " (%s, %s)\n", first_s, second_s);
+
+ g_free (first_s);
+ g_free (second_s);
+ }
+ }
+
+ g_string_append (buf, "Edit var map:");
+ if (g_hash_table_size (solver->edit_var_map) == 0)
+ g_string_append (buf, " <empty>\n");
+ else
+ {
+ GHashTableIter iter;
+ gpointer key_p, value_p;
+
+ g_string_append (buf, "\n");
+
+ g_hash_table_iter_init (&iter, solver->edit_var_map);
+ while (g_hash_table_iter_next (&iter, &key_p, &value_p))
+ {
+ char *var = gtk_constraint_variable_to_string (key_p);
+ const EditInfo *ei = value_p;
+ char *c = gtk_constraint_ref_to_string (ei->constraint);
+
+ g_string_append_printf (buf, " %s => %s\n", var, c);
+
+ g_free (var);
+ g_free (c);
+ }
+ }
+
+ return g_string_free (buf, FALSE);
+}
+
+char *
+gtk_constraint_solver_statistics (GtkConstraintSolver *solver)
+{
+ GString *buf = g_string_new (NULL);
+
+ g_string_append_printf (buf, "Variables: %d\n", solver->var_counter);
+ g_string_append_printf (buf, "Slack vars: %d\n", solver->slack_counter);
+ g_string_append_printf (buf, "Artificial vars: %d\n", solver->artificial_counter);
+ g_string_append_printf (buf, "Dummy vars: %d\n", solver->dummy_counter);
+ g_string_append_printf (buf, "Stay vars: %d\n", g_hash_table_size (solver->stay_var_map));
+ g_string_append_printf (buf, "Optimize count: %d\n", solver->optimize_count);
+ g_string_append_printf (buf, "Rows: %d\n", g_hash_table_size (solver->rows));
+ g_string_append_printf (buf, "Columns: %d\n", g_hash_table_size (solver->columns));
+
+ if (g_hash_table_size (solver->columns) > 0)
+ {
+ GHashTableIter iter;
+ gpointer val;
+ double sum = 0;
+
+ g_hash_table_iter_init (&iter, solver->columns);
+ while (g_hash_table_iter_next (&iter, NULL, &val))
+ {
+ GtkConstraintVariableSet *set = val;
+ sum += gtk_constraint_variable_set_size (set);
+ }
+ g_string_append_printf (buf, "Avg column size: %g\n", sum / g_hash_table_size (solver->columns));
+ }
+
+ g_string_append_printf (buf, "Infeasible rows: %d\n", solver->infeasible_rows->len);
+ g_string_append_printf (buf, "External basic variables: %d\n", g_hash_table_size (solver->external_rows));
+ g_string_append_printf (buf, "External parametric variables: %d\n", g_hash_table_size (solver->external_parametric_vars));
+
+ return g_string_free (buf, FALSE);
+}
diff --git a/gtk/gtkconstraintsolverprivate.h b/gtk/gtkconstraintsolverprivate.h
new file mode 100644
index 0000000000..cb7f74e681
--- /dev/null
+++ b/gtk/gtkconstraintsolverprivate.h
@@ -0,0 +1,117 @@
+/* gtkconstraintsolverprivate.h: Constraint solver based on the Cassowary method
+ * 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
+ */
+
+#pragma once
+
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_CONSTRAINT_SOLVER (gtk_constraint_solver_get_type())
+
+G_DECLARE_FINAL_TYPE (GtkConstraintSolver, gtk_constraint_solver, GTK, CONSTRAINT_SOLVER, GObject)
+
+GtkConstraintSolver *
+gtk_constraint_solver_new (void);
+
+void
+gtk_constraint_solver_freeze (GtkConstraintSolver *solver);
+
+void
+gtk_constraint_solver_thaw (GtkConstraintSolver *solver);
+
+void
+gtk_constraint_solver_resolve (GtkConstraintSolver *solver);
+
+GtkConstraintVariable *
+gtk_constraint_solver_create_variable (GtkConstraintSolver *solver,
+ const char *prefix,
+ const char *name,
+ double value);
+
+GtkConstraintRef *
+gtk_constraint_solver_add_constraint (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable,
+ GtkConstraintRelation relation,
+ GtkConstraintExpression *expression,
+ int strength);
+
+void
+gtk_constraint_solver_remove_constraint (GtkConstraintSolver *solver,
+ GtkConstraintRef *reference);
+
+GtkConstraintRef *
+gtk_constraint_solver_add_stay_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable,
+ int strength);
+
+void
+gtk_constraint_solver_remove_stay_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_solver_has_stay_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable);
+
+GtkConstraintRef *
+gtk_constraint_solver_add_edit_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable,
+ int strength);
+
+void
+gtk_constraint_solver_remove_edit_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable);
+
+gboolean
+gtk_constraint_solver_has_edit_variable (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable);
+
+void
+gtk_constraint_solver_suggest_value (GtkConstraintSolver *solver,
+ GtkConstraintVariable *variable,
+ double value);
+
+void
+gtk_constraint_solver_begin_edit (GtkConstraintSolver *solver);
+
+void
+gtk_constraint_solver_end_edit (GtkConstraintSolver *solver);
+
+void
+gtk_constraint_solver_note_added_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintVariable *subject);
+
+void
+gtk_constraint_solver_note_removed_variable (GtkConstraintSolver *self,
+ GtkConstraintVariable *variable,
+ GtkConstraintVariable *subject);
+
+void
+gtk_constraint_solver_clear (GtkConstraintSolver *solver);
+
+char *
+gtk_constraint_solver_to_string (GtkConstraintSolver *solver);
+
+char *
+gtk_constraint_solver_statistics (GtkConstraintSolver *solver);
+
+G_END_DECLS
diff --git a/gtk/gtkconstrainttypesprivate.h b/gtk/gtkconstrainttypesprivate.h
new file mode 100644
index 0000000000..052280ae98
--- /dev/null
+++ b/gtk/gtkconstrainttypesprivate.h
@@ -0,0 +1,50 @@
+/* gtkconstrainttypesprivate.h: Private types for the constraint solver
+ * 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
+ */
+
+#pragma once
+
+#include <gtk/gtktypes.h>
+#include <gtk/gtkenums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkConstraintVariable GtkConstraintVariable;
+typedef struct _GtkConstraintExpression GtkConstraintExpression;
+typedef struct _GtkConstraintExpressionBuilder GtkConstraintExpressionBuilder;
+
+/*< private >
+ * GtkConstraintRef:
+ *
+ * A reference to a constraint stored inside the solver; while #GtkConstraint
+ * represent the public API, a #GtkConstraintRef represents data stored inside
+ * the solver. A #GtkConstraintRef is completely opaque, and should only be
+ * used to remove a constraint from the solver.
+ */
+typedef struct _GtkConstraintRef GtkConstraintRef;
+
+/*< private >
+ * GtkConstraintSolver:
+ *
+ * A simplex solver using the Cassowary constraint solving algorithm.
+ */
+typedef struct _GtkConstraintSolver GtkConstraintSolver;
+
+G_END_DECLS
diff --git a/gtk/gtkconstraintvflparser.c b/gtk/gtkconstraintvflparser.c
new file mode 100644
index 0000000000..79be0b0037
--- /dev/null
+++ b/gtk/gtkconstraintvflparser.c
@@ -0,0 +1,1227 @@
+/* gtkconstraintvflparser.c: VFL constraint definition parser
+ *
+ * Copyright 2017 Endless
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include "gtkconstraintvflparserprivate.h"
+#include "gtkenums.h"
+
+#include <string.h>
+
+typedef enum {
+ VFL_HORIZONTAL,
+ VFL_VERTICAL
+} VflOrientation;
+
+typedef struct {
+ GtkConstraintRelation relation;
+
+ double constant;
+ double multiplier;
+ const char *subject;
+ char *object;
+ const char *attr;
+
+ double priority;
+} VflPredicate;
+
+typedef enum {
+ SPACING_SET = 1 << 0,
+ SPACING_DEFAULT = 1 << 1,
+ SPACING_PREDICATE = 1 << 2
+} VflSpacingFlags;
+
+typedef struct {
+ double size;
+ VflSpacingFlags flags;
+ VflPredicate predicate;
+} VflSpacing;
+
+typedef struct _VflView VflView;
+
+struct _VflView
+{
+ char *name;
+
+ /* Decides which attributes are admissible */
+ VflOrientation orientation;
+
+ /* A set of predicates, which will be used to
+ * set up constraints
+ */
+ GArray *predicates;
+
+ VflSpacing spacing;
+
+ VflView *prev_view;
+ VflView *next_view;
+};
+
+struct _GtkConstraintVflParser
+{
+ char *buffer;
+ gsize buffer_len;
+
+ int error_offset;
+ int error_range;
+
+ int default_spacing[2];
+
+ /* Set<name, double> */
+ GHashTable *metrics_set;
+ /* Set<name, widget> */
+ GHashTable *views_set;
+
+ const char *cursor;
+
+ /* Decides which attributes are admissible */
+ VflOrientation orientation;
+
+ VflView *leading_super;
+ VflView *trailing_super;
+
+ VflView *current_view;
+ VflView *views;
+};
+
+/* NOTE: These two symbols are defined in gtkconstraintlayout.h, but we
+ * cannot include that header here
+ */
+#define GTK_CONSTRAINT_VFL_PARSER_ERROR (gtk_constraint_vfl_parser_error_quark ())
+GQuark gtk_constraint_vfl_parser_error_quark (void);
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void)
+{
+ GtkConstraintVflParser *res = g_new0 (GtkConstraintVflParser, 1);
+
+ res->default_spacing[VFL_HORIZONTAL] = 8;
+ res->default_spacing[VFL_VERTICAL] = 8;
+
+ res->orientation = VFL_HORIZONTAL;
+
+ return res;
+}
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+ int hspacing,
+ int vspacing)
+{
+ parser->default_spacing[VFL_HORIZONTAL] = hspacing < 0 ? 8 : hspacing;
+ parser->default_spacing[VFL_VERTICAL] = vspacing < 0 ? 8 : vspacing;
+}
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+ GHashTable *metrics)
+{
+ parser->metrics_set = metrics;
+}
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+ GHashTable *views)
+{
+ parser->views_set = views;
+}
+
+static int
+get_default_spacing (GtkConstraintVflParser *parser)
+{
+ return parser->default_spacing[parser->orientation];
+}
+
+/* Default attributes, if unnamed, depending on the orientation */
+static const char *default_attribute[2] = {
+ [VFL_HORIZONTAL] = "width",
+ [VFL_VERTICAL] = "height",
+};
+
+static gboolean
+parse_relation (const char *str,
+ GtkConstraintRelation *relation,
+ char **endptr,
+ GError **error)
+{
+ const char *cur = str;
+
+ if (*cur == '=')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_EQ;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+ else if (*cur == '>')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_GE;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+ else if (*cur == '<')
+ {
+ cur += 1;
+
+ if (*cur == '=')
+ {
+ *relation = GTK_CONSTRAINT_RELATION_LE;
+ *endptr = (char *) cur + 1;
+ return TRUE;
+ }
+
+ goto out;
+ }
+
+out:
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION,
+ "Unknown relation; must be one of '==', '>=', or '<='");
+ return FALSE;
+}
+
+static gboolean
+has_metric (GtkConstraintVflParser *parser,
+ const char *name)
+{
+ if (parser->metrics_set == NULL)
+ return FALSE;
+
+ return g_hash_table_contains (parser->metrics_set, name);
+}
+
+static gboolean
+has_view (GtkConstraintVflParser *parser,
+ const char *name)
+{
+ if (parser->views_set == NULL)
+ return FALSE;
+
+ if (!g_hash_table_contains (parser->views_set, name))
+ return FALSE;
+
+ return g_hash_table_lookup (parser->views_set, name) != NULL;
+}
+
+/* Valid attributes */
+static const struct {
+ int len;
+ const char *name;
+} valid_attributes[] = {
+ { 5, "width" },
+ { 6, "height" },
+ { 7, "centerX" },
+ { 7, "centerY" },
+ { 3, "top" },
+ { 6, "bottom" },
+ { 4, "left" },
+ { 5, "right" },
+ { 5, "start" },
+ { 3, "end" },
+ { 8, "baseline" }
+};
+
+static char *
+get_offset_to (const char *str,
+ const char *tokens)
+{
+ char *offset = NULL;
+ int n_tokens = strlen (tokens);
+
+ for (int i = 0; i < n_tokens; i++)
+ {
+ if ((offset = strchr (str, tokens[i])) != NULL)
+ break;
+ }
+
+ return offset;
+}
+
+static gboolean
+parse_predicate (GtkConstraintVflParser *parser,
+ const char *cursor,
+ VflPredicate *predicate,
+ char **endptr,
+ GError **error)
+{
+ VflOrientation orientation = parser->orientation;
+ const char *end = cursor;
+
+ predicate->object = NULL;
+ predicate->multiplier = 1.0;
+
+ /* <predicate> = (<relation>)? (<objectOfPredicate>) ('.'<attribute>)? (<operator>)? ('@'<priority>)?
+ * <relation> = '==' | '<=' | '>='
+ * <objectOfPredicate> = <constant> | <viewName>
+ * <constant> = <number> | <metricName>
+ * <viewName> = [A-Za-z_]([A-Za-z0-9_]*)
+ * <metricName> = [A-Za-z_]([A-Za-z0-9_]*)
+ * <operator> = (['*'|'/']<positiveNumber>)? (['+'|'-']<positiveNumber>)?
+ * <priority> = <positiveNumber> | 'weak' | 'medium' | 'strong' | 'required'
+ */
+
+ /* Parse relation */
+ if (*end == '=' || *end == '>' || *end == '<')
+ {
+ GtkConstraintRelation relation;
+ char *tmp;
+
+ if (!parse_relation (end, &relation, &tmp, error))
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ return FALSE;
+ }
+
+ predicate->relation = relation;
+
+ end = tmp;
+ }
+ else
+ predicate->relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ /* Parse object of predicate */
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ /* <constant> */
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = g_ascii_strtod (end, &tmp);
+
+ end = tmp;
+ }
+ else if (g_ascii_isalpha (*end) || *end == '_')
+ {
+ const char *name_start = end;
+
+ while (g_ascii_isalnum (*end) || *end == '_')
+ end += 1;
+
+ char *name = g_strndup (name_start, end - name_start);
+
+ /* We only accept view names if the subject of the predicate
+ * is a view, i.e. we do not allow view names inside a spacing
+ * predicate
+ */
+ if (predicate->subject == NULL)
+ {
+ if (parser->metrics_set == NULL || !has_metric (parser, name))
+ {
+ parser->error_offset = name_start - parser->cursor;
+ parser->error_range = end - name_start;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+ "Unable to find metric with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+
+ double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = *val;
+
+ g_free (name);
+
+ goto parse_operators;
+ }
+
+ if (has_metric (parser, name))
+ {
+ double *val = g_hash_table_lookup (parser->metrics_set, name);
+
+ predicate->object = NULL;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = *val;
+
+ g_free (name);
+
+ goto parse_operators;
+ }
+
+ if (has_view (parser, name))
+ {
+ /* Transfer name's ownership to the predicate */
+ predicate->object = name;
+ predicate->attr = default_attribute[orientation];
+ predicate->constant = 0;
+
+ goto parse_attribute;
+ }
+
+ parser->error_offset = name_start - parser->cursor;
+ parser->error_range = end - name_start;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "Unable to find view with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+ else
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected constant, view name, or metric");
+ return FALSE;
+ }
+
+parse_attribute:
+ if (*end == '.')
+ {
+ end += 1;
+ predicate->attr = NULL;
+
+ for (int i = 0; i < G_N_ELEMENTS (valid_attributes); i++)
+ {
+ if (g_ascii_strncasecmp (valid_attributes[i].name, end, valid_attributes[i].len) == 0)
+ {
+ predicate->attr = valid_attributes[i].name;
+ end += valid_attributes[i].len;
+ }
+ }
+
+ if (predicate->attr == NULL)
+ {
+ char *range_end = get_offset_to (end, "*/+-@,)]");
+
+ if (range_end != NULL)
+ parser->error_range = range_end - end - 1;
+ else
+ parser->error_range = 0;
+
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+ "Attribute must be on one of 'width', 'height', "
+ "'centerX', 'centerY', 'top', 'bottom', "
+ "'left', 'right', 'start', 'end', 'baseline'");
+ return FALSE;
+ }
+ }
+
+parse_operators:
+ /* Parse multiplier operator */
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if ((*end == '*') || (*end == '/'))
+ {
+ double multiplier;
+ const char *operator;
+
+ operator = end;
+ end += 1;
+
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ multiplier = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else
+ {
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected a positive number as a multiplier");
+ return FALSE;
+ }
+
+ if (predicate->object != NULL)
+ {
+ if (*operator == '*')
+ predicate->multiplier = multiplier;
+ else
+ predicate->multiplier = 1.0 / multiplier;
+ }
+ else
+ {
+ /* If the subject is a constant then apply multiplier directly */
+ if (*operator == '*')
+ predicate->constant *= multiplier;
+ else
+ predicate->constant *= 1.0 / multiplier;
+ }
+ }
+
+ /* Parse constant operator */
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if ((*end == '+') || (*end == '-'))
+ {
+ double constant;
+ const char *operator;
+
+ operator = end;
+ end += 1;
+
+ while (g_ascii_isspace (*end))
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ constant = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else
+ {
+ g_free (predicate->object);
+
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected positive number as a constant");
+ return FALSE;
+ }
+
+ if (*operator == '+')
+ predicate->constant += constant;
+ else
+ predicate->constant += -1.0 * constant;
+ }
+
+ /* Parse priority */
+ if (*end == '@')
+ {
+ double priority;
+ end += 1;
+
+ if (g_ascii_isdigit (*end))
+ {
+ char *tmp;
+
+ priority = g_ascii_strtod (end, &tmp);
+ end = tmp;
+ }
+ else if (strncmp (end, "weak", 4) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_WEAK;
+ end += 4;
+ }
+ else if (strncmp (end, "medium", 6) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_MEDIUM;
+ end += 6;
+ }
+ else if (strncmp (end, "strong", 6) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_STRONG;
+ end += 6;
+ }
+ else if (strncmp (end, "required", 8) == 0)
+ {
+ priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ end += 8;
+ }
+ else
+ {
+ char *range_end = get_offset_to (end, ",)]");
+
+ g_free (predicate->object);
+
+ if (range_end != NULL)
+ parser->error_range = range_end - end - 1;
+ else
+ parser->error_range = 0;
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+ "Priority must be a positive number or one of "
+ "'weak', 'medium', 'strong', and 'required'");
+ return FALSE;
+ }
+
+ predicate->priority = priority;
+ }
+ else
+ predicate->priority = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ if (endptr != NULL)
+ *endptr = (char *) end;
+
+ return TRUE;
+}
+
+static gboolean
+parse_view (GtkConstraintVflParser *parser,
+ const char *cursor,
+ VflView *view,
+ char **endptr,
+ GError **error)
+{
+ const char *end = cursor;
+
+ /* <view> = '[' <viewName> (<predicateListWithParens>)? ']'
+ * <viewName> = [A-Za-z_]([A-Za-z0-9_]+)
+ */
+
+ g_assert (*end == '[');
+
+ /* Skip '[' */
+ end += 1;
+
+ if (!(g_ascii_isalpha (*end) || *end == '_'))
+ {
+ parser->error_offset = end - parser->cursor;
+ parser->error_range = 0;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "View identifiers must be valid C identifiers");
+ return FALSE;
+ }
+
+ while (g_ascii_isalnum (*end))
+ end += 1;
+
+ if (*end == '\0')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A view must end with ']'");
+ return FALSE;
+ }
+
+ char *name = g_strndup (cursor + 1, end - cursor - 1);
+ if (!has_view (parser, name))
+ {
+ parser->error_offset = (cursor + 1) - parser->cursor;
+ parser->error_range = end - cursor - 1;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ "Unable to find view with name '%s'", name);
+ g_free (name);
+ return FALSE;
+ }
+
+ view->name = name;
+ view->predicates = g_array_new (FALSE, FALSE, sizeof (VflPredicate));
+
+ if (*end == ']')
+ {
+ if (endptr != NULL)
+ *endptr = (char *) end + 1;
+
+ return TRUE;
+ }
+
+ /* <predicateListWithParens> = '(' <predicate> (',' <predicate>)* ')' */
+ if (*end != '(')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A predicate must follow a view name");
+ return FALSE;
+ }
+
+ end += 1;
+
+ while (*end != '\0')
+ {
+ VflPredicate cur_predicate;
+ char *tmp;
+
+ if (*end == ']' || *end == '\0')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "A predicate on a view must end with ')'");
+ return FALSE;
+ }
+
+ memset (&cur_predicate, 0, sizeof (VflPredicate));
+
+ cur_predicate.subject = view->name;
+ if (!parse_predicate (parser, end, &cur_predicate, &tmp, error))
+ return FALSE;
+
+ end = tmp;
+
+#ifdef G_ENABLE_DEBUG
+ g_debug ("*** Found predicate: %s.%s %s %g %s (%g)\n",
+ cur_predicate.object != NULL ? cur_predicate.object : view->name,
+ cur_predicate.attr,
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_EQ ? "==" :
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_LE ? "<=" :
+ cur_predicate.relation == GTK_CONSTRAINT_RELATION_GE ? ">=" :
+ "unknown relation",
+ cur_predicate.constant,
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_WEAK ? "weak" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_MEDIUM ? "medium" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_STRONG ? "strong" :
+ cur_predicate.priority == GTK_CONSTRAINT_STRENGTH_REQUIRED ? "required" :
+ "explicit strength",
+ cur_predicate.priority);
+#endif
+
+ g_array_append_val (view->predicates, cur_predicate);
+
+ /* If the predicate is a list, iterate again */
+ if (*end == ',')
+ {
+ end += 1;
+ continue;
+ }
+
+ /* We reached the end of the predicate */
+ if (*end == ')')
+ {
+ end += 1;
+ break;
+ }
+
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ')' at the end of a predicate, not '%c'", *end);
+ return FALSE;
+ }
+
+ if (*end != ']')
+ {
+ parser->error_offset = end - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ']' at the end of a view, not '%c'", *end);
+ return FALSE;
+ }
+
+ if (endptr != NULL)
+ *endptr = (char *) end + 1;
+
+ return TRUE;
+}
+
+static void
+vfl_view_free (VflView *view)
+{
+ if (view == NULL)
+ return;
+
+ g_free (view->name);
+
+ if (view->predicates != NULL)
+ {
+ for (int i = 0; i < view->predicates->len; i++)
+ {
+ VflPredicate *p = &g_array_index (view->predicates, VflPredicate, i);
+
+ g_free (p->object);
+ }
+
+ g_array_free (view->predicates, TRUE);
+ view->predicates = NULL;
+ }
+
+ view->prev_view = NULL;
+ view->next_view = NULL;
+
+ g_slice_free (VflView, view);
+}
+
+static void
+gtk_constraint_vfl_parser_clear (GtkConstraintVflParser *parser)
+{
+ parser->error_offset = 0;
+ parser->error_range = 0;
+
+ VflView *iter = parser->views;
+ while (iter != NULL)
+ {
+ VflView *next = iter->next_view;
+
+ vfl_view_free (iter);
+
+ iter = next;
+ }
+
+ parser->views = NULL;
+ parser->current_view = NULL;
+ parser->leading_super = NULL;
+ parser->trailing_super = NULL;
+
+ parser->cursor = NULL;
+
+ g_free (parser->buffer);
+ parser->buffer_len = 0;
+}
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser)
+{
+ if (parser == NULL)
+ return;
+
+ gtk_constraint_vfl_parser_clear (parser);
+
+ g_free (parser);
+}
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+ const char *buffer,
+ gssize len,
+ GError **error)
+{
+ gtk_constraint_vfl_parser_clear (parser);
+
+ if (len > 0)
+ {
+ parser->buffer = g_strndup (buffer, len);
+ parser->buffer_len = len;
+ }
+ else
+ {
+ parser->buffer = g_strdup (buffer);
+ parser->buffer_len = strlen (buffer);
+ }
+
+ parser->cursor = parser->buffer;
+
+ const char *cur = parser->cursor;
+
+ /* Skip leading whitespace */
+ while (g_ascii_isspace (*cur))
+ cur += 1;
+
+ /* Check orientation; if none is specified, then we assume horizontal */
+ parser->orientation = VFL_HORIZONTAL;
+ if (*cur == 'H')
+ {
+ cur += 1;
+
+ if (*cur != ':')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ':' after horizontal orientation");
+ return FALSE;
+ }
+
+ parser->orientation = VFL_HORIZONTAL;
+ cur += 1;
+ }
+ else if (*cur == 'V')
+ {
+ cur += 1;
+
+ if (*cur != ':')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ':' after vertical orientation");
+ return FALSE;
+ }
+
+ parser->orientation = VFL_VERTICAL;
+ cur += 1;
+ }
+
+ while (*cur != '\0')
+ {
+ /* Super-view */
+ if (*cur == '|')
+ {
+ if (parser->views == NULL && parser->leading_super == NULL)
+ {
+ parser->leading_super = g_slice_new0 (VflView);
+
+ parser->leading_super->name = g_strdup ("super");
+ parser->leading_super->orientation = parser->orientation;
+
+ parser->current_view = parser->leading_super;
+ parser->views = parser->leading_super;
+ }
+ else if (parser->trailing_super == NULL)
+ {
+ parser->trailing_super = g_slice_new0 (VflView);
+
+ parser->trailing_super->name = g_strdup ("super");
+ parser->trailing_super->orientation = parser->orientation;
+
+ parser->current_view->next_view = parser->trailing_super;
+ parser->trailing_super->prev_view = parser->current_view;
+
+ parser->current_view = parser->trailing_super;
+
+ /* If we reached the second '|' then we completed a line
+ * of layout, and we can stop
+ */
+ break;
+ }
+ else
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Super views can only appear at the beginning "
+ "and end of the layout, and not multiple times");
+ return FALSE;
+ }
+
+ cur += 1;
+
+ continue;
+ }
+
+ /* Spacing */
+ if (*cur == '-')
+ {
+ if (*(cur + 1) == '\0')
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Unterminated spacing");
+ return FALSE;
+ }
+
+ if (parser->current_view == NULL)
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing cannot be set without a view");
+ return FALSE;
+ }
+
+ if (*(cur + 1) == '|' || *(cur + 1) == '[')
+ {
+ VflSpacing *spacing = &(parser->current_view->spacing);
+
+ /* Default spacer */
+ spacing->flags = SPACING_SET | SPACING_DEFAULT;
+ spacing->size = 0;
+
+ cur += 1;
+
+ continue;
+ }
+ else if (*(cur + 1) == '(')
+ {
+ VflPredicate *predicate;
+ VflSpacing *spacing;
+ char *tmp;
+
+ /* Predicate */
+ cur += 1;
+
+ spacing = &(parser->current_view->spacing);
+ spacing->flags = SPACING_SET | SPACING_PREDICATE;
+ spacing->size = 0;
+
+ /* Spacing predicates have no subject */
+ predicate = &(spacing->predicate);
+ predicate->subject = NULL;
+
+ cur += 1;
+ if (!parse_predicate (parser, cur, predicate, &tmp, error))
+ return FALSE;
+
+ if (*tmp != ')')
+ {
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Expected ')' at the end of a predicate, not '%c'", *tmp);
+ return FALSE;
+ }
+
+ cur = tmp + 1;
+ if (*cur != '-')
+ {
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Explicit spacing must be followed by '-'");
+ return FALSE;
+ }
+
+ cur += 1;
+
+ continue;
+ }
+ else if (g_ascii_isdigit (*(cur + 1)))
+ {
+ VflSpacing *spacing;
+ char *tmp;
+
+ /* Explicit spacing */
+ cur += 1;
+
+ spacing = &(parser->current_view->spacing);
+ spacing->flags = SPACING_SET;
+ spacing->size = g_ascii_strtod (cur, &tmp);
+
+ if (tmp == cur)
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing must be a number");
+ return FALSE;
+ }
+
+ if (*tmp != '-')
+ {
+ parser->error_offset = cur - parser->cursor;
+ parser->error_range = tmp - cur;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Explicit spacing must be followed by '-'");
+ return FALSE;
+ }
+
+ cur = tmp + 1;
+
+ continue;
+ }
+ else
+ {
+ parser->error_offset = cur - parser->cursor;
+ g_set_error (error, GTK_CONSTRAINT_VFL_PARSER_ERROR,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ "Spacing can either be '-' or a number");
+ return FALSE;
+ }
+ }
+
+ if (*cur == '[')
+ {
+ VflView *view = g_slice_new0 (VflView);
+ char *tmp;
+
+ view->orientation = parser->orientation;
+
+ if (!parse_view (parser, cur, view, &tmp, error))
+ {
+ vfl_view_free (view);
+ return FALSE;
+ }
+
+ cur = tmp;
+
+ if (parser->views == NULL)
+ parser->views = view;
+
+ view->prev_view = parser->current_view;
+
+ if (parser->current_view != NULL)
+ parser->current_view->next_view = view;
+
+ parser->current_view = view;
+
+ continue;
+ }
+
+ cur += 1;
+ }
+
+ return TRUE;
+}
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+ int *n_constraints)
+{
+ GArray *constraints;
+ VflView *iter;
+
+ constraints = g_array_new (FALSE, FALSE, sizeof (GtkConstraintVfl));
+
+ iter = parser->views;
+ while (iter != NULL)
+ {
+ GtkConstraintVfl c;
+
+ if (iter->predicates != NULL)
+ {
+ for (int i = 0; i < iter->predicates->len; i++)
+ {
+ const VflPredicate *p = &g_array_index (iter->predicates, VflPredicate, i);
+
+ c.view1 = iter->name;
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "width" : "height";
+ if (p->object != NULL)
+ {
+ c.view2 = p->object;
+ c.attr2 = p->attr;
+ }
+ else
+ {
+ c.view2 = NULL;
+ c.attr2 = NULL;
+ }
+ c.relation = p->relation;
+ c.constant = p->constant;
+ c.multiplier = p->multiplier;
+ c.strength = p->priority;
+
+ g_array_append_val (constraints, c);
+ }
+ }
+
+ if ((iter->spacing.flags & SPACING_SET) != 0)
+ {
+ c.view1 = iter->name;
+
+ /* If this is the first view, we need to anchor the leading edge */
+ if (iter == parser->leading_super)
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+ else
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+ c.view2 = iter->next_view != NULL ? iter->next_view->name : "super";
+
+ if (iter == parser->trailing_super || iter->next_view == parser->trailing_super)
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+ else
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+ if ((iter->spacing.flags & SPACING_PREDICATE) != 0)
+ {
+ const VflPredicate *p = &(iter->spacing.predicate);
+
+ c.constant = p->constant * -1.0;
+ c.relation = p->relation;
+ c.strength = p->priority;
+ }
+ else if ((iter->spacing.flags & SPACING_DEFAULT) != 0)
+ {
+ c.constant = get_default_spacing (parser) * -1.0;
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ }
+ else
+ {
+ c.constant = iter->spacing.size * -1.0;
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+ }
+
+ c.multiplier = 1.0;
+
+ g_array_append_val (constraints, c);
+ }
+ else if (iter->next_view != NULL)
+ {
+ c.view1 = iter->name;
+
+ /* If this is the first view, we need to anchor the leading edge */
+ if (iter == parser->leading_super)
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+ else
+ c.attr1 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+
+ c.relation = GTK_CONSTRAINT_RELATION_EQ;
+
+ c.view2 = iter->next_view->name;
+
+ if (iter->next_view == parser->trailing_super)
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "end" : "bottom";
+ else
+ c.attr2 = iter->orientation == VFL_HORIZONTAL ? "start" : "top";
+
+ c.constant = 0.0;
+ c.multiplier = 1.0;
+ c.strength = GTK_CONSTRAINT_STRENGTH_REQUIRED;
+
+ g_array_append_val (constraints, c);
+ }
+
+ iter = iter->next_view;
+ }
+
+ if (n_constraints != NULL)
+ *n_constraints = constraints->len;
+
+#ifdef G_ENABLE_DEBUG
+ for (int i = 0; i < constraints->len; i++)
+ {
+ const GtkConstraintVfl *c = &g_array_index (constraints, GtkConstraintVfl, i);
+
+ g_debug ("{\n"
+ " .view1: '%s',\n"
+ " .attr1: '%s',\n"
+ " .relation: '%d',\n"
+ " .view2: '%s',\n"
+ " .attr2: '%s',\n"
+ " .constant: %g,\n"
+ " .multiplier: %g,\n"
+ " .strength: %g\n"
+ "}\n",
+ c->view1, c->attr1,
+ c->relation,
+ c->view2 != NULL ? c->view2 : "none", c->attr2 != NULL ? c->attr2 : "none",
+ c->constant,
+ c->multiplier,
+ c->strength);
+ }
+#endif
+
+ return (GtkConstraintVfl *) g_array_free (constraints, FALSE);
+}
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser)
+{
+ return parser->error_offset;
+}
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser)
+{
+ return parser->error_range;
+}
diff --git a/gtk/gtkconstraintvflparserprivate.h b/gtk/gtkconstraintvflparserprivate.h
new file mode 100644
index 0000000000..dd214f5049
--- /dev/null
+++ b/gtk/gtkconstraintvflparserprivate.h
@@ -0,0 +1,76 @@
+/* gtkconstraintvflparserprivate.h: VFL constraint definition parser
+ *
+ * Copyright 2017 Endless
+ * 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/>.
+ */
+
+#pragma once
+
+#include "gtkconstrainttypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GtkConstraintVflParser GtkConstraintVflParser;
+
+typedef struct {
+ const char *view1;
+ const char *attr1;
+ GtkConstraintRelation relation;
+ const char *view2;
+ const char *attr2;
+ double constant;
+ double multiplier;
+ double strength;
+} GtkConstraintVfl;
+
+GtkConstraintVflParser *
+gtk_constraint_vfl_parser_new (void);
+
+void
+gtk_constraint_vfl_parser_free (GtkConstraintVflParser *parser);
+
+void
+gtk_constraint_vfl_parser_set_default_spacing (GtkConstraintVflParser *parser,
+ int hspacing,
+ int vspacing);
+
+void
+gtk_constraint_vfl_parser_set_metrics (GtkConstraintVflParser *parser,
+ GHashTable *metrics);
+
+void
+gtk_constraint_vfl_parser_set_views (GtkConstraintVflParser *parser,
+ GHashTable *views);
+
+gboolean
+gtk_constraint_vfl_parser_parse_line (GtkConstraintVflParser *parser,
+ const char *line,
+ gssize len,
+ GError **error);
+
+int
+gtk_constraint_vfl_parser_get_error_offset (GtkConstraintVflParser *parser);
+
+int
+gtk_constraint_vfl_parser_get_error_range (GtkConstraintVflParser *parser);
+
+GtkConstraintVfl *
+gtk_constraint_vfl_parser_get_constraints (GtkConstraintVflParser *parser,
+ int *n_constraints);
+
+G_END_DECLS
diff --git a/gtk/gtkdebug.h b/gtk/gtkdebug.h
index 87affc189a..936a990639 100644
--- a/gtk/gtkdebug.h
+++ b/gtk/gtkdebug.h
@@ -51,7 +51,8 @@ typedef enum {
GTK_DEBUG_ACTIONS = 1 << 13,
GTK_DEBUG_RESIZE = 1 << 14,
GTK_DEBUG_LAYOUT = 1 << 15,
- GTK_DEBUG_SNAPSHOT = 1 << 16
+ GTK_DEBUG_SNAPSHOT = 1 << 16,
+ GTK_DEBUG_CONSTRAINTS = 1 << 17,
} GtkDebugFlag;
#ifdef G_ENABLE_DEBUG
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index 6d1bf81295..9bd6f1632e 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -1053,4 +1053,98 @@ typedef enum {
GTK_PICK_NON_TARGETABLE = 1 << 1
} GtkPickFlags;
+/**
+ * GtkConstraintRelation:
+ * @GTK_CONSTRAINT_RELATION_EQ: Equal
+ * @GTK_CONSTRAINT_RELATION_LE: Less than, or equal
+ * @GTK_CONSTRAINT_RELATION_GE: Greater than, or equal
+ *
+ * The relation between two terms of a constraint.
+ */
+typedef enum {
+ GTK_CONSTRAINT_RELATION_LE = -1,
+ GTK_CONSTRAINT_RELATION_EQ = 0,
+ 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 = 1001001000,
+ GTK_CONSTRAINT_STRENGTH_STRONG = 1000000000,
+ GTK_CONSTRAINT_STRENGTH_MEDIUM = 1000,
+ GTK_CONSTRAINT_STRENGTH_WEAK = 1
+} 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;
+
+/**
+ * GtkConstraintVflParserError:
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL: Invalid or unknown symbol
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE: Invalid or unknown attribute
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW: Invalid or unknown view
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC: Invalid or unknown metric
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY: Invalid or unknown priority
+ * @GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION: Invalid or unknown relation
+ *
+ * Domain for VFL parsing errors.
+ */
+typedef enum {
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_SYMBOL,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_ATTRIBUTE,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_VIEW,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_METRIC,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_PRIORITY,
+ GTK_CONSTRAINT_VFL_PARSER_ERROR_INVALID_RELATION
+} GtkConstraintVflParserError;
+
#endif /* __GTK_ENUMS_H__ */
diff --git a/gtk/gtklayoutmanager.c b/gtk/gtklayoutmanager.c
index c41a604b99..547c76bbdf 100644
--- a/gtk/gtklayoutmanager.c
+++ b/gtk/gtklayoutmanager.c
@@ -91,6 +91,7 @@
typedef struct {
GtkWidget *widget;
+ GtkRoot *root;
/* HashTable<Widget, LayoutChild> */
GHashTable *layout_children;
@@ -98,6 +99,16 @@ typedef struct {
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GtkLayoutManager, gtk_layout_manager, G_TYPE_OBJECT)
+static void
+gtk_layout_manager_real_root (GtkLayoutManager *manager)
+{
+}
+
+static void
+gtk_layout_manager_real_unroot (GtkLayoutManager *manager)
+{
+}
+
static GtkSizeRequestMode
gtk_layout_manager_real_get_request_mode (GtkLayoutManager *manager,
GtkWidget *widget)
@@ -189,12 +200,29 @@ gtk_layout_manager_real_create_layout_child (GtkLayoutManager *manager,
}
static void
+gtk_layout_manager_finalize (GObject *gobject)
+{
+ GtkLayoutManager *self = GTK_LAYOUT_MANAGER (gobject);
+ GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (self);
+
+ g_clear_pointer (&priv->layout_children, g_hash_table_unref);
+
+ G_OBJECT_CLASS (gtk_layout_manager_parent_class)->finalize (gobject);
+}
+
+static void
gtk_layout_manager_class_init (GtkLayoutManagerClass *klass)
{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = gtk_layout_manager_finalize;
+
klass->get_request_mode = gtk_layout_manager_real_get_request_mode;
klass->measure = gtk_layout_manager_real_measure;
klass->allocate = gtk_layout_manager_real_allocate;
klass->create_layout_child = gtk_layout_manager_real_create_layout_child;
+ klass->root = gtk_layout_manager_real_root;
+ klass->unroot = gtk_layout_manager_real_unroot;
}
static void
@@ -226,6 +254,38 @@ gtk_layout_manager_set_widget (GtkLayoutManager *layout_manager,
}
priv->widget = widget;
+
+ if (widget != NULL)
+ gtk_layout_manager_set_root (layout_manager, gtk_widget_get_root (widget));
+}
+
+/*< private >
+ * gtk_layout_manager_set_root:
+ * @layout_manager: a #GtkLayoutManager
+ * @root: (nullable): a #GtkWidget implementing #GtkRoot
+ *
+ * Sets a back pointer from @root to @layout_manager.
+ *
+ * This function is called by #GtkWidget when getting rooted and unrooted,
+ * and will call #GtkLayoutManagerClass.root() or #GtkLayoutManagerClass.unroot()
+ * depending on whether @root is a #GtkWidget or %NULL.
+ */
+void
+gtk_layout_manager_set_root (GtkLayoutManager *layout_manager,
+ GtkRoot *root)
+{
+ GtkLayoutManagerPrivate *priv = gtk_layout_manager_get_instance_private (layout_manager);
+ GtkRoot *old_root = priv->root;
+
+ priv->root = root;
+
+ if (old_root != root)
+ {
+ if (priv->root != NULL)
+ GTK_LAYOUT_MANAGER_GET_CLASS (layout_manager)->root (layout_manager);
+ else
+ GTK_LAYOUT_MANAGER_GET_CLASS (layout_manager)->unroot (layout_manager);
+ }
}
/**
diff --git a/gtk/gtklayoutmanager.h b/gtk/gtklayoutmanager.h
index 06659ee70e..3066dc5660 100644
--- a/gtk/gtklayoutmanager.h
+++ b/gtk/gtklayoutmanager.h
@@ -42,6 +42,10 @@ G_DECLARE_DERIVABLE_TYPE (GtkLayoutManager, gtk_layout_manager, GTK, LAYOUT_MANA
* @layout_child_type: the type of #GtkLayoutChild used by this layout manager
* @create_layout_child: a virtual function, used to create a #GtkLayoutChild
* meta object for the layout properties
+ * @root: a virtual function, called when the widget using the layout
+ * manager is attached to a #GtkRoot
+ * @unroot: a virtual function, called when the widget using the layout
+ * manager is detached from a #GtkRoot
*
* The `GtkLayoutManagerClass` structure contains only private data, and
* should only be accessed through the provided API, or when subclassing
@@ -77,6 +81,9 @@ struct _GtkLayoutManagerClass
GtkWidget *widget,
GtkWidget *for_child);
+ void (* root) (GtkLayoutManager *manager);
+ void (* unroot) (GtkLayoutManager *manager);
+
/*< private >*/
gpointer _padding[16];
};
diff --git a/gtk/gtklayoutmanagerprivate.h b/gtk/gtklayoutmanagerprivate.h
index 14eb30e161..5cdbf2e440 100644
--- a/gtk/gtklayoutmanagerprivate.h
+++ b/gtk/gtklayoutmanagerprivate.h
@@ -10,4 +10,7 @@ void gtk_layout_manager_set_widget (GtkLayoutManager *manager,
void gtk_layout_manager_remove_layout_child (GtkLayoutManager *manager,
GtkWidget *widget);
+void gtk_layout_manager_set_root (GtkLayoutManager *manager,
+ GtkRoot *root);
+
G_END_DECLS
diff --git a/gtk/gtkmain.c b/gtk/gtkmain.c
index 35ea753e1f..e3e592595d 100644
--- a/gtk/gtkmain.c
+++ b/gtk/gtkmain.c
@@ -179,7 +179,8 @@ static const GDebugKey gtk_debug_keys[] = {
{ "actions", GTK_DEBUG_ACTIONS },
{ "resize", GTK_DEBUG_RESIZE },
{ "layout", GTK_DEBUG_LAYOUT },
- { "snapshot", GTK_DEBUG_SNAPSHOT }
+ { "snapshot", GTK_DEBUG_SNAPSHOT },
+ { "constraints", GTK_DEBUG_CONSTRAINTS },
};
#endif /* G_ENABLE_DEBUG */
diff --git a/gtk/gtkroot.c b/gtk/gtkroot.c
index 919aa65f94..dce284a57f 100644
--- a/gtk/gtkroot.c
+++ b/gtk/gtkroot.c
@@ -50,10 +50,18 @@ gtk_root_default_get_display (GtkRoot *self)
return gdk_display_get_default ();
}
+
+static GtkConstraintSolver *
+gtk_root_default_get_constraint_solver (GtkRoot *self)
+{
+ return NULL;
+}
+
static void
gtk_root_default_init (GtkRootInterface *iface)
{
iface->get_display = gtk_root_default_get_display;
+ iface->get_constraint_solver = gtk_root_default_get_constraint_solver;
g_object_interface_install_property (iface,
g_param_spec_object ("focus-widget",
@@ -82,6 +90,17 @@ gtk_root_get_display (GtkRoot *self)
return iface->get_display (self);
}
+GtkConstraintSolver *
+gtk_root_get_constraint_solver (GtkRoot *self)
+{
+ GtkRootInterface *iface;
+
+ g_return_val_if_fail (GTK_IS_ROOT (self), NULL);
+
+ iface = GTK_ROOT_GET_IFACE (self);
+ return iface->get_constraint_solver (self);
+}
+
/**
* gtk_root_set_focus:
* @self: a #GtkRoot
diff --git a/gtk/gtkroot.h b/gtk/gtkroot.h
index c147c09ff0..08b3888d3c 100644
--- a/gtk/gtkroot.h
+++ b/gtk/gtkroot.h
@@ -34,20 +34,6 @@ G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
G_DECLARE_INTERFACE (GtkRoot, gtk_root, GTK, ROOT, GtkWidget)
-/**
- * GtkRootIface:
- *
- * The list of functions that must be implemented for the #GtkRoot interface.
- */
-struct _GtkRootInterface
-{
- /*< private >*/
- GTypeInterface g_iface;
-
- /*< public >*/
- GdkDisplay * (* get_display) (GtkRoot *self);
-};
-
GDK_AVAILABLE_IN_ALL
GdkDisplay * gtk_root_get_display (GtkRoot *self);
diff --git a/gtk/gtkrootprivate.h b/gtk/gtkrootprivate.h
index 3afbae712c..aae7a674a5 100644
--- a/gtk/gtkrootprivate.h
+++ b/gtk/gtkrootprivate.h
@@ -3,8 +3,28 @@
#include "gtkroot.h"
+#include "gtkconstraintsolverprivate.h"
+
G_BEGIN_DECLS
+/**
+ * GtkRootIface:
+ *
+ * The list of functions that must be implemented for the #GtkRoot interface.
+ */
+struct _GtkRootInterface
+{
+ /*< private >*/
+ GTypeInterface g_iface;
+
+ /*< public >*/
+ GdkDisplay * (* get_display) (GtkRoot *self);
+
+ GtkConstraintSolver * (* get_constraint_solver) (GtkRoot *self);
+};
+
+GtkConstraintSolver * gtk_root_get_constraint_solver (GtkRoot *self);
+
enum {
GTK_ROOT_PROP_FOCUS_WIDGET,
GTK_ROOT_NUM_PROPERTIES
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 0c653a90f8..dd6e67dda7 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -73,6 +73,7 @@
#include "gtkwindowgroup.h"
#include "gtkwindowprivate.h"
#include "gtknativeprivate.h"
+#include "gtkconstraint.h"
#include "a11y/gtkwidgetaccessible.h"
#include "inspector/window.h"
@@ -770,6 +771,13 @@ gtk_widget_get_type (void)
NULL /* interface data */
};
+ const GInterfaceInfo constraint_target_info =
+ {
+ (GInterfaceInitFunc) NULL,
+ (GInterfaceFinalizeFunc) NULL,
+ NULL /* interface data */
+ };
+
widget_type = g_type_register_static (G_TYPE_INITIALLY_UNOWNED, g_intern_static_string ("GtkWidget"),
&widget_info, G_TYPE_FLAG_ABSTRACT);
@@ -782,6 +790,8 @@ gtk_widget_get_type (void)
&accessibility_info) ;
g_type_add_interface_static (widget_type, GTK_TYPE_BUILDABLE,
&buildable_info) ;
+ g_type_add_interface_static (widget_type, GTK_TYPE_CONSTRAINT_TARGET,
+ &constraint_target_info) ;
}
return widget_type;
@@ -2893,6 +2903,9 @@ gtk_widget_root (GtkWidget *widget)
_gtk_widget_update_parent_muxer (widget);
+ if (priv->layout_manager)
+ gtk_layout_manager_set_root (priv->layout_manager, priv->root);
+
GTK_WIDGET_GET_CLASS (widget)->root (widget);
if (!GTK_IS_ROOT (widget))
@@ -2920,6 +2933,9 @@ gtk_widget_unroot (GtkWidget *widget)
if (priv->context)
gtk_style_context_set_display (priv->context, gdk_display_get_default ());
+ if (priv->layout_manager)
+ gtk_layout_manager_set_root (priv->layout_manager, NULL);
+
if (g_object_get_qdata (G_OBJECT (widget), quark_pango_context))
g_object_set_qdata (G_OBJECT (widget), quark_pango_context, NULL);
diff --git a/gtk/gtkwindow.c b/gtk/gtkwindow.c
index 3730841f36..dab50be990 100644
--- a/gtk/gtkwindow.c
+++ b/gtk/gtkwindow.c
@@ -281,6 +281,8 @@ typedef struct
GskRenderer *renderer;
GList *foci;
+
+ GtkConstraintSolver *constraint_solver;
} GtkWindowPrivate;
#ifdef GDK_WINDOWING_X11
@@ -1883,6 +1885,9 @@ gtk_window_init (GtkWindow *window)
g_signal_connect_swapped (priv->key_controller, "key-released",
G_CALLBACK (gtk_window_key_released), window);
gtk_widget_add_controller (widget, priv->key_controller);
+
+ /* Shared constraint solver */
+ priv->constraint_solver = gtk_constraint_solver_new ();
}
static GtkGesture *
@@ -2353,6 +2358,15 @@ gtk_window_native_get_renderer (GtkNative *native)
return priv->renderer;
}
+static GtkConstraintSolver *
+gtk_window_root_get_constraint_solver (GtkRoot *root)
+{
+ GtkWindow *self = GTK_WINDOW (root);
+ GtkWindowPrivate *priv = gtk_window_get_instance_private (self);
+
+ return priv->constraint_solver;
+}
+
static void
gtk_window_native_get_surface_transform (GtkNative *native,
int *x,
@@ -2381,6 +2395,7 @@ static void
gtk_window_root_interface_init (GtkRootInterface *iface)
{
iface->get_display = gtk_window_root_get_display;
+ iface->get_constraint_solver = gtk_window_root_get_constraint_solver;
}
static void
@@ -4723,6 +4738,7 @@ gtk_window_finalize (GObject *object)
priv->mnemonics_display_timeout_id = 0;
}
+ g_clear_object (&priv->constraint_solver);
g_clear_object (&priv->renderer);
G_OBJECT_CLASS (gtk_window_parent_class)->finalize (object);
diff --git a/gtk/meson.build b/gtk/meson.build
index 0b271b6f50..428e1e49ab 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -37,6 +37,9 @@ gtk_private_sources = files([
'gtkcolorpickershell.c',
'gtkcolorscale.c',
'gtkcolorswatch.c',
+ 'gtkconstraintexpression.c',
+ 'gtkconstraintsolver.c',
+ 'gtkconstraintvflparser.c',
'gtkcssanimatedstyle.c',
'gtkcssanimation.c',
'gtkcssarrayvalue.c',
@@ -200,6 +203,9 @@ gtk_public_sources = files([
'gtkcombobox.c',
'gtkcomboboxtext.c',
'gtkcomposetable.c',
+ 'gtkconstraintguide.c',
+ 'gtkconstraintlayout.c',
+ 'gtkconstraint.c',
'gtkcontainer.c',
'gtkcssprovider.c',
'gtkdialog.c',
@@ -459,6 +465,9 @@ gtk_public_headers = files([
'gtkcolorutils.h',
'gtkcombobox.h',
'gtkcomboboxtext.h',
+ 'gtkconstraintguide.h',
+ 'gtkconstraintlayout.h',
+ 'gtkconstraint.h',
'gtkcontainer.h',
'gtkcssprovider.h',
'gtkcustomlayout.h',
diff --git a/testsuite/gtk/constraint-solver.c b/testsuite/gtk/constraint-solver.c
new file mode 100644
index 0000000000..a4597c70ba
--- /dev/null
+++ b/testsuite/gtk/constraint-solver.c
@@ -0,0 +1,380 @@
+#include <locale.h>
+
+#include "../../gtk/gtkconstrainttypesprivate.h"
+#include "../../gtk/gtkconstraintsolverprivate.h"
+#include "../../gtk/gtkconstraintexpressionprivate.h"
+
+static void
+constraint_solver_simple (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 167.0);
+ GtkConstraintVariable *y = gtk_constraint_solver_create_variable (solver, NULL, "y", 2.0);
+
+ GtkConstraintExpression *e = gtk_constraint_expression_new_from_variable (y);
+
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+ double y_value = gtk_constraint_variable_get_value (y);
+
+ g_assert_cmpfloat_with_epsilon (x_value, y_value, 0.001);
+ g_assert_cmpfloat_with_epsilon (x_value, 0.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (y_value, 0.0, 0.001);
+
+ gtk_constraint_variable_unref (y);
+ gtk_constraint_variable_unref (x);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_stay (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 5.0);
+ GtkConstraintVariable *y = gtk_constraint_solver_create_variable (solver, NULL, "y", 10.0);
+
+ gtk_constraint_solver_add_stay_variable (solver, x, GTK_CONSTRAINT_STRENGTH_WEAK);
+ gtk_constraint_solver_add_stay_variable (solver, y, GTK_CONSTRAINT_STRENGTH_WEAK);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+ double y_value = gtk_constraint_variable_get_value (y);
+
+ g_assert_cmpfloat_with_epsilon (x_value, 5.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (y_value, 10.0, 0.001);
+
+ gtk_constraint_variable_unref (x);
+ gtk_constraint_variable_unref (y);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_variable_geq_constant (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 10.0);
+ GtkConstraintExpression *e = gtk_constraint_expression_new (100.0);
+
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_GE, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+
+ g_assert_cmpfloat (x_value, >=, 100.0);
+
+ gtk_constraint_variable_unref (x);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_variable_leq_constant (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 100.0);
+ GtkConstraintExpression *e = gtk_constraint_expression_new (10.0);
+
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_LE, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+
+ g_assert_cmpfloat (x_value, <=, 10.0);
+
+ gtk_constraint_variable_unref (x);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_variable_eq_constant (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 10.0);
+ GtkConstraintExpression *e = gtk_constraint_expression_new (100.0);
+
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+
+ g_assert_cmpfloat_with_epsilon (x_value, 100.0, 0.001);
+
+ gtk_constraint_variable_unref (x);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_eq_with_stay (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 10.0);
+ GtkConstraintVariable *width = gtk_constraint_solver_create_variable (solver, NULL, "width", 10.0);
+ GtkConstraintVariable *right_min = gtk_constraint_solver_create_variable (solver, NULL, "rightMin", 100.0);
+
+ GtkConstraintExpressionBuilder builder;
+ gtk_constraint_expression_builder_init (&builder, solver);
+ gtk_constraint_expression_builder_term (&builder, x);
+ gtk_constraint_expression_builder_plus (&builder);
+ gtk_constraint_expression_builder_term (&builder, width);
+ GtkConstraintExpression *right = gtk_constraint_expression_builder_finish (&builder);
+
+ gtk_constraint_solver_add_stay_variable (solver, width, GTK_CONSTRAINT_STRENGTH_WEAK);
+ gtk_constraint_solver_add_stay_variable (solver, right_min, GTK_CONSTRAINT_STRENGTH_WEAK);
+ gtk_constraint_solver_add_constraint (solver,
+ right_min, GTK_CONSTRAINT_RELATION_EQ, right,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ double x_value = gtk_constraint_variable_get_value (x);
+ double width_value = gtk_constraint_variable_get_value (width);
+
+ g_assert_cmpfloat_with_epsilon (x_value, 90.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (width_value, 10.0, 0.001);
+
+ gtk_constraint_variable_unref (right_min);
+ gtk_constraint_variable_unref (width);
+ gtk_constraint_variable_unref (x);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_cassowary (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *x = gtk_constraint_solver_create_variable (solver, NULL, "x", 0.0);
+ GtkConstraintVariable *y = gtk_constraint_solver_create_variable (solver, NULL, "y", 0.0);
+
+ GtkConstraintExpression *e;
+
+ e = gtk_constraint_expression_new_from_variable (y);
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_LE, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ e = gtk_constraint_expression_plus_constant (gtk_constraint_expression_new_from_variable (x), 3.0);
+ gtk_constraint_solver_add_constraint (solver,
+ y, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ e = gtk_constraint_expression_new (10.0);
+ gtk_constraint_solver_add_constraint (solver,
+ x, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_WEAK);
+
+ e = gtk_constraint_expression_new (10.0);
+ gtk_constraint_solver_add_constraint (solver,
+ y, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_WEAK);
+
+ double x_val = gtk_constraint_variable_get_value (x);
+ double y_val = gtk_constraint_variable_get_value (y);
+
+ g_test_message ("x = %g, y = %g", x_val, y_val);
+
+ /* The system is unstable and has two possible solutions we need to test */
+ g_assert_true ((G_APPROX_VALUE (x_val, 10.0, 1e-8) &&
+ G_APPROX_VALUE (y_val, 13.0, 1e-8)) ||
+ (G_APPROX_VALUE (x_val, 7.0, 1e-8) &&
+ G_APPROX_VALUE (y_val, 10.0, 1e-8)));
+
+ gtk_constraint_variable_unref (x);
+ gtk_constraint_variable_unref (y);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_edit_var_required (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *a = gtk_constraint_solver_create_variable (solver, NULL, "a", 0.0);
+ gtk_constraint_solver_add_stay_variable (solver, a, GTK_CONSTRAINT_STRENGTH_STRONG);
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 0.0, 0.001);
+
+ gtk_constraint_solver_add_edit_variable (solver, a, GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_solver_begin_edit (solver);
+ gtk_constraint_solver_suggest_value (solver, a, 2.0);
+ gtk_constraint_solver_resolve (solver);
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 2.0, 0.001);
+
+ gtk_constraint_solver_suggest_value (solver, a, 10.0);
+ gtk_constraint_solver_resolve (solver);
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 10.0, 0.001);
+
+ gtk_constraint_solver_end_edit (solver);
+
+ gtk_constraint_variable_unref (a);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_edit_var_suggest (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *a = gtk_constraint_solver_create_variable (solver, NULL, "a", 0.0);
+ GtkConstraintVariable *b = gtk_constraint_solver_create_variable (solver, NULL, "b", 0.0);
+
+ gtk_constraint_solver_add_stay_variable (solver, a, GTK_CONSTRAINT_STRENGTH_STRONG);
+
+ GtkConstraintExpression *e = gtk_constraint_expression_new_from_variable (b);
+ gtk_constraint_solver_add_constraint (solver,
+ a, GTK_CONSTRAINT_RELATION_EQ, e,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ gtk_constraint_solver_resolve (solver);
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 0.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (b), 0.0, 0.001);
+
+ gtk_constraint_solver_add_edit_variable (solver, a, GTK_CONSTRAINT_STRENGTH_REQUIRED);
+ gtk_constraint_solver_begin_edit (solver);
+
+ gtk_constraint_solver_suggest_value (solver, a, 2.0);
+ gtk_constraint_solver_resolve (solver);
+
+ g_test_message ("Check values after first edit");
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 2.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (b), 2.0, 0.001);
+
+ gtk_constraint_solver_suggest_value (solver, a, 10.0);
+ gtk_constraint_solver_resolve (solver);
+
+ g_test_message ("Check values after second edit");
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 10.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (b), 10.0, 0.001);
+
+ gtk_constraint_solver_suggest_value (solver, a, 12.0);
+ gtk_constraint_solver_resolve (solver);
+
+ g_test_message ("Check values after third edit");
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (a), 12.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (b), 12.0, 0.001);
+
+ gtk_constraint_variable_unref (a);
+ gtk_constraint_variable_unref (b);
+
+ g_object_unref (solver);
+}
+
+static void
+constraint_solver_paper (void)
+{
+ GtkConstraintSolver *solver = gtk_constraint_solver_new ();
+
+ GtkConstraintVariable *left = gtk_constraint_solver_create_variable (solver, NULL, "left", 0.0);
+ GtkConstraintVariable *middle = gtk_constraint_solver_create_variable (solver, NULL, "middle", 0.0);
+ GtkConstraintVariable *right = gtk_constraint_solver_create_variable (solver, NULL, "right", 0.0);
+
+ GtkConstraintExpressionBuilder builder;
+ GtkConstraintExpression *expr;
+
+ 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, right);
+ gtk_constraint_expression_builder_divide_by (&builder);
+ gtk_constraint_expression_builder_constant (&builder, 2.0);
+ expr = gtk_constraint_expression_builder_finish (&builder);
+ gtk_constraint_solver_add_constraint (solver,
+ middle, GTK_CONSTRAINT_RELATION_EQ, expr,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ gtk_constraint_expression_builder_init (&builder, solver);
+ gtk_constraint_expression_builder_term (&builder, left);
+ gtk_constraint_expression_builder_plus (&builder);
+ gtk_constraint_expression_builder_constant (&builder, 10.0);
+ expr = gtk_constraint_expression_builder_finish (&builder);
+ gtk_constraint_solver_add_constraint (solver,
+ right, GTK_CONSTRAINT_RELATION_EQ, expr,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ expr = gtk_constraint_expression_new (100.0);
+ gtk_constraint_solver_add_constraint (solver,
+ right, GTK_CONSTRAINT_RELATION_LE, expr,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ expr = gtk_constraint_expression_new (0.0);
+ gtk_constraint_solver_add_constraint (solver,
+ left, GTK_CONSTRAINT_RELATION_GE, expr,
+ GTK_CONSTRAINT_STRENGTH_REQUIRED);
+
+ g_test_message ("Check constraints hold");
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (middle),
+ (gtk_constraint_variable_get_value (left) + gtk_constraint_variable_get_value (right)) / 2.0,
+ 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (right),
+ gtk_constraint_variable_get_value (left) + 10.0,
+ 0.001);
+ g_assert_cmpfloat (gtk_constraint_variable_get_value (right), <=, 100.0);
+ g_assert_cmpfloat (gtk_constraint_variable_get_value (left), >=, 0.0);
+
+ gtk_constraint_variable_set_value (middle, 45.0);
+ gtk_constraint_solver_add_stay_variable (solver, middle, GTK_CONSTRAINT_STRENGTH_WEAK);
+
+ g_test_message ("Check constraints hold after setting middle");
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (middle),
+ (gtk_constraint_variable_get_value (left) + gtk_constraint_variable_get_value (right)) / 2.0,
+ 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (right),
+ gtk_constraint_variable_get_value (left) + 10.0,
+ 0.001);
+ g_assert_cmpfloat (gtk_constraint_variable_get_value (right), <=, 100.0);
+ g_assert_cmpfloat (gtk_constraint_variable_get_value (left), >=, 0.0);
+
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (left), 40.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (middle), 45.0, 0.001);
+ g_assert_cmpfloat_with_epsilon (gtk_constraint_variable_get_value (right), 50.0, 0.001);
+
+ gtk_constraint_variable_unref (left);
+ gtk_constraint_variable_unref (middle);
+ gtk_constraint_variable_unref (right);
+
+ g_object_unref (solver);
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ setlocale (LC_ALL, "C");
+
+ g_test_add_func ("/constraint-solver/paper", constraint_solver_paper);
+ g_test_add_func ("/constraint-solver/simple", constraint_solver_simple);
+ g_test_add_func ("/constraint-solver/constant/eq", constraint_solver_variable_eq_constant);
+ g_test_add_func ("/constraint-solver/constant/ge", constraint_solver_variable_geq_constant);
+ g_test_add_func ("/constraint-solver/constant/le", constraint_solver_variable_leq_constant);
+ g_test_add_func ("/constraint-solver/stay/simple", constraint_solver_stay);
+ g_test_add_func ("/constraint-solver/stay/eq", constraint_solver_eq_with_stay);
+ g_test_add_func ("/constraint-solver/cassowary", constraint_solver_cassowary);
+ g_test_add_func ("/constraint-solver/edit/required", constraint_solver_edit_var_required);
+ g_test_add_func ("/constraint-solver/edit/suggest", constraint_solver_edit_var_suggest);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index afa5c3c7dd..b02d3f51ce 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -17,6 +17,11 @@ tests = [
['builderparser'],
['cellarea'],
['check-icon-names'],
+ ['constraint-solver', [
+ '../../gtk/gtkconstraintsolver.c',
+ '../../gtk/gtkconstraintexpression.c',
+ ], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']
+ ],
['cssprovider'],
['rbtree-crash', ['../../gtk/gtkrbtree.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']],
['defaultvalue'],