From 9ca06d2895154eb2c985b85df186db3ade1a5e1e Mon Sep 17 00:00:00 2001 From: Emanuele Aina Date: Sat, 25 Aug 2012 16:23:23 +0200 Subject: [PATCH] pan-action: add PanAction, to handle panning in scrollable actors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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(+) create mode 100644 clutter/clutter-pan-action.c create mode 100644 clutter/clutter-pan-action.h create mode 100644 examples/pan-action.c diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 7d479b278..100c1f551 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 1110ebaa4..7682ff516 100644 --- a/clutter/clutter-enums.h +++ b/clutter/clutter-enums.h @@ -1002,6 +1002,25 @@ typedef enum { /*< prefix=CLUTTER_SWIPE_DIRECTION >*/ CLUTTER_SWIPE_DIRECTION_RIGHT = 1 << 3 } 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 diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list index 681b4e746..7eff755a9 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 000000000..30e860eba --- /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 . + * + * Author: + * Emanuele Aina + * + * Based on ClutterDragAction, ClutterSwipeAction, and MxKineticScrollView, + * written by: + * Emmanuele Bassi + * Tomeu Vizoso + * Chris Lord + */ + +/** + * 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 + +#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 000000000..10ed67686 --- /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 . + * + * Author: + * Emanuele Aina + * + * Based on ClutterDragAction, ClutterSwipeAction, and MxKineticScrollView, + * written by: + * Emmanuele Bassi + * Tomeu Vizoso + * Chris Lord + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_PAN_ACTION_H__ +#define __CLUTTER_PAN_ACTION_H__ + +#include + +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 ClutterPanAction 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 ClutterPanActionClass 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 e5bd24d8b..76a94bc7a 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 9991579eb..6afa7d34a 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 de1fa4fae..08aa2494c 100644 --- a/doc/reference/clutter/clutter-docs.xml.in +++ b/doc/reference/clutter/clutter-docs.xml.in @@ -96,6 +96,7 @@ + diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index 72b788965..5513348aa 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 + +
+clutter-pan-action +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 + +clutter_pan_action_get_interpolated_coords +clutter_pan_action_get_interpolated_delta + +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 + +ClutterPanActionPrivate +clutter_pan_action_get_type +
diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types index 5b640b8e2..cc6d8b2c9 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 3d9bdb5c2..4c6998f64 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 000000000..2eb382d64 --- /dev/null +++ b/examples/pan-action.c @@ -0,0 +1,127 @@ +#include +#include +#include + +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 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; +}