All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.scenes.scene2d.Group Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * 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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
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.utils.ObjectMap;

/** A group is an Actor that contains other Actors (also other Groups which are Actors).
 * @author mzechner
 * @author Nathan Sweet */
public class Group extends Actor implements Cullable {
	public static Texture debugTexture;
	public static boolean debug = false;

	protected final List children;
	protected final List immutableChildren;
	protected final List groups;
	protected final List immutableGroups;
	protected final ObjectMap namesToActors;

	protected final Matrix3 localTransform = new Matrix3();
	protected final Matrix3 worldTransform = new Matrix3();
	protected final Matrix4 batchTransform = new Matrix4();
	protected final Matrix4 oldBatchTransform = new Matrix4();

	public boolean transform = true;
	public Actor lastTouchedChild;

	protected Rectangle cullingArea;
	protected final Vector2 point = new Vector2();

	public Group () {
		this(null);
	}

	/** Creates a new Group with the given name.
	 * @param name the name of the group */
	public Group (String name) {
		super(name);
		children = new ArrayList();
		immutableChildren = Collections.unmodifiableList(children);
		groups = new ArrayList();
		immutableGroups = Collections.unmodifiableList(groups);
		namesToActors = new ObjectMap();
	}

	public void act (float delta) {
		super.act(delta);
		for (int i = 0; i < children.size(); i++) {
			Actor child = children.get(i);
			child.act(delta);
			if (child.isMarkedToRemove()) {
				child.markToRemove(false);
				removeActor(child);
				i--;
			}
		}
	}

	@Override
	public void draw (SpriteBatch batch, float parentAlpha) {
		if (debug && debugTexture != null && parent != null)
			batch.draw(debugTexture, x, y, originX, originY, width == 0 ? 200 : width, height == 0 ? 200 : height, scaleX, scaleY,
				rotation, 0, 0, debugTexture.getWidth(), debugTexture.getHeight(), false, false);

		if (transform) applyTransform(batch);
		drawChildren(batch, parentAlpha);
		if (transform) resetTransform(batch);
	}

	protected void drawChildren (SpriteBatch batch, float parentAlpha) {
		parentAlpha *= color.a;
		if (cullingArea != null) {
			if (transform) {
				for (int i = 0; i < children.size(); i++) {
					Actor child = children.get(i);
					if (!child.visible) continue;
					if (child.x <= cullingArea.x + cullingArea.width && child.x + child.width >= cullingArea.x
						&& child.y <= cullingArea.y + cullingArea.height && child.y + child.height >= cullingArea.y) {
						child.draw(batch, parentAlpha);
					}
				}
				batch.flush();
			} else {
				float offsetX = x;
				float offsetY = y;
				x = 0;
				y = 0;
				for (int i = 0; i < children.size(); i++) {
					Actor child = children.get(i);
					if (!child.visible) continue;
					if (child.x <= cullingArea.x + cullingArea.width && child.x + child.width >= cullingArea.x
						&& child.y <= cullingArea.y + cullingArea.height && child.y + child.height >= cullingArea.y) {
						child.x += offsetX;
						child.y += offsetY;
						child.draw(batch, parentAlpha);
						child.x -= offsetX;
						child.y -= offsetY;
					}
				}
				x = offsetX;
				y = offsetY;
			}
		} else {
			if (transform) {
				for (int i = 0; i < children.size(); i++) {
					Actor child = children.get(i);
					if (!child.visible) continue;
					child.draw(batch, parentAlpha);
				}
				batch.flush();
			} else {
				float offsetX = x;
				float offsetY = y;
				x = 0;
				y = 0;
				for (int i = 0; i < children.size(); i++) {
					Actor child = children.get(i);
					if (!child.visible) continue;
					child.x += offsetX;
					child.y += offsetY;
					child.draw(batch, parentAlpha);
					child.x -= offsetX;
					child.y -= offsetY;
				}
				x = offsetX;
				y = offsetY;
			}
		}
	}

	protected void drawChild (Actor child, SpriteBatch batch, float parentAlpha) {
		if (child.visible) child.draw(batch, parentAlpha * color.a);
		if (transform) batch.flush();
	}

