Lemur DefaultMouseListener pick no isPressed event with Checkbox

When implementing the click method of DefaultMouseListener with SimEthereal,

event.isPressed always returns as false.

I have to override mouseButtonEvent (unedited copy paste word for word)

to get isPressed to be true. I was thinking this might be default behavior since its suggested to use

isReleased for triggering things. I have looked to see where setting isPressed to false is being applied and I just cant find it though.

Is this expected behavior and if so, where is it getting set?

Without looking at the code… I’m 99% sure these events are generated by JME. So maybe JME isn’t setting it?

What version of lwjgl are you using? Maybe that’s relevant?

3.3.0-alpha-2

How would i know for sure?

I mean lwjgl version, not JME.

I don’t know where the MouseButtonEvents come from off the top of my head (I’d have to look) but I know there is a close relationship between input handling and lwjgl.

LWJGL 2.9.3

Seems like a JME bug then. Strange I’ve never encountered it because somehow button presses and things must work.

But here is the relevant Lemur code that’s just receiving the event from JME:

And I guess Lemur does create its own MouseButtonEvent but it’s from the values passed from JME:

I can’t find a case where a MouseButtonEvent is created that isn’t using the ‘pressed’ value from JME. I admit to only looking for a minute or two, though.

I did see this but wasn’t sure it was related.

I will try a test case.

That code is only run if the button is already pressed… to make sure it gets unpressed properly.

Sorry for time delay, am stepping through gui globals and hit this just after intializing MouseAppState.

Exception occurred in target VM:  
java.lang.NullPointerException: 
	at java.util.concurrent.CopyOnWriteArrayList.size(CopyOnWriteArrayList.java:162)
	at java.util.concurrent.CopyOnWriteArrayList.<init>(CopyOnWriteArrayList.java:121)
	at com.simsilica.lemur.input.InputMapper.<init>(InputMapper.java:103)
	at com.simsilica.lemur.GuiGlobals.<init>(GuiGlobals.java:152)
	at com.simsilica.lemur.GuiGlobals.initialize(GuiGlobals.java:124)
	at com.polygeddon.client.Main.simpleInitApp(Main.java:122)
	at com.jme3.app.SimpleApplication.initialize(SimpleApplication.java:237)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.initInThread(LwjglAbstractDisplay.java:130)
	at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:211)
	at java.lang.Thread.run(Thread.java:748)

This is a silent exception from this line,

Not ever seen this one so not sure if it even means anything.

I will continue with debug and get back.

Weird. The exception seems to happen when creating an empty list:

Edit: what version of Java are you running?

1.8.0_192

That’s weird enough error that if you are stepping through in a debugger… I’d consider the “debugger” to be part of the problem.

Like, I don’t usually see those “this code should never fail but it is failing in a strange way” type of issues unless some previous line was stepped over (skipped) instead of stepped or something.

I can’t think of a normal case where creating an empty CopyOnWriteArrayList should NPE.

I was thinking debugger also.

Looking into the JDK 8 code, size() is never called by any of the constructors for CopyOnWriteArrayList. So I’m thinking it’s maybe the debugger trying to query information about the list before it’s fully created?

    final void setArray(Object[] a) {
        array = a;
    }

    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

...
    public int size() {
        return getArray().length;
    }

Edit: P.S.: this is one of the reasons I haven’t really run a debugger since C coding. I think I’ve run a Java debugger maybe 3 times since 1997.

Test case which loads minimum SimEthereal, Lemur and displays the same behavior.

        // Make sure JUL logging goes to our log4j configuration
        LogAdapter.initialize();

Requires gradle 5.4.+ unless you tweak it.

A single Lemur Checkbox with attached listener.

Build.gradle

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'eclipse'
apply plugin: 'idea'

mainClassName='mygame.TestIsPressed'

repositories {
    //This is where jme3 dependencies are stored.
    jcenter()

    //Uncomment this if you install local dependencies.
    mavenLocal()

    //Uncomment this if you use external dependencies
    mavenCentral()
    
    maven { url "https://www.jitpack.io" }

    //Uncomment this if you use jme3-niftygui
    //maven{url 'http://nifty-gui.sourceforge.net/nifty-maven-repo'}

}

