Say what the drag payload actually is: the arming, not the cargo
`drag-payload` was documented as "called when a drag starts, so it always reflects the selection as it is at that moment". Neither half is true, and the comment is the only reason anyone would believe the drop reads it. `DragArea` tests `data.is_empty()` in its event filter — on every pointer event, the first one included, which arrives long before there is a drag. And a Slint binding that calls a callback has no dependency to be invalidated on, so it is evaluated once, when a finger first lands on that cell, and cached for the life of the cell. What it answers is therefore always an empty selection. None of the drop handlers read it; every one of them reads `dragging`, which `drag-started` fills in at the moment that matters. What this callback does is keep the `DragArea` armed, and it manages that only because `set_user_data` is called unconditionally — an empty `Vec` is still user data. Guarding that call, which reads as an obvious tidy-up, would silently stop the grid dragging at all. Comments only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1889,16 +1889,33 @@ pub fn wire<S, R, P, C>(
|
||||
// Slint owns the gesture (see the preamble). What is left here is the
|
||||
// payload — the image ids the drop will act on — and the spring.
|
||||
|
||||
// The payload is built when the drag starts, so it is the selection as it
|
||||
// stands at that moment rather than whatever it becomes mid-flight.
|
||||
// TRACES: FR-CAT-7
|
||||
// **What arms the drag, not what it carries.**
|
||||
//
|
||||
// `DragArea` declines to start a drag while its `data` is empty, and it
|
||||
// tests that on every pointer event that reaches it — the first one
|
||||
// included, which arrives long before any drag. The binding in
|
||||
// `library.slint` calls this callback, and a binding that calls a callback
|
||||
// has nothing Slint can invalidate it on: it is evaluated once, when a
|
||||
// finger first lands on that cell, and cached. `dragging` is empty at that
|
||||
// moment and stays empty until `drag-started` fires.
|
||||
//
|
||||
// So this is answered at the wrong time, and always will be. That is
|
||||
// harmless only because **`set_user_data` is called unconditionally**: an
|
||||
// empty `Vec` is still user data, so the transfer is never `is_empty()` and
|
||||
// the `DragArea` stays armed. Skipping the call for an empty selection —
|
||||
// which looks like an obvious tidy-up — would disarm every cell a finger
|
||||
// had ever touched outside a drag, and dragging would simply stop working
|
||||
// with nothing to see.
|
||||
//
|
||||
// What the drop actually reads is `dragging`, set by `drag-started` and
|
||||
// read back by `dropped-on`. `user_data` rather than plain text so that
|
||||
// nothing outside the application can interpret it as a paste.
|
||||
{
|
||||
let ctl = ctl.clone();
|
||||
window.on_library_drag_payload(move || {
|
||||
let carried = ctl.dragging.borrow().clone();
|
||||
let mut data = slint::DataTransfer::default();
|
||||
// `user_data` rather than plain text: these are catalog ids for our
|
||||
// own drop handler, not something another application should be
|
||||
// able to interpret as a paste.
|
||||
data.set_user_data(Rc::new(carried));
|
||||
data
|
||||
});
|
||||
|
||||
@@ -1377,15 +1377,29 @@ export component LibraryGrid inherits Rectangle {
|
||||
/// selected. The name arrives from the sheet below rather than being
|
||||
/// invented by Rust and corrected afterwards — see `naming`.
|
||||
callback collection-from-selection(string);
|
||||
/// The drag payload: the selected image ids, wrapped by Rust. Called when a
|
||||
/// drag starts, so it always reflects the selection as it is at that moment.
|
||||
/// **What arms the drag, not what it carries.**
|
||||
///
|
||||
/// `DragArea` refuses to begin a drag while its `data` is empty, and it
|
||||
/// asks on every pointer event — including the first one, long before
|
||||
/// anything is being dragged. A binding that calls a callback is evaluated
|
||||
/// once and cached, because there is nothing for Slint to invalidate it on,
|
||||
/// so whatever this answers the first time a finger touches a cell is what
|
||||
/// that cell's `DragArea` believes for the rest of its life.
|
||||
///
|
||||
/// It is therefore *not* the payload the drop reads, whatever it looks
|
||||
/// like: what travels is `dragging` in `collections_ui`, recorded by
|
||||
/// `drag-started` and read back by `dropped-on`. See the Rust side for why
|
||||
/// this has to answer something non-empty unconditionally.
|
||||
pure callback drag-payload() -> data-transfer;
|
||||
/// What travels under the cursor: the dragged thumbnail, or a fanned stack
|
||||
/// of them where several are being carried. Composited in Rust, because
|
||||
/// Slint accepts one bitmap here and cannot draw a pile of images into it.
|
||||
in property <image> drag-image;
|
||||
/// A drag began on this cell. Lets Rust promote an unselected cell to the
|
||||
/// selection before the payload is read.
|
||||
/// A drag began on this cell.
|
||||
///
|
||||
/// This, not `drag-payload`, is where what the drag carries is decided: it
|
||||
/// fires with the selection as it stands at the moment the drag starts, and
|
||||
/// lets Rust promote an unselected cell into the selection first.
|
||||
callback drag-started(int);
|
||||
/// The drag ended — dropped or cancelled. Clears the transient UI state.
|
||||
callback drag-finished();
|
||||
@@ -2901,6 +2915,7 @@ export component LibraryGrid inherits Rectangle {
|
||||
// is what a join table means, and it is why the modifier-free
|
||||
// gesture must not be `move`.
|
||||
allow-copy: true;
|
||||
// Not the payload — the arming. See `drag-payload`.
|
||||
data: root.drag-payload();
|
||||
// What travels under the cursor is the photograph itself — and
|
||||
// where several are being dragged, a stack of them. Composited
|
||||
|
||||
Reference in New Issue
Block a user