Skip to content

scroll: one wheel notch flings for about forty seconds on Linux #424

Description

@sepehr-safari

One mouse-wheel notch starts a fling that runs for roughly forty seconds. It reads as a view that cannot be stopped, only reversed, because reversing is the only thing that cancels the velocity.

All references are v0.10.1, checked against main as well.

The arithmetic

ScrollPhysics (src/primitives/canvas/tokens.zig:774) turns a wheel delta into a velocity and then decays it:

wheel_velocity_scale: f32 = 60,
deceleration_per_second: f32 = 0.86,
stop_velocity: f32 = 5,

Velocity retains 86% per second, so the time for one notch to fall under the stop threshold is ln(stop / v0) / ln(0.86).

On Linux that is much worse than it looks, because the GTK host multiplies a wheel notch by 40 before the physics ever sees it (src/platform/linux/gtk_host.c:1503-1508, converting GDK_SCROLL_UNIT_WHEEL to points):

v0 = 1 notch x 40 x 60  = 2400 points/second
stop at 5               = ln(5/2400) / ln(0.86) ~ 41 seconds

Two notches is not two flings, it is one faster one, so ordinary scrolling makes it worse rather than better.

Why it is a wheel problem specifically

These defaults are right for a trackpad, where deltas arrive small and continuous and the decay reads as momentum. A wheel notch is a discrete displacement, not a throw, and it is the same code path with a 40x multiplier in front of it.

Reproduction, headless, no compositor

The automation channel drives the canvas directly, so this needs no window manager and no real input device:

  1. Build a canvas app with -Dautomation=true, run it under Xvfb.
  2. native automate widget-wheel <view> <scroll-region-id> 5 (one command, once).
  3. Snapshot three times over the following fifteen seconds and compare widget bounds.

I measured a feed row moving 400.6 -> 57.6 -> -412.3 -> -885.4 across snapshots at +3s, +9s and +17s, from that single command with no further input. Deceleration is visible (114, then 78, then 59 points/second) which is what confirms it is the decay curve rather than repeated events.

Suggested fix

Treat a wheel notch as a displacement rather than a fling. The unit is already known at the source: gtk_event_controller_scroll_get_unit returns GDK_SCROLL_UNIT_WHEEL for a notch and GDK_SCROLL_UNIT_SURFACE for a trackpad, and the host already branches on exactly that to apply the 40x. Carrying that distinction through to the physics, so a wheel scrolls by its delta and only a surface delta contributes velocity, fixes it without changing how a trackpad feels.

Failing that, the default decay is too slow to be a fling. At 0.86 per second a throw is still moving perceptibly half a minute later. Something nearer 0.05 per second stops in about a second, which is what a fling usually means.

And the 40x and the 60x compound. Whatever else changes, wheel_velocity_scale being applied on top of the host's wheel-to-points conversion is worth a look: one of the two is enough.

The tokens are overridable, so I have set wheel_velocity_scale and deceleration_per_second in my own app to keep it usable, scoped to non-macOS. That is a workaround for one app rather than a fix, since the defaults are what every canvas app gets.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions