[cairo-commit] cairo-demo/gtk_slide COPYING, NONE, 1.1 ChangeLog, NONE, 1.1 Makefile, NONE, 1.1 README, NONE, 1.1 cairo_custom.c, NONE, 1.1 cairo_custom.h, NONE, 1.1 gtk_slide.c, NONE, 1.1 puzzle.c, NONE, 1.1 puzzle.h, NONE, 1.1

Behdad Esfahbod commit at pdx.freedesktop.org
Thu Aug 11 23:55:18 PDT 2005


Committed by: behdad

Update of /cvs/cairo/cairo-demo/gtk_slide
In directory gabe:/tmp/cvs-serv26486

Added Files:
	COPYING ChangeLog Makefile README cairo_custom.c 
	cairo_custom.h gtk_slide.c puzzle.c puzzle.h 
Log Message:
2005-08-12  Behdad Esfahbod  <behdad at behdad.org>

        * *: port to Gtk+ >= 2.7 instead of GtkCairo.  Change the
        name everywhere (in filenames too) to reflect this change.

(used to be in gtkcairo_slide)


--- NEW FILE: COPYING ---
(This appears to be a binary file; contents omitted.)

--- NEW FILE: ChangeLog ---
2005-08-12  Behdad Esfahbod  <behdad at behdad.org>

	* *: port to Gtk+ >= 2.7 instead of GtkCairo.  Change the
	name everywhere (in filenames too) to reflect this change.

2005-08-08  Øyvind Kolås  <pippin at freedesktop.org>

	* puzzle.c: updated to work with newer cairo api.

2005-04-29  Øyvind Kolås  <pippin at freedesktop.org>

	* gtk_slide.c: reordered functions, made game easier and added
	welcome scroll/splash.

2005-01-20  Carl Worth  <cworth at cworth.org>

	* puzzle.c: Add include of stdio.h now that cairo.h doesn't
	provide it.

2004-11-11  Oeyvind Kolaas  <pippin at freedesktop.org>

	* *.[ch]: reindentation to GNU coding style, and general code
	cleanups.

2004-06-23  OEyvind Kolaas  <pippin at freedesktop.org>

	* puzzle.c : moved the font selection out of the loop drawing the
	pieces, this leads to a speedup making gtk_slide useable.

2004-05-13  OEyvind Kolaas  <pippin at freedesktop.org>

	* *.[ch] : removed gdk_cairo_set_color, replaced with
	gtk_cairo_set_gdk_color from GtkCairo

2004-05-11  OEyvind Kolaas  <pippin at freedesktop.org>

	* *.[ch] : replacing ct with cr

2004-03-01  OEyvind Kolaas  <pippin at freedesktop.org>

	* cairo_custom.[ch], puzzle.c : replaced cairo_set_gtk_color with
                                    gdk_cairo_set_color

2004-02-15  OEyvind Kolaas  <pippin at freedesktop.org>

	* gtk_slide.c : removed superflous cairo_save/cairo_restor pairs

2004-02-14  OEyvind Kolaas  <pippin at freedesktop.org> 

	Initial import

--- NEW FILE: Makefile ---
SRCS   = $(wildcard *.c)
OBJS   = $(SRCS:.c=.o)

LIBS   +=`pkg-config --libs gtk+-2.0`
CFLAGS +=`pkg-config --cflags gtk+-2.0` -g
CC     = gcc

all: dep gtk_slide
gtk_slide: $(OBJS)
	$(CC) -o $@ $(OBJS) $(LIBS) $(LDFLAGS)
	@echo

clean:	
	rm -f *~ gtk_slide .depend *.o *.orig
dep: 
	$(MAKE) .depend
.depend: $(SRCS) *.c *.h
	$(CC) -MM $(CFLAGS) $(SRCS) 1>.depend

--- NEW FILE: README ---
gtk_slide
==============

Custom GTK+ widget and application, demonstrating Cairo support

A cairo implementation of the classic sliding block puzzle, where you're supposed
to slide the blocks into a configuration where the blocks are numbered incrementally.

Implementation
==============

GtkSlide is well behaved and creates a puzzle widget that is derived from the
GtkDrawingArea widget, this is the proper object oriented way of deriving a
custom widget from GtkDrawingArea. Most other examples use the api directly.

Dependencies
============

GtkSlide depends on Gtk+ >= 2.7, and it's dependencies

2004 (c) Øyvind Kolås, pippin at freedesktop.org

--- NEW FILE: cairo_custom.c ---
#include "cairo_custom.h"

