OSDN Git Service

updated IKVM to latest
[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         float amountX, amountY;\r
60         float visualAmountX, visualAmountY;\r
61         float maxX, maxY;\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
68 \r
69         boolean flickScroll = true;\r
70         float velocityX, velocityY;\r
71         float flingTimer;\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
80 \r
81         /** @param widget May be null. */\r
82         public ScrollPane (Actor widget) {\r
83                 this(widget, new ScrollPaneStyle());\r
84         }\r
85 \r
86         /** @param widget May be null. */\r
87         public ScrollPane (Actor widget, Skin skin) {\r
88                 this(widget, skin.get(ScrollPaneStyle.class));\r
89         }\r
90 \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
94         }\r
95 \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
99                 this.style = style;\r
100                 setWidget(widget);\r
101                 setWidth(150);\r
102                 setHeight(150);\r
103 \r
104                 addCaptureListener(new InputListener() {\r
105                         private float handlePosition;\r
106 \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
111 \r
112                                 if (!flickScroll) resetFade();\r
113 \r
114                                 if (fadeAlpha == 0) return false;\r
115 \r
116                                 if (scrollX && hScrollBounds.contains(x, y)) {\r
117                                         event.stop();\r
118                                         resetFade();\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
124                                                 return true;\r
125                                         }\r
126                                         setScrollX(amountX + getPageScrollX() * (x < hKnobBounds.x ? -1 : 1));\r
127                                         return true;\r
128                                 }\r
129                                 if (scrollY && vScrollBounds.contains(x, y)) {\r
130                                         event.stop();\r
131                                         resetFade();\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
137                                                 return true;\r
138                                         }\r
139                                         setScrollY(amountY + getPageScrollY() * (y < vKnobBounds.y ? 1 : -1));\r
140                                         return true;\r
141                                 }\r
142                                 return false;\r
143                         }\r
144 \r
145                         public void touchUp (InputEvent event, float x, float y, int pointer, int button) {\r
146                                 if (pointer != draggingPointer) return;\r
147                                 cancel();\r
148                         }\r
149 \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
170                                 }\r
171                         }\r
172 \r
173                         public boolean mouseMoved (InputEvent event, float x, float y) {\r
174                                 if (!flickScroll) resetFade();\r
175                                 return false;\r
176                         }\r
177                 });\r
178 \r
179                 flickScrollListener = new ActorGestureListener() {\r
180                         public void pan (InputEvent event, float x, float y, float deltaX, float deltaY) {\r
181                                 resetFade();\r
182                                 amountX -= deltaX;\r
183                                 amountY += deltaY;\r
184                                 clamp();\r
185                                 cancelTouchFocusedChild(event);\r
186                         }\r
187 \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
191                                         velocityX = x;\r
192                                         cancelTouchFocusedChild(event);\r
193                                 }\r
194                                 if (Math.abs(y) > 150) {\r
195                                         flingTimer = flingTime;\r
196                                         velocityY = -y;\r
197                                         cancelTouchFocusedChild(event);\r
198                                 }\r
199                         }\r
200 \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
204                                         return true;\r
205                                 }\r
206                                 return false;\r
207                         }\r
208                 };\r
209                 addListener(flickScrollListener);\r
210 \r
211                 addListener(new InputListener() {\r
212                         public boolean scrolled (InputEvent event, float x, float y, int amount) {\r
213                                 resetFade();\r
214                                 if (scrollY)\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
218                                 return true;\r
219                         }\r
220                 });\r
221         }\r
222 \r
223         void resetFade () {\r
224                 fadeAlpha = fadeAlphaSeconds;\r
225                 fadeDelay = fadeDelaySeconds;\r
226         }\r
227 \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
232         }\r
233 \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
240         }\r
241 \r
242         void clamp () {\r
243                 if (!clamp) return;\r
244                 scrollX(overscrollX ? MathUtils.clamp(amountX, -overscrollDistance, maxX + overscrollDistance) : MathUtils.clamp(amountX,\r
245                         0, maxX));\r
246                 scrollY(overscrollY ? MathUtils.clamp(amountY, -overscrollDistance, maxY + overscrollDistance) : MathUtils.clamp(amountY,\r
247                         0, maxY));\r
248         }\r
249 \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
254         }\r
255 \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
259                 return style;\r
260         }\r
261 \r
262         public void act (float delta) {\r
263                 super.act(delta);\r
264 \r
265                 boolean panning = flickScrollListener.getGestureDetector().isPanning();\r
266 \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
270                 }\r
271 \r
272                 if (flingTimer > 0) {\r
273                         resetFade();\r
274 \r
275                         float alpha = flingTimer / flingTime;\r
276                         amountX -= velocityX * alpha * delta;\r
277                         amountY -= velocityY * alpha * delta;\r
278                         clamp();\r
279 \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
285 \r
286                         flingTimer -= delta;\r
287                         if (flingTimer <= 0) {\r
288                                 velocityX = 0;\r
289                                 velocityY = 0;\r
290                         }\r
291                 }\r
292 \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
297                                 else\r
298                                         visualScrollX(Math.max(amountX, visualAmountX - Math.max(150 * delta, (visualAmountX - amountX) * 5 * delta)));\r
299                         }\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
303                                 else\r
304                                         visualScrollY(Math.max(amountY, visualAmountY - Math.max(150 * delta, (visualAmountY - amountY) * 5 * delta)));\r
305                         }\r
306                 } else {\r
307                         if (visualAmountX != amountX) visualScrollX(amountX);\r
308                         if (visualAmountY != amountY) visualScrollY(amountY);\r
309                 }\r
310 \r
311                 if (!panning) {\r
312                         if (overscrollX && scrollX) {\r
313                                 if (amountX < 0) {\r
314                                         resetFade();\r
315                                         amountX += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountX / overscrollDistance)\r
316                                                 * delta;\r
317                                         if (amountX > 0) scrollX(0);\r
318                                 } else if (amountX > maxX) {\r
319                                         resetFade();\r
320                                         amountX -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxX - amountX)\r
321                                                 / overscrollDistance)\r
322                                                 * delta;\r
323                                         if (amountX < maxX) scrollX(maxX);\r
324                                 }\r
325                         }\r
326                         if (overscrollY && scrollY) {\r
327                                 if (amountY < 0) {\r
328                                         resetFade();\r
329                                         amountY += (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -amountY / overscrollDistance)\r
330                                                 * delta;\r
331                                         if (amountY > 0) scrollY(0);\r
332                                 } else if (amountY > maxY) {\r
333                                         resetFade();\r
334                                         amountY -= (overscrollSpeedMin + (overscrollSpeedMax - overscrollSpeedMin) * -(maxY - amountY)\r
335                                                 / overscrollDistance)\r
336                                                 * delta;\r
337                                         if (amountY < maxY) scrollY(maxY);\r
338                                 }\r
339                         }\r
340                 }\r
341         }\r
342 \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
347 \r
348                 float bgLeftWidth = 0, bgRightWidth = 0, bgTopHeight = 0, bgBottomHeight = 0;\r
349                 if (bg != null) {\r
350                         bgLeftWidth = bg.getLeftWidth();\r
351                         bgRightWidth = bg.getRightWidth();\r
352                         bgTopHeight = bg.getTopHeight();\r
353                         bgBottomHeight = bg.getBottomHeight();\r
354                 }\r
355 \r
356                 float width = getWidth();\r
357                 float height = getHeight();\r
358 \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
365 \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
369 \r
370                 if (widget == null) return;\r
371 \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
378                 } else {\r
379                         widgetWidth = widget.getWidth();\r
380                         widgetHeight = widget.getHeight();\r
381                 }\r
382 \r
383                 // Determine if horizontal/vertical scrollbars are needed.\r
384                 scrollX = forceScrollX || (widgetWidth > areaWidth && !disableX);\r
385                 scrollY = forceScrollY || (widgetHeight > areaHeight && !disableY);\r
386 \r
387                 boolean fade = fadeScrollBars;\r
388                 if (!fade) {\r
389                         // Check again, now taking into account the area that's taken up by any enabled scrollbars.\r
390                         if (scrollY) {\r
391                                 areaWidth -= scrollbarWidth;\r
392                                 if (!scrollX && widgetWidth > areaWidth && !disableX) {\r
393                                         scrollX = true;\r
394                                 }\r
395                         }\r
396                         if (scrollX) {\r
397                                 areaHeight -= scrollbarHeight;\r
398                                 if (!scrollY && widgetHeight > areaHeight && !disableY) {\r
399                                         scrollY = true;\r
400                                         areaWidth -= scrollbarWidth;\r
401                                 }\r
402                         }\r
403                 }\r
404 \r
405                 // Set the widget area bounds.\r
406                 widgetAreaBounds.set(bgLeftWidth, bgBottomHeight, areaWidth, areaHeight);\r
407 \r
408                 if (fade) {\r
409                         // Make sure widget is drawn under fading scrollbars.\r
410                         if (scrollX) areaHeight -= scrollbarHeight;\r
411                         if (scrollY) areaWidth -= scrollbarWidth;\r
412                 } else {\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
417                         } else {\r
418                                 // Offset widget area y for horizontal scrollbar.\r
419                                 if (scrollX) widgetAreaBounds.y += scrollbarHeight;\r
420                         }\r
421                 }\r
422 \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
426 \r
427                 maxX = widgetWidth - areaWidth;\r
428                 maxY = widgetHeight - areaHeight;\r
429                 if (fade) {\r
430                         // Make sure widget is drawn under fading scrollbars.\r
431                         if (scrollX) maxY -= scrollbarHeight;\r
432                         if (scrollY) maxX -= scrollbarWidth;\r
433                 }\r
434                 scrollX(MathUtils.clamp(amountX, 0, maxX));\r
435                 scrollY(MathUtils.clamp(amountY, 0, maxY));\r
436 \r
437                 // Set the bounds and scroll knob sizes if scrollbars are needed.\r
438                 if (scrollX) {\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
446                         } else {\r
447                                 hScrollBounds.set(0, 0, 0, 0);\r
448                                 hKnobBounds.set(0, 0, 0, 0);\r
449                         }\r
450                 }\r
451                 if (scrollY) {\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
459                         } else {\r
460                                 vScrollBounds.set(0, 0, 0, 0);\r
461                                 vKnobBounds.set(0, 0, 0, 0);\r
462                         }\r
463                 }\r
464 \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
471                                 layout.validate();\r
472                         }\r
473                 } else {\r
474                         if (widget instanceof Layout) ((Layout)widget).validate();\r
475                 }\r
476         }\r
477 \r
478         @Override\r
479         public void draw (SpriteBatch batch, float parentAlpha) {\r
480                 if (widget == null) return;\r
481 \r
482                 validate();\r
483 \r
484                 // Setup transform for this group.\r
485                 applyTransform(batch, computeTransform());\r
486 \r
487                 if (scrollX) hKnobBounds.x = hScrollBounds.x + (int)((hScrollBounds.width - hKnobBounds.width) * getScrollPercentX());\r
488                 if (scrollY)\r
489                         vKnobBounds.y = vScrollBounds.y + (int)((vScrollBounds.height - vKnobBounds.height) * (1 - getScrollPercentY()));\r
490 \r
491                 // Calculate the widget's position depending on the scroll state and available widget area.\r
492                 float y = widgetAreaBounds.y;\r
493                 if (!scrollY)\r
494                         y -= (int)maxY;\r
495                 else\r
496                         y -= (int)(maxY - visualAmountY);\r
497 \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
503                 }\r
504 \r
505                 float x = widgetAreaBounds.x;\r
506                 if (scrollX) x -= (int)visualAmountX;\r
507                 widget.setPosition(x, y);\r
508 \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
515                 }\r
516 \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
520 \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
525                 batch.flush();\r
526 \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
531                 }\r
532 \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
537                                 style.corner\r
538                                         .draw(batch, hScrollBounds.x + hScrollBounds.width, hScrollBounds.y, vScrollBounds.width, vScrollBounds.y);\r
539                         }\r
540                 }\r
541                 if (scrollX) {\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
546                 }\r
547                 if (scrollY) {\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
552                 }\r
553 \r
554                 resetTransform(batch);\r
555         }\r
556 \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
561                         return width;\r
562                 }\r
563                 return 150;\r
564         }\r
565 \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
570                         return height;\r
571                 }\r
572                 return 150;\r
573         }\r
574 \r
575         public float getMinWidth () {\r
576                 return 0;\r
577         }\r
578 \r
579         public float getMinHeight () {\r
580                 return 0;\r
581         }\r
582 \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
590         }\r
591 \r
592         /** Returns the actor embedded in this scroll pane, or null. */\r
593         public Actor getWidget () {\r
594                 return widget;\r
595         }\r
596 \r
597         /** @deprecated */\r
598         public void addActor (Actor actor) {\r
599                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
600         }\r
601 \r
602         /** @deprecated */\r
603         public void addActorAt (int index, Actor actor) {\r
604                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
605         }\r
606 \r
607         /** @deprecated */\r
608         public void addActorBefore (Actor actorBefore, Actor actor) {\r
609                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
610         }\r
611 \r
612         /** @deprecated */\r
613         public void addActorAfter (Actor actorAfter, Actor actor) {\r
614                 throw new UnsupportedOperationException("Use ScrollPane#setWidget.");\r
615         }\r
616 \r
617         public boolean removeActor (Actor actor) {\r
618                 if (actor != widget) return false;\r
619                 setWidget(null);\r
620                 return true;\r
621         }\r
622 \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
628         }\r
629 \r
630         /** Called whenever the x scroll amount is changed. */\r
631         protected void scrollX (float pixelsX) {\r
632                 this.amountX = pixelsX;\r
633         }\r
634 \r
635         /** Called whenever the y scroll amount is changed. */\r
636         protected void scrollY (float pixelsY) {\r
637                 this.amountY = pixelsY;\r
638         }\r
639 \r
640         /** Called whenever the visual x scroll amount is changed. */\r
641         protected void visualScrollX (float pixelsX) {\r
642                 this.visualAmountX = pixelsX;\r
643         }\r
644 \r
645         /** Called whenever the visual y scroll amount is changed. */\r
646         protected void visualScrollY (float pixelsY) {\r
647                 this.visualAmountY = pixelsY;\r
648         }\r
649 \r
650         /** Returns the amount to scroll horizontally when the scrollbar is clicked. */\r
651         protected float getPageScrollX () {\r
652                 return areaWidth;\r
653         }\r
654 \r
655         /** Returns the amount to scroll vertically when the scrollbar is clicked. */\r
656         protected float getPageScrollY () {\r
657                 return areaHeight;\r
658         }\r
659 \r
660         public void setScrollX (float pixels) {\r
661                 scrollX(MathUtils.clamp(pixels, 0, maxX));\r
662         }\r
663 \r
664         /** Returns the x scroll position in pixels. */\r
665         public float getScrollX () {\r
666                 return amountX;\r
667         }\r
668 \r
669         public void setScrollY (float pixels) {\r
670                 scrollY(MathUtils.clamp(pixels, 0, maxY));\r
671         }\r
672 \r
673         /** Returns the y scroll position in pixels. */\r
674         public float getScrollY () {\r
675                 return amountY;\r
676         }\r
677 \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
682         }\r
683 \r
684         public float getVisualScrollX () {\r
685                 return !scrollX ? 0 : visualAmountX;\r
686         }\r
687 \r
688         public float getVisualScrollY () {\r
689                 return !scrollY ? 0 : visualAmountY;\r
690         }\r
691 \r
692         public float getScrollPercentX () {\r
693                 return MathUtils.clamp(amountX / maxX, 0, 1);\r
694         }\r
695 \r
696         public void setScrollPercentX (float percentX) {\r
697                 scrollX(maxX * MathUtils.clamp(percentX, 0, 1));\r
698         }\r
699 \r
700         public float getScrollPercentY () {\r
701                 return MathUtils.clamp(amountY / maxY, 0, 1);\r
702         }\r
703 \r
704         public void setScrollPercentY (float percentY) {\r
705                 scrollY(maxY * MathUtils.clamp(percentY, 0, 1));\r
706         }\r
707 \r
708         public void setFlickScroll (boolean flickScroll) {\r
709                 if (this.flickScroll == flickScroll) return;\r
710                 this.flickScroll = flickScroll;\r
711                 if (flickScroll)\r
712                         addListener(flickScrollListener);\r
713                 else\r
714                         removeListener(flickScrollListener);\r
715                 invalidate();\r
716         }\r
717 \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
725 \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
730         }\r
731 \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
739 \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
744         }\r
745 \r
746         /** Returns the maximum scroll value in the x direction. */\r
747         public float getMaxX () {\r
748                 return maxX;\r
749         }\r
750 \r
751         /** Returns the maximum scroll value in the y direction. */\r
752         public float getMaxY () {\r
753                 return maxY;\r
754         }\r
755 \r
756         public float getScrollBarHeight () {\r
757                 return style.hScrollKnob == null || !scrollX ? 0 : style.hScrollKnob.getMinHeight();\r
758         }\r
759 \r
760         public float getScrollBarWidth () {\r
761                 return style.vScrollKnob == null || !scrollY ? 0 : style.vScrollKnob.getMinWidth();\r
762         }\r
763 \r
764         public boolean isScrollX () {\r
765                 return scrollX;\r
766         }\r
767 \r
768         public boolean isScrollY () {\r
769                 return scrollY;\r
770         }\r
771 \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
774                 disableX = x;\r
775                 disableY = y;\r
776         }\r
777 \r
778         public boolean isDragging () {\r
779                 return draggingPointer != -1;\r
780         }\r
781 \r
782         public boolean isPanning () {\r
783                 return flickScrollListener.getGestureDetector().isPanning();\r
784         }\r
785 \r
786         public boolean isFlinging () {\r
787                 return flingTimer > 0;\r
788         }\r
789 \r
790         public void setVelocityX (float velocityX) {\r
791                 this.velocityX = velocityX;\r
792         }\r
793 \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
800         }\r
801 \r
802         public void setVelocityY (float velocityY) {\r
803                 this.velocityY = velocityY;\r
804         }\r
805 \r
806         /** Gets the flick scroll y velocity. */\r
807         public float getVelocityY () {\r
808                 return velocityY;\r
809         }\r
810 \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
816         }\r
817 \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
824         }\r
825 \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
829                 forceScrollX = x;\r
830                 forceScrollY = y;\r
831         }\r
832 \r
833         public boolean isForceScrollX () {\r
834                 return forceScrollX;\r
835         }\r
836 \r
837         public boolean isForceScrollY () {\r
838                 return forceScrollY;\r
839         }\r
840 \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
844         }\r
845 \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
849         }\r
850 \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
856                 invalidate();\r
857         }\r
858 \r
859         public void setupFadeScrollBars (float fadeAlphaSeconds, float fadeDelaySeconds) {\r
860                 this.fadeAlphaSeconds = fadeAlphaSeconds;\r
861                 this.fadeDelaySeconds = fadeDelaySeconds;\r
862         }\r
863 \r
864         public void setSmoothScrolling (boolean smoothScrolling) {\r
865                 this.smoothScrolling = smoothScrolling;\r
866         }\r
867 \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
873                 invalidate();\r
874         }\r
875 \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
880         }\r
881 \r
882         /** The style for a scroll pane, see {@link ScrollPane}.\r
883          * @author mzechner\r
884          * @author Nathan Sweet */\r
885         static public class ScrollPaneStyle {\r
886                 /** Optional. */\r
887                 public Drawable background, corner;\r
888                 /** Optional. */\r
889                 public Drawable hScroll, hScrollKnob;\r
890                 /** Optional. */\r
891                 public Drawable vScroll, vScrollKnob;\r
892 \r
893                 public ScrollPaneStyle () {\r
894                 }\r
895 \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
902                 }\r
903 \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
910                 }\r
911         }\r
912 }\r