Better code needed for hands on watchface

Former Member
Former Member
Hi,

I'm having trouble making sharp, readable hands on a watchface.
Can anyone send me a better version, maybe with polygons if that is better. I really cannot get the hang of polygons as I don't understand the command.
Or perhaps steer me in the right direction ?
Thank you in advance

I've been using this code :

// Define variables
var center_x;
var center_y;
var minute_radius;
var hour_radius;
var seconds_radius;
var TWO_PI = Math.PI * 2;
var ANGLE_ADJUST = Math.PI / 2.0;
var showSeconds=false;
var now = Time.now();
var clockTime = Sys.getClockTime();
var hour = clockTime.hour;
var minute = clockTime.min;
var seconds = clockTime.sec;
var hour_fraction = minute / 60.0;
var minute_angle = hour_fraction * TWO_PI;
var seconds_angle = seconds * TWO_PI / 60.0;
var hour_angle = (((hour % 12) / 12.0) + (hour_fraction / 12.0)) * TWO_PI;
seconds_angle -= ANGLE_ADJUST;
minute_angle -= ANGLE_ADJUST;
hour_angle -= ANGLE_ADJUST;

// draw the hour hand
dc.setPenWidth(9); hour_radius = 60;
dc.setColor(Gfx.COLOR_YELLOW,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + hour_radius * Math.cos(hour_angle)),
(center_y + hour_radius * Math.sin(hour_angle)));
dc.setPenWidth(5); hour_radius = 59;
dc.setColor(Gfx.COLOR_BLACK,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + hour_radius * Math.cos(hour_angle)),
(center_y + hour_radius * Math.sin(hour_angle)));
dc.setPenWidth(3); hour_radius = 50;
dc.setColor(Gfx.COLOR_WHITE,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + hour_radius * Math.cos(hour_angle)),
(center_y + hour_radius * Math.sin(hour_angle)));

// draw the minute hand
dc.setPenWidth(9); minute_radius = 95;
dc.setColor(Gfx.COLOR_YELLOW,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + minute_radius * Math.cos(minute_angle)),
(center_y + minute_radius * Math.sin(minute_angle)));
dc.setPenWidth(5); minute_radius = 94;
dc.setColor(Gfx.COLOR_BLACK,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + minute_radius * Math.cos(minute_angle)),
(center_y + minute_radius * Math.sin(minute_angle)));
dc.setPenWidth(3); minute_radius = 85;
dc.setColor(Gfx.COLOR_WHITE,Gfx.COLOR_BLACK);
dc.drawLine(center_x, center_y,
(center_x + minute_radius * Math.cos(minute_angle)),
(center_y + minute_radius * Math.sin(minute_angle)));

if (showSeconds) {
// draw the seconds hand
dc.setPenWidth(3); seconds_radius = 95;
dc.setColor(Gfx.COLOR_YELLOW,Gfx.COLOR_TRANSPARENT);
dc.drawLine(center_x, center_y,
(center_x + seconds_radius * Math.cos(seconds_angle)),
(center_y + seconds_radius * Math.sin(seconds_angle)));
dc.drawCircle((center_x + seconds_radius * Math.cos(seconds_angle)),
(center_y + seconds_radius * Math.sin(seconds_angle)),2);}
  • Take a look at the Analog sample in the SDK - the "drawHand()" function.

    here it is. Look at the sample to see how it's called for hours and mins.
    //! Draw the watch hand
    //! @param dc Device Context to Draw
    //! @param angle Angle to draw the watch hand
    //! @param length Length of the watch hand
    //! @param width Width of the watch hand
    function drawHand(dc, angle, length, width)
    {
    // Map out the coordinates of the watch hand
    var coords = [ [-(width/2),0], [-(width/2), -length], [width/2, -length], [width/2, 0] ];
    var result = new [4];
    var centerX = dc.getWidth() / 2;
    var centerY = dc.getHeight() / 2;
    var cos = Math.cos(angle);
    var sin = Math.sin(angle);

    // Transform the coordinates
    for (var i = 0; i < 4; i += 1)
    {
    var x = (coords[0] * cos) - (coords[1] * sin);
    var y = (coords[0] * sin) + (coords[1] * cos);
    result= [ centerX+x, centerY+y];
    }

    // Draw the polygon
    dc.fillPolygon(result);
    dc.fillPolygon(result);
    }
    [/code]

    Note. this is a case where things look better on a real device than in the simulator, as the pixels are bigger in the sim!
  • Former Member
    Former Member over 9 years ago
    Other suggestions ?

    I really want to make complex looking hands so I guess the analog example is insufficient.
    Are there any other contenders to help ?

    Thanks
  • Former Member
    Former Member over 9 years ago
    I really want to make complex looking hands so I guess the analog example is insufficient.
    Are there any other contenders to help ?

    Thanks


    You can't transform an image, so drawing a nice polygon and transforming that really is the way to go. Jim provided a good option for that. Now it's just up to you to draw a nice polygon.

    It's not hard to draw a polygon. It's just connecting one point to the other, to the next.... and so on. Keep in mind that if you use the function Jim provided, to take the coordinate 0,0 as what will be at the center of your watch face.
  • using Toybox.System as Sys;
    using Toybox.WatchUi as Ui;
    using Toybox.Graphics as Gfx;
    using Toybox.Time as Time;
    using Toybox.Time.Gregorian as Calendar;


    class cifernikView extends Ui.WatchFace {

    var cinnost = true;

    function onLayout(dc) {
    }

    function onUpdate(dc) {

    View.onUpdate(dc);
    var width = dc.getWidth();
    var height = dc.getHeight();
    var clockTime = Sys.getClockTime();
    var hour;
    var min;
    var sec;

    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_WHITE);
    dc.fillRectangle(0,0,dc.getWidth(), dc.getHeight());

    hour = ( ( ( clockTime.hour % 12 ) * 60 ) + clockTime.min );
    hour = hour / (12 * 60.0);
    hour = hour * Math.PI * 2;
    min = ( clockTime.min / 60.0) * Math.PI * 2;
    sec = ( clockTime.sec / 60.0) * Math.PI * 2;



    dc.setColor(Gfx.COLOR_DK_GRAY, Gfx.COLOR_BLACK);
    drawHand(dc, width/2, height/2, min, 107, 7);
    drawHand(dc, width/2, height/2, min, 105, 6);
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    drawHand(dc, width/2, height/2, min, 106, 7);



    dc.setColor(Gfx.COLOR_DK_GRAY, Gfx.COLOR_BLACK);
    drawHand(dc, width/2, height/2, hour, 69, 7);
    drawHand(dc, width/2, height/2, hour, 67, 6);
    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    drawHand(dc, width/2, height/2, hour, 68, 7);




    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_BLACK);
    dc.fillCircle(width/2, height/2, 5.5);
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_BLACK);
    dc.fillCircle(width/2, height/2, 2);


    if(cinnost){
    dc.setColor(Gfx.COLOR_ORANGE, Gfx.COLOR_BLACK);
    drawSecHand(dc, width/2, height/2, sec, 105, 1.5);
    drawSecHand(dc, width/2, height/2, sec, -18, 1.5);
    dc.setColor(Gfx.COLOR_RED, Gfx.COLOR_BLACK);
    drawSecHand(dc, width/2, height/2, sec, 104.5, 1);
    drawSecHand(dc, width/2, height/2, sec, -17, 1);
    dc.fillCircle(width/2, height/2, 4);
    }

    }
    function drawHand(dc, x, y, angle, length, width)
    {
    var coords = [ [0,0], [-width, -(length)*1/3], [-(width*1/4), -(length)*9/10],[-(width*1/4), -length], [0, -length], [0, -(length*8/10)], [-(width*3/4), -(length*1/3)], [0, -(length*2/10)],[(width*3/4), -(length*1/3)], [0, -(length*8/10)],[0, -length],[(width*1/4), -length],[(width*1/4), -(length)*9/10],[width, -(length)*1/3]];
    var result = new [coords.size()];
    var centerX = x;
    var centerY = y;
    var cos = Math.cos(angle);
    var sin = Math.sin(angle);

    for (var i = 0; i < coords.size(); i += 1)
    {
    var x = (coords[0] * cos) - (coords[1] * sin);
    var y = (coords[0] * sin) + (coords[1] * cos);
    result= [ centerX+x, centerY+y];
    }
    dc.fillPolygon(result);
    }


    function drawSecHand(dc, x, y, angle, length, width)
    {
    var coords = [[width, 0], [width, -length], [-width, -length], [-width, 0]];
    var result = new [coords.size()];
    var centerX = x;
    var centerY = y;
    var cos = Math.cos(angle);
    var sin = Math.sin(angle);

    for (var i = 0; i < coords.size(); i += 1)
    {
    var x = (coords[0] * cos) - (coords[1] * sin);
    var y = (coords[0] * sin) + (coords[1] * cos);
    result= [ centerX+x, centerY+y];
    }
    dc.fillPolygon(result);
    }

    function onExitSleep() {
    cinnost = true;
    Ui.requestUpdate();
    }

    function onEnterSleep() {
    cinnost = false;
    Ui.requestUpdate();
    }

    }[/CODE]
  • Former Member
    Former Member over 9 years ago
    Alright

    Thanks. Will try this code. Other suggestions are always welcome.
  • Former Member
    Former Member over 9 years ago
    Would it actually be beneficial to pre-calculate all coordinates? So, trade-off math calculations by storage. After all, there are only 60 (discrete) seconds in a minute for example, and they're the same for every minute ever.

    If so, what would be a good place to do the pre-calculations? The constructor? Or even outside the watchface?

    As both processing power (battery power) and storage are limited in the IQ devices, I'm not sure if this trade-off is feasible.

    PS When is the constructor of a watchface called anyway?
  • Would it actually be beneficial to pre-calculate all coordinates? So, trade-off math calculations by storage.

    IMO, this doesn't make sense. A clock hand is a Lang.Array of at least 4 data points. Each data point is a Lang.Array of two Lang.Number objects. Based on some numbers I gathered a while ago (see here) each Lang.Array is ~57 bytes, and each Lang.Number is 4. So (5 * 57) + ((4 * 2) * 4) = 317 bytes. To store the transformed coordinate data for 60 positions of a watch hand, you'd need over 18K. If you have three different watch hands, you're looking at over 50K. There are a lot better things you can do with that storage. I suppose you may be able to put them into the object store and fetch them as necessary. I'm not sure if that would work or not, but it is a possibility.

    The code shown above only needs to transform the points for each watch hand every second at most and typically once a minute. That is 3x (1xsin, 1xcos, 4x multiplies, and 2 adds, plus allocation/deallocation for the temporary buffer which could be optimized away).

    PS When is the constructor of a watchface called anyway?

    The initialize() function of the derived AppBase is called when the system instantiates your derived class. From there, the app object functions are called as described in the AppBase documentation.

    When you create your derived View or WatchFace, the initialize() function will be called. After you've returned that view from App.getInitialView() the functions of the View will be called as described in the View documentation.

    Travis
  • Former Member
    Former Member over 9 years ago
    Thank you very much for your elaborate reply.

    Not that it is very pertinent but even based on the documentation I still don't understand what the lifecycle is of a watchface object. For example, if the user switches into a widget is the watchface object completely destroyed? And constructed again when the user switches into a watchface? Or does the object remain, but simply not in running an event handling routine?
  • I don't actually know, but I'm betting that the watch face is re-created every time it becomes active (like a widget). If you wanted to investigate, you could add tracing to one of the sample watch faces and then look in the log file.
  • Is there a way to draw a rounded edge hand?

    I´m only able to create a sharp edge or a pointed end.
    In the Garmin Analog Watchface are rounded edge hands, so it must be possible, but how?

    Thanke you for your help!