NiftyGUI non-modal popup

Right, so I’ve been looking through the Nifty manual, wiki and through the forum here and I haven’t been able to find anything about this.

To give a bit of context: I require the following for a game I am designing. I require windows that act as report-windows, which contain a lot of detailed information about a unit. The player needs to be able to compare unit’s values against each other by opening such a report-window for each unit and placing the windows next to each other.

So what I am trying to do is create popup-windows that are non-modal and draggable. These popup-windows will contain a lot of sub-elements, so I would like to be able to define them in my XML, rather than in my source-code.
From what I understand, I cannot do this with popups, since they block all input to other layers. However, I do not understand how, if at all, I can do this using the draggable-control element.
I can, on demand, dynamically create a draggable control and add it to my screen, but how do I create this control so that it contains the elements defined in the XML? In other words, how do I essentially clone a subset of elements from the XML to dynamically add them to a newly created control?

The question really boils down to:
Is there some in-built way of cloning an existing Nifty-Element?
Is there some other way of doing this with NiftyGUI that I am unaware of?

Sorry for the rather lengthy question, I thought that giving context might help to identify whether there are alternatives that I could try.

Thanks in advance for your help :smiley:

I use lots of draggable windows in my project using NiftyGUI, but I do not use NiftyGUI’s built in popups or draggable controls. Instead each window is a panel with absolute layout in its layer. On the panel I set interactOnClickMouseMove to point to a method in my ScreenController, I also pass the ID of the Element to this method which is also used as the key for the window object, used to hold methods and variables concerning each window, in a HashMap contained in my window manager class.

Various things can happen when a window panel is clicked, in this case the method obtains the instance of the window through the window manager and checks to see if the mouse is in a pre-defined title bar area of the window, if so the x and y coordinates of the window panel are modified.

I only use XML to define styles and custom controls, but not for element layouts so I don’t know much about that. I do all of my layouts using Java and Element/Control builders, I much prefer this.

P.S. When a window is initially clicked, if it is not currently the “active” window it is detached from the scene and using an EndNotify() re-attached which brings it to the ‘front’ which is actually the end of the rendering order so that it is rendered last or on top of everything else. Actually it’s not re-attached per-se, it’s re-created which isn’t that big of a deal since people aren’t likely to switch active windows every frame :stuck_out_tongue:

1 Like

Hmm, that’s a very interesting approach, I’m definitely going to keep that in mind and try it out, it sounds like you get a lot of flexibility out of your window-management this way :smiley:.
You say you prefer using the Java Builders. Do you have any specific reason for this preference or is it just personal taste?

I don’t quite understand how or if this solves my main problem though, which is that I need to be able to have multiple instances of the same window, but with their layouts defined in XML.
If all else fails, I could switch to using the Java-Builders, but this means that whoever wants to mess around with the GUI layout later will have to write code, which I would really like to avoid (I’ve designed the game to be highly moddable and I would like an easy way of messing around with the GUI).

Yeah my windows are pretty flexible and dynamic. I would say using builders instead of XML is primarily personal preference, but it also affords me a lot of flexibility in creating my Nifty Elements since, within the builders, I can use if statements, while/for loops, callbacks, etc…

Anyway, I’m not particularly familiar with creating layouts with XML in NiftyGUI so you might need to elicit the support of someone else in that area; however, I did have a thought that might help you out. Have you considered using XML to define custom controls rather than layouts. A custom control is a reusable collection of Nifty Elements that you can use in your layouts like you would a button or a panel.

Here’s an example of a quick and dirty custom control I made for my project, a number picker control. First we have the XML control definition file:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-controls>
  <controlDefinition name="number-picker" style="number-picker" controller="carpeDiem.UI.NiftyControls.NumberPickerControl">
    <panel childLayout="horizontal">
        <text id="#text" style="#text" text="$value" />
        <panel style="#upButton" id="#upButton" controller="carpeDiem.UI.NiftyControls.NumberPickerButtonControl" inputMapping="de.lessvoid.nifty.input.mapping.MenuInputMapping">
            <interact onClick="onClick()" />
        </panel>
        <panel style="#downButton" id="#downButton" controller="carpeDiem.UI.NiftyControls.NumberPickerButtonControl" inputMapping="de.lessvoid.nifty.input.mapping.MenuInputMapping">
            <interact onClick="onClick()" />
        </panel>
    </panel>
  </controlDefinition>
</nifty-controls>

