Details
-
Suggestion
-
Resolution: Unresolved
-
Not Evaluated
-
None
-
6.8
-
None
Description
Currently, Flickable in Qt Quick 2 comes with a built-in smooth scrolling animation, which is good. Qt 6.8 now also has FluentWinUI3 style, which is also good.
However, the current smooth scrolling animation behavior is problematic in many ways comparing to WinUI ( WinUI 3 Gallery link ).
1. Scrolling speed is too slow
A mouse with unlockable wheel can scroll the content continuously with the inertia. When using such mouse, it's very easy to notice that, in Qt Quick applications, the scrolling speed of Flickable is throttled to a certain velocity. It feels as if:
- (less likely) Either when the mouse wheel event comes in too frequent, some of them get dropped;
- (more likely) Or that the target scroll position is based on current position + fixed delta, and therefore no matter how fast the event comes in, the scrolling speed is capped because the animation speed is capped.
The scrolling target position should not base on current position when wheel event is fired repeatedly as it'll cap the scrolling speed, instead the wheel event should directly change the target position.
2. Counterintuitive overscroll animation when hitting the edge
Flickable has an overscroll animation which bounces at the edge and goes back. This kind of animation makes sense with a touchpad or touchscreen, however it goes against the common sense for a mouse wheel. Specifically, none of the other major GUI toolkits/implementations do this.
- WinUI: always gracefully stops at the edge; overscroll animation is only active for touchpad/touchscreen
- Chromium/Electron: overall feeling is very similar to WinUI.
- Firefox: overall feeling is very similar to WinUI.
- Win32/MFC: no smooth scrolling at all; stops at the edge; no overscroll
- KDE apps w/ QWidgets: Has smooth scrolling animation; stops at the edge; no overscroll
- GTK: Smooth scrolling animation only for touchpad/touchscreen; no overscroll for mouse wheel
Flickable should not overscroll with a mouse wheel. The animation should be active only when using a touchpad/touchscreen.
3. Doesn't respect system animation toggle state
Windows disables UI animations over a remote session to save bandwidth, KDE Plasma has an animation speed slider, which when adjusted to instant, essentially turns off the animation. However, none of them are respected by Flickable. The current KDE implementation of animation toggle basically reimplements the whole animation, and then setting the custom animation duration to 0 to pretend as the turned off state ( relevant source ).
Flickable should respect system animation settings, or (more favorably) the system animation toggle state should be easily accessible within QML/C++/PySide6 and the Flickable scrolling animation can be turned off with just a property.
I've played around the animation for a few hours and came up with an implementation that reassembles the feeling of WinUI:
// SmoothScroll.qml import QtQuick Item { id: root readonly property Flickable target: parent.parent as Flickable // If casting failed, don't fill the parent // (which blocks the mouse wheel events), // let the underlying item handle mouse wheel events instead. anchors.fill: !!target ? parent : null readonly property real minContentX: 0 // Math.max is used here to prevent getting a negative horizontal position. readonly property real maxContentX: Math.max(target.contentWidth - target.width, 0) readonly property real minContentY: 0 readonly property real maxContentY: Math.max(target.contentHeight - target.height, 0) function getBoundedX(x) { if (x <= minContentX) return minContentX; else if (x >= maxContentX) return maxContentX; else return x; } function getBoundedY(y) { if (y <= minContentY) return minContentY; else if (y >= maxContentY) return maxContentY; else return y; } property real targetContentX property real targetContentY property bool animatingX property bool animatingY // When flickable is not moving by the custom animation, // bind the targetContentX/Y to the actual ContentX/Y. // Otherwise, the animation should take care of // updating the contentX/Y to match targetContentX/Y. Binding { root.targetContentX: root.target.contentX when: !root.animatingX restoreMode: Binding.RestoreBinding } Binding { root.targetContentY: root.target.contentY when: !root.animatingY restoreMode: Binding.RestoreBinding } WheelHandler { id: mouse // Only want mouse here, touchpad scrolling should be untouched. acceptedDevices: PointerDevice.Mouse // Boilerplates onWheel: event => { const old_targetContentX = root.targetContentX; const old_targetContentY = root.targetContentY; const deltaX = event.angleDelta.x / -2; const deltaY = event.angleDelta.y / -2; let new_targetContentX; let new_targetContentY; if (event.modifiers & Qt.ShiftModifier) { new_targetContentX = root.targetContentX + deltaY; new_targetContentY = root.targetContentY + deltaX; } else if (!event.modifiers) { new_targetContentX = root.targetContentX + deltaX; new_targetContentY = root.targetContentY + deltaY; } else if (event.modifiers & Qt.ControlModifier) { event.accepted = false; return; } else { event.accepted = false; return; } new_targetContentX = root.getBoundedX(new_targetContentX); new_targetContentY = root.getBoundedY(new_targetContentY); if (new_targetContentX != old_targetContentX) { root.targetContentX = new_targetContentX; animationX.restart(); } if (new_targetContentY != old_targetContentY) { root.targetContentY = new_targetContentY; animationY.restart(); } } } NumberAnimation { id: animationX target: root.target properties: "contentX" duration: 300 from: root.target.contentX to: root.targetContentX // stop() by default stops the animation. // This prevents that from happening. alwaysRunToEnd: true // OutQuad closely replicates WinUI style scrolling animation easing.type: Easing.OutQuad onStarted: { root.animatingX = true; } onStopped: { root.animatingX = false; } } NumberAnimation { // The same animation for Y axis id: animationY target: root.target properties: "contentY" duration: 300 from: root.target.contentY to: root.targetContentY alwaysRunToEnd: true easing.type: Easing.OutQuad onStarted: { root.animatingY = true; } onStopped: { root.animatingY = false; } } }
It can be used like this:
Flickable {
...
SmoothScroll {} // activates the alternate scrolling animation behavior
}
Since this implementation is done in only a few hours, I don't expect it to be bullet-proof mature, and it doesn't handle system animation toggle state. However, the point is that QML is clearly capable of emulating WinUI's scrolling animation behavior with just the existing building blocks without too much effort.
Flickable's scrolling animation should match the WinUI behavior at least on Windows to emulate the platform behavior. Alternatively the scrolling animation behavior should be customizable similar to the control style.
Attachments
Issue Links
- duplicates
-
QTBUG-116388 Flickable no longer takes mouse scroll speed into account
-
- Reported
-