/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2010 Intel Corporation.
* Copyright (C) 2011 Robert Bosch Car Multimedia GmbH.
* Copyright (C) 2012, 2014 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
*/
/**
* ClutterPanAction:
*
* Action for pan gestures
*
* #ClutterPanAction is a sub-class of [class@GestureAction] that implements
* the logic for recognizing pan gestures.
*
* The simplest usage of #ClutterPanAction consists in adding it to
* a [class@Actor] with a child and setting it as reactive; for instance,
* the following code:
*
* ```c
* 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.
*/
#include "config.h"
#include "clutter/clutter-pan-action.h"
#include "clutter/clutter-debug.h"
#include "clutter/clutter-enum-types.h"
#include "clutter/clutter-marshal.h"
#include "clutter/clutter-private.h"
#include "clutter/clutter-timeline.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;
typedef enum
{
SCROLL_PINNED_UNKNOWN,
SCROLL_PINNED_NONE,
SCROLL_PINNED_HORIZONTAL,
SCROLL_PINNED_VERTICAL
} PinState;
typedef 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;
PinState pin_state;
} ClutterPanActionPrivate;
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_WITH_PRIVATE (ClutterPanAction, clutter_pan_action,
CLUTTER_TYPE_GESTURE_ACTION)
static void
emit_pan (ClutterPanAction *self,
ClutterActor *actor,
gboolean is_interpolated)
{
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
gboolean retval;
if (priv->pin_state == SCROLL_PINNED_UNKNOWN)
{
priv->pin_state = SCROLL_PINNED_NONE;
if (priv->pan_axis == CLUTTER_PAN_AXIS_AUTO)
{
gfloat delta_x;
gfloat delta_y;
gfloat scroll_threshold = G_PI_4/2;
gfloat drag_angle;
clutter_gesture_action_get_motion_delta (CLUTTER_GESTURE_ACTION (self),
0,
&delta_x,
&delta_y);
if (delta_x != 0.0f)
drag_angle = atanf (delta_y / delta_x);
else
drag_angle = G_PI_2;
if ((drag_angle > -scroll_threshold) &&
(drag_angle < scroll_threshold))
priv->pin_state = SCROLL_PINNED_HORIZONTAL;
else if ((drag_angle > (G_PI_2 - scroll_threshold)) ||
(drag_angle < -(G_PI_2 - scroll_threshold)))
priv->pin_state = SCROLL_PINNED_VERTICAL;
}
}
g_signal_emit (self, pan_signals[PAN], 0, actor, is_interpolated, &retval);
}
static void
emit_pan_stopped (ClutterPanAction *self,
ClutterActor *actor)
{
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
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 =
clutter_pan_action_get_instance_private (self);
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 =
clutter_pan_action_get_instance_private (self);
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_prepare (ClutterGestureAction *gesture,
ClutterActor *actor)
{
ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
if (priv->state == PAN_STATE_INTERPOLATING && priv->deceleration_timeline)
clutter_timeline_stop (priv->deceleration_timeline);
return TRUE;
}
static gboolean
gesture_begin (ClutterGestureAction *gesture,
ClutterActor *actor)
{
ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
priv->pin_state = SCROLL_PINNED_UNKNOWN;
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 =
clutter_pan_action_get_instance_private (self);
priv->state = PAN_STATE_INACTIVE;
}
static void
gesture_end (ClutterGestureAction *gesture,
ClutterActor *actor)
{
ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
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)
{
ClutterActor *pan_actor =
clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (gesture));
priv->interpolated_x = priv->interpolated_y = 0.0f;
priv->deceleration_timeline = clutter_timeline_new_for_actor (pan_actor,
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 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 =
clutter_pan_action_get_instance_private (self);
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_constructed (GObject *gobject)
{
ClutterGestureAction *gesture;
ClutterGestureTriggerEdge edge;
gesture = CLUTTER_GESTURE_ACTION (gobject);
edge = CLUTTER_GESTURE_TRIGGER_EDGE_AFTER;
clutter_gesture_action_set_threshold_trigger_edge (gesture, edge);
}
static void
clutter_pan_action_dispose (GObject *gobject)
{
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (CLUTTER_PAN_ACTION (gobject));
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 =
clutter_pan_action_get_instance_private (self);
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);
else if (priv->deceleration_timeline)
clutter_timeline_set_actor (priv->deceleration_timeline, actor);
}
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);
gesture_class->gesture_prepare = gesture_prepare;
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
*/
pan_props[PROP_PAN_AXIS] =
g_param_spec_enum ("pan-axis", NULL, NULL,
CLUTTER_TYPE_PAN_AXIS,
CLUTTER_PAN_AXIS_NONE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
/**
* ClutterPanAction:interpolate:
*
* Whether interpolated events emission is enabled.
*/
pan_props[PROP_INTERPOLATE] =
g_param_spec_boolean ("interpolate", NULL, NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
/**
* 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.
*/
pan_props[PROP_DECELERATION] =
g_param_spec_double ("deceleration", NULL, NULL,
FLOAT_EPSILON, 1.0, default_deceleration_rate,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
/**
* 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.
*/
pan_props[PROP_ACCELERATION_FACTOR] =
g_param_spec_double ("acceleration-factor", NULL, NULL,
1.0, G_MAXDOUBLE, default_acceleration_factor,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
gobject_class->constructed = clutter_pan_action_constructed;
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 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 should continue, and %FALSE if
* the pan should be cancelled.
*/
pan_signals[PAN] =
g_signal_new (I_("pan"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0, g_signal_accumulator_true_handled, 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 signal is emitted at the end of the interpolation
* phase of the pan action, only when :interpolate is set to %TRUE.
*/
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, NULL,
G_TYPE_NONE, 1,
CLUTTER_TYPE_ACTOR);
}
static void
clutter_pan_action_init (ClutterPanAction *self)
{
ClutterPanActionPrivate *priv =
clutter_pan_action_get_instance_private (self);
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
*/
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
*/
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_AXIS_AUTO);
priv = clutter_pan_action_get_instance_private (self);
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 [method@PanAction.set_pan_axis]
*
* Return value: the axis constraint
*/
ClutterPanAxis
clutter_pan_action_get_pan_axis (ClutterPanAction *self)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self),
CLUTTER_PAN_AXIS_NONE);
priv = clutter_pan_action_get_instance_private (self);
return 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.
*/
void
clutter_pan_action_set_interpolate (ClutterPanAction *self,
gboolean should_interpolate)
{
ClutterPanActionPrivate *priv;
g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
priv = clutter_pan_action_get_instance_private (self);
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.
*/
gboolean
clutter_pan_action_get_interpolate (ClutterPanAction *self)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self),
FALSE);
priv = clutter_pan_action_get_instance_private (self);
return 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.
*/
void
clutter_pan_action_set_deceleration (ClutterPanAction *self,
gdouble rate)
{
ClutterPanActionPrivate *priv;
g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
g_return_if_fail (rate <= 1.0);
g_return_if_fail (rate > 0.0);
priv = clutter_pan_action_get_instance_private (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.
*/
gdouble
clutter_pan_action_get_deceleration (ClutterPanAction *self)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 0.95);
priv = clutter_pan_action_get_instance_private (self);
return 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.
*/
void
clutter_pan_action_set_acceleration_factor (ClutterPanAction *self,
gdouble factor)
{
ClutterPanActionPrivate *priv;
g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
g_return_if_fail (factor >= 0.0);
priv = clutter_pan_action_get_instance_private (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.
*/
gdouble
clutter_pan_action_get_acceleration_factor (ClutterPanAction *self)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 1.0);
priv = clutter_pan_action_get_instance_private (self);
return 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 [method@GestureAction.get_motion_coords].
*/
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 = clutter_pan_action_get_instance_private (self);
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 [method@GestureAction.get_motion_delta].
*
* Return value: the distance since the latest interpolated event
*/
gfloat
clutter_pan_action_get_interpolated_delta (ClutterPanAction *self,
gfloat *delta_x,
gfloat *delta_y)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 0.0f);
priv = clutter_pan_action_get_instance_private (self);
if (delta_x)
*delta_x = priv->dx;
if (delta_y)
*delta_y = priv->dy;
return sqrt ((priv->dx * priv->dx) + (priv->dy * priv->dy));
}
/**
* clutter_pan_action_get_constrained_motion_delta:
* @self: A #ClutterPanAction
* @point: the touch point index, with 0 being the first touch
* point received by the action
* @delta_x: (out) (optional): return location for the X delta
* @delta_y: (out) (optional): return location for the Y delta
*
* Retrieves the delta, in stage space, dependent on the current state
* of the #ClutterPanAction, and respecting the constraint specified by the
* [property@PanAction:pan-axis] property.
*
* Return value: the distance since last motion event4
*/
gfloat
clutter_pan_action_get_constrained_motion_delta (ClutterPanAction *self,
guint point,
gfloat *out_delta_x,
gfloat *out_delta_y)
{
ClutterPanActionPrivate *priv;
gfloat delta_x = 0.f, delta_y = 0.f, distance;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 0.0f);
priv = clutter_pan_action_get_instance_private (self);
distance = clutter_pan_action_get_motion_delta (self, point,
&delta_x,
&delta_y);
switch (priv->pan_axis)
{
case CLUTTER_PAN_AXIS_NONE:
break;
case CLUTTER_PAN_AXIS_AUTO:
if (priv->pin_state == SCROLL_PINNED_VERTICAL)
delta_x = 0.0f;
else if (priv->pin_state == SCROLL_PINNED_HORIZONTAL)
delta_y = 0.0f;
break;
case CLUTTER_PAN_X_AXIS:
delta_y = 0.0f;
break;
case CLUTTER_PAN_Y_AXIS:
delta_x = 0.0f;
break;
}
if (out_delta_x)
*out_delta_x = delta_x;
if (out_delta_y)
*out_delta_y = delta_y;
return distance;
}
/**
* clutter_pan_action_get_motion_delta:
* @self: A #ClutterPanAction
* @point: the touch point index, with 0 being the first touch
* point received by the action
* @delta_x: (out) (allow-none): return location for the X delta
* @delta_y: (out) (allow-none): return location for the Y delta
*
* Retrieves the delta, in stage space, dependent on the current state
* of the #ClutterPanAction. If it is inactive, both fields will be
* set to 0. If it is panning by user action, the values will be equivalent
* to those returned by [method@GestureAction.get_motion_delta].
* If it is interpolating with some form of kinetic scrolling, the values
* will be equivalent to those returned by
* [method@PanAction.get_interpolated_delta]. This is a convenience
* method designed to be used in replacement "pan" signal handlers.
*/
gfloat
clutter_pan_action_get_motion_delta (ClutterPanAction *self,
guint point,
gfloat *delta_x,
gfloat *delta_y)
{
ClutterPanActionPrivate *priv;
g_return_val_if_fail (CLUTTER_IS_PAN_ACTION (self), 0.0f);
priv = clutter_pan_action_get_instance_private (self);
switch (priv->state)
{
case PAN_STATE_INACTIVE:
if (delta_x)
*delta_x = 0;
if (delta_y)
*delta_y = 0;
return 0;
case PAN_STATE_PANNING:
return clutter_gesture_action_get_motion_delta (CLUTTER_GESTURE_ACTION (self),
point, delta_x, delta_y);
case PAN_STATE_INTERPOLATING:
return clutter_pan_action_get_interpolated_delta (self, delta_x, delta_y);
default:
g_assert_not_reached ();
return 0.0f;
}
}
/**
* clutter_pan_action_get_motion_coords:
* @self: A #ClutterPanAction
* @point: the touch point index, with 0 being the first touch
* point received by the action
* @motion_x: (out) (allow-none): return location for the X coordinate
* @motion_y: (out) (allow-none): return location for the Y coordinate
*
* Retrieves the coordinates, in stage space, dependent on the current state
* of the #ClutterPanAction. If it is inactive, both fields will be
* set to 0. If it is panning by user action, the values will be equivalent
* to those returned by [method@GestureAction.get_motion_coords].
* If it is interpolating with some form of kinetic scrolling, the values
* will be equivalent to those returned by
* [method@PanAction.get_interpolated_coords]. This is a convenience
* method designed to be used in replacement "pan" signal handlers.
*/
void
clutter_pan_action_get_motion_coords (ClutterPanAction *self,
guint point,
gfloat *motion_x,
gfloat *motion_y)
{
ClutterPanActionPrivate *priv;
g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
priv = clutter_pan_action_get_instance_private (self);
switch (priv->state)
{
case PAN_STATE_INACTIVE:
if (motion_x)
*motion_x = 0;
if (motion_y)
*motion_y = 0;
break;
case PAN_STATE_PANNING:
clutter_gesture_action_get_motion_coords (CLUTTER_GESTURE_ACTION (self),
point, motion_x, motion_y);
break;
case PAN_STATE_INTERPOLATING:
clutter_pan_action_get_interpolated_coords (self, motion_x, motion_y);
break;
default:
g_assert_not_reached ();
}
}