com.badlogic.gdx.scenes.scene2d.ui.FlickScrollPane.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.scenes.scene2d.ui.FlickScrollPane.java

Source

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.scenes.scene2d.ui;

import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.input.GestureDetector.GestureListener;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Layout;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.utils.ScissorStack;

/** @author Nathan Sweet
 * @author mzechner */
public class FlickScrollPane extends Group implements Layout {
    private final Stage stage;
    private Actor widget;
    protected boolean needsLayout;

    private final Rectangle widgetAreaBounds = new Rectangle();
    private final Rectangle scissorBounds = new Rectangle();
    private GestureDetector gestureDetector;

    private boolean scrollX, scrollY;
    float amountX, amountY;
    private float maxX, maxY;
    float velocityX, velocityY;
    float flingTimer;

    public boolean bounces = true;
    public float flingTime = 1f;
    public float bounceDistance = 50, bounceSpeedMin = 30, bounceSpeedMax = 200;
    public boolean emptySpaceOnlyScroll;
    public boolean forceScrollX, forceScrollY;
    public boolean clamp = true;

    public FlickScrollPane(Actor widget, Stage stage) {
        this(widget, stage, null);
    }

    public FlickScrollPane(Actor widget, Stage stage, String name) {
        super(name);

        this.stage = stage;
        this.widget = widget;
        if (widget != null)
            this.addActor(widget);

        gestureDetector = new GestureDetector(new GestureListener() {
            public boolean pan(int x, int y, int deltaX, int deltaY) {
                amountX -= deltaX;
                amountY += deltaY;
                clamp();
                return false;
            }

            public boolean fling(float x, float y) {
                if (Math.abs(x) > 150) {
                    flingTimer = flingTime;
                    velocityX = x;
                }
                if (Math.abs(y) > 150) {
                    flingTimer = flingTime;
                    velocityY = -y;
                }
                return flingTimer > 0;
            }

            public boolean touchDown(int x, int y, int pointer) {
                flingTimer = 0;
                return true;
            }

            public boolean zoom(float originalDistance, float currentDistance) {
                return false;
            }

            public boolean tap(int x, int y, int count) {
                return FlickScrollPane.this.tap(x, y);
            }

            public boolean longPress(int x, int y) {
                return false;
            }
        });
    }

    boolean tap(int x, int y) {
        focus(null, 0);
        if (!super.touchDown(x, y, 0))
            return false;
        Actor actor = focusedActor[0];
        toLocalCoordinates(actor, point);
        actor.touchUp(point.x, point.y, 0);
        return true;
    }

    public void toLocalCoordinates(Actor actor, Vector2 point) {
        if (actor.parent == this)
            return;
        toLocalCoordinates(actor.parent, point);
        Group.toChildCoordinates(actor, point.x, point.y, point);
    }

    void clamp() {
        if (!clamp)
            return;
        if (bounces) {
            amountX = Math.max(-bounceDistance, amountX);
            amountX = Math.min(maxX + bounceDistance, amountX);
            amountY = Math.max(-bounceDistance, amountY);
            amountY = Math.min(maxY + bounceDistance, amountY);
        } else {
            amountX = Math.max(0, amountX);
            amountX = Math.min(maxX, amountX);
            amountY = Math.max(0, amountY);
            amountY = Math.min(maxY, amountY);
        }
    }

    public void act(float delta) {
        if (flingTimer > 0) {
            float alpha = flingTimer / flingTime;
            alpha = alpha * alpha * alpha;
            amountX -= velocityX * alpha * delta;
            amountY -= velocityY * alpha * delta;
            clamp();

            // Stop fling if hit bounce distance.
            if (amountX == -bounceDistance)
                velocityX = 0;
            if (amountX >= maxX + bounceDistance)
                velocityX = 0;
            if (amountY == -bounceDistance)
                velocityY = 0;
            if (amountY >= maxY + bounceDistance)
                velocityY = 0;

            flingTimer -= delta;
        }

        if (bounces && !gestureDetector.isPanning()) {
            if (amountX < 0) {
                amountX += (bounceSpeedMin + (bounceSpeedMax - bounceSpeedMin) * -amountX / bounceDistance) * delta;
                if (amountX > 0)
                    amountX = 0;
            } else if (amountX > maxX) {
                amountX -= (bounceSpeedMin + (bounceSpeedMax - bounceSpeedMin) * -(maxX - amountX) / bounceDistance)
                        * delta;
                if (amountX < maxX)
                    amountX = maxX;
            }
            if (amountY < 0) {
                amountY += (bounceSpeedMin + (bounceSpeedMax - bounceSpeedMin) * -amountY / bounceDistance) * delta;
                if (amountY > 0)
                    amountY = 0;
            } else if (amountY > maxY) {
                amountY -= (bounceSpeedMin + (bounceSpeedMax - bounceSpeedMin) * -(maxY - amountY) / bounceDistance)
                        * delta;
                if (amountY < maxY)
                    amountY = maxY;
            }
        }
    }

