using Toybox.Activity as Act; using Toybox.ActivityMonitor as Amon; using Toybox.Graphics as Gfx; using Toybox.Lang; using Toybox.Math; using Toybox.System as Sys; using Toybox.Time.Gregorian as Cal; using Toybox.WatchUi as Ui; using C as C; // // Crucial life cycle changes that aren't well documented. // // There are two main modes of operation: // // 1) As a watch that you look at periodically // // 1) onEnterSleep - stop looking and the screen goes to sleep // 2) onExitSleep - look at watch and the screen wakes up // // 2) When you launch an Activity/App/Widget // // 1) onExitSleep() // 2) onHide() // ... // 3) initialize() // 4) onLayout() // 5) onShow() // 6) onEnterSleep() // class FacadeView extends Ui.WatchFace { // // Classes to handle the hands // private var hr_hand; private var min_hand; private var sec_hand; private var hr2_hand; // // Classes for hash marks around the dial // // // partial updates // private var partial; private var bitm; private var sec_clip; private var cur_min = -1; // optimization for slow watches with partial updates // // Class for the moon phase calculator // private var moon; // // Day abbreviations (3 characters from the standard API is too wide // The day_of_week variable is 1 based so add an empty value at the front // private var days = ["", "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; // // Deug // private var sec_count = 0; function initialize() { partial = (WatchUi.WatchFace has :onPartialUpdate); C.logger("initialize():" + partial); set_days(); WatchFace.initialize(); } // Load your resources here function onLayout(dc) { C.logger("onLayout()"); C.center_x = dc.getWidth() / 2; C.center_y = dc.getHeight() / 2; C.radius = (C.center_x > C.center_y) ? C.center_y : C.center_x; C.dot_sz = C.radius * 0.05; C.logger(dc.getWidth() + "x" + dc.getHeight() + " => " + C.radius); var r10 = C.radius * 0.1; hr_hand = new Hand(C.radius * 0.6, r10/2, C.alpha(C.WHITE, 0xe0)); min_hand = new Hand(C.radius * 0.95, r10/2, C.alpha(C.WHITE, 0xe0)); sec_hand = new Hand(C.radius * 0.95, 2, C.alpha(C.RED, 0xff)); hr2_hand = new HandHr2(C.radius, r10); moon = new Moon(); if (Gfx has :createBufferedBitmap) { bitm = Gfx.createBufferedBitmap({:width=>dc.getWidth(), :height=>dc.getHeight()}).get(); } else if (Gfx has :BufferedBitmap) { bitm = new Gfx.BufferedBitmap({:width=>dc.getWidth(), :height=>dc.getHeight()}); } else { bitm = null; } } // 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() as Void { C.logger("onShow():" + (bitm != null)); C.settings(); if (bitm != null) { min_update(C.get_now(), bitm.getDc()); } C.pr_mem("setup done"); } // Update the view function onUpdate(dc) { var t = C.get_now(); if (bitm != null) { dc.clearClip(); dc.drawBitmap(0, 0, bitm); sec_update(t, dc); if ((t.value() % 60) == 59) { min_update(new Time.Moment(t.value() + 1), bitm.getDc()); } } else { min_update(t, dc); sec_update(t, dc); } } function onPartialUpdate(dc) { var t = C.get_now(); sec_count += 1; sec_update(t, dc); if ((t.value() % 60) == 59) { onUpdate(dc); } } // Called when this View is removed from the screen. Save the // state of this View here. This includes freeing resources from // memory. function onHide() as Void { C.logger("onHide()"); } // The user has just looked at their watch. Timers and animations may be started here. // // The screen just went from dark to on. // function onExitSleep() as Void { C.logger("onExitSleep()"); if (bitm != null) { min_update(C.get_now(), bitm.getDc()); } } // Terminate any active timers and prepare for slow updates. // // The watch just went from on to dark // function onEnterSleep() as Void { C.logger("onEnterSleep()"); cur_min = -1; } function sh_quad(g, type, dc, now) { var x = 0; var y = 0; switch (g) { case C.GRAVITY_UL: x = C.center_x * 0.55; y = C.center_y * 0.55; break; case C.GRAVITY_UR: x = C.center_x * 1.45; y = C.center_y * 0.55; break; case C.GRAVITY_LR: x = C.center_x * 1.45; y = C.center_y * 1.45; break; case C.GRAVITY_LL: x = C.center_x * 0.55; y = C.center_y * 1.45; break; } switch (type) { default: case C.DATA_NONE: // nothing break; case C.DATA_BATTERY: // battery sh_battery(x, y, g, dc); break; case C.DATA_HEART: // heart rate var ai = Act.getActivityInfo(); var hr = ((ai == null) ? null : ai.currentHeartRate); sh_info(n2comma(Act.getActivityInfo().currentHeartRate), Rez.Drawables.heart, C.WHITE, x, y, dc); break; case C.DATA_MOON: // moon phase moon.show(now, x, y, dc); break; case C.DATA_STEPS: // steps sh_info(n2comma(Amon.getInfo().steps), Rez.Drawables.steps, C.WHITE, x, y, dc); break; case C.DATA_CALORIES: // calories sh_info(n2comma(Amon.getInfo().calories), Rez.Drawables.calories, C.WHITE, x, y, dc); break; case C.DATA_DIST: // distance in kilometers var d = Amon.getInfo().distance; if (d != null) { d /= 100; } sh_info(n2dec(d), Rez.Drawables.distance, C.WHITE, x, y, dc); break; case C.DATA_ASCENT: // ascent in meters sh_info(n2comma(Amon.getInfo().metersClimbed), Rez.Drawables.ascent, C.WHITE, x, y, dc); break; case C.DATA_ACTIVE: // active minutes var act = Amon.getInfo().activeMinutesDay; if (act != null) { act = act.moderate + act.vigorous; } sh_info(n2comma(act), Rez.Drawables.active, C.WHITE, x, y, dc); break; case C.DATA_RESP: // respiration in breaths per min sh_info(n2comma(Amon.getInfo().respirationRate), Rez.Drawables.resp, C.WHITE, x, y, dc); break; case C.DATA_TEMP: // tempearture (cached data) var t = C.get_temp(now); sh_info(t[0], t[1], t[2], x, y, dc); break; } } function min_update(t, dc) { // Get and show the current time var time = C.time_adj(t, C.tz1_info); //C.logger("sh_days:" + time.hour + ":" + time.info.min + " " + time.info.day + ", " + time.info.day_of_week); var time2 = (C.tz2 == 0) ? time : C.time_adj(t, C.tz2_info); if (time.info.sec == 0) { C.do_dbg(partial, bitm != null, sec_count); sec_count = 0; } dc.setColor(C.WHITE, C.BLACK); dc.clear(); dc.setColor(C.WHITE, C.TRANSPARENT); sh_dial(dc); sh_days(dc, time); sh_quad(C.GRAVITY_UL, C.quad_ul, dc, t); sh_quad(C.GRAVITY_UR, C.quad_ur, dc, t); sh_quad(C.GRAVITY_LR, C.quad_lr, dc, t); sh_quad(C.GRAVITY_LL, C.quad_ll, dc, t); hr2_hand.show(dc, time2); dc.setColor(C.WHITE, C.TRANSPARENT); hr_hand.show(dc, time.hmins, 12 * 60, ((C.tz1 != 0) ? time.abbrev : null), false); sh_ampm(dc, time.pm); min_hand.show(dc, time.info.min, 60, null, false); hr2_hand.show_min(dc, time, time2); } function sec_update(t, dc) { if (bitm != null) { dc.drawBitmap(0, 0, bitm); dc.setColor(C.WHITE, C.TRANSPARENT); dc.fillCircle(C.center_x, C.center_y, C.dot_sz); } sec_hand.show(dc, t.value() % 60, 60, null, true); dc.setColor(C.WHITE, C.TRANSPARENT); dc.fillCircle(C.center_x, C.center_y, C.dot_sz); } function sh_dial(dc) { var r10 = C.radius * 0.1; var big_hash = new Hash(C.radius, r10, 6); var sml_hash = new Hash(C.radius, r10/2, 2); for (var i = 0; i < 60; i += 1) { if ((i % 5) == 0) { big_hash.show(dc, i, 60); } else { sml_hash.show(dc, i, 60); } } var id = big_hash.start_x(); dc.drawText(C.center_x, C.center_y - id, Gfx.FONT_XTINY, "12", Gfx.TEXT_JUSTIFY_CENTER); } function sh_ampm(dc, pm) { switch (C.ampm) { case 0: // no indicator return; case 2: // AM or PM as appropriate if (!pm) { dc.setColor(C.WHITE, C.TRANSPARENT); dc.drawText(C.center_x, C.center_y + C.dot_sz, Gfx.FONT_XTINY, "AM", Gfx.TEXT_JUSTIFY_CENTER); return; } // fall thru case 1: // PM only if (pm) { dc.setColor(C.WHITE, C.TRANSPARENT); dc.drawText(C.center_x, C.center_y + C.dot_sz, Gfx.FONT_XTINY, "PM", Gfx.TEXT_JUSTIFY_CENTER); } break; } } function set_days() { days[1] = WatchUi.loadResource(Rez.Strings.Sunday); days[2] = WatchUi.loadResource(Rez.Strings.Monday); days[3] = WatchUi.loadResource(Rez.Strings.Tuesday); days[4] = WatchUi.loadResource(Rez.Strings.Wednesday); days[5] = WatchUi.loadResource(Rez.Strings.Thursday); days[6] = WatchUi.loadResource(Rez.Strings.Friday); days[7] = WatchUi.loadResource(Rez.Strings.Saturday); } function sh_days(dc, time) { var dw = days[time.info.day_of_week]; dc.setColor(C.WHITE, C.TRANSPARENT); dc.drawText(C.center_x * 0.3, C.center_y, Gfx.FONT_TINY, dw, Gfx.TEXT_JUSTIFY_LEFT | Gfx.TEXT_JUSTIFY_VCENTER); var x = C.center_x + (C.center_x * 0.7); var y = C.center_y; dc.drawText(C.center_x + (C.center_x * 0.7), C.center_y, Gfx.FONT_TINY, time.info.day, Gfx.TEXT_JUSTIFY_RIGHT | Gfx.TEXT_JUSTIFY_VCENTER); var dim = dc.getTextDimensions("88", Gfx.FONT_TINY); var w = dim[0]; var h = dim[1]; dc.setPenWidth(1); dc.drawRectangle(x - w - 2, y - h/2, w + 6, h); } function sh_battery(x, y, g, dc) { var color, icon; var lvl = Sys.getSystemStats().battery.toNumber(); if (lvl >= 50) { color = C.GREEN; icon = Rez.Drawables.battery100; } else if (lvl >= 25) { color = C.YELLOW; icon = Rez.Drawables.battery050; } else { color = C.RED; icon = Rez.Drawables.battery000; } var dim = dc.getTextDimensions("100", Gfx.FONT_XTINY); var w = dim[0]; var h = dim[1]; var r = Math.sqrt(w*w + h*h) / 2; var a = (360 * lvl) / 100; a = 360 - a + 90; dc.setColor(color, C.TRANSPARENT); dc.drawText(x, y - (h/2), Gfx.FONT_XTINY, lvl, Gfx.TEXT_JUSTIFY_CENTER); dc.setPenWidth(6); dc.setColor(color, C.TRANSPARENT); dc.drawArc(x, y, r, Gfx.ARC_CLOCKWISE, 90, a); var x_off = 0; var y_off = 0; var off = r / 1.4; // // Note the magic 24 in the following calculations. The // battery icon is 24x24 and these magic numbers move the // icon to it proper 0x0 position. Change the size of the // icon => make sure you change the magic number // switch (g) { case C.GRAVITY_UL: x_off = off; y_off = off; break; case C.GRAVITY_UR: x_off = -off - 24; y_off = off; break; case C.GRAVITY_LR: x_off = -off - 24; y_off = -off - 24; break; case C.GRAVITY_LL: x_off = off; y_off = -off - 24; break; } var bits = new Ui.Bitmap({ :rezId => icon, :locX => x + x_off, :locY => y + y_off }); bits.draw(dc); } function n2comma(n) { if (n == null) { return "--"; } if (n < 1000) { return n.format("%d"); } var th = n % 1000; n /= 1000; return n.format("%d") + "," + th.format("%03d"); } function n2dec(n) { if (n == null) { return "--"; } var th = n % 1000; n /= 1000; return n.format("%d") + "." + th.format("%03d"); } function sh_info(data, icon, c, x, y, dc) { var fh = dc.getFontHeight(Gfx.FONT_TINY); dc.setColor(c, C.TRANSPARENT); dc.drawText(x, y - fh, Gfx.FONT_TINY, data, Gfx.TEXT_JUSTIFY_CENTER); var h = dc.getFontHeight(Gfx.FONT_TINY); var bits = new Ui.Bitmap({ :rezId => icon, :locX => x, :locY => y }); bits.draw(dc); } }