Accessing Custom Attributes in Nifty

I know you can create custom element attributes, but is there any way to access them from Java code. The Nifty stuff itself is done in XML, not with JavaBuilder classes.

All I’m trying to do is create a textfield which is centered, and I can get the text itself centered through modifying the style, but the cursor seems like it can only be moved in XML through padding. So the way I figure is that I’d need to recalculate the padding with each character added/subtracted, but I don’t know of any way to access this attribute from Java. I filter it through the style with the $ notation, and can change the value in the XML itself, but is there any way to access and change this custom attribute from Java?

This is the only change to the textfield style regarding the cursor.
[java]<style id=“nifty-textfield#cursor-panel”>
<attributes childLayout=“absolute” padding="$cursorPad"/>
</style>[/java]

This is the control definition (with only a single change, the addition of cursorPad).
[java]<?xml version=“1.0” encoding=“UTF-8”?>
<nifty-controls>
<controlDefinition style=“nifty-textfield” name=“textfield” controller=“de.lessvoid.nifty.controls.textfield.TextFieldControl” inputMapping=“de.lessvoid.nifty.controls.textfield.TextFieldInputMapping” passwordChar="$passwordChar" maxLength="$maxLength" cursorPad="$cursorPad">
<panel style="#panel" focusable=“true”>
<interact onClick=“onClick()” onClickMouseMove=“onClickMouseMove()” />
<panel id="#field" style="#field" visibleToMouse=“true”>
<text id="#text" style="#text" text="$text" />
</panel>
<panel id="#cursor-panel" style="#cursor-panel">
<image id="#cursor" style="#cursor"/>
</panel>
</panel>
</controlDefinition>
</nifty-controls>[/java]

And this is the XML using it.
[java]<panel id=“StockEnterPanel” width=“60%” height=“10%” x=“20%” y=“10%” childLayout=“vertical”>
<control id=“StockValue” name=“textfield” maxLength=“3” cursorPad=“0px, 45%”/>
</panel>[/java]

The closest I can figure is to change it, then reload the XML file (if I set cursorPad="${CALL.cursorPad()}" instead of just setting it), however, then it doesn’t switch particularly cleanly (cause it’ll flash every time, due to the whole thing being reloaded).

I’m pretty sure this could be done by making a custom control from scratch and such, but to be frank, I lack the skill and patience for that, so I was hoping there could be an alternative solution, or any suggestions at all.

Thanks for any input.

The only thing I can suggest is to look through the code for the existing control and seeing if there is a way to do what you need. If not you might need to create your own variant.

Is there a way to access the current control code (the java controllers) for Nifty? I can find outdated versions on the internet, I’m just not sure where to find the most current java files.

https://github.com/void256/nifty-gui should be current I believe.

Thanks :).

I think I may have found a solution, with the following piece of code:

[java]event.getTextFieldControl().getElement().getElements().get(1).setPaddingLeft(new SizeValue(“20px”));[/java]

Apparently, you can access all the subelements of the controls in Java. And the subelements of those elements. However, the actual cursor-panel element is null (hence using get(1) rather than findElementByName(“elementName”)), but once you can get in there it appears you can make the changes. The SizeValue value can change, of course.

Now I just have to find a way to access the font, so I can calculate things correctly, I think.

Hang on, which version of Nifty is actually being used by JME right now? I was trying to copy the TextFieldControl code and adapt it a bit, because I want to access the current cursor position, but when I take that code, it says none of these exist:

[java]import de.lessvoid.nifty.controls.textfield.filter.delete.TextFieldDeleteFilter;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptDigits;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptFloat;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptLetters;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptLowerCase;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptNegativeDigits;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptRegex;
import de.lessvoid.nifty.controls.textfield.filter.input.FilterAcceptUpperCase;
import de.lessvoid.nifty.controls.textfield.filter.input.TextFieldInputCharFilter;
import de.lessvoid.nifty.controls.textfield.filter.input.TextFieldInputCharSequenceFilter;
import de.lessvoid.nifty.controls.textfield.filter.input.TextFieldInputFilter;
import de.lessvoid.nifty.controls.textfield.format.FormatPassword;
import de.lessvoid.nifty.controls.textfield.format.TextFieldDisplayFormat;[/java]

And when I opened the jar file, there were no folders for anything like those, either. Is JME using an older version of Nifty now? Is there any way to update to the newest stuff, or do things like that wait until the next major upgrade (like moving up to 1.4 or something)?

Is there some other way to access the cursor position? I mean, I suppose I could copy over all the files and use those, but if JME is going to be updated (or already is updated, and I somehow don’t have that update), that would kind of be a waste of time…

