Nifty GUI Controls

A GUI is not very useful if it can only display screens.  To have a useful user interface, we need to put in UI elements, such as buttons, menus, text fields, etc. into our screens and hook up java listeners to them.

Before we do all that, we will adhere to proper OO principles by creating multiple classes with clear responsibilities.

We will start by creating a class responsible for creating nifty screens, as follows:

import de.lessvoid.nifty.*;
import de.lessvoid.nifty.screen.*;
import de.lessvoid.nifty.builder.*;
import de.lessvoid.nifty.controls.*;
import de.lessvoid.nifty.controls.slider.builder.*;

/**
 * The job of ScreenFactory is to construct different Screens
 */
public class ScreenFactory
{
    /**
     * Build a particular screen.  
     * Implemented as a static method to make it easier to call.
     */
    public static void buildScreen(String id, final ScreenController control, Nifty nifty) 
    {
        ScreenBuilder builder = null;
        
        if (id.equals("HelloScreen")) {
            builder = new ScreenBuilder(id) {
                {
                    // Attach the controller to this screen.
                    controller(control);
                    layer(new LayerBuilder() {
                        {
                            // Layout the child elements in the middle
                            childLayoutCenter();
                            // Each layer can have one or more panel
                            panel(new PanelBuilder() {
                                {
                                    // We simply have an empty panel with a 
                                    // translucent background
                                    alignCenter();
                                    valignCenter();
                                    childLayoutCenter();
                                    // Take up the entire screen space
                                    width(percentage(100));
                                    height(percentage(100));
                                    // Put a slider in the middle of the screen
                                    control(new SliderBuilder("mySlider", false) {
                                        {
                                            alignCenter();
                                            valignCenter();
                                        }
                                    });
                                    // color is expressed using the RGBA notation
                                    backgroundColor("#ffffff88");
                                }
                            });
                        }
                    });          
                }
            };
            
            builder.build(nifty);
        }
     
    }
}

The static method ScreenFactory.buildScreen(), when called, will create the screen within nifty.  A couple of addition to the screen above. 

First, we put a "Slider" control in the middle of the screen.  All controls must be put inside panels.  You can find out all the nifty controls by checking out the Nifty GUI standard controls page.

Second, just because we have a slider on screen, we still need to make it respond to user manipulation.  We can do this by attaching a controller to the screen. 

 

The Controller

Line 24 from the ScreenFactory class attaches a ScreenController object to the screen being built.  This ScreenController Object is the "glue" that ties together the GUI and the code that responds to changes in the UI control elements. 

The ScreenController is an interface and requires implementation of 3 methods, onBind(), onStartScreen(), and onEndScreen().  None of these methods, however, responds to changes in the slider.  To have our program respond to slider changes, we will need to create a method that "listens" to the changes in the slider.  We create this method by using the following:

    @NiftyEventSubscriber(id = "mySlider")
    public void onSliderChange(String id, SliderChangedEvent event) 
    {
        System.out.println(event.getValue());
    }

The most curious part of this method is the @NiftyEventSubscriber line right before the method declaration. This is called a "java annotation". This annotation tells nifty that the method onSliderChange() will respond to changes to the slider with the name "mySlider".  We can acutally name the method anything we like, as the annotation provides the hook between method and the events.

All event handling methods must have 2 parameters, the first parameter must be of String type and contains the id of the element.  The second parameter is the event that is produced.  In this case, the slider produces the SliderChangeEvent.  The Nifty Standard Controls page have detailed listings of each control and the events that are produced by them.

By using java annotation instead of explicit event listener, we are able to use a single controller with different methods to respond to different events within a single screen.

 

Putting It All Together

The following code puts everything together. This program allows the user to change the size of the rotating sphere by adjusting the slider in the middle of the screen.

The Game class:

import env3d.advanced.EnvAdvanced;
import env3d.advanced.EnvNode;

public class Game 
{
    private EnvNode node;
    
    public void play() {
        EnvAdvanced env = new EnvAdvanced();
         
        env.setDefaultControl(false);
        
        // Build the screens, attach controller to the screen
        ScreenFactory.buildScreen("HelloScreen", new MyController(this), env.getNiftyGUI());
         
        // Activate the screen
        env.getNiftyGUI().gotoScreen("HelloScreen");
         
        // put a simple object in our environment.  
        node = new EnvNode();
        env.addObject(node);
        node.setX(5); node.setY(1); node.setZ(0);
        node.setScale(3);
        
        // Render the screen until escape key is pressed
        while (env.getKey() != 1) {
            // Rotate our object
            node.setRotateY(node.getRotateY()+1);
            env.advanceOneFrame();
        }
         
        env.exit();
    }
     
    public EnvNode getNode() 
    {
        return node;
    }

    
    public static void main(String args[]) {
        (new Game()).play();
    }
}

The Controller:

import env3d.advanced.EnvNode;
 
import de.lessvoid.nifty.*;
import de.lessvoid.nifty.screen.*;
import de.lessvoid.nifty.controls.*;

public class MyController implements ScreenController
{    
    private Game game;
    
    public MyController(Game game) 
    {
        this.game = game;
    }           

    // We use java anootaion to specify that this method will be called
    // when the object id "mySlider" is changed.
    //
    // By using java annotation, the actual name of the method does not
    // matter at all!  The result is that we can use have different methods
    // to respond to different events all inside the same controller.
    @NiftyEventSubscriber(id = "mySlider")
    public void onSliderChange(String id, SliderChangedEvent event) 
    {
        game.getNode().setScale(event.getValue()/20);
    }
    
    public void bind(Nifty nifty, Screen screen) 
    {        
    }
    
    public void onStartScreen()    
    {
    }
    
    public void onEndScreen() 
    {
    }    
}

The ScreenFactory:

import de.lessvoid.nifty.*;
import de.lessvoid.nifty.screen.*;
import de.lessvoid.nifty.builder.*;
import de.lessvoid.nifty.controls.*;
import de.lessvoid.nifty.controls.slider.builder.*;

/**
 * The job of ScreenFactory is to construct different Screens
 */
public class ScreenFactory
{
    /**
     * Build a particular screen.  Implmented as a static method to make it easier to call.
     */
    public static void buildScreen(String id, final ScreenController control, Nifty nifty) 
    {
        ScreenBuilder builder = null;
        
        if (id.equals("HelloScreen")) {
            builder = new ScreenBuilder(id) {
                {
                    // Attach the controller to this screen.
                    controller(control);
                    layer(new LayerBuilder() {
                        {
                            // Layout the child elements in the middle
                            childLayoutCenter();
                            // Each layer can have one or more panel
                            panel(new PanelBuilder() {
                                {
                                    // We simply have an empty panel with a 
                                    // translucent background
                                    alignCenter();
                                    valignCenter();
                                    childLayoutCenter();
                                    
                                    // Take up the entire screen space
                                    width(percentage(100));
                                    height(percentage(100));        
                                    
                                    // Put a slider in the middle of the screen
                                    control(new SliderBuilder("mySlider", false) {
                                        {
                                            alignCenter();
                                            valignCenter();
                                        }
                                    });
                                    
                                    // color is expressed using the RGBA notation
                                    backgroundColor("#ffffff88");
                                }                            
                            });
                        }
                    });          
                }
            };
            
            builder.build(nifty);
        }
     
    }
}