com.kotcrab.vis.ui.util.dialog.Dialogs Maven / Gradle / Ivy
Show all versions of vis-ui Show documentation
/*
* Copyright 2014-2016 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.kotcrab.vis.ui.util.dialog;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.Actor;
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.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.I18NBundle;
import com.badlogic.gdx.utils.StringBuilder;
import com.kotcrab.vis.ui.Locales;
import com.kotcrab.vis.ui.Sizes;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.i18n.BundleText;
import com.kotcrab.vis.ui.util.InputValidator;
import com.kotcrab.vis.ui.util.TableUtils;
import com.kotcrab.vis.ui.util.Validators;
import com.kotcrab.vis.ui.widget.*;
import com.kotcrab.vis.ui.widget.ButtonBar.ButtonType;
/**
* Utilities for displaying various type of dialogs. Equivalent of JOptionPane from Swing.
* @author Kotcrab
* @since 0.2.0
*/
public class Dialogs {
private static final int BUTTON_OK = 1;
private static final int BUTTON_DETAILS = 2;
/**
* Dialog with given text and single OK button.
* @param title dialog title
*/
public static VisDialog showOKDialog (Stage stage, String title, String text) {
final VisDialog dialog = new VisDialog(title);
dialog.closeOnEscape();
dialog.text(text);
dialog.button(ButtonType.OK.getText()).padBottom(3);
dialog.pack();
dialog.centerWindow();
dialog.addListener(new InputListener() {
@Override
public boolean keyDown (InputEvent event, int keycode) {
if (keycode == Keys.ENTER) {
dialog.fadeOut();
return true;
}
return false;
}
});
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with text and buttons like Yes, No, Cancel.
* @param title dialog title
* @param type specifies what types of buttons will this dialog have
* @param listener dialog buttons listener.
* @return dialog for the purpose of changing buttons text.
* @see OptionDialog
* @since 0.6.0
*/
public static OptionDialog showOptionDialog (Stage stage, String title, String text, OptionDialogType type, OptionDialogListener listener) {
OptionDialog dialog = new OptionDialog(title, text, type, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with title, text and n amount of buttons. If you need dialog with only buttons like Yes, No, Cancel then
* see {@link #showOptionDialog(Stage, String, String, OptionDialogType, OptionDialogListener)}.
*
* @param title dialog title.
* @param listener button listener for this dialog. This dialog is generic, listener type will depend on
* 'returns' param type.
* @since 0.7.0
*/
public static ConfirmDialog showConfirmDialog (Stage stage, String title, String text, String[] buttons, T[] returns, ConfirmDialogListener listener) {
ConfirmDialog dialog = new ConfirmDialog(title, text, buttons, returns, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with text and text field for user input. Cannot be canceled.
* @param title dialog title.
* @param fieldTitle displayed before input field, may be null.
* @param listener dialog buttons listener.
*/
public static InputDialog showInputDialog (Stage stage, String title, String fieldTitle, InputDialogListener listener) {
InputDialog dialog = new InputDialog(title, fieldTitle, true, null, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with text and text field for user input. Cannot be canceled.
* @param title dialog title.
* @param fieldTitle displayed before input field, may be null.
* @param validator used to validate user input. Eg. limit input to integers only. See {@link Validators} for built-in validators.
* @param listener dialog buttons listener.
*/
public static InputDialog showInputDialog (Stage stage, String title, String fieldTitle, InputValidator validator, InputDialogListener listener) {
InputDialog dialog = new InputDialog(title, fieldTitle, true, validator, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with text and text field for user input.
* @param title dialog title.
* @param cancelable if true dialog may be canceled by user.
* @param fieldTitle displayed before input field, may be null.
* @param listener dialog buttons listener.
*/
public static InputDialog showInputDialog (Stage stage, String title, String fieldTitle, boolean cancelable, InputDialogListener listener) {
InputDialog dialog = new InputDialog(title, fieldTitle, cancelable, null, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/**
* Dialog with text and text field for user input.
* @param title dialog title
* @param validator used to validate user input, can be used to easily limit input to int etc. See {@link Validators} for premade validators.
* @param cancelable if true dialog may be canceled.
* @param fieldTitle displayed before input field, may be null.
*/
public static InputDialog showInputDialog (Stage stage, String title, String fieldTitle, boolean cancelable, InputValidator validator, InputDialogListener listener) {
InputDialog dialog = new InputDialog(title, fieldTitle, cancelable, validator, listener);
stage.addActor(dialog.fadeIn());
return dialog;
}
/** Dialog with title "Error" and provided text. */
public static DetailsDialog showErrorDialog (Stage stage, String text) {
return showErrorDialog(stage, text, (String) null);
}
/** Dialog with title "Error", provided text and exception stacktrace available after pressing 'Details' button. */
public static DetailsDialog showErrorDialog (Stage stage, String text, Exception exception) {
if (exception == null)
return showErrorDialog(stage, text, (String) null);
else
return showErrorDialog(stage, text, getStackTrace(exception));
}
/** Dialog with title "Error", provided text, and provided details available after pressing 'Details' button. */
public static DetailsDialog showErrorDialog (Stage stage, String text, String details) {
DetailsDialog dialog = new DetailsDialog(text, Text.ERROR.get(), details);
stage.addActor(dialog.fadeIn());
return dialog;
}
/** Dialog with given title, provided text, and more details available after pressing 'Details' button. */
public static DetailsDialog showDetailsDialog (Stage stage, String text, String title, String details) {
return showDetailsDialog(stage, text, title, details, false);
}
/**
* Dialog with given title, provided text, and more details available after pressing 'Details' button.
* @param expandDetails if true details will be visible without need to press 'Details' button
*/
public static DetailsDialog showDetailsDialog (Stage stage, String text, String title, String details, boolean expandDetails) {
DetailsDialog dialog = new DetailsDialog(text, title, details);
dialog.setDetailsVisible(expandDetails);
stage.addActor(dialog.fadeIn());
return dialog;
}
private static VisScrollPane createScrollPane (Actor widget) {
VisScrollPane scrollPane = new VisScrollPane(widget);
scrollPane.setOverscroll(false, true);
scrollPane.setFadeScrollBars(false);
return scrollPane;
}
private static String getStackTrace (Throwable throwable) {
StringBuilder builder = new StringBuilder();
getStackTrace(throwable, builder);
return builder.toString();
}
private static void getStackTrace (Throwable throwable, StringBuilder builder) {
String msg = throwable.getMessage();
if (msg != null) {
builder.append(msg);
builder.append("\n\n");
}
for (StackTraceElement element : throwable.getStackTrace()) {
builder.append(element);
builder.append("\n");
}
if (throwable.getCause() != null) {
builder.append("\nCaused by: ");
getStackTrace(throwable.getCause(), builder);
}
}
public enum OptionDialogType {
YES_NO, YES_NO_CANCEL, YES_CANCEL
}
/**
* Dialog with input field and optional {@link InputValidator}. Can be used directly although you should use {@link Dialogs}
* showInputDialog methods.
*/
public static class InputDialog extends VisWindow {
private InputDialogListener listener;
private VisTextField field;
private VisTextButton okButton;
private VisTextButton cancelButton;
public InputDialog (String title, String fieldTitle, boolean cancelable, InputValidator validator, InputDialogListener listener) {
super(title);
this.listener = listener;
TableUtils.setSpacingDefaults(this);
setModal(true);
if (cancelable) {
addCloseButton();
closeOnEscape();
}
ButtonBar buttonBar = new ButtonBar();
buttonBar.setIgnoreSpacing(true);
buttonBar.setButton(ButtonType.CANCEL, cancelButton = new VisTextButton(ButtonType.CANCEL.getText()));
buttonBar.setButton(ButtonType.OK, okButton = new VisTextButton(ButtonType.OK.getText()));
VisTable fieldTable = new VisTable(true);
if (validator == null)
field = new VisTextField();
else
field = new VisValidatableTextField(validator);
if (fieldTitle != null) fieldTable.add(new VisLabel(fieldTitle));
fieldTable.add(field).expand().fill();
add(fieldTable).padTop(3).spaceBottom(4);
row();
add(buttonBar.createTable()).padBottom(3);
addListeners();
if (validator != null) {
addValidatableFieldListener(field);
okButton.setDisabled(!field.isInputValid());
}
pack();
centerWindow();
}
@Override
protected void close () {
super.close();
listener.canceled();
}
@Override
protected void setStage (Stage stage) {
super.setStage(stage);
if (stage != null)
field.focusField();
}
public InputDialog setText (String text) {
return setText(text, false);
}
/** @param selectText if true text will be selected (this can be useful if you want to allow user quickly erase all text). */
public InputDialog setText (String text, boolean selectText) {
field.setText(text);
field.setCursorPosition(text.length());
if (selectText) {
field.selectAll();
}
return this;
}
private InputDialog addValidatableFieldListener (final VisTextField field) {
field.addListener(new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
if (field.isInputValid()) {
okButton.setDisabled(false);
} else {
okButton.setDisabled(true);
}
}
});
return this;
}
private void addListeners () {
okButton.addListener(new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
listener.finished(field.getText());
fadeOut();
}
});
cancelButton.addListener(new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
close();
}
});
field.addListener(new InputListener() {
@Override
public boolean keyDown (InputEvent event, int keycode) {
if (keycode == Keys.ENTER && okButton.isDisabled() == false) {
listener.finished(field.getText());
fadeOut();
}
return super.keyDown(event, keycode);
}
});
}
}
/**
* Dialog with text and buttons like Yes, No, Cancel. Can be used directly although you should use {@link Dialogs}
* showOptionDialog methods.
*/
public static class OptionDialog extends VisWindow {
//NOTE: when updating this class, don't forget about Editor's DisableableOptionDialog
private final ButtonBar buttonBar;
public OptionDialog (String title, String text, OptionDialogType type, final OptionDialogListener listener) {
super(title);
setModal(true);
add(new VisLabel(text, Align.center));
row();
defaults().space(6);
defaults().padBottom(3);
buttonBar = new ButtonBar();
buttonBar.setIgnoreSpacing(true);
ChangeListener yesBtnListener = new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
listener.yes();
fadeOut();
}
};
ChangeListener noBtnListener = new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
listener.no();
fadeOut();
}
};
ChangeListener cancelBtnListener = new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
listener.cancel();
fadeOut();
}
};
switch (type) {
case YES_NO:
buttonBar.setButton(ButtonType.YES, yesBtnListener);
buttonBar.setButton(ButtonType.NO, noBtnListener);
break;
case YES_CANCEL:
buttonBar.setButton(ButtonType.YES, yesBtnListener);
buttonBar.setButton(ButtonType.CANCEL, cancelBtnListener);
break;
case YES_NO_CANCEL:
buttonBar.setButton(ButtonType.YES, yesBtnListener);
buttonBar.setButton(ButtonType.NO, noBtnListener);
buttonBar.setButton(ButtonType.CANCEL, cancelBtnListener);
break;
}
add(buttonBar.createTable());
pack();
centerWindow();
}
public OptionDialog setNoButtonText (String text) {
buttonBar.getTextButton(ButtonType.NO).setText(text);
pack();
return this;
}
public OptionDialog setYesButtonText (String text) {
buttonBar.getTextButton(ButtonType.YES).setText(text);
pack();
return this;
}
public OptionDialog setCancelButtonText (String text) {
buttonBar.getTextButton(ButtonType.CANCEL).setText(text);
pack();
return this;
}
}
/**
* Dialog with text and exception stacktrace available after pressing Details button.
* Can be used directly although you should use {@link Dialogs} showErrorDialog methods.
*/
public static class DetailsDialog extends VisDialog {
private VisTable detailsTable = new VisTable(true);
private Cell> detailsCell;
private boolean detailsVisible;
private VisTextButton copyButton;
private VisLabel detailsLabel;
public DetailsDialog (String text, String title, String details) {
super(title);
text(text);
if (details != null) {
copyButton = new VisTextButton(Text.COPY.get());
detailsLabel = new VisLabel(details);
Sizes sizes = VisUI.getSizes();
copyButton.addListener(new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
Gdx.app.getClipboard().setContents((detailsLabel.getText().toString()));
copyButton.setText(Text.COPIED.get());
}
});
detailsTable.add(new VisLabel(Text.DETAILS_COLON.get())).left().expand().padTop(6);
detailsTable.add(copyButton);
detailsTable.row();
VisTable detailsTable = new VisTable();
detailsTable.add(detailsLabel).top().expand().fillX();
this.detailsTable.add(createScrollPane(detailsTable)).colspan(2).minWidth(600 * sizes.scaleFactor).height(300 * sizes.scaleFactor);
getContentTable().row();
detailsCell = getContentTable().add(this.detailsTable);
detailsCell.setActor(null);
button(Text.DETAILS.get(), BUTTON_DETAILS);
}
button(ButtonType.OK.getText(), BUTTON_OK).padBottom(3);
pack();
centerWindow();
}
@Override
protected void result (Object object) {
int result = (Integer) object;
if (result == BUTTON_DETAILS) {
setDetailsVisible(!detailsVisible);
cancel();
}
}
public void setWrapDetails (boolean wrap) {
detailsLabel.setWrap(wrap);
}
public void setCopyDetailsButtonVisible (boolean visible) {
copyButton.setVisible(visible);
}
public boolean isCopyDetailsButtonVisible () {
return copyButton.isVisible();
}
/**
* Changes visibility of details pane. Note that Window must be added to Stage or Window won't be packed properly and
* it's size will be wrong. If Window is not added to Stage packing will be performed next frame, if it is still
* not added at that point, Window size will be incorrect.
*/
public void setDetailsVisible (boolean visible) {
if (detailsVisible == visible) return;
detailsVisible = visible;
detailsCell.setActor(detailsCell.hasActor() ? null : detailsTable);
//looks like Stage is required to properly pack window
//if it's null do packing next frame and hope that window have been already added to Stage at that point
if (getStage() == null) {
Gdx.app.postRunnable(new Runnable() {
@Override
public void run () {
pack();
centerWindow();
}
});
} else {
pack();
centerWindow();
}
}
public boolean isDetailsVisible () {
return detailsVisible;
}
}
/**
* Dialog with title, text and n amount of buttons. Can be used directly although you should use {@link Dialogs}
* showConfirmDialog methods.
* @author Javier
* @author Kotcrab
*/
public static class ConfirmDialog extends VisDialog {
private ConfirmDialogListener listener;
public ConfirmDialog (String title, String text, String[] buttons, T[] returns, ConfirmDialogListener listener) {
super(title);
if (buttons.length != returns.length) {
throw new IllegalStateException("buttons.length must be equal to returns.length");
}
this.listener = listener;
text(new VisLabel(text, Align.center));
defaults().padBottom(3);
for (int i = 0; i < buttons.length; i++) {
button(buttons[i], returns[i]);
}
padBottom(3);
pack();
centerWindow();
}
@Override
protected void result (Object object) {
listener.result((T) object);
}
}
/** {@link Dialogs} I18N properties. */
private enum Text implements BundleText {
DETAILS("details"),
DETAILS_COLON("detailsColon"),
COPY("copy"),
COPIED("copied"),
ERROR("error");
private final String name;
Text (final String name) {
this.name = name;
}
private static I18NBundle getBundle () {
return Locales.getDialogsBundle();
}
@Override
public final String getName () {
return name;
}
@Override
public final String get () {
return getBundle().get(name);
}
@Override
public final String format () {
return getBundle().format(name);
}
@Override
public final String format (final Object... arguments) {
return getBundle().format(name, arguments);
}
@Override
public final String toString () {
return get();
}
}
}