[gnome-shell/wip/gdm-shell: 6/8] wip: StScrollView: add swipe support



commit df736ab764c10d35e2015e6f6b83d89238930cbb
Author: Ray Strode <rstrode redhat com>
Date:   Wed Jul 6 23:58:04 2011 -0400

    wip: StScrollView: add swipe support
    
    This adds the ability to drag around the scroll view without
    using the scrollbars and the ability to "throw" the thing being
    dragged so it keeps some momentum after release.
    
    TODO:
    
     - Need to fix horizontal swipes
     - Need to investigate potentially using least squares
     curve fitting to find a parabolic function to model the
     throw instead of hardcoding easeOutCirc
     - Need to drop drag_x, drag_start_x etc since they're in
     the captured event queue anyway.

 src/st/st-box-layout.c  |   14 +-
 src/st/st-scroll-view.c |  514 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/st/st-scroll-view.h |    4 +
 3 files changed, 526 insertions(+), 6 deletions(-)
---
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
index 3dbf533..052af44 100644
--- a/src/st/st-box-layout.c
+++ b/src/st/st-box-layout.c
@@ -646,7 +646,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                                 &min_height, &natural_height);
 
 
-  /* update adjustments for scrolling */
+  /* update adjustments for scrolling.
+   * We only update the value if not interpolating
+   */
   if (priv->vadjustment)
     {
       gdouble prev_value;
@@ -659,8 +661,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                     "page-increment", avail_height - avail_height / 6,
                     NULL);
 
-      prev_value = st_adjustment_get_value (priv->vadjustment);
-      st_adjustment_set_value (priv->vadjustment, prev_value);
+      g_object_get (G_OBJECT (priv->vadjustment), "value", &prev_value, NULL);
+      if (prev_value == st_adjustment_get_value (priv->vadjustment))
+        st_adjustment_set_value (priv->vadjustment, prev_value);
     }
 
   if (priv->hadjustment)
@@ -675,8 +678,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                     "page-increment", avail_width - avail_width / 6,
                     NULL);
 
-      prev_value = st_adjustment_get_value (priv->hadjustment);
-      st_adjustment_set_value (priv->hadjustment, prev_value);
+      g_object_get (G_OBJECT (priv->hadjustment), "value", &prev_value, NULL);
+      if (prev_value == st_adjustment_get_value (priv->hadjustment))
+        st_adjustment_set_value (priv->hadjustment, prev_value);
     }
 
   if (avail_height < min_height)
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
index a628690..1b44fd7 100644
--- a/src/st/st-scroll-view.c
+++ b/src/st/st-scroll-view.c
@@ -64,9 +64,12 @@
 #include "st-scrollable.h"
 #include "st-scroll-view-fade.h"
 #include <clutter/clutter.h>
+#include <clutter/x11/clutter-x11.h>
+#include <gdk/gdkx.h>
 #include <math.h>
 
 static void clutter_container_iface_init (ClutterContainerIface *iface);
+static void stop_capturing_events (StScrollView *self);
 
 static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
 
@@ -78,6 +81,14 @@ G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
                                                              ST_TYPE_SCROLL_VIEW, \
                                                              StScrollViewPrivate))
 
+#define ANIMATION_TIME 1.0
+
+typedef struct
+{
+  ClutterEvent *clutter_event;
+  guint64       timestamp;
+} StSwipeEvent;
+
 struct _StScrollViewPrivate
 {
   /* a pointer to the child; this is actually stored
@@ -91,6 +102,15 @@ struct _StScrollViewPrivate
   StAdjustment *vadjustment;
   ClutterActor *vscroll;
 
+  guint64       capture_start_time;
+  GQueue       *captured_events_queue;
+  double        drag_start_horizontal_value;
+  double        drag_start_vertical_value;
+  float         drag_start_x;
+  float         drag_start_y;
+  float         drag_x;
+  float         drag_y;
+
   GtkPolicyType hscrollbar_policy;
   GtkPolicyType vscrollbar_policy;
 
@@ -99,9 +119,13 @@ struct _StScrollViewPrivate
 
   StScrollViewFade *vfade_effect;
 
+  guint         captured_event_signal_id;
+
   gboolean      row_size_set : 1;
   gboolean      column_size_set : 1;
   guint         mouse_scroll : 1;
+  guint         swipe_scroll : 1;
+  guint         drag_in_progress : 1;
   guint         hscrollbar_visible : 1;
   guint         vscrollbar_visible : 1;
 };
@@ -114,6 +138,7 @@ enum {
   PROP_HSCROLLBAR_POLICY,
   PROP_VSCROLLBAR_POLICY,
   PROP_MOUSE_SCROLL,
+  PROP_SWIPE_SCROLL,
 };
 
 static void
@@ -141,6 +166,9 @@ st_scroll_view_get_property (GObject    *object,
     case PROP_MOUSE_SCROLL:
       g_value_set_boolean (value, priv->mouse_scroll);
       break;
+    case PROP_SWIPE_SCROLL:
+      g_value_set_boolean (value, priv->swipe_scroll);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -200,6 +228,10 @@ st_scroll_view_set_property (GObject      *object,
       st_scroll_view_set_mouse_scrolling (self,
                                           g_value_get_boolean (value));
       break;
+    case PROP_SWIPE_SCROLL:
+      st_scroll_view_set_swipe_scrolling (self,
+                                          g_value_get_boolean (value));
+      break;
     case PROP_HSCROLLBAR_POLICY:
       st_scroll_view_set_policy (self,
                                  g_value_get_enum (value),
@@ -218,7 +250,8 @@ st_scroll_view_set_property (GObject      *object,
 static void
 st_scroll_view_dispose (GObject *object)
 {
-  StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv;
+  StScrollView *self = ST_SCROLL_VIEW (object);
+  StScrollViewPrivate *priv = self->priv;
 
   if (priv->vfade_effect)
     {
@@ -251,6 +284,8 @@ st_scroll_view_dispose (GObject *object)
       priv->vadjustment = NULL;
     }
 
+  stop_capturing_events (self);
+
   G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
 }
 
@@ -730,6 +765,381 @@ st_scroll_view_scroll_event (ClutterActor       *self,
   return TRUE;
 }
 
+static StSwipeEvent *
+st_swipe_event_new (ClutterEvent *event,
+                    guint64       timestamp)
+{
+  StSwipeEvent *swipe_event;
+
+  swipe_event = g_slice_new (StSwipeEvent);
+
+  swipe_event->clutter_event = clutter_event_copy (event);
+  swipe_event->timestamp = timestamp;
+
+  return swipe_event;
+}
+
+static void
+record_captured_event (StScrollView *self,
+                       ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+
+  StSwipeEvent *swipe_event;
+  swipe_event = st_swipe_event_new (event,
+                                    g_get_monotonic_time() - self->priv->capture_start_time);
+
+  g_queue_push_tail (priv->captured_events_queue,
+                     swipe_event);
+}
+
+static void
+st_swipe_event_free (StSwipeEvent *swipe_event)
+{
+    g_slice_free (StSwipeEvent, swipe_event);
+}
+
+static void
+replay_captured_events (StScrollView *self)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeEvent *swipe_event;
+
+  while ((swipe_event = g_queue_pop_head (priv->captured_events_queue)) != NULL)
+    {
+      ClutterEvent *event;
+      ClutterActor *actor;
+      float x, y;
+
+      event = swipe_event->clutter_event;
+      clutter_event_get_coords (event, &x, &y);
+
+      actor = clutter_stage_get_actor_at_pos (clutter_event_get_stage (event),
+                                              CLUTTER_PICK_REACTIVE,
+                                              x, y);
+      if (actor != NULL)
+        clutter_actor_event (actor, event, FALSE);
+
+      clutter_event_free (event);
+    }
+}
+
+static void
+discard_captured_events (StScrollView *self)
+{
+  StScrollViewPrivate *priv = self->priv;
+
+  g_queue_foreach (priv->captured_events_queue,
+                   (GFunc) st_swipe_event_free,
+                   NULL);
+  g_queue_clear (priv->captured_events_queue);
+}
+
+static gboolean
+handle_captured_button_press_event (StScrollView *self,
+                                    ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  float x, y;
+  ClutterActorBox box;
+
+  if (!priv->swipe_scroll || clutter_event_get_button (event) != 1 ||
+      !(priv->hscrollbar_visible || priv->vscrollbar_visible))
+    return FALSE;
+
+  if (priv->drag_in_progress)
+    return FALSE;
+
+  clutter_event_get_coords (event, &x, &y);
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (self), &box);
+
+  if (!clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (priv->hscroll), &box);
+
+  if (clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (priv->vscroll), &box);
+
+  if (clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  record_captured_event (self, event);
+
+  priv->drag_in_progress = TRUE;
+  priv->drag_start_x = priv->drag_x = x;
+  priv->drag_start_y = priv->drag_y = y;
+  g_object_get (priv->hadjustment, "value", &priv->drag_start_horizontal_value, NULL);
+  g_object_get (priv->vadjustment, "value", &priv->drag_start_vertical_value, NULL);
+
+  /* catch and stop any motion from a previous swipe
+   */
+  st_adjustment_set_value (priv->hadjustment, priv->drag_start_horizontal_value);
+  st_adjustment_set_value (priv->vadjustment, priv->drag_start_vertical_value);
+
+  return TRUE;
+}
+
+static void
+find_swipe_velocity_and_time (StScrollView *self,
+                              double       *horizontal_velocity,
+                              double       *vertical_velocity)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeEvent *newer_event;
+  gboolean first_time;
+  float newer_x, newer_y;
+  GList *node;
+
+  /* FIXME: need to add horizontal back
+   */
+  node =  g_queue_peek_tail_link (priv->captured_events_queue);
+  newer_event = (StSwipeEvent *) node->data;
+  clutter_event_get_coords (newer_event->clutter_event, &newer_x, &newer_y);
+
+  *vertical_velocity = 0;
+
+  node = node->prev;
+  first_time = TRUE;
+  while (node != NULL)
+    {
+       float older_y;
+       float dt;
+       float dy;
+       float y_velocity;
+
+       StSwipeEvent *older_event = (StSwipeEvent *) node->data;
+
+       dt = (float) (newer_event->timestamp - older_event->timestamp) / G_USEC_PER_SEC;
+
+       /* Compress nearby events together
+        */
+       if (ABS (dt) < .1)
+         {
+           node = node->prev;
+           continue;
+         }
+
+       clutter_event_get_coords (older_event->clutter_event, NULL, &older_y);
+       dy = newer_y - older_y;
+
+       y_velocity = dy / dt;
+
+       if (first_time)
+         *vertical_velocity = y_velocity;
+
+       /* Only read events up until the user stopped or switched directions
+        */
+       if (*vertical_velocity * y_velocity <= 0)
+         break;
+
+       *vertical_velocity = (*vertical_velocity + y_velocity) / 2;
+
+       first_time = FALSE;
+       newer_event = older_event;
+       newer_y = older_y;
+       node = node->prev;
+    }
+}
+
+static gboolean
+handle_captured_button_release_event (StScrollView *self,
+                                      ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  GtkSettings *settings;
+  gboolean should_replay_click;
+  int threshold;
+  float x, y;
+  double horizontal_value, min_horizontal_value, max_horizontal_value, horizontal_page_size;
+  double vertical_value, min_vertical_value, max_vertical_value, vertical_page_size;
+  double horizontal_velocity, vertical_velocity;
+
+  if (!priv->drag_in_progress)
+    return FALSE;
+
+  settings = gtk_settings_get_default();
+  g_object_get (G_OBJECT (settings), "gtk-dnd-drag-threshold", &threshold, NULL);
+
+  clutter_event_get_coords (event, &x, &y);
+
+  /* See if the user has moved the mouse enough to trigger
+   * a drag
+   */
+  if (ABS (x - priv->drag_start_x) < threshold &&
+      ABS (y - priv->drag_start_y) < threshold)
+    {
+      record_captured_event (self, event);
+
+      /* No motion? It's a click! */
+      should_replay_click = TRUE;
+      goto out;
+    }
+  else
+    {
+      should_replay_click = FALSE;
+    }
+
+  g_object_get (priv->hadjustment,
+                "value", &horizontal_value,
+                "lower", &min_horizontal_value,
+                "upper", &max_horizontal_value,
+                "page-size", &horizontal_page_size,
+                NULL);
+
+  g_object_get (priv->vadjustment,
+                "value", &vertical_value,
+                "lower", &min_vertical_value,
+                "upper", &max_vertical_value,
+                "page-size", &vertical_page_size,
+                NULL);
+  record_captured_event (self, event);
+
+  find_swipe_velocity_and_time (self,
+                                &horizontal_velocity,
+                                &vertical_velocity);
+
+  if (ABS (vertical_velocity) < threshold)
+    {
+      st_adjustment_set_value (priv->vadjustment, vertical_value);
+    }
+  else
+    {
+      ClutterActorBox box;
+      ClutterAlpha *alpha;
+      double new_vertical_value;
+
+      clutter_actor_get_paint_box (CLUTTER_ACTOR (self), &box);
+
+      new_vertical_value = vertical_value - vertical_velocity * ANIMATION_TIME;
+      new_vertical_value = CLAMP (new_vertical_value,
+                                  min_vertical_value,
+                                  MAX (min_vertical_value,
+                                       max_vertical_value - vertical_page_size));
+
+      alpha = clutter_alpha_new ();
+      clutter_alpha_set_mode (alpha, CLUTTER_EASE_OUT_CIRC);
+      st_adjustment_interpolate (priv->vadjustment,
+                                 alpha,
+                                 new_vertical_value,
+                                 ANIMATION_TIME * 1000);
+      g_object_unref (alpha);
+    }
+
+out:
+  priv->drag_in_progress = FALSE;
+
+  /* We've discarded all enter and leave events, so
+   * inform children that rely on those events to
+   * update their state.
+   */
+  st_widget_sync_hover (ST_WIDGET (self));
+
+  if (should_replay_click)
+    replay_captured_events (self);
+  else
+    discard_captured_events (self);
+
+  return TRUE;
+}
+
+static gboolean
+handle_captured_motion_event (StScrollView *self,
+                              ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  GtkSettings *settings;
+  int threshold;
+  gfloat x, y;
+  gfloat dx, dy;
+  ClutterActorBox box;
+  gdouble value;
+  gdouble horizontal_value, horizontal_page_size;
+  gdouble vertical_value, vertical_page_size;
+
+  if (!priv->drag_in_progress)
+    return FALSE;
+
+  record_captured_event (self, event);
+
+  settings = gtk_settings_get_default();
+  g_object_get (G_OBJECT (settings), "gtk-dnd-drag-threshold", &threshold, NULL);
+
+  clutter_event_get_coords (event, &x, &y);
+
+  g_object_get (priv->hadjustment,
+                "value", &horizontal_value,
+                "page-size", &horizontal_page_size,
+                NULL);
+
+  g_object_get (priv->vadjustment,
+                "value", &vertical_value,
+                "page-size", &vertical_page_size,
+                NULL);
+
+  dx = priv->drag_x - x;
+
+  if (st_widget_get_direction (ST_WIDGET (self)) == ST_TEXT_DIRECTION_RTL)
+    dx *= -1;
+
+  dy = priv->drag_y - y;
+
+  priv->drag_x = x;
+  priv->drag_y = y;
+
+  /* See if the user has moved the mouse enough to trigger
+     a drag */
+  if ((ABS (x - priv->drag_start_x) < threshold) &&
+      (ABS (y - priv->drag_start_y) < threshold))
+    return TRUE;
+
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (self), &box);
+
+  value = horizontal_value + (dx / (box.x2 - box.x1)) * horizontal_page_size;
+  st_adjustment_set_value (priv->hadjustment, value);
+  st_adjustment_clamp (priv->hadjustment, FALSE, 0);
+
+  value = vertical_value + (dy / (box.y2 - box.y1) * vertical_page_size);
+  st_adjustment_set_value (priv->vadjustment, value);
+  st_adjustment_clamp (priv->vadjustment, FALSE, 0);
+
+  return TRUE;
+}
+
+static gboolean
+on_captured_event (ClutterActor *stage,
+                   ClutterEvent *event,
+                   gpointer      data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (data);
+  StScrollViewPrivate *priv = self->priv;
+
+  switch (event->type)
+    {
+      case CLUTTER_BUTTON_PRESS:
+        return handle_captured_button_press_event (self, event);
+
+      case CLUTTER_BUTTON_RELEASE:
+        return handle_captured_button_release_event (self, event);
+
+      case CLUTTER_MOTION:
+        return handle_captured_motion_event (self, event);
+
+      /* Block enter/leave events to avoid prelights
+         during swipe scrolling */
+      case CLUTTER_ENTER:
+      case CLUTTER_LEAVE:
+        return priv->drag_in_progress;
+
+      default:
+        break;
+    }
+
+  return FALSE;
+}
+
 static void
 st_scroll_view_class_init (StScrollViewClass *klass)
 {
@@ -795,6 +1205,70 @@ st_scroll_view_class_init (StScrollViewClass *klass)
                                    PROP_MOUSE_SCROLL,
                                    pspec);
 
