[gtk/constraint-guide-2] Add an interactive constraint solver test



commit 615f1aed651004715108539008d58865fc09758c
Author: Matthias Clasen <mclasen redhat com>
Date:   Fri Jun 28 04:51:18 2019 +0000

    Add an interactive constraint solver test
    
    This is an example described in the original
    Cassowary paper, reimplemented from scratch.
    It exposes some instability and crashiness
    in our solver.

 tests/constrainttree.c | 381 +++++++++++++++++++++++++++++++++++++++++++++++++
 tests/meson.build      |  10 +-
 2 files changed, 390 insertions(+), 1 deletion(-)
---
diff --git a/tests/constrainttree.c b/tests/constrainttree.c
new file mode 100644
index 0000000000..44d0238aea
--- /dev/null
+++ b/tests/constrainttree.c
@@ -0,0 +1,381 @@
+#include <gtk/gtk.h>
+
+#include "../../gtk/gtkconstrainttypesprivate.h"
+#include "../../gtk/gtkconstraintsolverprivate.h"
+#include "../../gtk/gtkconstraintexpressionprivate.h"
+
+typedef struct _Node Node;
+
+static GtkConstraintSolver *solver;
+static Node *tree;
+static Node *drag_node;
+static double drag_start_x;
+static double drag_start_y;
+static GtkConstraintVariable *width_var;
+static GtkConstraintVariable *height_var;
+
+struct _Node {
+  double x;
+  double y;
+  Node *parent;
+  Node *left;
+  Node *right;
+
+  GtkConstraintVariable *x_var;
+  GtkConstraintVariable *y_var;
+};
+
+static Node *
+make_tree (Node *parent,
+           int   depth,
+           int   x,
+           int   y,
+           int   dx,
+           int   dy)
+{
+  Node *node;
+
+  node = g_new0 (Node, 1);
+  node->parent = parent;
+
+  if (depth > 0)
+    {
+      node->left = make_tree (node, depth - 1, x - dx, y + dy, dx / 2, dy);
+      node->right = make_tree (node, depth - 1, x  + dx, y + dy, dx / 2, dy);
+    }
+
+  node->x = x;
+  node->y = y;
+
+  node->x_var = gtk_constraint_solver_create_variable (solver, NULL, "x", x);
+  node->y_var = gtk_constraint_solver_create_variable (solver, NULL, "y", y);
+
+  /* weak stay for the current position */
+  gtk_constraint_solver_add_stay_variable (solver, node->x_var, GTK_CONSTRAINT_WEIGHT_WEAK);
+  gtk_constraint_solver_add_stay_variable (solver, node->y_var, GTK_CONSTRAINT_WEIGHT_WEAK);
+
+  /* require to stay in area */
+  gtk_constraint_solver_add_constraint (solver,
+                                        node->x_var,
+                                        GTK_CONSTRAINT_RELATION_GE,
+                                        gtk_constraint_expression_new (0.0),
+                                        GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_add_constraint (solver,
+                                        node->x_var,
+                                        GTK_CONSTRAINT_RELATION_LE,
+                                        gtk_constraint_expression_new (1600.0),
+                                        //gtk_constraint_expression_new_from_variable (width_var),
+                                        GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_add_constraint (solver,
+                                        node->y_var,
+                                        GTK_CONSTRAINT_RELATION_GE,
+                                        gtk_constraint_expression_new (0.0),
+                                        GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_add_constraint (solver,
+                                        node->y_var,
+                                        GTK_CONSTRAINT_RELATION_LE,
+                                        gtk_constraint_expression_new (600.0),
+                                        //gtk_constraint_expression_new_from_variable (height_var),
+                                        GTK_CONSTRAINT_WEIGHT_REQUIRED);
+
+  if (node->left)
+    {
+      GtkConstraintExpressionBuilder builder;
+
+      /* left.y = right.y */
+      gtk_constraint_solver_add_constraint (solver,
+                                            node->left->y_var,
+                                            GTK_CONSTRAINT_RELATION_EQ,
+                                            gtk_constraint_expression_new_from_variable (node->right->y_var),
+                                            GTK_CONSTRAINT_WEIGHT_REQUIRED);
+
+      /* left.y >= parent.y + 10 */
+      gtk_constraint_expression_builder_init (&builder, solver);
+      gtk_constraint_expression_builder_term (&builder, node->y_var);
+      gtk_constraint_expression_builder_plus (&builder);
+      gtk_constraint_expression_builder_constant (&builder, 10.0);
+      gtk_constraint_solver_add_constraint (solver,
+                                            node->left->y_var,
+                                            GTK_CONSTRAINT_RELATION_GE,
+                                            gtk_constraint_expression_builder_finish (&builder),
+                                            GTK_CONSTRAINT_WEIGHT_REQUIRED);
+
+      /* right.y >= parent.y + 10 */
+      gtk_constraint_expression_builder_init (&builder, solver);
+      gtk_constraint_expression_builder_term (&builder, node->y_var);
+      gtk_constraint_expression_builder_plus (&builder);
+      gtk_constraint_expression_builder_constant (&builder, 10.0);
+      gtk_constraint_solver_add_constraint (solver,
+                                            node->right->y_var,
+                                            GTK_CONSTRAINT_RELATION_GE,
+                                            gtk_constraint_expression_builder_finish (&builder),
+                                            GTK_CONSTRAINT_WEIGHT_REQUIRED);
+      /* parent.x = (left.x + right.x) / 2 */
+      gtk_constraint_expression_builder_init (&builder, solver);
+      gtk_constraint_expression_builder_term (&builder, node->left->x_var);
+      gtk_constraint_expression_builder_plus (&builder);
+      gtk_constraint_expression_builder_term (&builder, node->right->x_var);
+      gtk_constraint_expression_builder_divide_by (&builder);
+      gtk_constraint_expression_builder_constant (&builder, 2.0);
+      gtk_constraint_solver_add_constraint (solver,
+                                            node->x_var,
+                                            GTK_CONSTRAINT_RELATION_EQ,
+                                            gtk_constraint_expression_builder_finish (&builder),
+                                            GTK_CONSTRAINT_WEIGHT_REQUIRED);
+    }
+
+  return node;
+}
+
+static void
+draw_node (Node *node, cairo_t *cr)
+{
+  if (node->left)
+    draw_node (node->left, cr);
+  if (node->right)
+    draw_node (node->right, cr);
+
+  if (node->parent)
+    {
+      cairo_set_source_rgb (cr, 0, 0, 0);
+      cairo_move_to (cr, node->parent->x, node->parent->y);
+      cairo_line_to (cr, node->x, node->y);
+      cairo_stroke (cr);
+    }
+
+  if (node == drag_node)
+    cairo_set_source_rgb (cr, 1, 0, 0);
+  else
+    cairo_set_source_rgb (cr, 0, 0, 0);
+
+  cairo_move_to (cr, node->x, node->y);
+  cairo_arc (cr, node->x, node->y, 5, 0, 2*M_PI);
+  cairo_close_path (cr);
+  cairo_fill (cr);
+}
+
+static void
+draw_func (GtkDrawingArea *da,
+           cairo_t        *cr,
+           int             width,
+           int             height,
+           gpointer        data)
+{
+  cairo_set_line_width (cr, 1);
+  cairo_set_source_rgb (cr, 1, 1, 1);
+  cairo_paint (cr);
+
+  draw_node (tree, cr);
+}
+
+static Node *
+find_node (Node   *node,
+           double  x,
+           double  y)
+{
+  Node *ret;
+
+  double dx = x - node->x;
+  double dy = y - node->y;
+
+  if (dx*dx + dy*dy < 10*10)
+    return node;
+
+  if (node->left)
+    {
+      ret = find_node (node->left, x, y);
+      if (ret)
+        return ret;
+    }
+
+  if (node->right)
+    {
+      ret = find_node (node->right, x, y);
+      if (ret)
+        return ret;
+    }
+
+  return NULL;
+}
+
+static void
+drag_begin (GtkGestureDrag *drag,
+            double          start_x,
+            double          start_y,
+            gpointer        data)
+{
+  drag_node = find_node (tree, start_x, start_y);
+  if (!drag_node)
+    return;
+
+  drag_start_x = start_x;
+  drag_start_y = start_y;
+  gtk_widget_queue_draw (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag)));
+}
+
+static void
+update_tree (Node *node)
+{
+  if (!node)
+    return;
+
+  node->x = gtk_constraint_variable_get_value (node->x_var);
+  node->y = gtk_constraint_variable_get_value (node->y_var);
+
+  update_tree (node->left);
+  update_tree (node->right);
+}
+
+static void
+drag_update (GtkGestureDrag *drag,
+             double          offset_x,
+             double          offset_y,
+             gpointer        data)
+{
+  if (!drag_node)
+    return;
+
+  gtk_constraint_solver_add_edit_variable (solver,
+                                           drag_node->x_var,
+                                           GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_add_edit_variable (solver,
+                                           drag_node->y_var,
+                                           GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_begin_edit (solver);
+  gtk_constraint_solver_suggest_value (solver,
+                                       drag_node->x_var,
+                                       drag_start_x + offset_x);
+  gtk_constraint_solver_suggest_value (solver,
+                                       drag_node->y_var,
+                                       drag_start_y + offset_y);
+  gtk_constraint_solver_resolve (solver);
+
+  update_tree (tree);
+
+  gtk_constraint_solver_remove_edit_variable (solver, drag_node->x_var);
+  gtk_constraint_solver_remove_edit_variable (solver, drag_node->y_var);
+  gtk_constraint_solver_end_edit (solver);
+  gtk_widget_queue_draw (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag)));
+}
+
+static void
+drag_end (GtkGestureDrag *drag,
+          double          offset_x,
+          double          offset_y,
+          gpointer        data)
+{
+  if (!drag_node)
+    return;
+
+
+  drag_node = NULL;
+
+  gtk_widget_queue_draw (gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag)));
+}
+
+static void
+size_change (GtkWidget *da,
+             int        width,
+             int        height,
+             int        baseline,
+             gpointer   data)
+{
+  gtk_constraint_variable_set_value (width_var, width);
+  gtk_constraint_variable_set_value (height_var, height);
+  gtk_constraint_solver_resolve (solver);
+}
+
+static void
+reset_tree (Node *node,
+            int   x,
+            int   y,
+            int   dx,
+            int   dy)
+{
+  node->x = x;
+  node->y = y;
+
+  gtk_constraint_solver_remove_stay_variable (solver, node->x_var);
+  gtk_constraint_solver_remove_stay_variable (solver, node->y_var);
+  gtk_constraint_variable_set_value (node->x_var, x);
+  gtk_constraint_variable_set_value (node->y_var, y);
+  gtk_constraint_solver_add_stay_variable (solver, node->x_var, GTK_CONSTRAINT_WEIGHT_WEAK);
+  gtk_constraint_solver_add_stay_variable (solver, node->y_var, GTK_CONSTRAINT_WEIGHT_WEAK);
+
+  if (node->left)
+    reset_tree (node->left, x - dx, y + dy, dx / 2, dy);
+  if (node->right)
+    reset_tree (node->right, x  + dx, y + dy, dx / 2, dy);
+}
+
+static void
+reset (GtkButton *button,
+       GtkWidget *da)
+{
+  int width, height;
+
+  width = gtk_widget_get_allocated_width (da);
+  height = gtk_widget_get_allocated_height (da);
+
+  gtk_constraint_solver_freeze (solver);
+  reset_tree (tree, width / 2, 20, width / 4 - 40, (height - 40) / 7);
+  gtk_constraint_solver_thaw (solver);
+
+  gtk_widget_queue_draw (da);
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window;
+  GtkWidget *header;
+  GtkWidget *button;
+  GtkWidget *da;
+  GtkGesture *drag;
+  int width = 1600;
+  int height = 600;
+
+  gtk_init ();
+
+  da = gtk_drawing_area_new ();
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  header = gtk_header_bar_new ();
+  gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE);
+  button = gtk_button_new_with_label ("Reset");
+  g_signal_connect (button, "clicked", G_CALLBACK (reset), da);
+  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), button);
+  gtk_window_set_titlebar (GTK_WINDOW (window), header);
+
+  gtk_drawing_area_set_content_width (GTK_DRAWING_AREA (da), width);
+  gtk_drawing_area_set_content_height (GTK_DRAWING_AREA (da), height);
+  gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
+
+  gtk_container_add (GTK_CONTAINER (window), da);
+
+  drag = gtk_gesture_drag_new ();
+  g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), NULL);
+  g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), NULL);
+  g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), NULL);
+  gtk_widget_add_controller (da, GTK_EVENT_CONTROLLER (drag));
+
+  solver = g_object_new (g_type_from_name ("GtkConstraintSolver"), NULL);
+  gtk_constraint_solver_freeze (solver);
+
+  width_var = gtk_constraint_solver_create_variable (solver, NULL, "width", width);
+  height_var = gtk_constraint_solver_create_variable (solver, NULL, "height", height);
+  gtk_constraint_solver_add_stay_variable (solver, width_var, GTK_CONSTRAINT_WEIGHT_REQUIRED);
+  gtk_constraint_solver_add_stay_variable (solver, height_var, GTK_CONSTRAINT_WEIGHT_REQUIRED);
+
+  g_signal_connect (da, "size-allocate", G_CALLBACK (size_change), NULL);
+
+  tree = make_tree (NULL, 7, width / 2, 20, width / 4 - 40, (height - 40) / 7);
+
+  gtk_constraint_solver_thaw (solver);
+
+  gtk_widget_show (window);
+
+  gtk_main ();
+
+  return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 677fbb74e6..1fcc53ca1d 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -1,5 +1,10 @@
 gtk_tests = [
   # testname, optional extra sources
+  ['constrainttree', [
+      '../gtk/gtkconstraintsolver.c',
+      '../gtk/gtkconstraintexpression.c',
+    ], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG', '-DGTK_TEST_EXTERNAL']
+  ],
   ['rendernode'],
   ['rendernode-create-tests'],
   ['overlayscroll'],
@@ -142,9 +147,12 @@ test_args = ['-DGTK_SRCDIR="@0@"'.format(meson.current_source_dir())]
 foreach t: gtk_tests
   test_name = t.get(0)
   test_srcs = ['@0@.c'.format(test_name), t.get(1, [])]
+  test_extra_cargs = t.get(2, [])
+  test_extra_ldflags = t.get(3, [])
   executable(test_name, test_srcs,
              include_directories: [confinc, gdkinc],
-             c_args: test_args,
+             c_args: test_args + test_extra_cargs,
+             link_args : test_extra_ldflags,
              dependencies: [libgtk_dep, libm])
 endforeach
 


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