/*******************************************************************************
* 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.ui;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener.ChangeEvent;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Pools;
/**
* A button is a {@link Table} with a checked state and additional {@link ButtonStyle style} fields for pressed,
* unpressed, and checked. Each time a button is clicked, the checked state is toggled. Being a table, a button can
* contain any other actors.
* <p>
* {@link ChangeEvent} is fired when the button is clicked. Cancelling the event will restore the checked button state
* to what is was previously.
* <p>
* The preferred size of the button is determined by the background and the button contents.
*
* @author Nathan Sweet
*/
public class Button extends Table {
private ButtonStyle style;
boolean isChecked, isDisabled;
ButtonGroup buttonGroup;
private ClickListener clickListener;
public Button(Skin skin) {
super(skin);
initialize();
setStyle(skin.get(ButtonStyle.class));
setWidth(getPrefWidth());
setHeight(getPrefHeight());
}
public Button(Skin skin, String styleName) {
super(skin);
initialize();
setStyle(skin.get(styleName, ButtonStyle.class));
setWidth(getPrefWidth());
setHeight(getPrefHeight());
}
public Button(Actor child, Skin skin, String styleName) {
this(child, skin.get(styleName, ButtonStyle.class));
}
public Button(Actor child, ButtonStyle style) {
initialize();
add(child);
setStyle(style);
setWidth(getPrefWidth());
setHeight(getPrefHeight());
}
public Button(ButtonStyle style) {
initialize();
setStyle(style);
setWidth(getPrefWidth());
setHeight(getPrefHeight());
}
private void initialize() {
setTouchable(Touchable.enabled);
addListener(clickListener = new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
if (isDisabled)
return;
boolean wasChecked = isChecked;
setChecked(!isChecked);
}
});
}
public Button(Drawable up) {
this(new ButtonStyle(up, null, null));
}
public Button(Drawable up, Drawable down) {
this(new ButtonStyle(up, down, null));
}
public Button(Drawable up, Drawable down, Drawable checked) {
this(new ButtonStyle(up, down, checked));
}
public Button(Actor child, Skin skin) {
this(child, skin.get(ButtonStyle.class));
}
public void setChecked(boolean isChecked) {
if (this.isChecked == isChecked)
return;
if (buttonGroup != null && !buttonGroup.canCheck(this, isChecked))
return;
this.isChecked = isChecked;
if (!isDisabled) {
ChangeEvent changeEvent = Pools.obtain(ChangeEvent.class);
if (fire(changeEvent))
this.isChecked = !isChecked;
Pools.free(changeEvent);
}
}
/**
* Toggles the checked state. This method changes the checked state, which fires a {@link ChangeEvent}, so can be
* used to simulate a button click.
*/
public void toggle() {
setChecked(!isChecked);
}
public boolean isChecked() {
return isChecked;
}
public boolean isPressed() {
return clickListener.isPressed();
}
public boolean isOver() {
return clickListener.isOver();
}
public boolean isDisabled() {
return isDisabled;
}
/** When true, the button will not toggle {@link #isChecked()} when clicked and will not fire a {@link ChangeEvent}. */
public void setDisabled(boolean isDisabled) {
this.isDisabled = isDisabled;
}
public void setStyle(ButtonStyle style) {
if (style == null)
throw new IllegalArgumentException("style cannot be null.");
this.style = style;
Drawable background = style.up;
if (background == null) {
background = style.down;
if (background == null)
background = style.checked;
}
if (background != null) {
padBottom(background.getBottomHeight());
padTop(background.getTopHeight());
padLeft(background.getLeftWidth());
padRight(background.getRightWidth());
}
invalidateHierarchy();
}
/**
* Returns the button's style. Modifying the returned style may not have an effect until
* {@link #setStyle(ButtonStyle)} is called.
*/
public ButtonStyle getStyle() {
return style;
}
public void draw(SpriteBatch batch, float parentAlpha) {
validate();
Drawable background = null;
float offsetX = 0, offsetY = 0;
if (isPressed() && !isDisabled) {
background = style.down == null ? style.up : style.down;
offsetX = style.pressedOffsetX;
offsetY = style.pressedOffsetY;
} else {
if (isDisabled && style.disabled != null)
background = style.disabled;
else if (isChecked && style.checked != null)
background = (isOver() && style.checkedOver != null) ? style.checkedOver : style.checked;
else if (isOver() && style.over != null)
background = style.over;
else
background = style.up;
offsetX = style.unpressedOffsetX;
offsetY = style.unpressedOffsetY;
}
if (background != null) {
Color color = getColor();
batch.setColor(color.r, color.g, color.b, color.a * parentAlpha);
background.draw(batch, getX(), getY(), getWidth(), getHeight());
}
Array<Actor> children = getChildren();
for (int i = 0; i < children.size; i++)
children.get(i).translate(offsetX, offsetY);
super.draw(batch, parentAlpha);
for (int i = 0; i < children.size; i++)
children.get(i).translate(-offsetX, -offsetY);
}
protected void drawBackground(SpriteBatch batch, float parentAlpha) {
}
public float getPrefWidth() {
float width = super.getPrefWidth();
if (style.up != null)
width = Math.max(width, style.up.getMinWidth());
if (style.down != null)
width = Math.max(width, style.down.getMinWidth());
if (style.checked != null)
width = Math.max(width, style.checked.getMinWidth());
return width;
}
public float getPrefHeight() {
float height = super.getPrefHeight();
if (style.up != null)
height = Math.max(height, style.up.getMinHeight());
if (style.down != null)
height = Math.max(height, style.down.getMinHeight());
if (style.checked != null)
height = Math.max(height, style.checked.getMinHeight());
return height;
}
public float getMinWidth() {
return getPrefWidth();
}
public float getMinHeight() {
return getPrefHeight();
}
/**
* The style for a button, see {@link Button}.
*
* @author mzechner
*/
static public class ButtonStyle {
/** Optional. */
public Drawable up, down, over, checked, checkedOver, disabled;
/** Optional. */
public float pressedOffsetX, pressedOffsetY;
/** Optional. */
public float unpressedOffsetX, unpressedOffsetY;
public ButtonStyle() {
}
public ButtonStyle(Drawable up, Drawable down, Drawable checked) {
this.up = up;
this.down = down;
this.checked = checked;
}
public ButtonStyle(ButtonStyle style) {
this.up = style.up;
this.down = style.down;
this.over = style.over;
this.checked = style.checked;
this.checkedOver = style.checkedOver;
this.disabled = style.disabled;
this.pressedOffsetX = style.pressedOffsetX;
this.pressedOffsetY = style.pressedOffsetY;
this.unpressedOffsetX = style.unpressedOffsetX;
this.unpressedOffsetY = style.unpressedOffsetY;
}
}
}