I solved it with a ScrollPanel instead of a ListBox in the nifty chat control, only requiring a few modifications.
Here’s my solution for anyone who’s interested:
The modified control definition:
<controlDefinition name="chat-control" style="chat-control" controller="com.limewoodGames.holicity.client.gui.controllers.NiftyChatControl" lines="10" sendLabel="Send" chatLineIconWidth="25px" chatLineIconHeight="25px" chatLineHeight="27px">
<panel style="#mainPanel">
<panel style="#chatPanel">
<panel style="#chatArea">
<control id="#chatBox" name="scrollPanel" style="nifty-listbox" vertical="true" horizontal="false" autoScroll="bottom" width="100%" height="100%">
</control>
</panel>
<panel style="#playerArea">
<control id="#playerList" name="listBox" vertical="on" horizontal="off" selection="Disabled" displayItems="$lines" viewConverterClass="de.lessvoid.nifty.controls.chatcontrol.ChatBoxViewConverter">
<control name="nifty-chat-line" chatLineIconWidth="$chatLineIconWidth" chatLineIconHeight="$chatLineIconHeight" chatLineHeight="$chatLineHeight" />
</control>
</panel>
</panel>
<panel style="#spacer"/>
<panel style="#chatTextArea">
<control id="#chat-text-input" name="textfield" />
<control id="#chat-text-button" name="button" width="" label="$sendLabel">
<interact onClick="sendText()" />
</control>
</panel>
</panel>
</controlDefinition>
The modified controller:
[...]
private ScrollPanel scrollPanel;
private List<ChatEntryModelClass> chatLines = new ArrayList<ChatEntryModelClass>();
/**
* Default constructor.
*/
public NiftyChatControl() {
}
/**
* {@inheritDoc}
*/
@Override
public final void bind(final Nifty niftyParam, final Screen screenParam, final Element newElement, final Properties properties, final Attributes controlDefinitionAttributes) {
super.bind(newElement);
LOGGER.fine("binding chat control");
nifty = niftyParam;
PanelBuilder panel = new PanelBuilder() {{
childLayout(ChildLayoutType.Vertical);
width(percentage(100));
x("0px");
y("0px");
align(Align.Left);
valign(VAlign.Bottom);
padding("2px");
paddingRight("15px");
}};
scrollPanel = getScrollPanel(CHAT_BOX);
panel.build(niftyParam, nifty.getCurrentScreen(), scrollPanel.getElement().findElementByName("#nifty-scrollpanel-child-root"));
// this buffer is needed because in some cases the entry is added to either list before the emelent is bound.
final ListBox<ChatEntryModelClass> playerList = getListBox(PLAYER_LIST);
while (!playerBuffer.isEmpty()) {
ChatEntryModelClass player = playerBuffer.remove(0);
LOGGER.log(Level.FINE, "adding player {0}", (playerList.itemCount() + 1));
playerList.addItem(player);
playerList.sortAllItems(playerComparator);
playerList.showItem(player);
}
while (!linesBuffer.isEmpty()) {
ChatEntryModelClass line = linesBuffer.remove(0);
receivedChatLine(line.getLabel(), line.getIcon(), line.getStyle());
}
}
[...]
/**
* {@inheritDoc}
*/
@Override
public void receivedChatLine(final String text, NiftyImage icon, String style) {
if (linesBuffer.isEmpty()) {
try {
final Element chatBox = scrollPanel.getElement();
LOGGER.log(Level.FINE, "adding message {0}", (chatBox.getElements().size() + 1));
PanelBuilder pb = new PanelBuilder() {{
width(percentage(100));
childLayout(ChildLayoutType.Horizontal);
paddingTop("2px");
image(new ImageBuilder() {{
width("25px");
height("25px");
filename("Interface/player_icon.png");
}});
text(new TextBuilder() {{
text(text);
font("Interface/Fonts/freesans-12.fnt");
textHAlign(Align.Left);
wrap(true);
width(percentage(100));
}});
}};
Element contents = chatBox.findElementByName("#nifty-scrollpanel-child-root").getElements().get(0);
Element panel = pb.build(nifty, nifty.getCurrentScreen(), contents);
if(icon != null) {
panel.getElements().get(0).getRenderer(ImageRenderer.class).setImage(icon);
}
// Autoscroll to bottom
scrollPanel.getElement().layoutElements();
scrollPanel.setUp(0, 5, 0, 50, AutoScroll.OFF);
scrollPanel.setVerticalPos(contents.getHeight());
// Add line to lines list
chatLines.add(new ChatEntryModelClass(text, icon, style));
} catch (NullPointerException npe) {
linesBuffer.add(new ChatEntryModelClass(text, icon, style));
}
} else {
linesBuffer.add(new ChatEntryModelClass(text, icon, style));
}
}
[...]
/**
* {@inheritDoc}
*/
@Override
public List<ChatEntryModelClass> getLines() {
return chatLines;
}
[...]
@SuppressWarnings("unchecked")
private ListBox<ChatEntryModelClass> getListBox(final String name) {
return (ListBox<ChatEntryModelClass>) getElement().findNiftyControl(name, ListBox.class);
}
private ScrollPanel getScrollPanel(final String name) {
return (ScrollPanel) getElement().findNiftyControl(name, ScrollPanel.class);
}
[...]
It’s not the most flexible solution, but it works for now for my purposes and perhaps it might be of help to someone else too.