/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package com.badlogic.gdx.scenes.scene2d;
import com.badlogic.gdx.Application.ApplicationType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;
import com.badlogic.gdx.scenes.scene2d.utils.FocusListener.FocusEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.Pool.Poolable;
import com.badlogic.gdx.utils.Pools;
import com.badlogic.gdx.utils.SnapshotArray;
/**
* A 2D scenegraph containing hierarchies of {@link Actor actors}. Stage handles the viewport and distributing input
* events.
* <p>
* A stage fills the whole screen. {@link #setViewport} controls the coordinates used within the stage and sets up the
* camera used to convert between stage coordinates and screen coordinates. *
* <p>
* A stage must receive input events so it can distribute them to actors. This is typically done by passing the stage to
* {@link Input#setInputProcessor(com.badlogic.gdx.InputProcessor) Gdx.input.setInputProcessor}. An
* {@link InputMultiplexer} may be used to handle input events before or after the stage does. If an actor handles an
* event by returning true from the input method, then the stage's input method will also return true, causing
* subsequent InputProcessors to not receive the event.
*
* @author mzechner
* @author Nathan Sweet
*/
public class Stage extends InputAdapter implements Disposable {
private float width, height;
private float gutterWidth, gutterHeight;
private float centerX, centerY;
public boolean visible = true;
protected OrthographicCamera camera;
protected final SpriteBatch batch;
private final boolean ownsBatch;
protected Group root;
private final Vector2 stageCoords = new Vector2();
private Actor[] pointerOverActors = new Actor[20];
private boolean[] pointerTouched = new boolean[20];
private int[] pointerScreenX = new int[20];
private int[] pointerScreenY = new int[20];
private int mouseScreenX, mouseScreenY;
private Actor mouseOverActor;
private Actor keyboardFocus, scrollFocus;
private SnapshotArray<TouchFocus> touchFocuses = new SnapshotArray(true, 4, TouchFocus.class);
/**
* Creates a stage with a {@link #setViewport(float, float, boolean) viewport} equal to the device screen
* resolution. The stage will use its own {@link SpriteBatch}.
*/
public Stage() {
this(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false, true);
}
/**
* Creates a stage with the specified {@link #setViewport(float, float, boolean) viewport}. The stage will use its
* own {@link SpriteBatch}, which will be disposed when the stage is disposed.
*/
public Stage(float width, float height, boolean keepAspectRatio, boolean own_camera) {
batch = new SpriteBatch();
ownsBatch = true;
initialize(width, height, keepAspectRatio, own_camera);
}
/**
* Creates a stage with the specified {@link #setViewport(float, float, boolean) viewport} and {@link SpriteBatch}.
* This can be used to avoid creating a new SpriteBatch (which can be somewhat slow) if multiple stages are used
* during an applications life time.
*
* @param batch
* Will not be disposed if {@link #dispose()} is called. Handle disposal yourself.
*/
public Stage(float width, float height, boolean keepAspectRatio, SpriteBatch batch, boolean own_camera) {
this.batch = batch;
ownsBatch = false;
initialize(width, height, keepAspectRatio, own_camera);
}
private void initialize(float width, float height, boolean keepAspectRatio, boolean own_camera) {
this.width = width;
this.height = height;
root = new Group();
root.setStage(this);
if (own_camera) {
camera = new OrthographicCamera();
setViewport(width, height, keepAspectRatio);
}
}
/**
* Sets the dimensions of the stage's viewport. The viewport covers the entire screen. If keepAspectRatio is false,
* the viewport is simply stretched to the screen resolution, which may distort the aspect ratio. If keepAspectRatio
* is true, the viewport is first scaled to fit then the shorter dimension is lengthened to fill the screen, which
* keeps the aspect ratio from changing. The {@link #getGutterWidth()} and {@link #getGutterHeight()} provide access
* to the amount that was lengthened.
*/
public void setViewport(float width, float height, boolean keepAspectRatio) {
if (keepAspectRatio) {
float screenWidth = Gdx.graphics.getWidth();
float screenHeight = Gdx.graphics.getHeight();
if (screenHeight / screenWidth < height / width) {
float toScreenSpace = screenHeight / height;
float toViewportSpace = height / screenHeight;
float deviceWidth = width * toScreenSpace;
float lengthen = (screenWidth - deviceWidth) * toViewportSpace;
this.width = width + lengthen;
this.height = height;
gutterWidth = lengthen / 2;
gutterHeight = 0;
} else {
float toScreenSpace = screenWidth / width;
float toViewportSpace = width / screenWidth;
float deviceHeight = height * toScreenSpace;
float lengthen = (screenHeight - deviceHeight) * toViewportSpace;
this.height = height + lengthen;
this.width = width;
gutterWidth = 0;
gutterHeight = lengthen / 2;
}
} else {
this.width = width;
this.height = height;
gutterWidth = 0;
gutterHeight = 0;
}
centerX = this.width / 2;
centerY = this.height / 2;
camera.position.set(centerX, centerY, 0);
camera.viewportWidth = this.width;
camera.viewportHeight = this.height;
}
public void draw() {
camera.update();
if (!root.isVisible())
return;
batch.setProjectionMatrix(camera.combined);
batch.begin();
root.draw(batch, 1);
batch.end();
}
/** Calls {@link #act(float)} with {@link Graphics#getDeltaTime()}. */
public void act() {
act(Math.min(Gdx.graphics.getDeltaTime(), 1 / 30f));
}
/**
* Calls the {@link Actor#act(float)} method on each actor in the stage. Typically called each frame. This method
* also fires enter and exit events.
*
* @param delta
* Time in seconds since the last frame.
*/
public void act(float delta) {
// Update over actors. Done in act() because actors may change position, which can fire enter/exit without an input event.
for (int pointer = 0, n = pointerOverActors.length; pointer < n; pointer++) {
Actor overLast = pointerOverActors[pointer];
// Check if pointer is gone.
if (!pointerTouched[pointer]) {
if (overLast != null) {
pointerOverActors[pointer] = null;
screenToStageCoordinates(stageCoords.set(pointerScreenX[pointer], pointerScreenY[pointer]));
// Exit over last.
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(InputEvent.Type.exit);
event.setStage(this);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
event.setRelatedActor(overLast);
event.setPointer(pointer);
overLast.fire(event);
Pools.free(event);
}
continue;
}
// Update over actor for the pointer.
pointerOverActors[pointer] = fireEnterAndExit(overLast, pointerScreenX[pointer], pointerScreenY[pointer],
pointer);
}
// Update over actor for the mouse on the desktop.
// ApplicationType type = Gdx.app.getType();
// if (type == ApplicationType.Desktop || type == ApplicationType.Applet || type == ApplicationType.WebGL)
// mouseOverActor = fireEnterAndExit(mouseOverActor, mouseScreenX, mouseScreenY, -1);
root.act(delta);
}
private Actor fireEnterAndExit(Actor overLast, int screenX, int screenY, int pointer) {
// Find the actor under the point.
screenToStageCoordinates(stageCoords.set(screenX, screenY));
Actor over = hit(stageCoords.x, stageCoords.y, true);
if (over == overLast)
return overLast;
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
event.setPointer(pointer);
// Exit overLast.
if (overLast != null) {
event.setType(InputEvent.Type.exit);
event.setRelatedActor(over);
overLast.fire(event);
}
// Enter over.
if (over != null) {
event.setType(InputEvent.Type.enter);
event.setRelatedActor(overLast);
over.fire(event);
}
Pools.free(event);
return over;
}
/**
* Applies a touch down event to the stage and returns true if an actor in the scene {@link Event#handle() handled}
* the event.
*/
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
pointerTouched[pointer] = true;
pointerScreenX[pointer] = screenX;
pointerScreenY[pointer] = screenY;
screenToStageCoordinates(stageCoords.set(screenX, screenY));
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.touchDown);
event.setStage(this);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
event.setPointer(pointer);
event.setButton(button);
Actor target = hit(stageCoords.x, stageCoords.y, true);
if (target == null)
target = root;
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a touch moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled}
* the event. Only {@link InputListener listeners} that returned true for touchDown will receive this event.
*/
public boolean touchDragged(int screenX, int screenY, int pointer) {
pointerScreenX[pointer] = screenX;
pointerScreenY[pointer] = screenY;
if (touchFocuses.size == 0)
return false;
screenToStageCoordinates(stageCoords.set(screenX, screenY));
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.touchDragged);
event.setStage(this);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
event.setPointer(pointer);
SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
TouchFocus[] focuses = touchFocuses.begin();
for (int i = 0, n = touchFocuses.size; i < n; i++) {
TouchFocus focus = focuses[i];
if (focus.pointer != pointer)
continue;
event.setTarget(focus.target);
event.setListenerActor(focus.listenerActor);
if (focus.listener.handle(event))
event.handle();
}
touchFocuses.end();
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a touch up event to the stage and returns true if an actor in the scene {@link Event#handle() handled}
* the event. Only {@link InputListener listeners} that returned true for touchDown will receive this event.
*/
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
pointerTouched[pointer] = false;
pointerScreenX[pointer] = screenX;
pointerScreenY[pointer] = screenY;
if (touchFocuses.size == 0)
return false;
screenToStageCoordinates(stageCoords.set(screenX, screenY));
InputEvent event = Pools.obtain(InputEvent.class);
event.setType(Type.touchUp);
event.setStage(this);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
event.setPointer(pointer);
event.setButton(button);
SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
TouchFocus[] focuses = touchFocuses.begin();
for (int i = 0, n = touchFocuses.size; i < n; i++) {
TouchFocus focus = focuses[i];
if (focus.pointer != pointer || focus.button != button)
continue;
if (!touchFocuses.removeValue(focus, true))
continue; // Touch focus already gone.
event.setTarget(focus.target);
event.setListenerActor(focus.listenerActor);
if (focus.listener.handle(event))
event.handle();
Pools.free(focus);
}
touchFocuses.end();
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a mouse moved event to the stage and returns true if an actor in the scene {@link Event#handle() handled}
* the event. This event only occurs on the desktop.
*/
public boolean mouseMoved(int screenX, int screenY) {
mouseScreenX = screenX;
mouseScreenY = screenY;
screenToStageCoordinates(stageCoords.set(screenX, screenY));
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(Type.mouseMoved);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
Actor target = hit(stageCoords.x, stageCoords.y, true);
if (target == null)
target = root;
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a mouse scroll event to the stage and returns true if an actor in the scene {@link Event#handle()
* handled} the event. This event only occurs on the desktop.
*/
public boolean scrolled(int amount) {
Actor target = scrollFocus == null ? root : scrollFocus;
screenToStageCoordinates(stageCoords.set(mouseScreenX, mouseScreenY));
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(InputEvent.Type.scrolled);
event.setScrollAmount(amount);
event.setStageX(stageCoords.x);
event.setStageY(stageCoords.y);
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a key down event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and
* returns true if the event was {@link Event#handle() handled}.
*/
public boolean keyDown(int keyCode) {
Actor target = keyboardFocus == null ? root : keyboardFocus;
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(InputEvent.Type.keyDown);
event.setKeyCode(keyCode);
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a key up event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and
* returns true if the event was {@link Event#handle() handled}.
*/
public boolean keyUp(int keyCode) {
Actor target = keyboardFocus == null ? root : keyboardFocus;
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(InputEvent.Type.keyUp);
event.setKeyCode(keyCode);
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Applies a key typed event to the actor that has {@link Stage#setKeyboardFocus(Actor) keyboard focus}, if any, and
* returns true if the event was {@link Event#handle() handled}.
*/
public boolean keyTyped(char character) {
Actor target = keyboardFocus == null ? root : keyboardFocus;
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(InputEvent.Type.keyTyped);
event.setCharacter(character);
target.fire(event);
boolean handled = event.isHandled();
Pools.free(event);
return handled;
}
/**
* Adds the listener to be notified for all touchDragged and touchUp events for the specified pointer and button.
* The actor will be used as the {@link Event#getListenerActor() listener actor} and {@link Event#getTarget()
* target}.
*/
public void addTouchFocus(EventListener listener, Actor listenerActor, Actor target, int pointer, int button) {
TouchFocus focus = Pools.obtain(TouchFocus.class);
focus.listenerActor = listenerActor;
focus.target = target;
focus.listener = listener;
focus.pointer = pointer;
focus.button = button;
touchFocuses.add(focus);
}
/**
* Removes the listener from being notified for all touchDragged and touchUp events for the specified pointer and
* button. Note the listener may never receive a touchUp event if this method is used.
*/
public void removeTouchFocus(EventListener listener, Actor listenerActor, Actor target, int pointer, int button) {
SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
for (int i = touchFocuses.size - 1; i >= 0; i--) {
TouchFocus focus = touchFocuses.get(i);
if (focus.listener == listener && focus.listenerActor == listenerActor && focus.target == target
&& focus.pointer == pointer && focus.button == button) {
touchFocuses.removeIndex(i);
Pools.free(focus);
}
}
}
/**
* Sends a touchUp event to all listeners that are registered to receive touchDragged and touchUp events and removes
* their touch focus. The location of the touchUp is {@link Integer#MIN_VALUE}. This method removes all touch focus
* listeners, but sends a touchUp event so that the state of the listeners remains consistent (listeners typically
* expect to receive touchUp eventually).
*/
public void cancelTouchFocus() {
cancelTouchFocus(null, null);
}
/**
* Cancels touch focus for all listeners except the specified listener.
*
* @see #cancelTouchFocus()
*/
public void cancelTouchFocus(EventListener listener, Actor actor) {
InputEvent event = Pools.obtain(InputEvent.class);
event.setStage(this);
event.setType(InputEvent.Type.touchUp);
event.setStageX(Integer.MIN_VALUE);
event.setStageY(Integer.MIN_VALUE);
// Cancel all current touch focuses except for the specified listener, allowing for concurrent modification, and never
// cancel the same focus twice.
SnapshotArray<TouchFocus> touchFocuses = this.touchFocuses;
TouchFocus[] items = touchFocuses.begin();
for (int i = 0, n = touchFocuses.size; i < n; i++) {
TouchFocus focus = items[i];
if (focus.listener == listener && focus.listenerActor == actor)
continue;
if (!touchFocuses.removeValue(focus, true))
continue; // Touch focus already gone.
event.setTarget(focus.target);
event.setListenerActor(focus.listenerActor);
event.setPointer(focus.pointer);
event.setButton(focus.button);
focus.listener.handle(event);
// Cannot return TouchFocus to pool, as it may still be in use (eg if cancelTouchFocus is called from touchDragged).
}
touchFocuses.end();
Pools.free(event);
}
/**
* Adds an actor to the root of the stage.
*
* @see Group#addActor(Actor)
* @see Actor#remove()
*/
public void addActor(Actor actor) {
root.addActor(actor);
}
/**
* Adds an action to the root of the stage.
*
* @see Group#addAction(Action)
*/
public void addAction(Action action) {
root.addAction(action);
}
/**
* Returns the root's child actors.
*
* @see Group#getChildren()
*/
public Array<Actor> getActors() {
return root.getChildren();
}
/**
* Adds a listener to the root.
*
* @see Actor#addListener(EventListener)
*/
public boolean addListener(EventListener listener) {
return root.addListener(listener);
}
/**
* Removes a listener from the root.
*
* @see Actor#removeListener(EventListener)
*/
public boolean removeListener(EventListener listener) {
return root.removeListener(listener);
}
/**
* Adds a capture listener to the root.
*
* @see Actor#addCaptureListener(EventListener)
*/
public boolean addCaptureListener(EventListener listener) {
return root.addCaptureListener(listener);
}
/**
* Removes a listener from the root.
*
* @see Actor#removeCaptureListener(EventListener)
*/
public boolean removeCaptureListener(EventListener listener) {
return root.removeCaptureListener(listener);
}
/** Clears the stage, removing all actors. */
public void clear() {
unfocusAll();
root.clear();
}
/** Removes the touch, keyboard, and scroll focused actors. */
public void unfocusAll() {
scrollFocus = null;
keyboardFocus = null;
cancelTouchFocus();
}
/** Removes the touch, keyboard, and scroll focus for the specified actor and any descendants. */
public void unfocus(Actor actor) {
if (scrollFocus != null && scrollFocus.isDescendantOf(actor))
scrollFocus = null;
if (keyboardFocus != null && keyboardFocus.isDescendantOf(actor))
keyboardFocus = null;
}
/**
* Sets the actor that will receive key events.
*
* @param actor
* May be null.
*/
public void setKeyboardFocus(Actor actor) {
if (keyboardFocus == actor)
return;
FocusEvent event = Pools.obtain(FocusEvent.class);
event.setStage(this);
event.setType(FocusEvent.Type.keyboard);
Actor oldKeyboardFocus = keyboardFocus;
if (oldKeyboardFocus != null) {
event.setFocused(false);
event.setRelatedActor(actor);
oldKeyboardFocus.fire(event);
}
if (!event.isCancelled()) {
keyboardFocus = actor;
if (actor != null) {
event.setFocused(true);
event.setRelatedActor(oldKeyboardFocus);
actor.fire(event);
if (event.isCancelled())
setKeyboardFocus(oldKeyboardFocus);
}
}
Pools.free(event);
}
/**
* Gets the actor that will receive key events.
*
* @return May be null.
*/
public Actor getKeyboardFocus() {
return keyboardFocus;
}
/**
* Sets the actor that will receive scroll events.
*
* @param actor
* May be null.
*/
public void setScrollFocus(Actor actor) {
if (scrollFocus == actor)
return;
FocusEvent event = Pools.obtain(FocusEvent.class);
event.setStage(this);
event.setType(FocusEvent.Type.scroll);
Actor oldScrollFocus = keyboardFocus;
if (oldScrollFocus != null) {
event.setFocused(false);
event.setRelatedActor(actor);
oldScrollFocus.fire(event);
}
if (!event.isCancelled()) {
scrollFocus = actor;
if (actor != null) {
event.setFocused(true);
event.setRelatedActor(oldScrollFocus);
actor.fire(event);
if (event.isCancelled())
setScrollFocus(oldScrollFocus);
}
}
Pools.free(event);
}
/**
* Gets the actor that will receive scroll events.
*
* @return May be null.
*/
public Actor getScrollFocus() {
return scrollFocus;
}
/**
* The width of the stage's viewport.
*
* @see #setViewport(float, float, boolean)
*/
public float getWidth() {
return width;
}
/**
* The height of the stage's viewport.
*
* @see #setViewport(float, float, boolean)
*/
public float getHeight() {
return height;
}
/**
* Half the amount in the x direction that the stage's viewport was lengthened to fill the screen.
*
* @see #setViewport(float, float, boolean)
*/
public float getGutterWidth() {
return gutterWidth;
}
/**
* Half the amount in the y direction that the stage's viewport was lengthened to fill the screen.
*
* @see #setViewport(float, float, boolean)
*/
public float getGutterHeight() {
return gutterHeight;
}
public SpriteBatch getSpriteBatch() {
return batch;
}
public Camera getCamera() {
return camera;
}
/**
* Sets the stage's camera. The camera must be configured properly or {@link #setViewport(float, float, boolean)}
* can be called after the camera is set. {@link Stage#draw()} will call {@link Camera#update()} and use the
* {@link Camera#combined} matrix for the SpriteBatch
* {@link SpriteBatch#setProjectionMatrix(com.badlogic.gdx.math.Matrix4) projection matrix}.
*/
public void setCamera(OrthographicCamera camera) {
this.camera = camera;
}
/** Returns the root group which holds all actors in the stage. */
public Group getRoot() {
return root;
}
/**
* Returns the {@link Actor} at the specified location in stage coordinates. Hit testing is performed in the order
* the actors were inserted into the stage, last inserted actors being tested first. To get stage coordinates from
* screen coordinates, use {@link #screenToStageCoordinates(Vector2)}.
*
* @param touchable
* If true, the hit detection will respect the {@link Actor#setTouchable(Touchable) touchability}.
* @return May be null if no actor was hit.
*/
public Actor hit(float stageX, float stageY, boolean touchable) {
Vector2 actorCoords = Vector2.tmp;
root.parentToLocalCoordinates(actorCoords.set(stageX, stageY));
return root.hit(actorCoords.x, actorCoords.y, touchable);
}
/**
* Transforms the screen coordinates to stage coordinates.
*
* @param screenCoords
* Stores the result.
*/
public Vector2 screenToStageCoordinates(Vector2 screenCoords) {
camera.unproject(Vector3.tmp.set(screenCoords.x, screenCoords.y, 0));
screenCoords.x = Vector3.tmp.x;
screenCoords.y = Vector3.tmp.y;
return screenCoords;
}
/** Transforms the stage coordinates to screen coordinates. */
public Vector2 stageToScreenCoordinates(Vector2 stageCoords) {
Vector3.tmp.set(stageCoords.x, stageCoords.y, 0);
camera.project(Vector3.tmp);
stageCoords.x = Vector3.tmp.x;
stageCoords.y = Vector3.tmp.y;
return stageCoords;
}
/**
* Transforms the coordinates to screen coordinates. The coordinates can be anywhere in the stage since the
* transform matrix describes how to convert them. The transform matrix is typically obtained from
* {@link SpriteBatch#getTransformMatrix()}.
*/
public Vector2 toScreenCoordinates(Vector2 coords, Matrix4 transformMatrix) {
ScissorStack.toWindowCoordinates(camera, transformMatrix, coords);
return coords;
}
public void dispose() {
if (ownsBatch)
batch.dispose();
}
/**
* Internal class for managing touch focus. Public only for GWT.
*
* @author Nathan Sweet
*/
public static final class TouchFocus implements Poolable {
EventListener listener;
Actor listenerActor, target;
int pointer, button;
public void reset() {
listenerActor = null;
listener = null;
}
}
}