Search code examples
javalibgdx

How to scroll a ScrollPane in code?


I’m trying to make a scrollwheel component like this in LibGDX: enter image description here

I’m using ScrollPane since it has input and fling handling built in. I have an image for the scrollwheel that is divided into 14 sections, the scrollpane itself is two sections shorter so that there will be one section on the right and left sides that it can scroll to in either direction. Once the scroll position reaches the end in either direction I want to reset the scroll position back to the center. Doing this over and over again should create the illusion of an infinite scrolling wheel (hopefully).

The problem I’m having is how to position the ScrollPane in code to reset the image once it reaches either end. So far nothing I have tried to set the scroll position has worked. I’ve tried setScrollX() and scrollTo() methods. I’ve also tried setting the size of the scrollpane to be various sizes (same size as image and two sections smaller than the image). I’ve tried calling layout, invalidate, and pack on the scrollpane to make sure it is laid out correctly before I set the scroll value. I thought that updateVisualScroll() might force it to update the scroll position, but this also has no effect.

No matter what I do it simply ignores all of my calls to change the scroll position so I’m clearly missing something. In my code below I'm trying to get the scrollwheel to start in the center of the image and instead it's starting position is all the way at the left.

I also need to be able to get the current scroll position to detect when it has reached either end. I tried overriding the act() method and printing out scrollPane.getX(), but this value was always “0” even when I manually clicked and dragged it to scroll the ScrollPane.

The scrolling does work when manually clicking and dragging, so I believe the ScrollPane is set up correctly, I just can’t get it to scroll within the code.

Here is my code, and for simplicity I took all of my experimentation code out because none of my experimenting worked.

public class MyScrollWheel extends Container<ScrollPane> {
    private ScrollPane scrollPane;
    private Image image;
    private int scrollOffset;

    public MyScrollWheel(){
        Texture texture = new Texture(Gdx.files.internal("internal/scrollwheel.png"));
        image = new Image(texture);

        scrollOffset = (int)(image.getWidth()/14);

        scrollPane = new ScrollPane(image);
        scrollPane.setOverscroll(false, false);

        setActor(scrollPane);
        size(image.getWidth()-(scrollOffset*2), image.getHeight());

        scrollPane.setScrollX(scrollOffset); // << this doesn't scroll
        scrollPane.updateVisualScroll();
    }
}

Solution

  • Well, I hopefully managed to get something you can build upon. What I simply did was extending actor and have it accept a Texture so I could use Texture.wrap and have it draw with SpriteBatch.draw(). I am able to keep scrolling it now and based on the scroll delta you can figure out how far it has been scrolled. I don't see any need to reset the wheel but if you really want to you can just do wheel.setScroll(0);.

    One limitation is that it is not a Drawable so it cannot be scaled like a NinePatch. You have to give it a plain wheel texture draw it the size you want it to spear, you can add normal scaling however and keep aspect ratio manually. Then add the sides to it and perhaps overlay a gradient on those to create depth.

    ScrollWheel:

    public class ScrollWheel extends Actor {
        Texture wheelTexture;
        private int scroll = 0;
    
        public int getScroll() {
            return scroll;
        }
        public void setScroll(int scroll) {
            this.scroll = scroll;
        }
    
        public ScrollWheel(Texture texture)
        {
            wheelTexture = texture;
            wheelTexture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.ClampToEdge);
    
            setWidth(texture.getWidth());
            setHeight(texture.getHeight());
        }
    
        @Override
        public void draw(Batch batch, float parentAlpha) {
            super.draw(batch, parentAlpha);
    
            batch.draw(wheelTexture, getX(), getY(), scroll, 0,
                    wheelTexture.getWidth(), wheelTexture.getHeight());
        }
    }
    

    usage in a Screen:

    public class TestScreen implements Screen {
        Stage stage;
        ScrollWheel wheel;
    
        public TestScreen() {
            stage = new Stage();
            Table t = new Table();
            t.setFillParent(true);
            stage.addActor(t);
    
            wheel = new ScrollWheel(new Texture("hud/wheel_part.png"));
            wheel.addListener(new DragListener() {
                @Override
                public void drag(InputEvent event, float x, float y, int pointer) {
                    super.drag(event, x, y, pointer);
                    wheel.setScroll(wheel.getScroll() + (int)getDeltaX());
                }
            });
    
            t.add(wheel);
            Gdx.input.setInputProcessor(stage);
        }
    
        @Override
        public void render(float delta) {
            Gdx.gl.glClearColor(.3f, .36f, .42f, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    
            stage.act();
            stage.draw();
        }
        //...Other mandatory screen methods...
    }
    

    So just create a wheel texture that is tillable and include that with the ScrollWheel constructor. It will draw the wheel in the center of the screen if you use this exact code.

    The scroll variable essentially holds the amount of scroll so if you you want limit this between 0 and 100 you would just add this functionality in setScroll().

    if (scroll > 100) scroll = 100; 
    else if (scroll < 0) scroll = 0;
    

    You could then add a step to it. So if you want to rotate a image with the slider you could set the rotation by scroll * 3,6f or scroll * (maxScroll / maxStep)

    I really liked the way this turned out, I will be using this for my slider in the future :D. I have extended and altered it a bit already and you can see my implementation here: https://youtu.be/RNLk5B-VfYg