	protected void applyTransform (SpriteBatch batch) {
		Matrix4 newBatchTransform = updateTransform();
		batch.end();
		oldBatchTransform.set(batch.getTransformMatrix());
		batch.setTransformMatrix(newBatchTransform);
		batch.begin();
	}

	protected Matrix4 updateTransform () {
		Matrix3 temp = worldTransform;
		if (originX != 0 || originY != 0)
			localTransform.setToTranslation(originX, originY);
		else
			localTransform.idt();
		if (rotation != 0) localTransform.mul(temp.setToRotation(rotation));
		if (scaleX != 1 || scaleY != 1) localTransform.mul(temp.setToScaling(scaleX, scaleY));
		if (originX != 0 || originY != 0) localTransform.mul(temp.setToTranslation(-originX, -originY));
		localTransform.trn(x, y);

		Group parentGroup = parent;
		while (parentGroup != null) {
			if (parentGroup.transform) break;
			parentGroup = parentGroup.parent;
		}

		if (parentGroup != null) {
			worldTransform.set(parentGroup.worldTransform);
			worldTransform.mul(localTransform);
		} else {
			worldTransform.set(localTransform);
		}

		batchTransform.set(worldTransform);
		return batchTransform;
	}

	protected void resetTransform (SpriteBatch batch) {
		batch.end();
		batch.setTransformMatrix(oldBatchTransform);
		batch.begin();
	}

	public void setCullingArea (Rectangle cullingArea) {
		this.cullingArea = cullingArea;
	}

	@Override
	public boolean touchDown (float x, float y, int pointer) {
		if (!touchable || !visible) return false;

		if (debug) Gdx.app.log("Group", name + ": " + x + ", " + y);

		int len = children.size() - 1;
		for (int i = len; i >= 0; i--) {
			Actor child = children.get(i);
			if (!child.touchable || !child.visible) continue;

			toChildCoordinates(child, x, y, point);
			if (child.hit(point.x, point.y) == null) continue;

			// Allows lastTouchedChild to be the group itself, but means lastTouchedChild is cleared if the group isn't hit.
			if (child instanceof Group) ((Group)child).lastTouchedChild = null;

			if (child.touchDown(point.x, point.y, pointer)) {
				// The first actor that accepts touchDown is focused.
				if (stage != null && stage.getTouchFocus(pointer) == null) stage.setTouchFocus(child, pointer);

				if (child instanceof Group) {
					lastTouchedChild = ((Group)child).lastTouchedChild;
					if (lastTouchedChild == null) lastTouchedChild = child; // If still null, the child group itself was touched.
				} else
					lastTouchedChild = child;
				return true;
			}
		}

		lastTouchedChild = null;
		return false;
	}

	@Override
	public boolean touchMoved (float x, float y) {
		if (!touchable || !visible) return false;

		int len = children.size() - 1;
		for (int i = len; i >= 0; i--) {
			Actor child = children.get(i);
			if (!child.touchable || !child.visible) continue;

			toChildCoordinates(child, x, y, point);

			if (child.touchMoved(point.x, point.y)) return true;
		}
		return false;
	}

	public Actor hit (float x, float y) {
		int len = children.size() - 1;
		for (int i = len; i >= 0; i--) {
			Actor child = children.get(i);

			toChildCoordinates(child, x, y, point);

			Actor hit = child.hit(point.x, point.y);
			if (hit != null) {
				return hit;
			}
		}
		return x > 0 && x < width && y > 0 && y < height ? this : null;
	}

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

	/** Adds an {@link Actor} to this Group. The order Actors are added is reversed for hit testing.
	 * @param actor the Actor */
	public void addActor (Actor actor) {
		actor.remove();
		children.add(actor);
		if (actor instanceof Group) groups.add((Group)actor);
		if (actor.name != null) namesToActors.put(actor.name, actor);
		actor.parent = this;
		setStage(actor, stage);
		childrenChanged();
	}

