• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.scenes.scene2d;
18 
19 import com.badlogic.gdx.Application.ApplicationType;
20 import com.badlogic.gdx.Gdx;
21 import com.badlogic.gdx.Graphics;
22 import com.badlogic.gdx.Input;
23 import com.badlogic.gdx.InputAdapter;
24 import com.badlogic.gdx.InputMultiplexer;
25 import com.badlogic.gdx.graphics.Camera;
26 import com.badlogic.gdx.graphics.Color;
27 import com.badlogic.gdx.graphics.GL20;
28 import com.badlogic.gdx.graphics.OrthographicCamera;
29 import com.badlogic.gdx.graphics.g2d.Batch;
30 import com.badlogic.gdx.graphics.g2d.SpriteBatch;
31 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
32 import com.badlogic.gdx.math.Matrix4;
33 import com.badlogic.gdx.math.Rectangle;
34 import com.badlogic.gdx.math.Vector2;
35 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;
36 import com.badlogic.gdx.scenes.scene2d.ui.Table;
37 import com.badlogic.gdx.scenes.scene2d.ui.Table.Debug;
38 import com.badlogic.gdx.scenes.scene2d.utils.FocusListener.FocusEvent;
39 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
40 import com.badlogic.gdx.utils.Array;
41 import com.badlogic.gdx.utils.Disposable;
42 import com.badlogic.gdx.utils.Pool.Poolable;
43 import com.badlogic.gdx.utils.Pools;
44 import com.badlogic.gdx.utils.Scaling;
45 import com.badlogic.gdx.utils.SnapshotArray;
46 import com.badlogic.gdx.utils.viewport.ScalingViewport;
47 import com.badlogic.gdx.utils.viewport.Viewport;
48 
49 /** A 2D scene graph containing hierarchies of {@link Actor actors}. Stage handles the viewport and distributes input events.
50  * <p>
51  * {@link #setViewport(Viewport)} controls the coordinates used within the stage and sets up the camera used to convert between
52  * stage coordinates and screen coordinates.
53  * <p>
54  * A stage must receive input events so it can distribute them to actors. This is typically done by passing the stage to
55  * {@link Input#setInputProcessor(com.badlogic.gdx.InputProcessor) Gdx.input.setInputProcessor}. An {@link InputMultiplexer} may be
56  * used to handle input events before or after the stage does. If an actor handles an event by returning true from the input
57  * method, then the stage's input method will also return true, causing subsequent InputProcessors to not receive the event.
58  * <p>
59  * The Stage and its constituents (like Actors and Listeners) are not thread-safe and should only be updated and queried from a
60  * single thread (presumably the main render thread). Methods should be reentrant, so you can update Actors and Stages from within
61  * callbacks and handlers.
62  * @author mzechner
63  * @author Nathan Sweet */
64 public class Stage extends InputAdapter implements Disposable {
65 	/** True if any actor has ever had debug enabled. */
66 	static boolean debug;
67 
68 	private Viewport viewport;
69 	private final Batch batch;
70 	private boolean ownsBatch;
71 	private final Group root;
72 	private final Vector2 tempCoords = new Vector2();
73 	private final Actor[] pointerOverActors = new Actor[20];
74 	private final boolean[] pointerTouched = new boolean[20];
75 	private final int[] pointerScreenX = new int[20];
76 	private final int[] pointerScreenY = new int[20];
77 	private int mouseScreenX, mouseScreenY;
78 	private Actor mouseOverActor;
79 	private Actor keyboardFocus, scrollFocus;
80 	private final SnapshotArray<TouchFocus> touchFocuses = new SnapshotArray(true, 4, TouchFocus.class);
81 	private boolean actionsRequestRendering = true;
82 
83 	private ShapeRenderer debugShapes;
84 	private boolean debugInvisible, debugAll, debugUnderMouse, debugParentUnderMouse;
85 	private Debug debugTableUnderMouse = Debug.none;
86 	private final Color debugColor = new Color(0, 1, 0, 0.85f);
87 
88 	/** Creates a stage with a {@link ScalingViewport} set to {@link Scaling#stretch}. The stage will use its own {@link Batch}
89 	 * which will be disposed when the stage is disposed. */
Stage()90 	public Stage () {
91 		this(new ScalingViewport(Scaling.stretch, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera()),
92 			new SpriteBatch());
93 		ownsBatch = true;
94 	}
95 
96 	/** Creates a stage with the specified viewport. The stage will use its own {@link Batch} which will be disposed when the stage
97 	 * is disposed. */
Stage(Viewport viewport)98 	public Stage (Viewport viewport) {
99 		this(viewport, new SpriteBatch());
100 		ownsBatch = true;
101 	}
102 
103 	/** Creates a stage with the specified viewport and batch. This can be used to avoid creating a new batch (which can be somewhat
104 	 * slow) if multiple stages are used during an application's life time.
105 	 * @param batch Will not be disposed if {@link #dispose()} is called, handle disposal yourself. */
Stage(Viewport viewport, Batch batch)106 	public Stage (Viewport viewport, Batch batch) {
107 		if (viewport == null) throw new IllegalArgumentException("viewport cannot be null.");
108 		if (batch == null) throw new IllegalArgumentException("batch cannot be null.");
109 		this.viewport = viewport;
110 		this.batch = batch;
111 
112 		root = new Group();
113 		root.setStage(this);
114 
115 		viewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
116 	}
117 
draw()118 	public void draw () {
119 		Camera camera = viewport.getCamera();
120 		camera.update();
121 
122 		if (!root.isVisible()) return;
123 
124 		Batch batch = this.batch;
125 		if (batch != null) {
126 			batch.setProjectionMatrix(camera.combined);
127 			batch.begin();
128 			root.draw(batch, 1);
129 			batch.end();
130 		}
131 
132 		if (debug) drawDebug();
133 	}
134 
drawDebug()135 	private void drawDebug () {
136 		if (debugShapes == null) {
137 			debugShapes = new ShapeRenderer();
138 			debugShapes.setAutoShapeType(true);
139 		}
140 
141 		if (debugUnderMouse || debugParentUnderMouse || debugTableUnderMouse != Debug.none) {
142 			screenToStageCoordinates(tempCoords.set(Gdx.input.getX(), Gdx.input.getY()));
143 			Actor actor = hit(tempCoords.x, tempCoords.y, true);
144 			if (actor == null) return;
145 
146 			if (debugParentUnderMouse && actor.parent != null) actor = actor.parent;
147 
148 			if (debugTableUnderMouse == Debug.none)
149 				actor.setDebug(true);
150 			else {
151 				while (actor != null) {
152 					if (actor instanceof Table) break;
153 					actor = actor.parent;
154 				}
155 				if (actor == null) return;
156 				((Table)actor).debug(debugTableUnderMouse);
157 			}
158 
159 			if (debugAll && actor instanceof Group) ((Group)actor).debugAll();
160 
161 			disableDebug(root, actor);
162 		} else {
163 			if (debugAll) root.debugAll();
164 		}
165 
166 		Gdx.gl.glEnable(GL20.GL_BLEND);
167 		debugShapes.setProjectionMatrix(viewport.getCamera().combined);
168 		debugShapes.begin();
169 		root.drawDebug(debugShapes);
170 		debugShapes.end();
171 	}
172 
173 	/** Disables debug on all actors recursively except the specified actor and any children. */
disableDebug(Actor actor, Actor except)174 	private void disableDebug (Actor actor, Actor except) {
175 		if (actor == except) return;
176 		actor.setDebug(false);
177 		if (actor instanceof Group) {
178 			SnapshotArray<Actor> children = ((Group)actor).children;
179 			for (int i = 0, n = children.size; i < n; i++)
180 				disableDebug(children.get(i), except);
181 		}
182 	}
183 
184 	/** Calls {@link #act(float)} with {@link Graphics#getDeltaTime()}, limited to a minimum of 30fps. */
act()185 	public void act () {
186 		act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
187 	}
188 
189 	/** Calls the {@link Actor#act(float)} method on each actor in the stage. Typically called each frame. This method also fires
190 	 * enter and exit events.
191 	 * @param delta Time in seconds since the last frame. */
act(float delta)192 	public void act (float delta) {
193 		// Update over actors. Done in act() because actors may change position, which can fire enter/exit without an input event.
194 		for (int pointer = 0, n = pointerOverActors.length; pointer < n; pointer++) {
195 			Actor overLast = pointerOverActors[pointer];
196 			// Check if pointer is gone.
197 			if (!pointerTouched[pointer]) {
198 				if (overLast != null) {
199 					pointerOverActors[pointer] = null;
200 					screenToStageCoordinates(tempCoords.set(pointerScreenX[pointer], pointerScreenY[pointer]));
201 					// Exit over last.
202 					InputEvent event = Pools.obtain(InputEvent.class);
203 					event.setType(InputEvent.Type.exit);
204 					event.setStage(this);
205 					event.setStageX(tempCoords.x);
206 					event.setStageY(tempCoords.y);
207 					event.setRelatedActor(overLast);
208 					event.setPointer(pointer);
209 					overLast.fire(event);
210 					Pools.free(event);
211 				}
212 				continue;
213 			}
214 			// Update over actor for the pointer.
215 			pointerOverActors[pointer] = fireEnterAndExit(overLast, pointerScreenX[pointer], pointerScreenY[pointer], pointer);
216 		}
217 		// Update over actor for the mouse on the desktop.
218 		ApplicationType type = Gdx.app.getType();
219 		if (type == ApplicationType.Desktop || type == ApplicationType.Applet || type == ApplicationType.WebGL)
220 			mouseOverActor = fireEnterAndExit(mouseOverActor, mouseScreenX, mouseScreenY, -1);
221 
222 		root.act(delta);
223 	}
224 
fireEnterAndExit(Actor overLast, int screenX, int screenY, int pointer)225 	private Actor fireEnterAndExit (Actor overLast, int screenX, int screenY, int pointer) {
226 		// Find the actor under the point.
227 		screenToStageCoordinates(tempCoords.set(screenX, screenY));
228 		Actor over = hit(tempCoords.x, tempCoords.y, true);
229 		if (over == overLast) return overLast;
230 
231 		// Exit overLast.
232 		if (overLast != null) {
233 			InputEvent event = Pools.obtain(InputEvent.class);
234 			event.setStage(this);
235 			event.setStageX(tempCoords.x);
236 			event.setStageY(tempCoords.y);
237 			event.setPointer(pointer);
238 			event.setType(InputEvent.Type.exit);
239 			event.setRelatedActor(over);
240 			overLast.fire(event);
241 			Pools.free(event);
242 		}
243 		// Enter over.
244 		if (over != null) {
245 			InputEvent event = Pools.obtain(InputEvent.class);
246 			event.setStage(this);
247 			event.setStageX(tempCoords.x);
248 			event.setStageY(tempCoords.y);
249 			event.setPointer(pointer);
250 			event.setType(InputEvent.Type.enter);
251 			event.setRelatedActor(overLast);
252 			over.fire(event);
253 			Pools.free(event);
254 		}
255 		return over;
256 	}
257 
258 	/** Applies a touch down event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event. */
touchDown(int screenX, int screenY, int pointer, int button)259 	public boolean touchDown (int screenX, int screenY, int pointer, int button) {
260 		if (screenX < viewport.getScreenX() || screenX >= viewport.getScreenX() + viewport.getScreenWidth()) return false;
261 		if (Gdx.graphics.getHeight() - screenY < viewport.getScreenY()
262 			|| Gdx.graphics.getHeight() - screenY >= viewport.getScreenY() + viewport.getScreenHeight()) return false;
263 
264 		pointerTouched[pointer] = true;
265 		pointerScreenX[pointer] = screenX;
266 		pointerScreenY[pointer] = screenY;
267 
268 		screenToStageCoordinates(tempCoords.set(screenX, screenY));
269 
270 		InputEvent event = Pools.obtain(InputEvent.class);
271 		event.setType(Type.touchDown);
272 		event.setStage(this);
273 		event.setStageX(tempCoords.x);
274 		event.setStageY(tempCoords.y);
275 		event.setPointer(pointer);
276 		event.setButton(button);
277 
278 		Actor target = hit(tempCoords.x, tempCoords.y, true);
279 		if (target == null) {
280 			if (root.getTouchable() == Touchable.enabled) root.fire(event);
281 		} else {
282 			target.fire(event);
283 		}
284 
285 		boolean handled = event.isHandled();
286 		Pools.free(event);
287 		return handled;
288 	}
289 
290 	/** Applies a touch moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event.
291 	 * Only {@link InputListener listeners} that returned true for touchDown will receive this event. */
touchDragged(int screenX, int screenY, int pointer)292 	public boolean touchDragged (int screenX, int screenY, int pointer) {
293 		pointerScreenX[pointer] = screenX;
294 		pointerScreenY[pointer] = screenY;
295 		mouseScreenX = screenX;
296 		mouseScreenY = screenY;
297 
298 		if (touchFocuses.size == 0) return false;
299 
300 		screenToStageCoordinates(tempCoords.set(screenX, screenY));
301 
302 		InputEvent event = Pools.obtain(InputEvent.class);
303 		event.setType(Type.touchDragged);
304 		event.setStage(this);
305 		event.setStageX(tempCoords.x);
306 		event.setStageY(tempCoords.y);
307 		event.setPointer(pointer);
308 
309 		SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
310 		TouchFocus[] focuses = touchFocuses.begin();
311 		for (int i = 0, n = touchFocuses.size; i < n; i++) {
312 			TouchFocus focus = focuses[i];
313 			if (focus.pointer != pointer) continue;
314 			if (!touchFocuses.contains(focus, true)) continue; // Touch focus already gone.
315 			event.setTarget(focus.target);
316 			event.setListenerActor(focus.listenerActor);
317 			if (focus.listener.handle(event)) event.handle();
318 		}
319 		touchFocuses.end();
320 
321 		boolean handled = event.isHandled();
322 		Pools.free(event);
323 		return handled;
324 	}
325 
326 	/** Applies a touch up event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event.
327 	 * Only {@link InputListener listeners} that returned true for touchDown will receive this event. */
touchUp(int screenX, int screenY, int pointer, int button)328 	public boolean touchUp (int screenX, int screenY, int pointer, int button) {
329 		pointerTouched[pointer] = false;
330 		pointerScreenX[pointer] = screenX;
331 		pointerScreenY[pointer] = screenY;
332 
333 		if (touchFocuses.size == 0) return false;
334 
335 		screenToStageCoordinates(tempCoords.set(screenX, screenY));
336 
337 		InputEvent event = Pools.obtain(InputEvent.class);
338 		event.setType(Type.touchUp);
339 		event.setStage(this);
340 		event.setStageX(tempCoords.x);
341 		event.setStageY(tempCoords.y);
342 		event.setPointer(pointer);
343 		event.setButton(button);
344 
345 		SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
346 		TouchFocus[] focuses = touchFocuses.begin();
347 		for (int i = 0, n = touchFocuses.size; i < n; i++) {
348 			TouchFocus focus = focuses[i];
349 			if (focus.pointer != pointer || focus.button != button) continue;
350 			if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone.
351 			event.setTarget(focus.target);
352 			event.setListenerActor(focus.listenerActor);
353 			if (focus.listener.handle(event)) event.handle();
354 			Pools.free(focus);
355 		}
356 		touchFocuses.end();
357 
358 		boolean handled = event.isHandled();
359 		Pools.free(event);
360 		return handled;
361 	}
362 
363 	/** Applies a mouse moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the event.
364 	 * This event only occurs on the desktop. */
mouseMoved(int screenX, int screenY)365 	public boolean mouseMoved (int screenX, int screenY) {
366 		if (screenX < viewport.getScreenX() || screenX >= viewport.getScreenX() + viewport.getScreenWidth()) return false;
367 		if (Gdx.graphics.getHeight() - screenY < viewport.getScreenY()
368 			|| Gdx.graphics.getHeight() - screenY >= viewport.getScreenY() + viewport.getScreenHeight()) return false;
369 
370 		mouseScreenX = screenX;
371 		mouseScreenY = screenY;
372 
373 		screenToStageCoordinates(tempCoords.set(screenX, screenY));
374 
375 		InputEvent event = Pools.obtain(InputEvent.class);
376 		event.setStage(this);
377 		event.setType(Type.mouseMoved);
378 		event.setStageX(tempCoords.x);
379 		event.setStageY(tempCoords.y);
380 
381 		Actor target = hit(tempCoords.x, tempCoords.y, true);
382 		if (target == null) target = root;
383 
384 		target.fire(event);
385 		boolean handled = event.isHandled();
386 		Pools.free(event);
387 		return handled;
388 	}
389 
390 	/** Applies a mouse scroll event to the stage and returns true if an actor in the scene {@link Event#handle() handled} the
391 	 * event. This event only occurs on the desktop. */
scrolled(int amount)392 	public boolean scrolled (int amount) {
393 		Actor target = scrollFocus == null ? root : scrollFocus;
394 
395 		screenToStageCoordinates(tempCoords.set(mouseScreenX, mouseScreenY));
396 
397 		InputEvent event = Pools.obtain(InputEvent.class);
398 		event.setStage(this);
399 		event.setType(InputEvent.Type.scrolled);
400 		event.setScrollAmount(amount);
401 		event.setStageX(tempCoords.x);
402 		event.setStageY(tempCoords.y);
403 		target.fire(event);
404 		boolean handled = event.isHandled();
405 		Pools.free(event);
406 		return handled;
407 	}
408 
409 	/** Applies a key down event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns
410 	 * true if the event was {@link Event#handle() handled}. */
keyDown(int keyCode)411 	public boolean keyDown (int keyCode) {
412 		Actor target = keyboardFocus == null ? root : keyboardFocus;
413 		InputEvent event = Pools.obtain(InputEvent.class);
414 		event.setStage(this);
415 		event.setType(InputEvent.Type.keyDown);
416 		event.setKeyCode(keyCode);
417 		target.fire(event);
418 		boolean handled = event.isHandled();
419 		Pools.free(event);
420 		return handled;
421 	}
422 
423 	/** Applies a key up event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns true
424 	 * if the event was {@link Event#handle() handled}. */
keyUp(int keyCode)425 	public boolean keyUp (int keyCode) {
426 		Actor target = keyboardFocus == null ? root : keyboardFocus;
427 		InputEvent event = Pools.obtain(InputEvent.class);
428 		event.setStage(this);
429 		event.setType(InputEvent.Type.keyUp);
430 		event.setKeyCode(keyCode);
431 		target.fire(event);
432 		boolean handled = event.isHandled();
433 		Pools.free(event);
434 		return handled;
435 	}
436 
437 	/** Applies a key typed event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and returns
438 	 * true if the event was {@link Event#handle() handled}. */
keyTyped(char character)439 	public boolean keyTyped (char character) {
440 		Actor target = keyboardFocus == null ? root : keyboardFocus;
441 		InputEvent event = Pools.obtain(InputEvent.class);
442 		event.setStage(this);
443 		event.setType(InputEvent.Type.keyTyped);
444 		event.setCharacter(character);
445 		target.fire(event);
446 		boolean handled = event.isHandled();
447 		Pools.free(event);
448 		return handled;
449 	}
450 
451 	/** Adds the listener to be notified for all touchDragged and touchUp events for the specified pointer and button. The actor
452 	 * will be used as the {@link Event#getListenerActor() listener actor} and {@link Event#getTarget() target}. */
addTouchFocus(EventListener listener, Actor listenerActor, Actor target, int pointer, int button)453 	public void addTouchFocus (EventListener listener, Actor listenerActor, Actor target, int pointer, int button) {
454 		TouchFocus focus = Pools.obtain(TouchFocus.class);
455 		focus.listenerActor = listenerActor;
456 		focus.target = target;
457 		focus.listener = listener;
458 		focus.pointer = pointer;
459 		focus.button = button;
460 		touchFocuses.add(focus);
461 	}
462 
463 	/** Removes the listener from being notified for all touchDragged and touchUp events for the specified pointer and button. Note
464 	 * the listener may never receive a touchUp event if this method is used. */
removeTouchFocus(EventListener listener, Actor listenerActor, Actor target, int pointer, int button)465 	public void removeTouchFocus (EventListener listener, Actor listenerActor, Actor target, int pointer, int button) {
466 		SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
467 		for (int i = touchFocuses.size - 1; i >= 0; i--) {
468 			TouchFocus focus = touchFocuses.get(i);
469 			if (focus.listener == listener && focus.listenerActor == listenerActor && focus.target == target
470 				&& focus.pointer == pointer && focus.button == button) {
471 				touchFocuses.removeIndex(i);
472 				Pools.free(focus);
473 			}
474 		}
475 	}
476 
477 	/** Cancels touch focus for the specified actor.
478 	 * @see #cancelTouchFocus() */
cancelTouchFocus(Actor actor)479 	public void cancelTouchFocus (Actor actor) {
480 		InputEvent event = Pools.obtain(InputEvent.class);
481 		event.setStage(this);
482 		event.setType(InputEvent.Type.touchUp);
483 		event.setStageX(Integer.MIN_VALUE);
484 		event.setStageY(Integer.MIN_VALUE);
485 
486 		// Cancel all current touch focuses for the specified listener, allowing for concurrent modification, and never cancel the
487 		// same focus twice.
488 		SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
489 		TouchFocus[] items = touchFocuses.begin();
490 		for (int i = 0, n = touchFocuses.size; i < n; i++) {
491 			TouchFocus focus = items[i];
492 			if (focus.listenerActor != actor) continue;
493 			if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone.
494 			event.setTarget(focus.target);
495 			event.setListenerActor(focus.listenerActor);
496 			event.setPointer(focus.pointer);
497 			event.setButton(focus.button);
498 			focus.listener.handle(event);
499 			// Cannot return TouchFocus to pool, as it may still be in use (eg if cancelTouchFocus is called from touchDragged).
500 		}
501 		touchFocuses.end();
502 
503 		Pools.free(event);
504 	}
505 
506 	/** Sends a touchUp event to all listeners that are registered to receive touchDragged and touchUp events and removes their
507 	 * touch focus. This method removes all touch focus listeners, but sends a touchUp event so that the state of the listeners
508 	 * remains consistent (listeners typically expect to receive touchUp eventually). The location of the touchUp is
509 	 * {@link Integer#MIN_VALUE}. Listeners can use {@link InputEvent#isTouchFocusCancel()} to ignore this event if needed. */
cancelTouchFocus()510 	public void cancelTouchFocus () {
511 		cancelTouchFocusExcept(null, null);
512 	}
513 
514 	/** Cancels touch focus for all listeners except the specified listener.
515 	 * @see #cancelTouchFocus() */
cancelTouchFocusExcept(EventListener exceptListener, Actor exceptActor)516 	public void cancelTouchFocusExcept (EventListener exceptListener, Actor exceptActor) {
517 		InputEvent event = Pools.obtain(InputEvent.class);
518 		event.setStage(this);
519 		event.setType(InputEvent.Type.touchUp);
520 		event.setStageX(Integer.MIN_VALUE);
521 		event.setStageY(Integer.MIN_VALUE);
522 
523 		// Cancel all current touch focuses except for the specified listener, allowing for concurrent modification, and never
524 		// cancel the same focus twice.
525 		SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
526 		TouchFocus[] items = touchFocuses.begin();
527 		for (int i = 0, n = touchFocuses.size; i < n; i++) {
528 			TouchFocus focus = items[i];
529 			if (focus.listener == exceptListener && focus.listenerActor == exceptActor) continue;
530 			if (!touchFocuses.removeValue(focus, true)) continue; // Touch focus already gone.
531 			event.setTarget(focus.target);
532 			event.setListenerActor(focus.listenerActor);
533 			event.setPointer(focus.pointer);
534 			event.setButton(focus.button);
535 			focus.listener.handle(event);
536 			// Cannot return TouchFocus to pool, as it may still be in use (eg if cancelTouchFocus is called from touchDragged).
537 		}
538 		touchFocuses.end();
539 
540 		Pools.free(event);
541 	}
542 
543 	/** Adds an actor to the root of the stage.
544 	 * @see Group#addActor(Actor) */
addActor(Actor actor)545 	public void addActor (Actor actor) {
546 		root.addActor(actor);
547 	}
548 
549 	/** Adds an action to the root of the stage.
550 	 * @see Group#addAction(Action) */
addAction(Action action)551 	public void addAction (Action action) {
552 		root.addAction(action);
553 	}
554 
555 	/** Returns the root's child actors.
556 	 * @see Group#getChildren() */
getActors()557 	public Array<Actor> getActors () {
558 		return root.children;
559 	}
560 
561 	/** Adds a listener to the root.
562 	 * @see Actor#addListener(EventListener) */
addListener(EventListener listener)563 	public boolean addListener (EventListener listener) {
564 		return root.addListener(listener);
565 	}
566 
567 	/** Removes a listener from the root.
568 	 * @see Actor#removeListener(EventListener) */
removeListener(EventListener listener)569 	public boolean removeListener (EventListener listener) {
570 		return root.removeListener(listener);
571 	}
572 
573 	/** Adds a capture listener to the root.
574 	 * @see Actor#addCaptureListener(EventListener) */
addCaptureListener(EventListener listener)575 	public boolean addCaptureListener (EventListener listener) {
576 		return root.addCaptureListener(listener);
577 	}
578 
579 	/** Removes a listener from the root.
580 	 * @see Actor#removeCaptureListener(EventListener) */
removeCaptureListener(EventListener listener)581 	public boolean removeCaptureListener (EventListener listener) {
582 		return root.removeCaptureListener(listener);
583 	}
584 
585 	/** Removes the root's children, actions, and listeners. */
clear()586 	public void clear () {
587 		unfocusAll();
588 		root.clear();
589 	}
590 
591 	/** Removes the touch, keyboard, and scroll focused actors. */
unfocusAll()592 	public void unfocusAll () {
593 		scrollFocus = null;
594 		keyboardFocus = null;
595 		cancelTouchFocus();
596 	}
597 
598 	/** Removes the touch, keyboard, and scroll focus for the specified actor and any descendants. */
unfocus(Actor actor)599 	public void unfocus (Actor actor) {
600 		cancelTouchFocus(actor);
601 		if (scrollFocus != null && scrollFocus.isDescendantOf(actor)) scrollFocus = null;
602 		if (keyboardFocus != null && keyboardFocus.isDescendantOf(actor)) keyboardFocus = null;
603 	}
604 
605 	/** Sets the actor that will receive key events.
606 	 * @param actor May be null. */
setKeyboardFocus(Actor actor)607 	public void setKeyboardFocus (Actor actor) {
608 		if (keyboardFocus == actor) return;
609 		FocusEvent event = Pools.obtain(FocusEvent.class);
610 		event.setStage(this);
611 		event.setType(FocusEvent.Type.keyboard);
612 		Actor oldKeyboardFocus = keyboardFocus;
613 		if (oldKeyboardFocus != null) {
614 			event.setFocused(false);
615 			event.setRelatedActor(actor);
616 			oldKeyboardFocus.fire(event);
617 		}
618 		if (!event.isCancelled()) {
619 			keyboardFocus = actor;
620 			if (actor != null) {
621 				event.setFocused(true);
622 				event.setRelatedActor(oldKeyboardFocus);
623 				actor.fire(event);
624 				if (event.isCancelled()) setKeyboardFocus(oldKeyboardFocus);
625 			}
626 		}
627 		Pools.free(event);
628 	}
629 
630 	/** Gets the actor that will receive key events.
631 	 * @return May be null. */
getKeyboardFocus()632 	public Actor getKeyboardFocus () {
633 		return keyboardFocus;
634 	}
635 
636 	/** Sets the actor that will receive scroll events.
637 	 * @param actor May be null. */
setScrollFocus(Actor actor)638 	public void setScrollFocus (Actor actor) {
639 		if (scrollFocus == actor) return;
640 		FocusEvent event = Pools.obtain(FocusEvent.class);
641 		event.setStage(this);
642 		event.setType(FocusEvent.Type.scroll);
643 		Actor oldScrollFocus = scrollFocus;
644 		if (oldScrollFocus != null) {
645 			event.setFocused(false);
646 			event.setRelatedActor(actor);
647 			oldScrollFocus.fire(event);
648 		}
649 		if (!event.isCancelled()) {
650 			scrollFocus = actor;
651 			if (actor != null) {
652 				event.setFocused(true);
653 				event.setRelatedActor(oldScrollFocus);
654 				actor.fire(event);
655 				if (event.isCancelled()) setScrollFocus(oldScrollFocus);
656 			}
657 		}
658 		Pools.free(event);
659 	}
660 
661 	/** Gets the actor that will receive scroll events.
662 	 * @return May be null. */
getScrollFocus()663 	public Actor getScrollFocus () {
664 		return scrollFocus;
665 	}
666 
getBatch()667 	public Batch getBatch () {
668 		return batch;
669 	}
670 
getViewport()671 	public Viewport getViewport () {
672 		return viewport;
673 	}
674 
setViewport(Viewport viewport)675 	public void setViewport (Viewport viewport) {
676 		this.viewport = viewport;
677 	}
678 
679 	/** The viewport's world width. */
getWidth()680 	public float getWidth () {
681 		return viewport.getWorldWidth();
682 	}
683 
684 	/** The viewport's world height. */
getHeight()685 	public float getHeight () {
686 		return viewport.getWorldHeight();
687 	}
688 
689 	/** The viewport's camera. */
getCamera()690 	public Camera getCamera () {
691 		return viewport.getCamera();
692 	}
693 
694 	/** Returns the root group which holds all actors in the stage. */
getRoot()695 	public Group getRoot () {
696 		return root;
697 	}
698 
699 	/** Returns the {@link Actor} at the specified location in stage coordinates. Hit testing is performed in the order the actors
700 	 * were inserted into the stage, last inserted actors being tested first. To get stage coordinates from screen coordinates, use
701 	 * {@link #screenToStageCoordinates(Vector2)}.
702 	 * @param touchable If true, the hit detection will respect the {@link Actor#setTouchable(Touchable) touchability}.
703 	 * @return May be null if no actor was hit. */
hit(float stageX, float stageY, boolean touchable)704 	public Actor hit (float stageX, float stageY, boolean touchable) {
705 		root.parentToLocalCoordinates(tempCoords.set(stageX, stageY));
706 		return root.hit(tempCoords.x, tempCoords.y, touchable);
707 	}
708 
709 	/** Transforms the screen coordinates to stage coordinates.
710 	 * @param screenCoords Input screen coordinates and output for resulting stage coordinates. */
screenToStageCoordinates(Vector2 screenCoords)711 	public Vector2 screenToStageCoordinates (Vector2 screenCoords) {
712 		viewport.unproject(screenCoords);
713 		return screenCoords;
714 	}
715 
716 	/** Transforms the stage coordinates to screen coordinates.
717 	 * @param stageCoords Input stage coordinates and output for resulting screen coordinates. */
stageToScreenCoordinates(Vector2 stageCoords)718 	public Vector2 stageToScreenCoordinates (Vector2 stageCoords) {
719 		viewport.project(stageCoords);
720 		stageCoords.y = viewport.getScreenHeight() - stageCoords.y;
721 		return stageCoords;
722 	}
723 
724 	/** Transforms the coordinates to screen coordinates. The coordinates can be anywhere in the stage since the transform matrix
725 	 * describes how to convert them. The transform matrix is typically obtained from {@link Batch#getTransformMatrix()} during
726 	 * {@link Actor#draw(Batch, float)}.
727 	 * @see Actor#localToStageCoordinates(Vector2) */
toScreenCoordinates(Vector2 coords, Matrix4 transformMatrix)728 	public Vector2 toScreenCoordinates (Vector2 coords, Matrix4 transformMatrix) {
729 		return viewport.toScreenCoordinates(coords, transformMatrix);
730 	}
731 
732 	/** Calculates window scissor coordinates from local coordinates using the batch's current transformation matrix.
733 	 * @see ScissorStack#calculateScissors(Camera, float, float, float, float, Matrix4, Rectangle, Rectangle) */
calculateScissors(Rectangle localRect, Rectangle scissorRect)734 	public void calculateScissors (Rectangle localRect, Rectangle scissorRect) {
735 		viewport.calculateScissors(batch.getTransformMatrix(), localRect, scissorRect);
736 		Matrix4 transformMatrix;
737 		if (debugShapes != null && debugShapes.isDrawing())
738 			transformMatrix = debugShapes.getTransformMatrix();
739 		else
740 			transformMatrix = batch.getTransformMatrix();
741 		viewport.calculateScissors(transformMatrix, localRect, scissorRect);
742 	}
743 
744 	/** If true, any actions executed during a call to {@link #act()}) will result in a call to {@link Graphics#requestRendering()}.
745 	 * Widgets that animate or otherwise require additional rendering may check this setting before calling
746 	 * {@link Graphics#requestRendering()}. Default is true. */
setActionsRequestRendering(boolean actionsRequestRendering)747 	public void setActionsRequestRendering (boolean actionsRequestRendering) {
748 		this.actionsRequestRendering = actionsRequestRendering;
749 	}
750 
getActionsRequestRendering()751 	public boolean getActionsRequestRendering () {
752 		return actionsRequestRendering;
753 	}
754 
755 	/** The default color that can be used by actors to draw debug lines. */
getDebugColor()756 	public Color getDebugColor () {
757 		return debugColor;
758 	}
759 
760 	/** If true, debug lines are shown for actors even when {@link Actor#isVisible()} is false. */
setDebugInvisible(boolean debugInvisible)761 	public void setDebugInvisible (boolean debugInvisible) {
762 		this.debugInvisible = debugInvisible;
763 	}
764 
765 	/** If true, debug lines are shown for all actors. */
setDebugAll(boolean debugAll)766 	public void setDebugAll (boolean debugAll) {
767 		if (this.debugAll == debugAll) return;
768 		this.debugAll = debugAll;
769 		if (debugAll)
770 			debug = true;
771 		else
772 			root.setDebug(false, true);
773 	}
774 
775 	/** If true, debug is enabled only for the actor under the mouse. Can be combined with {@link #setDebugAll(boolean)}. */
setDebugUnderMouse(boolean debugUnderMouse)776 	public void setDebugUnderMouse (boolean debugUnderMouse) {
777 		if (this.debugUnderMouse == debugUnderMouse) return;
778 		this.debugUnderMouse = debugUnderMouse;
779 		if (debugUnderMouse)
780 			debug = true;
781 		else
782 			root.setDebug(false, true);
783 	}
784 
785 	/** If true, debug is enabled only for the parent of the actor under the mouse. Can be combined with
786 	 * {@link #setDebugAll(boolean)}. */
setDebugParentUnderMouse(boolean debugParentUnderMouse)787 	public void setDebugParentUnderMouse (boolean debugParentUnderMouse) {
788 		if (this.debugParentUnderMouse == debugParentUnderMouse) return;
789 		this.debugParentUnderMouse = debugParentUnderMouse;
790 		if (debugParentUnderMouse)
791 			debug = true;
792 		else
793 			root.setDebug(false, true);
794 	}
795 
796 	/** If not {@link Debug#none}, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can
797 	 * be combined with {@link #setDebugAll(boolean)}.
798 	 * @param debugTableUnderMouse May be null for {@link Debug#none}. */
setDebugTableUnderMouse(Debug debugTableUnderMouse)799 	public void setDebugTableUnderMouse (Debug debugTableUnderMouse) {
800 		if (debugTableUnderMouse == null) debugTableUnderMouse = Debug.none;
801 		if (this.debugTableUnderMouse == debugTableUnderMouse) return;
802 		this.debugTableUnderMouse = debugTableUnderMouse;
803 		if (debugTableUnderMouse != Debug.none)
804 			debug = true;
805 		else
806 			root.setDebug(false, true);
807 	}
808 
809 	/** If true, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can be combined with
810 	 * {@link #setDebugAll(boolean)}. */
setDebugTableUnderMouse(boolean debugTableUnderMouse)811 	public void setDebugTableUnderMouse (boolean debugTableUnderMouse) {
812 		setDebugTableUnderMouse(debugTableUnderMouse ? Debug.all : Debug.none);
813 	}
814 
dispose()815 	public void dispose () {
816 		clear();
817 		if (ownsBatch) batch.dispose();
818 	}
819 
820 	/** Internal class for managing touch focus. Public only for GWT.
821 	 * @author Nathan Sweet */
822 	public static final class TouchFocus implements Poolable {
823 		EventListener listener;
824 		Actor listenerActor, target;
825 		int pointer, button;
826 
reset()827 		public void reset () {
828 			listenerActor = null;
829 			listener = null;
830 			target = null;
831 		}
832 	}
833 }
834