Scroll panel in lemur

For a scroll panel that just contains arbitrary stuff then you will need to manage a viewport, etc… I think someone has already done something like this in another thread.

Lemur doesn’t (yet) have this functionality. JME doesn’t provide an easy way to do this and viewports come with a bunch of caveats that make a general solution tricky.

i can´t put work
this is my code

package mygame;

import com.jme3.app.Application;
import com.jme3.app.state.BaseAppState;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.simsilica.lemur.ActionButton;
import com.simsilica.lemur.Axis;
import com.simsilica.lemur.CallMethodAction;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.FillMode;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.PasswordField;
import com.simsilica.lemur.TabbedPanel;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.component.SpringGridLayout;
import com.simsilica.lemur.style.ElementId;
import com.simsilica.lemur.RollupPanel;

/**
 *
 * @author Pedro Alves
 */
public class OptionsState  extends BaseAppState  {
    private Container loginPanel;
    private TextField nameField;
    private PasswordField passwordfield;
    

    public OptionsState() {
    
    }
    @Override
    protected void initialize(Application app) {
      loginPanel = new Container();
        loginPanel.addChild(new Label("Options", new ElementId("title")));
        
        Container props = loginPanel.addChild(new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.Last)));
        props.setBackground(null);    
        TabbedPanel tabs = new TabbedPanel();
Container panel1 = tabs.addTab("Game Options", new Container());
panel1.addChild(new Label("Game Options"));
panel1.addChild(new RollupPanel("fsdfdsf",new ElementId("ds"),"grass"));
Container panel2 = tabs.addTab("Graphic Options", new Container());
panel2.addChild(new Label("Grapicos options"));
Container panel3 = tabs.addTab(" Audio Options", new Container());
panel3.addChild(new Label("Audio"));
Container panel4 = tabs.addTab("Key Mapping ", new Container());
panel4.addChild(new Label("Key Mapping"));
 loginPanel.addChild(tabs);
      /*  props.addChild(new Label("Username:"));
        nameField = props.addChild(new TextField(System.getProperty("user.name")), 1);
        props.addChild(new Label("Password:"));
        passwordfield=props.addChild(new PasswordField(System.getProperty("Pass.word")), 1);
       Container buttons = loginPanel.addChild(new Container(new SpringGridLayout(Axis.X, Axis.Y)));
      */ // buttons.setBackground(null);
        //buttons.setLayout(new SpringGridLayout(Axis.X, Axis.Y));
       // buttons.addChild(new ActionButton(new CallMethodAction("Join", this, "join"))); 
       // buttons.addChild(new ActionButton(new CallMethodAction("Cancel", this, "cancel")));
        
        float scale = 1.5f * getState(MainMenuState.class).getStandardScale();
        loginPanel.setLocalScale(scale);
        
        Vector3f prefs = loginPanel.getPreferredSize().clone();
        prefs.x = Math.max(300, prefs.x);
        loginPanel.setPreferredSize(prefs.clone());
        
        // Now account for scaling
        prefs.multLocal(scale);
        
        int width = app.getCamera().getWidth();
        int height = app.getCamera().getHeight();
        
        loginPanel.setLocalTranslation(width * 0.5f - prefs.x * 0.5f, height * 0.5f + prefs.y * 0.5f, 10);       
    }

    @Override
    protected void cleanup(Application app) {
        
    }

    @Override
    protected void onEnable() {
         Node root = ((Main)getApplication()).getGuiNode();
        root.attachChild(loginPanel);
    }

    @Override
    protected void onDisable() {
        //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    loginPanel.removeFromParent();
    }
    
}

Because I can’t think of too many different ways to say this today, I’m just going to repeat myself.

Can’t put the viewport work
This is my code
protected void initialize(Application app) {
loginPanel = new Container();
loginPanel.addChild(new Label(“Options”, new ElementId(“title”)));

    Container props = loginPanel.addChild(new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.Last)));
    props.setBackground(null);    
    TabbedPanel tabs = new TabbedPanel();
    Panel panel1=new Panel(500,2000);
    ViewportPanel panel= new ViewportPanel(panel1.getElementId(),panel1.getStyle());

