Layouts in XML questions

Hello, I have a few questions about the Connect IQ XML layouts.

Is there some support for a "stack panel" in layouts? (= objects are positioned one after each other) Because it seems that we always need to specify the Y position for every object... That's not very useful, if we have more items, some labels, buttons etc.

<layout id="DetailLayout">
<drawable id="grayBackground"></drawable>
<label id="title1" x="8" y="8" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
<label id="title2" x="8" y="16" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
<label id="title3" x="8"y="24" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
</layout>


Looking at the samples, it seems that we can specify a piece of code as the X/Y parameter. Is this a supported scenario? Because it seems to work, but it shows warnings when the project is compiled. What are the limitations? Can we call any code / property from the View here?

<label id="title" x="8" y="dc.getHeight()/3+40" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />


A similar question. What does this exactly mean? x="center" and y="bottom" Are these properties bound to some private properties of the View object? It works, but maybe it's only a coincidence :)

<button x="center" y="bottom" width="dc.getWidth()" height="35" background="Gfx.COLOR_GREEN" behavior="onRetry">
<state id="stateDefault" color="0x78B62E"></state>
<state id="stateHighlighted" color="0xADF449"></state>
</button>

  • Is there some support for a "stack panel" in layouts? (= objects are positioned one after each other)

    It sounds like you're looking for something like the wxWidgets wxBoxSizer class. There is nothing like this available in ConnectIQ. If you wanted to implement something like this, it seems you will run into two issues; the layout xml schema doesn't allow for nesting elements, and the input processing and element highlighting used by Selectable is not likely to work.

    If you wanted to do something outside of the layout itself, that just moves objects around, that could be made to work, but it would not be ideal.

    it seems that we can specify a piece of code as the X/Y parameter. Is this a supported scenario?

    Yes. The warning you get about not being able to validate the expression can be ignored. The resource compiler allows, but does not know how to handle, expressions like these. The compiler, which runs on the code generated by the resource compiler, will validate it. Maybe the resource compiler should just let it go and let the compiler choke on them if they're wrong.

    Can we call any code / property from the View here?

    No. The expression is evaluated in the generated layout function. For the sample layout above, the function would be Rez.Layouts.DetailLayout(dc). This means the function only has access to symbols that you'd have access to in that function.

    What does this exactly mean? x="center" and y="bottom"

    These are pneumonics for positioning a drawable relative to the dc it is being drawn in. As an example, x="center" is just a shortcut for writing (dc.getWidth() + self.width) / 2. These are alaises for the WatchUi.LAYOUT_HALIGN_{LEFT,CENTER,RIGHT} and WatchUi.LAYOUT_VALIGN_{TOP,CENTER,BOTTOM} constants you may not have noticed previously.

  • Thank you Travis!

    And we cannot use the self.width in the expression? It would also solve the issue. If we have three labels under each other, we could set the y=8, y=8+self.height, y=8+self.height*2

    I want to avoid the absolute positioning, because every device has a different font size. But maybe I can just hardcode it for every device family, it will be easier :)
  • And we cannot use the self.width in the expression?

    No, you can't. As I said above, the expressions are evaluated inside the layout function, so there is no self object to refer to.

    As an example, here is a resources.xml based on what you pasted above:

    <resources>
    <drawables>
    <drawable-list id="grayBackground" background="Gfx.COLOR_LT_GRAY" />
    </drawables>

    <layouts>
    <layout id="DetailLayout">
    <drawable id="grayBackground"></drawable>
    <label id="title1" x="8" y="8" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
    <label id="title2" x="8" y="16" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
    <label id="title3" x="8" y="24" font="Gfx.FONT_SYSTEM_SMALL" color="Gfx.COLOR_WHITE" />
    </layout>
    </layouts>
    </resources>


    If you build with -g to see the assembler output, which contains this information (enhanced a bit for readability):

    module Rez
    {
    module Drawables
    {

    class grayBackground extends WatchUi.Drawable
    {
    function initialize() {
    Drawable.initialize({});
    }

    function draw(dc) {
    dc.setColor(Gfx.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT);
    dc.fillRectangle((0+0),(0+0),(240-0),(240-0));
    dc.setColor(Graphics.COLOR_TRANSPARENT, Gfx.COLOR_LT_GRAY);
    }
    }

    } // Drawables

    module Layouts {

    function DetailLayout(dc) {
    var rez_cmp_local_custom_drawable_grayBackground = new $.Rez.Drawables.grayBackground();
    if(rez_cmp_local_custom_drawable_grayBackground.identifier==null) {
    rez_cmp_local_custom_drawable_grayBackground.identifier="grayBackground";
    }

    rez_cmp_local_custom_drawable_grayBackground.identifier = "grayBackground"; // this seems like a bug... will investigate

    var rez_cmp_local_text_title1 = new WatchUi.Text({
    :identifier=>"title1",
    :color=>Gfx.COLOR_WHITE,
    :locX=>8,
    :locY=>8,
    :font=>Gfx.FONT_SYSTEM_SMALL
    });

    var rez_cmp_local_text_title2 = new WatchUi.Text({
    :identifier=>"title2",
    :color=>Gfx.COLOR_WHITE,
    :locX=>8,
    :locY=>16,
    :font=>Gfx.FONT_SYSTEM_SMALL
    });

    var rez_cmp_local_text_title3 = new WatchUi.Text({
    :identifier=>"title3",
    :color=>Gfx.COLOR_WHITE,
    :locX=>8,
    :locY=>24,
    :font=>Gfx.FONT_SYSTEM_SMALL
    });

    return [
    rez_cmp_local_custom_drawable_grayBackground,
    rez_cmp_local_text_title1,
    rez_cmp_local_text_title2,
    rez_cmp_local_text_title3
    ];
    }

    } // Layouts
    } // Rez


    As you can see, the positioning expressions (which are literals in this case) are evaluated inside the DetailLayout() function. There is no self. You may notice that the names of the objects are spelled out in the output. I would *not* refer to these variable names in your expressions. It may work now, but this is something that is subject to change (technically this is all subject to change, so...).

    I believe the expectation is that you'll hard-code the positions of elements in the layout for each device or device family as needed. This is a lot of work to do it manually. As I said earlier, you could create a class to do positioning after the fact, something like this...

    class DrawablePacker
    {
    hidden var mDirection;
    hidden var mInitialOffset;
    hidden var mSpacing;

    static const PACK_VERTICAL = 1;
    static const PACK_HORIZONTAL = 2;

    function initialize(direction, initialOffset, spacing) {
    mDirection = direction;
    mInitialOffset = initialOffset;
    mSpacing = spacing;
    }

    function pack(drawables) {
    if (mDirection == PACK_VERTICAL) {
    var offset = initialOffset;
    for (var i = 0; i < drawables.size(); ++i) {
    var drawable = drawables;
    drawable.locY = offset;
    offset += drawable.height + mSpacing;
    }
    }
    else {
    var offset = initialOffset;
    for (var i = 0; i < drawables.size(); ++i) {
    var drawable = drawables;
    drawable.locX = offset;
    offset += drawable.width + mSpacing;
    }
    }
    }
    }
    [/code]

    Theoretically (I haven't tested this), you should be able to use this inside View.onLayout() to pack things together.

    function onLayout(dc) {
    var layout = Rez.Layouts.DetailLayout(dc);

    var textDrawables = [];
    textDrawables.add(findDrawableById("title1"));
    textDrawables.add(findDrawableById("title2"));
    textDrawables.add(findDrawableById("title3"));

    var verticalPacker = new DrawablePacker(DrawablePacker.PACK_VERTICAL, 10, 0);
    verticalPacker.pack(textDrawables);

    setLayout(layout);
    }


    I'm not sure if this makes things better or not, but it seems like it would work.