Fix ListView scrolling with non-uniformly sized items#11657
Fix ListView scrolling with non-uniformly sized items#11657LeonMatthes wants to merge 9 commits into
Conversation
LeonMatthes
commented
May 7, 2026
- Flickable: Fix viewport_y updating while scrolling
- Fix the ListView
|
The test file we used for maximum punishment of the ListView. // Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
import { ListView, ScrollView } from "std-widgets.slint";
export component Test inherits Window {
ListView {
mouse-drag-pan-enabled: true;
for i in 100: Rectangle {
height: (i.mod(10) * 50 + 30 + sin(animation-tick() / 1s * 360deg) * 20) * 1px;
background: i.mod(2) == 0 ?
@linear-gradient(0deg, blue, darkblue) :
@linear-gradient(0deg, red, darkred);
Text {
text: "\{i}";
font-size: 24px;
}
}
}
// ScrollView {
// mouse-drag-pan-enabled: true;
// VerticalLayout {
// for i in 100: Rectangle {
// height: (i.mod(10) * 50 + 30 + sin(animation-tick() / 1s * 360deg) * 20) * 1px;
// background: i.mod(2) == 0 ? @linear-gradient(0deg, blue, darkblue) : @linear-gradient(0deg, red, darkred);
// Text {
// text: "\{i}";
// font-size: 24px;
// }
// }
// }
// }
} |
ogoffart
left a comment
There was a problem hiding this comment.
Another idea would be to implement the intercept_set on the animation binding to correct itself.
| @@ -349,15 +349,17 @@ fn update_visible_instances( | |||
| viewport_height.set(LogicalLength::new(state.cached_item_height * row_count as Coord)); | |||
| viewport_width.set(LogicalLength::new(vp_width)); | |||
| // If an animation is ongoing we should not interrupt it | |||
There was a problem hiding this comment.
this comment is no longer attached to the right code, or is stale.
| viewport_x.remove_binding(); | ||
| viewport_y.remove_binding(); |
There was a problem hiding this comment.
Why do we do that?
Will that not break two way bindings as well?
| // We must not yet trigger a re-evaluation of that binding, as we have already updated the | ||
| // viewport_width and viewport_height, but the viewport_y is not yet consistent. | ||
| // So the physics animations limit value may be inconsistent. | ||
| if new_viewport_y != viewport_y.get_internal().get() { |
There was a problem hiding this comment.
this change looks good.
In case there is an animation, and the item had different size, the animation will stop, but that's better than having the flickable misbehave.
Yeah, we could have done that, but I don't really see the point of it. There's really no reason to store the current value in the animation anyway, because we don't need it. We just have a velocity stored that we add to the current value. We don't need to carry another copy of the value at all. |
This improves upon slint-ui#9696, which relied on a side-effect of the ListView updating the viewport_height, together with the viewport_y. The new algorithm no longer relies on the viewport_y to remain stable, and only relies on the delta in the flickable coordinate system, which we expect to remain stable.
affb3e1 to
10960ec
Compare
This was mostly caused by the listview not updating the viewport_y if there is an active animation. Instead, we now always update the viewport_y, but make sure the animation supports this. Fixes in the Physics Simulation: 1. The limit now updates from a property binding 2. The value is not duplicated and the binding supports intercept_set now. This now causes the ListView to update the viewport_y/viewport_height dynamically without destroying the animation.
This allows especially two-way bindings to remain while animated bindings are removed.
winit reports a TouchPhase::Moved for a non-touchpad wheel event, so use the same TouchPhase state for the Qt backend.
This moves the animation for scroll wheel to relative coordinates, which allows the ListView to continuously adapt the viewport position and size without the animation breaking
When scrolling with the touchpad we used to store the viewport position which is not correct. We must always use either positions within the flickable or we must use a relative deltas because the viewport position might jump while scrolling. So we now store the relative deltas in the ring buffer, which is the better abstraction anyway, because what we care about at the end is the remaining velocity, which we can easily calculate out of the deltas.
The Flickable now already starts capturing if a flick in that axis is possible, even if the flickable currently is at the bounds in that direction.
10960ec to
3e8df36
Compare
|
This turned out to be a much bigger refactor than we had originally assumed. Basically we had to change everything to use physics animations and use relative positions that are relative to the flickable and not the inner viewport, as that might change if the ListView provides a new size estimate. but now all three cases an animated scroll using the scroll wheel, a touchpad or the touchscreen / mouse should all work correctly with a ListView that has differently sized items. |
|
Also big thanks to @Murmele for working with me on this 🙌 |