package com.github.czyzby.tests.reflected; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.Interpolation; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.scenes.scene2d.Action; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.Stage; import com.badlogic.gdx.scenes.scene2d.actions.Actions; import com.badlogic.gdx.scenes.scene2d.ui.Button; import com.badlogic.gdx.scenes.scene2d.ui.Container; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.ProgressBar; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.utils.Layout; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.viewport.ScreenViewport; import com.github.czyzby.kiwi.util.common.Strings; import com.github.czyzby.kiwi.util.gdx.GdxUtilities; import com.github.czyzby.kiwi.util.gdx.collection.GdxArrays; import com.github.czyzby.kiwi.util.gdx.collection.disposable.DisposableArray; import com.github.czyzby.kiwi.util.gdx.scene2d.Actors; import com.github.czyzby.lml.annotation.LmlAction; import com.github.czyzby.lml.annotation.LmlActor; import com.github.czyzby.lml.annotation.LmlAfter; import com.github.czyzby.lml.annotation.LmlBefore; import com.github.czyzby.lml.annotation.LmlInject; import com.github.czyzby.lml.parser.LmlParser; import com.github.czyzby.lml.parser.LmlView; import com.github.czyzby.lml.parser.action.ActionContainer; import com.github.czyzby.lml.parser.impl.AbstractLmlView; import com.github.czyzby.lml.parser.impl.tag.macro.NewAttributeLmlMacroTag.AttributeParsingData; import com.github.czyzby.lml.scene2d.ui.reflected.ReflectedLmlDialog; import com.github.czyzby.lml.util.Lml; import com.github.czyzby.lml.util.LmlUtilities; import com.github.czyzby.lml.vis.ui.VisTabTable; import com.github.czyzby.tests.reflected.widgets.BlinkingLabel; import com.github.czyzby.tests.reflected.widgets.LmlSourceHighlighter; import com.github.czyzby.tests.reflected.widgets.MockHighlighter; import com.kotcrab.vis.ui.util.ToastManager; import com.kotcrab.vis.ui.util.adapter.ListAdapter; import com.kotcrab.vis.ui.util.adapter.SimpleListAdapter; import com.kotcrab.vis.ui.util.highlight.BaseHighlighter; import com.kotcrab.vis.ui.widget.*; import com.kotcrab.vis.ui.widget.toast.ToastTable; /** * Main view of the application. Since it extends {@link AbstractLmlView}, it is both {@link LmlView} (allowing its * {@link Stage} to be filled) and {@link ActionContainer} (allowing it methods to be reflected and available in LML * templates. Thanks to {@link LmlParser#createView(Class, com.badlogic.gdx.files.FileHandle)} method, parsed root * actors go directly into this view's {@link #getStage()}, and an instance of this class is registered as an action * container with "main" ID (returned by {@link #getViewId()}). * @author MJ */ public class MainView extends AbstractLmlView { // Contains template to parse: @LmlActor("templateInput") private HighlightTextArea templateInput; // Is filled with parsed actors after template processing: @LmlActor("resultTable") private Table resultTable; // Manages buttons. Will be created and filled by the parser. @LmlInject private ButtonManager buttonManager; // Used to create this view. @LmlInject-ing this field would also work. private LmlParser parser; // Used to dispose heavy widgets, like ColorPicker: private final DisposableArray<Disposable> heavyWidgets = DisposableArray.newArray(); /** Creates a new main view with default stage. */ public MainView() { // You'd generally want to construct the stage with a custom SpriteBatch, sharing it across all views. super(new Stage(new ScreenViewport())); } @Override public String getViewId() { return "main"; } /* Method annotation examples: */ @LmlBefore public void example(final LmlParser parser) { // This method will be invoked before parsing of the main.lml template. You can uncomment the log or some other // method to see how it works. // Gdx.app.log(Lml.LOGGER_TAG, "About to parse main.lml."); // Assigning parser - if you want to use @LmlInject instead to fill the parser field, try commenting this line: this.parser = parser; // Note that both LmlBefore- and LmlAfter-annotated methods can have either no arguments or a single argument: // LmlParser; parser argument will never be null - the parser used to process template will be injected. } @LmlAfter public void example() { // This method will be invoked after main.lml is parsed and MainView's fields are fully injected and processed. // Gdx.app.log(Lml.LOGGER_TAG, "Parsed main.lml with: " + parser); } /* Reflected methods, available in LML views: */ /* templates/main.lml */ /** @return action that sets the action invisible and slowly fades it in. */ public Action fadeIn() { // Used by main window just after view show. return Actions.sequence(Actions.alpha(0f), Actions.fadeIn(0.5f, Interpolation.fade)); } /** Parses template currently stored in template text area and adds the created actors to result table. */ public void parseTemplate() { destroyHeavyWidgets(); final String template = templateInput.getText(); parser.getData().addActionContainer(getViewId(), this); // Making our methods available in templates. try { final Array<Actor> actors = parser.parseTemplate(template); resultTable.clear(); for (final Actor actor : actors) { resultTable.add(actor).row(); } } catch (final Exception exception) { onParsingError(exception); } } private void onParsingError(final Exception exception) { // Printing the message without stack trace - we don't want to completely flood the console and its usually not // relevant anyway. Change to '(...), "Unable to parse LML template:", exception);' for stacks. Gdx.app.error(Lml.LOGGER_TAG, "Unable to parse LML template: " + exception); resultTable.clear(); resultTable.add("Error occurred. Sorry."); parser.fillStage(getStage(), Gdx.files.internal("templates/dialogs/error.lml")); } /** * Switches the current content of template input to the content of a chosen file. * @param actor invokes the action. Expected to have an ID that points to a template. */ @LmlAction("switch") public void switchTemplate(final Button actor) { buttonManager.switchCheckedButton(actor); // In LML template, we set each button's ID to a template name. Now we extract these: final String templateName = LmlUtilities.getActorId(actor); templateInput.setProgrammaticChangeEvents(true); templateInput.setText(Gdx.files.internal(toExamplePath(templateName)).readString()); // Forcing view to recalculate preferred size (required to update scroll pane): templateInput.invalidateHierarchy(); parseTemplate(); } // Converts template name to a example template path. private static String toExamplePath(final String templateName) { return "templates/examples/" + templateName + ".lml"; } /** @return {@link BaseHighlighter} implementation that might highlight LML source code. */ @LmlAction("codeHighlighter") public BaseHighlighter getCodeSourceHighlighter() { if(GdxUtilities.isRunningOnGwt()) { return new BaseHighlighter(); } return new LmlSourceHighlighter(); } /** @return true if current platform is not GWT. */ @LmlAction("isNotGwt") public boolean isNotRunningOnGwt() { return !GdxUtilities.isRunningOnGwt(); } /** @param button if checked, will highlight LML source code. */ @LmlAction("toggleSyntaxHighlight") public void toggleSyntaxHighlight(Button button) { templateInput.setHighlighter(button.isChecked() ? new LmlSourceHighlighter() : new MockHighlighter()); templateInput.processHighlighter(); } /** @return {@link BaseHighlighter} implementation that highlights LML source code. */ @LmlAction("lmlHighlighter") public BaseHighlighter getLmlSourceHighlighter() { return new LmlSourceHighlighter(); } /* templates/dialogs/error.lml */ @LmlAction("onErrorApprove") public void acceptError(final VisDialog dialog) { ((Label) LmlUtilities.getActorWithId(dialog, "resultMessage")).setText("Thanks!"); } @LmlAction("onErrorDecline") public boolean declineError(final VisDialog dialog) { ((Label) LmlUtilities.getActorWithId(dialog, "resultMessage")).setText("It's not like you have a choice."); // Returning true boolean cancels dialog hiding: return ReflectedLmlDialog.CANCEL_HIDING; } /* templates/examples/arrays.lml */ @LmlAction("someMethodReturningArray") public Array<Float> getSomeNumbers() { return GdxArrays.newArray(4.2f, 3f); } /* templates/examples/checkBox.lml */ /** @param button will have its text changed. */ public void switchCase(final TextButton button) { if (button.isChecked()) { button.setText(button.getText().toString().toUpperCase()); } else { button.setText(button.getText().toString().toLowerCase()); } } /* templates/examples/progressBar.lml */ @LmlAction("load") public void advanceLoadingProgress(final ProgressBar progressBar) { progressBar.setValue(progressBar.getValue() + progressBar.getStepSize() * 5f); } /* templates/examples/actions.lml */ public String someAction() { return "Extracted from method of MainView."; } @LmlAction("someNamedAction") public String getSomeText() { return "@LmlAction-annotated method result."; } /** * @param container has to be sized. * @return semi-random size depending on length of container's toString() result. */ @LmlAction({ "size", "getRandomSize" }) public float getSize(final Container<?> container) { return container.toString().length() * 30f * MathUtils.random(); } /** @param button its text will be modified if it's not too long. */ public void pressButton(final VisTextButton button) { if (button.getText().length() < 30) { button.setText(button.getText() + "!"); } else { button.setText("Isn't it enough?!"); } } /** @param bar will have a random initial value set. */ public void setInitialValue(final ProgressBar bar) { bar.setValue(MathUtils.random(bar.getMinValue(), bar.getMaxValue())); } /* templates/examples/actorMacro.lml */ /** @return a new instance of customized actor. */ public Label getSomeActor() { final Label actor = new Label("Actor created in plain Java.", parser.getData().getDefaultSkin()); actor.setColor(Color.PINK); return actor; } /* templates/examples/evaluate.lml */ @LmlAction("stringConsumingMethod") public String toUpperCase(final String value) { return value.toUpperCase(); } /* templates/examples/while.lml */ /** @return returns a random value between 0 and 1. */ @LmlAction("random") public float getRandomValue() { return MathUtils.random(); } /* templates/examples/newAttribute.lml */ /** @param data contains data necessary to parse LML attribute. */ @LmlAction("upperCaseAttribute") public void processUpperCaseAttribute(final AttributeParsingData data) { if (data.getActor() instanceof Label) { final Label label = (Label) data.getActor(); label.setText( label.getText() + data.getParser().parseString(data.getRawAttributeData(), label).toUpperCase()); } else { data.getParser().throwErrorIfStrict("My attribute supports only labels."); } } /* templates/examples/newTag.lml */ /** @return a new instance of customized widget: {@link BlinkingLabel}; */ public BlinkingLabel getBlinkingLabel() { return new BlinkingLabel(Strings.EMPTY_STRING, parser.getData().getDefaultSkin(), Actors.DEFAULT_STYLE); } /* templates/examples/vis/collapsibleWidget.lml */ @LmlAction("collapse") public void toggleCollapsedStatus(final CollapsibleWidget collapsibleWidget) { collapsibleWidget.setCollapsed(!collapsibleWidget.isCollapsed()); } @LmlAction("uncollapseAll") public void showAllCollapsedWidgets() { for (final Actor actor : resultTable.getChildren()) { if (actor instanceof CollapsibleWidget) { ((CollapsibleWidget) actor).setCollapsed(false); } else if (actor instanceof HorizontalCollapsibleWidget) { ((HorizontalCollapsibleWidget) actor).setCollapsed(false); } } } /* templates/examples/vis/horizontalCollapsibleWidget.lml */ @LmlAction("collapseHorizontal") public void toggleCollapsedStatus(final HorizontalCollapsibleWidget collapsibleWidget) { collapsibleWidget.setCollapsed(!collapsibleWidget.isCollapsed()); } /* templates/examples/vis/colorPicker.lml */ /** @param result result of a color picker dialog. Will become an actor's color. */ @LmlAction("setColor") public void setColorFromColorPicker(final Color result) { if (result != null) { // null -> cancelled LmlUtilities.getActorWithId(resultTable, "set").setColor(result); } } /** @param currentColor currently selected color in a color picker dialog. Will become an actor's color. */ @LmlAction("changeColor") public void changeColorWithColorPicker(final Color currentColor) { if (currentColor != null) { LmlUtilities.getActorWithId(resultTable, "change").setColor(currentColor); } } /** @param disposable will be eventually disposed after a new template is parsed or before application is closed. */ @LmlAction("scheduleDispose") public void addHeavyWidget(final Disposable disposable) { heavyWidgets.add(disposable); } /** @param result result of a color picker dialog. Will become an actor's color. */ @LmlAction("customColor") public void setColorFromCustomColorPicker(final Color result) { if (result != null) { LmlUtilities.getActorWithId(resultTable, "new").setColor(result); } } /* templates/examples/vis/listView.lml */ @LmlAction("listAdapter") public ListAdapter<?> getCustomListAdapter() { final String prompt = "This list was created directly in Java and passed with a custom adapter that converts text to labels."; final Array<String> values = new Array<String>(Strings.split(prompt, ' ')); return new SimpleListAdapter<String>(values); } @LmlAction("itemListener") public void handleItemClick(final String selectedItem) { // Printing selected item into the console: Gdx.app.log(Lml.LOGGER_TAG, "Selected: " + selectedItem); } /* templates/examples/vis/menu.lml */ /** @param menuItem its text will be appended to the result table. */ @LmlAction("chooseItem") public void selectMenuItem(final MenuItem menuItem) { final Label result = (Label) LmlUtilities.getActorWithId(resultTable, "result"); result.setText(menuItem.getText()); } /* templates/examples/vis/tabbedPane.lml */ /** @return simple fading in action. */ @LmlAction("showTab") public Action getTabShowingAction() { return Actions.sequence(Actions.alpha(0f), Actions.fadeIn(0.1f)); } /** @return simple fading out action. */ @LmlAction("hideTab") public Action getTabHidingAction() { return Actions.fadeOut(0.1f); } /** * @param tabTable will contain an extra label each time this method is invoked. Expects that the table contains a * label in first cell. */ @LmlAction("showSomeTab") public void doOnCustomTabShow(final VisTabTable tabTable) { // Note that this method might be called a few times before the tag is even shown due to TabbedPane // implementation. This attribute should be used for some simple additional showing actions (visual-only, if // possible), rather than modifying table's state, like in this example. final Label label = (Label) tabTable.getCells().first().getActor(); label.setText(label.getText() + "!"); } /* templates/examples/vis/toast.lml */ private ToastManager toastManager; @Override public void resize(final int width, final int height, final boolean centerCamera) { super.resize(width, height, centerCamera); if (toastManager != null) { toastManager.resize(); } } private ToastManager getToastManager() { if (toastManager == null) { toastManager = new ToastManager(getStage()); } return toastManager; } /** @param toast will be displayed on the stage using toast manager. */ @LmlAction("addToast") public void addToast(final ToastTable toast) { final ToastManager manager = getToastManager(); manager.show(toast); manager.toFront(); } /* templates/examples/vis/validatableTextField.lml */ @LmlAction("isNotBlank") public boolean isStringNotBlank(final String value) { return Strings.isNotBlank(value); } // Utility methods: @Override public void dispose() { super.dispose(); destroyHeavyWidgets(); } @LmlAction("closeDialog") public void closeDialog(Actor actor) { while (actor != null && !(actor instanceof VisWindow)) { actor = actor.getParent(); } if (actor != null) { ((VisWindow) actor).fadeOut(); } } private void destroyHeavyWidgets() { heavyWidgets.dispose(); heavyWidgets.clear(); } @Override public String toString() { // Printing all example buttons and current playground content: return resultTable + buttonManager.printButtons(); } @LmlAction("clearLss") public void clearStyleSheets() { parser.getStyleSheet().clearStyles(); } }