Now, thanks to the window masking and transparency utilities provided by JNA, the drag images can escape the bounds of java windows.
This wasn't quite as easy as expected, even with the shaped, transparent windows already taken care of.
First, I had intended to use Swing's built-in drag support, given that Shannon Hickey has put so much work into improving it. Unfortunately, that built-in support, while making some operations very easy, makes others impossible. Namely, drag image support requires updating the image's location in response to cursor motion. While it's technically possible to listen for motion outside the drag operation using a global
DragSourcelistener, that's a hacky workaround. So instead, I fall back to raw DnD with
DragSourceListener, et al. These provide the necessary hooks to move the drag image, and I already have some abstract base classes that provide a basic, customizable implementation.
Next, I uncovered a bug in the JNA example code converting an Icon into a region mask. It turns out that a
TYPE_BYTE_BINARYapplies something like a 50% luminance threshold, when what I really wanted was a zero/non-zero threshold based on the alpha component of the icon's pixels. I tried writing my own binary
Compositeto do just that, but wound up with errors downstream of the actual composite operation. Fortunately,
BufferedImage.getAlphaRasteron a standard ARGB image gives me pretty much what I want.
Finally, once I instantiated a window under the current drag operation, I could no longer drop anywhere. Which makes sense when you consider there is now a window in between the cursor and any place you'd like to drop. I almost gave up thinking this was an intractable problem given that DnD is so tightly wound up with native implementations, but I'm always game for one more variation. Remember that a window region can have holes in it, where events pass through to whatever lies beneath. Well, what if we put a hole right where the cursor is? Since it's directly under the cursor, it's not really apparent, but it allows events to pass through to the underlying drop target. We only need a single pixel, because that's all the DnD system cares about (I initially made it much bigger just so I could be sure I put it in the right place). Voila! It works.