[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: Wed, 20 Jul 2011 05:14:51 +0000 (UTC)
commit 4bc83f4ff374646a4e7ba5f5f040dfc52ed4e236
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.
src/st/st-box-layout.c | 14 +-
src/st/st-scroll-view.c | 580 ++++++++++++++++++++++++++++++++++++++++++++++-
src/st/st-scroll-view.h | 4 +
3 files changed, 592 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 63e66c1..688299d 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_watching_for_swipes (StScrollView *self);
static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
@@ -78,6 +81,26 @@ G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
ST_TYPE_SCROLL_VIEW, \
StScrollViewPrivate))
+typedef struct
+{
+ ClutterEvent *event;
+ double x;
+ double y;
+ double time_since_start;
+ double time_since_last_x_motion;
+ double time_since_last_y_motion;
+ double movement_since_last_x_motion;
+ double movement_since_last_y_motion;
+ double vertical_velocity;
+ double horizontal_velocity;
+} StSwipeSample;
+
+typedef enum
+{
+ ST_SWIPE_DIRECTION_HORIZONTAL,
+ ST_SWIPE_DIRECTION_VERTICAL
+} StSwipeDirection;
+
struct _StScrollViewPrivate
{
/* a pointer to the child; this is actually stored
@@ -91,6 +114,11 @@ struct _StScrollViewPrivate
StAdjustment *vadjustment;
ClutterActor *vscroll;
+ double capture_start_time;
+ double capture_start_x;
+ double capture_start_y;
+ GQueue *recorded_samples_queue;
+
GtkPolicyType hscrollbar_policy;
GtkPolicyType vscrollbar_policy;
@@ -99,9 +127,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;
};
@@ -116,6 +148,7 @@ enum {
PROP_HSCROLLBAR_VISIBLE,
PROP_VSCROLLBAR_VISIBLE,
PROP_MOUSE_SCROLL,
+ PROP_SWIPE_SCROLL,
};
static void
@@ -149,6 +182,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);
}
@@ -208,6 +244,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),
@@ -226,7 +266,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)
{
@@ -259,6 +300,8 @@ st_scroll_view_dispose (GObject *object)
priv->vadjustment = NULL;
}
+ stop_watching_for_swipes (self);
+
G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
}
@@ -752,6 +795,432 @@ st_scroll_view_scroll_event (ClutterActor *self,
return TRUE;
}
+static StSwipeSample *
+st_swipe_sample_new (ClutterEvent *event,
+ double time_since_start,
+ double x,
+ double y,
+ double movement_since_last_x_motion,
+ double movement_since_last_y_motion)
+{
+ StSwipeSample *swipe_sample;
+
+ swipe_sample = g_slice_new0 (StSwipeSample);
+
+ swipe_sample->event = clutter_event_copy (event);
+ swipe_sample->time_since_start = time_since_start;
+ swipe_sample->x = x;
+ swipe_sample->y = y;
+ swipe_sample->movement_since_last_x_motion = movement_since_last_x_motion;
+ swipe_sample->movement_since_last_y_motion = movement_since_last_y_motion;
+
+ return swipe_sample;
+}
+
+static StSwipeSample *
+record_sample_from_event (StScrollView *self,
+ ClutterEvent *event)
+{
+ StScrollViewPrivate *priv = self->priv;
+ StSwipeSample *swipe_sample;
+ float x, y, older_x, older_y;
+
+ clutter_event_get_coords (event, &x, &y);
+
+ if (g_queue_get_length (priv->recorded_samples_queue) == 0)
+ {
+ self->priv->capture_start_time = g_get_monotonic_time() / (1.0 * G_USEC_PER_SEC);
+ self->priv->capture_start_x = x;
+ self->priv->capture_start_y = y;
+ older_x = 0;
+ older_y = 0;
+ }
+ else
+ {
+ StSwipeSample *older_sample;
+
+ older_sample = g_queue_peek_tail (priv->recorded_samples_queue);
+ clutter_event_get_coords (older_sample->event, &older_x, &older_y);
+ }
+
+ swipe_sample = st_swipe_sample_new (event,
+ g_get_monotonic_time() / (1.0 * G_USEC_PER_SEC) - self->priv->capture_start_time,
+ x - self->priv->capture_start_x,
+ y - self->priv->capture_start_y,
+ x - older_x,
+ y - older_y);
+
+ g_queue_push_tail (priv->recorded_samples_queue,
+ swipe_sample);
+
+ return swipe_sample;
+}
+
+static void
+st_swipe_sample_free (StSwipeSample *swipe_sample)
+{
+ clutter_event_free (swipe_sample->event);
+ g_slice_free (StSwipeSample, swipe_sample);
+}
+
+static void
+replay_events_from_recorded_samples (StScrollView *self)
+{
+ StScrollViewPrivate *priv = self->priv;
+ StSwipeSample *swipe_sample;
+
+ while ((swipe_sample = g_queue_pop_head (priv->recorded_samples_queue)) != NULL)
+ {
+ ClutterEvent *event;
+ ClutterActor *actor;
+ float x, y;
+
+ event = swipe_sample->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);
+ }
+}
+
+static void
+analyze_recorded_samples (StScrollView *self,
+ StSwipeDirection direction,
+ double *stroke_length,
+ double *average_velocity)
+{
+ StScrollViewPrivate *priv = self->priv;
+ StSwipeSample *sample;
+ gboolean first_time, done;
+ float position;
+ GList *node;
+
+ node = g_queue_peek_tail_link (priv->recorded_samples_queue);
+ sample = (StSwipeSample *) node->data;
+
+ if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+ position = sample->x;
+ else
+ position = sample->y;
+
+ *average_velocity = 0;
+ node = node->prev;
+ first_time = TRUE;
+ done = FALSE;
+ while (node != NULL && !done)
+ {
+ float older_position;
+ float dt;
+ float du;
+ float velocity;
+
+ StSwipeSample *older_sample = (StSwipeSample *) node->data;
+
+ dt = (float) (sample->time_since_start - older_sample->time_since_start);
+
+ /* Compress nearby events together
+ */
+ if (ABS (dt) < (1 / 60.) && node->prev != NULL)
+ {
+ node = node->prev;
+ continue;
+ }
+
+ if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+ {
+ older_position = older_sample->x;
+ sample->time_since_last_x_motion = dt;
+ }
+ else
+ {
+ older_position = older_sample->y;
+ sample->time_since_last_y_motion = dt;
+ }
+
+ du = position - older_position;
+
+ *stroke_length += ABS (du);
+
+ velocity = du / dt;
+
+ /* Only read samples up until the user switches directions
+ * or there are no samples left
+ */
+ if (first_time)
+ {
+ *average_velocity = velocity;
+ }
+ else if (*average_velocity * velocity < 0 || node->prev == NULL)
+ {
+ velocity = 0;
+ done = TRUE;
+ }
+ else
+ {
+ *average_velocity = (*average_velocity + velocity) / 2;
+ }
+
+ if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+ sample->horizontal_velocity = velocity;
+ else
+ sample->vertical_velocity = velocity;
+
+ first_time = FALSE;
+ sample = older_sample;
+ position = older_position;
+ node = node->prev;
+ }
+}
+
+static void
+discard_recorded_samples (StScrollView *self)
+{
+ StScrollViewPrivate *priv = self->priv;
+
+ g_queue_foreach (priv->recorded_samples_queue,
+ (GFunc) st_swipe_sample_free,
+ NULL);
+ g_queue_clear (priv->recorded_samples_queue);
+}
+
+static gboolean
+handle_captured_button_press_event (StScrollView *self,
+ ClutterEvent *event)
+{
+ StScrollViewPrivate *priv = self->priv;
+ gfloat x, y;
+ gdouble value;
+ 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_sample_from_event (self, event);
+
+ priv->drag_in_progress = TRUE;
+
+ /* catch and stop any motion from a previous swipe
+ */
+ g_object_get (priv->hadjustment, "value", &value, NULL);
+ st_adjustment_set_value (priv->hadjustment, value);
+ st_adjustment_clamp (priv->hadjustment, FALSE, 0);
+
+ g_object_get (priv->vadjustment, "value", &value, NULL);
+ st_adjustment_set_value (priv->vadjustment, value);
+ st_adjustment_clamp (priv->vadjustment, FALSE, 0);
+
+ return TRUE;
+}
+
+static void
+throw_child (StScrollView *self,
+ StSwipeDirection direction,
+ double velocity,
+ double swipe_time)
+{
+ StAdjustment *adjustment;
+ StScrollViewPrivate *priv = self->priv;
+ double value, lower, upper, step_increment, page_size;
+ double distance, air_time;
+
+ if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+ adjustment = priv->hadjustment;
+ else
+ adjustment = priv->vadjustment;
+
+ g_object_get (adjustment,
+ "value", &value,
+ "lower", &lower,
+ "upper", &upper,
+ "step-increment", &step_increment,
+ "page-size", &page_size,
+ NULL);
+
+ /* air time sort of arbitrarily chosen
+ */
+ air_time = 1.0 + swipe_time;
+
+ /* If the user released the button while moving at a fast
+ * velocity (faster than a step a second) then assume
+ * they're throwing what they were dragging,
+ * and send it flying.
+ */
+ distance = velocity * air_time;
+ if (ABS (velocity) >= step_increment)
+ {
+ ClutterAlpha *alpha;
+ double new_value;
+
+ new_value = value - distance;
+ new_value = CLAMP (new_value, lower, MAX (lower, upper - page_size));
+
+ alpha = clutter_alpha_new ();
+ clutter_alpha_set_mode (alpha, CLUTTER_EASE_OUT_QUAD);
+ st_adjustment_interpolate (adjustment,
+ alpha,
+ new_value,
+ air_time * 1000);
+ g_object_unref (alpha);
+ }
+}
+
+static gboolean
+handle_captured_button_release_event (StScrollView *self,
+ ClutterEvent *event)
+{
+ StScrollViewPrivate *priv = self->priv;
+ StSwipeSample *sample;
+ GtkSettings *settings;
+ int threshold;
+
+ double horizontal_stroke_length, average_horizontal_velocity;
+ double vertical_stroke_length, average_vertical_velocity;
+
+ if (!priv->drag_in_progress)
+ return FALSE;
+
+ sample = record_sample_from_event (self, event);
+
+ analyze_recorded_samples (self,
+ ST_SWIPE_DIRECTION_HORIZONTAL,
+ &horizontal_stroke_length,
+ &average_horizontal_velocity);
+ analyze_recorded_samples (self,
+ ST_SWIPE_DIRECTION_VERTICAL,
+ &vertical_stroke_length,
+ &average_vertical_velocity);
+
+ settings = gtk_settings_get_default();
+ g_object_get (G_OBJECT (settings), "gtk-dnd-drag-threshold", &threshold, NULL);
+
+ /* See if the user is clicking or swiping
+ */
+ if (vertical_stroke_length < threshold &&
+ horizontal_stroke_length < threshold)
+ {
+ /* No motion? It's a click! */
+ replay_events_from_recorded_samples (self);
+ }
+ else
+ {
+ throw_child (self,
+ ST_SWIPE_DIRECTION_HORIZONTAL,
+ average_horizontal_velocity,
+ sample->time_since_start);
+ throw_child (self,
+ ST_SWIPE_DIRECTION_VERTICAL,
+ average_vertical_velocity,
+ sample->time_since_start);
+ }
+
+ 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));
+
+ discard_recorded_samples (self);
+
+ return TRUE;
+}
+
+static void
+drag_child (StScrollView *self,
+ StSwipeDirection direction,
+ double delta)
+{
+ StScrollViewPrivate *priv = self->priv;
+ StAdjustment *adjustment;
+ gdouble value;
+
+ if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+ adjustment = priv->hadjustment;
+ else
+ adjustment = priv->vadjustment;
+
+ g_object_get (adjustment,
+ "value", &value,
+ NULL);
+
+ st_adjustment_set_value (adjustment, value + delta);
+ st_adjustment_clamp (adjustment, FALSE, 0);
+}
+
+static gboolean
+handle_captured_motion_event (StScrollView *self,
+ ClutterEvent *event)
+{
+ StSwipeSample *sample;
+ StScrollViewPrivate *priv = self->priv;
+
+ if (!priv->drag_in_progress)
+ return FALSE;
+
+ sample = record_sample_from_event (self, event);
+
+ drag_child (self, ST_SWIPE_DIRECTION_HORIZONTAL, - sample->movement_since_last_x_motion);
+ drag_child (self, ST_SWIPE_DIRECTION_VERTICAL, - sample->movement_since_last_y_motion);
+
+ 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)
{
@@ -831,6 +1300,69 @@ 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
+watch_for_swipes (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->recorded_samples_queue == NULL)
+ priv->recorded_samples_queue = g_queue_new ();
+}
+
+static void
+stop_watching_for_swipes (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->recorded_samples_queue != NULL)
+ {
+ discard_recorded_samples (self);
+
+ g_queue_free (priv->recorded_samples_queue);
+ priv->recorded_samples_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);
+ StScrollViewPrivate *priv = self->priv;
+
+ if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (self)) && priv->swipe_scroll)
+ watch_for_swipes (self);
+ else
+ stop_watching_for_swipes (self);
}
static void
@@ -859,6 +1391,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->recorded_samples_queue = NULL;
}
static void
@@ -1093,6 +1631,46 @@ 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);
+ if (CLUTTER_ACTOR_IS_VISIBLE ((ClutterActor *) scroll))
+ watch_for_swipes (scroll);
+ }
+ else
+ {
+ stop_watching_for_swipes (scroll);
+ }
+ }
+}
+
+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]