Next we have the XML style definition file:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-styles>
  <style id="number-picker#text">
    <attributes font="Interface/Fonts/SpaceAge_16.fnt" width="*" align="center" valign="center" textHAlign="center" textVAlign="center" visibleToMouse="false" color="#28ff9c"/>
  </style>
  
  <style id="number-picker#upButton">
    <attributes backgroundImage="Interface/UI/Buttons/Button_ArrowUp.png" imageMode="sprite:18,18,0" childLayout="center" visibleToMouse="true" focusable="false"  width="18px" height="18px" />
    <effect>
      <onHover name="imageOverlay" filename="Interface/UI/Buttons/Button_ArrowUp.png" imageMode="sprite:18,18,1" post="true" />
      <onClick name="imageOverlay" filename="Interface/UI/Buttons/Button_ArrowUp.png" imageMode="sprite:18,18,2" post="true" />
    </effect>
  </style>
  
  <style id="number-picker#downButton">
    <attributes backgroundImage="Interface/UI/Buttons/Button_ArrowDown.png" imageMode="sprite:18,18,0" childLayout="center" visibleToMouse="true" focusable="false" width="18px" height="18px" />
    <effect>
      <onHover name="imageOverlay" filename="Interface/UI/Buttons/Button_ArrowDown.png" imageMode="sprite:18,18,1" post="true" />
      <onClick name="imageOverlay" filename="Interface/UI/Buttons/Button_ArrowDown.png" imageMode="sprite:18,18,2" post="true" />
    </effect>
  </style>
</nifty-styles>

And finally we have two control classes, the first one for the Number Picker control:

package carpeDiem.UI.NiftyControls;

import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.AbstractController;
import de.lessvoid.nifty.controls.Parameters;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.screen.Screen;

/**
 *
 * @author Adam T. Ryder http://1337atr.weebly.com
 */
public class NumberPickerControl extends AbstractController {
    private TextRenderer textRenderer;
    
    private int value;
    private int max;
    private int min;
    
    @Override
    public void bind(final Nifty niftyParam, final Screen screenParam, final Element newElement, final Parameters parameter) {
        super.bind(newElement);
        textRenderer = getElement().findElementById("#text").getRenderer(TextRenderer.class);
        
        max = Integer.parseInt(parameter.getProperty("max", "10"));
        min = Integer.parseInt(parameter.getProperty("min", "0"));
        value = Integer.parseInt(parameter.getProperty("value", Integer.toString(min)));
        value = (value > max) ? max : (value < min) ? min : value;
        textRenderer.setText(Integer.toString(value));
    }
    
    public int getValue() {
        return value;
    }
    
    public void setValue(final int value) {
        this.value = (value > max) ? max : (value < min) ? min : value;
        textRenderer.setText(Integer.toString(this.value));
    }
    
    public void setMax(final int maxValue) {
        max = maxValue;
        min = (max < min) ? max : min;
        if (value > max) { setValue(max); }
    }
    
    public int getMax() {
        return max;
    }
    
    public void setMin(final int minValue) {
        min = minValue;
        max = (max < min) ? min : max;
        if (value < min) { setValue(min); }
    }
    
    public int getMin() {
        return min;
    }
    
    @Override
    public void init(final Parameters parameter) {
        
    }
    
    @Override
    public void onStartScreen() {
        
    }
    
    @Override
    public boolean inputEvent(final NiftyInputEvent inputEvent) {
        return false;
    }
}

and the second one for the Number Picker buttons:

package carpeDiem.UI.NiftyControls;

import carpeDiem.UI.TooltipManager;
import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.AbstractController;
import de.lessvoid.nifty.controls.Parameters;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.input.NiftyStandardInputEvent;
import de.lessvoid.nifty.screen.Screen;

/**
 *
 * @author Adam T. Ryder http://1337atr.weebly.com
 */
public class NumberPickerButtonControl extends AbstractController {
    private NumberPickerControl picker;
    
    private boolean upButton;
    
    @Override
    public void bind(final Nifty niftyParam, final Screen screenParam, final Element newElement, final Parameters parameter) {
        super.bind(newElement);
        picker = getElement().getParent().getParent().getControl(NumberPickerControl.class);
        
        upButton = getElement().getId().endsWith("#upButton");
    }
    
    @Override
    public void init(final Parameters parameter) {
        
    }
    
    @Override
    public void onStartScreen() {
        
    }
    
    @Override
    public boolean inputEvent(final NiftyInputEvent inputEvent) {
        if (inputEvent == NiftyStandardInputEvent.Activate) {
            TooltipManager.clear();
            if (upButton) {
                if (picker.getValue() < picker.getMax()) {
                    picker.setValue(picker.getValue() + 1);
                }
            } else {
                if (picker.getValue() > picker.getMin()) {
                    picker.setValue(picker.getValue() - 1);
                }
            }
            return true;
        }
        
        return false;
    }
    
    public boolean onClick() {
        TooltipManager.clear();
        if (upButton) {
            if (picker.getValue() < picker.getMax()) {
                picker.setValue(picker.getValue() + 1);
            }
        } else {
            if (picker.getValue() > picker.getMin()) {
                picker.setValue(picker.getValue() - 1);
            }
        }
        
        return true;
    }
}

