summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2018-06-04 05:41:44 +0200
committerMatthias Clasen <mclasen@redhat.com>2018-06-07 07:56:48 -0400
commitd93b9bedc38dc747d7f1cbef2ebdb4518e445203 (patch)
treeb18417a5bd1955d9fcc583000aa38842f67c54c5
parent5c24bbf00c1208a7550c723d46ef501dd644959a (diff)
downloadgtk+-d93b9bedc38dc747d7f1cbef2ebdb4518e445203.tar.gz
demo: Add the sliding puzzle demo
-rw-r--r--demos/gtk-demo/demo.gresource.xml12
-rw-r--r--demos/gtk-demo/meson.build3
-rw-r--r--demos/gtk-demo/puzzlepiece.c220
-rw-r--r--demos/gtk-demo/puzzlepiece.h23
-rw-r--r--demos/gtk-demo/sliding_puzzle.c303
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;
+}