refactor: chord drags are stock JUCE drag-and-drop, and the ghost crosses windows - #13
Conversation
…sses windows Every chord drag in Keys - a tray candidate onto a pad, a pad onto the reference box, a card onto an arp slot, tab or macro row, a card off the row to clear it - was hand-rolled on mouseDown/mouseDrag/mouseUp plus Desktop::findComponentAt, with the editor in the middle forwarding screen positions between two windows that could not see each other. It rested on a premise the code and the docs asserted as settled fact: that no DragAndDropContainer can deliver a drop across two top-level windows. That premise is false. startDragging takes a fourth parameter, allowDraggingToOtherJuceWindows, defaulting to false; pass true and the drag image is added to the desktop rather than to the container, which makes getParentComponent() null inside JUCE's own findTarget and routes target lookup through findDesktopComponentBelow - every desktop component in z-order, walking up each parent chain for an interested target. That is the same hit test the workaround was performing by hand, and it was there the whole time. Verified against JUCE 8.0.8. User-visible: the ghost now follows the cursor out of one window and across the other. It used to be an 84x26 chip painted inside whichever component owned the gesture, so it vanished at the window edge - exactly where the drop you were aiming for lived. The card itself now travels, at full size, carrying its own lock dot. No gesture behaviour changed. A drop still refuses a locked pad, still calls clearChordPad before setChordPad so a pad left ringing by Sustain or feeding the arp gives its notes up properly, and dragging a card off the row still clears it unless something took it. That last one is the sharp edge - reaching for the reference box means dragging a card off the strip - so the veto rides on the drag payload as `taken`, with `consumed` as the separate answer for a tray candidate (committed to a pad its cell empties, copied to the reference it does not). Both flags are read one message-loop turn after mouse-up rather than in dragOperationEnded: a source's own mouseUp runs before its mouse listeners and so before itemDropped, while dragOperationEnded waits out a 120 ms dismissal animation and a timer - a third of a second in which a card dragged off the row still looks like it is there. Gone: ChordPads::externalDropSlotAt and ArpPanel::externalDropSlotAt / externalDropLineAt (the same logic written twice), setExternalDropTarget, setExternalDropSlot, dropExternalChord, draggedSlot, onDragOutside / onDropOutside / onDragEnd, the three onCandidate* pass-throughs on ChordGenPanel and its three reference-drop methods. Two bug classes went with them. A target's highlight is now put out by JUCE's own itemDragExit on every path a drag can end, including the far window being closed mid-gesture, which the editor had to remember by hand; and the reference box no longer lights up through a window sitting on top of it, because z-order is the framework's answer rather than a bounds test. One affordance did change: the locked card's ghost no longer fades when it is over nothing. The ghost is the real card now and carries its lock dot, which says the same thing without depending on where the pointer is. Noted in CONTROLS.md.
d65840b to
934b5fc
Compare
|
Reviewed and rebased onto main now that #12 has landed. Base retargeted, The premise checks out, which is the thing worth confirming: All four pad gestures survive the move, including the three lock rules, which I checked one at a time against the old The one thing to keep an eye on is Rebase was clean, no conflicts. Local build and |
The premise was false
Every chord drag in Keys — a tray candidate onto a pad, a pad onto the reference box, a card onto an arp slot, tab or macro row, a card off the row to clear it — was hand-rolled on
mouseDown/mouseDrag/mouseUpplusjuce::Desktop::findComponentAt, with the editor in the middle forwarding screen positions between two windows that could not see each other.It rested on something the code and the docs stated as settled fact: that no
DragAndDropContainercan deliver a drop across two top-level windows.That is not true.
startDraggingtakes a fourth parameter,allowDraggingToOtherJuceWindows, defaulting tofalse. Passtrueand the drag image is added to the desktop instead of to the container, which makesgetParentComponent()null inside JUCE's ownfindTargetand routes target lookup throughfindDesktopComponentBelow— every desktop component in z-order, walking up each parent chain for an interestedDragAndDropTarget. That is the same hit test the workaround was performing by hand, and it was there the whole time.Verified against JUCE 8.0.8 (
juce_DragAndDropContainer.cpp:520-527and:300-320). A docs PR is open upstream as juce-framework/JUCE#1692. I also checked that the VST3 wrapper callsaddToDesktop, so the editor is inDesktop's list in a DAW and not only in the standalone — the same mechanism the old code already depended on.What you will see
The ghost follows your cursor out of the window. It used to be an 84×26 chip painted inside whichever component owned the gesture, so it vanished at the window edge — exactly where the drop you were aiming for lived. The card itself now travels, at full size, carrying its own lock dot.
Nothing else about any gesture changed.
The seven behaviours, and where each one lives now
Payload::taken, set by every target.ChordPads::itemDroppedsets it for any release landing on the strip at all, refused or notChordRefCardsetstakenonly;ChordPadssets both for a tray source{-1,-1}sentinel is goneclearChordPadbeforesetChordPadChordPads::itemDroppeddropCellForand the clear both check it. An internal move onto a lock is still allowed —moveChordPadswaps two slots and destroys nothing-2is the live cardPayload::indexis absolute, so a page flip mid-drag cannot move ititemDragExit, including the far window closed mid-drag —~DragImageComponentsends itTwo decisions worth arguing with
Boxing the chord, not passing an index. A tray candidate belongs to no slot and is not in the session, so there is no index the far end could look it up by.
Payloadis aReferenceCountedObjectin thevar; the index it carries is provenance, not a way to fetch the chord.The veto is read one message-loop turn after mouse-up, not in
dragOperationEnded. A source's ownmouseUpis too early — JUCE dispatches a component'smouseUpbefore its mouse listeners, and the drag image is a listener, soitemDroppedhas not run yet.dragOperationEndedis late enough but fires from~DragImageComponentafter a 120 ms dismissal animation and a timer: a third of a second in which a card dragged off the row still looks like it is there. Posting frommouseUplands after the same event's listener dispatch and before the next frame.I considered tracking hover state and reading it synchronously instead. Rejected: its failure mode is "the reference box takes the chord and the pad clears" — data loss, however rare — against a delay you cannot see.
Deleted
ChordPads::externalDropSlotAtandArpPanel::externalDropSlotAt/externalDropLineAt(the same logic written twice, whichArpPanel.cppadmitted in a comment),setExternalDropTarget,setExternalDropSlot,dropExternalChord,draggedSlot,onDragOutside/onDropOutside/onDragEnd, the threeonCandidate*pass-throughs onChordGenPaneland its three reference-drop methods.Two bug classes went with them: a highlight lit on the way out can no longer be left glowing at nothing, and the reference box no longer lights through an overlapping window.
One affordance changed
The locked card's ghost no longer fades when it is over nothing. The ghost is the real card now and carries its lock dot, which says the same thing without depending on where the pointer is. Noted in
CONTROLS.mdrather than left to be discovered.While I was there I also fixed a
CONTROLS.mdsentence that was wrong before this branch: it claimed a drop that misses everything leaves the card where it was, when off-the-strip has always cleared it.Testing
Builds clean (VST3,
Keys_Standalone,KeysHost_Standalone),ctestpasses, both windows render — the generator opens with a filled tray and an empty reference card.Not yet hand-tested in a DAW, and that matters here. The drag image is now a plugin-owned temporary top-level window, which is the thing hosts differ on, so a passing standalone is not evidence for Live. Worth walking through there: tray→pad, tray→reference, pad→arp slot, pad→reference, drag off the row to clear, drag out and back onto a pad, and closing the generator window mid-drag.