Now the above controls were created for use with NiftyGUI 1.3.3, but recently fudged a bit to work with NiftyGUI 1.4.1 so if you’re using NiftyGUI 1.3.3 there are some very minor differences so you’ll want to do a search on creating custom controls with NiftyGUI for additional information, also I would encourage you to download the NiftyGUI source code and take a look at some of the control definitions and controller classes for some of the default controls to get a better idea of how you want to create yours.

I don’t know exactly how you want to display your windows, but my thought was that you might have an XML layout for a window that’s basically a panel that includes your custom control which displays all the necessary information for the units in your game. When you instantiate your window you might do something like:

MyCustomControl unitDisplay = MyNewWindowElement.getControl(MyCustomControl.class);

unitDisplay.populateInfo(instanceOfMyUnit);

Then your populateInfo() method in the custom controller class would look at the information associated with the unit you pass in and populate whatever fields need to be populated with that information.

I haven’t actually tried any of this yet, but just from reading your reply this looks to me like exactly the thing I was looking for!
Thank you so much for your help! :smiley:

When I get this working I’ll post my solution in case anybody else is having a similar problem.

One more quick item to note. When you create custom controls and styles you need to declare them in a style and control file and then, in your Java code, tell Nifty to load your custom controls and styles when you load Nifty:

nifty.loadStyleFile("nifty-default-styles.xml");
nifty.loadControlFile("nifty-default-controls.xml");

nifty.loadStyleFile("Interface/Styles/CustomStyles.xml");
nifty.loadControlFile("Interface/Controls/CustomControls.xml");

the definition files look something like this:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-controls>
  <useControls filename="Interface/Controls/customControl1.xml" />
  <useControls filename="Interface/Controls/customControl2.xml" />
</nifty-controls>

Styles:

<?xml version="1.0" encoding="UTF-8"?>
<nifty-styles>
  <useStyles filename="Interface/Styles/customStyle1.xml" />
  <useStyles filename="Interface/Styles/customStyle2.xml" />
</nifty-styles>

You’ve probably already worked with custom styles, declaring custom controls isn’t really any different. The main thing is that you want to load your custom styles/controls after you load the default styles/controls, this way you can override the default controls and styles if you want by creating a control or style with the same name as a default control/style.

P.S. That’s how I styled scrollbars. If I recall correctly you couldn’t assign a style for a scrollbar when using a scrollpanel so I created a custom style for scrollbars with the same name as the style used for the default nifty scrollbar which just ends up replacing the default style in Nifty with the new one when you load it. I don’t even use Nifty scrollpanels anymore though having created my own scrollpanel control :slight_smile:

Yup, I’m aware of that, but thanks for pointing it out nontheless.
I usually add those definitions at the top of my main XML and then just load the one XML. This lets me modify my custom styles without writing code (I’m all about removing everything from my code that doesn’t have to be in there :smile:). It’s very likely that I’ll do that same with custom controls.
Thanks again for the help :smiley:

1 Like

Right then, for posteriority’s sake:

Tryders suggestion to use custom-controls for this worked!
For my case I solved them problem as follows:
I defined a custom-control like so:

<controlDefinition name="unitReport"
                       controller="graphics.ReportWindowControl"
                       style="nifty-panel"
                       childLayout="vertical"
                       width="40%" height="80%"
                       x="30%" y="10%"
                       visibleToMouse="true">
        <interact onClick="dragStart()" onClickMouseMove="drag()" onRelease="dragStop()"/>
        <panel id="#containerPanel" focusable="false">
            <!-- the contents of the window are defined within this container panel -->
            <panel id="#titlePanel" childLayout="center" height="10%">
                <text id="#text" text="Unit Report" font="Interface/Fonts/Default.fnt" width="100%" height="50%"/>
            </panel>
            <panel id="#dataPanel" style="nifty-panel-no-shadow" childLayout="center" height="90%">
                <text id="#text" text="Data" font="Interface/Fonts/Default.fnt" width="100%" height="50%"/>
            </panel>
        </panel>
    </controlDefinition>

Note that everything inside the panel with id “#containerPanel” is what will be displayed in the window. Also, and this confused me a bit, the childLayout of the control-definition defines the actual childLayout of the contained elements, setting the childLayout of the containerPanel had no effect whatsoever.
The control I’m using (graphics.ReportWindowControl) is a copy-pasted, cut down version of the standard DraggableControl class used for standard Nifty Draggable controls (I removed everything related to Droppables since that code wasn’t required and taking up computation time).
The XML containing this control-definition is referenced in my main XML that contains all my screens.

In my source-code I then instantiate and display a report window by doing the following:

new ControlBuilder("unitReport").build(nifty, gameScreen, reportLayer);
reportLayer.layoutElements();

gameScreen and reportLayer are the screen and layer I want the window displayed on.

1 Like