Lemur released event with multiple MouseEventControl

Hi, I’m trying to do a simple drag and drop action with Lemur.

I have a Container “A” with MouseEventControl and a Container “B” with other MouseEventControl.
“B” is child of “A”.

I want to control the released event of the mouse to do something when the mouse is over the Container “A”, even if the Container “B” is overlapping it. (You released over the Container “B” and below is the Container “A”).

The problem is that there is only one event with the Container “B” as target, none with the “A”.
I’m doing something wrong (shouldn’t it being triggering two events, once for each container?)? Is there a way to do what I want?

Thanks :slight_smile:

I think I would need to know more about where you pressed, what events were consumed, and so on. If you consume the pressed event then by default the move and release events are going to go to that control first… and if they also consume the event then it won’t be delivered to any other controls.

Ok, I left this but mostly ignore it… I was focused on the “drag and drop” part and got confused… I’m leaving it because it might be useful:
I think in these cases the target and capture will be different. The capture will be wherever you started the drag (you need to know that, too)… and the target will be what you are currently over. But yeah, if your original control is consuming the events then they won’t be delivered to the target. (And maybe they shouldn’t be.)

I’ve done drag and drop a few times… though I might have used CursorEvents because they provide the actual collision information also.

Ok, my bad. I forgot to mention that there is a third component involved. So, the context is: 3 lemur components, 1 acting (X) like the capture and other two that are being targets (A and B - where B overlaps A) so I start dragging (from X), and I release on B (and so, on A).

Doing this I only receive one event trigger (X: capture, B: target), but the A component is just below the B so I expect a second event trigger (X: capture, A: target) that is never coming

Then the B must be consuming the event. If you don’t setConsumed() the events then they are delivered everywhere… even up the hierarchy.

Here is a test case that doesn’t work (is the simplest one :S, no setConsumed on any of the components).

import com.jme3.app.SimpleApplication;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.event.DefaultMouseListener;
import com.simsilica.lemur.event.MouseEventControl;
import com.simsilica.lemur.style.BaseStyles;

public class TestLemurMouseEvent extends SimpleApplication {

    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setWidth(640);
        settings.setHeight(480);
        settings.setFrameRate(60);

        new TestLemurMouseEvent().start();
    }

    public void simpleInitApp() {
        // Lemur Gui setup
        GuiGlobals.initialize(stateManager.getApplication());
        BaseStyles.loadGlassStyle();
        GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");

        Container a = new Container();
        a.addChild(new Label("Over A (without listener)"));
        a.setName("A");

        Label x = a.addChild(new Label("X"));
        x.setName("X");
        Label b = a.addChild(new Label("B (Over A, with listener)"));
        b.setName("B");

        MouseEventControl.addListenersToSpatial(b, new DefaultMouseListener());
        MouseEventControl.addListenersToSpatial(a, new DefaultMouseListener());

        MouseEventControl.addListenersToSpatial(x, new DefaultMouseListener() {
            @Override
            public void mouseButtonEvent(MouseButtonEvent event, Spatial target, Spatial capture) {
                System.out.println("[" + x.getName() + "] Button Event (" + (event.isConsumed() ? "Consumed" : "Not consumed") + ") from lemur. Released: " + event.isReleased() +
                        ", Target: " + cleanName(target) + ", Capture: " + cleanName(capture));
            }
        });

        a.setLocalTranslation(300, 300, 0);
        guiNode.attachChild(a);
    }


    private String cleanName(Spatial spatial) {
        return spatial != null ? spatial.getName() : "null";
    }

}

DefaultMouseListener will consume some events by default so that others are delivered properly.

If you want fancier event handling then you may have to override those methods or just implement all of the methods of MouseListener directly to give yourself maximum control.