Container tab1 = tabs.addTab(“Game Options”, new Container());
tab1.addChild(new Label(“Game Options”));
panel.attachScene(panel1);
tab1.addChild(panel1);
Container tab2 = tabs.addTab(“Graphic Options”, new Container());
tab2.addChild(new Label(“Grapicos options”));
Container tab3 = tabs.addTab(" Audio Options", new Container());
tab3.addChild(new Label(“Audio”));
Container tab4 = tabs.addTab("Key Mapping ", new Container());
tab4.addChild(new Label(“Key Mapping”));
loginPanel.addChild(tabs);

  /*  props.addChild(new Label("Username:"));
    nameField = props.addChild(new TextField(System.getProperty("user.name")), 1);
    props.addChild(new Label("Password:"));
    passwordfield=props.addChild(new PasswordField(System.getProperty("Pass.word")), 1);
  */ Container buttons = loginPanel.addChild(new Container(new SpringGridLayout(Axis.X, Axis.Y)));
    buttons.setBackground(null);
    buttons.setLayout(new SpringGridLayout(Axis.X, Axis.Y));
   buttons.addChild(new ActionButton(new CallMethodAction("Default", this, "join"))); 
   buttons.addChild(new ActionButton(new CallMethodAction("Reset", this, "join")));
   buttons.addChild(new ActionButton(new CallMethodAction("Apply", this, "join"))); 
     buttons.addChild(new ActionButton(new CallMethodAction("OK", this, "join"))); 
     buttons.addChild(new ActionButton(new CallMethodAction("Cancel", this, "cancel"))); 
    
    
    float scale = 1.5f * getState(MainMenuState.class).getStandardScale();
    loginPanel.setLocalScale(scale);
    
    Vector3f prefs = loginPanel.getPreferredSize().clone();
    prefs.x = Math.max(300, prefs.x);
    loginPanel.setPreferredSize(prefs.clone());
    
    // Now account for scaling
    prefs.multLocal(scale);
    
    int width = app.getCamera().getWidth();
    int height = app.getCamera().getHeight();
    
    loginPanel.setLocalTranslation(width * 0.5f - prefs.x * 0.5f, height * 0.5f + prefs.y * 0.5f, 10);       
}

A custom scroll view of something where you know what the list is can be done without a view port. I hacked a “hello world” example. You manage the sublist that is visible manually. The visual state is just this sublist. Now you only need to manage a fixed number of items with a forward/back button.

I think this would be easier than viewports.

I did the same thing with a array of items that don’t fit.

Attach the viewport panel (pointed by your var “panel”) to any other container. You are currently creating that panel but doing nothing with it.

I always thought about porting this back from my own ui back then, but did not find time(and need) to do so.

Basically I used a own material (e.g replace all lemur default ones for this) that contains a cliprectangle as an shader parameter. Everything outside those is then getting discarded in the fragment shader. This way a proper scroll is implementable without having to use a secondary viewport. Scrolltables would be required to set the cliprectange for all children recursivly if the child is not a scrollpane itself. Also they need to merge a cliprectangle from the parent. Further optimisation is to simply flip the cullhint, if a objects cliprectangle is 0 in any direction.

Isn’t that too much work for something that can be easily done with a viewport?.

What about efficiency?, is any of the methods noticeable more/less efficient than the other? (I don’t really know how a new viewport can impact in the overall performance)

Well using a cliprectangle is extremly fast, because all extra work (appart from setting and updating it) is exclusivly done by the gpu. It further allows other similar features, like a button to cutoff text, if it does not fit without resorting to manually manipulating strings.

However I think a viewport will not be noticeably slower, after all we talk about like 1-3 Panels max at a given ui.
Theoretically could a Viewport also render with slower framerate than the main window.

How exactly is the work to make event handling properly work trough a viewport is however outside of my knowledge, but it might work just out of the box with the system pspeed provides.

I actually do not care much about the technical way this is done, after all if it has a clean interface it could be easily replaced later on if problems appear, no matter what approach was choosen. I’m more interested in a working Scrolllpanel that just works out of the box :slight_smile:

The viewportpanel I shared manages the pspeed’s input correctly. Which is the one is using OP (or that I assume after receiving some PM from him… I never verified it O.o).

I use the 2D version to create a scroll panel too:

However, it scrolls with the mouse or the arrows (not bar). It’s as easy as translate it up/down when desired. It should be easy to make a generic scrollpanel to use in most common cases (with toggleable horizontal/vertical bars) but didn’t have the time yet.

I did a profile:

No ViewportPanel :

Four empty ViewportPanel :

And four ViewportPanel with high poly child (~ 12K face) attached

Edit:

And one ViewportPanel with four child:

1 Like

As I said it is probably not that much.
However might I ask, why you render that items with a Viewport? Do they change frequently? or is it just to get a Model there?
I use a system for inventorys, where I generate a image via a secondary viewport only once, and then reuse that image.

(Of course that would not be useable for scrolls, but for a character select it might be an alternative option)

I am using a Viewport because :

Yes I want a mode there (and I am playing tween animation on them).
Which will participate on drag & drop stuff on scene.
But it is not a must to put 3D models in there, of course I can just put an icon instead, and I will do if I run to performance issues.

can i put a example
when i try put
tab1.addChild(panel);
give this error
Uncaught exception thrown in Thread[jME3 Main,5,main]
NullPointerException

You’ve left out all of the important parts of the exception.

Don’t bother posting exceptions without a track trace. They aren’t useful.

i fix the error but i have this result
how put a scroll bar in my panel

    package mygame;

    import com.jme3.app.Application;
    import com.jme3.app.state.BaseAppState;
    import com.jme3.math.Vector3f;
    import com.jme3.renderer.ViewPort;
    import com.jme3.scene.Node;
    import com.simsilica.lemur.ActionButton;
    import com.simsilica.lemur.Axis;
    import com.simsilica.lemur.CallMethodAction;
    import com.simsilica.lemur.Container;
    import com.simsilica.lemur.FillMode;
    import com.simsilica.lemur.Insets3f;
    import com.simsilica.lemur.Label;
    import com.simsilica.lemur.Panel;
    import com.simsilica.lemur.PasswordField;
    import com.simsilica.lemur.TabbedPanel;
    import com.simsilica.lemur.TextField;
    import com.simsilica.lemur.component.SpringGridLayout;
    import com.simsilica.lemur.style.ElementId;
    import com.simsilica.lemur.RollupPanel;
    import mygame.panels.ViewportPanel;

    /**
     *
     * @author Pedro Alves
     */
    public class OptionsState  extends BaseAppState  {
        private Container loginPanel;
        private TextField nameField;
        private PasswordField passwordfield;
          private ViewPort  viewPort;
     protected void apply() {
            
           // String name = nameField.getText().trim();
          // String password= passwordfield.getText().trim();
           // if( getState(ConnectionState.class).joinserver(nameField.getText(),passwordfield.getText()) ) {
            //    getStateManager().detach(this);
            //}
        } 
      protected void join() {
            
           // String name = nameField.getText().trim();
          // String password= passwordfield.getText().trim();
           // if( getState(ConnectionState.class).joinserver(nameField.getText(),passwordfield.getText()) ) {
            //    getStateManager().detach(this);
            //}
        } 
     protected void cancel() {
         getStateManager().attach(new MainMenuState());
         getStateManager().detach(this); 
           // String name = nameField.getText().trim();
          // String password= passwordfield.getText().trim();
           // if( getState(ConnectionState.class).joinserver(nameField.getText(),passwordfield.getText()) ) {
            //    getStateManager().detach(this);
            //}
        } 
        public OptionsState() {
        
        }
        @Override
        protected void initialize(Application app) {
          loginPanel = new Container();
            loginPanel.addChild(new Label("Options", new ElementId("title")));
            
            Container props = loginPanel.addChild(new Container(new SpringGridLayout(Axis.Y, Axis.X, FillMode.None, FillMode.Last)));
            props.setBackground(null);    
            TabbedPanel tabs = new TabbedPanel();
            Panel panel1=new Panel(500,200);
            panel1.setInsets(new Insets3f(10, 10, 10, 10));
            ViewportPanel panel= new ViewportPanel(getStateManager(),panel1.getElementId(),panel1.getStyle());
    panel.attachScene(panel1);

    Container tab1 = tabs.addTab("Game Options", new Container());
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));
    panel1=tab1.addChild(new Label("Game Options"));

    tab1.addChild(panel1);

    tab1.addChild(panel);
    Container tab2 = tabs.addTab("Graphic Options", new Container());
    tab2.addChild(new Label("Grapicos options"));
    Container tab3 = tabs.addTab(" Audio Options", new Container());
    tab3.addChild(new Label("Audio"));
    Container tab4 = tabs.addTab("Key Mapping ", new Container());
    tab4.addChild(new Label("Key Mapping"));
     loginPanel.addChild(tabs);

         Container buttons = loginPanel.addChild(new Container(new SpringGridLayout(Axis.X, Axis.Y)));
            buttons.setBackground(null);
            buttons.setLayout(new SpringGridLayout(Axis.X, Axis.Y));
           buttons.addChild(new ActionButton(new CallMethodAction("Default", this, "join"))); 
           buttons.addChild(new ActionButton(new CallMethodAction("Reset", this, "join")));
           buttons.addChild(new ActionButton(new CallMethodAction("Apply", this, "join"))); 
             buttons.addChild(new ActionButton(new CallMethodAction("OK", this, "join"))); 
             buttons.addChild(new ActionButton(new CallMethodAction("Cancel", this, "cancel"))); 
            float scale = 1.5f * getState(MainMenuState.class).getStandardScale();
            loginPanel.setLocalScale(scale);
            Vector3f prefs = loginPanel.getPreferredSize().clone();
            prefs.x = Math.max(300, prefs.x);
            loginPanel.setPreferredSize(prefs.clone());
            // Now account for scaling
            prefs.multLocal(scale);
            int width = app.getCamera().getWidth();
            int height = app.getCamera().getHeight();
            loginPanel.setLocalTranslation(width * 0.5f - prefs.x * 0.5f, height * 0.5f + prefs.y * 0.5f, 10);       
        }

        @Override
        protected void cleanup(Application app) {
            
        }

        @Override
        protected void onEnable() {
             Node root = ((Main)getApplication()).getGuiNode();
            root.attachChild(loginPanel);
        }

        @Override
        protected void onDisable() {
            //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
        loginPanel.removeFromParent();
        }
        
    }

