package com.galvarez.ttw.rendering.ui;
import java.util.function.Consumer;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
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.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.Button.ButtonStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Cell;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox.CheckBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Label.LabelStyle;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox;
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox.SelectBoxStyle;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextField;
import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldListener;
import com.badlogic.gdx.scenes.scene2d.ui.TextField.TextFieldStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.utils.Scaling;
public class FramedMenu {
private int nbColumns = 2;
private Image frame;
private ScrollPane scrollPane;
private Table table;
private final Skin skin;
/** max's represent the largest we want the menu getting */
private final float maxHeight;
private float width;
/** The parent menu is the one we will focus on if the user closes this one */
private final FramedMenu parent;
public FramedMenu(Skin skin, float width, float maxHeight) {
this(skin, width, maxHeight, null);
}
public FramedMenu(Skin skin, float width, float maxHeight, FramedMenu parent) {
this.skin = skin;
this.table = new Table();
table.defaults().expandX().fillX().left();
this.maxHeight = maxHeight;
this.width = width;
this.parent = parent;
}
public FramedMenu nbColumns(int nb) {
this.nbColumns = nb;
return this;
}
/** Use only for specific menus. */
public Skin getSkin() {
return skin;
}
/** Use only for specific menus. */
public Table getTable() {
return table;
}
public <E> void addSelectBox(String label, E selected, E[] values, Consumer<E> lis) {
LabelStyle style = skin.get(LabelStyle.class);
Label l = new Label(label, style);
table.add(l).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
SelectBox<E> sb = new SelectBox<E>(skin.get(SelectBoxStyle.class));
sb.setItems(values);
sb.setSelected(selected);
sb.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
lis.accept(sb.getSelected());
}
});
Cell<SelectBox<E>> right = table.add(sb).minHeight(sb.getMinHeight()).prefHeight(sb.getMinHeight());
if (nbColumns > 2)
right.colspan(nbColumns - 1);
table.row();
}
public void addBooleanSelectBox(String label, boolean selected, Consumer<Boolean> lis) {
LabelStyle style = skin.get(LabelStyle.class);
Label l = new Label(label, style);
table.add(l).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
SelectBox<Boolean> sb = new SelectBox<Boolean>(skin.get(SelectBoxStyle.class));
sb.setItems(Boolean.TRUE, Boolean.FALSE);
sb.setSelected(selected);
sb.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
lis.accept(sb.getSelected());
}
});
Cell<SelectBox<Boolean>> right = table.add(sb).minHeight(sb.getMinHeight()).prefHeight(sb.getMinHeight());
if (nbColumns > 2)
right.colspan(nbColumns - 1);
table.row();
}
public void addTextField(String label, Object value, TextFieldListener lis) {
Label l = new Label(label, skin.get(LabelStyle.class));
table.add(l).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
TextField t = new TextField(String.valueOf(value), skin.get(TextFieldStyle.class));
t.setTextFieldListener(lis);
t.setDisabled(false);
t.setMaxLength(6);
if (value instanceof Number)
t.setTextFieldFilter((textField, c) -> Character.isDigit(c));
table.add(t).right().padLeft(1f);
table.row();
}
/** Adds a button to the menu */
public void addButton(String label, Runnable action) {
addButton(label, null, action, true);
}
/**
* Adds a button to the menu, with a secondary label (like MP cost) aligned to
* the right
*/
public void addButton(String label, String secondaryLabel, Runnable action, boolean active) {
addButton(active ? skin.get(LabelStyle.class) : skin.get("inactive", LabelStyle.class), label, secondaryLabel,
action, active);
}
public void addButton(LabelStyle style, String label, String secondaryLabel, Runnable action, boolean active) {
Button b = new Button(skin.get(ButtonStyle.class));
if (action != null)
b.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
action.run();
}
});
b.setDisabled(!active);
b.add(new Label(label, style)).left();
if (secondaryLabel != null && !secondaryLabel.isEmpty())
b.add(new Label(secondaryLabel, style)).padRight(15f);
table.add(b).minHeight(b.getMinHeight()).prefHeight(b.getPrefHeight()).left().padLeft(1f).colspan(nbColumns);
table.row();
}
/**
* Adds a button to the menu, with an icon on the left and label on the right.
*/
public Button addButtonSprite(Drawable icon, String label, Runnable action, boolean active) {
LabelStyle style = active ? skin.get(LabelStyle.class) : skin.get("inactive", LabelStyle.class);
Button b = new Button(skin.get(ButtonStyle.class));
if (action != null)
b.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
action.run();
}
});
b.setDisabled(!active);
b.add(new Image(icon, Scaling.fit)).left();
Label l = new Label(label, style);
l.setWrap(true);
b.add(l).padLeft(8).right().expandX().fillX();
table.add(b).minHeight(b.getMinHeight()).prefHeight(b.getPrefHeight()).left().padLeft(1f).colspan(nbColumns);
table.row();
return b;
}
/** Adds a label followed by a sprite to the menu. */
public Label addLabelSprite(String label, TextureRegion region, Color color) {
LabelStyle style = skin.get(LabelStyle.class);
Label l = new Label(label, style);
table.add(l).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
Image i = new Image(region);
i.setColor(color);
i.setScaling(Scaling.fit);
Cell<Image> right = table.add(i).minHeight(region.getRegionHeight()).prefHeight(region.getRegionHeight()).right();
if (nbColumns > 2)
right.colspan(nbColumns - 1);
table.row();
return l;
}
/** Adds a label to the menu. */
public Label addLabel(String label) {
LabelStyle style = skin.get(LabelStyle.class);
Label l = new Label(label, style);
l.setWrap(true);
l.setWidth(width);
table.add(l).left().colspan(nbColumns).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
table.row();
return l;
}
/** Adds a label to the menu. */
public void addTable(Object[] ... data) {
addTable(skin.get(LabelStyle.class), data);
}
/** Adds a label to the menu. */
public void addTable(LabelStyle style, Object[] ... data) {
for (int row = 0; row < data.length; row++) {
for (int col = 0; col < data[row].length; col++)
table.add(new Label(String.valueOf(data[row][col]), style)).left().padLeft(5f).padRight(15f);
table.row();
}
}
/**
* Adds a label to the menu, using color markup from
* https://github.com/libgdx/libgdx/wiki/Color-Markup-Language.
*/
public Label addColoredLabel(String label) {
Label l = new Label(label, skin.get("colored", LabelStyle.class));
l.setWrap(true);
table.add(l).left().colspan(nbColumns).minHeight(l.getMinHeight()).prefHeight(l.getPrefHeight());
table.row();
return l;
}
/** Adds a sprite to the menu */
public void addSprite(TextureRegion region) {
Image i = new Image(region);
i.setScaling(Scaling.fit);
table.add(i).colspan(nbColumns).minHeight(region.getRegionHeight()).prefHeight(region.getRegionHeight());
table.row();
}
public void addCheckBox(String text, boolean checked, Consumer<Boolean> lis) {
CheckBoxStyle style = skin.get(CheckBoxStyle.class);
CheckBox cb = new CheckBox(text, style);
cb.setChecked(checked);
if (lis != null)
cb.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
lis.accept(Boolean.valueOf(cb.isChecked()));
}
});
table.add(cb).colspan(nbColumns).minHeight(cb.getMinHeight()).prefHeight(cb.getPrefHeight());
table.row();
}
/**
* Adds the frame and scrollpane to the specified stage at the specified
* location. Sizes the scrollpane to the (estimated) table size, up to a
* maximum given in the constructor.
* <p>
* If x or y coordinate is negative, center the menu in the corresponding
* dimension.
*
* @param canEscape if true pressing on ESC key will close the menu
*/
public void addToStage(final Stage stage, float x, float y, boolean canEscape) {
scrollPane = new ScrollPane(table, skin);
frame = new Image(skin.getPatch("frame"));
if (canEscape) {
// If the user presses "ESC", close this menu and focus on the "parent"
stage.setKeyboardFocus(scrollPane);
scrollPane.addListener(new InputListener() {
@Override
public boolean keyDown(InputEvent event, int keycode) {
if (keycode == Keys.ESCAPE) {
// If this menu is invisible, don't do anything
if (!frame.isVisible())
return false;
// If there is a parent, get rid of this
// menu and focus on it
if (parent != null) {
stage.setKeyboardFocus(parent.scrollPane);
parent.enable();
clear();
}
// Otherwise this must be the last one, so just clear it all
else {
stage.clear();
}
}
return true;
}
});
}
// Go ahead and add them to the stage
stage.addActor(scrollPane);
stage.addActor(frame);
// having a background in the table would prevent us from using the correct
// preferred size as the table is at least as big as its background
// TODO avoid this shit
table.setBackground((Drawable) null);
// If the table does not fill our maximum size, resize it to our
// estimated height, and disable scrolling both x and y
if (table.getPrefHeight() < maxHeight) {
scrollPane.setScrollingDisabled(true, true);
scrollPane.setHeight(table.getPrefHeight());
}
// Otherwise, it's bigger than our maximum size, so we need to
// enable vertical scrolling, and set the height to our max.
else {
scrollPane.setScrollingDisabled(true, false);
scrollPane.setHeight(maxHeight);
}
// For now, no matter what, the width is set from constructor
scrollPane.setWidth(width);
table.setBackground(skin.getTiledDrawable("menuTexture"));
// Move the table to the far left of the scrollPane
table.left();
// Prevent the scrollPane from scrolling (and snapping back) beyond the
// scroll limits
scrollPane.setOverscroll(false, false);
scrollPane.setFillParent(false);
// If y is negative, center the scrollPane vertically on the stage
if (y < 0)
scrollPane.setY((stage.getHeight() - scrollPane.getHeight()) / 2f);
else
scrollPane.setY(y - scrollPane.getHeight());
// If x is negative, do likewise
if (x < 0)
scrollPane.setX((stage.getWidth() - scrollPane.getWidth()) / 2f);
else
scrollPane.setX(x);
// Make sure we can't touch the frame - that would make the scrollPane
// inaccessible
frame.setTouchable(Touchable.disabled);
// Now set the Frame's position and size based on the scrollPane's stuff
frame.setX(scrollPane.getX() - 1);
frame.setY(scrollPane.getY() - 3);
frame.setWidth(scrollPane.getWidth() + 4);
frame.setHeight(scrollPane.getHeight() + 4);
// In case they became invisible earlier, make them visible now
scrollPane.setVisible(true);
frame.setVisible(true);
}
public void setWidth(float width) {
this.width = width;
}
/** Wipe all the buttons off, and remove widgets from stage. */
public void clear() {
table = new Table();
table.defaults().expandX().fillX().left();
if (scrollPane != null)
scrollPane.remove();
if (frame != null)
frame.remove();
}
public float getY() {
return scrollPane.getY();
}
public float getX() {
return scrollPane.getX();
}
public float getWidth() {
return scrollPane.getWidth();
}
public float getHeight() {
return scrollPane.getHeight();
}
/** Make it untouchable, and gray/transparent it out */
public void disable() {
scrollPane.setTouchable(Touchable.disabled);
table.setColor(0.7f, 0.7f, 0.7f, 0.7f);
}
/** Re-enable */
public void enable() {
scrollPane.setTouchable(Touchable.enabled);
table.setColor(1, 1, 1, 1);
}
/** Make invisible or visible */
public void setVisible(boolean visible) {
if (frame == null)
return;
frame.setVisible(visible);
scrollPane.setVisible(visible);
}
/** Let someone else know who your parent is - currently used in MenuBuilder */
public FramedMenu getParent() {
return parent;
}
}