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 static com.badlogic.gdx.utils.Align.*;
     20 
     21 import com.badlogic.gdx.Gdx;
     22 import com.badlogic.gdx.graphics.Color;
     23 import com.badlogic.gdx.graphics.g2d.Batch;
     24 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
     25 import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
     26 import com.badlogic.gdx.math.MathUtils;
     27 import com.badlogic.gdx.math.Rectangle;
     28 import com.badlogic.gdx.math.Vector2;
     29 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;
     30 import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;
     31 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
     32 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
     33 import com.badlogic.gdx.utils.Align;
     34 import com.badlogic.gdx.utils.Array;
     35 import com.badlogic.gdx.utils.DelayedRemovalArray;
     36 import com.badlogic.gdx.utils.Pools;
     37 
     38 /** 2D scene graph node. An actor has a position, rectangular size, origin, scale, rotation, Z index, and color. The position
     39  * corresponds to the unrotated, unscaled bottom left corner of the actor. The position is relative to the actor's parent. The
     40  * origin is relative to the position and is used for scale and rotation.
     41  * <p>
     42  * An actor has a list of in progress {@link Action actions} that are applied to the actor (often over time). These are generally
     43  * used to change the presentation of the actor (moving it, resizing it, etc). See {@link #act(float)}, {@link Action} and its
     44  * many subclasses.
     45  * <p>
     46  * An actor has two kinds of listeners associated with it: "capture" and regular. The listeners are notified of events the actor
     47  * or its children receive. The regular listeners are designed to allow an actor to respond to events that have been delivered.
     48  * The capture listeners are designed to allow a parent or container actor to handle events before child actors. See {@link #fire}
     49  * for more details.
     50  * <p>
     51  * An {@link InputListener} can receive all the basic input events. More complex listeners (like {@link ClickListener} and
     52  * {@link ActorGestureListener}) can listen for and combine primitive events and recognize complex interactions like multi-touch
     53  * or pinch.
     54  * @author mzechner
     55  * @author Nathan Sweet */
     56 public class Actor {
     57 	private Stage stage;
     58 	Group parent;
     59 	private final DelayedRemovalArray<EventListener> listeners = new DelayedRemovalArray(0);
     60 	private final DelayedRemovalArray<EventListener> captureListeners = new DelayedRemovalArray(0);
     61 	private final Array<Action> actions = new Array(0);
     62 
     63 	private String name;
     64 	private Touchable touchable = Touchable.enabled;
     65 	private boolean visible = true, debug;
     66 	float x, y;
     67 	float width, height;
     68 	float originX, originY;
     69 	float scaleX = 1, scaleY = 1;
     70 	float rotation;
     71 	final Color color = new Color(1, 1, 1, 1);
     72 	private Object userObject;
     73 
     74 	/** Draws the actor. The batch is configured to draw in the parent's coordinate system.
     75 	 * {@link Batch#draw(com.badlogic.gdx.graphics.g2d.TextureRegion, float, float, float, float, float, float, float, float, float)
     76 	 * This draw method} is convenient to draw a rotated and scaled TextureRegion. {@link Batch#begin()} has already been called on
     77 	 * the batch. If {@link Batch#end()} is called to draw without the batch then {@link Batch#begin()} must be called before the
     78 	 * method returns.
     79 	 * <p>
     80 	 * The default implementation does nothing.
     81 	 * @param parentAlpha Should be multiplied with the actor's alpha, allowing a parent's alpha to affect all children. */
     82 	public void draw (Batch batch, float parentAlpha) {
     83 	}
     84 
     85 	/** Updates the actor based on time. Typically this is called each frame by {@link Stage#act(float)}.
     86 	 * <p>
     87 	 * The default implementation calls {@link Action#act(float)} on each action and removes actions that are complete.
     88 	 * @param delta Time in seconds since the last frame. */
     89 	public void act (float delta) {
     90 		Array<Action> actions = this.actions;
     91 		if (actions.size > 0) {
     92 			if (stage != null && stage.getActionsRequestRendering()) Gdx.graphics.requestRendering();
     93 			for (int i = 0; i < actions.size; i++) {
     94 				Action action = actions.get(i);
     95 				if (action.act(delta) && i < actions.size) {
     96 					Action current = actions.get(i);
     97 					int actionIndex = current == action ? i : actions.indexOf(action, true);
     98 					if (actionIndex != -1) {
     99 						actions.removeIndex(actionIndex);
    100 						action.setActor(null);
    101 						i--;
    102 					}
    103 				}
    104 			}
    105 		}
    106 	}
    107 
    108 	/** Sets this actor as the event {@link Event#setTarget(Actor) target} and propagates the event to this actor and ancestor
    109 	 * actors as necessary. If this actor is not in the stage, the stage must be set before calling this method.
    110 	 * <p>
    111 	 * Events are fired in 2 phases:
    112 	 * <ol>
    113 	 * <li>The first phase (the "capture" phase) notifies listeners on each actor starting at the root and propagating downward to
    114 	 * (and including) this actor.</li>
    115 	 * <li>The second phase notifies listeners on each actor starting at this actor and, if {@link Event#getBubbles()} is true,
    116 	 * propagating upward to the root.</li>
    117 	 * </ol>
    118 	 * If the event is {@link Event#stop() stopped} at any time, it will not propagate to the next actor.
    119 	 * @return true if the event was {@link Event#cancel() cancelled}. */
    120 	public boolean fire (Event event) {
    121 		if (event.getStage() == null) event.setStage(getStage());
    122 		event.setTarget(this);
    123 
    124 		// Collect ancestors so event propagation is unaffected by hierarchy changes.
    125 		Array<Group> ancestors = Pools.obtain(Array.class);
    126 		Group parent = this.parent;
    127 		while (parent != null) {
    128 			ancestors.add(parent);
    129 			parent = parent.parent;
    130 		}
    131 
    132 		try {
    133 			// Notify all parent capture listeners, starting at the root. Ancestors may stop an event before children receive it.
    134 			Object[] ancestorsArray = ancestors.items;
    135 			for (int i = ancestors.size - 1; i >= 0; i--) {
    136 				Group currentTarget = (Group)ancestorsArray[i];
    137 				currentTarget.notify(event, true);
    138 				if (event.isStopped()) return event.isCancelled();
    139 			}
    140 
    141 			// Notify the target capture listeners.
    142 			notify(event, true);
    143 			if (event.isStopped()) return event.isCancelled();
    144 
    145 			// Notify the target listeners.
    146 			notify(event, false);
    147 			if (!event.getBubbles()) return event.isCancelled();
    148 			if (event.isStopped()) return event.isCancelled();
    149 
    150 			// Notify all parent listeners, starting at the target. Children may stop an event before ancestors receive it.
    151 			for (int i = 0, n = ancestors.size; i < n; i++) {
    152 				((Group)ancestorsArray[i]).notify(event, false);
    153 				if (event.isStopped()) return event.isCancelled();
    154 			}
    155 
    156 			return event.isCancelled();
    157 		} finally {
    158 			ancestors.clear();
    159 			Pools.free(ancestors);
    160 		}
    161 	}
    162 
    163 	/** Notifies this actor's listeners of the event. The event is not propagated to any parents. Before notifying the listeners,
    164 	 * this actor is set as the {@link Event#getListenerActor() listener actor}. The event {@link Event#setTarget(Actor) target}
    165 	 * must be set before calling this method. If this actor is not in the stage, the stage must be set before calling this method.
    166 	 * @param capture If true, the capture listeners will be notified instead of the regular listeners.
    167 	 * @return true of the event was {@link Event#cancel() cancelled}. */
    168 	public boolean notify (Event event, boolean capture) {
    169 		if (event.getTarget() == null) throw new IllegalArgumentException("The event target cannot be null.");
    170 
    171 		DelayedRemovalArray<EventListener> listeners = capture ? captureListeners : this.listeners;
    172 		if (listeners.size == 0) return event.isCancelled();
    173 
    174 		event.setListenerActor(this);
    175 		event.setCapture(capture);
    176 		if (event.getStage() == null) event.setStage(stage);
    177 
    178 		listeners.begin();
    179 		for (int i = 0, n = listeners.size; i < n; i++) {
    180 			EventListener listener = listeners.get(i);
    181 			if (listener.handle(event)) {
    182 				event.handle();
    183 				if (event instanceof InputEvent) {
    184 					InputEvent inputEvent = (InputEvent)event;
    185 					if (inputEvent.getType() == Type.touchDown) {
    186 						event.getStage().addTouchFocus(listener, this, inputEvent.getTarget(), inputEvent.getPointer(),
    187 							inputEvent.getButton());
    188 					}
    189 				}
    190 			}
    191 		}
    192 		listeners.end();
    193 
    194 		return event.isCancelled();
    195 	}
    196 
    197 	/** Returns the deepest actor that contains the specified point and is {@link #getTouchable() touchable} and
    198 	 * {@link #isVisible() visible}, or null if no actor was hit. The point is specified in the actor's local coordinate system
    199 	 * (0,0 is the bottom left of the actor and width,height is the upper right).
    200 	 * <p>
    201 	 * This method is used to delegate touchDown, mouse, and enter/exit events. If this method returns null, those events will not
    202 	 * occur on this Actor.
    203 	 * <p>
    204 	 * The default implementation returns this actor if the point is within this actor's bounds.
    205 	 * @param touchable If true, the hit detection will respect the {@link #setTouchable(Touchable) touchability}.
    206 	 * @see Touchable */
    207 	public Actor hit (float x, float y, boolean touchable) {
    208 		if (touchable && this.touchable != Touchable.enabled) return null;
    209 		return x >= 0 && x < width && y >= 0 && y < height ? this : null;
    210 	}
    211 
    212 	/** Removes this actor from its parent, if it has a parent.
    213 	 * @see Group#removeActor(Actor) */
    214 	public boolean remove () {
    215 		if (parent != null) return parent.removeActor(this, true);
    216 		return false;
    217 	}
    218 
    219 	/** Add a listener to receive events that {@link #hit(float, float, boolean) hit} this actor. See {@link #fire(Event)}.
    220 	 * @see InputListener
    221 	 * @see ClickListener */
    222 	public boolean addListener (EventListener listener) {
    223 		if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    224 		if (!listeners.contains(listener, true)) {
    225 			listeners.add(listener);
    226 			return true;
    227 		}
    228 		return false;
    229 	}
    230 
    231 	public boolean removeListener (EventListener listener) {
    232 		if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    233 		return listeners.removeValue(listener, true);
    234 	}
    235 
    236 	public Array<EventListener> getListeners () {
    237 		return listeners;
    238 	}
    239 
    240 	/** Adds a listener that is only notified during the capture phase.
    241 	 * @see #fire(Event) */
    242 	public boolean addCaptureListener (EventListener listener) {
    243 		if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    244 		if (!captureListeners.contains(listener, true)) captureListeners.add(listener);
    245 		return true;
    246 	}
    247 
    248 	public boolean removeCaptureListener (EventListener listener) {
    249 		if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
    250 		return captureListeners.removeValue(listener, true);
    251 	}
    252 
    253 	public Array<EventListener> getCaptureListeners () {
    254 		return captureListeners;
    255 	}
    256 
    257 	public void addAction (Action action) {
    258 		action.setActor(this);
    259 		actions.add(action);
    260 
    261 		if (stage != null && stage.getActionsRequestRendering()) Gdx.graphics.requestRendering();
    262 	}
    263 
    264 	public void removeAction (Action action) {
    265 		if (actions.removeValue(action, true)) action.setActor(null);
    266 	}
    267 
    268 	public Array<Action> getActions () {
    269 		return actions;
    270 	}
    271 
    272 	/** Returns true if the actor has one or more actions. */
    273 	public boolean hasActions () {
    274 		return actions.size > 0;
    275 	}
    276 
    277 	/** Removes all actions on this actor. */
    278 	public void clearActions () {
    279 		for (int i = actions.size - 1; i >= 0; i--)
    280 			actions.get(i).setActor(null);
    281 		actions.clear();
    282 	}
    283 
    284 	/** Removes all listeners on this actor. */
    285 	public void clearListeners () {
    286 		listeners.clear();
    287 		captureListeners.clear();
    288 	}
    289 
    290 	/** Removes all actions and listeners on this actor. */
    291 	public void clear () {
    292 		clearActions();
    293 		clearListeners();
    294 	}
    295 
    296 	/** Returns the stage that this actor is currently in, or null if not in a stage. */
    297 	public Stage getStage () {
    298 		return stage;
    299 	}
    300 
    301 	/** Called by the framework when this actor or any parent is added to a group that is in the stage.
    302 	 * @param stage May be null if the actor or any parent is no longer in a stage. */
    303 	protected void setStage (Stage stage) {
    304 		this.stage = stage;
    305 	}
    306 
    307 	/** Returns true if this actor is the same as or is the descendant of the specified actor. */
    308 	public boolean isDescendantOf (Actor actor) {
    309 		if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
    310 		Actor parent = this;
    311 		while (true) {
    312 			if (parent == null) return false;
    313 			if (parent == actor) return true;
    314 			parent = parent.parent;
    315 		}
    316 	}
    317 
    318 	/** Returns true if this actor is the same as or is the ascendant of the specified actor. */
    319 	public boolean isAscendantOf (Actor actor) {
    320 		if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
    321 		while (true) {
    322 			if (actor == null) return false;
    323 			if (actor == this) return true;
    324 			actor = actor.parent;
    325 		}
    326 	}
    327 
    328 	/** Returns true if the actor's parent is not null. */
    329 	public boolean hasParent () {
    330 		return parent != null;
    331 	}
    332 
    333 	/** Returns the parent actor, or null if not in a group. */
    334 	public Group getParent () {
    335 		return parent;
    336 	}
    337 
    338 	/** Called by the framework when an actor is added to or removed from a group.
    339 	 * @param parent May be null if the actor has been removed from the parent. */
    340 	protected void setParent (Group parent) {
    341 		this.parent = parent;
    342 	}
    343 
    344 	/** Returns true if input events are processed by this actor. */
    345 	public boolean isTouchable () {
    346 		return touchable == Touchable.enabled;
    347 	}
    348 
    349 	public Touchable getTouchable () {
    350 		return touchable;
    351 	}
    352 
    353 	/** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */
    354 	public void setTouchable (Touchable touchable) {
    355 		this.touchable = touchable;
    356 	}
    357 
    358 	public boolean isVisible () {
    359 		return visible;
    360 	}
    361 
    362 	/** If false, the actor will not be drawn and will not receive touch events. Default is true. */
    363 	public void setVisible (boolean visible) {
    364 		this.visible = visible;
    365 	}
    366 
    367 	/** Returns an application specific object for convenience, or null. */
    368 	public Object getUserObject () {
    369 		return userObject;
    370 	}
    371 
    372 	/** Sets an application specific object for convenience. */
    373 	public void setUserObject (Object userObject) {
    374 		this.userObject = userObject;
    375 	}
    376 
    377 	/** Returns the X position of the actor's left edge. */
    378 	public float getX () {
    379 		return x;
    380 	}
    381 
    382 	/** Returns the X position of the specified {@link Align alignment}. */
    383 	public float getX (int alignment) {
    384 		float x = this.x;
    385 		if ((alignment & right) != 0)
    386 			x += width;
    387 		else if ((alignment & left) == 0) //
    388 			x += width / 2;
    389 		return x;
    390 	}
    391 
    392 	public void setX (float x) {
    393 		if (this.x != x) {
    394 			this.x = x;
    395 			positionChanged();
    396 		}
    397 	}
    398 
    399 	/** Returns the Y position of the actor's bottom edge. */
    400 	public float getY () {
    401 		return y;
    402 	}
    403 
    404 	public void setY (float y) {
    405 		if (this.y != y) {
    406 			this.y = y;
    407 			positionChanged();
    408 		}
    409 	}
    410 
    411 	/** Returns the Y position of the specified {@link Align alignment}. */
    412 	public float getY (int alignment) {
    413 		float y = this.y;
    414 		if ((alignment & top) != 0)
    415 			y += height;
    416 		else if ((alignment & bottom) == 0) //
    417 			y += height / 2;
    418 		return y;
    419 	}
    420 
    421 	/** Sets the position of the actor's bottom left corner. */
    422 	public void setPosition (float x, float y) {
    423 		if (this.x != x || this.y != y) {
    424 			this.x = x;
    425 			this.y = y;
    426 			positionChanged();
    427 		}
    428 	}
    429 
    430 	/** Sets the position using the specified {@link Align alignment}. Note this may set the position to non-integer
    431 	 * coordinates. */
    432 	public void setPosition (float x, float y, int alignment) {
    433 		if ((alignment & right) != 0)
    434 			x -= width;
    435 		else if ((alignment & left) == 0) //
    436 			x -= width / 2;
    437 
    438 		if ((alignment & top) != 0)
    439 			y -= height;
    440 		else if ((alignment & bottom) == 0) //
    441 			y -= height / 2;
    442 
    443 		if (this.x != x || this.y != y) {
    444 			this.x = x;
    445 			this.y = y;
    446 			positionChanged();
    447 		}
    448 	}
    449 
    450 	/** Add x and y to current position */
    451 	public void moveBy (float x, float y) {
    452 		if (x != 0 || y != 0) {
    453 			this.x += x;
    454 			this.y += y;
    455 			positionChanged();
    456 		}
    457 	}
    458 
    459 	public float getWidth () {
    460 		return width;
    461 	}
    462 
    463 	public void setWidth (float width) {
    464 		if (this.width != width) {
    465 			this.width = width;
    466 			sizeChanged();
    467 		}
    468 	}
    469 
    470 	public float getHeight () {
    471 		return height;
    472 	}
    473 
    474 	public void setHeight (float height) {
    475 		if (this.height != height) {
    476 			this.height = height;
    477 			sizeChanged();
    478 		}
    479 	}
    480 
    481 	/** Returns y plus height. */
    482 	public float getTop () {
    483 		return y + height;
    484 	}
    485 
    486 	/** Returns x plus width. */
    487 	public float getRight () {
    488 		return x + width;
    489 	}
    490 
    491 	/** Called when the actor's position has been changed. */
    492 	protected void positionChanged () {
    493 	}
    494 
    495 	/** Called when the actor's size has been changed. */
    496 	protected void sizeChanged () {
    497 	}
    498 
    499 	/** Called when the actor's rotation has been changed. */
    500 	protected void rotationChanged () {
    501 	}
    502 
    503 	/** Sets the width and height. */
    504 	public void setSize (float width, float height) {
    505 		if (this.width != width || this.height != height) {
    506 			this.width = width;
    507 			this.height = height;
    508 			sizeChanged();
    509 		}
    510 	}
    511 
    512 	/** Adds the specified size to the current size. */
    513 	public void sizeBy (float size) {
    514 		if (size != 0) {
    515 			width += size;
    516 			height += size;
    517 			sizeChanged();
    518 		}
    519 	}
    520 
    521 	/** Adds the specified size to the current size. */
    522 	public void sizeBy (float width, float height) {
    523 		if (width != 0 || height != 0) {
    524 			this.width += width;
    525 			this.height += height;
    526 			sizeChanged();
    527 		}
    528 	}
    529 
    530 	/** Set bounds the x, y, width, and height. */
    531 	public void setBounds (float x, float y, float width, float height) {
    532 		if (this.x != x || this.y != y) {
    533 			this.x = x;
    534 			this.y = y;
    535 			positionChanged();
    536 		}
    537 		if (this.width != width || this.height != height) {
    538 			this.width = width;
    539 			this.height = height;
    540 			sizeChanged();
    541 		}
    542 	}
    543 
    544 	public float getOriginX () {
    545 		return originX;
    546 	}
    547 
    548 	public void setOriginX (float originX) {
    549 		this.originX = originX;
    550 	}
    551 
    552 	public float getOriginY () {
    553 		return originY;
    554 	}
    555 
    556 	public void setOriginY (float originY) {
    557 		this.originY = originY;
    558 	}
    559 
    560 	/** Sets the origin position which is relative to the actor's bottom left corner. */
    561 	public void setOrigin (float originX, float originY) {
    562 		this.originX = originX;
    563 		this.originY = originY;
    564 	}
    565 
    566 	/** Sets the origin position to the specified {@link Align alignment}. */
    567 	public void setOrigin (int alignment) {
    568 		if ((alignment & left) != 0)
    569 			originX = 0;
    570 		else if ((alignment & right) != 0)
    571 			originX = width;
    572 		else
    573 			originX = width / 2;
    574 
    575 		if ((alignment & bottom) != 0)
    576 			originY = 0;
    577 		else if ((alignment & top) != 0)
    578 			originY = height;
    579 		else
    580 			originY = height / 2;
    581 	}
    582 
    583 	public float getScaleX () {
    584 		return scaleX;
    585 	}
    586 
    587 	public void setScaleX (float scaleX) {
    588 		this.scaleX = scaleX;
    589 	}
    590 
    591 	public float getScaleY () {
    592 		return scaleY;
    593 	}
    594 
    595 	public void setScaleY (float scaleY) {
    596 		this.scaleY = scaleY;
    597 	}
    598 
    599 	/** Sets the scale for both X and Y */
    600 	public void setScale (float scaleXY) {
    601 		this.scaleX = scaleXY;
    602 		this.scaleY = scaleXY;
    603 	}
    604 
    605 	/** Sets the scale X and scale Y. */
    606 	public void setScale (float scaleX, float scaleY) {
    607 		this.scaleX = scaleX;
    608 		this.scaleY = scaleY;
    609 	}
    610 
    611 	/** Adds the specified scale to the current scale. */
    612 	public void scaleBy (float scale) {
    613 		scaleX += scale;
    614 		scaleY += scale;
    615 	}
    616 
    617 	/** Adds the specified scale to the current scale. */
    618 	public void scaleBy (float scaleX, float scaleY) {
    619 		this.scaleX += scaleX;
    620 		this.scaleY += scaleY;
    621 	}
    622 
    623 	public float getRotation () {
    624 		return rotation;
    625 	}
    626 
    627 	public void setRotation (float degrees) {
    628 		if (this.rotation != degrees) {
    629 			this.rotation = degrees;
    630 			rotationChanged();
    631 		}
    632 	}
    633 
    634 	/** Adds the specified rotation to the current rotation. */
    635 	public void rotateBy (float amountInDegrees) {
    636 		if (amountInDegrees != 0) {
    637 			rotation += amountInDegrees;
    638 			rotationChanged();
    639 		}
    640 	}
    641 
    642 	public void setColor (Color color) {
    643 		this.color.set(color);
    644 	}
    645 
    646 	public void setColor (float r, float g, float b, float a) {
    647 		color.set(r, g, b, a);
    648 	}
    649 
    650 	/** Returns the color the actor will be tinted when drawn. The returned instance can be modified to change the color. */
    651 	public Color getColor () {
    652 		return color;
    653 	}
    654 
    655 	/** @see #setName(String)
    656 	 * @return May be null. */
    657 	public String getName () {
    658 		return name;
    659 	}
    660 
    661 	/** Set the actor's name, which is used for identification convenience and by {@link #toString()}.
    662 	 * @param name May be null.
    663 	 * @see Group#findActor(String) */
    664 	public void setName (String name) {
    665 		this.name = name;
    666 	}
    667 
    668 	/** Changes the z-order for this actor so it is in front of all siblings. */
    669 	public void toFront () {
    670 		setZIndex(Integer.MAX_VALUE);
    671 	}
    672 
    673 	/** Changes the z-order for this actor so it is in back of all siblings. */
    674 	public void toBack () {
    675 		setZIndex(0);
    676 	}
    677 
    678 	/** Sets the z-index of this actor. The z-index is the index into the parent's {@link Group#getChildren() children}, where a
    679 	 * lower index is below a higher index. Setting a z-index higher than the number of children will move the child to the front.
    680 	 * Setting a z-index less than zero is invalid. */
    681 	public void setZIndex (int index) {
    682 		if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");
    683 		Group parent = this.parent;
    684 		if (parent == null) return;
    685 		Array<Actor> children = parent.children;
    686 		if (children.size == 1) return;
    687 		index = Math.min(index, children.size - 1);
    688 		if (index == children.indexOf(this, true)) return;
    689 		if (!children.removeValue(this, true)) return;
    690 		children.insert(index, this);
    691 	}
    692 
    693 	/** Returns the z-index of this actor.
    694 	 * @see #setZIndex(int) */
    695 	public int getZIndex () {
    696 		Group parent = this.parent;
    697 		if (parent == null) return -1;
    698 		return parent.children.indexOf(this, true);
    699 	}
    700 
    701 	/** Calls {@link #clipBegin(float, float, float, float)} to clip this actor's bounds. */
    702 	public boolean clipBegin () {
    703 		return clipBegin(x, y, width, height);
    704 	}
    705 
    706 	/** Clips the specified screen aligned rectangle, specified relative to the transform matrix of the stage's Batch. The
    707 	 * transform matrix and the stage's camera must not have rotational components. Calling this method must be followed by a call
    708 	 * to {@link #clipEnd()} if true is returned.
    709 	 * @return false if the clipping area is zero and no drawing should occur.
    710 	 * @see ScissorStack */
    711 	public boolean clipBegin (float x, float y, float width, float height) {
    712 		if (width <= 0 || height <= 0) return false;
    713 		Rectangle tableBounds = Rectangle.tmp;
    714 		tableBounds.x = x;
    715 		tableBounds.y = y;
    716 		tableBounds.width = width;
    717 		tableBounds.height = height;
    718 		Stage stage = this.stage;
    719 		Rectangle scissorBounds = Pools.obtain(Rectangle.class);
    720 		stage.calculateScissors(tableBounds, scissorBounds);
    721 		if (ScissorStack.pushScissors(scissorBounds)) return true;
    722 		Pools.free(scissorBounds);
    723 		return false;
    724 	}
    725 
    726 	/** Ends clipping begun by {@link #clipBegin(float, float, float, float)}. */
    727 	public void clipEnd () {
    728 		Pools.free(ScissorStack.popScissors());
    729 	}
    730 
    731 	/** Transforms the specified point in screen coordinates to the actor's local coordinate system. */
    732 	public Vector2 screenToLocalCoordinates (Vector2 screenCoords) {
    733 		Stage stage = this.stage;
    734 		if (stage == null) return screenCoords;
    735 		return stageToLocalCoordinates(stage.screenToStageCoordinates(screenCoords));
    736 	}
    737 
    738 	/** Transforms the specified point in the stage's coordinates to the actor's local coordinate system. */
    739 	public Vector2 stageToLocalCoordinates (Vector2 stageCoords) {
    740 		if (parent != null) parent.stageToLocalCoordinates(stageCoords);
    741 		parentToLocalCoordinates(stageCoords);
    742 		return stageCoords;
    743 	}
    744 
    745 	/** Transforms the specified point in the actor's coordinates to be in the stage's coordinates.
    746 	 * @see Stage#toScreenCoordinates(Vector2, com.badlogic.gdx.math.Matrix4) */
    747 	public Vector2 localToStageCoordinates (Vector2 localCoords) {
    748 		return localToAscendantCoordinates(null, localCoords);
    749 	}
    750 
    751 	/** Transforms the specified point in the actor's coordinates to be in the parent's coordinates. */
    752 	public Vector2 localToParentCoordinates (Vector2 localCoords) {
    753 		final float rotation = -this.rotation;
    754 		final float scaleX = this.scaleX;
    755 		final float scaleY = this.scaleY;
    756 		final float x = this.x;
    757 		final float y = this.y;
    758 		if (rotation == 0) {
    759 			if (scaleX == 1 && scaleY == 1) {
    760 				localCoords.x += x;
    761 				localCoords.y += y;
    762 			} else {
    763 				final float originX = this.originX;
    764 				final float originY = this.originY;
    765 				localCoords.x = (localCoords.x - originX) * scaleX + originX + x;
    766 				localCoords.y = (localCoords.y - originY) * scaleY + originY + y;
    767 			}
    768 		} else {
    769 			final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);
    770 			final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);
    771 			final float originX = this.originX;
    772 			final float originY = this.originY;
    773 			final float tox = (localCoords.x - originX) * scaleX;
    774 			final float toy = (localCoords.y - originY) * scaleY;
    775 			localCoords.x = (tox * cos + toy * sin) + originX + x;
    776 			localCoords.y = (tox * -sin + toy * cos) + originY + y;
    777 		}
    778 		return localCoords;
    779 	}
    780 
    781 	/** Converts coordinates for this actor to those of a parent actor. The ascendant does not need to be a direct parent. */
    782 	public Vector2 localToAscendantCoordinates (Actor ascendant, Vector2 localCoords) {
    783 		Actor actor = this;
    784 		while (actor != null) {
    785 			actor.localToParentCoordinates(localCoords);
    786 			actor = actor.parent;
    787 			if (actor == ascendant) break;
    788 		}
    789 		return localCoords;
    790 	}
    791 
    792 	/** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */
    793 	public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {
    794 		final float rotation = this.rotation;
    795 		final float scaleX = this.scaleX;
    796 		final float scaleY = this.scaleY;
    797 		final float childX = x;
    798 		final float childY = y;
    799 		if (rotation == 0) {
    800 			if (scaleX == 1 && scaleY == 1) {
    801 				parentCoords.x -= childX;
    802 				parentCoords.y -= childY;
    803 			} else {
    804 				final float originX = this.originX;
    805 				final float originY = this.originY;
    806 				parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;
    807 				parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;
    808 			}
    809 		} else {
    810 			final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);
    811 			final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);
    812 			final float originX = this.originX;
    813 			final float originY = this.originY;
    814 			final float tox = parentCoords.x - childX - originX;
    815 			final float toy = parentCoords.y - childY - originY;
    816 			parentCoords.x = (tox * cos + toy * sin) / scaleX + originX;
    817 			parentCoords.y = (tox * -sin + toy * cos) / scaleY + originY;
    818 		}
    819 		return parentCoords;
    820 	}
    821 
    822 	/** Draws this actor's debug lines if {@link #getDebug()} is true. */
    823 	public void drawDebug (ShapeRenderer shapes) {
    824 		drawDebugBounds(shapes);
    825 	}
    826 
    827 	/** Draws a rectange for the bounds of this actor if {@link #getDebug()} is true. */
    828 	protected void drawDebugBounds (ShapeRenderer shapes) {
    829 		if (!debug) return;
    830 		shapes.set(ShapeType.Line);
    831 		shapes.setColor(stage.getDebugColor());
    832 		shapes.rect(x, y, originX, originY, width, height, scaleX, scaleY, rotation);
    833 	}
    834 
    835 	/** If true, {@link #drawDebug(ShapeRenderer)} will be called for this actor. */
    836 	public void setDebug (boolean enabled) {
    837 		debug = enabled;
    838 		if (enabled) Stage.debug = true;
    839 	}
    840 
    841 	public boolean getDebug () {
    842 		return debug;
    843 	}
    844 
    845 	/** Calls {@link #setDebug(boolean)} with {@code true}. */
    846 	public Actor debug () {
    847 		setDebug(true);
    848 		return this;
    849 	}
    850 
    851 	public String toString () {
    852 		String name = this.name;
    853 		if (name == null) {
    854 			name = getClass().getName();
    855 			int dotIndex = name.lastIndexOf('.');
    856 			if (dotIndex != -1) name = name.substring(dotIndex + 1);
    857 		}
    858 		return name;
    859 	}
    860 }
    861