czyzby/gdx-lml

Access object field in a loop

Opened this issue · 4 comments

Namek commented

Current state

I have a collection of objects, this is working:

@LmlAction("blocks")
public Array<Block> getBlocks() {
    return level.blocks;
}
<:foreach element="$blocks">
    <textbutton color="red">{element}</textbutton>
</:foreach>
public class Block {
    public boolean isWritten = false;

    @Override
    public String toString() {
        return isWritten ? "x" : " ";
    }
}

image

Goal

I'd like to colorize "x" blocks to red based on isWritten field.

Problem

Accessing the isWritten field is the unknown to me. How do I do that?

I thought about 2 ways:

  1. pass the element (or index) to a method to evaluate it
  2. access the field directly from the template which doesn't work for me:
<:foreach element="$blocks">
    <textbutton color="red">{element.isWritten}</textbutton>
</:foreach>

Try using the condition syntax. It should work similarly to this:

<textbutton color="{? '{element}' = 'x' ? red : default}">{element}</textbutton>

You'd have to check if conditions support executing methods, but I'm positive that they just might.

Also, loops support going through multiple collections at once. While it isn't an ideal solution, you can also do something like this:

// Java 8-ish pseudo-code:
@LmlAction("blocksWritten")
public Array<Boolean> getBlocksWrittenStatus() {
    return level.blocks.map(Block::isWritten);
}
<:foreach element="$blocks" isWritten="$blocksWritten">
    <textbutton color="{? {isWritten} ? red : white}">{element}</textbutton>
</:foreach>
Namek commented

Thank you for your kind response.

1. Workaround but not a solution

This 'x' comparison would be a workaround, I know that. But, there are going to be more booleans than just isWritten. So, neither is a solution for me.

2. Obfuscation?

I've seen between threads that obfuscation matters so Java things should be named with LmlAction annotation to be accessible from a template. However, actor macro doesn't seem to fit this rule:

  1. https://github.com/czyzby/gdx-lml/blob/master/examples/gdx-lml-tests/assets/templates/examples/actorMacro.lml
  2. https://github.com/czyzby/gdx-lml/blob/master/examples/gdx-lml-tests/core/src/main/java/com/github/czyzby/tests/reflected/MainView.java#L232

Thus, I think either the macro should be "fixed" (please don't) or field access from a template (stated in this thread) should be supported :)

3. Redraw

However, the worst thing is I realized all of this probably won't automatically update since there is no change detection. What would be the way to properly redraw some elements (taking fresh data from Java part), then?

I'm sorry for asking so many things but it's all related and I'd like to keep the same context.

This 'x' comparison would be a workaround, I know that. But, there are going to be more booleans than just isWritten. So, neither is a solution for me.

I'm sorry to hear that. If field access does not work, I'm afraid you're stuck with the workarounds or doing part of the job in Java, as LML is in maintenance mode.

I've seen between threads that obfuscation matters so Java things should be named with LmlAction annotation to be accessible from a template. However, actor macro doesn't seem to fit this rule:

@LmlAction is encouraged, but not necessary. If no annotated method is found, the parser looks for methods by name from any registered action containers.

Field access is somewhat supported, as I recall, in a sense that you can access a field similarly to how you would invoke a method: with $ operator. I don't think nested fields are supported though.

What would be the way to properly redraw some elements (taking fresh data from Java part), then?

You should implement your listeners in Java. All properties that are available in LML, are simple Java methods that you can call in your views. Look into @LmlActor and getting references to the widgets from LML templates.

I'm sorry for asking so many things but it's all related and I'd like to keep the same context.

It's OK, I'm happy to help.