diff --git a/apps.json b/apps.json index 269e9577e..84e95330c 100644 --- a/apps.json +++ b/apps.json @@ -3612,5 +3612,18 @@ {"name":"ffcniftya.app.js","url":"app.js"}, {"name":"ffcniftya.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "stopwatch", + "name": "Stopwatch", + "shortName":"Stopwatch", + "icon": "stopwatch.png", + "version":"0.01", + "description": "A touch controlled stopwatch for Bangle 2", + "readme": "README.md", + "tags": "tool, b2", + "storage": [ + {"name":"stopwatch.app.js","url":"stopwatch.js"}, + {"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true} + ] } ] diff --git a/apps/stopwatch/ChangeLog b/apps/stopwatch/ChangeLog new file mode 100644 index 000000000..9db0e26c5 --- /dev/null +++ b/apps/stopwatch/ChangeLog @@ -0,0 +1 @@ +0.01: first release diff --git a/apps/stopwatch/README.md b/apps/stopwatch/README.md new file mode 100644 index 000000000..1924cc343 --- /dev/null +++ b/apps/stopwatch/README.md @@ -0,0 +1,20 @@ +# Stopwatch + +A touch screen based stop watch for Bangle 2 + +## Screenshots + +## Features + +* Feature A +* Feature B + +## Future features + +I'm keen to complete this project with + +* Ability to dismiss the app and leave it running in the background +* A small widget to show the elapsed time on the curren active clock +* Laptimes, with a way to view all the laptimes on a scrollable screen + + diff --git a/apps/stopwatch/pause-24.png b/apps/stopwatch/pause-24.png new file mode 100644 index 000000000..eb3d8feaa Binary files /dev/null and b/apps/stopwatch/pause-24.png differ diff --git a/apps/stopwatch/pause-24a.png b/apps/stopwatch/pause-24a.png new file mode 100644 index 000000000..7838ef640 Binary files /dev/null and b/apps/stopwatch/pause-24a.png differ diff --git a/apps/stopwatch/play-24.png b/apps/stopwatch/play-24.png new file mode 100644 index 000000000..268b5dc31 Binary files /dev/null and b/apps/stopwatch/play-24.png differ diff --git a/apps/stopwatch/stop-24.png b/apps/stopwatch/stop-24.png new file mode 100644 index 000000000..658d614ca Binary files /dev/null and b/apps/stopwatch/stop-24.png differ diff --git a/apps/stopwatch/stop-24a.png b/apps/stopwatch/stop-24a.png new file mode 100644 index 000000000..e89ddae05 Binary files /dev/null and b/apps/stopwatch/stop-24a.png differ diff --git a/apps/stopwatch/stopwatch.app.js b/apps/stopwatch/stopwatch.app.js new file mode 100644 index 000000000..80fa7902e --- /dev/null +++ b/apps/stopwatch/stopwatch.app.js @@ -0,0 +1,222 @@ +let w = g.getWidth(); +let h = g.getHeight(); +let tTotal = Date.now(); +let tStart = tTotal; +let tCurrent = tTotal; +let running = false; +let timeY = 2*h/5; +let displayInterval; +let redrawButtons = true; +const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size + +// 24 pixel images, scale to watch +// 1 bit optimal, image string, no E.toArrayBuffer() +//const reset_img_a = atob("GBiBAf////////////AAD+AAB+AAB+AAB+AAB+D/B+D/B+D/B+D/B+D/B+D/B+D/B+D/B+AAB+AAB+AAB+AAB/AAD////////////w=="); +const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w=="); +const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA="); +const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w=="); + +function log_debug(o) { + //console.log(o); +} + +function timeToText(t) { + let hrs = Math.floor(t/3600000); + let mins = Math.floor(t/60000)%60; + let secs = Math.floor(t/1000)%60; + let tnth = Math.floor(t/100)%10; + let text; + + if (hrs === 0) + text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; + else + text = (""+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth; + + //log_debug(text); + return text; +} + +function drawButtons() { + log_debug("drawButtons()"); + if (!running && tCurrent == tTotal) { + bigPlayPauseBtn.draw(); + } else if (!running && tCurrent != tTotal) { + resetBtn.draw(); + smallPlayPauseBtn.draw(); + } else { + bigPlayPauseBtn.draw(); + } + + redrawButtons = false; +} + +function drawTime() { + //log_debug("drawTime()"); + let Tt = tCurrent-tTotal; + let Ttxt = timeToText(Tt); + + // total time + g.setFont("Vector",38); // check + g.setFontAlign(0,0); + g.clearRect(0, timeY - 21, w, timeY + 21); + g.setColor(g.theme.fg); + g.drawString(Ttxt, w/2, timeY); +} + +function draw() { + let last = tCurrent; + if (running) tCurrent = Date.now(); + g.setColor(g.theme.fg); + if (redrawButtons) drawButtons(); + drawTime(); +} + +function startTimer() { + log_debug("startTimer()"); + draw(); + displayInterval = setInterval(draw, 100); +} + +function stopTimer() { + log_debug("stopTimer()"); + if (displayInterval) { + clearInterval(displayInterval); + displayInterval = undefined; + } +} + +// BTN stop start +function stopStart() { + log_debug("stopStart()"); + + if (running) + stopTimer(); + + running = !running; + Bangle.buzz(); + + if (running) + tStart = Date.now() + tStart- tCurrent; + tTotal = Date.now() + tTotal - tCurrent; + tCurrent = Date.now(); + + setButtonImages(); + redrawButtons = true; + if (running) { + startTimer(); + } else { + draw(); + } +} + +function setButtonImages() { + if (running) { + bigPlayPauseBtn.setImage(pause_img); + smallPlayPauseBtn.setImage(pause_img); + resetBtn.setImage(reset_img); + } else { + bigPlayPauseBtn.setImage(play_img); + smallPlayPauseBtn.setImage(play_img); + resetBtn.setImage(reset_img); + } +} + +// lap or reset +function lapReset() { + log_debug("lapReset()"); + if (!running && tStart != tCurrent) { + redrawButtons = true; + Bangle.buzz(); + tStart = tCurrent = tTotal = Date.now(); + log_debug("lapReset - clear screen"); + g.clearRect(0,24,w,h); + draw(); + } +} + +// simple on screen button class +function BUTTON(name,x,y,w,h,c,f,i) { + this.name = name; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.color = c; + this.callback = f; + this.img = i; +} + +BUTTON.prototype.setImage = function(i) { + this.img = i; +} + +// if pressed the callback +BUTTON.prototype.check = function(x,y) { + //console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); + + if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { + log_debug(this.name + ":callback\n"); + this.callback(); + return true; + } + return false; +}; + +BUTTON.prototype.draw = function() { + g.setColor(this.color); + g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); + g.setColor("#000"); // the icons and boxes are drawn black + if (this.img != undefined) { + let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale + let ix = this.x + ((this.w - iw) /2); + let iy = this.y + ((this.h - iw) /2); + log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})"); + g.drawImage(this.img, ix, iy, {scale: iconScale}); + } + g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h); +}; + + +var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img); +var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img); +var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img); + +bigPlayPauseBtn.setImage(play_img); +smallPlayPauseBtn.setImage(play_img); +resetBtn.setImage(pause_img); + + +Bangle.on('touch', function(button, xy) { + // not running, and reset + if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and hit play + if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(xy.x, xy.y)) return; + + // paused and press reset + if (!running && tCurrent != tTotal && resetBtn.check(xy.x, xy.y)) return; + + // must be running + if (running && bigPlayPauseBtn.check(xy.x, xy.y)) return; +}); + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); + +// Clear the screen once, at startup +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(); +// above not working, hence using next 2 lines +g.setColor("#000"); +g.fillRect(0,0,w,h); + +Bangle.loadWidgets(); +Bangle.drawWidgets(); +draw(); +Bangle.setUI("clock"); // Show launcher when button pressed diff --git a/apps/stopwatch/stopwatch.icon.js b/apps/stopwatch/stopwatch.icon.js new file mode 100644 index 000000000..867b95bd2 --- /dev/null +++ b/apps/stopwatch/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")); diff --git a/apps/stopwatch/stopwatch.png b/apps/stopwatch/stopwatch.png new file mode 100644 index 000000000..92ffe73b7 Binary files /dev/null and b/apps/stopwatch/stopwatch.png differ