Adding controls dynamically at runtime

It appears this is possible and I’m finding traces of how it’s done it in the Nifty source, but I’m having a hard time figuring it out.



Basically what I’m trying to do is create custom controls at runtime, without having to define them all in the layout/xml definitions.

We need to populate a map at runtime with interactive icons (our custom control, not just a panel or textfield), and don’t know the exact amount of icons to generate until runtime (game play).



Does anyone know how this is done? take a look at the quick example code below of a technical description.





[java]import com.jme3.app.SimpleApplication;

import com.jme3.niftygui.NiftyJmeDisplay;

import de.lessvoid.nifty.Nifty;

import de.lessvoid.nifty.builder.*;



public class HelloTestScript extends SimpleApplication {



public static final String SCREEN_RUNTIME = “screenRuntime”;

private Nifty nifty;



public static void main(String[] args){

HelloTestScript app = new HelloTestScript();

app.setPauseOnLostFocus(false);

app.setShowSettings(false);

app.start();

}



public void simpleInitApp() {

NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,

inputManager,

audioRenderer,

guiViewPort);

nifty = niftyDisplay.getNifty();

guiViewPort.addProcessor(niftyDisplay);

inputManager.setCursorVisible(true);



// Define Screen Layout which contains parent element

nifty.addScreen(SCREEN_RUNTIME, new ScreenBuilder(SCREEN_RUNTIME) {{



layer(new LayerBuilder("#mapLayer") {{

childLayoutCenter();



panel(new PanelBuilder("#map") {{

childLayoutCenter();

width(“70%”);

height(“40%”);

backgroundColor("#0000ff");

}});

}});

}}.build(nifty));



// Define the Custom control that is our child element

new ControlDefinitionBuilder("#icon") {{

panel(new PanelBuilder("#icon_panel") {{

childLayoutAbsolute();

backgroundColor("#33ff00");

// … would include several custom interactive child elements …

}});



}}.registerControlDefintion(nifty);



nifty.gotoScreen(SCREEN_RUNTIME); // start the screen

}



@Override

public void simpleUpdate(float tpf) {



/**

  • During runtime we need to be able to add our custom controls ‘icons’ to the ‘map’
  • How is this possible ?!?
  • After digging through the source I thought I found how this works internally (shown below)
  • But it’s not working this way and the below code will through a NullPointerException

    /



    /


    // !! How come this doesn’t work? Is this possible ?!?

    Screen screen = nifty.getScreen(SCREEN_RUNTIME);

    Element parentElement = screen.findElementByName("#map");



    Element childElement = screen.findElementByName(“panel_main”);

    nifty.createElementFromType(screen, parentElement, childElement.getElementType());

    */



    }



    }

    [/java]

Well I use something like this:

[java]

Element yourParentPanel = // find your parent panel on screen

ControlBuilder cb = new ControlBuilder("yourCustomControlName") {

{

// add some other attributes whatever

}

}

Element newElement = cb.build(yourParentPanel.getNifty(), yourParentPanel.getNifty().getCurrentScreen(), yourParentPanel);

YourCustomControl yourCustomControl = newElement.getNiftyControl(YourCustomControl.class);

// you can set attributes to your custom control also here, by using reference to yourCustomControl.

[/java]

Thanks InShadow that is quite useful. can you explain to me what ‘YourCustomClass’ looks like in your example?

What class does that extend?



I’m confused because I’ve been used to defining my custom controls this way using DefinitionBuilder:



[java]new ControlDefinitionBuilder(ID) {{



panel(new PanelBuilder(“controls_panel”) {{

childLayoutAbsolute();

backgroundColor("#33ff00");

// … more elements included …

}});



}}.registerControlDefintion(nifty);[/java]

In my example, YourCustomControl extends AbstractController - a class provided by nifty library, which is used to make, well Java-mapped controllers for your custom defined controls. :slight_smile: I define controller in xml with “controller=path.to.my.CustomControl”. The same can be probably achieved in java code also, but I didn’t try it. Because my workflow look like this: I define my custom control in xml and map it to some Java class. Then I dynamically create instance of this custom control in java code, find its mapped Java class, as described in previous post, which then I fill with custom attributes.

Hopefully you can understand my confused writing. :slight_smile: Anyway, if you can’t get it to work, I can post you my code later (I am at work now :)).

Thanks InShadow, that’s what I was looking for!

Makes sense, and I see the slight difference in workflow when using XML.



Hopefully this can be helpful to others, here is the complete example source code, using all Java and no XML.



cheers!



[java]



public class HelloTestScript extends SimpleApplication {



public static final String SCREEN_RUNTIME = “screenRuntime”;

private Nifty nifty;



public static void main(String[] args){

HelloTestScript app = new HelloTestScript();

app.setPauseOnLostFocus(false);

app.setShowSettings(false);

app.start();

}



public void simpleInitApp() {

NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager,

inputManager,

audioRenderer,

guiViewPort);

nifty = niftyDisplay.getNifty();

guiViewPort.addProcessor(niftyDisplay);

inputManager.setCursorVisible(true);



// Define Screen Layout which contains parent element

nifty.addScreen(SCREEN_RUNTIME, new ScreenBuilder(SCREEN_RUNTIME) {{



layer(new LayerBuilder("#mapLayer") {{

childLayoutCenter();



panel(new PanelBuilder("#map") {{

childLayoutCenter();

width(“70%”);

height(“40%”);

backgroundColor("#0000ff");

}});

}});

}}.build(nifty));



// Define the Custom control that is our child element

new ControlDefinitionBuilder("#icon") {{

panel(new PanelBuilder("#icon_panel") {{

childLayoutAbsolute();

backgroundColor("#00ffff");

width(“100%”);

height(“20%”);

// … would include several custom interactive child elements …

}});



}}.registerControlDefintion(nifty);

nifty.gotoScreen(SCREEN_RUNTIME); // start the screen



//



//


Dynamically add the CustomControl and map it to the control 'YourCustomControl'
Screen screen = nifty.getScreen(SCREEN_RUNTIME);
Element parentElement = screen.findElementByName("#map");
ControlBuilder cb = new ControlBuilder("#icon") {
{
// add some other attributes whatever
}
};
Element newElement = cb.build(nifty, screen, parentElement);
YourCustomControl yourCustomControl = newElement.getNiftyControl(YourCustomControl.class);
}

@Override
public void simpleUpdate(float tpf) {
}
}


public class YourCustomControl extends AbstractController {
private Screen screen;
private Element element;
private FocusHandler focusHandler;

public void bind(
final Nifty nifty,
final Screen screenParam,
final Element newElement,
final Properties properties,
final Attributes controlDefinitionAttributes) {
element = newElement;
screen = screenParam;
}

public void onStartScreen() {
focusHandler = screen.getFocusHandler();
}

public boolean inputEvent(final NiftyInputEvent inputEvent) {
return false;
}
}
[/java]
1 Like

In control definition builder, you are probably missing the attribute that links your control definition to YourCustomControl class. Should probably be something like this, but can’t check it since I am still at work:

[java]

// Define the Custom control that is our child element

new ControlDefinitionBuilder("#icon") {{

controller(“path.to.YourCustomControl”);

panel(new PanelBuilder("#icon_panel") {{

childLayoutAbsolute();

backgroundColor("#00ffff");

width(“100%”);

height(“20%”);

// … would include several custom interactive child elements …

}});



}}.registerControlDefintion(nifty);

[/java]

I would also suggest that YourCustomControl is public class, defined in its own file, so specifying its path should be easier. :wink: