martes, 1 de marzo de 2016

Open source Connect IQ face for Garmin watches - Analog & Skies (lunar phase and sunlight terminator)

Download Analog & Bars [source code | prg for vivoactive | Garmin store]
Download Analog & Skies [source code | prg for vivoactive | Garmin store]

Garmin empowers their watches by providing the Connect IQ SDK so that we can program our own apps, faces, etc. I wrote this open source (under GNU GPL v3.0) watch face which includes the calculations for the lunar phase and a sunlight terminator. It is space efficient and energy-efficient, because many of the calculations are only recomputed once a day (as opposed to the default every-minute), like the lunar phase or the sun declination. The sun declination is calculated in order to know the exact shape of the sunlight terminator, which is different, depending on the date. See, for example, the difference between summer and winter in the next two images:





The rest of the information displayed is explained in the following scheme:




The lunar phase and the sunlight terminator code are in two separate classes. They should be declared as class members, and initialized only once:


class AnalogBarsView extends Ui.WatchFace {

    var moon, earth;

    function initialize() {
        WatchFace.initialize();
 

        moon = new Moon(Ui.loadResource(Rez.Drawables.moon), 48, 18, 3);
        earth = new SunlightTerminator(Ui.loadResource(Rez.Drawables.earth), 100, 50, 86, 20);
    }

    function onUpdate(dc) {

        earth.updateable_sunlight_terminator(dc, time_sec, dateinfo, clockTime);
        moon.updateable_calcmoonphase(dc, dateinfo, clockTime.hour);


        // ...
    }
}


These two calls in onUpdate will not recompute all their values on every call. Instead, they will conserve the old values and recalculate them every so often.

Here's Moon.mc:

using Toybox.Math as Math;
using Toybox.System as Sys;
using Toybox.WatchUi as Ui;
using Toybox.Graphics as Gfx;

// By using updateable_calcmoonphase, the moon phase picture will be drawn,
// but it will be only recomputed once a day.
class Moon {
    var moon_width;
    var moon_bitmap;
    var moonx, moony;
   
    var c_phase, t_phase; // day of month when last updated
    var c_moon_label, c_moon_y; // y1, y2, y1, y2, ... for the moon shadow

    function initialize(bitmap, width, x, y) {
        moon_bitmap = bitmap; // Ui.loadResource(Rez.Drawables.moon);
        moon_width = width;
        moonx = x;
        moony = y;
        t_phase = -1;
    }   
       
   
    function calcmoonphase(day, month, year) {
        var r = (year % 100);
        r = (r % 19);
        if (r>9) {
            r = r - 19;
        }
        r = ((r * 11) % 30) + month + day;
        if (month<3) {
            r = r + 2;
        }
        r = 1.0*r - 8.3 + 0.5;
        r = (r.toNumber() % 30);
        if (r < 0) {
            r = r + 30;
        }
        return r;
    }
    function updateable_calcmoonphase(dc, dateinfo, hour) {
        if (t_phase != dateinfo.day) {
            t_phase = dateinfo.day;

            c_phase = calcmoonphase(dateinfo.day, dateinfo.month, dateinfo.year);
            if (hour > 12) { // change it at noon
                c_phase = (c_phase + 1) % 30;
            }

            calc_drawmoon(c_phase);// updates c_moon_label and c_moon_y

        }
       
        drawmoon(dc, moonx, moony); // uses c_moon_y
       
           return c_phase;
    }

    function drawmoon(dc, moonx, moony) {
        dc.drawBitmap(moonx, moony, moon_bitmap);
        var x, xby2;
        dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_WHITE);
        for (x=1; x<moon_width; x++) {
            xby2 = x*2;
            if (c_moon_y[xby2] >= 0) {
                dc.drawLine(moonx+x, moony+c_moon_y[xby2], moonx+x, moony+c_moon_y[xby2+1]);
            } else {
                dc.drawLine(moonx+x, moony+1, moonx+x, moony-c_moon_y[xby2]);
                dc.drawLine(moonx+x, moony-c_moon_y[xby2+1], moonx+x, moony+moon_width);
            }
        }
    }

    function calc_drawmoon(moonphase) {
        var mw = moon_width; // image width
        var c = mw/2; // image center
        var intc = c.toNumber();
        var r = (mw-2)*0.5-0.5; // radius depends on image
        c_moon_label = "";
        var step = 1;
        var r1edge= -1;
        var rSedge= 0;
        var r1rest= -1;
        var rSrest= 0;
        var edgelight = false;
        if (moonphase <= 8) {
              c_moon_label = "wax.";
            r1edge = intc; rSedge = step;
            r1rest = intc; rSrest = -step;
            edgelight = true;
            if (moonphase == 8) {
                r1edge = -1; rSedge = 0;
            } else {
                if (moonphase == 0) {
                    c_moon_label = "new";
                }
            }
        } else {
            if (moonphase <=16) {
                  c_moon_label = "wax.";
                r1rest = -1; rSrest = 0;
                r1edge = intc; rSedge = -step;
                edgelight = false;
                if (moonphase == 16) {
                      c_moon_label = "full";
                    r1edge = -1; rSedge = 0;
                }
            } else {
                  c_moon_label = "wan.";
                if (moonphase <=23) {
                    r1rest = -1; rSrest = 0;
                    r1edge = intc; rSedge = step;
                    edgelight = false;
                    if (moonphase == 23) {
                        r1edge = -1; rSedge = 0;
                        r1rest = intc; rSrest = step;
                    }
                } else {
                    r1edge = intc; rSedge = -step;
                    r1rest = intc; rSrest = step;
                    edgelight = true;
                }
            }
        }    
       
        var a;
        if (moonphase > 16) {
            a = 1.0 - (moonphase - 16.0) / 7.0;
        } else {
            a = 1.0 - moonphase/8.0;
        }
       
       
        c_moon_y = new [mw*2+2];
        var i;
        for (i = 0; i<mw*2; i++) {
            c_moon_y[i] = 0;
        }
       
        var x, xx, ra, sq, y1, y2, xby2;
        for (x=r1rest; x<=mw && x>=1; x+=rSrest) {
            //dc.drawLine(moonx+x, moony+1, moonx+x, moony+mw);
            xby2 = 2*x;
            c_moon_y[xby2] = 1;
            c_moon_y[xby2+1] = mw;
        }
        for (x=r1edge; x<=mw && x>=1; x+=rSedge) {
            xx = (x-c)/a;
            ra = r*r - xx*xx;
            xby2 = 2*x;
           
            if (ra > 0) {
                sq = Math.sqrt(ra);
                y1 = c - sq + 0.5;
                y1 = y1.toNumber();
                y2 = c + sq + 0.5;
                y2 = y2.toNumber();
                if (edgelight) {
//                    dc.drawLine(moonx+x, moony+y1, moonx+x, moony+y2);
                    c_moon_y[xby2] = y1;
                    c_moon_y[xby2+1] = y2;
                } else {
                    //dc.drawLine(moonx+x, moony+1, moonx+x, moony+y1);
                    //dc.drawLine(moonx+x, moony+y2, moonx+x, moony+mw);
                    c_moon_y[xby2] = -y1;
                    c_moon_y[xby2+1] = -y2;
                }
            } else {
                if (!edgelight) {
//                    dc.drawLine(moonx+x, moony+1, moonx+x, moony+mw);
                    c_moon_y[xby2] = 1;
                    c_moon_y[xby2+1] = mw;
                }
            }
        }
       
        return c_moon_label;
    }


}


And here's SunlightTerminator.mc:

using Toybox.Math as Math;
using Toybox.System as Sys;
using Toybox.WatchUi as Ui;
using Toybox.Graphics as Gfx;

// By using updateable_sunlight_terminator the terminator will be drawn, but it will
// only be recalculated every 300 seconds. The declination will be recalculated daily.
class SunlightTerminator {
    var earthx, earthy, width, height;
    var c_declination, t_declination; // day of month when last updated
    var c_sunlight, t_sunlight; // time in seconds of next update.

    var image;

    function initialize(earth_bitmap, w, h, x, y) {
        image = earth_bitmap;
       
        t_declination = -1;
        t_sunlight = -1;
       
        earthx = x;
        earthy = y;
        width = w;
        height = h;
       
    }
   
    function sun_declination(dateinfo) {
        var jul = julian( dateinfo.year,  dateinfo.month,  dateinfo.day);
        var radians = Math.PI/180.0;
        var lambda = ecliptic_longitude(jul, radians);
        var obliquity = 23.439 * radians - 0.0000004 * radians * jul;
        var delta = Math.asin(Math.sin(obliquity) * Math.sin(lambda));
        if (delta == 0) {
            return 0.000001;
        } else {
            return delta;       
        }
    }

    function updateable_sun_declination(dateinfo) {
        if (t_declination != dateinfo.day) {
            c_declination = sun_declination(dateinfo);
            t_declination = dateinfo.day;
        }
        return c_declination;
    }
   
    function ecliptic_longitude(jul, radians) {
        var meanlongitude = getAngle(280.461 * radians + 0.9856474 * radians * jul);
        var meananomaly = getAngle(357.528 * radians + .9856003 * radians * jul);
        return getAngle(meanlongitude + 1.915 * radians * Math.sin(meananomaly)
                        + 0.02 * radians * Math.sin(2.0 * meananomaly));
    }

    // returns a floating point angle in the range 0 .. 2*pi
    function getAngle(x) {
        var b = 0.5*x / Math.PI;
        var a = 2.0*Math.PI * (b - b.toNumber());
        if (a < 0) {
            a = 2.0*pi + a;
        }
        return a;
    }
   
    // between 1901 to 2099
    function julian( y,  m,  d) {
        var a = (m + 9)/12.0;
        var b = (y + a.toNumber())/4.0;
        var c = 275*m/9.0;
        var l = -7 * b.toNumber() + c.toNumber() + d;
        l = l.toNumber() + y*367;
        return l - 730531;
    }
       
    function updateable_sunlight_terminator(dc, time_sec, dateinfo, time) {
        if (time_sec.value() > t_sunlight) {
            t_sunlight = time_sec.value()+300; // update interval of 300 seconds
   
            updateable_sun_declination(dateinfo);
            calc_sunlight_terminator(dc, c_declination, time, earthx, earthy, width, height);
            // updates c_sunlight array
        }
        // uses c_sunlight array
        draw_sunlight_terminator(dc, c_declination, time, earthx, earthy, width, height);
    }

    function draw_sunlight_terminator(dc, declination, time, earthx, earthy, width, height){
        dc.drawBitmap(earthx, earthy, image);
        //hide some unnecessary part of the map:
        dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_WHITE);
        dc.drawLine(earthx,earthy,earthx+100,earthy);
        dc.fillRectangle(earthx,earthy+47,earthx+100,earthy+50);

        var x, x2;
        if (declination < 0) {
            for (x=1; x<=c_sunlight.size(); x+=1) {
                x2 = earthx + x*2 - 1;
                dc.drawLine(x2, earthy, x2, earthy+c_sunlight[x-1]);
            }
        } else {
            for (x=1; x<=c_sunlight.size(); x+=1) {
                x2 = earthx + x*2 - 1;
                dc.drawLine(x2, earthy+c_sunlight[x-1], x2, earthy+height);
            }
        }
   
    }
   
    function calc_sunlight_terminator(dc, declination, time, earthx, earthy, width, height) {
        var num_el = width / 2;
        c_sunlight = new [num_el.toNumber()];
       
        var hour = (((time.hour*3600-time.timeZoneOffset)%86400) - 43200 + time.min*60.0) / 3600.0;
        //var lat1 = -1.5708; // -pi/2
        //var lat2 = 1.5708; // pi/2
        //var latrange = lat2 - lat1;
        //lat1 = (-lat1+1.5708)/3.1416;

        var latrange = 3.1416;
        var lat1 = 1.0;

        var x, y, longitude, latitude, x2;
        x2 = 0;
        if (declination < 0) {
            for (x=2; x<=width; x+=2) {
                longitude = (x-1.0)/width*6.2832-3.1416+hour/24*6.2832;
                latitude = Math.atan(-Math.cos(longitude)/Math.tan(declination));
                y = (-latitude + 1.5708) / latrange - (1-lat1);
                y = y * height + 0.5;
                y = y.toNumber();
                //x2 = earthx + x - 1;
                //dc.drawLine(x2, earthy, x2, earthy+y);
                c_sunlight[x2] = y;
                x2 += 1;
            }
        } else {
            for (x=2; x<=width; x+=2) {
                longitude = (x-1.0)/width*6.2832-3.1416+hour/24*6.2832;
                latitude = Math.atan(-Math.cos(longitude)/Math.tan(declination));
                y = (-latitude + 1.5708) / latrange - (1-lat1);
                y = y * height + 0.5;
                y = y.toNumber();
                //x2 = earthx + x - 1;
                //dc.drawLine(x2, earthy+y, x2, earthy+height);
                c_sunlight[x2] = y;
                x2 +=1;
            }
        }
    }
 
   

}














domingo, 17 de enero de 2016

Open source Connect IQ face for Garmin watches - Analog & Bars

Download Analog & Bars [source code | prg for vivoactive | Garmin store]
Download Analog & Skies [source code | prg for vivoactive | Garmin store]

Garmin empowers their watches by providing the Connect IQ SDK so that we can program our own apps, faces, etc. However, there's little documentation, and even almost no Open Source apps. So, let me publish (under GNU GPL v3.0) a watch face I wrote: Analog & Bars [source code | prg for vivoactive | Garmin store]. Maybe you can improve it and publish your own.



  • Note on the Garmin Vivoactive device: It is a thin and comfortable watch designed for tracking sport activities and displaying some basic information. Do not expect anything similar to an Android operating system. It is very basic and very well optimized for its purposes. The battery lasts for weeks. The only drawback it might have is the bluetooth connection with your phone. I never managed to connect it to my own Galaxy Note 3 phone, which is among the supported ones (I factory reset it to make sure, plus it works with other bluetooth devices). The Garmin tech service was very patient but all they could say is they don't know why a supported phone doesn't work with it. I asked them what happens when Android phones get their OS updated: does Garmin check if the connection to that device continues to work? They don't. I have to say, my Garmin watch did connect to many other phones, including some which are not officially supported by Garmin. If your phone is an unlucky one, each time you charge it, it will transmit the tracking data to your Garmin account. Unless you only have Linux at home, for which Garmin does not provide support.

The app may be available on the Garmin Connect Apps site, uploaded by myself or by someone else with their own modifications. I will not maintain it and I will not fix bugs, I will not add new functionalities, and I will not compile it for different devices.

I tested it on Garmin Vivoactive. You should be able to compile it for other square watches but the background is stored as a PNG with the Vivoactive screen size . This should not be a big issue, as I provide Matlab/Octave code for generating the background image. It is transparent, which is pretty convenient if you want to display something behind the minute ticks. Just display the background in last place, as I do in my app.

% vivoactive watch face background generation. Matlab / Octave
% Boyan Bonev, January 2016
function watchface()

im = uint8(zeros(148,205));


%hours
cx = 205/2; cy = 148/2+0.5;
r1 = 67.5; r2 = 150;
%im = ticks(im,12, r1, r2, cy, cx, [-0.01 -0.005 0 0.005 0.01 ]);
im = ticks(im,12, r1, r2, [cy cy-1 cy+1], [cx cx-1 cx+1], 0);
im(3:end-2,cx-1:cx+1) = 0;
m=16;
im(1+m:148-m,1+m:205-m) = 0;
imhours = im;

%minutes
r1 = 66.5; r2 = 150;
im = uint8(zeros(148,205));
im = ticks(im,12*5, r1, r2, cy, cx, [-0.006 -0.005 0 0.005 0.006]);
m=10;
im(1+m:148-m,1+m:205-m) = 0;
r1 = 100; r2 = 150;
im = ticks(im,2, r1, r2, cy, cx, [-0.005, 0, 0.005]+pi/6/5*9);
im = ticks(im,2, r1, r2, cy, cx, [-0.005, 0, 0.005]+pi/6/5*21);
im(3:end-2,cx-1:cx+1) = 0;
immins = im;

imr = imhours;
img = imhours;
imb = imhours;
imr(immins==255) = 255;

rgb = cat(3,imr,img,imb);
imwrite(rgb,'face.png');

im2 = immins/255;
im2(imhours==255) = 2;
%imagesc(im2);
cmap = [0 0 0; 1 0 0; 1 1 1];
imwrite(im2, cmap, 'face2.png', 'png', 'Transparency', 0);

end % of function

function imout = ticks(im,n,r1,r2, cys, cxs, offsets)
    for offset = offsets
        for i=0:n-1
            angle = 2*pi/n*i + offset-pi/2;

            for r=r1:r2
                x0 = cos(angle)*r;
                y0 = sin(angle)*r;
                for cy = cys
                    for cx = cxs
                        x = round(x0+cx);
                        y = round(y0+cy);
                        if y>0 && x>0 && y<=148 && x<=205
                            im(y,x) = 255;
                        end
                    end
                end
            end
        end
    end
    imout = im;
end % of function
You don't need to run this code if you are going to use the Vivoactive device. Just download the result:





Due to the transparency, you may not see the white hour ticks. Fill a black rectangle of the size of the watch screen before you display it, and you'll see the hours and minutes ticks like in the app's screenshot on the top.


In this watch face, the bars represent the steps history for the last week. The ones in red are weekend days. The bar in gray is the goal, and the gray digits on its right are the number of steps the goal bar represents. The blue symbols represent the memory, battery, sound, vibration, phone connection, alarms, and the ones you don't see here are the ones for notifications and for sleep mode. They were all included in a font which I generated for the purpose. See the Connect IQ documentation for generating fonts. Here is how the app's fonts look like:

 

They come with a text file indicating the coordinates of each character. Bitmap Font Generator is a freeware that generates them, recommended by Garmin's Connect IQ guide. My antivirus complained about it, but I believe that was a false positive. I was able to run it in Linux, with Wine. (I didn't try to run the Connect IQ SDK on Linux). Generate the bitmaps with 128x128 size. You don't need to smooth them, as the Garmin device will display only a few colors anyway. Generate their descriptor as a text file. Finally, don't forget to include the fonts in the resources.xml file of your project. I used the filter option because I don't use all the symbols.

A good programming practice would have been to extract all the strings to the strings.xml file. It makes it easier to keep the app multilanguage.

And here goes the code. I've zipped the whole project here: source code. By the way, code is code both in singular and plural. Unless you mean "access codes".



// Boyan Bonev, January 2016

//

// This app needs the Connect IQ SDK to be compiled.

// It is designed for the Vivoactive watch by Garmin.

// It may work for other platforms, but you need to

// compile it yourself.

// I will NOT maintain it and I will NOT respond to inquiries

// for upgrades or bug fixes. However, feel free to

// modify it and distribute it, as long as you keep it Free Software.

// You may also acknowledge the original author if you feel like doing it.

//

// The present code is licensed under the GNU GPL V3.0 license http://www.gnu.org/licenses/gpl.txt

// This doesn't apply to the fonts and other resources.

//

// If you have questions about Connect IQ and C Monkey, please,

// make them public via the Garmin Forum.

//





using Toybox.WatchUi as Ui;

using Toybox.Graphics as Gfx;

using Toybox.System as Sys;

using Toybox.Lang as Lang;

using Toybox.Sensor as Sens;

using Toybox.ActivityMonitor as Act;

using Toybox.Attention as Att;

using Toybox.Math as Math;

using Toybox.Time as Time;

using Toybox.Time.Gregorian as Greg;



class AnalogBarsView extends Ui.WatchFace {



       var font, font2, fontnumbers, fsyms;

       var h, w, h2, w2;



    function initialize() {

        WatchFace.initialize();

        font2 = Ui.loadResource(Rez.Fonts.sawade20);

        fontnumbers = Ui.loadResource(Rez.Fonts.sawade35);

        fsyms = Ui.loadResource(Rez.Fonts.typicons22);

    }



    //! Load your resources here

    function onLayout(dc) {

       // setLayout(Rez.Layouts.WatchFace(dc));

    }



    //! Called when this View is brought to the foreground. Restore

    //! the state of this View and prepare it to be shown. This includes

    //! loading resources into memory.

    function onShow() {

    }



    //! Update the view

    function onUpdate(dc) {

              w = dc.getWidth();

              h = dc.getHeight();

              h2 = h/2;

              w2 = w/2;

              var n;



             

              var dateinfo = Greg.info(Time.now(), Time.FORMAT_SHORT);

              //Sys.println(dateinfo.day_of_week);

              var daynames = ["U","M","T","W","R","F","S"];

             

             

              // Clear the screen

              dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_WHITE);

              dc.fillRectangle(0,0,w,h);





              var settings = Sys.getDeviceSettings();

              var systemStats = Sys.getSystemStats();

              var actinfo = Act.getInfo();



              // Plot Bars

             

              var stepsnorm = actinfo.stepGoal * 3;

              if (stepsnorm <= 0){

                     stepsnorm = 10000;

              }

              var steps = actinfo.steps;

              if (steps > stepsnorm) {

                     stepsnorm = steps;

              }



              // steps history

              var hist = Act.getHistory();

              var color;

              var dn;

              for (n=0;n<hist.size();n++){

                     dn = (dateinfo.day_of_week+5-n) % 7;

                     if (dn == 6 || dn == 0) { color = Gfx.COLOR_RED;}

                     else { color = Gfx.COLOR_GREEN;}

                     bar(dc, 13-n, 1.0*hist[n].steps / stepsnorm,

                     color, daynames[dn]);

              }

             

              // steps today

              dn = dateinfo.day_of_week-1;

              if (dn == 6 || dn == 0) { color = Gfx.COLOR_RED;}

                     else { color = Gfx.COLOR_GREEN;}

              bar(dc, 14, 1.0*steps / stepsnorm,

              color, daynames[dn], true);

             

              // goal

              bar(dc, 15, 1.0*actinfo.stepGoal / stepsnorm,

              Gfx.COLOR_DK_GRAY, "g");

              var s = actinfo.stepGoal;

              var sn;

              var pos = -1;

              for (n=10000;n>0;n=n/10) {

                     sn = s/n;

                     if (pos == -1) {

                           if (sn > 0) { // start printing

                                  pos = 82;

                           } else {

                                  continue;

                           }

                     }

                     dc.drawText(w-23, pos, font2, ""+sn, Gfx.TEXT_JUSTIFY_CENTER);

                     s = s - sn*n;

                     pos = pos + 11;

              }





              // write the date

              var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

              var weekdays = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];

              dc.setColor(Gfx.COLOR_GREEN, Gfx.COLOR_TRANSPARENT);

              dc.drawText(35, 20, font2,

              weekdays[dateinfo.day_of_week-1],

              Gfx.TEXT_JUSTIFY_CENTER);

              dc.drawText(35, 32, font2,

              months[dateinfo.month-1]+ " "+ dateinfo.day,

              Gfx.TEXT_JUSTIFY_CENTER);

              dc.drawText(35, 44, font2,

              ""+ dateinfo.year,

              Gfx.TEXT_JUSTIFY_CENTER);









              // Plot Symbols



              //a:sun b:battery c:battery d:battery e:battery f:bell g:vibrate h:vibrate

              //i:bulb j:gps k:bubble l:bubbles m:notes n:notes o:phone p:phone q:chrono

              //r:thumbsdown s:thumbsup t:crossed u:user v:sound w:nosound

              //Check the pictures in resources/fonts/typicons22_0.png

              //Be aware that the characters loading filter is set in resource.xml

       

              var flags = "";

              if (settings.tonesOn) {flags = flags + "v";} else {flags = flags + "w";}

              if (settings.vibrateOn) {flags = flags + "h";}

              if (settings.phoneConnected) {flags = flags + "p";}

              if (settings.notificationCount>0) {flags = flags + "l";}

              if (settings.alarmCount>0) {flags = flags + "f";}

              if (actinfo.moveBarLevel>Act.MOVE_BAR_LEVEL_MIN) {flags = flags + "q";}

              if (actinfo.isSleepMode) {flags = flags + "i";}

       

              dc.setColor(Gfx.COLOR_BLUE, Gfx.COLOR_TRANSPARENT);

              dc.drawText(17, h-37, fsyms, flags,  Gfx.TEXT_JUSTIFY_LEFT);

             

              // Battery

              var battery = systemStats.battery;

              dc.drawText(17, h-53, fsyms, "d",  Gfx.TEXT_JUSTIFY_LEFT);

              dc.fillRectangle(17+3,h-44, 11.0*battery/100, 7);

              dc.drawText(17+19, h-52, font2, battery.format("%d") + "%", Gfx.TEXT_JUSTIFY_LEFT);



              // Memory

              //memory usage

              var mem = 100.0*systemStats.usedMemory/systemStats.totalMemory;

              dc.drawText(17, h-68, fsyms, "e",  Gfx.TEXT_JUSTIFY_LEFT);

              dc.drawText(17+19, h-66, font2, mem.format("%d") + "%", Gfx.TEXT_JUSTIFY_LEFT);



       

              // Write calories, distance

              var dist1 = actinfo.distance/160934.0;

              var dist2 = actinfo.distance/100000.0;

              dc.drawText(w-20, 20, font2, steps + " steps", Gfx.TEXT_JUSTIFY_RIGHT);

              dc.drawText(w-20, 32, font2, dist1.format("%0.1f") +" mi / "+ dist2.format("%0.1f") + " km", Gfx.TEXT_JUSTIFY_RIGHT);

              dc.drawText(w-20, 44, font2, actinfo.calories + " kcal", Gfx.TEXT_JUSTIFY_RIGHT);

       

       

              // plot the background with transparency

              dc.drawBitmap(0, 0, Ui.loadResource(Rez.Drawables.background));





       

              // write the clock numbers

              dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);

              dc.drawText(w2+1, -6, fontnumbers, ""+12, Gfx.TEXT_JUSTIFY_CENTER);

              dc.drawText(w2, h-30, fontnumbers, ""+6, Gfx.TEXT_JUSTIFY_CENTER);

              dc.drawText(17, h2-17, fontnumbers, ""+9, Gfx.TEXT_JUSTIFY_LEFT);

              dc.drawText(w-17, h2-17, fontnumbers, ""+3, Gfx.TEXT_JUSTIFY_RIGHT);

       

       

              // Get and show the current time

              var clockTime = Sys.getClockTime();


 
              // Draw hands

              var alpha, r, r2, hand;

             

              // hours

              alpha = Math.PI/6*(1.0*clockTime.hour+clockTime.min/60.0);

              r = 50;

              r2 = 12;

              hand =         [[w2,h2],

                                         [w2+r2*Math.sin(alpha-0.4),h2-r2*Math.cos(alpha-0.4)],

                                         [w2+r*Math.sin(alpha),h2-r*Math.cos(alpha)],

                                         [w2+r2*Math.sin(alpha+0.4),h2-r2*Math.cos(alpha+0.4)]   ];

              dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);

              dc.fillPolygon(hand);

              dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);

              for (n=0; n<3; n++) {

                     dc.drawLine(hand[n][0], hand[n][1], hand[n+1][0], hand[n+1][1]);

              }

              dc.drawLine(hand[n][0], hand[n][1], hand[0][0], hand[0][1]);



              // minutes

              alpha = Math.PI/30.0*clockTime.min;

              r = 90;

              r2 = 20;

              hand =         [[w2,h2],

                                         [w2+r2*Math.sin(alpha-0.15),h2-r2*Math.cos(alpha-0.15)],

                                         [w2+r*Math.sin(alpha),h2-r*Math.cos(alpha)],

                                         [w2+r2*Math.sin(alpha+0.15),h2-r2*Math.cos(alpha+0.15)]   ];

              dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);

              dc.fillPolygon(hand);

              dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_TRANSPARENT);

              for (n=0; n<3; n++) {

                     dc.drawLine(hand[n][0], hand[n][1], hand[n+1][0], hand[n+1][1]);

              }

              dc.drawLine(hand[n][0], hand[n][1], hand[0][0], hand[0][1]);





    }

   



   

    function bar(dc, position, percentage, color1, text, strong) {

        var len = (h - 21) * (percentage);

        len = len.toNumber();

       dc.setColor(color1, Gfx.COLOR_TRANSPARENT);

              dc.fillRectangle(18+position*10, h-len-10, 8, len+1);

              dc.drawText(22+position*10, h-len-28, font2, text, Gfx.TEXT_JUSTIFY_CENTER);

              if(strong){

                     dc.drawText(22+position*10-1, h-len-28-1, font2, text, Gfx.TEXT_JUSTIFY_CENTER);

                     dc.drawText(22+position*10-1, h-len-28, font2, text, Gfx.TEXT_JUSTIFY_CENTER);

                     dc.drawText(22+position*10, h-len-28-1, font2, text, Gfx.TEXT_JUSTIFY_CENTER);

              }

    }

   

   

    //! Called when this View is removed from the screen. Save the

    //! state of this View here. This includes freeing resources from

    //! memory.

    function onHide() {

    }



    //! The user has just looked at their watch. Timers and animations may be started here.

    function onExitSleep() {

    }



    //! Terminate any active timers and prepare for slow updates.

    function onEnterSleep() {

    }



}