Skip to content

Instantly share code, notes, and snippets.

@altacountbabi
Created May 3, 2025 17:14
Show Gist options
  • Save altacountbabi/c7bb9e2987c7921dbf433cb162f9fb41 to your computer and use it in GitHub Desktop.
Save altacountbabi/c7bb9e2987c7921dbf433cb162f9fb41 to your computer and use it in GitHub Desktop.
A patch for gtk4 to add basic interpolation between scrolls
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index f1b5b5112e0455d650fda8aed2c0ecf634ea94e7..252be9e2c217102e4e0e150b6bbd28b3ff9eb420 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -1207,23 +1207,12 @@ check_update_scrollbar_proximity (GtkScrolledWindow *sw,
}
static double
-get_wheel_detent_scroll_step (GtkScrolledWindow *sw,
- GtkOrientation orientation)
+get_wheel_detent_scroll_step (GtkScrollbar *scrollbar)
{
- GtkScrolledWindowPrivate *priv = gtk_scrolled_window_get_instance_private (sw);
- GtkScrollbar *scrollbar;
GtkAdjustment *adj;
double page_size;
double scroll_step;
- if (orientation == GTK_ORIENTATION_HORIZONTAL)
- scrollbar = GTK_SCROLLBAR (priv->hscrollbar);
- else
- scrollbar = GTK_SCROLLBAR (priv->vscrollbar);
-
- if (!scrollbar)
- return 0;
-
adj = gtk_scrollbar_get_adjustment (scrollbar);
page_size = gtk_adjustment_get_page_size (adj);
scroll_step = pow (page_size, 2.0 / 3.0);
@@ -1381,8 +1370,7 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
{
- delta_x *= get_wheel_detent_scroll_step (scrolled_window,
- GTK_ORIENTATION_HORIZONTAL);
+ delta_x *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->hscrollbar));
}
else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
delta_x *= MAGIC_SCROLL_FACTOR;
@@ -1404,8 +1392,7 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
{
- delta_y *= get_wheel_detent_scroll_step (scrolled_window,
- GTK_ORIENTATION_VERTICAL);
+ delta_y *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->vscrollbar));
}
else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
delta_y *= MAGIC_SCROLL_FACTOR;
@@ -1461,6 +1448,9 @@ scroll_controller_decelerate (GtkEventControllerScroll *scroll,
double initial_vel_y,
GtkScrolledWindow *scrolled_window)
{
+ GtkScrolledWindowPrivate *priv =
+ gtk_scrolled_window_get_instance_private (scrolled_window);
+
GdkScrollUnit scroll_unit;
gboolean shifted;
GdkModifierType state;
@@ -1481,11 +1471,11 @@ scroll_controller_decelerate (GtkEventControllerScroll *scroll,
if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
{
- initial_vel_x *= get_wheel_detent_scroll_step (scrolled_window,
- GTK_ORIENTATION_HORIZONTAL);
+ initial_vel_x *=
+ get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->hscrollbar));
- initial_vel_y *= get_wheel_detent_scroll_step (scrolled_window,
- GTK_ORIENTATION_VERTICAL);
+ initial_vel_y *=
+ get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->vscrollbar));
}
else
{
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index 252be9e2c217102e4e0e150b6bbd28b3ff9eb420..f0d93c5e0ffba7d98f8daf5f281379fd21e3a1c6 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -198,6 +198,17 @@
/* Scrolled off indication */
#define UNDERSHOOT_SIZE 40
+/* Min/max duration of the interpolation animation run with low
+ * precision mouse scroll. It's the time between the last wheel
+ * click and the animation end, when we arrive at the target
+ * scroll position. The duration of the animation will be
+ * shorter when we scroll farther because the animation is
+ * especially valuable over short distance. The value is in
+ * microseconds.
+*/
+#define INTERPOLATION_ANIMATION_MIN_DURATION 100000
+#define INTERPOLATION_ANIMATION_MAX_DURATION 300000
+
#define MAGIC_SCROLL_FACTOR 2.5
typedef struct _GtkScrolledWindowClass GtkScrolledWindowClass;
@@ -238,6 +249,69 @@ typedef struct
guint over_timeout_id;
} Indicator;
+/* Interpolation scroll animation informations about
+ * one axis (horizontal or vertical).
+ */
+typedef struct
+{
+ /* Timestamp of the latest wheel click which is the
+ * beginning time of the animation.
+ */
+ gint64 start_time;
+
+ // Scroll position from where the animation starts.
+ double start;
+
+ /* Speed of the animation at its beginning. This is
+ * the derivative of the animation curve at its
+ * start.
+ */
+ double start_speed;
+
+ /* Target scroll position of the animation. The
+ * animation curve must reach this point at
+ * start_time + duration.
+ */
+ double target;
+
+ // How much time the animation will durate (in microseconds).
+ double duration;
+
+ /* True if a scroll animation is running on this
+ * axis.
+ */
+ gboolean running;
+
+ /* The time when we get the last continuous
+ * scroll delta from the mouse. Continuous
+ * deltas come from high precision scroll
+ * wheel and we don't want an interpolation
+ * animation for this type of device.
+ *
+ * But it's not impossible to get discrete
+ * scroll deltas from high precision wheel.
+ * So we use this variable to know if we
+ * have not received a continuous scroll
+ * delta for a long time. In this case, we
+ * suppose we have a low precision wheel
+ * and we enable the interpolation
+ * animation.
+ */
+ gboolean last_continuous_delta_time_defined;
+ long last_continuous_delta_time;
+} InterpolationAnimationAxis;
+
+// Interpolation scroll animation informations.
+typedef struct
+{
+ // Axis informations.
+ InterpolationAnimationAxis horizontal;
+ InterpolationAnimationAxis vertical;
+
+ // Tick id of the animation callback.
+ guint tick_id;
+} InterpolationAnimation;
+
typedef struct
{
GtkWidget *child;
@@ -296,6 +370,8 @@ typedef struct
double unclamped_hadj_value;
double unclamped_vadj_value;
+
+ InterpolationAnimation interpolation_animation;
} GtkScrolledWindowPrivate;
enum {
@@ -1333,6 +1409,336 @@ stop_kinetic_scrolling_cb (GtkEventControllerScroll *scroll,
gtk_kinetic_scrolling_stop (priv->vscrolling);
}
+/*
+ Interpolation animation formulas explanations :
+
+ s : start position of the curve
+ w : final position of the curve
+ v : speed at the start of the curve
+ t : duration of the animation
+
+ ax³ + bx² + cx + d Position formula
+ 3ax² + 2bx + c Derivate of the position formula, so it's the "speed" formula
+
+ We have 4 equalities that set the conditions of the system :
+
+ a0³ + b0² + c0 + d = s At time 0, the position is the desired start position.
+ 3a0² + 2b0 + c = v At time 0, the speed is the desired start speed.
+ at³ + bt² + ct + d = w At the end of the animation, the position is the desired target position.
+ 3at² + 2bt + c = 0 At the end of the animation, the speed is 0 (smooth end).
+
+ "s", "w", "v" and "t" are known values. We need to express "a", "b", "c", and
+ "d" from these values.
+
+ a0³ + b0² + c0 + d = s
+ d = s
+
+ 3a0² + 2b0 + c = v
+ c = v
+
+ We have "c" and "d". We're left with "a" and "b".
+
+ at³ + bt² + ct + d = w
+
+ at³ = w - d - ct - bt²
+
+ w - d - ct - bt²
+ a = ----------------
+ t³
+
+ We have "a" but it is calculated with "b" which is unknown.
+ So we reinject this definition of "a" in the last equality.
+
+ 3at² + 2bt + c = 0
+
+ w - d - ct - bt²
+ 3 * ---------------- * t² + 2bt + c = 0
+ t³
+
+ ...
+
+ 3w - 3d - 2ct
+ b = -------------
+ t²
+
+ We have expressed "b" with known values only. So "b" become a known value and
+ "a" too!
+*/
+static inline double
+interpolation_animation_axis_get_current_value (const InterpolationAnimationAxis *axis_animation,
+ const double current_time)
+{
+ const double t = axis_animation->duration;
+ const double w = axis_animation->target;
+
+ const double d = axis_animation->start;
+ const double c = axis_animation->start_speed;
+
+ const double b = (3 * w - 3 * d - 2 * c * t) / (t * t);
+ const double a = (w - d - c * t - b * t * t) / (t * t * t);
+
+ const double x = current_time - axis_animation->start_time;
+
+ return a * x * x * x + b * x * x + c * x + d;
+}
+
+static inline double
+interpolation_animation_axis_get_current_speed (const InterpolationAnimationAxis *axis_animation,
+ const double current_time)
+{
+ const double t = axis_animation->duration;
+ const double w = axis_animation->target;
+
+ const double d = axis_animation->start;
+ const double c = axis_animation->start_speed;
+
+ const double b = (3 * w - 3 * d - 2 * c * t) / (t * t);
+ const double a = (w - d - c * t - b * t * t) / (t * t * t);
+
+ const double x = current_time - axis_animation->start_time;
+
+ return 3 * a * x * x + 2 * b * x + c;
+}
+
+// Handle scroll interpolation animation tick on one scroll axis (horizontal or vertical).
+static void
+interpolation_animation_axis_cb (InterpolationAnimationAxis *axis_animation,
+ const double current_time,
+ GtkScrolledWindow *scrolled_window,
+ GtkAdjustment *adj,
+ gboolean (*may_scroll)(GtkScrolledWindow*))
+{
+ // Nothing to do if the animation is not running.
+ if (!axis_animation->running)
+ {
+ return;
+ }
+
+ const double elapsed_time = current_time - axis_animation->start_time;
+
+ if (elapsed_time < axis_animation->duration)
+ {
+ /* If the animation duration has not been reached,
+ * we keep animation going.
+ */
+ const double animation_value =
+ interpolation_animation_axis_get_current_value (axis_animation, current_time);
+
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
+ animation_value);
+ }
+ else
+ {
+ /* If the animation duration has been reached, we set
+ * the scroll position to the animation target
+ * position and we mark the animation as stopped.
+ */
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
+ axis_animation->target);
+
+ axis_animation->running = FALSE;
+ }
+}
+
+// Handle scroll interpolation animation tick.
+static gboolean
+interpolation_animation_cb (GtkWidget *widget,
+ GdkFrameClock *clock,
+ gpointer user_data)
+{
+ GtkScrolledWindow *scrolled_window = GTK_SCROLLED_WINDOW (widget);
+ GtkScrolledWindowPrivate *priv =
+ gtk_scrolled_window_get_instance_private (scrolled_window);
+
+ InterpolationAnimation *animation = &priv->interpolation_animation;
+ const double current_time = gdk_frame_clock_get_frame_time (clock);
+
+ // Handle tick on each axis.
+
+ GtkAdjustment *h_adjustment =
+ gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->hscrollbar));
+ GtkAdjustment *v_adjustment =
+ gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->vscrollbar));
+
+ interpolation_animation_axis_cb (&animation->horizontal,
+ current_time,
+ scrolled_window,
+ h_adjustment,
+ may_hscroll);
+
+ interpolation_animation_axis_cb (&animation->vertical,
+ current_time,
+ scrolled_window,
+ v_adjustment,
+ may_vscroll);
+
+ gtk_scrolled_window_invalidate_overshoot (scrolled_window);
+
+ if (animation->horizontal.running || animation->vertical.running)
+ {
+ // If at least one animation is running, we keep the callback up.
+ return G_SOURCE_CONTINUE;
+ }
+ else
+ {
+ if(_gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
+ {
+ // Starting the overshoot animation
+ gtk_scrolled_window_start_deceleration (scrolled_window);
+ }
+
+ /* If all animations are done, we stop the
+ * interpolation animation callback.
+ */
+ return G_SOURCE_REMOVE;
+ }
+}
+
+static void
+interpolation_animation_axis_prevent_overshoot_delay (InterpolationAnimationAxis *axis_animation,
+ GtkScrollbar *scrollbar,
+ const double pixels_delta)
+{
+ GtkAdjustment *adjustment = gtk_scrollbar_get_adjustment (scrollbar);
+ const double lower = gtk_adjustment_get_lower (adjustment);
+ const double upper = gtk_adjustment_get_upper (adjustment)
+ - gtk_adjustment_get_page_size (adjustment);
+
+ /* If the animation start position is in the overshoot area and we
+ * want to scroll in the undershoot direction, start from
+ * undershoot bounds to prevent the delay effect.
+ */
+ if ((axis_animation->start < lower && pixels_delta > 0)
+ || (axis_animation->start > upper && pixels_delta < 0))
+ {
+ axis_animation->start = CLAMP (axis_animation->start, lower, upper);
+ axis_animation->target = axis_animation->start + pixels_delta;
+ axis_animation->start_speed = 0;
+ }
+}
+
+static void
+interpolation_animation_axis_compute_duration (InterpolationAnimationAxis *axis_animation,
+ GtkScrollbar *scrollbar)
+{
+ /* Calculate an appropriate interpolation animation duration.
+ *
+ * The interpolation animation allows the user to better "follow"
+ * the scrolling content on screen when he's using a low
+ * precision mouse wheel.
+ *
+ * When the user scroll delta is short, he doesn't want to scroll
+ * too far. This means that the content he would like to see is
+ * already almost entirely visible on the screen. Example: he's
+ * reading paragraph 3 and wants to make the paragraph 4 come
+ * into the view, even though he's still reading paragraph 3.
+ * In this case, the animation can be slow because the target
+ * is near.
+ *
+ * On the other hand, if the scroll delta is big, the user wants
+ * to access quickly a distant part of the document. The content
+ * on screen will completly change and the need of "following"
+ * content is less present, so the animation can be faster. If
+ * it is too slow, it feels too unresponsive and boring.
+ *
+ * So the larger the scroll delta, the shorter the animation
+ * time.
+ */
+
+ const double abs_delta = fabs(axis_animation->target - axis_animation->start);
+
+ GtkAdjustment *adjustment = gtk_scrollbar_get_adjustment (scrollbar);
+ const double page_size = gtk_adjustment_get_page_size (adjustment);
+
+ /* When delta = 0, duration = INTERPOLATION_ANIMATION_MAX_DURATION.
+ * When delta = scroll view size, duration = INTERPOLATION_ANIMATION_MIN_DURATION.
+ * We do a linear interpolation between these 2 "points".
+ */
+ const double duration = INTERPOLATION_ANIMATION_MAX_DURATION
+ + abs_delta * (INTERPOLATION_ANIMATION_MIN_DURATION
+ - INTERPOLATION_ANIMATION_MAX_DURATION) / page_size;
+
+ // Prevent the duration to be out of bounds.
+ axis_animation->duration = CLAMP (duration, INTERPOLATION_ANIMATION_MIN_DURATION,
+ INTERPOLATION_ANIMATION_MAX_DURATION);
+}
+
+static gboolean
+interpolation_animation_axis_heuristic (InterpolationAnimationAxis *axis_animation,
+ const double current_time,
+ const double delta)
+{
+ // If the delta is discrete
+ if (delta == (int) delta)
+ {
+ if (axis_animation->last_continuous_delta_time_defined
+ && axis_animation->last_continuous_delta_time > current_time - 1000000)
+ {
+ return FALSE;
+ }
+ else
+ {
+ axis_animation->last_continuous_delta_time_defined = FALSE;
+ return TRUE;
+ }
+ }
+ else // If the delta is continuous
+ {
+ axis_animation->last_continuous_delta_time_defined = TRUE;
+ axis_animation->last_continuous_delta_time = current_time;
+
+ return FALSE;
+ }
+}
+
+static void
+interpolation_animation_axis_handle_scroll (InterpolationAnimationAxis *axis_animation,
+ const double current_time,
+ const double delta,
+ GtkScrollbar *scrollbar,
+ const double unclamped_adj_value,
+ gboolean (*may_scroll)(GtkScrolledWindow*),
+ GtkScrolledWindow *scrolled_window)
+{
+ const double pixels_delta = delta * get_wheel_detent_scroll_step (scrollbar);
+ const double elapsed_time = current_time - axis_animation->start_time;
+
+ if (axis_animation->running && elapsed_time < axis_animation->duration)
+ {
+ /* If the animation is running, start from the current animation
+ * position and move the target. We keep the same speed to keep
+ * things smooth and not have animation jumps.
+ */
+ const double animation_value =
+ interpolation_animation_axis_get_current_value (axis_animation,
+ current_time);
+
+ axis_animation->start = animation_value;
+ axis_animation->target += pixels_delta;
+ axis_animation->start_speed =
+ interpolation_animation_axis_get_current_speed (axis_animation,
+ current_time);
+ }
+ else
+ {
+ // If animation was not running, smoothly start with a 0 speed.
+ axis_animation->start = unclamped_adj_value;
+ axis_animation->target = axis_animation->start + pixels_delta;
+ axis_animation->start_speed = 0;
+
+ axis_animation->running = TRUE;
+ }
+
+ interpolation_animation_axis_prevent_overshoot_delay (axis_animation,
+ scrollbar,
+ pixels_delta);
+
+ interpolation_animation_axis_compute_duration (axis_animation,
+ scrollbar);
+
+ axis_animation->start_time = current_time;
+}
+
static void
scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
double delta_x,
@@ -1347,8 +1753,6 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
state = gtk_event_controller_get_current_event_state (GTK_EVENT_CONTROLLER (scroll));
shifted = (state & GDK_SHIFT_MASK) != 0;
- gtk_scrolled_window_invalidate_overshoot (scrolled_window);
-
if (shifted)
{
double delta;
@@ -1358,54 +1762,94 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
delta_y = delta;
}
+ g_clear_handle_id (&priv->scroll_events_overshoot_id, g_source_remove);
+
+ GdkScrollUnit scroll_unit = gtk_event_controller_scroll_get_unit (scroll);
+
+ InterpolationAnimation *animation = &priv->interpolation_animation;
+
+ const gboolean animation_was_running =
+ animation->horizontal.running || animation->vertical.running;
+
+ GdkFrameClock *clock = gtk_widget_get_frame_clock (GTK_WIDGET (scrolled_window));
+ const double current_time = gdk_frame_clock_get_frame_time (clock);
+
if (delta_x != 0.0 &&
may_hscroll (scrolled_window))
{
- GtkAdjustment *adj;
- double new_value;
- GdkScrollUnit scroll_unit;
-
- adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->hscrollbar));
- scroll_unit = gtk_event_controller_scroll_get_unit (scroll);
-
- if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
+ if (interpolation_animation_axis_heuristic (&animation->horizontal, current_time, delta_x))
{
- delta_x *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->hscrollbar));
+ interpolation_animation_axis_handle_scroll (&animation->horizontal,
+ current_time, delta_x,
+ GTK_SCROLLBAR (priv->hscrollbar),
+ priv->unclamped_hadj_value,
+ may_hscroll,
+ scrolled_window);
}
- else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
- delta_x *= MAGIC_SCROLL_FACTOR;
+ else
+ {
+ GtkAdjustment *adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->hscrollbar));
- new_value = priv->unclamped_hadj_value + delta_x;
- _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
- new_value);
+ if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
+ {
+ delta_x *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->hscrollbar));
+ }
+ else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
+ delta_x *= MAGIC_SCROLL_FACTOR;
+
+ double new_value = priv->unclamped_hadj_value + delta_x;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
+ new_value);
+
+ gtk_scrolled_window_invalidate_overshoot (scrolled_window);
+ }
}
if (delta_y != 0.0 &&
may_vscroll (scrolled_window))
{
- GtkAdjustment *adj;
- double new_value;
- GdkScrollUnit scroll_unit;
-
- adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->vscrollbar));
- scroll_unit = gtk_event_controller_scroll_get_unit (scroll);
-
- if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
+ if (interpolation_animation_axis_heuristic (&animation->vertical, current_time, delta_y))
{
- delta_y *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->vscrollbar));
+ interpolation_animation_axis_handle_scroll (&animation->vertical,
+ current_time, delta_y,
+ GTK_SCROLLBAR (priv->vscrollbar),
+ priv->unclamped_vadj_value,
+ may_vscroll,
+ scrolled_window);
}
- else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
- delta_y *= MAGIC_SCROLL_FACTOR;
+ else
+ {
+ GtkAdjustment *adj = gtk_scrollbar_get_adjustment (GTK_SCROLLBAR (priv->vscrollbar));
- new_value = priv->unclamped_vadj_value + delta_y;
- _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
- new_value);
+ if (scroll_unit == GDK_SCROLL_UNIT_WHEEL)
+ {
+ delta_y *= get_wheel_detent_scroll_step (GTK_SCROLLBAR (priv->vscrollbar));
+ }
+ else if (scroll_unit == GDK_SCROLL_UNIT_SURFACE)
+ delta_y *= MAGIC_SCROLL_FACTOR;
+
+ double new_value = priv->unclamped_vadj_value + delta_y;
+ _gtk_scrolled_window_set_adjustment_value (scrolled_window, adj,
+ new_value);
+
+ gtk_scrolled_window_invalidate_overshoot (scrolled_window);
+ }
}
- g_clear_handle_id (&priv->scroll_events_overshoot_id, g_source_remove);
+ /* Start the tick callback if the animation is running now but was not running
+ * before.
+ */
+ if (!animation_was_running &&
+ (animation->horizontal.running || animation->vertical.running))
+ {
+ animation->tick_id =
+ gtk_widget_add_tick_callback (GTK_WIDGET (scrolled_window),
+ interpolation_animation_cb,
+ NULL, NULL);
+ }
- if (!priv->smooth_scroll &&
- _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
+ if (!priv->interpolation_animation.horizontal.running && !priv->interpolation_animation.vertical.running &&
+ !priv->smooth_scroll && _gtk_scrolled_window_get_overshoot (scrolled_window, NULL, NULL))
{
priv->scroll_events_overshoot_id =
g_timeout_add (50, start_scroll_deceleration_cb, scrolled_window);
@@ -2086,6 +2530,12 @@ gtk_scrolled_window_init (GtkScrolledWindow *scrolled_window)
priv->overlay_scrolling = TRUE;
+ priv->interpolation_animation.horizontal.running = FALSE;
+ priv->interpolation_animation.vertical.running = FALSE;
+
+ priv->interpolation_animation.horizontal.last_continuous_delta_time_defined = FALSE;
+ priv->interpolation_animation.vertical.last_continuous_delta_time_defined = FALSE;
+
priv->drag_gesture = gtk_gesture_drag_new ();
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE);
g_signal_connect_swapped (priv->drag_gesture, "drag-begin",
@@ -2687,6 +3137,15 @@ gtk_scrolled_window_dispose (GObject *object)
priv->deceleration_id = 0;
}
+ InterpolationAnimation *interpolation_animation = &priv->interpolation_animation;
+
+ if (interpolation_animation->horizontal.running ||
+ interpolation_animation->vertical.running)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (self),
+ interpolation_animation->tick_id);
+ }
+
g_clear_pointer (&priv->hscrolling, gtk_kinetic_scrolling_free);
g_clear_pointer (&priv->vscrolling, gtk_kinetic_scrolling_free);
g_clear_handle_id (&priv->scroll_events_overshoot_id, g_source_remove);
diff --git a/gtk/gtkscrolledwindow.c b/gtk/gtkscrolledwindow.c
index f0d93c5e0ffba7d98f8daf5f281379fd21e3a1c6..bac8446dbbf7a3b5956c7deb5d6e83aef9794494 100644
--- a/gtk/gtkscrolledwindow.c
+++ b/gtk/gtkscrolledwindow.c
@@ -1764,6 +1764,10 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
g_clear_handle_id (&priv->scroll_events_overshoot_id, g_source_remove);
+ GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (scrolled_window));
+ gboolean interpolation_enabled;
+ g_object_get (settings, "gtk-scroll-interpolation-animation", &interpolation_enabled, NULL);
+
GdkScrollUnit scroll_unit = gtk_event_controller_scroll_get_unit (scroll);
InterpolationAnimation *animation = &priv->interpolation_animation;
@@ -1777,7 +1781,8 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
if (delta_x != 0.0 &&
may_hscroll (scrolled_window))
{
- if (interpolation_animation_axis_heuristic (&animation->horizontal, current_time, delta_x))
+ if (interpolation_enabled &&
+ interpolation_animation_axis_heuristic (&animation->horizontal, current_time, delta_x))
{
interpolation_animation_axis_handle_scroll (&animation->horizontal,
current_time, delta_x,
@@ -1808,7 +1813,8 @@ scrolled_window_scroll (GtkScrolledWindow *scrolled_window,
if (delta_y != 0.0 &&
may_vscroll (scrolled_window))
{
- if (interpolation_animation_axis_heuristic (&animation->vertical, current_time, delta_y))
+ if (interpolation_enabled &&
+ interpolation_animation_axis_heuristic (&animation->vertical, current_time, delta_y))
{
interpolation_animation_axis_handle_scroll (&animation->vertical,
current_time, delta_y,
diff --git a/gtk/gtksettings.c b/gtk/gtksettings.c
index 15cc3aa0dfb25a3759c6016b5ffca8f547470fe8..c5376bce7da92c9a6d032f88dfff8fc4e21b0e0b 100644
--- a/gtk/gtksettings.c
+++ b/gtk/gtksettings.c
@@ -202,6 +202,7 @@ enum {
PROP_KEYNAV_USE_CARET,
PROP_OVERLAY_SCROLLING,
PROP_FONT_RENDERING,
+ PROP_SCROLL_INTERPOLATION_ANIMATION,
NUM_PROPERTIES
};
@@ -976,6 +977,23 @@ gtk_settings_class_init (GtkSettingsClass *class)
GTK_FONT_RENDERING_AUTOMATIC,
GTK_PARAM_READWRITE);
+ /**
+ * GtkSettings:gtk-scroll-interpolation-animation:
+ *
+ * Whether scroll interpolation animation is enabled.
+ *
+ * The animation is triggered on low precision mouse scroll and
+ * makes the content smoothly scroll and not "jumpy" scroll as
+ * it is without animation. The goal is to make the content
+ * easier to follow on screen when using low precision mouse
+ * scroll.
+ *
+ * Since: 4.20
+ */
+ pspecs[PROP_SCROLL_INTERPOLATION_ANIMATION] = g_param_spec_boolean ("gtk-scroll-interpolation-animation", NULL, NULL,
+ TRUE,
+ GTK_PARAM_READWRITE);
+
g_object_class_install_properties (gobject_class, NUM_PROPERTIES, pspecs);
}
diff --git a/testsuite/tools/settings b/testsuite/tools/settings
index 19c78cf7c0235ecfeb0d1a1e45caf18fcaed4919..57c72fe56c7813fcb8ea1805be34bbac828fe9bc 100755
--- a/testsuite/tools/settings
+++ b/testsuite/tools/settings
@@ -10,7 +10,7 @@ echo "1..1"
name=gtk-query-settings
result=$TEST_RESULT_DIR/$name.out
$GTK_QUERY_SETTINGS 2>/dev/null >$result
-EXPECTED=52
+EXPECTED=53
SEEN=$(wc -l $result | cut -f1 -d' ')
if [ $SEEN -eq $EXPECTED ]; then
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment