Nifty ScrollPanel and ChaseCamera: How to consume MouseWheel Event?

Hello monkeys! I’ve already got lots of help from this forum as a passive reader, thanks for that. Now I finally made an account here :slight_smile: because i can’t find anything useful on this topic.

I’m working on a RTS and I use ChaseCamera for zooming and controlling the viewing angle.
Zooming is bound to the mouse wheel. The ChaseCamera receives events as AnalogListener from InputManager.

I’m using Nifty and its Window controls for the higher-level parts of my GUI. Inside each window, there’s a ScollPanel control which also reacts on mouse wheel input.

My issue basically is: When I scroll a ScrollPanel it also zooms the ChaseCamera.
But I think it’s not particularly related to ScrollPanel and ChaseCamera. I guess the problem boils down to the MouseWheel events not being consumed by Nifty so they are forwarded to the InputManager.

Here’s a simple test case. It listens on mouse click and wheel events via InputManager. Nifty should catch them when the cursor is inside the red square. Clicks are consumed by Nifty, as expected.

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.niftygui.NiftyJmeDisplay;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;

public class NiftyMouseWheelTest extends SimpleApplication implements AnalogListener {
    public static class TestScreenController implements ScreenController {
        public void bind(Nifty nifty, Screen screen) {}
        public void onStartScreen() {}
        public void onEndScreen() {}

        public boolean onMouseWheel() {
            System.out.println("Nifty onMouseWheel()");
            return true; // Set consumed? Doesn't work no matter what i return
        }


        public void onClick() {
            System.out.println("Nifty onClick()");
            // This one gets consumed
        }
    }


    @Override
    public void simpleInitApp() {
        flyCam.setEnabled(false);

        // Input
        inputManager.addMapping("MouseWheelUp", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
        inputManager.addMapping("MouseWheelDown", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(this, "MouseWheelUp", "MouseWheelDown", "Click");

        // Nifty
        NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);
        niftyDisplay.getNifty().fromXml("Interface/MouseWheelTest.xml", "start");
        guiViewPort.addProcessor(niftyDisplay);
    }


    public void onAnalog(String name, float value, float tpf) {
        System.out.println("Analog: " + name);
    }


    public static void main(String[] args) {
        NiftyMouseWheelTest app = new NiftyMouseWheelTest();
        app.setShowSettings(false);
        app.start();
    }
}

I couldn’t find a way to set interact/onMouseWheel through an ElementBuilder. :unamused:
So it has to use this XML (Assets/Interface/MouseWheelTest.xml):

<?xml version="1.0" encoding="UTF-8"?>
<nifty>
    <screen id="start" controller="mygame.NiftyMouseWheelTest$TestScreenController">
        <layer id="GLayer0" childLayout="center">
            <panel width="100px" height="100px" backgroundColor="#f00f" visibleToMouse="true">
                <interact onMouseWheel="onMouseWheel()" onClick="onClick()"/>
            </panel>
        </layer>
    </screen>
</nifty>

sgold suggested a workaround here: Filtering out AXIS_WHEEL events handled by a Nifty HUD
But that’s very cumbersome and I’d rather use a more elegant solution if there is one.

How can i consume MouseWheel events in Nifty?

Version:
jMonkeyEngine 3.1-alpha1 (c5c893f, built on 2015-08-17),
Nifty 1.4.1 (2015-01-25 13:48:55)

Ok, i figured it out.
MouseMotionEvents are explicitly not consumed by Nifty. And since wheel events are transported by MouseMotionEvents they will also pass through.

I guess there’s a good reason for that. But in my case I just want it to consume the events. I forked the jmonkeyengine project on Github and will now use my own implementation of InputSystemJme where I simply changed the code to:

