After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 681648 - add Pan action
add Pan action
Status: RESOLVED FIXED
Product: clutter
Classification: Platform
Component: ClutterAction
git master
Other Linux
: Normal enhancement
: ---
Assigned To: clutter-maint
clutter-maint
Depends on:
Blocks:
 
 
Reported: 2012-08-11 11:43 UTC by Emmanuele Bassi (:ebassi)
Modified: 2012-08-28 13:15 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
pan-action: add PanAction, to handle panning in scrollable actors (23.01 KB, patch)
2012-08-17 15:41 UTC, Emanuele Aina
needs-work Details | Review
pan-action: add inertial/kinetic events (28.44 KB, patch)
2012-08-17 15:41 UTC, Emanuele Aina
needs-work Details | Review
Get rid of GTimeVal and use g_get_monotonic_time() (28.14 KB, patch)
2012-08-17 18:18 UTC, Emanuele Aina
none Details | Review
gesture-action: add allow-none annotations to getters (2.46 KB, patch)
2012-08-25 15:04 UTC, Emanuele Aina
committed Details | Review
gesture-action: add _get_motion_delta()/_get_velocity() (9.55 KB, patch)
2012-08-25 15:04 UTC, Emanuele Aina
none Details | Review
pan-action: add PanAction, to handle panning in scrollable actors (37.63 KB, patch)
2012-08-25 15:05 UTC, Emanuele Aina
none Details | Review
Oops, I left two references to ::pan-end around, fixed. (37.64 KB, patch)
2012-08-25 15:17 UTC, Emanuele Aina
none Details | Review
pan-action: add PanAction, to handle panning in scrollable actors (37.64 KB, patch)
2012-08-27 11:26 UTC, Emanuele Aina
none Details | Review
pan-action: add PanAction, to handle panning in scrollable actors (41.77 KB, patch)
2012-08-27 13:40 UTC, Emanuele Aina
reviewed Details | Review
gesture-action: add _get_motion_delta()/_get_velocity() (10.89 KB, patch)
2012-08-27 13:43 UTC, Emanuele Aina
committed Details | Review
pan-action: add PanAction, to handle panning in scrollable actors (41.68 KB, patch)
2012-08-27 14:56 UTC, Emanuele Aina
committed Details | Review

Description Emmanuele Bassi (:ebassi) 2012-08-11 11:43:21 UTC
people have started using DragAction as a way to pan an actor around (e.g. an actor with large content inside a ScrollActor). DragAction is sub-optimal for this role:

  - it does not implement GestureAction;
  - it has lots of logic for dragging children around;
  - it integrates with DropAction.

in short, DragAction should be used for intra-window rearranging of actors.

for panning, especially in touch-based environments, we are going to need a PanAction - something that is just meant to translate an actor within its parent. in turn, a PanAction can implement GestureAction's contract - including touch points handling - and accrue features like momentum and acceleration.
Comment 1 Emanuele Aina 2012-08-17 15:41:05 UTC
Created attachment 221622 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

PanAction is a GestureAction-subclass that implements the panning
concept for scrollable actors. PanAction provides:

• pan-begin and pan-end signals, to mark the event sequence boundaries;

• pan-progress signal, reporting the incremental motion delta to
  user code;

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

An interactive test demonstrating its usage with ScrollActor also provided.
Comment 2 Emanuele Aina 2012-08-17 15:41:19 UTC
Created attachment 221623 [details] [review]
pan-action: add inertial/kinetic events

Port the kinetic scroll logic from MxKineticScrollView to generate
fake progress events with decreasing delta after the user pointer
has been released. PanAction now provides:

• pan-inertial-begin signal to divide the standard ::pan-progress
  events from the ::pan-inertial-progress event sequence;

• pan-inertial-progress signal, analogous to ::pan-progress, to notify
  user code of the decreasing inertial momentum;

• inertial property, to enable or disable the inertial behaviour;

• deceleration property, to customize the rate at which the inertial
  panning will slow down;

• acceleration-factor property, applied to the inertial momentum when
  starting the ::pan-inertial-progress sequence.