void
cairo_rectangle_round (cairo_t *cr,
                       double x0,    double y0,
                       double width, double height,
                       double radius)
{
  double x1,y1;

  x1=x0+width;
  y1=y0+height;

  if (!width || !height)
    return;
  if (width/2<radius)
    {
      if (height/2<radius)
        {
          cairo_move_to  (cr, x0, (y0 + y1)/2);
          cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1)/2, y0);
          cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1)/2);
          cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0)/2, y1);
          cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1)/2);
        }
      else
        {
          cairo_move_to  (cr, x0, y0 + radius);
          cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1)/2, y0);
          cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
          cairo_line_to (cr, x1 , y1 - radius);
          cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0)/2, y1);
          cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
        }
    }
  else
    {
      if (height/2<radius)
        {
          cairo_move_to  (cr, x0, (y0 + y1)/2);
          cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
          cairo_line_to (cr, x1 - radius, y0);
          cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1)/2);
          cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
          cairo_line_to (cr, x0 + radius, y1);
          cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1)/2);
        }
      else
        {
          cairo_move_to  (cr, x0, y0 + radius);
          cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
          cairo_line_to (cr, x1 - radius, y0);
          cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
          cairo_line_to (cr, x1 , y1 - radius);
          cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
          cairo_line_to (cr, x0 + radius, y1);
          cairo_curve_to (cr, x0, y1, x0, y1, x0, y1- radius);
        }
    }

  cairo_close_path (cr);
}

void
cairo_edgeline (cairo_t *cr,
                double x0,
                double y0,
                double x1,
                double y1,
                double backoff)
{
  y0 = y0 - 1;
  y1 = y1 + 1;

  cairo_move_to (cr, x0, y0);
  if (y1 > y0 + backoff / 2)
    {
      cairo_curve_to (cr, x0, (y0 + y1) / 2, x1, (y0 + y1) / 2, x1, y1);
    }
  else
    {
      cairo_curve_to (cr, x0,
                      y0 + (backoff / 4 + (y0 + backoff / 2 - y1)), x1,
                      y1 - (backoff / 4 + (y0 + backoff / 2 - y1)), x1,
                      y1);
    }
}

--- NEW FILE: cairo_custom.h ---
#ifndef CAIRO_CUSTOM_H
#define CAIRO_CUSTOM_H
#include <cairo.h>

void
cairo_rectangle_round (cairo_t * cr,
                       double x0,
                       double y0,
                       double width,
                       double height,
                       double radius);

void
cairo_edgeline (cairo_t * cr,
                double x0,
                double y0,
                double x1,
                double y1,
                double backoff);

#endif /* CAIRO_CUSTOM_H */

--- NEW FILE: gtk_slide.c ---
/* Gtk Slide, a sample game built around the puzzle widget */

/* since the puzzle widget leaves the drawing behavior in a sane state, we can hook up the menu / score
 * display on top of it, this might be considered an evil hack, but might make some programming easier
 */

#include <gtk/gtk.h>
#include "puzzle.h"

#define INITIAL_WIDTH  200
#define INITIAL_HEIGHT 210

GtkWidget *puzzle_window;
GtkWidget *puzzle;

GList  *messages = NULL;
double  y_pos = 0.0;
double  y_inc = 0.1;

double fade      = 0.0;

int    rows      = 3;
int    cols      = 3;
int    shuffles = 15;

gboolean update_messages (gpointer data)
{
  if (messages)
    {
      y_pos += y_inc;
      if (y_pos > 1.0)
      {
        char *msg = messages->data;
        messages = g_list_remove (messages, msg);
        g_free (msg);
        y_pos = 0.0;
      }
      gtk_widget_queue_draw (puzzle);
    }
  return TRUE;
}

void add_message (const char *message)
{
  messages = g_list_append (messages, g_strdup(message));
}

static GtkWidget *
puzzle_window_new (void);

gint
main (gint argc,
      gchar *argv[])
{

  gtk_init (&argc, &argv);

  puzzle_window = puzzle_window_new ();
  gtk_widget_show (puzzle_window);
  add_message ("");
  add_message ("Gtk Slide");
  add_message ("");
  add_message ("order the boxes");
  g_timeout_add (100, update_messages ,puzzle);

  gtk_main ();

  return 0;
}

static void paint_status  (GtkWidget      *wdiget,
                           GdkEventExpose *eev,
                           gpointer        user_data);

static void
puzzle_solved (GtkWidget *widget,
               gpointer   data);

