• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.scenes.scene2d;
18 
19 import 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. */
draw(Batch batch, float parentAlpha)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. */
act(float delta)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}. */
fire(Event event)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}. */
notify(Event event, boolean capture)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 */
hit(float x, float y, boolean 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) */
remove()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 */
addListener(EventListener listener)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 
removeListener(EventListener listener)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 
getListeners()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) */
addCaptureListener(EventListener listener)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 
removeCaptureListener(EventListener listener)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 
getCaptureListeners()253 	public Array<EventListener> getCaptureListeners () {
254 		return captureListeners;
255 	}
256 
addAction(Action action)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 
removeAction(Action action)264 	public void removeAction (Action action) {
265 		if (actions.removeValue(action, true)) action.setActor(null);
266 	}
267 
getActions()268 	public Array<Action> getActions () {
269 		return actions;
270 	}
271 
272 	/** Returns true if the actor has one or more actions. */
hasActions()273 	public boolean hasActions () {
274 		return actions.size > 0;
275 	}
276 
277 	/** Removes all actions on this actor. */
clearActions()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. */
clearListeners()285 	public void clearListeners () {
286 		listeners.clear();
287 		captureListeners.clear();
288 	}
289 
290 	/** Removes all actions and listeners on this actor. */
clear()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. */
getStage()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. */
setStage(Stage 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. */
isDescendantOf(Actor 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. */
isAscendantOf(Actor 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. */
hasParent()329 	public boolean hasParent () {
330 		return parent != null;
331 	}
332 
333 	/** Returns the parent actor, or null if not in a group. */
getParent()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. */
setParent(Group parent)340 	protected void setParent (Group parent) {
341 		this.parent = parent;
342 	}
343 
344 	/** Returns true if input events are processed by this actor. */
isTouchable()345 	public boolean isTouchable () {
346 		return touchable == Touchable.enabled;
347 	}
348 
getTouchable()349 	public Touchable getTouchable () {
350 		return touchable;
351 	}
352 
353 	/** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */
setTouchable(Touchable touchable)354 	public void setTouchable (Touchable touchable) {
355 		this.touchable = touchable;
356 	}
357 
isVisible()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. */
setVisible(boolean visible)363 	public void setVisible (boolean visible) {
364 		this.visible = visible;
365 	}
366 
367 	/** Returns an application specific object for convenience, or null. */
getUserObject()368 	public Object getUserObject () {
369 		return userObject;
370 	}
371 
372 	/** Sets an application specific object for convenience. */
setUserObject(Object userObject)373 	public void setUserObject (Object userObject) {
374 		this.userObject = userObject;
375 	}
376 
377 	/** Returns the X position of the actor's left edge. */
getX()378 	public float getX () {
379 		return x;
380 	}
381 
382 	/** Returns the X position of the specified {@link Align alignment}. */
getX(int 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 
setX(float x)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. */
getY()400 	public float getY () {
401 		return y;
402 	}
403 
setY(float y)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}. */
getY(int 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. */
setPosition(float x, float y)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. */
setPosition(float x, float y, int alignment)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 */
moveBy(float x, float y)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 
getWidth()459 	public float getWidth () {
460 		return width;
461 	}
462 
setWidth(float width)463 	public void setWidth (float width) {
464 		if (this.width != width) {
465 			this.width = width;
466 			sizeChanged();
467 		}
468 	}
469 
getHeight()470 	public float getHeight () {
471 		return height;
472 	}
473 
setHeight(float height)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. */
getTop()482 	public float getTop () {
483 		return y + height;
484 	}
485 
486 	/** Returns x plus width. */
getRight()487 	public float getRight () {
488 		return x + width;
489 	}
490 
491 	/** Called when the actor's position has been changed. */
positionChanged()492 	protected void positionChanged () {
493 	}
494 
495 	/** Called when the actor's size has been changed. */
sizeChanged()496 	protected void sizeChanged () {
497 	}
498 
499 	/** Called when the actor's rotation has been changed. */
rotationChanged()500 	protected void rotationChanged () {
501 	}
502 
503 	/** Sets the width and height. */
setSize(float width, float 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. */
sizeBy(float 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. */
sizeBy(float width, float height)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. */
setBounds(float x, float y, float width, float 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 
getOriginX()544 	public float getOriginX () {
545 		return originX;
546 	}
547 
setOriginX(float originX)548 	public void setOriginX (float originX) {
549 		this.originX = originX;
550 	}
551 
getOriginY()552 	public float getOriginY () {
553 		return originY;
554 	}
555 
setOriginY(float originY)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. */
setOrigin(float originX, float originY)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}. */
setOrigin(int 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 
getScaleX()583 	public float getScaleX () {
584 		return scaleX;
585 	}
586 
setScaleX(float scaleX)587 	public void setScaleX (float scaleX) {
588 		this.scaleX = scaleX;
589 	}
590 
getScaleY()591 	public float getScaleY () {
592 		return scaleY;
593 	}
594 
setScaleY(float scaleY)595 	public void setScaleY (float scaleY) {
596 		this.scaleY = scaleY;
597 	}
598 
599 	/** Sets the scale for both X and Y */
setScale(float scaleXY)600 	public void setScale (float scaleXY) {
601 		this.scaleX = scaleXY;
602 		this.scaleY = scaleXY;
603 	}
604 
605 	/** Sets the scale X and scale Y. */
setScale(float scaleX, float scaleY)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. */
scaleBy(float scale)612 	public void scaleBy (float scale) {
613 		scaleX += scale;
614 		scaleY += scale;
615 	}
616 
617 	/** Adds the specified scale to the current scale. */
scaleBy(float scaleX, float scaleY)618 	public void scaleBy (float scaleX, float scaleY) {
619 		this.scaleX += scaleX;
620 		this.scaleY += scaleY;
621 	}
622 
getRotation()623 	public float getRotation () {
624 		return rotation;
625 	}
626 
setRotation(float degrees)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. */
rotateBy(float amountInDegrees)635 	public void rotateBy (float amountInDegrees) {
636 		if (amountInDegrees != 0) {
637 			rotation += amountInDegrees;
638 			rotationChanged();
639 		}
640 	}
641 
setColor(Color color)642 	public void setColor (Color color) {
643 		this.color.set(color);
644 	}
645 
setColor(float r, float g, float b, float a)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. */
getColor()651 	public Color getColor () {
652 		return color;
653 	}
654 
655 	/** @see #setName(String)
656 	 * @return May be null. */
getName()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) */
setName(String name)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. */
toFront()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. */
toBack()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. */
setZIndex(int index)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) */
getZIndex()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. */
clipBegin()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 */
clipBegin(float x, float y, float width, float height)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)}. */
clipEnd()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. */
screenToLocalCoordinates(Vector2 screenCoords)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. */
stageToLocalCoordinates(Vector2 stageCoords)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) */
localToStageCoordinates(Vector2 localCoords)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. */
localToParentCoordinates(Vector2 localCoords)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. */
localToAscendantCoordinates(Actor ascendant, Vector2 localCoords)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. */
parentToLocalCoordinates(Vector2 parentCoords)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. */
drawDebug(ShapeRenderer shapes)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. */
drawDebugBounds(ShapeRenderer shapes)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. */
setDebug(boolean enabled)836 	public void setDebug (boolean enabled) {
837 		debug = enabled;
838 		if (enabled) Stage.debug = true;
839 	}
840 
getDebug()841 	public boolean getDebug () {
842 		return debug;
843 	}
844 
845 	/** Calls {@link #setDebug(boolean)} with {@code true}. */
debug()846 	public Actor debug () {
847 		setDebug(true);
848 		return this;
849 	}
850 
toString()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