[Solved]BToggleButtonScrollingList!!

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!)

I'll look at this and add it to SVN today.