    if(nic.processMouseEvent(x, y, wheel, -1, false))
        evt.setConsumed();

Works well so far :slight_smile: Cheers!

Edit: Maybe we could add an option to switch between the two behaviours?

I would not patch jmonkey to solve your issue but I would remove the mouse wheel listener from the chase cam. I think that is possible.

Thanks for your suggestion. That was my first approach, too. But since I plan to add other HUD elements that are controlled with the mouse wheel, and which should not react if they are behind Nifty windows, it seemed like a workaround that introduces unnecessary coupling. I already had code for it and it was starting to get quite bloated.

However, I can now see why MouseMotionEvents generally should pass through the Nifty layer. My change from above lead to other problems with my “native jME” HUD elements. My icons would not receive MouseLeave events when partially covered by a window and therefore the tooltip would not disappear. Similar behaviour when dragging items: They would stick to the window borders.

Now I disconnected the MouseWheel events from the MouseMove events by spawning two separate MouseMotionEvents in LwjglMouseInput. I saw that the Lwjgl3 Input Backend “GlfwMouseInput” already does that but a quick search didn’t give much information about switching backends.

For anyone interested I made these small changes:

I feel quite liberated now that I can quickly adjust engine code to my needs.
If this has unforseen implications I will soon find out :wink:

Ok. I just thought if that scrollable element is some sort of a popup you have pretty good indicator to turn the chase chams mouse wheel of, sort of a statemachine solution maybe. Btw. I would turn off all events in this case and add them on leave it. Your statemachine could have an enter/leave method which is called on enter and leave where you can do capsulate the unregister/register input events and in the main state method you can do your nifity stuff or game stuff vise versa.
IMO when you need to change the engine you have an architectual problem. I’m pretty sure you are not the only one who hit this problem…
But yes of course I have too few information to judge that of course. No offend meant.

Thanks for your thoughts. That sounds like a clean approach. However, it still looks like a workaround to me which addresses the symptoms, not the source of the problem. You’re right to question patches to the engine. But one could also question the decision of using the same event object for mouse move and wheel events.

I already use a statemachine to switch input modes (different kinds of target selection) and I’d rather not add more transitions to keep it simple.
Sorry for sounding reluctant but it might be easier for me to maintain 10 changed lines in a fork than to add more complexity to the core of my game. After all, that’s the nice thing about open source :slight_smile:

I think so too but I couldn’t find much information about this. Everybody that uses the mouse wheel on the Nifty and jME side probably has to deal with this.

For future reference:
To implement the solution that @ia97lies describes in the last post you can listen on MouseEnter/MouseLeave events like this:

<panel visibleToMouse="true">
    <effect>
        <onHover name="nop" onStartEffect="mouseEnter()" onEndEffect="mouseLeave()"/>
    </effect>
</panel>

If your elements can overlap (like windows) you have to use a counter. Increase it on every MouseEnter event and decrease it on MouseLeave. Detach your jME input handling when the counter reaches 1 and re-attach it when it becomes 0.


If you want to use the solution that @sgold suggested in the linked thread, there now is a simpler way to poll the state. You can check if the mouse cursor lies above a Nifty element (one that handles input e.g. with visibleToMouse=true) by calling:

nifty.getCurrentScreen().isMouseOverElement()

This simply checks the size of an internal list so it’s quite efficient.


I would prefer a solution that filters the events before it even reaches the input mapping. And a solution that doesn’t need changing the engine. Unfortunately, because mouse move and wheel events are not separated, I couldn’t find an easy way.

EDIT: It’s not pretty but it works :stuck_out_tongue_winking_eye: :stuck_out_tongue_closed_eyes:

import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.screen.Screen;
import java.lang.reflect.Field;

public class NiftyWheelEventFilter implements RawInputListener {
    private final Nifty nifty;

    public NiftyWheelEventFilter(Nifty nifty) {
        this.nifty = nifty;
    }

    public void onMouseMotionEvent(MouseMotionEvent evt) {
        if(evt.getDeltaWheel() == 0)
            return;

        Screen screen = nifty.getCurrentScreen();
        if(screen == null || !screen.isMouseOverElement())
            return;

        if(evt.getDX() != 0 || evt.getDY() != 0) {
            try {
                Field field = MouseMotionEvent.class.getDeclaredField("deltaWheel");
                field.setAccessible(true);
                field.set(evt, 0);
            } catch(Exception ex) {
                // Please don't hurt me
            }
        }
        else
            evt.setConsumed();
    }

    public void beginInput() {}
    public void endInput() {}
    public void onJoyAxisEvent(JoyAxisEvent evt) {}
    public void onJoyButtonEvent(JoyButtonEvent evt) {}
    public void onMouseButtonEvent(MouseButtonEvent evt) {}
    public void onKeyEvent(KeyInputEvent evt) {}
    public void onTouchEvent(TouchEvent evt) {}
}

To insert this filter after Nifty’s input handling I had to skip an update cycle (enqueue it):

final NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort);
guiViewPort.addProcessor(niftyDisplay);

enqueue(new Callable() {
    public Object call() throws Exception {
        inputManager.addRawInputListener( new NiftyWheelEventFilter(niftyDisplay.getNifty()) );
        return null;
    }
});

Not sure it helps, but note also that you can add your own RawInputListener that can consume the events based on whatever conditions you decide. May not work any better than hacking JME’s nifty support but I just thought I’d point it out.

I think these fully separated UI layers will always have some interesting event issue or another. It’s one of the things a JME-based UI (like Lemur) doesn’t have to worry as much about.

I think I will have a look at Lemur :smile: Do Lemur consume the mouse event then?
I had a similar problem like @1000ml have here and I worked somehow around (statemachine). I agree with @1000ml if you have a HUD on top your game scene it is pretty annoying to get ride of the side effects when the mouse event hits your game scene as well.

Just wonder why is Lemur and Zay Es not part of jmonkeyengine?

It’s entirely up to the event listeners.

Lemur also provides 3D scene picking by default.

Partly because JME releases happen every 3+ years and Lemur and Zay-ES releases happen every few months. Partly because it just hasn’t happened yet.

The ideal situation would be to have these as plug-ins that are automatically bundled. So they could be released more often but you also get them by default. No one is maintaining the SDK much at the moment so it’s hard to say if that’s even feasible.

Thanks, @pspeed. I actually posted a solution based on that in my last post. I know - walls of text :smiley:
It’s a simple plug-in-and-forget fix that seems to work well without changes to the engine. And it makes me confident that you proposed the same approach.

The main reason for me to use a separate GUI library are the ScrollPanels. I’d like to avoid implementing clipping myself. Does Lemur have something similar? If so, could you point me to the code? I’d be very interested how to do that on top of jME (without rendering to textures?).

Should I mark this thread as solved? Is there any automatism for doing so or do I simply edit the title?

Not yet because JME doesn’t provide normal access to clipping like the backdoor it provided for nifty. At least not yet.

However, for some things you can simply manage a viewport that is just a portion of the screen. I’d like a more general solution for Lemur, though.