static GtkWidget *
puzzle_window_new (void) {
  GtkWidget *self;
  GtkWidget *vbox;

  self = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (self), "Sliding Gtk Puzzle");
  g_signal_connect (G_OBJECT (self), "delete-event",
                    G_CALLBACK (gtk_main_quit), NULL);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 0);

  puzzle = puzzle_new ();

  g_signal_connect (G_OBJECT (puzzle), "expose-event",
    G_CALLBACK (paint_status), NULL);

  g_object_set (G_OBJECT (puzzle), "rows", rows, NULL);
  g_object_set (G_OBJECT (puzzle), "cols", cols, NULL);
  g_object_set (G_OBJECT (puzzle), "shuffles", shuffles, NULL);

  gtk_widget_set_usize (GTK_WIDGET (puzzle), INITIAL_WIDTH, INITIAL_HEIGHT);

  g_signal_connect (G_OBJECT (puzzle), "puzzle_solved",
                    G_CALLBACK (puzzle_solved), puzzle);

  gtk_container_add (GTK_CONTAINER (vbox), puzzle);

  gtk_container_add (GTK_CONTAINER (self), vbox);
  gtk_widget_show_all (vbox);

  return self;
}


static void paint_status  (GtkWidget      *widget,
                           GdkEventExpose *eev,
                           gpointer        user_data)
{
  cairo_t *cr;
  double w = widget->allocation.width;
  double h = widget->allocation.height;

  if (!messages)  /* bail out early, if nothing to paint */
    return;
  
  cr = gdk_cairo_create (widget->window);
    {
    if (fade>0.01)
      {
        cairo_set_source_rgba (cr, 0,0,0, fade);
        cairo_rectangle (cr, 0, 0, w*1.0, h*1.0);
        cairo_fill (cr);
      }

#define FONT_HEIGHT 0.1
#define LINE_HEIGHT 1.3

    cairo_set_font_size (cr, h*FONT_HEIGHT);
    cairo_select_font_face (cr, "Sans", 0, 0);

      {
        GList *item = messages;
        double x,y;

        /*
        char utf8[42];
        snprintf (utf8, 42, "%i×%i @%i", cols, rows, shuffles);
        */

        y = FONT_HEIGHT * LINE_HEIGHT - y_pos * FONT_HEIGHT * LINE_HEIGHT;
        while (item)
          {
            cairo_text_extents_t extents;
            const char *utf8;

            utf8 = item->data;
            cairo_text_extents (cr, utf8, &extents);
            x = 0.5 - (extents.width / 2 + extents.x_bearing) / w;
            cairo_move_to (cr, w*x, h*y);
            cairo_text_path (cr, utf8);

            cairo_save (cr);
              {
              cairo_set_source_rgba (cr, 0,0,0,0.2);
              cairo_set_line_width (cr, h*0.02);
              cairo_stroke (cr);
              }
            cairo_restore (cr);
            cairo_set_source_rgba (cr, 1,1,1,1.0);
            cairo_fill (cr);

            y+= FONT_HEIGHT * LINE_HEIGHT;
            item = g_list_next (item);
          }

        
      }
    }
  cairo_destroy (cr);
  
}

static void
puzzle_solved (GtkWidget *widget,
               gpointer   data)
{
  GtkWidget *dialog;
  GtkWidget *label;
  GtkWidget *parent;

  parent = gtk_widget_get_toplevel (widget);

  dialog = gtk_dialog_new_with_buttons ("GtkSlide - solved",
                                        GTK_WINDOW (parent),
                                        GTK_DIALOG_MODAL,
                                        GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
                                        NULL);

  label = gtk_label_new ("You solved the puzzle!");
  gtk_misc_set_padding (GTK_MISC (label), 20, 20);
  gtk_widget_show (label);

  gtk_container_add (GTK_CONTAINER (GTK_DIALOG(dialog)->vbox), label);
  gtk_widget_show (dialog);

  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_main_quit ();
}

--- NEW FILE: puzzle.c ---
#include <stdio.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include "cairo_custom.h"

#include "puzzle.h"
#include "math.h"    /* floor() fabs() */

/** game specific functions, defined at end of this file */

static void
board_init (GtkWidget *widget);

static int
query_pos (Puzzle *puzzle,
           gint    x, 
           gint    y);

static void
push_block (Puzzle *puzzle, 
            gint    block_no, 
            gdouble xdelta,
            gdouble ydelta);

static void
draw_board (Puzzle *puzzle, 
            cairo_t *cr);

static gint
board_solved (Puzzle *puzzle);

/** drawing_area derived widget **/

static GObjectClass *parent_class = NULL;

typedef struct
{
  double x;
  double y;
  char  label[16];
} PuzzleItem;

