-
Notifications
You must be signed in to change notification settings - Fork 26
Actions
Note: do not confuse with Action from Scene2D actors API.
Any value can also be extracted from a method result. By registering a ActorConsumer (handles single action, not based on reflection) or ActionContainer (its methods are scanned and can be invoked by their names), you can access your regular Java code in LML templates. For example, by registering this consumer:
lmlData.addActorConsumer("labelPadding", new ActorConsumer<Integer, Label>() {
@Override
public Integer consume(Label actor) {
return actor.getText().length;
}
});...you can assign padding value like this:
<table>
<label pad=$labelPadding>Label</label>
</table>As you can see, default method invocation marker is $. This will look for a method mapped to "labelPadding", invoke it with the Label as its argument, and assign the returned value to the argument. (Method result is not assigned directly in most cases; it's converted to a String and then into the required type - in this case: a float.)
Hovewer, this syntax in pre-Java 8 (lambdas) gets tiring fast. Instead, you might want to create a class implementing ActionContainer interface and have its methods invoked by reflection (don't worry; it's not nearly as heavy as you might think on modern CPUs):
public class ExampleContainer implements ActionContainer {
public int getLabelPadding(Label label) {
return label.getText().length;
}
public float getPadding() {
return 8f;
}
}
(...)
lmlData.addActionContainer("paddingHandler", new ExampleContainer());Now you can use access "getLabelPadding" (for labels only) and "getPadding" (for any widget) methods: they will be mapped to keys matching their names. If the method has one argument, it will be invoked with the actor that contains the method reference; if the method has no arguments, it will be simply invoked.
You can easily register action containers and actor consumers during parser building:
Lml.parser()
// Registering an ActorConsumer implementation:
.action("id", new MyActorConsumer())
// Registering an ActionContainer class - will be created with reflection:
.actions("containerId", MyActionContainer.class)
// TODO: other parser settings.
build();Note that there can be multiple action containers with the same method name; if you don't specify action container ID, the method will be extracted, but from an undetermined container. Following the previous example, both action references result in getPadding() invocation, but the second one always gets the correct container:
<table>
<button padding=$getPadding/>
<button padding=$paddingHandler.getPadding/>
</table>You can also assign a single method to a different name - or even multiple names - by using @LmlAction annotation. For example:
@LmlAction({ "pad", "padding", "defaultPad" })
public float getPadding() {
return 4f;
}getPadding() method will be available through "pad", "padding", "defaultPad" and "getPadding" keys (with or without their container key). It is actually advised to annotate your methods with @LmlAction, as it speeds up method look-up (important if multiple actors reference the same action) and lets you refactor/obfuscate your code without breaking your templates, which would normally occur upon method name changing.
Fields can be also used as actions and referenced by their names (and annotated with @LmlAction). Instead of invoking an actual action, field's current value will be extracted and returned as "method" result. This is not advised to use if you plan to cover GWT platform, as GWT reflection causes problems on fields look-up. (Instead of throwing exceptions or returning null, some kind of junk objects seem to be returned; hard to tell, GWT debugging is... yeah.) If you want field extraction, you have to globally turn it on by setting Lml.EXTRACT_FIELDS_AS_METHODS to true.
Action marker ($) is optional if the attribute expects an action. There are some default attributes available for all widgets that expect a method ID:
-
onClick: creates aClickListenerfor the actor; method is invoked each time actor is clicked. -
onChange: creates aChangeListenerfor the actor; method is invoked each time actor is changed. -
onCreate: invoked after actor object is created and all its attributes are parsed. Since the action can consume the actor, this allows to set custom, complex widget properties that could not have been set directly in LML for some reason. -
onClose: this is similar toonCreate, except it invokes the method after actor's tag is closed. At this point, the actor will have all its children and plain text parsed and will be fully created. UseonCloseif you need access to actor's children or you need it fully created; useonCreateif you want to set actor's properties before its children are parsed or simply if you do not care. -
onResult: this is aDialogutility; if a child ofDialoghas this attribute, the actor will be added to button table and the referenced action will be invoked when the actor is clicked. If the action returns boolean "true" (ReflectedLmlDialog#CANCEL_HIDING), dialog closing will be canceled. -
onShow: invokes found method, expecting anActionresult. UsesActor#addAction(Action)to add an initial action that will be processed just after the stage is shown and updated.
All these attributes have aliases that you will find in their docs or DefaultLmlSyntax class.
Method references fill the gaps created by the limitations of a markup language. Basically: if you want to attach a listener or do something more than LML was meant to do, use an action. If all you want is creating actors and modifying their properties, use tags and attributes. If you need to modify the structure of LML templates itself (conditionals, loops, etc.), use macros.
MJ 2016