From 4f957277fd2f37bfbe9e70aaaa5cbf4c59b9202f Mon Sep 17 00:00:00 2001 From: Sebastian Kauertz <70376899+skauertz@users.noreply.github.com> Date: Sat, 23 Jan 2021 13:47:38 +0100 Subject: [PATCH] Add mclockplus app --- apps.json | 15 +- apps/mclockplus/ChangeLog | 1 + apps/mclockplus/mclockplus-icon.js | 1 + apps/mclockplus/mclockplus.app.js | 318 +++++++++++++++++++++++++++++ apps/mclockplus/mclockplus.png | Bin 0 -> 1737 bytes 5 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 apps/mclockplus/ChangeLog create mode 100644 apps/mclockplus/mclockplus-icon.js create mode 100644 apps/mclockplus/mclockplus.app.js create mode 100644 apps/mclockplus/mclockplus.png diff --git a/apps.json b/apps.json index 9c09d6050..739a6943c 100644 --- a/apps.json +++ b/apps.json @@ -2635,5 +2635,18 @@ {"name":"gpsservice.wid.js","url":"widget.js"}, {"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true} ] -} +}, +{ "id": "mclockplus", + "name": "Morph Clock+", + "shortName":"Morph Clock+", + "icon": "mclockplus.png", + "version":"1.0", + "description": "Morphing Clock with more readable seconds and date and additional stopwatch", + "tags": "clock", + "type": "clock", + "storage": [ + {"name":"mclockplus.app.js","url":"mclockplus.app.js"}, + {"name":"mclockplus.img","url":"mclockplus-icon.js","evaluate":true} + ] +} ] diff --git a/apps/mclockplus/ChangeLog b/apps/mclockplus/ChangeLog new file mode 100644 index 000000000..835c33353 --- /dev/null +++ b/apps/mclockplus/ChangeLog @@ -0,0 +1 @@ +1.0: Created app diff --git a/apps/mclockplus/mclockplus-icon.js b/apps/mclockplus/mclockplus-icon.js new file mode 100644 index 000000000..41a59f503 --- /dev/null +++ b/apps/mclockplus/mclockplus-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwkEogA/AFGIAAQVVDKQWHDB1IC5OECx8z///mYYOBoWDCoIADnBJLFwQWGDAgwIEYU/CQXwh4EC+YwKBIOPFQYXE//4C5BGCIQgXF/5IILo4XGMIQXHLoYXIMIRGMC45IHC4KkGC45IBC4yNEC5KRBC7h2HC5B4GC5EggQXOBwvygEAl6QHC4sikRGEhGAJAgNBC75HIgZHNO48AgIJER54xCiYXKa5AxCGAjvPGA4XIwYXHbQs4C46QGGAbZDB4IXEPBQAEOwwXDJBJGEC4xILIxQwDSJCNDFwwXDMIh0ELoQXIJARhDC4hdCIw4wEDAQXDCwQuIGAgABmYXBmYHDFxIYGAAoWLJIgAGCxgYJCxwZGCqIA/AC4A=")) diff --git a/apps/mclockplus/mclockplus.app.js b/apps/mclockplus/mclockplus.app.js new file mode 100644 index 000000000..495e78f35 --- /dev/null +++ b/apps/mclockplus/mclockplus.app.js @@ -0,0 +1,318 @@ +// Morphing Clock + +// Modifies original Morphing Clock to make seconds and date more readable, and adds a simple stopwatch +// Icon by https://icons8.com +var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"]; +var locale = require("locale"); +var CHARW = 28; // how tall are digits? +var CHARP = 2; // how chunky are digits? +var Y = 50; // start height +// Offscreen buffer +var buf = Graphics.createArrayBuffer(CHARW+CHARP*2,CHARW*2 + CHARP*2,1,{msb:true}); +var bufimg = {width:buf.getWidth(),height:buf.getHeight(),buffer:buf.buffer}; +// The last time that we displayed +var lastTime = "-----"; +// If animating, this is the interval's id +var animInterval; +var timeInterval; +// Variables for the stopwatch +var counter = -1; // Counts seconds +var oldDate = new Date(2020,0,1); // Initialize to a past date +var swInterval; // The interval's id +var B3 = 0; // Flag to track BTN3's current function +var w1; // watch id for BTN1 +var w3; // watch id for BTN3 + +/* Get array of lines from digit d to d+1. + n is the amount (0..1) + maxFive is true is this digit only counts 0..5 */ +const DIGITS = { + " ":n=>[], + "0":n=>[ + [n,0,1,0], + [1,0,1,1], + [1,1,1,2], + [n,2,1,2], + [n,1,n,2], + [n,0,n,1]], + "1":n=>[ + [1-n,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1-n,1,1-n,2], + [1-n,2,1,2]], + "2":n=>[ + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [0,1+n,0,2], + [1,2-n,1,2], + [0,2,1,2]], + "3":n=>[ + [0,0,1-n,0], + [0,0,0,n], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [n,2,1,2]], + "4":n=>[ + [0,0,0,1], + [1,0,1-n,0], + [1,0,1,1-n], + [0,1,1,1], + [1,1,1,2], + [1-n,2,1,2]], + "5to0": n=>[ // 5 -> 0 + [0,0,0,1], + [0,0,1,0], + [n,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2,0,2], + [1,1-n,1,1], + [0,1,0,1+n]], + "5to6": n=>[ // 5 -> 6 + [0,0,0,1], + [0,0,1,0], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,2-n,0,2]], + "6":n=>[ + [0,0,0,1-n], + [0,0,1,0], + [n,1,1,1], + [1,1-n,1,1], + [1,1,1,2], + [n,2,1,2], + [0,1-n,0,2-2*n]], + "7":n=>[ + [0,0,0,n], + [0,0,1,0], + [1,0,1,1], + [1-n,1,1,1], + [1,1,1,2], + [1-n,2,1,2], + [1-n,1,1-n,2]], + "8":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1,1], + [1,1,1,2], + [0,2,1,2], + [0,1,0,2-n]], + "9":n=>[ + [0,0,0,1], + [0,0,1,0], + [1,0,1,1], + [0,1,1-n,1], + [0,1,0,1+n], + [1,1,1,2], + [0,2,1,2]], + ":":n=>[ + [0.4,0.4,0.6,0.4], + [0.6,0.4,0.6,0.6], + [0.6,0.6,0.4,0.6], + [0.4,0.4,0.4,0.6], + [0.4,1.4,0.6,1.4], + [0.6,1.4,0.6,1.6], + [0.6,1.6,0.4,1.6], + [0.4,1.4,0.4,1.6]] +}; + +/* Draw a transition between lastText and thisText. + 'n' is the amount - 0..1 */ +function drawDigits(lastText,thisText,n) { + "ram" + const p = CHARP; // padding around digits + const s = CHARW; // character size + var x = 16; // x offset + g.reset(); + for (var i=0;i{ + if (c[0]!=c[2]) // horiz + buf.fillRect(p+c[0]*s,c[1]*s,p+c[2]*s,2*p+c[3]*s); + else if (c[1]!=c[3]) // vert + buf.fillRect(c[0]*s,p+c[1]*s,2*p+c[2]*s,p+c[3]*s); + }); + g.drawImage(bufimg,x,Y); + } + if (thisCh==":") x-=4; + x+=s+p+7; + } +} +function drawDate() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // meridian + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x+8, Y+0, true); + // date + g.setFont("Vector16"); + g.setFontAlign(0,-1); + // Only draw the date if it has changed: + if ((d.getDate()!=oldDate.getDate())||(d.getMonth()!=oldDate.getMonth())||(d.getFullYear()!=oldDate.getFullYear())) { + var date = locale.date(d,false); + g.clearRect(1,y+8,g.getWidth(),y+24); + g.drawString(date, g.getWidth()/2, y+8, true); + oldDate = d; + } +} + +function drawSeconds() { + var x = (CHARW + CHARP + 8)*5; + var y = Y + 2*CHARW + CHARP; + var d = new Date(); + // seconds + g.reset(); + g.setFont("6x8",2); + g.setFontAlign(-1,-1); + g.drawString(("0"+d.getSeconds()).substr(-2), x+8, y-12, true); +} + +/* Show the current time, and animate if needed */ +function showTime() { + if (animInterval) return; // in animation - quit + var d = new Date(); + var hours = d.getHours(); + if (is12Hour) hours = ((hours + 11) % 12) + 1; + var t = (" "+hours).substr(-2)+":"+ + ("0"+d.getMinutes()).substr(-2); + var l = lastTime; + // same - don't animate + if (t==l || l=="-----") { + drawDigits(l,t,0); + drawDate(); + drawSeconds(); + lastTime = t; + return; + } + var n = 0; + animInterval = setInterval(function() { + n += 1/10; + if (n>=1) { + n=1; + clearInterval(animInterval); + animInterval = undefined; + } + drawDigits(l,t,n); + drawSeconds(); + }, 20); + lastTime = t; +} + +function stopWatch() { + + counter++; + + var hrs = Math.floor(counter/3600); + var mins = Math.floor((counter-hrs*3600)/60); + var secs = counter - mins*60 - hrs*3600; + + // When starting the stopwatch: + if (B3) { + // Set BTN3 to stop the stopwatch and bind itself to restart it: + w3=setWatch(() => {clearInterval(swInterval); + swInterval=undefined; + if (w3) {clearWatch(w3);w3=undefined;} + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 1;}, + BTN3, {repeat:false,edge:"falling"}); + B3 = 0; // BTN3 is bound to stop the stopwatch + } + + // Bind BTN1 to call the reset function: + if (!w1) w1 = setWatch(resetStopWatch, BTN1, {repeat:false,edge:"falling"}); + + // Draw elapsed time: + g.reset(); + g.setColor(0.0,0.5,1.0).setFontAlign(0,-1).setFont("Vector24"); + g.clearRect(1,180,g.getWidth(),210); + if (hrs>0) { + g.drawString(("0"+parseInt(hrs)).substr(-2), g.getWidth()/2 - 72, 180, true); + g.drawString( ":", g.getWidth()/2 - 48, 180, true); + } + g.drawString(("0"+parseInt(mins)).substr(-2), g.getWidth()/2 - 24, 180, true); + g.drawString( ":", g.getWidth()/2, 180, true); + g.drawString(("0"+parseInt(secs)).substr(-2), g.getWidth()/2 + 24, 180, true); + +} + +function resetStopWatch() { + + // Stop the interval if necessary: + if (swInterval) { + clearInterval(swInterval); + swInterval=undefined; + } + + // Clear the stopwatch: + g.clearRect(1,180,g.getWidth(),210); + + // Reset the counter: + counter = -1; + + // Set BTN3 to start the stopwatch again: + if (!B3) { + // In case the stopwatch is reset while still running, the watch on BTN3 is still active, so we need to reset it manually: + if (w3) {clearWatch(w3);w3=undefined;} + // Set BTN3 to start the watch again: + setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); + B3 = 1; // BTN3 is bound to start the stopwatch + } + + // Reset watch on BTN1: + if (w1) {clearWatch(w1);w1=undefined;} +} + + +Bangle.on('lcdPower',function(on) { + if (animInterval) { + clearInterval(animInterval); + animInterval = undefined; + } + if (timeInterval) { + clearInterval(timeInterval); + timeInterval = undefined; + } + if (on) { + showTime(); + timeInterval = setInterval(showTime, 1000); + } else { + lastTime = "-----"; + } +}); + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +// Update time once a second +timeInterval = setInterval(showTime, 1000); +showTime(); + +// Show launcher when middle button pressed +setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + +// Start stopwatch when BTN3 is pressed +setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"}); +B3 = 1; // BTN3 is bound to start the stopwatch diff --git a/apps/mclockplus/mclockplus.png b/apps/mclockplus/mclockplus.png new file mode 100644 index 0000000000000000000000000000000000000000..751f4ec00d35ce7415fa4ee6615caea445ddb3e3 GIT binary patch literal 1737 zcmV;)1~&PLP)w+32MlCjr2BXw?sZ>yF?FHzC6|mdV-FDCT zp=>>S_UxWL+x3(DlTCJ>nP;B=f9LYd%)&XG!@nCMCA~B?*`V-02&xcbA#f3bVk9|d zqxg~X7>FYX(F&m!+LnAxy-=qbdTIjR_m&c(7RgFrmPPMg;0=Vjo$9LIWGN&kplN>r zihKrm0B|H5{49bXUP1}}k7YObtU6m4u*-W7l5Ie~W#9PlA*iLg@*OKqi3@n4)sD-* z11TOfs?JSQxXC$<#WQl4pPNBxx}E81HULhKhv*56(Kd93{U`l=a$*R7DCYFviBnxe zRn>Uv1ngr+*4-IZvaA0Tja`Fuj0B<^NcNJEUBm5* zM$Gy$kN85X9p~@|dh!{H;HmN=9x8E?CX!o9W2$6luaAwt^$}2IWTj*;E_JEdNGPUJ zT>c&C$d_lOv+v5e)Rwy9lF!f2CqF;-Qn4$7HDxaLEVzJ~j1!L9ggTg~Xj7x1q4 zUj3ZEEH{&n7F<9@c7|Es31iW;EcRVFmrHW=eaeUNcx&SM#ul)<*@?myt*JaK9na-+ zaOr=o6yUscJMUaRhr;N-ckot=E0$ku0jhl?l1ayOMexe}vVVkp80V$idHb?CWJGQT zP8>>oEFWV5yIM+^NK%tnS6;;Ys3A^0u9%j|qq7V2<_BroXPD_@jDWHlO@^JD<=~+b zXNoAAqqeL-Kb6uEwpydM(E{qFg5+MUscv>*(pzOR$hIjwetu!3QHc8{4vmqB7I3lm zDxf4>%uQ2RHZw2vEKP8C(F}5I5%gw3(L#N@XaRyMZSiL3w1hKkDv@PVxY3y#X;kEO z`gR6uKrGUhuAPyST4oloIA0fA%0xy$UZ^e3&$Yhu1cO29>+6|5eL8u0c{DUM5DW$_ z_Y;-agqWvqW3Z`O?@}NwtMosVpG^yT&}GS zu)H#2Sj-P|NkD|RTq!3$T3f1PMOi#vADY|NFV|Gyw0L}IXlR(6HenPL6ma0c0i4dn z6XIZSoOxgTq-A@8>dJ^TZ7>4zxVF?C7)v-{7;D$ArLL}yqM{DJIj(PGw2dComfD8SBpfgdhr@x}?WVoGot~Z^>gwtMICSWcB@sUkjq0e0 zs6`YlK+12l#r-G!2@{V6x7(eR2(Lc28p#ce1Ss+|ZSmt1LyW|}fF}&AB1S?g&Ay?y z+`vdc+mf$=o^WwEq_TV9bh5l7uzvk|R;^kEKzn;T&CSg*+j{y>F&z2Bb<c;5t`2%8$a23avP5Bn6)L_!EQY}kMhg3{7bR;*ZI*e<9_wsZ~Zo!>BeiZo`l ztEB{~bf8IS4RecGQx@}Pmr`u$8sz^SeOglhb-bLK>yPT&8Yi;qs$LK;X-yk{>!bBg z(`T9~8g+$R89Or=_y9uZfGsTB( zydjoPYymY_|A7>bXiWpbaqj;9caHr7B8~>eSpHon$Mt;+u`1r9Z~U{+U5noac4|$B z{G%*6_$voa4V#kM{Mzn_!&xjGJUzno2Y;pAuS;A~Zlij!C!VkQVNUH|jpRM8=|pgx z>hC()+}S^+91&C{&vy-S$G4pf1obb7@`P4V0Pf~wF%5AvhW2t>!*TUniL9ku> zYL_AmAVjU%oY%wpg=TqW6CwLNU^`&&xNjMwNV$!Ga*