Repository navigation
fix(ListViewTransitions): null transitions when duration truncates to 0ms - #2791
Merged
bbedward merged 1 commit intoJul 9, 2026
Merged
Conversation
… 0ms A zero-duration ViewTransition still engages ListView's transition machinery but resolves within the same frame, so removed delegates are never released and displaced items are never repositioned. On a filtered ScriptModel that re-filters on every keystroke this leaves stale rows and empty gaps behind, visible in any DankListView-backed list. The shortest sub-duration (remove/add = expressiveDurations.fast = base * 0.4) is coerced to an int, so it truncates to 0ms for any animation base duration below 3: animation speed None (0), or a Custom duration of 1-2ms (1*0.4=0.4->0, 2*0.4=0.8->0, only 3*0.4=1.2->1 survives). Presets (250/500/750) are unaffected, which is why this only reproduces with animations disabled or a very small custom value. Gate the four transitions to null whenever that shortest sub-duration would truncate to 0, so the view takes the correct instant path instead of a broken 0ms transition. Base >= 3 is unchanged.
14Do
marked this pull request as ready for review
July 9, 2026 15:43
purian23
pushed a commit
that referenced
this pull request
Jul 10, 2026
… 0ms (#2791) A zero-duration ViewTransition still engages ListView's transition machinery but resolves within the same frame, so removed delegates are never released and displaced items are never repositioned. On a filtered ScriptModel that re-filters on every keystroke this leaves stale rows and empty gaps behind, visible in any DankListView-backed list. The shortest sub-duration (remove/add = expressiveDurations.fast = base * 0.4) is coerced to an int, so it truncates to 0ms for any animation base duration below 3: animation speed None (0), or a Custom duration of 1-2ms (1*0.4=0.4->0, 2*0.4=0.8->0, only 3*0.4=1.2->1 survives). Presets (250/500/750) are unaffected, which is why this only reproduces with animations disabled or a very small custom value. Gate the four transitions to null whenever that shortest sub-duration would truncate to 0, so the view takes the correct instant path instead of a broken 0ms transition. Base >= 3 is unchanged. (cherry picked from commit ce1595d)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
ListViewTransitionshands everyDankListViewa set of add/remove/displaced/moveTransitions whose durations come fromTheme.expressiveDurations.*. When the effective duration is 0ms, the transitions still engage ListView'sViewTransitionmachinery but resolve within the same frame, so removed delegates are never released and displaced items are never repositioned. On a filteredScriptModelthat re-filters on every keystroke, this leaves stale rows and empty gaps behind - reproducible in anyDankListView-backed list that filters or reorders.It happens in two cases:
The second case comes from how the durations are derived. In
Theme.qml:So the shortest sub-duration (remove/add) is
base * 0.4, and it is coerced to anintfor the animationduration, which truncates toward zero.1*0.4=0.4 -> 0,2*0.4=0.8 -> 0, and only3*0.4=1.2 -> 1survives. So the cliff is base < 3:Presets (250 / 500 / 750) sit well above the cliff, which is why this only shows up with animations disabled or a tiny custom value. Note base 2:
displacedis already 1ms butremoveis still 0ms and it is still broken, which pins the stale-delegate half specifically on theremovetransition.Fix
Gate the four transitions to
nullwhenever the shortest sub-duration would truncate to 0ms, so the view takes the correct instant path instead of a broken 0msTransition.base >= 3is unchanged. The invariant: a view transition should never be a 0msTransitionobject - it is eithernull(instant, correct) or>= 1ms(runs correctly).Because
DankListViewalready readsListViewTransitions.*for its defaults, this fixes every consumer at once. The per-call-siteadd/remove/displaced/move: nulloverrides added in #2315 (ProcessesView.qml,ClipboardContent.qml) become redundant and could be reverted in a follow-up to restore their fade animations for users who do have animations enabled - left out of this PR to keep it minimal.Type of change
Related issues
None. Generalizes the per-call-site fix from #2315.
Screenshots / video
Minimal repro: two
DankListViews over identical data / delegate /ScriptModel, differing only in the transitions. The left uses the shippedListViewTransitions.*; the right uses the proposed gate (null when the effective duration is 0ms).Interactive repro plugin: https://github.com/14Do/dms-listview-repro
listview-repro-demo.mp4
Checklist
I18n.tr()with translator context - N/A, no new stringsmake lint-qmlwith no new warnings