From 1d96447e3d5fa293debb7612de8b85304620efbe Mon Sep 17 00:00:00 2001 From: Devin Leamy Date: Sat, 28 Oct 2023 13:29:38 -0400 Subject: [PATCH] Initial commit for multi-timer --- .gitignore | 4 + README.md | 2 + env-config.yaml | 7 + metadata.json | 18 +++ package.json | 19 +++ src/app.js | 219 +++++++++++++++++++++++++++++ src/app.ts | 320 ++++++++++++++++++++++++++++++++++++++++++ src/stopwatch.app.js | 264 ++++++++++++++++++++++++++++++++++ src/stopwatch.icon.js | 1 + tsconfig.json | 14 ++ 10 files changed, 868 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 env-config.yaml create mode 100644 metadata.json create mode 100644 package.json create mode 100644 src/app.js create mode 100644 src/app.ts create mode 100644 src/stopwatch.app.js create mode 100644 src/stopwatch.icon.js create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..adb8354 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +dist +node_modules +app-config.user.yaml +src/app-config.ts \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb7a616 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# espruino-ts-quickstart +Quickstart for Espruino using typescript and Visual Studio Code IDE diff --git a/env-config.yaml b/env-config.yaml new file mode 100644 index 0000000..0e7d4d3 --- /dev/null +++ b/env-config.yaml @@ -0,0 +1,7 @@ +# Port where mcu device is located +port: COM4 + +# ESP32 after flash has 115200 speed. +port_speed: 115200 + +board: ESP32 \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..514f350 --- /dev/null +++ b/metadata.json @@ -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"} + ] + } diff --git a/package.json b/package.json new file mode 100644 index 0000000..90e2182 --- /dev/null +++ b/package.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" + } +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..906d611 --- /dev/null +++ b/src/app.js @@ -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(); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..fb3deec --- /dev/null +++ b/src/app.ts @@ -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() diff --git a/src/stopwatch.app.js b/src/stopwatch.app.js new file mode 100644 index 0000000..e294e60 --- /dev/null +++ b/src/stopwatch.app.js @@ -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(); + } +} diff --git a/src/stopwatch.icon.js b/src/stopwatch.icon.js new file mode 100644 index 0000000..32281b7 --- /dev/null +++ b/src/stopwatch.icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA=")) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c8e93cb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": [ + "es2015", + "dom" // setInterval, console + ], + "strict": true + }, + "include": [ + "src/**/*.ts" + ] +} \ No newline at end of file