+  pspec = g_param_spec_boolean ("enable-swipe-scrolling",
+                                "Enable Swipe Scrolling",
+                                "Enable scrolling when dragging the mouse",
+                                TRUE,
+                                G_PARAM_READWRITE);
+  g_object_class_install_property (object_class,
+                                   PROP_SWIPE_SCROLL,
+                                   pspec);
+
+}
+
+static void
+start_capturing_events (StScrollView *self)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (self);
+  StScrollViewPrivate *priv = self->priv;
+
+ if (priv->captured_event_signal_id <= 0)
+    priv->captured_event_signal_id = g_signal_connect (clutter_actor_get_stage (actor),
+                                                       "captured-event",
+                                                       G_CALLBACK (on_captured_event),
+                                                       self);
+
+  if (priv->captured_events_queue == NULL)
+    priv->captured_events_queue = g_queue_new ();
+
+  priv->capture_start_time = g_get_monotonic_time();
+}
+
+static void
+stop_capturing_events (StScrollView *self)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (self);
+  StScrollViewPrivate *priv = self->priv;
+
+  if (priv->captured_event_signal_id > 0)
+    {
+      g_signal_handler_disconnect (clutter_actor_get_stage (actor),
+                                   priv->captured_event_signal_id);
+      priv->captured_event_signal_id = 0;
+    }
+
+  if (priv->captured_events_queue != NULL)
+    {
+      discard_captured_events (self);
+
+      g_queue_free (priv->captured_events_queue);
+      priv->captured_events_queue = NULL;
+    }
+
+  priv->capture_start_time = 0;
+}
+
+static void
+on_notify_visible (GObject    *object,
+                   GParamSpec  param_id,
+                   gpointer    data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (object);
+
+  if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (self)))
+    start_capturing_events (self);
+  else
+    stop_capturing_events (self);
 }
 
 static void