ext.jmeVersion = '[3.2,)'

project(":assets") {
    apply plugin: "java"

    buildDir = rootProject.file("build/assets")

    sourceSets {
        main {
            resources {
                srcDir '.'
            }
        }
    }
}

dependencies {

    implementation "org.jmonkeyengine:jme3-core:$jmeVersion"
    implementation "org.jmonkeyengine:jme3-desktop:$jmeVersion"
    implementation "org.jmonkeyengine:jme3-lwjgl:$jmeVersion"

    //Those are jme3 additional library uncomment the ones you need
    //compile "org.jmonkeyengine:jme3-android-native:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-android:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-bullet-native-android:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-blender:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-bullet-native:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-bullet:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-effects:$jmeVersion"
    implementation "org.jmonkeyengine:jme3-jogg:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-jogl:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-lwjgl3:$jmeVersion"
    implementation "org.jmonkeyengine:jme3-networking:$jmeVersion"
    implementation "org.jmonkeyengine:jme3-plugins:$jmeVersion"
    //compile "org.jmonkeyengine:jme3-terrain:$jmeVersion"

    //You need to uncomment nifty repository in the repositories section if you use this dependency
    //compile "org.jmonkeyengine:jme3-niftygui:$jmeVersion"
    //SimEthereal
    implementation 'com.github.Simsilica:SimEthereal:master-SNAPSHOT'
//    implementation "com.simsilica:sim-ethereal-v1.5.0"

    //SimMath
    implementation 'com.github.Simsilica:SimMath:master-SNAPSHOT'
//    implementation 'com.simsilica:sim-math-v1.4.0'    

    //SiO2
    implementation 'com.github.Simsilica:SiO2:master-SNAPSHOT'
//    implementation 'com.simsilica:sio2:1.3.0'
    
    //Logging
    implementation 'org.slf4j:slf4j-api:1.7.26'
    runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.11.2'
    
    //Lemur
    implementation 'com.github.jMonkeyEngine-Contributions.Lemur:lemur:master-SNAPSHOT'
//    implementation "com.simsilica:lemur:1.13.0"
    implementation "com.simsilica:lemur-proto:1.11.0"
    implementation 'com.simsilica:lemur-props:1.1.0'       

    runtime project(':assets')
}

task (createDirs).doLast {

    def pkg = 'mygame'
    def dirs = [
        file("./src/main/java/$pkg"),
        file("./src/main/resources"),
        file("./assets/Interface"),
        file("./assets/MatDefs"),
        file("./assets/Materials"),
        file("./assets/Models"),
        file("./assets/Scenes"),
        file("./assets/Shaders"),
        file("./assets/Sounds"),
        file("./assets/Textures"),
    ]

    dirs.each {
        if( !it.exists() ) {
            println "Creating " + it
            it.mkdirs()
        }
        if( it.listFiles().length == 0 ) {
            def stub = new File(it, 'removeme.txt')
            println "Creating stub file to allow git checkin, file:$stub"
            stub.text = "Remove me when there are files here."
        }
    }
}

TestIsPressed.java

package mygame;

import com.jme3.app.BasicProfilerState;
import com.jme3.app.DebugKeysAppState;
import com.jme3.app.LostFocusBehavior;
import com.jme3.app.SimpleApplication;
import com.jme3.app.StatsAppState;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.system.AppSettings;
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.GuiGlobals;
import com.simsilica.lemur.OptionPanelState;
import com.simsilica.lemur.anim.AnimationState;
import com.simsilica.lemur.event.DefaultMouseListener;
import com.simsilica.lemur.event.MouseEventControl;
import com.simsilica.lemur.style.BaseStyles;
import com.simsilica.util.LogAdapter;

/**
 *
 * @author mitm
 */
public class TestIsPressed extends SimpleApplication {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        // Make sure JUL logging goes to our log4j configuration
        LogAdapter.initialize();
        
        TestIsPressed main = new TestIsPressed();
        main.setLostFocusBehavior(LostFocusBehavior.Disabled);
   
        AppSettings settings = new AppSettings(true);
        // Set some defaults that will get overwritten if
        // there were previously saved settings from the last time the user
        // ran.       
        settings.setResolution(1280, 720);
        settings.setVSync(true);
        settings.setGammaCorrection(true);
        
