com.badlogic.gdx.scenes.scene2d.Group.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.scenes.scene2d.Group.java

Source

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.scenes.scene2d;

import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Affine2;
import com.badlogic.gdx.math.Matrix3;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.utils.Cullable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.SnapshotArray;

/** 2D scene graph node that may contain other actors.
 * <p>
 * Actors have a z-order equal to the order they were inserted into the group. Actors inserted later will be drawn on top of
 * actors added earlier. Touch events that hit more than one actor are distributed to topmost actors first.
 * @author mzechner
 * @author Nathan Sweet */
public class Group extends Actor implements Cullable {
    static private final Vector2 tmp = new Vector2();

    final SnapshotArray<Actor> children = new SnapshotArray(true, 4, Actor.class);
    private final Affine2 worldTransform = new Affine2();
    private final Matrix4 computedTransform = new Matrix4();
    private final Matrix4 oldTransform = new Matrix4();
    boolean transform = true;
    private Rectangle cullingArea;

    public void act(float delta) {
        super.act(delta);
        Actor[] actors = children.begin();
        for (int i = 0, n = children.size; i < n; i++)
            actors[i].act(delta);
        children.end();
    }

    /** Draws the group and its children. The default implementation calls {@link #applyTransform(Batch, Matrix4)} if needed, then
     * {@link #drawChildren(Batch, float)}, then {@link #resetTransform(Batch)} if needed. */
    public void draw(Batch batch, float parentAlpha) {
        if (transform)
            applyTransform(batch, computeTransform());
        drawChildren(batch, parentAlpha);
        if (transform)
            resetTransform(batch);
    }

    /** Draws all children. {@link #applyTransform(Batch, Matrix4)} should be called before and {@link #resetTransform(Batch)} after
     * this method if {@link #setTransform(boolean) transform} is true. If {@link #setTransform(boolean) transform} is false these
     * methods don't need to be called, children positions are temporarily offset by the group position when drawn. This method
     * avoids drawing children completely outside the {@link #setCullingArea(Rectangle) culling area}, if set. */
    protected void drawChildren(Batch batch, float parentAlpha) {
        parentAlpha *= this.color.a;
        SnapshotArray<Actor> children = this.children;
        Actor[] actors = children.begin();
        Rectangle cullingArea = this.cullingArea;
        if (cullingArea != null) {
            // Draw children only if inside culling area.
            float cullLeft = cullingArea.x;
            float cullRight = cullLeft + cullingArea.width;
            float cullBottom = cullingArea.y;
            float cullTop = cullBottom + cullingArea.height;
            if (transform) {
                for (int i = 0, n = children.size; i < n; i++) {
                    Actor child = actors[i];
                    if (!child.isVisible())
                        continue;
                    float cx = child.x, cy = child.y;
                    if (cx <= cullRight && cy <= cullTop && cx + child.width >= cullLeft
                            && cy + child.height >= cullBottom)
                        child.draw(batch, parentAlpha);
                }
            } else {
                // No transform for this group, offset each child.
                float offsetX = x, offsetY = y;
                x = 0;
                y = 0;
                for (int i = 0, n = children.size; i < n; i++) {
                    Actor child = actors[i];
                    if (!child.isVisible())
                        continue;
                    float cx = child.x, cy = child.y;
                    if (cx <= cullRight && cy <= cullTop && cx + child.width >= cullLeft
                            && cy + child.height >= cullBottom) {
                        child.x = cx + offsetX;
                        child.y = cy + offsetY;
                        child.draw(batch, parentAlpha);
                        child.x = cx;
                        child.y = cy;
                    }
                }
                x = offsetX;
                y = offsetY;
            }
        } else {
            // No culling, draw all children.
            if (transform) {
                for (int i = 0, n = children.size; i < n; i++) {
                    Actor child = actors[i];
                    if (!child.isVisible())
                        continue;
                    child.draw(batch, parentAlpha);
                }
            } else {
                // No transform for this group, offset each child.
                float offsetX = x, offsetY = y;
                x = 0;
                y = 0;
                for (int i = 0, n = children.size; i < n; i++) {
                    Actor child = actors[i];
                    if (!child.isVisible())
                        continue;
                    float cx = child.x, cy = child.y;
                    child.x = cx + offsetX;
                    child.y = cy + offsetY;
                    child.draw(batch, parentAlpha);
                    child.x = cx;
                    child.y = cy;
                }
                x = offsetX;
                y = offsetY;
            }
        }
        children.end();
    }

    /** Draws this actor's debug lines if {@link #getDebug()} is true and, regardless of {@link #getDebug()}, calls
     * {@link Actor#drawDebug(ShapeRenderer)} on each child. */
    public void drawDebug(ShapeRenderer shapes) {
        drawDebugBounds(shapes);
        if (transform)
            applyTransform(shapes, computeTransform());
        drawDebugChildren(shapes);
        if (transform)
            resetTransform(shapes);
    }

    /** Draws all children. {@link #applyTransform(Batch, Matrix4)} should be called before and {@link #resetTransform(Batch)} after
     * this method if {@link #setTransform(boolean) transform} is true. If {@link #setTransform(boolean) transform} is false these
     * methods don't need to be called, children positions are temporarily offset by the group position when drawn. This method
     * avoids drawing children completely outside the {@link #setCullingArea(Rectangle) culling area}, if set. */
    protected void drawDebugChildren(ShapeRenderer shapes) {
        SnapshotArray<Actor> children = this.children;
        Actor[] actors = children.begin();
        // No culling, draw all children.
        if (transform) {
            for (int i = 0, n = children.size; i < n; i++) {
                Actor child = actors[i];
                if (!child.isVisible())
                    continue;
                child.drawDebug(shapes);
            }
            shapes.flush();
        } else {
            // No transform for this group, offset each child.
            float offsetX = x, offsetY = y;
            x = 0;
            y = 0;
            for (int i = 0, n = children.size; i < n; i++) {
                Actor child = actors[i];
                if (!child.isVisible())
                    continue;
                float cx = child.x, cy = child.y;
                child.x = cx + offsetX;
                child.y = cy + offsetY;
                child.drawDebug(shapes);
                child.x = cx;
                child.y = cy;
            }
            x = offsetX;
            y = offsetY;
        }
        children.end();
    }

    /** Returns the transform for this group's coordinate system. */
    protected Matrix4 computeTransform() {
        Affine2 worldTransform = this.worldTransform;

        float originX = this.originX;
        float originY = this.originY;
        float rotation = this.rotation;
        float scaleX = this.scaleX;
        float scaleY = this.scaleY;

        worldTransform.setToTrnRotScl(x + originX, y + originY, rotation, scaleX, scaleY);
        if (originX != 0 || originY != 0)
            worldTransform.translate(-originX, -originY);

        // Find the first parent that transforms.
        Group parentGroup = parent;
        while (parentGroup != null) {
            if (parentGroup.transform)
                break;
            parentGroup = parentGroup.parent;
        }
        if (parentGroup != null)
            worldTransform.preMul(parentGroup.worldTransform);

        computedTransform.set(worldTransform);
        return computedTransform;
    }

    /** Set the batch's transformation matrix, often with the result of {@link #computeTransform()}. Note this causes the batch to
     * be flushed. {@link #resetTransform(Batch)} will restore the transform to what it was before this call. */
    protected void applyTransform(Batch batch, Matrix4 transform) {
        oldTransform.set(batch.getTransformMatrix());
        batch.setTransformMatrix(transform);
    }

    /** Restores the batch transform to what it was before {@link #applyTransform(Batch, Matrix4)}. Note this causes the batch to be
     * flushed. */
    protected void resetTransform(Batch batch) {
        batch.setTransformMatrix(oldTransform);
    }

