[gtk+/multitouch: 37/121] gtk, scrolledwindow: Rework physics behind kinetic scrolling
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/multitouch: 37/121] gtk, scrolledwindow: Rework physics behind kinetic scrolling
- Date: Thu, 12 Jan 2012 14:20:12 +0000 (UTC)
commit 3f6537aec6e6b9afcd69922037bda7d922ccf745
Author: Carlos Garnacho <carlosg gnome org>
Date: Tue Nov 22 20:08:46 2011 +0100
gtk,scrolledwindow: Rework physics behind kinetic scrolling
The maths being used didn't resemble much about velocities or
friction/deceleration, so reimplement it in terms of velocity
vectors and decelerations, measured in pixels/ms^2.
Overshooting is also handled within the deceleration effect,
turning into a constant acceleration vector in the opposite
direction so it returns elastically within the boundaries.
gtk/gtkscrolledwindow.c | 954 ++++++++++++++++-------------------------------
1 files changed, 316 insertions(+), 638 deletions(-)
---
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index 3ef101a..469ee97 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -35,7 +35,6 @@
#include "gtkscrolledwindow.h"
#include "gtkwindow.h"
#include "gtkviewport.h"
-#include "gtktimeline.h"
#include "gtkdnd.h"
#include "gtkmain.h"
#include "gtkprivate.h"
@@ -128,24 +127,10 @@
#define TOUCH_BYPASS_CAPTURED_THRESHOLD 30
/* Kinetic scrolling */
-#define FPS 60
-#define FRAME_INTERVAL(fps) (1000. / fps)
-#define INTERPOLATION_DURATION 250
-#define INTERPOLATION_DURATION_OVERSHOOT(overshoot) (overshoot > 0.0 ? INTERPOLATION_DURATION : 10)
+#define FRAME_INTERVAL (1000 / 60)
#define MAX_OVERSHOOT_DISTANCE 50
-
-typedef struct
-{
- gdouble x;
- gdouble y;
- guint32 time;
-} MotionData;
-
-typedef struct
-{
- GArray *buffer;
- guint len;
-} MotionEventList;
+#define FRICTION_DECELERATION 0.003
+#define OVERSHOOT_INVERSE_ACCELERATION 0.012
struct _GtkScrolledWindowPrivate
{
@@ -173,27 +158,38 @@ struct _GtkScrolledWindowPrivate
GdkDevice *drag_device;
guint kinetic_scrolling_enabled : 1;
guint in_drag : 1;
- guint hmoving : 1;
- guint vmoving : 1;
guint button_press_id;
guint motion_notify_id;
guint button_release_id;
+
guint release_timeout_id;
- MotionEventList motion_events;
- GtkTimeline *deceleration_timeline;
- gdouble dx;
- gdouble dy;
- gdouble deceleration_rate;
- gdouble overshoot;
- guint accumulated_delta;
+ guint deceleration_id;
gdouble last_button_event_x_root;
gdouble last_button_event_y_root;
+ gdouble last_motion_event_x_root;
+ gdouble last_motion_event_y_root;
+ guint32 last_motion_event_time;
+
+ gdouble x_velocity;
+ gdouble y_velocity;
+
gdouble unclamped_hadj_value;
gdouble unclamped_vadj_value;
};
+typedef struct
+{
+ GtkScrolledWindow *scrolled_window;
+ gint64 last_deceleration_time;
+
+ gdouble x_velocity;
+ gdouble y_velocity;
+ gdouble vel_cosine;
+ gdouble vel_sine;
+} KineticScrollData;
+
enum {
PROP_0,
PROP_HADJUSTMENT,
@@ -256,6 +252,8 @@ static void gtk_scrolled_window_relative_allocation(GtkWidget *widge
GtkAllocation *allocation);
static void gtk_scrolled_window_adjustment_changed (GtkAdjustment *adjustment,
gpointer data);
+static void gtk_scrolled_window_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer data);
static void gtk_scrolled_window_update_real_placement (GtkScrolledWindow *scrolled_window);
@@ -279,14 +277,14 @@ static void gtk_scrolled_window_unrealize (GtkWidget *wid
static void gtk_scrolled_window_map (GtkWidget *widget);
static void gtk_scrolled_window_unmap (GtkWidget *widget);
-static void motion_event_list_init (MotionEventList *motion_events,
- guint size);
-static void motion_event_list_clear (MotionEventList *motion_events);
-
static void gtk_scrolled_window_auto_hide_scrollbars_start (GtkScrolledWindow *scrolled_window,
guint delay);
static void gtk_scrolled_window_auto_hide_scrollbars_stop (GtkScrolledWindow *scrolled_window);
+static gboolean _gtk_scrolled_window_set_adjustment_value (GtkScrolledWindow *scrolled_window,
+ GtkAdjustment *adjustment,
+ gdouble value,
+ gboolean allow_overshooting);
static guint signals[LAST_SIGNAL] = {0};
@@ -610,7 +608,6 @@ gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
gtk_scrolled_window_update_real_placement (scrolled_window);
priv->min_content_width = -1;
priv->min_content_height = -1;
- priv->deceleration_rate = 1.1f;
priv->last_button_event_x_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
priv->last_button_event_y_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
@@ -705,7 +702,12 @@ gtk_scrolled_window_set_hadjustment (GtkScrolledWindow *scrolled_window,
"changed",
G_CALLBACK (gtk_scrolled_window_adjustment_changed),
scrolled_window);
+ g_signal_connect (hadjustment,
+ "value-changed",
+ G_CALLBACK (gtk_scrolled_window_adjustment_value_changed),
+ scrolled_window);
gtk_scrolled_window_adjustment_changed (hadjustment, scrolled_window);
+ gtk_scrolled_window_adjustment_value_changed (hadjustment, scrolled_window);
child = gtk_bin_get_child (bin);
if (GTK_IS_SCROLLABLE (child))
@@ -768,7 +770,12 @@ gtk_scrolled_window_set_vadjustment (GtkScrolledWindow *scrolled_window,
"changed",
G_CALLBACK (gtk_scrolled_window_adjustment_changed),
scrolled_window);
+ g_signal_connect (vadjustment,
+ "value-changed",
+ G_CALLBACK (gtk_scrolled_window_adjustment_value_changed),
+ scrolled_window);
gtk_scrolled_window_adjustment_changed (vadjustment, scrolled_window);
+ gtk_scrolled_window_adjustment_value_changed (vadjustment, scrolled_window);
child = gtk_bin_get_child (bin);
if (GTK_IS_SCROLLABLE (child))
@@ -1137,7 +1144,6 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
priv->kinetic_scrolling_enabled = enable;
if (priv->kinetic_scrolling_enabled)
{
- motion_event_list_init (&priv->motion_events, 3);
priv->button_press_id =
g_signal_connect (scrolled_window, "captured-event",
G_CALLBACK (gtk_scrolled_window_button_press_event),
@@ -1149,21 +1155,6 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
}
else
{
- if (priv->deceleration_timeline)
- {
- g_object_unref (priv->deceleration_timeline);
- priv->deceleration_timeline = NULL;
- }
- if (priv->hscrollbar)
- {
- g_object_set_data (G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar))),
- I_("gtk-adjustment-interpolation"), NULL);
- }
- if (priv->vscrollbar)
- {
- g_object_set_data (G_OBJECT (gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar))),
- I_("gtk-adjustment-interpolation"), NULL);
- }
if (priv->button_press_id > 0)
{
g_signal_handler_disconnect (scrolled_window, priv->button_press_id);
@@ -1184,7 +1175,11 @@ gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
- motion_event_list_clear (&priv->motion_events);
+ if (priv->deceleration_id)
+ {
+ g_source_remove (priv->deceleration_id);
+ priv->deceleration_id = 0;
+ }
/* Restore the scrollbars */
gtk_scrolled_window_auto_hide_scrollbars_stop (scrolled_window);
@@ -1265,8 +1260,12 @@ gtk_scrolled_window_destroy (GtkWidget *widget)
g_source_remove (priv->release_timeout_id);
priv->release_timeout_id = 0;
}
+ if (priv->deceleration_id)
+ {
+ g_source_remove (priv->deceleration_id);
+ priv->deceleration_id = 0;
+ }
- motion_event_list_clear (&priv->motion_events);
gtk_scrolled_window_auto_hide_scrollbars_stop (scrolled_window);
GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->destroy (widget);
@@ -1734,14 +1733,14 @@ gtk_scrolled_window_relative_allocation (GtkWidget *widget,
}
}
-static void
+static gboolean
_gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_window,
gint *overshoot_x,
gint *overshoot_y)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkAdjustment *vadjustment, *hadjustment;
- gdouble lower, upper;
+ gdouble lower, upper, x, y;
/* Vertical overshoot */
vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
@@ -1750,11 +1749,11 @@ _gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_window,
gtk_adjustment_get_page_size (vadjustment);
if (priv->unclamped_vadj_value < lower)
- *overshoot_y = priv->unclamped_vadj_value - lower;
+ y = priv->unclamped_vadj_value - lower;
else if (priv->unclamped_vadj_value > upper)
- *overshoot_y = priv->unclamped_vadj_value - upper;
+ y = priv->unclamped_vadj_value - upper;
else
- *overshoot_y = 0;
+ y = 0;
/* Horizontal overshoot */
hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
@@ -1763,11 +1762,19 @@ _gtk_scrolled_window_get_overshoot (GtkScrolledWindow *scrolled_window,
gtk_adjustment_get_page_size (hadjustment);
if (priv->unclamped_hadj_value < lower)
- *overshoot_x = priv->unclamped_hadj_value - lower;
+ x = priv->unclamped_hadj_value - lower;
else if (priv->unclamped_hadj_value > upper)
- *overshoot_x = priv->unclamped_hadj_value - upper;
+ x = priv->unclamped_hadj_value - upper;
else
- *overshoot_x = 0;
+ x = 0;
+
+ if (overshoot_x)
+ *overshoot_x = x;
+
+ if (overshoot_y)
+ *overshoot_y = y;
+
+ return (x != 0 || y != 0);
}
static void
@@ -2207,7 +2214,6 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget,
gdouble delta;
delta = _gtk_range_get_wheel_delta (GTK_RANGE (range), event->direction);
-
gtk_adjustment_set_value (adjustment, gtk_adjustment_get_value (adjustment) + delta);
return TRUE;
@@ -2263,563 +2269,241 @@ gtk_scrolled_window_auto_hide_scrollbars_stop (GtkScrolledWindow *scrolled_windo
}
}
-static void
-motion_event_list_init (MotionEventList *motion_events,
- guint size)
-{
- if (G_UNLIKELY (motion_events->buffer))
- g_array_free (motion_events->buffer, TRUE);
- motion_events->buffer = g_array_sized_new (FALSE, TRUE, sizeof (MotionData), size);
- g_array_set_size (motion_events->buffer, size);
- motion_events->len = 0;
-}
-
-static void
-motion_event_list_reset (MotionEventList *motion_events)
-{
- motion_events->len = 0;
-}
-
-static void
-motion_event_list_clear (MotionEventList *motion_events)
-{
- if (G_LIKELY (motion_events->buffer))
- g_array_free (motion_events->buffer, TRUE);
- motion_events->buffer = NULL;
- motion_events->len = 0;
-}
-
-static MotionData *
-motion_event_list_first (MotionEventList *motion_events)
-{
- return &g_array_index (motion_events->buffer, MotionData, 0);
-}
-
-static MotionData *
-motion_event_list_last (MotionEventList *motion_events)
+static gboolean
+_gtk_scrolled_window_set_adjustment_value (GtkScrolledWindow *scrolled_window,
+ GtkAdjustment *adjustment,
+ gdouble value,
+ gboolean allow_overshooting)
{
- guint n_motions = MIN (motion_events->len, motion_events->buffer->len);
-
- if (n_motions == 0)
- return NULL;
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ gdouble lower, upper;
- return &g_array_index (motion_events->buffer, MotionData, n_motions - 1);
-}
+ lower = gtk_adjustment_get_lower (adjustment);
+ upper = gtk_adjustment_get_upper (adjustment) -
+ gtk_adjustment_get_page_size (adjustment);
-static MotionData *
-motion_event_list_append (MotionEventList *motion_events)
-{
- if (motion_events->len == motion_events->buffer->len)
+ if (allow_overshooting)
{
- motion_events->buffer = g_array_remove_index (motion_events->buffer, 0);
- g_array_set_size (motion_events->buffer, motion_events->len);
+ lower -= MAX_OVERSHOOT_DISTANCE;
+ upper += MAX_OVERSHOOT_DISTANCE;
}
- else
- {
- motion_events->len++;
- }
-
- return &g_array_index (motion_events->buffer, MotionData, motion_events->len - 1);
-}
-
-static void
-motion_event_list_average (MotionEventList *motion_events,
- gdouble *x_average,
- gdouble *y_average,
- guint32 *time_average)
-{
- guint i;
- guint n_motions = MIN (motion_events->len, motion_events->buffer->len);
- guint64 avg = 0;
- for (i = 0; i < n_motions; i++)
+ if (adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar)))
{
- MotionData *motion = &g_array_index (motion_events->buffer, MotionData, i);
+ priv->unclamped_hadj_value = CLAMP (value, lower, upper);
+ gtk_adjustment_set_value (adjustment, value);
- *x_average += motion->x;
- *y_average += motion->y;
- avg += motion->time;
+ return (priv->unclamped_hadj_value != value);
}
-
- *x_average /= n_motions;
- *y_average /= n_motions;
- *time_average = avg / n_motions;
-}
-
-typedef struct {
- gdouble old_position;
- gdouble new_position;
- gdouble value;
-
- GtkTimeline *timeline;
- gdouble progress;
-} InterpolationData;
-
-static void
-interpolation_free (InterpolationData *interpolation)
-{
- if (G_UNLIKELY (!interpolation))
- return;
-
- if (interpolation->timeline)
- g_object_unref (interpolation->timeline);
-
- g_slice_free (InterpolationData, interpolation);
-}
-
-static InterpolationData *
-adjustment_get_interpolation (GtkAdjustment *adjustment)
-{
- InterpolationData *interpolation;
-
- interpolation = g_object_get_data (G_OBJECT (adjustment), "gtk-adjustment-interpolation");
- if (!interpolation)
+ else if (adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar)))
{
- interpolation = g_slice_new0 (InterpolationData);
- g_object_set_data_full (G_OBJECT (adjustment),
- I_("gtk-adjustment-interpolation"),
- interpolation,
- (GDestroyNotify)interpolation_free);
+ priv->unclamped_vadj_value = CLAMP (value, lower, upper);
+ gtk_adjustment_set_value (adjustment, value);
+
+ return (priv->unclamped_vadj_value != value);
}
- return interpolation;
+ return FALSE;
}
-static void
-interpolation_frame_cb (GtkTimeline *timeline,
- gdouble progress,
- GtkAdjustment *adjustment)
+static gboolean
+scrolled_window_deceleration_cb (gpointer user_data)
{
- gdouble new_value, lower, upper, page_size;
- InterpolationData *interpolation;
+ KineticScrollData *data = user_data;
+ GtkScrolledWindow *scrolled_window = data->scrolled_window;
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkAdjustment *hadjustment, *vadjustment;
+ gint old_overshoot_x, old_overshoot_y, overshoot_x, overshoot_y;
+ gdouble value, clamp_value;
+ gint64 current_time;
+ guint elapsed;
- interpolation = adjustment_get_interpolation (adjustment);
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
- /* EASE_OUT_QUAD */
- progress = -1.0 * progress * (progress - 2);
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &old_overshoot_x, &old_overshoot_y);
- new_value = interpolation->old_position +
- (interpolation->new_position - interpolation->old_position) * progress;
+ current_time = g_get_monotonic_time ();
+ elapsed = (current_time - data->last_deceleration_time) / 1000;
+ data->last_deceleration_time = current_time;
- gtk_adjustment_set_value (adjustment, new_value);
+ if (hadjustment && priv->hscrollbar_visible)
+ {
+ value = priv->unclamped_hadj_value + (data->x_velocity * elapsed);
- lower = gtk_adjustment_get_lower (adjustment);
- upper = gtk_adjustment_get_upper (adjustment);
- page_size = gtk_adjustment_get_page_size (adjustment);
+ if (_gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ hadjustment,
+ value, TRUE))
+ data->x_velocity = 0;
+ }
+ else
+ data->x_velocity = 0;
- /* Stop the interpolation if we've reached the end of the adjustment */
- if (new_value < lower || new_value > upper - page_size)
+ if (vadjustment && priv->vscrollbar_visible)
{
- g_object_unref (interpolation->timeline);
- interpolation->timeline = NULL;
- }
-}
+ value = priv->unclamped_vadj_value + (data->y_velocity * elapsed);
-static void
-interpolation_finished_cb (GtkTimeline *timeline,
- GtkAdjustment *adjustment)
-{
- InterpolationData *interpolation;
- gdouble value;
+ if (_gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ vadjustment,
+ value, TRUE))
+ data->y_velocity = 0;
+ }
+ else
+ data->y_velocity = 0;
- interpolation = adjustment_get_interpolation (adjustment);
- value = interpolation->new_position;
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &overshoot_x, &overshoot_y);
-#if 0 /* FIXME: elastic effect doesn't work because gtk_adjustment_set_value() always clamps */
- if (_gtk_timeline_get_direction (timeline) == GTK_TIMELINE_DIRECTION_FORWARD)
+ if (overshoot_x == 0)
{
- gdouble lower, upper, page_size;
-
- _gtk_timeline_set_direction (timeline, GTK_TIMELINE_DIRECTION_BACKWARD);
- _gtk_timeline_set_duration (timeline, INTERPOLATION_DURATION);
- _gtk_timeline_rewind (timeline);
+ if (old_overshoot_x != 0)
+ {
+ /* Overshooting finished, clamp to border */
+ clamp_value = (old_overshoot_x < 0) ?
+ gtk_adjustment_get_lower (hadjustment) :
+ gtk_adjustment_get_upper (hadjustment) -
+ gtk_adjustment_get_page_size (hadjustment);
- lower = gtk_adjustment_get_lower (adjustment);
- upper = gtk_adjustment_get_upper (adjustment);
- page_size = gtk_adjustment_get_page_size (adjustment);
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ hadjustment,
+ clamp_value,
+ FALSE);
- if (interpolation->new_position < lower)
+ data->x_velocity = 0;
+ }
+ else if (data->x_velocity > 0)
{
- interpolation->old_position = lower;
- _gtk_timeline_start (interpolation->timeline);
+ data->x_velocity -= FRICTION_DECELERATION * elapsed * data->vel_sine;
+ data->x_velocity = MAX (0, data->x_velocity);
}
- else if (interpolation->new_position > (upper - page_size))
+ else if (data->x_velocity < 0)
{
- interpolation->old_position = upper - page_size;
- _gtk_timeline_start (interpolation->timeline);
+ data->x_velocity += FRICTION_DECELERATION * elapsed * data->vel_sine;
+ data->x_velocity = MIN (0, data->x_velocity);
}
- return;
}
- else
- value = interpolation->old_position;
-#endif
+ else if (overshoot_x < 0)
+ data->x_velocity += OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+ else if (overshoot_x > 0)
+ data->x_velocity -= OVERSHOOT_INVERSE_ACCELERATION * elapsed;
- /* Stop interpolation */
- g_object_unref (interpolation->timeline);
- interpolation->timeline = NULL;
- gtk_adjustment_set_value (adjustment, value);
-}
-
-static void
-adjustment_interpolate (GtkAdjustment *adjustment,
- gdouble value,
- guint duration)
-{
- InterpolationData *interpolation;
-
- interpolation = adjustment_get_interpolation (adjustment);
+ if (overshoot_y == 0)
+ {
+ if (old_overshoot_y != 0)
+ {
+ /* Overshooting finished, clamp to border */
+ clamp_value = (old_overshoot_y < 0) ?
+ gtk_adjustment_get_lower (vadjustment) :
+ gtk_adjustment_get_upper (vadjustment) -
+ gtk_adjustment_get_page_size (vadjustment);
- interpolation->old_position = gtk_adjustment_get_value (adjustment);
- interpolation->new_position = value;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ vadjustment,
+ clamp_value,
+ FALSE);
+ data->y_velocity = 0;
+ }
+ else if (data->y_velocity > 0)
+ {
+ data->y_velocity -= FRICTION_DECELERATION * elapsed * data->vel_cosine;
+ data->y_velocity = MAX (0, data->y_velocity);
+ }
+ else if (data->y_velocity < 0)
+ {
+ data->y_velocity += FRICTION_DECELERATION * elapsed * data->vel_cosine;
+ data->y_velocity = MIN (0, data->y_velocity);
+ }
+ }
+ else if (overshoot_y < 0)
+ data->y_velocity += OVERSHOOT_INVERSE_ACCELERATION * elapsed;
+ else if (overshoot_y > 0)
+ data->y_velocity -= OVERSHOOT_INVERSE_ACCELERATION * elapsed;
- if (!interpolation->timeline)
+ if (old_overshoot_x != overshoot_x ||
+ old_overshoot_y != overshoot_y)
{
- interpolation->timeline = _gtk_timeline_new (duration);
- _gtk_timeline_set_fps (interpolation->timeline, FPS);
- g_signal_connect (interpolation->timeline, "frame",
- G_CALLBACK (interpolation_frame_cb),
- adjustment);
- g_signal_connect (interpolation->timeline, "finished",
- G_CALLBACK (interpolation_finished_cb),
- adjustment);
+ if (overshoot_x >= 0 || overshoot_y >= 0)
+ {
+ /* We need to reallocate the widget to have it at
+ * negative offset, so there's a "gravity" on the
+ * bottom/right corner
+ */
+ gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+ }
+ else if (overshoot_x < 0 || overshoot_y < 0)
+ _gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
}
+
+ if (overshoot_x != 0 || overshoot_y != 0 ||
+ data->x_velocity != 0 || data->y_velocity != 0)
+ return TRUE;
else
{
- /* Extend the animation if it gets interrupted, otherwise frequent calls
- * to this function will end up with no advancements until the calls
- * finish (as the animation never gets a chance to start).
- */
- _gtk_timeline_set_direction (interpolation->timeline, GTK_TIMELINE_DIRECTION_FORWARD);
- _gtk_timeline_rewind (interpolation->timeline);
- _gtk_timeline_set_duration (interpolation->timeline, duration);
+ priv->deceleration_id = 0;
+ return FALSE;
}
-
- _gtk_timeline_start (interpolation->timeline);
}
static void
-gtk_scrolled_window_clamp_adjustments (GtkScrolledWindow *scrolled_window,
- guint duration,
- gboolean horizontal,
- gboolean vertical)
+gtk_scrolled_window_cancel_deceleration (GtkScrolledWindow *scrolled_window)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
- GtkWidget *child;
- GtkAdjustment *hadjustment;
- GtkAdjustment *vadjustment;
- gdouble value, lower, upper, step_increment, page_size;
- gdouble new_value;
-
- child = gtk_bin_get_child (GTK_BIN (scrolled_window));
- if (!child)
- return;
- hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
- vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
-
- if (horizontal && hadjustment)
+ if (priv->deceleration_id)
{
- value = gtk_adjustment_get_value (hadjustment);
- lower = gtk_adjustment_get_lower (hadjustment);
- upper = gtk_adjustment_get_upper (hadjustment);
- page_size = gtk_adjustment_get_page_size (hadjustment);
- step_increment = gtk_adjustment_get_step_increment (hadjustment);
-
- new_value = (rint ((value - lower) / step_increment) * step_increment) + lower;
- new_value = CLAMP (new_value, lower, upper - page_size);
- adjustment_interpolate (hadjustment, new_value, duration);
-
- priv->unclamped_hadj_value = new_value;
- gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+ g_source_remove (priv->deceleration_id);
+ priv->deceleration_id = 0;
}
- if (vertical && vadjustment)
+ /* Ensure the overshoot window is clamped to the adjustments' limits */
+ if (_gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
{
- value = gtk_adjustment_get_value (vadjustment);
- lower = gtk_adjustment_get_lower (vadjustment);
- upper = gtk_adjustment_get_upper (vadjustment);
- page_size = gtk_adjustment_get_page_size (vadjustment);
- step_increment = gtk_adjustment_get_step_increment (vadjustment);
+ GtkAdjustment *vadjustment, *hadjustment;
- new_value = (rint ((value - lower) / step_increment) * step_increment) + lower;
- new_value = CLAMP (new_value, lower, upper - page_size);
- adjustment_interpolate (vadjustment, new_value, duration);
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
- priv->unclamped_vadj_value = new_value;
- gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ vadjustment,
+ gtk_adjustment_get_value (vadjustment),
+ FALSE);
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window,
+ hadjustment,
+ gtk_adjustment_get_value (hadjustment),
+ FALSE);
}
}
static void
-deceleration_finished_cb (GtkTimeline *timeline,
- GtkScrolledWindow *scrolled_window)
-{
- GtkScrolledWindowPrivate *priv = scrolled_window->priv;
-
- gtk_scrolled_window_clamp_adjustments (scrolled_window,
- INTERPOLATION_DURATION_OVERSHOOT (priv->overshoot),
- priv->hmoving, priv->vmoving);
- g_object_unref (timeline);
- priv->deceleration_timeline = NULL;
-}
-
-static void
-deceleration_frame_cb (GtkTimeline *timeline,
- gdouble progress,
- GtkScrolledWindow *scrolled_window)
+gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window)
{
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
- GtkWidget *child;
- GtkAdjustment *hadjustment;
- GtkAdjustment *vadjustment;
- gboolean stop = TRUE;
- gdouble frame_interval;
+ KineticScrollData *data;
+ gdouble angle;
- child = gtk_bin_get_child (GTK_BIN (scrolled_window));
- if (!child)
- return;
-
- hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
- vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
-
- priv->accumulated_delta += _gtk_timeline_get_elapsed_time (timeline);
- frame_interval = FRAME_INTERVAL (FPS);
-
- if (priv->accumulated_delta <= frame_interval)
- stop = FALSE;
-
- while (priv->accumulated_delta > frame_interval)
- {
- gdouble value;
-
- if (hadjustment)
- {
- if (ABS (priv->dx) > 0.1)
- {
- value = priv->dx + gtk_adjustment_get_value (hadjustment);
- gtk_adjustment_set_value (hadjustment, value);
-
- if (priv->overshoot > 0.0)
- {
- if (value > gtk_adjustment_get_upper (hadjustment) - gtk_adjustment_get_page_size (hadjustment) ||
- value < gtk_adjustment_get_lower (hadjustment))
- priv->dx *= priv->overshoot;
- }
-
- priv->dx = priv->dx / priv->deceleration_rate;
-
- stop = FALSE;
- }
- else if (priv->hmoving)
- {
- priv->hmoving = FALSE;
- gtk_scrolled_window_clamp_adjustments (scrolled_window,
- INTERPOLATION_DURATION_OVERSHOOT (priv->overshoot),
- TRUE, FALSE);
- }
- }
+ data = g_new0 (KineticScrollData, 1);
+ data->scrolled_window = scrolled_window;
+ data->last_deceleration_time = g_get_monotonic_time ();
+ data->x_velocity = priv->x_velocity;
+ data->y_velocity = priv->y_velocity;
- if (vadjustment)
- {
- if (ABS (priv->dy) > 0.1)
- {
- value = priv->dy + gtk_adjustment_get_value (vadjustment);
- gtk_adjustment_set_value (vadjustment, value);
-
- if (priv->overshoot > 0.0)
- {
- if (value > gtk_adjustment_get_upper (vadjustment) - gtk_adjustment_get_page_size (vadjustment) ||
- value < gtk_adjustment_get_lower (vadjustment))
- priv->dy *= priv->overshoot;
- }
-
- priv->dy = priv->dy / priv->deceleration_rate;
-
- stop = FALSE;
- }
- else if (priv->vmoving)
- {
- priv->vmoving = FALSE;
- gtk_scrolled_window_clamp_adjustments (scrolled_window,
- INTERPOLATION_DURATION_OVERSHOOT (priv->overshoot),
- FALSE, TRUE);
- }
- }
- priv->accumulated_delta -= frame_interval;
- }
-
- if (stop)
- {
- _gtk_timeline_pause (timeline);
- deceleration_finished_cb (timeline, scrolled_window);
- }
-}
-
-static gdouble
-gtk_scrolled_window_get_deceleration_distance (GtkScrolledWindow *scrolled_window,
- gdouble pos_x,
- gdouble pos_y,
- guint32 release_time)
-{
- GtkScrolledWindowPrivate *priv = scrolled_window->priv;
- gdouble x_origin, y_origin;
- guint32 motion_time;
- gfloat frac;
- gdouble y, nx, ny;
-
- /* Get average position/time of last x mouse events */
- x_origin = y_origin = 0;
- motion_event_list_average (&priv->motion_events, &x_origin, &y_origin, &motion_time);
-
- /* Work out the fraction of 1/60th of a second that has elapsed */
- frac = (release_time - motion_time) / FRAME_INTERVAL (FPS);
-
- /* See how many units to move in 1/60th of a second */
- priv->dx = (x_origin - pos_x) / frac;
- priv->dy = (y_origin - pos_y) / frac;
-
- /* If the delta is too low for the equations to work,
- * bump the values up a bit.
+ /* We use sine/cosine as a factor to deceleration x/y components
+ * of the vector, so we care about the sign later.
*/
- if (ABS (priv->dx) < 1)
- priv->dx = (priv->dx > 0) ? 1 : -1;
- if (ABS (priv->dy) < 1)
- priv->dy = (priv->dy > 0) ? 1 : -1;
-
- /* We want n, where x / y^n < z,
- * x = Distance to move per frame
- * y = Deceleration rate
- * z = maximum distance from target
- *
- * Rearrange to n = log (x / z) / log (y)
- * To simplify, z = 1, so n = log (x) / log (y)
- */
- y = priv->deceleration_rate;
- nx = logf (ABS (priv->dx)) / logf (y);
- ny = logf (ABS (priv->dy)) / logf (y);
-
- return MAX (nx, ny);
-}
-
-static void
-gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window,
- gdouble distance)
-{
- GtkScrolledWindowPrivate *priv = scrolled_window->priv;
- guint duration;
-
- duration = MAX (1, (gint)(distance * FRAME_INTERVAL (FPS)));
- if (duration > INTERPOLATION_DURATION)
- {
- GtkAdjustment *hadjustment;
- GtkAdjustment *vadjustment;
- gdouble value, lower, upper, step_increment, page_size;
- gdouble n, y, d;
-
- /* Now we have n, adjust dx/dy so that we finish on a step
- * boundary.
- *
- * Distance moved, using the above variable names:
- *
- * d = x + x/y + x/y^2 + ... + x/y^n
- *
- * Using geometric series,
- *
- * d = (1 - 1/y^(n+1))/(1 - 1/y)*x
- *
- * Let a = (1 - 1/y^(n+1))/(1 - 1/y),
- *
- * d = a * x
- *
- * Find d and find its nearest page boundary, then solve for x
- *
- * x = d / a
- */
- n = distance;
- y = priv->deceleration_rate;
-
- hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
- if (hadjustment)
- {
- gdouble ax;
-
- value = gtk_adjustment_get_value (hadjustment);
- lower = gtk_adjustment_get_lower (hadjustment);
- upper = gtk_adjustment_get_upper (hadjustment);
- page_size = gtk_adjustment_get_page_size (hadjustment);
- step_increment = gtk_adjustment_get_step_increment (hadjustment);
-
- ax = (1.0 - 1.0 / pow (y, n + 1)) / (1.0 - 1.0 / y);
-
- /* Make sure we pick the next nearest step increment in the
- * same direction as the push.
- */
- priv->dx *= n;
- if (ABS (priv->dx) < step_increment / 2)
- d = round ((value + priv->dx - lower) / step_increment);
- else if (priv->dx > 0)
- d = ceil ((value + priv->dx - lower) / step_increment);
- else
- d = floor ((value + priv->dx - lower) / step_increment);
-
- if (priv->overshoot <= 0.0)
- d = CLAMP ((d * step_increment) + lower, lower, upper - page_size) - value;
- else
- d = ((d * step_increment) + lower) - value;
-
- priv->dx = d / ax;
- }
-
- vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
- if (vadjustment)
- {
- gdouble ay;
-
- value = gtk_adjustment_get_value (vadjustment);
- lower = gtk_adjustment_get_lower (vadjustment);
- upper = gtk_adjustment_get_upper (vadjustment);
- page_size = gtk_adjustment_get_page_size (vadjustment);
- step_increment = gtk_adjustment_get_step_increment (vadjustment);
-
- ay = (1.0 - 1.0 / pow (y, n + 1)) / (1.0 - 1.0 / y);
-
- priv->dy *= n;
- if (ABS (priv->dy) < step_increment / 2)
- d = round ((value + priv->dy - lower) / step_increment);
- else if (priv->dy > 0)
- d = ceil ((value + priv->dy - lower) / step_increment);
- else
- d = floor ((value + priv->dy - lower) / step_increment);
-
- if (priv->overshoot <= 0.0)
- d = CLAMP ((d * step_increment) + lower, lower, upper - page_size) - value;
- else
- d = ((d * step_increment) + lower) - value;
-
- priv->dy = d / ay;
- }
-
- priv->deceleration_timeline = _gtk_timeline_new (duration);
- _gtk_timeline_set_fps (priv->deceleration_timeline, FPS);
- g_signal_connect (priv->deceleration_timeline, "frame",
- G_CALLBACK (deceleration_frame_cb),
- scrolled_window);
- g_signal_connect (priv->deceleration_timeline, "finished",
- G_CALLBACK (deceleration_finished_cb),
- scrolled_window);
- priv->accumulated_delta = 0;
- priv->hmoving = priv->vmoving = TRUE;
- _gtk_timeline_start (priv->deceleration_timeline);
- }
- else
- {
- duration = INTERPOLATION_DURATION;
- gtk_scrolled_window_clamp_adjustments (scrolled_window,
- duration, TRUE, TRUE);
- }
-
+ angle = atan2 (ABS (data->x_velocity), ABS (data->y_velocity));
+ data->vel_cosine = cos (angle);
+ data->vel_sine = sin (angle);
+
+ scrolled_window->priv->deceleration_id =
+ gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT,
+ FRAME_INTERVAL,
+ scrolled_window_deceleration_cb,
+ data, (GDestroyNotify) g_free);
+#if 0
gtk_scrolled_window_auto_hide_scrollbars_start (scrolled_window,
duration + AUTO_HIDE_SCROLLBARS_TIMEOUT);
+#endif
}
static gboolean
@@ -2857,7 +2541,6 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget,
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkWidget *child;
- gdouble distance;
GdkEventButton *event;
if (_event->type != GDK_BUTTON_RELEASE)
@@ -2908,21 +2591,24 @@ gtk_scrolled_window_button_release_event (GtkWidget *widget,
}
priv->in_drag = FALSE;
- distance =
- gtk_scrolled_window_get_deceleration_distance (scrolled_window,
- event->x_root, event->y_root,
- event->time);
- gtk_scrolled_window_start_deceleration (scrolled_window, distance);
+ /* Zero out vector components without a visible scrollbar */
+ if (!priv->hscrollbar_visible)
+ priv->x_velocity = 0;
+ if (!priv->vscrollbar_visible)
+ priv->y_velocity = 0;
- if (distance == 0)
+ if (priv->x_velocity != 0 || priv->y_velocity != 0 ||
+ _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
+ {
+ gtk_scrolled_window_start_deceleration (scrolled_window);
+ priv->x_velocity = priv->y_velocity = 0;
+ }
+ else
{
priv->last_button_event_x_root = event->x_root;
priv->last_button_event_y_root = event->y_root;
}
- /* Reset motion event buffer */
- motion_event_list_reset (&priv->motion_events);
-
return GTK_CAPTURED_EVENT_HANDLED;
}
@@ -2932,8 +2618,9 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
{
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ gint old_overshoot_x, old_overshoot_y;
+ gint new_overshoot_x, new_overshoot_y;
GtkWidget *child;
- MotionData *motion;
GtkAdjustment *hadjustment;
GtkAdjustment *vadjustment;
gdouble dx, dy;
@@ -2957,8 +2644,10 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
/* Check if we've passed the drag threshold */
if (!priv->in_drag)
{
- motion = motion_event_list_first (&priv->motion_events);
- if (gtk_drag_check_threshold (widget, motion->x, motion->y, event->x_root, event->y_root))
+ if (gtk_drag_check_threshold (widget,
+ priv->last_button_event_x_root,
+ priv->last_button_event_y_root,
+ event->x_root, event->y_root))
{
if (priv->release_timeout_id)
{
@@ -2975,75 +2664,55 @@ gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
priv->last_button_event_y_root = -TOUCH_BYPASS_CAPTURED_THRESHOLD;
gtk_widget_release_captured_events (widget, FALSE);
- motion = motion_event_list_last (&priv->motion_events);
- if (motion)
- {
- gint old_overshoot_x, old_overshoot_y;
- gint new_overshoot_x, new_overshoot_y;
- gdouble lower, upper;
-
- _gtk_scrolled_window_get_overshoot (scrolled_window,
- &old_overshoot_x, &old_overshoot_y);
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &old_overshoot_x, &old_overshoot_y);
- hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
- if (hadjustment && priv->hscrollbar_visible)
- {
- lower = gtk_adjustment_get_lower (hadjustment);
- upper = gtk_adjustment_get_upper (hadjustment) -
- gtk_adjustment_get_page_size (hadjustment);
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
+ if (hadjustment && priv->hscrollbar_visible)
+ {
+ dx = (priv->last_motion_event_x_root - event->x_root) + priv->unclamped_hadj_value;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, hadjustment, dx, TRUE);
+ }
- dx = (motion->x - event->x_root) + priv->unclamped_hadj_value;
- priv->unclamped_hadj_value = dx;
- gtk_adjustment_set_value (hadjustment, dx);
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+ if (vadjustment && priv->vscrollbar_visible)
+ {
+ dy = (priv->last_motion_event_y_root - event->y_root) + priv->unclamped_vadj_value;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, vadjustment, dy, TRUE);
+ }
- priv->unclamped_hadj_value =
- CLAMP (priv->unclamped_hadj_value,
- lower - MAX_OVERSHOOT_DISTANCE,
- upper + MAX_OVERSHOOT_DISTANCE);
- }
+ _gtk_scrolled_window_get_overshoot (scrolled_window,
+ &new_overshoot_x, &new_overshoot_y);
- vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
- if (vadjustment && priv->vscrollbar_visible)
+ if (old_overshoot_x != new_overshoot_x ||
+ old_overshoot_y != new_overshoot_y)
+ {
+ if (new_overshoot_x >= 0 || new_overshoot_y >= 0)
{
- lower = gtk_adjustment_get_lower (vadjustment);
- upper = gtk_adjustment_get_upper (vadjustment) -
- gtk_adjustment_get_page_size (vadjustment);
-
- dy = (motion->y - event->y_root) + priv->unclamped_vadj_value;
- priv->unclamped_vadj_value = dy;
- gtk_adjustment_set_value (vadjustment, dy);
-
- priv->unclamped_vadj_value =
- CLAMP (priv->unclamped_vadj_value,
- lower - MAX_OVERSHOOT_DISTANCE,
- upper + MAX_OVERSHOOT_DISTANCE);
+ /* We need to reallocate the widget to have it at
+ * negative offset, so there's a "gravity" on the
+ * bottom/right corner
+ */
+ gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
}
+ else if (new_overshoot_x < 0 || new_overshoot_y < 0)
+ _gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
+ }
- _gtk_scrolled_window_get_overshoot (scrolled_window,
- &new_overshoot_x, &new_overshoot_y);
+ /* Find out X/Y components of the velocity vector, in pixels/ms */
+ if (event->time != priv->last_motion_event_time)
+ {
+ priv->x_velocity = (priv->last_motion_event_x_root - event->x_root) /
+ (gdouble) (event->time - priv->last_motion_event_time);
+ priv->y_velocity = (priv->last_motion_event_y_root - event->y_root) /
+ (gdouble) (event->time - priv->last_motion_event_time);
- if (old_overshoot_x != new_overshoot_x ||
- old_overshoot_y != new_overshoot_y)
- {
- if (new_overshoot_x >= 0 || new_overshoot_y >= 0)
- {
- /* We need to reallocate the widget to have it at
- * negative offset, so there's a "gravity" on the
- * bottom/right corner
- */
- gtk_widget_queue_resize (GTK_WIDGET (scrolled_window));
- }
- else if (new_overshoot_x < 0 || new_overshoot_y < 0)
- _gtk_scrolled_window_allocate_overshoot_window (scrolled_window);
- }
+ priv->last_motion_event_x_root = event->x_root;
+ priv->last_motion_event_y_root = event->y_root;
+ priv->last_motion_event_time = event->time;
}
- motion = motion_event_list_append (&priv->motion_events);
- motion->x = event->x_root;
- motion->y = event->y_root;
- motion->time = event->time;
-
return GTK_CAPTURED_EVENT_HANDLED;
}
@@ -3054,7 +2723,6 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
GtkScrolledWindowPrivate *priv = scrolled_window->priv;
GtkWidget *child;
- MotionData *motion;
gint threshold;
GtkWidget *event_widget;
GdkEventButton *event;
@@ -3096,8 +2764,9 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
return GTK_CAPTURED_EVENT_NONE;
}
- priv->last_button_event_x_root = event->x_root;
- priv->last_button_event_y_root = event->y_root;
+ priv->last_button_event_x_root = priv->last_motion_event_x_root = event->x_root;
+ priv->last_button_event_y_root = priv->last_motion_event_y_root = event->y_root;
+ priv->last_motion_event_time = event->time;
if (event->button != 1)
return GTK_CAPTURED_EVENT_NONE;
@@ -3119,18 +2788,7 @@ gtk_scrolled_window_button_press_event (GtkWidget *widget,
event->time);
gtk_device_grab_add (widget, priv->drag_device, TRUE);
- /* Reset motion buffer */
- motion_event_list_reset (&priv->motion_events);
- motion = motion_event_list_append (&priv->motion_events);
- motion->x = event->x_root;
- motion->y = event->y_root;
- motion->time = event->time;
-
- if (priv->deceleration_timeline)
- {
- g_object_unref (priv->deceleration_timeline);
- priv->deceleration_timeline = NULL;
- }
+ gtk_scrolled_window_cancel_deceleration (scrolled_window);
g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
"gtk-press-and-hold-timeout", &timeout,
@@ -3260,6 +2918,26 @@ gtk_scrolled_window_adjustment_changed (GtkAdjustment *adjustment,
}
static void
+gtk_scrolled_window_adjustment_value_changed (GtkAdjustment *adjustment,
+ gpointer user_data)
+{
+ GtkScrolledWindow *scrolled_window = user_data;
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+
+ /* Allow overshooting for kinetic scrolling operations */
+ if (priv->drag_device || priv->deceleration_id)
+ return;
+
+ /* Ensure GtkAdjustment and unclamped values are in sync */
+ if (priv->vscrollbar &&
+ adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar)))
+ priv->unclamped_vadj_value = gtk_adjustment_get_value (adjustment);
+ else if (priv->hscrollbar &&
+ adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar)))
+ priv->unclamped_hadj_value = gtk_adjustment_get_value (adjustment);
+}
+
+static void
gtk_scrolled_window_add (GtkContainer *container,
GtkWidget *child)
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]