banglejs-multi-timer/src/app.ts

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()