Usability of the layout system

The layout system is there to abstract differences between devices, but it isn't sufficient to make it easy, or I'm missing something.

Assume for a second that I have a drawable that displays a battery status. Assume also that I want to be able to control the positioning of that battery status indicator via the layout file. As it currently stands, I can create a layout with text/bitmaps/circles/boxes/points/lines. If I want to place my battery indicator, I have to create a label that represents the position of the indicator and use that...

function onLayout(dc) {
var layout = Ui.loadResource(Rez.Layouts.TestLayout);

// the 4th layout element is a label that acts as a position indicator for
// the battery status indicator. replace that label with the battery status.

layout[3] = new BatteryIndicator({
:locX => layout[3].locX,
:locY => layout[3].locY - 17, // hack because fonts are specified by bottom edge instead of top like everything else
:width => 28,
:height => 20
});

setLayout(layout);
}


This is a frustrating hack to have to make because some of the position/size code for my indicator is still stuck in the application code, and some of it is stuck in the layout specification. It would be nice if I could somehow register my drawable classes with the factory that builds up the layouts from the xml data. That way I could specify the layout of my application screens fully inside the layout file...

<layout id="TestLayout" background="Gfx.COLOR_WHITE">
<label x="8" y="8" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
<label x="8" y="44" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
<label x="8" y="80" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
<batteryIndicator x="2" y="2" width="28" height="20" bgcolor="Gfx.COLOR_WHITE" />
<moveBar x="0" y="140" width="205" height="8" bgcolor="Gfx.COLOR_WHITE" />
</layout>


In order for something like this to work, I'd have to register a function to create my object given a name and a dictionary of attributes. Something like this would be ideal.

class BatteryIndicator
{
hidden var locX;
hidden var locY;
hidden var width;
hidden var height;
hidden var color;

function initialize(params) {
locX = params.get(:locX);
locY = params.get(:locY);
width = params.get(:width);
height = params.get(:height);
color = params.get(:color);
}

function draw(dc) {
// snipped...
}

// snipped...
}

class TestApp extends App.AppBase {

function onStart(state) {
Ui.registerType("batteryIndicator", BatteryIndicator);
Ui.registerType("moveBar", MoveBar);
}

function onStop(state) {
Ui.deregisterType("moveBar");
Ui.deregisterType("batteryIndicator");
}
}


I'm not exactly sure how the machinery would work, but it would be very helpful to have this. It seems a large part of the system is in place already, so maybe this is the direction you are already headed.
  • I just realized that the last part of my request is not necessary. Since the layouts resources are compiled by the resource compiler, the type names need to be known at compile time, so there is no need to register a class for creation at runtime. Regardless, I still think this is a useful enhancement.
  • Former Member
    Former Member over 10 years ago
    Can't you just add batteryIndicator and moveBar as references to drawable lists?

    layouts/TestLayout.xml
    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <label x="8" y="8" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
    <label x="8" y="44" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
    <label x="8" y="80" font="Gfx.FONT_LARGE" text="" justification="Gfx.TEXT_JUSTIFY_LEFT" color="Gfx.COLOR_WHITE" />
    <drawable id="batteryIndicator" />
    <drawable id="moveBar" />
    </layout>


    drawable/batteryIndicator.xml
    <drawable-list id="batteryIndicator x="2" y="2">
    <shape type="rectangle" width="28" height="20" color="Gfx.COLOR_WHITE" />
    </drawable-list>


    drawable/moveBar.xml
    <drawable-list id="moveBar" x="0" y="140">
    <shape type="rectangle" width="205" height="8" color="Gfx.COLOR_WHITE" />
    </drawable-list>
  • Yes, that would get something rendered at the position specified in the xml, but it doesn't seem to do anything to address the problem. Maybe it does and I'm just missing something. I can certainly define a move bar and battery indicator as drawable-list instances. As long as they are static (they don't change their appearance) it would work out fine, but for a battery status indicator icon or a move bar, it is no good.

    I have a MoveBar class that renders as a horizontal progress bar indicating steps vs step goal for a day. For a 920xt, the resource file would look like this...

    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <!-- snip -->
    <moveBar x="0" y="140" width="205" height="8" bgcolor="Gfx.COLOR_DK_GRAY" />
    </layout>


    Now I'm trying to add support for a square watch that has twice the resolution. So I simply create a new resource file for that watch...

    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <!-- snip -->
    <moveBar x="0" y="280" width="410" height="16" bgcolor="Gfx.COLOR_DK_GRAY" />
    </layout>


    Lets say that I want to support a watch with a round face, but instead of having a horizontal bar I want to render it as an arc along the left edge of the screen. I create a class similar to my existing move bar class (assume I call it MoveArc). For a fenix3...

    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <!-- snip -->

    <!-- xml attributes describe properties that would be passed to the constructor of the named type. -->
    <!-- in this case we describe an arc that is 9 pixels wide and drawn from the 7 o'clock position to -->
    <!-- the 11 o'clock position. it is drawn in dark gray, but the class implementation draws the progress -->
    <!-- as a blue arc over the top of the gray one as progress toward the steps goal increases -->
    <moveArc x="109" y="109" radius="100" width="9" start="7" stop="11" bgcolor="Gfx.COLOR_DK_GRAY" />
    </layout>


    As it is now, I can describe the layout of my application, but only for a very limited number of types (label, bitmap, and the drawables) each of which supports only a limited number of operations. My proposal would make it so that any class that had a draw(dc) method and could be constructed from a Dictionary of properties could be defined in a device or language-specific layout file. Short of using a resource to identify each device and having conditional code for each I see no other way to do this. Of course I could create a new application for each, but that would be silly. If there is a better way, then I'd like to see it.

    Travis
  • Former Member
    Former Member over 10 years ago
    Travis, thanks for your input. At some point in the future we will need to redesign bits of the drawables section of the resource compiler (admittedly the drawable-list came into existence before the concept of a Drawable object did). We have a task in our back log to redo this part of the resource compiler. I will link to this thread in that issue as I think the design you highlighted here needs to be taken into consideration when that happens.

    You could use an approach similar to what SHARKBAIT suggested. You could define drawables for movebar20, movebar40, movebar60, etc and then replace them in the layout based on the current status in onUpdate using layout.getDrawableById(). This would allow you to define the movebar drawables to be device specific but the code to set the proper movebar would be device independent. I know this option isn't great; obviously for a battery indicator this gets a bit more tedious.

    I don't have time to write code to fully test this but here's a general idea of what I'm thinking.
    var moveBar20, moveBar40, moveBar60, moveBar80, moveBar100;

    function onLayout(dc) {
    // Load move bar drawable resources.
    moveBar20 = Ui.loadResource(Rez.Drawables.moveBar20);
    }

    function onUpdate(dc) {
    var moveBar = View.findDrawableById("moveBar");
    // Decide which move bar needs to be displayed
    view = moveBarXX;
    }
  • Travis, thanks for your input. At some point in the future we will need to redesign bits of the drawables section of the resource compiler (admittedly the drawable-list came into existence before the concept of a Drawable object did). We have a task in our back log to redo this part of the resource compiler. I will link to this thread in that issue as I think the design you highlighted here needs to be taken into consideration when that happens.

    You could use an approach similar to what SHARKBAIT suggested. You could define drawables for movebar20, movebar40, movebar60, etc and then replace them in the layout based on the current status in onUpdate using layout.getDrawableById(). This would allow you to define the movebar drawables to be device specific but the code to set the proper movebar would be device independent. I know this option isn't great; obviously for a battery indicator this gets a bit more tedious.

    I don't have time to write code to fully test this but here's a general idea of what I'm thinking.
    var moveBar20, moveBar40, moveBar60, moveBar80, moveBar100;

    function onLayout(dc) {
    // Load move bar drawable resources.
    moveBar20 = Ui.loadResource(Rez.Drawables.moveBar20);
    }

    function onUpdate(dc) {
    var moveBar = View.findDrawableById("moveBar");
    // Decide which move bar needs to be displayed
    view = moveBarXX;
    }


    I am stuck at this. Also struggle to have my move bars as drawables that can be differently drawn for different devices
  • I am stuck at this. Also struggle to have my move bars as drawables that can be differently drawn for different devices


    For the system proposed (by Ken) to work, you need to create device-specific layout resources. It can easily be done for a move bar, but doing a steps bar on something like a 920xt would require 205 different drawables to get the most accurate representation of the percent of steps completed. i.e., it wouldn't work.
  • For the system proposed (by Ken) to work, you need to create device-specific layout resources. It can easily be done for a move bar, but doing a steps bar on something like a 920xt would require 205 different drawables to get the most accurate representation of the percent of steps completed. i.e., it wouldn't work.


    I came to that conclusion after spending hours. Problem also is that the resources and drawables etc are not documented in detail, or there is a lack of all the possibilities which make it hard to understand.

    At moment everything is hardcoded.
    I have focussed on the 920xt as this is the watch I have, but unfortunately it is gonna take a lot of work to make it compatible for the rest like fenix3, vivoactive and epix :(

    The more I do the more I start to dislike the API because of its limitations. We need something fresh.
  • For the system proposed (by Ken) to work, you need to create device-specific layout resources. It can easily be done for a move bar, but doing a steps bar on something like a 920xt would require 205 different drawables to get the most accurate representation of the percent of steps completed. i.e., it wouldn't work.



    Hi, could you please assist:

    Is it possible for me to have different images for a battery (for each state of the battery), but the position of the bitmap is defined in a device specific layout.
    Then somehow in code I can get a handle on this drawable, and change the filename to the one that will show the correct battery state at that moment?
  • Sure. I've already sorta covered in my original post. You create a layout with a label at the position you want the bitmap to appear.

    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <!-- snip -->
    <label id="Battery" x="2" y="2" />
    <!-- snip -->
    </layout>


    If you want, you can define one drawable for each of the bitmaps you want to render. Then every time the battery status changes you can load the drawable resource and swap it into your layout.

    hidden var batteryBitmap;

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

    batteryBitmap = new Ui.Bitmap({});

    // replace the label with a bitmap
    for (var i = 0; i < layout.size(); ++i) {
    if ("Battery".equals(layout.identifier)) {
    batteryBitmap.setLocation(layout.locX, layout.locY);
    layout= batteryBitmap;
    }
    }

    updateBatteryBitmap();
    }

    hidden var previousBatteryLevel;

    hidden function updateBatteryBitmap()
    {
    var batteryLevel = Sys.getSystemStats().battery / 25;

    if (previousBatteryLevel == null ||
    previousBatteryLevel != batteryLevel) {

    previousBatteryLevel = batteryLevel;

    var rezId;
    if (batteryLevel == 0) {
    rezId = Rez.Bitmaps.Battery0to25;
    }
    else if (batteryLevel == 1) {
    rezId = Rez.Bitmaps.Battery25to50;
    }
    else if (batteryLevel == 2) {
    rezId = Rez.Bitmaps.Battery50to75;
    }
    else {
    rezId = Rez.Bitmaps.Battery75to100;
    }

    batteryBitmap.setBitmap(rezId);
    }
    }

    function onUpdate(dc) {
    updateBatteryBitmap();

    View.onUpdate(dc);
    }
    [/code]
  • Sure. I've already sorta covered in my original post. You create a layout with a label at the position you want the bitmap to appear.

    <layout id="TestLayout" background="Gfx.COLOR_WHITE">
    <!-- snip -->
    <label id="Battery" x="2" y="2" />
    <!-- snip -->
    </layout>


    If you want, you can define one drawable for each of the bitmaps you want to render. Then every time the battery status changes you can load the drawable resource and swap it into your layout.

    hidden var batteryBitmap;

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

    batteryBitmap = new Ui.Bitmap({});

    // replace the label with a bitmap
    for (var i = 0; i < layout.size(); ++i) {
    if ("Battery".equals(layout.identifier)) {
    batteryBitmap.setLocation(layout.locX, layout.locY);
    layout= batteryBitmap;
    }
    }

    updateBatteryBitmap();
    }

    hidden var previousBatteryLevel;

    hidden function updateBatteryBitmap()
    {
    var batteryLevel = Sys.getSystemStats().battery / 25;

    if (previousBatteryLevel == null ||
    previousBatteryLevel != batteryLevel) {

    previousBatteryLevel = batteryLevel;

    var rezId;
    if (batteryLevel == 0) {
    rezId = Rez.Bitmaps.Battery0to25;
    }
    else if (batteryLevel == 1) {
    rezId = Rez.Bitmaps.Battery25to50;
    }
    else if (batteryLevel == 2) {
    rezId = Rez.Bitmaps.Battery50to75;
    }
    else {
    rezId = Rez.Bitmaps.Battery75to100;
    }

    batteryBitmap.setBitmap(rezId);
    }
    }

    function onUpdate(dc) {
    updateBatteryBitmap();

    View.onUpdate(dc);
    }
    [/code]

    Thank you so much!!