Home | History | Annotate | Download | only in scene2d
      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. */
     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. */
     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. */
    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 
    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 
    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. */
    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. */
    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. */
    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 
    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. */
    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. */
    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. */
    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. */
    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. */
    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}. */
    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}. */
    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}. */
    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}. */
    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. */
    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() */
    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. */
    510 	public void cancelTouchFocus () {
    511 		cancelTouchFocusExcept(null, null);
    512 	}
    513 
    514 	/** Cancels touch focus for all listeners except the specified listener.
    515 	 * @see #cancelTouchFocus() */
    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) */
    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) */
    551 	public void addAction (Action action) {
    552 		root.addAction(action);
    553 	}
    554 
    555 	/** Returns the root's child actors.
    556 	 * @see Group#getChildren() */
    557 	public Array<Actor> getActors () {
    558 		return root.children;
    559 	}
    560 
    561 	/** Adds a listener to the root.
    562 	 * @see Actor#addListener(EventListener) */
    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) */
    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) */
    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) */
    581 	public boolean removeCaptureListener (EventListener listener) {
    582 		return root.removeCaptureListener(listener);
    583 	}
    584 
    585 	/** Removes the root's children, actions, and listeners. */
    586 	public void clear () {
    587 		unfocusAll();
    588 		root.clear();
    589 	}
    590 
    591 	/** Removes the touch, keyboard, and scroll focused actors. */
    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. */
    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. */
    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. */
    632 	public Actor getKeyboardFocus () {
    633 		return keyboardFocus;
    634 	}
    635 
    636 	/** Sets the actor that will receive scroll events.
    637 	 * @param actor May be null. */
    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. */
    663 	public Actor getScrollFocus () {
    664 		return scrollFocus;
    665 	}
    666 
    667 	public Batch getBatch () {
    668 		return batch;
    669 	}
    670 
    671 	public Viewport getViewport () {
    672 		return viewport;
    673 	}
    674 
    675 	public void setViewport (Viewport viewport) {
    676 		this.viewport = viewport;
    677 	}
    678 
    679 	/** The viewport's world width. */
    680 	public float getWidth () {
    681 		return viewport.getWorldWidth();
    682 	}
    683 
    684 	/** The viewport's world height. */
    685 	public float getHeight () {
    686 		return viewport.getWorldHeight();
    687 	}
    688 
    689 	/** The viewport's camera. */
    690 	public Camera getCamera () {
    691 		return viewport.getCamera();
    692 	}
    693 
    694 	/** Returns the root group which holds all actors in the stage. */
    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. */
    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. */
    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. */
    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) */
    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) */
    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. */
    747 	public void setActionsRequestRendering (boolean actionsRequestRendering) {
    748 		this.actionsRequestRendering = actionsRequestRendering;
    749 	}
    750 
    751 	public boolean getActionsRequestRendering () {
    752 		return actionsRequestRendering;
    753 	}
    754 
    755 	/** The default color that can be used by actors to draw debug lines. */
    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. */
    761 	public void setDebugInvisible (boolean debugInvisible) {
    762 		this.debugInvisible = debugInvisible;
    763 	}
    764 
    765 	/** If true, debug lines are shown for all actors. */
    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)}. */
    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)}. */
    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}. */
    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)}. */
    811 	public void setDebugTableUnderMouse (boolean debugTableUnderMouse) {
    812 		setDebugTableUnderMouse(debugTableUnderMouse ? Debug.all : Debug.none);
    813 	}
    814 
    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 
    827 		public void reset () {
    828 			listenerActor = null;
    829 			listener = null;
    830 			target = null;
    831 		}
    832 	}
    833 }
    834