@@ -823,6 +1297,12 @@ st_scroll_view_init (StScrollView *self)
   /* mouse scroll is enabled by default, so we also need to be reactive */
   priv->mouse_scroll = TRUE;
   g_object_set (G_OBJECT (self), "reactive", TRUE, NULL);
+
+  g_signal_connect (G_OBJECT (self), "notify::visible",
+                    G_CALLBACK (on_notify_visible),
+                    NULL);
+
+  priv->captured_events_queue = NULL;
 }
 
 static void
@@ -1057,6 +1537,38 @@ st_scroll_view_get_mouse_scrolling (StScrollView *scroll)
   return priv->mouse_scroll;
 }
 
+void
+st_scroll_view_set_swipe_scrolling (StScrollView *scroll,
+                                    gboolean      enabled)
+{
+  StScrollViewPrivate *priv;
+
+  g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+  priv = ST_SCROLL_VIEW (scroll)->priv;
+
+  if (priv->swipe_scroll != enabled)
+    {
+      priv->swipe_scroll = enabled;
+
+      /* make sure we can receive swipe events */
+      if (enabled)
+        clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE);
+    }
+}
+
+gboolean
+st_scroll_view_get_swipe_scrolling (StScrollView *scroll)
+{
+  StScrollViewPrivate *priv;
+
+  g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+  priv = ST_SCROLL_VIEW (scroll)->priv;
+
+  return priv->swipe_scroll;
+}
+
 /**
  * st_scroll_view_set_policy:
  * @scroll: A #StScrollView
diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h
index c00d16e..ad2d203 100644
--- a/src/st/st-scroll-view.h
+++ b/src/st/st-scroll-view.h
@@ -80,6 +80,10 @@ void          st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
                                                   gboolean      enabled);
 gboolean      st_scroll_view_get_mouse_scrolling (StScrollView *scroll);
 
+void          st_scroll_view_set_swipe_scrolling (StScrollView *scroll,
+                                                  gboolean      enabled);
+gboolean      st_scroll_view_get_swipe_scrolling (StScrollView *scroll);
+
 void          st_scroll_view_set_policy          (StScrollView   *scroll,
                                                   GtkPolicyType   hscroll,
                                                   GtkPolicyType   vscroll);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]