diff --git a/apps/run/ChangeLog b/apps/run/ChangeLog index 44c391d958..81678d788c 100644 --- a/apps/run/ChangeLog +++ b/apps/run/ChangeLog @@ -13,5 +13,5 @@ 0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11 0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643) 0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working -0.15: (beta) Swipe to intensity interface a la Korvonnen (curtesy of +0.15: (beta) Swipe to intensity interface a la Karvonnen (curtesy of FTeacher at https://github.com/f-teacher) diff --git a/apps/run/karvonnen.js b/apps/run/karvonnen.js index 9f45f67b9e..4758d30938 100644 --- a/apps/run/karvonnen.js +++ b/apps/run/karvonnen.js @@ -1,20 +1,27 @@ -// Korvonnen pasted inside a function exports.show = function karvonnen(hrmSettings, exsHrmStats) { //This app is an extra feature implementation for the Run.app of the bangle.js. It's called run+ //The calculation of the Heart Rate Zones is based on the Karvonnen method. It requires to know maximum and minimum heart rates. More precise calculation methods require a lab. //Other methods are even more approximative. - wu = require("widget_utils"); + let wu = require("widget_utils"); wu.hide(); let R = Bangle.appRect; + - g.reset().clearRect(R); + g.reset().clearRect(R).setFontAlign(0,0,0); + + const x = "x"; const y = "y"; + function Rdiv(xOrY, divisor) { // Used when placing things on the screen + return xOrY=="x" ? (R.x + (R.w-1)/divisor):(R.y + (R.h-1)/divisor); + } function drawLines() { - g.drawLine(40,64,88,52,136,64); - g.drawLine(88,52,136,64); - g.drawLine(40,112,88,124); - g.drawLine(88,124,132,112); - g.setFont("Vector",20); + g.setColor(g.theme.fg); + // Upper + g.drawLine(Rdiv(x,175/40), Rdiv(y,175/64), Rdiv(x,2), Rdiv(y,175/52)); + g.drawLine(Rdiv(x,2), Rdiv(y,175/52), Rdiv(x,175/135), Rdiv(y,175/64)); + // Lower + g.drawLine(Rdiv(x,175/40), Rdiv(y,175/110), Rdiv(x,2), Rdiv(y,175/122)); + g.drawLine(Rdiv(x,2), Rdiv(y,175/122), Rdiv(x,175/135), Rdiv(y,175/110)); } //To calculate Heart rate zones, we need to know the heart rate reserve (HRR) @@ -42,26 +49,44 @@ exports.show = function karvonnen(hrmSettings, exsHrmStats) { let maxzone3 = hrr * 0.8 + minhr; let maxzone4 = hrr * 0.9 + minhr; let maxzone5 = hrr * 0.99 + minhr; - + // HR data: large, readable, in the middle of the screen function drawHR() { - g.clearRect(62,66,62+90,70+40); - g.setColor(1,1,1); + g.setFontAlign(-1,0,0); + g.clearRect(Rdiv(x,11/4),Rdiv(y,2)-25,Rdiv(x,11/4)+50*2,Rdiv(y,2)+25); + g.setColor(g.theme.fg); g.setFont("Vector",50); - g.drawString(hr, 62,66); + g.drawString(hr, Rdiv(x,11/4), Rdiv(y,2)+4); + } + + function drawWaitHR() { + g.setFontAlign(0,0,0); + //g.clearRect(62,66,62+90,70+40); + g.setColor(g.theme.fg); + g.setFont("Vector",50); + g.drawString("--", Rdiv(x,2), Rdiv(y,2)+4); + + // draw values of upper and lower limit of current zone + g.setFont("Vector",20); + g.setColor(g.theme.fg); + g.drawString("--", Rdiv(x,2), Rdiv(y,9/2)); + g.drawString("--", Rdiv(x,2), Rdiv(y,9/7)); + + g.setFont("Vector",24); + g.drawString("Z-", Rdiv(x,4.3), Rdiv(y,2)+2); } - drawHR(); + //These functions call arcs to show different HR zones. //To shorten the code, I'll reference some letiables and reuse them. - let centreX = R.x + 0.5 * R.w; //g.getWidth(); - let centreY = R.y + 0.5 * R.h; //g.getWidth(); - let minRadius = 0.38 * R.h; //g.getWidth(); - let maxRadius = 0.50 * R.h; //g.getWidth(); + let centreX = R.x + 0.5 * R.w; + let centreY = R.y + 0.5 * R.h; + let minRadius = 0.38 * R.h; + let maxRadius = 0.50 * R.h; //draw background image (dithered green zones)(I should draw different zones in different dithered colors) const HRzones= require("graphics_utils"); - let minRadiusz = 0.44 * R.h;//g.getWidth(); + let minRadiusz = 0.44 * R.h; let startAngle = HRzones.degreesToRadians(-88.5); let endAngle = HRzones.degreesToRadians(268.5); @@ -69,7 +94,6 @@ exports.show = function karvonnen(hrmSettings, exsHrmStats) { g.setColor("#002200"); HRzones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); } - drawBackGround(); const zones = require("graphics_utils"); //####### A function to simplify a bit the code ###### @@ -79,39 +103,42 @@ exports.show = function karvonnen(hrmSettings, exsHrmStats) { if (currentZone == lastZone) zones.fillArc(g, centreX, centreY, minRadius, maxRadius, startAngle, endAngle); else zones.fillArc(g, centreX, centreY, minRadiusz, maxRadius, startAngle, endAngle); g.setFont("Vector",24); - g.clearRect(29,80,29+26,80+24); - g.drawString(Z, 29,80); + g.clearRect(Rdiv(x,4.3)-12, Rdiv(y,2)+2-12,Rdiv(x,4.3)+12, Rdiv(y,2)+2+12); + g.setFontAlign(0,0,0); + g.drawString(Z, Rdiv(x,4.3), Rdiv(y,2)+2); } - //####### A function to simplify next&previous zones ###### - function zoning (max, min) { + function zoning (max, min) { // draw values of upper and lower limit of current zone g.setFont("Vector",20); - g.setColor(1,1,1); - g.clearRect(56,28,56+20*3,28+20).clearRect(60,128,60+20*3,128+20); - g.drawString(max, 56,28);g.drawString(min, 60,128); + g.setColor(g.theme.fg); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/2)-10,Rdiv(x,2)+20*2, Rdiv(y,9/2)+10); + g.clearRect(Rdiv(x,2)-20*2, Rdiv(y,9/7)-10,Rdiv(x,2)+20*2, Rdiv(y,9/7)+10); + g.setFontAlign(0,0,0); + g.drawString(max, Rdiv(x,2), Rdiv(y,9/2)); + g.drawString(min, Rdiv(x,2), Rdiv(y,9/7)); } - function clearInnerRing() { - g.setColor(0,0,0); + function clearCurrentZone() { // Clears the extension of the current zone by painting the extension area in background color + g.setColor(g.theme.bg); HRzones.fillArc(g, centreX, centreY, minRadius-1, minRadiusz, startAngle, endAngle); } function getZone(zone) { drawBackGround(); - clearInnerRing(); - if (zone >= 0) {g.setColor("#00ffff");{(simplify(-88.5, -45, "Z1", 0, zone));}zoning(minzone2, minhr);} - if (zone >= 1) {g.setColor("#00ff00");{(simplify(-43.5, -21.5, "Z2", 1, zone));}zoning(maxzone2, minzone2);} - if (zone >= 2) {g.setColor("#00ff00");{(simplify(-20, 1.5, "Z2", 2, zone));}zoning(maxzone2, minzone2);} - if (zone >= 3) {g.setColor("#00ff00");{(simplify(3, 24, "Z2", 3, zone));}zoning(maxzone2, minzone2);} - if (zone >= 4) {g.setColor("#ffff00");{(simplify(25.5, 46.5, "Z3", 4, zone));}zoning(maxzone3, maxzone2);} - if (zone >= 5) {g.setColor("#ffff00");{(simplify(48, 69, "Z3", 5, zone));}zoning(maxzone3, maxzone2);} - if (zone >= 6) {g.setColor("#ffff00");{(simplify(70.5, 91.5, "Z3", 6, zone));}zoning(maxzone3, maxzone2);} - if (zone >= 7) {g.setColor("#ff8000");{(simplify(93, 114.5, "Z4", 7, zone));}zoning(maxzone4, maxzone3);} - if (zone >= 8) {g.setColor("#ff8000");{(simplify(116, 137.5, "Z4", 8, zone));}zoning(maxzone4, maxzone3);} - if (zone >= 9) {g.setColor("#ff8000");{(simplify(139, 160, "Z4", 9, zone));}zoning(maxzone4, maxzone3);} - if (zone >= 10) {g.setColor("#ff0000");{(simplify(161.5, 182.5, "Z5", 10, zone));}zoning(maxzone5, maxzone4);} - if (zone >= 11) {g.setColor("#ff0000");{(simplify(184, 205, "Z5", 11, zone));}zoning(maxzone5, maxzone4);} - if (zone == 12) {g.setColor("#ff0000");{(simplify(206.5, 227.5, "Z5", 12, zone));}zoning(maxzone5, maxzone4);} + clearCurrentZone(); + if (zone >= 0) {zoning(minzone2, minhr);g.setColor("#00ffff");simplify(-88.5, -45, "Z1", 0, zone);} + if (zone >= 1) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-43.5, -21.5, "Z2", 1, zone);} + if (zone >= 2) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(-20, 1.5, "Z2", 2, zone);} + if (zone >= 3) {zoning(maxzone2, minzone2);g.setColor("#00ff00");simplify(3, 24, "Z2", 3, zone);} + if (zone >= 4) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(25.5, 46.5, "Z3", 4, zone);} + if (zone >= 5) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(48, 69, "Z3", 5, zone);} + if (zone >= 6) {zoning(maxzone3, maxzone2);g.setColor("#ffff00");simplify(70.5, 91.5, "Z3", 6, zone);} + if (zone >= 7) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(93, 114.5, "Z4", 7, zone);} + if (zone >= 8) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(116, 137.5, "Z4", 8, zone);} + if (zone >= 9) {zoning(maxzone4, maxzone3);g.setColor("#ff8000");simplify(139, 160, "Z4", 9, zone);} + if (zone >= 10) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(161.5, 182.5, "Z5", 10, zone);} + if (zone >= 11) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(184, 205, "Z5", 11, zone);} + if (zone == 12) {zoning(maxzone5, maxzone4);g.setColor("#ff0000");simplify(206.5, 227.5, "Z5", 12, zone);} } function getZoneAlert() { @@ -128,27 +155,32 @@ exports.show = function karvonnen(hrmSettings, exsHrmStats) { //Subdivided zones for better readability of zones when calling the images. //Changing HR zones will trigger the function with the image and previous&next HR zones. let subZoneLast; function drawZones() { - if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawLines(); drawHR();} - - if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} - else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} - else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} - else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} - else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} - else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} - else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} - else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} - else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} - else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} - else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} - else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} - else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} - else if (hr >= maxhr - 2) {if (subZoneLast!=13) {subZoneLast=13; g.clear();getZoneAlert();}} + if ((hr < maxhr - 2) && subZoneLast==13) {g.clear(); drawLines(); drawHR();} // Reset UI when coming down from zone alert. + if (hr <= hrr * 0.6 + minhr) {if (subZoneLast!=0) {subZoneLast=0; getZone(subZoneLast);}} // Z1 + else if (hr <= hrr * 0.64 + minhr) {if (subZoneLast!=1) {subZoneLast=1; getZone(subZoneLast);}} // Z2a + else if (hr <= hrr * 0.67 + minhr) {if (subZoneLast!=2) {subZoneLast=2; getZone(subZoneLast);}} // Z2b + else if (hr <= hrr * 0.70 + minhr) {if (subZoneLast!=3) {subZoneLast=3; getZone(subZoneLast);}} // Z2c + else if (hr <= hrr * 0.74 + minhr) {if (subZoneLast!=4) {subZoneLast=4; getZone(subZoneLast);}} // Z3a + else if (hr <= hrr * 0.77 + minhr) {if (subZoneLast!=5) {subZoneLast=5; getZone(subZoneLast);}} // Z3b + else if (hr <= hrr * 0.80 + minhr) {if (subZoneLast!=6) {subZoneLast=6; getZone(subZoneLast);}} // Z3c + else if (hr <= hrr * 0.84 + minhr) {if (subZoneLast!=7) {subZoneLast=7; getZone(subZoneLast);}} // Z4a + else if (hr <= hrr * 0.87 + minhr) {if (subZoneLast!=8) {subZoneLast=8; getZone(subZoneLast);}} // Z4b + else if (hr <= hrr * 0.90 + minhr) {if (subZoneLast!=9) {subZoneLast=9; getZone(subZoneLast);}} // Z4c + else if (hr <= hrr * 0.94 + minhr) {if (subZoneLast!=10) {subZoneLast=10; getZone(subZoneLast);}} // Z5a + else if (hr <= hrr * 0.96 + minhr) {if (subZoneLast!=11) {subZoneLast=11; getZone(subZoneLast);}} // Z5b + else if (hr <= hrr * 0.98 + minhr) {if (subZoneLast!=12) {subZoneLast=12; getZone(subZoneLast);}} // Z5c + else if (hr >= maxhr - 2) {subZoneLast=13; g.clear();getZoneAlert();} // Alert } - drawZones(); + function initDraw() { + drawLines(); + drawWaitHR(); + drawBackGround(); + //drawZones(); + } + let hrLast; - function updateUI() { + function updateUI() { // Update UI, only draw if warranted by change in HR hrLast = hr; hr = exsHrmStats.getValue(); if (hr!=hrLast) { @@ -156,11 +188,13 @@ exports.show = function karvonnen(hrmSettings, exsHrmStats) { drawZones(); } } - updateUI(); - - karvonnenInterval = setInterval(function() { - if (!isMenuDisplayed && karvonnenActive) updateUI(); - }, 1000); + initDraw(); + + // check for updates every second. + karvonnenInterval = setInterval(function() { + if (!isMenuDisplayed && karvonnenActive) updateUI(); + }, 1000); + return karvonnenInterval; };