datafiels with watches and layouts

Former Member
Former Member

trying to add support for fenix6 devices, but i'm getting ready to just kill support altogether for watches as i'm tired of having to do a lot of hacks to get my datafield to display correctly in all the possible positions now.  i keep going back and forth between using layouts and just using dc directly.  layouts seem a lot simpler and more straightforward until you start running out of memory and then have to break into more and more sub resources.  if i go back to using direct dc calls, then i end up with a huge block of if then or switch statements to move my values around on the screen based on all the what now feels like hundreds of permutations of screen width, height and obscurity.  arggggh.  i'd have to look at my breakdown of watch users, but just killing those off seems like my (hobby) life of this would be so much less frustrating.

/end of rant

i thought i saw something about "easier layouts" being in the newest SDK but i can't find anything.  any details on that anyone can point me to?  or what have other datafield devs done to handle all the watches out now?

  • If I recall, the new thing with layouts is you can now use a percent vs fixed values.  check out the programmer's guide on layouts.  There's also the TextArea.

    The reason I'm not sure on the layouts, is I'm a fan of direct dc calls, as for me, it's easier.  The standard thing I do it set up as much as I can in onLayout(), so things link the x,y,font are all set and onUpdate just uses them.  Some of my earlier stuff, I've gutted to use this method and avoid a whole bunch of spaghetti code.

    I've ported a bunch of my apps to the va4/f6 devices (there are some where I've held back on the venu), but I've had a number where there was either no change to the code or on the order of a few lines.  The pain has been testing all the new devices in the sim to just check that things fit and look right. (5 minutes of code changes, and an hour+ of testing).  One trick is to base everything on the actual display center, vs assuming 240x240 and using 120x120 for example, and this is were the percentage in layouts could come in handy.

  • if i go back to using direct dc calls, then i end up with a huge block of if then or switch statements to move my values around on the screen based on all the what now feels like hundreds of permutations of screen width, height and obscurity

    You can use jungles to compile different source files (or folders) for different devices. You still end up with some code that picks a layout (or even a function) based on the field size and obscurity, but you only have to worry about the options available to the devices that use those source files so it does give you some separation.

    When I was writing apps, I'd create a project file hierarchy similar to this:

    +---resources
    |   |   drawables.xml
    |   |   strings.xml
    |   |
    |   \---bitmaps
    |           app_icon.bmp
    |
    +---resources-fenix5
    |       layouts.xml
    |
    +---resources-fr630
    |       layouts.xml
    |
    +---resources-vivoactive_hr
    |       layouts.xml
    |
    +---source
    |       DataFieldApp.mc
    |       DataFieldView.mc
    |
    +---source-fenix5
    |       getDeviceSpecificLayout.mc
    |
    +---source-fr630
    |       getDeviceSpecificLayout.mc
    |
    \---source-vivoactive_hr
            getDeviceSpecificLayout.mc

    Each of the getDeviceSpecificLayout.mc files would implement a single function that would get the layout for the given device based on the width/height/obscurity... something like this fictional example for the vivoactive_hr:

    using Toybox.WatchUi;
    
    function getDeviceSpecificLayout(obscurity, dc)
    {
        var height = dc.getHeight();
    
        // use width/height/obscurity to pick the layout to use
        // for this device
    
        if (height > 140) {
            return :vivoactive_hr_layout1;
        } else if (height > 100) {
            return :vivoactive_hr_layout2;
        } else {
            return :vivoactive_hr_layout3;
        }
    }

    Each of the layout.xml files would provide just the layouts that were needed by that device and nothing else. Again, here is a fictional example for the vivoactive_hr.

    <resources>
        <layouts>
            <layout id="vivoactive_hr_layout1">
                <label id="always" x="center" y="68" font="Graphics.FONT_LARGE" color="Graphics.COLOR_RED" justification="Graphics.TEXT_JUSTIFY_CENTER" />
                <label id="maybe" x="center" y="137" font="Graphics.FONT_MEDIUM" color="Graphics.COLOR_RED" justification="Graphics.TEXT_JUSTIFY_CENTER" />
            </layout>
            
            <layout id="vivoactive_hr_layout2">
                <label id="always" x="center" y="center" font="Graphics.FONT_SMALL" color="Graphics.COLOR_WHITE" justification="Graphics.TEXT_JUSTIFY_CENTER" />
            </layout>
            
            <layout id="vivoactive_hr_layout3">
                <label id="always" x="center" y="center" font="Graphics.FONT_XTINY" color="Graphics.COLOR_BLUE" justification="Graphics.TEXT_JUSTIFY_CENTER" />
            </layout>
        </layouts>
    </resources>
    

    Then, in the common code, I'd use the getDeviceSpecificLayout function to load the appropriate layout for given data field on the given device (sorry this isn't in a code block, the CAPTCHA thinks I'm a bot)...

    using Toybox.Lang;
    using Toybox.WatchUi;

    class DataFieldView extends WatchUi.DataField
    {
        hidden var _always;
        hidden var _maybe;

        function initialize() {
            DataField.initialize();
        }

        function onLayout(dc) {
            // call to device-specific code to get layout to use for the current field
            var layoutSymbol = $.getDeviceSpecificLayout(self.getObscurityFlags(), dc);
            if (layoutSymbol != null) {

                // given a device-specific layout symbol, load that layout from Rez
                var layoutMethod = new Lang.Method(Rez.Layouts, layoutSymbol);
                setLayout(layoutMethod.invoke(dc));

                // get drawable that is in every layout on all devices
                _always = findDrawableById("always");

                // get drawable that might not exist in some layouts
                _maybe = findDrawableById("maybe");
            }
        }

        function onUpdate(dc) {
            // update the data in the field that is always there
            var w = dc.getWidth();
            var h = dc.getHeight();
            _always.setText([ w, h ].toString());

            // update the data in the field if it is visible
            if (_maybe != null) {
                var obscurity = self.getObscurityFlags();
                _maybe.setText(obscurity.format("%x"));
            }

            DataField.onUpdate(dc);
        }
    }

  • Then you use jungles to add the device-specific source paths:

    project.manifest = manifest.xml
    
    # override the source paths to pick up the device-specific source files
    fenix5.sourcePath=fenix5.sourcePath;source-fenix5
    fr630.sourcePath=fr630.sourcePath;source-fr630
    vivoactive_hr.sourcePath=vivoactive_hr.sourcePath;source-vivoactive_hr
    

  • Former Member
    Former Member in reply to Travis.ConnectIQ

    Thanks Travis.  That’s pretty much what I did this morning using your getdevicespecificlayout and individual layout files specified in jungles.  The problem I ran into was out of memory errors. Once I put in 4 or so layouts in the same layout file, then I blow past the 26k limit of watches.   I’d need more than that to support all the field layouts so that’s when I gave up and went back to direct dc calls. 

    hopefully someone else can use your amazingly well out together answer though. 

  • The problem I ran into was out of memory errors. Once I put in 4 or so layouts in the same layout file, then I blow past the 26k limit of watches.

    Yes, this can happen. I'm surprised 4 layouts was enough to put you over the line... or are you considering a layout to be each field option on a screen (a 4-field layout in the simulator requires you to implement 4 size/obscurity options as layouts in the xml). I guess it really depends on how much stuff you have in each layout too.

    For the most part you can use something similar to what I suggested above. Just define a function like getDeviceSpecificLayout to do the drawing for each device. Maybe something like this:

    function vivoactive_hr_layout1(obscurity, dc, dict)
    {
        // draw layout1 on `dc` using data in `dict`
    }
    
    function vivoactive_hr_layout2(obscurity, dc, dict)
    {
        // draw layout2 on `dc` using data in `dict`
    }
    
    function vivoactive_hr_layout3(obscurity, dc, dict)
    {
        // draw layout3 on `dc` using data in `dict`
    }
    
    function drawDeviceSpecificLayout(obscurity, dc, dict)
    {
        var height = dc.getHeight();
    
        // use width/height/obscurity to pick the layout to use
        // for this device
    
        if (height > 140) {
            vivoactive_hr_layout1(obscurity, dc, dict);
        } else if (height > 100) {
            vivoactive_hr_layout2(obscurity, dc, dict);
        } else {
            vivoactive_hr_layout3(obscurity, dc, dict);
        }
    }

    Then, you move the logic for getting the right layout code to onUpdate:

        function onUpdate(dc) {
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
            dc.clear();
    
            // data your draw functions will need
            var params = {
                :data1 => 9,
                :data2 => "hello"
            };
    
            $.drawDeviceSpecificLayout(self.getObscurityFlags(), dc, params);
        }

    If that still isn't enough, then you'll have to get creative. Slight smile