1 /*******************************************************************************
\r
2 * Copyright 2011 See AUTHORS file.
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
15 ******************************************************************************/
\r
17 package com.badlogic.gdx.scenes.scene2d.ui;
\r
19 import com.badlogic.gdx.graphics.Color;
\r
20 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
\r
21 import com.badlogic.gdx.math.Interpolation;
\r
22 import com.badlogic.gdx.math.MathUtils;
\r
23 import com.badlogic.gdx.math.Rectangle;
\r
24 import com.badlogic.gdx.math.Vector2;
\r
25 import com.badlogic.gdx.scenes.scene2d.Actor;
\r
26 import com.badlogic.gdx.scenes.scene2d.Event;
\r
27 import com.badlogic.gdx.scenes.scene2d.InputEvent;
\r
28 import com.badlogic.gdx.scenes.scene2d.InputListener;
\r
29 import com.badlogic.gdx.scenes.scene2d.Stage;
\r
30 import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
\r
31 import com.badlogic.gdx.scenes.scene2d.utils.Cullable;
\r
32 import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
\r
33 import com.badlogic.gdx.scenes.scene2d.utils.Layout;
\r
34 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
\r
36 /** A group that scrolls a child widget using scrollbars and/or mouse or touch dragging.
\r
38 * 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,
\r
39 * it is set to the size of this scroll pane. Scrollbars appear when the widget is larger than the scroll pane.
\r
41 * 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
\r
42 * scroll pane is typically sized by ignoring the preferred size in one or both directions.
\r
44 * @author Nathan Sweet */
\r
45 public class ScrollPane extends WidgetGroup {
\r
46 private ScrollPaneStyle style;
\r
47 private Actor widget;
\r
49 final Rectangle hScrollBounds = new Rectangle();
\r
50 final Rectangle vScrollBounds = new Rectangle();
\r
51 final Rectangle hKnobBounds = new Rectangle();
\r
52 final Rectangle vKnobBounds = new Rectangle();
\r
53 private final Rectangle widgetAreaBounds = new Rectangle();
\r
54 private final Rectangle widgetCullingArea = new Rectangle();
\r
55 private final Rectangle scissorBounds = new Rectangle();
\r
56 private ActorGestureListener flickScrollListener;
\r
58 boolean scrollX, scrollY;
\r
59 float amountX, amountY;
\r
60 float visualAmountX, visualAmountY;
\r
62 boolean touchScrollH, touchScrollV;
\r
63 final Vector2 lastPoint = new Vector2();
\r
64 float areaWidth, areaHeight;
\r
65 private boolean fadeScrollBars = true, smoothScrolling = true;
\r
66 float fadeAlpha, fadeAlphaSeconds = 1, fadeDelay, fadeDelaySeconds = 1;
\r
67 boolean cancelTouchFocus = true;
\r
69 boolean flickScroll = true;
\r
70 float velocityX, velocityY;
\r
72 private boolean overscrollX = true, overscrollY = true;
\r
73 float flingTime = 1f;
\r
74 private float overscrollDistance = 50, overscrollSpeedMin = 30, overscrollSpeedMax = 200;
\r
75 private boolean forceScrollX, forceScrollY;
\r
76 private boolean disableX, disableY;
\r
77 private boolean clamp = true;
\r
78 private boolean scrollbarsOnTop;
\r
79 int draggingPointer = -1;
\r
81 /** @param widget May be null. */
\r
82 public ScrollPane (Actor widget) {
\r
83 this(widget, new ScrollPaneStyle());
\r
86 /** @param widget May be null. */
\r
87 public ScrollPane (Actor widget, Skin skin) {
\r
88 this(widget, skin.get(ScrollPaneStyle.class));
\r
91 /** @param widget May be null. */
\r
92 public ScrollPane (Actor widget, Skin skin, String styleName) {
\r
93 this(widget, skin.get(styleName, ScrollPaneStyle.class));
\r
96 /** @param widget May be null. */
\r
97 public ScrollPane (Actor widget, ScrollPaneStyle style) {
\r
98 if (style == null) throw new IllegalArgumentException("style cannot be null.");
\r
104 addCaptureListener(new InputListener() {
\r
105 private float handlePosition;
\r
107 public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
\r
108 if (draggingPointer != -1) return false;
\r
109 if (pointer == 0 && button != 0) return false;
\r
110 getStage().setScrollFocus(ScrollPane.this);
\r
112 if (!flickScroll) resetFade();
\r
114 if (fadeAlpha == 0) return false;
\r
116 if (scrollX && hScrollBounds.contains(x, y)) {
\r
119 if (hKnobBounds.contains(x, y)) {
\r
120 lastPoint.set(x, y);
\r
121 handlePosition = hKnobBounds.x;
\r
122 touchScrollH = true;
\r
123 draggingPointer = pointer;
\r
126 setScrollX(amountX + getPageScrollX() * (x < hKnobBounds.x ? -1 : 1));
\r
129 if (scrollY && vScrollBounds.contains(x, y)) {
\r
132 if (vKnobBounds.contains(x, y)) {
\r
133 lastPoint.set(x, y);
\r
134 handlePosition = vKnobBounds.y;
\r
135 touchScrollV = true;
\r
136 draggingPointer = pointer;
\r
139 setScrollY(amountY + getPageScrollY() * (y < vKnobBounds.y ? 1 : -1));
\r
145 public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
\r
146 if (pointer != draggingPointer) return;
\r
150 public void touchDragged (InputEvent event, float x, float y, int pointer) {
\r
151 if (pointer != draggingPointer) return;
\r
152 if (touchScrollH) {
\r
153 float delta = x - lastPoint.x;
\r
154 float scrollH = handlePosition + delta;
\r
155 handlePosition = scrollH;
\r
156 scrollH = Math.max(hScrollBounds.x, scrollH);
\r
157 scrollH = Math.min(hScrollBounds.x + hScrollBounds.width - hKnobBounds.width, scrollH);
\r
158 float total = hScrollBounds.width - hKnobBounds.width;
\r
159 if (total != 0) setScrollPercentX((scrollH - hScrollBounds.x) / total);
\r
160 lastPoint.set(x, y);
\r
161 } else if (touchScrollV) {
\r
162 float delta = y - lastPoint.y;
\r
163 float scrollV = handlePosition + delta;
\r
164 handlePosition = scrollV;
\r
165 scrollV = Math.max(vScrollBounds.y, scrollV);
\r
166 scrollV = Math.min(vScrollBounds.y + vScrollBounds.height - vKnobBounds.height, scrollV);
\r
167 float total = vScrollBounds.height - vKnobBounds.height;
\r
168 if (total != 0) setScrollPercentY(1 - ((scrollV - vScrollBounds.y) / total));
\r
169 lastPoint.set(x, y);
\r
173 public boolean mouseMoved (InputEvent event, float x, float y) {
\r
174 if (!flickScroll) resetFade();
\r
179 flickScrollListener = new ActorGestureListener() {
\r
180 public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
\r
185 cancelTouchFocusedChild(event);
\r
188 public void fling (InputEvent event, float x, float y, int button) {
\r
189 if (Math.abs(x) > 150) {
\r
190 flingTimer = flingTime;
\r
192 cancelTouchFocusedChild(event);
\r
194 if (Math.abs(y) > 150) {
\r
195 flingTimer = flingTime;
\r
197 cancelTouchFocusedChild(event);
\r
201 public boolean handle (Event event) {
\r
202 if (super.handle(event)) {
\r
203 if (((InputEvent)event).getType() == InputEvent.Type.touchDown) flingTimer = 0;
\r
209 addListener(flickScrollListener);
\r
211 addListener(new InputListener() {
\r
212 public boolean scrolled (InputEvent event, float x, float y, int amount) {
\r
215 setScrollY(amountY + Math.max(areaHeight * 0.9f, maxY * 0.1f) / 4 * amount);
\r
216 else if (scrollX) //
\r
217 setScrollX(amountX + Math.max(areaWidth * 0.9f, maxX * 0.1f) / 4 * amount);
\r
223 void resetFade () {
\r
224 fadeAlpha = fadeAlphaSeconds;
\r
225 fadeDelay = fadeDelaySeconds;
\r
228 void cancelTouchFocusedChild (InputEvent event) {
\r
229 if (!cancelTouchFocus) return;
\r
230 Stage stage = getStage();
\r
231 if (stage != null) stage.cancelTouchFocus(flickScrollListener, this);
\r
234 /** If currently scrolling by tracking a touch down, stop scrolling. */
\r
235 public void cancel () {
\r
236 draggingPointer = -1;
\r
237 touchScrollH = false;
\r
238 touchScrollV = false;
\r
239 flickScrollListener.getGestureDetector().cancel();
\r
243 if (!clamp) return;
\r
244 scrollX(overscrollX ? MathUtils.clamp(amountX, -overscrollDistance, maxX + overscrollDistance) : MathUtils.clamp(amountX,
\r
246 scrollY(overscrollY ? MathUtils.clamp(amountY, -overscrollDistance, maxY + overscrollDistance) : MathUtils.clamp(amountY,
\r
250 public void setStyle (ScrollPaneStyle style) {
\r
251 if (style == null) throw new IllegalArgumentException("style cannot be null.");
\r
252 this.style = style;
\r
253 invalidateHierarchy();
\r
256 /** Returns the scroll pane's style. Modifying the returned style may not have an effect until
\r
257 * {@link #setStyle(ScrollPaneStyle)} is called. */
\r
258 public ScrollPaneStyle getStyle () {
\r
262 public void act (float delta) {
\r
265 boolean panning = flickScrollListener.getGestureDetector().isPanning();
\r
267 if (fadeAlpha > 0 && fadeScrollBars && !panning && !touchScrollH && !touchScrollV) {
\r
268 fadeDelay -= delta;
\r
269 if (fadeDelay <= 0) fadeAlpha = Math.max(0, fadeAlpha - delta);
\r
272 if (flingTimer > 0) {
\r
275 float alpha = flingTimer / flingTime;
\r
276 amountX -= velocityX * alpha * delta;
\r
277 amountY -= velocityY * alpha * delta;
\r
280 // Stop fling if hit overscroll distance.
\r
281 if (amountX == -overscrollDistance) velocityX = 0;
\r
282 if (amountX >= maxX + overscrollDistance) velocityX = 0;
\r
283 if (amountY == -overscrollDistance) velocityY = 0;
\r
284 if (amountY >= maxY + overscrollDistance) velocityY = 0;
\r
286 flingTimer -= delta;
\r
287 if (flingTimer <= 0) {
\r
293 if (smoothScrolling && flingTimer <= 0 && !touchScrollH && !touchScrollV && !panning) {
\r
294 if (visualAmountX != amountX) {
\r
295 if (visualAmountX < amountX)
\r
296 visualScrollX(Math.min(amountX, visualAmountX + Math.max(150 * delta, (amountX - visualAmountX) * 5 * delta)));
\r
298 visualScrollX(Math.max(amountX, visualAmountX - Math.max(150 * delta, (visualAmountX - amountX) * 5 * delta)));
\r
300 if (visualAmountY != amountY) {
\r
301 if (visualAmountY < amountY)
\r
302 visualScrollY(Math.min(amountY, visualAmountY + Math.max(150 * delta, (amountY - visualAmountY) * 5 * delta)));
\r
304 visualScrollY(Math.max(amountY, visualAmountY - Math.max(150 * delta, (visualAmountY - amountY) * 5 * delta)));
\r
307 if (visualAmountX != amountX) visualScrollX(amountX);
\r
308 if (visualAmountY != amountY) visualScrollY(amountY);
\r
312 if (overscrollX && scrollX) {
\r
315 amountX += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountX / overscrollDistance)
\r
317 if (amountX > 0) scrollX(0);
\r
318 } else if (amountX > maxX) {
\r
320 amountX -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxX - amountX)
\r
321 / overscrollDistance)
\r
323 if (amountX < maxX) scrollX(maxX);
\r
326 if (overscrollY && scrollY) {
\r
329 amountY += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountY / overscrollDistance)
\r
331 if (amountY > 0) scrollY(0);
\r
332 } else if (amountY > maxY) {
\r
334 amountY -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxY - amountY)
\r
335 / overscrollDistance)
\r
337 if (amountY < maxY) scrollY(maxY);
\r
343 public void layout () {
\r
344 final Drawable bg = style.background;
\r
345 final Drawable hScrollKnob = style.hScrollKnob;
\r
346 final Drawable vScrollKnob = style.vScrollKnob;
\r
348 float bgLeftWidth = 0, bgRightWidth = 0, bgTopHeight = 0, bgBottomHeight = 0;
\r
350 bgLeftWidth = bg.getLeftWidth();
\r
351 bgRightWidth = bg.getRightWidth();
\r
352 bgTopHeight = bg.getTopHeight();
\r
353 bgBottomHeight = bg.getBottomHeight();
\r
356 float width = getWidth();
\r
357 float height = getHeight();
\r
359 float scrollbarHeight = 0;
\r
360 if (hScrollKnob != null) scrollbarHeight = hScrollKnob.getMinHeight();
\r
361 if (style.hScroll != null) scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
\r
362 float scrollbarWidth = 0;
\r
363 if (vScrollKnob != null) scrollbarWidth = vScrollKnob.getMinWidth();
\r
364 if (style.vScroll != null) scrollbarWidth = Math.max(scrollbarWidth, style.vScroll.getMinWidth());
\r
366 // Get available space size by subtracting background's padded area.
\r
367 areaWidth = width - bgLeftWidth - bgRightWidth;
\r
368 areaHeight = height - bgTopHeight - bgBottomHeight;
\r
370 if (widget == null) return;
\r
372 // Get widget's desired width.
\r
373 float widgetWidth, widgetHeight;
\r
374 if (widget instanceof Layout) {
\r
375 Layout layout = (Layout)widget;
\r
376 widgetWidth = layout.getPrefWidth();
\r
377 widgetHeight = layout.getPrefHeight();
\r
379 widgetWidth = widget.getWidth();
\r
380 widgetHeight = widget.getHeight();
\r
383 // Determine if horizontal/vertical scrollbars are needed.
\r
384 scrollX = forceScrollX || (widgetWidth > areaWidth && !disableX);
\r
385 scrollY = forceScrollY || (widgetHeight > areaHeight && !disableY);
\r
387 boolean fade = fadeScrollBars;
\r
389 // Check again, now taking into account the area that's taken up by any enabled scrollbars.
\r
391 areaWidth -= scrollbarWidth;
\r
392 if (!scrollX && widgetWidth > areaWidth && !disableX) {
\r
397 areaHeight -= scrollbarHeight;
\r
398 if (!scrollY && widgetHeight > areaHeight && !disableY) {
\r
400 areaWidth -= scrollbarWidth;
\r
405 // Set the widget area bounds.
\r
406 widgetAreaBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, areaHeight);
\r
409 // Make sure widget is drawn under fading scrollbars.
\r
410 if (scrollX) areaHeight -= scrollbarHeight;
\r
411 if (scrollY) areaWidth -= scrollbarWidth;
\r
413 if (scrollbarsOnTop) {
\r
414 // Make sure widget is drawn under non-fading scrollbars.
\r
415 if (scrollX) widgetAreaBounds.height += scrollbarHeight;
\r
416 if (scrollY) widgetAreaBounds.width += scrollbarWidth;
\r
418 // Offset widget area y for horizontal scrollbar.
\r
419 if (scrollX) widgetAreaBounds.y += scrollbarHeight;
\r
423 // If the widget is smaller than the available space, make it take up the available space.
\r
424 widgetWidth = disableX ? width : Math.max(areaWidth, widgetWidth);
\r
425 widgetHeight = disableY ? height : Math.max(areaHeight, widgetHeight);
\r
427 maxX = widgetWidth - areaWidth;
\r
428 maxY = widgetHeight - areaHeight;
\r
430 // Make sure widget is drawn under fading scrollbars.
\r
431 if (scrollX) maxY -= scrollbarHeight;
\r
432 if (scrollY) maxX -= scrollbarWidth;
\r
434 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
435 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
437 // Set the bounds and scroll knob sizes if scrollbars are needed.
\r
439 if (hScrollKnob != null) {
\r
440 float hScrollHeight = style.hScroll != null ? style.hScroll.getMinHeight() : hScrollKnob.getMinHeight();
\r
441 hScrollBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, hScrollHeight);
\r
442 hKnobBounds.width = Math.max(hScrollKnob.getMinWidth(), (int)(hScrollBounds.width * areaWidth / widgetWidth));
\r
443 hKnobBounds.height = hScrollKnob.getMinHeight();
\r
444 hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
\r
445 hKnobBounds.y = hScrollBounds.y;
\r
447 hScrollBounds.set(0, 0, 0, 0);
\r
448 hKnobBounds.set(0, 0, 0, 0);
\r
452 if (vScrollKnob != null) {
\r
453 float vScrollWidth = style.vScroll != null ? style.vScroll.getMinWidth() : vScrollKnob.getMinWidth();
\r
454 vScrollBounds.set(width - bgRightWidth - vScrollWidth, height - bgTopHeight - areaHeight, vScrollWidth, areaHeight);
\r
455 vKnobBounds.width = vScrollKnob.getMinWidth();
\r
456 vKnobBounds.height = Math.max(vScrollKnob.getMinHeight(), (int)(vScrollBounds.height * areaHeight / widgetHeight));
\r
457 vKnobBounds.x = width - bgRightWidth - vScrollKnob.getMinWidth();
\r
458 vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
\r
460 vScrollBounds.set(0, 0, 0, 0);
\r
461 vKnobBounds.set(0, 0, 0, 0);
\r
465 if (widget.getWidth() != widgetWidth || widget.getHeight() != widgetHeight) {
\r
466 widget.setWidth(widgetWidth);
\r
467 widget.setHeight(widgetHeight);
\r
468 if (widget instanceof Layout) {
\r
469 Layout layout = (Layout)widget;
\r
470 layout.invalidate();
\r
474 if (widget instanceof Layout) ((Layout)widget).validate();
\r
479 public void draw (SpriteBatch batch, float parentAlpha) {
\r
480 if (widget == null) return;
\r
484 // Setup transform for this group.
\r
485 applyTransform(batch, computeTransform());
\r
487 if (scrollX) hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
\r
489 vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
\r
491 // Calculate the widget's position depending on the scroll state and available widget area.
\r
492 float y = widgetAreaBounds.y;
\r
496 y -= (int)(maxY - visualAmountY);
\r
498 if (!fadeScrollBars && scrollbarsOnTop && scrollX) {
\r
499 float scrollbarHeight = 0;
\r
500 if (style.hScrollKnob != null) scrollbarHeight = style.hScrollKnob.getMinHeight();
\r
501 if (style.hScroll != null) scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
\r
502 y += scrollbarHeight;
\r
505 float x = widgetAreaBounds.x;
\r
506 if (scrollX) x -= (int)visualAmountX;
\r
507 widget.setPosition(x, y);
\r
509 if (widget instanceof Cullable) {
\r
510 widgetCullingArea.x = -widget.getX() + widgetAreaBounds.x;
\r
511 widgetCullingArea.y = -widget.getY() + widgetAreaBounds.y;
\r
512 widgetCullingArea.width = widgetAreaBounds.width;
\r
513 widgetCullingArea.height = widgetAreaBounds.height;
\r
514 ((Cullable)widget).setCullingArea(widgetCullingArea);
\r
517 // Caculate the scissor bounds based on the batch transform, the available widget area and the camera transform. We need to
\r
518 // project those to screen coordinates for OpenGL ES to consume.
\r
519 ScissorStack.calculateScissors(getStage().getCamera(), batch.getTransformMatrix(), widgetAreaBounds, scissorBounds);
\r
521 // Draw the background ninepatch.
\r
522 Color color = getColor();
\r
523 batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
\r
524 if (style.background != null) style.background.draw(batch, 0, 0, getWidth(), getHeight());
\r
527 // Enable scissors for widget area and draw the widget.
\r
528 if (ScissorStack.pushScissors(scissorBounds)) {
\r
529 drawChildren(batch, parentAlpha);
\r
530 ScissorStack.popScissors();
\r
533 // Render scrollbars and knobs on top.
\r
534 batch.setColor(color.r, color.g, color.b, color.a * parentAlpha * Interpolation.fade.apply(fadeAlpha / fadeAlphaSeconds));
\r
535 if (scrollX && scrollY) {
\r
536 if (style.corner != null) {
\r
538 .draw(batch, hScrollBounds.x + hScrollBounds.width, hScrollBounds.y, vScrollBounds.width, vScrollBounds.y);
\r
542 if (style.hScroll != null)
\r
543 style.hScroll.draw(batch, hScrollBounds.x, hScrollBounds.y, hScrollBounds.width, hScrollBounds.height);
\r
544 if (style.hScrollKnob != null)
\r
545 style.hScrollKnob.draw(batch, hKnobBounds.x, hKnobBounds.y, hKnobBounds.width, hKnobBounds.height);
\r
548 if (style.vScroll != null)
\r
549 style.vScroll.draw(batch, vScrollBounds.x, vScrollBounds.y, vScrollBounds.width, vScrollBounds.height);
\r
550 if (style.vScrollKnob != null)
\r
551 style.vScrollKnob.draw(batch, vKnobBounds.x, vKnobBounds.y, vKnobBounds.width, vKnobBounds.height);
\r
554 resetTransform(batch);
\r
557 public float getPrefWidth () {
\r
558 if (widget instanceof Layout) {
\r
559 float width = ((Layout)widget).getPrefWidth();
\r
560 if (style.background != null) width += style.background.getLeftWidth() + style.background.getRightWidth();
\r
566 public float getPrefHeight () {
\r
567 if (widget instanceof Layout) {
\r
568 float height = ((Layout)widget).getPrefHeight();
\r
569 if (style.background != null) height += style.background.getTopHeight() + style.background.getBottomHeight();
\r
575 public float getMinWidth () {
\r
579 public float getMinHeight () {
\r
583 /** Sets the {@link Actor} embedded in this scroll pane.
\r
584 * @param widget May be null to remove any current actor. */
\r
585 public void setWidget (Actor widget) {
\r
586 if (widget == this) throw new IllegalArgumentException("widget cannot be same object");
\r
587 if (this.widget != null) super.removeActor(this.widget);
\r
588 this.widget = widget;
\r
589 if (widget != null) super.addActor(widget);
\r
592 /** Returns the actor embedded in this scroll pane, or null. */
\r
593 public Actor getWidget () {
\r
598 public void addActor (Actor actor) {
\r
599 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
603 public void addActorAt (int index, Actor actor) {
\r
604 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
608 public void addActorBefore (Actor actorBefore, Actor actor) {
\r
609 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
613 public void addActorAfter (Actor actorAfter, Actor actor) {
\r
614 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
617 public boolean removeActor (Actor actor) {
\r
618 if (actor != widget) return false;
\r
623 public Actor hit (float x, float y, boolean touchable) {
\r
624 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return null;
\r
625 if (scrollX && hScrollBounds.contains(x, y)) return this;
\r
626 if (scrollY && vScrollBounds.contains(x, y)) return this;
\r
627 return super.hit(x, y, touchable);
\r
630 /** Called whenever the x scroll amount is changed. */
\r
631 protected void scrollX (float pixelsX) {
\r
632 this.amountX = pixelsX;
\r
635 /** Called whenever the y scroll amount is changed. */
\r
636 protected void scrollY (float pixelsY) {
\r
637 this.amountY = pixelsY;
\r
640 /** Called whenever the visual x scroll amount is changed. */
\r
641 protected void visualScrollX (float pixelsX) {
\r
642 this.visualAmountX = pixelsX;
\r
645 /** Called whenever the visual y scroll amount is changed. */
\r
646 protected void visualScrollY (float pixelsY) {
\r
647 this.visualAmountY = pixelsY;
\r
650 /** Returns the amount to scroll horizontally when the scrollbar is clicked. */
\r
651 protected float getPageScrollX () {
\r
655 /** Returns the amount to scroll vertically when the scrollbar is clicked. */
\r
656 protected float getPageScrollY () {
\r
660 public void setScrollX (float pixels) {
\r
661 scrollX(MathUtils.clamp(pixels, 0, maxX));
\r
664 /** Returns the x scroll position in pixels. */
\r
665 public float getScrollX () {
\r
669 public void setScrollY (float pixels) {
\r
670 scrollY(MathUtils.clamp(pixels, 0, maxY));
\r
673 /** Returns the y scroll position in pixels. */
\r
674 public float getScrollY () {
\r
678 /** Sets the visual scroll amount equal to the scroll amount. This can be used when setting the scroll amount without animating. */
\r
679 public void updateVisualScroll () {
\r
680 visualAmountX = amountX;
\r
681 visualAmountY = amountY;
\r
684 public float getVisualScrollX () {
\r
685 return !scrollX ? 0 : visualAmountX;
\r
688 public float getVisualScrollY () {
\r
689 return !scrollY ? 0 : visualAmountY;
\r
692 public float getScrollPercentX () {
\r
693 return MathUtils.clamp(amountX / maxX, 0, 1);
\r
696 public void setScrollPercentX (float percentX) {
\r
697 scrollX(maxX * MathUtils.clamp(percentX, 0, 1));
\r
700 public float getScrollPercentY () {
\r
701 return MathUtils.clamp(amountY / maxY, 0, 1);
\r
704 public void setScrollPercentY (float percentY) {
\r
705 scrollY(maxY * MathUtils.clamp(percentY, 0, 1));
\r
708 public void setFlickScroll (boolean flickScroll) {
\r
709 if (this.flickScroll == flickScroll) return;
\r
710 this.flickScroll = flickScroll;
\r
712 addListener(flickScrollListener);
\r
714 removeListener(flickScrollListener);
\r
718 /** Sets the scroll offset so the specified rectangle is fully in view, if possible. Coordinates are in the scroll pane widget's
\r
719 * coordinate system. */
\r
720 public void scrollTo (float x, float y, float width, float height) {
\r
721 float amountX = this.amountX;
\r
722 if (x + width > amountX + areaWidth) amountX = x + width - areaWidth;
\r
723 if (x < amountX) amountX = x;
\r
724 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
726 float amountY = this.amountY;
\r
727 if (amountY > maxY - y - height + areaHeight) amountY = maxY - y - height + areaHeight;
\r
728 if (amountY < maxY - y) amountY = maxY - y;
\r
729 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
732 /** Sets the scroll offset so the specified rectangle is fully in view and centered vertically in the scroll pane, if possible.
\r
733 * Coordinates are in the scroll pane widget's coordinate system. */
\r
734 public void scrollToCenter (float x, float y, float width, float height) {
\r
735 float amountX = this.amountX;
\r
736 if (x + width > amountX + areaWidth) amountX = x + width - areaWidth;
\r
737 if (x < amountX) amountX = x;
\r
738 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
740 float amountY = this.amountY;
\r
741 float centerY = maxY - y + areaHeight / 2 - height / 2;
\r
742 if (amountY < centerY - areaHeight / 4 || amountY > centerY + areaHeight / 4) amountY = centerY;
\r
743 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
746 /** Returns the maximum scroll value in the x direction. */
\r
747 public float getMaxX () {
\r
751 /** Returns the maximum scroll value in the y direction. */
\r
752 public float getMaxY () {
\r
756 public float getScrollBarHeight () {
\r
757 return style.hScrollKnob == null || !scrollX ? 0 : style.hScrollKnob.getMinHeight();
\r
760 public float getScrollBarWidth () {
\r
761 return style.vScrollKnob == null || !scrollY ? 0 : style.vScrollKnob.getMinWidth();
\r
764 public boolean isScrollX () {
\r
768 public boolean isScrollY () {
\r
772 /** Disables scrolling in a direction. The widget will be sized to the FlickScrollPane in the disabled direction. */
\r
773 public void setScrollingDisabled (boolean x, boolean y) {
\r
778 public boolean isDragging () {
\r
779 return draggingPointer != -1;
\r
782 public boolean isPanning () {
\r
783 return flickScrollListener.getGestureDetector().isPanning();
\r
786 public boolean isFlinging () {
\r
787 return flingTimer > 0;
\r
790 public void setVelocityX (float velocityX) {
\r
791 this.velocityX = velocityX;
\r
794 /** Gets the flick scroll y velocity. */
\r
795 public float getVelocityX () {
\r
796 if (flingTimer <= 0) return 0;
\r
797 float alpha = flingTimer / flingTime;
\r
798 alpha = alpha * alpha * alpha;
\r
799 return velocityX * alpha * alpha * alpha;
\r
802 public void setVelocityY (float velocityY) {
\r
803 this.velocityY = velocityY;
\r
806 /** Gets the flick scroll y velocity. */
\r
807 public float getVelocityY () {
\r
811 /** For flick scroll, if true the widget can be scrolled slightly past its bounds and will animate back to its bounds when
\r
812 * scrolling is stopped. Default is true. */
\r
813 public void setOverscroll (boolean overscrollX, boolean overscrollY) {
\r
814 this.overscrollX = overscrollX;
\r
815 this.overscrollY = overscrollY;
\r
818 /** For flick scroll, sets the overscroll distance in pixels and the speed it returns to the widget's bounds in seconds. Default
\r
819 * is 50, 30, 200. */
\r
820 public void setupOverscroll (float distance, float speedMin, float speedMax) {
\r
821 overscrollDistance = distance;
\r
822 overscrollSpeedMin = speedMin;
\r
823 overscrollSpeedMax = speedMax;
\r
826 /** Forces enabling scrollbars (for non-flick scroll) and overscrolling (for flick scroll) in a direction, even if the contents
\r
827 * do not exceed the bounds in that direction. */
\r
828 public void setForceScroll (boolean x, boolean y) {
\r
833 public boolean isForceScrollX () {
\r
834 return forceScrollX;
\r
837 public boolean isForceScrollY () {
\r
838 return forceScrollY;
\r
841 /** For flick scroll, sets the amount of time in seconds that a fling will continue to scroll. Default is 1. */
\r
842 public void setFlingTime (float flingTime) {
\r
843 this.flingTime = flingTime;
\r
846 /** For flick scroll, prevents scrolling out of the widget's bounds. Default is true. */
\r
847 public void setClamp (boolean clamp) {
\r
848 this.clamp = clamp;
\r
851 /** When true the scroll bars fade out after some time of not being used. */
\r
852 public void setFadeScrollBars (boolean fadeScrollBars) {
\r
853 if (this.fadeScrollBars == fadeScrollBars) return;
\r
854 this.fadeScrollBars = fadeScrollBars;
\r
855 if (!fadeScrollBars) fadeAlpha = fadeAlphaSeconds;
\r
859 public void setupFadeScrollBars (float fadeAlphaSeconds, float fadeDelaySeconds) {
\r
860 this.fadeAlphaSeconds = fadeAlphaSeconds;
\r
861 this.fadeDelaySeconds = fadeDelaySeconds;
\r
864 public void setSmoothScrolling (boolean smoothScrolling) {
\r
865 this.smoothScrolling = smoothScrolling;
\r
868 /** When false (the default), the widget is clipped so it is not drawn under the scrollbars. When true, the widget is clipped to
\r
869 * the entire scroll pane bounds and the scrollbars are drawn on top of the widget. If {@link #setFadeScrollBars(boolean)} is
\r
870 * true, the scroll bars are always drawn on top. */
\r
871 public void setScrollbarsOnTop (boolean scrollbarsOnTop) {
\r
872 this.scrollbarsOnTop = scrollbarsOnTop;
\r
876 /** When true (default), the {@link Stage#cancelTouchFocus()} touch focus} is cancelled when flick scrolling begins. This causes
\r
877 * widgets inside the scrollpane that have received touchDown to receive touchUp when flick scrolling begins. */
\r
878 public void setCancelTouchFocus (boolean cancelTouchFocus) {
\r
879 this.cancelTouchFocus = cancelTouchFocus;
\r
882 /** The style for a scroll pane, see {@link ScrollPane}.
\r
884 * @author Nathan Sweet */
\r
885 static public class ScrollPaneStyle {
\r
887 public Drawable background, corner;
\r
889 public Drawable hScroll, hScrollKnob;
\r
891 public Drawable vScroll, vScrollKnob;
\r
893 public ScrollPaneStyle () {
\r
896 public ScrollPaneStyle (Drawable background, Drawable hScroll, Drawable hScrollKnob, Drawable vScroll, Drawable vScrollKnob) {
\r
897 this.background = background;
\r
898 this.hScroll = hScroll;
\r
899 this.hScrollKnob = hScrollKnob;
\r
900 this.vScroll = vScroll;
\r
901 this.vScrollKnob = vScrollKnob;
\r
904 public ScrollPaneStyle (ScrollPaneStyle style) {
\r
905 this.background = style.background;
\r
906 this.hScroll = style.hScroll;
\r
907 this.hScrollKnob = style.hScrollKnob;
\r
908 this.vScroll = style.vScroll;
\r
909 this.vScrollKnob = style.vScrollKnob;
\r