--- /dev/null
+/**
+ * Sandcastle - Falling sand game for Maemo 5
+ * Copyright (c) 2009 Thomas Thurman <thomas.thurman@collabora.co.uk>
+ * This is a demonstration of the accelerometer.
+ * It's only a few hours' work and could stand to be improved in
+ * several ways:
+ * - use a sorted list of grains to speed up sand processing
+ * - add different colours of sand?
+ * - add various other stuff like plants, water, explosives...
+ * (as in the web game "Hell of Sand")
+ *
+ * I also haven't tracked down why it crashes sometimes when
+ * you run it from the launcher. Run it from a terminal as
+ * /usr/bin/sandcastle to get around this.
+ */
+
+/**
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <mce/mode-names.h>
+#include <mce/dbus-names.h>
+#include <hildon/hildon.h>
+#include <dbus/dbus-glib.h>
+
+GdkPixbuf *pixbuf;
+GtkWidget *window, *area;
+int width, height, rowstride;
+int minx, maxx, miny, maxy;
+DBusGProxy *dbus_proxy = NULL;
+DBusGProxyCall *dbus_call = NULL;
+guint32 *pixels = NULL;
+GList *grains = NULL;
+
+const guint32 SAND = 0xff00ffff;
+const guint32 WALL = 0xffffffff;
+const guint32 EMPTY = 0xff000000;
+
+enum {
+ UP = 0,
+ UP_RIGHT = 1,
+ RIGHT = 2,
+ DOWN_RIGHT = 3,
+ DOWN = 4,
+ DOWN_LEFT = 5,
+ LEFT = 6,
+ UP_LEFT = 7
+};
+
+int gravity = DOWN;
+int there_is_gravity = TRUE;
+
+static inline guint32*
+translate_pointer_by_direction(guint32 *src, int direction, int x, int y)
+{
+ static guint32 edge = WALL;
+
+ switch (direction%8) {
+ case UP:
+ if (y<=0) return &edge;
+ return src-rowstride;
+ case UP_RIGHT:
+ if (y<=0 || x>=width) return &edge;
+ return (src-rowstride)+1;
+ case RIGHT:
+ if (x>=width) return &edge;
+ return src+1;
+ case DOWN_RIGHT:
+ if (x>=width || y>=height) return &edge;
+ return (src+rowstride)+1;
+ case DOWN:
+ if (y>=height) return &edge;
+ return src+rowstride;
+ case DOWN_LEFT:
+ if (x<=0 || y>=height) return &edge;
+ return (src+rowstride)-1;
+ case LEFT:
+ if (x<=0) return &edge;
+ return src-1;
+ case UP_LEFT:
+ if (x<=0 || y<=0) return &edge;
+ return (src-rowstride)-1;
+ }
+}
+
+static gint
+expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data)
+{
+ gdk_draw_pixbuf (GDK_DRAWABLE(widget->window),
+ widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
+ pixbuf, event->area.x, event->area.y,
+ event->area.x, event->area.y,
+ event->area.width, event->area.height,
+ GDK_RGB_DITHER_NONE, 0, 0);
+ return TRUE;
+}
+
+static gboolean
+tap_event (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer user_data)
+{
+ int rx, ry;
+
+ for (rx=-5; rx<=5; rx++)
+ for (ry=-5; ry<=5; ry++)
+ {
+ guint32 *p = pixels + (ry+(int)event->y)*rowstride + (rx+(int)event->x);
+
+ if (*p==EMPTY) *p=SAND;
+ }
+
+ gtk_widget_queue_draw_area (area, ((int)event->x)-5, ((int)event->y)-5, 10, 10);
+ minx=miny=0;
+ maxx=width; maxy=height;
+}
+
+static void
+orientation_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *data)
+{
+ gchar *s1, *s2, *s3;
+ gint x, y, z;
+ gchar facing = 0;
+
+ if (dbus_g_proxy_end_call (proxy, call, NULL,
+ G_TYPE_STRING, &s1,
+ G_TYPE_STRING, &s2,
+ G_TYPE_STRING, &s3,
+ G_TYPE_INT, &x,
+ G_TYPE_INT, &y,
+ G_TYPE_INT, &z,
+ G_TYPE_INVALID)) {
+
+ if (x<=-500) facing |= 0x10;
+ else if (x<=0) facing |= 0x20;
+ else if (x<=500) facing |= 0x30;
+ else facing |= 0x40;
+
+ if (y<=-500) facing |= 0x1;
+ else if (y<=0) facing |= 0x2;
+ else if (y<=500) facing |= 0x3;
+ else facing |= 0x4;
+
+ g_free(s1); g_free(s2); g_free(s3);
+
+ there_is_gravity = TRUE;
+
+ switch (facing) {
+ case 0x34: case 0x36: case 0x24: gravity=UP; break;
+ case 0x13: case 0x14: gravity=UP_RIGHT; break;
+ case 0x12: gravity=RIGHT; break;
+ case 0x11: gravity=DOWN_RIGHT; break;
+ case 0x21: case 0x31: gravity=DOWN; break;
+ case 0x41: gravity=DOWN_LEFT; break;
+ case 0x42: case 0x43: gravity=LEFT; break;
+ case 0x44: gravity=UP_LEFT; break;
+ default:
+ there_is_gravity=FALSE;
+ }
+
+ } else {
+ g_warning ("Couldn't end the call!\n");
+ }
+ dbus_call = NULL;
+}
+
+gint
+sand_sort (gpointer sand1, gpointer sand2)
+{
+ if (sand1<sand2)
+ return -1;
+ else if (sand1==sand1)
+ return 0;
+ else
+ return 1;
+}
+
+static gboolean
+update_screen (gpointer data)
+{
+ int x, y;
+ int newminx=width, newminy=height, newmaxx=0, newmaxy=0;
+
+ int xstart, xend, xdelta, ystart, yend, ydelta;
+
+ if (!dbus_call)
+ dbus_call = dbus_g_proxy_begin_call (
+ dbus_proxy,
+ "get_device_orientation",
+ orientation_callback,
+ NULL, NULL,
+ G_TYPE_INVALID);
+
+ if (!there_is_gravity) return TRUE;
+
+ switch(gravity)
+ {
+ case UP:
+ case UP_RIGHT:
+ case DOWN_RIGHT:
+ case DOWN:
+ case RIGHT:
+ xstart=maxx; xend=minx; xdelta=-1;
+ break;
+ case DOWN_LEFT:
+ case LEFT:
+ case UP_LEFT:
+ xstart=minx; xend=maxx; xdelta=1;
+ break;
+
+ }
+
+ switch(gravity)
+ {
+ case UP:
+ case UP_LEFT:
+ case UP_RIGHT:
+ case LEFT:
+ ystart=miny; yend=maxy; ydelta=1;
+ break;
+
+ case DOWN:
+ case DOWN_LEFT:
+ case DOWN_RIGHT:
+ case RIGHT:
+ ystart=maxy; yend=miny; ydelta=-1;
+ break;
+
+ }
+
+ g_assert( (xstart-xend)*xdelta < 0);
+ g_assert( (ystart-yend)*ydelta < 0);
+
+ for (y=ystart; y!=yend; y+=ydelta) {
+
+ guint32 *p = pixels + y*rowstride + xstart;
+
+ for (x=xstart; x!=xend; x+=xdelta) {
+
+ if (*p==SAND) {
+ const int directions[] = {0, 1, -1};
+ int i;
+ for (i=0; i<3; i++) {
+ guint32 *below = translate_pointer_by_direction(p, gravity+directions[i], x, y);
+ if (*below==EMPTY) {
+ *below=SAND;
+ *p=EMPTY;
+ break;
+ }
+ }
+
+ if (x<newminx) newminx=x;
+ if (x>newmaxx) newmaxx=x;
+ if (y<newminy) newminy=y;
+ if (y>newmaxy) newmaxy=y;
+ }
+ p += xdelta;
+ }
+ }
+
+ minx=newminx; if (minx>2) minx--; else minx=0;
+ maxx=newmaxx; if (maxx<width-2) maxx++; else maxx=width;
+ miny=newminy; if (miny>2) miny--; else miny=0;
+ maxy=newmaxy; if (maxy<height-2) maxy++; else maxy=height-2;
+
+ gtk_widget_queue_draw_area (area, minx, miny, maxx-minx, maxy-miny);
+
+ return TRUE;
+}
+
+void
+count_grains (void)
+{
+ int x, y;
+
+ for (y=0; y<=height; y++) {
+
+ guint32 *p = pixels + y*rowstride;
+
+ for (x=0; x<width; x++) {
+ grains = g_list_append(grains, p+x);
+ }
+ }
+
+ g_warning ("There are %d grains.\n", g_list_length (grains));
+}
+
+int
+main(int argc, char **argv)
+{
+ hildon_gtk_init (&argc, &argv);
+
+ dbus_proxy = dbus_g_proxy_new_for_name_owner (
+ dbus_g_bus_get (DBUS_BUS_SYSTEM, NULL),
+ "com.nokia.mce",
+ "/com/nokia/mce/request",
+ "com.nokia.mce.request", NULL);
+
+ g_assert (dbus_proxy);
+
+ pixbuf = gdk_pixbuf_new_from_file ("/usr/share/sandcastle/sandcastle.png", NULL);
+ pixels = (guint32*) gdk_pixbuf_get_pixels (pixbuf);
+ minx = miny = 0;
+ maxx = width = gdk_pixbuf_get_width (pixbuf);
+ maxy = height = gdk_pixbuf_get_height (pixbuf);
+ maxx-=2; maxy-=2;
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf)/4;
+
+ count_grains ();
+
+ window = hildon_stackable_window_new ();
+ gtk_window_set_title (GTK_WINDOW (window), "Sandcastle");
+
+ area = gtk_drawing_area_new ();
+ gtk_widget_add_events (area, GDK_BUTTON_MOTION_MASK|GDK_BUTTON_PRESS_MASK);
+
+ gtk_container_add (GTK_CONTAINER (window), area);
+ g_signal_connect (G_OBJECT (area), "expose_event", G_CALLBACK (expose_event), NULL);
+ g_signal_connect (G_OBJECT (area), "motion-notify-event", G_CALLBACK (tap_event), NULL);
+ g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_widget_show_all (window);
+
+ g_timeout_add (20, update_screen, NULL);
+
+ gtk_main ();
+
+ return 0;
+}
+
--- /dev/null
+/* XPM */
+static char * rfk_xpm[] = {
+"32 26 3 1",
+" c None",
+". c #000000",
+"+ c #FFFF00",
+"................................",
+"................................",
+"................................",
+"................................",
+"...++.++................++.++...",
+"...++.++................++.++...",
+"...+++++................+++++...",
+"...+++++................+++++...",
+"...+++++..++..++..++..+++++++...",
+"...+++++..++..++..++..+++++++...",
+"...++++++++++++++++++++++++++...",
+"...++++++++++++++++++++++++++...",
+"...++++++++++++++++++++++++++...",
+"...+++++++.++++++++++.+++++++...",
+"...++++++...++++++++...++++++...",
+"...+++++++.++++++++++.+++++++...",
+"...++++++++++++++++++++++++++...",
+"...+++++++++++++.++++++++++++...",
+"...++++++++++++...+++++++++++...",
+"...+++++++++++.....++++++++++...",
+"...+++++++++++.....++++++++++...",
+"...++++++++++.......+++++++++...",
+"...++++++++++.......+++++++++...",
+"...++++++++++.......+++++++++...",
+"...++++++++++.......+++++++++...",
+"...++++++++++.......+++++++++..."};