action: Add DragAction, an action implementing drag capabilities
authorEmmanuele Bassi <ebassi@linux.intel.com>
Wed, 12 May 2010 11:00:33 +0000 (12:00 +0100)
committerEmmanuele Bassi <ebassi@linux.intel.com>
Mon, 17 May 2010 15:42:11 +0000 (16:42 +0100)
DragAction is an Action sub-class that provides dragging capabilities to
any actor. DragAction has:

  • drag-begin, drag-motion and drag-end signals, relaying the event
    information like coordinates, button and modifiers to user code;

  • drag-threshold property, for delaying the drag start by a given
    amount of pixels;

  • drag-handle property, to allow using other actors as the drag
    handle.

  • drag-axis property, to allow constraining the dragging to a specific
    axis.

An interactive test demonstrating the various features is also provided.

.gitignore
clutter/Makefile.am
clutter/clutter-drag-action.c [new file with mode: 0644]
clutter/clutter-drag-action.h [new file with mode: 0644]
clutter/clutter-marshal.list
clutter/clutter.h
tests/interactive/Makefile.am
tests/interactive/test-drag.c [new file with mode: 0644]

index 65a6948..495821a 100644 (file)
@@ -149,6 +149,7 @@ TAGS
 /tests/interactive/stamp-test-interactive
 /tests/interactive/test-animator
 /tests/interactive/test-stage-sizing
+/tests/interactive/test-drag
 /tests/conform/stamp-test-conformance
 /tests/conform/test-anchors
 /tests/conform/test-cogl-backface-culling
index e0ba87a..3f55d97 100644 (file)
@@ -91,6 +91,7 @@ source_h =                                    \
        $(srcdir)/clutter-container.h           \
        $(srcdir)/clutter-deprecated.h          \
        $(srcdir)/clutter-device-manager.h      \
+       $(srcdir)/clutter-drag-action.h         \
        $(srcdir)/clutter-event.h               \
        $(srcdir)/clutter-feature.h             \
        $(srcdir)/clutter-fixed.h               \
@@ -166,6 +167,7 @@ source_c = \
        $(srcdir)/clutter-color.c               \
        $(srcdir)/clutter-container.c           \
        $(srcdir)/clutter-device-manager.c      \
+       $(srcdir)/clutter-drag-action.c         \
        clutter-enum-types.c                    \
        $(srcdir)/clutter-event.c               \
        $(srcdir)/clutter-feature.c             \
