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