Nifty gui documentation is somewhat confusing

Hi,

I spent the last few days digging my way out of null pointer and various strange behaviors while trying to get fairly trivial things done with Nifty gui.

The thing is that now that I figured out what was going on, I’d like to make some updates to the docs to help others out. Here are a few things I learned the hard way:

  1. For just about any interaction beyond helloworld, dialog boxes need to display in game information, and notify game of user selections. That requires some kind of connection between the app and controllers. The tutorials suggest extending AbstractAppState in the controllers. But it’s not enough to extend AbstractAppState to get access to the app object. You still need to: instantiate the controller in question in the app, attach it to state manager, and then you have to use nifty’s fromXml method to pass the controller in as the last arg. If you don’t do everything exactly right and in the right order, the stack traces or api docs will not help you. If you just need access to the app object, it is simpler to pass it in at controller construction in app’s init and forget the whole app state business.

  2. To hide nifty controls and return to game, I create an empty screen with a single layer and use nifty’s gotoScreen method. Do not ever use nifty’s exit method.

  3. fromXml should only be used once. There’s an add method that can be used afterwards, but I haven’t experimented with it. If you’re not ready to display any screen right when fromxml is called, you can use the empty screen from tip above as the second parameter.

  4. use nifty.validateXml before loading. Nifty has very unpredictable behavior if you don’t get the xml just right.

Overall, I found many unpleasant surprises when interacting with Nifty. Between nifty’s sparsely documented javadoc, and Jmonkey’s documentation on Nifty, this may be a rather discouraging experience for people who are just starting out. I think I could write a followup tutorial to the HelloWorld one that may clarify a lot of things for developers who just want to move forward along the path of least resistance.

2 Likes

Well perhaps nifty.exit() isn’t so bad after all. Now that I have everything else under control, it does not seem to be as bad as it used to. Looking at the source code, it doesn’t seem to be doing anything bad, just closing the current screen.

I think the AppState and Controller confusions comes from the usual approach that controllers are more or less the standard way to hook behaviour and screen elements, so the information that you need for a GUI element is usually available through a controller.
The problem that you hit might be that you put your data in Application, not that accessing stuff via a Controller is the wrong approach :wink: … on the other hand, some stuff legitimately belongs into Application, and in that case, passing on Application or its members is the best approach.
In general, I’m seeing that the documentation often refers to “how do I access my data”, and there’s always that issue whether it’s a global, a member in an Application subclass, inside a Controller, or somewhere else. It might be a good idea to reorganize the docs a bit, describe the various approaches and their impacts and how data gets accessed, and just link to that page and the pertinent section instead of re-describing the issue on every page that encounters it. It would be a somewhat large-ish undertaking, so anybody who tries that should probably open a thread and talk it over before or while doing it.

Exiting Nifty is essentially an unsupported use case. Nifty uses global state, and it’s generally struggling to keep all its state unter control, so I’m not surprised that it has trouble cleaning up after itself. It’s really best to return to an empty screen.
Closing the current screen is a bit… erm, well, “cheap” if you will because it doesn’t completely shut down Nifty, but then moving to an empty screen isn’t better. Of course, it’s debatable whether a complete shutdown is even desirable.

The add method should work. I think Nifty does not retain state from XML reading, so opening and reading another XML file starts with the same kind of state that the first fromXml call started with.

I didn’t know about the validateXml function, that sounds like a very useful thing.
Can you add a section for that on https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:nifty_gui_best_practices ?

@zarch made a post somewhere about appstate controller interaction. Don’t forget to also check out the nifty bible! Definitely the best doc out there (made by the creator). I will make some nifty tutorial videos at some point, to help the new users to it

1 Like

Thanks for the comments.

I made a couple of fairly small changes to the documentation. I added the validate XML section:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:nifty_gui_best_practices#validate_the_xml_before_loading

and a note about initializing the app state controller:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:nifty_gui_scenarios?&#get_access_to_application_and_update_loop

Perhaps I’m not a typical user, but after getting fairly good traction by jumping from one Jmonkey tutorial to another I hit a wall with Nifty for several days. I still think there’s a need for an intermediate tutorial after the helloWorld and before the elaborate one that starts here:
https://wiki.jmonkeyengine.org/legacy/doku.php/jme3:advanced:nifty_gui_overlay . The tutorial should draw a cube, popup a menu screen when user hits a button, allow user to change cube’s color, and close the screen. This is the next step a new developer may undertake after running into limitations with jmonkey’s basic input controls.

2 Likes

addXML and registerScreenController is what I used. My appstates inherit these methods from a parent class. (Hope the forum doesn’t mangle the code posts)

