From ced3c2c63872021885cf91f134dde818096bae42 Mon Sep 17 00:00:00 2001 From: Kristian Rietveld Date: Wed, 19 Sep 2012 07:22:39 +0200 Subject: [PATCH 23/68] Improve overshooting behavior This is done by taking the stiffness calculations as found in the WebKit source code. These calculations are now used to compute the overshooting distance as well as handling the snap back animation. Also, we only start overshooting after we have hit a border in the container (i.e. an adjustment has reached its upper/lower bound). Also rename the timeout functions from deceleration to snap back, since these are not handling kinetic deceleration in this code. --- gtk/gtkscrolledwindow.c | 179 ++++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 80 deletions(-) diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c index 92e75a0..f3be303 100644 --- a/gtk/gtkscrolledwindow.c +++ b/gtk/gtkscrolledwindow.c @@ -84,6 +84,10 @@ #define OVERSHOOT_INVERSE_ACCELERATION 0.003 #define RELEASE_EVENT_TIMEOUT 1000 +#define BAND_STIFFNESS 20.0f +#define BAND_AMPLITUDE 0.31f +#define BAND_PERIOD 1.6f + /* Overlay scrollbars */ #define SCROLL_INTERVAL_INITIAL 300 #define SCROLL_INTERVAL_REPEAT 100 @@ -107,6 +111,9 @@ typedef struct { gdouble x_velocity; gdouble y_velocity; + gdouble x_force; + gdouble y_force; + gdouble unclamped_hadj_value; gdouble unclamped_vadj_value; @@ -143,12 +150,13 @@ typedef struct { typedef struct { GtkScrolledWindow *scrolled_window; - gint64 last_deceleration_time; + gint64 start_snap_back_time; gdouble x_velocity; gdouble y_velocity; - gdouble vel_cosine; - gdouble vel_sine; + + gint x_overshoot; + gint y_overshoot; } KineticScrollData; enum { @@ -260,7 +268,7 @@ static void gtk_scrolled_window_overlay_scrollbars_changed (GtkSettings *setting gpointer user_data); -static void gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window); +static void gtk_scrolled_window_start_snap_back (GtkScrolledWindow *scrolled_window); static gboolean gtk_scrolled_window_calculate_velocity (GtkScrolledWindow *scrolled_window, GdkEvent *event); static void gtk_scrolled_window_init_overlay_scrollbars (GtkScrolledWindow *window); @@ -2016,6 +2024,9 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, if (event->phase == GDK_EVENT_SCROLL_PHASE_START) priv->is_snapping_back = FALSE; + if (is_momentum_event && !is_overshot) + gtk_scrolled_window_calculate_velocity (scrolled_window, (GdkEvent *)event); + /* Scroll events are handled in two cases: * 1) We are not overshot and not snapping back, so scroll as * usual and also handle momentum events. @@ -2031,15 +2042,29 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, if (delta_x != 0.0 && scrolled_window->hscrollbar && (priv->overlay_scrollbars || gtk_widget_get_visible (scrolled_window->hscrollbar))) { + gboolean may_overshoot = FALSE; GtkAdjustment *adj; + /* Overshooting is allowed once the adjustment has reached + * the left/right. + */ adj = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)); + gdouble max_adj = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj); + if (gtk_adjustment_get_value (adj) < 1.0 || + gtk_adjustment_get_value (adj) > max_adj - 1.0) + may_overshoot = TRUE; - if (scrolled_window->hscrollbar_visible) + if (scrolled_window->hscrollbar_visible && (is_overshot || may_overshoot)) { + gdouble damped_delta; + + priv->x_force += delta_x; + damped_delta = ceil(priv->x_force / BAND_STIFFNESS); + damped_delta = damped_delta - old_overshoot_x; + _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj, - priv->unclamped_hadj_value + delta_x, + priv->unclamped_hadj_value + damped_delta, TRUE, FALSE); } @@ -2064,15 +2089,29 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, if (delta_y != 0.0 && scrolled_window->vscrollbar && (priv->overlay_scrollbars || gtk_widget_get_visible (scrolled_window->vscrollbar))) { + gboolean may_overshoot = FALSE; GtkAdjustment *adj; + /* Overshooting is allowed once the adjustment has reached + * the top/bottom. + */ adj = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->vscrollbar)); + gdouble max_adj = gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj); + if (gtk_adjustment_get_value (adj) < 1.0 || + gtk_adjustment_get_value (adj) > max_adj - 1.0) + may_overshoot = TRUE; - if (scrolled_window->vscrollbar_visible) + if (scrolled_window->vscrollbar_visible && (is_overshot || may_overshoot)) { + gdouble damped_delta; + + priv->y_force += delta_y; + damped_delta = ceil(priv->y_force / BAND_STIFFNESS); + damped_delta = damped_delta - old_overshoot_y; + _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj, - priv->unclamped_vadj_value + delta_y, + priv->unclamped_vadj_value + damped_delta, TRUE, FALSE); } @@ -2093,10 +2132,6 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, handled = TRUE; } - - - priv->last_scroll_event_time = gdk_event_get_time ((GdkEvent *)event); - gtk_scrolled_window_calculate_velocity (scrolled_window, (GdkEvent *)event); } _gtk_scrolled_window_get_overshoot (scrolled_window, @@ -2112,9 +2147,17 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, * also signals that the user's gesture has ended. */ if (is_overshot && - (event->phase == GDK_EVENT_SCROLL_PHASE_END || is_momentum_event)) + ((priv->last_scroll_event_time > 0 && is_momentum_event) || + event->phase == GDK_EVENT_SCROLL_PHASE_END)) start_snap_back = TRUE; + /* Reset force if gesture has ended. */ + if (event->phase == GDK_EVENT_SCROLL_PHASE_END) + { + priv->x_force = 0.0; + priv->y_force = 0.0; + } + /* If we should start a snap back and no current deceleration * is active, start the snap back. */ @@ -2130,9 +2173,10 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget, if (new_overshoot_x != 0 || new_overshoot_y != 0) { - gtk_scrolled_window_start_deceleration (scrolled_window); + gtk_scrolled_window_start_snap_back (scrolled_window); priv->x_velocity = 0.0; priv->y_velocity = 0.0; + priv->last_scroll_event_time = 0; } } } @@ -2207,16 +2251,16 @@ _gtk_scrolled_window_set_adjustment_value (GtkScrolledWindow *scrolled_window, } static gboolean -scrolled_window_deceleration_cb (gpointer user_data) +scrolled_window_snap_back_cb (gpointer user_data) { KineticScrollData *data = user_data; GtkScrolledWindow *scrolled_window = data->scrolled_window; GtkScrolledWindowPrivate *priv; GtkAdjustment *hadjustment, *vadjustment; gint old_overshoot_x, old_overshoot_y, overshoot_x, overshoot_y; - gdouble value; gint64 current_time; - guint elapsed; + gdouble elapsed; + gdouble damp_factor; priv = GTK_SCROLLED_WINDOW_GET_PRIVATE (scrolled_window); hadjustment = gtk_range_get_adjustment (GTK_RANGE (scrolled_window->hscrollbar)); @@ -2226,12 +2270,22 @@ scrolled_window_deceleration_cb (gpointer user_data) &old_overshoot_x, &old_overshoot_y); current_time = g_get_monotonic_time (); - elapsed = (current_time - data->last_deceleration_time) / 1000; - data->last_deceleration_time = current_time; + elapsed = (current_time - data->start_snap_back_time) / 1000000.0; + damp_factor = exp((((double)-elapsed) * BAND_STIFFNESS) / BAND_PERIOD); if (hadjustment && scrolled_window->hscrollbar_visible) { - value = priv->unclamped_hadj_value + (data->x_velocity * elapsed); + gdouble delta_x, value; + + delta_x = (data->x_overshoot + (data->x_velocity * elapsed * BAND_AMPLITUDE)) * damp_factor; + + if (fabs (delta_x) >= 1.0) + value = priv->unclamped_hadj_value + (delta_x - old_overshoot_x); + else + value = CLAMP (priv->unclamped_hadj_value, + gtk_adjustment_get_lower (hadjustment), + gtk_adjustment_get_upper (hadjustment) - + gtk_adjustment_get_page_size (hadjustment)); if (_gtk_scrolled_window_set_adjustment_value (scrolled_window, hadjustment, @@ -2243,7 +2297,17 @@ scrolled_window_deceleration_cb (gpointer user_data) if (vadjustment && scrolled_window->vscrollbar_visible) { - value = priv->unclamped_vadj_value + (data->y_velocity * elapsed); + gdouble delta_y, value; + + delta_y = (data->y_overshoot + (data->y_velocity * elapsed * BAND_AMPLITUDE)) * damp_factor; + + if (fabs (delta_y) >= 1.0) + value = priv->unclamped_vadj_value + (delta_y - old_overshoot_y); + else + value = CLAMP (priv->unclamped_vadj_value, + gtk_adjustment_get_lower (vadjustment), + gtk_adjustment_get_upper (vadjustment) - + gtk_adjustment_get_page_size (vadjustment)); if (_gtk_scrolled_window_set_adjustment_value (scrolled_window, vadjustment, @@ -2256,58 +2320,17 @@ scrolled_window_deceleration_cb (gpointer user_data) _gtk_scrolled_window_get_overshoot (scrolled_window, &overshoot_x, &overshoot_y); - if (overshoot_x == 0) - { - if (old_overshoot_x != 0) - { - /* Overshooting finished snapping back */ - data->x_velocity = 0; - } - else if (data->x_velocity > 0) - { - data->x_velocity -= FRICTION_DECELERATION * elapsed * data->vel_sine; - data->x_velocity = MAX (0, data->x_velocity); - } - else if (data->x_velocity < 0) - { - data->x_velocity += FRICTION_DECELERATION * elapsed * data->vel_sine; - data->x_velocity = MIN (0, data->x_velocity); - } - } - else if (overshoot_x < 0) - data->x_velocity += OVERSHOOT_INVERSE_ACCELERATION * elapsed; - else if (overshoot_x > 0) - data->x_velocity -= OVERSHOOT_INVERSE_ACCELERATION * elapsed; + if (overshoot_x != 0) + priv->x_force = overshoot_x * BAND_STIFFNESS; - if (overshoot_y == 0) - { - if (old_overshoot_y != 0) - { - /* Overshooting finished snapping back */ - 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 (overshoot_y != 0) + priv->y_force = overshoot_y * BAND_STIFFNESS; if (old_overshoot_x != overshoot_x || old_overshoot_y != overshoot_y) _gtk_scrolled_window_allocate_overshoot_window (scrolled_window); - if (overshoot_x != 0 || overshoot_y != 0 || - data->x_velocity != 0 || data->y_velocity != 0) + if (overshoot_x != 0 || overshoot_y != 0) return TRUE; else { @@ -2317,29 +2340,24 @@ scrolled_window_deceleration_cb (gpointer user_data) } static void -gtk_scrolled_window_start_deceleration (GtkScrolledWindow *scrolled_window) +gtk_scrolled_window_start_snap_back (GtkScrolledWindow *scrolled_window) { GtkScrolledWindowPrivate *priv = GTK_SCROLLED_WINDOW_GET_PRIVATE (scrolled_window); KineticScrollData *data; - gdouble angle; data = g_new0 (KineticScrollData, 1); data->scrolled_window = scrolled_window; - data->last_deceleration_time = g_get_monotonic_time (); + data->start_snap_back_time = g_get_monotonic_time (); data->x_velocity = priv->x_velocity; data->y_velocity = priv->y_velocity; - - /* We use sine/cosine as a factor to deceleration x/y components - * of the vector, so we care about the sign later. - */ - angle = atan2 (ABS (data->x_velocity), ABS (data->y_velocity)); - data->vel_cosine = cos (angle); - data->vel_sine = sin (angle); + _gtk_scrolled_window_get_overshoot (scrolled_window, + &data->x_overshoot, + &data->y_overshoot); priv->deceleration_id = gdk_threads_add_timeout_full (G_PRIORITY_DEFAULT, FRAME_INTERVAL, - scrolled_window_deceleration_cb, + scrolled_window_snap_back_cb, data, (GDestroyNotify) g_free); } @@ -2360,13 +2378,14 @@ gtk_scrolled_window_calculate_velocity (GtkScrolledWindow *scrolled_window, gdouble delta_x, delta_y; if (gdk_event_get_scroll_deltas (event, &delta_x, &delta_y) && + priv->last_scroll_event_time > 0 && ABS (_time - priv->last_scroll_event_time) > STILL_THRESHOLD) { priv->x_velocity = delta_x / (gdouble) (_time - priv->last_scroll_event_time); priv->y_velocity = delta_y / (gdouble) (_time - priv->last_scroll_event_time); - - priv->last_scroll_event_time = _time; } + + priv->last_scroll_event_time = _time; } #undef STILL_THRESHOLD -- 1.7.10.2 (Apple Git-33)