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 < 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. 