diff --git a/clutter/clutter-drag-action.c b/clutter/clutter-drag-action.c
new file mode 100644 (file)
index 0000000..1a34b67
--- /dev/null
@@ -0,0 +1,813 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010  Intel Corporation.
+ *
+ * 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:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+/**
+ * SECTION:ClutterDragAction
+ * @Title: ClutterDragAction
+ * @Short_Description: Action enabling dragging on actors
+ *
+ * #ClutterDragAction is a sub-class of #ClutterAction that implements
+ * all the necessary logic for dragging actors.
+ *
+ * The simplest usage of #ClutterDragAction consists in adding it to
+ * a #ClutterActor and connecting to the #ClutterDragAction::drag-motion
+ * signal handler to move the actor; for instance:
+ *
+ * |[
+ *   ClutterAction *action = clutter_drag_action_new ();
+ *
+ *   g_signal_connect (action, "drag-motion",
+ *                     G_CALLBACK (on_drag_motion),
+ *                     NULL);
+ *
+ *   clutter_actor_add_action (actor, action);
+ * ]|
+ *
+ * Where the on_drag_motion() signal handler calls clutter_actor_move_by()
+ * using the delta between motion events passed to the handler:
+ *
+ * |[
+ *   static void
+ *   on_drag_motion (ClutterDragAction   *action,
+ *                   ClutterActor        *actor,
+ *                   gfloat               delta_x,
+ *                   gfloat               delta_y,
+ *                   ClutterModifierType  modifiers)
+ *   {
+ *     clutter_actor_move_by (actor, delta_x, delta_y);
+ *   }
+ * ]|
+ *
+ * It is also possible to set another #ClutterActor as the dragged actor
+ * by calling clutter_drag_action_set_drag_handle() from within a handle
+ * of the #ClutterDragAction::drag-begin signal. The drag handle must be
+ * parented and exist between the emission of #ClutterDragAction::drag-begin
+ * and #ClutterDragAction::drag-end.
+ *
+ * #ClutterDragAction is available since Clutter 1.4
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "clutter-drag-action.h"
+
+#include "clutter-debug.h"
+#include "clutter-enum-types.h"
+#include "clutter-marshal.h"
+#include "clutter-private.h"
+
+struct _ClutterDragActionPrivate
+{
+  ClutterActor *stage;
+
+  gfloat drag_threshold;
+  ClutterActor *drag_handle;
+  ClutterDragAxis drag_axis;
+
+  gulong button_press_id;
+  gulong capture_id;
+
+  gfloat press_x;
+  gfloat press_y;
+  ClutterModifierType press_state;
+  gint press_button;
+
+  gfloat last_motion_x;
+  gfloat last_motion_y;
+
+  gfloat transformed_press_x;
+  gfloat transformed_press_y;
+
+  guint emit_delayed_press : 1;
+  guint in_drag            : 1;
+};
+
+enum
+{
+  PROP_0,
+
+  PROP_DRAG_THRESHOLD,
+  PROP_DRAG_HANDLE,
+  PROP_DRAG_AXIS
+};
+
+enum
+{
+  DRAG_BEGIN,
+  DRAG_MOTION,
+  DRAG_END,
+
+  LAST_SIGNAL
+};
+
+static guint drag_signals[LAST_SIGNAL] = { 0, };
+
+/* forward declaration */
+static gboolean on_captured_event (ClutterActor      *stage,
+                                   ClutterEvent      *event,
+                                   ClutterDragAction *action);
+
+G_DEFINE_TYPE (ClutterDragAction, clutter_drag_action, CLUTTER_TYPE_ACTION);
+
+static void
+emit_drag_begin (ClutterDragAction *action,
+                 ClutterActor      *actor,
+                 ClutterEvent      *event)
+{
+  ClutterDragActionPrivate *priv = action->priv;
+
+  g_signal_emit (action, drag_signals[DRAG_BEGIN], 0,
+                 actor,
+                 priv->press_x, priv->press_y,
+                 priv->press_button,
+                 priv->press_state);
+}
+
+static void
+emit_drag_motion (ClutterDragAction *action,
+                  ClutterActor      *actor,
+                  ClutterEvent      *event)
+{
+  ClutterDragActionPrivate *priv = action->priv;
+  ClutterActor *drag_handle = NULL;
+  gfloat delta_x, delta_y;
+  gfloat motion_x, motion_y;
+
+  clutter_event_get_coords (event, &priv->last_motion_x, &priv->last_motion_y);
+
+  if (priv->drag_handle != NULL && !priv->emit_delayed_press)
+    drag_handle = priv->drag_handle;
+  else
+    drag_handle = actor;
+
+  motion_x = motion_y = 0.0f;
+  clutter_actor_transform_stage_point (drag_handle,
+                                       priv->last_motion_x,
+                                       priv->last_motion_y,
+                                       &motion_x, &motion_y);
+
+  delta_x = delta_y = 0.0f;
+
+  switch (priv->drag_axis)
+    {
+    case CLUTTER_DRAG_AXIS_NONE:
+      delta_x = motion_x - priv->transformed_press_x;
+      delta_y = motion_y - priv->transformed_press_y;
+      break;
+
+    case CLUTTER_DRAG_X_AXIS:
+      delta_x = motion_x - priv->transformed_press_x;
+      break;
+
+    case CLUTTER_DRAG_Y_AXIS:
+      delta_y = motion_y - priv->transformed_press_y;
+      break;
+
+    default:
+      g_assert_not_reached ();
+      return;
+    }
+
+  if (priv->emit_delayed_press)
+    {
+      if (ABS (delta_x) >= priv->drag_threshold ||
+          ABS (delta_y) >= priv->drag_threshold)
+        {
+          priv->emit_delayed_press = FALSE;
+
+          emit_drag_begin (action, actor, NULL);
+        }
+      else
+        return;
+    }
+
+  g_signal_emit (action, drag_signals[DRAG_MOTION], 0,
+                 actor,
+                 delta_x, delta_y);
+}
+
+static void
+emit_drag_end (ClutterDragAction *action,
+               ClutterActor      *actor,
+               ClutterEvent      *event)
+{
+  ClutterDragActionPrivate *priv = action->priv;
+
+  clutter_event_get_coords (event, &priv->last_motion_x, &priv->last_motion_y);
+
+  /* we might not have emitted ::drag-begin yet */
+  if (!priv->emit_delayed_press)
+    g_signal_emit (action, drag_signals[DRAG_END], 0,
+                   actor,
+                   priv->last_motion_x, priv->last_motion_y,
+                   clutter_event_get_button (event),
+                   clutter_event_get_state (event));
+
+  /* disconnect the capture */
+  if (priv->capture_id != 0)
+    {
+      g_signal_handler_disconnect (priv->stage, priv->capture_id);
+      priv->capture_id = 0;
+    }
+
+  priv->in_drag = FALSE;
+}
+
+static gboolean
+on_captured_event (ClutterActor      *stage,
+                   ClutterEvent      *event,
+                   ClutterDragAction *action)
+{
+  ClutterDragActionPrivate *priv = action->priv;
+  ClutterActor *actor;
+  
+  actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (action));
+
+  if (!priv->in_drag)
+    return FALSE;
+
+  switch (clutter_event_type (event))
+    {
+    case CLUTTER_MOTION:
+      {
+        ClutterModifierType mods = clutter_event_get_state (event);
+
+        /* we might miss a button-release event in case of grabs,
+         * so we need to check whether the button is still down
+         * during a motion event
+         */
+        if (mods & CLUTTER_BUTTON1_MASK)
+          emit_drag_motion (action, actor, event);
+        else
+          emit_drag_end (action, actor, event);
+      }
+      break;
+
+    case CLUTTER_BUTTON_RELEASE:
+      if (priv->in_drag)
+        emit_drag_end (action, actor, event);
+      break;
+
+    default:
+      break;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+on_button_press (ClutterActor      *actor,
+                 ClutterEvent      *event,
+                 ClutterDragAction *action)
+{
+  ClutterDragActionPrivate *priv = action->priv;
+
+  if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (action)))
+    return FALSE;
+
+  if (priv->stage == NULL)
+    priv->stage = clutter_actor_get_stage (actor);
+
+  clutter_event_get_coords (event, &priv->press_x, &priv->press_y);
+  priv->press_button = clutter_event_get_button (event);
+  priv->press_state = clutter_event_get_state (event);
+
+  priv->last_motion_x = priv->press_x;
+  priv->last_motion_y = priv->press_y;
+
+  priv->transformed_press_x = priv->press_x;
+  priv->transformed_press_y = priv->press_y;
+  clutter_actor_transform_stage_point (actor, priv->press_x, priv->press_y,
+                                       &priv->transformed_press_x,
+                                       &priv->transformed_press_y);
+
+  if (priv->drag_threshold == 0)
+    emit_drag_begin (action, actor, event);
+  else
+    priv->emit_delayed_press = TRUE;
+
+  priv->in_drag = TRUE;
+  priv->capture_id = g_signal_connect_after (priv->stage, "captured-event",
+                                             G_CALLBACK (on_captured_event),
+                                             action);
+
+  return FALSE;
+}
+
+static void
+clutter_drag_action_set_actor (ClutterActorMeta *meta,
+                               ClutterActor     *actor)
+{
+  ClutterDragActionPrivate *priv = CLUTTER_DRAG_ACTION (meta)->priv;
+
+  if (priv->button_press_id != 0)
+    {
+      ClutterActor *old_actor;
+
+      old_actor = clutter_actor_meta_get_actor (meta);
+
+      g_signal_handler_disconnect (old_actor, priv->button_press_id);
+
+      if (priv->capture_id != 0)
+        g_signal_handler_disconnect (old_actor, priv->capture_id);
+
+      priv->button_press_id = 0;
+      priv->capture_id = 0;
+
+      priv->stage = NULL;
+    }
+
+  if (actor != NULL)
+    priv->button_press_id = g_signal_connect (actor, "button-press-event",
+                                              G_CALLBACK (on_button_press),
+                                              meta);
+
+  CLUTTER_ACTOR_META_CLASS (clutter_drag_action_parent_class)->set_actor (meta, actor);
+}
+
+static void
+clutter_drag_action_set_property (GObject      *gobject,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  ClutterDragAction *action = CLUTTER_DRAG_ACTION (gobject);
+
+  switch (prop_id)
+    {
+    case PROP_DRAG_THRESHOLD:
+      clutter_drag_action_set_drag_threshold (action, g_value_get_uint (value));
+      break;
+
+    case PROP_DRAG_HANDLE:
+      clutter_drag_action_set_drag_handle (action, g_value_get_object (value));
+      break;
+
+    case PROP_DRAG_AXIS:
+      clutter_drag_action_set_drag_axis (action, g_value_get_enum (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+clutter_drag_action_get_property (GObject    *gobject,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  ClutterDragActionPrivate *priv = CLUTTER_DRAG_ACTION (gobject)->priv;
+
+  switch (prop_id)
+    {
+    case PROP_DRAG_THRESHOLD:
+      g_value_set_uint (value, priv->drag_threshold);
+      break;
+
+    case PROP_DRAG_HANDLE:
+      g_value_set_object (value, priv->drag_handle);
+      break;
+
+    case PROP_DRAG_AXIS:
+      g_value_set_enum (value, priv->drag_axis);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+    }
+}
+
+static void
+clutter_drag_action_dispose (GObject *gobject)
+{
+  ClutterDragActionPrivate *priv = CLUTTER_DRAG_ACTION (gobject)->priv;
+
+  if (priv->capture_id != 0)
+    {
+      if (priv->stage != NULL)
+        g_signal_handler_disconnect (priv->stage, priv->capture_id);
+
+      priv->capture_id = 0;
+      priv->stage = NULL;
+    }
+
+  if (priv->button_press_id != 0)
+    {
+      ClutterActor *actor;
+
+      actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (gobject));
+      g_signal_handler_disconnect (actor, priv->button_press_id);
+      priv->button_press_id = 0;
+    }
+
+  G_OBJECT_CLASS (clutter_drag_action_parent_class)->dispose (gobject);
+}
+
+static void
+clutter_drag_action_class_init (ClutterDragActionClass *klass)
+{
+  ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (ClutterDragActionPrivate));
+
+  gobject_class->set_property = clutter_drag_action_set_property;
+  gobject_class->get_property = clutter_drag_action_get_property;
+  gobject_class->dispose = clutter_drag_action_dispose;
+
+  meta_class->set_actor = clutter_drag_action_set_actor;
+
+  /**
+   * ClutterDragAction:drag-threshold:
+   *
+   * The threshold, in pixels, that begins a drag action
+   *
+   * When set to a non-zero value, #ClutterDragAction will only emit
+   * #ClutterDragAction::drag-begin if the pointer has moved at least
+   * of the given amount of pixels since the button press event
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_uint ("drag-threshold",
+                             "Drag Threshold",
+                             "The amount of pixels required to start "
+                             "dragging",
+                             0, G_MAXUINT,
+                             0,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DRAG_THRESHOLD, pspec);
+
+  /**
+   * ClutterDragAction:drag-handle:
+   *
+   * The #ClutterActor that is effectively being dragged
+   *
+   * A #ClutterDragActor will, be default, use the #ClutterActor that
+   * has been attached to the action; it is possible to create a
+   * separate #ClutterActor and use it instead.
+   *
+   * Setting this property has no effect on the #ClutterActor argument
+   * passed to the #ClutterDragAction signals
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_object ("drag-handle",
+                               "Drag Handle",
+                               "The actor that is being dragged",
+                               CLUTTER_TYPE_ACTOR,
+                               CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DRAG_HANDLE, pspec);
+
+  /**
+   * ClutterDragAction:drag-axis:
+   *
+   * Constraints the dragging action to the specified axis
+   *
+   * Since: 1.4
+   */
+  pspec = g_param_spec_enum ("drag-axis",
+                             "Drag Axis",
+                             "Constraints the dragging to an axis",
+                             CLUTTER_TYPE_DRAG_AXIS,
+                             CLUTTER_DRAG_AXIS_NONE,
+                             CLUTTER_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DRAG_AXIS, pspec);
+
+  /**
+   * ClutterDragAction::drag-begin:
+   * @action: the #ClutterDragAction that emitted the signal
+   * @actor: the #ClutterActor attached to the action
+   * @event_x: the X coordinate (in stage space) of the press event
+   * @event_y: the Y coordinate (in stage space) of the press event
+   * @button: the button of the press event
+   * @modifiers: the modifiers of the press event
+   *
+   * The ::drag-begin signal is emitted when the #ClutterDragAction
+   * starts the dragging
+   *
+   * The emission of this signal can be delayed by using the
+   * #ClutterDragAction:drag-threshold property
+   *
+   * Since: 1.4
+   */
+  drag_signals[DRAG_BEGIN] =
+    g_signal_new (I_("drag-begin"),
+                  CLUTTER_TYPE_DRAG_ACTION,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterDragActionClass, drag_begin),
+                  NULL, NULL,
+                  clutter_marshal_VOID__OBJECT_FLOAT_FLOAT_INT_FLAGS,
+                  G_TYPE_NONE, 5,
+                  CLUTTER_TYPE_ACTOR,
+                  G_TYPE_FLOAT,
+                  G_TYPE_FLOAT,
+                  G_TYPE_INT,
+                  CLUTTER_TYPE_MODIFIER_TYPE);
+
+  /**
+   * ClutterDragAction::drag-motion
+   * @action: the #ClutterDragAction that emitted the signal
+   * @actor: the #ClutterActor attached to the action
+   * @delta_x: the X component of the distance between the press event
+   *   that began the dragging and the current position of the pointer,
+   *   as of the latest motion event
+   * @delta_y: the Y component of the distance between the press event
+   *   that began the dragging and the current position of the pointer,
+   *   as of the latest motion event
+   * @modifiers: the modifiers of the latest motion event
+   *
+   * The ::drag-motion signal is emitted for each motion event after
+   * the #ClutterDragAction::drag-begin signal has been emitted.
+   *
+   * The components of the distance between the press event and the
+   * latest motion event are computed in the actor's coordinate space,
+   * to take into account eventual transformations. If you want the
+   * stage coordinates of the latest motion event you can use
+   * clutter_drag_action_get_motion_coords().
+   *
+   * Since: 1.4
+   */
+  drag_signals[DRAG_MOTION] =
+    g_signal_new (I_("drag-motion"),
+                  CLUTTER_TYPE_DRAG_ACTION,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterDragActionClass, drag_motion),
+                  NULL, NULL,
+                  clutter_marshal_VOID__OBJECT_FLOAT_FLOAT,
+                  G_TYPE_NONE, 3,
+                  CLUTTER_TYPE_ACTOR,
+                  G_TYPE_FLOAT,
+                  G_TYPE_FLOAT);
+
+  /**
+   * ClutterDragAction::drag-end:
+   * @action: the #ClutterDragAction that emitted the signal
+   * @actor: the #ClutterActor attached to the action
+   * @event_x: the X coordinate (in stage space) of the release event
+   * @event_y: the Y coordinate (in stage space) of the release event
+   * @button: the button of the release event
+   * @modifiers: the modifiers of the release event
+   *
+   * The ::drag-end signal is emitted at the end of the dragging,
+   * when the pointer button's is released
+   *
+   * This signal is emitted if and only if the #ClutterDragAction::drag-begin
+   * signal has been emitted first
+   *
+   * Since: 1.4
+   */
+  drag_signals[DRAG_END] =
+    g_signal_new (I_("drag-end"),
+                  CLUTTER_TYPE_DRAG_ACTION,
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (ClutterDragActionClass, drag_end),
+                  NULL, NULL,
+                  clutter_marshal_VOID__OBJECT_FLOAT_FLOAT_INT_FLAGS,
+                  G_TYPE_NONE, 5,
+                  CLUTTER_TYPE_ACTOR,
+                  G_TYPE_FLOAT,
+                  G_TYPE_FLOAT,
+                  G_TYPE_INT,
+                  CLUTTER_TYPE_MODIFIER_TYPE);
+}
+
+static void
+clutter_drag_action_init (ClutterDragAction *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DRAG_ACTION,
+                                            ClutterDragActionPrivate);
+}
+
+/**
+ * clutter_drag_action_new:
+ *
+ * Creates a new #ClutterDragAction instance
+ *
+ * Return value: the newly created #ClutterDragAction
+ *
+ * Since: 1.4
+ */
+ClutterAction *
+clutter_drag_action_new (void)
+{
+  return g_object_new (CLUTTER_TYPE_DRAG_ACTION, NULL);
+}
+
+/**
+ * clutter_drag_action_set_drag_threshold:
+ * @action: a #ClutterDragAction
+ * @threshold: a distance, in pixels
+ *
+ * Sets the drag threshold that must be cleared by the pointer
+ * before @action can begin the dragging
+ *
+ * Since: 1.4
+ */
+void
+clutter_drag_action_set_drag_threshold (ClutterDragAction *action,
+                                        guint              threshold)
+{
+  ClutterDragActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_DRAG_ACTION (action));
+
+  priv = action->priv;
+
+  if (priv->drag_threshold == threshold)
+    return;
+
+  priv->drag_threshold = threshold;
+
+  g_object_notify (G_OBJECT (action), "drag-threshold");
+}
+
+/**
+ * clutter_drag_action_get_drag_threshold:
+ * @action: a #ClutterDragAction
+ *
+ * Retrieves the value set by clutter_drag_action_set_drag_threshold()
+ *
+ * Return value: the drag threshold value, in pixels
+ *
+ * Since: 1.4
+ */
+guint
+clutter_drag_action_get_drag_threshold (ClutterDragAction *action)
+{
+  g_return_val_if_fail (CLUTTER_IS_DRAG_ACTION (action), 0);
+
+  return action->priv->drag_threshold;
+}
+
+/**
+ * clutter_drag_action_set_drag_handle:
+ * @action: a #ClutterDragHandle
+ * @handle: a #ClutterActor
+ *
+ * Sets the actor to be used as the drag handle
+ *
+ * Since: 1.4
+ */
+void
+clutter_drag_action_set_drag_handle (ClutterDragAction *action,
+                                     ClutterActor      *handle)
+{
+  ClutterDragActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_DRAG_ACTION (action));
+  g_return_if_fail (CLUTTER_IS_ACTOR (handle));
+
+  priv = action->priv;
+
+  if (priv->drag_handle == handle)
+    return;
+
+  priv->drag_handle = handle;
+
+  g_object_notify (G_OBJECT (action), "drag-handle");
+}
+
+/**
+ * clutter_drag_action_get_drag_handle:
+ * @action: a #ClutterDragAction
+ *
+ * Retrieves the drag handle set by clutter_drag_action_set_drag_handle()
+ *
+ * Return value: (transfer none): a #ClutterActor, used as the drag
+ *   handle, or %NULL if none was set
+ *
+ * Since: 1.4
+ */
+ClutterActor *
+clutter_drag_action_get_drag_handle (ClutterDragAction *action)
+{
+  g_return_val_if_fail (CLUTTER_IS_DRAG_ACTION (action), NULL);
+
+  return action->priv->drag_handle;
+}
+
+/**
+ * clutter_drag_action_set_drag_axis:
+ * @action: a #ClutterDragAction
+ * @axis: the axis to constraint the dragging to
+ *
+ * Restricts the dragging action to a specific axis
+ *
+ * Since: 1.4
+ */
+void
+clutter_drag_action_set_drag_axis (ClutterDragAction *action,
+                                   ClutterDragAxis    axis)
+{
+  ClutterDragActionPrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_DRAG_ACTION (action));
+  g_return_if_fail (axis >= CLUTTER_DRAG_AXIS_NONE &&
+                    axis <= CLUTTER_DRAG_Y_AXIS);
+
+  priv = action->priv;
+
+  if (priv->drag_axis == axis)
+    return;
+
+  priv->drag_axis = axis;
+
+  g_object_notify (G_OBJECT (action), "drag-axis");
+}
+
+/**
+ * clutter_drag_action_get_drag_axis:
+ * @action: a #ClutterDragAction
+ *
+ * Retrieves the axis constraint set by clutter_drag_action_set_drag_axis()
+ *
+ * Return value: the axis constraint
+ *
+ * Since: 1.4
+ */
+ClutterDragAxis
+clutter_drag_action_get_drag_axis (ClutterDragAction *action)
+{
+  g_return_val_if_fail (CLUTTER_IS_DRAG_ACTION (action),
+                        CLUTTER_DRAG_AXIS_NONE);
+
+  return action->priv->drag_axis;
+}
+
+/**
+ * clutter_drag_action_get_press_coords:
+ * @action: a #ClutterDragAction
+ * @press_x: (out): return location for the press event's X coordinate
+ * @press_y: (out): return location for the press event's Y coordinate
+ *
+ * Retrieves the coordinates, in stage space, of the press event
+ * that started the dragging
+ *
+ * Since: 1.4
+ */
+void
+clutter_drag_action_get_press_coords (ClutterDragAction *action,
+                                      gfloat            *press_x,
+                                      gfloat            *press_y)
+{
+  g_return_if_fail (CLUTTER_IS_DRAG_ACTION (action));
+
+  if (press_x)
+    *press_x = action->priv->press_x;
+
+  if (press_y)
+    *press_y = action->priv->press_y;
+}
+
+/**
+ * clutter_drag_action_get_motion_coords:
+ * @action: a #ClutterDragAction
+ * @motion_x: (out): return location for the latest motion
+ *   event's X coordinate
+ * @motion_y: (out): return location for the latest motion
+ *   event's Y coordinate
+ *
+ * Retrieves the coordinates, in stage space, of the latest motion
+ * event during the dragging
+ *
+ * Since: 1.4
+ */
+void
+clutter_drag_action_get_motion_coords (ClutterDragAction *action,
+                                       gfloat            *motion_x,
+                                       gfloat            *motion_y)
+{
+  g_return_if_fail (CLUTTER_IS_DRAG_ACTION (action));
+
+  if (motion_x)
+    *motion_x = action->priv->last_motion_x;
+
+  if (motion_y)
+    *motion_y = action->priv->last_motion_y;
+}
diff --git a/clutter/clutter-drag-action.h b/clutter/clutter-drag-action.h
new file mode 100644 (file)
index 0000000..78290dc
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Copyright (C) 2010  Intel Corporation.
+ *
+ * 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:
+ *   Emmanuele Bassi <ebassi@linux.intel.com>
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_DRAG_ACTION_H__
+#define __CLUTTER_DRAG_ACTION_H__
+
+#include <clutter/clutter-action.h>
+#include <clutter/clutter-event.h>
+
+G_BEGIN_DECLS
+
+#define CLUTTER_TYPE_DRAG_ACTION                (clutter_drag_action_get_type ())
+#define CLUTTER_DRAG_ACTION(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_DRAG_ACTION, ClutterDragAction))
+#define CLUTTER_IS_DRAG_ACTION(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_DRAG_ACTION))
+#define CLUTTER_DRAG_ACTION_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), CLUTTER_TYPE_DRAG_ACTION, ClutterDragActionClass))
+#define CLUTTER_IS_DRAG_ACTION_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), CLUTTER_TYPE_DRAG_ACTION))
+#define CLUTTER_DRAG_ACTION_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), CLUTTER_TYPE_DRAG_ACTION, ClutterDragActionClass))
+
+typedef struct _ClutterDragAction               ClutterDragAction;
+typedef struct _ClutterDragActionPrivate        ClutterDragActionPrivate;
+typedef struct _ClutterDragActionClass          ClutterDragActionClass;
+
+/**
+ * ClutterDragAxis:
+ * @CLUTTER_DRAG_AXIS_NONE: No constraint
+ * @CLUTTER_DRAG_X_AXIS: Set a constraint on the X axis
+ * @CLUTTER_DRAG_Y_AXIS: Set a constraint on the Y axis
+ *
+ * The axis of the constraint that should be applied on the
+ * dragging action
+ *
+ * Since: 1.4
+ */
+typedef enum { /*< prefix=CLUTTER_DRAG >*/
+  CLUTTER_DRAG_AXIS_NONE = 0,
+
+  CLUTTER_DRAG_X_AXIS,
+  CLUTTER_DRAG_Y_AXIS
+} ClutterDragAxis;
+
+/**
+ * ClutterDragAction:
+ *
+ * The <structname>ClutterDragAction</structname> structure contains only
+ * private data and should be accessed using the provided API
+ *
+ * Since: 1.4
+ */
+struct _ClutterDragAction
+{
+  /*< private >*/
+  ClutterAction parent_instance;
+
+  ClutterDragActionPrivate *priv;
+};
+
+/**
+ * ClutterDragActionClass:
+ * @drag_begin: class handler of the #ClutterDragAction::drag-begin signal
+ * @drag_motion: class handler of the #ClutterDragAction::drag-motion signal
+ * @drag_end: class handler of the #ClutterDragAction::drag-end signal
+ *
+ * The <structname>ClutterDragActionClass</structname> structure contains
+ * only private data
+ *
+ * Since: 1.4
+ */
+struct _ClutterDragActionClass
+{
+  /*< private >*/
+  ClutterActionClass parent_class;
+
+  /*< public >*/
+  void (* drag_begin)  (ClutterDragAction   *action,
+                        ClutterActor        *actor,
+                        gfloat               event_x,
+                        gfloat               event_y,
+                        gint                 button,
+                        ClutterModifierType  modifiers);
+  void (* drag_motion) (ClutterDragAction   *action,
+                        ClutterActor        *actor,
+                        gfloat               delta_x,
+                        gfloat               delta_y,
+                        ClutterModifierType  modifiers);
+  void (* drag_end)    (ClutterDragAction   *action,
+                        ClutterActor        *actor,
+                        gfloat               event_x,
+                        gfloat               event_y,
+                        gint                 button,
+                        ClutterModifierType  modifiers);
+
+  /*< private >*/
+  void (* _clutter_drag_action1) (void);
+  void (* _clutter_drag_action2) (void);
+  void (* _clutter_drag_action3) (void);
+  void (* _clutter_drag_action4) (void);
+  void (* _clutter_drag_action5) (void);
+};
+
+GType clutter_drag_action_get_type (void) G_GNUC_CONST;
+
+ClutterAction * clutter_drag_action_new                   (void);
+
+void            clutter_drag_action_set_drag_threshold (ClutterDragAction *action,
+                                                        guint              threshold);
+guint           clutter_drag_action_get_drag_threshold (ClutterDragAction *action);
+void            clutter_drag_action_set_drag_handle    (ClutterDragAction *action,
+                                                        ClutterActor      *handle);
+ClutterActor *  clutter_drag_action_get_drag_handle    (ClutterDragAction *action);
+void            clutter_drag_action_set_drag_axis      (ClutterDragAction *action,
+                                                        ClutterDragAxis    axis);
+ClutterDragAxis clutter_drag_action_get_drag_axis      (ClutterDragAction *action);
+
+void            clutter_drag_action_get_press_coords   (ClutterDragAction *action,
+                                                        gfloat            *press_x,
+                                                        gfloat            *press_y);
+void            clutter_drag_action_get_motion_coords  (ClutterDragAction *action,
+                                                        gfloat            *motion_x,
+                                                        gfloat            *motion_y);
+
+G_END_DECLS
+
+#endif /* __CLUTTER_DRAG_ACTION_H__ */
index 8296f9a..0ad5742 100644 (file)
@@ -10,6 +10,8 @@ VOID:INT,INT
 VOID:FLOAT,FLOAT
 VOID:INT,INT,INT,INT
 VOID:OBJECT