	/** Adds an {@link Actor} at the given index in the group. The first Actor added will be at index 0 and so on. Throws an
	 * IndexOutOfBoundsException in case the index is invalid.
	 * @param index the index to add the actor at. */
	public void addActorAt (int index, Actor actor) {
		actor.remove();
		children.add(index, actor);
		if (actor instanceof Group) groups.add((Group)actor);
		if (actor.name != null) namesToActors.put(actor.name, actor);
		actor.parent = this;
		setStage(actor, stage);
		childrenChanged();
	}

	/** Adds an {@link Actor} before the given Actor.
	 * @param actorBefore the Actor to add the other actor in front of
	 * @param actor the Actor to add */
	public void addActorBefore (Actor actorBefore, Actor actor) {
		actor.remove();
		int index = children.indexOf(actorBefore);
		children.add(index, actor);
		if (actor instanceof Group) groups.add((Group)actor);
		if (actor.name != null) namesToActors.put(actor.name, actor);
		actor.parent = this;
		setStage(actor, stage);
		childrenChanged();
	}

	/** Adds an {@link Actor} after the given Actor.
	 * @param actorAfter the Actor to add the other Actor behind
	 * @param actor the Actor to add */
	public void addActorAfter (Actor actorAfter, Actor actor) {
		actor.remove();
		int index = children.indexOf(actorAfter);
		if (index == children.size())
			children.add(actor);
		else
			children.add(index + 1, actor);
		if (actor instanceof Group) groups.add((Group)actor);
		if (actor.name != null) namesToActors.put(actor.name, actor);
		actor.parent = this;
		setStage(actor, stage);
		childrenChanged();
	}

	/** Removes an {@link Actor} from this Group.
	 * @param actor */
	public void removeActor (Actor actor) {
		children.remove(actor);
		if (actor instanceof Group) groups.remove((Group)actor);
		if (actor.name != null) namesToActors.remove(actor.name);
		if (stage != null) stage.unfocus(actor);
		actor.parent = null;
		setStage(actor, null);
		childrenChanged();
	}

	/** Removes an {@link Actor} from this Group recursively by checking if the Actor is in this group or one of its child-groups.
	 * @param actor the Actor */
	public void removeActorRecursive (Actor actor) {
		if (children.remove(actor)) {
			if (actor instanceof Group) groups.remove((Group)actor);
			if (actor.name != null) namesToActors.remove(actor.name);
			if (stage != null) stage.unfocus(actor);
			actor.parent = null;
			setStage(actor, null);
			return;
		}
		for (int i = 0; i < groups.size(); i++) {
			groups.get(i).removeActorRecursive(actor);
		}
		childrenChanged();
	}

	private void setStage (Actor actor, Stage stage) {
		actor.stage = stage;
		if (actor instanceof Group) {
			List children = ((Group)actor).getActors();
			for (int i = 0; i < children.size(); i++)
				setStage(children.get(i), stage);
		}
	}

	/** Finds the {@link Actor} with the given name in this Group and its children.
	 * @param name the name of the Actor
	 * @return the Actor or null */
	public Actor findActor (String name) {
		Actor actor = namesToActors.get(name);
		if (actor == null) {
			int len = groups.size();
			for (int i = 0; i < len; i++) {
				actor = groups.get(i).findActor(name);
				if (actor != null) return actor;
			}
		}

		return actor;
	}

