Get access to custom drawable in watch face view class

Former Member
Former Member

Hello everyone, 

I am quit newbie here, so apologise for "stupid" question. This what I got:

layout.xml
 

<layout id="WatchFace">
    <drawable id="BackDrawable" class="BackgroundDrawable"></drawable>
    ...
</layout>

Separate class file:

using Toybox.Graphics;
using Toybox.WatchUi as Ui;

class BackgroundDrawable extends Ui.Drawable
{
	
	var clockBackX = 0;
	var clockBackY = 0;
	var clockBackWidth = 0;
	var clockBackHeight = 0;
	var clockBackColor = Graphics.COLOR_LT_GRAY;
	
	function initialize(options) {
		Drawable.initialize(options);
	}
	
	function draw(dc) {
		clockBackWidth = dc.getWidth();
		clockBackHeight = dc.getHeight();
		drawClockBack(dc, clockBackX, clockBackY, clockBackWidth, clockBackHeight, clockBackColor);
	}
	
	function drawClockBack(dc, xPosition, yPosition, width, height, color) {
		dc.setColor(color, Graphics.COLOR_TRANSPARENT);
		dc.fillRectangle(xPosition,yPosition,width,height);
	}
	
	function setColor(color){
		clockBackColor = color;
	}
}

Question:

I would like to update color of this custom drawable with application settings. So how to get access to that drawable in my WatchFaceView class? or link it somehow to application settings?

Thanks in advance/

  • There are a few steps necessary to get what you want. I'm assuming that your app looks something like this to start...

    class MyWatchFace extends WatchUi.WatchFace
    {
        function initialize() {
            WatchFace.initialize();
        }
        
        function onShow() {
        }
        
        function onLayout(dc) {
            setLayout(Rez.Layouts.WatchFace(dc));
        }
        
        function onUpdate(dc) {
            // handle any changes to drawables in the layout
            
            View.onUpdate();
            
            // draw anything on top of the layout
        }
        
        function onHide() {
        }
        
    }
    
    class MyApp extends Application.AppBase {
    
        function initialize() {
            AppBase.initialize();
        }
        
        function onStart(params) {
        }
        
        function getInitialView() {
            return [ new MyWatchFace() ];
        }
        
        function onStop(params) {
        }
    }

    The first steps are getting access to the background drawable, getting the property value, and setting the color. The View object has a findDrawableById() method that can be used to get a drawable. So this part is pretty easy:

    // snip...
        
        function onLayout(dc) {
            setLayout(Rez.Layouts.WatchFace(dc));
    
            // read the property
            var color = Properties.getValue("BackColor");
            if (color == null) {
                color = Graphics.COLOR_BLACK;
            }
    
            // find the drawable
            var drawable = findDrawableById("BackDrawable");
    
            // set the color        
            drawable.setColor(color);
        }

    Here I'm updating the color from onLayout(). This will update the color after the properties have changed and the watch face is been re-started (enter the widget loop and return). You could also put that new code into onUpdate() or onPartialUpdate() to update the color more frequently. That might seem good at first, but the screen updates at least once a minute, and typically the setting isn't changing every update, so that would be a lot of wasted cycles over a day. We can do better.

    The AppBase object has an onSettingsChanged() callback that is invoked by the system when settings have been changed externally (Garmin Connect Mobile, the ConnectIQ app, or Garmin Express). You can update your AppBase object to keep a reference to the WatchFace, and then use this to notify the view to refresh itself when the settings have changed.

    class MyWatchFace extends WatchUi.WatchFace
    {
        hidden var _settingsChanged = true;
    
        function onLayout(dc) {
            setLayout(Rez.Layouts.WatchFace(dc));
            
            // read the settings from storage on initial display
            applySettings();
        }
        
        function onUpdate(dc) {
            if (_settingsChanged) {
    
                // read the settings from storage because they have changed
                applySettings();
            }
    
            View.onUpdate();
        }
        
        function applySettings() {
            // read the property
            var color = Properties.getValue("BackColor");
            if (color == null) {
                color = Graphics.COLOR_BLACK;
            }
    
            // find the drawable
            var drawable = findDrawableById("BackDrawable");
    
            // set the color        
            drawable.setColor(color);
            
            // we've appllied settings, clear the flag so we don't update
            // unless necessary
            _settingsChanged = false;
        }
        
        function onSettingsChanged() {
            _settingsChanged = true;
        }
        
    }
    
    class MyApp extends Application.AppBase {
    
        hidden var _view;
    
        // snip...
    
        function getInitialView() {
            _view = new MyWatchFace();
            return [ _view ];
        }
        
        function onSettingsChanged() {
            if (_view != null) {
                _view.onSettingsChanged();
            }
        }
    
        // snip...
    }

  • Another option here is to just skip using layouts and do it all with dc calls.  It's something to look at if you're getting tight on memory.

    You only need to worry about settings in two places - when the app starts and in onSettingsChanged.

    Then in onUpdate, all you need is

    dc.setColor(MySettings.background,MySettings.background);

    dc.clear();

    dc.setColor(MySettings.textcolor,Graphix.COLOR_TRANSPARENT);    //set color used for the foreground

    And then just reference MySettings in onPartialUpdate if that's being used.

  • Former Member
    0 Former Member over 4 years ago

    Thank you guys. I suppose, my main problem was that i was trying to get access calling "View.findDrawableById()". Well, anyway, "findDrawableById()" really helped. 

    Furthermore, I decided to get property of app settings directly in "draw(dc)" method. Like that:

    class BackgroundDrawable extends Ui.Drawable
    {
    	
    	var clockBackX = 0;
    	var clockBackY = 0;
    	var clockBackWidth = 0;
    	var clockBackHeight = 0;
    	var clockBackColor = Graphics.COLOR_LT_GRAY;
    	
    	function initialize(options) {
    		Drawable.initialize(options);
    	}
    	
    	function draw(dc) {
    		var clockBackX = 0;
    	    var clockBackY = get_y_wyth_percent(dc, 28);
    		var clockBackWidth = get_x_wyth_percent(dc, 100);
    		var clockBackHeight = get_y_wyth_percent(dc, 30);
    		var clockBackgroudColor = Application.Properties.getValue("ClockBacgroundColor");
    		dc.setColor(clockBackgroudColor, Graphics.COLOR_TRANSPARENT);
    		dc.fillRectangle(clockBackX,clockBackY,clockBackWidth,clockBackHeight);		
    	}
    }

  • You sacrifice efficiency by doing that.  The background color rarely changes, yet you are making a call to read it whenever draw() is called. get_x_wyth_percent and get_y_wyth_percent can also just be called in onLayout instead of each time draw() is called.

  • Former Member
    0 Former Member over 4 years ago in reply to jim_m_58

    Yes, you are right! Is it means that I should leave "draw(dc)" empty, and move logic to some method which will be called in class WatchFaceView in method "onLayout(dc)", as Travis suggested? 

  • That's a way to do it.  When you are looking to get a battery efficient WF, it's typically not one big thing, it's a whole bunch of little things - one of which is re-calculating a bunch of things in onUpdate each time it's called that can only need to be done once - like calculating x/y based on dc width and height.  That doesn't change.  Same with doing things like "has" checks or reading Application.Storage.  Application.Properties only need to be run when an app starts or if onSettingsChanged() has occured.