struct _Puzzle {
  GtkDrawingArea drawing_area;

  double ratio_x;
  double ratio_y;

  double cursorx;
  double cursory;

  PuzzleItem *item;

  gint grabbed;

  gint rows;
  gint cols;
  gint shuffles;
};

struct _PuzzleClass {
  GtkDrawingAreaClass parent_class;
};


enum {
  PUZZLE_SOLVED_SIGNAL,
  LAST_SIGNAL
};

enum {
  PUZZLE_ROWS=1,
  PUZZLE_COLS,
  PUZZLE_SHUFFLES
};

static void
set_property (GObject      *object,
              guint         property_id,
              const GValue *value,
              GParamSpec   *psec)
{
  Puzzle *puzzle = PUZZLE (object);

  switch (property_id)
    {

    case PUZZLE_COLS:
      puzzle->cols = g_value_get_int (value);
      board_init (GTK_WIDGET (puzzle));
      break;

    case PUZZLE_ROWS:
      puzzle->rows = g_value_get_int (value);
      board_init (GTK_WIDGET (puzzle));
      break;

    case PUZZLE_SHUFFLES:
      puzzle->shuffles = g_value_get_int (value);
      board_init (GTK_WIDGET (puzzle));
      break;

    default:
      g_assert (FALSE);
      break;
  }
}

static void
get_property (GObject *object,
                guint  property_id,
               GValue *value,
           GParamSpec *pspec)
{
  Puzzle *puzzle = PUZZLE (object);

  switch (property_id)
    {
    case PUZZLE_ROWS:
      g_value_set_int (value, puzzle->rows);
      break;

    case PUZZLE_COLS:
      g_value_set_int (value, puzzle->rows);
      break;

    case PUZZLE_SHUFFLES:
      g_value_set_int (value, puzzle->shuffles);
      break;

    default:
      g_assert (FALSE);
      break;
  }
}

static void
finalize (GObject *object)
  {

    Puzzle *puzzle = PUZZLE (object);

    if (puzzle->item)
      {
        free (puzzle->item);
      }

    parent_class->finalize (object);
}

static gint puzzle_signals[LAST_SIGNAL] = { 0 };

static void
puzzle_class_init (PuzzleClass * class)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (class);
  parent_class = g_type_class_peek (GTK_TYPE_DRAWING_AREA);

  gobject_class->finalize =    finalize;
  gobject_class->get_property = get_property;
  gobject_class->set_property = set_property;

  g_object_class_install_property (gobject_class,
                                   PUZZLE_ROWS,
                                   g_param_spec_int ("rows",
                                                     "Number of rows",
                                                     "",
                                                     0,
                                                     20,
                                                     0,
                                                     G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class,
                                   PUZZLE_COLS,
                                   g_param_spec_int ("cols",
                                                     "Number of cols",
                                                     "",
                                                     0,
                                                     20,
                                                     0,
                                                     G_PARAM_READWRITE));

  g_object_class_install_property (gobject_class,
                                   PUZZLE_SHUFFLES,
                                   g_param_spec_int ("shuffles",
                                                     "Number of moves shuffling board, (approximate)",
                                                     "",
                                                     0,
                                                     1000000,
                                                     0,
                                                     G_PARAM_READWRITE));

  puzzle_signals[PUZZLE_SOLVED_SIGNAL] =
      g_signal_new ("puzzle_solved",
                    G_TYPE_FROM_CLASS (gobject_class),
                    G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL);
}

static void
puzzle_init (Puzzle * puzzle)
{
  puzzle->cursorx = 0;
  puzzle->cursory = 0;
  puzzle->rows = 4;
  puzzle->cols = 4;
  puzzle->shuffles = 200;
  puzzle->grabbed = -1;
  puzzle->ratio_x = 1;
  puzzle->ratio_y = 1;
  board_init (GTK_WIDGET (puzzle));
  gtk_widget_show_all (GTK_WIDGET (puzzle));
}

GType
puzzle_get_type (void)
{
  static GType ge_type = 0;

  if (!ge_type)
    {
      static const GTypeInfo ge_info =
        {
             sizeof (PuzzleClass),
             NULL,
             NULL,
             (GClassInitFunc) puzzle_class_init,
             NULL,
             NULL,
             sizeof (Puzzle),
             0,
             (GInstanceInitFunc) puzzle_init,
        };

      ge_type = g_type_register_static (GTK_TYPE_DRAWING_AREA,
                                        "Puzzle",
                                        &ge_info,
                                        0);
    }
  return ge_type;
}

/* prototypes for event functions registered with the object */

