Skip to content

Fix ListView scrolling with non-uniformly sized items#11657

Open
LeonMatthes wants to merge 9 commits into
slint-ui:masterfrom
LeonMatthes:debug-list-view
Open

Fix ListView scrolling with non-uniformly sized items#11657
LeonMatthes wants to merge 9 commits into
slint-ui:masterfrom
LeonMatthes:debug-list-view

Conversation

@LeonMatthes
Copy link
Copy Markdown
Member

  • Flickable: Fix viewport_y updating while scrolling
  • Fix the ListView

@LeonMatthes
Copy link
Copy Markdown
Member Author

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;
    //             }
    //         }
    //     }
    // }
}

Copy link
Copy Markdown
Member

@ogoffart ogoffart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is no longer attached to the right code, or is stale.

Comment on lines +470 to +471
viewport_x.remove_binding();
viewport_y.remove_binding();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@LeonMatthes
Copy link
Copy Markdown
Member Author

Another idea would be to implement the intercept_set on the animation binding to correct itself.

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.
@LeonMatthes LeonMatthes force-pushed the debug-list-view branch 2 times, most recently from affb3e1 to 10960ec Compare May 13, 2026 09:55
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.
@LeonMatthes LeonMatthes marked this pull request as ready for review May 13, 2026 16:51
@LeonMatthes
Copy link
Copy Markdown
Member Author

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.

@LeonMatthes
Copy link
Copy Markdown
Member Author

Also big thanks to @Murmele for working with me on this 🙌

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants