[gtk/wip/otte/lottie: 13/13] gtk-demo: Add cute maze demo




commit e6e4cae41ef6f9c86ce4d302f19b9eca6fb9bf37
Author: Benjamin Otte <otte redhat com>
Date:   Thu Nov 26 01:29:42 2020 +0100

    gtk-demo: Add cute maze demo

 demos/gtk-demo/demo.gresource.xml |   1 +
 demos/gtk-demo/meson.build        |   1 +
 demos/gtk-demo/path_maze.c        | 338 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 340 insertions(+)
---
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;
+}


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