[java]
@Override
public void initialize(AppStateManager stateManager, Application app) {
final Set<ScreenController> screenControllers = getScreenControllers();
if ((screenControllers != null) && (screenControllers.size() > 0)) {
for (ScreenController screenController : screenControllers) {
this.nifty.registerScreenController(screenController);
}
}
Set<String> screenAssetNames = getScreenAssetNames();
if ((screenAssetNames != null) && (screenAssetNames.size() > 0)) {
for (String screenAssetName : screenAssetNames) {
this.nifty.addXml(“Interface/” + screenAssetName);
}
}
}

protected abstract Set&lt;ScreenController&gt; getScreenControllers();

protected abstract Set&lt;String&gt; getScreenAssetNames();

[/java]

So the concrete appstates tells which XML-files they want to “register” and instances of ScreenControllers. The screen controllers can be “this” but I usually have a helper class that implements ScreenController that the appstate knows about and can update with values that should be shown by nifty.

The glue that ties the XML to the screen controller is in the XML-file. For example:

[java]
<screen id=“hud” controller=“nu.zoom.frozenmoon.nifty.VehicleHUDScreen”>
[/java]

It is important that the screen controller is registered before adding the XML file, otherwise nifty will try to instantiate the screen controller itself (and usually fail).

This is far from perfect but it worked in my use case - I wanted a way to add a new appstate, a new controller and a new XML-file without changing a “central” registry class.

Johan, how does your abstract app state get injected with an instance of nifty?

I too experienced a steep learning curve with Nifty, I think mainly due to not finding any short sample apps to dissect. A followup tutorial would be most welcome, particularly if it were associated with a test app.

@pslusarz said: Johan, how does your abstract app state get injected with an instance of nifty?

It doesn’t :slight_smile: I create the appstates so I can give them whatever they need in the constructor.

[java]
public void simpleInitApp() {
NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
inputManager,
audioRenderer,
guiViewPort);
Nifty nifty = niftyDisplay.getNifty();
guiViewPort.addProcessor(niftyDisplay);

// Just pass it in the constructor
MyAppState myAppState = new MyAppState(nifty);
this.stateManager.attach(myAppState);
myAppState.setEnabled(true);

[/java]

I do not appear to have permissions to create new pages, so here is the tutorial I am proposing to put up. Can someone with privileges make a page for it? Also, please give your feedback.

In this tutorial, you will see how you can change colors of a node using Nifty’s list box.

There are 3 entities interacting in this demo: the app, the controller and nifty GUI.

  • HelloNiftySelect.java - app wires everything up, displays the box
  • HelloNiftySelectController.java - controller receives events from the GUI, and passes them on to the app
  • hello-nifty-select-gui.xml - GUI contains the selection screen layout

[java]
package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.niftygui.NiftyJmeDisplay;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import de.lessvoid.nifty.Nifty;
import java.util.HashMap;
import java.util.Map;

public class HelloNiftySelect extends SimpleApplication {

private Nifty nifty;
Material selectedColor;
Map&lt;String, ColorRGBA&gt; colorSelections = new HashMap&lt;String, ColorRGBA&gt;();

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

public void simpleInitApp() {        
    colorSelections.put("Red", ColorRGBA.Red);
    colorSelections.put("Blue", ColorRGBA.Blue);
    
    createBox();
    
    NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,
            inputManager,
            audioRenderer,
            guiViewPort, 2048, 2048);
    nifty = niftyDisplay.getNifty();
    HelloNiftySelectController controller = new HelloNiftySelectController(this);
    nifty.fromXml("Interface/hello-nifty-select-gui.xml", "select", controller);
    guiViewPort.addProcessor(niftyDisplay);
    flyCam.setEnabled(false);
    inputManager.setCursorVisible(true);
}

public void colorSelected(String color) {
selectedColor.setColor(“Color”, colorSelections.get(color));
}

public void doneSelecting() {
    nifty.gotoScreen("start");
    flyCam.setEnabled(true);
    inputManager.setCursorVisible(false);
}

   
private void createBox() {
    Box box = new Box(1,1,1);
    Geometry geometry = new Geometry("Box", box);
    selectedColor = new Material(assetManager, 
            "Common/MatDefs/Misc/Unshaded.j3md");
    selectedColor.setColor("Color", colorSelections.values().iterator().next());
    geometry.setMaterial(selectedColor);
    rootNode.attachChild(geometry);
}

}
[/java]

The important parts to pay attention to in the simpleInitApp method are around the controller creation. In the constructor, we pass reference to the app to the controller, so it can notify the app when GUI events are happening. We then pass this controller to nifty, and we tell nifty to start with “select” screen. We also tell nifty to initialize GUI screen layout based on hello-nifty-select-gui.xml. When select screen is displayed, user will need a mouse pointer to interact with the selections, so we disable the flycam and display the mouse.

