Skip to content

[scroll] Route each mouse wheel event on its own, instead of by the last trackpad gesture's target #107

Description

@honghaoz

Context: found while checking CodeRabbit's nested-scroll finding on #106.

Observation

On macOS, ScrollView.scrollWheel(with:) sends each scroll event either to the scroll view itself or to a parent scroll view, so that scrolling chains to the parent at the edge. A private ScrollSession decides the target when a trackpad gesture begins (phase == .began) and keeps it for every later event until the next gesture begins.

Mouse wheel events have no phase, so they never begin a session:

  • Before any trackpad gesture, a wheel event gets a new session's default target, the scroll view itself, so the mouse wheel never chains to the parent.
  • After a trackpad gesture, every wheel event reuses that gesture's target. If the gesture went to the parent, the mouse wheel over the inner scroll view keeps going to the parent, even where the inner view can scroll, until the next trackpad gesture begins.

In a probe, an inner scroll view at the bottom end of its range, nested in a parent that could scroll in every direction, got a one-line wheel event toward the top. With no earlier gesture, the inner view scrolled from 200 to 190. After a trackpad gesture toward the bottom had gone to the parent, the same wheel event went to the parent too, and the inner view stayed at 200.

This affects nested ComposeUI scroll views that scroll along the same axis, on a Mac used with both a trackpad and a mouse.

Direction

Route a wheel event on its own instead of by the last gesture's target. Momentum events, which have an empty phase but a non-empty momentumPhase, belong to the gesture before them and should keep its target. Decide between:

  1. Route each wheel event, with both phases empty, by the same rule as a gesture's beginning. The mouse wheel then chains at the edge like the trackpad, which is new: today it never chains unless a trackpad gesture set the target.
  2. Always let the scroll view handle a wheel event, as it does today before any gesture. The mouse wheel doesn't chain, but no longer inherits a gesture's target.

To do

  1. Pick an option, and route wheel events accordingly.
  2. Tests: a wheel event after a gesture that went to the parent, a momentum event after a gesture, and the chosen behavior at the edge. A wheel scroll applies on the next run loop turn, so check the offset with toEventually. Synthetic trackpad gestures can be built from CGEvents with scrollWheelEventScrollPhase set, as the routing tests added in [scroll] keep a set contentOffset as set on macOS, instead of clamping it into the scrollable range #106 do.

Related: #102 (half-pixel tolerance for the routing comparisons), #106.

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions