Files
qbz/crates/qbz-qt/qml/controls/QbzRowRemove.qml
T
vicrodh b3aa992954 feat(qt): a keyed list model whose rows change instead of being replaced
Handing an item view a new JS array is a model swap: every delegate is
thrown away, the scroll offset resets and the page is built again.
QbzKeyedModel takes the same arrays and reconciles them by key into
removes, inserts, moves and in-place updates, so a view keeps its
delegates, its offset and its artwork, and a leaving row can fade out
while its neighbours slide into the gap.

Each model row carries only `rowKey` and `rowRev`; a delegate looks the
row up by key, so it reads the host's own JS object (no copy into a
ListModel map) and a rev bump hands it a changed row. New objects for an
existing key are compared field by field, so a republished document with
the same data touches no delegate. A reorder keeps the longest in-order
run steady and moves only the rest; past a cap, and on any scope change
(tab, sort, search), the rows are replaced without animation, laid out
synchronously and started from the top, like the swap they replace.

Three shared transitions (add, remove, displaced) give every adopter the
same motion. The remove transition restores opacity and scale as its last
step: the view pools a delegate the moment the transition ends and hands
it back as it was left, which measured 76 invisible rows after three
removals and a scroll.

tst_keyed_model covers order and identity under 400 random edits, the
one-move reorder, silent resets, a removal that keeps the viewport and
creates at most two delegates, the fade that keeps its content, and the
pooled rows; it runs in the runtime gate.

cc
2026-09-14 05:37:31 -06:00

41 lines
1.5 KiB
QML

// QbzRowRemove — a row leaving a QbzKeyedModel view fades out, a card also
// shrinks a touch (2026-09-14). Assign it to the view's `remove`, with
// `enabled: <model>.animate`. The view keeps the delegate alive until this
// ends; its content stays because a removed row's roles are not re-notified.
//
// THE LAST STEP PUTS OPACITY AND SCALE BACK. With `reuseItems` the view pools
// the delegate the moment this transition finishes (QQuickItemViewPrivate::
// viewItemTransitionFinished -> releaseItem -> setVisible(false), all in one
// call), and a pooled delegate is reused as it was left: without the restore
// the next row it renders is invisible. Measured 2026-09-14: 76 blank rows
// after three removals and a scroll. The restore runs before the release, in
// the same call chain, so it never paints.
import QtQuick
Transition {
id: root
property int duration: 180
/// 1.0 for list rows; cards read better with a slight shrink.
property real shrink: 1.0
SequentialAnimation {
ParallelAnimation {
NumberAnimation {
property: "opacity"
to: 0.0
duration: root.duration
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "scale"
to: root.shrink
duration: root.duration
easing.type: Easing.OutCubic
}
}
PropertyAction { property: "opacity"; value: 1.0 }
PropertyAction { property: "scale"; value: 1.0 }
}
}