yeah, was quite easy to make one, however… i can't make it to untoggle a button if another is pressed (in another words, have only one button "on" at all times!)
Edit: just managed to make it, if you guys want, here is the class
package States.HUD.GUIAdaptations;
import com.jme.renderer.Renderer;
import com.jmex.bui.BComponent;
import com.jmex.bui.BContainer;
import com.jmex.bui.BRootNode;
import com.jmex.bui.BScrollBar;
import com.jmex.bui.BToggleButton;
import com.jmex.bui.BWindow;
import com.jmex.bui.BoundedRangeModel;
import com.jmex.bui.enumeratedConstants.Orientation;
import com.jmex.bui.event.ChangeEvent;
import com.jmex.bui.event.ChangeListener;
import com.jmex.bui.event.MouseWheelListener;
import com.jmex.bui.layout.BorderLayout;
import com.jmex.bui.layout.GroupLayout;
import com.jmex.bui.layout.GroupLayout.Justification;
import com.jmex.bui.layout.GroupLayout.Policy;
import com.jmex.bui.util.Insets;
import com.jmex.bui.util.Rectangle;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
//Provides a scrollable, lazily instantiated component view of values
public final class GBList<V> extends BContainer {
//Instantiates an empty {@link BScrollingList}.
public GBList() {
this(null);
}
//Instantiates a {@link BScrollingList} with an initial value collection.
public GBList(Collection<V> values) {
super(new BorderLayout(0, 0));
_values = new ArrayList<Entry<V>>();
if (values != null) {
for (V value : values) {
_values.add(new Entry<V>(value, value.toString()));
}
}
_lastBottom = 0;
// we'll set up our model in layout()
_model = new BoundedRangeModel(0, 0, 1, 1);
// create our viewport and scrollbar
add(_vport = new BViewport(), BorderLayout.CENTER);
_model.addChangeListener(_vport);
add(_vbar = new BScrollBar(Orientation.VERTICAL, _model),
BorderLayout.EAST);
}
// Appends a value to our list, possibly scrolling our view to display it.
public void addValue(V value,
boolean snapToBottom) {
addValue(_values.size(), value, value.toString(), snapToBottom);
}
//Inserts a value into our list at the specified position.
public void addValue(int index,
V value) {
addValue(index, value, value.toString(), false);
}
public void addValue(V value, String t,
boolean snapToBottom) {
addValue(_values.size(), value, t, snapToBottom);
}
//Inserts a value into our list at the specified position.
public void addValue(int index, String t,
V value) {
addValue(index, value, t, false);
}
//Clears all the current values and any related components.
public void removeValues() {
_values.clear();
_model.setValue(0);
_vport.removeAll();
_vport.invalidate();
}
/**
* Removes values from the top of the list.
*/
public void removeValuesFromTop(int num) {
num = Math.min(num, _values.size());
for (int ii = 0; ii < num; ii++) {
Entry<V> value = _values.remove(0);
_vport.remove(value.component);
}
_vport.invalidate();
}
private GToggleButton createComponent(Object obj, String name) {
GToggleButton t = new GToggleButton(obj, name);
t.setPreferredSize(_width - 10, buttonHeight);
return t;
}
public V getSelectedObject() {
return lastSelec;
}
//Adds a value to the list and snaps to the bottom of the list if desired.
protected void addValue(int index,
V value, String t,
boolean snap) {
_values.add(index, new Entry<V>(value, t));
if (snap) {
_vport.invalidateAndSnap();
} else {
_vport.invalidate();
}
}
//Does all the heavy lifting for the {@link BScrollingList}.
private class BViewport extends BContainer
implements ChangeListener {
public BViewport() {
super(GroupLayout.makeVert(
Policy.NONE,
Justification.TOP,
Policy.STRETCH));
}
//Returns a reference to the vertical scroll bar.
public BScrollBar getVerticalScrollBar() {
return _vbar;
}
/**
* Recomputes our layout and snaps to the bottom if we were at the bottom previously.
*/
public void invalidateAndSnap() {
_snap =
_model.getValue() + _model.getExtent() >= _model.getMaximum();
invalidate();
}
@Override
// documentation inherited
protected void wasAdded() {
super.wasAdded();
addListener(_wheelListener = _model.createWheelListener());
}
@Override
// from BComponent
protected void wasRemoved() {
super.wasRemoved();
if (_wheelListener != null) {
removeListener(_wheelListener);
_wheelListener = null;
}
}
// from interface ChangeListener
public void stateChanged(ChangeEvent event) {
invalidate();
}
@Override
// from BComponent
public void invalidate() {
// if we're not attached, don't worry about it
BWindow window;
BRootNode root;
if (!_valid || (window = getWindow()) == null ||
(root = window.getRootNode()) == null) {
return;
}
_valid = false;
root.rootInvalidated(this);
}
@Override
// from BComponent
public void layout() {
Insets insets = getInsets();
int twidth = getWidth() - insets.getHorizontal();
int theight = getHeight() - insets.getVertical();
int gap = ((GroupLayout) getLayoutManager()).getGap();
// first make sure all of our entries have been measured and
// compute our total height and extent
int totheight = 0;
for (Entry<V> entry : _values) {
if (entry.height < 0) {
if (entry.component == null) {
entry.component = createComponent(entry.value, entry.text);
}
boolean remove = false;
if (!entry.component.isAdded()) {
add(entry.component);
remove = true;
}
entry.height =
entry.component.getPreferredSize(twidth, 0).height;
if (remove) {
remove(entry.component);
}
}
totheight += entry.height;
}
if (_values.size() > 1) {
totheight += (gap * _values.size() - 1);
}
int extent = Math.min(theight, totheight);
// if our most recent value was added with _snap then we scroll to
// the bottom on this layout and clear our snappy flag
int value = _snap ? (totheight - extent) : _model.getValue();
_snap = false;
// if our extent or total height have changed, update the model
// (because we're currently invalid, the resulting call to
// invalidate() will have no effect)
if (extent != _model.getExtent() ||
totheight != _model.getMaximum()) {
_model.setRange(0, value, extent, totheight);
}
// now back up from the last component until we reach the first one
// that's in view
_offset = _model.getValue();
int compIx = 0;
for (int ii = 0; ii < _values.size(); ii++) {
Entry<V> entry = _values.get(ii);
if (_offset < entry.height) {
compIx = ii;
break;
}
_offset -= (entry.height + gap);
// remove and clear out the components before our top component
if (entry.component != null) {
if (entry.component.isAdded()) {
remove(entry.component);
}
entry.component = null;
}
}
// compensate for the partially visible topmost component
extent += _offset;
// now add components until we use up our extent
int topIx = compIx;
while (compIx < _values.size() && extent > 0) {
Entry<V> entry = _values.get(compIx);
if (entry.component == null) {
entry.component = createComponent(entry.value, entry.text);
}
if (!entry.component.isAdded()) {
add(compIx - topIx, entry.component);
}
extent -= (entry.height + gap);
compIx++;
}
// lastly remove any components below the last visible component
while (compIx < _values.size()) {
Entry<V> entry = _values.get(compIx);
if (entry.component != null) {
if (entry.component.isAdded()) {
remove(entry.component);
}
entry.component = null;
}
compIx++;
}
// now have the layout manager layout our added components
super.layout();
}
@Override
// from BComponent
protected void renderComponent(Renderer renderer) {
Insets insets = getInsets();
GL11.glTranslatef(0, _offset, 0);
boolean scissored = intersectScissorBox(_srect,
getAbsoluteX() + insets.left,
getAbsoluteY() + insets.bottom,
_width - insets.getHorizontal(),
_height - insets.getVertical());
try {
// render our children
for (int ii = 0, ll = getComponentCount(); ii < ll; ii++) {
getComponent(ii).render(renderer);
}
} finally {
restoreScissorState(scissored, _srect);
GL11.glTranslatef(0, -_offset, 0);
}
}
@Override
// documentation inherited
public BComponent getHitComponent(int mx,
int my) {
// if we're not within our bounds, we needn't check our target
Insets insets = getInsets();
if ((mx < _x + insets.left) || (my < _y + insets.bottom) ||
(mx >= _x + _width - insets.right) ||
(my >= _y + _height - insets.top)) {
return null;
}
// translate the coordinate into our children's coordinates
mx -= _x;
my -= (_y + _offset);
BComponent hit;
for (int ii = 0, ll = getComponentCount(); ii < ll; ii++) {
BComponent child = getComponent(ii);
if ((hit = child.getHitComponent(mx, my)) != null) {
return hit;
}
}
return this;
}
private int _offset;
private boolean _snap;
private Rectangle _srect = new Rectangle();
}
/**
* Used to track the total height of our entries.
*/
protected class Entry<V> {
private GToggleButton component;
private V value;
private String text;
private int height = -1;
private Entry(V _value, String t) {
value = _value;
text = t;
}
}
private class GToggleButton<V> extends BToggleButton {
V what;
private GToggleButton(V obj) {
super((String) obj);
if (obj == lastSelec) {
this.setSelected(true);
lastSelected = this;
}
what = obj;
}
private GToggleButton(V obj, String name) {
super(name);
if (obj == lastSelec) {
this.setSelected(true);
lastSelected = this;
}
what = obj;
}
@Override
protected void fireAction(long when,
int modifiers) {
if (lastSelected != null) {
lastSelected.setSelected(false);
}
lastSelected = this;
setLastSelec(what);
super.fireAction(when, modifiers);
}
}
private void setLastSelec(Object what) {
lastSelec = (V) what;
}
private GToggleButton lastSelected = null;
private V lastSelec = null;
private int buttonHeight = 20;
private MouseWheelListener _wheelListener;
private BoundedRangeModel _model;
private List<Entry<V>> _values;
private BViewport _vport;
private BScrollBar _vbar;
private int _lastBottom;
private static final int EXTENT = 2;
}
To use it, you can use the method .addValue(Object, snap); and the button will have a Object.toString() text, or .addValue(Object, text, snap); and the button will have a text as text. I do not recomend to use the constructor GBList(Values); as it will ALWAYS only use the Object.toString for the button labbel.
you can't know the index of the selected button, i've only managed to return the object it is made of. (Ie: is the button created by .addValue(Object, snap); is selected the list will return Object when asked for .getSelected())
Edit: This class is NOT meant to have two buttons made of the same Object, or they will BOTH get selected when recreated, and only the last one will be diselected when you click on some other button! (Strings initiated by String mah = "mahString" that are .equal will be the same Object, only Strings initiated by String mah = new String("mahString") will be diferent objects even if their .equals is true!)