[gtk/constraint-guide-3: 7/7] Add an interactive constraint solver test
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/constraint-guide-3: 7/7] Add an interactive constraint solver test
- Date: Fri, 28 Jun 2019 13:01:31 +0000 (UTC)
commit 1e46874eb0eb4a35a399404b4f75c4cd86e3ea64
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]