    private void calculateBoundsAndPositions(Matrix4 batchTransform) {
        // Get widget's desired width.
        float widgetWidth, widgetHeight;
        if (widget instanceof Layout) {
            Layout layout = (Layout) widget;
            widgetWidth = layout.getPrefWidth();
            widgetHeight = layout.getPrefHeight();
        } else {
            widgetWidth = widget.width;
            widgetHeight = widget.height;
        }

        // Figure out if we need horizontal/vertical scrollbars,
        scrollX = widgetWidth > width || forceScrollX;
        scrollY = widgetHeight > height || forceScrollY;

        // If the widget is smaller than the available space, make it take up the available space.
        widgetWidth = Math.max(width, widgetWidth);
        widgetHeight = Math.max(height, widgetHeight);
        if (widget.width != widgetWidth || widget.height != widgetHeight) {
            widget.width = widgetWidth;
            widget.height = widgetHeight;
            needsLayout = true;
        }

        // Set the widget area bounds.
        widgetAreaBounds.set(0, 0, width, height);

        // Calculate the widgets offset depending on the scroll state and available widget area.
        maxX = widget.width - width;
        maxY = widget.height - height;
        widget.y = (int) (scrollY ? amountY : maxY) - widget.height + height;
        widget.x = -(int) (scrollX ? amountX : 0);

        // Caculate the scissor bounds based on the batch transform, the available widget area and the camera transform. We need to
        // project those to screen coordinates for OpenGL ES to consume.
        ScissorStack.calculateScissors(stage.getCamera(), batchTransform, widgetAreaBounds, scissorBounds);
    }

    @Override
    public void draw(SpriteBatch batch, float parentAlpha) {
        if (widget == null)
            return;

        // Setup transform for this group.
        applyTransform(batch);

        // Calculate the bounds for the scrollbars, the widget area and the scissor area.
        calculateBoundsAndPositions(batch.getTransformMatrix()); // BOZO - Call every frame?

        if (needsLayout)
            layout();

        // Enable scissors for widget area and draw the widget.
        ScissorStack.pushScissors(scissorBounds);
        drawChildren(batch, parentAlpha);
        ScissorStack.popScissors();

        resetTransform(batch);
    }

    @Override
    public void layout() {
        if (!needsLayout)
            return;
        needsLayout = false;
        if (widget instanceof Layout) {
            Layout layout = (Layout) widget;
            layout.invalidate();
            layout.layout();
        }
    }

    @Override
    public void invalidate() {
        needsLayout = true;
    }

    @Override
    public boolean touchDown(float x, float y, int pointer) {
        if (pointer != 0)
            return false;
        if (emptySpaceOnlyScroll && super.touchDown(x, y, pointer))
            return true;
        return gestureDetector.touchDown((int) x, (int) y, pointer, 0);
    }

    @Override
    public void touchUp(float x, float y, int pointer) {
        clamp();
        gestureDetector.touchUp((int) x, (int) y, pointer, 0);
        if (focusedActor[pointer] != null)
            super.touchUp(x, y, pointer);
    }

    @Override
    public void touchDragged(float x, float y, int pointer) {
        gestureDetector.touchDragged((int) x, (int) y, pointer);
        super.touchDragged(x, y, pointer);
    }

    @Override
    public Actor hit(float x, float y) {
        return x > 0 && x < width && y > 0 && y < height ? this : null;
    }

    public void setScrollX(float pixels) {
        this.amountX = pixels;
    }

    public float getScrollX() {
        return amountX;
    }

    public void setScrollY(float pixels) {
        amountY = pixels;
    }

    public float getScrollY() {
        return amountY;
    }

    /** Sets the {@link Actor} embedded in this scroll pane.
     * @param widget the Actor */
    public void setWidget(Actor widget) {
        if (this.widget != null)
            removeActor(this.widget);
        this.widget = widget;
        if (widget != null)
            addActor(widget);
    }

    public Actor getWidget() {
        return widget;
    }

    public boolean isPanning() {
        return gestureDetector.isPanning();
    }

    public float getVelocityX() {
        if (flingTimer <= 0)
            return 0;
        float alpha = flingTimer / flingTime;
        alpha = alpha * alpha * alpha;
        return velocityX * alpha * alpha * alpha;
    }

    public float getVelocityY() {
        return velocityY;
    }

    public float getPrefWidth() {
        return 150;
    }

    public float getPrefHeight() {
        return 150;
    }

    public float getMinWidth() {
        return 0;
    }

    public float getMinHeight() {
        return 0;
    }

    public float getMaxWidth() {
        return 0;
    }

    public float getMaxHeight() {
        return 0;
    }
}