Add PanAxis mode that automatically pins scroll based on initial movement

This code is inspired by the implementation of the same feature for the
Mx toolkit's MxKineticScrollView. See commit 4d08771.

https://bugzilla.gnome.org/show_bug.cgi?id=707982
This commit is contained in:
Gustavo Noronha Silva 2013-09-11 12:33:57 -03:00 committed by Gustavo Noronha Silva
parent 2105055a34
commit 0c75e17814
5 changed files with 167 additions and 23 deletions

View File

@ -1018,6 +1018,8 @@ typedef enum { /*< prefix=CLUTTER_SWIPE_DIRECTION >*/
* @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
* @CLUTTER_PAN_AXIS_AUTO: Constrain panning automatically based on initial
* movement (available since 1.24)
*
* The axis of the constraint that should be applied on the
* panning action
@ -1028,7 +1030,9 @@ typedef enum { /*< prefix=CLUTTER_PAN >*/
CLUTTER_PAN_AXIS_NONE = 0,
CLUTTER_PAN_X_AXIS,
CLUTTER_PAN_Y_AXIS
CLUTTER_PAN_Y_AXIS,
CLUTTER_PAN_AXIS_AUTO
} ClutterPanAxis;

View File

@ -5,7 +5,7 @@
*
* Copyright (C) 2010 Intel Corporation.
* Copyright (C) 2011 Robert Bosch Car Multimedia GmbH.
* Copyright (C) 2012 Collabora Ltd.
* 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
@ -80,6 +80,14 @@ typedef enum
PAN_STATE_INTERPOLATING
} PanState;
typedef enum
{
SCROLL_PINNED_UNKNOWN,
SCROLL_PINNED_NONE,
SCROLL_PINNED_HORIZONTAL,
SCROLL_PINNED_VERTICAL
} PinState;
struct _ClutterPanActionPrivate
{
ClutterPanAxis pan_axis;
@ -102,6 +110,8 @@ struct _ClutterPanActionPrivate
gfloat release_y;
guint should_interpolate : 1;
PinState pin_state;
};
enum
@ -135,7 +145,34 @@ emit_pan (ClutterPanAction *self,
ClutterActor *actor,
gboolean is_interpolated)
{
ClutterPanActionPrivate *priv = self->priv;
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);
}
@ -207,6 +244,7 @@ gesture_begin (ClutterGestureAction *gesture,
ClutterPanAction *self = CLUTTER_PAN_ACTION (gesture);
ClutterPanActionPrivate *priv = self->priv;
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;
@ -297,25 +335,10 @@ clutter_pan_action_real_pan (ClutterPanAction *self,
ClutterActor *actor,
gboolean is_interpolated)
{
ClutterPanActionPrivate *priv = self->priv;
gfloat dx, dy;
ClutterMatrix transform;
clutter_pan_action_get_motion_delta (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_pan_action_get_constrained_motion_delta (self, 0, &dx, &dy);
clutter_actor_get_child_transform (actor, &transform);
cogl_matrix_translate (&transform, dx, dy, 0.0f);
@ -605,7 +628,7 @@ clutter_pan_action_set_pan_axis (ClutterPanAction *self,
g_return_if_fail (CLUTTER_IS_PAN_ACTION (self));
g_return_if_fail (axis >= CLUTTER_PAN_AXIS_NONE &&
axis <= CLUTTER_PAN_Y_AXIS);
axis <= CLUTTER_PAN_AXIS_AUTO);
priv = self->priv;
@ -831,6 +854,67 @@ clutter_pan_action_get_interpolated_delta (ClutterPanAction *self,
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
* #ClutterPanAction:pan-axis property.
*
* Return value: the distance since last motion event
*
* Since: 1.24
*/
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 = self->priv;
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

View File

@ -142,6 +142,11 @@ void clutter_pan_action_get_motion_coords (ClutterPanAction *s
guint point,
gfloat *motion_x,
gfloat *motion_y);
CLUTTER_AVAILABLE_IN_1_24
gfloat clutter_pan_action_get_constrained_motion_delta (ClutterPanAction *self,
guint point,
gfloat *delta_x,
gfloat *delta_y);
G_END_DECLS
#endif /* __CLUTTER_PAN_ACTION_H__ */

View File

@ -3566,6 +3566,7 @@ clutter_pan_action_get_interpolated_coords
clutter_pan_action_get_interpolated_delta
clutter_pan_action_get_motion_coords
clutter_pan_action_get_motion_delta
clutter_pan_action_get_constrained_motion_delta
<SUBSECTION Standard>
CLUTTER_IS_PAN_ACTION
CLUTTER_IS_PAN_ACTION_CLASS

View File

@ -83,7 +83,7 @@ create_scroll_actor (ClutterActor *stage)
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_add_action_with_name (scroll, "pan", pan_action);
clutter_actor_set_reactive (scroll, TRUE);
@ -113,10 +113,45 @@ on_key_press (ClutterActor *stage,
return CLUTTER_EVENT_STOP;
}
static gboolean
label_clicked_cb (ClutterText *label, ClutterEvent *event, ClutterActor *scroll)
{
ClutterPanAction *action = CLUTTER_PAN_ACTION (clutter_actor_get_action (scroll, "pan"));
const gchar *label_text = clutter_text_get_text (label);
if (g_str_equal (label_text, "X AXIS"))
clutter_pan_action_set_pan_axis (action, CLUTTER_PAN_X_AXIS);
else if (g_str_equal (label_text, "Y AXIS"))
clutter_pan_action_set_pan_axis (action, CLUTTER_PAN_Y_AXIS);
else if (g_str_equal (label_text, "AUTO"))
clutter_pan_action_set_pan_axis (action, CLUTTER_PAN_AXIS_AUTO);
else
clutter_pan_action_set_pan_axis (action, CLUTTER_PAN_AXIS_NONE);
return TRUE;
}
static void
add_label (const gchar *text, ClutterActor *box, ClutterActor *scroll)
{
ClutterActor *label;
label = clutter_text_new_with_text (NULL, text);
clutter_actor_set_reactive (label, TRUE);
clutter_actor_set_x_align (label, CLUTTER_ACTOR_ALIGN_START);
clutter_actor_set_x_expand (label, TRUE);
clutter_actor_add_child (box, label);
g_signal_connect (label, "button-release-event",
G_CALLBACK (label_clicked_cb), scroll);
}
int
main (int argc, char *argv[])
{
ClutterActor *stage, *scroll, *info;
ClutterActor *stage, *scroll, *box, *info;
ClutterLayoutManager *layout;
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
return EXIT_FAILURE;
@ -129,9 +164,24 @@ main (int argc, char *argv[])
scroll = create_scroll_actor (stage);
clutter_actor_add_child (stage, scroll);
box = clutter_actor_new ();
clutter_actor_add_child (stage, box);
clutter_actor_set_position (box, 12, 12);
layout = clutter_box_layout_new ();
clutter_box_layout_set_orientation (CLUTTER_BOX_LAYOUT (layout), CLUTTER_ORIENTATION_VERTICAL);
clutter_actor_set_layout_manager (box, layout);
info = clutter_text_new_with_text (NULL, "Press <space> to reset the image position.");
clutter_actor_add_child (stage, info);
clutter_actor_set_position (info, 12, 12);
clutter_actor_add_child (box, info);
info = clutter_text_new_with_text (NULL, "Click labels below to change AXIS pinning.");
clutter_actor_add_child (box, info);
add_label ("NONE", box, scroll);
add_label ("X AXIS", box, scroll);
add_label ("Y AXIS", box, scroll);
add_label ("AUTO", box, scroll);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
g_signal_connect (stage, "key-press-event", G_CALLBACK (on_key_press), scroll);