/*******************************************************************************
* 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.Gdx;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
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.Disableable;
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.<br>
* <br>
* The button's padding is set to the background drawable's padding when the background changes, overwriting any padding set
* manually. Padding can still be set on the button's table cells.
* <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 implements Disableable {
private ButtonStyle style;
boolean isChecked, isDisabled;
ButtonGroup buttonGroup;
private ClickListener clickListener;
private boolean programmaticChangeEvents = true;
public Button (Skin skin) {
super(skin);
initialize();
setStyle(skin.get(ButtonStyle.class));
setSize(getPrefWidth(), getPrefHeight());
}
public Button (Skin skin, String styleName) {
super(skin);
initialize();
setStyle(skin.get(styleName, ButtonStyle.class));
setSize(getPrefWidth(), getPrefHeight());
}
public Button (Actor child, Skin skin, String styleName) {
this(child, skin.get(styleName, ButtonStyle.class));
setSkin(skin);
}
public Button (Actor child, ButtonStyle style) {
initialize();
add(child);
setStyle(style);
setSize(getPrefWidth(), getPrefHeight());
}
public Button (ButtonStyle style) {
initialize();
setStyle(style);
setSize(getPrefWidth(), getPrefHeight());
}
/** Creates a button without setting the style or size. At least a style must be set before using this button. */
public Button () {
initialize();
}
private void initialize () {
setTouchable(Touchable.enabled);
addListener(clickListener = new ClickListener() {
public void clicked (InputEvent event, float x, float y) {
if (isDisabled()) return;
setChecked(!isChecked, true);
}
});
}
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) {
setChecked(isChecked, programmaticChangeEvents);
}
void setChecked (boolean isChecked, boolean fireEvent) {
if (this.isChecked == isChecked) return;
if (buttonGroup != null && !buttonGroup.canCheck(this, isChecked)) return;
this.isChecked = isChecked;
if (fireEvent) {
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} (if programmatic change
* events are enabled), so can be used to simulate a button click. */
public void toggle () {
setChecked(!isChecked);
}
public boolean isChecked () {
return isChecked;
}
public boolean isPressed () {
return clickListener.isVisualPressed();
}
public boolean isOver () {
return clickListener.isOver();
}
public ClickListener getClickListener () {
return clickListener;
}
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;
}
/** If false, {@link #setChecked(boolean)} and {@link #toggle()} will not fire {@link ChangeEvent}, event will be fired only
* when user clicked the button */
public void setProgrammaticChangeEvents (boolean programmaticChangeEvents) {
this.programmaticChangeEvents = programmaticChangeEvents;
}
public void setStyle (ButtonStyle style) {
if (style == null) throw new IllegalArgumentException("style cannot be null.");
this.style = style;
Drawable background = null;
if (isPressed() && !isDisabled()) {
background = style.down == null ? style.up : style.down;
} 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;
}
setBackground(background);
}
/** 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;
}
/** @return May be null. */
public ButtonGroup getButtonGroup () {
return buttonGroup;
}
public void draw (Batch batch, float parentAlpha) {
validate();
boolean isDisabled = isDisabled();
boolean isPressed = isPressed();
boolean isChecked = isChecked();
boolean isOver = isOver();
Drawable background = null;
if (isDisabled && style.disabled != null)
background = style.disabled;
else if (isPressed && style.down != null)
background = style.down;
else if (isChecked && style.checked != null)
background = (style.checkedOver != null && isOver) ? style.checkedOver : style.checked;
else if (isOver && style.over != null)
background = style.over;
else if (style.up != null) //
background = style.up;
setBackground(background);
float offsetX = 0, offsetY = 0;
if (isPressed && !isDisabled) {
offsetX = style.pressedOffsetX;
offsetY = style.pressedOffsetY;
} else if (isChecked && !isDisabled) {
offsetX = style.checkedOffsetX;
offsetY = style.checkedOffsetY;
} else {
offsetX = style.unpressedOffsetX;
offsetY = style.unpressedOffsetY;
}
Array<Actor> children = getChildren();
for (int i = 0; i < children.size; i++)
children.get(i).moveBy(offsetX, offsetY);
super.draw(batch, parentAlpha);
for (int i = 0; i < children.size; i++)
children.get(i).moveBy(-offsetX, -offsetY);
Stage stage = getStage();
if (stage != null && stage.getActionsRequestRendering() && isPressed != clickListener.isPressed())
Gdx.graphics.requestRendering();
}
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, unpressedOffsetX, unpressedOffsetY, checkedOffsetX, checkedOffsetY;
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;
this.checkedOffsetX = style.checkedOffsetX;
this.checkedOffsetY = style.checkedOffsetY;
}
}
}