package com.badlogic.gdx.scenes.scene2d.ui;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Value.Fixed;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
import com.badlogic.gdx.utils.Align;
/** A group with a single child that sizes and positions the child using constraints. This provides layout similar to a
* {@link Table} with a single cell but is more lightweight.
* @author Nathan Sweet */
public class Container<T extends Actor> extends WidgetGroup {
private T actor;
private Value minWidth = Value.minWidth, minHeight = Value.minHeight;
private Value prefWidth = Value.prefWidth, prefHeight = Value.prefHeight;
private Value maxWidth = Value.zero, maxHeight = Value.zero;
private Value padTop = Value.zero, padLeft = Value.zero, padBottom = Value.zero, padRight = Value.zero;
private float fillX, fillY;
private int align;
private Drawable background;
private boolean clip;
private boolean round = true;
/** Creates a container with no actor. */
public Container () {
setTouchable(Touchable.childrenOnly);
setTransform(false);
}
public Container (T actor) {
this();
setActor(actor);
}
public void draw (Batch batch, float parentAlpha) {
validate();
if (isTransform()) {
applyTransform(batch, computeTransform());
drawBackground(batch, parentAlpha, 0, 0);
if (clip) {
batch.flush();
float padLeft = this.padLeft.get(this), padBottom = this.padBottom.get(this);
if (clipBegin(padLeft, padBottom, getWidth() - padLeft - padRight.get(this),
getHeight() - padBottom - padTop.get(this))) {
drawChildren(batch, parentAlpha);
batch.flush();
clipEnd();
}
} else
drawChildren(batch, parentAlpha);
resetTransform(batch);
} else {
drawBackground(batch, parentAlpha, getX(), getY());
super.draw(batch, parentAlpha);
}
}
/** Called to draw the background, before clipping is applied (if enabled). Default implementation draws the background
* drawable. */
protected void drawBackground (Batch batch, float parentAlpha, float x, float y) {
if (background == null) return;
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
background.draw(batch, x, y, getWidth(), getHeight());
}
/** Sets the background drawable and adjusts the container's padding to match the background.
* @see #setBackground(Drawable, boolean) */
public void setBackground (Drawable background) {
setBackground(background, true);
}
/** Sets the background drawable and, if adjustPadding is true, sets the container's padding to
* {@link Drawable#getBottomHeight()} , {@link Drawable#getTopHeight()}, {@link Drawable#getLeftWidth()}, and
* {@link Drawable#getRightWidth()}.
* @param background If null, the background will be cleared and padding removed. */
public void setBackground (Drawable background, boolean adjustPadding) {
if (this.background == background) return;
this.background = background;
if (adjustPadding) {
if (background == null)
pad(Value.zero);
else
pad(background.getTopHeight(), background.getLeftWidth(), background.getBottomHeight(), background.getRightWidth());
invalidate();
}
}
/** @see #setBackground(Drawable) */
public Container<T> background (Drawable background) {
setBackground(background);
return this;
}
public Drawable getBackground () {
return background;
}
public void layout () {
if (actor == null) return;
float padLeft = this.padLeft.get(this), padBottom = this.padBottom.get(this);
float containerWidth = getWidth() - padLeft - padRight.get(this);
float containerHeight = getHeight() - padBottom - padTop.get(this);
float minWidth = this.minWidth.get(actor), minHeight = this.minHeight.get(actor);
float prefWidth = this.prefWidth.get(actor), prefHeight = this.prefHeight.get(actor);
float maxWidth = this.maxWidth.get(actor), maxHeight = this.maxHeight.get(actor);
float width;
if (fillX > 0)
width = containerWidth * fillX;
else
width = Math.min(prefWidth, containerWidth);
if (width < minWidth) width = minWidth;
if (maxWidth > 0 && width > maxWidth) width = maxWidth;
float height;
if (fillY > 0)
height = containerHeight * fillY;
else
height = Math.min(prefHeight, containerHeight);
if (height < minHeight) height = minHeight;
if (maxHeight > 0 && height > maxHeight) height = maxHeight;
float x = padLeft;
if ((align & Align.right) != 0)
x += containerWidth - width;
else if ((align & Align.left) == 0) // center
x += (containerWidth - width) / 2;
float y = padBottom;
if ((align & Align.top) != 0)
y += containerHeight - height;
else if ((align & Align.bottom) == 0) // center
y += (containerHeight - height) / 2;
if (round) {
x = Math.round(x);
y = Math.round(y);
width = Math.round(width);
height = Math.round(height);
}
actor.setBounds(x, y, width, height);
if (actor instanceof Layout) ((Layout)actor).validate();
}
/** @param actor May be null. */
public void setActor (T actor) {
if (actor == this) throw new IllegalArgumentException("actor cannot be the Container.");
if (actor == this.actor) return;
if (this.actor != null) super.removeActor(this.actor);
this.actor = actor;
if (actor != null) super.addActor(actor);
}
/** @return May be null. */
public T getActor () {
return actor;
}
/** @deprecated Container may have only a single child.
* @see #setActor(Actor) */
public void addActor (Actor actor) {
throw new UnsupportedOperationException("Use Container#setActor.");
}
/** @deprecated Container may have only a single child.
* @see #setActor(Actor) */
public void addActorAt (int index, Actor actor) {
throw new UnsupportedOperationException("Use Container#setActor.");
}
/** @deprecated Container may have only a single child.
* @see #setActor(Actor) */
public void addActorBefore (Actor actorBefore, Actor actor) {
throw new UnsupportedOperationException("Use Container#setActor.");
}
/** @deprecated Container may have only a single child.
* @see #setActor(Actor) */
public void addActorAfter (Actor actorAfter, Actor actor) {
throw new UnsupportedOperationException("Use Container#setActor.");
}
public boolean removeActor (Actor actor) {
if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
if (actor != this.actor) return false;
setActor(null);
return true;
}
public boolean removeActor (Actor actor, boolean unfocus) {
if (actor == null) throw new IllegalArgumentException("actor cannot be null.");
if (actor != this.actor) return false;
this.actor = null;
return super.removeActor(actor, unfocus);
}
/** Sets the minWidth, prefWidth, maxWidth, minHeight, prefHeight, and maxHeight to the specified value. */
public Container<T> size (Value size) {
if (size == null) throw new IllegalArgumentException("size cannot be null.");
minWidth = size;
minHeight = size;
prefWidth = size;
prefHeight = size;
maxWidth = size;
maxHeight = size;
return this;
}
/** Sets the minWidth, prefWidth, maxWidth, minHeight, prefHeight, and maxHeight to the specified values. */
public Container<T> size (Value width, Value height) {
if (width == null) throw new IllegalArgumentException("width cannot be null.");
if (height == null) throw new IllegalArgumentException("height cannot be null.");
minWidth = width;
minHeight = height;
prefWidth = width;
prefHeight = height;
maxWidth = width;
maxHeight = height;
return this;
}
/** Sets the minWidth, prefWidth, maxWidth, minHeight, prefHeight, and maxHeight to the specified value. */
public Container<T> size (float size) {
size(new Fixed(size));
return this;
}
/** Sets the minWidth, prefWidth, maxWidth, minHeight, prefHeight, and maxHeight to the specified values. */
public Container<T> size (float width, float height) {
size(new Fixed(width), new Fixed(height));
return this;
}
/** Sets the minWidth, prefWidth, and maxWidth to the specified value. */
public Container<T> width (Value width) {
if (width == null) throw new IllegalArgumentException("width cannot be null.");
minWidth = width;
prefWidth = width;
maxWidth = width;
return this;
}
/** Sets the minWidth, prefWidth, and maxWidth to the specified value. */
public Container<T> width (float width) {
width(new Fixed(width));
return this;
}
/** Sets the minHeight, prefHeight, and maxHeight to the specified value. */
public Container<T> height (Value height) {
if (height == null) throw new IllegalArgumentException("height cannot be null.");
minHeight = height;
prefHeight = height;
maxHeight = height;
return this;
}
/** Sets the minHeight, prefHeight, and maxHeight to the specified value. */
public Container<T> height (float height) {
height(new Fixed(height));
return this;
}
/** Sets the minWidth and minHeight to the specified value. */
public Container<T> minSize (Value size) {
if (size == null) throw new IllegalArgumentException("size cannot be null.");
minWidth = size;
minHeight = size;
return this;
}
/** Sets the minWidth and minHeight to the specified values. */
public Container<T> minSize (Value width, Value height) {
if (width == null) throw new IllegalArgumentException("width cannot be null.");
if (height == null) throw new IllegalArgumentException("height cannot be null.");
minWidth = width;
minHeight = height;
return this;
}
public Container<T> minWidth (Value minWidth) {
if (minWidth == null) throw new IllegalArgumentException("minWidth cannot be null.");
this.minWidth = minWidth;
return this;
}
public Container<T> minHeight (Value minHeight) {
if (minHeight == null) throw new IllegalArgumentException("minHeight cannot be null.");
this.minHeight = minHeight;
return this;
}
/** Sets the minWidth and minHeight to the specified value. */
public Container<T> minSize (float size) {
minSize(new Fixed(size));
return this;
}
/** Sets the minWidth and minHeight to the specified values. */
public Container<T> minSize (float width, float height) {
minSize(new Fixed(width), new Fixed(height));
return this;
}
public Container<T> minWidth (float minWidth) {
this.minWidth = new Fixed(minWidth);
return this;
}
public Container<T> minHeight (float minHeight) {
this.minHeight = new Fixed(minHeight);
return this;
}
/** Sets the prefWidth and prefHeight to the specified value. */
public Container<T> prefSize (Value size) {
if (size == null) throw new IllegalArgumentException("size cannot be null.");
prefWidth = size;
prefHeight = size;
return this;
}
/** Sets the prefWidth and prefHeight to the specified values. */
public Container<T> prefSize (Value width, Value height) {
if (width == null) throw new IllegalArgumentException("width cannot be null.");
if (height == null) throw new IllegalArgumentException("height cannot be null.");
prefWidth = width;
prefHeight = height;
return this;
}
public Container<T> prefWidth (Value prefWidth) {
if (prefWidth == null) throw new IllegalArgumentException("prefWidth cannot be null.");
this.prefWidth = prefWidth;
return this;
}
public Container<T> prefHeight (Value prefHeight) {
if (prefHeight == null) throw new IllegalArgumentException("prefHeight cannot be null.");
this.prefHeight = prefHeight;
return this;
}
/** Sets the prefWidth and prefHeight to the specified value. */
public Container<T> prefSize (float width, float height) {
prefSize(new Fixed(width), new Fixed(height));
return this;
}
/** Sets the prefWidth and prefHeight to the specified values. */
public Container<T> prefSize (float size) {
prefSize(new Fixed(size));
return this;
}
public Container<T> prefWidth (float prefWidth) {
this.prefWidth = new Fixed(prefWidth);
return this;
}
public Container<T> prefHeight (float prefHeight) {
this.prefHeight = new Fixed(prefHeight);
return this;
}
/** Sets the maxWidth and maxHeight to the specified value. */
public Container<T> maxSize (Value size) {
if (size == null) throw new IllegalArgumentException("size cannot be null.");
maxWidth = size;
maxHeight = size;
return this;
}
/** Sets the maxWidth and maxHeight to the specified values. */
public Container<T> maxSize (Value width, Value height) {
if (width == null) throw new IllegalArgumentException("width cannot be null.");
if (height == null) throw new IllegalArgumentException("height cannot be null.");
maxWidth = width;
maxHeight = height;
return this;
}
public Container<T> maxWidth (Value maxWidth) {
if (maxWidth == null) throw new IllegalArgumentException("maxWidth cannot be null.");
this.maxWidth = maxWidth;
return this;
}
public Container<T> maxHeight (Value maxHeight) {
if (maxHeight == null) throw new IllegalArgumentException("maxHeight cannot be null.");
this.maxHeight = maxHeight;
return this;
}
/** Sets the maxWidth and maxHeight to the specified value. */
public Container<T> maxSize (float size) {
maxSize(new Fixed(size));
return this;
}
/** Sets the maxWidth and maxHeight to the specified values. */
public Container<T> maxSize (float width, float height) {
maxSize(new Fixed(width), new Fixed(height));
return this;
}
public Container<T> maxWidth (float maxWidth) {
this.maxWidth = new Fixed(maxWidth);
return this;
}
public Container<T> maxHeight (float maxHeight) {
this.maxHeight = new Fixed(maxHeight);
return this;
}
/** Sets the padTop, padLeft, padBottom, and padRight to the specified value. */
public Container<T> pad (Value pad) {
if (pad == null) throw new IllegalArgumentException("pad cannot be null.");
padTop = pad;
padLeft = pad;
padBottom = pad;
padRight = pad;
return this;
}
public Container<T> pad (Value top, Value left, Value bottom, Value right) {
if (top == null) throw new IllegalArgumentException("top cannot be null.");
if (left == null) throw new IllegalArgumentException("left cannot be null.");
if (bottom == null) throw new IllegalArgumentException("bottom cannot be null.");
if (right == null) throw new IllegalArgumentException("right cannot be null.");
padTop = top;
padLeft = left;
padBottom = bottom;
padRight = right;
return this;
}
public Container<T> padTop (Value padTop) {
if (padTop == null) throw new IllegalArgumentException("padTop cannot be null.");
this.padTop = padTop;
return this;
}
public Container<T> padLeft (Value padLeft) {
if (padLeft == null) throw new IllegalArgumentException("padLeft cannot be null.");
this.padLeft = padLeft;
return this;
}
public Container<T> padBottom (Value padBottom) {
if (padBottom == null) throw new IllegalArgumentException("padBottom cannot be null.");
this.padBottom = padBottom;
return this;
}
public Container<T> padRight (Value padRight) {
if (padRight == null) throw new IllegalArgumentException("padRight cannot be null.");
this.padRight = padRight;
return this;
}
/** Sets the padTop, padLeft, padBottom, and padRight to the specified value. */
public Container<T> pad (float pad) {
Value value = new Fixed(pad);
padTop = value;
padLeft = value;
padBottom = value;
padRight = value;
return this;
}
public Container<T> pad (float top, float left, float bottom, float right) {
padTop = new Fixed(top);
padLeft = new Fixed(left);
padBottom = new Fixed(bottom);
padRight = new Fixed(right);
return this;
}
public Container<T> padTop (float padTop) {
this.padTop = new Fixed(padTop);
return this;
}
public Container<T> padLeft (float padLeft) {
this.padLeft = new Fixed(padLeft);
return this;
}
public Container<T> padBottom (float padBottom) {
this.padBottom = new Fixed(padBottom);
return this;
}
public Container<T> padRight (float padRight) {
this.padRight = new Fixed(padRight);
return this;
}
/** Sets fillX and fillY to 1. */
public Container<T> fill () {
fillX = 1f;
fillY = 1f;
return this;
}
/** Sets fillX to 1. */
public Container<T> fillX () {
fillX = 1f;
return this;
}
/** Sets fillY to 1. */
public Container<T> fillY () {
fillY = 1f;
return this;
}
public Container<T> fill (float x, float y) {
fillX = x;
fillY = y;
return this;
}
/** Sets fillX and fillY to 1 if true, 0 if false. */
public Container<T> fill (boolean x, boolean y) {
fillX = x ? 1f : 0;
fillY = y ? 1f : 0;
return this;
}
/** Sets fillX and fillY to 1 if true, 0 if false. */
public Container<T> fill (boolean fill) {
fillX = fill ? 1f : 0;
fillY = fill ? 1f : 0;
return this;
}
/** Sets the alignment of the actor within the container. Set to {@link Align#center}, {@link Align#top}, {@link Align#bottom},
* {@link Align#left}, {@link Align#right}, or any combination of those. */
public Container<T> align (int align) {
this.align = align;
return this;
}
/** Sets the alignment of the actor within the container to {@link Align#center}. This clears any other alignment. */
public Container<T> center () {
align = Align.center;
return this;
}
/** Sets {@link Align#top} and clears {@link Align#bottom} for the alignment of the actor within the container. */
public Container<T> top () {
align |= Align.top;
align &= ~Align.bottom;
return this;
}
/** Sets {@link Align#left} and clears {@link Align#right} for the alignment of the actor within the container. */
public Container<T> left () {
align |= Align.left;
align &= ~Align.right;
return this;
}
/** Sets {@link Align#bottom} and clears {@link Align#top} for the alignment of the actor within the container. */
public Container<T> bottom () {
align |= Align.bottom;
align &= ~Align.top;
return this;
}
/** Sets {@link Align#right} and clears {@link Align#left} for the alignment of the actor within the container. */
public Container<T> right () {
align |= Align.right;
align &= ~Align.left;
return this;
}
public float getMinWidth () {
return minWidth.get(actor) + padLeft.get(this) + padRight.get(this);
}
public Value getMinHeightValue () {
return minHeight;
}
public float getMinHeight () {
return minHeight.get(actor) + padTop.get(this) + padBottom.get(this);
}
public Value getPrefWidthValue () {
return prefWidth;
}
public float getPrefWidth () {
float v = prefWidth.get(actor);
if (background != null) v = Math.max(v, background.getMinWidth());
return Math.max(getMinWidth(), v + padLeft.get(this) + padRight.get(this));
}
public Value getPrefHeightValue () {
return prefHeight;
}
public float getPrefHeight () {
float v = prefHeight.get(actor);
if (background != null) v = Math.max(v, background.getMinHeight());
return Math.max(getMinHeight(), v + padTop.get(this) + padBottom.get(this));
}
public Value getMaxWidthValue () {
return maxWidth;
}
public float getMaxWidth () {
float v = maxWidth.get(actor);
if (v > 0) v += padLeft.get(this) + padRight.get(this);
return v;
}
public Value getMaxHeightValue () {
return maxHeight;
}
public float getMaxHeight () {
float v = maxHeight.get(actor);
if (v > 0) v += padTop.get(this) + padBottom.get(this);
return v;
}
/** @return May be null if this value is not set. */
public Value getPadTopValue () {
return padTop;
}
public float getPadTop () {
return padTop.get(this);
}
/** @return May be null if this value is not set. */
public Value getPadLeftValue () {
return padLeft;
}
public float getPadLeft () {
return padLeft.get(this);
}
/** @return May be null if this value is not set. */
public Value getPadBottomValue () {
return padBottom;
}
public float getPadBottom () {
return padBottom.get(this);
}
/** @return May be null if this value is not set. */
public Value getPadRightValue () {
return padRight;
}
public float getPadRight () {
return padRight.get(this);
}
/** Returns {@link #getPadLeft()} plus {@link #getPadRight()}. */
public float getPadX () {
return padLeft.get(this) + padRight.get(this);
}
/** Returns {@link #getPadTop()} plus {@link #getPadBottom()}. */
public float getPadY () {
return padTop.get(this) + padBottom.get(this);
}
public float getFillX () {
return fillX;
}
public float getFillY () {
return fillY;
}
public int getAlign () {
return align;
}
/** If true (the default), positions and sizes are rounded to integers. */
public void setRound (boolean round) {
this.round = round;
}
/** Causes the contents to be clipped if they exceed the container bounds. Enabling clipping will set
* {@link #setTransform(boolean)} to true. */
public void setClip (boolean enabled) {
clip = enabled;
setTransform(enabled);
invalidate();
}
public boolean getClip () {
return clip;
}
public Actor hit (float x, float y, boolean touchable) {
if (clip) {
if (touchable && getTouchable() == Touchable.disabled) return null;
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) return null;
}
return super.hit(x, y, touchable);
}
public void drawDebug (ShapeRenderer shapes) {
validate();
if (isTransform()) {
applyTransform(shapes, computeTransform());
if (clip) {
shapes.flush();
float padLeft = this.padLeft.get(this), padBottom = this.padBottom.get(this);
boolean draw = background == null ? clipBegin(0, 0, getWidth(), getHeight())
: clipBegin(padLeft, padBottom, getWidth() - padLeft - padRight.get(this),
getHeight() - padBottom - padTop.get(this));
if (draw) {
drawDebugChildren(shapes);
clipEnd();
}
} else
drawDebugChildren(shapes);
resetTransform(shapes);
} else
super.drawDebug(shapes);
}
}