can you put the code with viewportpanel

Link to ViewportPanel is available here

i want know how you put the view port panel to work
i can´t make it work

I am not sure if this will help you or not.
I am using it in my CellRenderer for GridPanel

/**
 * Used to render 3D Items (items which have spatial) in a Gui panel
 *
 * @author Ali-RS <ali_codmw@yahoo.com>
 */
public class ItemRenderer extends DefaultCellRenderer<Item> {

    protected Map<Item, Spatial> spatials = new HashMap<>();
    private final AppStateManager stateManager;

    public ItemRenderer(AppStateManager stateManager) {
        super(null);
        this.stateManager = stateManager;
    }

    @Override
    public Panel getView(Item item, boolean selected, Panel existing) {

        if (existing == null) {

            //Container container = new Container();
            ViewportPanel container = new ViewportPanel(stateManager, new ElementId(Container.ELEMENT_ID), null);
            existing = container;

            /**
             * A white, directional light source
             */
            DirectionalLight sun = new DirectionalLight();
            sun.setDirection(Vector3f.UNIT_Z.negate());
            sun.setColor(ColorRGBA.White);
            container.addLight(sun);

            GuiGlobals.getInstance().getAnimationState().
                    add(new TweenAnimation(true, Tweens.smoothStep(SpatialTweens.rotate(container.getViewPortNode(), new Quaternion().fromAngleAxis(-FastMath.QUARTER_PI, Vector3f.UNIT_Y), new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y), 2)),
                            Tweens.smoothStep(SpatialTweens.rotate(container.getViewPortNode(), new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Y), new Quaternion().fromAngleAxis(-FastMath.QUARTER_PI, Vector3f.UNIT_Y), 2))));

//            GuiGlobals.getInstance().getAnimationState().
//                    add(new TweenAnimation(true, (new RotateSpatial(container.getViewPortNode(),
//                            new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Y), 5))));
        }

        if (item != null) {
            if (!spatials.containsKey(item)) {
                Spatial spatial = loadSpatial(item);
                spatials.put(item, spatial);

                //No need when using ViewportPanel  
//                Tween attachTween = Tweens.callTweenMethod(5, this, "inset", (Container) existing, (Container) existing.getChild(0), spatial);
//                GuiGlobals.getInstance().getAnimationState().add(attachTween);
            }

            Spatial spatial = spatials.get(item);
            spatial.removeFromParent();
            ((ViewportPanel) existing).attachScene(spatial);
        } else {
            ((ViewportPanel) existing).getViewPortNode().detachAllChildren();
        }

        return existing;
    }

    public Spatial getSpatial(Item item) {
        return spatials.get(item);
    }

    public Spatial removeSpatial(Item item) {
        return spatials.remove(item);
    }

    protected Spatial loadSpatial(Item item) {
        return ItemLoader.getSpatial(item);
    }

}

I am using ViewportPanel to display jme spatials inside gui elements.

Note, for scrolling I am not using viewport at all, it’s just a Lemur Slider with a GridPanel just like ListBox.