package com.github.czyzby.lml.parser.impl.tag.macro; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.EventListener; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.ObjectMap; import com.github.czyzby.kiwi.util.common.Strings; import com.github.czyzby.lml.parser.LmlParser; import com.github.czyzby.lml.parser.LmlParserListener; import com.github.czyzby.lml.parser.impl.tag.AbstractMacroLmlTag; import com.github.czyzby.lml.parser.impl.tag.macro.util.Equation; import com.github.czyzby.lml.parser.tag.LmlTag; import com.github.czyzby.lml.util.LmlUtilities; /** This base class for macros that create event listeners and attach them to their parent actor. When the event is * detected, content between macro tag is parsed with {@link LmlParser} and the result is added to the stage. * * @author MJ */ public abstract class AbstractListenerLmlMacroTag extends AbstractMacroLmlTag implements LmlParserListener { /** Represents the condition that has to be met before the selected template is parsed. */ public static final String IF_ATTRIBUTE = "if"; /** If this (optional) attribute is set to true, selected code snippet will be parsed once - the created actors will * be saved and added to the stage on each event occurrence. */ public static final String CACHE_ATTRIBUTE = "cache"; /** If this (optional) attribute is set to true, the managed listener will be attached to the actors with selected * IDs ({@link #IDS_ATTRIBUTE}) after every following template parsing. This allows to keep this macro's listener * and share it across multiple views. */ public static final String KEEP_ATTRIBUTE = "keep"; /** Optional attribute that might be used to attach this macro to additional actors. Represents an array of IDs of * actors that this listener should be attached to. */ public static final String IDS_ATTRIBUTE = "ids"; private Array<Actor> cachedActors; private String content; private boolean cacheActors; private String[] ids; private boolean keep = REMOVE; public AbstractListenerLmlMacroTag(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) { super(parser, parentTag, rawTagData); } @Override public void handleDataBetweenTags(final CharSequence rawData) { setContent(rawData.toString()); } @Override protected boolean supportsNamedAttributes() { return true; } @Override public void closeTag() { if (content == null) { getParser().throwErrorIfStrict("Listener macro should not be empty."); return; } final Actor actor = getActor(); final boolean hasIdsAttribute = Strings.isNotWhitespace(getAttribute(IDS_ATTRIBUTE)); if (actor == null) { if (!hasIdsAttribute) { getParser().throwErrorIfStrict( "Listener macro can be attached only to valid actors. No valid actor parent tag and no non-empty 'attachTo' attribute was found."); return; } } else { attachListener(actor); } cacheActors = hasAttribute(CACHE_ATTRIBUTE) && getParser().parseBoolean(getAttribute(CACHE_ATTRIBUTE), actor); // Adding listener that attaches listener to tags after parsing: if (hasIdsAttribute) { ids = getParser().parseArray(getAttribute(IDS_ATTRIBUTE), actor); getParser().doAfterParsing(this); } setKeepListener( hasAttribute(KEEP_ATTRIBUTE) ? getParser().parseBoolean(getAttribute(KEEP_ATTRIBUTE), actor) : REMOVE); } /** @param actor should have the proper listener attached. The listener should invoke {@link #doOnEvent(Actor)} when * the event occurs. */ protected void attachListener(final Actor actor) { actor.addListener(getEventListener()); } /** @param keep see {@link #KEEP_ATTRIBUTE}. */ public void setKeepListener(final boolean keep) { this.keep = keep; } /** @return true if actors are cached. Defaults to false. Always returns false before the tag is closed. */ protected boolean isCachingActors() { return cacheActors; } /** @param actor has the listener attached. */ protected void doOnEvent(final Actor actor) { // Checking optional condition: if (hasAttribute(IF_ATTRIBUTE)) { final boolean condition = new Equation(getParser(), actor).getBooleanResult(getAttribute(IF_ATTRIBUTE)); if (!condition) { return; } } if (cachedActors != null) { // Actors already parsed - returning cached values: addActors(actor, cachedActors); } else { // Template not parsed or actors are not being cached - parsing template: addActors(actor, parseSnippet()); } } /** @param actor has the listener attached. * @param actors should be added to the stage. * @see #determineStage(Actor) */ protected void addActors(final Actor actor, final Array<Actor> actors) { LmlUtilities.appendActorsToStage(determineStage(actor), actors); } /** @return current content between macro tags. */ protected String getContent() { return content; } /** @param content will become current content between macro tags. */ protected void setContent(final String content) { this.content = content; } /** @return an array of actors parsed from the content of the macro. Will be cached if {@link #isCachingActors()} * returns true. */ protected Array<Actor> parseSnippet() { final Array<Actor> actors = getParser().parseTemplate(content); if (isCachingActors()) { cachedActors = actors; } return actors; } /** Invoked after template parsing. Hooks up the listener to actors registered by {@link #IDS_ATTRIBUTE} attribute. * * @param parser parsed the template. * @param parsingResult parsed actors. * @return {@link LmlParserListener#REMOVE} by default. {@link #KEEP_ATTRIBUTE} value if it is set. */ @Override public boolean onEvent(final LmlParser parser, final Array<Actor> parsingResult) { final ObjectMap<String, Actor> actorsByIds = parser.getActorsMappedByIds(); for (final String id : ids) { final Actor actor = actorsByIds.get(id); if (actor != null) { attachListener(actor); } else if (!keep) { parser.throwErrorIfStrict("Unknown ID: '" + id + "'. Cannot attach listener."); } } return keep; } /** @return managed {@link EventListener} instance. */ protected abstract EventListener getEventListener(); /** @return instance of the managed {@link EventListener}. */ @Override public Object getManagedObject() { return getEventListener(); } @Override public String[] getExpectedAttributes() { return new String[] { IF_ATTRIBUTE, CACHE_ATTRIBUTE, KEEP_ATTRIBUTE, IDS_ATTRIBUTE }; } }