/******************************************************************************* * 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.Color; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.g2d.Batch; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.InputEvent.Type; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.Table.Debug; import com.badlogic.gdx.scenes.scene2d.utils.FocusListener; 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.Scaling; import com.badlogic.gdx.utils.SnapshotArray; import com.badlogic.gdx.utils.viewport.ScalingViewport; import com.badlogic.gdx.utils.viewport.Viewport; /** A 2D scene graph containing hierarchies of {@link Actor actors}. Stage handles the viewport and distributes input events. * <p> * {@link #setViewport(Viewport)} 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. * <p> * The Stage and its constituents (like Actors and Listeners) are not thread-safe and should only be updated and queried from a * single thread (presumably the main render thread). Methods should be reentrant, so you can update Actors and Stages from within * callbacks and handlers. * @author mzechner * @author Nathan Sweet */ public class Stage extends InputAdapter implements Disposable { /** True if any actor has ever had debug enabled. */ static boolean debug; private Viewport viewport; private final Batch batch; private boolean ownsBatch; private Group root; private final Vector2 tempCoords = new Vector2(); private final Actor[] pointerOverActors = new Actor[20]; private final boolean[] pointerTouched = new boolean[20]; private final int[] pointerScreenX = new int[20]; private final int[] pointerScreenY = new int[20]; private int mouseScreenX, mouseScreenY; private Actor mouseOverActor; private Actor keyboardFocus, scrollFocus; private final SnapshotArray<TouchFocus> touchFocuses = new SnapshotArray(true, 4, TouchFocus.class); private boolean actionsRequestRendering = true; private ShapeRenderer debugShapes; private boolean debugInvisible, debugAll, debugUnderMouse, debugParentUnderMouse; private Debug debugTableUnderMouse = Debug.none; private final Color debugColor = new Color(0, 1, 0, 0.85f); /** Creates a stage with a {@link ScalingViewport} set to {@link Scaling#stretch}. The stage will use its own {@link Batch} * which will be disposed when the stage is disposed. */ public Stage () { this(new ScalingViewport(Scaling.stretch, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera()), new SpriteBatch()); ownsBatch = true; } /** Creates a stage with the specified viewport. The stage will use its own {@link Batch} which will be disposed when the stage * is disposed. */ public Stage (Viewport viewport) { this(viewport, new SpriteBatch()); ownsBatch = true; } /** Creates a stage with the specified viewport and batch. This can be used to avoid creating a new batch (which can be * somewhat slow) if multiple stages are used during an application's life time. * @param batch Will not be disposed if {@link #dispose()} is called, handle disposal yourself. */ public Stage (Viewport viewport, Batch batch) { if (viewport == null) throw new IllegalArgumentException("viewport cannot be null."); if (batch == null) throw new IllegalArgumentException("batch cannot be null."); this.viewport = viewport; this.batch = batch; root = new Group(); root.setStage(this); viewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true); } public void draw () { Camera camera = viewport.getCamera(); camera.update(); if (!root.isVisible()) return; Batch batch = this.batch; batch.setProjectionMatrix(camera.combined); batch.begin(); root.draw(batch, 1); batch.end(); if (debug) drawDebug(); } private void drawDebug () { if (debugShapes == null) { debugShapes = new ShapeRenderer(); debugShapes.setAutoShapeType(true); } if (debugUnderMouse || debugParentUnderMouse || debugTableUnderMouse != Debug.none) { screenToStageCoordinates(tempCoords.set(Gdx.input.getX(), Gdx.input.getY())); Actor actor = hit(tempCoords.x, tempCoords.y, true); if (actor == null) return; if (debugParentUnderMouse && actor.parent != null) actor = actor.parent; if (debugTableUnderMouse == Debug.none) actor.setDebug(true); else { while (actor != null) { if (actor instanceof Table) break; actor = actor.parent; } if (actor == null) return; ((Table)actor).debug(debugTableUnderMouse); } if (debugAll && actor instanceof Group) ((Group)actor).debugAll(); disableDebug(root, actor); } else { if (debugAll) root.debugAll(); } Gdx.gl.glEnable(GL20.GL_BLEND); debugShapes.setProjectionMatrix(viewport.getCamera().combined); debugShapes.begin(); root.drawDebug(debugShapes); debugShapes.end(); } /** Disables debug on all actors recursively except the specified actor and any children. */ private void disableDebug (Actor actor, Actor except) { if (actor == except) return; actor.setDebug(false); if (actor instanceof Group) { SnapshotArray<Actor> children = ((Group)actor).children; for (int i = 0, n = children.size; i < n; i++) disableDebug(children.get(i), except); } } /** Calls {@link #act(float)} with {@link Graphics#getDeltaTime()}, limited to a minimum of 30fps. */ 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(tempCoords.set(pointerScreenX[pointer], pointerScreenY[pointer])); // Exit over last. InputEvent event = Pools.obtain(InputEvent.class); event.setType(InputEvent.Type.exit); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.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(tempCoords.set(screenX, screenY)); Actor over = hit(tempCoords.x, tempCoords.y, true); if (over == overLast) return overLast; // Exit overLast. if (overLast != null) { InputEvent event = Pools.obtain(InputEvent.class); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.y); event.setPointer(pointer); event.setType(InputEvent.Type.exit); event.setRelatedActor(over); overLast.fire(event); Pools.free(event); } // Enter over. if (over != null) { InputEvent event = Pools.obtain(InputEvent.class); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.y); event.setPointer(pointer); 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) { if (!isInsideViewport(screenX, screenY)) return false; pointerTouched[pointer] = true; pointerScreenX[pointer] = screenX; pointerScreenY[pointer] = screenY; screenToStageCoordinates(tempCoords.set(screenX, screenY)); InputEvent event = Pools.obtain(InputEvent.class); event.setType(Type.touchDown); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.y); event.setPointer(pointer); event.setButton(button); Actor target = hit(tempCoords.x, tempCoords.y, true); if (target == null) { if (root.getTouchable() == Touchable.enabled) root.fire(event); } else { 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; mouseScreenX = screenX; mouseScreenY = screenY; if (touchFocuses.size == 0) return false; screenToStageCoordinates(tempCoords.set(screenX, screenY)); InputEvent event = Pools.obtain(InputEvent.class); event.setType(Type.touchDragged); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.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; if (!touchFocuses.contains(focus, true)) continue; // Touch focus already gone. 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(tempCoords.set(screenX, screenY)); InputEvent event = Pools.obtain(InputEvent.class); event.setType(Type.touchUp); event.setStage(this); event.setStageX(tempCoords.x); event.setStageY(tempCoords.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; if (!isInsideViewport(screenX, screenY)) return false; screenToStageCoordinates(tempCoords.set(screenX, screenY)); InputEvent event = Pools.obtain(InputEvent.class); event.setStage(this); event.setType(Type.mouseMoved); event.setStageX(tempCoords.x); event.setStageY(tempCoords.y); Actor target = hit(tempCoords.x, tempCoords.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(tempCoords.set(mouseScreenX, mouseScreenY)); InputEvent event = Pools.obtain(InputEvent.class); event.setStage(this); event.setType(InputEvent.Type.scrolled); event.setScrollAmount(amount); event.setStageX(tempCoords.x); event.setStageY(tempCoords.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); } } } /** Cancels touch focus for the specified actor. * @see #cancelTouchFocus() */ public void cancelTouchFocus (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 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.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); } /** Sends a touchUp event to all listeners that are registered to receive touchDragged and touchUp events and removes their * touch focus. 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). The location of the touchUp is * {@link Integer#MIN_VALUE}. Listeners can use {@link InputEvent#isTouchFocusCancel()} to ignore this event if needed. */ public void cancelTouchFocus () { cancelTouchFocusExcept(null, null); } /** Cancels touch focus for all listeners except the specified listener. * @see #cancelTouchFocus() */ public void cancelTouchFocusExcept (EventListener exceptListener, Actor exceptActor) { 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 == exceptListener && focus.listenerActor == exceptActor) 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) */ 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.children; } /** 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); } /** Removes the root's children, actions, and listeners. */ public void clear () { unfocusAll(); root.clear(); } /** Removes the touch, keyboard, and scroll focused actors. */ public void unfocusAll () { setScrollFocus(null); setKeyboardFocus(null); cancelTouchFocus(); } /** Removes the touch, keyboard, and scroll focus for the specified actor and any descendants. */ public void unfocus (Actor actor) { cancelTouchFocus(actor); if (scrollFocus != null && scrollFocus.isDescendantOf(actor)) setScrollFocus(null); if (keyboardFocus != null && keyboardFocus.isDescendantOf(actor)) setKeyboardFocus(null); } /** Sets the actor that will receive key events. * @param actor May be null. * @return true if the unfocus and focus events were not cancelled by a {@link FocusListener}. */ public boolean setKeyboardFocus (Actor actor) { if (keyboardFocus == actor) return true; 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); } boolean success = !event.isCancelled(); if (success) { keyboardFocus = actor; if (actor != null) { event.setFocused(true); event.setRelatedActor(oldKeyboardFocus); actor.fire(event); success = !event.isCancelled(); if (!success) setKeyboardFocus(oldKeyboardFocus); } } Pools.free(event); return success; } /** 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. * @return true if the unfocus and focus events were not cancelled by a {@link FocusListener}. */ public boolean setScrollFocus (Actor actor) { if (scrollFocus == actor) return true; FocusEvent event = Pools.obtain(FocusEvent.class); event.setStage(this); event.setType(FocusEvent.Type.scroll); Actor oldScrollFocus = scrollFocus; if (oldScrollFocus != null) { event.setFocused(false); event.setRelatedActor(actor); oldScrollFocus.fire(event); } boolean success = !event.isCancelled(); if (success) { scrollFocus = actor; if (actor != null) { event.setFocused(true); event.setRelatedActor(oldScrollFocus); actor.fire(event); success = !event.isCancelled(); if (!success) setScrollFocus(oldScrollFocus); } } Pools.free(event); return success; } /** Gets the actor that will receive scroll events. * @return May be null. */ public Actor getScrollFocus () { return scrollFocus; } public Batch getBatch () { return batch; } public Viewport getViewport () { return viewport; } public void setViewport (Viewport viewport) { this.viewport = viewport; } /** The viewport's world width. */ public float getWidth () { return viewport.getWorldWidth(); } /** The viewport's world height. */ public float getHeight () { return viewport.getWorldHeight(); } /** The viewport's camera. */ public Camera getCamera () { return viewport.getCamera(); } /** Returns the root group which holds all actors in the stage. */ public Group getRoot () { return root; } /** Replaces the root group. Usually this is not necessary but a subclass may be desired in some cases, eg being notified of * {@link Group#childrenChanged()}. */ public void setRoot (Group root) { this.root = 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) { root.parentToLocalCoordinates(tempCoords.set(stageX, stageY)); return root.hit(tempCoords.x, tempCoords.y, touchable); } /** Transforms the screen coordinates to stage coordinates. * @param screenCoords Input screen coordinates and output for resulting stage coordinates. */ public Vector2 screenToStageCoordinates (Vector2 screenCoords) { viewport.unproject(screenCoords); return screenCoords; } /** Transforms the stage coordinates to screen coordinates. * @param stageCoords Input stage coordinates and output for resulting screen coordinates. */ public Vector2 stageToScreenCoordinates (Vector2 stageCoords) { viewport.project(stageCoords); stageCoords.y = viewport.getScreenHeight() - stageCoords.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 Batch#getTransformMatrix()} during * {@link Actor#draw(Batch, float)}. * @see Actor#localToStageCoordinates(Vector2) */ public Vector2 toScreenCoordinates (Vector2 coords, Matrix4 transformMatrix) { return viewport.toScreenCoordinates(coords, transformMatrix); } /** Calculates window scissor coordinates from local coordinates using the batch's current transformation matrix. * @see ScissorStack#calculateScissors(Camera, float, float, float, float, Matrix4, Rectangle, Rectangle) */ public void calculateScissors (Rectangle localRect, Rectangle scissorRect) { viewport.calculateScissors(batch.getTransformMatrix(), localRect, scissorRect); Matrix4 transformMatrix; if (debugShapes != null && debugShapes.isDrawing()) transformMatrix = debugShapes.getTransformMatrix(); else transformMatrix = batch.getTransformMatrix(); viewport.calculateScissors(transformMatrix, localRect, scissorRect); } /** If true, any actions executed during a call to {@link #act()}) will result in a call to {@link Graphics#requestRendering()} * . Widgets that animate or otherwise require additional rendering may check this setting before calling * {@link Graphics#requestRendering()}. Default is true. */ public void setActionsRequestRendering (boolean actionsRequestRendering) { this.actionsRequestRendering = actionsRequestRendering; } public boolean getActionsRequestRendering () { return actionsRequestRendering; } /** The default color that can be used by actors to draw debug lines. */ public Color getDebugColor () { return debugColor; } /** If true, debug lines are shown for actors even when {@link Actor#isVisible()} is false. */ public void setDebugInvisible (boolean debugInvisible) { this.debugInvisible = debugInvisible; } /** If true, debug lines are shown for all actors. */ public void setDebugAll (boolean debugAll) { if (this.debugAll == debugAll) return; this.debugAll = debugAll; if (debugAll) debug = true; else root.setDebug(false, true); } public boolean isDebugAll () { return debugAll; } /** If true, debug is enabled only for the actor under the mouse. Can be combined with {@link #setDebugAll(boolean)}. */ public void setDebugUnderMouse (boolean debugUnderMouse) { if (this.debugUnderMouse == debugUnderMouse) return; this.debugUnderMouse = debugUnderMouse; if (debugUnderMouse) debug = true; else root.setDebug(false, true); } /** If true, debug is enabled only for the parent of the actor under the mouse. Can be combined with * {@link #setDebugAll(boolean)}. */ public void setDebugParentUnderMouse (boolean debugParentUnderMouse) { if (this.debugParentUnderMouse == debugParentUnderMouse) return; this.debugParentUnderMouse = debugParentUnderMouse; if (debugParentUnderMouse) debug = true; else root.setDebug(false, true); } /** If not {@link Debug#none}, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can * be combined with {@link #setDebugAll(boolean)}. * @param debugTableUnderMouse May be null for {@link Debug#none}. */ public void setDebugTableUnderMouse (Debug debugTableUnderMouse) { if (debugTableUnderMouse == null) debugTableUnderMouse = Debug.none; if (this.debugTableUnderMouse == debugTableUnderMouse) return; this.debugTableUnderMouse = debugTableUnderMouse; if (debugTableUnderMouse != Debug.none) debug = true; else root.setDebug(false, true); } /** If true, debug is enabled only for the first ascendant of the actor under the mouse that is a table. Can be combined with * {@link #setDebugAll(boolean)}. */ public void setDebugTableUnderMouse (boolean debugTableUnderMouse) { setDebugTableUnderMouse(debugTableUnderMouse ? Debug.all : Debug.none); } public void dispose () { clear(); if (ownsBatch) batch.dispose(); } /** Check if screen coordinates are inside the viewport's screen area. */ protected boolean isInsideViewport (int screenX, int screenY) { int x0 = viewport.getScreenX(); int x1 = x0 + viewport.getScreenWidth(); int y0 = viewport.getScreenY(); int y1 = y0 + viewport.getScreenHeight(); screenY = Gdx.graphics.getHeight() - screenY; return screenX >= x0 && screenX < x1 && screenY >= y0 && screenY < y1; } /** 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; target = null; } } }