I’ve just done a ScrollPanel (basically copy/pasting ListBox’s code and removing all the unnecessary… more or less xD).
By the moment just added the possibility to set the scrollbar position and if this must be hidden if there is no enough content to it to be useful.
Well, the code is:
import com.google.common.base.Objects;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.BorderLayout;
import com.simsilica.lemur.component.QuadBackgroundComponent;
import com.simsilica.lemur.core.AbstractGuiControlListener;
import com.simsilica.lemur.core.GuiControl;
import com.simsilica.lemur.core.VersionedList;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.grid.GridModel;
import com.simsilica.lemur.list.CellRenderer;
import com.simsilica.lemur.list.DefaultCellRenderer;
import com.simsilica.lemur.style.*;
import java.util.List;
public class ScrollPanel<T> extends Panel {
public static final String ELEMENT_ID = "list";
public static final String CONTAINER_ID = "container";
public static final String ITEMS_ID = "items";
public static final String SLIDER_ID = "slider";
// public static final String SELECTOR_ID = "selector";
private BorderLayout layout;
private VersionedList<T> model;
private VersionedReference<List<T>> modelRef;
private CellRenderer<T> cellRenderer;
// private ScrollPanel.ClickListener clickListener = new ScrollPanel.ClickListener();
private CustomGridPanel grid;
private Slider slider;
private Node selectorArea;
// private Panel selector;
private Vector3f selectorAreaOrigin = new Vector3f();
private Vector3f selectorAreaSize = new Vector3f();
private RangedValueModel baseIndex; // upside down actually
private VersionedReference<Double> indexRef;
private int maxIndex;
private BorderLayout.Position sliderPosition = BorderLayout.Position.East;
private boolean sliderAlwaysVisible = false;
public ScrollPanel() {
this(true, new VersionedList<T>(), null,
new ElementId(ELEMENT_ID), null);
}
public ScrollPanel( VersionedList<T> model ) {
this(true, model, null,
new ElementId(ELEMENT_ID), null);
}
public ScrollPanel( VersionedList<T> model, CellRenderer<T> renderer, String style ) {
this(true, model, renderer, new ElementId(ELEMENT_ID), style);
}
public ScrollPanel( VersionedList<T> model, String style ) {
this(true, model, null, new ElementId(ELEMENT_ID), style);
}
public ScrollPanel( VersionedList<T> model, ElementId elementId, String style ) {
this(true, model, null, elementId, style);
}
public ScrollPanel( VersionedList<T> model, CellRenderer<T> renderer, ElementId elementId, String style ) {
this(true, model, renderer, elementId, style);
}
protected ScrollPanel( boolean applyStyles, VersionedList<T> model, CellRenderer<T> cellRenderer,
ElementId elementId, String style ) {
super(false, elementId.child(CONTAINER_ID), style);
if( cellRenderer == null ) {
// Create a default one
cellRenderer = new DefaultCellRenderer(elementId.child("item"), style);
}
this.cellRenderer = cellRenderer;
this.layout = new BorderLayout();
getControl(GuiControl.class).setLayout(layout);
grid = new CustomGridPanel(new ScrollPanel.GridModelDelegate(), elementId.child(ITEMS_ID), style);
grid.setVisibleColumns(1);
grid.getControl(GuiControl.class).addListener(new ScrollPanel.GridListener());
layout.addChild(grid, BorderLayout.Position.Center);
baseIndex = new DefaultRangedValueModel();
indexRef = baseIndex.createReference();
slider = new Slider(baseIndex, Axis.Y, elementId.child(SLIDER_ID), style);
if(sliderAlwaysVisible) {
layout.addChild(slider, sliderPosition);
}
if( applyStyles ) {
Styles styles = GuiGlobals.getInstance().getStyles();
styles.applyStyles(this, getElementId(), style);
}
// Need a spacer so that the 'selector' panel doesn't think
// it's being managed by this panel.
// Have to set this up after applying styles so that the default
// styles are properly initialized the first time.
selectorArea = new Node("selectorArea");
attachChild(selectorArea);
// selector = new Panel(elementId.child(SELECTOR_ID), style);
setModel(model);
resetModelRange();
}
@StyleDefaults(ELEMENT_ID)
public static void initializeDefaultStyles( Styles styles, Attributes attrs ) {
ElementId parent = new ElementId(ELEMENT_ID);
//QuadBackgroundComponent quad = new QuadBackgroundComponent(new ColorRGBA(0.5f, 0.5f, 0.5f, 1));
QuadBackgroundComponent quad = new QuadBackgroundComponent(new ColorRGBA(0.8f, 0.9f, 0.1f, 1));
quad.getMaterial().getMaterial().getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Exclusion);
// styles.getSelector(parent.child(SELECTOR_ID), null).set("background", quad, false);
}
@Override
public void updateLogicalState( float tpf ) {
super.updateLogicalState(tpf);
if( modelRef.update() ) {
resetModelRange();
}
boolean indexUpdate = indexRef.update();
if( indexUpdate ) {
int index = (int)(maxIndex - baseIndex.getValue());
grid.setRow(index);
}
}
protected void gridResized( Vector3f pos, Vector3f size ) {
if( pos.equals(selectorAreaOrigin) && size.equals(selectorAreaSize) ) {
return;
}
selectorAreaOrigin.set(pos);
selectorAreaSize.set(size);
}
public void setModel( VersionedList<T> model ) {
if( this.model == model && model != null ) {
return;
}
if( this.model != null ) {
// Clean up the old one
// detachItemListeners();
}
if( model == null ) {
// Easier to create a default one than to handle a null model
// everywhere
model = new VersionedList<T>();
}
this.model = model;
this.modelRef = model.createReference();
grid.setLocation(0,0);
grid.setModel(new ScrollPanel.GridModelDelegate()); // need a new one for a new version
resetModelRange();
baseIndex.setValue(maxIndex);
}
public void scrollToBottom() {
baseIndex.setValue(0);
}
public boolean isSliderAlwaysVisible() {
return sliderAlwaysVisible;
}
public void setSliderAlwaysVisible(boolean sliderAlwaysVisible) {
this.sliderAlwaysVisible = sliderAlwaysVisible;
}
public BorderLayout.Position getSliderPosition() {
return sliderPosition;
}
public void setSliderPosition(BorderLayout.Position sliderPosition) {
this.sliderPosition = sliderPosition;
}
public VersionedList<T> getModel() {
return model;
}
public Slider getSlider() {
return slider;
}
public GridPanel getGridPanel() {
return grid;
}
@StyleAttribute(value="visibleItems", lookupDefault=false)
public void setVisibleItems( int count ) {
grid.setVisibleRows(count);
resetModelRange();
}
public int getVisibleItems() {
return grid.getVisibleRows();
}
@StyleAttribute(value="cellRenderer", lookupDefault=false)
public void setCellRenderer( CellRenderer renderer ) {
if( Objects.equal(this.cellRenderer, renderer) ) {
return;
}
this.cellRenderer = renderer;
grid.refreshGrid(); // cheating
}
public CellRenderer getCellRenderer() {
return cellRenderer;
}
protected void resetModelRange() {
int count = model == null ? 0 : model.size();
int visible = grid.getVisibleRows();
maxIndex = Math.max(0, count - visible);
// Because the slider is upside down, we have to
// do some math if we want our base not to move as
// items are added to the list after us
double val = baseIndex.getMaximum() - baseIndex.getValue();
baseIndex.setMinimum(0);
baseIndex.setMaximum(maxIndex);
baseIndex.setValue(maxIndex - val);
// Hide scrollbar
if(!sliderAlwaysVisible) {
if (count > visible) {
if (slider.getParent() == null) {
layout.addChild(slider, sliderPosition);
}
} else {
if (slider.getParent() != null) {
layout.removeChild(slider);
}
}
}
}
protected Panel getListCell( int row, int col, Panel existing ) {
T value = model.get(row);
Panel cell = cellRenderer.getView(value, false, existing);
if( cell != existing ) {
}
return cell;
}
@Override
public String toString() {
return getClass().getName() + "[elementId=" + getElementId() + "]";
}
private class GridListener extends AbstractGuiControlListener {
public void reshape( GuiControl source, Vector3f pos, Vector3f size ) {
gridResized(pos, size);
// If the grid was re-laid out then we probably need
// to refresh our selector
// refreshSelector();
}
}
protected class GridModelDelegate implements GridModel<Panel> {
@Override
public int getRowCount() {
if( model == null ) {
return 0;
}
return model.size();
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public Panel getCell( int row, int col, Panel existing ) {
return getListCell(row, col, existing);
}
@Override
public void setCell( int row, int col, Panel value ) {
throw new UnsupportedOperationException("ListModel is read only.");
}
@Override
public long getVersion() {
return model == null ? 0 : model.getVersion();
}
@Override
public GridModel<Panel> getObject() {
return this;
}
@Override
public VersionedReference<GridModel<Panel>> createReference() {
return new VersionedReference<GridModel<Panel>>(this);
}
}
}