Initial commit for multi-timer
commit
1d96447e3d
|
|
@ -0,0 +1,4 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
app-config.user.yaml
|
||||||
|
src/app-config.ts
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# espruino-ts-quickstart
|
||||||
|
Quickstart for Espruino using typescript and Visual Studio Code IDE
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Port where mcu device is located
|
||||||
|
port: COM4
|
||||||
|
|
||||||
|
# ESP32 after flash has 115200 speed.
|
||||||
|
port_speed: 115200
|
||||||
|
|
||||||
|
board: ESP32
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"id": "stopwatch",
|
||||||
|
"name": "Stopwatch Touch",
|
||||||
|
"version": "0.05",
|
||||||
|
"description": "A touch based stop watch for Bangle JS 2",
|
||||||
|
"icon": "stopwatch.png",
|
||||||
|
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||||
|
"tags": "tools,app",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"stopwatch.app.js","url":"stopwatch.app.js"},
|
||||||
|
{"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"stopwatch.json"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "espruino-ts-quickstart",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Quickstart for Espruino using typescript and Visual Studio Code IDE",
|
||||||
|
"main": "app.js",
|
||||||
|
"repository": {
|
||||||
|
"url": "https://github.com/stasberkov/espruino-ts-quickstart"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc ./src/app.ts"
|
||||||
|
},
|
||||||
|
"author": "Stanislav Berkov",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/espruino": "^1.94",
|
||||||
|
"espruino": "^0.1",
|
||||||
|
"typescript": "^4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
var STORAGE_FILE = "timer.json";
|
||||||
|
var PAUSE_IMG = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
|
||||||
|
var PLAY_IMG = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
|
||||||
|
var 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==");
|
||||||
|
var BLUE_COLOR = "#0ff";
|
||||||
|
var YELLOW_COLOR = "#ff0";
|
||||||
|
var BLACK_COLOR = "#000";
|
||||||
|
var ICON_SCALE = g.getWidth() / 178;
|
||||||
|
var ICON_SIZE_IN_PIXELS = 24;
|
||||||
|
var UPDATE_DELAY_MS = 100;
|
||||||
|
function convertTimeToText(time) {
|
||||||
|
var hours = Math.floor(time / 3600000);
|
||||||
|
var minutes = Math.floor(time / 60000) % 60;
|
||||||
|
var seconds = Math.floor(time / 1000) % 60;
|
||||||
|
var tenthsOfASecond = Math.floor(time / 100) % 10;
|
||||||
|
if (hours == 0) {
|
||||||
|
return ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2) + "." + tenthsOfASecond;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "0" + hours + ":" + ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var Button = /** @class */ (function () {
|
||||||
|
function Button(x, y, width, height, color, callback, image) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.color = color;
|
||||||
|
this.callback = callback;
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
Button.prototype.setImage = function (image) {
|
||||||
|
this.image = image;
|
||||||
|
};
|
||||||
|
Button.prototype.center = function () {
|
||||||
|
return {
|
||||||
|
x: this.x + this.width / 2,
|
||||||
|
y: this.y + this.height / 2
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Button.prototype.draw = function () {
|
||||||
|
g.setColor(this.color);
|
||||||
|
g.fillRect(this.x, this.y, this.x + this.width, this.y + this.height);
|
||||||
|
if (this.image != undefined) {
|
||||||
|
var center = this.center();
|
||||||
|
var imageSize = ICON_SCALE * ICON_SIZE_IN_PIXELS;
|
||||||
|
var iconX = center.x - imageSize / 2.0;
|
||||||
|
var iconY = center.y - imageSize / 2.0;
|
||||||
|
g.drawImage(this.image, iconX, iconY, { scale: ICON_SCALE });
|
||||||
|
}
|
||||||
|
g.drawRect(this.x, this.y, this.x + this.width, this.y + this.height);
|
||||||
|
};
|
||||||
|
Button.prototype.isOnButton = function (x, y) {
|
||||||
|
return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
|
||||||
|
};
|
||||||
|
// Returns true if clicked.
|
||||||
|
Button.prototype.onClick = function (x, y) {
|
||||||
|
if (this.isOnButton(x, y)) {
|
||||||
|
this.callback();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
return Button;
|
||||||
|
}());
|
||||||
|
var TimerApp = /** @class */ (function () {
|
||||||
|
function TimerApp() {
|
||||||
|
this.width = g.getWidth();
|
||||||
|
this.height = g.getWidth();
|
||||||
|
this.timers = [];
|
||||||
|
this.displayedTimerIndex = 0;
|
||||||
|
this.timeTextY = this.height * (2.0 / 5.0);
|
||||||
|
this.loadStateOrDefault();
|
||||||
|
this.initializeButtons();
|
||||||
|
this.initializeScreen();
|
||||||
|
this.initApp();
|
||||||
|
}
|
||||||
|
TimerApp.prototype.run = function () {
|
||||||
|
this.draw();
|
||||||
|
var self = this;
|
||||||
|
this.updateInterval = setInterval(function () {
|
||||||
|
self.draw();
|
||||||
|
}, UPDATE_DELAY_MS);
|
||||||
|
};
|
||||||
|
TimerApp.prototype.initApp = function () {
|
||||||
|
var self = this;
|
||||||
|
var originalTheme = g.theme;
|
||||||
|
Bangle.setUI({
|
||||||
|
mode: "custom",
|
||||||
|
btn: function () { return load(); },
|
||||||
|
touch: function (button, point) {
|
||||||
|
var x = point.x;
|
||||||
|
var y = point.y;
|
||||||
|
// adjust for outside the dimension of the screen
|
||||||
|
// http://forum.espruino.com/conversations/371867/#comment16406025
|
||||||
|
if (y > self.height)
|
||||||
|
y = self.height;
|
||||||
|
if (y < 0)
|
||||||
|
y = 0;
|
||||||
|
if (x > self.width)
|
||||||
|
x = self.width;
|
||||||
|
if (x < 0)
|
||||||
|
x = 0;
|
||||||
|
// not running, and reset
|
||||||
|
var timer = self.displayedTimer();
|
||||||
|
self.largeButton.onClick(x, y);
|
||||||
|
// if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
// // paused and hit play
|
||||||
|
// if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
|
||||||
|
// // paused and press reset
|
||||||
|
// if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
|
||||||
|
// // must be running
|
||||||
|
// if (running && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
},
|
||||||
|
remove: function () {
|
||||||
|
self.pauseTimer();
|
||||||
|
// Bangle.removeListener("lcdPower", onLCDPower)
|
||||||
|
g.setTheme(originalTheme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
TimerApp.prototype.initializeScreen = function () {
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
g.setColor(BLACK_COLOR);
|
||||||
|
g.fillRect(0, 0, this.width, this.height);
|
||||||
|
};
|
||||||
|
TimerApp.prototype.initializeButtons = function () {
|
||||||
|
function largeButtonClick() {
|
||||||
|
console.log("BIG BUTTON");
|
||||||
|
}
|
||||||
|
function leftButtonClick() {
|
||||||
|
console.log("LEFT BUTTON");
|
||||||
|
}
|
||||||
|
function rightButtonClick() {
|
||||||
|
console.log("RIGHT BUTTON");
|
||||||
|
}
|
||||||
|
this.largeButton = new Button(0.0, (3.0 / 4.0) * this.height, this.width, this.height / 4.0, BLUE_COLOR, largeButtonClick, PLAY_IMG);
|
||||||
|
this.leftButton = new Button(0.0, (3.0 / 4.0) * this.height, this.width / 2.0, this.height / 4.0, BLUE_COLOR, leftButtonClick, PLAY_IMG);
|
||||||
|
this.rightButton = new Button(this.width / 2.0, (3.0 / 4.0) * this.height, this.width / 2.0, this.height / 4.0, BLUE_COLOR, rightButtonClick, PAUSE_IMG);
|
||||||
|
};
|
||||||
|
TimerApp.prototype.resumeTimer = function () {
|
||||||
|
var self = this;
|
||||||
|
this.updateInterval = setInterval(function () {
|
||||||
|
self.draw();
|
||||||
|
}, UPDATE_DELAY_MS);
|
||||||
|
};
|
||||||
|
TimerApp.prototype.displayedTimer = function () {
|
||||||
|
return this.timers[this.displayedTimerIndex];
|
||||||
|
};
|
||||||
|
TimerApp.prototype.save = function () {
|
||||||
|
require("Storage").writeJSON(STORAGE_FILE, {
|
||||||
|
displayedTimerIndex: this.displayedTimerIndex,
|
||||||
|
timers: this.timers
|
||||||
|
});
|
||||||
|
};
|
||||||
|
TimerApp.prototype.loadStateOrDefault = function () {
|
||||||
|
this.timers = [
|
||||||
|
{
|
||||||
|
startTime: 0.0,
|
||||||
|
currentTime: 3000.0,
|
||||||
|
totalTime: 5000.0,
|
||||||
|
running: true
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
TimerApp.prototype.drawButtons = function () {
|
||||||
|
console.log("DRAW BUTTONS", JSON.stringify(this.timers));
|
||||||
|
var timer = this.displayedTimer();
|
||||||
|
if (timer.running) {
|
||||||
|
this.largeButton.draw();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.leftButton.draw();
|
||||||
|
this.rightButton.draw();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TimerApp.prototype.drawTime = function () {
|
||||||
|
var timer = this.displayedTimer();
|
||||||
|
var totalTime = Date.now();
|
||||||
|
var timeText = convertTimeToText(totalTime);
|
||||||
|
g.setFont("Vector", 38);
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
g.clearRect(0, this.timeTextY - 21, this.width, this.timeTextY + 21);
|
||||||
|
g.drawString(timeText, this.width / 2, this.timeTextY);
|
||||||
|
};
|
||||||
|
TimerApp.prototype.draw = function () {
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
this.drawButtons();
|
||||||
|
this.drawTime();
|
||||||
|
};
|
||||||
|
TimerApp.prototype.pauseTimer = function () {
|
||||||
|
if (this.displayedTimer().running) {
|
||||||
|
clearInterval(this.updateInterval);
|
||||||
|
this.updateInterval = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return TimerApp;
|
||||||
|
}());
|
||||||
|
var Timer = /** @class */ (function () {
|
||||||
|
function Timer(state) {
|
||||||
|
this.totalTime = state.totalTime;
|
||||||
|
this.startTime = state.startTime;
|
||||||
|
this.currentTime = state.currentTime;
|
||||||
|
this.running = state.running;
|
||||||
|
}
|
||||||
|
Timer.prototype.state = function () {
|
||||||
|
return {
|
||||||
|
totalTime: this.totalTime,
|
||||||
|
startTime: this.startTime,
|
||||||
|
currentTime: this.currentTime,
|
||||||
|
running: this.running
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return Timer;
|
||||||
|
}());
|
||||||
|
var app = new TimerApp();
|
||||||
|
app.run();
|
||||||
|
|
@ -0,0 +1,320 @@
|
||||||
|
const STORAGE_FILE = "timer.json"
|
||||||
|
|
||||||
|
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=="
|
||||||
|
)
|
||||||
|
|
||||||
|
const BLUE_COLOR = "#0ff"
|
||||||
|
const YELLOW_COLOR = "#ff0"
|
||||||
|
const BLACK_COLOR = "#000"
|
||||||
|
|
||||||
|
const ICON_SCALE = g.getWidth() / 178
|
||||||
|
const ICON_SIZE_IN_PIXELS = 24
|
||||||
|
const UPDATE_DELAY_MS = 100
|
||||||
|
|
||||||
|
function convertTimeToText(time: number): string {
|
||||||
|
let hours = Math.floor(time / 3600000)
|
||||||
|
let minutes = Math.floor(time / 60000) % 60
|
||||||
|
let seconds = Math.floor(time / 1000) % 60
|
||||||
|
let tenthsOfASecond = Math.floor(time / 100) % 10
|
||||||
|
|
||||||
|
if (hours == 0) {
|
||||||
|
return ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2) + "." + tenthsOfASecond
|
||||||
|
} else {
|
||||||
|
return "0" + hours + ":" + ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimerState {
|
||||||
|
totalTime: number
|
||||||
|
startTime: number
|
||||||
|
currentTime: number
|
||||||
|
running: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimersState {
|
||||||
|
displayedTimerIndex: number
|
||||||
|
timers: TimerState[]
|
||||||
|
}
|
||||||
|
|
||||||
|
class Button {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
color: string
|
||||||
|
callback: () => any
|
||||||
|
image: string
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
color: string,
|
||||||
|
callback: () => any,
|
||||||
|
image: string
|
||||||
|
) {
|
||||||
|
this.x = x
|
||||||
|
this.y = y
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
|
this.color = color
|
||||||
|
this.callback = callback
|
||||||
|
this.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
setImage(image: string) {
|
||||||
|
this.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
center(): { x: number; y: number } {
|
||||||
|
return {
|
||||||
|
x: this.x + this.width / 2,
|
||||||
|
y: this.y + this.height / 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
g.setColor(this.color)
|
||||||
|
g.fillRect(this.x, this.y, this.x + this.width, this.y + this.height)
|
||||||
|
if (this.image != undefined) {
|
||||||
|
const center = this.center()
|
||||||
|
const imageSize = ICON_SCALE * ICON_SIZE_IN_PIXELS
|
||||||
|
const iconX = center.x - imageSize / 2.0
|
||||||
|
const iconY = center.y - imageSize / 2.0
|
||||||
|
g.drawImage(this.image, iconX, iconY, { scale: ICON_SCALE })
|
||||||
|
}
|
||||||
|
g.drawRect(this.x, this.y, this.x + this.width, this.y + this.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnButton(x: number, y: number): boolean {
|
||||||
|
return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true if clicked.
|
||||||
|
onClick(x: number, y: number): boolean {
|
||||||
|
if (this.isOnButton(x, y)) {
|
||||||
|
this.callback()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimerApp {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
timers: TimerState[]
|
||||||
|
displayedTimerIndex: number
|
||||||
|
timeTextY: number
|
||||||
|
largeButton!: Button
|
||||||
|
leftButton!: Button
|
||||||
|
rightButton!: Button
|
||||||
|
updateInterval: undefined | any
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.width = g.getWidth()
|
||||||
|
this.height = g.getWidth()
|
||||||
|
this.timers = []
|
||||||
|
this.displayedTimerIndex = 0
|
||||||
|
this.timeTextY = this.height * (2.0 / 5.0)
|
||||||
|
this.loadStateOrDefault()
|
||||||
|
this.initializeButtons()
|
||||||
|
this.initializeScreen()
|
||||||
|
this.initApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
this.draw()
|
||||||
|
const self = this
|
||||||
|
this.updateInterval = setInterval(function () {
|
||||||
|
self.draw()
|
||||||
|
}, UPDATE_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
initApp() {
|
||||||
|
const self = this
|
||||||
|
const originalTheme = g.theme
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode: "custom",
|
||||||
|
btn: () => load(),
|
||||||
|
touch: (button, point) => {
|
||||||
|
let x = point.x
|
||||||
|
let y = point.y
|
||||||
|
|
||||||
|
// adjust for outside the dimension of the screen
|
||||||
|
// http://forum.espruino.com/conversations/371867/#comment16406025
|
||||||
|
if (y > self.height) y = self.height
|
||||||
|
if (y < 0) y = 0
|
||||||
|
if (x > self.width) x = self.width
|
||||||
|
if (x < 0) x = 0
|
||||||
|
|
||||||
|
// not running, and reset
|
||||||
|
const timer = self.displayedTimer()
|
||||||
|
self.largeButton.onClick(x, y)
|
||||||
|
// if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// // paused and hit play
|
||||||
|
// if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// // paused and press reset
|
||||||
|
// if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// // must be running
|
||||||
|
// if (running && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
},
|
||||||
|
remove: () => {
|
||||||
|
self.pauseTimer()
|
||||||
|
// Bangle.removeListener("lcdPower", onLCDPower)
|
||||||
|
g.setTheme(originalTheme)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeScreen() {
|
||||||
|
Bangle.loadWidgets()
|
||||||
|
Bangle.drawWidgets()
|
||||||
|
g.setColor(BLACK_COLOR)
|
||||||
|
g.fillRect(0, 0, this.width, this.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeButtons() {
|
||||||
|
function largeButtonClick() {
|
||||||
|
console.log("BIG BUTTON")
|
||||||
|
}
|
||||||
|
function leftButtonClick() {
|
||||||
|
console.log("LEFT BUTTON")
|
||||||
|
}
|
||||||
|
function rightButtonClick() {
|
||||||
|
console.log("RIGHT BUTTON")
|
||||||
|
}
|
||||||
|
|
||||||
|
this.largeButton = new Button(
|
||||||
|
0.0,
|
||||||
|
(3.0 / 4.0) * this.height,
|
||||||
|
this.width,
|
||||||
|
this.height / 4.0,
|
||||||
|
BLUE_COLOR,
|
||||||
|
largeButtonClick,
|
||||||
|
PLAY_IMG
|
||||||
|
)
|
||||||
|
|
||||||
|
this.leftButton = new Button(
|
||||||
|
0.0,
|
||||||
|
(3.0 / 4.0) * this.height,
|
||||||
|
this.width / 2.0,
|
||||||
|
this.height / 4.0,
|
||||||
|
BLUE_COLOR,
|
||||||
|
leftButtonClick,
|
||||||
|
PLAY_IMG
|
||||||
|
)
|
||||||
|
|
||||||
|
this.rightButton = new Button(
|
||||||
|
this.width / 2.0,
|
||||||
|
(3.0 / 4.0) * this.height,
|
||||||
|
this.width / 2.0,
|
||||||
|
this.height / 4.0,
|
||||||
|
BLUE_COLOR,
|
||||||
|
rightButtonClick,
|
||||||
|
PAUSE_IMG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeTimer() {
|
||||||
|
const self = this
|
||||||
|
this.updateInterval = setInterval(function () {
|
||||||
|
self.draw()
|
||||||
|
}, UPDATE_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayedTimer(): TimerState {
|
||||||
|
return this.timers[this.displayedTimerIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
require("Storage").writeJSON(STORAGE_FILE, {
|
||||||
|
displayedTimerIndex: this.displayedTimerIndex,
|
||||||
|
timers: this.timers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadStateOrDefault() {
|
||||||
|
this.timers = [
|
||||||
|
{
|
||||||
|
startTime: 0.0,
|
||||||
|
currentTime: 3000.0,
|
||||||
|
totalTime: 5000.0,
|
||||||
|
running: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
drawButtons() {
|
||||||
|
console.log("DRAW BUTTONS", JSON.stringify(this.timers))
|
||||||
|
const timer = this.displayedTimer()
|
||||||
|
if (timer.running) {
|
||||||
|
this.largeButton.draw()
|
||||||
|
} else {
|
||||||
|
this.leftButton.draw()
|
||||||
|
this.rightButton.draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTime() {
|
||||||
|
const timer = this.displayedTimer()
|
||||||
|
const totalTime = Date.now()
|
||||||
|
const timeText = convertTimeToText(totalTime)
|
||||||
|
|
||||||
|
g.setFont("Vector", 38)
|
||||||
|
g.setFontAlign(0, 0)
|
||||||
|
g.clearRect(0, this.timeTextY - 21, this.width, this.timeTextY + 21)
|
||||||
|
g.drawString(timeText, this.width / 2, this.timeTextY)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
g.setColor(g.theme.fg)
|
||||||
|
this.drawButtons()
|
||||||
|
this.drawTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseTimer() {
|
||||||
|
if (this.displayedTimer().running) {
|
||||||
|
clearInterval(this.updateInterval)
|
||||||
|
this.updateInterval = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Timer {
|
||||||
|
totalTime: number
|
||||||
|
startTime: number
|
||||||
|
currentTime: number
|
||||||
|
running: boolean
|
||||||
|
|
||||||
|
constructor(state: TimerState) {
|
||||||
|
this.totalTime = state.totalTime
|
||||||
|
this.startTime = state.startTime
|
||||||
|
this.currentTime = state.currentTime
|
||||||
|
this.running = state.running
|
||||||
|
}
|
||||||
|
|
||||||
|
state(): TimerState {
|
||||||
|
return {
|
||||||
|
totalTime: this.totalTime,
|
||||||
|
startTime: this.startTime,
|
||||||
|
currentTime: this.currentTime,
|
||||||
|
running: this.running,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new TimerApp()
|
||||||
|
app.run()
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
{
|
||||||
|
const CONFIGFILE = "stopwatch.json";
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const config = Object.assign({
|
||||||
|
state: {
|
||||||
|
total: now,
|
||||||
|
start: now,
|
||||||
|
current: now,
|
||||||
|
running: false,
|
||||||
|
}
|
||||||
|
}, require("Storage").readJSON(CONFIGFILE, 1) || {});
|
||||||
|
|
||||||
|
let w = g.getWidth();
|
||||||
|
let h = g.getHeight();
|
||||||
|
let tTotal = config.state.total;
|
||||||
|
let tStart = config.state.start;
|
||||||
|
let tCurrent = config.state.current;
|
||||||
|
let running = config.state.running;
|
||||||
|
let timeY = 2 * h / 5;
|
||||||
|
let displayInterval;
|
||||||
|
let redrawButtons = true;
|
||||||
|
const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
|
||||||
|
const origTheme = g.theme;
|
||||||
|
|
||||||
|
// 24 pixel images, scale to watch
|
||||||
|
// 1 bit optimal, image string, no E.toArrayBuffer()
|
||||||
|
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==");
|
||||||
|
|
||||||
|
const saveState = function () {
|
||||||
|
config.state.total = tTotal;
|
||||||
|
config.state.start = tStart;
|
||||||
|
config.state.current = tCurrent;
|
||||||
|
config.state.running = running;
|
||||||
|
require("Storage").writeJSON(CONFIGFILE, config);
|
||||||
|
};
|
||||||
|
|
||||||
|
const log_debug = function (o) {
|
||||||
|
//console.log(o);
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeToText = function (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 = ("0" + hrs) + ":" + ("0" + mins).substr(-2) + ":" + ("0" + secs).substr(-2);
|
||||||
|
|
||||||
|
//log_debug(text);
|
||||||
|
return text;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawButtons = function () {
|
||||||
|
log_debug("drawButtons()");
|
||||||
|
if (!running && tCurrent == tTotal) {
|
||||||
|
bigPlayPauseBtn.draw();
|
||||||
|
} else if (!running && tCurrent != tTotal) {
|
||||||
|
resetBtn.draw();
|
||||||
|
smallPlayPauseBtn.draw();
|
||||||
|
} else {
|
||||||
|
bigPlayPauseBtn.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
redrawButtons = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawTime = function () {
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const draw = function () {
|
||||||
|
if (running) tCurrent = Date.now();
|
||||||
|
g.setColor(g.theme.fg);
|
||||||
|
if (redrawButtons) drawButtons();
|
||||||
|
drawTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
const startTimer = function () {
|
||||||
|
log_debug("startTimer()");
|
||||||
|
draw();
|
||||||
|
displayInterval = setInterval(draw, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopTimer = function () {
|
||||||
|
log_debug("stopTimer()");
|
||||||
|
if (displayInterval) {
|
||||||
|
clearInterval(displayInterval);
|
||||||
|
displayInterval = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// BTN stop start
|
||||||
|
const stopStart = function () {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setButtonImages = function () {
|
||||||
|
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
|
||||||
|
const lapReset = function () {
|
||||||
|
log_debug("lapReset()");
|
||||||
|
if (!running && tStart != tCurrent) {
|
||||||
|
redrawButtons = true;
|
||||||
|
Bangle.buzz();
|
||||||
|
tStart = tCurrent = tTotal = Date.now();
|
||||||
|
g.clearRect(0, 24, w, h);
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
};
|
||||||
|
|
||||||
|
// simple on screen button class
|
||||||
|
const BUTTON = function (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);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const bigPlayPauseBtn = new BUTTON("big", 0, 3 * h / 4, w, h / 4, "#0ff", stopStart, play_img);
|
||||||
|
const smallPlayPauseBtn = new BUTTON("small", w / 2, 3 * h / 4, w / 2, h / 4, "#0ff", stopStart, play_img);
|
||||||
|
const 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.setUI({
|
||||||
|
mode: "custom", btn: () => load(), touch: (button, xy) => {
|
||||||
|
let x = xy.x;
|
||||||
|
let y = xy.y;
|
||||||
|
|
||||||
|
// adjust for outside the dimension of the screen
|
||||||
|
// http://forum.espruino.com/conversations/371867/#comment16406025
|
||||||
|
if (y > h) y = h;
|
||||||
|
if (y < 0) y = 0;
|
||||||
|
if (x > w) x = w;
|
||||||
|
if (x < 0) x = 0;
|
||||||
|
|
||||||
|
// not running, and reset
|
||||||
|
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// paused and hit play
|
||||||
|
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// paused and press reset
|
||||||
|
if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
|
||||||
|
|
||||||
|
// must be running
|
||||||
|
if (running && bigPlayPauseBtn.check(x, y)) return;
|
||||||
|
}, remove: () => {
|
||||||
|
if (displayInterval) {
|
||||||
|
clearInterval(displayInterval);
|
||||||
|
displayInterval = undefined;
|
||||||
|
}
|
||||||
|
Bangle.removeListener('lcdPower', onLCDPower);
|
||||||
|
g.setTheme(origTheme);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop updates when LCD is off, restart when on
|
||||||
|
const onLCDPower = (on) => {
|
||||||
|
if (on) {
|
||||||
|
draw(); // draw immediately, queue redraw
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Bangle.on('lcdPower', onLCDPower);
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
setButtonImages();
|
||||||
|
if (running) {
|
||||||
|
startTimer();
|
||||||
|
} else {
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="))
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": [
|
||||||
|
"es2015",
|
||||||
|
"dom" // setInterval, console
|
||||||
|
],
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue