[gtk] demo: Add the sliding puzzle demo



commit b6c8943bbfb046f4f8c2b468a71b407115735760
Author: Benjamin Otte <otte redhat com>
Date:   Mon Jun 4 05:41:44 2018 +0200

    demo: Add the sliding puzzle demo

 demos/gtk-demo/demo.gresource.xml |  12 +-
 demos/gtk-demo/meson.build        |   3 +-
 demos/gtk-demo/puzzlepiece.c      | 220 +++++++++++++++++++++++++++
 demos/gtk-demo/puzzlepiece.h      |  23 +++
 demos/gtk-demo/sliding_puzzle.c   | 303 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 557 insertions(+), 4 deletions(-)
---
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index 0b6952d237..1849e509f9 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -105,9 +105,6 @@
     <file>gnome-fs-directory.png</file>
     <file>gnome-fs-regular.png</file>
   </gresource>
-  <gresource prefix="/stack">
-    <file>stack.ui</file>
-  </gresource>
   <gresource prefix="/shortcuts">
     <file>shortcuts.ui</file>
     <file>shortcuts-builder.ui</file>
@@ -115,6 +112,14 @@
     <file>shortcuts-clocks.ui</file>
     <file>shortcuts-boxes.ui</file>
   </gresource>
+  <gresource prefix="/sliding_puzzle">
+    <file>puzzlepiece.c</file>
+    <file>puzzlepiece.h</file>
+    <file>portland-rose.jpg</file>
+  </gresource>
+  <gresource prefix="/stack">
+    <file>stack.ui</file>
+  </gresource>
   <gresource prefix="/revealer">
     <file>revealer.ui</file>
   </gresource>
@@ -198,6 +203,7 @@
     <file>shortcuts.c</file>
     <file>sizegroup.c</file>
     <file>sidebar.c</file>
