Confused about DragAndDropListener in Lemur

Hey all!

I’m confused and I maybe don’t see the forest because of the trees.

How is DragAndDrop control meant to be used? Let’s say I want to have two objects, one is a “draggable” that can be dragged around. The other is a “droppable” that can receive the draggable object. Does this mean that:

  • I have a DragAndDropControl with a DragAndDropListener that return a Draggable from onDragDetected.
  • I have a DragAndDropControl with a DragAndDopListener that only listens to the drop and also sets the DragStatus in drag over method?

That’s it, right? I’m trying to implement it but it just doesn’t work properly for me and I got stuck at the “how to make object draggable” part. Either the draggable gets events reported on itself, or I get the following error message constantly:

2023-10-15 23:09:22 WARN DragAndDropControl:221 - Received event for different target, this spatial:com.simsilica.lemur.Panel[elementId=ElementId[panel]], target:com.simsilica.lemur.Panel[elementId=ElementId[panel]], capture=com.simsilica.lemur.Panel[elementId=ElementId[panel]]

Or the third option is that my draggable doesn’t move at all.

What am I doing wrong?

Please see my simple reproduction:

public class LemurDragAndDropTest extends SimpleApplication {

    // [....]

    @Override
    public void simpleInitApp() {
        super.simpleInitApp();

        GuiGlobals.initialize(this);

        Panel draggable = new Panel();
        draggable.setName("draggable");
        draggable.setBackground(new QuadBackgroundComponent(ColorRGBA.Red));
        draggable.setSize(new Vector3f(100f, 100f, 10f));
        draggable.setPreferredSize(new Vector3f(100f, 100f, 10f));
        draggable.move(600f, 350f, 0f);
        guiNode.attachChild(draggable);
        draggable.addControl(new DragAndDropControl(new DragAndDropListener() {
            @Override
            public Draggable onDragDetected(DragEvent event) {
                System.out.println("SOURCE DRAG DETECTED");
                Spatial item = event.getSession().getDragSource();
                event.getSession().set(DragSession.ITEM, item);
                event.getSession().set("parent", item.getParent());

                Spatial clone = item.clone();
                clone.setLocalRotation(item.getWorldRotation());
                clone.setLocalTranslation(item.getWorldTranslation());
                guiNode.attachChild(clone);

                // OPTION 1:
                // Uncommenting this causes the red draggable to move a few pixels but after that it
                // just stays in one place.
//                 item.removeFromParent();

                 // OPTION 2:
                // Tried to hide object by moving it far away but it causes draggable reporting onDragOver on itself:
                // SOURCE DRAG ENTER
                // SOURCE DRAG OVER
                // SOURCE DRAG EXIT
                // And also constantly throws this error:
                // 2023-10-15 23:20:58 WARN  DragAndDropControl:221 - Received event for different target, this spatial:com.simsilica.lemur.Panel[elementId=ElementId[panel]], target:com.simsilica.lemur.Panel[elementId=ElementId[panel]], capture=com.simsilica.lemur.Panel[elementId=ElementId[panel]]
//                item.move(3000f, 3000f, 20f);

                // OPTION 3:
                // Comment both item.move and item.removeFromParent() and I will get the same results as Option 2: self
                // event reports and "Received event for different target..." error.

                return new DefaultDraggable(event.getLocation(), clone, clone.getWorldTranslation(),
                        Vector3f.UNIT_X, Vector3f.UNIT_Y);
            }

            @Override
            public void onDragEnter(DragEvent event) {
                System.out.println("SOURCE DRAG ENTER");
            }

            @Override
            public void onDragExit(DragEvent event) {
                System.out.println("SOURCE DRAG EXIT");
            }

            @Override
            public void onDragOver(DragEvent event) {
                System.out.println("SOURCE DRAG OVER");
            }

            @Override
            public void onDrop(DragEvent event) {
                System.out.println("SOURCE DRAG DROP");
            }

            @Override
            public void onDragDone(DragEvent event) {
                System.out.println("SOURCE DRAG DONE");
                Spatial item = event.getSession().get(DragSession.ITEM, null);
                Node parent = event.getSession().get("parent", null);
                parent.attachChild(item);

                // Ignore this please.
                // I know this is not the proper way, I just quickly slapped it near the cursor where I drop it.
//                Vector2f location = event.getSession().getDraggable().getLocation();
//                item.setLocalTranslation(location.x, location.y, 20f);
            }
        }));

        Panel droppable = new Panel();
        droppable.setName("droppable");
        droppable.setBackground(new QuadBackgroundComponent(ColorRGBA.DarkGray));
        droppable.setSize(new Vector3f(200f, 200f, 9f));
        droppable.setPreferredSize(new Vector3f(200f, 200f, 9f));
        droppable.move(100f, 400f, 0f);
        guiNode.attachChild(droppable);
        droppable.addControl(new DragAndDropControl(new DragAndDropListener() {
            @Override
            public Draggable onDragDetected(DragEvent event) {
                System.out.println("TARGET DRAG ENTER");
                return null;
            }

            @Override
            public void onDragEnter(DragEvent event) {
                System.out.println("TARGET DRAG ENTER");

            }

            @Override
            public void onDragExit(DragEvent event) {
                System.out.println("TARGET DRAG EXIT");

            }

            @Override
            public void onDragOver(DragEvent event) {
                System.out.println("TARGET DRAG OVER");

            }

            @Override
            public void onDrop(DragEvent event) {
                System.out.println("TARGET DRAG DROP");

            }

            @Override
            public void onDragDone(DragEvent event) {
                System.out.println("TARGET DRAG DONE");

            }
        }));
    }
}
1 Like

Before we dive in too deeply… have you looked at the drag and drop demo code in the Lemur examples:

1 Like

Yes I did. I put together the code based on that. I would like the simplest possible repoduction where I have one draggable, one droppable and that’s it. No slots and extra stuff. But still I can’t make the draggable properly drag because it either doesn’t follow the mouse or reports events on itself.

How did you imagined the DragAndDrop controller? Should I add it to one node which has children as the dragabbles? Or if I have 40 draggable objects, I should have 40 DragAndDrop controllers for them each?

1 Like

DragAndDropControl is for the containers. It initiates the ‘drag session’ and is the target for drag session termination.

The thing being dragged might not even exist until the drag operation is initiated (think of the case where you have a static picture and don’t even create the draggable thing until the drag operation starts). And then the “thing being dragged” need only be draggable.

Container (whether source or target) → DragAndDropControl
Thing being dragged → implements Draggable

1 Like

Aha!!! That’s what I messed up I think! I had a container for the target drop but I didn’t have a container for the draggable’s origin. I simpli put a DragAndDropControl onto the item I wanted to drag. Instead I should put the item into a container, where I’m dragging it away! I see. Thank you for the help!

2 Likes