Ok, that made sense, however, I created a new MouseListener and replaced it in the same code above and the results are just the same :S. The resulting code:

    public static class CustomMouseListener implements MouseListener {
        @Override
        public void mouseButtonEvent(MouseButtonEvent event, Spatial target, Spatial capture) { }

        @Override
        public void mouseEntered(MouseMotionEvent event, Spatial target, Spatial capture) { }

        @Override
        public void mouseExited(MouseMotionEvent event, Spatial target, Spatial capture) { }

        @Override
        public void mouseMoved(MouseMotionEvent event, Spatial target, Spatial capture) { }
    }

    public void simpleInitApp() {
        // Lemur Gui setup
        GuiGlobals.initialize(stateManager.getApplication());
        BaseStyles.loadGlassStyle();
        GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");

        Container a = new Container();
        a.addChild(new Label("Over A (without listener)"));
        a.setName("A");

        Label x = a.addChild(new Label("X"));
        x.setName("X");
        Label b = a.addChild(new Label("B (Over A, with listener)"));
        b.setName("B");

        MouseEventControl.addListenersToSpatial(b, new CustomMouseListener());

        MouseEventControl.addListenersToSpatial(a, new CustomMouseListener());

        MouseEventControl.addListenersToSpatial(x, new CustomMouseListener() {
            @Override
            public void mouseButtonEvent(MouseButtonEvent event, Spatial target, Spatial capture) {
                System.out.println("[" + x.getName() + "] Button Event (" + (event.isConsumed() ? "Consumed" : "Not consumed") + ") from lemur. Released: " + event.isReleased() +
                        ", Target: " + cleanName(target) + ", Capture: " + cleanName(capture));
            }
        });

        a.setLocalTranslation(300, 300, 0);
        guiNode.attachChild(a);
    }

Yeah, I forgot that button events are handled very narrowly. I’m not even sure I remember why it’s the case. Sorry for the run around.

Can we take a step back a minute and describe what you are trying to do (pictures would be nice, too) and maybe I can help plot a way to make it happen. I have done drag-and-drop before in Lemur a few times so I know it can be done… but maybe there is a better way if I know some other use-cases.

Ok, let’s see:

I pressed the mouse on the red panel and drag it to the blue panel:

Then I released the click.

I want to know if I released the click inside the bounds of the green panel, inside the blue bounds or inside both.

To be more concrete, in this case, the green A panel would be like a bag. In this bag I can release any other object. The thing is that if I release it having a object (in this case, B) in the middle, I want it to ignore that object (just drop what I want inside the bag without worrying about the containing objects).

I’m trying to go from memory here because I don’t have time to grep my own source code at the moment but I still want to provide hints as to direction. Some of it may require playing with the events a little.

Oh, one question… is X in a another container to start with or just randomly hanging out in space?

X can be a container outside or even inside the A container or any other container (so I suppose, we could say it is randomly hanging out in space)

1 Like

Ok… I think how I did this was to not consume the down event and then manage the dragging through the mouse motion events. Those will be delivered everywhere.

I think you can even consume the down event as long as you don’t consume the motion events but I’m not 100% sure.

Anyway, I think I have some central drag-and-drop app state that these controls communicate with. So when the drag starts it coordinates with the drag manager to say dragging has started. I may only do that to make it easier to snap the object back to where it started, I don’t know.

In the listener (and I’m 99% sure I use CursorEventListeners so that I can get the collision information beyond just the capture versus target… but again that may be because I want to know the grid cell I’m dropping in… pretty sure that’s why) I track what containers I’m over, if I’m allowed to drop there, etc… with some coordination with the manager.

In Mythruna, I half-remember that it’s all decoupled, though. So as I said, the manager part may be unnecessary. The key is the mouse motion events, though. The button release just lets you know the dragging is done but it’s the last state of the mouse motion that tells you where you’re dropping.

Ok, trying some work around tracking I find out that the “mouseEntered”/“mouseExited” is triggering constantly while moving over two overlapped components. It is considering that it is exiting of B when it enters on A even if it is inside of both and so, in the next frame update it enters again B and A (leaving and entering both all the time). This seems quite buggy, the entered/exited events become useless in overlapping cases unless the event is consumed at the first component (B).

(It happens the same thing with cursorEntered/cursorExited).

Yes, well only one component at a time can be “entered”, though, else the state gets all messed up. You are guaranteed 100% to always get an exit if you get an enter.

In your case, you could get away with letting your containers consume the mouse motion then only the top (nearest) container will get the events and the enter/exit will work properly. It’s the original dragging object that you need to make sure not to consume. Though it may still mess up the enter/exits.

In my case, my draggable objects aren’t even listening so they don’t factor in.

Edit: and it does bug me that a component will get enter/exit events even if it doesn’t consume the mouse but I’d have to flip the order of delivery otherwise… and I’m hesitant to do that without some consideration as it may mess up existing code to get a motion before an enter.