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 boolean vScrollOnRight = true;
\r
60 boolean hScrollOnBottom = true;
\r
61 float amountX, amountY;
\r
62 float visualAmountX, visualAmountY;
\r
64 boolean touchScrollH, touchScrollV;
\r
65 final Vector2 lastPoint = new Vector2();
\r
66 float areaWidth, areaHeight;
\r
67 private boolean fadeScrollBars = true, smoothScrolling = true;
\r
68 float fadeAlpha, fadeAlphaSeconds = 1, fadeDelay, fadeDelaySeconds = 1;
\r
69 boolean cancelTouchFocus = true;
\r
71 boolean flickScroll = true;
\r
72 float velocityX, velocityY;
\r
74 private boolean overscrollX = true, overscrollY = true;
\r
75 float flingTime = 1f;
\r
76 private float overscrollDistance = 50, overscrollSpeedMin = 30, overscrollSpeedMax = 200;
\r
77 private boolean forceScrollX, forceScrollY;
\r
78 private boolean disableX, disableY;
\r
79 private boolean clamp = true;
\r
80 private boolean scrollbarsOnTop;
\r
81 int draggingPointer = -1;
\r
83 /** @param widget May be null. */
\r
84 public ScrollPane (Actor widget) {
\r
85 this(widget, new ScrollPaneStyle());
\r
88 /** @param widget May be null. */
\r
89 public ScrollPane (Actor widget, Skin skin) {
\r
90 this(widget, skin.get(ScrollPaneStyle.class));
\r
93 /** @param widget May be null. */
\r
94 public ScrollPane (Actor widget, Skin skin, String styleName) {
\r
95 this(widget, skin.get(styleName, ScrollPaneStyle.class));
\r
98 /** @param widget May be null. */
\r
99 public ScrollPane (Actor widget, ScrollPaneStyle style) {
\r
100 if (style == null) throw new IllegalArgumentException("style cannot be null.");
\r
101 this.style = style;
\r
106 addCaptureListener(new InputListener() {
\r
107 private float handlePosition;
\r
109 public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
\r
110 if (draggingPointer != -1) return false;
\r
111 if (pointer == 0 && button != 0) return false;
\r
112 getStage().setScrollFocus(ScrollPane.this);
\r
114 if (!flickScroll) resetFade();
\r
116 if (fadeAlpha == 0) return false;
\r
118 if (scrollX && hScrollBounds.contains(x, y)) {
\r
121 if (hKnobBounds.contains(x, y)) {
\r
122 lastPoint.set(x, y);
\r
123 handlePosition = hKnobBounds.x;
\r
124 touchScrollH = true;
\r
125 draggingPointer = pointer;
\r
128 setScrollX(amountX + areaWidth * (x < hKnobBounds.x ? -1 : 1));
\r
131 if (scrollY && vScrollBounds.contains(x, y)) {
\r
134 if (vKnobBounds.contains(x, y)) {
\r
135 lastPoint.set(x, y);
\r
136 handlePosition = vKnobBounds.y;
\r
137 touchScrollV = true;
\r
138 draggingPointer = pointer;
\r
141 setScrollY(amountY + areaHeight * (y < vKnobBounds.y ? 1 : -1));
\r
147 public void touchUp (InputEvent event, float x, float y, int pointer, int button) {
\r
148 if (pointer != draggingPointer) return;
\r
152 public void touchDragged (InputEvent event, float x, float y, int pointer) {
\r
153 if (pointer != draggingPointer) return;
\r
154 if (touchScrollH) {
\r
155 float delta = x - lastPoint.x;
\r
156 float scrollH = handlePosition + delta;
\r
157 handlePosition = scrollH;
\r
158 scrollH = Math.max(hScrollBounds.x, scrollH);
\r
159 scrollH = Math.min(hScrollBounds.x + hScrollBounds.width - hKnobBounds.width, scrollH);
\r
160 float total = hScrollBounds.width - hKnobBounds.width;
\r
161 if (total != 0) setScrollPercentX((scrollH - hScrollBounds.x) / total);
\r
162 lastPoint.set(x, y);
\r
163 } else if (touchScrollV) {
\r
164 float delta = y - lastPoint.y;
\r
165 float scrollV = handlePosition + delta;
\r
166 handlePosition = scrollV;
\r
167 scrollV = Math.max(vScrollBounds.y, scrollV);
\r
168 scrollV = Math.min(vScrollBounds.y + vScrollBounds.height - vKnobBounds.height, scrollV);
\r
169 float total = vScrollBounds.height - vKnobBounds.height;
\r
170 if (total != 0) setScrollPercentY(1 - ((scrollV - vScrollBounds.y) / total));
\r
171 lastPoint.set(x, y);
\r
175 public boolean mouseMoved (InputEvent event, float x, float y) {
\r
176 if (!flickScroll) resetFade();
\r
181 flickScrollListener = new ActorGestureListener() {
\r
182 public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {
\r
187 cancelTouchFocusedChild(event);
\r
190 public void fling (InputEvent event, float x, float y, int button) {
\r
191 if (Math.abs(x) > 150) {
\r
192 flingTimer = flingTime;
\r
194 cancelTouchFocusedChild(event);
\r
196 if (Math.abs(y) > 150) {
\r
197 flingTimer = flingTime;
\r
199 cancelTouchFocusedChild(event);
\r
203 public boolean handle (Event event) {
\r
204 if (super.handle(event)) {
\r
205 if (((InputEvent)event).getType() == InputEvent.Type.touchDown) flingTimer = 0;
\r
211 addListener(flickScrollListener);
\r
213 addListener(new InputListener() {
\r
214 public boolean scrolled (InputEvent event, float x, float y, int amount) {
\r
217 setScrollY(amountY + getMouseWheelY() * amount);
\r
218 else if (scrollX) //
\r
219 setScrollX(amountX + getMouseWheelX() * amount);
\r
225 void resetFade () {
\r
226 fadeAlpha = fadeAlphaSeconds;
\r
227 fadeDelay = fadeDelaySeconds;
\r
230 void cancelTouchFocusedChild (InputEvent event) {
\r
231 if (!cancelTouchFocus) return;
\r
232 Stage stage = getStage();
\r
233 if (stage != null) stage.cancelTouchFocus(flickScrollListener, this);
\r
236 /** If currently scrolling by tracking a touch down, stop scrolling. */
\r
237 public void cancel () {
\r
238 draggingPointer = -1;
\r
239 touchScrollH = false;
\r
240 touchScrollV = false;
\r
241 flickScrollListener.getGestureDetector().cancel();
\r
245 if (!clamp) return;
\r
246 scrollX(overscrollX ? MathUtils.clamp(amountX, -overscrollDistance, maxX + overscrollDistance) : MathUtils.clamp(amountX,
\r
248 scrollY(overscrollY ? MathUtils.clamp(amountY, -overscrollDistance, maxY + overscrollDistance) : MathUtils.clamp(amountY,
\r
252 public void setStyle (ScrollPaneStyle style) {
\r
253 if (style == null) throw new IllegalArgumentException("style cannot be null.");
\r
254 this.style = style;
\r
255 invalidateHierarchy();
\r
258 /** Returns the scroll pane's style. Modifying the returned style may not have an effect until
\r
259 * {@link #setStyle(ScrollPaneStyle)} is called. */
\r
260 public ScrollPaneStyle getStyle () {
\r
264 public void act (float delta) {
\r
267 boolean panning = flickScrollListener.getGestureDetector().isPanning();
\r
269 if (fadeAlpha > 0 && fadeScrollBars && !panning && !touchScrollH && !touchScrollV) {
\r
270 fadeDelay -= delta;
\r
271 if (fadeDelay <= 0) fadeAlpha = Math.max(0, fadeAlpha - delta);
\r
274 if (flingTimer > 0) {
\r
277 float alpha = flingTimer / flingTime;
\r
278 amountX -= velocityX * alpha * delta;
\r
279 amountY -= velocityY * alpha * delta;
\r
282 // Stop fling if hit overscroll distance.
\r
283 if (amountX == -overscrollDistance) velocityX = 0;
\r
284 if (amountX >= maxX + overscrollDistance) velocityX = 0;
\r
285 if (amountY == -overscrollDistance) velocityY = 0;
\r
286 if (amountY >= maxY + overscrollDistance) velocityY = 0;
\r
288 flingTimer -= delta;
\r
289 if (flingTimer <= 0) {
\r
295 if (smoothScrolling && flingTimer <= 0 && !touchScrollH && !touchScrollV && !panning) {
\r
296 if (visualAmountX != amountX) {
\r
297 if (visualAmountX < amountX)
\r
298 visualScrollX(Math.min(amountX, visualAmountX + Math.max(150 * delta, (amountX - visualAmountX) * 5 * delta)));
\r
300 visualScrollX(Math.max(amountX, visualAmountX - Math.max(150 * delta, (visualAmountX - amountX) * 5 * delta)));
\r
302 if (visualAmountY != amountY) {
\r
303 if (visualAmountY < amountY)
\r
304 visualScrollY(Math.min(amountY, visualAmountY + Math.max(150 * delta, (amountY - visualAmountY) * 5 * delta)));
\r
306 visualScrollY(Math.max(amountY, visualAmountY - Math.max(150 * delta, (visualAmountY - amountY) * 5 * delta)));
\r
309 if (visualAmountX != amountX) visualScrollX(amountX);
\r
310 if (visualAmountY != amountY) visualScrollY(amountY);
\r
314 if (overscrollX && scrollX) {
\r
317 amountX += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountX / overscrollDistance)
\r
319 if (amountX > 0) scrollX(0);
\r
320 } else if (amountX > maxX) {
\r
322 amountX -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxX - amountX)
\r
323 / overscrollDistance)
\r
325 if (amountX < maxX) scrollX(maxX);
\r
328 if (overscrollY && scrollY) {
\r
331 amountY += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountY / overscrollDistance)
\r
333 if (amountY > 0) scrollY(0);
\r
334 } else if (amountY > maxY) {
\r
336 amountY -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxY - amountY)
\r
337 / overscrollDistance)
\r
339 if (amountY < maxY) scrollY(maxY);
\r
345 public void layout () {
\r
346 final Drawable bg = style.background;
\r
347 final Drawable hScrollKnob = style.hScrollKnob;
\r
348 final Drawable vScrollKnob = style.vScrollKnob;
\r
350 float bgLeftWidth = 0, bgRightWidth = 0, bgTopHeight = 0, bgBottomHeight = 0;
\r
352 bgLeftWidth = bg.getLeftWidth();
\r
353 bgRightWidth = bg.getRightWidth();
\r
354 bgTopHeight = bg.getTopHeight();
\r
355 bgBottomHeight = bg.getBottomHeight();
\r
358 float width = getWidth();
\r
359 float height = getHeight();
\r
361 float scrollbarHeight = 0;
\r
362 if (hScrollKnob != null) scrollbarHeight = hScrollKnob.getMinHeight();
\r
363 if (style.hScroll != null) scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
\r
364 float scrollbarWidth = 0;
\r
365 if (vScrollKnob != null) scrollbarWidth = vScrollKnob.getMinWidth();
\r
366 if (style.vScroll != null) scrollbarWidth = Math.max(scrollbarWidth, style.vScroll.getMinWidth());
\r
368 // Get available space size by subtracting background's padded area.
\r
369 areaWidth = width - bgLeftWidth - bgRightWidth;
\r
370 areaHeight = height - bgTopHeight - bgBottomHeight;
\r
372 if (widget == null) return;
\r
374 // Get widget's desired width.
\r
375 float widgetWidth, widgetHeight;
\r
376 if (widget instanceof Layout) {
\r
377 Layout layout = (Layout)widget;
\r
378 widgetWidth = layout.getPrefWidth();
\r
379 widgetHeight = layout.getPrefHeight();
\r
381 widgetWidth = widget.getWidth();
\r
382 widgetHeight = widget.getHeight();
\r
385 // Determine if horizontal/vertical scrollbars are needed.
\r
386 scrollX = forceScrollX || (widgetWidth > areaWidth && !disableX);
\r
387 scrollY = forceScrollY || (widgetHeight > areaHeight && !disableY);
\r
389 boolean fade = fadeScrollBars;
\r
391 // Check again, now taking into account the area that's taken up by any enabled scrollbars.
\r
393 areaWidth -= scrollbarWidth;
\r
394 if (!scrollX && widgetWidth > areaWidth && !disableX) {
\r
399 areaHeight -= scrollbarHeight;
\r
400 if (!scrollY && widgetHeight > areaHeight && !disableY) {
\r
402 areaWidth -= scrollbarWidth;
\r
407 // Set the widget area bounds.
\r
408 widgetAreaBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, areaHeight);
\r
411 // Make sure widget is drawn under fading scrollbars.
\r
412 if (scrollX) areaHeight -= scrollbarHeight;
\r
413 if (scrollY) areaWidth -= scrollbarWidth;
\r
415 if (scrollbarsOnTop) {
\r
416 // Make sure widget is drawn under non-fading scrollbars.
\r
417 if (scrollX) widgetAreaBounds.height += scrollbarHeight;
\r
418 if (scrollY) widgetAreaBounds.width += scrollbarWidth;
\r
420 // Offset widget area y for horizontal scrollbar.
\r
422 if (hScrollOnBottom) {
\r
423 widgetAreaBounds.y += scrollbarHeight;
\r
425 widgetAreaBounds.y = 0;
\r
428 // Offset widget area x for vertical scrollbar.
\r
430 if (vScrollOnRight) {
\r
431 widgetAreaBounds.x = 0;
\r
433 widgetAreaBounds.x += scrollbarWidth;
\r
439 // If the widget is smaller than the available space, make it take up the available space.
\r
440 widgetWidth = disableX ? width : Math.max(areaWidth, widgetWidth);
\r
441 widgetHeight = disableY ? height : Math.max(areaHeight, widgetHeight);
\r
443 maxX = widgetWidth - areaWidth;
\r
444 maxY = widgetHeight - areaHeight;
\r
446 // Make sure widget is drawn under fading scrollbars.
\r
447 if (scrollX) maxY -= scrollbarHeight;
\r
448 if (scrollY) maxX -= scrollbarWidth;
\r
450 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
451 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
453 // Set the bounds and scroll knob sizes if scrollbars are needed.
\r
455 if (hScrollKnob != null) {
\r
456 float hScrollHeight = style.hScroll != null ? style.hScroll.getMinHeight() : hScrollKnob.getMinHeight();
\r
457 // the small gap where the two scroll bars intersect might have to flip from right to left
\r
458 float boundsX, boundsY;
\r
459 if (vScrollOnRight) {
\r
460 boundsX = bgLeftWidth;
\r
462 boundsX = bgLeftWidth + scrollbarWidth;
\r
464 // bar on the top or bottom
\r
465 if (hScrollOnBottom) {
\r
466 boundsY = bgBottomHeight;
\r
468 boundsY = height - bgTopHeight - hScrollHeight;
\r
470 hScrollBounds.set(boundsX, boundsY, areaWidth, hScrollHeight);
\r
471 hKnobBounds.width = Math.max(hScrollKnob.getMinWidth(), (int)(hScrollBounds.width * areaWidth / widgetWidth));
\r
472 hKnobBounds.height = hScrollKnob.getMinHeight();
\r
473 hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
\r
474 hKnobBounds.y = hScrollBounds.y;
\r
476 hScrollBounds.set(0, 0, 0, 0);
\r
477 hKnobBounds.set(0, 0, 0, 0);
\r
481 if (vScrollKnob != null) {
\r
482 float vScrollWidth = style.vScroll != null ? style.vScroll.getMinWidth() : vScrollKnob.getMinWidth();
\r
483 // the small gap where the two scroll bars intersect might have to flip from bottom to top
\r
484 float boundsX, boundsY;
\r
485 if (hScrollOnBottom) {
\r
486 boundsY = height - bgTopHeight - areaHeight;
\r
488 boundsY = bgBottomHeight;
\r
490 // bar on the left or right
\r
491 if (vScrollOnRight) {
\r
492 boundsX = width - bgRightWidth - vScrollWidth;
\r
494 boundsX = bgLeftWidth;
\r
496 vScrollBounds.set(boundsX, boundsY, vScrollWidth, areaHeight);
\r
497 vKnobBounds.width = vScrollKnob.getMinWidth();
\r
498 vKnobBounds.height = Math.max(vScrollKnob.getMinHeight(), (int)(vScrollBounds.height * areaHeight / widgetHeight));
\r
499 if (vScrollOnRight) {
\r
500 vKnobBounds.x = width - bgRightWidth - vScrollKnob.getMinWidth();
\r
502 vKnobBounds.x = bgLeftWidth;
\r
504 vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
\r
506 vScrollBounds.set(0, 0, 0, 0);
\r
507 vKnobBounds.set(0, 0, 0, 0);
\r
511 if (widget.getWidth() != widgetWidth || widget.getHeight() != widgetHeight) {
\r
512 widget.setWidth(widgetWidth);
\r
513 widget.setHeight(widgetHeight);
\r
514 if (widget instanceof Layout) {
\r
515 Layout layout = (Layout)widget;
\r
516 layout.invalidate();
\r
520 if (widget instanceof Layout) ((Layout)widget).validate();
\r
525 public void draw (SpriteBatch batch, float parentAlpha) {
\r
526 if (widget == null) return;
\r
530 // Setup transform for this group.
\r
531 applyTransform(batch, computeTransform());
\r
533 if (scrollX) hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());
\r
535 vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));
\r
537 // Calculate the widget's position depending on the scroll state and available widget area.
\r
538 float y = widgetAreaBounds.y;
\r
542 y -= (int)(maxY - visualAmountY);
\r
544 if (!fadeScrollBars && scrollbarsOnTop && scrollX) {
\r
545 float scrollbarHeight = 0;
\r
546 if (style.hScrollKnob != null) scrollbarHeight = style.hScrollKnob.getMinHeight();
\r
547 if (style.hScroll != null) scrollbarHeight = Math.max(scrollbarHeight, style.hScroll.getMinHeight());
\r
548 y += scrollbarHeight;
\r
551 float x = widgetAreaBounds.x;
\r
552 if (scrollX) x -= (int)visualAmountX;
\r
553 widget.setPosition(x, y);
\r
555 if (widget instanceof Cullable) {
\r
556 widgetCullingArea.x = -widget.getX() + widgetAreaBounds.x;
\r
557 widgetCullingArea.y = -widget.getY() + widgetAreaBounds.y;
\r
558 widgetCullingArea.width = widgetAreaBounds.width;
\r
559 widgetCullingArea.height = widgetAreaBounds.height;
\r
560 ((Cullable)widget).setCullingArea(widgetCullingArea);
\r
563 // Caculate the scissor bounds based on the batch transform, the available widget area and the camera transform. We need to
\r
564 // project those to screen coordinates for OpenGL ES to consume.
\r
565 getStage().calculateScissors(widgetAreaBounds, scissorBounds);
\r
567 // Draw the background ninepatch.
\r
568 Color color = getColor();
\r
569 batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
\r
570 if (style.background != null) style.background.draw(batch, 0, 0, getWidth(), getHeight());
\r
573 // Enable scissors for widget area and draw the widget.
\r
574 if (ScissorStack.pushScissors(scissorBounds)) {
\r
575 drawChildren(batch, parentAlpha);
\r
576 ScissorStack.popScissors();
\r
579 // Render scrollbars and knobs on top.
\r
580 batch.setColor(color.r, color.g, color.b, color.a * parentAlpha * Interpolation.fade.apply(fadeAlpha / fadeAlphaSeconds));
\r
581 if (scrollX && scrollY) {
\r
582 if (style.corner != null) {
\r
584 .draw(batch, hScrollBounds.x + hScrollBounds.width, hScrollBounds.y, vScrollBounds.width, vScrollBounds.y);
\r
588 if (style.hScroll != null)
\r
589 style.hScroll.draw(batch, hScrollBounds.x, hScrollBounds.y, hScrollBounds.width, hScrollBounds.height);
\r
590 if (style.hScrollKnob != null)
\r
591 style.hScrollKnob.draw(batch, hKnobBounds.x, hKnobBounds.y, hKnobBounds.width, hKnobBounds.height);
\r
594 if (style.vScroll != null)
\r
595 style.vScroll.draw(batch, vScrollBounds.x, vScrollBounds.y, vScrollBounds.width, vScrollBounds.height);
\r
596 if (style.vScrollKnob != null)
\r
597 style.vScrollKnob.draw(batch, vKnobBounds.x, vKnobBounds.y, vKnobBounds.width, vKnobBounds.height);
\r
600 resetTransform(batch);
\r
603 public float getPrefWidth () {
\r
604 if (widget instanceof Layout) {
\r
605 float width = ((Layout)widget).getPrefWidth();
\r
606 if (style.background != null) width += style.background.getLeftWidth() + style.background.getRightWidth();
\r
612 public float getPrefHeight () {
\r
613 if (widget instanceof Layout) {
\r
614 float height = ((Layout)widget).getPrefHeight();
\r
615 if (style.background != null) height += style.background.getTopHeight() + style.background.getBottomHeight();
\r
621 public float getMinWidth () {
\r
625 public float getMinHeight () {
\r
629 /** Sets the {@link Actor} embedded in this scroll pane.
\r
630 * @param widget May be null to remove any current actor. */
\r
631 public void setWidget (Actor widget) {
\r
632 if (widget == this) throw new IllegalArgumentException("widget cannot be same object");
\r
633 if (this.widget != null) super.removeActor(this.widget);
\r
634 this.widget = widget;
\r
635 if (widget != null) super.addActor(widget);
\r
638 /** Returns the actor embedded in this scroll pane, or null. */
\r
639 public Actor getWidget () {
\r
644 public void addActor (Actor actor) {
\r
645 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
649 public void addActorAt (int index, Actor actor) {
\r
650 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
654 public void addActorBefore (Actor actorBefore, Actor actor) {
\r
655 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
659 public void addActorAfter (Actor actorAfter, Actor actor) {
\r
660 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");
\r
663 public boolean removeActor (Actor actor) {
\r
664 if (actor != widget) return false;
\r
669 public Actor hit (float x, float y, boolean touchable) {
\r
670 if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return null;
\r
671 if (scrollX && hScrollBounds.contains(x, y)) return this;
\r
672 if (scrollY && vScrollBounds.contains(x, y)) return this;
\r
673 return super.hit(x, y, touchable);
\r
676 /** Called whenever the x scroll amount is changed. */
\r
677 protected void scrollX (float pixelsX) {
\r
678 this.amountX = pixelsX;
\r
681 /** Called whenever the y scroll amount is changed. */
\r
682 protected void scrollY (float pixelsY) {
\r
683 this.amountY = pixelsY;
\r
686 /** Called whenever the visual x scroll amount is changed. */
\r
687 protected void visualScrollX (float pixelsX) {
\r
688 this.visualAmountX = pixelsX;
\r
691 /** Called whenever the visual y scroll amount is changed. */
\r
692 protected void visualScrollY (float pixelsY) {
\r
693 this.visualAmountY = pixelsY;
\r
696 /** Returns the amount to scroll horizontally when the mouse wheel is scrolled. */
\r
697 protected float getMouseWheelX () {
\r
698 return Math.max(areaWidth * 0.9f, maxX * 0.1f) / 4;
\r
701 /** Returns the amount to scroll vertically when the mouse wheel is scrolled. */
\r
702 protected float getMouseWheelY () {
\r
703 return Math.max(areaHeight * 0.9f, maxY * 0.1f) / 4;
\r
706 public void setScrollX (float pixels) {
\r
707 scrollX(MathUtils.clamp(pixels, 0, maxX));
\r
710 /** Returns the x scroll position in pixels. */
\r
711 public float getScrollX () {
\r
715 public void setScrollY (float pixels) {
\r
716 scrollY(MathUtils.clamp(pixels, 0, maxY));
\r
719 /** Returns the y scroll position in pixels. */
\r
720 public float getScrollY () {
\r
724 /** Sets the visual scroll amount equal to the scroll amount. This can be used when setting the scroll amount without animating. */
\r
725 public void updateVisualScroll () {
\r
726 visualAmountX = amountX;
\r
727 visualAmountY = amountY;
\r
730 public float getVisualScrollX () {
\r
731 return !scrollX ? 0 : visualAmountX;
\r
734 public float getVisualScrollY () {
\r
735 return !scrollY ? 0 : visualAmountY;
\r
738 public float getScrollPercentX () {
\r
739 return MathUtils.clamp(amountX / maxX, 0, 1);
\r
742 public void setScrollPercentX (float percentX) {
\r
743 scrollX(maxX * MathUtils.clamp(percentX, 0, 1));
\r
746 public float getScrollPercentY () {
\r
747 return MathUtils.clamp(amountY / maxY, 0, 1);
\r
750 public void setScrollPercentY (float percentY) {
\r
751 scrollY(maxY * MathUtils.clamp(percentY, 0, 1));
\r
754 public void setFlickScroll (boolean flickScroll) {
\r
755 if (this.flickScroll == flickScroll) return;
\r
756 this.flickScroll = flickScroll;
\r
758 addListener(flickScrollListener);
\r
760 removeListener(flickScrollListener);
\r
764 /** Sets the scroll offset so the specified rectangle is fully in view, if possible. Coordinates are in the scroll pane widget's
\r
765 * coordinate system. */
\r
766 public void scrollTo (float x, float y, float width, float height) {
\r
767 float amountX = this.amountX;
\r
768 if (x + width > amountX + areaWidth) amountX = x + width - areaWidth;
\r
769 if (x < amountX) amountX = x;
\r
770 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
772 float amountY = this.amountY;
\r
773 if (amountY > maxY - y - height + areaHeight) amountY = maxY - y - height + areaHeight;
\r
774 if (amountY < maxY - y) amountY = maxY - y;
\r
775 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
778 /** Sets the scroll offset so the specified rectangle is fully in view and centered vertically in the scroll pane, if possible.
\r
779 * Coordinates are in the scroll pane widget's coordinate system. */
\r
780 public void scrollToCenter (float x, float y, float width, float height) {
\r
781 float amountX = this.amountX;
\r
782 if (x + width > amountX + areaWidth) amountX = x + width - areaWidth;
\r
783 if (x < amountX) amountX = x;
\r
784 scrollX(MathUtils.clamp(amountX, 0, maxX));
\r
786 float amountY = this.amountY;
\r
787 float centerY = maxY - y + areaHeight / 2 - height / 2;
\r
788 if (amountY < centerY - areaHeight / 4 || amountY > centerY + areaHeight / 4) amountY = centerY;
\r
789 scrollY(MathUtils.clamp(amountY, 0, maxY));
\r
792 /** Returns the maximum scroll value in the x direction. */
\r
793 public float getMaxX () {
\r
797 /** Returns the maximum scroll value in the y direction. */
\r
798 public float getMaxY () {
\r
802 public float getScrollBarHeight () {
\r
803 return style.hScrollKnob == null || !scrollX ? 0 : style.hScrollKnob.getMinHeight();
\r
806 public float getScrollBarWidth () {
\r
807 return style.vScrollKnob == null || !scrollY ? 0 : style.vScrollKnob.getMinWidth();
\r
810 public boolean isScrollX () {
\r
814 public boolean isScrollY () {
\r
818 /** Disables scrolling in a direction. The widget will be sized to the FlickScrollPane in the disabled direction. */
\r
819 public void setScrollingDisabled (boolean x, boolean y) {
\r
824 public boolean isDragging () {
\r
825 return draggingPointer != -1;
\r
828 public boolean isPanning () {
\r
829 return flickScrollListener.getGestureDetector().isPanning();
\r
832 public boolean isFlinging () {
\r
833 return flingTimer > 0;
\r
836 public void setVelocityX (float velocityX) {
\r
837 this.velocityX = velocityX;
\r
840 /** Gets the flick scroll y velocity. */
\r
841 public float getVelocityX () {
\r
842 if (flingTimer <= 0) return 0;
\r
843 float alpha = flingTimer / flingTime;
\r
844 alpha = alpha * alpha * alpha;
\r
845 return velocityX * alpha * alpha * alpha;
\r
848 public void setVelocityY (float velocityY) {
\r
849 this.velocityY = velocityY;
\r
852 /** Gets the flick scroll y velocity. */
\r
853 public float getVelocityY () {
\r
857 /** For flick scroll, if true the widget can be scrolled slightly past its bounds and will animate back to its bounds when
\r
858 * scrolling is stopped. Default is true. */
\r
859 public void setOverscroll (boolean overscrollX, boolean overscrollY) {
\r
860 this.overscrollX = overscrollX;
\r
861 this.overscrollY = overscrollY;
\r
864 /** For flick scroll, sets the overscroll distance in pixels and the speed it returns to the widget's bounds in seconds. Default
\r
865 * is 50, 30, 200. */
\r
866 public void setupOverscroll (float distance, float speedMin, float speedMax) {
\r
867 overscrollDistance = distance;
\r
868 overscrollSpeedMin = speedMin;
\r
869 overscrollSpeedMax = speedMax;
\r
872 /** Forces enabling scrollbars (for non-flick scroll) and overscrolling (for flick scroll) in a direction, even if the contents
\r
873 * do not exceed the bounds in that direction. */
\r
874 public void setForceScroll (boolean x, boolean y) {
\r
879 public boolean isForceScrollX () {
\r
880 return forceScrollX;
\r
883 public boolean isForceScrollY () {
\r
884 return forceScrollY;
\r
887 /** For flick scroll, sets the amount of time in seconds that a fling will continue to scroll. Default is 1. */
\r
888 public void setFlingTime (float flingTime) {
\r
889 this.flingTime = flingTime;
\r
892 /** For flick scroll, prevents scrolling out of the widget's bounds. Default is true. */
\r
893 public void setClamp (boolean clamp) {
\r
894 this.clamp = clamp;
\r
897 /** Put the vertical scroll bar and knob on the left or right side of the ScrollPane
\r
898 * @param atRight set to true to draw on the right, false to draw on the left (default : right)
\r
900 public void setVScrollBarAtRight(boolean atRight) {
\r
901 vScrollOnRight = atRight;
\r
904 /** Put the vertical scroll bar and knob on the top (y coords, not draw order) or bottom of the ScrollPane
\r
905 * @param atBottom set to true to draw on the bottom, false to draw on the top (default : bottom)
\r
907 public void setHScrollBarAtBottom(boolean atBottom) {
\r
908 hScrollOnBottom = atBottom;
\r
911 /** When true the scroll bars fade out after some time of not being used. */
\r
912 public void setFadeScrollBars (boolean fadeScrollBars) {
\r
913 if (this.fadeScrollBars == fadeScrollBars) return;
\r
914 this.fadeScrollBars = fadeScrollBars;
\r
915 if (!fadeScrollBars) fadeAlpha = fadeAlphaSeconds;
\r
919 public void setupFadeScrollBars (float fadeAlphaSeconds, float fadeDelaySeconds) {
\r
920 this.fadeAlphaSeconds = fadeAlphaSeconds;
\r
921 this.fadeDelaySeconds = fadeDelaySeconds;
\r
924 public void setSmoothScrolling (boolean smoothScrolling) {
\r
925 this.smoothScrolling = smoothScrolling;
\r
928 /** When false (the default), the widget is clipped so it is not drawn under the scrollbars. When true, the widget is clipped to
\r
929 * the entire scroll pane bounds and the scrollbars are drawn on top of the widget. If {@link #setFadeScrollBars(boolean)} is
\r
930 * true, the scroll bars are always drawn on top. */
\r
931 public void setScrollbarsOnTop (boolean scrollbarsOnTop) {
\r
932 this.scrollbarsOnTop = scrollbarsOnTop;
\r
936 /** When true (default), the {@link Stage#cancelTouchFocus()} touch focus} is cancelled when flick scrolling begins. This causes
\r
937 * widgets inside the scrollpane that have received touchDown to receive touchUp when flick scrolling begins. */
\r
938 public void setCancelTouchFocus (boolean cancelTouchFocus) {
\r
939 this.cancelTouchFocus = cancelTouchFocus;
\r
942 /** The style for a scroll pane, see {@link ScrollPane}.
\r
944 * @author Nathan Sweet */
\r
945 static public class ScrollPaneStyle {
\r
947 public Drawable background, corner;
\r
949 public Drawable hScroll, hScrollKnob;
\r
951 public Drawable vScroll, vScrollKnob;
\r
953 public ScrollPaneStyle () {
\r
956 public ScrollPaneStyle (Drawable background, Drawable hScroll, Drawable hScrollKnob, Drawable vScroll, Drawable vScrollKnob) {
\r
957 this.background = background;
\r
958 this.hScroll = hScroll;
\r
959 this.hScrollKnob = hScrollKnob;
\r
960 this.vScroll = vScroll;
\r
961 this.vScrollKnob = vScrollKnob;
\r
964 public ScrollPaneStyle (ScrollPaneStyle style) {
\r
965 this.background = style.background;
\r
966 this.hScroll = style.hScroll;
\r
967 this.hScrollKnob = style.hScrollKnob;
\r
968 this.vScroll = style.vScroll;
\r
969 this.vScrollKnob = style.vScrollKnob;
\r