376 lines
9.5 KiB
TypeScript
376 lines
9.5 KiB
TypeScript
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 {
|
|
elapsedTime: 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
|
|
lastTickTimeMS: number
|
|
|
|
constructor() {
|
|
this.width = g.getWidth()
|
|
this.height = g.getWidth()
|
|
this.timers = []
|
|
this.displayedTimerIndex = 0
|
|
this.timeTextY = this.height * (2.0 / 5.0)
|
|
this.lastTickTimeMS = Date.now()
|
|
this.loadStateOrDefault()
|
|
this.initializeButtons()
|
|
this.initializeScreen()
|
|
this.initApp()
|
|
}
|
|
|
|
run() {
|
|
this.startTimer()
|
|
}
|
|
|
|
initApp() {
|
|
const self = this
|
|
const originalTheme = g.theme
|
|
|
|
Bangle.setUI({
|
|
mode: "custom",
|
|
btn: () => load(),
|
|
touch: (button, point) => {
|
|
const x = Math.min(self.width, Math.max(0, point.x))
|
|
const y = Math.min(self.height, Math.max(0, point.y))
|
|
|
|
if (self.displayedTimerIsRunning()) {
|
|
self.largeButton.onClick(x, y)
|
|
} else if (!self.displayedTimerIsRunning() && !self.displayedTimerHasStarted()) {
|
|
self.largeButton.onClick(x, y)
|
|
} else {
|
|
self.leftButton.onClick(x, y)
|
|
self.rightButton.onClick(x, y)
|
|
}
|
|
},
|
|
remove: () => {
|
|
self.pauseTimer()
|
|
// Bangle.removeListener("lcdPower", onLCDPower)
|
|
g.setTheme(originalTheme)
|
|
},
|
|
})
|
|
}
|
|
|
|
initializeScreen() {
|
|
g.setTheme({ bg: "#000", fg: "#fff", dark: true }).clear()
|
|
g.setColor(BLACK_COLOR)
|
|
g.fillRect(0, 0, this.width, this.height)
|
|
Bangle.loadWidgets()
|
|
Bangle.drawWidgets()
|
|
}
|
|
|
|
initializeButtons() {
|
|
const self = this
|
|
function startOrPauseTimer() {
|
|
if (self.displayedTimerIsRunning()) {
|
|
self.pauseDisplayedTimer()
|
|
} else {
|
|
self.playDisplayedTimer()
|
|
}
|
|
}
|
|
|
|
function resetTimer() {
|
|
self.resetDisplayedTimer()
|
|
}
|
|
|
|
function resumeTimer() {
|
|
self.resumeDisplayedTimer()
|
|
}
|
|
|
|
this.largeButton = new Button(
|
|
0.0,
|
|
(3.0 / 4.0) * this.height,
|
|
this.width,
|
|
this.height / 4.0,
|
|
BLUE_COLOR,
|
|
startOrPauseTimer,
|
|
PLAY_IMG
|
|
)
|
|
|
|
this.leftButton = new Button(
|
|
0.0,
|
|
(3.0 / 4.0) * this.height,
|
|
this.width / 2.0,
|
|
this.height / 4.0,
|
|
YELLOW_COLOR,
|
|
resetTimer,
|
|
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,
|
|
resumeTimer,
|
|
PAUSE_IMG
|
|
)
|
|
}
|
|
|
|
startTimer() {
|
|
const self = this
|
|
this.updateInterval = setInterval(function () {
|
|
const now = Date.now()
|
|
const dt = now - self.lastTickTimeMS
|
|
self.lastTickTimeMS = now
|
|
self.update(dt)
|
|
}, UPDATE_DELAY_MS)
|
|
}
|
|
|
|
displayedTimer(): TimerState {
|
|
return this.timers[this.displayedTimerIndex]
|
|
}
|
|
|
|
update(dt: number) {
|
|
this.updateTimers(dt)
|
|
this.updateButtons()
|
|
this.draw()
|
|
this.save()
|
|
}
|
|
|
|
updateTimers(dt: number) {
|
|
for (let timer of this.timers) {
|
|
if (timer.running) {
|
|
timer.elapsedTime += dt
|
|
}
|
|
}
|
|
}
|
|
|
|
updateButtons() {
|
|
if (this.displayedTimerIsRunning()) {
|
|
this.largeButton.setImage(PAUSE_IMG)
|
|
this.leftButton.setImage(RESET_IMG)
|
|
this.rightButton.setImage(PLAY_IMG)
|
|
} else {
|
|
this.largeButton.setImage(PLAY_IMG)
|
|
this.leftButton.setImage(RESET_IMG)
|
|
this.rightButton.setImage(PLAY_IMG)
|
|
}
|
|
}
|
|
|
|
pauseDisplayedTimer() {
|
|
this.displayedTimer().running = false
|
|
}
|
|
|
|
resetDisplayedTimer() {
|
|
this.displayedTimer().elapsedTime = 0.0
|
|
this.displayedTimer().running = false
|
|
}
|
|
|
|
resumeDisplayedTimer() {
|
|
this.displayedTimer().running = true
|
|
}
|
|
|
|
playDisplayedTimer() {
|
|
this.displayedTimer().running = true
|
|
}
|
|
|
|
displayedTimerIsRunning(): boolean {
|
|
return this.displayedTimer().running
|
|
}
|
|
|
|
displayedTimerHasStarted(): boolean {
|
|
return this.displayedTimer().elapsedTime > 0.0
|
|
}
|
|
|
|
save() {
|
|
require("Storage").writeJSON(STORAGE_FILE, {
|
|
displayedTimerIndex: this.displayedTimerIndex,
|
|
timers: this.timers,
|
|
})
|
|
}
|
|
|
|
loadStateOrDefault() {
|
|
this.timers = [
|
|
{
|
|
elapsedTime: 0.0,
|
|
running: false,
|
|
},
|
|
{
|
|
elapsedTime: 0.0,
|
|
running: false,
|
|
},
|
|
{
|
|
elapsedTime: 0.0,
|
|
running: false,
|
|
},
|
|
{
|
|
elapsedTime: 0.0,
|
|
running: false,
|
|
},
|
|
]
|
|
}
|
|
|
|
drawButtons() {
|
|
console.log("DRAW BUTTONS", JSON.stringify(this.timers))
|
|
if (this.displayedTimerIsRunning() || !this.displayedTimerHasStarted()) {
|
|
this.largeButton.draw()
|
|
} else {
|
|
this.leftButton.draw()
|
|
this.rightButton.draw()
|
|
}
|
|
}
|
|
|
|
drawTime() {
|
|
const timer = this.displayedTimer()
|
|
const timeText = convertTimeToText(timer.elapsedTime)
|
|
|
|
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()
|