+    <file>sliding_puzzle.c</file>
     <file>stack.c</file>
     <file>spinbutton.c</file>
     <file>spinner.c</file>
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 06e8b7f502..98edcfd6d4 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -61,6 +61,7 @@ demos = files([
   'shortcuts.c',
   'sidebar.c',
   'sizegroup.c',
+  'sliding_puzzle.c',
   'spinbutton.c',
   'spinner.c',
   'stack.c',
@@ -76,7 +77,7 @@ demos = files([
 
 gtkdemo_deps = [ libgtk_dep, ]
 
-extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c'])
+extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', 'puzzlepiece.c'])
 
 if harfbuzz_dep.found() and pangoft_dep.found()
   demos += files('font_features.c')
diff --git a/demos/gtk-demo/puzzlepiece.c b/demos/gtk-demo/puzzlepiece.c
new file mode 100644
index 0000000000..cb8a81b9b9
--- /dev/null
+++ b/demos/gtk-demo/puzzlepiece.c
@@ -0,0 +1,220 @@
+/* Paintable/A simple paintable
+ *
+ * GdkPaintable is an interface used by GTK for drawings of any sort
+ * that do not require layouting or positioning.
+ *
+ * This demo code gives a simple example on how a paintable can
+ * be created.
+ *
+ * Paintables can be used in many places inside GTK widgets, but the
+ * most common usage is inside GtkImage and that's what we're going
+ * to do here.
+ */
+
+#include <gtk/gtk.h>
+
+#include "puzzlepiece.h"
+
+/* Declare the struct. */
+struct _GtkPuzzlePiece
+{
+  GObject parent_instance;
+
+  GdkPaintable *puzzle;
+  guint x;
+  guint y;
+  guint width;
+  guint height;
+};
+
+struct _GtkPuzzlePieceClass
+{
+  GObjectClass parent_class;
+};
+
+/* This is the function that draws the puzzle piece.
+ * It just draws a rectangular cutout of the puzzle by clipping
+ * away the rest.
+ */
+static void
+gtk_puzzle_piece_snapshot (GdkPaintable *paintable,
+                           GdkSnapshot  *snapshot,
+                           double        width,
+                           double        height)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (paintable);
+
+  gtk_snapshot_push_clip (snapshot,
+                          &GRAPHENE_RECT_INIT (0, 0, width, height));
+
+  gtk_snapshot_offset (snapshot,
+                       - width * self->x,
+                       - height * self->y);
+  gdk_paintable_snapshot (self->puzzle,
+                          snapshot,
+                          width * self->width,
+                          height * self->height);
+
+  gtk_snapshot_pop (snapshot);
+}
+
+static GdkPaintableFlags
+gtk_puzzle_piece_get_flags (GdkPaintable *paintable)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (paintable);
+
+  /* The flags are the same as the ones of the puzzle.
+   * If the puzzle changes in some way, so do the pieces.
+   */
+  return gdk_paintable_get_flags (self->puzzle);
+}
+
+static int
+gtk_puzzle_piece_get_intrinsic_width (GdkPaintable *paintable)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (paintable);
+
+  /* We can compute our width relative to the puzzle.
+   * This logic even works for the case where the puzzle
+   * has no width, because the 0 return value is unchanged.
+   * Round up the value.
+   */
+  return (gdk_paintable_get_intrinsic_width (self->puzzle) + self->width - 1) / self->width;
+}
+
+static int
+gtk_puzzle_piece_get_intrinsic_height (GdkPaintable *paintable)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (paintable);
+
+  /* Do the same thing we did for the width with the height.
+   */
+  return (gdk_paintable_get_intrinsic_height (self->puzzle) + self->height - 1) / self->height;
+}
+
+static double
+gtk_puzzle_piece_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (paintable);
+
+  /* We can compute our aspect ratio relative to the puzzle.
+   * This logic again works for the case where the puzzle
+   * has no aspect ratio, because the 0 return value is unchanged.
+   */
+  return gdk_paintable_get_intrinsic_aspect_ratio (self->puzzle) * self->height / self->width;
+}
+
+static void
+gtk_puzzle_piece_paintable_init (GdkPaintableInterface *iface)
+{
+  iface->snapshot = gtk_puzzle_piece_snapshot;
+  iface->get_flags = gtk_puzzle_piece_get_flags;
+  iface->get_intrinsic_width = gtk_puzzle_piece_get_intrinsic_width;
+  iface->get_intrinsic_height = gtk_puzzle_piece_get_intrinsic_height;
+  iface->get_intrinsic_aspect_ratio = gtk_puzzle_piece_get_intrinsic_aspect_ratio;
+}
+
+/* When defining the GType, we need to implement the GdkPaintable interface */
+G_DEFINE_TYPE_WITH_CODE (GtkPuzzlePiece, gtk_puzzle_piece, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+                                                gtk_puzzle_piece_paintable_init))
+
+/* We need to declare a destructor to release our reference to the
+ * puzzle paintable and disconnect our signal handlers.
+ */
+static void
+gtk_puzzle_piece_dispose (GObject *object)
+{
+  GtkPuzzlePiece *self = GTK_PUZZLE_PIECE (object);
+
+  if (self->puzzle)
+    {
+      g_signal_handlers_disconnect_by_func (self->puzzle, gdk_paintable_invalidate_contents, self);
+      g_signal_handlers_disconnect_by_func (self->puzzle, gdk_paintable_invalidate_size, self);
+      g_clear_object (&self->puzzle);
+    }
+
+  G_OBJECT_CLASS (gtk_puzzle_piece_parent_class)->dispose (object);
+}
+
+/* Here's the boilerplate for the GObject declaration.
+ */
+static void
+gtk_puzzle_piece_class_init (GtkPuzzlePieceClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->dispose = gtk_puzzle_piece_dispose;
+}
+
+static void
+gtk_puzzle_piece_init (GtkPuzzlePiece *self)
+{
+}
+
+/* And finally, we add a constructor.
+ * It is declared in the header so that the other examples
+ * can use it.
+ */
+GdkPaintable *
+gtk_puzzle_piece_new (GdkPaintable *puzzle,
+                      guint         x,
+                      guint         y,
+                      guint         width,
+                      guint         height)
+{
+  GtkPuzzlePiece *self;
+
+  /* These are sanity checks, so that we get warnings if we accidentally
+   * do anything stupid. */
+  g_return_val_if_fail (GDK_IS_PAINTABLE (puzzle), NULL);
+  g_return_val_if_fail (width > 0, NULL);
+  g_return_val_if_fail (height > 0, NULL);
+  g_return_val_if_fail (x < width, NULL);
+  g_return_val_if_fail (y < height, NULL);
+
+  self = g_object_new (GTK_TYPE_PUZZLE_PIECE, NULL);
+
+  self->puzzle = g_object_ref (puzzle);
+  g_signal_connect_swapped (puzzle, "invalidate-contents", G_CALLBACK (gdk_paintable_invalidate_contents), 
self);
+  g_signal_connect_swapped (puzzle, "invalidate-size", G_CALLBACK (gdk_paintable_invalidate_size), self);
+  self->x = x;
+  self->y = y;
+  self->width = width;
+  self->height = height;
+
+  return GDK_PAINTABLE (self);
+}
+
+/* Here are the accessors that we need to inspect the puzzle
+ * pieces in other code.
+ */
+GdkPaintable *
+gtk_puzzle_piece_get_puzzle (GtkPuzzlePiece *self)
+{
+  /* Add sanity checks here, too.
+   * If you make a habit out of this, you can always rely
+   * on your code having sanity checks, which makes it
+   * way easier to debug.
+   */
+  g_return_val_if_fail (GTK_IS_PUZZLE_PIECE (self), NULL);
+
+  return self->puzzle;
+}
+
+guint
+gtk_puzzle_piece_get_x (GtkPuzzlePiece *self)
+{
+  g_return_val_if_fail (GTK_IS_PUZZLE_PIECE (self), 0);
+
+  return self->x;
+}
+
+guint
+gtk_puzzle_piece_get_y (GtkPuzzlePiece *self)
+{
+  g_return_val_if_fail (GTK_IS_PUZZLE_PIECE (self), 0);
+
+  return self->y;
+}
+
diff --git a/demos/gtk-demo/puzzlepiece.h b/demos/gtk-demo/puzzlepiece.h
new file mode 100644
index 0000000000..b7fc97dc05
--- /dev/null
+++ b/demos/gtk-demo/puzzlepiece.h
@@ -0,0 +1,23 @@
+#ifndef __PUZZLE_PIECE_H__
+#define __PUZZLE_PIECE_H__
+
+#include <gtk/gtk.h>
+
+/* First, add the boilerplate for the object itself.
+ */
+#define GTK_TYPE_PUZZLE_PIECE (gtk_puzzle_piece_get_type ())
+G_DECLARE_FINAL_TYPE (GtkPuzzlePiece, gtk_puzzle_piece, GTK, PUZZLE_PIECE, GObject)
+
+/* Then, declare all constructors */
+GdkPaintable *  gtk_puzzle_piece_new            (GdkPaintable           *puzzle,
+                                                 guint                   x,
+                                                 guint                   y,
+                                                 guint                   width,
+                                                 guint                   height);
+
+/* Next, add the getters and setters for object properties */
+GdkPaintable *  gtk_puzzle_piece_get_puzzle     (GtkPuzzlePiece         *self);
+guint           gtk_puzzle_piece_get_x          (GtkPuzzlePiece         *self);
+guint           gtk_puzzle_piece_get_y          (GtkPuzzlePiece         *self);
+
+#endif /* __PUZZLE_PIECE_H__ */
diff --git a/demos/gtk-demo/sliding_puzzle.c b/demos/gtk-demo/sliding_puzzle.c
new file mode 100644
index 0000000000..0ba76155d8
--- /dev/null
+++ b/demos/gtk-demo/sliding_puzzle.c
@@ -0,0 +1,303 @@
+/* Sliding puzzle
+ *
+ * This demo demonstrates how to use gestures and paintables to create a
+ * small sliding puzzle game.
+ *
+ */
+
+#include <gtk/gtk.h>
+
+/* Include the header for the puzzle piece */
+#include "puzzlepiece.h"
+
+static GtkWidget *window = NULL;
+
+static gboolean solved = TRUE;
+static guint width = 6;
+static guint height = 6;
+static guint pos_x;
+static guint pos_y;
+
+static gboolean
+move_puzzle (GtkWidget *grid,
+             int        dx,
+             int        dy)
+{
+  GtkWidget *pos, *next;
+  GdkPaintable *piece;
+  guint next_x, next_y;
+
+  /* We don't move anything if the puzzle is solved */
+  if (solved)
+    return FALSE;
+
+  /* Return FALSE if we can't move to where the call 
+   * wants us to move.
+   */
+  if ((dx < 0 && pos_x < -dx) ||
+      dx + pos_x >= width ||
+      (dy < 0 && pos_y < -dy) ||
+      dy + pos_y >= height)
+    return FALSE;
+
+  /* Compute the new position */
+  next_x = pos_x + dx;
+  next_y = pos_y + dy;
+
+  /* Get the current and next image */
+  pos = gtk_grid_get_child_at (GTK_GRID (grid), pos_x, pos_y);
+  next = gtk_grid_get_child_at (GTK_GRID (grid), next_x, next_y);
+
+  /* Move the displayed piece. */
+  piece = gtk_image_get_paintable (GTK_IMAGE (next));
+  gtk_image_set_from_paintable (GTK_IMAGE (pos), piece);
+  gtk_image_clear (GTK_IMAGE (next));
+
+  /* Update the current position */
+  pos_x = next_x;
+  pos_y = next_y;
+
+  /* Return TRUE because we successfully moved the piece */
+  return TRUE;
+}
+
+static void
+shuffle_puzzle (GtkWidget *grid)
+{
+  guint i, n_steps;
+
+  /* Do this many random moves */
+  n_steps = width * height * 50;
+
+  for (i = 0; i < n_steps; i++)
+    {
+      /* Get a random number for the direction to move in */
+      switch (g_random_int_range (0, 4))
+        {
+        case 0:
+          /* left */
+          move_puzzle (grid, -1, 0);
+          break;
+
+        case 1:
+          /* up */
+          move_puzzle (grid, 0, -1);
+          break;
+
+        case 2:
+          /* right */
+          move_puzzle (grid, 1, 0);
+          break;
+
+        case 3:
+          /* down */
+          move_puzzle (grid, 0, 1);
+          break;
+
+        default:
+          g_assert_not_reached ();
+          continue;
+        }
+    }
+}
+
+static gboolean
+check_solved (GtkWidget *grid)
+{
+  GtkWidget *image;
+  GdkPaintable *piece;
+  guint x, y;
+
+  /* Nothing to check if the puzzle is already solved */
+  if (solved)
+    return TRUE;
+
+  /* If the empty cell isn't in the bottom right,
+   * the puzzle is obviously not solved */
+  if (pos_x != width - 1 ||
+      pos_y != height - 1)
+    return FALSE;
+
+  /* Check that all pieces are in the right position */
+  for (y = 0; y < height; y++)
+    {
+      for (x = 0; x < width; x++)
+        {
+          image = gtk_grid_get_child_at (GTK_GRID (grid), x, y);
+          piece = gtk_image_get_paintable (GTK_IMAGE (image));
+
+          /* empty cell */
+          if (piece == NULL)
+            continue;
+
+          if (gtk_puzzle_piece_get_x (GTK_PUZZLE_PIECE (piece)) != x ||
+              gtk_puzzle_piece_get_y (GTK_PUZZLE_PIECE (piece)) != y)
+            return FALSE;
+        }
+    }
+
+  /* We solved the puzzle!
+   */
+  solved = TRUE;
+
+  /* Fill the empty cell to show that we're done.
+   */
+  image = gtk_grid_get_child_at (GTK_GRID (grid), 0, 0);
+  piece = gtk_image_get_paintable (GTK_IMAGE (image));
+
+  piece = gtk_puzzle_piece_new (gtk_puzzle_piece_get_puzzle (GTK_PUZZLE_PIECE (piece)),
+                                pos_x, pos_y,
+                                width, height);
+  image = gtk_grid_get_child_at (GTK_GRID (grid), pos_x, pos_y);
+  gtk_image_set_from_paintable (GTK_IMAGE (image), piece);
+
+  return TRUE;
+}
+
+static gboolean
+puzzle_key_pressed (GtkEventControllerKey *controller,
+                    guint                  keyval,
+                    guint                  keycode,
+                    GdkModifierType        state,
+                    GtkWidget             *grid)
+{
+  int dx, dy;
+
+  dx = 0;
+  dy = 0;
+
+  switch (keyval)
+    {
+    case GDK_KEY_KP_Left:
+    case GDK_KEY_Left:
+      /* left */
+      dx = -1;
+      break;
+
+    case GDK_KEY_KP_Up:
+    case GDK_KEY_Up:
+      /* up */
+      dy = -1;
+      break;
+
+    case GDK_KEY_KP_Right:
+    case GDK_KEY_Right:
+      /* right */
+      dx = 1;
+      break;
+
+    case GDK_KEY_KP_Down:
+    case GDK_KEY_Down:
+      /* down */
+      dy = 1;
+      break;
+
+    default:
+      /* We return FALSE here because we didn't handle the key that was pressed */
+      return FALSE;
+    }
+
+  if (!move_puzzle (grid, dx, dy))
+    {
+      /* Make the error sound and then return TRUE.
+       * We handled this key, even though we didn't
+       * do anything to the puzzle.
+       */
+      gtk_widget_error_bell (grid);
+      return TRUE;
+    }
+
+  check_solved (grid);
+
+  return TRUE;
+}
+
+static void
+start_puzzle (GdkPaintable *puzzle)
+{
+  GtkWidget *image, *grid;
+  GtkEventController *controller;
+  guint x, y;
+
+  /* Remove the old grid (if there is one) */
+  grid = gtk_bin_get_child (GTK_BIN (window));
+  if (grid)
+    gtk_container_remove (GTK_CONTAINER (window), grid);
+
+  /* Create a new grid */
+  grid = gtk_grid_new ();
+  gtk_widget_set_can_focus (grid, TRUE);
+  gtk_container_add (GTK_CONTAINER (window), grid);
+
+  /* Add a key event controller so people can use the arrow
+   * keys to move the puzzle */
+  controller = gtk_event_controller_key_new ();
+  g_signal_connect (controller, "key-pressed",
+                    G_CALLBACK (puzzle_key_pressed),
+                    grid);
+  gtk_widget_add_controller (GTK_WIDGET (grid), controller);
+
+  /* Make sure the cells have equal size */
+  gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
+  gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
+
+  /* Reset the variables */
+  solved = FALSE;
+  pos_x = width - 1;
+  pos_y = height - 1;
+
+  /* add an image for every cell */
+  for (y = 0; y < height; y++)
+    {
+      for (x = 0; x < width; x++)
+        {
+          GdkPaintable *piece;
+
+          /* Don't paint anything for the lsiding part of the video */
+          if (x == pos_x && y == pos_y)
+            piece = NULL;
+          else
+            piece = gtk_puzzle_piece_new (puzzle,
+                                          x, y,
+                                          width, height);
+          image = gtk_image_new_from_paintable (piece);
+          gtk_image_set_keep_aspect_ratio (GTK_IMAGE (image), FALSE);
+          gtk_image_set_can_shrink (GTK_IMAGE (image), TRUE);
+          gtk_grid_attach (GTK_GRID (grid),
+                           image,
+                           x, y,
+                           1, 1);
+        }
+    }
+
+  shuffle_puzzle (grid);
+}
+
+GtkWidget *
+do_sliding_puzzle (GtkWidget *do_widget)
+{
+  GdkPaintable *puzzle;
+
+  if (!window)
+    {
+      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+      gtk_window_set_display (GTK_WINDOW (window),
+                              gtk_widget_get_display (do_widget));
+      gtk_window_set_title (GTK_WINDOW (window), "Sliding Puzzle");
+      gtk_window_set_default_size (GTK_WINDOW (window), 300, 200);
+      g_signal_connect (window, "destroy",
+                        G_CALLBACK (gtk_widget_destroyed), &window);
+
+      /* Start a puzzle with a default image */
+      puzzle = GDK_PAINTABLE (gdk_texture_new_from_resource ("/sliding_puzzle/portland-rose.jpg"));
+      start_puzzle (puzzle);
+      g_object_unref (puzzle);
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_show (window);
+  else
+    gtk_widget_destroy (window);
+
+  return window;
+}


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