/*******************************************************************************
* 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 static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle;
import com.badlogic.gdx.scenes.scene2d.utils.Align;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.FocusListener;
import com.badlogic.gdx.scenes.scene2d.utils.FocusListener.FocusEvent;
import com.badlogic.gdx.utils.ObjectMap;
/**
* Displays a dialog, which is a modal window containing a content table with a button table underneath it. Methods are
* provided to add a label to the content table and buttons to the button table, but any widgets can be added. When a
* button is clicked, {@link #result(Object)} is called and the dialog is removed from the stage.
*
* @author Nathan Sweet
*/
public class Dialog extends Window {
/** The time in seconds that dialogs will fade in and out. Set to zero to disable fading. */
static public float fadeDuration = 0.4f;
Table contentTable, buttonTable;
private Skin skin;
ObjectMap<Actor, Object> values = new ObjectMap();
boolean cancelHide;
Actor previousKeyboardFocus, previousScrollFocus;
public Dialog(String title, Skin skin) {
super(title, skin.get(WindowStyle.class));
this.skin = skin;
initialize();
}
public Dialog(String title, Skin skin, String windowStyleName) {
super(title, skin.get(windowStyleName, WindowStyle.class));
this.skin = skin;
initialize();
}
public Dialog(String title, WindowStyle windowStyle) {
super(title, windowStyle);
initialize();
}
private void initialize() {
setModal(true);
defaults().space(6);
add(contentTable = new Table(skin)).expand().fill();
row();
add(buttonTable = new Table(skin));
contentTable.defaults().space(6);
buttonTable.defaults().space(6);
buttonTable.addListener(new ChangeListener() {
public void changed(ChangeEvent event, Actor actor) {
while (actor.getParent() != buttonTable)
actor = actor.getParent();
result(values.get(actor));
if (!cancelHide)
hide();
cancelHide = false;
}
});
addListener(new FocusListener() {
public void keyboardFocusChanged(FocusEvent event, Actor actor, boolean focused) {
if (!focused)
focusChanged(event);
}
public void scrollFocusChanged(FocusEvent event, Actor actor, boolean focused) {
if (!focused)
focusChanged(event);
}
private void focusChanged(FocusEvent event) {
Stage stage = getStage();
if (isModal && stage != null && stage.getRoot().getChildren().peek() == Dialog.this) { // Dialog is top most actor.
Actor newFocusedActor = event.getRelatedActor();
if (newFocusedActor == null || !newFocusedActor.isDescendantOf(Dialog.this))
event.cancel();
}
}
});
}
public Table getContentTable() {
return contentTable;
}
public Table getButtonTable() {
return buttonTable;
}
/** Adds a label to the content table. The dialog must have been constructed with a skin to use this method. */
public Dialog text(String text) {
if (skin == null)
throw new IllegalStateException("This method may only be used if the dialog was constructed with a Skin.");
return text(text, skin.get(LabelStyle.class));
}
/** Adds a label to the content table. */
public Dialog text(String text, LabelStyle labelStyle) {
return text(new Label(text, labelStyle));
}
/** Adds the given Label to the content table */
public Dialog text(Label label) {
contentTable.add(label);
return this;
}
/**
* Adds a text button to the button table. Null will be passed to {@link #result(Object)} if this button is clicked.
* The dialog must have been constructed with a skin to use this method.
*/
public Dialog button(String text) {
return button(text, null);
}
/**
* Adds a text button to the button table. The dialog must have been constructed with a skin to use this method.
*
* @param object
* The object that will be passed to {@link #result(Object)} if this button is clicked. May be null.
*/
public Dialog button(String text, Object object) {
if (skin == null)
throw new IllegalStateException("This method may only be used if the dialog was constructed with a Skin.");
return button(text, object, skin.get(TextButtonStyle.class));
}
/**
* Adds a text button to the button table.
*
* @param object
* The object that will be passed to {@link #result(Object)} if this button is clicked. May be null.
*/
public Dialog button(String text, Object object, TextButtonStyle buttonStyle) {
return button(new TextButton(text, buttonStyle), object);
}
/** Adds the given button to the button table. */
public Dialog button(Button button) {
return button(button, null);
}
/**
* Adds the given button to the button table.
*
* @param object
* The object that will be passed to {@link #result(Object)} if this button is clicked. May be null.
*/
public Dialog button(Button button, Object object) {
buttonTable.add(button);
setObject(button, object);
return this;
}
/** {@link #pack() Packs} the dialog and adds it to the stage, centered. */
public Dialog show(Stage stage) {
clearActions();
previousKeyboardFocus = stage.getKeyboardFocus();
previousScrollFocus = stage.getScrollFocus();
pack();
setPosition(Math.round((stage.getWidth() - getWidth()) / 2), Math.round((stage.getHeight() - getHeight()) / 2));
stage.addActor(this);
stage.setKeyboardFocus(this);
stage.setScrollFocus(this);
if (fadeDuration > 0) {
getColor().a = 0;
addAction(Actions.fadeIn(fadeDuration, Interpolation.fade));
}
return this;
}
/**
* Hides the dialog. Called automatically when a button is clicked. The default implementation fades out the dialog
* over {@link #fadeDuration} seconds and then removes it from the stage.
*/
public void hide() {
addAction(sequence(fadeOut(fadeDuration, Interpolation.fade), Actions.removeActor()));
}
protected void setParent(Group parent) {
super.setParent(parent);
if (parent == null) {
Stage stage = getStage();
if (stage != null) {
Actor actor = stage.getKeyboardFocus();
if (actor == this || actor == null)
stage.setKeyboardFocus(previousKeyboardFocus);
actor = stage.getScrollFocus();
if (actor == this || actor == null)
stage.setScrollFocus(previousScrollFocus);
}
}
}
public void setObject(Actor actor, Object object) {
values.put(actor, object);
}
/**
* If this key is pressed, {@link #result(Object)} is called with the specified object.
*
* @see Keys
*/
public Dialog key(final int keycode, final Object object) {
addListener(new InputListener() {
public boolean keyDown(InputEvent event, int keycode2) {
if (keycode == keycode2) {
result(object);
if (!cancelHide)
hide();
cancelHide = false;
}
return false;
}
});
return this;
}
/**
* Called when a button is clicked. The dialog will be hidden after this method returns unless {@link #cancel()} is
* called.
*
* @param object
* The object specified when the button was added.
*/
protected void result(Object object) {
}
public void cancel() {
cancelHide = true;
}
}