jme is on 1.3 I believe. I don’t know if we are planning to go to 1.4 for release?

If you just remove the nifty library and add your own one you can swap in your own version of nifty reasonably easily - so long as it’s still compatible. Or it might be easier just to use the latest from the 1.3 branch to base yourself off.

I ended up getting the version of the Nifty Controls with the source from a link on the Nifty site. So right now, I’m looking through the TextFieldControl class, trying to understand it enough to adapt it into a Centered Text Field. Alternatively, I could use the getCursorPosition method that I added, which just calls the TextFieldLogic’s method, but then it wouldn’t be quite as easy to use, I’d think.

Hopefully I can figure out how all these things work; it’s incredibly impressive what’s been done so far, I can only imagine what it must’ve been like writing all this in the first place.

Nightly jME uses Nifty version 1.3.3

Well, as far as I’m aware, I’ve gotten it to work correctly, for my purposes, at least. However, the version I have only supports numbers, and only really supports those that have fonts with even xadvance values… and it messes up if you go outside the bounds of the box (majorly). But even then, it’s a start if anyone ever had a use for it.

So basically, it’s far from perfect, but if anyone is ever looking for the beginnings of a full centered textfield, here’s what I have:

centered-textfield.xml - Control Definition:
[java]<?xml version=“1.0” encoding=“UTF-8”?>
<nifty-controls>
<controlDefinition style=“centered-textfield” name=“centeredtextfield” controller=“AdaptedControls.CenteredTextFieldControl” inputMapping=“de.lessvoid.nifty.controls.textfield.TextFieldInputMapping” passwordChar="$passwordChar" maxLength="$maxLength" font="$font">
<panel style="#panel" focusable=“true”>
<interact onClick=“onClick()” onClickMouseMove=“onClickMouseMove()” />
<panel id="#field" style="#field" visibleToMouse=“true”>
<text id="#text" style="#text" text="$text" />
</panel>
<panel id="#cursor-panel" style="#cursor-panel">
<image id="#cursor" style="#cursor"/>
</panel>
</panel>
</controlDefinition>
</nifty-controls>[/java]

centered-textfield - Style Definition
[java]<?xml version=“1.0” encoding=“UTF-8”?>
<nifty-styles xmlns=“http://nifty-gui.sourceforge.net/nifty-styles-1.3.xsd” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=“http://nifty-gui.sourceforge.net/nifty-styles-1.3.xsd http://nifty-gui.sourceforge.net/nifty-styles-1.3.xsd”>
<registerMouseCursor id=“textFieldCursor” filename=“showdown-style/centered-textfield/mouse-cursor-textfield.png” hotspotX=“3” hotspotY=“12”/>

<!-- the background of the textfield -->
<style id=“centered-textfield#panel”>
<attributes childLayout=“overlay” height=“100%” />
</style>

<!-- the actual input field -->
<style id=“centered-textfield#field”>
<attributes childLayout=“center” visibleToMouse=“true” childClip=“true” backgroundColor="#666f" padding=“0px,2px”/>
<effect>
<onActive name=“border” color="#222f" post=“true” inset=“1px” />
<onFocus name=“colorBar” color="#800f" post=“true” inset=“1px” />
<onHover name=“changeMouseCursor” id=“textFieldCursor” />
<onHover name=“border” color="#822f" post=“true” />
<onEnabled name=“renderQuad” startColor="#2228" endColor="#2220" post=“true” length=“150” />
<onDisabled name=“renderQuad” startColor="#2220" endColor="#2228" post=“true” length=“150” />
</effect>
</style>

<!-- the text in the input field -->
<style id=“centered-textfield#text” >
<attributes color="#000f" font="$font" selectionColor="#f00f" visibleToMouse=“false” align=“center” valign=“center” textHAlign=“center” />
<effect>
<onFocus name=“textColor” post=“false” color="#cccf" />
</effect>
</style>

<!-- the cursor is rendered in a separate layer on top of the input field and this is the parent panel of this -->
<style id=“centered-textfield#cursor-panel”>
<attributes childLayout=“absolute” padding=“0px,2px”/>
</style>

<!-- the actual cursor -->
<style id=“centered-textfield#cursor”>
<attributes filename=“showdown-style/centered-textfield/cursor-empty.png” height=“100%” />
<effect>
<onCustom name=“imageOverlayPulsate” period=“250” timeType=“infinite” pulsateType=“rectangle” filename=“showdown-style/centered-textfield/cursor.png” post=“true”/>
</effect>
</style>

</nifty-styles>
[/java]

CenteredTextField.java - Interface (to follow what was done with the existing controls)
[java]package AdaptedControls;

import de.lessvoid.nifty.controls.TextField;

public interface CenteredTextField extends TextField {

/**

  • Get the current TextField text.
  • @return text
    */
    String getText();

/**

  • Set the Text of the TextField.
  • @param text new text
    */
    void setText(String text);

/**

  • Change the max. input length to a new length.
  • @param maxLength max length
    */
    void setMaxLength(int maxLength);

/**

  • Get the cursorposition
  • @return cursor position
    */
    int getCursorPosition();

/**

  • Set the cursorposition to the given index.
  • @param position new cursor position
    */
    void setCursorPosition(int position);

/**

  • Enable a password character that is displayed instead of the actual text.
  • @param passwordChar charcter to use, like ‘*’
    */
    void enablePasswordChar(final char passwordChar);

/**

  • Disable the password character which displays the text again,
    */
    void disablePasswordChar();

/**

  • Checks if a password character is currently enabled.
  • @return true if password character is enabled and false if not.
    */
    boolean isPasswordCharEnabled();
    }
    [/java]

CenteredTextFieldControl.java (the deprecation part is also to follow what Nifty currently has for the Standard Controls)
[java]package AdaptedControls;

import de.lessvoid.nifty.ClipboardAWT;
import java.util.Properties;

import de.lessvoid.nifty.Nifty;
import de.lessvoid.nifty.controls.AbstractController;
import de.lessvoid.nifty.controls.FocusHandler;
import de.lessvoid.nifty.controls.textfield.TextFieldLogic;
import de.lessvoid.nifty.controls.textfield.TextFieldView;
import de.lessvoid.nifty.effects.EffectEventId;
import de.lessvoid.nifty.elements.Element;
import de.lessvoid.nifty.elements.render.TextRenderer;
import de.lessvoid.nifty.elements.tools.FontHelper;
import de.lessvoid.nifty.input.NiftyInputEvent;
import de.lessvoid.nifty.screen.Screen;
import de.lessvoid.nifty.tools.SizeValue;
import de.lessvoid.xml.xpp3.Attributes;

/**

  • A TextFieldControl.
  • @author void
  • @author Inferno
  • @deprecated Please use {@link AdaptedControls.CenteredTextField} when accessing NiftyControls.
    */
    @Deprecated
    public class CenteredTextFieldControl extends AbstractController implements CenteredTextField, TextFieldView {
    private static final int CURSOR_Y = 0;
    private Nifty nifty;
    private Screen screen;
    private Element textElement;
    private Element fieldElement;
    private Element cursorElement;
    private TextFieldLogic textField;
    private int firstVisibleCharacterIndex;
    private int lastVisibleCharacterIndex;
    private int fieldWidth;
    private int fromClickCursorPos;
    private int toClickCursorPos;
    private FocusHandler focusHandler;
    private Character passwordChar;

public void bind(
final Nifty niftyParam,
final Screen screenParam,
final Element newElement,
final Properties properties,
final Attributes controlDefinitionAttributes) {
super.bind(newElement);

this.nifty = niftyParam;
this.screen = screenParam;
this.fromClickCursorPos = -1;
this.toClickCursorPos = -1;

this.textField = new TextFieldLogic(properties.getProperty("text", ""), new ClipboardAWT(), this);
this.textField.toFirstPosition();

this.textElement = getElement().findElementByName("#text");
this.fieldElement = getElement().findElementByName("#field");
this.cursorElement = getElement().findElementByName("#cursor");

passwordChar = null;
if (properties.containsKey("passwordChar")) {
  passwordChar = properties.get("passwordChar").toString().charAt(0);
}
if (properties.containsKey("maxLength")) {
  setMaxLength(new Integer(properties.getProperty("maxLength")));
}

}

@Override
public void init(final Properties parameter, final Attributes controlDefinitionAttributes) {
this.focusHandler = screen.getFocusHandler();

this.textField.initWithText(textElement.getRenderer(TextRenderer.class).getOriginalText());
this.fieldWidth = this.fieldElement.getWidth() - this.cursorElement.getWidth();

TextRenderer textRenderer = textElement.getRenderer(TextRenderer.class);
this.firstVisibleCharacterIndex = 0;
this.lastVisibleCharacterIndex = FontHelper.getVisibleCharactersFromStart(textRenderer.getFont(), this.textField.getText(), fieldWidth, 1.0f);

updateCursor();
super.init(parameter, controlDefinitionAttributes);

}

public void onStartScreen() {
}

@Override
public void layoutCallback() {
this.fieldWidth = this.fieldElement.getWidth() - this.cursorElement.getWidth();
}

public void onClick(final int mouseX, final int mouseY) {
String visibleString = textField.getText().substring(firstVisibleCharacterIndex, lastVisibleCharacterIndex);
int indexFromPixel = getCursorPosFromMouse(mouseX, visibleString);
if (indexFromPixel != -1) {
fromClickCursorPos = firstVisibleCharacterIndex + indexFromPixel;
}
textField.resetSelection();
textField.setCursorPosition(fromClickCursorPos);
updateCursor();
}

public void onClickMouseMove(final int mouseX, final int mouseY) {
String visibleString = textField.getText().substring(firstVisibleCharacterIndex, lastVisibleCharacterIndex);
int indexFromPixel = getCursorPosFromMouse(mouseX, visibleString);
if (indexFromPixel != -1) {
toClickCursorPos = firstVisibleCharacterIndex + indexFromPixel;
}

textField.setCursorPosition(fromClickCursorPos);
textField.startSelecting();
textField.setCursorPosition(toClickCursorPos);
textField.endSelecting();
updateCursor();

}

private int getCursorPosFromMouse(final int mouseX, final String visibleString) {
TextRenderer textRenderer = textElement.getRenderer(TextRenderer.class);

int textLength = textField.getText().length();
int characterAdvance = textRenderer.getFont().getCharacterAdvance('0', '1', 1.0f);
int cursorPixelPos = (fieldElement.getWidth() / 2) - ((characterAdvance / 2) * textLength) - 2;


return FontHelper.getCharacterIndexFromPixelPosition(textRenderer.getFont(), visibleString,
    (mouseX - (fieldElement.getX() + cursorPixelPos)), 1.0f);

}

public int getCursorPosition()
{
return textField.getCursorPosition();
}

public boolean inputEvent(final NiftyInputEvent inputEvent) {
if (inputEvent == NiftyInputEvent.MoveCursorLeft) {
textField.cursorLeft();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.MoveCursorRight) {
textField.cursorRight();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.Delete) {
textField.delete();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.Backspace) {
textField.backspace();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.MoveCursorToLastPosition) {
textField.toLastPosition();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.MoveCursorToFirstPosition) {
textField.toFirstPosition();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.SelectionStart) {
textField.startSelecting();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.SelectionEnd) {
textField.endSelecting();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.Cut) {
textField.cut(passwordChar);
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.Copy) {
textField.copy(passwordChar);
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.Paste) {
textField.put();
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.SelectAll) {
if (textField.getText().length() > 0) {
textField.setCursorPosition(0);
textField.startSelecting();
textField.setCursorPosition(textField.getText().length());
textField.endSelecting();
updateCursor();
return true;
}
} else if (inputEvent == NiftyInputEvent.Character) {
textField.insert(inputEvent.getCharacter());
updateCursor();
return true;
} else if (inputEvent == NiftyInputEvent.NextInputElement) {
if (focusHandler != null) {
focusHandler.getNext(fieldElement).setFocus();
updateCursor();
return true;
}
} else if (inputEvent == NiftyInputEvent.PrevInputElement) {
textField.endSelecting();
if (focusHandler != null) {
focusHandler.getPrev(fieldElement).setFocus();
updateCursor();
return true;
}
}

updateCursor();
return false;

}

private void updateCursor() {
TextRenderer textRenderer = textElement.getRenderer(TextRenderer.class);
String text = textField.getText();
checkBounds(text, textRenderer);
calcLastVisibleIndex(textRenderer);

// update text
if (isPassword(passwordChar)) {
  int numChar = text.length();
  char[] chars = new char[numChar];
  for (int i = 0; i &lt; numChar; ++i) {
    chars[i] = passwordChar;
  }
  text = new String(chars);
}
textRenderer.setText(text);
textRenderer.setSelection(textField.getSelectionStart(), textField.getSelectionEnd());

// calc cursor position
int cursorPos = textField.getCursorPosition();

// outside, move window to fit cursorPos inside [first,last]
calcFirstVisibleIndex(cursorPos);
calcLastVisibleIndex(textRenderer);

String substring2 = text.substring(0, firstVisibleCharacterIndex);
int d = textRenderer.getFont().getWidth(substring2);
textRenderer.setXoffsetHack(-d);

String substring = text.substring(0, cursorPos);
int textWidth = textRenderer.getFont().getWidth(substring);
int cursorPixelPos = textWidth - d;

int textLength = textField.getText().length();
int characterAdvance = textRenderer.getFont().getCharacterAdvance('0', '1', 1.0f);

if (textLength == 0)
{
    cursorPixelPos = (fieldElement.getWidth() / 2) - (characterAdvance / 2) - 2;
}
else
{
    cursorPixelPos = (fieldElement.getWidth() / 2) - ((characterAdvance / 2) * textLength) + (characterAdvance * cursorPos) - 2;
}
// Starts off center - (xadvance / 2)
// Goes up by (xadvance / 2) each time after first

cursorElement.setConstraintX(new SizeValue(cursorPixelPos + "px"));
cursorElement.setConstraintY(new SizeValue((getElement().getHeight() - cursorElement.getHeight()) / 2 + CURSOR_Y + "px"));
cursorElement.startEffect(EffectEventId.onActive, null);
if (screen != null) {
  screen.layoutLayers();
}

}

private boolean isPassword(final Character currentPasswordChar) {
return currentPasswordChar != null;
}

private void calcFirstVisibleIndex(final int cursorPos) {
if (cursorPos > lastVisibleCharacterIndex) {
int cursorPosDelta = cursorPos - lastVisibleCharacterIndex;
firstVisibleCharacterIndex += cursorPosDelta;
} else if (cursorPos < firstVisibleCharacterIndex) {
int cursorPosDelta = firstVisibleCharacterIndex - cursorPos;
firstVisibleCharacterIndex -= cursorPosDelta;
}
}

private void checkBounds(final String text, final TextRenderer textRenderer) {
int textLen = text.length();
if (firstVisibleCharacterIndex > textLen) {
// re position so that we show at much possible text
lastVisibleCharacterIndex = textLen;
firstVisibleCharacterIndex = FontHelper.getVisibleCharactersFromEnd(textRenderer.getFont(), text, fieldWidth, 1.0f);
}
}

private void calcLastVisibleIndex(final TextRenderer textRenderer) {
String currentText = this.textField.getText();
if (firstVisibleCharacterIndex < currentText.length()) {
String textToCheck = currentText.substring(firstVisibleCharacterIndex);
int lengthFitting = FontHelper.getVisibleCharactersFromStart(textRenderer.getFont(), textToCheck, fieldWidth, 1.0f);
lastVisibleCharacterIndex = lengthFitting + firstVisibleCharacterIndex;
} else {
lastVisibleCharacterIndex = firstVisibleCharacterIndex;
}
}

@Override
public void onFocus(final boolean getFocus) {
if (cursorElement != null) {
super.onFocus(getFocus);
if (getFocus) {
cursorElement.startEffect(EffectEventId.onCustom);
} else {
cursorElement.stopEffect(EffectEventId.onCustom);
}
updateCursor();
}
}

@Override
public String getText() {
return textField.getText();
}

@Override
public void setText(final String newText) {
textField.initWithText(nifty.specialValuesReplace(newText));
updateCursor();
}

@Override
public void setMaxLength(final int maxLength) {
textField.setMaxLength(maxLength);
updateCursor();
}

@Override
public void setCursorPosition(final int position) {
textField.setCursorPosition(position);
updateCursor();
}

@Override
public void textChangeEvent(final String newText) {
nifty.publishEvent(getElement().getId(), new CenteredTextFieldChangedEvent(this, newText));
}

@Override
public void enablePasswordChar(final char passwordChar) {
this.passwordChar = passwordChar;
updateCursor();
}

@Override
public void disablePasswordChar() {
this.passwordChar = null;
updateCursor();
}

@Override
public boolean isPasswordCharEnabled() {
return passwordChar != null;
}
}
[/java]

CenteredTextFieldChangeEvent.java
[java]package AdaptedControls;

import de.lessvoid.nifty.NiftyEvent;

/**

  • Nifty generates this event when the current text of an TextField changes.
  • @author void
    */
    public class CenteredTextFieldChangedEvent implements NiftyEvent<Void> {
    private CenteredTextField textField;
    private String currentText;

public CenteredTextFieldChangedEvent(final CenteredTextField textFieldControl, final String currentText) {
this.textField = textFieldControl;
this.currentText = currentText;
}

public CenteredTextField getTextFieldControl() {
return textField;
}

public String getText() {
return currentText;
}
}[/java]

Like I said, it’s far from perfect, but it worked for me, and with a bit of tweaking, I’m sure it could work for others if they wanted it.

And with that, I’d say this issue has it’s solution (not sure if that means something or not).

Thanks everyone for your help. :slight_smile:

1 Like