static gboolean event_press (GtkWidget      *widget,
                             GdkEventButton *bev,
                             gpointer        user_data);

static gboolean event_release (GtkWidget      *widget,
                               GdkEventButton *bev,
                               gpointer        user_data);

static gboolean event_motion (GtkWidget      *widget,
                              GdkEventMotion *mev,
                              gpointer        user_data);

static void puzzle_class_init (PuzzleClass *class);

/* prototpe for the redraw callback we register with drawing_area */

static void paint  (GtkDrawingArea *drawing_area,
		    GdkEventExpose *eev,
                    gpointer  user_data);

GtkWidget *
puzzle_new (void)
{
  GtkWidget *widget = GTK_WIDGET (g_object_new (puzzle_get_type (), NULL));
  Puzzle *puzzle = PUZZLE (widget);

  gtk_widget_set_events (widget,
                         GDK_EXPOSURE_MASK        |
                         GDK_POINTER_MOTION_HINT_MASK |  /* since we do dragging, let the ui update request redraw events */
                         GDK_BUTTON1_MOTION_MASK  |
                         GDK_BUTTON2_MOTION_MASK  |
                         GDK_BUTTON3_MOTION_MASK  |
                         GDK_BUTTON_PRESS_MASK    |
                         GDK_BUTTON_RELEASE_MASK);

  gtk_widget_set_size_request (widget, 256, 256);

  g_signal_connect (G_OBJECT (widget), "expose-event",
                    G_CALLBACK (paint), puzzle);

  g_signal_connect (G_OBJECT (widget), "motion_notify_event",
                    G_CALLBACK (event_motion), puzzle);

  g_signal_connect (G_OBJECT (widget), "button_press_event",
                    G_CALLBACK (event_press), puzzle);

  g_signal_connect (G_OBJECT (widget), "button_release_event",
                    G_CALLBACK (event_release), puzzle);

  return widget;
}

/* the actually user interface event functions */

static gboolean
event_press (GtkWidget      *widget,
             GdkEventButton *bev,
             gpointer        user_data)
{
  Puzzle *puzzle = PUZZLE (user_data);

  puzzle->cursorx = bev->x;
  puzzle->cursory = bev->y;

  switch (bev->button)
    {
      case 1:
        puzzle->grabbed = query_pos (puzzle, bev->x, bev->y);
        /* request a redraw, since we've changed the state of our widget */
        gtk_widget_queue_draw (GTK_WIDGET (puzzle));
        break;
    }
  return FALSE;
}

static gboolean
event_release (GtkWidget * widget,
               GdkEventButton * bev, gpointer user_data) {
    Puzzle *puzzle = PUZZLE (user_data);

    switch (bev->button)
      {
        case 1:
          puzzle->grabbed = -1;

          /* request a redraw, since we've changed the state of our widget */
          gtk_widget_queue_draw (GTK_WIDGET (puzzle));
          break;
      }
    return FALSE;
}

static gboolean
event_motion (GtkWidget      *widget,
              GdkEventMotion *mev,
              gpointer        user_data)
{
    Puzzle *puzzle = PUZZLE (user_data);

    if (puzzle->grabbed>=0)
      {
        double xdelta = (mev->x-puzzle->cursorx) / puzzle->ratio_x;
        double ydelta = (mev->y-puzzle->cursory) / puzzle->ratio_y;

        push_block (puzzle, puzzle->grabbed, xdelta, ydelta);

        if (board_solved (puzzle))
          {
            g_signal_emit (G_OBJECT (puzzle),
                           puzzle_signals
                           [PUZZLE_SOLVED_SIGNAL], 0);
          }


        /* request a redraw, since we've changed the state of our widget */
        gtk_widget_queue_draw (GTK_WIDGET (puzzle));

        puzzle->cursorx = mev->x;
        puzzle->cursory = mev->y;
      }
    return FALSE;
}

/* actual paint function */

static void
paint (GtkDrawingArea *drawing_area,
       GdkEventExpose *eev,
       gpointer  user_data)
{
  GtkWidget *widget = GTK_WIDGET (drawing_area);
  Puzzle *puzzle = PUZZLE (drawing_area);
  cairo_t *cr = gdk_cairo_create (widget->window);

  puzzle->ratio_x = (double) widget->allocation.width/puzzle->cols;
  puzzle->ratio_y = (double) widget->allocation.height/puzzle->rows;

  draw_board (puzzle, cr);

  if (cairo_status (cr))
    {
      fprintf (stderr, "Cairo is unhappy: %s\n",
               cairo_status_to_string (cairo_status (cr)));
    }

  cairo_destroy (cr);

  /* since we are requesting motion hints,. make sure another motion
     event is delivered after redrawing the ui */
  gdk_window_get_pointer (widget->window, NULL, NULL, NULL);
}