    /** Set the shape renderer transformation matrix, often with the result of {@link #computeTransform()}. Note this causes the
     * shape renderer to be flushed. {@link #resetTransform(ShapeRenderer)} will restore the transform to what it was before this
     * call. */
    protected void applyTransform(ShapeRenderer shapes, Matrix4 transform) {
        oldTransform.set(shapes.getTransformMatrix());
        shapes.setTransformMatrix(transform);
    }

    /** Restores the shape renderer transform to what it was before {@link #applyTransform(Batch, Matrix4)}. Note this causes the
     * shape renderer to be flushed. */
    protected void resetTransform(ShapeRenderer shapes) {
        shapes.setTransformMatrix(oldTransform);
    }

    /** Children completely outside of this rectangle will not be drawn. This is only valid for use with unrotated and unscaled
     * actors! */
    public void setCullingArea(Rectangle cullingArea) {
        this.cullingArea = cullingArea;
    }

    public Actor hit(float x, float y, boolean touchable) {
        if (touchable && getTouchable() == Touchable.disabled)
            return null;
        Vector2 point = tmp;
        Actor[] childrenArray = children.items;
        for (int i = children.size - 1; i >= 0; i--) {
            Actor child = childrenArray[i];
            if (!child.isVisible())
                continue;
            child.parentToLocalCoordinates(point.set(x, y));
            Actor hit = child.hit(point.x, point.y, touchable);
            if (hit != null)
                return hit;
        }
        return super.hit(x, y, touchable);
    }

    /** Called when actors are added to or removed from the group. */
    protected void childrenChanged() {
    }

    /** Adds an actor as a child of this group. The actor is first removed from its parent group, if any.
     * @see #remove() */
    public void addActor(Actor actor) {
        actor.remove();
        children.add(actor);
        actor.setParent(this);
        actor.setStage(getStage());
        childrenChanged();
    }

    /** Adds an actor as a child of this group, at a specific index. The actor is first removed from its parent group, if any.
     * @param index May be greater than the number of children. */
    public void addActorAt(int index, Actor actor) {
        actor.remove();
        if (index >= children.size)
            children.add(actor);
        else
            children.insert(index, actor);
        actor.setParent(this);
        actor.setStage(getStage());
        childrenChanged();
    }

    /** Adds an actor as a child of this group, immediately before another child actor. The actor is first removed from its parent
     * group, if any. */
    public void addActorBefore(Actor actorBefore, Actor actor) {
        actor.remove();
        int index = children.indexOf(actorBefore, true);
        children.insert(index, actor);
        actor.setParent(this);
        actor.setStage(getStage());
        childrenChanged();
    }

    /** Adds an actor as a child of this group, immediately after another child actor. The actor is first removed from its parent
     * group, if any. */
    public void addActorAfter(Actor actorAfter, Actor actor) {
        actor.remove();
        int index = children.indexOf(actorAfter, true);
        if (index == children.size)
            children.add(actor);
        else
            children.insert(index + 1, actor);
        actor.setParent(this);
        actor.setStage(getStage());
        childrenChanged();
    }

    /** Removes an actor from this group. If the actor will not be used again and has actions, they should be
     * {@link Actor#clearActions() cleared} so the actions will be returned to their
     * {@link Action#setPool(com.badlogic.gdx.utils.Pool) pool}, if any. This is not done automatically. */
    public boolean removeActor(Actor actor) {
        if (!children.removeValue(actor, true))
            return false;
        Stage stage = getStage();
        if (stage != null)
            stage.unfocus(actor);
        actor.setParent(null);
        actor.setStage(null);
        childrenChanged();
        return true;
    }

    /** Removes all actors from this group. */
    public void clearChildren() {
        Actor[] actors = children.begin();
        for (int i = 0, n = children.size; i < n; i++) {
            Actor child = actors[i];
            child.setStage(null);
            child.setParent(null);
        }
        children.end();
        children.clear();
        childrenChanged();
    }

    /** Removes all children, actions, and listeners from this group. */
    public void clear() {
        super.clear();
        clearChildren();
    }

    /** Returns the first actor found with the specified name. Note this recursively compares the name of every actor in the group. */
    public <T extends Actor> T findActor(String name) {
        Array<Actor> children = this.children;
        for (int i = 0, n = children.size; i < n; i++)
            if (name.equals(children.get(i).getName()))
                return (T) children.get(i);
        for (int i = 0, n = children.size; i < n; i++) {
            Actor child = children.get(i);
            if (child instanceof Group) {
                Actor actor = ((Group) child).findActor(name);
                if (actor != null)
                    return (T) actor;
            }
        }
        return null;
    }

    protected void setStage(Stage stage) {
        super.setStage(stage);
        Actor[] childrenArray = children.items;
        for (int i = 0, n = children.size; i < n; i++)
            childrenArray[i].setStage(stage); // StackOverflowError here means the group is its own ancestor.
    }

    /** Swaps two actors by index. Returns false if the swap did not occur because the indexes were out of bounds. */
    public boolean swapActor(int first, int second) {
        int maxIndex = children.size;
        if (first < 0 || first >= maxIndex)
            return false;
        if (second < 0 || second >= maxIndex)
            return false;
        children.swap(first, second);
        return true;
    }

    /** Swaps two actors. Returns false if the swap did not occur because the actors are not children of this group. */
    public boolean swapActor(Actor first, Actor second) {
        int firstIndex = children.indexOf(first, true);
        int secondIndex = children.indexOf(second, true);
        if (firstIndex == -1 || secondIndex == -1)
            return false;
        children.swap(firstIndex, secondIndex);
        return true;
    }

    /** Returns an ordered list of child actors in this group. */
    public SnapshotArray<Actor> getChildren() {
        return children;
    }

    public boolean hasChildren() {
        return children.size > 0;
    }

    /** When true (the default), the Batch is transformed so children are drawn in their parent's coordinate system. This has a
     * performance impact because {@link Batch#flush()} must be done before and after the transform. If the actors in a group are
     * not rotated or scaled, then the transform for the group can be set to false. In this case, each child's position will be
     * offset by the group's position for drawing, causing the children to appear in the correct location even though the Batch has
     * not been transformed. */
    public void setTransform(boolean transform) {
        this.transform = transform;
    }

    public boolean isTransform() {
        return transform;
    }

    /** Converts coordinates for this group to those of a descendant actor. The descendant does not need to be a direct child.
     * @throws IllegalArgumentException if the specified actor is not a descendant of this group. */
    public Vector2 localToDescendantCoordinates(Actor descendant, Vector2 localCoords) {
        Group parent = descendant.parent;
        if (parent == null)
            throw new IllegalArgumentException("Child is not a descendant: " + descendant);
        // First convert to the actor's parent coordinates.
        if (parent != this)
            localToDescendantCoordinates(parent, localCoords);
        // Then from each parent down to the descendant.
        descendant.parentToLocalCoordinates(localCoords);
        return localCoords;
    }

    /** If true, {@link #drawDebug(ShapeRenderer)} will be called for this group and, optionally, all children recursively. */
    public void setDebug(boolean enabled, boolean recursively) {
        setDebug(enabled);
        if (recursively) {
            for (Actor child : children) {
                if (child instanceof Group) {
                    ((Group) child).setDebug(enabled, recursively);
                } else {
                    child.setDebug(enabled);
                }
            }
        }
    }

    /** Calls {@link #setDebug(boolean, boolean)} with {@code true, true}. */
    public Group debugAll() {
        setDebug(true, true);
        return this;
    }

    /** Prints the actor hierarchy recursively for debugging purposes. */
    public void print() {
        print("");
    }

    private void print(String indent) {
        Actor[] actors = children.begin();
        for (int i = 0, n = children.size; i < n; i++) {
            System.out.println(indent + actors[i]);
            if (actors[i] instanceof Group)
                ((Group) actors[i]).print(indent + "|  ");
        }
        children.end();
    }
}