+VOID:OBJECT,FLOAT,FLOAT
+VOID:OBJECT,FLOAT,FLOAT,INT,FLAGS
 VOID:OBJECT,OBJECT,PARAM
 VOID:OBJECT,POINTER
 VOID:POINTER
index d69f74f..08d2088 100644 (file)
@@ -55,6 +55,7 @@
 #include "clutter-color.h"
 #include "clutter-container.h"
 #include "clutter-device-manager.h"
+#include "clutter-drag-action.h"
 #include "clutter-event.h"
 #include "clutter-feature.h"
 #include "clutter-fixed-layout.h"
index b7e6295..d45caa8 100644 (file)
@@ -48,7 +48,8 @@ UNIT_TESTS = \
        test-bin-layout.c \
        test-flow-layout.c \
        test-box-layout.c \
-       test-stage-sizing.c
+       test-stage-sizing.c \
+       test-drag.c
 
 if X11_TESTS
 UNIT_TESTS += test-pixmap.c
diff --git a/tests/interactive/test-drag.c b/tests/interactive/test-drag.c
new file mode 100644 (file)
index 0000000..e8c479c
--- /dev/null
@@ -0,0 +1,189 @@
+#include <stdlib.h>
+#include <gmodule.h>
+#include <clutter/clutter.h>
+
+static void
+on_drag_begin (ClutterDragAction   *action,
+               ClutterActor        *actor,
+               gfloat               event_x,
+               gfloat               event_y,
+               gint                 button,
+               ClutterModifierType  modifiers)
+{
+  gboolean is_copy = (modifiers & CLUTTER_SHIFT_MASK) ? TRUE : FALSE;
+  ClutterActor *drag_handle = NULL;
+
+  if (is_copy)
+    {
+      ClutterActor *stage = clutter_actor_get_stage (actor);
+      ClutterColor handle_color;
+
+      drag_handle = clutter_rectangle_new ();
+      clutter_actor_set_size (drag_handle, 48, 48);
+
+      clutter_color_from_string (&handle_color, "#204a87aa");
+      clutter_rectangle_set_color (CLUTTER_RECTANGLE (drag_handle), &handle_color);
+
+      clutter_container_add_actor (CLUTTER_CONTAINER (stage), drag_handle);
+      clutter_actor_set_position (drag_handle, event_x, event_y);
+    }
+  else
+    drag_handle = actor;
+
+  clutter_drag_action_set_drag_handle (action, drag_handle);
+
+  clutter_actor_set_opacity (actor, 128);
+}
+
+static void
+on_drag_motion (ClutterDragAction   *action,
+                ClutterActor        *actor,
+                gfloat               delta_x,
+                gfloat               delta_y,
+                ClutterModifierType  modifiers)
+{
+  ClutterActor *drag_handle;
+
+  drag_handle = clutter_drag_action_get_drag_handle (action);
+  g_assert (drag_handle != NULL);
+
+  clutter_actor_move_by (drag_handle, delta_x, delta_y);
+}
+
+static void
+on_drag_end (ClutterDragAction   *action,
+             ClutterActor        *actor,
+             gfloat               event_x,
+             gfloat               event_y,
+             gint                 button,
+             ClutterModifierType  modifiers)
+{
+  ClutterActor *drag_handle;
+
+  drag_handle = clutter_drag_action_get_drag_handle (action);
+  if (actor != drag_handle)
+    {
+      gfloat real_x, real_y;
+      ClutterActor *parent;
+
+      /* if we are dragging a copy we can destroy the copy now
+       * and animate the real actor to the drop coordinates,
+       * transformed in the parent's coordinate space
+       */
+      clutter_actor_animate (drag_handle, CLUTTER_LINEAR, 150,
+                             "opacity", 0,
+                             "signal-swapped-after::completed",
+                               G_CALLBACK (clutter_actor_destroy),
+                               drag_handle,
+                             NULL);
+
+      parent = clutter_actor_get_parent (actor);
+      clutter_actor_transform_stage_point (parent, event_x, event_y,
+                                           &real_x,
+                                           &real_y);
+
+      clutter_actor_animate (actor, CLUTTER_EASE_OUT_CUBIC, 150,
+                             "opacity", 255,
+                             "x", real_x,
+                             "y", real_y,
+                             NULL);
+    }
+  else
+    clutter_actor_animate (actor, CLUTTER_LINEAR, 150,
+                           "opacity", 255,
+                           NULL);
+}
+
+static ClutterDragAxis
+get_drag_axis (const gchar *str)
+{
+  if (str == NULL || *str == '\0')
+    return CLUTTER_DRAG_AXIS_NONE;
+
+  if (*str == 'x' || *str == 'X')
+    return CLUTTER_DRAG_X_AXIS;
+
+  if (*str == 'y' || *str == 'Y')
+    return CLUTTER_DRAG_Y_AXIS;
+
+  g_warn_if_reached ();
+
+  return CLUTTER_DRAG_AXIS_NONE;
+}
+
+static gchar *drag_axis = NULL;
+static gint drag_threshold = 0;
+
+static GOptionEntry entries[] = {
+  {
+    "threshold", 't',
+    0,
+    G_OPTION_ARG_INT,
+    &drag_threshold,
+    "Set the drag threshold", "PIXELS"
+  },
+  {
+    "axis", 'a',
+    0,
+    G_OPTION_ARG_STRING,
+    &drag_axis,
+    "Set the drag axis", "AXIS"
+  },
+
+  { NULL }
+};
+
+G_MODULE_EXPORT int
+test_drag_main (int argc, char *argv[])
+{
+  ClutterActor *stage, *handle;
+  ClutterAction *action;
+  ClutterColor handle_color;
+  GError *error;
+
+  error = NULL;
+  clutter_init_with_args (&argc, &argv,
+                          "test-drag",
+                          entries,
+                          NULL,
+                          &error);
+  if (error != NULL)
+    {
+      g_print ("Unable to run test-drag: %s\n", error->message);
+      g_error_free (error);
+
+      return EXIT_FAILURE;
+    }
+
+  stage = clutter_stage_new ();
+  clutter_stage_set_title (CLUTTER_STAGE (stage), "Drag Test");
+  clutter_actor_set_size (stage, 800, 600);
+  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
+
+  clutter_color_from_string (&handle_color, "#729fcfff");
+
+  handle = clutter_rectangle_new ();
+  clutter_rectangle_set_color (CLUTTER_RECTANGLE (handle), &handle_color);
+  clutter_actor_set_size (handle, 128, 128);
+  clutter_actor_set_position (handle, (800 - 128) / 2, (600 - 128) / 2);
+  clutter_actor_set_reactive (handle, TRUE);
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage), handle);
+
+  action = clutter_drag_action_new ();
+  clutter_drag_action_set_drag_threshold (CLUTTER_DRAG_ACTION (action),
+                                          drag_threshold);
+  clutter_drag_action_set_drag_axis (CLUTTER_DRAG_ACTION (action),
+                                     get_drag_axis (drag_axis));
+
+  g_signal_connect (action, "drag-begin", G_CALLBACK (on_drag_begin), NULL);
+  g_signal_connect (action, "drag-motion", G_CALLBACK (on_drag_motion), NULL);
+  g_signal_connect (action, "drag-end", G_CALLBACK (on_drag_end), NULL);
+
+  clutter_actor_add_action (handle, action);
+
+  clutter_actor_show (stage);
+
+  clutter_main ();
+
+  return EXIT_SUCCESS;
+}