A simple analog watch face for the Garmin Forerunner 55.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

548 lines
15 KiB

using Toybox.Graphics as Gfx;
using Toybox.System as Sys;
using Toybox.Lang as Lang;
using Toybox.Math as Math;
using Toybox.Time as Time;
using Toybox.Time.Gregorian as Calendar;
using Toybox.WatchUi as Ui;
using Toybox.Application as App;
using Toybox.ActivityMonitor as ActMon;
using Toybox.SensorHistory as Sensor;
using Toybox.Activity as Activity;
enum
{
LAT,
LON
}
class SnapshotWatchView extends Ui.WatchFace {
var usePreferences = true;
var showHeartRate = true;
var showDigitalTime = false;
var digitalTimeOffset = 0;
var showSeconds = true;
var background_color = Gfx.COLOR_BLACK;
var width_screen, height_screen;
var hashMarksArray = new [60];
//! Load your resources here
function onLayout(dc) {
//get screen dimensions
width_screen = dc.getWidth();
height_screen = dc.getHeight();
//get hash marks position
for(var i = 0; i < 60; i+=1)
{
hashMarksArray[i] = new [2];
hashMarksArray[i][0] = (i / 60.0) * Math.PI * 2;
if(i != 0 && i != 15 && i != 30 && i != 45)
{
hashMarksArray[i][1] = -85;
}
else
{
hashMarksArray[i][1] = -67;
}
}
setLayout(Rez.Layouts.WatchFace(dc));
}
//! Restore the state of the app and prepare the view to be shown
function onShow() {
}
//! Update the view
function onUpdate(dc) {
if (usePreferences) {
showHeartRate = Application.getApp().getProperty("showHeartRate");
showDigitalTime = Application.getApp().getProperty("showDigitalTime");
digitalTimeOffset = Application.getApp().getProperty("digitalTimeOffset");
}
if (showDigitalTime)
{
if (digitalTimeOffset != null && digitalTimeOffset <= 24 && digitalTimeOffset >= -24) {
digitalTimeOffset = digitalTimeOffset.toNumber();
}
else
{
showDigitalTime = false;
digitalTimeOffset = 0;
}
}
var clockTime = Sys.getClockTime();
// Clear screen
dc.setColor(background_color, Gfx.COLOR_WHITE);
// dc.setColor(Gfx.COLOR_DK_GRAY, Gfx.COLOR_DK_GRAY);
dc.fillRectangle(0,0, width_screen, height_screen);
var heartNow = 0;
var heartMin = 0;
var heartMax = 0;
if (showHeartRate)
{
// Plot heart rate graph
var sample = Sensor.getHeartRateHistory( {:order=>Sensor.ORDER_NEWEST_FIRST} );
if (sample != null)
{
if (sample.getMin() != null)
{ heartMin = sample.getMin(); }
if (sample.getMax() != null)
{ heartMax = sample.getMax(); }
var heart = sample.next();
if (heart.data != null)
{ heartNow = heart.data; }
dc.setColor(Gfx.COLOR_DK_GREEN, Gfx.COLOR_TRANSPARENT);
var maxSecs = 14355;//14400; //4 hours
var totHeight = 44;
var totWidth = 165;
var binPixels = 1;
var totBins = Math.ceil(totWidth / binPixels).toNumber();
var binWidthSecs = Math.floor(binPixels * maxSecs / totWidth).toNumber();
var heartSecs;
var heartValue = 0;
var secsBin = 0;
var lastHeartSecs = sample.getNewestSampleTime().value();
var heartBinMax;
var heartBinMin;
var finished = false;
for (var i = 0; i < totBins; ++i) {
heartBinMax = 0;
heartBinMin = 0;
if (!finished)
{
//deal with carryover values
if (secsBin > 0 && heartValue != null)
{
heartBinMax = heartValue;
heartBinMin = heartValue;
}
//deal with new values in this bin
while (!finished && secsBin < binWidthSecs)
{
heart = sample.next();
if (heart != null)
{
heartValue = heart.data;
if (heartValue != null)
{
if (heartBinMax == 0)
{
heartBinMax = heartValue;
heartBinMin = heartValue;
}
else
{
if (heartValue > heartBinMax)
{ heartBinMax = heartValue; }
if (heartValue < heartBinMin)
{ heartBinMin = heartValue; }
}
}
// keep track of time in this bin
heartSecs = lastHeartSecs - heart.when.value();
lastHeartSecs = heart.when.value();
secsBin += heartSecs;
// Sys.println(i + ": " + heartValue + " " + heartSecs + " " + secsBin + " " + heartBinMin + " " + heartBinMax);
}
else
{
finished = true;
}
} // while secsBin < binWidthSecs
if (secsBin >= binWidthSecs)
{ secsBin -= binWidthSecs; }
// only plot bar if we have valid values
if (heartBinMax > 0 && heartBinMax >= heartBinMin)
{
var height = ((heartBinMax+heartBinMin)/2-heartMin*0.9) / (heartMax-heartMin*0.9) * totHeight;
var xVal = (width_screen-totWidth)/2 + totWidth - i*binPixels -2;
var yVal = height_screen/2+28 + totHeight - height;
dc.fillRectangle(xVal, yVal, binPixels, height);
// Sys.println(i + ": " + binWidthSecs + " " + secsBin + " " + heartBinMin + " " + heartBinMax);
}
} // if !finished
} // loop over all bins
} // if sample != null
}
// First draw hash marks the analogue time hands
drawHashMarks(dc);
drawHands(dc, clockTime.hour, clockTime.min, clockTime.sec);
if (showHeartRate)
{
// Now show HR information (calculated above)
dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
if (heartNow == 0)
{ dc.drawText(width_screen/2, height_screen/2 + 20, Gfx.FONT_SMALL, "-- bpm", Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER); }
else
{ dc.drawText(width_screen/2, height_screen/2 + 20, Gfx.FONT_SMALL, Lang.format("$1$ bpm", [heartNow]), Graphics.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER); }
var heartMinMaxString;
if (heartMin == 0 || heartMax == 0)
{ heartMinMaxString = "-- / -- bpm"; }
else
{ heartMinMaxString = Lang.format("$1$ / $2$ bpm", [heartMin, heartMax]); }
dc.drawText(width_screen/2, height_screen - 19, Gfx.FONT_SMALL, heartMinMaxString, Graphics.TEXT_JUSTIFY_CENTER);
}
// Date
dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
drawDate(dc);
// Digital time
if (showDigitalTime)
{
dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
drawDigitalTime(dc, clockTime);
}
// BT, alarm, notification, and do not disturb icons
if (Sys.getDeviceSettings().phoneConnected)
{
dc.drawBitmap(39, 6, Ui.loadResource(Rez.Drawables.BluetoothIcon));
}
if (Sys.getDeviceSettings().alarmCount > 0)
{
dc.drawBitmap(25, 23, Ui.loadResource(Rez.Drawables.AlarmIcon));
}
if (Sys.getDeviceSettings().doNotDisturb)
{
dc.drawBitmap(10, 49, Ui.loadResource(Rez.Drawables.MuteIcon));
}
if (Sys.getDeviceSettings().notificationCount > 0)
{
var offset = 0;
if (Sys.getDeviceSettings().notificationCount >= 10)
{
offset = 6;
}
dc.drawText(width_screen/2+16+offset, 7, Gfx.FONT_SMALL, Sys.getDeviceSettings().notificationCount, Graphics.TEXT_JUSTIFY_RIGHT|Graphics.TEXT_JUSTIFY_VCENTER);
dc.drawBitmap(width_screen/2+18+offset, 2, Ui.loadResource(Rez.Drawables.NotificationIcon));
}
// Battery
var systemStats = Sys.getSystemStats();
var battery = systemStats.battery;
var offset = 0;
if (battery == 100)
{ offset = 6; }
dc.drawText(width_screen/2-33-offset, 7, Gfx.FONT_SMALL, Lang.format("$1$%", [battery.format("%2d")]), Graphics.TEXT_JUSTIFY_LEFT|Graphics.TEXT_JUSTIFY_VCENTER);
// Steps
var stepsInfo = ActMon.getInfo();
var steps = stepsInfo.steps;
var goal = stepsInfo.stepGoal;
dc.drawText(width_screen-4, height_screen/2 - 14, Gfx.FONT_SMALL, steps, Graphics.TEXT_JUSTIFY_RIGHT|Graphics.TEXT_JUSTIFY_VCENTER);
dc.drawText(width_screen-4, height_screen/2 + 11, Gfx.FONT_SMALL, goal, Graphics.TEXT_JUSTIFY_RIGHT|Graphics.TEXT_JUSTIFY_VCENTER);
// Sunrise & sunset
dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
drawSunriseSunset(dc);
}
//! The user has just looked at their watch. Timers and animations may be started here.
function onExitSleep() {
showSeconds = true;
}
//! Terminate any active timers and prepare for slow updates.
function onEnterSleep() {
showSeconds = false;
requestUpdate();
}
//! Draw the watch hand
function drawHand(dc, angle, whichHand, width, handColour)
{
dc.setColor(handColour, Gfx.COLOR_TRANSPARENT);
var length, r1, r2, r3, r4, deflect1, deflect2;
var centerX = width_screen/2;
var centerY = height_screen/2;
if (whichHand == 0) //hour hand
{
length = 0.6*centerX;
r1 = 0.0*length;
r2 = 0.39*length;
r3 = 0.49*length;
r4 = 1.1*length;
deflect1 = 0.10*width;
deflect2 = 0.08*width;
}
else //minute hand
{
length = 1.0*centerX;
r1 = 0.0*length;
r2 = 0.37*length;
r3 = 0.47*length;
r4 = 1.2*length;
deflect1 = 0.10*width;
deflect2 = 0.08*width;
}
var coords = [
[centerX + r1 * Math.sin(angle) , centerY - r1 * Math.cos(angle)],
[centerX + r2 * Math.sin(angle+deflect1) , centerY - r2 * Math.cos(angle+deflect1)],
[centerX + r3 * Math.sin(angle+deflect2) , centerY - r3 * Math.cos(angle+deflect2)],
[centerX + r4 * Math.sin(angle) , centerY - r4 * Math.cos(angle)],
[centerX + r3 * Math.sin(angle-deflect2) , centerY - r3 * Math.cos(angle-deflect2)],
[centerX + r2 * Math.sin(angle-deflect1) , centerY - r2 * Math.cos(angle-deflect1)],
[centerX + r1 * Math.sin(angle) , centerY - r1 * Math.cos(angle)]
];
dc.fillPolygon(coords);
}
function drawHands(dc, clock_hour, clock_min, clock_sec)
{
var hour, min, sec;
// Draw the hour. Convert it to minutes and compute the angle.
hour = ( ( ( clock_hour % 12 ) * 60 ) + clock_min ); // hour = 2*60.0;
hour = hour / (12 * 60.0) * Math.PI * 2;
drawHand(dc, hour, 0, 2.0, Gfx.COLOR_DK_BLUE);
drawHand(dc, hour, 0, 1.6, Gfx.COLOR_LT_GRAY);
// Draw the minute
min = ( clock_min / 60.0); // min = 40/60.0;
min = min * Math.PI * 2;
drawHand(dc, min, 1, 1.2, Gfx.COLOR_DK_BLUE);
drawHand(dc, min, 1, 1.0, Gfx.COLOR_LT_GRAY);
// Draw the seconds (use hash graphic here)
if(showSeconds){
sec = ( clock_sec / 60.0) * Math.PI * 2;
drawHash(dc, sec, width_screen/2, 4, 25, Gfx.COLOR_DK_BLUE);
drawHash(dc, sec, width_screen/2, 2, 25, Gfx.COLOR_LT_GRAY);
}
// Draw the inner circle
dc.setColor(Gfx.COLOR_DK_GRAY, background_color);
dc.fillCircle(width_screen/2, height_screen/2, 6);
dc.setColor(background_color,background_color);
dc.drawCircle(width_screen/2, height_screen/2, 6);
dc.fillCircle(width_screen/2, height_screen/2, 2);
}
function drawHash(dc, angle, length, width, overheadLine, handColour)
{
dc.setColor(handColour, Gfx.COLOR_TRANSPARENT);
var centerX = width_screen/2;
var centerY = height_screen/2;
var result = new [4];
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var coords = [
[-(width/2), 0 + overheadLine],
[-(width/2), -length],
[width/2, -length],
[width/2, 0 + overheadLine]
];
for (var i = 0; i < 4; i += 1)
{
var x = (coords[i][0] * cos) - (coords[i][1] * sin);
var y = (coords[i][0] * sin) + (coords[i][1] * cos);
result[i] = [ centerX + x, centerY + y];
}
dc.fillPolygon(result);
}
//! Draw the hash mark symbols
function drawHashMarks(dc)
{
for(var i = 0; i < 60; i += 5)
{
if(i != 30)
{
if(i != 0 && i != 15 && i != 45)
{
drawHash(dc, hashMarksArray[i][0], 110, 3, hashMarksArray[i][1], Gfx.COLOR_WHITE);
} else {
drawHash(dc, hashMarksArray[i][0], 110, 5, hashMarksArray[i][1], Gfx.COLOR_WHITE);
}
}
if(!showHeartRate && i == 30)
{
drawHash(dc, hashMarksArray[i][0], 110, 5, hashMarksArray[i][1], Gfx.COLOR_WHITE);
}
}
}
function drawDate(dc)
{
var info = Calendar.info(Time.now(), Time.FORMAT_LONG);
var dateStr = Lang.format("$1$ $2$ $3$", [info.day_of_week, info.month, info.day]);
if (showDigitalTime)
{
dc.drawText(width_screen/2, height_screen/2 - 60, Gfx.FONT_MEDIUM, dateStr, Gfx.TEXT_JUSTIFY_CENTER);
}
else
{
dc.drawText(width_screen/2, height_screen/2 - 55, Gfx.FONT_MEDIUM, dateStr, Gfx.TEXT_JUSTIFY_CENTER);
}
}
function drawDigitalTime(dc, clockTime)
{
var offsetHour = clockTime.hour + digitalTimeOffset;
if (offsetHour > 23)
{
offsetHour -= 24;
}
else if (offsetHour < 0)
{
offsetHour += 24;
}
var ampm = "am";
if (offsetHour >= 12)
{
ampm = "pm";
}
var timeString = Lang.format("$1$:$2$$3$", [to12hourFormat(offsetHour), clockTime.min.format("%02d"), ampm]);
dc.drawText(width_screen/2, height_screen/2 - 30, Gfx.FONT_SMALL, timeString, Gfx.TEXT_JUSTIFY_CENTER|Graphics.TEXT_JUSTIFY_VCENTER);
}
function drawSunriseSunset(dc)
{
var sc = new SunCalc();
var lat;
var lon;
var loc = Activity.getActivityInfo().currentLocation;
if (loc == null)
{
lat = App.getApp().getProperty(LAT);
lon = App.getApp().getProperty(LON);
}
else
{
lat = loc.toDegrees()[0] * Math.PI / 180.0;
App.getApp().setProperty(LAT, lat);
lon = loc.toDegrees()[1] * Math.PI / 180.0;
App.getApp().setProperty(LON, lon);
}
// lat = -37.81400 * Math.PI / 180.0;
// lon = 144.96332 * Math.PI / 180.0;
if(lat != null && lon != null)
{
var ampm;
var timeString;
var now = new Time.Moment(Time.now().value());
var sunrise_moment = sc.calculate(now, lat.toDouble(), lon.toDouble(), SUNRISE);
var sunset_moment = sc.calculate(now, lat.toDouble(), lon.toDouble(), SUNSET);
var timeInfoSunrise = Calendar.info(sunrise_moment, Time.FORMAT_SHORT);
var timeInfoSunset = Calendar.info(sunset_moment, Time.FORMAT_SHORT);
ampm = "a";
if (timeInfoSunrise.hour >= 12)
{
ampm = "p";
}
timeString = Lang.format("$1$:$2$$3$", [to12hourFormat(timeInfoSunrise.hour), timeInfoSunrise.min.format("%02d"), ampm]);
dc.drawText(3, height_screen/2 - 14, Gfx.FONT_SMALL, timeString, Gfx.TEXT_JUSTIFY_LEFT|Graphics.TEXT_JUSTIFY_VCENTER);
ampm = "a";
if (timeInfoSunset.hour >= 12)
{
ampm = "p";
}
timeString = Lang.format("$1$:$2$$3$", [to12hourFormat(timeInfoSunset.hour), timeInfoSunset.min.format("%02d"), ampm]);
dc.drawText(3, height_screen/2 + 11, Gfx.FONT_SMALL, timeString, Gfx.TEXT_JUSTIFY_LEFT|Graphics.TEXT_JUSTIFY_VCENTER);
}
}
function to12hourFormat(hour)
{
var hour12 = hour % 12;
if (hour12 == 0) {
hour12 = 12;
}
return hour12;
}
}