[gtk/wip/ebassi/constraint-layout: 69/69] Add a constraint editor demo



commit 514de0b91a34e90166a2f4a35ff29a0558590f1d
Author: Matthias Clasen <mclasen redhat com>
Date:   Sun Jun 30 13:25:30 2019 +0000

    Add a constraint editor demo
    
    This is an initial cut at providing a tool
    for interactive exploration of constraints.

 .../constraint-editor-application.c                |  94 ++++
 .../constraint-editor-application.h                |  28 +
 demos/constraint-editor/constraint-editor-window.c | 354 +++++++++++++
 demos/constraint-editor/constraint-editor-window.h |  34 ++
 .../constraint-editor/constraint-editor-window.ui  |  68 +++
 demos/constraint-editor/constraint-editor.c        | 583 +++++++++++++++++++++
 demos/constraint-editor/constraint-editor.css      |  12 +
 .../constraint-editor.gresource.xml                |   9 +
 demos/constraint-editor/constraint-editor.h        |  29 +
 demos/constraint-editor/constraint-editor.ui       | 180 +++++++
 demos/constraint-editor/constraint-view-child.c    |  93 ++++
 demos/constraint-editor/constraint-view-child.h    |  44 ++
 demos/constraint-editor/constraint-view.c          | 277 ++++++++++
 demos/constraint-editor/constraint-view.h          |  44 ++
 demos/constraint-editor/guide-editor.c             | 318 +++++++++++
 demos/constraint-editor/guide-editor.h             |  28 +
 demos/constraint-editor/guide-editor.ui            | 188 +++++++
 demos/constraint-editor/main.c                     |  28 +
 demos/constraint-editor/meson.build                |  19 +
 demos/meson.build                                  |   1 +
 20 files changed, 2431 insertions(+)
---
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..eb5852a94c
--- /dev/null
+++ b/demos/constraint-editor/constraint-view.c
@@ -0,0 +1,277 @@
+/* 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;
+};
+
+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
+constraint_view_init (ConstraintView *self)
+{
+  gtk_widget_set_layout_manager (GTK_WIDGET (self),
+                                 gtk_constraint_layout_new ());
+
+  self->store = g_list_store_new (G_TYPE_OBJECT);
+}
+
+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));
+
+  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_set_parent (frame, GTK_WIDGET (view));
+
+  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);
+
+  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..e4baf8ca32
--- /dev/null
+++ b/demos/constraint-editor/guide-editor.c
@@ -0,0 +1,318 @@
+/*
+ * 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");
+}
+
+#if 0
+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;
+}
+#endif
+
+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)
+{
+#if 0
+  const char *id;
+  int strength;
+#endif
+  const char *name;
+  int w, h;
+  GtkConstraintGuide *guide;
+
+  //guide = gtk_constraint_guide_new ();
+  if (editor->guide)
+    guide = g_object_ref (editor->guide);
+  else
+    guide = g_object_new (GTK_TYPE_CONSTRAINT_GUIDE, NULL);
+
+  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);
+  g_object_set (guide, "min-width", w, "min-height", h, NULL);
+
+  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);
+  g_object_set (guide, "nat-width", w, "nat-height", h, NULL);
+
+#if 0
+  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);
+#endif
+
+  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 void
+guide_editor_constructed (GObject *object)
+{
+  GuideEditor *editor = GUIDE_EDITOR (object);
+
+  guide_strength_combo (editor->strength);
+
+  gtk_widget_set_sensitive (editor->max_width, FALSE);
+  gtk_widget_set_sensitive (editor->max_height, FALSE);
+  gtk_widget_set_sensitive (editor->strength, FALSE);
+
+  if (editor->guide)
+    {
+#if 0
+      GtkConstaintStrength strength;
+#endif
+      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_constaint_guide_get_min_size (editor->guide, &w, &h);
+      g_object_get (editor->guide, "min-width", &w, "min-height", &h, NULL);
+      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_constaint_guide_get_nat_size (editor->guide, &w, &h);
+      g_object_get (editor->guide, "nat-width", &w, "nat-height", &h, NULL);
+      gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_width), w);
+      gtk_spin_button_set_value (GTK_SPIN_BUTTON (editor->nat_height), h);
+
+#if 0
+      gtk_constaint_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_guide_get_strength (editor->guide);
+      nick = get_strength_nick (strength);
+      gtk_combo_box_set_active_id (GTK_COMBO_BOX (editor->strength), nick);
+#endif
+
+      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);
+#if 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");
+#endif
+
+      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/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')



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]