From ddd4855bbca4a3c7f8947049c9b3a4d71ebba0e9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 27 Aug 2023 12:36:30 -0400 Subject: [PATCH 1/2] Add a test for rectangle segments --- testsuite/gsk/path-special-cases.c | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/testsuite/gsk/path-special-cases.c b/testsuite/gsk/path-special-cases.c index e458e36dc4..f7d570c4d0 100644 --- a/testsuite/gsk/path-special-cases.c +++ b/testsuite/gsk/path-special-cases.c @@ -957,6 +957,44 @@ test_length (void) gsk_path_measure_unref (measure2); } +static void +test_rect_segment (void) +{ + GskPathBuilder *builder; + GskPath *path, *path1, *path2; + GskPathMeasure *measure, *measure1, *measure2; + GskPathPoint point0, point1; + + builder = gsk_path_builder_new (); + gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (0, 0, 100, 100)); + path = gsk_path_builder_free_to_path (builder); + measure = gsk_path_measure_new (path); + + gsk_path_measure_get_point (measure, 20, &point0); + gsk_path_measure_get_point (measure, 80, &point1); + + builder = gsk_path_builder_new (); + gsk_path_builder_add_segment (builder, path, &point0, &point1); + path1 = gsk_path_builder_free_to_path (builder); + measure1 = gsk_path_measure_new (path1); + + g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure1), 60, 0.001); + + builder = gsk_path_builder_new (); + gsk_path_builder_add_segment (builder, path, &point1, &point0); + path2 = gsk_path_builder_free_to_path (builder); + measure2 = gsk_path_measure_new (path2); + + g_assert_cmpfloat_with_epsilon (gsk_path_measure_get_length (measure2), 340, 0.001); + + gsk_path_unref (path); + gsk_path_unref (path1); + gsk_path_unref (path2); + gsk_path_measure_unref (measure); + gsk_path_measure_unref (measure1); + gsk_path_measure_unref (measure2); +} + int main (int argc, char *argv[]) { @@ -975,6 +1013,7 @@ main (int argc, char *argv[]) g_test_add_func ("/path/rounded-rect", test_rounded_rect); g_test_add_func ("/path/circle", test_circle); g_test_add_func ("/path/length", test_length); + g_test_add_func ("/path/rect/segment", test_rect_segment); return g_test_run (); } From 2582fd45e45cae81c711636c81af246378f9787d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 26 Nov 2020 01:29:42 +0100 Subject: [PATCH 2/2] demos: Add cute maze demo --- demos/gtk-demo/demo.gresource.xml | 1 + demos/gtk-demo/meson.build | 1 + demos/gtk-demo/path_maze.c | 344 ++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 demos/gtk-demo/path_maze.c diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 2c8f1caf88..dbb06fd2d3 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -336,6 +336,7 @@ panes.c password_entry.c path_fill.c + path_maze.c path_spinner.c path_walk.c path_text.c diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index fe60493016..082b938d9a 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -73,6 +73,7 @@ demos = files([ 'panes.c', 'password_entry.c', 'path_fill.c', + 'path_maze.c', 'path_spinner.c', 'path_walk.c', 'path_text.c', diff --git a/demos/gtk-demo/path_maze.c b/demos/gtk-demo/path_maze.c new file mode 100644 index 0000000000..0fe896ff35 --- /dev/null +++ b/demos/gtk-demo/path_maze.c @@ -0,0 +1,344 @@ +/* Path/Maze + * + * This demo shows how to use a GskPath to create a maze and use + * gsk_path_get_closest_point() to check the mouse stays + * on the path. + * + * It also shows off the performance of GskPath (or not) as this + * is a rather complex path. + */ + +#include +#include + +#include "paintable.h" + +#define MAZE_GRID_SIZE 20 +#define MAZE_STROKE_SIZE_ACTIVE (MAZE_GRID_SIZE - 4) +#define MAZE_STROKE_SIZE_INACTIVE (MAZE_GRID_SIZE - 12) +#define MAZE_WIDTH 31 +#define MAZE_HEIGHT 21 + +#define GTK_TYPE_MAZE (gtk_maze_get_type ()) +G_DECLARE_FINAL_TYPE (GtkMaze, gtk_maze, GTK, MAZE, GtkWidget) + +struct _GtkMaze +{ + GtkWidget parent_instance; + + int width; + int height; + GskPath *path; + GskPathMeasure *measure; + GdkPaintable *background; + + gboolean active; +}; + +struct _GtkMazeClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (GtkMaze, gtk_maze, GTK_TYPE_WIDGET) + +static void +gtk_maze_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkMaze *self = GTK_MAZE (widget); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + *minimum = *natural = self->width; + else + *minimum = *natural = self->height; +} + +static void +gtk_maze_snapshot (GtkWidget *widget, + GdkSnapshot *snapshot) +{ + GtkMaze *self = GTK_MAZE (widget); + double width = gtk_widget_get_width (widget); + double height = gtk_widget_get_height (widget); + GskStroke *stroke; + + stroke = gsk_stroke_new (MAZE_STROKE_SIZE_INACTIVE); + if (self->active) + gsk_stroke_set_line_width (stroke, MAZE_STROKE_SIZE_ACTIVE); + gsk_stroke_set_line_join (stroke, GSK_LINE_JOIN_ROUND); + gsk_stroke_set_line_cap (stroke, GSK_LINE_CAP_ROUND); + gtk_snapshot_push_stroke (snapshot, self->path, stroke); + gsk_stroke_free (stroke); + + if (self->background) + { + gdk_paintable_snapshot (self->background, snapshot, width, height); + } + else + { + gtk_snapshot_append_linear_gradient (snapshot, + &GRAPHENE_RECT_INIT (0, 0, width, height), + &GRAPHENE_POINT_INIT (0, 0), + &GRAPHENE_POINT_INIT (width, height), + (GskColorStop[8]) { + { 0.0, { 1.0, 0.0, 0.0, 1.0 } }, + { 0.2, { 1.0, 0.0, 0.0, 1.0 } }, + { 0.3, { 1.0, 1.0, 0.0, 1.0 } }, + { 0.4, { 0.0, 1.0, 0.0, 1.0 } }, + { 0.6, { 0.0, 1.0, 1.0, 1.0 } }, + { 0.7, { 0.0, 0.0, 1.0, 1.0 } }, + { 0.8, { 1.0, 0.0, 1.0, 1.0 } }, + { 1.0, { 1.0, 0.0, 1.0, 1.0 } } + }, + 8); + } + + gtk_snapshot_pop (snapshot); + +} + +static void +gtk_maze_dispose (GObject *object) +{ + GtkMaze *self = GTK_MAZE (object); + + g_clear_pointer (&self->path, gsk_path_unref); + g_clear_pointer (&self->measure, gsk_path_measure_unref); + if (self->background) + { + g_signal_handlers_disconnect_matched (self->background, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, self); + g_clear_object (&self->background); + } + + G_OBJECT_CLASS (gtk_maze_parent_class)->dispose (object); +} + +static void +gtk_maze_class_init (GtkMazeClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gtk_maze_dispose; + + widget_class->measure = gtk_maze_measure; + widget_class->snapshot = gtk_maze_snapshot; +} + +static void +pointer_motion (GtkEventControllerMotion *controller, + double x, + double y, + GtkMaze *self) +{ + GskPathPoint point; + graphene_point_t pos; + + if (!self->active) + return; + + gsk_path_get_closest_point (self->path, &GRAPHENE_POINT_INIT (x, y), INFINITY, &point); + + gsk_path_point_get_position (&point, self->path, &pos); + if (graphene_point_distance (&GRAPHENE_POINT_INIT (x, y), &pos, NULL, NULL) <= MAZE_STROKE_SIZE_ACTIVE / 2.0f) + return; + + self->active = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +pointer_leave (GtkEventControllerMotion *controller, + GtkMaze *self) +{ + if (!self->active) + { + self->active = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +gtk_maze_init (GtkMaze *self) +{ + GtkEventController *controller; + + controller = GTK_EVENT_CONTROLLER (gtk_event_controller_motion_new ()); + g_signal_connect (controller, "motion", G_CALLBACK (pointer_motion), self); + g_signal_connect (controller, "leave", G_CALLBACK (pointer_leave), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + self->active = TRUE; +} + +static void +gtk_maze_set_path (GtkMaze *self, + GskPath *path) +{ + g_clear_pointer (&self->path, gsk_path_unref); + g_clear_pointer (&self->measure, gsk_path_measure_unref); + self->path = gsk_path_ref (path); + self->measure = gsk_path_measure_new (path); + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +GtkWidget * +gtk_maze_new (GskPath *path, + GdkPaintable *background, + int width, + int height) +{ + GtkMaze *self; + + self = g_object_new (GTK_TYPE_MAZE, NULL); + + gtk_maze_set_path (self, path); + gsk_path_unref (path); + self->background = background; + if (self->background) + { + g_signal_connect_swapped (self->background, "invalidate-contents", G_CALLBACK (gtk_widget_queue_draw), self); + g_signal_connect_swapped (self->background, "invalidate-size", G_CALLBACK (gtk_widget_queue_resize), self); + } + self->width = width; + self->height = height; + + return GTK_WIDGET (self); +} + +static void +add_point_to_maze (GtkBitset *maze, + GskPathBuilder *builder, + guint x, + guint y) +{ + gboolean set[4] = { FALSE, FALSE, FALSE, FALSE }; + guint dir; + + gtk_bitset_add (maze, y * MAZE_WIDTH + x); + + while (TRUE) + { + set[0] = set[0] || x == 0 || gtk_bitset_contains (maze, y * MAZE_WIDTH + x - 1); + set[1] = set[1] || y == 0 || gtk_bitset_contains (maze, (y - 1) * MAZE_WIDTH + x); + set[2] = set[2] || x + 1 == MAZE_WIDTH || gtk_bitset_contains (maze, y * MAZE_WIDTH + x + 1); + set[3] = set[3] || y + 1 == MAZE_HEIGHT || gtk_bitset_contains (maze, (y + 1) * MAZE_WIDTH + x); + + if (set[0] && set[1] && set[2] && set[3]) + return; + + do + { + dir = g_random_int_range (0, 4); + } + while (set[dir]); + + switch (dir) + { + case 0: + gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, (x - 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + add_point_to_maze (maze, builder, x - 1, y); + break; + + case 1: + gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y - 0.5) * MAZE_GRID_SIZE); + add_point_to_maze (maze, builder, x, y - 1); + break; + + case 2: + gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, (x + 1.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + add_point_to_maze (maze, builder, x + 1, y); + break; + + case 3: + gsk_path_builder_move_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 0.5) * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, (x + 0.5) * MAZE_GRID_SIZE, (y + 1.5) * MAZE_GRID_SIZE); + add_point_to_maze (maze, builder, x, y + 1); + break; + + default: + g_assert_not_reached (); + break; + } + } +} + +static GskPath * +create_path_for_maze (GtkWidget *widget) +{ + GskPathBuilder *builder; + GtkBitset *maze; + + builder = gsk_path_builder_new (); + maze = gtk_bitset_new_empty (); + /* make sure the outer lines are unreachable: + * Set the full range, then remove the center again. */ + gtk_bitset_add_range (maze, 0, MAZE_WIDTH * MAZE_HEIGHT); + gtk_bitset_remove_rectangle (maze, MAZE_WIDTH + 1, MAZE_WIDTH - 2, MAZE_HEIGHT - 2, MAZE_WIDTH); + + /* Fill the maze */ + add_point_to_maze (maze, builder, MAZE_WIDTH / 2, MAZE_HEIGHT / 2); + + /* Add start and stop lines */ + gsk_path_builder_move_to (builder, 1.5 * MAZE_GRID_SIZE, -0.5 * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, 1.5 * MAZE_GRID_SIZE, 1.5 * MAZE_GRID_SIZE); + gsk_path_builder_move_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT - 1.5) * MAZE_GRID_SIZE); + gsk_path_builder_line_to (builder, (MAZE_WIDTH - 1.5) * MAZE_GRID_SIZE, (MAZE_HEIGHT + 0.5) * MAZE_GRID_SIZE); + + + gtk_bitset_unref (maze); + + return gsk_path_builder_free_to_path (builder); +} + +GtkWidget * +do_path_maze (GtkWidget *do_widget) +{ + static GtkWidget *window = NULL; + + if (!window) + { + GtkWidget *maze; + GtkMediaStream *stream; + GskPath *path; + + window = gtk_window_new (); + gtk_window_set_resizable (GTK_WINDOW (window), TRUE); + gtk_window_set_title (GTK_WINDOW (window), "Follow the maze with the mouse"); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + +#if 0 + stream = gtk_media_file_new_for_resource ("/images/gtk-logo.webm"); +#else + stream = gtk_nuclear_media_stream_new (); +#endif + gtk_media_stream_play (stream); + gtk_media_stream_set_loop (stream, TRUE); + + path = create_path_for_maze (window); + + maze = gtk_maze_new (path, + GDK_PAINTABLE (stream), + MAZE_WIDTH * MAZE_GRID_SIZE, + MAZE_HEIGHT * MAZE_GRID_SIZE); + + gtk_window_set_child (GTK_WINDOW (window), maze); + } + + if (!gtk_widget_get_visible (window)) + gtk_window_present (GTK_WINDOW (window)); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +}