-
Notifications
You must be signed in to change notification settings - Fork 25
Actors and macros
Note: this article covers an advanced topic: LML tag parsing internals. You might find it useful if you want to add an advanced custom actor tag or macro in Java code.
Macro marker - :
- is not there just to clearly separate macros from actors, making your life a little easier. This sign is also an important hint for the parser: tags starting with :
are supposed to be parsed in a completely different manner. But first things first - let's clarify what a "regular" tag is.
<this>
is a regular tag. Its name does not start with :
- that's all the parser needs to determine that this should be an actor tag. Such tags are processed sequentially, one after another. The parser creates actors as it goes along - due to conditionals, loops and whatnot, the final structure of the LML file is not clear and it couldn't be simply read all at once. The parser has to adapt to the changes in template structure and be prepared to insert or remove some part of the template at all times.
Actor tags are simple: once one of them is opened, an actor is created and customized with tag attributes. Any direct children are automatically appended - the tag decides what should be done with children actors. For example, Table
tag might add children to its cells, while Slider
tag might just throw an exception, complaining that it doesn't know what to do with children.
That's why most actor tags are just actor tags. They cannot significantly modify the template.
But there are some things that need to be created immediately and have not need to turn the template upside down. They aren't exactly your regular actors either - examples might include listeners or tooltips. They should be attached to their parents, but aren't regular actors immediately added to the Stage
. Following the Table
example: you obviously don't want to add a tooltip directly to the table's cell - you want to display it after the cursor is over the table for long enough.
Some more advanced actor tags that can be parsed sequentially, but still need some kind of special treatment, are called attachables. One is able to identify these tags using LmlTag#isAttachable()
in Java. They all provide their custom LmlTag#attachTo(LmlTag)
implementation which is used instead of regular actor appending method. The key is that they are parsed along with the rest of the template, but not necessarily shown or used immediately. For example, <tooltip>
can have any children and be fully customized, but rather than added to Stage
right after parsing, it is shown when a specific event occurs (in this case: cursor hovering).
So, for example, if <tooltip>
tag was inside <window>
, Tooltip
instance would be attached to Table
using attachTo
rather than simply added to its cells. In this particular case, Tooltip
would be added as an EventListener
to Table
instance.
Attachables include tooltips, listeners (click, change, input) and text field validators in gdx-lml-vis
.
As mentioned, attachables still follow the same rules as other actor tags. Consider this template:
<button>
<tooltip>
<label>This is a label.</label>
</tooltip>
</button>
As soon as <tooltip>
ends, parser immediately begins processing <label>
. Yes: the Label
instance is added to the Tooltip
, but other than that, the process of parsing children of attachables is no different than parsing regular actors. Attachables do not modify the way how parser works: they change the way they are added to their parents. And that's it.
Needless to say, there are times when you need to tell the parser what to do.
When the parser detects :
(the macro marker), the parsing process is entirely different. Instead of processing the tags sequentially, parser tries to find the closing tag of the macro - storing all data before it does - and then asks the macro to process the collected text that was between its tags. Given this example:
<:macro>
<label>Some unfortunate label</label>
</:macro>
Macro tag implementation mapped to :macro
would be asked to process \n <label>Some unfortunate label</label>\n
. Note that Label
instance would NOT be created immediately - or even at all, if the macro decides to discard or delay the actor creation. This gives the macro creator the freedom to process raw text and change the structure of parsed template. This is why conditionals, loops, iterations and other interesting features are implemented through macros: they are the most flexible mechanism around.
Funny thing: comment macro logic required "zero" lines - it just ignores all incoming data. And the parser never bothers to process macro content. Unless asked to do so.
LML templates are read character by character by a LmlTemplateReader
implementation. Not only is it prepared to read any CharSequence
implementation (String
, StringBuilder
), but also allows to appends a new sequence at any time. For example, consider this template:
<table><:loop times="2"><label test="OK"/></:loop></table>
The parser will begin processing the template, reading <, t, a, b, l, e, ...
- no surprise there.
But as soon as it detects a macro (<:loop times="2">
), it will burn the characters up to </:loop>
and let the :loop
tag handle its text.
As you'd expect, loop macro will tell the template reader to parse it's content between tags twice (times="2"
). So template reader data would look like this:
QUEUED: <table><:loop times="2"><label test="OK"/></:loop>|CURRENT_INDEX|</table>
CURRENT: |CURRENT_INDEX|<label test="OK"/><label test="OK"/>
Instead of processing <, /, t, a, b, ...
from the original template, reader will now return the characters from the new registered snippet: <, l, a, b, e, l, ...
. The parser will start processing label as if there were there originally: the parser itself does not see the difference between original or appended data, it just parses it. Once it's done with the labels snippet, the reader will start feeding it <, /, t, a, b, ...
- which is the rest of the original template at the index where the macro ended.
Once you think about it, this is a pretty powerful mechanism.
gdx-lml-tests
and gdx-lml-vis-tests
both feature custom macros examples.
The abstract class you're looking for is AbstractMacroLmlTag
:
return new LmlTagProvider() {
@Override
public LmlTag create(final LmlParser parser, final LmlTag parentTag, final StringBuilder rawTagData) {
return new AbstractMacroLmlTag(parser, parentTag, rawTagData) {
@Override
public void handleDataBetweenTags(final CharSequence rawData) {
// Changes passed text to upper case:
appendTextToParse(rawData.toString().toUpperCase());
}
};
}
};
LmlTagProvider
is the standard functional interface for providing tag instances - you can register them with LmlSyntax#addMacroTagProvider
or LmlParserBuilder#macro
methods. (LmlParserBuilder
is the object that actually handles your LmlParser
creation when you use Lml
/VisLml
utilities.)
As you can see, appendTextToParse
method will automatically inform the LmlTemplateReader
that you want to parser to handle some additional text. This gives you pretty much endless options, as you can do anything with the raw text that comes in. Make sure to go through AbstractMacroLmlTag
API, as you'll also find methods for attribute handling, so you can customize your macros.
If the mechanism is still unclear, try playing with gdx-lml-tests
and gdx-lml-vis-tests
projects.
MJ 2016