OSDN Git Service

fix vKnobBounds.x
[mikumikustudio/libgdx-mikumikustudio.git] / gdx / src / com / badlogic / gdx / scenes / scene2d / ui / ScrollPane.java
1 /*******************************************************************************\r
2  * Copyright 2011 See AUTHORS file.\r
3  * \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
7  * \r
8  *   http://www.apache.org/licenses/LICENSE-2.0\r
9  * \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
16 \r
17 package com.badlogic.gdx.scenes.scene2d.ui;\r
18 \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
35 \r
36 /** A group that scrolls a child widget using scrollbars and/or mouse or touch dragging.\r
37  * <p>\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
40  * <p>\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
43  * @author mzechner\r
44  * @author Nathan Sweet */\r
45 public class ScrollPane extends WidgetGroup {\r
46         private ScrollPaneStyle style;\r
47         private Actor widget;\r
48 \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
57 \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
63         float maxX, maxY;\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
70 \r
71         boolean flickScroll = true;\r
72         float velocityX, velocityY;\r
73         float flingTimer;\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
82 \r
83         /** @param widget May be null. */\r
84         public ScrollPane (Actor widget) {\r
85                 this(widget, new ScrollPaneStyle());\r
86         }\r
87 \r
88         /** @param widget May be null. */\r
89         public ScrollPane (Actor widget, Skin skin) {\r
90                 this(widget, skin.get(ScrollPaneStyle.class));\r
91         }\r
92 \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
96         }\r
97 \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
102                 setWidget(widget);\r
103                 setWidth(150);\r
104                 setHeight(150);\r
105 \r
106                 addCaptureListener(new InputListener() {\r
107                         private float handlePosition;\r
108 \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
113 \r
114                                 if (!flickScroll) resetFade();\r
115 \r
116                                 if (fadeAlpha == 0) return false;\r
117 \r
118                                 if (scrollX && hScrollBounds.contains(x, y)) {\r
119                                         event.stop();\r
120                                         resetFade();\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
126                                                 return true;\r
127                                         }\r
128                                         setScrollX(amountX + areaWidth * (x < hKnobBounds.x ? -1 : 1));\r
129                                         return true;\r
130                                 }\r
131                                 if (scrollY && vScrollBounds.contains(x, y)) {\r
132                                         event.stop();\r
133                                         resetFade();\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
139                                                 return true;\r
140                                         }\r
141                                         setScrollY(amountY + areaHeight * (y < vKnobBounds.y ? 1 : -1));\r
142                                         return true;\r
143                                 }\r
144                                 return false;\r
145                         }\r
146 \r
147                         public void touchUp (InputEvent event, float x, float y, int pointer, int button) {\r
148                                 if (pointer != draggingPointer) return;\r
149                                 cancel();\r
150                         }\r
151 \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
172                                 }\r
173                         }\r
174 \r
175                         public boolean mouseMoved (InputEvent event, float x, float y) {\r
176                                 if (!flickScroll) resetFade();\r
177                                 return false;\r
178                         }\r
179                 });\r
180 \r
181                 flickScrollListener = new ActorGestureListener() {\r
182                         public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {\r
183                                 resetFade();\r
184                                 amountX -= deltaX;\r
185                                 amountY += deltaY;\r
186                                 clamp();\r
187                                 cancelTouchFocusedChild(event);\r
188                         }\r
189 \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
193                                         velocityX = x;\r
194                                         cancelTouchFocusedChild(event);\r
195                                 }\r
196                                 if (Math.abs(y) > 150) {\r
197                                         flingTimer = flingTime;\r
198                                         velocityY = -y;\r
199                                         cancelTouchFocusedChild(event);\r
200                                 }\r
201                         }\r
202 \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
206                                         return true;\r
207                                 }\r
208                                 return false;\r
209                         }\r
210                 };\r
211                 addListener(flickScrollListener);\r
212 \r
213                 addListener(new InputListener() {\r
214                         public boolean scrolled (InputEvent event, float x, float y, int amount) {\r
215                                 resetFade();\r
216                                 if (scrollY)\r
217                                         setScrollY(amountY + getMouseWheelY() * amount);\r
218                                 else if (scrollX) //\r
219                                         setScrollX(amountX + getMouseWheelX() * amount);\r
220                                 return true;\r
221                         }\r
222                 });\r
223         }\r
224 \r
225         void resetFade () {\r
226                 fadeAlpha = fadeAlphaSeconds;\r
227                 fadeDelay = fadeDelaySeconds;\r
228         }\r
229 \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
234         }\r
235 \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
242         }\r
243 \r
244         void clamp () {\r
245                 if (!clamp) return;\r
246                 scrollX(overscrollX ? MathUtils.clamp(amountX, -overscrollDistance, maxX + overscrollDistance) : MathUtils.clamp(amountX,\r
247                         0, maxX));\r
248                 scrollY(overscrollY ? MathUtils.clamp(amountY, -overscrollDistance, maxY + overscrollDistance) : MathUtils.clamp(amountY,\r
249                         0, maxY));\r
250         }\r
251 \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
256         }\r
257 \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
261                 return style;\r
262         }\r
263 \r
264         public void act (float delta) {\r
265                 super.act(delta);\r
266 \r
267                 boolean panning = flickScrollListener.getGestureDetector().isPanning();\r
268 \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
272                 }\r
273 \r
274                 if (flingTimer > 0) {\r
275                         resetFade();\r
276 \r
277                         float alpha = flingTimer / flingTime;\r
278                         amountX -= velocityX * alpha * delta;\r
279                         amountY -= velocityY * alpha * delta;\r
280                         clamp();\r
281 \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
287 \r
288                         flingTimer -= delta;\r
289                         if (flingTimer <= 0) {\r
290                                 velocityX = 0;\r
291                                 velocityY = 0;\r
292                         }\r
293                 }\r
294 \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
299                                 else\r
300                                         visualScrollX(Math.max(amountX, visualAmountX - Math.max(150 * delta, (visualAmountX - amountX) * 5 * delta)));\r
301                         }\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
305                                 else\r
306                                         visualScrollY(Math.max(amountY, visualAmountY - Math.max(150 * delta, (visualAmountY - amountY) * 5 * delta)));\r
307                         }\r
308                 } else {\r
309                         if (visualAmountX != amountX) visualScrollX(amountX);\r
310                         if (visualAmountY != amountY) visualScrollY(amountY);\r
311                 }\r
312 \r
313                 if (!panning) {\r
314                         if (overscrollX && scrollX) {\r
315                                 if (amountX < 0) {\r
316                                         resetFade();\r
317                                         amountX += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountX / overscrollDistance)\r
318                                                 * delta;\r
319                                         if (amountX > 0) scrollX(0);\r
320                                 } else if (amountX > maxX) {\r
321                                         resetFade();\r
322                                         amountX -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxX - amountX)\r
323                                                 / overscrollDistance)\r
324                                                 * delta;\r
325                                         if (amountX < maxX) scrollX(maxX);\r
326                                 }\r
327                         }\r
328                         if (overscrollY && scrollY) {\r
329                                 if (amountY < 0) {\r
330                                         resetFade();\r
331                                         amountY += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountY / overscrollDistance)\r
332                                                 * delta;\r
333                                         if (amountY > 0) scrollY(0);\r
334                                 } else if (amountY > maxY) {\r
335                                         resetFade();\r
336                                         amountY -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxY - amountY)\r
337                                                 / overscrollDistance)\r
338                                                 * delta;\r
339                                         if (amountY < maxY) scrollY(maxY);\r
340                                 }\r
341                         }\r
342                 }\r
343         }\r
344 \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
349 \r
350                 float bgLeftWidth = 0, bgRightWidth = 0, bgTopHeight = 0, bgBottomHeight = 0;\r
351                 if (bg != null) {\r
352                         bgLeftWidth = bg.getLeftWidth();\r
353                         bgRightWidth = bg.getRightWidth();\r
354                         bgTopHeight = bg.getTopHeight();\r
355                         bgBottomHeight = bg.getBottomHeight();\r
356                 }\r
357 \r
358                 float width = getWidth();\r
359                 float height = getHeight();\r
360 \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
367 \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
371 \r
372                 if (widget == null) return;\r
373 \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
380                 } else {\r
381                         widgetWidth = widget.getWidth();\r
382                         widgetHeight = widget.getHeight();\r
383                 }\r
384 \r
385                 // Determine if horizontal/vertical scrollbars are needed.\r
386                 scrollX = forceScrollX || (widgetWidth > areaWidth && !disableX);\r
387                 scrollY = forceScrollY || (widgetHeight > areaHeight && !disableY);\r
388 \r
389                 boolean fade = fadeScrollBars;\r
390                 if (!fade) {\r
391                         // Check again, now taking into account the area that's taken up by any enabled scrollbars.\r
392                         if (scrollY) {\r
393                                 areaWidth -= scrollbarWidth;\r
394                                 if (!scrollX && widgetWidth > areaWidth && !disableX) {\r
395                                         scrollX = true;\r
396                                 }\r
397                         }\r
398                         if (scrollX) {\r
399                                 areaHeight -= scrollbarHeight;\r
400                                 if (!scrollY && widgetHeight > areaHeight && !disableY) {\r
401                                         scrollY = true;\r
402                                         areaWidth -= scrollbarWidth;\r
403                                 }\r
404                         }\r
405                 }\r
406 \r
407                 // Set the widget area bounds.\r
408                 widgetAreaBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, areaHeight);\r
409 \r
410                 if (fade) {\r
411                         // Make sure widget is drawn under fading scrollbars.\r
412                         if (scrollX) areaHeight -= scrollbarHeight;\r
413                         if (scrollY) areaWidth -= scrollbarWidth;\r
414                 } else {\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
419                         } else {\r
420                                 // Offset widget area y for horizontal scrollbar.\r
421                                 if (scrollX) {\r
422                                         if (hScrollOnBottom) {\r
423                                                 widgetAreaBounds.y += scrollbarHeight;\r
424                                         } else {\r
425                                                 widgetAreaBounds.y = 0;\r
426                                         }\r
427                                 }\r
428                                 // Offset widget area x for vertical scrollbar.\r
429                                 if (scrollY) {\r
430                                         if (vScrollOnRight) {\r
431                                                 widgetAreaBounds.x = 0;\r
432                                         } else {\r
433                                                 widgetAreaBounds.x += scrollbarWidth;\r
434                                         }\r
435                                 }\r
436                         }\r
437                 }\r
438 \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
442 \r
443                 maxX = widgetWidth - areaWidth;\r
444                 maxY = widgetHeight - areaHeight;\r
445                 if (fade) {\r
446                         // Make sure widget is drawn under fading scrollbars.\r
447                         if (scrollX) maxY -= scrollbarHeight;\r
448                         if (scrollY) maxX -= scrollbarWidth;\r
449                 }\r
450                 scrollX(MathUtils.clamp(amountX, 0, maxX));\r
451                 scrollY(MathUtils.clamp(amountY, 0, maxY));\r
452 \r
453                 // Set the bounds and scroll knob sizes if scrollbars are needed.\r
454                 if (scrollX) {\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
461                                 } else {\r
462                                         boundsX = bgLeftWidth + scrollbarWidth;\r
463                                 }\r
464                                 // bar on the top or bottom\r
465                                 if (hScrollOnBottom) {\r
466                                         boundsY = bgBottomHeight;\r
467                                 } else {                                        \r
468                                         boundsY = height - bgTopHeight - hScrollHeight;\r
469                                 }\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
475                         } else {\r
476                                 hScrollBounds.set(0, 0, 0, 0);\r
477                                 hKnobBounds.set(0, 0, 0, 0);\r
478                         }\r
479                 }\r
480                 if (scrollY) {\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
487                                 } else {\r
488                                         boundsY = bgBottomHeight;\r
489                                 }\r
490                                 // bar on the left or right\r
491                                 if (vScrollOnRight) {\r
492                                         boundsX = width - bgRightWidth - vScrollWidth;\r
493                                 } else {\r
494                                         boundsX = bgLeftWidth;\r
495                                 }\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
501                                 } else {\r
502                                         vKnobBounds.x = bgLeftWidth;\r
503                                 }\r
504                                 vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));\r
505                         } else {\r
506                                 vScrollBounds.set(0, 0, 0, 0);\r
507                                 vKnobBounds.set(0, 0, 0, 0);\r
508                         }\r
509                 }\r
510 \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
517                                 layout.validate();\r
518                         }\r
519                 } else {\r
520                         if (widget instanceof Layout) ((Layout)widget).validate();\r
521                 }\r
522         }\r
523 \r
524         @Override\r
525         public void draw (SpriteBatch batch, float parentAlpha) {\r
526                 if (widget == null) return;\r
527 \r
528                 validate();\r
529 \r
530                 // Setup transform for this group.\r
531                 applyTransform(batch, computeTransform());\r
532 \r
533                 if (scrollX) hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());\r
534                 if (scrollY)\r
535                         vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));\r
536 \r
537                 // Calculate the widget's position depending on the scroll state and available widget area.\r
538                 float y = widgetAreaBounds.y;\r
539                 if (!scrollY)\r
540                         y -= (int)maxY;\r
541                 else\r
542                         y -= (int)(maxY - visualAmountY);\r
543 \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
549                 }\r
550 \r
551                 float x = widgetAreaBounds.x;\r
552                 if (scrollX) x -= (int)visualAmountX;\r
553                 widget.setPosition(x, y);\r
554 \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
561                 }\r
562 \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
566 \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
571                 batch.flush();\r
572 \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
577                 }\r
578 \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
583                                 style.corner\r
584                                         .draw(batch, hScrollBounds.x + hScrollBounds.width, hScrollBounds.y, vScrollBounds.width, vScrollBounds.y);\r
585                         }\r
586                 }\r
587                 if (scrollX) {\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
592                 }\r
593                 if (scrollY) {\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
598                 }\r
599 \r
600                 resetTransform(batch);\r
601         }\r
602 \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
607                         return width;\r
608                 }\r
609                 return 150;\r
610         }\r
611 \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
616                         return height;\r
617                 }\r
618                 return 150;\r
619         }\r
620 \r
621         public float getMinWidth () {\r
622                 return 0;\r
623         }\r
624 \r
625         public float getMinHeight () {\r
626                 return 0;\r
627         }\r
628 \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
636         }\r
637 \r
638         /** Returns the actor embedded in this scroll pane, or null. */\r
639         public Actor getWidget () {\r
640                 return widget;\r
641         }\r
642 \r
643         /** @deprecated */\r
644         public void addActor (Actor actor) {\r
645                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
646         }\r
647 \r
648         /** @deprecated */\r
649         public void addActorAt (int index, Actor actor) {\r
650                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
651         }\r
652 \r
653         /** @deprecated */\r
654         public void addActorBefore (Actor actorBefore, Actor actor) {\r
655                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
656         }\r
657 \r
658         /** @deprecated */\r
659         public void addActorAfter (Actor actorAfter, Actor actor) {\r
660                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
661         }\r
662 \r
663         public boolean removeActor (Actor actor) {\r
664                 if (actor != widget) return false;\r
665                 setWidget(null);\r
666                 return true;\r
667         }\r
668 \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
674         }\r
675 \r
676         /** Called whenever the x scroll amount is changed. */\r
677         protected void scrollX (float pixelsX) {\r
678                 this.amountX = pixelsX;\r
679         }\r
680 \r
681         /** Called whenever the y scroll amount is changed. */\r
682         protected void scrollY (float pixelsY) {\r
683                 this.amountY = pixelsY;\r
684         }\r
685 \r
686         /** Called whenever the visual x scroll amount is changed. */\r
687         protected void visualScrollX (float pixelsX) {\r
688                 this.visualAmountX = pixelsX;\r
689         }\r
690 \r
691         /** Called whenever the visual y scroll amount is changed. */\r
692         protected void visualScrollY (float pixelsY) {\r
693                 this.visualAmountY = pixelsY;\r
694         }\r
695 \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
699         }\r
700 \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
704         }\r
705 \r
706         public void setScrollX (float pixels) {\r
707                 scrollX(MathUtils.clamp(pixels, 0, maxX));\r
708         }\r
709 \r
710         /** Returns the x scroll position in pixels. */\r
711         public float getScrollX () {\r
712                 return amountX;\r
713         }\r
714 \r
715         public void setScrollY (float pixels) {\r
716                 scrollY(MathUtils.clamp(pixels, 0, maxY));\r
717         }\r
718 \r
719         /** Returns the y scroll position in pixels. */\r
720         public float getScrollY () {\r
721                 return amountY;\r
722         }\r
723 \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
728         }\r
729 \r
730         public float getVisualScrollX () {\r
731                 return !scrollX ? 0 : visualAmountX;\r
732         }\r
733 \r
734         public float getVisualScrollY () {\r
735                 return !scrollY ? 0 : visualAmountY;\r
736         }\r
737 \r
738         public float getScrollPercentX () {\r
739                 return MathUtils.clamp(amountX / maxX, 0, 1);\r
740         }\r
741 \r
742         public void setScrollPercentX (float percentX) {\r
743                 scrollX(maxX * MathUtils.clamp(percentX, 0, 1));\r
744         }\r
745 \r
746         public float getScrollPercentY () {\r
747                 return MathUtils.clamp(amountY / maxY, 0, 1);\r
748         }\r
749 \r
750         public void setScrollPercentY (float percentY) {\r
751                 scrollY(maxY * MathUtils.clamp(percentY, 0, 1));\r
752         }\r
753 \r
754         public void setFlickScroll (boolean flickScroll) {\r
755                 if (this.flickScroll == flickScroll) return;\r
756                 this.flickScroll = flickScroll;\r
757                 if (flickScroll)\r
758                         addListener(flickScrollListener);\r
759                 else\r
760                         removeListener(flickScrollListener);\r
761                 invalidate();\r
762         }\r
763 \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
771 \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
776         }\r
777 \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
785 \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
790         }\r
791 \r
792         /** Returns the maximum scroll value in the x direction. */\r
793         public float getMaxX () {\r
794                 return maxX;\r
795         }\r
796 \r
797         /** Returns the maximum scroll value in the y direction. */\r
798         public float getMaxY () {\r
799                 return maxY;\r
800         }\r
801 \r
802         public float getScrollBarHeight () {\r
803                 return style.hScrollKnob == null || !scrollX ? 0 : style.hScrollKnob.getMinHeight();\r
804         }\r
805 \r
806         public float getScrollBarWidth () {\r
807                 return style.vScrollKnob == null || !scrollY ? 0 : style.vScrollKnob.getMinWidth();\r
808         }\r
809 \r
810         public boolean isScrollX () {\r
811                 return scrollX;\r
812         }\r
813 \r
814         public boolean isScrollY () {\r
815                 return scrollY;\r
816         }\r
817 \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
820                 disableX = x;\r
821                 disableY = y;\r
822         }\r
823 \r
824         public boolean isDragging () {\r
825                 return draggingPointer != -1;\r
826         }\r
827 \r
828         public boolean isPanning () {\r
829                 return flickScrollListener.getGestureDetector().isPanning();\r
830         }\r
831 \r
832         public boolean isFlinging () {\r
833                 return flingTimer > 0;\r
834         }\r
835 \r
836         public void setVelocityX (float velocityX) {\r
837                 this.velocityX = velocityX;\r
838         }\r
839 \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
846         }\r
847 \r
848         public void setVelocityY (float velocityY) {\r
849                 this.velocityY = velocityY;\r
850         }\r
851 \r
852         /** Gets the flick scroll y velocity. */\r
853         public float getVelocityY () {\r
854                 return velocityY;\r
855         }\r
856 \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
862         }\r
863 \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
870         }\r
871 \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
875                 forceScrollX = x;\r
876                 forceScrollY = y;\r
877         }\r
878 \r
879         public boolean isForceScrollX () {\r
880                 return forceScrollX;\r
881         }\r
882 \r
883         public boolean isForceScrollY () {\r
884                 return forceScrollY;\r
885         }\r
886 \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
890         }\r
891 \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
895         }\r
896 \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
899          */\r
900         public void setVScrollBarAtRight(boolean atRight) {\r
901                 vScrollOnRight = atRight;\r
902         }\r
903 \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
906          */\r
907         public void setHScrollBarAtBottom(boolean atBottom) {\r
908                 hScrollOnBottom = atBottom;\r
909         }\r
910 \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
916                 invalidate();\r
917         }\r
918 \r
919         public void setupFadeScrollBars (float fadeAlphaSeconds, float fadeDelaySeconds) {\r
920                 this.fadeAlphaSeconds = fadeAlphaSeconds;\r
921                 this.fadeDelaySeconds = fadeDelaySeconds;\r
922         }\r
923 \r
924         public void setSmoothScrolling (boolean smoothScrolling) {\r
925                 this.smoothScrolling = smoothScrolling;\r
926         }\r
927 \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
933                 invalidate();\r
934         }\r
935 \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
940         }\r
941 \r
942         /** The style for a scroll pane, see {@link ScrollPane}.\r
943          * @author mzechner\r
944          * @author Nathan Sweet */\r
945         static public class ScrollPaneStyle {\r
946                 /** Optional. */\r
947                 public Drawable background, corner;\r
948                 /** Optional. */\r
949                 public Drawable hScroll, hScrollKnob;\r
950                 /** Optional. */\r
951                 public Drawable vScroll, vScrollKnob;\r
952 \r
953                 public ScrollPaneStyle () {\r
954                 }\r
955 \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
962                 }\r
963 \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
970                 }\r
971         }\r
972 }\r