/*************************************************************************/
/***         end of GtkDrawingArea example, actual game logic follows        ***/
/*************************************************************************/


static void
get_empty (Puzzle *puzzle,
           gint   *col,
           gint   *row);

static int
get_at (Puzzle *puzzle,
        gint    col,
        gint    row);

/* shuffle an initialized board (important, do not
   shuffle a board where the coordinates have changed
   since initialization, since the shuffeling presumes that
   the order haven't changed.
*/

#include <assert.h>

static
void puzzle_shuffle (Puzzle *puzzle,
                     gint    shuffles)
{
  while (shuffles--)
    {
      gint empty_x;
      gint empty_y;
      gint block_no;
      gint direction = g_random_int_range (0, 5);

      get_empty (puzzle, &empty_x, &empty_y);

      switch (direction)
        {
        case 0:
          if ((block_no=get_at (puzzle, empty_x-1, empty_y))>=0)
            {
              push_block (puzzle, block_no, 0.5, 0  );
              push_block (puzzle, block_no, 0.5, 0  );
            }
          break;
        case 1:
          if ((block_no=get_at (puzzle, empty_x+1, empty_y))>=0)
            {
              push_block (puzzle, block_no, -0.5, 0  );
              push_block (puzzle, block_no, -0.5, 0  );
            }
          break;
        case 2:
          if ((block_no=get_at (puzzle, empty_x, empty_y-1))>=0)
            {
              push_block (puzzle, block_no, 0, 0.5);
              push_block (puzzle, block_no, 0, 0.5);
            }
          break;
        case 3:
          if ((block_no=get_at (puzzle, empty_x, empty_y+1))>=0)
            {
              push_block (puzzle, block_no, 0, -0.5);
              push_block (puzzle, block_no, 0, -0.5);
            }
          break;
        }
    }
}

static void
get_empty (Puzzle *puzzle,
             gint *col,
             gint *row)
{
  if (!col || !row)
    return;
  for (*row=0; *row < puzzle->rows; (*row)++)
     for (*col=0; *col < puzzle->cols; (*col)++)
       {
          gint item_no;
          gint found=0;
          for (item_no=0;item_no< puzzle->cols*puzzle->rows-1; item_no++)
            {
              if (puzzle->item[item_no].x == *col && puzzle->item[item_no].y == *row)
                found++;
            }
          if (!found)
            return;
       }
}

static int
get_at (Puzzle *puzzle,
        gint    col,
        gint    row)
{
  gint item_no;

  for (item_no=0;item_no< puzzle->cols*puzzle->rows-1; item_no++)
    {
      if (puzzle->item[item_no].x == col &&
          puzzle->item[item_no].y == row)
        return item_no;
    }
  return -1;
}

/* initialize a board of specified size */
static void
board_init (GtkWidget *widget)
{
  Puzzle *puzzle = PUZZLE (widget);
  gint row, col;

  if (puzzle->item)
    {
      free (puzzle->item);
    }

  puzzle->item = malloc (sizeof (PuzzleItem) * puzzle->rows * puzzle->cols);

  for (row=0;row<puzzle->rows;row++)
    {
      for (col=0;col<puzzle->cols;col++)
        {
          if (col==puzzle->cols-1 && row==puzzle->rows-1)
            {
              puzzle->item[row*puzzle->cols + col].x=col;
              puzzle->item[row*puzzle->cols + col].y=row;
              sprintf (puzzle->item[row*puzzle->cols + col].label, "-");
            }
          else 
            {
              puzzle->item[row*puzzle->cols + col].x=col;
              puzzle->item[row*puzzle->cols + col].y=row;
              sprintf (puzzle->item[row*puzzle->cols + col].label, "%i", row*puzzle->cols + col +1);
            }
        }
    }

  puzzle_shuffle (puzzle, puzzle->shuffles);
  gtk_widget_queue_draw (GTK_WIDGET (puzzle));
}

static gint
board_solved (Puzzle *puzzle)
{
  gint item_no=0;

  for (item_no=0;item_no<puzzle->rows * puzzle->cols-1;item_no++ )
    {
      PuzzleItem *item= & (puzzle->item [item_no]);

      gint row = item_no/puzzle->cols;
      gint col = item_no%puzzle->cols;

      if ( item->x != col || item->y != row)
        return 0;
    }
  return 1;
}


/* query which block is at the given mouse coordinates,
   returns -1 if no block was found
*/
static gint
query_pos (Puzzle *puzzle,
           gint    x,
           gint    y)
{
  int item_no;
  cairo_t *cr = gdk_cairo_create (GTK_WIDGET (puzzle)->window);

  if (!cr)
    {
    }
  cairo_save (cr);

  cairo_scale (cr, puzzle->ratio_x, puzzle->ratio_y);
  cairo_translate (cr, 0.5, 0.5);

  for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
    {
      PuzzleItem *item = & (puzzle->item [item_no]);

      cairo_save (cr);
      cairo_rectangle_round (cr, item->x-0.4, item->y-0.4, 0.8, 0.8, 0.4);

      if (cairo_in_fill (cr, (x)/ puzzle->ratio_x -0.5, (y) / puzzle->ratio_y -0.5))
        {
              cairo_restore (cr);
              cairo_restore (cr);
          return item_no;
        }
      cairo_restore (cr);
    }

  cairo_restore (cr);
  return -1;
}

static gboolean
item_is_aligned (PuzzleItem *item)
{
  return (fabs(floor(item->x)-item->x)<0.01 &&
          fabs(floor(item->y)-item->y)<0.01);
}

static void
draw_item (cairo_t    *cr,
           GtkStyle   *style,
           PuzzleItem *item,
           gboolean    grabbed)
{
  gboolean aligned = item_is_aligned (item);

  cairo_rectangle_round (cr, item->x-0.36, item->y-0.36, 0.8, 0.8, 0.4);
  cairo_set_line_width (cr, 0.07);

  if (grabbed)
    {
      gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
    }
  else
    {
      if (aligned)
        gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_NORMAL]);
      else
        gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_ACTIVE]);
    }

  cairo_stroke (cr);

  cairo_rectangle_round (cr, item->x-0.4, item->y-0.4, 0.8, 0.8, 0.4);
  cairo_save (cr);

    if (grabbed)
      {
        gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]);
      }
    else
      {
        if (aligned)
          gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
        else
          gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_NORMAL]);
      }

    cairo_fill (cr);
  cairo_restore (cr);

  if (grabbed)
    {
      gdk_cairo_set_source_color (cr, &style->mid[GTK_STATE_SELECTED]);
    }
  else
    {
     if (aligned)
       gdk_cairo_set_source_color (cr, &style->mid[GTK_STATE_NORMAL]);
     else
       gdk_cairo_set_source_color (cr, &style->dark[GTK_STATE_ACTIVE]);
    }
   
  cairo_set_line_width (cr, 0.05);
  cairo_stroke (cr);

  {
    cairo_text_extents_t extents;
    cairo_text_extents (cr, item->label, &extents);

    cairo_move_to (cr, item->x-extents.width/2 - extents.x_bearing,
                        item->y - extents.height/2 - extents.y_bearing);

  }
    
  if (grabbed)
    {
      gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_SELECTED]);
    }
  else
    {
      if (aligned)
        gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
      else
        gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
    }
   cairo_show_text (cr, item->label);
}
            

/* draw the game board using the provided cairo context
*/
static void
draw_board (Puzzle  *puzzle,
            cairo_t *cr)
{
  int item_no;
  GtkStyle *style;

  style = GTK_WIDGET (puzzle)->style;

  cairo_save (cr);
    cairo_select_font_face (cr, "sans", 0, 0);
    cairo_set_font_size (cr, 0.35);
    cairo_scale (cr, puzzle->ratio_x, puzzle->ratio_y);
    cairo_translate (cr, 0.5, 0.5);

    for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
      draw_item (cr, style, &(puzzle->item [item_no]), item_no == puzzle->grabbed);

  cairo_restore (cr);
}


/* returns which block would be pushed by shifting 'block' the given amount
   in x direction

   return: >=0 block id
            -1 none
            -2 going off board
*/
static int
who_is_pushed_x (Puzzle *puzzle,
                 int     block_no,
                 double  xdelta)
{
  int item_no;
  PuzzleItem *block= & (puzzle->item [block_no]);

  if (xdelta<0)
    {
      if (block->x+xdelta<0)
        return -2;

      for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
        {
          PuzzleItem *item= & (puzzle->item [item_no]);
          if (block->x > item->x && block->x + xdelta - 1 <= item->x
              && item->y < block->y + 1 && item->y > block->y -1  )
            {
              return item_no;
            }
        }
    }
  else
    {
      if (block->x+xdelta>puzzle->cols-1)
        return -2;

      for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
        {
          PuzzleItem *item= & (puzzle->item [item_no]);
          if (block->x < item->x && block->x + xdelta + 1 >= item->x
              && item->y < block->y + 1 && item->y > block->y -1  )
            {
              return item_no;
            }
        }
    }
  return -1;
}

