Layout Overwriting Drawing

I have a layout defined for my watchface. Because layouts are not dynamic and I want to draw a battery icon, I call View.onUpdate(dc) to display the layout view and then call a series of dc.drawRectangle() and dc.fillRectangle() functions to draw the battery gauge.

This seems to work fine in the simulator but when I put it on my device, I only see the Layout definition. It appears as if the Layout is superceding the manual drawing.

Can you not mix dynamic drawing with layouts?

Thanks,
Keith
  • Paste some code here so we can better see where the problem lies.
  • You should be able to combine the two, as long as you first call View.onUpdate and then do your drawing.
    A cleaner solution in this case would be to take one of the following approaches:
    - Use a custom font for your battery icon (icon for empty, half full and full?), put that in a label and set the text and color of the label before the update (this is the easiest and cleanest solution if it fits your needs);
    -create a custom drawable for your battery and make that part of your layout. Do the drawing in the .draw(dc) function.

    If you go for the custom drawable, here's an example

    using Toybox.WatchUi as Ui;

    class Battery extends Ui.Drawable
    {
    var batteryLevel;

    // pass location as locX, locY params
    function initialize(options) {
    Drawable.initialize(options);
    }

    function setLevel(percentage) {
    batteryLevel = percentage;
    }

    function draw(dc) {
    //draw your battery here, you can use the drawables x and y position as an offset
    //and optionaly scale the drawing based on the drawables width and/or height
    }
    }


    You use it in your layout like this:

    <layouts>
    <layout name="...">
    <drawable id="battery" class="Battery">
    <param name="locX">...</param>
    <param name="locY">...</param>
    </drawable>
    </layout>
    </layouts>


    And in your view:

    ....
    function onUpdate(dc) {
    var batteryLevel = System.getSystemStats().battery;
    var battery = View.findDrawableById("battery");
    battery.setLevel(batteryLevel);
    View.onUpdate(dc);
    }
    ....
  • I have tried two approaches, the first was to declare the drawables in the layout.xml file as such:

    <drawable id="Battery" />
    <drawable id="Battery_0_25" />
    <drawable id="Battery_25_50" />
    <drawable id="Battery_50_75" />
    <drawable id="Battery_75_100" />


    I then defined the drawables in the resources.xml file like this:

    <drawable-list id="Battery" >
    <shape type="rectangle" x="176" y="111" width="10" height="20" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    <shape type="rectangle" x="178" y="110" width="6" height="1" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_0_25" >
    <shape type="rectangle" x="178" y="125" width="6" height="4" color="Gfx.COLOR_RED" />
    </drawable-list>

    <drawable-list id="Battery_25_50" >
    <shape type="rectangle" x="178" y="121" width="6" height="8" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_50_75" >
    <shape type="rectangle" x="178" y="117" width="6" height="12" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_75_100" >
    <shape type="rectangle" x="178" y="113" width="6" height="16" color="Gfx.COLOR_RED" />
    </drawable-list>


    I then call the main battery icon like this:
    var myBattery = View.findDrawableById("Battery");
    myBattery.draw(dc);
    View.onUpdate(dc);


    What I found is that all of these drawables are being rendered when I call View.onUpdate() and not just the Battery drawable.


    The second approach I took was to use the layout approach above for the battery icon outline only and manually draw the battery level by calling fillRectangle().

    I did this AFTER the call to View.onUpdate(dc). This worked fine in the simulator, but when loaded onto the watch, the view layout overwrites the manual drawing.

    View.onUpdate(dc);

    //Set color
    dc.setColor(Gfx.COLOR_LT_GRAY,Gfx.COLOR_LT_GRAY);
    // Draw the gauge
    dc.fillRectangle(battTopX, battY, battWidth, battHeight);

  • I then call the main battery icon like this:
    var myBattery = View.findDrawableById("Battery");
    myBattery.draw(dc);
    View.onUpdate(dc);



    You don't need to explicitly draw a drawable that is already in the layout. Those will be drawn automatically in the View.onUpdate(dc) call. You're actually drawing the battery drawable twice, once before the screen is cleared, and then it is drawn automatically after.

    What I found is that all of these drawables are being rendered when I call View.onUpdate() and not just the Battery drawable.


    Your layout has 5 drawables and it is going to render them all unless you take some action to avoid that. What you are seeing is is exactly what I'd expect given the code you've shown. You should be able to remove the last four drawables from the layout (the ones that represent the battery bars) and leave the one that is the battery frame. Then, after the call to View.onUpdate(), you need to draw the correct drawable for the battery percentage. I don't think this is the best solution.

    The second approach I took was to use the layout approach above for the battery icon outline only and manually draw the battery level by calling fillRectangle().

    I did this AFTER the call to View.onUpdate(dc). This worked fine in the simulator, but when loaded onto the watch, the view layout overwrites the manual drawing.

    This should work just fine. I've used this technique (drawing on top of the layout) in several projects without problems. Something else must be going on.

    That said, I don't think I'd draw part of the battery using layouts and the other part dynamically. I'd use the technique outlined by TeunMo above and make a custom drawable class. That drawable can dynamically draw its content based on the position and size, or it could use drawable-lists. If you do opt to use drawable-lists, I think I'd remove the Battery drawable, and put the battery frame stuff into each of the other elements.

    Travis
  • I am not following TeunMo's example as far as the layout goes. I already have a layout for my watch face. Do I create a second layout or do I add the example code to my existing layout. The second thing I don't understand is how to make the locX and locY parameters dynamic? They are set in the layout file and are static....right? The only documentation I have been able to find on layout files and using their content is static.
  • I'm going to follow through with a more complete example. Right now your resource files look something like this...

    <resource>
    <drawables>
    <drawable-list id="Battery" >
    <shape type="rectangle" x="176" y="111" width="10" height="20" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    <shape type="rectangle" x="178" y="110" width="6" height="1" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_0_25" >
    <shape type="rectangle" x="178" y="125" width="6" height="4" color="Gfx.COLOR_RED" />
    </drawable-list>

    <drawable-list id="Battery_25_50" >
    <shape type="rectangle" x="178" y="121" width="6" height="8" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_50_75" >
    <shape type="rectangle" x="178" y="117" width="6" height="12" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_75_100" >
    <shape type="rectangle" x="178" y="113" width="6" height="16" color="Gfx.COLOR_RED" />
    </drawable-list>
    </drawables>

    <layouts>
    <layout name="MyLayout">
    <drawable id="Battery" />
    <drawable id="Battery_0_25" />
    <drawable id="Battery_25_50" />
    <drawable id="Battery_50_75" />
    <drawable id="Battery_75_100" />
    </layout>
    </layouts>
    </resource>


    You replace all of that with something like TeunMo showed...

    <layouts>
    <layout name="MyLayout">
    <drawable id="Battery" class="Battery">
    <param name="locX">176</param>
    <param name="locY">111</param>
    <param name="height">20</param>
    <param name="width">50</param>
    <!-- you can add other parameters here -->
    </drawable>
    </layout>
    </layouts>


    This tells the resource compiler to create a object of type Battery and pass it the given parameters in a Lang.Dictionary.

    If you code up the Battery class...

    class Battery extends Ui.Drawable
    {
    hidden var batteryLevel;

    // pass location as locX, locY params
    function initialize(options) {
    Drawable.initialize(options);
    }

    function setLevel(percentage) {
    batteryLevel = percentage;
    }

    function draw(dc) {
    dc.setColor(Gfx.COLOR_LT_GRAY, Gfx.COLOR_LT_GRAY);
    dc.drawRectangle(locX, locY, width, height);

    var color;
    if (batteryLevel < 0.25) {
    color = Gfx.COLOR_RED;
    }
    else {
    color = Gfx.COLOR_GREEN;
    }

    dc.setColor(color, color);
    dc.fillRectangle(locX + 2, locY + 2, (width - 4) * batteryLevel, height - 4);
    }
    }


    You should see a dynamic battery object. It isn't as pretty as what you had worked out, but you can easily fix that by improving the implementation of draw().
  • I'm going to follow through with a more complete example. Right now your resource files look something like this...

    <resource>
    </drawables>
    <drawable-list id="Battery" >
    <shape type="rectangle" x="176" y="111" width="10" height="20" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    <shape type="rectangle" x="178" y="110" width="6" height="1" color="Gfx.COLOR_TRANSPARENT" border_width="1" border_color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_0_25" >
    <shape type="rectangle" x="178" y="125" width="6" height="4" color="Gfx.COLOR_RED" />
    </drawable-list>

    <drawable-list id="Battery_25_50" >
    <shape type="rectangle" x="178" y="121" width="6" height="8" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_50_75" >
    <shape type="rectangle" x="178" y="117" width="6" height="12" color="Gfx.COLOR_LT_GRAY" />
    </drawable-list>

    <drawable-list id="Battery_75_100" >
    <shape type="rectangle" x="178" y="113" width="6" height="16" color="Gfx.COLOR_RED" />
    </drawable-list>
    </drawables>

    <layouts>
    <layout name="MyLayout">
    <drawable id="Battery" />
    <drawable id="Battery_0_25" />
    <drawable id="Battery_25_50" />
    <drawable id="Battery_50_75" />
    <drawable id="Battery_75_100" />
    </layout>
    </layouts>
    </resource>


    You replace all of that with something like TeunMo showed...

    <layouts>
    <layout name="MyLayout">
    <drawable id="Battery" class="Battery">
    <param name="locX">176</param>
    <param name="locY">111</param>
    <param name="height">20</param>
    <param name="width">50</param>
    <!-- you can add other parameters here -->
    </drawable>
    </layout>
    </layouts>


    This tells the resource compiler to create a object of type Battery and pass it the given parameters in a Lang.Dictionary.

    If you code up the Battery class...

    class Battery extends Ui.Drawable
    {
    hidden var batteryLevel;

    // pass location as locX, locY params
    function initialize(options) {
    Drawable.initialize(options);
    }

    function setLevel(percentage) {
    batteryLevel = percentage;
    }

    function draw(dc) {
    dc.setColor(Gfx.COLOR_LT_GRAY, Gfx.COLOR_LT_GRAY);
    dc.drawRectangle(locX, locY, width, height);

    var color;
    if (batteryLevel < 0.25) {
    color = Gfx.COLOR_RED;
    }
    else {
    color = Gfx.COLOR_GREEN;
    }

    dc.setColor(color, color);
    dc.fillRectangle(locX + 2, locY + 2, (width - 4) * batteryLevel, height - 4);
    }
    }


    You should see a dynamic battery object. It isn't as pretty as what you had worked out, but you can easily fix that by improving the implementation of draw().


    Where do you put the battery.mc in drawables dir or in source dir?

    Thanks
  • Where do you put the battery.mc in drawables dir or in source dir?


    I'm not sure it matters to Eclipse, but the file extension .mc is for MonkeyC files (source code).