[gnome-shell/wip/gdm-shell: 6/8] wip: StScrollView: add swipe support
- From: Ray Strode <halfline src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/wip/gdm-shell: 6/8] wip: StScrollView: add swipe support
- Date: Sat, 9 Jul 2011 05:54:32 +0000 (UTC)
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]