/*******************************************************************************
* 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.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
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.Event;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
import com.badlogic.gdx.scenes.scene2d.utils.Cullable;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
/**
* A group that scrolls a child widget using scrollbars and/or mouse or touch dragging.
* <p>
* The widget is sized to its preferred size. If the widget's preferred width or height is less than the size of this
* scroll pane, it is set to the size of this scroll pane. Scrollbars appear when the widget is larger than the scroll
* pane.
* <p>
* The scroll pane's preferred size is that of the child widget. At this size, the child widget will not need to scroll,
* so the scroll pane is typically sized by ignoring the preferred size in one or both directions.
*
* @author mzechner
* @author Nathan Sweet
*/
public class ScrollPane extends WidgetGroup {
private ScrollPaneStyle style;
private Actor widget;
final Rectangle hScrollBounds = new Rectangle();
final Rectangle vScrollBounds = new Rectangle();
final Rectangle hKnobBounds = new Rectangle();
final Rectangle vKnobBounds = new Rectangle();
private final Rectangle widgetAreaBounds = new Rectangle();
private final Rectangle widgetCullingArea = new Rectangle();
private final Rectangle scissorBounds = new Rectangle();
private ActorGestureListener flickScrollListener;
boolean scrollX, scrollY;
float amountX, amountY;
float visualAmountX, visualAmountY;
float maxX, maxY;
boolean touchScrollH, touchScrollV;
final Vector2 lastPoint = new Vector2();
float areaWidth, areaHeight;
private boolean fadeScrollBars = true, smoothScrolling = true;
float fadeAlpha, fadeAlphaSeconds = 1, fadeDelay, fadeDelaySeconds = 1;
boolean cancelTouchFocus = true;
boolean flickScroll = true;
float velocityX, velocityY;
float flingTimer;
private boolean overscrollX = true, overscrollY = true;
float flingTime = 1f;
private float overscrollDistance = 50, overscrollSpeedMin = 30, overscrollSpeedMax = 200;
private boolean forceOverscrollX, forceOverscrollY;
private boolean disableX, disableY;
private boolean clamp = true;
private boolean scrollbarsOnTop;
int draggingPointer = -1;
/**
* @param widget
* May be null.
*/
public ScrollPane(Actor widget) {
this(widget, new ScrollPaneStyle());
}
/**
* @param widget
* May be null.
*/
public ScrollPane(Actor widget, Skin skin) {
this(widget, skin.get(ScrollPaneStyle.class));
}
/**
* @param widget
* May be null.
*/
public ScrollPane(Actor widget, Skin skin, String styleName) {
this(widget, skin.get(styleName, ScrollPaneStyle.class));
}
/**
* @param widget
* May be null.
*/
public ScrollPane(Actor widget, ScrollPaneStyle style) {
if (style == null)
throw new IllegalArgumentException("style cannot be null.");
this.widget = widget;
this.style = style;
if (widget != null)
setWidget(widget);
setWidth(150);
setHeight(150);
addCaptureListener(new InputListener() {
private float handlePosition;
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
if (draggingPointer != -1)
return false;
if (pointer == 0 && button != 0)
return false;
getStage().setScrollFocus(ScrollPane.this);
if (!flickScroll)
resetFade();
if (fadeAlpha == 0)
return false;
if (scrollX && hScrollBounds.contains(x, y)) {
event.stop();
resetFade();
if (hKnobBounds.contains(x, y)) {
lastPoint.set(x, y);
handlePosition = hKnobBounds.x;
touchScrollH = true;
draggingPointer = pointer;
return true;
}
setScrollX(amountX + Math.max(areaWidth * 0.9f, maxX * 0.1f) * (x < hKnobBounds.x ? -1 : 1));
return true;
}
if (scrollY && vScrollBounds.contains(x, y)) {
event.stop();
resetFade();
if (vKnobBounds.contains(x, y)) {
lastPoint.set(x, y);
handlePosition = vKnobBounds.y;
touchScrollV = true;
draggingPointer = pointer;
return true;
}
setScrollY(amountY + Math.max(areaHeight * 0.9f, maxY * 0.1f) * (y < vKnobBounds.y ? 1 : -1));
return true;
}
return false;
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
if (pointer != draggingPointer)
return;
draggingPointer = -1;
touchScrollH = false;
touchScrollV = false;
}
public void touchDragged(InputEvent event, float x, float y, int pointer) {
if (pointer != draggingPointer)
return;
if (touchScrollH) {
float delta = x - lastPoint.x;
float scrollH = handlePosition + delta;
handlePosition = scrollH;
scrollH = Math.max(hScrollBounds.x, scrollH);
scrollH = Math.min(hScrollBounds.x + hScrollBounds.width - hKnobBounds.width, scrollH);
setScrollPercentX((scrollH - hScrollBounds.x) / (hScrollBounds.width - hKnobBounds.width));
lastPoint.set(x, y);
} else if (touchScrollV) {
float delta = y - lastPoint.y;
float scrollV = handlePosition + delta;
handlePosition = scrollV;
scrollV = Math.max(vScrollBounds.y, scrollV);
scrollV = Math.min(vScrollBounds.y + vScrollBounds.height - vKnobBounds.height, scrollV);
setScrollPercentY(1 - ((scrollV - vScrollBounds.y) / (vScrollBounds.height - vKnobBounds.height)));
lastPoint.set(x, y);
}
}
public boolean mouseMoved(InputEvent event, float x, float y) {
if (!flickScroll)
resetFade();
return false;
}
});
flickScrollListener = new ActorGestureListener() {
public void pan(InputEvent event, float x, float y, float deltaX, float deltaY) {
resetFade();
amountX -= deltaX;
amountY += deltaY;
clamp();
cancelTouchFocusedChild(event);
}
public void fling(InputEvent event, float x, float y, int button) {
if (Math.abs(x) > 150) {
flingTimer = flingTime;
velocityX = x;
cancelTouchFocusedChild(event);
}
if (Math.abs(y) > 150) {
flingTimer = flingTime;
velocityY = -y;
cancelTouchFocusedChild(event);
}
}
public boolean handle(Event event) {
if (super.handle(event)) {
if (((InputEvent) event).getType() == InputEvent.Type.touchDown)
flingTimer = 0;
return true;
}
return false;
}
};
addListener(flickScrollListener);
addListener(new InputListener() {
public boolean scrolled(InputEvent event, float x, float y, int amount) {
resetFade();
if (scrollY)
setScrollY(amountY + Math.max(areaHeight * 0.9f, maxY * 0.1f) / 4 * amount);
else if (scrollX) //
setScrollX(amountX + Math.max(areaWidth * 0.9f, maxX * 0.1f) / 4 * amount);
return true;
}
});
}
void resetFade() {
fadeAlpha = fadeAlphaSeconds;
fadeDelay = fadeDelaySeconds;
}
void cancelTouchFocusedChild(InputEvent event) {
if (!cancelTouchFocus)
return;
Stage stage = getStage();
if (stage != null)
stage.cancelTouchFocus(flickScrollListener, this);
}
void clamp() {
if (!clamp)
return;
scrollX(overscrollX ? MathUtils.clamp(amountX, -overscrollDistance, maxX + overscrollDistance) : MathUtils
.clamp(amountX, 0, maxX));
scrollY(overscrollY ? MathUtils.clamp(amountY, -overscrollDistance, maxY + overscrollDistance) : MathUtils
.clamp(amountY, 0, maxY));
}
public void setStyle(ScrollPaneStyle style) {
if (style == null)
throw new IllegalArgumentException("style cannot be null.");
this.style = style;
invalidateHierarchy();
}
/**
* Returns the scroll pane's style. Modifying the returned style may not have an effect until
* {@link #setStyle(ScrollPaneStyle)} is called.
*/
public ScrollPaneStyle getStyle() {
return style;
}
public void act(float delta) {
super.act(delta);
boolean panning = flickScrollListener.getGestureDetector().isPanning();
if (fadeAlpha > 0 && fadeScrollBars && !panning && !touchScrollH && !touchScrollV) {
fadeDelay -= delta;
if (fadeDelay <= 0)
fadeAlpha = Math.max(0, fadeAlpha - delta);
}
if (flingTimer > 0) {
resetFade();
float alpha = flingTimer / flingTime;
amountX -= velocityX * alpha * delta;
amountY -= velocityY * alpha * delta;
clamp();
// Stop fling if hit overscroll distance.
if (amountX == -overscrollDistance)
velocityX = 0;
if (amountX >= maxX + overscrollDistance)
velocityX = 0;
if (amountY == -overscrollDistance)
velocityY = 0;
if (amountY >= maxY + overscrollDistance)
velocityY = 0;
flingTimer -= delta;
if (flingTimer <= 0) {
velocityX = 0;
velocityY = 0;
}
}
if (smoothScrolling && flingTimer <= 0 && !touchScrollH && !touchScrollV && !panning) {
if (visualAmountX != amountX) {
if (visualAmountX < amountX)
visualScrollX(Math.min(amountX,
visualAmountX + Math.max(150 * delta, (amountX - visualAmountX) * 5 * delta)));
else
visualScrollX(Math.max(amountX,
visualAmountX - Math.max(150 * delta, (visualAmountX - amountX) * 5 * delta)));
}
if (visualAmountY != amountY) {
if (visualAmountY < amountY)
visualScrollY(Math.min(amountY,
visualAmountY + Math.max(150 * delta, (amountY - visualAmountY) * 5 * delta)));
else
visualScrollY(Math.max(amountY,
visualAmountY - Math.max(150 * delta, (visualAmountY - amountY) * 5 * delta)));
}
} else {
if (visualAmountX != amountX)
visualScrollX(amountX);
if (visualAmountY != amountY)
visualScrollY(amountY);
}
if (!panning) {
if (overscrollX && scrollX) {
if (amountX < 0) {
resetFade();
amountX += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountX
/ overscrollDistance)
* delta;
if (amountX > 0)
scrollX(0);
} else if (amountX > maxX) {
resetFade();
amountX -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxX - amountX)
/ overscrollDistance)
* delta;
if (amountX < maxX)
scrollX(maxX);
}
}
if (overscrollY && scrollY) {
if (amountY < 0) {
resetFade();
amountY += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountY
/ overscrollDistance)
* delta;
if (amountY > 0)
scrollY(0);
} else if (amountY > maxY) {
resetFade();
amountY -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxY - amountY)
/ overscrollDistance)
* delta;
if (amountY < maxY)
scrollY(maxY);
}
}
}
}
public void layout() {
final Drawable bg = style.background;
final Drawable hScrollKnob = style.hScrollKnob;
final Drawable vScrollKnob = style.vScrollKnob;
float bgLeftWidth = 0, bgRightWidth = 0, bgTopHeight = 0, bgBottomHeight = 0;
if (bg != null) {
bgLeftWidth = bg.getLeftWidth();
bgRightWidth = bg.getRightWidth();
bgTopHeight = bg.getTopHeight();
bgBottomHeight = bg.getBottomHeight();
}
float width = getWidth();
float height = getHeight();
float scrollbarHeight = 0;
if (hScrollKnob != null)
scrollbarHeight = hScrollKnob.getMinHeight();
if (style.hScroll != null)
scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
float scrollbarWidth = 0;
if (vScrollKnob != null)
scrollbarWidth = vScrollKnob.getMinWidth();
if (style.vScroll != null)
scrollbarWidth = Math.max(scrollbarWidth, style.vScroll.getMinWidth());
// Get available space size by subtracting background's padded area.
areaWidth = width - bgLeftWidth - bgRightWidth;
areaHeight = height - bgTopHeight - bgBottomHeight;
if (widget == null)
return;
// 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.getWidth();
widgetHeight = widget.getHeight();
}
// Determine if horizontal/vertical scrollbars are needed.
scrollX = forceOverscrollX || (widgetWidth > areaWidth && !disableX);
scrollY = forceOverscrollY || (widgetHeight > areaHeight && !disableY);
boolean fade = fadeScrollBars;
if (!fade) {
// Check again, now taking into account the area that's taken up by any enabled scrollbars.
if (scrollY) {
areaWidth -= scrollbarWidth;
if (!scrollX && widgetWidth > areaWidth && !disableX) {
scrollX = true;
}
}
if (scrollX) {
areaHeight -= scrollbarHeight;
if (!scrollY && widgetHeight > areaHeight && !disableY) {
scrollY = true;
areaWidth -= scrollbarWidth;
}
}
}
// Set the widget area bounds.
widgetAreaBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, areaHeight);
if (fade) {
// Make sure widget is drawn under fading scrollbars.
if (scrollX)
areaHeight -= scrollbarHeight;
if (scrollY)
areaWidth -= scrollbarWidth;
} else {
if (scrollbarsOnTop) {
// Make sure widget is drawn under non-fading scrollbars.
if (scrollX)
widgetAreaBounds.height += scrollbarHeight;
if (scrollY)
widgetAreaBounds.width += scrollbarWidth;
} else {
// Offset widget area y for horizontal scrollbar.
if (scrollX)
widgetAreaBounds.y += scrollbarHeight;
}
}
// If the widget is smaller than the available space, make it take up the available space.
widgetWidth = disableX ? width : Math.max(areaWidth, widgetWidth);
widgetHeight = disableY ? height : Math.max(areaHeight, widgetHeight);
maxX = widgetWidth - areaWidth;
maxY = widgetHeight - areaHeight;
if (fade) {
// Make sure widget is drawn under fading scrollbars.
if (scrollX)
maxY -= scrollbarHeight;
if (scrollY)
maxX -= scrollbarWidth;
}
scrollX(MathUtils.clamp(amountX, 0, maxX));
scrollY(MathUtils.clamp(amountY, 0, maxY));
// Set the bounds and scroll knob sizes if scrollbars are needed.
if (scrollX) {
if (hScrollKnob != null) {
float hScrollHeight = style.hScroll != null ? style.hScroll.getMinHeight() : hScrollKnob.getMinHeight();
hScrollBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, hScrollHeight);
hKnobBounds.width = Math.max(hScrollKnob.getMinWidth(),
(int) (hScrollBounds.width * areaWidth / widgetWidth));
hKnobBounds.height = hScrollKnob.getMinHeight();
hKnobBounds.x = hScrollBounds.x
+ (int) ((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
hKnobBounds.y = hScrollBounds.y;
} else {
hScrollBounds.set(0, 0, 0, 0);
hKnobBounds.set(0, 0, 0, 0);
}
}
if (scrollY) {
if (vScrollKnob != null) {
float vScrollWidth = style.vScroll != null ? style.vScroll.getMinWidth() : vScrollKnob.getMinWidth();
vScrollBounds.set(width - bgRightWidth - vScrollWidth, height - bgTopHeight - areaHeight, vScrollWidth,
areaHeight);
vKnobBounds.width = vScrollKnob.getMinWidth();
vKnobBounds.height = Math.max(vScrollKnob.getMinHeight(),
(int) (vScrollBounds.height * areaHeight / widgetHeight));
vKnobBounds.x = width - bgRightWidth - vScrollKnob.getMinWidth();
vKnobBounds.y = vScrollBounds.y
+ (int) ((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
} else {
vScrollBounds.set(0, 0, 0, 0);
vKnobBounds.set(0, 0, 0, 0);
}
}
if (widget.getWidth() != widgetWidth || widget.getHeight() != widgetHeight) {
widget.setWidth(widgetWidth);
widget.setHeight(widgetHeight);
if (widget instanceof Layout) {
Layout layout = (Layout) widget;
layout.invalidate();
layout.validate();
}
} else {
if (widget instanceof Layout)
((Layout) widget).validate();
}
}
@Override
public void draw(SpriteBatch batch, float parentAlpha) {
if (widget == null)
return;
validate();
// Setup transform for this group.
applyTransform(batch, computeTransform());
if (scrollX)
hKnobBounds.x = hScrollBounds.x + (int) ((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
if (scrollY)
vKnobBounds.y = vScrollBounds.y
+ (int) ((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
// Calculate the widget's position depending on the scroll state and available widget area.
float y = widgetAreaBounds.y;
if (!scrollY)
y -= (int) maxY;
else
y -= (int) (maxY - visualAmountY);
if (!fadeScrollBars && scrollbarsOnTop && scrollX) {
float scrollbarHeight = 0;
if (style.hScrollKnob != null)
scrollbarHeight = style.hScrollKnob.getMinHeight();
if (style.hScroll != null)
scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
y += scrollbarHeight;
}
float x = widgetAreaBounds.x;
if (scrollX)
x -= (int) visualAmountX;
widget.setPosition(x, y);
if (widget instanceof Cullable) {
widgetCullingArea.x = -widget.getX() + widgetAreaBounds.x;
widgetCullingArea.y = -widget.getY() + widgetAreaBounds.y;
widgetCullingArea.width = widgetAreaBounds.width;
widgetCullingArea.height = widgetAreaBounds.height;
((Cullable) widget).setCullingArea(widgetCullingArea);
}
// 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(getStage().getCamera(), batch.getTransformMatrix(), widgetAreaBounds,
scissorBounds);
// Draw the background ninepatch.
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
if (style.background != null)
style.background.draw(batch, 0, 0, getWidth(), getHeight());
batch.flush();
// Enable scissors for widget area and draw the widget.
if (ScissorStack.pushScissors(scissorBounds)) {
drawChildren(batch, parentAlpha);
ScissorStack.popScissors();
}
// Render scrollbars and knobs on top.
batch.setColor(color.r, color.g, color.b,
color.a * parentAlpha * Interpolation.fade.apply(fadeAlpha / fadeAlphaSeconds));
if (scrollX && scrollY) {
if (style.corner != null) {
style.corner.draw(batch, hScrollBounds.x + hScrollBounds.width, hScrollBounds.y, vScrollBounds.width,
vScrollBounds.y);
}
}
if (scrollX) {
if (style.hScroll != null)
style.hScroll.draw(batch, hScrollBounds.x, hScrollBounds.y, hScrollBounds.width, hScrollBounds.height);
if (style.hScrollKnob != null)
style.hScrollKnob.draw(batch, hKnobBounds.x, hKnobBounds.y, hKnobBounds.width, hKnobBounds.height);
}
if (scrollY) {
if (style.vScroll != null)
style.vScroll.draw(batch, vScrollBounds.x, vScrollBounds.y, vScrollBounds.width, vScrollBounds.height);
if (style.vScrollKnob != null)
style.vScrollKnob.draw(batch, vKnobBounds.x, vKnobBounds.y, vKnobBounds.width, vKnobBounds.height);
}
resetTransform(batch);
}
public float getPrefWidth() {
if (widget instanceof Layout)
return ((Layout) widget).getPrefWidth();
return 150;
}
public float getPrefHeight() {
if (widget instanceof Layout)
return ((Layout) widget).getPrefHeight();
return 150;
}
public float getMinWidth() {
return 0;
}
public float getMinHeight() {
return 0;
}
/**
* Sets the {@link Actor} embedded in this scroll pane.
*
* @param widget
* May be null to remove any current actor.
*/
public void setWidget(Actor widget) {
if (this.widget != null)
super.removeActor(this.widget);
this.widget = widget;
if (widget != null)
super.addActor(widget);
}
/** Returns the actor embedded in this scroll pane, or null. */
public Actor getWidget() {
return widget;
}
public void addActor(Actor actor) {
throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
}
public void addActorAt(int index, Actor actor) {
throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
}
public void addActorBefore(Actor actorBefore, Actor actor) {
throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
}
public boolean removeActor(Actor actor) {
if (actor != widget)
return false;
setWidget(null);
return true;
}
public Actor hit(float x, float y, boolean touchable) {
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight())
return null;
if (scrollX && hScrollBounds.contains(x, y))
return this;
if (scrollY && vScrollBounds.contains(x, y))
return this;
return super.hit(x, y, touchable);
}
/** Called whenever the x scroll amount is changed. */
protected void scrollX(float pixelsX) {
this.amountX = pixelsX;
}
/** Called whenever the y scroll amount is changed. */
protected void scrollY(float pixelsY) {
this.amountY = pixelsY;
}
/** Called whenever the visual x scroll amount is changed. */
protected void visualScrollX(float pixelsX) {
this.visualAmountX = pixelsX;
}
/** Called whenever the visual y scroll amount is changed. */
protected void visualScrollY(float pixelsY) {
this.visualAmountY = pixelsY;
}
public void setScrollX(float pixels) {
scrollX(MathUtils.clamp(pixels, 0, maxX));
}
/** Returns the x scroll position in pixels. */
public float getScrollX() {
return amountX;
}
public void setScrollY(float pixels) {
scrollY(MathUtils.clamp(pixels, 0, maxY));
}
/** Returns the y scroll position in pixels. */
public float getScrollY() {
return amountY;
}
public float getVisualScrollX() {
return !scrollX ? 0 : visualAmountX;
}
public float getVisualScrollY() {
return !scrollY ? 0 : visualAmountY;
}
public float getScrollPercentX() {
return MathUtils.clamp(amountX / maxX, 0, 1);
}
public void setScrollPercentX(float percentX) {
scrollX(maxX * MathUtils.clamp(percentX, 0, 1));
}
public float getScrollPercentY() {
return MathUtils.clamp(amountY / maxY, 0, 1);
}
public void setScrollPercentY(float percentY) {
scrollY(maxY * MathUtils.clamp(percentY, 0, 1));
}
public void setFlickScroll(boolean flickScroll) {
if (this.flickScroll == flickScroll)
return;
this.flickScroll = flickScroll;
if (flickScroll)
addListener(flickScrollListener);
else
removeListener(flickScrollListener);
invalidate();
}
/**
* Sets the scroll offset so the specified rectangle is fully in view, if possible. Coordinates are in the scroll
* pane widget's coordinate system.
*/
public void scrollTo(float x, float y, float width, float height) {
float amountX = this.amountX;
if (x + width > amountX + areaWidth)
amountX = x + width - areaWidth;
if (x < amountX)
amountX = x;
scrollX(MathUtils.clamp(amountX, 0, maxX));
float amountY = this.amountY;
if (amountY > maxY - y - height + areaHeight)
amountY = maxY - y - height + areaHeight;
if (amountY < maxY - y)
amountY = maxY - y;
scrollY(MathUtils.clamp(amountY, 0, maxY));
}
/**
* Sets the scroll offset so the specified rectangle is fully in view and centered vertically in the scroll pane, if
* possible. Coordinates are in the scroll pane widget's coordinate system.
*/
public void scrollToCenter(float x, float y, float width, float height) {
float amountX = this.amountX;
if (x + width > amountX + areaWidth)
amountX = x + width - areaWidth;
if (x < amountX)
amountX = x;
scrollX(MathUtils.clamp(amountX, 0, maxX));
float amountY = this.amountY;
float centerY = maxY - y + areaHeight / 2 - height / 2;
if (amountY < centerY - areaHeight / 4 || amountY > centerY + areaHeight / 4)
amountY = centerY;
scrollY(MathUtils.clamp(amountY, 0, maxY));
}
/** Returns the maximum scroll value in the x direction. */
public float getMaxX() {
return maxX;
}
/** Returns the maximum scroll value in the y direction. */
public float getMaxY() {
return maxY;
}
public float getScrollBarHeight() {
return style.hScrollKnob == null || !scrollX ? 0 : style.hScrollKnob.getMinHeight();
}
public float getScrollBarWidth() {
return style.vScrollKnob == null || !scrollY ? 0 : style.vScrollKnob.getMinWidth();
}
public boolean isScrollX() {
return scrollX;
}
public boolean isScrollY() {
return scrollY;
}
/** Disables scrolling in a direction. The widget will be sized to the FlickScrollPane in the disabled direction. */
public void setScrollingDisabled(boolean x, boolean y) {
disableX = x;
disableY = y;
}
public boolean isDragging() {
return draggingPointer != -1;
}
public boolean isPanning() {
return flickScrollListener.getGestureDetector().isPanning();
}
public boolean isFlinging() {
return flingTimer > 0;
}
public void setVelocityX(float velocityX) {
this.velocityX = velocityX;
}
/** Gets the flick scroll y velocity. */
public float getVelocityX() {
if (flingTimer <= 0)
return 0;
float alpha = flingTimer / flingTime;
alpha = alpha * alpha * alpha;
return velocityX * alpha * alpha * alpha;
}
public void setVelocityY(float velocityY) {
this.velocityY = velocityY;
}
/** Gets the flick scroll y velocity. */
public float getVelocityY() {
return velocityY;
}
/**
* For flick scroll, if true the widget can be scrolled slightly past its bounds and will animate back to its bounds
* when scrolling is stopped. Default is true.
*/
public void setOverscroll(boolean overscrollX, boolean overscrollY) {
this.overscrollX = overscrollX;
this.overscrollY = overscrollY;
}
/**
* For flick scroll, sets the overscroll distance in pixels and the speed it returns to the widget's bounds in
* seconds. Default is 50, 30, 200.
*/
public void setupOverscroll(float distance, float speedMin, float speedMax) {
overscrollDistance = distance;
overscrollSpeedMin = speedMin;
overscrollSpeedMax = speedMax;
}
/**
* For flick scroll, forces the enabling of overscrolling in a direction, even if the contents do not exceed the
* bounds in that direction.
*/
public void setForceOverscroll(boolean x, boolean y) {
forceOverscrollX = x;
forceOverscrollY = y;
}
/** For flick scroll, sets the amount of time in seconds that a fling will continue to scroll. Default is 1. */
public void setFlingTime(float flingTime) {
this.flingTime = flingTime;
}
/** For flick scroll, prevents scrolling out of the widget's bounds. Default is true. */
public void setClamp(boolean clamp) {
this.clamp = clamp;
}
/** When true the scroll bars fade out after some time of not being used. */
public void setFadeScrollBars(boolean fadeScrollBars) {
if (this.fadeScrollBars == fadeScrollBars)
return;
this.fadeScrollBars = fadeScrollBars;
if (!fadeScrollBars)
fadeAlpha = fadeAlphaSeconds;
invalidate();
}
public void setupFadeScrollBars(float fadeAlphaSeconds, float fadeDelaySeconds) {
this.fadeAlphaSeconds = fadeAlphaSeconds;
this.fadeDelaySeconds = fadeDelaySeconds;
}
public void setSmoothScrolling(boolean smoothScrolling) {
this.smoothScrolling = smoothScrolling;
}
/**
* When false (the default), the widget is clipped so it is not drawn under the scrollbars. When true, the widget is
* clipped to the entire scroll pane bounds and the scrollbars are drawn on top of the widget. If
* {@link #setFadeScrollBars(boolean)} is true, the scroll bars are always drawn on top.
*/
public void setScrollbarsOnTop(boolean scrollbarsOnTop) {
this.scrollbarsOnTop = scrollbarsOnTop;
invalidate();
}
/**
* When true (default), the {@link Stage#cancelTouchFocus()} touch focus} is cancelled when flick scrolling begins.
* This causes widgets inside the scrollpane that have received touchDown to receive touchUp when flick scrolling
* begins.
*/
public void setCancelTouchFocus(boolean cancelTouchFocus) {
this.cancelTouchFocus = cancelTouchFocus;
}
/**
* The style for a scroll pane, see {@link ScrollPane}.
*
* @author mzechner
* @author Nathan Sweet
*/
static public class ScrollPaneStyle {
/** Optional. */
public Drawable background, corner;
/** Optional. */
public Drawable hScroll, hScrollKnob;
/** Optional. */
public Drawable vScroll, vScrollKnob;
public ScrollPaneStyle() {
}
public ScrollPaneStyle(Drawable background, Drawable hScroll, Drawable hScrollKnob, Drawable vScroll,
Drawable vScrollKnob) {
this.background = background;
this.hScroll = hScroll;
this.hScrollKnob = hScrollKnob;
this.vScroll = vScroll;
this.vScrollKnob = vScrollKnob;
}
public ScrollPaneStyle(ScrollPaneStyle style) {
this.background = style.background;
this.hScroll = style.hScroll;
this.hScrollKnob = style.hScrollKnob;
this.vScroll = style.vScroll;
this.vScrollKnob = style.vScrollKnob;
}
}
}