The pan-action example has been updated to make use of inertial events.
Comment 3 Emmanuele Bassi (:ebassi) 2012-08-17 15:48:36 UTC
Review of attachment 221622 [details] [review]:

looks good, except the reimplementation of the GestureAction signals, which I think it's wrong.

::: clutter/clutter-pan-action.c
@@ +272,3 @@
+   * Since: 1.12
+   */
+  pan_signals[PAN_BEGIN] =

I don't think this is necessary at all, and doesn't match the other actions. you can use the gesture-begin signal.

@@ +292,3 @@
+   * Since: 1.12
+   */
+  pan_signals[PAN_PROGRESS] =

same as above: this can be gesture-progress.

if you want to have a signal emission, I'd use something like ::pan-update, though I'd probably just use a property with the gesture distance, and then ask users to connect to notify::pan-distance.

@@ +314,3 @@
+   * Since: 1.12
+   */
+  pan_signals[PAN_END] =

and, again, this can be gesture-end.
Comment 4 Emmanuele Bassi (:ebassi) 2012-08-17 15:59:45 UTC
Review of attachment 221623 [details] [review]:

this is looking a bit more complicated than I ever intended.

I'd expect users of this gesture to connect to ::gesture-progress and ask for the distance and velocity of the panning, or use a notification on a property to translate the actor. to be fair, tough, I'd make the actor associated with the gesture simply move on ::gesture-progress (with a cumulative velocity factor for multiple pan gestures) and then start an interpolation on ::gesture-end either using a deceleration factor or using an easing mode with a computed end value depending on the velocity.

::: clutter/clutter-pan-action.c
@@ +290,3 @@
+
+  /* Get time delta */
+  g_get_current_time (&release_time);

I'd use the event time here, not the wallclock; the wallclock also can go backwards, for instance.

@@ +295,3 @@
+  priv->last_motion ++;
+  x_origin = y_origin = 0;
+  motion_time = (GTimeVal){ 0, 0 };

this is a GCC extension; also, don't use GTimeVal for time keeping: use gint64.

@@ +720,3 @@
+   * Since: 1.12
+   */
+  pan_signals[PAN_INERTIAL_PROGRESS] =

again, these are far too many signals; you can use a boolean flag to distinguish between an inertial pan gesture and a regular one.
Comment 5 Emanuele Aina 2012-08-17 17:26:41 UTC
(In reply to comment #3)

> +  pan_signals[PAN_BEGIN] =
> 
> I don't think this is necessary at all, and doesn't match the other actions.
> you can use the gesture-begin signal.

I see. My only concern is that, as I envisioned it, the ::pan-end signal should be emitted after the inertial phase (if present), extending the duration of the gesture even after the user has released the pointer.

To be clearer, the current flow is something like that (with the inertial behaviour enabled):

::gesture-begin → ::pan-begin
::gesture-progress → ::pan-progress
::gesture-end → ::pan-inertial-begin
::new_frame (from the deceleration timeline) → ::pan-inertial-progress
::stopped (from the deceleration timeline) → ::pan-end

Also, I deemed more useful a ::pan-inertial-begin signal vs. a boolean on ::pan-progress because otherwise you need to wait for the next frame to know that the real panning has ended: eg. on reaching the upper limit of a elastic scrollable view you just ignore inertial event and snap the scrollable content as soon as you're sure the user has released the pointer, instead of waiting for the signal generated on ::new_frame to see that the boolean has changed (it also reduce a bit of state-tracking for the user, which I think is a good thing).

I'm not sure what you are proposing instead: can you please elaborate a bit on your proposed solution?
For the non-inertial case using the ::gesture-* should be straightforward, but what should I do in the inertial case? Should I stop the emission of ::gesture-end (with g_signal_stop_emission_by_name()) and generate ::gesture-progress events? Should I update the _gesture_motion_coords() with fake values or should I provide a PanAction-specific method (the 'pan-distance' property)?
Comment 6 Emanuele Aina 2012-08-17 17:49:48 UTC
(In reply to comment #4)
> this is looking a bit more complicated than I ever intended.

:(
 
> I'd expect users of this gesture to connect to ::gesture-progress and ask for
> the distance and velocity of the panning, or use a notification on a property
> to translate the actor. to be fair, tough, I'd make the actor associated with
> the gesture simply move on ::gesture-progress (with a cumulative velocity
> factor for multiple pan gestures) and then start an interpolation on
> ::gesture-end either using a deceleration factor or using an easing mode with a
> computed end value depending on the velocity.

So you'd move the actor on ::gesture-progress and then just keep moving it after ::gesture-end? What if I want to override the action (maybe because I already have a scrollable container)?

> ::: clutter/clutter-pan-action.c
> @@ +290,3 @@
> +
> +  /* Get time delta */
> +  g_get_current_time (&release_time);
> 
> I'd use the event time here, not the wallclock; the wallclock also can go
> backwards, for instance.

Yup, absolutely. Got it from MxKineticScrollView but I've replaced it with a gint64 and g_get_monotonic_time() (in the ::gesture-progress/end handler I have no event to get the time from).

> @@ +295,3 @@
> +  priv->last_motion ++;
> +  x_origin = y_origin = 0;
> +  motion_time = (GTimeVal){ 0, 0 };
> 
> this is a GCC extension; also, don't use GTimeVal for time keeping: use gint64.

Done.

> @@ +720,3 @@
> +   * Since: 1.12
> +   */
> +  pan_signals[PAN_INERTIAL_PROGRESS] =
> 
> again, these are far too many signals; you can use a boolean flag to
> distinguish between an inertial pan gesture and a regular one.

As I said, I'd rather have a signal that immediately marks the end of the proper pan events sequence, both to not lose frames and to minimize the state-tracking for users, but if you prefer I would be fine without it.

Thanks for the quick response!
Comment 7 Emanuele Aina 2012-08-17 18:18:21 UTC
Created attachment 221663 [details] [review]
Get rid of GTimeVal and use g_get_monotonic_time()
Comment 8 Emanuele Aina 2012-08-25 15:04:21 UTC
Created attachment 222399 [details] [review]
gesture-action: add allow-none annotations to getters
Comment 9 Emanuele Aina 2012-08-25 15:04:31 UTC
Created attachment 222400 [details] [review]
gesture-action: add _get_motion_delta()/_get_velocity()

Add some accessors to simplify common tasks for GestureAction users:

• clutter_gesture_action_get_motion_delta() to get the delta
  on the X and Y axis in stage coordinates since the last motion
  event, and the scalar distance travelled;

• clutter_gesture_action_get_velocity() to get an estimate of the
  speed of the last motion event along the X and Y axis and as a
  scalar value in pixels per millisecond.
Comment 10 Emanuele Aina 2012-08-25 15:05:00 UTC
Created attachment 222401 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

PanAction is a GestureAction-subclass that implements the panning
concept for scrollable actors, with the ability to emit interpolated
signals to emulate the kinetic inertia of the panning. PanAction provides:

• pan signal, notifying users of the panning gesture status;

• pan-stopped signal, emitted at the end of the interpolated phase
  of the panning gesture, if enabled;

• pan-axis property, to allow constraining the dragging to a specific
  axis;

• interpolated property, to enable or disable the inertial behaviour;

• deceleration property, to customize the rate at which the momentum
  of the panning will be slowed down;

• acceleration-factor property, applied to the inertial momentum when
  starting the interpolated sequence.

An interactive test is also provided.
Comment 11 Emanuele Aina 2012-08-25 15:15:10 UTC
I've reworked the PanAction patches:
• handle motion delta and velocity in GestureAction (naive implementation, not using a motion buffer);
• reduce the signals to just ::pan and ::pan-stopped (the latter only if interpolating);
• estimate duration and target position of the inertial movement and then just use the progress of the timeline for the motion coordinates;
• add clutter_pan_action_real_pan() to move the actor content on ::pan.

For the latter the following code doesn't seem to work as I thought, what should I do to move the actor contents?

clutter/clutter-pan-action.c:298 
  clutter_actor_get_child_transform (actor, &transform);
  cogl_matrix_translate (&transform, dx, dy, 0.0f);
  clutter_actor_set_child_transform (actor, &transform);
Comment 12 Emanuele Aina 2012-08-25 15:17:42 UTC
Created attachment 222405 [details] [review]
Oops, I left two references to ::pan-end around, fixed.
Comment 13 Emanuele Aina 2012-08-27 11:26:47 UTC
Created attachment 222528 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

Fixed the return value of the ::pan handler in the example program.
Comment 14 Emanuele Aina 2012-08-27 11:30:56 UTC
For future reference, the _set_child_transform() issue has been solved by http://git.gnome.org/browse/clutter/commit/?id=3937a7
Comment 15 Emanuele Aina 2012-08-27 13:40:15 UTC
Created attachment 222554 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

Added a missing documentation document and wired up gtk-doc.
Comment 16 Emanuele Aina 2012-08-27 13:43:08 UTC
Created attachment 222555 [details] [review]
gesture-action: add _get_motion_delta()/_get_velocity()

Add symbols to export list and gtk-doc.
Comment 17 Emmanuele Bassi (:ebassi) 2012-08-27 14:05:58 UTC
Review of attachment 222399 [details] [review]:

AFAIR, this should not be needed except maybe for Vala - but I guess it's perfectly fine.
Comment 18 Emmanuele Bassi (:ebassi) 2012-08-27 14:24:14 UTC
Review of attachment 222554 [details] [review]:

looks good, thanks! some minor style issues left.

::: clutter/clutter-pan-action.c
@@ +73,3 @@
+  PAN_STATE_PANNING,
+  PAN_STATE_INTERPOLATING
+} _ClutterPanState;

there's no need to use a leading underscore; you can use 'PanState' for the name.

@@ +81,3 @@
+  _ClutterPanState state;
+
+  gboolean should_interpolate;

this should be a:

  guint should_interpolate : 1;

and placed at the end of the struct.

@@ +85,3 @@
+  /* Variables for storing acceleration information */
+  ClutterTimeline     *deceleration_timeline;
+  gfloat               target_x;

please, don't leave this whitespace between type and name.

@@ +153,3 @@
+  ClutterActor *actor;
+
+  g_object_unref (timeline);

you can use g_clear_object() instead of g_object_unref() + set to NULL.

@@ +162,3 @@
+static void
+on_deceleration_new_frame (ClutterTimeline     *timeline,
+                           gint                 frame_num,

"frame_num" should be "elapsed_time"

@@ +232,3 @@
+  guint duration;
+  const gfloat min_velocity = 0.1f; // measured in px/ms
+  const gfloat reference_fps = 60.0f; // the fps assumed for the deceleration rate

you probably want to have these two as static consts declared at the top of the file; we may even allow configuration through properties later on.

@@ +251,3 @@
+   * 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));

missing space between logf and parenthesis.

@@ +259,3 @@
+
+  /* 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));

missing spaces between exp and parenthesis; missing space between duration, /, and tau. the cast to float should be done before the '-'.

@@ +260,3 @@
+  /* 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));

same as above.

@@ +330,3 @@
+    case PROP_INTERPOLATE :
+      clutter_pan_action_set_interpolate (self,
+          g_value_get_boolean (value));

do not break the line here.

@@ +335,3 @@
+    case PROP_DECELERATION :
+      clutter_pan_action_set_deceleration (self,
+          g_value_get_double (value));

do not break the line here.

@@ +340,3 @@
+    case PROP_ACCELERATION_FACTOR :
+      clutter_pan_action_set_acceleration_factor (self,
+          g_value_get_double (value));

do not break the line here.

@@ +388,3 @@
+    {
+      clutter_timeline_stop (priv->deceleration_timeline);
+      g_object_unref (priv->deceleration_timeline);

you should use g_clear_object(). disposing the timeline will also stop it, so there's no need to manually stop it. if you don't want to emit signals, disconnect the signal handlers before disposing the timeline.

@@ +410,3 @@
+        {
+          clutter_timeline_stop (priv->deceleration_timeline);
+          g_object_unref (priv->deceleration_timeline);

same as above - use g_clear_object() instead.

@@ +520,3 @@
+   * interpolation phase of the pan, after the drag has ended and
+   * the :interpolate property was set to %TRUE.
+   *

missing Return value: annotation.

@@ +561,3 @@
+    G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_PAN_ACTION,
+                                 ClutterPanActionPrivate);
+  priv->deceleration_rate = 0.95f;

the deceleration rate default value does not match the property's GParamSpec.

@@ +649,3 @@
+  priv = self->priv;
+
+  if (priv->should_interpolate == should_interpolate)

if you're comparing directly, you should do:

  should_interpolate = !!should_interpolate;

before the if().

@@ +701,3 @@
+  priv = self->priv;
+
+  if (rate == priv->deceleration_rate)

comparing double precision values directly with a '==' won't really work, especially if we're talking about small deltas.

you can either use abs(rate - priv->deceleration_rate) < float_epsilon, or use memcmp().

or you could just not bother.

@@ +737,3 @@
+void
+clutter_pan_action_set_acceleration_factor (ClutterPanAction *self,
+                                                      gdouble                    factor)

extraneous whitespace.

@@ +746,3 @@
+  priv = self->priv;
+
+  if (factor == priv->acceleration_factor)

same as above: direct comparison of doubles won't really work.

::: examples/pan-action.c
@@ +15,3 @@
+  clutter_actor_set_size (content, 720, 720);
+
+  pixbuf = gdk_pixbuf_new_from_file (TESTS_DATADIR G_DIR_SEPARATOR_S "redhand.png", NULL);

we should probably get a better image than this.

@@ +17,3 @@
+  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),

the indentation is wrong. you have to line up all arguments with the opening parenthesis.

@@ +20,3 @@
+      gdk_pixbuf_get_pixels (pixbuf),
+      gdk_pixbuf_get_has_alpha (pixbuf)
+      ? COGL_PIXEL_FORMAT_RGBA_8888

you should indent the ternary operator arguments by one level.

@@ +29,3 @@
+
+  clutter_actor_set_content_scaling_filters (content,
+      CLUTTER_SCALING_FILTER_TRILINEAR,

wrong indentation.

@@ +109,3 @@
+  /* create a new stage */
+  stage = clutter_stage_new ();
+  clutter_stage_set_title (CLUTTER_STAGE (stage), "Scroll Actor");

this should probably be titled "Pan Action"
Comment 19 Emmanuele Bassi (:ebassi) 2012-08-27 14:25:27 UTC
Review of attachment 222555 [details] [review]:

looks good.
Comment 20 Emanuele Aina 2012-08-27 14:56:54 UTC
Created attachment 222558 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

Re-spin. I ditched the float checks and now I just trigger property
notifications on every setter call.
Comment 21 Emmanuele Bassi (:ebassi) 2012-08-28 10:34:51 UTC
Review of attachment 222558 [details] [review]:

looks great, thanks!

even if we're in API freeze, I really want to have this before freezing the 1.x branch, so let's get it in.

::: examples/pan-action.c
@@ +3,3 @@
+#include <clutter/clutter.h>
+
+ClutterPoint initial_scroll = CLUTTER_POINT_INIT (0, 120);

this is not used anywhere in the example.
Comment 22 Gustavo Noronha (kov) 2012-08-28 13:15:08 UTC
Comment on attachment 222399 [details] [review]
gesture-action: add allow-none annotations to getters

e8e91b62c896223d8f7bc4421862b87ef002a972
Comment 23 Gustavo Noronha (kov) 2012-08-28 13:15:20 UTC
Comment on attachment 222555 [details] [review]
gesture-action: add _get_motion_delta()/_get_velocity()

436ebb2716b24743b3873aa7b330073c48793b86
Comment 24 Gustavo Noronha (kov) 2012-08-28 13:15:37 UTC
Comment on attachment 222558 [details] [review]
pan-action: add PanAction, to handle panning in scrollable actors

9ca06d2895154eb2c985b85df186db3ade1a5e1e