[clutter] pan-action: add PanAction, to handle panning in scrollable actors



commit 9ca06d2895154eb2c985b85df186db3ade1a5e1e
Author: Emanuele Aina <emanuele aina collabora com>
Date:   Sat Aug 25 16:23:23 2012 +0200

    pan-action: add PanAction, to handle panning in scrollable actors
    
    PanAction is a GestureAction-subclass that implements the panning
    concept for scrollable actors, with the ability to emit interpolated
    signals to emulate the kinetic inertia of the panning. PanAction provides:
    
    â pan signal, notifying users of the panning gesture status;
    
    â pan-stopped signal, emitted at the end of the interpolated phase
      of the panning gesture, if enabled;
    
    â pan-axis property, to allow constraining the dragging to a specific
      axis;
    
    â interpolated property, to enable or disable the inertial behaviour;
    
    â deceleration property, to customize the rate at which the momentum
      of the panning will be slowed down;
    
    â acceleration-factor property, applied to the inertial momentum when
      starting the interpolated sequence.
    
    An interactive test is also provided.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=681648

 clutter/Makefile.am                        |    2 +
 clutter/clutter-enums.h                    |   19 +
 clutter/clutter-marshal.list               |    1 +
 clutter/clutter-pan-action.c               |  814 ++++++++++++++++++++++++++++
 clutter/clutter-pan-action.h               |  137 +++++
 clutter/clutter.h                          |    1 +
 clutter/clutter.symbols                    |   13 +
 doc/reference/clutter/clutter-docs.xml.in  |    1 +
 doc/reference/clutter/clutter-sections.txt |   29 +
 doc/reference/clutter/clutter.types        |    1 +
 examples/Makefile.am                       |    1 +
 examples/pan-action.c                      |  127 +++++
 12 files changed, 1146 insertions(+), 0 deletions(-)
---
diff --git a/clutter/Makefile.am b/clutter/Makefile.am
index 7d479b2..100c1f5 100644
--- a/clutter/Makefile.am
+++ b/clutter/Makefile.am
@@ -102,6 +102,7 @@ source_h =					\
 	$(srcdir)/clutter-page-turn-effect.h	\
 	$(srcdir)/clutter-paint-nodes.h		\
 	$(srcdir)/clutter-paint-node.h		\
+	$(srcdir)/clutter-pan-action.h		\
 	$(srcdir)/clutter-path-constraint.h	\
 	$(srcdir)/clutter-path.h		\
 	$(srcdir)/clutter-property-transition.h	\
@@ -182,6 +183,7 @@ source_c = \
 	$(srcdir)/clutter-page-turn-effect.c	\
 	$(srcdir)/clutter-paint-nodes.c		\
 	$(srcdir)/clutter-paint-node.c		\
+	$(srcdir)/clutter-pan-action.c		\
 	$(srcdir)/clutter-path-constraint.c	\
 	$(srcdir)/clutter-path.c		\
 	$(srcdir)/clutter-property-transition.c	\
