• 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 com.badlogic.gdx.graphics.g2d.Batch;
20 import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
21 import com.badlogic.gdx.math.Affine2;
22 import com.badlogic.gdx.math.Matrix4;
23 import com.badlogic.gdx.math.Rectangle;
24 import com.badlogic.gdx.math.Vector2;
25 import com.badlogic.gdx.scenes.scene2d.utils.Cullable;
26 import com.badlogic.gdx.utils.Array;
27 import com.badlogic.gdx.utils.SnapshotArray;
28 
29 /** 2D scene graph node that may contain other actors.
30  * <p>
31  * Actors have a z-order equal to the order they were inserted into the group. Actors inserted later will be drawn on top of
32  * actors added earlier. Touch events that hit more than one actor are distributed to topmost actors first.
33  * @author mzechner
34  * @author Nathan Sweet */
35 public class Group extends Actor implements Cullable {
36 	static private final Vector2 tmp = new Vector2();
37 
38 	final SnapshotArray<Actor> children = new SnapshotArray(true, 4, Actor.class);
39 	private final Affine2 worldTransform = new Affine2();
40 	private final Matrix4 computedTransform = new Matrix4();
41 	private final Matrix4 oldTransform = new Matrix4();
42 	boolean transform = true;
43 	private Rectangle cullingArea;
44 
act(float delta)45 	public void act (float delta) {
46 		super.act(delta);
47 		Actor[] actors = children.begin();
48 		for (int i = 0, n = children.size; i < n; i++)
49 			actors[i].act(delta);
50 		children.end();
51 	}
52 
53 	/** Draws the group and its children. The default implementation calls {@link #applyTransform(Batch, Matrix4)} if needed, then
54 	 * {@link #drawChildren(Batch, float)}, then {@link #resetTransform(Batch)} if needed. */
draw(Batch batch, float parentAlpha)55 	public void draw (Batch batch, float parentAlpha) {
56 		if (transform) applyTransform(batch, computeTransform());
57 		drawChildren(batch, parentAlpha);
58 		if (transform) resetTransform(batch);
59 	}
60 
61 	/** Draws all children. {@link #applyTransform(Batch, Matrix4)} should be called before and {@link #resetTransform(Batch)}
62 	 * after this method if {@link #setTransform(boolean) transform} is true. If {@link #setTransform(boolean) transform} is false
63 	 * these methods don't need to be called, children positions are temporarily offset by the group position when drawn. This
64 	 * method avoids drawing children completely outside the {@link #setCullingArea(Rectangle) culling area}, if set. */
drawChildren(Batch batch, float parentAlpha)65 	protected void drawChildren (Batch batch, float parentAlpha) {
66 		parentAlpha *= this.color.a;
67 		SnapshotArray<Actor> children = this.children;
68 		Actor[] actors = children.begin();
69 		Rectangle cullingArea = this.cullingArea;
70 		if (cullingArea != null) {
71 			// Draw children only if inside culling area.
72 			float cullLeft = cullingArea.x;
73 			float cullRight = cullLeft + cullingArea.width;
74 			float cullBottom = cullingArea.y;
75 			float cullTop = cullBottom + cullingArea.height;
76 			if (transform) {
77 				for (int i = 0, n = children.size; i < n; i++) {
78 					Actor child = actors[i];
79 					if (!child.isVisible()) continue;
80 					float cx = child.x, cy = child.y;
81 					if (cx <= cullRight && cy <= cullTop && cx + child.width >= cullLeft && cy + child.height >= cullBottom)
82 						child.draw(batch, parentAlpha);
83 				}
84 			} else {
85 				// No transform for this group, offset each child.
86 				float offsetX = x, offsetY = y;
87 				x = 0;
88 				y = 0;
89 				for (int i = 0, n = children.size; i < n; i++) {
90 					Actor child = actors[i];
91 					if (!child.isVisible()) continue;
92 					float cx = child.x, cy = child.y;
93 					if (cx <= cullRight && cy <= cullTop && cx + child.width >= cullLeft && cy + child.height >= cullBottom) {
94 						child.x = cx + offsetX;
95 						child.y = cy + offsetY;
96 						child.draw(batch, parentAlpha);
97 						child.x = cx;
98 						child.y = cy;
99 					}
100 				}
101 				x = offsetX;
102 				y = offsetY;
103 			}
104 		} else {
105 			// No culling, draw all children.
106 			if (transform) {
107 				for (int i = 0, n = children.size; i < n; i++) {
108 					Actor child = actors[i];
109 					if (!child.isVisible()) continue;
110 					child.draw(batch, parentAlpha);
111 				}
112 			} else {
113 				// No transform for this group, offset each child.
114 				float offsetX = x, offsetY = y;
115 				x = 0;
116 				y = 0;
117 				for (int i = 0, n = children.size; i < n; i++) {
118 					Actor child = actors[i];
119 					if (!child.isVisible()) continue;
120 					float cx = child.x, cy = child.y;
121 					child.x = cx + offsetX;
122 					child.y = cy + offsetY;
123 					child.draw(batch, parentAlpha);
124 					child.x = cx;
125 					child.y = cy;
126 				}
127 				x = offsetX;
128 				y = offsetY;
129 			}
130 		}
131 		children.end();
132 	}
133 
134 	/** Draws this actor's debug lines if {@link #getDebug()} is true and, regardless of {@link #getDebug()}, calls
135 	 * {@link Actor#drawDebug(ShapeRenderer)} on each child. */
drawDebug(ShapeRenderer shapes)136 	public void drawDebug (ShapeRenderer shapes) {
137 		drawDebugBounds(shapes);
138 		if (transform) applyTransform(shapes, computeTransform());
139 		drawDebugChildren(shapes);
140 		if (transform) resetTransform(shapes);
141 	}
142 
143 	/** Draws all children. {@link #applyTransform(Batch, Matrix4)} should be called before and {@link #resetTransform(Batch)}
144 	 * after this method if {@link #setTransform(boolean) transform} is true. If {@link #setTransform(boolean) transform} is false
145 	 * these methods don't need to be called, children positions are temporarily offset by the group position when drawn. This
146 	 * method avoids drawing children completely outside the {@link #setCullingArea(Rectangle) culling area}, if set. */
drawDebugChildren(ShapeRenderer shapes)147 	protected void drawDebugChildren (ShapeRenderer shapes) {
148 		SnapshotArray<Actor> children = this.children;
149 		Actor[] actors = children.begin();
150 		// No culling, draw all children.
151 		if (transform) {
152 			for (int i = 0, n = children.size; i < n; i++) {
153 				Actor child = actors[i];
154 				if (!child.isVisible()) continue;
155 				if (!child.getDebug() && !(child instanceof Group)) continue;
156 				child.drawDebug(shapes);
157 			}
158 			shapes.flush();
159 		} else {
160 			// No transform for this group, offset each child.
161 			float offsetX = x, offsetY = y;
162 			x = 0;
163 			y = 0;
164 			for (int i = 0, n = children.size; i < n; i++) {
165 				Actor child = actors[i];
166 				if (!child.isVisible()) continue;
167 				if (!child.getDebug() && !(child instanceof Group)) continue;
168 				float cx = child.x, cy = child.y;
169 				child.x = cx + offsetX;
170 				child.y = cy + offsetY;
171 				child.drawDebug(shapes);
172 				child.x = cx;
173 				child.y = cy;
174 			}
175 			x = offsetX;
176 			y = offsetY;
177 		}
178 		children.end();
179 	}
180 
181 	/** Returns the transform for this group's coordinate system. */
computeTransform()182 	protected Matrix4 computeTransform () {
183 		Affine2 worldTransform = this.worldTransform;
184 		float originX = this.originX, originY = this.originY;
185 		worldTransform.setToTrnRotScl(x + originX, y + originY, rotation, scaleX, scaleY);
186 		if (originX != 0 || originY != 0) worldTransform.translate(-originX, -originY);
187 
188 		// Find the first parent that transforms.
189 		Group parentGroup = parent;
190 		while (parentGroup != null) {
191 			if (parentGroup.transform) break;
192 			parentGroup = parentGroup.parent;
193 		}
194 		if (parentGroup != null) worldTransform.preMul(parentGroup.worldTransform);
195 
196 		computedTransform.set(worldTransform);
197 		return computedTransform;
198 	}
199 
200 	/** Set the batch's transformation matrix, often with the result of {@link #computeTransform()}. Note this causes the batch to
201 	 * be flushed. {@link #resetTransform(Batch)} will restore the transform to what it was before this call. */
applyTransform(Batch batch, Matrix4 transform)202 	protected void applyTransform (Batch batch, Matrix4 transform) {
203 		oldTransform.set(batch.getTransformMatrix());
204 		batch.setTransformMatrix(transform);
205 	}
206 
207 	/** Restores the batch transform to what it was before {@link #applyTransform(Batch, Matrix4)}. Note this causes the batch to
208 	 * be flushed. */
resetTransform(Batch batch)209 	protected void resetTransform (Batch batch) {
210 		batch.setTransformMatrix(oldTransform);
211 	}
212 
213 	/** Set the shape renderer transformation matrix, often with the result of {@link #computeTransform()}. Note this causes the
214 	 * shape renderer to be flushed. {@link #resetTransform(ShapeRenderer)} will restore the transform to what it was before this
215 	 * call. */
applyTransform(ShapeRenderer shapes, Matrix4 transform)216 	protected void applyTransform (ShapeRenderer shapes, Matrix4 transform) {
217 		oldTransform.set(shapes.getTransformMatrix());
218 		shapes.setTransformMatrix(transform);
219 	}
220 
221 	/** Restores the shape renderer transform to what it was before {@link #applyTransform(Batch, Matrix4)}. Note this causes the
222 	 * shape renderer to be flushed. */
resetTransform(ShapeRenderer shapes)223 	protected void resetTransform (ShapeRenderer shapes) {
224 		shapes.setTransformMatrix(oldTransform);
225 	}
226 
227 	/** Children completely outside of this rectangle will not be drawn. This is only valid for use with unrotated and unscaled
228 	 * actors.
229 	 * @param cullingArea May be null. */
setCullingArea(Rectangle cullingArea)230 	public void setCullingArea (Rectangle cullingArea) {
231 		this.cullingArea = cullingArea;
232 	}
233 
234 	/** @return May be null.
235 	 * @see #setCullingArea(Rectangle) */
getCullingArea()236 	public Rectangle getCullingArea () {
237 		return cullingArea;
238 	}
239 
hit(float x, float y, boolean touchable)240 	public Actor hit (float x, float y, boolean touchable) {
241 		if (touchable && getTouchable() == Touchable.disabled) return null;
242 		Vector2 point = tmp;
243 		Actor[] childrenArray = children.items;
244 		for (int i = children.size - 1; i >= 0; i--) {
245 			Actor child = childrenArray[i];
246 			if (!child.isVisible()) continue;
247 			child.parentToLocalCoordinates(point.set(x, y));
248 			Actor hit = child.hit(point.x, point.y, touchable);
249 			if (hit != null) return hit;
250 		}
251 		return super.hit(x, y, touchable);
252 	}
253 
254 	/** Called when actors are added to or removed from the group. */
childrenChanged()255 	protected void childrenChanged () {
256 	}
257 
258 	/** Adds an actor as a child of this group. The actor is first removed from its parent group, if any. */
addActor(Actor actor)259 	public void addActor (Actor actor) {
260 		if (actor.parent != null) actor.parent.removeActor(actor, false);
261 		children.add(actor);
262 		actor.setParent(this);
263 		actor.setStage(getStage());
264 		childrenChanged();
265 	}
266 
267 	/** Adds an actor as a child of this group, at a specific index. The actor is first removed from its parent group, if any.
268 	 * @param index May be greater than the number of children. */
addActorAt(int index, Actor actor)269 	public void addActorAt (int index, Actor actor) {
270 		if (actor.parent != null) actor.parent.removeActor(actor, false);
271 		if (index >= children.size)
272 			children.add(actor);
273 		else
274 			children.insert(index, actor);
275 		actor.setParent(this);
276 		actor.setStage(getStage());
277 		childrenChanged();
278 	}
279 
280 	/** Adds an actor as a child of this group, immediately before another child actor. The actor is first removed from its parent
281 	 * group, if any. */
addActorBefore(Actor actorBefore, Actor actor)282 	public void addActorBefore (Actor actorBefore, Actor actor) {
283 		if (actor.parent != null) actor.parent.removeActor(actor, false);
284 		int index = children.indexOf(actorBefore, true);
285 		children.insert(index, actor);
286 		actor.setParent(this);
287 		actor.setStage(getStage());
288 		childrenChanged();
289 	}
290 
291 	/** Adds an actor as a child of this group, immediately after another child actor. The actor is first removed from its parent
292 	 * group, if any. */
addActorAfter(Actor actorAfter, Actor actor)293 	public void addActorAfter (Actor actorAfter, Actor actor) {
294 		if (actor.parent != null) actor.parent.removeActor(actor, false);
295 		int index = children.indexOf(actorAfter, true);
296 		if (index == children.size)
297 			children.add(actor);
298 		else
299 			children.insert(index + 1, actor);
300 		actor.setParent(this);
301 		actor.setStage(getStage());
302 		childrenChanged();
303 	}
304 
305 	/** Calls {@link #removeActor(Actor, boolean)} with true. */
removeActor(Actor actor)306 	public boolean removeActor (Actor actor) {
307 		return removeActor(actor, true);
308 	}
309 
310 	/** Removes an actor from this group. If the actor will not be used again and has actions, they should be
311 	 * {@link Actor#clearActions() cleared} so the actions will be returned to their
312 	 * {@link Action#setPool(com.badlogic.gdx.utils.Pool) pool}, if any. This is not done automatically.
313 	 * @param unfocus If true, {@link Stage#unfocus(Actor)} is called.
314 	 * @return true if the actor was removed from this group. */
removeActor(Actor actor, boolean unfocus)315 	public boolean removeActor (Actor actor, boolean unfocus) {
316 		if (!children.removeValue(actor, true)) return false;
317 		if (unfocus) {
318 			Stage stage = getStage();
319 			if (stage != null) stage.unfocus(actor);
320 		}
321 		actor.setParent(null);
322 		actor.setStage(null);
323 		childrenChanged();
324 		return true;
325 	}
326 
327 	/** Removes all actors from this group. */
clearChildren()328 	public void clearChildren () {
329 		Actor[] actors = children.begin();
330 		for (int i = 0, n = children.size; i < n; i++) {
331 			Actor child = actors[i];
332 			child.setStage(null);
333 			child.setParent(null);
334 		}
335 		children.end();
336 		children.clear();
337 		childrenChanged();
338 	}
339 
340 	/** Removes all children, actions, and listeners from this group. */
clear()341 	public void clear () {
342 		super.clear();
343 		clearChildren();
344 	}
345 
346 	/** Returns the first actor found with the specified name. Note this recursively compares the name of every actor in the
347 	 * group. */
findActor(String name)348 	public <T extends Actor> T findActor (String name) {
349 		Array<Actor> children = this.children;
350 		for (int i = 0, n = children.size; i < n; i++)
351 			if (name.equals(children.get(i).getName())) return (T)children.get(i);
352 		for (int i = 0, n = children.size; i < n; i++) {
353 			Actor child = children.get(i);
354 			if (child instanceof Group) {
355 				Actor actor = ((Group)child).findActor(name);
356 				if (actor != null) return (T)actor;
357 			}
358 		}
359 		return null;
360 	}
361 
setStage(Stage stage)362 	protected void setStage (Stage stage) {
363 		super.setStage(stage);
364 		Actor[] childrenArray = children.items;
365 		for (int i = 0, n = children.size; i < n; i++)
366 			childrenArray[i].setStage(stage); // StackOverflowError here means the group is its own ancestor.
367 	}
368 
369 	/** Swaps two actors by index. Returns false if the swap did not occur because the indexes were out of bounds. */
swapActor(int first, int second)370 	public boolean swapActor (int first, int second) {
371 		int maxIndex = children.size;
372 		if (first < 0 || first >= maxIndex) return false;
373 		if (second < 0 || second >= maxIndex) return false;
374 		children.swap(first, second);
375 		return true;
376 	}
377 
378 	/** Swaps two actors. Returns false if the swap did not occur because the actors are not children of this group. */
swapActor(Actor first, Actor second)379 	public boolean swapActor (Actor first, Actor second) {
380 		int firstIndex = children.indexOf(first, true);
381 		int secondIndex = children.indexOf(second, true);
382 		if (firstIndex == -1 || secondIndex == -1) return false;
383 		children.swap(firstIndex, secondIndex);
384 		return true;
385 	}
386 
387 	/** Returns an ordered list of child actors in this group. */
getChildren()388 	public SnapshotArray<Actor> getChildren () {
389 		return children;
390 	}
391 
hasChildren()392 	public boolean hasChildren () {
393 		return children.size > 0;
394 	}
395 
396 	/** When true (the default), the Batch is transformed so children are drawn in their parent's coordinate system. This has a
397 	 * performance impact because {@link Batch#flush()} must be done before and after the transform. If the actors in a group are
398 	 * not rotated or scaled, then the transform for the group can be set to false. In this case, each child's position will be
399 	 * offset by the group's position for drawing, causing the children to appear in the correct location even though the Batch has
400 	 * not been transformed. */
setTransform(boolean transform)401 	public void setTransform (boolean transform) {
402 		this.transform = transform;
403 	}
404 
isTransform()405 	public boolean isTransform () {
406 		return transform;
407 	}
408 
409 	/** Converts coordinates for this group to those of a descendant actor. The descendant does not need to be a direct child.
410 	 * @throws IllegalArgumentException if the specified actor is not a descendant of this group. */
localToDescendantCoordinates(Actor descendant, Vector2 localCoords)411 	public Vector2 localToDescendantCoordinates (Actor descendant, Vector2 localCoords) {
412 		Group parent = descendant.parent;
413 		if (parent == null) throw new IllegalArgumentException("Child is not a descendant: " + descendant);
414 		// First convert to the actor's parent coordinates.
415 		if (parent != this) localToDescendantCoordinates(parent, localCoords);
416 		// Then from each parent down to the descendant.
417 		descendant.parentToLocalCoordinates(localCoords);
418 		return localCoords;
419 	}
420 
421 	/** If true, {@link #drawDebug(ShapeRenderer)} will be called for this group and, optionally, all children recursively. */
setDebug(boolean enabled, boolean recursively)422 	public void setDebug (boolean enabled, boolean recursively) {
423 		setDebug(enabled);
424 		if (recursively) {
425 			for (Actor child : children) {
426 				if (child instanceof Group) {
427 					((Group)child).setDebug(enabled, recursively);
428 				} else {
429 					child.setDebug(enabled);
430 				}
431 			}
432 		}
433 	}
434 
435 	/** Calls {@link #setDebug(boolean, boolean)} with {@code true, true}. */
debugAll()436 	public Group debugAll () {
437 		setDebug(true, true);
438 		return this;
439 	}
440 
441 	/** Returns a description of the actor hierarchy, recursively. */
toString()442 	public String toString () {
443 		StringBuilder buffer = new StringBuilder(128);
444 		toString(buffer, 1);
445 		buffer.setLength(buffer.length() - 1);
446 		return buffer.toString();
447 	}
448 
toString(StringBuilder buffer, int indent)449 	void toString (StringBuilder buffer, int indent) {
450 		buffer.append(super.toString());
451 		buffer.append('\n');
452 
453 		Actor[] actors = children.begin();
454 		for (int i = 0, n = children.size; i < n; i++) {
455 			for (int ii = 0; ii < indent; ii++)
456 				buffer.append("|  ");
457 			Actor actor = actors[i];
458 			if (actor instanceof Group)
459 				((Group)actor).toString(buffer, indent + 1);
460 			else {
461 				buffer.append(actor);
462 				buffer.append('\n');
463 			}
464 		}
465 		children.end();
466 	}
467 }
468