        settings.setTitle("TestIsPressed");
        settings.setUseJoysticks(false);
        
        main.setSettings(settings);
        
        main.start();
    }
    
    public TestIsPressed() {
        super(new StatsAppState(), new DebugKeysAppState(), new BasicProfilerState(false),
              new AnimationState(), // from Lemur
              new OptionPanelState() );// from Lemur
    }
        
    @Override
    public void simpleInitApp() {        
        
        setPauseOnLostFocus(false);
        setDisplayFps(false);
        setDisplayStatView(false);
        
        GuiGlobals.initialize(this);
 
        GuiGlobals globals = GuiGlobals.getInstance();
        BaseStyles.loadGlassStyle();
        globals.getStyles().setDefaultStyle("glass");
        
        Container cont = new Container();
        
        Checkbox checkBox = cont.addChild(new Checkbox("checkbox"));
        MouseEventControl.addListenersToSpatial(checkBox, new DefaultMouseListener() {
            @Override
            protected void click( MouseButtonEvent event, Spatial target, Spatial capture ) {
                System.out.println("Checkbox Pressed " + event.isPressed());
                System.out.println("Checkbox Released " + event.isReleased());
            }
        });
        
        Button button = cont.addChild(new Button("Button"));
        MouseEventControl.addListenersToSpatial(button, new DefaultMouseListener() {
            @Override
            protected void click( MouseButtonEvent event, Spatial target, Spatial capture ) {
                System.out.println("Button Pressed " + event.isPressed());
                System.out.println("Button Released " + event.isReleased());
            }
        });        
        
        center(cont);
        guiNode.attachChild(cont);
        
    }
    
    private void center(Container cont) {
        // Position the panel   
        Vector3f pref = cont.getPreferredSize().clone();
        cont.setLocalTranslation((getCamera().getWidth() - pref.x)/2, 
                (getCamera().getHeight() + pref.y)/2, 0);
    }  

}

Probably something I did wrong.

Edit: Forgot to mention, uses jitpack so changes will be accessible immediately upon push.

Is it only the checkbox that is doing it or does a regular button do it?

In the glass style, the button moves when pressed down and moves back when released… which should prove that at least the button got the pressed/not-pressed events.

Do you see an event for what should have been the up… it’s just set wrong?

The up does fire:

Checkbox Released true
Button Released true

Both button and Checkbox.

Edited the above code to show listeners.

Edit: Down is always false.

Checkbox Pressed false
Checkbox Released true
Button Pressed false
Button Released true

Removed the if checks and just printed the method calls from printf.

Edited to show results.

So events seem to be working fine for me. I didn’t run your test case directly but I’ll explain what I did.

First, I’m running JDK8, JME master, Lemur master.

I edited the Lemur/examples/demo’s MainMenuState to add a MouseListener to the exit button.

        ActionButton exit = mainWindow.addChild(new ActionButton(new CallMethodAction("Exit Demo", app, "stop")));
        exit.setInsets(new Insets3f(10, 10, 10, 10)); 
        
        MouseEventControl.addListenersToSpatial(exit, new MouseListener() {
                public void mouseButtonEvent( MouseButtonEvent event, Spatial target, Spatial capture ) {
                    System.out.println("mouseButtonEvent(" + event + ")");
                    System.out.println("Released:" + event.isReleased() + "  Pressed:" + event.isPressed());
                }

                public void mouseEntered( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    System.out.println("mouseEntered(" + event + ")");
                }

                public void mouseExited( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    System.out.println("mouseExited(" + event + ")");
                }

                public void mouseMoved( MouseMotionEvent event, Spatial target, Spatial capture ) {
                    //System.out.println("mouseMoved(" + event + ")");
                }
       
            });

When I run the demo and click on the exit button (slow or long) with the mouse, I see:

mouseEntered(MouseMotion(X=204, Y=280, DX=0, DY=0, Wheel=0, dWheel=0))
mouseButtonEvent(MouseButton(BTN=0, PRESSED))
Released:false  Pressed:true
mouseButtonEvent(MouseButton(BTN=0, RELEASED))
Released:true  Pressed:false