diff --git a/clutter/clutter-enums.h b/clutter/clutter-enums.h
index 1110eba..7682ff5 100644
--- a/clutter/clutter-enums.h
+++ b/clutter/clutter-enums.h
@@ -1003,6 +1003,25 @@ typedef enum { /*< prefix=CLUTTER_SWIPE_DIRECTION >*/
 } ClutterSwipeDirection;
 
 /**
+ * ClutterPanAxis:
+ * @CLUTTER_PAN_AXIS_NONE: No constraint
+ * @CLUTTER_PAN_X_AXIS: Set a constraint on the X axis
+ * @CLUTTER_PAN_Y_AXIS: Set a constraint on the Y axis
+ *
+ * The axis of the constraint that should be applied on the
+ * panning action
+ *
+ * Since: 1.12
+ */
+typedef enum { /*< prefix=CLUTTER_PAN >*/
+  CLUTTER_PAN_AXIS_NONE = 0,
+
+  CLUTTER_PAN_X_AXIS,
+  CLUTTER_PAN_Y_AXIS
+} ClutterPanAxis;
+
+
+/**
  * ClutterTableAlignment:
  * @CLUTTER_TABLE_ALIGNMENT_START: Align the child to the top or to the
  *   left of a cell in the table, depending on the axis
diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list
index 681b4e7..7eff755 100644
--- a/clutter/clutter-marshal.list
+++ b/clutter/clutter-marshal.list
@@ -1,5 +1,6 @@
 BOOLEAN:BOXED
 BOOLEAN:BOXED,INT,INT
+BOOLEAN:OBJECT,BOOLEAN
 BOOLEAN:OBJECT,BOXED,DOUBLE
 BOOLEAN:OBJECT,DOUBLE
 BOOLEAN:OBJECT,ENUM
diff --git a/clutter/clutter-pan-action.c b/clutter/clutter-pan-action.c
new file mode 100644
index 0000000..30e860e
--- /dev/null
+++ b/clutter/clutter-pan-action.c
@@ -0,0 +1,814 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010  Intel Corporation.
+ * Copyright (C) 2011  Robert Bosch Car Multimedia GmbH.
+ * Copyright (C) 2012  Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emanuele Aina <emanuele aina collabora com>
+ *
+ * Based on ClutterDragAction, ClutterSwipeAction, and MxKineticScrollView,
+ * written by:
+ *   Emmanuele Bassi <ebassi linux intel com>
+ *   Tomeu Vizoso <tomeu vizoso collabora co uk>
+ *   Chris Lord <chris linux intel com>
+ */
+
+/**
+ * SECTION:clutter-pan-action
+ * @Title: ClutterPanAction
+ * @Short_Description: Action for pan gestures
+ *
+ * #ClutterPanAction is a sub-class of #ClutterGestureAction that implements
+ * the logic for recognizing pan gestures.
+ *
+ * The simplest usage of #ClutterPanAction consists in adding it to
+ * a #ClutterActor with a child and setting it as reactive; for instance,
+ * the following code:
+ *
+ * |[
+ *   clutter_actor_add_action (actor, clutter_pan_action_new ());
+ *   clutter_actor_set_reactive (actor, TRUE);
+ * ]|
+ *
+ * will automatically result in the actor children to be moved
+ * when dragging.
+ *
+ * Since: 1.12
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "clutter-pan-action.h"
+
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
+#include "clutter-marshal.h"
+#include "clutter-private.h"
+#include <math.h>
+
+#define FLOAT_EPSILON   (1e-15)
+
+static const gfloat min_velocity = 0.1f; // measured in px/ms
+static const gfloat reference_fps = 60.0f; // the fps assumed for the deceleration rate
+static const gfloat default_deceleration_rate = 0.95f;
+static const gfloat default_acceleration_factor = 1.0f;
+
+typedef enum
+{
+  PAN_STATE_INACTIVE,
+  PAN_STATE_PANNING,
+  PAN_STATE_INTERPOLATING
+} PanState;
+
+struct _ClutterPanActionPrivate
+{
+  ClutterPanAxis pan_axis;
+
+  PanState state;
+
+  /* Variables for storing acceleration information */
+  ClutterTimeline *deceleration_timeline;
+  gfloat target_x;
+  gfloat target_y;
+  gfloat dx;
+  gfloat dy;
+  gdouble deceleration_rate;
+  gdouble acceleration_factor;
+
+  /* Inertial motion tracking */
+  gfloat interpolated_x;
+  gfloat interpolated_y;
+  gfloat release_x;
+  gfloat release_y;
+
+  guint should_interpolate : 1;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_PAN_AXIS,
+  PROP_INTERPOLATE,
+  PROP_DECELERATION,
+  PROP_ACCELERATION_FACTOR,
+
+  PROP_LAST
+};
+
+static GParamSpec *pan_props[PROP_LAST] = { NULL, };
+
+enum
+{
+  PAN,
+  PAN_STOPPED,
+
+  LAST_SIGNAL
+};
+
+static guint pan_signals[LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (ClutterPanAction, clutter_pan_action,
+               CLUTTER_TYPE_GESTURE_ACTION);
+
+static void
+emit_pan (ClutterPanAction *self,
+          ClutterActor     *actor,
+          gboolean          is_interpolated)
+{
+  gboolean retval;
+  g_signal_emit (self, pan_signals[PAN], 0, actor, is_interpolated, &retval);
+}
+
+static void
+emit_pan_stopped (ClutterPanAction *self,
+                  ClutterActor     *actor)
+{
+  ClutterPanActionPrivate *priv = self->priv;
+
+  g_signal_emit (self, pan_signals[PAN_STOPPED], 0, actor);
+  priv->state = PAN_STATE_INACTIVE;
+}
+
+static void
+on_deceleration_stopped (ClutterTimeline           *timeline,
+                         gboolean                   is_finished,
+                         ClutterPanAction *self)
+{
+  ClutterPanActionPrivate *priv = self->priv;
+  ClutterActor *actor;
+
+  g_object_unref (timeline);
+  priv->deceleration_timeline = NULL;
+
+  actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
+  emit_pan_stopped (self, actor);
+}
+
+static void
+on_deceleration_new_frame (ClutterTimeline     *timeline,
+                           gint                 elapsed_time,
+                           ClutterPanAction    *self)
+{
+  ClutterPanActionPrivate *priv = self->priv;
+  ClutterActor *actor;
+  gdouble progress;
+  gfloat interpolated_x, interpolated_y;
+
+  progress = clutter_timeline_get_progress (timeline);
+
+  interpolated_x = priv->target_x * progress;
+  interpolated_y = priv->target_y * progress;
+  priv->dx = interpolated_x - priv->interpolated_x;
+  priv->dy = interpolated_y - priv->interpolated_y;
+  priv->interpolated_x = interpolated_x;
+  priv->interpolated_y = interpolated_y;
+
+  actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
+  emit_pan (self, actor, TRUE);
+}
+
+static gboolean
+gesture_begin (ClutterGestureAction  *gesture,
+               ClutterActor          *actor)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
+  ClutterPanActionPrivate *priv = self->priv;
+
+  if (priv->state == PAN_STATE_INTERPOLATING && priv->deceleration_timeline)
+    clutter_timeline_stop (priv->deceleration_timeline);
+
+  priv->state = PAN_STATE_PANNING;
+  priv->interpolated_x = priv->interpolated_y = 0.0f;
+  priv->dx = priv->dy = 0.0f;
+
+  return TRUE;
+}
+
+static gboolean
+gesture_progress (ClutterGestureAction *gesture,
+                  ClutterActor         *actor)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
+
+  emit_pan (self, actor, FALSE);
+
+  return TRUE;
+}
+
+static void
+gesture_cancel (ClutterGestureAction *gesture,
+                ClutterActor         *actor)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
+  ClutterPanActionPrivate *priv = self->priv;
+
+  priv->state = PAN_STATE_INACTIVE;
+}
+
+static void
+gesture_end (ClutterGestureAction *gesture,
+             ClutterActor         *actor)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
+  ClutterPanActionPrivate *priv = self->priv;
+  gfloat velocity, velocity_x, velocity_y;
+  gfloat delta_x, delta_y;
+  gfloat tau;
+  gint duration;
+
+  clutter_gesture_action_get_release_coords (CLUTTER_GESTURE_ACTION (self), 0, &priv->release_x, &priv->release_y);
+
+  if (!priv->should_interpolate)
+    {
+      priv->state = PAN_STATE_INACTIVE;
+      return;
+    }
+
+  priv->state = PAN_STATE_INTERPOLATING;
+
+  clutter_gesture_action_get_motion_delta (gesture, 0, &delta_x, &delta_y);
+  velocity = clutter_gesture_action_get_velocity (gesture, 0, &velocity_x, &velocity_y);
+
+  /* Exponential timing constant v(t) = v(0) * exp(-t/tau)
+   * tau = 1000ms / (frame_per_second * - ln(decay_per_frame))
+   * with frame_per_second = 60 and decay_per_frame = 0.95, tau ~= 325ms
+   * see http://ariya.ofilabs.com/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration.html */
+  tau = 1000.0f / (reference_fps * - logf (priv->deceleration_rate));
+
+  /* See where the decreasing velocity reaches $min_velocity px/ms
+   * v(t) = v(0) * exp(-t/tau) = min_velocity
+   * t = - tau * ln( min_velocity / |v(0)|) */
+  duration = - tau * logf (min_velocity / (ABS (velocity) * priv->acceleration_factor));
+
+  /* Target point: x(t) = v(0) * tau * [1 - exp(-t/tau)] */
+  priv->target_x = velocity_x * priv->acceleration_factor * tau * (1 - exp ((float)-duration / tau));
+  priv->target_y = velocity_y * priv->acceleration_factor * tau * (1 - exp ((float)-duration / tau));
+
+  if (ABS (velocity) * priv->acceleration_factor > min_velocity && duration > FLOAT_EPSILON)
+    {
+      priv->interpolated_x = priv->interpolated_y = 0.0f;
+      priv->deceleration_timeline = clutter_timeline_new (duration);
+      clutter_timeline_set_progress_mode (priv->deceleration_timeline, CLUTTER_EASE_OUT_EXPO);
+
+      g_signal_connect (priv->deceleration_timeline, "new_frame",
+                        G_CALLBACK (on_deceleration_new_frame), self);
+      g_signal_connect (priv->deceleration_timeline, "stopped",
+                        G_CALLBACK (on_deceleration_stopped), self);
+      clutter_timeline_start (priv->deceleration_timeline);
+    }
+  else
+    {
+      emit_pan_stopped (self, actor);
+    }
+}
+
+static gboolean
+clutter_pan_action_real_pan (ClutterPanAction *self,
+                             ClutterActor     *actor,
+                             gboolean          is_interpolated)
+{
+  ClutterPanActionPrivate *priv = self->priv;
+  gfloat dx, dy;
+  ClutterMatrix transform;
+
+  if (is_interpolated)
+    clutter_pan_action_get_interpolated_delta (self, &dx, &dy);
+  else
+    clutter_gesture_action_get_motion_delta (CLUTTER_GESTURE_ACTION (self), 0, &dx, &dy);
+
+  switch (priv->pan_axis)
+    {
+    case CLUTTER_PAN_AXIS_NONE:
+      break;
+
+    case CLUTTER_PAN_X_AXIS:
+      dy = 0.0f;
+      break;
+
+    case CLUTTER_PAN_Y_AXIS:
+      dx = 0.0f;
+      break;
+    }
+
+  clutter_actor_get_child_transform (actor, &transform);
+  cogl_matrix_translate (&transform, dx, dy, 0.0f);
+  clutter_actor_set_child_transform (actor, &transform);
+  return TRUE;
+}
+
+static void
+clutter_pan_action_set_property (GObject      *gobject,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_PAN_AXIS:
+      clutter_pan_action_set_pan_axis (self, g_value_get_enum (value));
+      break;
+
+    case PROP_INTERPOLATE :
+      clutter_pan_action_set_interpolate (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_DECELERATION :
+      clutter_pan_action_set_deceleration (self, g_value_get_double (value));
+      break;
+
+    case PROP_ACCELERATION_FACTOR :
+      clutter_pan_action_set_acceleration_factor (self, g_value_get_double (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+clutter_pan_action_get_property (GObject    *gobject,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (gobject);
+  ClutterPanActionPrivate *priv = self->priv;
+
+  switch (prop_id)
+    {
+    case PROP_PAN_AXIS:
+      g_value_set_enum (value, priv->pan_axis);
+      break;
+
+    case PROP_INTERPOLATE :
+      g_value_set_boolean (value, priv->should_interpolate);
+      break;
+
+    case PROP_DECELERATION :
+      g_value_set_double (value, priv->deceleration_rate);
+      break;
+
+    case PROP_ACCELERATION_FACTOR :
+      g_value_set_double (value, priv->acceleration_factor);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+clutter_pan_action_dispose (GObject *gobject)
+{
+  ClutterPanActionPrivate *priv = CLUTTER_PAN_ACTION (gobject)->priv;
+
+  g_clear_object (&priv->deceleration_timeline);
+
+  G_OBJECT_CLASS (clutter_pan_action_parent_class)->dispose (gobject);
+}
+
+static void
+clutter_pan_action_set_actor (ClutterActorMeta *meta,
+                              ClutterActor     *actor)
+{
+  ClutterPanAction *self = CLUTTER_PAN_ACTION (meta);
+  ClutterPanActionPrivate *priv = self->priv;
+  ClutterActor *old_actor;
+
+  old_actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
+  if (old_actor != actor)
+    {
+      /* make sure we reset the state */
+      if (priv->state == PAN_STATE_INTERPOLATING)
+        g_clear_object (&priv->deceleration_timeline);
+    }
+
+  CLUTTER_ACTOR_META_CLASS (clutter_pan_action_parent_class)->set_actor (meta, actor);
+}
+
+
+static void
+clutter_pan_action_class_init (ClutterPanActionClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+  ClutterGestureActionClass *gesture_class =
+      CLUTTER_GESTURE_ACTION_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (ClutterPanActionPrivate));
+
+  klass->pan = clutter_pan_action_real_pan;
+
+  gesture_class->gesture_begin = gesture_begin;
+  gesture_class->gesture_progress = gesture_progress;
+  gesture_class->gesture_cancel = gesture_cancel;
+  gesture_class->gesture_end = gesture_end;
+
+  meta_class->set_actor = clutter_pan_action_set_actor;
+
+  /**
+   * ClutterPanAction:pan-axis:
+   *
+   * Constraints the panning action to the specified axis
+   *
+   * Since: 1.12
+   */
+  pan_props[PROP_PAN_AXIS] =
+    g_param_spec_enum ("pan-axis",
+                       P_("Pan Axis"),
+                       P_("Constraints the panning to an axis"),
+                       CLUTTER_TYPE_PAN_AXIS,
+                       CLUTTER_PAN_AXIS_NONE,
+                       CLUTTER_PARAM_READWRITE);
+
+  /**
+   * ClutterPanAction:interpolate:
+   *
+   * Whether interpolated events emission is enabled.
+   *
+   * Since: 1.12
+   */
+  pan_props[PROP_INTERPOLATE] =
+    g_param_spec_boolean ("interpolate",
+                          P_("Interpolate"),
+                          P_("Whether interpolated events emission is enabled."),
+                          FALSE,
+                          CLUTTER_PARAM_READWRITE);
+
+  /**
+   * ClutterPanAction:deceleration:
+   *
+   * The rate at which the interpolated panning will decelerate in
+   *
+   * #ClutterPanAction will emit interpolated ::pan events with decreasing
+   * scroll deltas, using the rate specified by this property.
+   *
+   * Since: 1.12
+   */
+  pan_props[PROP_DECELERATION] =
+    g_param_spec_double ("deceleration",
+                         P_("Deceleration"),
+                         P_("Rate at which the interpolated panning will decelerate in"),
+                         FLOAT_EPSILON, 1.0, default_deceleration_rate,
+                         CLUTTER_PARAM_READWRITE);
+
+  /**
+   * ClutterPanAction:acceleration-factor:
+   *
+   * The initial acceleration factor
+   *
+   * The kinetic momentum measured at the time of releasing the pointer will
+   * be multiplied by the factor specified by this property before being used
+   * to generate interpolated ::pan events.
+   *
+   * Since: 1.12
+   */
+  pan_props[PROP_ACCELERATION_FACTOR] =
+    g_param_spec_double ("acceleration-factor",
+                         P_("Initial acceleration factor"),
+                         P_("Factor applied to the momentum when starting the interpolated phase"),
+                         1.0, G_MAXDOUBLE, default_acceleration_factor,
+                         CLUTTER_PARAM_READWRITE);
+
+  gobject_class->set_property = clutter_pan_action_set_property;
+  gobject_class->get_property = clutter_pan_action_get_property;
+  gobject_class->dispose = clutter_pan_action_dispose;
+  g_object_class_install_properties  (gobject_class,
+                                      PROP_LAST,
+                                      pan_props);
+
+  /**
+   * ClutterPanAction:pan:
+   * @action: the #ClutterPanAction that emitted the signal
+   * @actor: the #ClutterActor attached to the @action
+   * @is_interpolated: if the event is the result of interpolating
+   *                   the motion velocity at the end of the drag
+   *
+   * The ::pan signal is emitted to keep track of the motion during
+   * a pan gesture. @is_interpolated is set to %TRUE during the
+   * interpolation phase of the pan, after the drag has ended and
+   * the :interpolate property was set to %TRUE.
+   *
+   * Return value: TRUE if the pan action has been handled by one of
+   * the listener or %FALSE to continue the emission.
+   *
+   * Since: 1.12
+   */
+  pan_signals[PAN] =
+    g_signal_new (I_("pan"),
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterPanActionClass, pan),
+                  _clutter_boolean_continue_accumulator, NULL,
+                  _clutter_marshal_BOOLEAN__OBJECT_BOOLEAN,
+                  G_TYPE_BOOLEAN, 2,
+                  CLUTTER_TYPE_ACTOR,
+                  G_TYPE_BOOLEAN);
+
+  /**
+   * ClutterPanAction:pan-stopped:
+   * @action: the #ClutterPanAction that emitted the signal
+   * @actor: the #ClutterActor attached to the @action
+   *
+   * The ::pan-stopped signal is emitted at the end of the interpolation
+   * phase of the pan action, only when :interpolate is set to %TRUE.
+   *
+   * Since: 1.12
+   */
+  pan_signals[PAN_STOPPED] =
+    g_signal_new (I_("pan-stopped"),
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterPanActionClass, pan_stopped),
+                  NULL, NULL,
+                  _clutter_marshal_VOID__OBJECT,
+                  G_TYPE_NONE, 1,
+                  CLUTTER_TYPE_ACTOR);
+}
+
+static void
+clutter_pan_action_init (ClutterPanAction *self)
+{
+  ClutterPanActionPrivate *priv = self->priv =
+    G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_PAN_ACTION,
+                                 ClutterPanActionPrivate);
+  priv->deceleration_rate = default_deceleration_rate;
+  priv->acceleration_factor = default_acceleration_factor;
+  priv->state = PAN_STATE_INACTIVE;
+}
+
+/**
+ * clutter_pan_action_new:
+ *
+ * Creates a new #ClutterPanAction instance
+ *
+ * Return value: the newly created #ClutterPanAction
+ *
+ * Since: 1.12
+ */
+ClutterAction *
+clutter_pan_action_new (void)
+{
+  return g_object_new (CLUTTER_TYPE_PAN_ACTION, NULL);
+}
+
+/**
+ * clutter_pan_action_set_pan_axis:
+ * @self: a #ClutterPanAction
+ * @axis: the axis to constraint the panning to
+ *
+ * Restricts the panning action to a specific axis
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_set_pan_axis (ClutterPanAction *self,
+                                 ClutterPanAxis    axis)
+{
+  ClutterPanActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+  g_return_if_fail (axis >= CLUTTER_PAN_AXIS_NONE &&
+                    axis <= CLUTTER_PAN_Y_AXIS);
+
+  priv = self->priv;
+
+  if (priv->pan_axis == axis)
+    return;
+
+  priv->pan_axis = axis;
+
+  g_object_notify_by_pspec (G_OBJECT (self), pan_props[PROP_PAN_AXIS]);
+}
+
+/**
+ * clutter_pan_action_get_pan_axis:
+ * @self: a #ClutterPanAction
+ *
+ * Retrieves the axis constraint set by clutter_pan_action_set_pan_axis()
+ *
+ * Return value: the axis constraint
+ *
+ * Since: 1.12
+ */
+ClutterPanAxis
+clutter_pan_action_get_pan_axis (ClutterPanAction *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self),
+                        CLUTTER_PAN_AXIS_NONE);
+
+  return self->priv->pan_axis;
+}
+
+/**
+ * clutter_pan_action_set_interpolate:
+ * @self: a #ClutterPanAction
+ * @should_interpolate: whether to enable interpolated pan events
+ *
+ * Sets whether the action should emit interpolated ::pan events
+ * after the drag has ended, to emulate the gesture kinetic inertia.
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_set_interpolate (ClutterPanAction *self,
+                                    gboolean          should_interpolate)
+{
+  ClutterPanActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+
+  priv = self->priv;
+
+  should_interpolate = !!should_interpolate;
+
+  if (priv->should_interpolate == should_interpolate)
+    return;
+
+  priv->should_interpolate = should_interpolate;
+
+  g_object_notify_by_pspec (G_OBJECT (self), pan_props[PROP_INTERPOLATE]);
+}
+
+/**
+ * clutter_pan_action_get_interpolate:
+ * @self: a #ClutterPanAction
+ *
+ * Checks if the action should emit ::pan events even after releasing
+ * the pointer during a panning gesture, to emulate some kind of
+ * kinetic inertia.
+ *
+ * Return value: %TRUE if interpolated events emission is active.
+ *
+ * Since: 1.12
+ */
+gboolean
+clutter_pan_action_get_interpolate (ClutterPanAction *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self),
+                        FALSE);
+
+  return self->priv->should_interpolate;
+}
+
+/**
+ * clutter_pan_action_set_deceleration:
+ * @self: A #ClutterPanAction
+ * @rate: The deceleration rate
+ *
+ * Sets the deceleration rate of the interpolated ::pan events generated
+ * after a pan gesture. This is approximately the value that the momentum
+ * at the time of releasing the pointer is divided by every 60th of a second.
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_set_deceleration (ClutterPanAction *self,
+                                     gdouble           rate)
+{
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+  g_return_if_fail (rate <= 1.0);
+  g_return_if_fail (rate > 0.0);
+
+  self->priv->deceleration_rate = rate;
+  g_object_notify_by_pspec (G_OBJECT (self), pan_props[PROP_DECELERATION]);
+}
+
+/**
+ * clutter_pan_action_get_deceleration:
+ * @self: A #ClutterPanAction
+ *
+ * Retrieves the deceleration rate of interpolated ::pan events.
+ *
+ * Return value: The deceleration rate of the interpolated events.
+ *
+ * Since: 1.12
+ */
+gdouble
+clutter_pan_action_get_deceleration (ClutterPanAction *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 0.95);
+  return self->priv->deceleration_rate;
+}
+
+/**
+ * clutter_pan_action_set_acceleration_factor:
+ * @self: A #ClutterPanAction
+ * @factor: The acceleration factor
+ *
+ * Factor applied to the momentum velocity at the time of releasing the
+ * pointer when generating interpolated ::pan events.
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_set_acceleration_factor (ClutterPanAction *self,
+                                            gdouble           factor)
+{
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+  g_return_if_fail (factor >= 0.0);
+
+  self->priv->acceleration_factor = factor;
+  g_object_notify_by_pspec (G_OBJECT (self), pan_props[PROP_ACCELERATION_FACTOR]);
+}
+
+/**
+ * clutter_pan_action_get_acceleration_factor:
+ * @self: A #ClutterPanAction
+ *
+ * Retrieves the initial acceleration factor for interpolated ::pan events.
+ *
+ * Return value: The initial acceleration factor for interpolated events.
+ *
+ * Since: 1.12
+ */
+gdouble
+clutter_pan_action_get_acceleration_factor (ClutterPanAction *self)
+{
+  g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 1.0);
+  return self->priv->acceleration_factor;
+}
+
+/**
+ * clutter_pan_action_get_interpolated_coords:
+ * @self: A #ClutterPanAction
+ * @interpolated_x: (out) (allow-none): return location for the latest
+ *   interpolated event's X coordinate
+ * @interpolated_y: (out) (allow-none): return location for the latest
+ *   interpolated event's Y coordinate
+ *
+ * Retrieves the coordinates, in stage space, of the latest interpolated
+ * event, analogous to clutter_gesture_action_get_motion_coords().
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_get_interpolated_coords (ClutterPanAction *self,
+                                            gfloat           *interpolated_x,
+                                            gfloat           *interpolated_y)
+{
+  ClutterPanActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+
+  priv = self->priv;
+
+  if (interpolated_x)
+    *interpolated_x = priv->release_x + priv->interpolated_x;
+
+  if (interpolated_y)
+    *interpolated_y = priv->release_y + priv->interpolated_y;
+}
+
+/**
+ * clutter_pan_action_get_interpolated_delta:
+ * @self: A #ClutterPanAction
+ * @delta_x: (out) (allow-none): return location for the X delta since
+ *   the latest interpolated event
+ * @delta_y: (out) (allow-none): return location for the Y delta since
+ *   the latest interpolated event
+ *
+ * Retrieves the delta, in stage space, since the latest interpolated
+ * event, analogous to clutter_gesture_action_get_motion_delta().
+ *
+ * Since: 1.12
+ */
+void
+clutter_pan_action_get_interpolated_delta (ClutterPanAction *self,
+                                           gfloat           *delta_x,
+                                           gfloat           *delta_y)
+{
+  ClutterPanActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
+
+  priv = self->priv;
+
+  if (delta_x)
+    *delta_x = priv->dx;
+
+  if (delta_y)
+    *delta_y = priv->dy;
+}
diff --git a/clutter/clutter-pan-action.h b/clutter/clutter-pan-action.h
new file mode 100644
index 0000000..10ed676
--- /dev/null
+++ b/clutter/clutter-pan-action.h
@@ -0,0 +1,137 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010  Intel Corporation.
+ * Copyright (C) 2011  Robert Bosch Car Multimedia GmbH.
+ * Copyright (C) 2012  Collabora Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author:
+ *   Emanuele Aina <emanuele aina collabora com>
+ *
+ * Based on ClutterDragAction, ClutterSwipeAction, and MxKineticScrollView,
+ * written by:
+ *   Emmanuele Bassi <ebassi linux intel com>
+ *   Tomeu Vizoso <tomeu vizoso collabora co uk>
+ *   Chris Lord <chris linux intel com>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_PAN_ACTION_H__
+#define __CLUTTER_PAN_ACTION_H__
+
+#include <clutter/clutter-gesture-action.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_PAN_ACTION               (clutter_pan_action_get_type ())
+#define CLUTTER_PAN_ACTION(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_PAN_ACTION, ClutterPanAction))
+#define CLUTTER_IS_PAN_ACTION(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_PAN_ACTION))
+#define CLUTTER_PAN_ACTION_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_PAN_ACTION, ClutterPanActionClass))
+#define CLUTTER_IS_PAN_ACTION_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_PAN_ACTION))
+#define CLUTTER_PAN_ACTION_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_PAN_ACTION, ClutterPanActionClass))
+
+typedef struct _ClutterPanAction              ClutterPanAction;
+typedef struct _ClutterPanActionPrivate       ClutterPanActionPrivate;
+typedef struct _ClutterPanActionClass         ClutterPanActionClass;
+
+/**
+ * ClutterPanAction:
+ *
+ * The <structname>ClutterPanAction</structname> structure contains
+ * only private data and should be accessed using the provided API
+ *
+ * Since: 1.12
+ */
+struct _ClutterPanAction
+{
+  /*< private >*/
+  ClutterGestureAction parent_instance;
+
+  ClutterPanActionPrivate *priv;
+};
+
+/**
+ * ClutterPanActionClass:
+ * @pan: class handler for the #ClutterPanAction::pan signal
+ * @pan_stopped: class handler for the #ClutterPanAction::pan-stopped signal
+ *
+ * The <structname>ClutterPanActionClass</structname> structure contains
+ * only private data.
+ *
+ * Since: 1.12
+ */
+struct _ClutterPanActionClass
+{
+  /*< private >*/
+  ClutterGestureActionClass parent_class;
+
+  /*< public >*/
+  gboolean (* pan)               (ClutterPanAction    *action,
+                                  ClutterActor        *actor,
+                                  gboolean             is_interpolated);
+  void     (* pan_stopped)       (ClutterPanAction    *action,
+                                  ClutterActor        *actor);
+
+  /*< private >*/
+  void (* _clutter_pan_action1) (void);
+  void (* _clutter_pan_action2) (void);
+  void (* _clutter_pan_action3) (void);
+  void (* _clutter_pan_action4) (void);
+  void (* _clutter_pan_action5) (void);
+  void (* _clutter_pan_action6) (void);
+};
+
+CLUTTER_AVAILABLE_IN_1_12
+GType clutter_pan_action_get_type (void) G_GNUC_CONST;
+
+CLUTTER_AVAILABLE_IN_1_12
+ClutterAction * clutter_pan_action_new                      (void);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_set_pan_axis             (ClutterPanAction *self,
+                                                             ClutterPanAxis    axis);
+CLUTTER_AVAILABLE_IN_1_12
+ClutterPanAxis clutter_pan_action_get_pan_axis              (ClutterPanAction *self);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_set_interpolate          (ClutterPanAction *self,
+                                                             gboolean          should_interpolate);
+CLUTTER_AVAILABLE_IN_1_12
+gboolean        clutter_pan_action_get_interpolate          (ClutterPanAction *self);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_set_deceleration         (ClutterPanAction *self,
+                                                             gdouble           rate);
+CLUTTER_AVAILABLE_IN_1_12
+gdouble         clutter_pan_action_get_deceleration         (ClutterPanAction *self);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_set_acceleration_factor  (ClutterPanAction *self,
+                                                             gdouble           factor);
+CLUTTER_AVAILABLE_IN_1_12
+gdouble         clutter_pan_action_get_acceleration_factor  (ClutterPanAction *self);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_get_interpolated_coords  (ClutterPanAction *self,
+                                                             gfloat           *interpolated_x,
+                                                             gfloat           *interpolated_y);
+CLUTTER_AVAILABLE_IN_1_12
+void            clutter_pan_action_get_interpolated_delta   (ClutterPanAction *self,
+                                                             gfloat           *delta_x,
+                                                             gfloat           *delta_y);
+G_END_DECLS
+
+#endif /* __CLUTTER_PAN_ACTION_H__ */
diff --git a/clutter/clutter.h b/clutter/clutter.h
index e5bd24d..76a94bc 100644
--- a/clutter/clutter.h
+++ b/clutter/clutter.h
@@ -83,6 +83,7 @@
 #include "clutter-page-turn-effect.h"
 #include "clutter-paint-nodes.h"
 #include "clutter-paint-node.h"
