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.
This commit is contained in:
Matthias Clasen
2019-06-28 04:51:18 +00:00
parent 043fed8612
commit 1e46874eb0
2 changed files with 390 additions and 1 deletions

381
tests/constrainttree.c Normal file
View File

@@ -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;
}

View File

@@ -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