/* returns which block would be pushed by shifting 'block' the given amount
   in y direction

   return: >=0 block id
            -1 none
            -2 going off board
*/
static int
who_is_pushed_y (Puzzle  *puzzle,
                 gint     block_no,
                 gdouble  ydelta)
{
  int item_no;
  PuzzleItem *block= & (puzzle->item [block_no]);

  if (ydelta<0)
    {
      if (block->y+ydelta<0)
          return -2;

      for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
        {
          PuzzleItem *item= & (puzzle->item [item_no]);
          if (block->y > item->y && block->y + ydelta - 1 <= item->y
              && item->x < block->x + 1 && item->x > block->x -1  )
            {

              return item_no;
            }
        }
    }
  else
    {
      if (block->y+ydelta>puzzle->rows-1)
        return -2;

      for (item_no=0;item_no<puzzle->rows*puzzle->cols-1;item_no++)
        {
          PuzzleItem *item= & (puzzle->item [item_no]);
          if (block->y < item->y && block->y + ydelta + 1 >= item->y
              && item->x < block->x + 1 && item->x > block->x -1  )
            {
              return item_no;
            }
        }
    }
  return -1;
}

/* attempt to push a block the given delta in x and y directions */
static void
push_block (Puzzle  *puzzle,
            gint     block_no,
            gdouble  xdelta,
            gdouble  ydelta)
{
  PuzzleItem *block= & (puzzle->item [block_no]);

  gint pushed_x, pushed_y;

  if (floor (block->y)-block->y == 0 && xdelta != 0.0)
    {
      if (xdelta>=0.9)
        xdelta=0.9;
      else if (xdelta <= -0.9)
        xdelta=-0.9;

     pushed_x = who_is_pushed_x (puzzle, block_no, xdelta);

     if ( pushed_x == -1 || pushed_x == -2)
       {
         block->x += xdelta;
         if (block->x >= puzzle->cols-1)
           {
            block->x = puzzle->cols-1;
           }
         else if (block->x < 0)
           {
            block->x = 0;
           }
         return;
       }

     if (pushed_x >= 0)
       {
         push_block (puzzle, pushed_x, xdelta, 0);
         if (fabs (block->x - puzzle->item[pushed_x].x)>1.0001)
           {
             if (block->x < puzzle->item[pushed_x].x)
               block->x = puzzle->item[pushed_x].x - 1;
             else
               block->x = puzzle->item[pushed_x].x + 1;
           }
       }
    }

  if (floor (block->x)-block->x == 0)
    {
      if (ydelta>=0.9)
        ydelta=0.9;
      else if (ydelta <= -0.9)
        ydelta=-0.9;

      pushed_y = who_is_pushed_y (puzzle, block_no, ydelta);

      if (pushed_y == -1 || pushed_y == -2)
        {
          block->y += ydelta;
          if (block->y >= puzzle->rows-1)
            {
              block->y = puzzle->rows-1;
            }
          else if (block->y < 0)
            {
              block->y = 0;
            }
          return;
        }

      if (pushed_y >= 0)
        {
          push_block (puzzle, pushed_y, 0, ydelta);
          if (fabs (block->y - puzzle->item[pushed_y].y)>1.0001)
            {
              if (block->y < puzzle->item[pushed_y].y)
                block->y = puzzle->item[pushed_y].y - 1;
              else
                block->y = puzzle->item[pushed_y].y + 1;
            }
        }
    }
  return;
}

--- NEW FILE: puzzle.h ---
#ifndef PUZZLE_H
#define PUZZLE_H

#include <gtk/gtk.h>

#ifdef __cplusplus
extern "C" {
#endif                          /* __cplusplus */

#define PUZZLE(obj)          GTK_CHECK_CAST (obj, puzzle_get_type (), Puzzle)
#define PUZZLE_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, puzzle_get_type (), PuzzleClass)
#define IS_PUZZLE(obj)       GTK_CHECK_TYPE (obj, puzzle_get_type ())

    typedef struct _Puzzle       Puzzle;
    typedef struct _PuzzleClass  PuzzleClass;

    GtkType puzzle_get_type (void);

    GtkWidget *puzzle_new (void);

#ifdef __cplusplus

}
#endif                          /* __cplusplus */
#endif                          /* PUZZLE_H */




More information about the cairo-commit mailing list