Stopwatch: touch based stopwatch for Bangle 2

pull/854/head
hughbarney 2021-10-20 18:35:03 +01:00
parent 547893c261
commit a89545e02a
11 changed files with 257 additions and 0 deletions

View File

@ -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}
]
}
]

1
apps/stopwatch/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: first release

20
apps/stopwatch/README.md Normal file
View File

@ -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

BIN
apps/stopwatch/pause-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

BIN
apps/stopwatch/play-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

BIN
apps/stopwatch/stop-24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

BIN
apps/stopwatch/stop-24a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 B

View File

@ -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

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="));

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB