diff options
author | Benjamin Otte <otte@redhat.com> | 2020-11-26 01:29:42 +0100 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2020-11-28 07:35:55 +0100 |
commit | 2aab9061743c19a895c0a67cd0570af88ccec52d (patch) | |
tree | 9de3000b55f6645af234b473132d43a2639ae121 | |
parent | 52ca3fa73f069133dc5f6ac759fbc6f217e13bb9 (diff) | |
download | gtk+-2aab9061743c19a895c0a67cd0570af88ccec52d.tar.gz |
gtk-demo: Add cute maze demo
-rw-r--r-- | demos/gtk-demo/demo.gresource.xml | 1 | ||||
-rw-r--r-- | demos/gtk-demo/meson.build | 1 | ||||
-rw-r--r-- | demos/gtk-demo/path_maze.c | 338 |
3 files changed, 340 insertions, 0 deletions
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 4c1c3c3cc4..d4f00fdfc1 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -320,6 +320,7 @@ <file>panes.c</file> <file>password_entry.c</file> <file>path_fill.c</file> + <file>path_maze.c</file> <file>path_text.c</file> <file>peg_solitaire.c</file> <file>pickers.c</file> diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index c4890d14da..2c1d43f839 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -69,6 +69,7 @@ demos = files([ 'panes.c', 'password_entry.c', 'path_fill.c', + 'path_maze.c', 'path_text.c', 'peg_solitaire.c', 'pickers.c', diff --git a/demos/gtk-demo/path_maze.c b/demos/gtk-demo/path_maze.c new file mode 100644 index 0000000000..07452c4975 --- /dev/null +++ b/demos/gtk-demo/path_maze.c @@ -0,0 +1,338 @@ +/* Path/Maze + * + * This demo shows how to use a GskPath to create a maze and use + * gsk_path_measure_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 <glib/gi18n.h> +#include <gtk/gtk.h> + +#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) +{ + if (!self->active) + return; + + if (gsk_path_measure_get_closest_point (self->measure, &GRAPHENE_POINT_INIT (x, y), 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] = { }; + 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_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} |