Replace QScroller with custom KineticScroller.#327
Conversation
4e67bb3 to
c31e373
Compare
| if (OptionKineticScroller.value()) { | ||
| _scroller = new KineticScroller(this); | ||
| } else if (_scroller) { | ||
| QObject deleter; | ||
| _scroller->setParent(&deleter); | ||
| delete _scroller; |
There was a problem hiding this comment.
If we have access to constructor and destructor then use a smart pointer
| if (OptionKineticScroller.value()) { | |
| _scroller = new KineticScroller(this); | |
| } else if (_scroller) { | |
| QObject deleter; | |
| _scroller->setParent(&deleter); | |
| delete _scroller; | |
| _scroller = OptionKineticScroller.value() | |
| ? std::make_unique<KineticScroller>(this); | |
| : nullptr; |
| _wheelPos, | ||
| crl::now()); | ||
| } | ||
| // The press event's own delta counts too: a short fling |
There was a problem hiding this comment.
So we're resurrecting ScrollBegin's delta here while ignoring it multiple lines above if we're in KineticScroller::Scrolling state?
There was a problem hiding this comment.
These are different deltas. The one ignored above is the leaked macOS momentum stream (qnsview_mouse.mm can emit ScrollBegin straight from momentumPhase == Began when it misses the handoff), which we're already simulating ourselves.
On Wayland, though, ScrollBegin always has a zero delta, so it's the first ScrollUpdate that triggers the press, and its delta is a velocity sample we shouldn't drop.
There was a problem hiding this comment.
How it happens ScrollUpdate triggers the press? ScrollBegin should trigger InputPress and the next ScrollUpdate triggers InputMove.
There was a problem hiding this comment.
_wheelPos being null means no gesture is open. Normally ScrollBegin opens it with InputPress, then each ScrollUpdate sends InputMove. But the first event we see can be a ScrollUpdate, for example when the check above swallowed a real begin during a fling. In that case wasNull is true on the update, so the press comes from it. And unlike a begin, the update carries a real scroll delta, which we then send as the first InputMove: a short fling has only a few velocity samples, dropping one would noticeably weaken it.
There was a problem hiding this comment.
But the first event we see can be a
ScrollUpdate, for example when the check above swallowed a real begin during a fling.
You just said ScrollBegin always has zero delta on Wayland? If that's right, then this change has zero effect and should be reverted. If not, then remove the macOS workaround as macOS users aren't the expected users of this feature anyway.
There was a problem hiding this comment.
Alright, I've debugged raw events and this path is indeed useless, the problem was related to Bluetooth buffering some events (leading to losing some short flings because we use crl::now() timestamps instead of QWheelEvent::timestamp()).
(yeah I use a bluetooth touchpad)
There was a problem hiding this comment.
crl::now() was used only because QFlickGestureRecognizer uses QElapsedTimer so I mirrored it. If event timestamps are in fact better then we can use them.
| if (OptionKineticScroller.value()) { | ||
| _scroller = new KineticScroller(this); | ||
| } else if (_scroller) { | ||
| QObject deleter; | ||
| _scroller->setParent(&deleter); | ||
| delete _scroller; | ||
| } |
There was a problem hiding this comment.
Smart pointer too.
| if (OptionKineticScroller.value()) { | |
| _scroller = new KineticScroller(this); | |
| } else if (_scroller) { | |
| QObject deleter; | |
| _scroller->setParent(&deleter); | |
| delete _scroller; | |
| } | |
| _scroller = OptionKineticScroller.value() | |
| ? std::make_unique<KineticScroller>(this); | |
| : nullptr; |
4ee73c5 to
a7ed8d7
Compare
| QObject deleter; | ||
| _scroller->setParent(&deleter); | ||
| } | ||
|
|
There was a problem hiding this comment.
could the empty line be returned?
| QObject deleter; | ||
| _scroller->setParent(&deleter); | ||
| } | ||
|
|
|
Last stylistic thing I see right now: both new .cpp and .h seem to use spaces rather than tabs. And to technical things:
|
a7ed8d7 to
72cac0f
Compare
This reverts commit 1b34cf4.
QScroller has: 1. A bad inertia curve (all of the default ones). 2. A bad initial acceleration estimation. These can't be properly tuned, so the only option is to reimplement QScroller. This implementation's maths is based on Chromium and GTK4.
QScroller didn't expose it, so the only option was tracking the velocity from (rounded to integer) per-tick deltas, which read zero in the slow tail of a fling and suppressed the spring. The KineticScroller's velocity can be exposed and is exact at any instant, so use it instead.
Absolute positions came from QScroller. This was actually inconvenient, because we had to convert deltas to absolute positions and back, and rebase whenever loading messages moved the content.
The separation existed only for QScroller.
This only existed because QScroller couldn't watch mouse events itself. KineticScroller already event-filters its frame window, so stopping the fling on real pointer activity belongs to its Scrolling state.
72cac0f to
3ca8239
Compare
I don't think they do.
All three implemented (as separate commits for now, lmk if I should fixup). |
QScrollerhas:These can't be properly tuned, so the only option is to reimplement
QScroller.This implementation's maths is based on Chromium and GTK4.
This also replaces the screen refresh rate heuristics with proper frame scheduling (vsync-accurate).
KineticScrolleralso exposes velocity directly, allowing more precise overscroll behavior without relying on estimated velocity from rounded per-tick deltas (fixing the spring suppression in the slow fling tails).