+#include "clutter-pan-action.h"
 #include "clutter-path-constraint.h"
 #include "clutter-path.h"
 #include "clutter-property-transition.h"
diff --git a/clutter/clutter.symbols b/clutter/clutter.symbols
index 9991579..6afa7d3 100644
--- a/clutter/clutter.symbols
+++ b/clutter/clutter.symbols
@@ -1051,6 +1051,19 @@ clutter_paint_volume_set_origin
 clutter_paint_volume_set_width
 clutter_paint_volume_union_box
 clutter_paint_volume_union
+clutter_pan_axis_get_type
+clutter_pan_action_get_type
+clutter_pan_action_get_acceleration_factor
+clutter_pan_action_get_deceleration
+clutter_pan_action_get_interpolated_coords
+clutter_pan_action_get_interpolated_delta
+clutter_pan_action_get_interpolate
+clutter_pan_action_get_pan_axis
+clutter_pan_action_new
+clutter_pan_action_set_acceleration_factor
+clutter_pan_action_set_deceleration
+clutter_pan_action_set_interpolate
+clutter_pan_action_set_pan_axis
 clutter_param_color_get_type
 clutter_param_fixed_get_type
 clutter_param_spec_color
diff --git a/doc/reference/clutter/clutter-docs.xml.in b/doc/reference/clutter/clutter-docs.xml.in
index de1fa4f..08aa249 100644
--- a/doc/reference/clutter/clutter-docs.xml.in
+++ b/doc/reference/clutter/clutter-docs.xml.in
@@ -96,6 +96,7 @@
       <xi:include href="xml/clutter-drag-action.xml"/>
       <xi:include href="xml/clutter-drop-action.xml"/>
       <xi:include href="xml/clutter-gesture-action.xml"/>
+      <xi:include href="xml/clutter-pan-action.xml"/>
       <xi:include href="xml/clutter-swipe-action.xml"/>
       <xi:include href="xml/clutter-rotate-action.xml"/>
       <xi:include href="xml/clutter-zoom-action.xml"/>
diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt
index 72b7889..5513348 100644
--- a/doc/reference/clutter/clutter-sections.txt
+++ b/doc/reference/clutter/clutter-sections.txt
@@ -3449,3 +3449,32 @@ CLUTTER_ZOOM_ACTION_GET_CLASS
 ClutterZoomActionPrivate
 clutter_zoom_action_get_type
 </SECTION>
+
+<SECTION>
+<FILE>clutter-pan-action</FILE>
+ClutterPanAction
+ClutterPanActionClass
+clutter_pan_action_new
+ClutterPanAxis
+clutter_pan_action_set_pan_axis
+clutter_pan_action_get_pan_axis
+clutter_pan_action_set_interpolate
+clutter_pan_action_get_interpolate
+clutter_pan_action_set_deceleration
+clutter_pan_action_get_deceleration
+clutter_pan_action_set_acceleration_factor
+clutter_pan_action_get_acceleration_factor
+<SUBSECTION>
+clutter_pan_action_get_interpolated_coords
+clutter_pan_action_get_interpolated_delta
+<SUBSECTION Standard>
+CLUTTER_IS_PAN_ACTION
+CLUTTER_IS_PAN_ACTION_CLASS
+CLUTTER_TYPE_PAN_ACTION
+CLUTTER_PAN_ACTION
+CLUTTER_PAN_ACTION_CLASS
+CLUTTER_PAN_ACTION_GET_CLASS
+<SUBSECTION Private>
+ClutterPanActionPrivate
+clutter_pan_action_get_type
+</SECTION>
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types
index 5b640b8..cc6d8b2 100644
--- a/doc/reference/clutter/clutter.types
+++ b/doc/reference/clutter/clutter.types
@@ -54,6 +54,7 @@ clutter_model_get_type
 clutter_model_iter_get_type
 clutter_offscreen_effect_get_type
 clutter_page_turn_effect_get_type
+clutter_pan_action_get_type
 clutter_paint_node_get_type
 clutter_path_constraint_get_type
 clutter_path_get_type
diff --git a/examples/Makefile.am b/examples/Makefile.am
index 3d9bdb5..4c6998f 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -11,6 +11,7 @@ all_examples = \
 	flow-layout \
 	grid-layout \
 	layout-manager \
+	pan-action \
 	rounded-rectangle \
 	scroll-actor \
 	threads
diff --git a/examples/pan-action.c b/examples/pan-action.c
new file mode 100644
index 0000000..2eb382d
--- /dev/null
+++ b/examples/pan-action.c
@@ -0,0 +1,127 @@
+#include <stdlib.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <clutter/clutter.h>
+
+static ClutterActor *
+create_content_actor (void)
+{
+  ClutterActor *content;
+  ClutterContent *image;
+  GdkPixbuf *pixbuf;
+
+  content = clutter_actor_new ();
+  clutter_actor_set_size (content, 720, 720);
+
+  pixbuf = gdk_pixbuf_new_from_file (TESTS_DATADIR G_DIR_SEPARATOR_S "redhand.png", NULL);
+  image = clutter_image_new ();
+  clutter_image_set_data (CLUTTER_IMAGE (image),
+                          gdk_pixbuf_get_pixels (pixbuf),
+                          gdk_pixbuf_get_has_alpha (pixbuf)
+                            ? COGL_PIXEL_FORMAT_RGBA_8888
+                            : COGL_PIXEL_FORMAT_RGB_888,
+                          gdk_pixbuf_get_width (pixbuf),
+                          gdk_pixbuf_get_height (pixbuf),
+                          gdk_pixbuf_get_rowstride (pixbuf),
+                          NULL);
+  g_object_unref (pixbuf);
+
+  clutter_actor_set_content_scaling_filters (content,
+                                             CLUTTER_SCALING_FILTER_TRILINEAR,
+                                             CLUTTER_SCALING_FILTER_LINEAR);
+  clutter_actor_set_content_gravity (content, CLUTTER_CONTENT_GRAVITY_RESIZE_ASPECT);
+  clutter_actor_set_content (content, image);
+  g_object_unref (image);
+
+  return content;
+}
+
+static gboolean
+on_pan (ClutterPanAction *action,
+        ClutterActor     *scroll,
+        gboolean          is_interpolated,
+        gpointer         *user_data)
+{
+  gfloat delta_x, delta_y;
+
+  if (is_interpolated)
+    clutter_pan_action_get_interpolated_delta (action, &delta_x, &delta_y);
+  else
+    clutter_gesture_action_get_motion_delta (CLUTTER_GESTURE_ACTION (action), 0, &delta_x, &delta_y);
+
+  g_print ("panning dx:%.2f dy:%.2f\n", delta_x, delta_y);
+
+  return TRUE;
+}
+
+static ClutterActor *
+create_scroll_actor (ClutterActor *stage)
+{
+  ClutterActor *scroll;
+  ClutterAction *pan_action;
+
+  /* our scrollable viewport */
+  scroll = clutter_actor_new ();
+  clutter_actor_set_name (scroll, "scroll");
+
+  clutter_actor_add_constraint (scroll, clutter_align_constraint_new (stage, CLUTTER_ALIGN_X_AXIS, 0));
+  clutter_actor_add_constraint (scroll, clutter_bind_constraint_new (stage, CLUTTER_BIND_SIZE, 0));
+
+  clutter_actor_add_child (scroll, create_content_actor ());
+
+  pan_action = clutter_pan_action_new ();
+  clutter_pan_action_set_interpolate (CLUTTER_PAN_ACTION (pan_action), TRUE);
+  g_signal_connect (pan_action, "pan", G_CALLBACK (on_pan), NULL);
+  clutter_actor_add_action (scroll, pan_action);
+
+  clutter_actor_set_reactive (scroll, TRUE);
+
+  return scroll;
+}
+
+static gboolean
+on_key_press (ClutterActor *stage,
+              ClutterEvent *event,
+              gpointer      unused)
+{
+  ClutterActor *scroll;
+  guint key_symbol;
+
+  scroll = clutter_actor_get_first_child (stage);
+
+  key_symbol = clutter_event_get_key_symbol (event);
+
+  if (key_symbol == CLUTTER_KEY_space)
+    clutter_actor_set_child_transform (scroll, NULL);
+
+  return CLUTTER_EVENT_STOP;
+}
+
+int
+main (int argc, char *argv[])
+{
+  ClutterActor *stage, *scroll, *info;
+
+  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
+    return EXIT_FAILURE;
+
+  /* create a new stage */
+  stage = clutter_stage_new ();
+  clutter_stage_set_title (CLUTTER_STAGE (stage), "Pan Action");
+  clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
+
+  scroll = create_scroll_actor (stage);
+  clutter_actor_add_child (stage, scroll);
+
+  info = clutter_text_new_with_text (NULL, "Press <space> to reset the image position.");
+  clutter_actor_add_child (stage, info);
+  clutter_actor_set_position (info, 12, 12);
+
+  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
+  g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press), scroll);
+
+  clutter_actor_show (stage);
+
+  clutter_main ();
+
+  return EXIT_SUCCESS;
+}



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