Now take a look at the two notification methods that the controller will use. First one, colorSelected, tells the box to change colors. New color will be displayed on the screen during the next app update method. Second notification method is called when the done button is clicked. We want to hide the selection screen and return to the game. To hide the selection screen, we tell nifty to display a special empty screen called “start.” We also re-enable the flycam and hide mouse pointer. Now time to look at the controller.

[java]
package jme3test.helloworld;

import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.NiftyEventSubscriber;
import de.lessvoid.nifty.controls.ButtonClickedEvent;
import de.lessvoid.nifty.controls.ListBox;
import de.lessvoid.nifty.controls.ListBoxSelectionChangedEvent;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.screen.ScreenController;
import java.util.List;

public class HelloNiftySelectController implements ScreenController {

private final HelloNiftySelect app;

public HelloNiftySelectController(HelloNiftySelect app) {
    this.app = app;
}

public void bind(Nifty nifty, Screen screen) {
    ListBox theBox = screen.findNiftyControl("colorSelectionBox", ListBox.class);
    for (String color : app.colorSelections.keySet()) {
        theBox.addItem(color);
    }

}

@NiftyEventSubscriber(id = "colorSelectionBox")
public void onMyListBoxSelectionChanged(final String id, final ListBoxSelectionChangedEvent&lt;String&gt; event) {
    List&lt;String&gt; selection = event.getSelection();
    app.colorSelected(selection.get(0));
}

@NiftyEventSubscriber(id = "doneButton")
public void onDoneButtonClicked(final String id, final ButtonClickedEvent event) {
    app.doneSelecting();

}

public void onStartScreen() {
}

public void onEndScreen() {
}

}

[/java]

Bind method gets called by nifty when it initializes the screen. Nifty knows which controller class to bind to which screen, because it is specified in the screen layout xml file (see below). It also knows to bind the specific instance of the controller, because we passed it in the fromXml method in the app. Next, controller declares that it would like to be notified when list box “colorSelectionBox” selection changes, and when the done button is clicked.

Finally, here is how the screen is laid out in the hello-nifty-select-gui.xml file:
[xml]
<?xml version=“1.0” encoding=“UTF-8”?>
<nifty xmlns=“http://nifty-gui.sourceforge.net/nifty-1.3.xsd” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=“http://nifty-gui.sourceforge.net/nifty-1.3.xsd http://nifty-gui.sourceforge.net/nifty-1.3.xsd”>
<useStyles filename=“nifty-default-styles.xml” />
<useControls filename=“nifty-default-controls.xml” />

&lt;screen id="select" controller="jme3test.helloworld.HelloNiftySelectController"&gt;
    &lt;layer childLayout="center"&gt;
        &lt;panel id="panel" height="20%" width="30%" align="right" valign="top" childLayout="center" visibleToMouse="true"&gt;
            &lt;control name="label" text="Select cube color" align="left" valign="top"/&gt;
            &lt;control id="colorSelectionBox" width="60%" height="90%" align="left" 
                 valign="center" name="listBox" vertical="optional" horizontal="optional" 
                 displayItems="3" selectionMode="Single" 
                 forceSelection = "true" /&gt;
            &lt;control id="doneButton" name="button" label="Done" align = "left" valign="bottom" /&gt;
        &lt;/panel&gt;
    &lt;/layer&gt;
&lt;/screen&gt; 

&lt;screen id="start"&gt;
  &lt;layer id="baseLayer" childLayout="center"&gt;
  &lt;/layer&gt;
&lt;/screen&gt;     

</nifty>
[/xml]

We tell nifty that we will have two screens: one for selecting, and one for hiding. “Start” is a special screen that is required by the nifty framework, and here we use it to put nifty in idle state while we return main interaction to the game. While hiding nifty screens could also be accomplished by using nifty’s exit method, with the current version of the framework, you will save yourself from problems if you just follow the pattern in this tutorial.

Next, note that the select screen needs to specify the fully qualified name of the controller class. When fromXml method is called, nifty will pair up screens to controllers based on this assignments. If no instances of controllers are passed in, nifty will construct them automatically. As controllers are paired to screens, each controller is registered for event notifications and then bind method is called.

Another thing to point out is that the containing panel has “visibleToMouse” property set. If this property is not set, the contained controls will not respond to mouse events. Finally, note the control ids: doneButton and colorSelectionBox. These are strings that nifty uses to connect controller to the screen elements, the button and the listbox.

As you see, even a simple GUI interaction is a fairly complex matter to wire up. As interaction complexity grows, you will wonder how to split up responsibilities between classes and where to put certain logic. Most GUI developers follow a Model-View-Controller pattern to help them make these decisions.

References:

Nifty controls page

Nifty API

Nifty controls API

Model View Controller pattern

4 Likes

Here’s link to the github project:

GitHub - pslusarz/jmonkey-nifty-select-example: Hit the ground running with JMonkey and NiftyGUI

To help keep this request from becoming lost, I’ve opened a new docs issue (#612) for it.

1 Like