[gnome-games] Revert "aisleriot: Remove sol/clutter from 3.0 branch"
- From: Christian Persch <chpe src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games] Revert "aisleriot: Remove sol/clutter from 3.0 branch"
- Date: Thu, 7 Apr 2011 12:38:23 +0000 (UTC)
commit 2f5bb0b9c815fbe88f6e70527ab6d299a9345217
Author: Christian Persch <chpe gnome org>
Date: Sat Apr 2 21:56:31 2011 +0200
Revert "aisleriot: Remove sol/clutter from 3.0 branch"
This reverts commit d0ce382f6016986b6030be2e7f09d8c1f5274439.
aisleriot/Makefile.am | 65 +
aisleriot/ar-clutter-embed.c | 271 +++
aisleriot/ar-clutter-embed.h | 61 +
aisleriot/ar-style-gtk.c | 19 +
aisleriot/ar-style-private.h | 12 +
aisleriot/ar-style.c | 103 +
aisleriot/ar-style.h | 15 +
aisleriot/baize.c | 91 +
aisleriot/baize.h | 65 +
aisleriot/board.c | 3536 ++++++++++++++++++++++++++++++++
aisleriot/board.h | 99 +
aisleriot/card.c | 364 ++++
aisleriot/card.h | 79 +
aisleriot/game.c | 55 +
aisleriot/game.h | 14 +
aisleriot/lib/Makefile.am | 12 +
aisleriot/lib/ar-card-textures-cache.c | 374 ++++
aisleriot/lib/ar-card-textures-cache.h | 74 +
aisleriot/prop-editor.c | 95 +
aisleriot/slot-renderer.c | 691 +++++++
aisleriot/slot-renderer.h | 101 +
aisleriot/sol.c | 28 +
aisleriot/window.c | 156 ++-
configure.in | 19 +-
po/POTFILES.in | 2 +
25 files changed, 6396 insertions(+), 5 deletions(-)
---
diff --git a/aisleriot/Makefile.am b/aisleriot/Makefile.am
index 8fc8f0d..c5047aa 100644
--- a/aisleriot/Makefile.am
+++ b/aisleriot/Makefile.am
@@ -110,6 +110,71 @@ if PLATFORM_WIN32_NATIVE
sol_LDFLAGS += -mwindows
endif
+if HAVE_CLUTTER
+if ENABLE_AISLERIOT_CLUTTER
+
+noinst_PROGRAMS = sol-clutter
+
+sol_clutter_SOURCES = \
+ ar-clutter-embed.c \
+ ar-clutter-embed.h \
+ ar-cursor.c \
+ ar-cursor.h \
+ ar-game-chooser.c \
+ ar-game-chooser.h \
+ ar-style.c \
+ ar-style.h \
+ ar-style-private.h \
+ ar-style-gtk.c \
+ ar-style-gtk.h \
+ baize.c \
+ baize.h \
+ board.c \
+ board.h \
+ card.c \
+ card.h \
+ conf.c \
+ conf.h \
+ game.c \
+ game.h \
+ sol.c \
+ slot-renderer.c \
+ slot-renderer.h \
+ stats-dialog.c \
+ stats-dialog.h \
+ util.c \
+ util.h \
+ window.c \
+ window.h \
+ $(NULL)
+
+if HAVE_MAEMO_5
+sol_clutter_SOURCES += \
+ ar-fullscreen-button.c \
+ ar-fullscreen-button.h \
+ $(NULL)
+endif
+
+if !HAVE_GUILE_1_8
+sol_clutter_SOURCES += guile16-compat.h
+endif
+
+if ENABLE_DEBUG_UI
+sol_clutter_SOURCES += \
+ prop-editor.c \
+ prop-editor.h \
+ $(NULL)
+endif
+
+sol_clutter_CPPFLAGS = $(sol_CPPFLAGS) -DHAVE_CLUTTER
+sol_clutter_CFLAGS = $(sol_CFLAGS) $(CLUTTER_GTK_CFLAGS) $(CLUTTER_CFLAGS)
+sol_clutter_LDFLAGS = $(sol_LDFLAGS)
+sol_clutter_LDADD = $(sol_LDADD) $(CLUTTER_GTK_LIBS) $(CLUTTER_LIBS)
+
+
+endif # ENABLE_AISLERIOT_CLUTTER
+endif # HAVE_CLUTTER
+
desktop_in_files = \
sol.desktop.in.in
diff --git a/aisleriot/ar-clutter-embed.c b/aisleriot/ar-clutter-embed.c
new file mode 100644
index 0000000..e09ee9b
--- /dev/null
+++ b/aisleriot/ar-clutter-embed.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright © 1998, 2003 Jonathan Blandford <jrb mit edu>
+ * Copyright © 2007, 2008, 2009, 2010 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "ar-clutter-embed.h"
+
+#include "ar-style.h"
+#include "ar-style-gtk.h"
+
+/**
+ * SECTION: ar-clutter-embed
+ * @short_description: a #GtkClutterEmbed derivative
+ *
+ * #ArClutterEmbed is a #GtkClutterEmbed derivative that syncs the
+ * properties of a #ArStyle to its style properties and the #GtkSettings
+ * properties of its #GdkScreen.
+ */
+
+G_DEFINE_TYPE (ArClutterEmbed, ar_clutter_embed, GTK_CLUTTER_TYPE_EMBED)
+
+enum
+{
+ PROP_0,
+ PROP_STYLE
+};
+
+struct _ArClutterEmbedPrivate
+{
+ ArStyle *style;
+
+ GdkCursor *cursor[AR_LAST_CURSOR];
+};
+
+/* GtkWidgetClass impl */
+
+static void
+ar_clutter_embed_realize (GtkWidget *widget)
+{
+ ArClutterEmbed *embed = AR_CLUTTER_EMBED (widget);
+ ArClutterEmbedPrivate *priv = embed->priv;
+#ifndef HAVE_HILDON
+ GdkDisplay *display;
+ GdkWindow *window;
+#endif
+
+ GTK_WIDGET_CLASS (ar_clutter_embed_parent_class)->realize (widget);
+
+ /* FIXMEchpe: this isn't really HILDON, but don't-support-mouse */
+#ifndef HAVE_HILDON
+ /* Create cursors */
+ display = gtk_widget_get_display (widget);
+ window = gtk_widget_get_window (widget);
+
+ priv->cursor[AR_CURSOR_DEFAULT] = gdk_cursor_new_for_display (display, GDK_LEFT_PTR);
+ priv->cursor[AR_CURSOR_OPEN] = ar_cursor_new (window, AR_CURSOR_OPEN);
+ priv->cursor[AR_CURSOR_CLOSED] = ar_cursor_new (window, AR_CURSOR_CLOSED);
+ priv->cursor[AR_CURSOR_DROPPABLE] = gdk_cursor_new_for_display (display, GDK_DOUBLE_ARROW); /* FIXMEchpe: better cursor */
+#endif /* !HAVE_HILDON */
+
+ ar_clutter_embed_set_cursor (embed, AR_CURSOR_DEFAULT);
+}
+
+static void
+ar_clutter_embed_unrealize (GtkWidget *widget)
+{
+ /* FIXMEchpe */
+#ifndef HAVE_HILDON
+ ArClutterEmbed *embed = AR_CLUTTER_EMBED (widget);
+ ArClutterEmbedPrivate *priv = embed->priv;
+ guint i;
+
+ for (i = 0; i < AR_LAST_CURSOR; ++i) {
+ gdk_cursor_unref (priv->cursor[i]);
+ priv->cursor[i] = NULL;
+ }
+#endif /* !HAVE_HILDON*/
+
+ GTK_WIDGET_CLASS (ar_clutter_embed_parent_class)->unrealize (widget);
+}
+
+static gboolean
+ar_clutter_embed_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ gboolean retval;
+
+ retval = GTK_WIDGET_CLASS (ar_clutter_embed_parent_class)->focus_in_event (widget, event);
+
+#if 0
+ ClutterActor *stage;
+ stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (widget));
+ clutter_stage_set_key_focus (CLUTTER_STAGE (stage), FIXME board actor);
+#endif
+
+ return retval;
+}
+
+static gboolean
+ar_clutter_embed_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+#ifdef FIXMEchpe
+ clear_state (board);
+#endif
+
+ return GTK_WIDGET_CLASS (ar_clutter_embed_parent_class)->focus_out_event (widget, event);
+}
+
+static gboolean
+ar_clutter_embed_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+// ArClutterEmbed *embed = AR_CLUTTER_EMBED (embed);
+// ArClutterEmbedPrivate *priv = embed->priv;
+ int count;
+ gboolean retval = FALSE;
+
+ switch (direction) {
+ case GTK_DIR_TAB_FORWARD:
+ count = 1;
+ break;
+ case GTK_DIR_TAB_BACKWARD:
+ count = -1;
+ break;
+ default:
+ break;
+ }
+
+#ifdef FIXMEchpe
+ g_signal_emit_by_name (priv->board_actor, "focus", count, &retval);
+#endif
+
+ if (retval)
+ return TRUE;
+
+ return GTK_WIDGET_CLASS (ar_clutter_embed_parent_class)->focus (widget, direction);
+}
+
+/* GObjectClass impl */
+
+static void
+ar_clutter_embed_init (ArClutterEmbed *embed)
+{
+ GtkWidget *widget = GTK_WIDGET (embed);
+
+ embed->priv = G_TYPE_INSTANCE_GET_PRIVATE (embed, AR_TYPE_CLUTTER_EMBED, ArClutterEmbedPrivate);
+
+ gtk_widget_set_can_focus (widget, TRUE);
+}
+
+static void
+ar_clutter_embed_dispose (GObject *object)
+{
+ ArClutterEmbed *embed = AR_CLUTTER_EMBED (object);
+ ArClutterEmbedPrivate *priv = embed->priv;
+
+ if (priv->style != NULL) {
+ _ar_style_gtk_detach (priv->style, GTK_WIDGET (embed));
+
+ g_object_unref (priv->style);
+ priv->style = NULL;
+ }
+
+ G_OBJECT_CLASS (ar_clutter_embed_parent_class)->dispose (object);
+}
+
+static void
+ar_clutter_embed_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ArClutterEmbed *embed = AR_CLUTTER_EMBED (object);
+ ArClutterEmbedPrivate *priv = embed->priv;
+
+ switch (property_id) {
+ case PROP_STYLE:
+ priv->style = g_value_dup_object (value);
+ _ar_style_gtk_attach (priv->style, GTK_WIDGET (embed));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+ar_clutter_embed_class_init (ArClutterEmbedClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (ArClutterEmbedPrivate));
+
+ object_class->set_property = ar_clutter_embed_set_property;
+ object_class->dispose = ar_clutter_embed_dispose;
+
+ widget_class->realize = ar_clutter_embed_realize;
+ widget_class->unrealize = ar_clutter_embed_unrealize;
+ widget_class->focus_in_event = ar_clutter_embed_focus_in;
+ widget_class->focus_out_event = ar_clutter_embed_focus_out;
+ widget_class->focus = ar_clutter_embed_focus;
+
+ /**
+ * ArClutterEmbed:style:
+ *
+ * An #ArStyle that @embed will update with its widget style properties.
+ */
+ g_object_class_install_property
+ (object_class,
+ PROP_STYLE,
+ g_param_spec_object ("style", NULL, NULL,
+ AR_TYPE_STYLE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ _ar_style_gtk_class_install_style_properties (widget_class);
+}
+
+/* public API */
+
+/**
+ * ar_clutter_embed_new:
+ * @style: an #ArStyle
+ *
+ * Returns: a new #ArClutterEmbed
+ */
+ArClutterEmbed *
+ar_clutter_embed_new (ArStyle *style)
+{
+ return g_object_new (AR_TYPE_CLUTTER_EMBED,
+ "style", style,
+ NULL);
+}
+
+/**
+ * ar_clutter_embed_set_cursor:
+ * @embed: an #ArClutterEmbed
+ * @cursor_type: the cursor type
+ *
+ * Sets the cursor on @embed to @cursor_type.
+ */
+void
+ar_clutter_embed_set_cursor (ArClutterEmbed *embed,
+ ArCursorType cursor)
+{
+ /* FIXMEchpe */
+#ifndef HAVE_HILDON
+ ArClutterEmbedPrivate *priv = embed->priv;
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (embed)),
+ priv->cursor[cursor]);
+#endif /* !HAVE_HILDON */
+}
diff --git a/aisleriot/ar-clutter-embed.h b/aisleriot/ar-clutter-embed.h
new file mode 100644
index 0000000..6bc9e7c
--- /dev/null
+++ b/aisleriot/ar-clutter-embed.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright © 2009 Christian Persch <chpe gnome org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __AR_CLUTTER_EMBED_H__
+#define __AR_CLUTTER_EMBED_H__
+
+#include <clutter-gtk/clutter-gtk.h>
+
+#include "ar-style.h"
+#include "ar-cursor.h"
+
+G_BEGIN_DECLS
+
+#define AR_TYPE_CLUTTER_EMBED (ar_clutter_embed_get_type())
+#define AR_CLUTTER_EMBED(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), AR_TYPE_CLUTTER_EMBED, ArClutterEmbed))
+#define AR_CLUTTER_EMBED_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), AR_TYPE_CLUTTER_EMBED, ArClutterEmbedClass))
+#define AR_IS_CLUTTER_EMBED(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), AR_TYPE_CLUTTER_EMBED))
+#define AR_IS_CLUTTER_EMBED_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), AR_TYPE_CLUTTER_EMBED))
+#define AR_CLUTTER_EMBED_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), AR_TYPE_CLUTTER_EMBED, ArClutterEmbedClass))
+
+typedef struct _ArClutterEmbed ArClutterEmbed;
+typedef struct _ArClutterEmbedClass ArClutterEmbedClass;
+typedef struct _ArClutterEmbedPrivate ArClutterEmbedPrivate;
+
+struct _ArClutterEmbed
+{
+ GtkClutterEmbed parent;
+
+ /*< private >*/
+ ArClutterEmbedPrivate *priv;
+};
+
+struct _ArClutterEmbedClass
+{
+ GtkClutterEmbedClass parent_class;
+};
+
+GType ar_clutter_embed_get_type (void);
+
+ArClutterEmbed* ar_clutter_embed_new (ArStyle *style);
+
+void ar_clutter_embed_set_cursor (ArClutterEmbed *embed,
+ ArCursorType cursor_type);
+
+G_END_DECLS
+
+#endif /* __AR_CLUTTER_EMBED_H__ */
diff --git a/aisleriot/ar-style-gtk.c b/aisleriot/ar-style-gtk.c
index a1b9b0c..edfe917 100644
--- a/aisleriot/ar-style-gtk.c
+++ b/aisleriot/ar-style-gtk.c
@@ -269,6 +269,24 @@ style_set_cb (GtkWidget *widget,
g_object_notify (style_object, AR_STYLE_PROP_CARD_STEP);
}
+#ifdef HAVE_CLUTTER
+{
+ ClutterColor selection_color;
+
+ if (color != NULL) {
+ _ar_clutter_color_from_gdk_color (&selection_color, color);
+ gdk_color_free (color);
+ } else {
+ _ar_clutter_color_from_gdk_color (&selection_color, &default_selection_color);
+ }
+
+ if (!clutter_color_equal (&style_priv->selection_color, &selection_color)) {
+ style_priv->selection_color = selection_color;
+
+ g_object_notify (style_object, AR_STYLE_PROP_SELECTION_COLOR);
+ }
+}
+#else
{
GdkColor selection_color;
@@ -285,6 +303,7 @@ style_set_cb (GtkWidget *widget,
g_object_notify (style_object, AR_STYLE_PROP_SELECTION_COLOR);
}
}
+#endif /* HAVE_CLUTTER */
g_object_thaw_notify (style_object);
}
diff --git a/aisleriot/ar-style-private.h b/aisleriot/ar-style-private.h
index 22cf407..3e76f67 100644
--- a/aisleriot/ar-style-private.h
+++ b/aisleriot/ar-style-private.h
@@ -49,8 +49,13 @@ struct _ArStylePrivate
{
ArCardTheme* card_theme;
+#ifdef HAVE_CLUTTER
+ ClutterColor selection_color;
+ ClutterColor baize_color;
+#else
GdkColor selection_color;
GdkColor baize_color;
+#endif
double card_slot_ratio;
double card_overhang;
@@ -79,9 +84,16 @@ struct _ArStylePrivate
guint show_highlight : 1;
guint show_seleccion : 1;
+#ifndef HAVE_CLUTTER
guint pixbuf_drawing : 1;
+#endif
};
+#ifdef HAVE_CLUTTER
+void _ar_clutter_color_from_gdk_color (ClutterColor *clutter_color,
+ const GdkColor *gdk_color);
+#endif
+
G_END_DECLS
#endif /* __AR_STYLE_PRIVATE_H__ */
diff --git a/aisleriot/ar-style.c b/aisleriot/ar-style.c
index 2e4998a..3a074df 100644
--- a/aisleriot/ar-style.c
+++ b/aisleriot/ar-style.c
@@ -39,6 +39,9 @@ enum
PROP_FOCUS_LINE_WIDTH,
PROP_FOCUS_PADDING,
PROP_INTERIOR_FOCUS,
+#ifndef HAVE_CLUTTER
+ PROP_PIXBUF_DRAWING,
+#endif
PROP_RTL,
PROP_SELECTION_COLOR,
PROP_SHOW_TOOLTIPS,
@@ -59,8 +62,13 @@ ar_style_init (ArStyle *style)
priv = style->priv = G_TYPE_INSTANCE_GET_PRIVATE (style, AR_TYPE_STYLE, ArStylePrivate);
+#ifdef HAVE_CLUTTER
+ _ar_clutter_color_from_gdk_color (&priv->selection_color, &default_selection_color);
+ _ar_clutter_color_from_gdk_color (&priv->baize_color, &default_baize_color);
+#else
priv->selection_color = default_selection_color;
priv->baize_color = default_baize_color;
+#endif
priv->card_slot_ratio = DEFAULT_CARD_SLOT_RATIO;
priv->card_overhang = DEFAULT_CARD_OVERHANG;
@@ -80,6 +88,8 @@ ar_style_init (ArStyle *style)
priv->enable_tooltips = DEFAULT_SHOW_TOOLTIPS;
priv->enable_status_messages = DEFAULT_SHOW_STATUS_MESSAGES;
+#ifndef HAVE_CLUTTER
+
#ifdef HAVE_HILDON
priv->pixbuf_drawing = FALSE;
#else
@@ -96,6 +106,8 @@ ar_style_init (ArStyle *style)
_games_debug_print (GAMES_DEBUG_GAME_STYLE,
"[ArStyle %p] Using %s drawing\n",
style, priv->pixbuf_drawing ? "pixbuf" : "pixmap");
+
+#endif /* !HAVE_CLUTTER */
}
static void
@@ -173,6 +185,12 @@ ar_style_get_property (GObject *object,
g_value_set_boolean (value, ar_style_get_interior_focus (style));
break;
+#ifndef HAVE_CLUTTER
+ case PROP_PIXBUF_DRAWING:
+ g_value_set_boolean (value, ar_style_get_pixbuf_drawing (style));
+ break;
+#endif
+
case PROP_RTL:
g_value_set_boolean (value, ar_style_get_rtl (style));
break;
@@ -209,6 +227,15 @@ ar_style_set_property (GObject *object,
switch (property_id) {
case PROP_BAIZE_COLOR: {
+#ifdef HAVE_CLUTTER
+ ClutterColor *color;
+
+ if ((color = g_value_get_boxed (value)) != NULL) {
+ priv->baize_color = *color;
+ } else {
+ _ar_clutter_color_from_gdk_color (&priv->baize_color, &default_baize_color);
+ }
+#else
GdkColor *color;
if ((color = g_value_get_boxed (value)) != NULL) {
@@ -216,6 +243,7 @@ ar_style_set_property (GObject *object,
} else {
priv->baize_color = default_baize_color;
}
+#endif
break;
}
@@ -263,11 +291,25 @@ ar_style_set_property (GObject *object,
priv->interior_focus = g_value_get_boolean (value) != FALSE;
break;
+#ifndef HAVE_CLUTTER
+ case PROP_PIXBUF_DRAWING:
+ priv->pixbuf_drawing = g_value_get_boolean (value) != FALSE;
+#endif
+
case PROP_RTL:
priv->rtl = g_value_get_boolean (value) != FALSE;
break;
case PROP_SELECTION_COLOR: {
+#ifdef HAVE_CLUTTER
+ ClutterColor *color;
+
+ if ((color = g_value_get_boxed (value)) != NULL) {
+ priv->selection_color = *color;
+ } else {
+ _ar_clutter_color_from_gdk_color (&priv->selection_color, &default_selection_color);
+ }
+#else
GdkColor *color;
if ((color = g_value_get_boxed (value)) != NULL) {
@@ -275,6 +317,7 @@ ar_style_set_property (GObject *object,
} else {
priv->selection_color = default_selection_color;
}
+#endif
break;
}
@@ -303,6 +346,9 @@ static void
ar_style_class_init (ArStyleClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+#ifdef HAVE_CLUTTER
+ ClutterColor color;
+#endif
g_type_class_add_private (klass, sizeof (ArStylePrivate));
@@ -315,6 +361,16 @@ ar_style_class_init (ArStyleClass *klass)
*
* The board baize color.
*/
+#ifdef HAVE_CLUTTER
+ _ar_clutter_color_from_gdk_color (&color, &default_baize_color);
+ g_object_class_install_property
+ (object_class,
+ PROP_BAIZE_COLOR,
+ clutter_param_spec_color (AR_STYLE_PROP_BAIZE_COLOR, NULL, NULL,
+ &color,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+#else
g_object_class_install_property
(object_class,
PROP_BAIZE_COLOR,
@@ -322,6 +378,7 @@ ar_style_class_init (ArStyleClass *klass)
GDK_TYPE_COLOR,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+#endif /* HAVE_CLUTTER */
g_object_class_install_property
(object_class,
@@ -431,6 +488,16 @@ ar_style_class_init (ArStyleClass *klass)
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+#ifndef HAVE_CLUTTER
+ g_object_class_install_property
+ (object_class,
+ PROP_PIXBUF_DRAWING,
+ g_param_spec_boolean (AR_STYLE_PROP_PIXBUF_DRAWING, NULL, NULL,
+ DEFAULT_PIXBUF_DRAWING,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+#endif /* !HAVE_CLUTTER */
+
g_object_class_install_property
(object_class,
PROP_RTL,
@@ -439,6 +506,16 @@ ar_style_class_init (ArStyleClass *klass)
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+#ifdef HAVE_CLUTTER
+ _ar_clutter_color_from_gdk_color (&color, &default_selection_color);
+ g_object_class_install_property
+ (object_class,
+ PROP_SELECTION_COLOR,
+ clutter_param_spec_color (AR_STYLE_PROP_SELECTION_COLOR, NULL, NULL,
+ &color,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+#else
g_object_class_install_property
(object_class,
PROP_SELECTION_COLOR,
@@ -446,6 +523,7 @@ ar_style_class_init (ArStyleClass *klass)
GDK_TYPE_COLOR,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+#endif /* HAVE_CLUTTER */
/**
* ArStyle:show-tooltips:
@@ -484,6 +562,20 @@ ar_style_class_init (ArStyleClass *klass)
/* private API */
+#ifdef HAVE_CLUTTER
+
+void
+_ar_clutter_color_from_gdk_color (ClutterColor *clutter_color,
+ const GdkColor *gdk_color)
+{
+ clutter_color->red = gdk_color->red >> 8;
+ clutter_color->green = gdk_color->green >> 8;
+ clutter_color->blue = gdk_color->blue >> 8;
+ clutter_color->alpha = 0xff;
+}
+
+#endif /* HAVE_CLUTTER */
+
/* public API */
/**
@@ -805,7 +897,11 @@ ar_style_get_card_step (ArStyle *style)
*/
void
ar_style_get_selection_color (ArStyle *style,
+#ifdef HAVE_CLUTTER
+ ClutterColor * const color)
+#else
GdkColor * const color)
+#endif
{
ArStylePrivate *priv = style->priv;
@@ -820,7 +916,11 @@ ar_style_get_selection_color (ArStyle *style,
*/
void
ar_style_get_baize_color (ArStyle *style,
+#ifdef HAVE_CLUTTER
+ ClutterColor * const color)
+#else
GdkColor * const color)
+#endif
{
ArStylePrivate *priv = style->priv;
@@ -856,6 +956,7 @@ ar_style_check_dnd_drag_threshold (ArStyle *style,
ABS (y2 - y1) > priv->dnd_drag_threshold);
}
+#ifndef HAVE_CLUTTER
/**
* ar_style_get_pixbuf_drawing:
* @style:
@@ -869,3 +970,5 @@ ar_style_get_pixbuf_drawing (ArStyle *style)
return priv->pixbuf_drawing;
}
+
+#endif /* !HAVE_CLUTTER */
diff --git a/aisleriot/ar-style.h b/aisleriot/ar-style.h
index c75e5dc..db7dec8 100644
--- a/aisleriot/ar-style.h
+++ b/aisleriot/ar-style.h
@@ -20,7 +20,11 @@
#include <glib-object.h>
+#ifdef HAVE_CLUTTER
+#include <clutter/clutter.h>
+#else
#include <gdk/gdk.h>
+#endif
#include "ar-card-theme.h"
@@ -104,10 +108,17 @@ double ar_style_get_card_slot_ratio (ArStyle *style);
double ar_style_get_card_overhang (ArStyle *style);
double ar_style_get_card_step (ArStyle *style);
+#ifdef HAVE_CLUTTER
+void ar_style_get_selection_color (ArStyle *style,
+ ClutterColor * const color);
+void ar_style_get_baize_color (ArStyle *style,
+ ClutterColor * const color);
+#else
void ar_style_get_selection_color (ArStyle *style,
GdkColor * const color);
void ar_style_get_baize_color (ArStyle *style,
GdkColor * const color);
+#endif
gboolean ar_style_check_dnd_drag_threshold (ArStyle *style,
float x1,
@@ -115,10 +126,14 @@ gboolean ar_style_check_dnd_drag_threshold (ArStyle *style,
float x2,
float y2);
+#ifndef HAVE_CLUTTER
+
#define AR_STYLE_PROP_PIXBUF_DRAWING "pixbuf-drawing"
gboolean ar_style_get_pixbuf_drawing (ArStyle *style);
+#endif /* !HAVE_CLUTTER */
+
G_END_DECLS
#endif /* __AR_STYLE_H__ */
diff --git a/aisleriot/baize.c b/aisleriot/baize.c
new file mode 100644
index 0000000..4496a53
--- /dev/null
+++ b/aisleriot/baize.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "baize.h"
+
+#include <cogl/cogl.h>
+
+#include <libgames-support/games-runtime.h>
+
+/* Special version of ClutterTexture that repeats the texture to fill
+ the entire stage. This is used to paint the baize background */
+
+static void aisleriot_baize_paint (ClutterActor *actor);
+
+G_DEFINE_TYPE (AisleriotBaize, aisleriot_baize, CLUTTER_TYPE_TEXTURE);
+
+static void
+aisleriot_baize_class_init (AisleriotBaizeClass *klass)
+{
+ ClutterActorClass *actor_class = (ClutterActorClass *) klass;
+
+ actor_class->paint = aisleriot_baize_paint;
+}
+
+static void
+aisleriot_baize_init (AisleriotBaize *baize)
+{
+ char *path;
+ GError *error = NULL;
+
+ path = games_runtime_get_file (GAMES_RUNTIME_PIXMAP_DIRECTORY, "baize.png");
+ if (!clutter_texture_set_from_file (CLUTTER_TEXTURE (baize), path, &error)) {
+ g_warning ("Failed to load the baize from '%s': %s\n", path, error->message);
+ g_error_free (error);
+ }
+
+ g_free (path);
+}
+
+ClutterActor *
+aisleriot_baize_new (void)
+{
+ return g_object_new (AISLERIOT_TYPE_BAIZE, NULL);
+}
+
+static void
+aisleriot_baize_paint (ClutterActor *actor)
+{
+ ClutterActor *stage;
+ CoglHandle tex;
+ ClutterGeometry stage_geom;
+ guint tex_width, tex_height;
+
+ if ((stage = clutter_actor_get_stage (actor)) == NULL)
+ return;
+
+ if ((tex = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (actor)))
+ == COGL_INVALID_HANDLE)
+ return;
+
+ tex_width = cogl_texture_get_width (tex);
+ tex_height = cogl_texture_get_height (tex);
+
+ if (tex_width < 1 || tex_height < 1)
+ return;
+
+ clutter_actor_get_allocation_geometry (stage, &stage_geom);
+
+ /* Repeat the texture to fill the size of the stage */
+ cogl_set_source_texture (tex);
+ cogl_rectangle_with_texture_coords (0, 0, stage_geom.width, stage_geom.height,
+ 0, 0,
+ (gfloat) stage_geom.width / tex_width,
+ (gfloat) stage_geom.height / tex_height);
+}
diff --git a/aisleriot/baize.h b/aisleriot/baize.h
new file mode 100644
index 0000000..d6585b3
--- /dev/null
+++ b/aisleriot/baize.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AISLERIOT_BAIZE_H
+#define AISLERIOT_BAIZE_H
+
+#include <clutter/clutter.h>
+
+G_BEGIN_DECLS
+
+#define AISLERIOT_TYPE_BAIZE \
+ (aisleriot_baize_get_type())
+#define AISLERIOT_BAIZE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ AISLERIOT_TYPE_BAIZE, \
+ AisleriotBaize))
+#define AISLERIOT_BAIZE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ AISLERIOT_TYPE_BAIZE, \
+ AisleriotBaizeClass))
+#define AISLERIOT_IS_BAIZE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ AISLERIOT_TYPE_BAIZE))
+#define AISLERIOT_IS_BAIZE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ AISLERIOT_TYPE_BAIZE))
+#define AISLERIOT_BAIZE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ AISLERIOT_TYPE_BAIZE, \
+ AisleriotBaizeClass))
+
+typedef struct _AisleriotBaize AisleriotBaize;
+typedef struct _AisleriotBaizeClass AisleriotBaizeClass;
+
+struct _AisleriotBaizeClass
+{
+ ClutterTextureClass parent_class;
+};
+
+struct _AisleriotBaize
+{
+ ClutterTexture parent;
+};
+
+GType aisleriot_baize_get_type (void) G_GNUC_CONST;
+
+ClutterActor *aisleriot_baize_new (void);
+
+G_END_DECLS
+
+#endif /* AISLERIOT_BAIZE_H */
diff --git a/aisleriot/board.c b/aisleriot/board.c
new file mode 100644
index 0000000..bef8b86
--- /dev/null
+++ b/aisleriot/board.c
@@ -0,0 +1,3536 @@
+/*
+ * Copyright © 1998, 2003 Jonathan Blandford <jrb mit edu>
+ * Copyright © 2007, 2008, 2009, 2010 Christian Persch
+ *
+ * Some code copied from gtk+/gtk/gtkiconview (LGPL2+):
+ * Copyright © 2002, 2004 Anders Carlsson <andersca gnu org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "board.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include <clutter/clutter.h>
+
+#include <libgames-support/games-debug.h>
+#include <libgames-support/games-glib-compat.h>
+#include <libgames-support/games-marshal.h>
+#include <libgames-support/games-sound.h>
+
+#include "conf.h"
+
+#include "game.h"
+#include "card.h"
+#include "slot-renderer.h"
+#include "ar-card-textures-cache.h"
+#include "ar-cursor.h"
+
+#define AISLERIOT_BOARD_GET_PRIVATE(board)(G_TYPE_INSTANCE_GET_PRIVATE ((board), AISLERIOT_TYPE_BOARD, AisleriotBoardPrivate))
+
+/* Enable keynav on non-hildon by default */
+#if !defined(HAVE_HILDON) && !defined(DISABLE_KEYNAV)
+#define ENABLE_KEYNAV
+#endif /* !HAVE_HILDON */
+
+/* The limits for how much overlap there is between cards and
+ * how much is allowed to slip off the bottom or right.
+ */
+#define MIN_DELTA (0.05)
+
+/* The minimum size for the playing area. Almost completely arbitrary. */
+#define BOARD_MIN_WIDTH 300
+#define BOARD_MIN_HEIGHT 200
+
+#define DOUBLE_TO_INT_CEIL(d) ((int) (d + 0.5))
+
+#define I_(string) g_intern_static_string (string)
+
+/* FIXMEchpe: file a bug to get an exported function like gtk_accelerator_get_default_mod_mask() for this? */
+/* Copied from clutter-binding-pool.c */
+#define CLUTTER_DEFAULT_MOD_MASK ((CLUTTER_SHIFT_MASK | \
+ CLUTTER_CONTROL_MASK | \
+ CLUTTER_MOD1_MASK | \
+ CLUTTER_SUPER_MASK | \
+ CLUTTER_HYPER_MASK | \
+ CLUTTER_META_MASK) | \
+ CLUTTER_RELEASE_MASK)
+
+#pragma GCC poison GtkWidget
+#pragma GCC poison widget
+
+typedef enum {
+ STATUS_NONE,
+ STATUS_MAYBE_DRAG,
+ STATUS_NOT_DRAG,
+ STATUS_IS_DRAG,
+ STATUS_SHOW,
+ LAST_STATUS
+} MoveStatus;
+
+#ifdef ENABLE_KEYNAV
+
+#define MOVE_CURSOR_LEFT_RIGHT 'h'
+#define MOVE_CURSOR_LEFT_RIGHT_S "h"
+#define MOVE_CURSOR_UP_DOWN 'v'
+#define MOVE_CURSOR_UP_DOWN_S "v"
+#define MOVE_CURSOR_PAGES 'p'
+#define MOVE_CURSOR_PAGES_S "p"
+#define MOVE_CURSOR_START_END 'e'
+#define MOVE_CURSOR_START_END_S "e"
+
+#define MOVE_LEFT 'l'
+#define MOVE_LEFT_S "l"
+#define MOVE_RIGHT 'r'
+#define MOVE_RIGHT_S "r"
+
+#endif /* ENABLE_KEYNAV */
+
+struct _AisleriotBoardPrivate
+{
+ AisleriotGame *game;
+
+ ArStyle *style;
+
+ ClutterActorBox allocation;
+
+ /* Card theme */
+ CardSize card_size;
+
+ /* Cards cache */
+ ArCardTexturesCache *textures;
+
+ double width;
+ double height;
+
+ /* The size of a slot in pixels. */
+ double xslotstep;
+ double yslotstep;
+
+ /* How much of the slot the card should take up */
+ double card_slot_ratio;
+
+ /* The offset of the cards within the slot. */
+ int xoffset, yoffset;
+
+ /* The offset within the window. */
+ int xbaseoffset;
+
+ /* Button press */
+ int last_click_x;
+ int last_click_y;
+ guint32 last_click_time;
+
+ /* Moving cards */
+ ArSlot *moving_cards_origin_slot;
+ int moving_cards_origin_card_id; /* The index of the card that was clicked on in hslot->cards; or -1 if the click wasn't on a card */
+ ClutterActor *moving_cards_group;
+ GByteArray *moving_cards;
+
+ /* A group to put animated cards above the slots */
+ ClutterActor *animation_layer;
+
+ /* The 'reveal card' action's slot and card link */
+ ArSlot *show_card_slot;
+ int show_card_id;
+
+ /* Click data */
+ ArSlot *last_clicked_slot;
+ int last_clicked_card_id;
+
+ /* Focus handling */
+ ArSlot *focus_slot;
+ int focus_card_id; /* -1 for focused empty slot */
+ int focus_line_width;
+ int focus_padding;
+ GdkRectangle focus_rect;
+
+ /* Selection */
+ ArSlot *selection_slot;
+ int selection_start_card_id;
+
+ /* Highlight */
+ ArSlot *highlight_slot;
+
+ /* Array RemovedCards to be dropped in animations */
+ GArray *removed_cards;
+
+ /* Idle handler where the slots will be compared for changes to
+ trigger animations */
+ guint check_animations_handler;
+
+ /* Status message */
+ const char *status_message; /* interned */
+
+ /* Bit field */
+ guint droppable_supported : 1;
+ guint touchscreen_mode : 1;
+ guint use_pixbuf_drawing : 1;
+ guint show_focus : 1; /* whether the focus is drawn */
+ guint interior_focus : 1;
+
+ guint click_to_move : 1;
+
+ guint geometry_set : 1;
+ guint is_rtl : 1;
+
+ guint last_click_left_click : 1;
+ guint click_status : 4; /* enough bits for MoveStatus */
+
+ guint show_selection : 1;
+ guint show_highlight : 1;
+ guint show_status_messages : 1;
+
+ guint force_geometry_update : 1;
+};
+
+typedef struct _RemovedCard RemovedCard;
+
+struct _RemovedCard
+{
+ Card card;
+ gint cardx, cardy;
+ gboolean from_drag;
+};
+
+G_STATIC_ASSERT (LAST_STATUS < 16 /* 2^4 */);
+
+enum
+{
+ PROP_0,
+ PROP_GAME,
+ PROP_STYLE
+};
+
+enum
+{
+ REQUEST_CURSOR,
+ ERROR_BELL,
+ STATUS_MESSAGE,
+ FOCUS,
+#ifdef ENABLE_KEYNAV
+ ACTIVATE,
+ MOVE_CURSOR,
+ TOGGLE_SELECTION,
+ SELECT_ALL,
+ DESELECT_ALL,
+#endif /* ENABLE_KEYNAV */
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void get_slot_and_card_from_point (AisleriotBoard *board,
+ int x,
+ int y,
+ ArSlot **slot,
+ int *_cardid);
+static void slot_update_card_images (AisleriotBoard *board,
+ ArSlot *slot);
+static void slot_update_card_images_full (AisleriotBoard *board,
+ ArSlot *slot,
+ gint highlight_start_card_id);
+
+static void aisleriot_board_setup_geometry (AisleriotBoard *board);
+
+static void
+set_cursor (AisleriotBoard *board,
+ ArCursorType cursor)
+{
+ g_signal_emit (board, signals[REQUEST_CURSOR], 0, (int) cursor);
+}
+
+/* If we are over a slot, set the cursor to the given cursor,
+ * otherwise use the default cursor. */
+static void
+set_cursor_by_location (AisleriotBoard *board,
+ int x,
+ int y)
+{
+#ifndef HAVE_HILDON
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *selection_slot = priv->selection_slot;
+ int selection_start_card_id = priv->selection_start_card_id;
+ ArSlot *slot;
+ int card_id;
+ gboolean drop_valid = FALSE;
+ ArCursorType cursor = AR_CURSOR_DEFAULT;
+
+ get_slot_and_card_from_point (board, x, y, &slot, &card_id);
+
+ if (priv->click_to_move &&
+ slot != NULL &&
+ selection_slot != NULL &&
+ slot != selection_slot &&
+ selection_start_card_id >= 0) {
+ g_return_if_fail (selection_slot->cards->len > selection_start_card_id);
+
+ drop_valid = aisleriot_game_drop_valid (priv->game,
+ selection_slot->id,
+ slot->id,
+ selection_slot->cards->data + selection_start_card_id,
+ selection_slot->cards->len - selection_start_card_id);
+ }
+ /* FIXMEchpe: special cursor when _drag_ is possible? */
+
+ if (drop_valid) {
+ cursor = AR_CURSOR_DROPPABLE;
+ } else if (slot != NULL &&
+ card_id >= 0 &&
+ !CARD_GET_FACE_DOWN (CARD (slot->cards->data[card_id]))) {
+ if (priv->click_status == STATUS_NONE) {
+ cursor = AR_CURSOR_OPEN;
+ } else {
+ cursor = AR_CURSOR_CLOSED;
+ }
+ }
+
+ set_cursor (board, cursor);
+#endif /* !HAVE_HILDON */
+}
+
+/* status message */
+
+static void
+set_status_message (AisleriotBoard *board,
+ const char *message)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (g_strcmp0 (priv->status_message, message) == 0)
+ return;
+
+ priv->status_message = g_intern_string (message);
+
+ g_signal_emit (board, signals[STATUS_MESSAGE], 0, priv->status_message);
+}
+
+/* Slot helpers */
+
+static void
+get_slot_and_card_from_point (AisleriotBoard *board,
+ int x,
+ int y,
+ ArSlot **slot,
+ int *_cardid)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ gboolean got_slot = FALSE;
+ int num_cards;
+ int i, n_slots;
+ int cardid;
+
+ *slot = NULL;
+ cardid = -1;
+
+ slots = aisleriot_game_get_slots (priv->game);
+
+ n_slots = slots->len;
+ for (i = n_slots - 1; i >= 0; --i) {
+ ArSlot *hslot = slots->pdata[i];
+
+ /* if point is within our rectangle */
+ if (hslot->rect.x <= x && x <= hslot->rect.x + hslot->rect.width &&
+ hslot->rect.y <= y && y <= hslot->rect.y + hslot->rect.height) {
+ num_cards = hslot->cards->len;
+
+ if (got_slot == FALSE || num_cards > 0) {
+ /* if we support exposing more than one card,
+ * find the exact card */
+
+ gint depth = 1;
+
+ if (hslot->pixeldx > 0)
+ depth += (x - hslot->rect.x) / hslot->pixeldx;
+ else if (hslot->pixeldx < 0)
+ depth += (hslot->rect.x + hslot->rect.width - x) / -hslot->pixeldx;
+ else if (hslot->pixeldy > 0)
+ depth += (y - hslot->rect.y) / hslot->pixeldy;
+
+ /* account for the last card getting much more display area
+ * or no cards */
+
+ if (depth > hslot->exposed)
+ depth = hslot->exposed;
+ *slot = hslot;
+
+ /* card = #cards in slot + card chosen (indexed in # exposed cards) - # exposed cards */
+
+ cardid = num_cards + depth - hslot->exposed;
+
+ /* this is the topmost slot with a card */
+ /* take it and run */
+ if (num_cards > 0)
+ break;
+
+ got_slot = TRUE;
+ }
+ }
+ }
+
+ *_cardid = cardid > 0 ? cardid - 1 : -1;
+}
+
+#ifdef ENABLE_KEYNAV
+
+static gboolean
+test_slot_projection_intersects_x (ArSlot *slot,
+ int x_start,
+ int x_end)
+{
+ return slot->rect.x <= x_end &&
+ slot->rect.x + slot->rect.width >= x_start;
+}
+
+static int
+get_slot_index_from_slot (AisleriotBoard *board,
+ ArSlot *slot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ guint n_slots;
+ int slot_index;
+
+ g_assert (slot != NULL);
+
+ slots = aisleriot_game_get_slots (priv->game);
+ n_slots = slots->len;
+ g_assert (n_slots > 0);
+
+ for (slot_index = 0; slot_index < n_slots; ++slot_index) {
+ if (g_ptr_array_index (slots, slot_index) == slot)
+ break;
+ }
+
+ g_assert (slot_index < n_slots); /* the slot EXISTS after all */
+
+ return slot_index;
+}
+
+#endif /* ENABLE_KEYNAV */
+
+static void
+get_rect_by_slot_and_card (AisleriotBoard *board,
+ ArSlot *slot,
+ int card_id,
+ int num_cards,
+ GdkRectangle *rect)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ guint delta;
+ int first_card_id, num;
+
+ g_return_if_fail (slot != NULL && card_id >= -1);
+
+ first_card_id = ((int) slot->cards->len) - ((int) slot->exposed);
+
+ if (card_id >= first_card_id) {
+ delta = card_id - first_card_id;
+ num = num_cards - 1;
+
+ rect->x = slot->rect.x + delta * slot->pixeldx;
+ rect->y = slot->rect.y + delta * slot->pixeldy;
+ rect->width = priv->card_size.width + num * slot->pixeldx;
+ rect->height = priv->card_size.height + num * slot->pixeldy;
+
+ if (priv->is_rtl &&
+ slot->expanded_right) {
+ rect->x += slot->rect.width - priv->card_size.width;
+ }
+
+ } else {
+ /* card_id == -1 or no card available, return the slot rect.
+ * Its size should be card_size.
+ */
+ *rect = slot->rect;
+ }
+}
+
+/* Focus handling */
+
+static void
+widen_rect (GdkRectangle *rect,
+ int delta)
+{
+ int x, y;
+
+ x = rect->x - delta;
+ y = rect->y - delta;
+
+ rect->x = MAX (x, 0);
+ rect->y = MAX (y, 0);
+ rect->width = rect->width + 2 * delta;
+ rect->height = rect->height + 2 * delta;
+}
+
+static void
+get_focus_rect (AisleriotBoard *board,
+ GdkRectangle *rect)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!priv->focus_slot)
+ return;
+
+ get_rect_by_slot_and_card (board,
+ priv->focus_slot,
+ priv->focus_card_id,
+ 1, rect);
+ widen_rect (rect, priv->focus_line_width + priv->focus_padding);
+}
+
+static void
+set_focus (AisleriotBoard *board,
+ ArSlot *slot,
+ int card_id,
+ gboolean show_focus)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+// GtkWidget *widget = GTK_WIDGET (board);
+ int top_card_id;
+
+ /* Sanitise */
+ top_card_id = slot ? ((int) slot->cards->len) - 1 : -1;
+ card_id = MIN (card_id, top_card_id);
+
+ if (priv->focus_slot == slot &&
+ priv->focus_card_id == card_id &&
+ priv->show_focus == show_focus)
+ return;
+
+ if (priv->focus_slot != NULL) {
+#ifdef FIXMEchpe
+ if (priv->show_focus &&
+ gtk_widget_has_focus (widget)) {
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+
+ priv->show_focus = FALSE;
+ }
+#endif
+
+ priv->focus_slot = NULL;
+ priv->focus_card_id = -1;
+ }
+
+ priv->show_focus = show_focus;
+
+ if (!slot)
+ return;
+
+ priv->focus_slot = slot;
+ priv->focus_card_id = card_id;
+
+#ifdef FIXMEchpe
+ if (show_focus &&
+ gtk_widget_has_focus (widget)) {
+ get_focus_rect (board, &priv->focus_rect);
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+ }
+#endif
+}
+
+/* Selection handling */
+
+static void
+set_selection (AisleriotBoard *board,
+ ArSlot *slot,
+ int card_id,
+ gboolean show_selection)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (priv->selection_slot == slot &&
+ priv->selection_start_card_id == card_id &&
+ priv->show_selection == show_selection)
+ return;
+
+ if (priv->selection_slot != NULL) {
+ if (priv->show_selection) {
+ /* Clear selection card images */
+ slot_update_card_images_full (board, priv->selection_slot, G_MAXINT);
+ }
+
+ priv->selection_slot = NULL;
+ priv->selection_start_card_id = -1;
+ }
+
+ priv->show_selection = show_selection;
+ priv->selection_slot = slot;
+ priv->selection_start_card_id = card_id;
+ g_assert (slot != NULL || card_id == -1);
+
+ if (!slot)
+ return;
+
+ g_assert (card_id < 0 || card_id < slot->cards->len);
+
+ if (priv->show_selection) {
+ slot_update_card_images_full (board, slot, card_id);
+ }
+}
+
+/* Slot functions */
+
+static void
+slot_update_geometry (AisleriotBoard *board,
+ ArSlot *slot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+// GtkWidget *widget = GTK_WIDGET (board);
+ GdkRectangle old_rect;
+ GByteArray *cards;
+ int delta, xofs, yofs, pixeldx;
+ double card_step;
+
+ if (!priv->geometry_set)
+ return;
+
+ cards = slot->cards;
+ old_rect = slot->rect;
+
+ card_step = ar_style_get_card_step (priv->style);
+
+ xofs = priv->xoffset;
+ yofs = priv->yoffset;
+
+ /* FIXMEchpe: what exactly is the purpose of the following lines? */
+ if (slot->expanded_right)
+ xofs = yofs;
+ if (slot->expanded_down)
+ yofs = xofs;
+
+ if (priv->is_rtl) {
+ slot->rect.x = priv->xslotstep * (priv->width - slot->x) - priv->card_size.width - xofs + priv->xbaseoffset;
+ } else {
+ slot->rect.x = priv->xslotstep * slot->x + xofs + priv->xbaseoffset;
+ }
+
+ slot->rect.y = priv->yslotstep * slot->y + yofs; /* FIXMEchpe + priv->ybaseoffset; */
+
+ /* We need to make sure the cards fit within the board, even
+ * when there are many of them. See bug #171417.
+ */
+ /* FIXMEchpe: check |slot->exposed| instead of cards->len? */
+ pixeldx = 0;
+ if (cards->len > 1) {
+ double dx = 0, dy = 0;
+ double n_cards = cards->len - 1; /* FIXMEchpe: slot->exposed - 1 ? */
+
+ if (slot->expanded_down) {
+ double y_from_bottom, max_dy = card_step;
+ float allocation_height = priv->allocation.y2 - priv->allocation.y1;
+
+ if (slot->dy_set)
+ max_dy = slot->expansion.dy;
+
+ /* Calculate the compressed_dy that will let us fit within the board */
+#ifdef FIXMEchpe
+ y_from_bottom = ((double) (widget->allocation.height - slot->rect.y)) / ((double) priv->card_size.height);
+#else
+ y_from_bottom = ((double) (allocation_height - slot->rect.y)) / ((double) priv->card_size.height);
+#endif
+ dy = (y_from_bottom - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
+ dy = CLAMP (dy, MIN_DELTA, max_dy);
+ } else if (slot->expanded_right) {
+ if (priv->is_rtl) {
+ double x_from_left, max_dx = card_step;
+
+ if (slot->dx_set)
+ max_dx = slot->expansion.dx;
+
+ x_from_left = ((double) slot->rect.x) / ((double) priv->card_size.width) + 1.0;
+ dx = (x_from_left - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
+ dx = CLAMP (dx, MIN_DELTA, max_dx);
+
+ slot->pixeldx = DOUBLE_TO_INT_CEIL (- dx * priv->card_size.width);
+ pixeldx = -slot->pixeldx;
+ } else {
+ double x_from_right, max_dx = card_step;
+ float allocation_width = priv->allocation.x2 - priv->allocation.x1;
+
+ if (slot->dx_set)
+ max_dx = slot->expansion.dx;
+
+#ifdef FIXMEchpe
+ x_from_right = ((double) (widget->allocation.width - slot->rect.x)) / ((double) priv->card_size.width);
+#else
+ x_from_right = ((double) (allocation_width - slot->rect.x)) / ((double) priv->card_size.width);
+#endif
+ dx = (x_from_right - (1.0 - ar_style_get_card_overhang (priv->style))) / n_cards;
+ dx = CLAMP (dx, MIN_DELTA, max_dx);
+
+ pixeldx = slot->pixeldx = DOUBLE_TO_INT_CEIL (dx * priv->card_size.width);
+ }
+ }
+
+ slot->pixeldy = DOUBLE_TO_INT_CEIL (dy * priv->card_size.height);
+ } else {
+ slot->pixeldx = slot->pixeldy = 0;
+ }
+
+ slot->exposed = cards->len;
+ if (0 < slot->expansion_depth &&
+ slot->expansion_depth < slot->exposed) {
+ slot->exposed = slot->expansion_depth;
+ }
+
+ if ((slot->pixeldx == 0 &&
+ slot->pixeldy == 0 &&
+ slot->exposed > 1) ||
+ (cards->len > 0 &&
+ slot->exposed < 1)) {
+ slot->exposed = 1;
+ }
+
+ delta = slot->exposed > 0 ? slot->exposed - 1 : 0;
+
+ slot->rect.width = priv->card_size.width + delta * pixeldx;
+ slot->rect.height = priv->card_size.height + delta * slot->pixeldy;
+
+ if (priv->is_rtl) {
+ slot->rect.x -= slot->rect.width - priv->card_size.width;
+ }
+
+ if (slot->slot_renderer)
+ clutter_actor_set_position (slot->slot_renderer,
+ slot->rect.x, slot->rect.y);
+
+ slot->needs_update = FALSE;
+}
+
+static gboolean
+check_animations_cb (gpointer user_data)
+{
+ AisleriotBoard *board = user_data;
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ int slot_num, i;
+ ArSlot *slot;
+ GArray *animations = g_array_new (FALSE, FALSE, sizeof (AisleriotAnimStart));
+
+ slots = aisleriot_game_get_slots (priv->game);
+
+ /* Find any cards that have been removed from the top of the
+ slots */
+ for (slot_num = 0; slot_num < slots->len; slot_num++) {
+ slot = slots->pdata[slot_num];
+
+ if (slot->old_cards->len > slot->cards->len) {
+ for (i = 0; i < slot->cards->len; i++) {
+ Card old_card = CARD (slot->old_cards->data[i]);
+ Card new_card = CARD (slot->cards->data[i]);
+
+ if (old_card.attr.suit != new_card.attr.suit
+ || old_card.attr.rank != new_card.attr.rank)
+ break;
+ }
+
+ if (i >= slot->cards->len) {
+ for (; i < slot->old_cards->len; i++) {
+ RemovedCard removed_card;
+
+ removed_card.card = CARD (slot->old_cards->data[i]);
+ aisleriot_game_get_card_offset (slot, i,
+ TRUE,
+ &removed_card.cardx,
+ &removed_card.cardy);
+ removed_card.cardx += slot->rect.x;
+ removed_card.cardy += slot->rect.y;
+ removed_card.from_drag = FALSE;
+ g_array_append_val (priv->removed_cards, removed_card);
+ }
+ }
+ }
+ }
+
+ for (slot_num = 0; slot_num < slots->len; slot_num++) {
+ /* Number of extra cards that aren't visible to include in the
+ animation */
+ guint n_unexposed_animated_cards = 0;
+
+ slot = slots->pdata[slot_num];
+
+ g_array_set_size (animations, 0);
+
+ /* Check if the top card has been flipped over */
+ if (slot->old_cards->len >= slot->cards->len
+ && slot->cards->len >= 1
+ && !memcmp (slot->old_cards->data, slot->cards->data,
+ slot->cards->len - 1)) {
+ Card old_card = CARD (slot->old_cards->data[slot->cards->len - 1]);
+ Card new_card = CARD (slot->cards->data[slot->cards->len - 1]);
+
+ if (old_card.attr.suit == new_card.attr.suit
+ && old_card.attr.rank == new_card.attr.rank
+ && old_card.attr.face_down != new_card.attr.face_down) {
+ AisleriotAnimStart anim;
+
+ aisleriot_game_get_card_offset (slot, slot->cards->len - 1,
+ FALSE,
+ &anim.cardx,
+ &anim.cardy);
+ anim.cardx += slot->rect.x;
+ anim.cardy += slot->rect.y;
+ anim.old_card = old_card;
+ anim.raise = TRUE;
+
+ g_array_append_val (animations, anim);
+ }
+ /* Check if any cards have been added from the removed cards
+ pile */
+ } else if (slot->old_cards->len < slot->cards->len
+ && !memcmp (slot->old_cards->data, slot->cards->data,
+ slot->old_cards->len)) {
+ for (i = MAX (slot->old_cards->len, slot->cards->len - slot->exposed);
+ i < slot->cards->len;
+ i++) {
+ Card added_card = CARD (slot->cards->data[i]);
+ int j;
+
+ for (j = 0; j < priv->removed_cards->len; j++) {
+ RemovedCard *removed_card = &g_array_index (priv->removed_cards,
+ RemovedCard, j);
+
+ if (added_card.attr.suit == removed_card->card.attr.suit
+ && added_card.attr.rank == removed_card->card.attr.rank) {
+ AisleriotAnimStart anim;
+
+ anim.cardx = removed_card->cardx;
+ anim.cardy = removed_card->cardy;
+ anim.old_card = removed_card->card;
+ anim.raise = !removed_card->from_drag;
+
+ g_array_append_val (animations, anim);
+
+ g_array_remove_index (priv->removed_cards, j);
+
+ break;
+ }
+ }
+ }
+
+ /* Check if any extra unexposed cards are included in the
+ animation. There's no point in drawing these because they
+ will be hidden by the exposed cards but we don't want to draw
+ them at the slot either. This will for example happen in
+ Canfield when the discard pile is flipped over into the draw
+ pile */
+ if (animations->len > 0 && animations->len == slot->exposed)
+ {
+ AisleriotAnimStart *anim = &g_array_index (animations,
+ AisleriotAnimStart, 0);
+
+ n_unexposed_animated_cards = (slot->cards->len - slot->old_cards->len
+ - slot->exposed);
+
+ if (n_unexposed_animated_cards > 0)
+ {
+ /* Set the bottom card of the first animation to be the
+ lowest unexposed card */
+ anim->old_card
+ = CARD (slot->cards->data[slot->cards->len
+ - animations->len
+ - n_unexposed_animated_cards]);
+ anim->old_card.attr.face_down = !anim->old_card.attr.face_down;
+ }
+ }
+ }
+
+ aisleriot_slot_renderer_set_animations
+ (AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
+ animations->len, (const AisleriotAnimStart *) animations->data,
+ n_unexposed_animated_cards);
+
+ if (slot->cards->len == 0) {
+ clutter_actor_lower_bottom (slot->slot_renderer);
+ } else {
+ clutter_actor_raise_top (slot->slot_renderer);
+ ClutterActor *animation_layer = CLUTTER_ACTOR(aisleriot_slot_renderer_get_animation_layer(AISLERIOT_SLOT_RENDERER(slot->slot_renderer)));
+ clutter_actor_raise_top (animation_layer);
+ }
+
+ /* Set the old cards back to the new cards */
+ aisleriot_game_reset_old_cards (slot);
+ }
+
+ g_array_set_size (priv->removed_cards, 0);
+
+ g_array_free (animations, TRUE);
+
+ priv->check_animations_handler = 0;
+
+ return FALSE;
+}
+
+static void
+queue_check_animations (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!ar_style_get_enable_animations (priv->style))
+ return;
+
+ /* The animations are checked for in an idle handler so that this
+ function can be called whenever the scheme script makes changes
+ to the board but it won't actually check until control has been
+ given back to the glib main loop */
+
+ if (priv->check_animations_handler == 0)
+ /* Check for animations with a high priority to ensure that the
+ animations are setup before the stage is repainted. Clutter's
+ redraw priority is unfortunately higher than all of the idle
+ priorities so we need to use G_PRIORITY_DEFAULT */
+ priv->check_animations_handler =
+ clutter_threads_add_idle_full (G_PRIORITY_DEFAULT,
+ check_animations_cb,
+ board, NULL);
+}
+
+static void
+slot_update_card_images_full (AisleriotBoard *board,
+ ArSlot *slot,
+ gint highlight_start_card_id)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!priv->geometry_set)
+ return;
+
+ if (slot->slot_renderer == NULL) {
+ slot->slot_renderer = aisleriot_slot_renderer_new (priv->style, priv->textures, slot);
+ g_object_ref_sink (slot->slot_renderer);
+
+ aisleriot_slot_renderer_set_animation_layer
+ (AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
+ CLUTTER_CONTAINER (priv->animation_layer));
+
+ clutter_actor_set_position (slot->slot_renderer,
+ slot->rect.x, slot->rect.y);
+
+ clutter_container_add (CLUTTER_CONTAINER (board),
+ slot->slot_renderer, NULL);
+
+ clutter_actor_raise_top (priv->animation_layer);
+ }
+
+ aisleriot_slot_renderer_set_animations
+ (AISLERIOT_SLOT_RENDERER (slot->slot_renderer), 0, NULL, 0);
+
+ aisleriot_slot_renderer_set_highlight
+ (AISLERIOT_SLOT_RENDERER (slot->slot_renderer),
+ priv->show_highlight ? highlight_start_card_id : G_MAXINT);
+}
+
+static void
+slot_update_card_images (AisleriotBoard *board,
+ ArSlot *slot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ int highlight_start_card_id = G_MAXINT;
+
+ if (G_UNLIKELY (slot == priv->highlight_slot &&
+ priv->show_highlight)) {
+ highlight_start_card_id = slot->cards->len - 1;
+ } else if (G_UNLIKELY (slot == priv->selection_slot &&
+ priv->selection_start_card_id >= 0 &&
+ priv->show_selection)) {
+ highlight_start_card_id = priv->selection_start_card_id;
+ }
+
+ slot_update_card_images_full (board, slot, highlight_start_card_id);
+}
+
+/* helper functions */
+
+static void
+aisleriot_board_error_bell (AisleriotBoard *board)
+{
+ g_signal_emit (board, signals[ERROR_BELL], 0);
+}
+
+/* Work out new sizes and spacings for the cards. */
+static void
+aisleriot_board_setup_geometry (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ClutterActor *actor = CLUTTER_ACTOR (board);
+ ArCardTheme *theme;
+ GPtrArray *slots;
+ guint i, n_slots;
+ CardSize card_size;
+
+ if (!CLUTTER_ACTOR_IS_REALIZED (actor))
+ return;
+
+ /* Nothing to do yet */
+ if (aisleriot_game_get_state (priv->game) <= GAME_LOADED)
+ return;
+
+ theme = ar_style_get_card_theme (priv->style);
+ if (theme == NULL)
+ return;
+
+ g_return_if_fail (priv->width > 0 && priv->height > 0);
+
+ // FIXMEchpe
+ priv->xslotstep = ((double) priv->allocation.x2 - priv->allocation.x1) / priv->width;
+ priv->yslotstep = ((double) priv->allocation.y2 - priv->allocation.y1) / priv->height;
+
+ ar_card_theme_set_size (theme,
+ priv->xslotstep,
+ priv->yslotstep,
+ priv->card_slot_ratio);
+
+ ar_card_theme_get_size (theme, &card_size);
+ priv->card_size = card_size;
+
+ /* If the cards are too far apart, bunch them in the middle. */
+ priv->xbaseoffset = 0;
+ if (priv->xslotstep > (card_size.width * 3) / 2) {
+ priv->xslotstep = (card_size.width * 3) / 2;
+ /* FIXMEchpe: if there are expand-right slots, reserve the space for them instead? */
+#ifdef FIXMEchpe
+ priv->xbaseoffset = (widget->allocation.width - priv->xslotstep * priv->width) / 2;
+#else
+ priv->xbaseoffset = (priv->allocation.x2 - priv->allocation.x1 - priv->xslotstep * priv->width) / 2;
+#endif
+ }
+ if (priv->yslotstep > (card_size.height * 3) / 2) {
+ priv->yslotstep = (card_size.height * 3) / 2;
+ /* FIXMEchpe: if there are expand-down slots, reserve the space for them instead?
+ priv->ybaseoffset = (widget->allocation.height - priv->yslotstep * priv->height) / 2;
+ */
+ }
+
+ priv->xoffset = (priv->xslotstep - card_size.width) / 2;
+ priv->yoffset = (priv->yslotstep - card_size.height) / 2;
+
+ /* NOTE! Updating the slots checks that geometry is set, so
+ * we set it to TRUE already.
+ */
+ priv->geometry_set = TRUE;
+
+ /* Now recalculate the slot locations. */
+ slots = aisleriot_game_get_slots (priv->game);
+
+ n_slots = slots->len;
+ for (i = 0; i < n_slots; ++i) {
+ ArSlot *slot = slots->pdata[i];
+
+ slot_update_geometry (board, slot);
+ slot_update_card_images (board, slot);
+ }
+
+ /* Update the focus and selection rects */
+ get_focus_rect (board, &priv->focus_rect);
+}
+
+static void
+drag_begin (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *hslot;
+ int delta, height, width;
+ int x, y;
+ int num_moving_cards;
+ guint i;
+ GByteArray *cards;
+
+ if (!priv->selection_slot ||
+ priv->selection_start_card_id < 0) {
+ priv->click_status = STATUS_NONE;
+ return;
+ }
+
+ priv->click_status = STATUS_IS_DRAG;
+
+ hslot = priv->moving_cards_origin_slot = priv->selection_slot;
+ priv->moving_cards_origin_card_id = priv->selection_start_card_id;
+
+ num_moving_cards = hslot->cards->len - priv->moving_cards_origin_card_id;
+
+ cards = hslot->cards;
+
+ /* Save game state */
+ aisleriot_game_record_move (priv->game, hslot->id,
+ cards->data, cards->len);
+
+ /* Unset the selection and focus. It'll be re-set if the drag is aborted */
+ set_selection (board, NULL, -1, FALSE);
+ set_focus (board, NULL, -1, FALSE);
+
+ delta = hslot->exposed - num_moving_cards;
+
+ /* (x,y) is the upper left edge of the topmost dragged card */
+ x = hslot->rect.x + delta * hslot->pixeldx;
+ if (priv->is_rtl &&
+ hslot->expanded_right) {
+ x += hslot->rect.width - priv->card_size.width;
+ }
+
+ priv->last_click_x -= x;
+ priv->last_click_y -= y = hslot->rect.y + delta * hslot->pixeldy;;
+
+ g_byte_array_set_size (priv->moving_cards, 0);
+ g_byte_array_append (priv->moving_cards,
+ cards->data + priv->moving_cards_origin_card_id,
+ cards->len - priv->moving_cards_origin_card_id);
+
+ width = priv->card_size.width + (num_moving_cards - 1) * hslot->pixeldx;
+ height = priv->card_size.height + (num_moving_cards - 1) * hslot->pixeldy;
+
+ priv->moving_cards_group = g_object_ref_sink (clutter_group_new ());
+ clutter_actor_set_position (priv->moving_cards_group, x, y);
+
+ /* FIXMEchpe: RTL issue: this doesn't work right when we allow dragging of
+ * more than one card from a expand-right slot. (But right now no game .scm
+ * does allow that.)
+ */
+ x = y = 0;
+ width = priv->card_size.width;
+ height = priv->card_size.height;
+
+ for (i = 0; i < priv->moving_cards->len; ++i) {
+ Card hcard = CARD (priv->moving_cards->data[i]);
+ ClutterActor *card_tex;
+ RemovedCard removed_card;
+
+ removed_card.cardx = x;
+ removed_card.cardy = y;
+ removed_card.card = hcard;
+ removed_card.from_drag = TRUE;
+
+ g_array_append_val (priv->removed_cards, removed_card);
+
+ card_tex = aisleriot_card_new (priv->textures, hcard, hcard);
+ clutter_actor_set_position (card_tex, x, y);
+ clutter_container_add (CLUTTER_CONTAINER (priv->moving_cards_group),
+ card_tex, NULL);
+
+ x += hslot->pixeldx;
+ y += hslot->pixeldy;
+ }
+
+ /* Take the cards off of the stack */
+ g_byte_array_set_size (cards, priv->moving_cards_origin_card_id);
+
+ slot_update_geometry (board, hslot);
+ slot_update_card_images (board, hslot);
+ aisleriot_game_reset_old_cards (hslot);
+
+ clutter_container_add (CLUTTER_CONTAINER (board),
+ priv->moving_cards_group, NULL);
+
+ if (hslot->cards->len == 0) {
+ clutter_actor_lower_bottom (hslot->slot_renderer);
+ }
+
+ set_cursor (board, AR_CURSOR_CLOSED);
+}
+
+static void
+drag_end (AisleriotBoard *board,
+ gboolean moved)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (priv->moving_cards_group != NULL) {
+ clutter_actor_destroy (priv->moving_cards_group);
+ g_object_unref (priv->moving_cards_group);
+ priv->moving_cards_group = NULL;
+ }
+
+ /* FIXMEchpe: check that slot->cards->len == moving_cards_origin_card_id !!! FIXMEchpe what to do if not, abort the game? */
+ /* Add the origin cards back to the origin slot */
+ if (!moved &&
+ priv->moving_cards_origin_slot != NULL &&
+ priv->moving_cards->len > 0) {
+ aisleriot_game_slot_add_cards (priv->game,
+ priv->moving_cards_origin_slot,
+ priv->moving_cards->data,
+ priv->moving_cards->len);
+ clutter_actor_raise_top (priv->moving_cards_origin_slot->slot_renderer);
+ ClutterActor *animation_layer = CLUTTER_ACTOR(aisleriot_slot_renderer_get_animation_layer(AISLERIOT_SLOT_RENDERER(priv->moving_cards_origin_slot->slot_renderer)));
+ clutter_actor_raise_top (animation_layer);
+ }
+
+ priv->click_status = STATUS_NONE;
+ priv->moving_cards_origin_slot = NULL;
+ priv->moving_cards_origin_card_id = -1;
+}
+
+static gboolean
+cards_are_droppable (AisleriotBoard *board,
+ ArSlot *slot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ return slot != NULL &&
+ priv->moving_cards_origin_slot &&
+ aisleriot_game_drop_valid (priv->game,
+ priv->moving_cards_origin_slot->id,
+ slot->id,
+ priv->moving_cards->data,
+ priv->moving_cards->len);
+}
+
+static ArSlot *
+find_drop_target (AisleriotBoard *board,
+ gint x,
+ gint y)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *new_hslot;
+ ArSlot *retval = NULL;
+ gint i, new_cardid;
+ gint min_distance = G_MAXINT;
+
+ /* Find a target directly under the center of the card. */
+ get_slot_and_card_from_point (board,
+ x + priv->card_size.width / 2,
+ y + priv->card_size.height / 2,
+ &new_hslot, &new_cardid);
+
+ if (cards_are_droppable (board, new_hslot))
+ return new_hslot;
+
+ /* If that didn't work, look for a target at all 4 corners of the card. */
+ for (i = 0; i < 4; i++) {
+ get_slot_and_card_from_point (board,
+ x + priv->card_size.width * (i / 2),
+ y + priv->card_size.height * (i % 2),
+ &new_hslot, &new_cardid);
+
+ if (!new_hslot)
+ continue;
+
+ /* This skips corners we know are not droppable. */
+ if (!priv->droppable_supported || cards_are_droppable (board, new_hslot)) {
+ gint dx, dy, distance_squared;
+
+ dx = new_hslot->rect.x + (new_cardid - 1) * new_hslot->pixeldx - x;
+ dy = new_hslot->rect.y + (new_cardid - 1) * new_hslot->pixeldy - y;
+
+ distance_squared = dx * dx + dy * dy;
+
+ if (distance_squared <= min_distance) {
+ retval = new_hslot;
+ min_distance = distance_squared;
+ }
+ }
+ }
+
+ return retval;
+}
+
+static void
+drop_moving_cards (AisleriotBoard *board,
+ gint x,
+ gint y)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *hslot;
+ gboolean moved = FALSE;
+ guint i;
+
+ hslot = find_drop_target (board,
+ x - priv->last_click_x,
+ y - priv->last_click_y);
+
+ /* Reposition the removed cards so that they are relative to the
+ cursor position */
+ for (i = 0; i < priv->removed_cards->len; i++) {
+ RemovedCard *removed_card = &g_array_index (priv->removed_cards,
+ RemovedCard, i);
+
+ if (removed_card->from_drag) {
+ removed_card->cardx += x - priv->last_click_x;
+ removed_card->cardy += y - priv->last_click_y;
+ }
+ }
+
+ if (hslot) {
+ moved = aisleriot_game_drop_cards (priv->game,
+ priv->moving_cards_origin_slot->id,
+ hslot->id,
+ priv->moving_cards->data,
+ priv->moving_cards->len);
+ }
+
+ if (moved) {
+ aisleriot_game_end_move (priv->game);
+ games_sound_play ("click");
+ } else {
+ aisleriot_game_discard_move (priv->game);
+ games_sound_play ("slide");
+ }
+
+ drag_end (board, moved);
+
+ if (moved)
+ aisleriot_game_test_end_of_game (priv->game);
+}
+
+static void
+highlight_drop_target (AisleriotBoard *board,
+ ArSlot *slot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *old_slot = priv->highlight_slot;
+
+ if (slot == old_slot)
+ return;
+
+ /* Need to set the highlight slot even when we the cards aren't droppable
+ * since that can happen when the game doesn't support FEATURE_DROPPABLE.
+ */
+ priv->highlight_slot = slot;
+
+ /* Invalidate the old highlight rect */
+ if (old_slot != NULL &&
+ priv->show_highlight) {
+ /* FIXMEchpe only update the topmost card? */
+ /* It's ok to call this directly here, since the old highlight_slot cannot
+ * have been the same as the current selection_slot!
+ */
+ slot_update_card_images_full (board, old_slot, G_MAXINT);
+ }
+
+ if (!cards_are_droppable (board, slot))
+ return;
+
+ if (!priv->show_highlight)
+ return;
+
+ /* Prepare the highlight pixbuf/pixmaps */
+
+ /* FIXMEchpe only update the topmost card? */
+ /* It's ok to call this directly, since the highlight slot is always
+ * different from the selection slot!
+ */
+ slot_update_card_images_full (board, slot, ((int) slot->cards->len) - 1);
+}
+
+static void
+reveal_card (AisleriotBoard *board,
+ ArSlot *slot,
+ int cardid)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ Card card;
+ AisleriotSlotRenderer *renderer;
+
+ if (priv->show_card_slot == slot)
+ return;
+
+ if (priv->show_card_slot != NULL) {
+ if (priv->show_card_slot->slot_renderer) {
+ renderer = AISLERIOT_SLOT_RENDERER (priv->show_card_slot->slot_renderer);
+ aisleriot_slot_renderer_set_revealed_card (renderer, -1);
+ }
+ priv->show_card_slot = NULL;
+ priv->show_card_id = -1;
+ priv->click_status = STATUS_NONE;
+ }
+
+ if (!slot || cardid < 0 || cardid >= slot->cards->len - 1)
+ return;
+
+ card = CARD (slot->cards->data[cardid]);
+ if (CARD_GET_FACE_DOWN (card))
+ return;
+
+ priv->show_card_slot = slot;
+ priv->show_card_id = cardid;
+ priv->click_status = STATUS_SHOW;
+
+ renderer = AISLERIOT_SLOT_RENDERER (slot->slot_renderer);
+ aisleriot_slot_renderer_set_revealed_card (renderer, cardid);
+}
+
+static void
+clear_state (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ highlight_drop_target (board, NULL);
+ drag_end (board, FALSE /* FIXMEchpe ? */);
+
+ reveal_card (board, NULL, -1);
+
+ priv->click_status = STATUS_NONE;
+ priv->last_clicked_slot = NULL;
+ priv->last_clicked_card_id = -1;
+}
+
+/* Note: this unsets the selection! */
+static gboolean
+aisleriot_board_move_selected_cards_to_slot (AisleriotBoard *board,
+ ArSlot *hslot)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *selection_slot = priv->selection_slot;
+ int selection_start_card_id = priv->selection_start_card_id;
+ gboolean moved;
+ guint8 *cards;
+ guint n_cards;
+
+ if (!selection_slot ||
+ priv->selection_start_card_id < 0)
+ return FALSE;
+
+ /* NOTE: We cannot use aisleriot_game_drop_valid here since the
+ * game may not support the "droppable" feature.
+ */
+
+ set_selection (board, NULL, -1, FALSE);
+
+ priv->click_status = STATUS_NONE;
+
+ aisleriot_game_record_move (priv->game,
+ selection_slot->id,
+ selection_slot->cards->data,
+ selection_slot->cards->len);
+
+ /* Store the cards, since the move could alter slot->cards! */
+ g_assert (selection_slot->cards->len >= selection_start_card_id);
+ n_cards = selection_slot->cards->len - selection_start_card_id;
+
+ cards = g_alloca (n_cards);
+ memcpy (cards,
+ selection_slot->cards->data + selection_start_card_id,
+ n_cards);
+
+ /* Now take the cards off of the origin slot. We'll update the slot geometry later */
+ g_byte_array_set_size (selection_slot->cards, selection_start_card_id);
+ selection_slot->needs_update = TRUE;
+
+ moved = aisleriot_game_drop_cards (priv->game,
+ selection_slot->id,
+ hslot->id,
+ cards,
+ n_cards);
+ if (moved) {
+ aisleriot_game_end_move (priv->game);
+
+ games_sound_play ("click");
+
+ if (selection_slot->needs_update)
+ g_signal_emit_by_name (priv->game, "slot-changed", selection_slot); /* FIXMEchpe! */
+
+ aisleriot_game_test_end_of_game (priv->game);
+ } else {
+ /* Not moved; discard the move add the cards back to the origin slot */
+ aisleriot_game_discard_move (priv->game);
+ aisleriot_game_slot_add_cards (priv->game, selection_slot, cards, n_cards);
+ }
+
+ return moved;
+}
+
+/* Keynav */
+
+#ifdef ENABLE_KEYNAV
+
+/** Keynav specification:
+ *
+ * Focus state consists of the slot that has the focus, and the card
+ * in that slot of the card that has the focus; if the slot has no cards
+ * the focus is on the empty slot itself.
+ *
+ * Without modifiers, for focus movement:
+ * Left (Right): For right-extended slots, moves the focus to the card
+ * under (over) the currently focused card on the same slot.
+ * If the focused card is already the bottommost (topmost)
+ * card of the slot, or the slot is not right-extended, moves the focus
+ * to the topmost (bottommost) card on the slot left (right) to the currently
+ * focused slot (wraps around from first to last slot and vv.)
+ * Up (Down): For down-extended slots, moves the focus to the card
+ * under (over) the currently focused card on the same slot.
+ * If the focused card is already the bottommost (topmost) card
+ * of the slot, or the slot is not down-extended, moves the focus to
+ * the topmost (bottommost) card of the slot over (under) the currently
+ * focused slot (wraps around vertically)
+ * Home (End): For down- or right-extended slots, moves the focus to the
+ * start (end) of the stack of face-up cards on the currently focused slot.
+ * If the focus is already on that card, moves the focus over a stack of
+ * face-down cards into the next stack of face-up cards (e.g. Athena), or
+ * the bottommost (topmost) card on the slot if there are no such cards.
+ * PageUp, PageDown: Acts like <control>Up, <control>Down
+ * Space: selects the cards from the currently focused card to the topmost
+ * card of the slot, if this is allowed by the game. If the focused card is
+ * the first selected card already, unsets the selection.
+ * Return: Performs the button press action on the focused card.
+ * If no action was performed by that, moves the selected card(s) to the top
+ * of the focused slot, if this is allowed by the game.
+ *
+ * With <control>, for focus movement:
+ * Left (Right): Moves the focus to the bottommost (topmost) card on the
+ * slot; if that card is already focused, moves the focus to the
+ * topmost/bottommost card on the slot left/right to the currently
+ * focused slot (wraps around from first to last slot and vv.)
+ * Up (Down): Moves the focus to the topmost (bottommost) card of the slot
+ * over (under) the currently focused slot (wraps around vertically)
+ * Home (End): moves the focus to the bottommost (topmost) card on the
+ * first (last) slot
+ * Return: Performs the double-click action on the focused card
+ *
+ * With <shift>: extends the selection; focus movement itself occurs
+ * like for the same key without modifiers.
+ * Left (Right): for right-extended slots, extends (shrinks) the selection
+ * by one card, if this is allowed by the game
+ * Up (Down): for down-extended slots, extends (shrinks) the selection
+ * by one card, if this is allowed by the game
+ * Home: extends the selection maximally in the focused slot, as allowed
+ * by the game
+ * End: shrinks the selection into nonexistence
+ *
+ * With <control><shift>: extends selection like with <shift> alone,
+ * and moves focus like with <control> alone
+ *
+ * Other keyboard shortcuts:
+ * <control>A: extends the selection maximally in the focused slot, as allowed
+ * by the game
+ * <shift><control>A: unsets the selection
+ *
+ * Notes:
+ * If no slot is currently focused:
+ * Right, Up, Down, PgUp, PgDown, Home: moves the focus to the bottommost card on the first
+ * slot
+ * Left, End: moves the focus to the topmost card on the last slot
+ */
+
+static void
+aisleriot_board_add_move_binding (ClutterBindingPool *binding_pool,
+ GClosure *closure,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ clutter_binding_pool_install_closure (binding_pool,
+ action,
+ keyval,
+ modifiers,
+ closure);
+
+ if (modifiers & CLUTTER_CONTROL_MASK)
+ return;
+
+ clutter_binding_pool_install_closure (binding_pool,
+ action,
+ keyval,
+ modifiers | CLUTTER_CONTROL_MASK,
+ closure);
+}
+
+static void
+aisleriot_board_add_move_and_select_binding (ClutterBindingPool *binding_pool,
+ GClosure *closure,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ aisleriot_board_add_move_binding (binding_pool, closure, action, keyval, modifiers);
+ aisleriot_board_add_move_binding (binding_pool, closure, action, keyval, modifiers | CLUTTER_SHIFT_MASK);
+}
+
+static void
+aisleriot_board_add_activate_binding (ClutterBindingPool *binding_pool,
+ GClosure *closure,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("activate"),
+ keyval,
+ modifiers,
+ closure);
+
+ if (modifiers & CLUTTER_CONTROL_MASK)
+ return;
+
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("activate"),
+ keyval,
+ modifiers | CLUTTER_CONTROL_MASK,
+ closure);
+}
+
+static gboolean
+aisleriot_board_move_cursor_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot;
+ int new_focus_card_id, first_card_id;
+
+ focus_slot = priv->focus_slot;
+ first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+ new_focus_card_id = priv->focus_card_id + count;
+ if (new_focus_card_id < first_card_id || new_focus_card_id >= (int) focus_slot->cards->len)
+ return FALSE;
+
+ set_focus (board, focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_start_end_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot = priv->focus_slot;
+ int first_card_id, top_card_id, new_focus_card_id;
+ guint8 *cards;
+
+ if (focus_slot->cards->len == 0)
+ return FALSE;
+
+ g_assert (priv->focus_card_id >= 0);
+
+ /* Moves the cursor to the first/last card above/below a face-down
+ * card, or the start/end of the slot if there are no face-down cards
+ * between the currently focused card and the slot start/end.
+ * (Jumping over face-down cards and landing on a non-face-down card
+ * happens e.g. in Athena.)
+ */
+ cards = focus_slot->cards->data;
+ top_card_id = ((int) focus_slot->cards->len) - 1;
+ first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+ new_focus_card_id = priv->focus_card_id;
+
+ /* Set new_focus_card_id to the index of the last face-down card
+ * in the run of face-down cards.
+ */
+ do {
+ new_focus_card_id += count;
+ } while (new_focus_card_id >= first_card_id &&
+ new_focus_card_id <= top_card_id &&
+ CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id])));
+
+ /* We went one too far */
+ new_focus_card_id -= count;
+
+ /* Now get to the start/end of the run of face-up cards */
+ do {
+ new_focus_card_id += count;
+ } while (new_focus_card_id >= first_card_id &&
+ new_focus_card_id <= top_card_id &&
+ !CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id])));
+
+ if (new_focus_card_id < first_card_id ||
+ new_focus_card_id > top_card_id ||
+ CARD_GET_FACE_DOWN (((Card) cards[new_focus_card_id]))) {
+ /* We went one too far */
+ new_focus_card_id -= count;
+ }
+
+ new_focus_card_id = CLAMP (new_focus_card_id, first_card_id, top_card_id);
+ set_focus (board, focus_slot, new_focus_card_id, TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_extend_selection_in_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot, *selection_slot;
+ int new_selection_start_card_id, first_card_id;
+
+ focus_slot = priv->focus_slot;
+ selection_slot = priv->selection_slot;
+ first_card_id = ((int) focus_slot->cards->len) - ((int) focus_slot->exposed);
+
+ if (selection_slot == focus_slot) {
+ new_selection_start_card_id = priv->selection_start_card_id + count;
+
+ /* Can only extend the selection if the focus is adjacent to the selection */
+ if (priv->focus_card_id - 1 > new_selection_start_card_id ||
+ priv->focus_card_id + 1 < new_selection_start_card_id)
+ return FALSE;
+ } else {
+ /* No selection yet */
+ new_selection_start_card_id = ((int) focus_slot->cards->len) + count;
+
+ /* Must have the topmost card focused */
+ if (new_selection_start_card_id != priv->focus_card_id)
+ return FALSE;
+ }
+
+ if (new_selection_start_card_id < first_card_id)
+ return FALSE;
+
+ /* If it's the top card, unselect all */
+ if (new_selection_start_card_id >= focus_slot->cards->len) {
+ set_selection (board, NULL, -1, FALSE);
+ return TRUE;
+ }
+
+ if (!aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + new_selection_start_card_id,
+ focus_slot->cards->len - new_selection_start_card_id))
+ return FALSE;
+
+ set_selection (board, focus_slot, new_selection_start_card_id, TRUE);
+
+ /* Try to move the cursor too, but don't beep if that fails */
+ aisleriot_board_move_cursor_in_slot (board, count);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_extend_selection_in_slot_maximal (AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot = priv->focus_slot;
+ int new_selection_start_card_id, n_selected;
+
+ n_selected = 0;
+ new_selection_start_card_id = ((int) focus_slot->cards->len) - 1;
+ while (new_selection_start_card_id >= 0) {
+ if (!aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + new_selection_start_card_id,
+ focus_slot->cards->len - new_selection_start_card_id))
+ break;
+
+ ++n_selected;
+ --new_selection_start_card_id;
+ }
+
+ if (n_selected == 0)
+ return FALSE;
+
+ set_selection (board, focus_slot, new_selection_start_card_id + 1, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_left_right_by_slot (AisleriotBoard *board,
+ int count,
+ gboolean wrap)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ guint n_slots;
+ ArSlot *focus_slot, *new_focus_slot;
+ int focus_slot_index, new_focus_slot_index;
+ int new_focus_slot_topmost_card_id, new_focus_card_id;
+ gboolean is_rtl;
+
+ slots = aisleriot_game_get_slots (priv->game);
+ if (!slots || slots->len == 0)
+ return FALSE;
+
+ n_slots = slots->len;
+
+ focus_slot = priv->focus_slot;
+ g_assert (focus_slot != NULL);
+
+ focus_slot_index = get_slot_index_from_slot (board, focus_slot);
+
+ /* Move visually */
+ is_rtl = priv->is_rtl;
+ if (priv->is_rtl) {
+ new_focus_slot_index = focus_slot_index - count;
+ } else {
+ new_focus_slot_index = focus_slot_index + count;
+ }
+
+ /* Wrap-around? */
+ if (new_focus_slot_index < 0 ||
+ new_focus_slot_index >= n_slots) {
+ if (!wrap)
+ return FALSE;
+
+#ifdef FIXMEchpe
+{
+ GtkDirectionType direction;
+#if GTK_CHECK_VERSION (2, 12, 0)
+
+ if (count > 0) {
+ direction = GTK_DIR_RIGHT;
+ } else {
+ direction = GTK_DIR_LEFT;
+ }
+
+ if (!gtk_widget_keynav_failed (widget, direction)) {
+ return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
+ }
+#endif /* GTK 2.12. 0 */
+}
+#endif // FIXMEchpe
+
+ if (new_focus_slot_index < 0) {
+ new_focus_slot_index = ((int) n_slots) - 1;
+ } else {
+ new_focus_slot_index = 0;
+ }
+ }
+
+ g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
+
+ new_focus_slot = slots->pdata[new_focus_slot_index];
+ new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
+
+ if (new_focus_slot->expanded_right) {
+ if ((is_rtl && count < 0) ||
+ (!is_rtl && count > 0)) {
+ if (new_focus_slot->cards->len > 0) {
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
+ } else {
+ new_focus_card_id = -1;
+ }
+ } else {
+ new_focus_card_id = new_focus_slot_topmost_card_id;
+ }
+ } else {
+ /* Just take the topmost card */
+ new_focus_card_id = new_focus_slot_topmost_card_id;
+ }
+
+ set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_up_down_by_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ guint n_slots;
+ ArSlot *focus_slot, *new_focus_slot;
+ int focus_slot_index, new_focus_slot_index;
+ int new_focus_slot_topmost_card_id, new_focus_card_id;
+ int x_start, x_end;
+
+ slots = aisleriot_game_get_slots (priv->game);
+ if (!slots || slots->len == 0)
+ return FALSE;
+
+ n_slots = slots->len;
+
+ focus_slot = priv->focus_slot;
+ g_assert (focus_slot != NULL);
+
+ x_start = focus_slot->rect.x;
+ x_end = x_start + focus_slot->rect.width;
+
+ focus_slot_index = get_slot_index_from_slot (board, focus_slot);
+
+ new_focus_slot_index = focus_slot_index;
+ do {
+ new_focus_slot_index += count;
+ } while (new_focus_slot_index >= 0 &&
+ new_focus_slot_index < n_slots &&
+ !test_slot_projection_intersects_x (slots->pdata[new_focus_slot_index], x_start, x_end));
+
+ if (new_focus_slot_index < 0 || new_focus_slot_index == n_slots) {
+#ifdef FIXMEchpe
+#if GTK_CHECK_VERSION (2, 12, 0)
+ GtkWidget *widget = GTK_WIDGET (board);
+ GtkDirectionType direction;
+
+ if (count > 0) {
+ direction = GTK_DIR_DOWN;
+ } else {
+ direction = GTK_DIR_UP;
+ }
+
+ if (!gtk_widget_keynav_failed (widget, direction)) {
+ return gtk_widget_child_focus (gtk_widget_get_toplevel (widget), direction);
+ }
+#endif /* GTK 2.12. 0 */
+#endif // FIXMEchpe
+
+ /* Wrap around */
+ if (count > 0) {
+ new_focus_slot_index = -1;
+ } else {
+ new_focus_slot_index = n_slots;
+ }
+
+ do {
+ new_focus_slot_index += count;
+ } while (new_focus_slot_index != focus_slot_index &&
+ !test_slot_projection_intersects_x (slots->pdata[new_focus_slot_index], x_start, x_end));
+ }
+
+ g_assert (new_focus_slot_index >= 0 && new_focus_slot_index < n_slots);
+
+ new_focus_slot = slots->pdata[new_focus_slot_index];
+ new_focus_slot_topmost_card_id = ((int) new_focus_slot->cards->len) - 1;
+
+ if (new_focus_slot->expanded_down) {
+ if (count > 0) {
+ if (new_focus_slot->cards->len > 0) {
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
+ } else {
+ new_focus_card_id = -1;
+ }
+ } else {
+ new_focus_card_id = new_focus_slot_topmost_card_id;
+ }
+ } else {
+ /* Just take the topmost card */
+ new_focus_card_id = new_focus_slot_topmost_card_id;
+ }
+
+ set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_start_end_by_slot (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ GPtrArray *slots;
+ ArSlot *new_focus_slot;
+ int new_focus_card_id;
+
+ slots = aisleriot_game_get_slots (priv->game);
+ if (!slots || slots->len == 0)
+ return FALSE;
+
+ if (count > 0) {
+ new_focus_slot = (ArSlot *) slots->pdata[slots->len - 1];
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - 1;
+ } else {
+ new_focus_slot = (ArSlot *) slots->pdata[0];
+ if (new_focus_slot->cards->len > 0) {
+ new_focus_card_id = ((int) new_focus_slot->cards->len) - ((int) new_focus_slot->exposed);
+ } else {
+ new_focus_card_id = -1;
+ }
+ }
+
+ g_assert (new_focus_slot != NULL);
+ g_assert (new_focus_card_id >= -1);
+
+ set_focus (board, new_focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_move_cursor_left_right (AisleriotBoard *board,
+ int count,
+ gboolean is_control)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ /* First try in-slot focus movement */
+ if (!is_control &&
+ priv->focus_slot->expanded_right &&
+ aisleriot_board_move_cursor_in_slot (board, priv->is_rtl ? -count : count))
+ return TRUE;
+
+ /* Cannot move in-slot; move focused slot */
+ return aisleriot_board_move_cursor_left_right_by_slot (board, count, TRUE);
+}
+
+static gboolean
+aisleriot_board_move_cursor_up_down (AisleriotBoard *board,
+ int count,
+ gboolean is_control)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ g_assert (priv->focus_slot != NULL);
+
+ /* First try in-slot focus movement */
+ if (!is_control &&
+ priv->focus_slot->expanded_down &&
+ aisleriot_board_move_cursor_in_slot (board, count))
+ return TRUE;
+
+ /* Cannot move in-slot; move focused slot */
+ return aisleriot_board_move_cursor_up_down_by_slot (board, count);
+}
+
+static gboolean
+aisleriot_board_extend_selection_left_right (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!priv->focus_slot->expanded_right)
+ return FALSE;
+
+ return aisleriot_board_extend_selection_in_slot (board, count);
+}
+
+static gboolean
+aisleriot_board_extend_selection_up_down (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!priv->focus_slot->expanded_down)
+ return FALSE;
+
+ return aisleriot_board_extend_selection_in_slot (board, count);
+}
+
+static gboolean
+aisleriot_board_extend_selection_start_end (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot = priv->focus_slot;
+ int new_focus_card_id;
+
+ if (count > 0) {
+ /* Can only shrink the selection if the focus is on the selected slot,
+ * and the focused card is on or below the start of the selection.
+ */
+ if (priv->selection_slot == focus_slot &&
+ priv->selection_start_card_id >= priv->focus_card_id) {
+ set_selection (board, NULL, -1, FALSE);
+ new_focus_card_id = ((int) focus_slot->cards->len);
+ } else {
+ aisleriot_board_error_bell (board);
+ return FALSE;
+ }
+
+ } else {
+ if (!aisleriot_board_extend_selection_in_slot_maximal (board)) {
+ set_selection (board, NULL, -1, FALSE);
+ aisleriot_board_error_bell (board);
+ return FALSE;
+ }
+
+ new_focus_card_id = priv->selection_start_card_id;
+ }
+
+ set_focus (board, focus_slot, new_focus_card_id, TRUE);
+ return TRUE;
+}
+
+#endif /* ENABLE_KEYNAV */
+
+/* Game state handling */
+
+static void
+game_type_changed_cb (AisleriotGame *game,
+ AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ guint features;
+
+ features = aisleriot_game_get_features (game);
+
+ priv->droppable_supported = ((features & FEATURE_DROPPABLE) != 0);
+ priv->show_highlight = priv->droppable_supported;
+}
+
+static void
+game_cleared_cb (AisleriotGame *game,
+ AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ priv->geometry_set = FALSE;
+
+ /* So we don't re-add the cards to the now-dead slot */
+ priv->highlight_slot = NULL;
+ priv->last_clicked_slot = NULL;
+ priv->moving_cards_origin_slot = NULL;
+ priv->selection_slot = NULL;
+ priv->show_card_slot = NULL;
+
+ clear_state (board);
+}
+
+static void
+game_new_cb (AisleriotGame *game,
+ AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ clear_state (board);
+
+ set_focus (board, NULL, -1, FALSE);
+ set_selection (board, NULL, -1, FALSE);
+
+ aisleriot_game_get_geometry (game, &priv->width, &priv->height);
+
+ aisleriot_board_setup_geometry (board);
+
+#if 0
+ g_print ("{ %.3f , %.3f /* %s */ },\n",
+ priv->width, priv->height,
+ aisleriot_game_get_game_file (priv->game));
+#endif
+
+ /* Check for animations so that the differences will be reset */
+ queue_check_animations (board);
+}
+
+static void
+slot_changed_cb (AisleriotGame *game,
+ ArSlot *slot,
+ AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ slot_update_geometry (board, slot);
+ slot_update_card_images (board, slot);
+
+ if (slot == priv->moving_cards_origin_slot) {
+ /* PANIC! */
+ /* FIXMEchpe */
+ }
+ if (slot == priv->selection_slot) {
+ set_selection (board, NULL, -1, FALSE);
+
+ /* If this slot changes while we're in a click cycle, abort the action.
+ * That prevents a problem where the cards that were selected and
+ * about to be dragged have vanished because of autoplay; c.f. bug #449767.
+ * Note: we don't use clear_state() here since we only want to disable
+ * the highlight and revealed card if that particular slot changed, see
+ * the code above. And we don't clear last_clicked_slot/card_id either, so
+ * a double-click will still work.
+ */
+ priv->click_status = STATUS_NONE;
+ }
+ if (slot == priv->focus_slot) {
+ /* Try to keep the focus intact. If the focused card isn't there
+ * anymore, this will set the focus to the topmost card of there
+ * same slot, or the slot itself if there are no cards on it.
+ * If the slot was empty but now isn't, we set the focus to the
+ * topmost card.
+ */
+ if (priv->focus_card_id < 0) {
+ set_focus (board, slot, ((int) slot->cards->len) - 1, priv->show_focus);
+ } else {
+ set_focus (board, slot, priv->focus_card_id, priv->show_focus);
+ }
+ }
+ if (slot == priv->highlight_slot) {
+ highlight_drop_target (board, NULL);
+ }
+
+ queue_check_animations (board);
+}
+
+/* Style handling */
+
+static void
+aisleriot_board_sync_style (ArStyle *style,
+ GParamSpec *pspec,
+ AisleriotBoard *board)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ const char *pspec_name;
+ gboolean update_geometry = FALSE, redraw_focus = FALSE;
+
+ g_assert (style == priv->style);
+
+ if (pspec != NULL) {
+ pspec_name = pspec->name;
+ } else {
+ pspec_name = NULL;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_THEME)) {
+ ArCardTheme *theme;
+
+ theme = ar_style_get_card_theme (style);
+ if (theme != NULL) {
+ ar_card_textures_cache_set_theme (priv->textures, theme);
+
+ priv->geometry_set = FALSE;
+
+ update_geometry |= TRUE;
+ }
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_SLOT_RATIO)) {
+ double card_slot_ratio;
+
+ card_slot_ratio = ar_style_get_card_slot_ratio (style);
+
+ update_geometry |= (card_slot_ratio != priv->card_slot_ratio);
+
+ priv->card_slot_ratio = card_slot_ratio;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_OVERHANG)) {
+ update_geometry |= TRUE;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CARD_STEP)) {
+ update_geometry |= TRUE;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_INTERIOR_FOCUS)) {
+ gboolean interior_focus;
+
+ interior_focus = ar_style_get_interior_focus (style);
+
+ redraw_focus = (interior_focus != priv->interior_focus);
+
+ priv->interior_focus = interior_focus;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_LINE_WIDTH)) {
+ int focus_line_width;
+
+ focus_line_width = ar_style_get_focus_line_width (style);
+
+ redraw_focus = (focus_line_width != priv->focus_line_width);
+
+ priv->focus_line_width = focus_line_width;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_FOCUS_PADDING)) {
+ int focus_padding;
+
+ focus_padding = ar_style_get_focus_padding (style);
+
+ redraw_focus = (focus_padding != priv->focus_padding);
+
+ priv->focus_padding = focus_padding;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_RTL)) {
+ gboolean is_rtl;
+
+ is_rtl = ar_style_get_rtl (style);
+
+ update_geometry |= (is_rtl != priv->is_rtl);
+
+ priv->is_rtl = is_rtl;
+
+ /* FIXMEchpe: necessary? */
+ priv->force_geometry_update = TRUE;
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_ENABLE_ANIMATIONS)) {
+ /* FIXMEchpe: abort animations-in-progress if the setting is now OFF */
+ }
+
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_CLICK_TO_MOVE)) {
+ gboolean click_to_move;
+
+ click_to_move = ar_style_get_click_to_move (style);
+ if (click_to_move != priv->click_to_move) {
+ /* Clear the selection. Do this before setting the new value,
+ * since otherwise selection won't get cleared correctly.
+ */
+ set_selection (board, NULL, -1, FALSE);
+
+ priv->click_to_move = click_to_move;
+
+ /* FIXMEchpe: we used to queue a redraw here. WHY?? Check that it's safe not to. */
+ }
+ }
+
+#ifndef HAVE_HILDON
+ if (pspec_name == NULL || pspec_name == I_(AR_STYLE_PROP_SHOW_STATUS_MESSAGES)) {
+ gboolean show_status_messages;
+
+ show_status_messages = ar_style_get_show_status_messages (priv->style);
+
+ if (show_status_messages != priv->show_status_messages) {
+ priv->show_status_messages = show_status_messages;
+
+ if (!show_status_messages) {
+ /* Clear message */
+ set_status_message (board, NULL);
+ }
+ }
+ }
+#endif /* !HAVE_HILDON */
+
+ /* FIXMEchpe: queue a relayout instead? */
+ if (update_geometry) {
+ aisleriot_board_setup_geometry (board);
+ }
+
+ if (redraw_focus) {
+ /* FIXMEchpe: do redraw the focus! */
+ }
+}
+
+/* Class implementation */
+
+G_DEFINE_TYPE (AisleriotBoard, aisleriot_board, CLUTTER_TYPE_GROUP);
+
+/* AisleriotBoardClass methods */
+
+#ifdef ENABLE_KEYNAV
+
+static void
+aisleriot_board_activate (AisleriotBoard *board,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ AisleriotBoardPrivate *priv = board->priv; ArSlot *focus_slot = priv->focus_slot;
+ ArSlot *selection_slot = priv->selection_slot;
+ int selection_start_card_id = priv->selection_start_card_id;
+
+#ifdef FIXMEchpe
+ if (!gtk_widget_has_focus (widget))
+ return;
+#endif
+
+ _games_debug_print (GAMES_DEBUG_GAME_KEYNAV,
+ "board ::activate keyval %x modifiers %x\n",
+ keyval, modifiers);
+
+ if (!focus_slot) {
+ aisleriot_board_error_bell (board);
+ return;
+ }
+
+ /* Focus not shown? Show it, and do nothing else */
+ if (!priv->show_focus) {
+ set_focus (board, focus_slot, priv->focus_card_id, TRUE);
+ return;
+ }
+
+ /* Control-Activate is double-click */
+ if (modifiers & CLUTTER_CONTROL_MASK) {
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+ if (aisleriot_game_button_double_clicked_lambda (priv->game, focus_slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ } else {
+ aisleriot_game_discard_move (priv->game);
+ aisleriot_board_error_bell (board);
+ }
+
+ aisleriot_game_test_end_of_game (priv->game);
+
+ return;
+ }
+
+ /* Try single click action */
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+
+ if (aisleriot_game_button_clicked_lambda (priv->game, focus_slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ games_sound_play ("click");
+ aisleriot_game_test_end_of_game (priv->game);
+
+ return;
+ }
+
+ aisleriot_game_discard_move (priv->game);
+
+ /* If we have a selection, and the topmost card of a slot is focused,
+ * try to move the selected cards to the focused slot.
+ * Note that this needs to be tested even if selection_slot == focus_slot !
+ *
+ * NOTE: We cannot use aisleriot_game_drop_valid here since the
+ * game may not support the "droppable" feature.
+ */
+ if (selection_slot != NULL &&
+ selection_start_card_id >= 0 &&
+ priv->focus_card_id == ((int) focus_slot->cards->len) - 1) {
+ if (aisleriot_board_move_selected_cards_to_slot (board, focus_slot)) {
+ /* Select the new topmost card */
+ set_focus (board, focus_slot, ((int) focus_slot->cards->len - 1), TRUE);
+
+ return;
+ }
+
+ /* Trying to move the cards has unset the selection; re-select them */
+ set_selection (board, selection_slot, selection_start_card_id, TRUE);
+ }
+
+ aisleriot_board_error_bell (board);
+}
+
+static gboolean
+aisleriot_board_move_cursor (AisleriotBoard *board,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ gboolean is_control, is_shift, moved = FALSE;
+ char step;
+ int count;
+
+#ifdef FIXMEchpe
+ if (!gtk_widget_has_focus (widget))
+ return FALSE;
+#endif
+
+ step = action[0];
+ count = (action[1] == MOVE_LEFT ? -1 : 1);
+
+ _games_debug_print (GAMES_DEBUG_GAME_KEYNAV,
+ "board ::move-cursor keyval %x modifiers %x step '%c' count %d\n",
+ keyval, modifiers,
+ step, count);
+
+ /* No focus? Set focus to the first/last slot */
+ /* This will always return TRUE, no need for keynav-failed handling */
+ if (!priv->focus_slot) {
+ switch (step) {
+ case MOVE_CURSOR_UP_DOWN:
+ case MOVE_CURSOR_PAGES:
+ /* Focus the first slot */
+ return aisleriot_board_move_cursor_start_end_by_slot (board, -1);
+ case MOVE_CURSOR_LEFT_RIGHT:
+ /* Move as if we'd been on the last/first slot */
+ if (!priv->is_rtl) {
+ return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
+ }
+ /* fall-through */
+ default:
+ return aisleriot_board_move_cursor_start_end_by_slot (board, count);
+ }
+ }
+
+ g_assert (priv->focus_slot != NULL);
+
+ is_shift = (modifiers & CLUTTER_SHIFT_MASK) != 0;
+ is_control = (modifiers & CLUTTER_CONTROL_MASK) != 0;
+
+ switch (step) {
+ case MOVE_CURSOR_LEFT_RIGHT:
+ if (is_shift) {
+ moved = aisleriot_board_extend_selection_left_right (board, count);
+ } else {
+ moved = aisleriot_board_move_cursor_left_right (board, count, is_control);
+ }
+ break;
+ case MOVE_CURSOR_UP_DOWN:
+ if (is_shift) {
+ moved = aisleriot_board_extend_selection_up_down (board, count);
+ } else {
+ moved = aisleriot_board_move_cursor_up_down (board, count, is_control);
+ }
+ break;
+ case MOVE_CURSOR_PAGES:
+ if (!is_shift) {
+ moved = aisleriot_board_move_cursor_up_down (board, count, TRUE);
+ }
+ break;
+ case MOVE_CURSOR_START_END:
+ if (is_shift) {
+ moved = aisleriot_board_extend_selection_start_end (board, count);
+ } else if (is_control) {
+ moved = aisleriot_board_move_cursor_start_end_by_slot (board, count);
+ } else {
+ moved = aisleriot_board_move_cursor_start_end_in_slot (board, count);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Show focus */
+ if (!moved &&
+ !priv->show_focus) {
+ set_focus (board, priv->focus_slot, priv->focus_card_id, TRUE);
+ }
+
+ return moved;
+}
+
+static void
+aisleriot_board_select_all (AisleriotBoard *board,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot = priv->focus_slot;
+
+ _games_debug_print (GAMES_DEBUG_GAME_KEYNAV,
+ "board ::select-all keyval %x modifiers %x\n",
+ keyval, modifiers);
+
+ if (!focus_slot ||
+ focus_slot->cards->len == 0 ||
+ !aisleriot_board_extend_selection_in_slot_maximal (board)) {
+ set_selection (board, NULL, -1, FALSE);
+ aisleriot_board_error_bell (board);
+ }
+}
+
+static void
+aisleriot_board_deselect_all (AisleriotBoard *board,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_KEYNAV,
+ "board ::deselect-all keyval %x modifiers %x\n",
+ keyval, modifiers);
+
+ set_selection (board, NULL, -1, FALSE);
+}
+
+static void
+aisleriot_board_toggle_selection (AisleriotBoard *board,
+ const char *action,
+ guint keyval,
+ ClutterModifierType modifiers)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *focus_slot;
+ int focus_card_id;
+
+ _games_debug_print (GAMES_DEBUG_GAME_KEYNAV,
+ "board ::toggle-selection keyval %x modifiers %x\n",
+ keyval, modifiers);
+
+ focus_slot = priv->focus_slot;
+ if (!focus_slot)
+ return;
+
+ focus_card_id = priv->focus_card_id;
+
+ /* Focus not shown? Show it, and proceed */
+ if (!priv->show_focus) {
+ set_focus (board, focus_slot, focus_card_id, TRUE);
+ }
+
+ if (focus_card_id < 0) {
+ aisleriot_board_error_bell (board);
+ return;
+ }
+
+ /* If the selection isn't currently showing, don't truncate it.
+ * Otherwise we get unexpected results when clicking on some cards
+ * (which selects them but doesn't show the selection) and then press
+ * Space or Shift-Up/Down etc.
+ */
+ if (priv->selection_slot == focus_slot &&
+ priv->selection_start_card_id == focus_card_id &&
+ priv->show_selection) {
+ set_selection (board, NULL, -1, FALSE);
+ return;
+ }
+
+ if (!aisleriot_game_drag_valid (priv->game,
+ focus_slot->id,
+ focus_slot->cards->data + focus_card_id,
+ focus_slot->cards->len - focus_card_id)) {
+ aisleriot_board_error_bell (board);
+ return;
+ }
+
+ set_selection (board, focus_slot, focus_card_id, TRUE);
+}
+
+#endif /* ENABLE_KEYNAV */
+
+
+/* ClutterActorClass impl */
+
+#if 0
+static void
+aisleriot_board_realize (GtkWidget *widget)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (widget);
+// AisleriotBoardPrivate *priv = board->priv;
+
+ GTK_WIDGET_CLASS (aisleriot_board_parent_class)->realize (widget);
+
+ aisleriot_board_setup_geometry (board);
+}
+
+static void
+aisleriot_board_unrealize (GtkWidget *widget)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (widget);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ priv->geometry_set = FALSE;
+
+ clear_state (board);
+
+ GTK_WIDGET_CLASS (aisleriot_board_parent_class)->unrealize (widget);
+}
+#endif
+
+static void
+aisleriot_board_allocate (ClutterActor *actor,
+ const ClutterActorBox *box,
+ ClutterAllocationFlags flags)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (actor);
+ AisleriotBoardPrivate *priv = board->priv;
+ gboolean is_same;
+
+ is_same = clutter_actor_box_equal (box, &priv->allocation);
+
+ _games_debug_print (GAMES_DEBUG_GAME_SIZING,
+ "board ::allocate (%f / %f)-(%f / %f) => %f x %f is-same %s force-update %s\n",
+ box->x1, box->y1, box->x2, box->y2,
+ box->x2 - box->x1, box->y2 - box->y1,
+ is_same ? "t" : "f",
+ priv->force_geometry_update ? "t" : "f");
+
+ CLUTTER_ACTOR_CLASS (aisleriot_board_parent_class)->allocate (actor, box, flags);
+
+ priv->allocation = *box;
+
+ if (is_same && !priv->force_geometry_update)
+ return;
+
+ priv->force_geometry_update = FALSE;
+
+ /* FIXMEchpe: just queue this instead maybe? */
+ aisleriot_board_setup_geometry (board);
+}
+
+static void
+aisleriot_board_get_preferred_width (ClutterActor *actor,
+ float for_height,
+ float *min_width_p,
+ float *natural_width_p)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_SIZING,
+ "board ::get-preferred-width\n");
+
+ *min_width_p = BOARD_MIN_WIDTH;
+ *natural_width_p = 3 * BOARD_MIN_WIDTH;
+}
+
+static void
+aisleriot_board_get_preferred_height (ClutterActor *actor,
+ float for_width,
+ float *min_height_p,
+ float *natural_height_p)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_SIZING,
+ "board ::get-preferred-height\n");
+
+ *min_height_p = BOARD_MIN_HEIGHT;
+ *natural_height_p = 3 * BOARD_MIN_HEIGHT;
+}
+
+#ifdef ENABLE_KEYNAV
+
+static gboolean
+aisleriot_board_focus (AisleriotBoard *board,
+ int count)
+{
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (!priv->focus_slot) {
+ return aisleriot_board_move_cursor_start_end_by_slot (board, -count);
+ }
+
+#if 0
+ if (aisleriot_board_move_cursor_left_right_by_slot (board, count, FALSE))
+ return TRUE;
+#endif
+
+ return FALSE;
+}
+
+static gboolean
+aisleriot_board_key_press (ClutterActor *actor,
+ ClutterKeyEvent *event)
+{
+ ClutterBindingPool *pool;
+
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::key-press keyval %x modifiers %x\n",
+ event->keyval, event->modifier_state);
+
+ pool = clutter_binding_pool_get_for_class (CLUTTER_ACTOR_GET_CLASS (actor));
+ g_assert (pool != NULL);
+
+ return clutter_binding_pool_activate (pool,
+ event->keyval,
+ event->modifier_state,
+ G_OBJECT (actor));
+}
+
+#endif /* ENABLE_KEYNAV */
+
+#ifdef FIXMEchpe
+/* The gtkwidget.c focus in/out handlers queue a shallow draw;
+ * that's ok for us but maybe we want to optimise this a bit to
+ * only do it if we have a focus to draw/erase?
+ */
+static gboolean
+aisleriot_board_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+#ifdef ENABLE_KEYNAV
+ AisleriotBoard *board = AISLERIOT_BOARD (widget);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ /* Paint focus */
+ if (priv->show_focus &&
+ priv->focus_slot != NULL) {
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+ }
+#endif /* ENABLE_KEYNAV */
+
+ return FALSE;
+}
+
+static gboolean
+aisleriot_board_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (widget);
+#ifdef ENABLE_KEYNAV
+ AisleriotBoardPrivate *priv = board->priv;
+#endif /* ENABLE_KEYNAV */
+
+ clear_state (board);
+
+#ifdef ENABLE_KEYNAV
+ /* Hide focus */
+ if (priv->show_focus &&
+ priv->focus_slot != NULL) {
+ gdk_window_invalidate_rect (widget->window, &priv->focus_rect, FALSE);
+ }
+#endif /* ENABLE_KEYNAV */
+
+ return FALSE;
+}
+#endif /* FIXMEchpe */
+
+static gboolean
+aisleriot_board_button_press (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (actor);
+ AisleriotBoardPrivate *priv = board->priv;
+ ArSlot *hslot;
+ int cardid;
+ guint32 button;
+ gboolean drag_valid;
+ guint state;
+ gboolean is_double_click, show_focus;
+
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::button-press @(%f / %f) button %d click-count %d modifiers %x\n",
+ event->x, event->y,
+ event->button, event->click_count,
+ event->modifier_state);
+
+ /* NOTE: It's ok to just return instead of chaining up, since the
+ * parent classes have no class closure for this event.
+ */
+
+ /* FIXMEchpe: check event coordinate handling (float vs int!) */
+
+#ifdef FIXMEchpe
+ /* FIXMEchpe: we might be able to use ClutterButtonEvent::click_count for double-click detection! */
+ /* ignore the gdk synthetic double/triple click events */
+ if (event->type != GDK_BUTTON_PRESS)
+ return FALSE;
+#endif
+
+ /* Don't do anything if a modifier is pressed */
+ /* FIXMEchpe: is there anything like gtk_accelerator_get_default_mod_mask() in clutter? */
+ state = event->modifier_state & CLUTTER_DEFAULT_MOD_MASK;
+ if (state != 0)
+ return FALSE;
+
+ button = event->button;
+
+ /* We're only interested in left, middle and right-clicks */
+ if (button < 1 || button > 3)
+ return FALSE;
+
+ /* If we already have a click, ignore this new one */
+ if (priv->click_status != STATUS_NONE) {
+ return FALSE;
+ }
+
+ /* If the game hasn't started yet, start it now */
+ aisleriot_game_start (priv->game);
+
+ get_slot_and_card_from_point (board, event->x, event->y, &hslot, &cardid);
+
+ is_double_click = button == 2 ||
+ (priv->last_click_left_click &&
+ (event->time - priv->last_click_time <= ar_style_get_double_click_time (priv->style)) &&
+ priv->last_clicked_slot == hslot &&
+ priv->last_clicked_card_id == cardid);
+
+ priv->last_click_x = event->x;
+ priv->last_click_y = event->y;
+ priv->last_clicked_slot = hslot;
+ priv->last_clicked_card_id = cardid;
+ priv->last_click_time = event->time;
+ priv->last_click_left_click = button == 1;
+
+ if (!hslot) {
+ set_focus (board, NULL, -1, FALSE);
+ set_selection (board, NULL, -1, FALSE);
+
+ priv->click_status = STATUS_NONE;
+
+ return FALSE;
+ }
+
+ set_cursor (board, AR_CURSOR_CLOSED);
+
+ /* First check if it's a right-click: if so, we reveal the card and do nothing else */
+ if (button == 3) {
+ /* Don't change the selection here! */
+ reveal_card (board, hslot, cardid);
+
+ return TRUE;
+ }
+
+ /* Clear revealed card */
+ reveal_card (board, NULL, -1);
+
+ /* We can't let Gdk do the double-click detection; since the entire playing
+ * area is one big widget it can't distinguish between single-clicks on two
+ * different cards and a double-click on one card.
+ */
+ if (is_double_click) {
+ ArSlot *clicked_slot = hslot;
+
+ priv->click_status = STATUS_NONE;
+
+ /* Reset this since otherwise 3 clicks will be interpreted as 2 double-clicks */
+ priv->last_click_left_click = FALSE;
+
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+ if (aisleriot_game_button_double_clicked_lambda (priv->game, clicked_slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ } else {
+ aisleriot_game_discard_move (priv->game);
+ }
+
+ aisleriot_game_test_end_of_game (priv->game);
+
+ set_cursor (board, AR_CURSOR_OPEN);
+
+ return TRUE;
+ }
+
+ /* button == 1 from now on */
+
+ if (priv->selection_slot == NULL)
+ goto set_selection;
+
+ /* In click-to-move mode, we need to test whether moving the selected cards
+ * to this slot does a move. Note that it is necessary to do this both if
+ * the clicked slot is the selection_slot (and the clicked card the topmost
+ * card below the selection), and if it's not the selection_slot, since some
+ * games depend on this behaviour (e.g. Treize). See bug #565560.
+ *
+ * Note that aisleriot_board_move_selected_cards_to_slot unsets the selection,
+ * so we need to fall through to set_selection if no move was done.
+ */
+ if (priv->click_to_move &&
+ priv->selection_start_card_id >= 0 &&
+ (hslot != priv->selection_slot || cardid + 1 == priv->selection_start_card_id)) {
+
+ /* Try to move the selected cards to the clicked slot */
+ if (aisleriot_board_move_selected_cards_to_slot (board, hslot))
+ return TRUE;
+
+ /* Move failed if this wasn't the selection_slot slot */
+ if (hslot != priv->selection_slot) {
+ aisleriot_board_error_bell (board);
+ }
+ }
+
+ if (hslot != priv->selection_slot ||
+ cardid != priv->selection_start_card_id)
+ goto set_selection;
+
+ /* Single click on the selected slot & card, we take that to mean to deselect,
+ * but only in click-to-move mode.
+ */
+ if (priv->click_to_move) {
+ set_selection (board, NULL, -1, FALSE);
+
+ /* Reveal the card on left click */
+ reveal_card (board, hslot, cardid);
+
+ return TRUE;
+ }
+
+set_selection:
+
+ if (cardid >= 0) {
+ drag_valid = aisleriot_game_drag_valid (priv->game,
+ hslot->id,
+ hslot->cards->data + cardid,
+ hslot->cards->len - cardid);
+ } else {
+ drag_valid = FALSE;
+ }
+
+ if (drag_valid) {
+ set_selection (board, hslot, cardid, priv->click_to_move);
+ priv->click_status = priv->click_to_move ? STATUS_NOT_DRAG : STATUS_MAYBE_DRAG;
+ } else {
+ set_selection (board, NULL, -1, FALSE);
+ priv->click_status = STATUS_NOT_DRAG;
+ }
+
+ /* If we're already showing focus or just clicked on the
+ * card with the (hidden) focus, show the focus on the
+ * clicked card.
+ */
+ show_focus = priv->show_focus ||
+ (hslot == priv->focus_slot &&
+ cardid == priv->focus_card_id);
+ set_focus (board, hslot, cardid, show_focus);
+
+ /* Reveal the card on left click */
+ if (priv->click_to_move) {
+ reveal_card (board, hslot, cardid);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+aisleriot_board_button_release (ClutterActor *actor,
+ ClutterButtonEvent *event)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (actor);
+ AisleriotBoardPrivate *priv = board->priv;
+ /* guint state; */
+
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::button-release @(%f / %f) button %d click-count %d modifiers %x\n",
+ event->x, event->y,
+ event->button, event->click_count,
+ event->modifier_state);
+
+ /* FIXMEchpe: check event coordinate handling (float vs int!) */
+
+ /* NOTE: It's ok to just return instead of chaining up, since the
+ * parent classes have no class closure for this event.
+ */
+
+ /* We just abort any action on button release, even if the button-up
+ * is not the one that started the action. This greatly simplifies the code,
+ * and is also the right thing to do, anyway.
+ */
+
+ /* state = event->state & gtk_accelerator_get_default_mod_mask (); */
+
+ switch (priv->click_status) {
+ case STATUS_SHOW:
+ reveal_card (board, NULL, -1);
+ break;
+
+ case STATUS_IS_DRAG:
+ highlight_drop_target (board, NULL);
+ drop_moving_cards (board, event->x, event->y);
+ break;
+
+ case STATUS_MAYBE_DRAG:
+ case STATUS_NOT_DRAG: {
+ ArSlot *slot;
+ int card_id;
+
+ /* Don't do the action if the mouse moved away from the clicked slot; see bug #329183 */
+ get_slot_and_card_from_point (board, event->x, event->y, &slot, &card_id);
+ if (!slot || slot != priv->last_clicked_slot)
+ break;
+
+ aisleriot_game_record_move (priv->game, -1, NULL, 0);
+ if (aisleriot_game_button_clicked_lambda (priv->game, slot->id)) {
+ aisleriot_game_end_move (priv->game);
+ games_sound_play_for_event ("click", (GdkEvent *) event);
+ } else {
+ aisleriot_game_discard_move (priv->game);
+ }
+
+ aisleriot_game_test_end_of_game (priv->game);
+
+ break;
+ }
+
+ case STATUS_NONE:
+ break;
+ }
+
+ priv->click_status = STATUS_NONE;
+
+ set_cursor_by_location (board, event->x, event->y);
+
+ return TRUE;
+}
+
+static gboolean
+aisleriot_board_motion (ClutterActor *actor,
+ ClutterMotionEvent *event)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (actor);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::motion @(%f / %f) modifiers %x\n",
+ event->x, event->y,
+ event->modifier_state);
+
+ /* FIXMEchpe: check event coordinate handling (float vs int!) */
+
+ /* NOTE: It's ok to just return instead of chaining up, since the
+ * parent classes have no class closure for this event.
+ */
+
+#ifndef HAVE_HILDON
+ if (priv->show_status_messages) {
+ ArSlot *slot = NULL;
+ int cardid = -1;
+
+ get_slot_and_card_from_point (board, event->x, event->y, &slot, &cardid);
+ if (slot != NULL && ar_slot_get_slot_type (slot) != AR_SLOT_UNKNOWN) {
+ char *text;
+
+ text = ar_slot_get_hint_string (slot, cardid);
+ set_status_message (board, text);
+ g_free (text);
+ } else {
+ set_status_message (board, NULL);
+ }
+ }
+#endif /* !HAVE_HILDON */
+
+ if (priv->click_status == STATUS_IS_DRAG) {
+ ArSlot *slot;
+ int x, y;
+
+ x = event->x - priv->last_click_x;
+ y = event->y - priv->last_click_y;
+
+ slot = find_drop_target (board, x, y);
+ highlight_drop_target (board, slot);
+
+ clutter_actor_set_position (priv->moving_cards_group, x, y);
+ clutter_actor_raise_top (priv->moving_cards_group);
+
+ set_cursor (board, AR_CURSOR_CLOSED);
+ } else if (priv->click_status == STATUS_MAYBE_DRAG &&
+ ar_style_check_dnd_drag_threshold (priv->style,
+ priv->last_click_x,
+ priv->last_click_y,
+ event->x,
+ event->y)) {
+ drag_begin (board);
+ } else {
+ set_cursor_by_location (board, event->x, event->y);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+aisleriot_board_enter (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::enter @(%f / %f)\n",
+ event->x, event->y);
+
+ /* NOTE: It's ok to just return instead of chaining up, since the
+ * parent classes have no class closure for this event.
+ */
+
+ return FALSE;
+}
+
+static gboolean
+aisleriot_board_leave (ClutterActor *actor,
+ ClutterCrossingEvent *event)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (actor);
+
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::leave @(%f / %f)\n",
+ event->x, event->y);
+
+ /* NOTE: It's ok to just return instead of chaining up, since the
+ * parent classes have no class closure for this event.
+ */
+
+ set_cursor (board, AR_CURSOR_DEFAULT);
+
+ return FALSE;
+}
+
+static void
+aisleriot_board_key_focus_in (ClutterActor *actor)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::key-focus-in\n");
+}
+
+static void
+aisleriot_board_key_focus_out (ClutterActor *actor)
+{
+ _games_debug_print (GAMES_DEBUG_GAME_EVENTS,
+ "board ::key-focus-out\n");
+}
+
+/* GObjectClass methods */
+
+static void
+aisleriot_board_init (AisleriotBoard *board)
+{
+ ClutterActor *actor = CLUTTER_ACTOR (board);
+ AisleriotBoardPrivate *priv;
+
+ priv = board->priv = AISLERIOT_BOARD_GET_PRIVATE (board);
+
+ priv->textures = ar_card_textures_cache_new ();
+
+ memset (&priv->allocation, 0, sizeof (ClutterActorBox));
+
+ /* We want to receive events! */
+ clutter_actor_set_reactive (actor, TRUE);
+
+ priv->force_geometry_update = FALSE;
+
+ priv->click_to_move = FALSE;
+ priv->show_selection = FALSE;
+ priv->show_status_messages = FALSE;
+
+ priv->show_card_id = -1;
+
+ priv->moving_cards = g_byte_array_sized_new (SLOT_CARDS_N_PREALLOC);
+
+ priv->removed_cards = g_array_new (FALSE, FALSE, sizeof (RemovedCard));
+
+ priv->animation_layer = g_object_ref_sink (clutter_group_new ());
+ clutter_container_add (CLUTTER_CONTAINER (board),
+ priv->animation_layer, NULL);
+}
+
+static void
+aisleriot_board_finalize (GObject *object)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (object);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ g_signal_handlers_disconnect_matched (priv->game,
+ G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL, board);
+ g_object_unref (priv->game);
+
+ g_array_free (priv->removed_cards, TRUE);
+
+ g_byte_array_free (priv->moving_cards, TRUE);
+
+ if (priv->style != NULL) {
+ g_signal_handlers_disconnect_by_func (priv->style,
+ G_CALLBACK (aisleriot_board_sync_style),
+ board);
+
+ g_object_unref (priv->style);
+ }
+
+#if 0
+ screen = gtk_widget_get_settings (widget);
+ g_signal_handlers_disconnect_matched (settings, G_SIGNAL_MATCH_DATA,
+ 0, 0, NULL, NULL,
+ widget);
+#endif
+
+ G_OBJECT_CLASS (aisleriot_board_parent_class)->finalize (object);
+}
+
+static void
+aisleriot_board_dispose (GObject *object)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (object);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ if (priv->textures) {
+ g_object_unref (priv->textures);
+ priv->textures = NULL;
+ }
+
+ if (priv->animation_layer) {
+ g_object_unref (priv->animation_layer);
+ priv->animation_layer = NULL;
+ }
+
+ if (priv->check_animations_handler) {
+ g_source_remove (priv->check_animations_handler);
+ priv->check_animations_handler = 0;
+ }
+
+ G_OBJECT_CLASS (aisleriot_board_parent_class)->dispose (object);
+}
+
+static void
+aisleriot_board_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AisleriotBoard *board = AISLERIOT_BOARD (object);
+ AisleriotBoardPrivate *priv = board->priv;
+
+ switch (prop_id) {
+ case PROP_GAME:
+ priv->game = AISLERIOT_GAME (g_value_dup_object (value));
+
+ g_signal_connect (priv->game, "game-type",
+ G_CALLBACK (game_type_changed_cb), board);
+ g_signal_connect (priv->game, "game-cleared",
+ G_CALLBACK (game_cleared_cb), board);
+ g_signal_connect (priv->game, "game-new",
+ G_CALLBACK (game_new_cb), board);
+ g_signal_connect (priv->game, "slot-changed",
+ G_CALLBACK (slot_changed_cb), board);
+
+ break;
+
+ case PROP_STYLE:
+ priv->style = g_value_dup_object (value);
+
+ aisleriot_board_sync_style (priv->style, NULL, board);
+ g_signal_connect (priv->style, "notify",
+ G_CALLBACK (aisleriot_board_sync_style), board);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+aisleriot_board_class_init (AisleriotBoardClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
+#ifdef ENABLE_KEYNAV
+ ClutterBindingPool *binding_pool;
+ GClosure *closure;
+#endif
+
+ g_type_class_add_private (gobject_class, sizeof (AisleriotBoardPrivate));
+
+ gobject_class->dispose = aisleriot_board_dispose;
+ gobject_class->finalize = aisleriot_board_finalize;
+ gobject_class->set_property = aisleriot_board_set_property;
+
+#ifdef FIXMEchpe
+ widget_class->realize = aisleriot_board_realize;
+ widget_class->unrealize = aisleriot_board_unrealize;
+ widget_class->focus_in_event = aisleriot_board_focus_in;
+ widget_class->focus_out_event = aisleriot_board_focus_out;
+#endif // FIXMEchpe
+
+ actor_class->allocate = aisleriot_board_allocate;
+ actor_class->get_preferred_width = aisleriot_board_get_preferred_width;
+ actor_class->get_preferred_height = aisleriot_board_get_preferred_height;
+#ifdef ENABLE_KEYNAV
+ actor_class->key_press_event = aisleriot_board_key_press;
+#endif /* ENABLE_KEYNAV */
+ actor_class->button_press_event = aisleriot_board_button_press;
+ actor_class->button_release_event = aisleriot_board_button_release;
+ actor_class->motion_event = aisleriot_board_motion;
+ actor_class->enter_event = aisleriot_board_enter;
+ actor_class->leave_event = aisleriot_board_leave;
+ actor_class->key_focus_in = aisleriot_board_key_focus_in;
+ actor_class->key_focus_out = aisleriot_board_key_focus_out;
+
+
+ signals[REQUEST_CURSOR] =
+ g_signal_new (I_("request-cursor"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, request_cursor),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_INT);
+
+ signals[ERROR_BELL] =
+ g_signal_new (I_("error-bell"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, error_bell),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[STATUS_MESSAGE] =
+ g_signal_new (I_("status-message"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, status_message),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ signals[FOCUS] =
+ g_signal_new (I_("focus"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, focus),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_BOOLEAN,
+ 1,
+ G_TYPE_INT);
+
+#ifdef ENABLE_KEYNAV
+ klass->focus = aisleriot_board_focus;
+ klass->activate = aisleriot_board_activate;
+ klass->move_cursor = aisleriot_board_move_cursor;
+ klass->select_all = aisleriot_board_select_all;
+ klass->deselect_all = aisleriot_board_deselect_all;
+ klass->toggle_selection = aisleriot_board_toggle_selection;
+
+ /* Keybinding signals */
+#ifdef FIXMEchpe
+ widget_class->activate_signal =
+#endif
+ signals[ACTIVATE] =
+ g_signal_new (I_("activate"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, activate),
+ NULL, NULL,
+ games_marshal_BOOLEAN__STRING_UINT_ENUM,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_UINT,
+ CLUTTER_TYPE_MODIFIER_TYPE);
+
+ signals[MOVE_CURSOR] =
+ g_signal_new (I_("move-cursor"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor),
+ NULL, NULL,
+ games_marshal_BOOLEAN__STRING_UINT_ENUM,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_UINT,
+ CLUTTER_TYPE_MODIFIER_TYPE);
+
+ signals[TOGGLE_SELECTION] =
+ g_signal_new (I_("toggle-selection"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection),
+ NULL, NULL,
+ games_marshal_BOOLEAN__STRING_UINT_ENUM,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_UINT,
+ CLUTTER_TYPE_MODIFIER_TYPE);
+
+ signals[SELECT_ALL] =
+ g_signal_new (I_("select-all"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, select_all),
+ NULL, NULL,
+ games_marshal_BOOLEAN__STRING_UINT_ENUM,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_UINT,
+ CLUTTER_TYPE_MODIFIER_TYPE);
+
+ signals[DESELECT_ALL] =
+ g_signal_new (I_("deselect-all"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (AisleriotBoardClass, deselect_all),
+ NULL, NULL,
+ games_marshal_BOOLEAN__STRING_UINT_ENUM,
+ G_TYPE_BOOLEAN,
+ 3,
+ G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+ G_TYPE_UINT,
+ CLUTTER_TYPE_MODIFIER_TYPE);
+#endif /* ENABLE_KEYNAV */
+
+ /* Properties */
+ g_object_class_install_property
+ (gobject_class,
+ PROP_GAME,
+ g_param_spec_object ("game", NULL, NULL,
+ AISLERIOT_TYPE_GAME,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_STYLE,
+ g_param_spec_object ("style", NULL, NULL,
+ AR_TYPE_STYLE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+#ifdef ENABLE_KEYNAV
+ /* Keybindings */
+ binding_pool = clutter_binding_pool_get_for_class (klass);
+
+ /* Cursor movement */
+ closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
+ G_STRUCT_OFFSET (AisleriotBoardClass, move_cursor));
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_LEFT_S),
+ CLUTTER_Left, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_LEFT_S),
+ CLUTTER_KP_Left, 0);
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_RIGHT_S),
+ CLUTTER_Right, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_LEFT_RIGHT_S MOVE_RIGHT_S),
+ CLUTTER_KP_Right, 0);
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
+ CLUTTER_Up, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_UP_DOWN_S MOVE_LEFT_S),
+ CLUTTER_KP_Up, 0);
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_UP_DOWN_S MOVE_RIGHT_S),
+ CLUTTER_Down, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_UP_DOWN_S MOVE_RIGHT_S),
+ CLUTTER_KP_Down, 0);
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
+ CLUTTER_Home, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
+ CLUTTER_KP_Home, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_START_END_S MOVE_LEFT_S),
+ CLUTTER_Begin, 0);
+
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
+ CLUTTER_End, 0);
+ aisleriot_board_add_move_and_select_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_START_END_S MOVE_RIGHT_S),
+ CLUTTER_KP_End, 0);
+
+ aisleriot_board_add_move_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_PAGES_S MOVE_LEFT_S),
+ CLUTTER_Page_Up, 0);
+ aisleriot_board_add_move_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_PAGES_S MOVE_LEFT_S),
+ CLUTTER_KP_Page_Up, 0);
+
+ aisleriot_board_add_move_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_PAGES_S MOVE_RIGHT_S),
+ CLUTTER_Page_Down, 0);
+ aisleriot_board_add_move_binding (binding_pool, closure,
+ I_(MOVE_CURSOR_PAGES_S MOVE_RIGHT_S),
+ CLUTTER_KP_Page_Down, 0);
+
+ g_closure_unref (closure);
+
+ /* Selection */
+ closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
+ G_STRUCT_OFFSET (AisleriotBoardClass, toggle_selection));
+
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("toggle-selection"),
+ CLUTTER_space,
+ 0,
+ closure);
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("toggle-selection"),
+ CLUTTER_KP_Space,
+ 0,
+ closure);
+ g_closure_unref (closure);
+
+ closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
+ G_STRUCT_OFFSET (AisleriotBoardClass, select_all));
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("select-all"),
+ CLUTTER_a,
+ CLUTTER_CONTROL_MASK,
+ closure);
+ g_closure_unref (closure);
+
+ closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
+ G_STRUCT_OFFSET (AisleriotBoardClass, deselect_all));
+ clutter_binding_pool_install_closure (binding_pool,
+ I_("deselect-all"),
+ CLUTTER_a,
+ CLUTTER_CONTROL_MASK | CLUTTER_SHIFT_MASK,
+ closure);
+ g_closure_unref (closure);
+
+ /* Activate */
+ closure = g_signal_type_cclosure_new (G_TYPE_FROM_CLASS (klass),
+ G_STRUCT_OFFSET (AisleriotBoardClass, activate));
+
+ aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_Return, 0);
+ aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_ISO_Enter, 0);
+ aisleriot_board_add_activate_binding (binding_pool, closure, CLUTTER_KP_Enter, 0);
+
+ g_closure_unref (closure);
+#endif /* ENABLE_KEYNAV */
+}
+
+/* public API */
+
+ClutterActor *
+aisleriot_board_new (ArStyle *style,
+ AisleriotGame *game)
+{
+ return g_object_new (AISLERIOT_TYPE_BOARD,
+ "style", style,
+ "game", game,
+ NULL);
+}
+
+void
+aisleriot_board_abort_move (AisleriotBoard *board)
+{
+ clear_state (board);
+}
diff --git a/aisleriot/board.h b/aisleriot/board.h
new file mode 100644
index 0000000..9af01df
--- /dev/null
+++ b/aisleriot/board.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 1998, 2003 Jonathan Blandford <jrb mit edu>
+ * Copyright © 2007 Christian Persch
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AISLERIOT_BOARD_H
+#define AISLERIOT_BOARD_H
+
+#ifndef HAVE_CLUTTER
+#error board.h requires clutter
+#endif
+
+#include <clutter/clutter.h>
+
+#include "ar-style.h"
+#include "ar-cursor.h"
+#include "game.h"
+
+G_BEGIN_DECLS
+
+#define AISLERIOT_TYPE_BOARD (aisleriot_board_get_type ())
+#define AISLERIOT_BOARD(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), AISLERIOT_TYPE_BOARD, AisleriotBoard))
+#define AISLERIOT_BOARD_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), AISLERIOT_TYPE_BOARD, AisleriotBoardClass))
+#define AISLERIOT_IS_BOARD(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), AISLERIOT_TYPE_BOARD))
+#define AISLERIOT_IS_BOARD_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), AISLERIOT_TYPE_BOARD))
+#define AISLERIOT_BOARD_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), AISLERIOT_TYPE_BOARD, AisleriotBoardClass))
+
+typedef struct _AisleriotBoard AisleriotBoard;
+typedef struct _AisleriotBoardPrivate AisleriotBoardPrivate;
+typedef struct _AisleriotBoardClass AisleriotBoardClass;
+
+struct _AisleriotBoard {
+ ClutterGroup parent_instance;
+
+ /*< private >*/
+ AisleriotBoardPrivate *priv;
+};
+
+struct _AisleriotBoardClass {
+ ClutterGroupClass parent_class;
+
+ void (* request_cursor) (AisleriotBoard *board,
+ ArCursorType cursor_type);
+
+ void (* error_bell) (AisleriotBoard *board);
+
+ void (* status_message) (AisleriotBoard *board,
+ const char *message);
+
+ /* Focus */
+ gboolean (* focus) (AisleriotBoard *,
+ int direction);
+
+ /* keybinding signals */
+ gboolean (* move_cursor) (AisleriotBoard *,
+ const char *,
+ guint,
+ ClutterModifierType);
+ void (* activate) (AisleriotBoard *,
+ const char *,
+ guint,
+ ClutterModifierType);
+ void (* toggle_selection) (AisleriotBoard *,
+ const char *,
+ guint,
+ ClutterModifierType);
+ void (* select_all) (AisleriotBoard *,
+ const char *,
+ guint,
+ ClutterModifierType);
+ void (* deselect_all) (AisleriotBoard *,
+ const char *,
+ guint,
+ ClutterModifierType);
+};
+
+GType aisleriot_board_get_type (void);
+
+ClutterActor *aisleriot_board_new (ArStyle *style,
+ AisleriotGame *game);
+
+void aisleriot_board_abort_move (AisleriotBoard *board);
+
+G_END_DECLS
+
+#endif /* !AISLERIOT_BOARD_H */
diff --git a/aisleriot/card.c b/aisleriot/card.c
new file mode 100644
index 0000000..4409b45
--- /dev/null
+++ b/aisleriot/card.c
@@ -0,0 +1,364 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <clutter/clutter.h>
+#include <gtk/gtk.h>
+#include <cogl/cogl.h>
+
+#include "card.h"
+
+#include <libgames-support/games-glib-compat.h>
+
+static void aisleriot_card_paint (ClutterActor *actor);
+
+static void aisleriot_card_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p);
+static void aisleriot_card_get_preferred_height
+ (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p);
+
+static void aisleriot_card_dispose (GObject *self);
+
+static void aisleriot_card_set_property (GObject *self,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void aisleriot_card_get_property (GObject *self,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void aisleriot_card_unref_cache (AisleriotCard *card);
+
+static void aisleriot_card_set_cache (AisleriotCard *card,
+ ArCardTexturesCache *cache);
+
+#define ANGLE_IS_UPSIDE_DOWN(angle) \
+ ((ABS (angle) + CLUTTER_INT_TO_FIXED (90)) \
+ % CLUTTER_INT_TO_FIXED (360) \
+ >= CLUTTER_INT_TO_FIXED (180))
+
+G_DEFINE_TYPE (AisleriotCard, aisleriot_card, CLUTTER_TYPE_ACTOR);
+
+#define AISLERIOT_CARD_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AISLERIOT_TYPE_CARD, \
+ AisleriotCardPrivate))
+
+struct _AisleriotCardPrivate
+{
+ Card bottom_card, top_card;
+
+ ArCardTexturesCache *cache;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CACHE,
+ PROP_BOTTOM_CARD,
+ PROP_TOP_CARD
+};
+
+static void
+aisleriot_card_class_init (AisleriotCardClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ ClutterActorClass *actor_class = (ClutterActorClass *) klass;
+ GParamSpec *pspec;
+
+ gobject_class->dispose = aisleriot_card_dispose;
+ gobject_class->set_property = aisleriot_card_set_property;
+ gobject_class->get_property = aisleriot_card_get_property;
+
+ actor_class->paint = aisleriot_card_paint;
+ actor_class->get_preferred_width = aisleriot_card_get_preferred_width;
+ actor_class->get_preferred_height = aisleriot_card_get_preferred_height;
+
+ pspec = g_param_spec_uchar ("bottom-card", NULL, NULL,
+ 0, 255, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_BOTTOM_CARD, pspec);
+
+ pspec = g_param_spec_uchar ("top-card", NULL, NULL,
+ 0, 255, 0,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_TOP_CARD, pspec);
+
+ pspec = g_param_spec_object ("cache", NULL, NULL,
+ AR_TYPE_CARD_TEXTURES_CACHE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_CACHE, pspec);
+
+ g_type_class_add_private (klass, sizeof (AisleriotCardPrivate));
+}
+
+static void
+aisleriot_card_init (AisleriotCard *self)
+{
+ AisleriotCardPrivate *priv;
+
+ priv = self->priv = AISLERIOT_CARD_GET_PRIVATE (self);
+}
+
+static void
+aisleriot_card_dispose (GObject *self)
+{
+ AisleriotCard *card = AISLERIOT_CARD (self);
+
+ aisleriot_card_unref_cache (card);
+
+ G_OBJECT_CLASS (aisleriot_card_parent_class)->dispose (self);
+}
+
+ClutterActor *
+aisleriot_card_new (ArCardTexturesCache *cache,
+ Card bottom_card,
+ Card top_card)
+{
+ return g_object_new (AISLERIOT_TYPE_CARD,
+ "cache", cache,
+ "bottom-card", bottom_card.value,
+ "top-card", top_card.value,
+ NULL);
+}
+
+static void
+aisleriot_card_get_preferred_width (ClutterActor *self,
+ gfloat for_height,
+ gfloat *min_width_p,
+ gfloat *natural_width_p)
+{
+ AisleriotCard *card = AISLERIOT_CARD (self);
+ AisleriotCardPrivate *priv = card->priv;
+ CoglHandle tex;
+ guint width;
+
+ tex = ar_card_textures_cache_get_card_texture (priv->cache,
+ priv->top_card);
+
+ if (G_UNLIKELY (tex == COGL_INVALID_HANDLE))
+ width = 0;
+ else
+ width = cogl_texture_get_width (tex);
+
+ if (min_width_p)
+ *min_width_p = 0;
+
+ if (natural_width_p)
+ *natural_width_p = width;
+}
+
+static void
+aisleriot_card_get_preferred_height (ClutterActor *self,
+ gfloat for_width,
+ gfloat *min_height_p,
+ gfloat *natural_height_p)
+{
+ AisleriotCard *card = AISLERIOT_CARD (self);
+ AisleriotCardPrivate *priv = card->priv;
+ CoglHandle tex;
+ guint height;
+
+ tex = ar_card_textures_cache_get_card_texture (priv->cache,
+ priv->top_card);
+
+ if (G_UNLIKELY (tex == COGL_INVALID_HANDLE))
+ height = 0;
+ else
+ height = cogl_texture_get_height (tex);
+
+ if (min_height_p)
+ *min_height_p = 0;
+
+ if (natural_height_p)
+ *natural_height_p = height;
+}
+
+static void
+aisleriot_card_paint (ClutterActor *actor)
+{
+ AisleriotCard *card = (AisleriotCard *) actor;
+ AisleriotCardPrivate *priv = card->priv;
+ CoglHandle top_tex, bottom_tex;
+ ClutterActorBox alloc_box;
+ gboolean backface_culling_was_enabled = cogl_get_backface_culling_enabled ();
+
+ cogl_set_backface_culling_enabled (TRUE);
+
+ top_tex = ar_card_textures_cache_get_card_texture (priv->cache,
+ priv->top_card);
+ bottom_tex = ar_card_textures_cache_get_card_texture (priv->cache,
+ priv->bottom_card);
+ if (G_UNLIKELY (top_tex == COGL_INVALID_HANDLE
+ || bottom_tex == COGL_INVALID_HANDLE))
+ return;
+
+ clutter_actor_get_allocation_box (actor, &alloc_box);
+
+ /* Draw both sides of the card. Backface culling is enabled so only
+ one side will actually be rendered */
+
+ cogl_set_source_texture (top_tex);
+
+ cogl_rectangle (0.0f, 0.0f,
+ alloc_box.x2 - alloc_box.x1,
+ alloc_box.y2 - alloc_box.y1);
+
+ cogl_set_source_texture (bottom_tex);
+
+ /* Rotate along the y-axis about the center of the card to make the
+ bottom of the card face the other way */
+ cogl_push_matrix ();
+ cogl_translate ((alloc_box.x2 - alloc_box.x1) / 2,
+ 0, 0);
+ cogl_rotate (180, 0, 1, 0);
+ cogl_translate (-(alloc_box.x2 - alloc_box.x1) / 2,
+ 0, 0);
+ cogl_rectangle (0.0f, 0.0f,
+ alloc_box.x2 - alloc_box.x1,
+ alloc_box.y2 - alloc_box.y1);
+ cogl_pop_matrix ();
+
+ if (!backface_culling_was_enabled)
+ cogl_set_backface_culling_enabled (FALSE);
+}
+
+static void
+aisleriot_card_set_property (GObject *self,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AisleriotCard *card = AISLERIOT_CARD (self);
+
+ switch (property_id) {
+ case PROP_BOTTOM_CARD:
+ {
+ Card card_num;
+
+ card_num.value = g_value_get_uchar (value);
+
+ aisleriot_card_set_card (card, card_num, card->priv->top_card);
+ }
+ break;
+
+ case PROP_TOP_CARD:
+ {
+ Card card_num;
+
+ card_num.value = g_value_get_uchar (value);
+
+ aisleriot_card_set_card (card, card->priv->bottom_card, card_num);
+ }
+ break;
+
+ case PROP_CACHE:
+ aisleriot_card_set_cache (card, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+aisleriot_card_get_property (GObject *self,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AisleriotCard *card = AISLERIOT_CARD (self);
+ AisleriotCardPrivate *priv = card->priv;
+
+ switch (property_id) {
+ case PROP_BOTTOM_CARD:
+ g_value_set_uchar (value, priv->bottom_card.value);
+ break;
+
+ case PROP_TOP_CARD:
+ g_value_set_uchar (value, priv->top_card.value);
+ break;
+
+ case PROP_CACHE:
+ g_value_set_object (value, priv->cache);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+void
+aisleriot_card_set_card (AisleriotCard *card,
+ Card bottom_card,
+ Card top_card)
+{
+ AisleriotCardPrivate *priv;
+
+ g_return_if_fail (AISLERIOT_IS_CARD (card));
+
+ priv = card->priv;
+
+ priv->bottom_card = bottom_card;
+ priv->top_card = top_card;
+
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (card));
+
+ g_object_notify (G_OBJECT (card), "bottom-card");
+ g_object_notify (G_OBJECT (card), "top-card");
+}
+
+static void
+aisleriot_card_unref_cache (AisleriotCard *card)
+{
+ AisleriotCardPrivate *priv = card->priv;
+
+ if (priv->cache) {
+ g_object_unref (priv->cache);
+ priv->cache = NULL;
+ }
+}
+
+static void
+aisleriot_card_set_cache (AisleriotCard *card, ArCardTexturesCache *cache)
+{
+ AisleriotCardPrivate *priv = card->priv;
+
+ if (cache)
+ g_object_ref (cache);
+
+ aisleriot_card_unref_cache (card);
+
+ priv->cache = cache;
+
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (card));
+
+ g_object_notify (G_OBJECT (card), "cache");
+}
diff --git a/aisleriot/card.h b/aisleriot/card.h
new file mode 100644
index 0000000..b66775a
--- /dev/null
+++ b/aisleriot/card.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AISLERIOT_CARD_H
+#define AISLERIOT_CARD_H
+
+#include <clutter/clutter.h>
+#include "ar-card-textures-cache.h"
+
+G_BEGIN_DECLS
+
+#define AISLERIOT_TYPE_CARD \
+ (aisleriot_card_get_type())
+#define AISLERIOT_CARD(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ AISLERIOT_TYPE_CARD, \
+ AisleriotCard))
+#define AISLERIOT_CARD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ AISLERIOT_TYPE_CARD, \
+ AisleriotCardClass))
+#define AISLERIOT_IS_CARD(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ AISLERIOT_TYPE_CARD))
+#define AISLERIOT_IS_CARD_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ AISLERIOT_TYPE_CARD))
+#define AISLERIOT_CARD_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ AISLERIOT_TYPE_CARD, \
+ AisleriotCardClass))
+
+typedef struct _AisleriotCard AisleriotCard;
+typedef struct _AisleriotCardClass AisleriotCardClass;
+typedef struct _AisleriotCardPrivate AisleriotCardPrivate;
+
+struct _AisleriotCardClass
+{
+ ClutterActorClass parent_class;
+};
+
+struct _AisleriotCard
+{
+ ClutterActor parent;
+
+ AisleriotCardPrivate *priv;
+};
+
+GType aisleriot_card_get_type (void) G_GNUC_CONST;
+
+ClutterActor *aisleriot_card_new (ArCardTexturesCache *cache,
+ Card bottom_card,
+ Card top_card);
+
+void aisleriot_card_set_card (AisleriotCard *card,
+ Card bottom_card,
+ Card top_card);
+
+void aisleriot_card_set_highlighted (AisleriotCard *card,
+ gboolean highlighted);
+gboolean aisleriot_card_get_highlighted (AisleriotCard *card);
+
+G_END_DECLS
+
+#endif /* AISLERIOT_CARD_H */
diff --git a/aisleriot/game.c b/aisleriot/game.c
index 185d263..e77e316 100644
--- a/aisleriot/game.c
+++ b/aisleriot/game.c
@@ -31,6 +31,10 @@
#include <glib.h>
#include <glib/gi18n.h>
+#ifdef HAVE_CLUTTER
+#include <clutter/clutter.h>
+#endif
+
#include <libgames-support/games-debug.h>
#include <libgames-support/games-glib-compat.h>
#include <libgames-support/games-runtime.h>
@@ -233,7 +237,16 @@ clear_slots (AisleriotGame *game,
for (i = 0; i < n_slots; ++i) {
ArSlot *slot = game->slots->pdata[i];
+#ifdef HAVE_CLUTTER
+ if (slot->slot_renderer) {
+ clutter_actor_destroy (slot->slot_renderer);
+ g_object_unref (slot->slot_renderer);
+ }
+
+ g_byte_array_free (slot->old_cards, TRUE);
+#else
g_ptr_array_free (slot->card_images, TRUE);
+#endif /* HAVE_CLUTTER */
g_byte_array_free (slot->cards, TRUE);
g_slice_free (ArSlot, slot);
@@ -629,7 +642,11 @@ cscmi_add_slot (SCM slot_data)
slot->expanded_down = expanded_down != FALSE;
slot->expanded_right = expanded_right != FALSE;
+#ifdef HAVE_CLUTTER
+ slot->old_cards = g_byte_array_sized_new (SLOT_CARDS_N_PREALLOC);
+#else
slot->card_images = g_ptr_array_sized_new (SLOT_CARDS_N_PREALLOC);
+#endif
slot->needs_update = TRUE;
@@ -2466,3 +2483,41 @@ aisleriot_game_deal_cards (AisleriotGame *game)
aisleriot_game_end_move (game);
aisleriot_game_test_end_of_game (game);
}
+
+#ifdef HAVE_CLUTTER
+
+void
+aisleriot_game_get_card_offset (ArSlot *slot,
+ guint card_num,
+ gboolean old_cards,
+ gint *xoff, gint *yoff)
+{
+ gint n_cards, exposed;
+
+ if (old_cards) {
+ n_cards = (gint) slot->old_cards->len;
+ exposed = (gint) slot->old_exposed;
+ } else {
+ n_cards = (gint) slot->cards->len;
+ exposed = (gint) slot->exposed;
+ }
+
+ if (card_num >= n_cards - exposed) {
+ gint idx = card_num + exposed - n_cards;
+ *xoff = slot->pixeldx * idx;
+ *yoff = slot->pixeldy * idx;
+ } else {
+ *xoff = 0;
+ *yoff = 0;
+ }
+}
+
+void
+aisleriot_game_reset_old_cards (ArSlot *slot)
+{
+ g_byte_array_set_size (slot->old_cards, 0);
+ g_byte_array_append (slot->old_cards, slot->cards->data, slot->cards->len);
+ slot->old_exposed = slot->exposed;
+}
+
+#endif /* HAVE_CLUTTER */
diff --git a/aisleriot/game.h b/aisleriot/game.h
index 448886b..6d4a0a1 100644
--- a/aisleriot/game.h
+++ b/aisleriot/game.h
@@ -22,6 +22,10 @@
#include "ar-card.h"
+#ifdef HAVE_CLUTTER
+#include <clutter/clutter.h>
+#endif
+
G_BEGIN_DECLS
/* A slot */
@@ -40,6 +44,11 @@ typedef struct {
ArSlotType type;
GByteArray *cards;
+#ifdef HAVE_CLUTTER
+ /* The old state of the cards so we can check for differences */
+ guint old_exposed;
+ GByteArray *old_cards;
+#endif /* HAVE_CLUTTER */
/* the topmost |exposed| cards are shown on the pile */
guint exposed;
@@ -61,8 +70,13 @@ typedef struct {
/* The location in pixel units. Filled in by the scaling code. */
GdkRectangle rect;
+#ifdef HAVE_CLUTTER
+ /* Actor for the slot */
+ ClutterActor *slot_renderer;
+#else
/* GdkPixbuf* or GdkPixmap*, no reference owned */
GPtrArray *card_images;
+#endif /* HAVE_CLUTTER */
guint expanded_right : 1;
guint expanded_down : 1;
diff --git a/aisleriot/lib/Makefile.am b/aisleriot/lib/Makefile.am
index a1aa528..833daba 100644
--- a/aisleriot/lib/Makefile.am
+++ b/aisleriot/lib/Makefile.am
@@ -28,6 +28,13 @@ libaisleriot_la_SOURCES += \
ar-card-surface-cache.h \
$(NULL)
+if HAVE_CLUTTER
+libaisleriot_la_SOURCES += \
+ ar-card-textures-cache.c \
+ ar-card-textures-cache.h \
+ $(NULL)
+endif # HAVE_CLUTTER
+
if HAVE_RSVG
libaisleriot_la_SOURCES += ar-card-theme-preimage.c
@@ -79,6 +86,11 @@ libaisleriot_la_CFLAGS += $(RSVG_CFLAGS)
libaisleriot_la_LIBADD += $(RSVG_LIBS)
endif # HAVE_RSVG
+if HAVE_CLUTTER
+libaisleriot_la_CFLAGS += $(CLUTTER_CFLAGS)
+libaisleriot_la_LIBADD += $(CLUTTER_LIBS)
+endif # HAVE_CLUTTER
+
if HAVE_HILDON
libaisleriot_la_CFLAGS += $(HILDON_CFLAGS)
libaisleriot_la_LIBADD += $(HILDON_LIBS)
diff --git a/aisleriot/lib/ar-card-textures-cache.c b/aisleriot/lib/ar-card-textures-cache.c
new file mode 100644
index 0000000..b4bea44
--- /dev/null
+++ b/aisleriot/lib/ar-card-textures-cache.c
@@ -0,0 +1,374 @@
+/*
+ Copyright © 2008 Neil Roberts
+ Copyright © 2008 Christian Persch
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include <cogl/cogl.h>
+
+#include <libgames-support/games-debug.h>
+
+#include "ar-card-textures-cache.h"
+#include "ar-card-private.h"
+
+struct _ArCardTexturesCachePrivate
+{
+ ArCardTheme *theme;
+ guint theme_changed_handler;
+
+ CoglHandle *cards;
+
+#ifdef GNOME_ENABLE_DEBUG
+ guint n_calls;
+ guint cache_hits;
+#endif
+};
+
+enum
+{
+ PROP_0,
+ PROP_THEME
+};
+
+/* This is an invalid value for a CoglHandle, and distinct from COGL_INVALID_HANDLE */
+#define FAILED_HANDLE ((gpointer) 0x1)
+#define IS_FAILED_HANDLE(ptr) (G_UNLIKELY ((ptr) == FAILED_HANDLE))
+
+/* Logging */
+#ifdef GNOME_ENABLE_DEBUG
+#define LOG_CALL(obj) obj->priv->n_calls++
+#define LOG_CACHE_HIT(obj) obj->priv->cache_hits++
+#define LOG_CACHE_MISS(obj)
+#else
+#define LOG_CALL(obj)
+#define LOG_CACHE_HIT(obj)
+#define LOG_CACHE_MISS(obj)
+#endif /* GNOME_ENABLE_DEBUG */
+
+static void ar_card_textures_cache_dispose (GObject *object);
+static void ar_card_textures_cache_finalize (GObject *object);
+
+G_DEFINE_TYPE (ArCardTexturesCache, ar_card_textures_cache, G_TYPE_OBJECT);
+
+#define AR_CARD_TEXTURES_CACHE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AR_TYPE_CARD_TEXTURES_CACHE, ArCardTexturesCachePrivate))
+
+/* Helper functions */
+
+static void
+ar_card_textures_cache_clear (ArCardTexturesCache *cache)
+{
+ ArCardTexturesCachePrivate *priv = cache->priv;
+ int i;
+
+ _games_debug_print (GAMES_DEBUG_CARD_CACHE,
+ "ar_card_textures_cache_clear\n");
+
+ for (i = 0; i < AR_CARDS_TOTAL; i++) {
+ CoglHandle handle = priv->cards[i];
+
+ if (handle != COGL_INVALID_HANDLE &&
+ !IS_FAILED_HANDLE (handle)) {
+ cogl_handle_unref (handle);
+ }
+
+ priv->cards[i] = COGL_INVALID_HANDLE;
+ }
+}
+
+static void
+ar_card_textures_cache_unset_theme (ArCardTexturesCache *cache)
+{
+ ArCardTexturesCachePrivate *priv = cache->priv;
+
+ if (priv->theme) {
+ g_signal_handler_disconnect (priv->theme, priv->theme_changed_handler);
+ g_object_unref (priv->theme);
+ priv->theme = NULL;
+ priv->theme_changed_handler = 0;
+ }
+}
+
+/* Class implementation */
+
+static void
+ar_card_textures_cache_init (ArCardTexturesCache *self)
+{
+ ArCardTexturesCachePrivate *priv;
+
+ priv = self->priv = AR_CARD_TEXTURES_CACHE_GET_PRIVATE (self);
+
+ priv->cards = g_malloc0 (sizeof (CoglHandle) * AR_CARDS_TOTAL);
+}
+
+static void
+ar_card_textures_cache_dispose (GObject *object)
+{
+ ArCardTexturesCache *cache = AR_CARD_TEXTURES_CACHE (object);
+
+ ar_card_textures_cache_clear (cache);
+ ar_card_textures_cache_unset_theme (cache);
+
+ G_OBJECT_CLASS (ar_card_textures_cache_parent_class)->dispose (object);
+}
+
+static void
+ar_card_textures_cache_finalize (GObject *object)
+{
+ ArCardTexturesCache *cache = AR_CARD_TEXTURES_CACHE (object);
+ ArCardTexturesCachePrivate *priv = cache->priv;
+
+ g_free (priv->cards);
+
+#ifdef GNOME_ENABLE_DEBUG
+ _GAMES_DEBUG_IF (GAMES_DEBUG_CARD_CACHE) {
+ _games_debug_print (GAMES_DEBUG_CARD_CACHE,
+ "ArCardTexturesCache %p statistics: %u calls with %u hits and %u misses for a hit/total of %.3f\n",
+ cache, priv->n_calls, priv->cache_hits, priv->n_calls - priv->cache_hits,
+ priv->n_calls > 0 ? (double) priv->cache_hits / (double) priv->n_calls : 0.0);
+ }
+#endif
+
+ G_OBJECT_CLASS (ar_card_textures_cache_parent_class)->finalize (object);
+}
+
+static void
+ar_card_textures_cache_set_property (GObject *self,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ ArCardTexturesCache *cache = AR_CARD_TEXTURES_CACHE (self);
+
+ switch (property_id) {
+ case PROP_THEME:
+ ar_card_textures_cache_set_theme (cache, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+ar_card_textures_cache_get_property (GObject *self,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ ArCardTexturesCache *cache = AR_CARD_TEXTURES_CACHE (self);
+
+ switch (property_id) {
+ case PROP_THEME:
+ g_value_set_object (value, ar_card_textures_cache_get_theme (cache));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+ break;
+ }
+}
+
+static void
+ar_card_textures_cache_class_init (ArCardTexturesCacheClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GParamSpec *pspec;
+
+ gobject_class->dispose = ar_card_textures_cache_dispose;
+ gobject_class->finalize = ar_card_textures_cache_finalize;
+ gobject_class->set_property = ar_card_textures_cache_set_property;
+ gobject_class->get_property = ar_card_textures_cache_get_property;
+
+ g_type_class_add_private (klass, sizeof (ArCardTexturesCachePrivate));
+
+ pspec = g_param_spec_object ("theme", NULL, NULL,
+ AR_TYPE_CARD_THEME,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB);
+ g_object_class_install_property (gobject_class, PROP_THEME, pspec);
+}
+
+/* Public API */
+
+/**
+ * ar_card_textures_cache_new:
+ *
+ * Returns: a new #ArCardTexturesCache object
+ */
+ArCardTexturesCache *
+ar_card_textures_cache_new (void)
+{
+ return g_object_new (AR_TYPE_CARD_TEXTURES_CACHE, NULL);
+}
+
+/**
+ * ar_card_textures_cache_drop:
+ * @images: a #ArCardImages
+ *
+ * Clears the image cache.
+ */
+void
+ar_card_textures_cache_drop (ArCardTexturesCache *cache)
+{
+ g_return_if_fail (AR_IS_CARD_TEXTURES_CACHE (cache));
+
+ ar_card_textures_cache_clear (cache);
+}
+
+/**
+ * ar_card_textures_cache_set_theme:
+ * @cache:
+ * @theme:
+ *
+ * Sets the card theme.
+ */
+void
+ar_card_textures_cache_set_theme (ArCardTexturesCache *cache,
+ ArCardTheme *theme)
+{
+ ArCardTexturesCachePrivate *priv = cache->priv;
+
+ g_return_if_fail (AR_IS_CARD_TEXTURES_CACHE (cache));
+ g_return_if_fail (theme == NULL || AR_IS_CARD_THEME (theme));
+
+ if (priv->theme == theme)
+ return;
+
+ ar_card_textures_cache_clear (cache);
+ ar_card_textures_cache_unset_theme (cache);
+
+ priv->theme = theme;
+ if (theme) {
+ g_object_ref (theme);
+
+ priv->theme_changed_handler = g_signal_connect_swapped (theme, "changed",
+ G_CALLBACK (ar_card_textures_cache_clear),
+ cache);
+ }
+
+ g_object_notify (G_OBJECT (cache), "theme");
+}
+
+/**
+ * ar_card_textures_cache_get_theme:
+ * @cache:
+ *
+ * Returns: the the card theme of @cache
+ */
+ArCardTheme *
+ar_card_textures_cache_get_theme (ArCardTexturesCache *cache)
+{
+ g_return_val_if_fail (AR_IS_CARD_TEXTURES_CACHE (cache), NULL);
+
+ return cache->priv->theme;
+}
+
+/**
+ * ar_card_textures_cache_get_card_texture_by_id:
+ * @cache:
+ * @card_id:
+ *
+ * Returns: a cached #CoglHandle for @card_id.
+ */
+CoglHandle
+ar_card_textures_cache_get_card_texture_by_id (ArCardTexturesCache *cache,
+ guint card_id)
+{
+ ArCardTexturesCachePrivate *priv = cache->priv;
+ CoglHandle handle;
+
+ g_return_val_if_fail (card_id < AR_CARDS_TOTAL , NULL);
+
+ LOG_CALL (cache);
+
+ handle = priv->cards[card_id];
+ if (IS_FAILED_HANDLE (handle)) {
+ LOG_CACHE_HIT (cache);
+ return COGL_INVALID_HANDLE;
+ }
+
+ if (handle == COGL_INVALID_HANDLE) {
+ GdkPixbuf *pixbuf;
+
+ LOG_CACHE_MISS (cache);
+
+ pixbuf = ar_card_theme_get_card_pixbuf (priv->theme, card_id);
+ if (!pixbuf) {
+ priv->cards[card_id] = FAILED_HANDLE;
+ return COGL_INVALID_HANDLE;
+ }
+
+ handle = cogl_texture_new_from_data (gdk_pixbuf_get_width (pixbuf),
+ gdk_pixbuf_get_height (pixbuf),
+ COGL_TEXTURE_NONE,
+ gdk_pixbuf_get_has_alpha (pixbuf)
+ ? COGL_PIXEL_FORMAT_RGBA_8888
+ : COGL_PIXEL_FORMAT_RGB_888,
+ COGL_PIXEL_FORMAT_ANY,
+ gdk_pixbuf_get_rowstride (pixbuf),
+ gdk_pixbuf_get_pixels (pixbuf));
+ g_object_unref (pixbuf);
+
+ if (handle == COGL_INVALID_HANDLE) {
+ priv->cards[card_id] = FAILED_HANDLE;
+ return COGL_INVALID_HANDLE;
+ }
+
+ priv->cards[card_id] = handle;
+ } else {
+ LOG_CACHE_HIT (cache);
+ }
+
+ return handle;
+}
+
+/**
+ * ar_card_textures_cache_get_card_texture:
+ * @cache:
+ * @card:
+ * @highlighted:
+ *
+ * Returns: a cached #CoglHandle for @card.
+ */
+CoglHandle
+ar_card_textures_cache_get_card_texture (ArCardTexturesCache *cache,
+ Card card)
+{
+ guint card_id = _ar_card_to_index (card);
+
+ return ar_card_textures_cache_get_card_texture_by_id (cache, card_id);
+}
+
+/**
+ * ar_card_textures_cache_get_slot_texture:
+ * @cache:
+ * @highlighted:
+ *
+ * Returns: a cached #CoglHandle for the slot.
+ */
+CoglHandle
+ar_card_textures_cache_get_slot_texture (ArCardTexturesCache *cache)
+{
+ return ar_card_textures_cache_get_card_texture_by_id (cache, AR_CARD_SLOT);
+}
diff --git a/aisleriot/lib/ar-card-textures-cache.h b/aisleriot/lib/ar-card-textures-cache.h
new file mode 100644
index 0000000..3f281c4
--- /dev/null
+++ b/aisleriot/lib/ar-card-textures-cache.h
@@ -0,0 +1,74 @@
+/*
+ Copyright © 2008 Neil Roberts
+ Copyright © 2008 Christian Persch
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef AR_CARD_TEXTURES_CACHE_H
+#define AR_CARD_TEXTURES_CACHE_H
+
+#include <glib-object.h>
+#include <cogl/cogl.h>
+
+#include "ar-card.h"
+#include "ar-card-theme.h"
+
+G_BEGIN_DECLS
+
+#define AR_TYPE_CARD_TEXTURES_CACHE (ar_card_textures_cache_get_type())
+#define AR_CARD_TEXTURES_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), AR_TYPE_CARD_TEXTURES_CACHE, ArCardTexturesCache))
+#define AR_CARD_TEXTURES_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), AR_TYPE_CARD_TEXTURES_CACHE, ArCardTexturesCacheClass))
+#define AR_IS_CARD_TEXTURES_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), AR_TYPE_CARD_TEXTURES_CACHE))
+#define AR_IS_CARD_TEXTURES_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), AR_TYPE_CARD_TEXTURES_CACHE))
+#define AR_CARD_TEXTURES_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), AR_TYPE_CARD_TEXTURES_CACHE, ArCardTexturesCacheClass))
+
+typedef struct _ArCardTexturesCache ArCardTexturesCache;
+typedef struct _ArCardTexturesCacheClass ArCardTexturesCacheClass;
+typedef struct _ArCardTexturesCachePrivate ArCardTexturesCachePrivate;
+
+struct _ArCardTexturesCacheClass
+{
+ GObjectClass parent_class;
+};
+
+struct _ArCardTexturesCache
+{
+ GObject parent;
+
+ ArCardTexturesCachePrivate *priv;
+};
+
+GType ar_card_textures_cache_get_type (void);
+
+ArCardTexturesCache *ar_card_textures_cache_new (void);
+
+void ar_card_textures_cache_drop (ArCardTexturesCache *cache);
+
+void ar_card_textures_cache_set_theme (ArCardTexturesCache *cache,
+ ArCardTheme *theme);
+
+ArCardTheme *ar_card_textures_cache_get_theme (ArCardTexturesCache *cache);
+
+CoglHandle ar_card_textures_cache_get_card_texture (ArCardTexturesCache *cache,
+ Card card);
+
+CoglHandle ar_card_textures_cache_get_card_texture_by_id (ArCardTexturesCache *cache,
+ guint card_id);
+
+CoglHandle ar_card_textures_cache_get_slot_texture (ArCardTexturesCache *cache);
+
+G_END_DECLS
+
+#endif /* AR_CARD_TEXTURES_CACHE_H */
diff --git a/aisleriot/prop-editor.c b/aisleriot/prop-editor.c
index 323b920..11429c8 100644
--- a/aisleriot/prop-editor.c
+++ b/aisleriot/prop-editor.c
@@ -27,6 +27,10 @@
#include "prop-editor.h"
+#ifdef HAVE_CLUTTER
+#include <clutter/clutter.h>
+#endif
+
#ifdef HAVE_MAEMO_5
#include <hildon/hildon-gtk.h>
#include <hildon/hildon-pannable-area.h>
@@ -712,6 +716,81 @@ color_changed (GObject *object, GParamSpec *pspec, gpointer data)
g_value_unset (&val);
}
+#ifdef HAVE_CLUTTER
+
+static void
+_clutter_color_from_gdk_color (ClutterColor *clutter_color,
+ const GdkColor *gdk_color)
+{
+ clutter_color->red = gdk_color->red >> 8;
+ clutter_color->green = gdk_color->green >> 8;
+ clutter_color->blue = gdk_color->blue >> 8;
+ clutter_color->alpha = 0xff;
+}
+
+static void
+_clutter_color_to_gdk_color (GdkColor *gdk_color,
+ const ClutterColor *clutter_color)
+{
+ gdk_color->red = clutter_color->red << 8;
+ gdk_color->green = clutter_color->green << 8;
+ gdk_color->blue = clutter_color->blue << 8;
+ gdk_color->pixel = 0;
+}
+
+static void
+cluttercolor_modified (GtkColorButton *cb, gpointer data)
+{
+ ObjectProperty *p = data;
+ GdkColor color;
+ ClutterColor ccolor;
+
+ gtk_color_button_get_color (cb, &color);
+ _clutter_color_from_gdk_color (&ccolor, &color);
+
+ if (is_child_property (p->spec))
+ {
+ GtkWidget *widget = GTK_WIDGET (p->obj);
+ GtkWidget *parent = gtk_widget_get_parent (widget);
+
+ gtk_container_child_set (GTK_CONTAINER (parent),
+ widget, p->spec->name, &ccolor, NULL);
+ }
+ else
+ g_object_set (p->obj, p->spec->name, &ccolor, NULL);
+}
+
+static void
+cluttercolor_changed (GObject *object, GParamSpec *pspec, gpointer data)
+{
+ GtkColorButton *cb = GTK_COLOR_BUTTON (data);
+ GValue val = { 0, };
+ GdkColor cb_color;
+ ClutterColor *color;
+ ClutterColor cb_ccolor;
+
+ g_assert (G_PARAM_SPEC_VALUE_TYPE (pspec) == CLUTTER_TYPE_COLOR);
+
+ g_value_init (&val, CLUTTER_TYPE_COLOR);
+ get_property_value (object, pspec, &val);
+ color = g_value_get_boxed (&val);
+
+ gtk_color_button_get_color (cb, &cb_color);
+ _clutter_color_from_gdk_color (&cb_ccolor, &cb_color);
+
+ if (color != NULL && !clutter_color_equal (color, &cb_ccolor))
+ {
+ block_controller (G_OBJECT (cb));
+ _clutter_color_to_gdk_color (&cb_color, color);
+ gtk_color_button_set_color (cb, &cb_color);
+ unblock_controller (G_OBJECT (cb));
+ }
+
+ g_value_unset (&val);
+}
+
+#endif /* HAVE_CLUTTER */
+
static GtkWidget *
property_widget (GObject *object,
GParamSpec *spec,
@@ -950,6 +1029,22 @@ property_widget (GObject *object,
connect_controller (G_OBJECT (prop_edit), "color-set",
object, spec, G_CALLBACK (color_modified));
}
+#ifdef HAVE_CLUTTER
+ else if ((type == G_TYPE_PARAM_BOXED &&
+ G_PARAM_SPEC_VALUE_TYPE (spec) == CLUTTER_TYPE_COLOR) ||
+ type == CLUTTER_TYPE_PARAM_COLOR)
+ {
+ prop_edit = gtk_color_button_new ();
+
+ g_object_connect_property (object, spec,
+ G_CALLBACK (cluttercolor_changed),
+ prop_edit, G_OBJECT (prop_edit));
+
+ if (can_modify)
+ connect_controller (G_OBJECT (prop_edit), "color-set",
+ object, spec, G_CALLBACK (cluttercolor_modified));
+ }
+#endif /* HAVE_CLUTTER */
else
{
msg = g_strdup_printf ("uneditable property type: %s",
diff --git a/aisleriot/slot-renderer.c b/aisleriot/slot-renderer.c
new file mode 100644
index 0000000..8e233c7
--- /dev/null
+++ b/aisleriot/slot-renderer.c
@@ -0,0 +1,691 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <clutter/clutter.h>
+#include <gtk/gtk.h>
+#include <cogl/cogl.h>
+#include <string.h>
+#include <math.h>
+
+#include "slot-renderer.h"
+#include "card.h"
+
+#include <libgames-support/games-glib-compat.h>
+
+static void aisleriot_slot_renderer_dispose (GObject *object);
+static void aisleriot_slot_renderer_finalize (GObject *object);
+
+static void aisleriot_slot_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void aisleriot_slot_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void aisleriot_slot_renderer_paint (ClutterActor *actor);
+
+static void aisleriot_slot_renderer_set_cache (AisleriotSlotRenderer *srend,
+ ArCardTexturesCache *cache);
+
+static void completed_cb (AisleriotSlotRenderer *srend);
+
+
+G_DEFINE_TYPE (AisleriotSlotRenderer, aisleriot_slot_renderer,
+ CLUTTER_TYPE_ACTOR);
+
+#define AISLERIOT_SLOT_RENDERER_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), AISLERIOT_TYPE_SLOT_RENDERER, \
+ AisleriotSlotRendererPrivate))
+
+typedef struct _AnimationData AnimationData;
+
+struct _AisleriotSlotRendererPrivate
+{
+ ArStyle *style;
+
+ ArCardTexturesCache *cache;
+
+ ArSlot *slot;
+
+ CoglHandle material;
+
+ ClutterColor highlight_color;
+ gboolean show_highlight;
+ gint highlight_start;
+
+ gint revealed_card;
+
+ ClutterTimeline *timeline;
+ guint completed_handler;
+ GArray *animations;
+ guint n_unexposed_animated_cards;
+
+ ClutterContainer *animation_layer;
+};
+
+struct _AnimationData
+{
+ ClutterActor *card_tex;
+ ClutterBehaviour *move, *rotate, *depth;
+};
+
+enum
+{
+ PROP_0,
+
+ PROP_CACHE,
+ PROP_SLOT,
+ PROP_STYLE,
+ PROP_HIGHLIGHT,
+ PROP_REVEALED_CARD,
+ PROP_ANIMATION_LAYER
+};
+
+static void
+sync_style_selection_color (ArStyle *style,
+ GParamSpec *pspec,
+ AisleriotSlotRenderer *srend)
+{
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+ ClutterColor color;
+
+ ar_style_get_selection_color (style, &color);
+ if (clutter_color_equal (&color, &priv->highlight_color))
+ return;
+
+ priv->highlight_color = color;
+
+ if (priv->show_highlight)
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (srend));
+}
+
+static void
+aisleriot_slot_renderer_class_init (AisleriotSlotRendererClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ ClutterActorClass *actor_class = (ClutterActorClass *) klass;
+ GParamSpec *pspec;
+
+ gobject_class->dispose = aisleriot_slot_renderer_dispose;
+ gobject_class->finalize = aisleriot_slot_renderer_finalize;
+
+ gobject_class->set_property = aisleriot_slot_renderer_set_property;
+ gobject_class->get_property = aisleriot_slot_renderer_get_property;
+
+ actor_class->paint = aisleriot_slot_renderer_paint;
+
+ g_object_class_install_property
+ (gobject_class,
+ PROP_STYLE,
+ g_param_spec_object ("style", NULL, NULL,
+ AR_TYPE_STYLE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ pspec = g_param_spec_object ("cache", NULL, NULL,
+ AR_TYPE_CARD_TEXTURES_CACHE,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_CACHE, pspec);
+
+ pspec = g_param_spec_pointer ("slot", NULL, NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_SLOT, pspec);
+
+ pspec = g_param_spec_int ("highlight", NULL, NULL,
+ -1, G_MAXINT, G_MAXINT,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_HIGHLIGHT, pspec);
+
+ pspec = g_param_spec_int ("revealed-card", NULL, NULL,
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_REVEALED_CARD, pspec);
+
+ pspec = g_param_spec_object ("animation-layer", NULL, NULL,
+ CLUTTER_TYPE_CONTAINER,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (gobject_class, PROP_ANIMATION_LAYER, pspec);
+
+ g_type_class_add_private (klass, sizeof (AisleriotSlotRendererPrivate));
+}
+
+static void
+aisleriot_slot_renderer_init (AisleriotSlotRenderer *self)
+{
+ AisleriotSlotRendererPrivate *priv;
+
+ priv = self->priv = AISLERIOT_SLOT_RENDERER_GET_PRIVATE (self);
+
+ priv->revealed_card = -1;
+ priv->highlight_start = G_MAXINT;
+
+ priv->animations = g_array_new (FALSE, FALSE, sizeof (AnimationData));
+ priv->timeline = clutter_timeline_new (500);
+ g_signal_connect_swapped (priv->timeline, "completed",
+ G_CALLBACK (completed_cb), self);
+}
+
+static void
+aisleriot_slot_renderer_dispose (GObject *object)
+{
+ AisleriotSlotRenderer *self = (AisleriotSlotRenderer *) object;
+ AisleriotSlotRendererPrivate *priv = self->priv;
+
+ aisleriot_slot_renderer_set_cache (self, NULL);
+
+ /* Get rid of any running animations */
+ aisleriot_slot_renderer_set_animations (self, 0, NULL, 0);
+
+ if (priv->timeline) {
+ g_object_unref (priv->timeline);
+ priv->timeline = NULL;
+ }
+
+ if (priv->material != COGL_INVALID_HANDLE) {
+ cogl_handle_unref (priv->material);
+ priv->material = COGL_INVALID_HANDLE;
+ }
+
+ aisleriot_slot_renderer_set_animation_layer (self, NULL);
+
+ G_OBJECT_CLASS (aisleriot_slot_renderer_parent_class)->dispose (object);
+}
+
+static void
+aisleriot_slot_renderer_finalize (GObject *object)
+{
+ AisleriotSlotRenderer *srend = AISLERIOT_SLOT_RENDERER (object);
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+
+ g_array_free (priv->animations, TRUE);
+
+ g_signal_handlers_disconnect_by_func (priv->style,
+ G_CALLBACK (sync_style_selection_color),
+ srend);
+ g_object_unref (priv->style);
+
+ G_OBJECT_CLASS (aisleriot_slot_renderer_parent_class)->finalize (object);
+}
+
+ClutterActor *
+aisleriot_slot_renderer_new (ArStyle *style,
+ ArCardTexturesCache *cache,
+ ArSlot *slot)
+{
+ return g_object_new (AISLERIOT_TYPE_SLOT_RENDERER,
+ "style", style,
+ "cache", cache,
+ "slot", slot,
+ NULL);
+}
+
+static void
+aisleriot_slot_renderer_set_cache (AisleriotSlotRenderer *srend,
+ ArCardTexturesCache *cache)
+{
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+
+ if (cache == priv->cache)
+ return;
+
+ if (cache)
+ g_object_ref (cache);
+
+ if (priv->cache)
+ g_object_unref (priv->cache);
+
+ priv->cache = cache;
+}
+
+static void
+aisleriot_slot_renderer_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AisleriotSlotRenderer *srend = AISLERIOT_SLOT_RENDERER (object);
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+
+ switch (property_id) {
+ case PROP_CACHE:
+ aisleriot_slot_renderer_set_cache (srend, g_value_get_object (value));
+ break;
+
+ case PROP_SLOT:
+ priv->slot = g_value_get_pointer (value);
+ break;
+
+ case PROP_STYLE:
+ priv->style = g_value_dup_object (value);
+
+ sync_style_selection_color (priv->style, NULL, srend);
+ g_signal_connect (priv->style, "notify::" AR_STYLE_PROP_SELECTION_COLOR,
+ G_CALLBACK (sync_style_selection_color), srend);
+ break;
+
+ case PROP_HIGHLIGHT:
+ aisleriot_slot_renderer_set_highlight (srend,
+ g_value_get_int (value));
+ break;
+
+ case PROP_REVEALED_CARD:
+ aisleriot_slot_renderer_set_revealed_card (srend,
+ g_value_get_int (value));
+ break;
+
+ case PROP_ANIMATION_LAYER:
+ aisleriot_slot_renderer_set_animation_layer (srend,
+ g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+aisleriot_slot_renderer_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AisleriotSlotRenderer *srend = AISLERIOT_SLOT_RENDERER (object);
+
+ switch (property_id) {
+ case PROP_HIGHLIGHT:
+ g_value_set_int (value,
+ aisleriot_slot_renderer_get_highlight (srend));
+ break;
+
+ case PROP_REVEALED_CARD:
+ g_value_set_int (value,
+ aisleriot_slot_renderer_get_revealed_card (srend));
+ break;
+
+ case PROP_ANIMATION_LAYER:
+ g_value_set_object (value,
+ aisleriot_slot_renderer_get_animation_layer (srend));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+aisleriot_slot_renderer_set_material_for_card (AisleriotSlotRenderer *srend,
+ CoglHandle tex,
+ gboolean show_highlight)
+{
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+ guint8 opacity = clutter_actor_get_paint_opacity (CLUTTER_ACTOR (srend));
+
+ if (priv->material == COGL_INVALID_HANDLE)
+ priv->material = cogl_material_new ();
+
+ if (show_highlight)
+ {
+ CoglColor color;
+
+ /* The previous code for drawing the highlight rendered the
+ normal card texture and then rendered the card again
+ multiplied by the highlight color but with 50%
+ transparency. The blend function is alpha*src+(1-alpha*dst)
+ where src is effectively the tex times the highlight color
+ and the dst is the original tex. Therefore the final color is
+ 0.5*tex+0.5*tex*highlight which is the same as
+ (0.5+highlight/2)*tex. We can precompute that value to avoid
+ having to draw the card twice */
+ cogl_color_set_from_4ub (&color,
+ MIN (priv->highlight_color.red
+ / 2 + 128, 0xff),
+ MIN (priv->highlight_color.green
+ / 2 + 128, 0xff),
+ MIN (priv->highlight_color.blue
+ / 2 + 128, 0xff),
+ opacity);
+ cogl_color_premultiply (&color);
+ cogl_material_set_color (priv->material, &color);
+ }
+ else
+ cogl_material_set_color4ub (priv->material,
+ opacity, opacity, opacity, opacity);
+
+ cogl_material_set_layer (priv->material, 0, tex);
+ cogl_set_source (priv->material);
+}
+
+static void
+aisleriot_slot_renderer_paint_card (AisleriotSlotRenderer *srend,
+ guint card_num)
+{
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+ Card card = CARD (priv->slot->cards->data[card_num]);
+ CoglHandle cogl_tex;
+ guint tex_width, tex_height;
+ int cardx, cardy;
+
+ cogl_tex = ar_card_textures_cache_get_card_texture (priv->cache, card);
+ if (G_UNLIKELY (cogl_tex == COGL_INVALID_HANDLE))
+ return;
+
+ tex_width = cogl_texture_get_width (cogl_tex);
+ tex_height = cogl_texture_get_height (cogl_tex);
+
+ aisleriot_game_get_card_offset (priv->slot, card_num,
+ FALSE,
+ &cardx, &cardy);
+
+ aisleriot_slot_renderer_set_material_for_card
+ (srend, cogl_tex,
+ priv->show_highlight && card_num >= priv->highlight_start);
+
+ cogl_rectangle (cardx, cardy, cardx + tex_width, cardy + tex_height);
+}
+
+static void
+aisleriot_slot_renderer_paint (ClutterActor *actor)
+{
+ AisleriotSlotRenderer *srend = (AisleriotSlotRenderer *) actor;
+ AisleriotSlotRendererPrivate *priv = srend->priv;
+ guint n_cards, n_animated_cards;
+ guint8 *cards;
+ guint i;
+
+ g_return_if_fail (priv->cache != NULL);
+ g_return_if_fail (priv->slot != NULL);
+
+ cards = priv->slot->cards->data;
+ n_cards = priv->slot->cards->len;
+ n_animated_cards = priv->animations->len + priv->n_unexposed_animated_cards;
+
+ g_assert (n_cards >= priv->slot->exposed);
+ g_assert (priv->n_unexposed_animated_cards == 0 || priv->animations->len > 0);
+ g_assert (n_cards >= n_animated_cards);
+
+ if (n_cards <= n_animated_cards) {
+ CoglHandle cogl_tex;
+ guint tex_width, tex_height;
+
+ cogl_tex = ar_card_textures_cache_get_slot_texture (priv->cache);
+ if (G_UNLIKELY (cogl_tex == COGL_INVALID_HANDLE))
+ return;
+
+ tex_width = cogl_texture_get_width (cogl_tex);
+ tex_height = cogl_texture_get_height (cogl_tex);
+
+ aisleriot_slot_renderer_set_material_for_card (srend, cogl_tex,
+ priv->show_highlight);
+ cogl_rectangle (0, 0, tex_width, tex_height);
+ } else {
+ guint first_card, last_card;
+
+ first_card = MIN (n_cards - n_animated_cards - 1,
+ n_cards - priv->slot->exposed);
+ last_card = n_cards - n_animated_cards;
+
+ for (i = first_card; i < last_card; i++)
+ if (i != priv->revealed_card)
+ aisleriot_slot_renderer_paint_card (srend, i);
+
+ /* Paint the revealed card after all of the other cards so that it
+ * will appeear on top.
+ */
+ if (priv->revealed_card >= first_card && priv->revealed_card < last_card)
+ aisleriot_slot_renderer_paint_card (srend, priv->revealed_card);
+ }
+}
+
+guint
+aisleriot_slot_renderer_get_highlight (AisleriotSlotRenderer *srend)
+{
+ g_return_val_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend), 0);
+
+ return srend->priv->highlight_start;
+}
+
+void
+aisleriot_slot_renderer_set_highlight (AisleriotSlotRenderer *srend,
+ gint highlight)
+{
+ AisleriotSlotRendererPrivate *priv;
+
+ g_return_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend));
+
+ priv = srend->priv;
+
+ priv->highlight_start = highlight;
+ priv->show_highlight = priv->highlight_start != G_MAXINT;
+
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (srend));
+
+ g_object_notify (G_OBJECT (srend), "highlight");
+}
+
+gint
+aisleriot_slot_renderer_get_revealed_card (AisleriotSlotRenderer *srend)
+{
+ g_return_val_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend), 0);
+
+ return srend->priv->revealed_card;
+}
+
+void
+aisleriot_slot_renderer_set_revealed_card (AisleriotSlotRenderer *srend,
+ gint revealed_card)
+{
+ AisleriotSlotRendererPrivate *priv;
+
+ g_return_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend));
+
+ priv = srend->priv;
+
+ priv->revealed_card = revealed_card;
+
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (srend));
+
+ g_object_notify (G_OBJECT (srend), "revealed-card");
+}
+
+ClutterContainer *
+aisleriot_slot_renderer_get_animation_layer (AisleriotSlotRenderer *srend)
+{
+ AisleriotSlotRendererPrivate *priv;
+
+ g_return_val_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend), NULL);
+
+ priv = srend->priv;
+
+ return priv->animation_layer;
+}
+
+void
+aisleriot_slot_renderer_set_animation_layer (AisleriotSlotRenderer *srend,
+ ClutterContainer *animation_layer)
+{
+ AisleriotSlotRendererPrivate *priv;
+
+ g_return_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend));
+
+ priv = srend->priv;
+
+ if (animation_layer)
+ g_object_ref (animation_layer);
+
+ if (priv->animation_layer)
+ g_object_unref (priv->animation_layer);
+
+ priv->animation_layer = animation_layer;
+
+ g_object_notify (G_OBJECT (srend), "animation-layer");
+}
+
+static gdouble
+aisleriot_slot_sine_animation_mode (ClutterAlpha *alpha,
+ gpointer data)
+{
+ ClutterTimeline *tl = clutter_alpha_get_timeline (alpha);
+
+ return sin (clutter_timeline_get_progress (tl) * G_PI);
+}
+
+void
+aisleriot_slot_renderer_set_animations (AisleriotSlotRenderer *srend,
+ guint n_anims,
+ const AisleriotAnimStart *anims,
+ guint n_unexposed_animated_cards)
+{
+ AisleriotSlotRendererPrivate *priv;
+ guint i;
+ gint card_num;
+
+ g_return_if_fail (AISLERIOT_IS_SLOT_RENDERER (srend));
+
+ priv = srend->priv;
+
+ g_return_if_fail (n_anims <= priv->slot->exposed);
+
+ /* Destroy the current animations */
+ for (i = 0; i < priv->animations->len; i++) {
+ AnimationData *anim_data;
+
+ anim_data = &g_array_index (priv->animations, AnimationData, i);
+
+ if (anim_data->move)
+ g_object_unref (anim_data->move);
+ if (anim_data->rotate)
+ g_object_unref (anim_data->rotate);
+ if (anim_data->depth)
+ g_object_unref (anim_data->depth);
+
+ clutter_actor_destroy (anim_data->card_tex);
+ g_object_unref (anim_data->card_tex);
+ }
+
+ g_array_set_size (priv->animations, 0);
+
+ card_num = priv->slot->cards->len - n_anims;
+
+ for (i = 0; i < n_anims; i++) {
+ AnimationData anim_data;
+ ClutterAlpha *alpha;
+ ClutterKnot knots[2];
+ Card card = CARD (priv->slot->cards->data[card_num]);
+ guint card_width, card_height;
+
+ memset (&anim_data, 0, sizeof (anim_data));
+
+ anim_data.card_tex = aisleriot_card_new (priv->cache,
+ anims[i].old_card,
+ card);
+
+ card_width = clutter_actor_get_width (anim_data.card_tex);
+ card_height = clutter_actor_get_height (anim_data.card_tex);
+
+ g_object_ref_sink (anim_data.card_tex);
+ if (priv->animation_layer)
+ clutter_container_add (priv->animation_layer,
+ CLUTTER_ACTOR (anim_data.card_tex), NULL);
+
+ clutter_actor_set_position (anim_data.card_tex,
+ anims[i].cardx, anims[i].cardy);
+
+ knots[0].x = anims[i].cardx;
+ knots[0].y = anims[i].cardy;
+
+ aisleriot_game_get_card_offset (priv->slot, card_num, FALSE,
+ &knots[1].x, &knots[1].y);
+ knots[1].x += priv->slot->rect.x;
+ knots[1].y += priv->slot->rect.y;
+
+ alpha = clutter_alpha_new_full (priv->timeline, CLUTTER_LINEAR);
+
+ anim_data.move
+ = clutter_behaviour_path_new_with_knots (alpha, knots,
+ G_N_ELEMENTS (knots));
+ clutter_behaviour_apply (anim_data.move, anim_data.card_tex);
+
+ if (anims[i].old_card.value != card.value) {
+ int center_x, center_y;
+
+ center_x = card_width / 2;
+ center_y = card_height / 2;
+
+ clutter_actor_set_rotation (anim_data.card_tex, CLUTTER_Y_AXIS,
+ 180.0,
+ center_x, center_y, 0);
+
+ anim_data.rotate = clutter_behaviour_rotate_new (alpha,
+ CLUTTER_Y_AXIS,
+ CLUTTER_ROTATE_CW,
+ 180.0, 0.0);
+ clutter_behaviour_rotate_set_center (CLUTTER_BEHAVIOUR_ROTATE
+ (anim_data.rotate),
+ center_x, center_y, 0);
+
+ clutter_behaviour_apply (anim_data.rotate, anim_data.card_tex);
+ }
+
+ if (anims[i].raise) {
+ alpha = clutter_alpha_new_with_func (priv->timeline,
+ aisleriot_slot_sine_animation_mode,
+ NULL, NULL);
+
+ anim_data.depth = clutter_behaviour_depth_new (alpha,
+ 0, card_height);
+ clutter_behaviour_apply (anim_data.depth, anim_data.card_tex);
+ }
+
+ g_array_append_val (priv->animations, anim_data);
+
+ card_num++;
+ }
+
+ if (n_anims > 0) {
+ clutter_timeline_rewind (priv->timeline);
+ clutter_timeline_start (priv->timeline);
+ }
+
+ priv->n_unexposed_animated_cards = n_unexposed_animated_cards;
+
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (srend));
+}
+
+static void
+completed_cb (AisleriotSlotRenderer *srend)
+{
+ /* Get rid of all animation actors */
+ aisleriot_slot_renderer_set_animations (srend, 0, NULL, 0);
+
+ /* Redraw so that the animated actors will be drawn as part of the
+ renderer instead */
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (srend));
+}
diff --git a/aisleriot/slot-renderer.h b/aisleriot/slot-renderer.h
new file mode 100644
index 0000000..930abd5
--- /dev/null
+++ b/aisleriot/slot-renderer.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright © 2008 Neil Roberts
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __AISLERIOT_SLOT_RENDERER_H__
+#define __AISLERIOT_SLOT_RENDERER_H__
+
+#include <clutter/clutter.h>
+
+#include "game.h"
+#include "ar-card-textures-cache.h"
+#include "ar-style.h"
+
+G_BEGIN_DECLS
+
+#define AISLERIOT_TYPE_SLOT_RENDERER \
+ (aisleriot_slot_renderer_get_type())
+#define AISLERIOT_SLOT_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ AISLERIOT_TYPE_SLOT_RENDERER, \
+ AisleriotSlotRenderer))
+#define AISLERIOT_SLOT_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ AISLERIOT_TYPE_SLOT_RENDERER, \
+ AisleriotSlotRendererClass))
+#define AISLERIOT_IS_SLOT_RENDERER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ AISLERIOT_TYPE_SLOT_RENDERER))
+#define AISLERIOT_IS_SLOT_RENDERER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ AISLERIOT_TYPE_SLOT_RENDERER))
+#define AISLERIOT_SLOT_RENDERER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ AISLERIOT_TYPE_SLOT_RENDERER, \
+ AisleriotSlotRendererClass))
+
+typedef struct _AisleriotSlotRenderer AisleriotSlotRenderer;
+typedef struct _AisleriotSlotRendererClass AisleriotSlotRendererClass;
+typedef struct _AisleriotSlotRendererPrivate AisleriotSlotRendererPrivate;
+typedef struct _AisleriotAnimStart AisleriotAnimStart;
+
+struct _AisleriotSlotRendererClass
+{
+ ClutterActorClass parent_class;
+};
+
+struct _AisleriotSlotRenderer
+{
+ ClutterActor parent;
+
+ AisleriotSlotRendererPrivate *priv;
+};
+
+struct _AisleriotAnimStart
+{
+ gint cardx, cardy;
+ Card old_card;
+ gboolean raise;
+};
+
+GType aisleriot_slot_renderer_get_type (void) G_GNUC_CONST;
+
+ClutterActor *aisleriot_slot_renderer_new (ArStyle *style,
+ ArCardTexturesCache *cache,
+ ArSlot *slot);
+
+void aisleriot_slot_renderer_set_highlight (AisleriotSlotRenderer *srend,
+ gint hightlight_start);
+guint aisleriot_slot_renderer_get_highlight (AisleriotSlotRenderer *srend);
+
+void aisleriot_slot_renderer_set_revealed_card (AisleriotSlotRenderer *srend,
+ gint revealed_card);
+gint aisleriot_slot_renderer_get_revealed_card (AisleriotSlotRenderer *srend);
+
+ClutterContainer *aisleriot_slot_renderer_get_animation_layer
+ (AisleriotSlotRenderer *srend);
+void aisleriot_slot_renderer_set_animation_layer
+ (AisleriotSlotRenderer *srend,
+ ClutterContainer *animation_layer);
+
+void aisleriot_slot_renderer_set_animations (AisleriotSlotRenderer *srend,
+ guint n_anims,
+ const AisleriotAnimStart *anims,
+ guint n_unexposed_animated_cards);
+
+G_END_DECLS
+
+#endif /* __AISLERIOT_SLOT_RENDERER_H__ */
diff --git a/aisleriot/sol.c b/aisleriot/sol.c
index 28da985..fe3bf46 100644
--- a/aisleriot/sol.c
+++ b/aisleriot/sol.c
@@ -28,6 +28,16 @@
#include <gtk/gtk.h>
+#ifdef HAVE_CLUTTER
+#include <cogl/cogl.h>
+#include <clutter/clutter.h>
+#include <clutter-gtk/clutter-gtk.h>
+
+#ifndef CLUTTER_GTK_CHECK_VERSION
+#define CLUTTER_GTK_CHECK_VERSION(a,b,c) (0)
+#endif
+#endif
+
#ifdef HAVE_HILDON
#include <libosso.h>
@@ -262,6 +272,14 @@ main_prog (void *closure, int argc, char *argv[])
g_option_context_add_group (option_context, egg_sm_client_get_option_group ());
#endif /* WITH_SMCLIENT */
+#ifdef HAVE_CLUTTER
+ g_option_context_add_group (option_context, cogl_get_option_group ());
+ g_option_context_add_group (option_context, clutter_get_option_group_without_init ());
+#if CLUTTER_GTK_CHECK_VERSION (0, 90, 0)
+ g_option_context_add_group (option_context, gtk_clutter_get_option_group ());
+#endif
+#endif /* HAVE_CLUTTER */
+
#if defined(HAVE_HILDON) && defined(HAVE_MAEMO_5)
{
char *rc_file;
@@ -285,6 +303,16 @@ main_prog (void *closure, int argc, char *argv[])
goto cleanup;
}
+#ifdef HAVE_CLUTTER
+#if !CLUTTER_GTK_CHECK_VERSION (0, 90, 0)
+ if (gtk_clutter_init_with_args (NULL, NULL, NULL, NULL, NULL, &error) != CLUTTER_INIT_SUCCESS) {
+ g_printerr ("Failed to initialise clutter: %s\n", error->message);
+ g_error_free (error);
+ goto cleanup;
+ }
+#endif
+#endif /* HAVE_CLUTTER */
+
#ifdef HAVE_MAEMO
data.program = HILDON_PROGRAM (hildon_program_get_instance ());
diff --git a/aisleriot/window.c b/aisleriot/window.c
index 1af44ec..03eeed2 100644
--- a/aisleriot/window.c
+++ b/aisleriot/window.c
@@ -54,7 +54,14 @@
#include <libgames-support/games-settings.h>
#endif
+#ifdef HAVE_CLUTTER
+#include "ar-clutter-embed.h"
+#include "ar-style.h"
+#include "baize.h"
+#include "board.h"
+#else
#include "board-noclutter.h"
+#endif
#include "ar-card-theme.h"
#include "ar-card-themes.h"
@@ -124,7 +131,13 @@ struct _AisleriotWindowPrivate
{
AisleriotGame *game;
ArStyle *board_style;
+#ifdef HAVE_CLUTTER
+ ArClutterEmbed *board;
+ ClutterActor *baize_actor;
+ ClutterActor *board_actor;
+#else
AisleriotBoard *board;
+#endif
ArCardThemes *theme_manager;
ArCardTheme *theme;
@@ -366,7 +379,11 @@ undo_cb (GtkAction *action,
AisleriotWindowPrivate *priv = window->priv;
/* If a move is in progress, cancel it before changing the game! */
+#ifdef HAVE_CLUTTER
+ aisleriot_board_abort_move (AISLERIOT_BOARD (priv->board_actor));
+#else
aisleriot_board_abort_move (priv->board);
+#endif
aisleriot_game_undo_move (priv->game);
}
@@ -377,7 +394,11 @@ redo_cb (GtkAction *action,
{
AisleriotWindowPrivate *priv = window->priv;
+#ifdef HAVE_CLUTTER
+ aisleriot_board_abort_move (AISLERIOT_BOARD (priv->board_actor));
+#else
aisleriot_board_abort_move (priv->board);
+#endif
aisleriot_game_redo_move (priv->game);
}
@@ -1011,6 +1032,24 @@ sound_toggle_cb (GtkToggleAction *action,
#endif /* ENABLE_SOUND */
+#ifdef HAVE_CLUTTER
+
+static void
+animations_toggle_cb (GtkToggleAction *action,
+ AisleriotWindow *window)
+{
+ AisleriotWindowPrivate *priv = window->priv;
+ gboolean enabled;
+
+ enabled = gtk_toggle_action_get_active (action);
+
+ ar_style_set_enable_animations (priv->board_style, enabled);
+
+ games_conf_set_boolean (NULL, aisleriot_conf_get_key (CONF_ANIMATIONS), enabled);
+}
+
+#endif /* HAVE_CLUTTER */
+
static void
show_hint_cb (GtkAction *action,
AisleriotWindow *window)
@@ -1901,7 +1940,7 @@ game_exception_cb (AisleriotGame *game,
gtk_widget_show (dialog);
}
-#if defined(ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0)
+#if defined(HAVE_CLUTTER) || (defined(ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0))
static void
settings_changed_cb (GtkSettings *settings,
@@ -1918,12 +1957,23 @@ settings_changed_cb (GtkSettings *settings,
else
name = NULL;
+#ifdef HAVE_CLUTTER
+ if (name == NULL || strcmp (name, "gtk-enable-animations") == 0) {
+ g_object_get (settings, "gtk-enable-animations", &enabled, NULL);
+
+ action = gtk_action_group_get_action (priv->action_group, "Animations");
+ gtk_action_set_visible (action, enabled);
+ }
+#endif /* HAVE_CLUTTER */
+
+#if defined(ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0)
if (name == NULL || strcmp (name, "gtk-enable-event-sounds") == 0) {
g_object_get (settings, "gtk-enable-event-sounds", &enabled, NULL);
action = gtk_action_group_get_action (priv->action_group, "Sound");
gtk_action_set_visible (action, enabled);
}
+#endif /* ENABLE_SOUND && GTK >= 2.14 */
}
static void
@@ -1951,11 +2001,17 @@ screen_changed_cb (GtkWidget *widget,
settings = gtk_widget_get_settings (widget);
settings_changed_cb (settings, NULL, window);
+#ifdef HAVE_CLUTTER
+ g_signal_connect (settings, "notify::gtk-enable-animations",
+ G_CALLBACK (settings_changed_cb), window);
+#endif
+#if defined (ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0)
g_signal_connect (settings, "notify::gtk-enable-event-sounds",
G_CALLBACK (settings_changed_cb), window);
+#endif
}
-#endif /* ENABLE_SOUND && GTK+ >= 2.14.0 */
+#endif /* HAVE_CLUTTER || ENABLE_SOUND && GTK+ >= 2.14.0 */
/*
* aisleriot_window_set_freecell_mode:
@@ -2005,6 +2061,37 @@ board_status_message_cb (AisleriotBoard *board,
#endif /* !HAVE_HILDON */
+#ifdef HAVE_CLUTTER
+
+static void
+board_cursor_cb (AisleriotBoard *board,
+ int cursor_type,
+ ArClutterEmbed *embed)
+{
+ ar_clutter_embed_set_cursor (embed, (ArCursorType) cursor_type);
+}
+
+static void
+board_error_bell_cb (AisleriotBoard *board,
+ ArClutterEmbed *embed)
+{
+#if GTK_CHECK_VERSION (2, 12, 0) || (defined (HAVE_HILDON) && !defined(HAVE_MAEMO_3))
+ gtk_widget_error_bell (GTK_WIDGET (embed));
+#endif
+}
+
+static void
+embed_size_allocate_cb (ArClutterEmbed *embed,
+ GtkAllocation *allocation,
+ AisleriotWindow *window)
+{
+ AisleriotWindowPrivate *priv = window->priv;
+
+ clutter_actor_set_size (priv->board_actor, allocation->width, allocation->height);
+}
+
+#endif /* HAVE_CLUTTER */
+
/* Class implementation */
#ifdef HAVE_HILDON
@@ -2237,6 +2324,12 @@ aisleriot_window_init (AisleriotWindow *window)
G_CALLBACK (sound_toggle_cb),
FALSE /* not active by default */ },
#endif /* ENABLE_SOUND */
+#ifdef HAVE_CLUTTER
+ { "Animations", NULL, N_("_Animations"), NULL,
+ ACTION_TOOLTIP (N_("Whether or not to animate card moves")),
+ G_CALLBACK (animations_toggle_cb),
+ FALSE /* not active by default */ },
+#endif /* HAVE_CLUTTER */
};
static const char names[][16] = {
@@ -2334,6 +2427,9 @@ aisleriot_window_init (AisleriotWindow *window)
#ifdef ENABLE_SOUND
"<menuitem action='Sound'/>"
#endif
+#ifdef HAVE_CLUTTER
+ "<menuitem action='Animations'/>"
+#endif
"</menu>"
"<menu action='OptionsMenu'/>"
"<menu action='HelpMenu'>"
@@ -2398,6 +2494,9 @@ aisleriot_window_init (AisleriotWindow *window)
GtkStatusbar *statusbar;
GtkWidget *statusbar_hbox, *label, *time_box;
#endif
+#ifdef HAVE_CLUTTER
+ ClutterContainer *stage;
+#endif
g_assert (G_N_ELEMENTS (names) == LAST_ACTION);
@@ -2411,7 +2510,34 @@ aisleriot_window_init (AisleriotWindow *window)
priv->board_style = ar_style_new ();
+#ifdef HAVE_CLUTTER
+ priv->board = ar_clutter_embed_new (priv->board_style);
+
+ priv->baize_actor = aisleriot_baize_new ();
+
+ stage = CLUTTER_CONTAINER (gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (priv->board)));
+ clutter_container_add (stage, priv->baize_actor, NULL);
+ /* FIXMEchpe: how to ensure this is ALWAYS the lowest actor? */
+ clutter_actor_lower_bottom (priv->baize_actor);
+
+ priv->board_actor = aisleriot_board_new (priv->board_style, priv->game);
+ clutter_container_add (stage, priv->board_actor, NULL);
+
+ /* FIXMEchpe */
+ clutter_stage_set_key_focus (CLUTTER_STAGE (stage), priv->board_actor);
+
+ g_signal_connect_after (priv->board, "size-allocate",
+ G_CALLBACK (embed_size_allocate_cb), window);
+
+ g_signal_connect (priv->board_actor, "request-cursor",
+ G_CALLBACK (board_cursor_cb), priv->board);
+ g_signal_connect (priv->board_actor, "error-bell",
+ G_CALLBACK (board_error_bell_cb), priv->board);
+
+ /* FIXMEchpe: unref baize & board_actor here? */
+#else
priv->board = AISLERIOT_BOARD (aisleriot_board_new (priv->board_style, priv->game));
+#endif /* HAVE_CLUTTER */
theme_name = games_conf_get_string (NULL, aisleriot_conf_get_key (CONF_THEME), NULL);
theme = ar_card_themes_get_theme_by_name (priv->theme_manager, theme_name);
@@ -2463,8 +2589,13 @@ aisleriot_window_init (AisleriotWindow *window)
priv->game_message_id = gtk_statusbar_get_context_id (priv->statusbar, "board-message");
+#ifdef HAVE_CLUTTER
+ g_signal_connect (priv->board_actor, "status-message",
+ G_CALLBACK (board_status_message_cb), window);
+#else
g_signal_connect (priv->board, "status-message",
G_CALLBACK (board_status_message_cb), window);
+#endif
#if GTK_CHECK_VERSION (2, 91, 0)
gtk_window_set_has_resize_grip (GTK_WINDOW (window), TRUE);
@@ -2611,12 +2742,19 @@ aisleriot_window_init (AisleriotWindow *window)
set_fullscreen_actions (window, FALSE);
-#if defined(ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0)
+#ifdef HAVE_CLUTTER
+ action = gtk_action_group_get_action (priv->action_group, "Animations");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action),
+ games_conf_get_boolean (NULL, aisleriot_conf_get_key (CONF_ANIMATIONS), NULL));
+
+#endif /* HAVE_CLUTTER */
+
+#if defined(HAVE_CLUTTER) || (defined(ENABLE_SOUND) && GTK_CHECK_VERSION (2, 14, 0))
/* Set the action visibility and listen for animation and sound mode changes */
screen_changed_cb (GTK_WIDGET (window), NULL, window);
g_signal_connect (window, "screen-changed",
G_CALLBACK (screen_changed_cb), window);
-#endif /* ENABLE_SOUND && GTK+ >= 2.14.0 */
+#endif /* HAVE_CLUTTER || ENABLE_SOUND && GTK+ >= 2.14.0 */
/* Now set up the widgets */
main_vbox = gtk_vbox_new (FALSE, 0);
@@ -2702,6 +2840,12 @@ aisleriot_window_dispose (GObject *object)
AisleriotWindow *window = AISLERIOT_WINDOW (object);
AisleriotWindowPrivate *priv = window->priv;
+#ifdef HAVE_CLUTTER
+ g_signal_handlers_disconnect_by_func (gtk_widget_get_settings (GTK_WIDGET (window)),
+ G_CALLBACK (settings_changed_cb),
+ window);
+#endif /* HAVE_CLUTTER */
+
#ifndef HAVE_HILDON
if (priv->hint_dialog) {
gtk_widget_destroy (priv->hint_dialog);
@@ -2742,6 +2886,10 @@ aisleriot_window_finalize (GObject *object)
AisleriotWindow *window = AISLERIOT_WINDOW (object);
AisleriotWindowPrivate *priv = window->priv;
+#ifdef HAVE_CLUTTER
+ g_object_unref (priv->board_style);
+#endif /* HAVE_CLUTTER */
+
if (priv->theme) {
g_object_unref (priv->theme);
}
diff --git a/configure.in b/configure.in
index a2fc4cc..904df12 100644
--- a/configure.in
+++ b/configure.in
@@ -430,10 +430,27 @@ fi
# Clutter
# *******
+# We're using need_guile as a quick way to check whether building aisleriot
+if test "$need_guile" = "yes"; then
+ AC_MSG_CHECKING([whether to enable aisleriot/clutter])
+ AC_ARG_ENABLE([aisleriot-clutter],
+ [AS_HELP_STRING([--enable-aisleriot-clutter],[Whether to enable the clutter version of aisleriot (default: disabled)])],
+ [],[enable_aisleriot_clutter=no])
+ AC_MSG_RESULT([$enable_aisleriot_clutter])
+
+ if test "$enable_aisleriot_clutter" = "yes"; then
+ # Distro packagers: DO NOT ENABLE AISLERIOT/CLUTTER IN YOUR DISTRO PACKAGES, OR ELSE!
+ AC_MSG_NOTICE([Aisleriot/Clutter is experimental; do not enable this for distribution packages!])
+ need_clutter=yes
+ fi
+fi
+
+AM_CONDITIONAL([ENABLE_AISLERIOT_CLUTTER],[test "$enable_aisleriot_clutter" = "yes"])
+
case "$with_platform" in
gnome|gtk-only) ;;
hildon) if test "$need_clutter" = "yes"; then
- AC_MSG_ERROR([Clutter is not supported on hildon; disable Gnometris, Lights Off])
+ AC_MSG_ERROR([Clutter is not supported on hildon; disable Gnometris, Lights Off and Aisleriot/Clutter])
fi
;;
esac
diff --git a/po/POTFILES.in b/po/POTFILES.in
index f1e99c9..35f8dc5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,7 +2,9 @@
# Please keep this file in alphabetical order.
[encoding: UTF-8]
aisleriot/aisleriot.schemas.in
+aisleriot/ar-clutter-embed.c
aisleriot/ar-game-chooser.c
+aisleriot/board.c
aisleriot/board-noclutter.c
aisleriot/conf.c
aisleriot/freecell.desktop.in.in
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]