package net.alcuria.umbracraft.editor.widget; import net.alcuria.umbracraft.editor.Drawables; import net.alcuria.umbracraft.listeners.Listener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input.Keys; import com.badlogic.gdx.math.MathUtils; 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.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; import com.badlogic.gdx.utils.Align; import com.badlogic.gdx.utils.Array; import com.kotcrab.vis.ui.widget.VisLabel; import com.kotcrab.vis.ui.widget.VisTextField; import com.kotcrab.vis.ui.widget.VisTextField.TextFieldListener; /** A {@link VisTextField} with a selectable list of suggestions that appear * underneath. Suggestions are passed into the constructor. To add the widget to * the scene simply call {@link SuggestionWidget#getActor()}. * @author Andrew Keturi */ public class SuggestionWidget { private static final int MAX_SUGGESTIONS = 15; private final Array<String> allSuggestions; private final Array<String> curSuggestions; private int highlightIndex = -1; private final Array<Label> highlightLabels = new Array<Label>(); private final Array<Table> highlightTables = new Array<Table>(); private Listener selectListener; private Table suggestionTable; private VisTextField textField; private boolean useCustomListener = false; private final int width; /** Creates a widget with a default width of 150 and no custom key typed * listener. * @param suggestions an {@link Array} of suggestions */ public SuggestionWidget(Array<String> suggestions) { this(suggestions, 150); } /** Creates a widget with a custom width but no custom key typed listener * @param suggestions an {@link Array} of suggestions * @param width the width of the widget in px */ public SuggestionWidget(Array<String> suggestions, int width) { this(suggestions, width, false); } /** Creates a widget with all custom parameters * @param suggestions an {@link Array} of suggestions * @param width the width of the widget in px * @param useCustomListener whether or not you intend to use a custom * listener on key typed. <b>IMPORTANT</b> if you set this to * <code>true</code>, be sure to actually set the listener to invoke * on key typed otherwise you won't get suggestions. * {@link SuggestionWidget#setGenericPopulate(Listener)} does what * you want. */ public SuggestionWidget(Array<String> suggestions, int width, boolean useCustomListener) { allSuggestions = suggestions; curSuggestions = new Array<String>(); this.width = width; this.useCustomListener = useCustomListener; } /** Adds a listener that is invoked when the user selects a suggestion * @param listener the {@link Listener} */ public void addSelectListener(Listener listener) { selectListener = listener; } /** @return the widget, neatly wrapped into a table. */ public Table getActor() { textField = new VisTextField(); if (!useCustomListener) { setGenericPopulate(null); } return new Table() { { add(textField).width(width).height(25).expand().fill().row(); add(suggestionTable = new Table() { @Override public void act(float delta) { if (Gdx.input.isKeyJustPressed(Keys.DOWN) && curSuggestions.size > 0) { if (highlightIndex < curSuggestions.size - 1) { highlightIndex++; } } if (Gdx.input.isKeyJustPressed(Keys.UP) && curSuggestions.size > 0) { if (highlightIndex > 0) { highlightIndex--; } } if (Gdx.input.isKeyJustPressed(Keys.ENTER)) { if (highlightIndex >= 0 && highlightIndex < highlightLabels.size) { onSelection(highlightLabels.get(highlightIndex)); } } for (int i = 0; i < highlightLabels.size; i++) { if (i == highlightIndex) { highlightLabels.get(i).setColor(0, 0, 0, 1); highlightTables.get(i).setBackground(Drawables.get("yellow")); } else { highlightLabels.get(i).setColor(1, 1, 1, 1); highlightTables.get(i).setBackground(Drawables.get("blue")); } } }; }).expand().fill(); } }; } /** @return the {@link VisTextField} */ public VisTextField getTextField() { return textField; } /** Called when a selection is chosen * @param label the {@link Label} */ private void onSelection(final Label label) { textField.setText(label.getText().toString()); if (selectListener != null) { selectListener.invoke(); } curSuggestions.clear(); suggestionTable.clear(); } /** this MUST be called elsewhere to update suggestions, like when a key is * pressed. If you use {@link SuggestionWidget#setGenericPopulate(Listener)} * this handles that for you. Check your constructor. */ public void populateSuggestions() { curSuggestions.clear(); if (textField.getText().length() > 0) { for (String s : allSuggestions) { if (s.toLowerCase().contains(textField.getText().toLowerCase())) { curSuggestions.add(s); if (curSuggestions.size >= MAX_SUGGESTIONS) { break; } } } } updateSuggestions(); } /** Gives the {@link SuggestionWidget} a generic populate method. Does not * save fields or anything. * @param listener a {@link Listener} to invoke. May be <code>null</code>. */ public void setGenericPopulate(final Listener listener) { textField.setTextFieldListener(new TextFieldListener() { @Override public void keyTyped(VisTextField textField, char c) { populateSuggestions(); if (listener != null) { listener.invoke(); } } }); } /** Called when the field input changes to rebuild the suggestions list */ private void updateSuggestions() { suggestionTable.clear(); highlightLabels.clear(); highlightTables.clear(); for (int i = 0; i < curSuggestions.size; i++) { final VisLabel label = new VisLabel(curSuggestions.get(i)); label.setAlignment(Align.left); final int idx = i; suggestionTable.add(new Table() { { highlightLabels.add(label); highlightTables.add(this); WidgetUtils.divider(this, "black"); add(label).expand().fill().padLeft(5); setTouchable(Touchable.enabled); setBackground(Drawables.get("blue")); addListener(new ClickListener() { @Override public void clicked(InputEvent event, float x, float y) { onSelection(label); } @Override public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) { highlightIndex = idx; } }); } }).expandX().fill().height(30).row(); } highlightIndex = MathUtils.clamp(highlightIndex, 0, highlightLabels.size - 1); suggestionTable.add().expand().fill(); } }