[gtk+/touch-for-3.4: 4/65] scrolledwindow: Initial kinetic scrolling support
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk+/touch-for-3.4: 4/65] scrolledwindow: Initial kinetic scrolling support
- Date: Fri, 24 Feb 2012 15:25:10 +0000 (UTC)
commit bef9f5c6c4c6c7b5c65de6f50169d203069ffc51
Author: Carlos Garcia Campos <cgarcia igalia com>
Date: Fri Feb 11 13:43:56 2011 +0100
scrolledwindow: Initial kinetic scrolling support
Kinetic scrolling is only done on touch devices, since it is
sort of meaningless on pointer devices, besides it implies
a different input event handling on child widgets that is
unnecessary there.
docs/reference/gtk/gtk3-sections.txt | 2 +
gtk/gtk.symbols | 2 +
gtk/gtkscrolledwindow.c | 921 +++++++++++++++++++++++++++++++++-
gtk/gtkscrolledwindow.h | 3 +
4 files changed, 925 insertions(+), 3 deletions(-)
---
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index cdcad3f..e74a847 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -2939,6 +2939,8 @@ gtk_scrolled_window_get_min_content_width
gtk_scrolled_window_set_min_content_width
gtk_scrolled_window_get_min_content_height
gtk_scrolled_window_set_min_content_height
+gtk_scrolled_window_get_kinetic_scrolling
+gtk_scrolled_window_set_kinetic_scrolling
<SUBSECTION Standard>
GTK_SCROLLED_WINDOW
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index f3c3da8..2e976d5 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -2367,6 +2367,7 @@ gtk_scrollbar_new
gtk_scrolled_window_add_with_viewport
gtk_scrolled_window_get_hadjustment
gtk_scrolled_window_get_hscrollbar
+gtk_scrolled_window_get_kinetic_scrolling
gtk_scrolled_window_get_min_content_height
gtk_scrolled_window_get_min_content_width
gtk_scrolled_window_get_placement
@@ -2377,6 +2378,7 @@ gtk_scrolled_window_get_vadjustment
gtk_scrolled_window_get_vscrollbar
gtk_scrolled_window_new
gtk_scrolled_window_set_hadjustment
+gtk_scrolled_window_set_kinetic_scrolling
gtk_scrolled_window_set_min_content_height
gtk_scrolled_window_set_min_content_width
gtk_scrolled_window_set_placement
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index a86ffd9..5323576 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -35,6 +35,9 @@
#include "gtkscrolledwindow.h"
#include "gtkwindow.h"
#include "gtkviewport.h"
+#include "gtktimeline.h"
+#include "gtkdnd.h"
+#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtktypebuiltins.h"
#include "gtkintl.h"
@@ -122,6 +125,25 @@
#define DEFAULT_SCROLLBAR_SPACING 3
+/* 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)
+
+typedef struct
+{
+ gdouble x;
+ gdouble y;
+ guint32 time;
+} MotionData;
+
+typedef struct
+{
+ GArray *buffer;
+ guint len;
+} MotionEventList;
+
struct _GtkScrolledWindowPrivate
{
GtkWidget *hscrollbar;
@@ -140,8 +162,24 @@ struct _GtkScrolledWindowPrivate
gint min_content_width;
gint min_content_height;
-};
+ /* Kinetic scrolling */
+ GdkEvent *button_press_event;
+ 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;
+ MotionEventList motion_events;
+ GtkTimeline *deceleration_timeline;
+ gdouble dx;
+ gdouble dy;
+ gdouble deceleration_rate;
+ gdouble overshoot;
+ guint accumulated_delta;
+};
enum {
PROP_0,
@@ -153,7 +191,8 @@ enum {
PROP_WINDOW_PLACEMENT_SET,
PROP_SHADOW_TYPE,
PROP_MIN_CONTENT_WIDTH,
- PROP_MIN_CONTENT_HEIGHT
+ PROP_MIN_CONTENT_HEIGHT,
+ PROP_KINETIC_SCROLLING
};
/* Signals */
@@ -182,6 +221,8 @@ static void gtk_scrolled_window_size_allocate (GtkWidget *widge
GtkAllocation *allocation);
static gboolean gtk_scrolled_window_scroll_event (GtkWidget *widget,
GdkEventScroll *event);
+static gboolean gtk_scrolled_window_button_press_event (GtkWidget *widget,
+ GdkEvent *event);
static gboolean gtk_scrolled_window_focus (GtkWidget *widget,
GtkDirectionType direction);
static void gtk_scrolled_window_add (GtkContainer *container,
@@ -220,6 +261,11 @@ static void gtk_scrolled_window_get_preferred_width_for_height (GtkWidget
gint *minimum_height,
gint *natural_height);
+static void motion_event_list_init (MotionEventList *motion_events,
+ guint size);
+static void motion_event_list_clear (MotionEventList *motion_events);
+
+
static guint signals[LAST_SIGNAL] = {0};
G_DEFINE_TYPE (GtkScrolledWindow, gtk_scrolled_window, GTK_TYPE_BIN)
@@ -409,6 +455,22 @@ gtk_scrolled_window_class_init (GtkScrolledWindowClass *class)
P_("The minimum height that the scrolled window will allocate to its content"),
-1, G_MAXINT, -1,
GTK_PARAM_READWRITE));
+
+ /**
+ * GtkScrolledWindow:kinetic-scrolling:
+ *
+ * Whether kinetic scrolling mode is enabled,
+ * only applies to devices with source %GDK_SOURCE_TOUCH
+ *
+ * Since: 3.4
+ */
+ g_object_class_install_property (gobject_class,
+ PROP_KINETIC_SCROLLING,
+ g_param_spec_boolean ("kinetic-scrolling",
+ P_("Kinetic Scrolling"),
+ P_("Enable kinetic scrolling mode."),
+ TRUE,
+ GTK_PARAM_READABLE | GTK_PARAM_WRITABLE));
/**
* GtkScrolledWindow::scroll-child:
* @scrolled_window: a #GtkScrolledWindow
@@ -508,6 +570,9 @@ 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;
+
+ gtk_scrolled_window_set_kinetic_scrolling (scrolled_window, TRUE);
}
/**
@@ -1006,6 +1071,95 @@ gtk_scrolled_window_get_shadow_type (GtkScrolledWindow *scrolled_window)
return scrolled_window->priv->shadow_type;
}
+/**
+ * gtk_scrolled_window_set_kinetic_scrolling:
+ * @scrolled_window: a #GtkScrolledWindow
+ * @enable: %TRUE to enable kinetic scrolling
+ *
+ * Enables or disables kinetic scrolling in @scrolled_window.
+ *
+ * Since: 3.2
+ **/
+void
+gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
+ gboolean enable)
+{
+ GtkScrolledWindowPrivate *priv;
+
+ g_return_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window));
+
+ priv = scrolled_window->priv;
+ if (priv->kinetic_scrolling_enabled == enable)
+ return;
+
+ 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),
+ NULL);
+ }
+ 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);
+ priv->button_press_id = 0;
+ }
+ if (priv->motion_notify_id > 0)
+ {
+ g_signal_handler_disconnect (scrolled_window, priv->motion_notify_id);
+ priv->motion_notify_id = 0;
+ }
+ if (priv->button_release_id > 0)
+ {
+ g_signal_handler_disconnect (scrolled_window, priv->button_release_id);
+ priv->button_release_id = 0;
+ }
+ motion_event_list_clear (&priv->motion_events);
+ }
+ g_object_notify (G_OBJECT (scrolled_window), "kinetic-scrolling");
+}
+
+/**
+ * gtk_scrolled_window_get_kinetic_scrolling:
+ * @scrolled_window: a #GtkScrolledWindow
+ *
+ * Returns whether kinetic scrolling is turned on for @scrolled_window.
+ *
+ * Return value: %TRUE if kinetic scrolling in @scrolled_window is enabled.
+ *
+ * Since: 3.2
+ **/
+gboolean
+gtk_scrolled_window_get_kinetic_scrolling (GtkScrolledWindow *scrolled_window)
+{
+ GtkScrolledWindowPrivate *priv;
+
+ g_return_val_if_fail (GTK_IS_SCROLLED_WINDOW (scrolled_window), FALSE);
+
+ priv = scrolled_window->priv;
+
+ return priv->kinetic_scrolling_enabled;
+}
+
static void
gtk_scrolled_window_destroy (GtkWidget *widget)
{
@@ -1033,6 +1187,23 @@ gtk_scrolled_window_destroy (GtkWidget *widget)
priv->vscrollbar = NULL;
}
+ if (priv->button_press_id > 0)
+ {
+ g_signal_handler_disconnect (scrolled_window, priv->button_press_id);
+ priv->button_press_id = 0;
+ }
+ if (priv->motion_notify_id > 0)
+ {
+ g_signal_handler_disconnect (widget, priv->motion_notify_id);
+ priv->motion_notify_id = 0;
+ }
+ if (priv->button_release_id > 0)
+ {
+ g_signal_handler_disconnect (widget, priv->button_release_id);
+ priv->button_release_id = 0;
+ }
+ motion_event_list_clear (&priv->motion_events);
+
GTK_WIDGET_CLASS (gtk_scrolled_window_parent_class)->destroy (widget);
}
@@ -1086,6 +1257,10 @@ gtk_scrolled_window_set_property (GObject *object,
gtk_scrolled_window_set_min_content_height (scrolled_window,
g_value_get_int (value));
break;
+ case PROP_KINETIC_SCROLLING:
+ gtk_scrolled_window_set_kinetic_scrolling (scrolled_window,
+ g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1132,6 +1307,9 @@ gtk_scrolled_window_get_property (GObject *object,
case PROP_MIN_CONTENT_HEIGHT:
g_value_set_int (value, priv->min_content_height);
break;
+ case PROP_KINETIC_SCROLLING:
+ g_value_set_boolean (value, priv->kinetic_scrolling_enabled);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -1554,7 +1732,6 @@ gtk_scrolled_window_size_allocate (GtkWidget *widget,
gtk_widget_style_get (widget, "scrollbars-within-bevel", &scrollbars_within_bevel, NULL);
gtk_widget_set_allocation (widget, allocation);
-
gtk_style_context_restore (context);
if (priv->hscrollbar_policy == GTK_POLICY_ALWAYS)
@@ -1878,6 +2055,744 @@ gtk_scrolled_window_scroll_event (GtkWidget *widget,
return FALSE;
}
+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)
+{
+ guint n_motions = MIN (motion_events->len, motion_events->buffer->len);
+
+ if (n_motions == 0)
+ return NULL;
+
+ return &g_array_index (motion_events->buffer, MotionData, n_motions - 1);
+}
+
+static MotionData *
+motion_event_list_append (MotionEventList *motion_events)
+{
+ if (motion_events->len == motion_events->buffer->len)
+ {
+ motion_events->buffer = g_array_remove_index (motion_events->buffer, 0);
+ g_array_set_size (motion_events->buffer, motion_events->len);
+ }
+ 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++)
+ {
+ MotionData *motion = &g_array_index (motion_events->buffer, MotionData, i);
+
+ *x_average += motion->x;
+ *y_average += motion->y;
+ avg += motion->time;
+ }
+
+ *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)
+ {
+ interpolation = g_slice_new0 (InterpolationData);
+ g_object_set_data_full (G_OBJECT (adjustment),
+ I_("gtk-adjustment-interpolation"),
+ interpolation,
+ (GDestroyNotify)interpolation_free);
+ }
+
+ return interpolation;
+}
+
+static void
+interpolation_frame_cb (GtkTimeline *timeline,
+ gdouble progress,
+ GtkAdjustment *adjustment)
+{
+ gdouble new_value, lower, upper, page_size;
+ InterpolationData *interpolation;
+
+ interpolation = adjustment_get_interpolation (adjustment);
+
+ /* EASE_OUT_QUAD */
+ progress = -1.0 * progress * (progress - 2);
+
+ new_value = interpolation->old_position +
+ (interpolation->new_position - interpolation->old_position) * progress;
+
+ gtk_adjustment_set_value (adjustment, new_value);
+
+ lower = gtk_adjustment_get_lower (adjustment);
+ upper = gtk_adjustment_get_upper (adjustment);
+ page_size = gtk_adjustment_get_page_size (adjustment);
+
+ /* Stop the interpolation if we've reached the end of the adjustment */
+ if (new_value < lower || new_value > upper - page_size)
+ {
+ g_object_unref (interpolation->timeline);
+ interpolation->timeline = NULL;
+ }
+}
+
+static void
+interpolation_finished_cb (GtkTimeline *timeline,
+ GtkAdjustment *adjustment)
+{
+ InterpolationData *interpolation;
+ gdouble value;
+
+ interpolation = adjustment_get_interpolation (adjustment);
+ value = interpolation->new_position;
+
+#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)
+ {
+ 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);
+
+ lower = gtk_adjustment_get_lower (adjustment);
+ upper = gtk_adjustment_get_upper (adjustment);
+ page_size = gtk_adjustment_get_page_size (adjustment);
+
+ if (interpolation->new_position < lower)
+ {
+ interpolation->old_position = lower;
+ _gtk_timeline_start (interpolation->timeline);
+ }
+ else if (interpolation->new_position > (upper - page_size))
+ {
+ interpolation->old_position = upper - page_size;
+ _gtk_timeline_start (interpolation->timeline);
+ }
+ return;
+ }
+ else
+ value = interpolation->old_position;
+#endif
+
+ /* 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);
+
+ interpolation->old_position = gtk_adjustment_get_value (adjustment);
+ interpolation->new_position = value;
+
+ if (!interpolation->timeline)
+ {
+ 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);
+ }
+ 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);
+ }
+
+ _gtk_timeline_start (interpolation->timeline);
+}
+
+static void
+gtk_scrolled_window_clamp_adjustments (GtkScrolledWindow *scrolled_window,
+ guint duration,
+ gboolean horizontal,
+ gboolean vertical)
+{
+ 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)
+ {
+ 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);
+ }
+
+ if (vertical && vadjustment)
+ {
+ 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);
+
+ 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);
+ }
+}
+
+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)
+{
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ gboolean stop = TRUE;
+ gdouble frame_interval;
+
+ 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);
+ }
+ }
+
+ 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.
+ */
+ 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
+ {
+ gtk_scrolled_window_clamp_adjustments (scrolled_window,
+ INTERPOLATION_DURATION,
+ TRUE, TRUE);
+ }
+}
+
+static gboolean
+gtk_scrolled_window_button_release_event (GtkWidget *widget,
+ GdkEvent *_event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ gdouble distance;
+ GdkEventButton *event;
+
+ if (_event->type != GDK_BUTTON_RELEASE)
+ return FALSE;
+
+ event = (GdkEventButton *)_event;
+
+ if (event->button != 1)
+ return FALSE;
+
+ gdk_device_ungrab (gdk_event_get_device (_event), event->time);
+
+ if (priv->motion_notify_id > 0)
+ {
+ g_signal_handler_disconnect (widget, priv->motion_notify_id);
+ priv->motion_notify_id = 0;
+ }
+ if (priv->button_release_id > 0)
+ {
+ g_signal_handler_disconnect (widget, priv->button_release_id);
+ priv->button_release_id = 0;
+ }
+
+ if (!priv->in_drag)
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return 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);
+
+ /* Reset motion event buffer */
+ motion_event_list_reset (&priv->motion_events);
+
+ return TRUE;
+}
+
+static gboolean
+gtk_scrolled_window_motion_notify_event (GtkWidget *widget,
+ GdkEvent *_event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ MotionData *motion;
+ GtkAdjustment *hadjustment;
+ GtkAdjustment *vadjustment;
+ gdouble dx, dy;
+ GdkEventMotion *event;
+
+ if (_event->type != GDK_MOTION_NOTIFY)
+ return FALSE;
+
+ event = (GdkEventMotion *)_event;
+
+ if (!(event->state & GDK_BUTTON1_MASK))
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return FALSE;
+
+ /* 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))
+ priv->in_drag = TRUE;
+ else
+ return TRUE;
+ }
+
+ motion = motion_event_list_last (&priv->motion_events);
+
+ if (motion)
+ {
+ hadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->hscrollbar));
+ if (hadjustment)
+ {
+ dx = (motion->x - event->x_root) + gtk_adjustment_get_value (hadjustment);
+ gtk_adjustment_set_value (hadjustment, dx);
+ }
+
+ vadjustment = gtk_range_get_adjustment (GTK_RANGE (priv->vscrollbar));
+ if (vadjustment)
+ {
+ dy = (motion->y - event->y_root) + gtk_adjustment_get_value (vadjustment);
+ gtk_adjustment_set_value (vadjustment, dy);
+ }
+ }
+
+ motion = motion_event_list_append (&priv->motion_events);
+ motion->x = event->x_root;
+ motion->y = event->y_root;
+ motion->time = event->time;
+
+ return TRUE;
+}
+
+static gboolean
+gtk_scrolled_window_button_press_event (GtkWidget *widget,
+ GdkEvent *_event)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv = scrolled_window->priv;
+ GtkWidget *child;
+ MotionData *motion;
+ GtkWidget *event_widget;
+ GdkEventButton *event;
+ GdkDevice *device, *source_device;
+ GdkInputSource source;
+
+ if (_event->type != GDK_BUTTON_PRESS)
+ return FALSE;
+
+ /* If scrollbars are not visible, we don't do kinetic scrolling */
+ if (!priv->vscrollbar_visible && !priv->hscrollbar_visible)
+ return FALSE;
+
+ source_device = gdk_event_get_source_device (_event);
+ source = gdk_device_get_source (source_device);
+
+ if (source != GDK_SOURCE_TOUCH)
+ return FALSE;
+
+ event = (GdkEventButton *)_event;
+
+ if (event->button != 1)
+ return FALSE;
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+ if (!child)
+ return FALSE;
+
+ event_widget = gtk_get_event_widget (_event);
+ if (priv->hscrollbar == event_widget || priv->vscrollbar == event_widget)
+ return FALSE;
+
+ device = gdk_event_get_device (_event);
+ gdk_device_grab (device,
+ gtk_widget_get_window (widget),
+ GDK_OWNERSHIP_WINDOW,
+ TRUE,
+ GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK,
+ NULL,
+ event->time);
+ gtk_device_grab_add (widget, 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;
+ }
+
+ priv->motion_notify_id =
+ g_signal_connect (widget, "captured-event",
+ G_CALLBACK (gtk_scrolled_window_motion_notify_event),
+ NULL);
+ priv->button_release_id =
+ g_signal_connect (widget, "captured-event",
+ G_CALLBACK (gtk_scrolled_window_button_release_event),
+ NULL);
+
+ priv->in_drag = FALSE;
+
+ return FALSE;
+}
+
static gboolean
gtk_scrolled_window_focus (GtkWidget *widget,
GtkDirectionType direction)
diff --git a/gtk/gtkscrolledwindow.h b/gtk/gtkscrolledwindow.h
index b05b8aa..8571c1a 100644
--- a/gtk/gtkscrolledwindow.h
+++ b/gtk/gtkscrolledwindow.h
@@ -117,6 +117,9 @@ void gtk_scrolled_window_set_min_content_width (GtkScrolledWindow *sc
gint gtk_scrolled_window_get_min_content_height (GtkScrolledWindow *scrolled_window);
void gtk_scrolled_window_set_min_content_height (GtkScrolledWindow *scrolled_window,
gint height);
+void gtk_scrolled_window_set_kinetic_scrolling (GtkScrolledWindow *scrolled_window,
+ gboolean enable);
+gboolean gtk_scrolled_window_get_kinetic_scrolling (GtkScrolledWindow *scrolled_window);
gint _gtk_scrolled_window_get_scrollbar_spacing (GtkScrolledWindow *scrolled_window);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]