	/** Swap two actors' sort order by index. 0 is lowest while getActors().size() - 1 is largest.
	 * @param first first Actor index
	 * @param second second Actor index
	 * @return false if indices are out of bound. */
	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;
		Collections.swap(children, first, second);
		return true;
	}

	/** Swap two actors' sort order by reference.
	 * @param first first Actor
	 * @param second second Actor
	 * @return false if any of the Actors is not the child of this Group. */
	public boolean swapActor (Actor first, Actor second) {
		int firstIndex = children.indexOf(first);
		int secondIndex = children.indexOf(second);
		if (firstIndex == -1 || secondIndex == -1) return false;
		Collections.swap(children, firstIndex, secondIndex);
		return true;
	}

	/** @return all child {@link Actor}s as an ordered list. */
	public List getActors () {
		return immutableChildren;
	}

	/** @return all child {@link Group}s as an unordered list. */
	public List getGroups () {
		return immutableGroups;
	}

	/** Clears this Group, removing all contained {@link Actor}s. */
	public void clear () {
		for (int i = 0; i < children.size(); i++)
			setStage(children.get(i), null);		
		children.clear();
		groups.clear();
		namesToActors.clear();
		childrenChanged();
	}

	/** Sorts the children via the given {@link Comparator}.
	 * @param comparator the comparator. */
	public void sortChildren (Comparator comparator) {
		Collections.sort(children, comparator);
	}

	/** Converts coordinates for this group to those of a descendant actor.
	 * @throws IllegalArgumentException if the specified actor is not a descendant of this group. */
	public void toLocalCoordinates (Actor descendant, Vector2 point) {
		if (descendant.parent == null) throw new IllegalArgumentException("Child was not a descendant.");
		// First convert to the actor's parent coordinates.
		if (descendant.parent != this) toLocalCoordinates(descendant.parent, point);
		Group.toChildCoordinates(descendant, point.x, point.y, point);
	}

	public boolean isDescendant (Actor actor) {
		while (true) {
			if (actor == null) return false;
			if (actor == this) return true;
			actor = actor.parent;
		}
	}

	/** Transforms the coordinates given in the child's parent coordinate system to the child {@link Actor}'s coordinate system.
	 * @param child the child Actor
	 * @param x the x-coordinate in the Group's coordinate system
	 * @param y the y-coordinate in the Group's coordinate system
	 * @param out the output {@link Vector2} */
	static public void toChildCoordinates (Actor child, float x, float y, Vector2 out) {
		if (child.rotation == 0) {
			if (child.scaleX == 1 && child.scaleY == 1) {
				out.x = x - child.x;
				out.y = y - child.y;
			} else {
				if (child.originX == 0 && child.originY == 0) {
					out.x = (x - child.x) / child.scaleX;
					out.y = (y - child.y) / child.scaleY;
				} else {
					out.x = (x - child.x - child.originX) / child.scaleX + child.originX;
					out.y = (y - child.y - child.originY) / child.scaleY + child.originY;
				}
			}
		} else {
			final float cos = (float)Math.cos(child.rotation * MathUtils.degreesToRadians);
			final float sin = (float)Math.sin(child.rotation * MathUtils.degreesToRadians);

			if (child.scaleX == 1 && child.scaleY == 1) {
				if (child.originX == 0 && child.originY == 0) {
					float tox = x - child.x;
					float toy = y - child.y;

					out.x = tox * cos + toy * sin;
					out.y = tox * -sin + toy * cos;
				} else {
					final float worldOriginX = child.x + child.originX;
					final float worldOriginY = child.y + child.originY;
					float fx = -child.originX;
					float fy = -child.originY;

					float x1 = cos * fx - sin * fy;
					float y1 = sin * fx + cos * fy;
					x1 += worldOriginX;
					y1 += worldOriginY;

					float tox = x - x1;
					float toy = y - y1;

					out.x = tox * cos + toy * sin;
					out.y = tox * -sin + toy * cos;
				}
			} else {
				if (child.originX == 0 && child.originY == 0) {
					float tox = x - child.x;
					float toy = y - child.y;

					out.x = tox * cos + toy * sin;
					out.y = tox * -sin + toy * cos;

					out.x /= child.scaleX;
					out.y /= child.scaleY;
				} else {
					float srefX = child.originX * child.scaleX;
					float srefY = child.originY * child.scaleY;

					final float worldOriginX = child.x + child.originX;
					final float worldOriginY = child.y + child.originY;
					float fx = -srefX;
					float fy = -srefY;

					float x1 = cos * fx - sin * fy;
					float y1 = sin * fx + cos * fy;
					x1 += worldOriginX;
					y1 += worldOriginY;

					float tox = x - x1;
					float toy = y - y1;

					out.x = tox * cos + toy * sin;
					out.y = tox * -sin + toy * cos;

					out.x /= child.scaleX;
					out.y /= child.scaleY;
				}
			}
		}
	}

	static public void enableDebugging (String debugTextureFile) {
		debugTexture = new Texture(Gdx.files.internal(debugTextureFile), false);
		debug = true;
	}

	static public void disableDebugging () {
		if (debugTexture != null) debugTexture.dispose();
		debug = false;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy