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



commit 8013b96e5141666a7b2697988168bf1d4c832d36
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.
    
    TODO: need to propagate button press events to client if we
    decide the user isn't swiping.

 src/st/st-scroll-view.c |  340 +++++++++++++++++++++++++++++++++++++++++++++++
 src/st/st-scroll-view.h |    4 +
 2 files changed, 344 insertions(+), 0 deletions(-)
---
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
index a628690..f472440 100644
--- a/src/st/st-scroll-view.c
+++ b/src/st/st-scroll-view.c
@@ -64,6 +64,8 @@
 #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);
@@ -78,6 +80,8 @@ G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
                                                              ST_TYPE_SCROLL_VIEW, \
                                                              StScrollViewPrivate))
 
+#define ANIMATION_TIME 2500
+
 struct _StScrollViewPrivate
 {
   /* a pointer to the child; this is actually stored
@@ -91,6 +95,14 @@ struct _StScrollViewPrivate
   StAdjustment *vadjustment;
   ClutterActor *vscroll;
 
+  int          last_motion_time;
+  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 +111,13 @@ struct _StScrollViewPrivate
 
   StScrollViewFade *vfade_effect;
 
+  guint         captured_event_id;
+
   gboolean      row_size_set : 1;
   gboolean      column_size_set : 1;
   guint         mouse_scroll : 1;
+  guint         swipe_scroll : 1;
+  guint         in_swipe : 1;
   guint         hscrollbar_visible : 1;
   guint         vscrollbar_visible : 1;
 };
@@ -114,6 +130,7 @@ enum {
   PROP_HSCROLLBAR_POLICY,
   PROP_VSCROLLBAR_POLICY,
   PROP_MOUSE_SCROLL,
+  PROP_SWIPE_SCROLL,
 };
 
 static void
@@ -141,6 +158,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 +220,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),
@@ -251,6 +275,13 @@ st_scroll_view_dispose (GObject *object)
       priv->vadjustment = NULL;
     }
 
+  if (priv->captured_event_id > 0)
+    {
+      g_signal_handler_disconnect (clutter_actor_get_stage (CLUTTER_ACTOR (object)),
+                                   priv->captured_event_id);
+      priv->captured_event_id = 0;
+    }
+
   G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
 }
 
@@ -731,6 +762,242 @@ st_scroll_view_scroll_event (ClutterActor       *self,
 }
 
 static void
+get_area_of_monitor_surrounding_actor (ClutterActor *actor,
+                                       GdkRectangle *area)
+{
+  GdkDisplay *display = gdk_display_get_default ();
+  GdkScreen *screen;
+  ClutterActor *stage;
+  Window xwindow;
+  GdkWindow *window;
+  float x, y;
+  int monitor;
+
+  stage = clutter_actor_get_stage (actor);
+
+  xwindow = clutter_x11_get_stage_window (CLUTTER_STAGE (stage));
+
+  window = gdk_x11_window_lookup_for_display (display, xwindow);
+  if (window)
+    g_object_ref (window);
+  else
+    window = gdk_x11_window_foreign_new_for_display (display, xwindow);
+
+  screen = gdk_window_get_screen (window);
+
+  g_object_unref (window);
+
+  clutter_actor_get_transformed_position (actor, &x, &y);
+
+  /* assumes stage covers screen
+   */
+  monitor = gdk_screen_get_monitor_at_point (screen, (int) x + .5, (int) y + .5);
+
+  gdk_screen_get_monitor_geometry (screen, monitor, area);
+}
+
+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;
+
+  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;
+
+  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);
+  priv->last_motion_time = -1;
+  priv->in_swipe = TRUE;
+
+  return TRUE;
+}
+
+static gboolean
+handle_captured_button_release_event (StScrollView *self,
+                                      ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  GtkSettings *settings;
+  int threshold;
+  gfloat x, y;
+  gdouble new_horizontal_value, horizontal_value, min_horizontal_value, max_horizontal_value;
+  gdouble new_vertical_value, vertical_value, min_vertical_value, max_vertical_value;
+  gboolean was_thrown;
+
+  if (!priv->in_swipe)
+    return FALSE;
+
+  priv->in_swipe = 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)
+    {
+      /* No motion? It's a click! */
+      return FALSE;
+    }
+
+  g_object_get (priv->hadjustment,
+                "value", &horizontal_value,
+                "lower", &min_horizontal_value,
+                "upper", &max_horizontal_value,
+                NULL);
+
+  g_object_get (priv->vadjustment,
+                "value", &vertical_value,
+                "lower", &min_vertical_value,
+                "upper", &max_vertical_value,
+                NULL);
+
+  /* We detect if the user threw what they were dragging by
+     comparing the timestamp of the button release with the
+     timestamp of the last motion. Experimentally, a difference
+     of 0 or 1 millisecond indicates that the mouse is in motion,
+     a larger difference indicates the user dropped what they
+     were dragging. */
+  was_thrown = priv->last_motion_time > 0 && priv->last_motion_time > clutter_event_get_time (event) - 2;
+
+  if (!was_thrown)
+    {
+      new_horizontal_value = horizontal_value;
+      new_vertical_value = vertical_value;
+    }
+  else
+    {
+      GdkRectangle area;
+      gfloat dx, dy;
+
+      dx = priv->drag_start_x - x;
+      dy = priv->drag_start_y - y;
+      get_area_of_monitor_surrounding_actor (CLUTTER_ACTOR (self), &area);
+
+      if (st_widget_get_direction (ST_WIDGET (self)) == ST_TEXT_DIRECTION_RTL)
+        dx *= -1;
+
+      new_horizontal_value = horizontal_value + (dx / area.width) * (max_horizontal_value - min_horizontal_value);
+      new_vertical_value = vertical_value + (dy / area.height) * (max_vertical_value - min_vertical_value);
+    }
+
+  st_adjustment_interpolate (priv->hadjustment, new_horizontal_value, ANIMATION_TIME);
+  st_adjustment_interpolate (priv->vadjustment, new_vertical_value, ANIMATION_TIME);
+
+  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;
+  GdkRectangle area;
+  gdouble horizontal_value, horizontal_page_size;
+  gdouble vertical_value, vertical_page_size;
+
+  if (!priv->in_swipe)
+    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);
+
+  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;
+  dy = priv->drag_y - y;
+  get_area_of_monitor_surrounding_actor (CLUTTER_ACTOR (self), &area);
+
+  priv->drag_x = x;
+  priv->drag_y = y;
+  priv->last_motion_time = clutter_event_get_time (event);
+
+  /* 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;
+
+  if (st_widget_get_direction (ST_WIDGET (self)) == ST_TEXT_DIRECTION_RTL)
+      st_adjustment_set_value (priv->hadjustment, horizontal_value - (dx / area.width) * horizontal_page_size);
+  else
+      st_adjustment_set_value (priv->hadjustment, horizontal_value + (dx / area.width) * horizontal_page_size);
+
+  st_adjustment_set_value (priv->vadjustment, vertical_value + (dy / area.height) * vertical_page_size);
+  return TRUE;
+}
+
+static gboolean
+on_captured_event (ClutterActor *stage,
+                   ClutterEvent *event,
+                   gpointer      data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (data);
+
+  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-scroll */
+      case CLUTTER_ENTER:
+      case CLUTTER_LEAVE:
+        return TRUE;
+      default:
+        break;
+    }
+
+  return FALSE;
+}
+
+static void
 st_scroll_view_class_init (StScrollViewClass *klass)
 {
   GParamSpec *pspec;
@@ -795,6 +1062,43 @@ 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
+on_notify_visible (GObject    *object,
+                   GParamSpec  param_id,
+                   gpointer    data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (object);
+  ClutterActor *actor = CLUTTER_ACTOR (self);
+  StScrollViewPrivate *priv = self->priv;
+
+  if (CLUTTER_ACTOR_IS_VISIBLE (actor))
+    {
+      if (priv->captured_event_id <= 0)
+        priv->captured_event_id = g_signal_connect (clutter_actor_get_stage (actor),
+                                                    "captured-event",
+                                                    G_CALLBACK (on_captured_event),
+                                                    self);
+    }
+  else
+    {
+      if (priv->captured_event_id > 0)
+        {
+          g_signal_handler_disconnect (clutter_actor_get_stage (actor),
+                                       priv->captured_event_id);
+          priv->captured_event_id = 0;
+        }
+    }
 }
 
 static void
@@ -823,6 +1127,10 @@ 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);
 }
 
 static void
@@ -1057,6 +1365,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]