From 33105c58204d3515bb3a4e84c1501e9f2b995b53 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 27 Mar 2021 19:17:18 +0100 Subject: [PATCH 1/5] gbmusic: code style (add semicolons) --- apps/gbmusic/app.js | 526 ++++++++++++++++++++++---------------------- 1 file changed, 263 insertions(+), 263 deletions(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index ab26c22ee..a27f4daa7 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -3,24 +3,24 @@ * Control the music on your Gadgetbridge-connected phone **/ { - let autoClose = false // only if opened automatically - let state = "" + let autoClose = false; // only if opened automatically + let state = ""; let info = { artist: "", album: "", track: "", n: 0, c: 0, - } + }; const screen = { width: g.getWidth(), height: g.getHeight(), center: g.getWidth()/2, middle: g.getHeight()/2, - } + }; - const TIMEOUT = 5*1000*60 // auto close timeout: 5 minutes + const TIMEOUT = 5*1000*60; // auto close timeout: 5 minutes // drawText defaults const defaults = { time: { // top center @@ -78,36 +78,36 @@ font: "6x8", // volume buttons volSize: 2, // volume buttons }, - } + }; class Ticker { constructor(interval) { - this.i = null - this.interval = interval - this.active = false + this.i = null; + this.interval = interval; + this.active = false; } clear() { if (this.i) { - clearInterval(this.i) + clearInterval(this.i); } - this.i = null + this.i = null; } start() { - this.active = true - this.resume() + this.active = true; + this.resume(); } stop() { - this.active = false - this.clear() + this.active = false; + this.clear(); } pause() { - this.clear() + this.clear(); } resume() { - this.clear() + this.clear(); if (this.active && Bangle.isLCDOn()) { - this.tick() - this.i = setInterval(() => {this.tick()}, this.interval) + this.tick(); + this.i = setInterval(() => {this.tick();}, this.interval); } } } @@ -117,28 +117,28 @@ */ class Clock extends Ticker { constructor() { - super(1000) + super(1000); } tick() { - g.reset() - const now = new Date - drawText("time", this.text(now)) - drawText("date", require("locale").date(now, true)) + g.reset(); + const now = new Date; + drawText("time", this.text(now)); + drawText("date", require("locale").date(now, true)); } text(time) { - const l = require("locale") - const is12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] + const l = require("locale"); + const is12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; if (!is12hour) { - return l.time(time, true) + return l.time(time, true); } - const date12 = new Date(time.getTime()) - const hours = date12.getHours() + const date12 = new Date(time.getTime()); + const hours = date12.getHours(); if (hours===0) { - date12.setHours(12) + date12.setHours(12); } else if (hours>12) { - date12.setHours(hours-12) + date12.setHours(hours-12); } - return l.time(date12, true)+l.meridian(time) + return l.time(date12, true)+l.meridian(time); } } @@ -147,26 +147,26 @@ */ class Fader extends Ticker { constructor() { - super(defaults.track.interval) // redraw at same speed as scroller + super(defaults.track.interval); // redraw at same speed as scroller } tick() { - drawMusic() + drawMusic(); } start() { - this.since = Date.now() - super.start() + this.since = Date.now(); + super.start(); } stop() { - super.stop() - this.since = Date.now() // force redraw at 100% brightness - drawMusic() - this.since = null + super.stop(); + this.since = Date.now(); // force redraw at 100% brightness + drawMusic(); + this.since = null; } brightness() { if (fadeOut.since) { - return Math.max(0, 1-((Date.now()-fadeOut.since)/TIMEOUT)) + return Math.max(0, 1-((Date.now()-fadeOut.since)/TIMEOUT)); } - return 1 + return 1; } } @@ -175,35 +175,35 @@ */ class Scroller extends Ticker { constructor() { - super(defaults.track.interval) + super(defaults.track.interval); } tick() { - this.offset += defaults.track.step - this.draw() + this.offset += defaults.track.step; + this.draw(); } draw() { - const s = defaults.track - const sep = " " - g.setFont(s.font, s.size) - g.setColor(infoColor("track")) + const s = defaults.track; + const sep = " "; + g.setFont(s.font, s.size); + g.setColor(infoColor("track")); const text = sep+info.track, text2 = text.repeat(2), w1 = g.stringWidth(text), - bottom = screen.height-s.bottom - this.offset = this.offset%w1 - g.setFontAlign(-1, 1) + bottom = screen.height-s.bottom; + this.offset = this.offset%w1; + g.setFontAlign(-1, 1); g.clearRect(0, bottom-s.size, screen.width, bottom) - .drawString(text2, -this.offset, screen.height-s.bottom) + .drawString(text2, -this.offset, screen.height-s.bottom); } start() { - this.offset = 0 - super.start() + this.offset = 0; + super.start(); } stop() { - super.stop() + super.stop(); const s = defaults.track, - bottom = screen.height-s.bottom - g.clearRect(0, bottom-s.size, screen.width, bottom) + bottom = screen.height-s.bottom; + g.clearRect(0, bottom-s.size, screen.width, bottom); } } @@ -212,49 +212,49 @@ color: infoColor(name), size: infoSize(name), force: fadeOut.active, - }, options)) + }, options)); } - let oldText = {} + let oldText = {}; function drawText(name, text, options) { if (name in oldText && oldText[name].text===text && !(options || {}).force) { - return // nothing to do + return; // nothing to do } const s = Object.assign( // deep clone defaults to prevent them being overwritten with options JSON.parse(JSON.stringify(defaults[name])), options || {}, - ) - g.setColor(s.color) - g.setFont(s.font, s.size) + ); + g.setColor(s.color); + g.setFont(s.font, s.size); const ax = "left" in s ? -1 : ("right" in s ? 1 : 0), - ay = "top" in s ? -1 : ("bottom" in s ? 1 : 0) - g.setFontAlign(ax, ay) + ay = "top" in s ? -1 : ("bottom" in s ? 1 : 0); + g.setFontAlign(ax, ay); // drawString coordinates const x = "left" in s ? s.left : ("right" in s ? screen.width-s.right : s.center), - y = "top" in s ? s.top : ("bottom" in s ? screen.height-s.bottom : s.middle) + y = "top" in s ? s.top : ("bottom" in s ? screen.height-s.bottom : s.middle); // bounding rectangle const w = g.stringWidth(text), h = g.getFontHeight(), left = "left" in s ? x : ("right" in s ? x-w : x-w/2), - top = "top" in s ? y : ("bottom" in s ? y-h : y-h/2) + top = "top" in s ? y : ("bottom" in s ? y-h : y-h/2); if (name in oldText) { - const old = oldText[name] + const old = oldText[name]; // only clear if text/area has changed if (old.text!==text || old.left!==left || old.top!==top || old.w!==w || old.h!==h) { - g.clearRect(old.left, old.top, old.left+old.w, old.top+old.h) + g.clearRect(old.left, old.top, old.left+old.w, old.top+old.h); } } if (text.length) { - g.drawString(text, x, y) + g.drawString(text, x, y); // remember which rectangle to clear before next draw oldText[name] = { text: text, left: left, top: top, w: w, h: h, - } + }; } else { - delete oldText[name] + delete oldText[name]; } } @@ -265,26 +265,26 @@ */ function fitText(text) { if (!text.length) { - return Infinity + return Infinity; } // Vector: make a guess, then shrink/grow until it fits const getWidth = (size) => g.setFont("Vector", size).stringWidth(text) - , sw = screen.width - let guess = Math.round(sw/(text.length*0.6)) + , sw = screen.width; + let guess = Math.round(sw/(text.length*0.6)); if (getWidth(guess)===sw) { // good guess! - return guess + return guess; } if (getWidth(guess) target do { - guess-- - } while(getWidth(guess)>sw) - return guess + guess--; + } while(getWidth(guess)>sw); + return guess; } /** @@ -293,210 +293,210 @@ */ function infoSize(name) { if (name==="num") { // fixed size - return defaults[name].size + return defaults[name].size; } return Math.min( defaults[name].size, fitText(info[name]), - ) + ); } /** * @param name * @return {string} Semi-random color to use for given info */ - let infoColors = {} + let infoColors = {}; function infoColor(name) { - let h, s, v + let h, s, v; if (name==="num") { // always white - h = 0 - s = 0 + h = 0; + s = 0; } else { // complicated scheme to make color depend deterministically on info // s=1 and hue depends on the text, so we always get a bright color - let text = "" + let text = ""; switch(name) { case "track": - text = info.track + text = info.track; // fallthrough: also use album+artist case "album": - text += info.album + text += info.album; // fallthrough: also use artist case "artist": - text += info.artist - break + text += info.artist; + break; default: - text = info[name] + text = info[name]; } if (name in infoColors && infoColors[name].text===text && !fadeOut.active) { - return infoColors[name].color + return infoColors[name].color; } - let code = 0 // just the sum of all ascii values of text - text.split("").forEach(c => code += c.charCodeAt(0)) + let code = 0; // just the sum of all ascii values of text + text.split("").forEach(c => code += c.charCodeAt(0)); // dark magic - h = code%360 - s = 1 + h = code%360; + s = 1; } - v = fadeOut.brightness() + v = fadeOut.brightness(); const hsv2rgb = (h, s, v) => { const f = (n) => { - const k = (n+h/60)%6 - return v-v*s*Math.max(Math.min(k, 4-k, 1), 0) - } - return {r: f(5), g: f(3), b: f(1)} - } - const rgb = hsv2rgb(h, s, v) - const f2hex = (f) => ("00"+(Math.round(f*255)).toString(16)).substr(-2) - const color = "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b) - infoColors[name] = color - return color + const k = (n+h/60)%6; + return v-v*s*Math.max(Math.min(k, 4-k, 1), 0); + }; + return {r: f(5), g: f(3), b: f(1)}; + }; + const rgb = hsv2rgb(h, s, v); + const f2hex = (f) => ("00"+(Math.round(f*255)).toString(16)).substr(-2); + const color = "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); + infoColors[name] = color; + return color; } - let lastTrack + let lastTrack; function drawTrack() { // we try if we can squeeze this in with a slightly smaller font, but if // the title is too long we start up the scroller instead - const trackInfo = ([info.artist, info.album, info.n, info.track]).join("-") + const trackInfo = ([info.artist, info.album, info.n, info.track]).join("-"); if (trackInfo===lastTrack) { - return // already visible + return; // already visible } if (infoSize("track")0) { - info.num = "#"+info.n + info.num = "#"+info.n; if ("c" in info && info.c>0) { // I've seen { c:-1 } - info.num += "/"+info.c + info.num += "/"+info.c; } } } function drawMusic() { - g.reset() - setNumInfo() - drawInfo("num") - drawTrack() - drawArtistAlbum() - drawControls() + g.reset(); + setNumInfo(); + drawInfo("num"); + drawTrack(); + drawArtistAlbum(); + drawControls(); } - let tQuit + let tQuit; function updateMusic() { // if paused for five minutes, load the clock // (but timeout resets if we get new info, even while paused) if (tQuit) { - clearTimeout(tQuit) + clearTimeout(tQuit); } - tQuit = null + tQuit = null; if (state!=="play" && autoClose) { if (state==="stop") { // never actually happens with my phone :-( - load() + load(); } else { // also quit when paused for a long time - tQuit = setTimeout(load, TIMEOUT) - fadeOut.start() + tQuit = setTimeout(load, TIMEOUT); + fadeOut.start(); } } else { - fadeOut.stop() + fadeOut.stop(); } - drawMusic() + drawMusic(); } // create tickers - const clock = new Clock() - const fadeOut = new Fader() - const scroller = new Scroller() + const clock = new Clock(); + const fadeOut = new Fader(); + const scroller = new Scroller(); //////////////////// // Events @@ -505,17 +505,17 @@ // pause timers while screen is off Bangle.on("lcdPower", on => { if (on) { - clock.resume() - scroller.resume() - fadeOut.resume() + clock.resume(); + scroller.resume(); + fadeOut.resume(); } else { - clock.pause() - scroller.pause() - fadeOut.pause() + clock.pause(); + scroller.pause(); + fadeOut.pause(); } - }) + }); - let tLauncher + let tLauncher; // we put starting of watches inside a function, so we can defer it until we // asked the user about autoStart function startLauncherWatch() { @@ -523,156 +523,156 @@ // short-press: toggle play/pause setWatch(function() { if (tLauncher) { - clearTimeout(tLauncher) + clearTimeout(tLauncher); } - tLauncher = setTimeout(Bangle.showLauncher, 1000) - }, BTN2, {repeat: true, edge: "rising"}) + tLauncher = setTimeout(Bangle.showLauncher, 1000); + }, BTN2, {repeat: true, edge: "rising"}); setWatch(function() { if (tLauncher) { - clearTimeout(tLauncher) - tLauncher = null + clearTimeout(tLauncher); + tLauncher = null; } - togglePlay() - }, BTN2, {repeat: true, edge: "falling"}) + togglePlay(); + }, BTN2, {repeat: true, edge: "falling"}); } - let tCommand = {} + let tCommand = {}; /** * Send command and highlight corresponding control * @param command "play/pause/next/previous/volumeup/volumedown" */ function sendCommand(command) { - Bluetooth.println(JSON.stringify({t: "music", n: command})) + Bluetooth.println(JSON.stringify({t: "music", n: command})); // for controlColor if (command in tCommand) { - clearTimeout(tCommand[command]) + clearTimeout(tCommand[command]); } tCommand[command] = setTimeout(function() { - delete tCommand[command] - drawControls() - }, defaults.controls.highlight) - drawControls() + delete tCommand[command]; + drawControls(); + }, defaults.controls.highlight); + drawControls(); } // BTN1/3: volume control (with repeat after long-press) - let tVol, volCmd + let tVol, volCmd; function volUp() { - volStart("up") + volStart("up"); } function volDown() { - volStart("down") + volStart("down"); } function volStart(dir) { - const command = "volume"+dir - stopVol() - sendCommand(command) - volCmd = command - tVol = setTimeout(repeatVol, 500) + const command = "volume"+dir; + stopVol(); + sendCommand(command); + volCmd = command; + tVol = setTimeout(repeatVol, 500); } function repeatVol() { - sendCommand(volCmd) - tVol = setTimeout(repeatVol, 100) + sendCommand(volCmd); + tVol = setTimeout(repeatVol, 100); } function stopVol() { if (tVol) { - clearTimeout(tVol) - tVol = null + clearTimeout(tVol); + tVol = null; } - volCmd = null - drawControls() + volCmd = null; + drawControls(); } function startVolWatches() { - setWatch(volUp, BTN1, {repeat: true, edge: "rising"}) - setWatch(stopVol, BTN1, {repeat: true, edge: "falling"}) - setWatch(volDown, BTN3, {repeat: true, edge: "rising"}) - setWatch(stopVol, BTN3, {repeat: true, edge: "falling"}) + setWatch(volUp, BTN1, {repeat: true, edge: "rising"}); + setWatch(stopVol, BTN1, {repeat: true, edge: "falling"}); + setWatch(volDown, BTN3, {repeat: true, edge: "rising"}); + setWatch(stopVol, BTN3, {repeat: true, edge: "falling"}); } // touch/swipe: navigation function togglePlay() { - sendCommand(state==="play" ? "pause" : "play") + sendCommand(state==="play" ? "pause" : "play"); } function startTouchWatches() { Bangle.on("touch", function(side) { switch(side) { case 1: - sendCommand(state==="play" ? "pause" : "previous") - break + sendCommand(state==="play" ? "pause" : "previous"); + break; case 2: - sendCommand(state==="play" ? "next" : "play") - break + sendCommand(state==="play" ? "next" : "play"); + break; case 3: - togglePlay() + togglePlay(); } - }) + }); Bangle.on("swipe", function(dir) { - sendCommand(dir===1 ? "previous" : "next") - }) + sendCommand(dir===1 ? "previous" : "next"); + }); } ///////////////////// // Startup ///////////////////// // check for saved music state (by widget) to load - g.clear() - global.gbmusic_active = true // we don't need our widget - Bangle.loadWidgets() - Bangle.drawWidgets() - delete (global.gbmusic_active) + g.clear(); + global.gbmusic_active = true; // we don't need our widget + Bangle.loadWidgets(); + Bangle.drawWidgets(); + delete (global.gbmusic_active); function startEmulator() { if (typeof Bluetooth==="undefined") { // emulator! Bluetooth = { - println: (line) => {console.log("Bluetooth:", line)}, - } + println: (line) => {console.log("Bluetooth:", line);}, + }; // some example info - GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}) - GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}) + GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); + GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); } } function startWatches() { - startVolWatches() - startLauncherWatch() - startTouchWatches() + startVolWatches(); + startLauncherWatch(); + startTouchWatches(); } function start() { // start listening for music updates - const _GB = global.GB + const _GB = global.GB; global.GB = (event) => { // we eat music events! switch(event.t) { case "musicinfo": - info = event - delete (info.t) - break + info = event; + delete (info.t); + break; case "musicstate": - state = event.state - break + state = event.state; + break; default: // pass on other events if (_GB) { - setTimeout(_GB, 0, event) + setTimeout(_GB, 0, event); } - return // no drawMusic + return; // no drawMusic } - updateMusic() - } - startWatches() - drawMusic() - clock.start() - startEmulator() + updateMusic(); + }; + startWatches(); + drawMusic(); + clock.start(); + startEmulator(); } - let saved = require("Storage").readJSON("gbmusic.load.json", true) - require("Storage").erase("gbmusic.load.json") + let saved = require("Storage").readJSON("gbmusic.load.json", true); + require("Storage").erase("gbmusic.load.json"); if (saved) { // autoloaded: load state was saved by widget - info = saved.info - state = saved.state - delete (saved) - autoClose = true - start() + info = saved.info; + state = saved.state; + delete (saved); + autoClose = true; + start(); } else { - const s = require("Storage").readJSON("gbmusic.json", 1) || {} + const s = require("Storage").readJSON("gbmusic.json", 1) || {}; if (!("autoStart" in s)) { // user opened the app, but has not picked a setting yet // ask them about autoloading now @@ -680,12 +680,12 @@ "Automatically load\n"+ "when playing music?\n", ).then(function(autoStart) { - s.autoStart = autoStart - require("Storage").writeJSON("gbmusic.json", s) - setTimeout(start, 0) - }) + s.autoStart = autoStart; + require("Storage").writeJSON("gbmusic.json", s); + setTimeout(start, 0); + }); } else { - start() + start(); } } } From 721c0e7ba5326080cdae654f5df362731ca3e4d1 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 27 Mar 2021 22:13:50 +0100 Subject: [PATCH 2/5] gbmusic: Increase text brightness, improve memory usage --- apps.json | 2 +- apps/gbmusic/ChangeLog | 1 + apps/gbmusic/app.js | 102 ++++++++++++++++++++--------------------- 3 files changed, 53 insertions(+), 52 deletions(-) diff --git a/apps.json b/apps.json index f6ac2f7f0..2f4e4067a 100644 --- a/apps.json +++ b/apps.json @@ -3034,7 +3034,7 @@ "name": "Gadgetbridge Music Controls", "shortName":"Music Controls", "icon": "icon.png", - "version":"0.01", + "version":"0.02", "description": "Control the music on your Gadgetbridge-connected phone", "tags": "tools,bluetooth,gadgetbridge,music", "type":"app", diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index ec66c5568..25a6f010c 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -1 +1,2 @@ 0.01: Initial version +0.02: Increase text brightness, try to improve memory usage \ No newline at end of file diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index a27f4daa7..f17d07e52 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -52,6 +52,7 @@ // Smaller interval+step might be smoother, but flickers :-( interval: 200, // scroll interval in ms step: 10, // scroll speed per interval + space: 40, // pixels between scrolling text }, artist: { // center below middle font: "Vector", @@ -163,10 +164,10 @@ this.since = null; } brightness() { - if (fadeOut.since) { - return Math.max(0, 1-((Date.now()-fadeOut.since)/TIMEOUT)); + if (!fadeOut.since) { + return 1; } - return 1; + return Math.max(0, 1-((Date.now()-fadeOut.since)/TIMEOUT)); } } @@ -183,27 +184,27 @@ } draw() { const s = defaults.track; - const sep = " "; g.setFont(s.font, s.size); g.setColor(infoColor("track")); - const text = sep+info.track, - text2 = text.repeat(2), - w1 = g.stringWidth(text), + const w = g.stringWidth(info.track)+s.space, bottom = screen.height-s.bottom; - this.offset = this.offset%w1; + this.offset = this.offset%w; g.setFontAlign(-1, 1); g.clearRect(0, bottom-s.size, screen.width, bottom) - .drawString(text2, -this.offset, screen.height-s.bottom); + .drawString(info.track, -this.offset+s.space, screen.height-s.bottom) + .drawString(info.track, -this.offset+s.space+w, screen.height-s.bottom); } start() { this.offset = 0; super.start(); } stop() { + if (this.active) { + const s = defaults.track, + bottom = screen.height-s.bottom; + g.clearRect(0, bottom-s.size, screen.width, bottom); + } super.stop(); - const s = defaults.track, - bottom = screen.height-s.bottom; - g.clearRect(0, bottom-s.size, screen.width, bottom); } } @@ -214,9 +215,9 @@ force: fadeOut.active, }, options)); } - let oldText = {}; + let oldText = {}, clear = {}; function drawText(name, text, options) { - if (name in oldText && oldText[name].text===text && !(options || {}).force) { + if (name in oldText && !(name in clear) && !(options || {}).force) { return; // nothing to do } const s = Object.assign( @@ -239,20 +240,16 @@ if (name in oldText) { const old = oldText[name]; // only clear if text/area has changed - if (old.text!==text - || old.left!==left || old.top!==top - || old.w!==w || old.h!==h) { - g.clearRect(old.left, old.top, old.left+old.w, old.top+old.h); + if (name in clear || [left, top, w, h].toString()!==old.toString()) { + // left top left+w left+h + g.clearRect(old[0], old[1], old[0]+old[2], old[1]+old[3]); } } + delete clear[name]; if (text.length) { g.drawString(text, x, y); // remember which rectangle to clear before next draw - oldText[name] = { - text: text, - left: left, top: top, - w: w, h: h, - }; + oldText[name] = [left, top, w, h]; } else { delete oldText[name]; } @@ -306,6 +303,9 @@ */ let infoColors = {}; function infoColor(name) { + if (name in infoColors && !fadeOut.active) { + return infoColors[name]; + } let h, s, v; if (name==="num") { // always white @@ -313,31 +313,29 @@ s = 0; } else { // complicated scheme to make color depend deterministically on info - // s=1 and hue depends on the text, so we always get a bright color - let text = ""; + let code = 0; + const textCode = t => { + let c = 0; + for(let i = 0; i code += c.charCodeAt(0)); - // dark magic h = code%360; - s = 1; + s = 0.7; } v = fadeOut.brightness(); + // dark magic const hsv2rgb = (h, s, v) => { const f = (n) => { const k = (n+h/60)%6; @@ -352,21 +350,19 @@ return color; } - let lastTrack; function drawTrack() { // we try if we can squeeze this in with a slightly smaller font, but if // the title is too long we start up the scroller instead - const trackInfo = ([info.artist, info.album, info.n, info.track]).join("-"); - if (trackInfo===lastTrack) { - return; // already visible - } if (infoSize("track") Date: Tue, 30 Mar 2021 22:36:04 +0200 Subject: [PATCH 3/5] gbmusic: cleanup/simplify code Get rid of the whole settings+drawText(), just hardcode positions in drawSomething() functions. --- apps/gbmusic/app.js | 514 ++++++++++++++++++++------------------------ 1 file changed, 239 insertions(+), 275 deletions(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index f17d07e52..757c4b732 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -12,103 +12,47 @@ n: 0, c: 0, }; - - const screen = { - width: g.getWidth(), - height: g.getHeight(), - center: g.getWidth()/2, - middle: g.getHeight()/2, - }; - const TIMEOUT = 5*1000*60; // auto close timeout: 5 minutes - // drawText defaults - const defaults = { - time: { // top center - color: -1, - font: "Vector", - size: 24, - left: 10, - top: 30, - }, - date: { // bottom center - color: -1, - font: "Vector", - size: 16, - bottom: 26, - center: screen.width/2, - }, - num: { // top right - font: "Vector", - size: 30, - top: 30, - right: 15, - }, - track: { // center above middle - font: "Vector", - size: 40, // maximum size - min_size: 25, // scroll (at maximum size) if this doesn't fit - bottom: (screen.height/2)+10, - center: screen.width/2, - // Smaller interval+step might be smoother, but flickers :-( - interval: 200, // scroll interval in ms - step: 10, // scroll speed per interval - space: 40, // pixels between scrolling text - }, - artist: { // center below middle - font: "Vector", - size: 30, // maximum size - middle: (screen.height/2)+17, - center: screen.width/2, - }, - album: { // center below middle - font: "Vector", - size: 20, // maximum size - middle: (screen.height/2)+18, // moved down if artist is present - center: screen.width/2, - }, - // these work a bit different, as they apply to all controls - controls: { - color: "#008800", - highlight: 200, // highlight pressed controls for this long, ms - activeColor: "#ff0000", - size: 20, // icons - left: 10, // for right-side - right: 20, // for left-side (more space because of +- buttons) - top: 30, - bottom: 30, - font: "6x8", // volume buttons - volSize: 2, // volume buttons - }, - }; + /** + * Base ticker class, needs children to implement `redraw` + */ class Ticker { - constructor(interval) { + constructor(ms) { this.i = null; - this.interval = interval; + this.ms = ms; this.active = false; + this.onLCD = (on) => { + if (this.i) { + clearInterval(this.i); + this.i = null; + } + if (on) { + this.i = setInterval(() => {this.tick();}, this.ms); + this.redraw(); + } + }; } - clear() { + start() { if (this.i) { clearInterval(this.i); } - this.i = null; - } - start() { + this.i = setInterval(() => {this.tick();}, this.ms); this.active = true; - this.resume(); + Bangle.on("lcdPower", this.onLCD); } stop() { + if (this.i) { + clearInterval(this.i); + this.i = null; + } this.active = false; - this.clear(); + Bangle.removeListener("lcdPower", this.onLCD); } - pause() { - this.clear(); - } - resume() { - this.clear(); - if (this.active && Bangle.isLCDOn()) { - this.tick(); - this.i = setInterval(() => {this.tick();}, this.interval); + tick() { + // default: just redraw + if (Bangle.isLCDOn()) { + this.redraw(); } } } @@ -119,38 +63,29 @@ class Clock extends Ticker { constructor() { super(1000); + this.lastTime = -1; } tick() { - g.reset(); + // only redraw if time has changed const now = new Date; - drawText("time", this.text(now)); - drawText("date", require("locale").date(now, true)); + if (Bangle.isLCDOn() && now.getHours()*60+now.getMinutes()!==this.lastTime) { + this.redraw(); + this.lastTime = now.getHours()*60+now.getMinutes(); + } } - text(time) { - const l = require("locale"); - const is12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; - if (!is12hour) { - return l.time(time, true); - } - const date12 = new Date(time.getTime()); - const hours = date12.getHours(); - if (hours===0) { - date12.setHours(12); - } else if (hours>12) { - date12.setHours(hours-12); - } - return l.time(date12, true)+l.meridian(time); + redraw() { + drawDateTime(); } } /** - * Update all info every second while fading out + * Keep redrawing music while fading out */ class Fader extends Ticker { constructor() { - super(defaults.track.interval); // redraw at same speed as scroller + super(500); } - tick() { + redraw() { drawMusic(); } start() { @@ -160,7 +95,7 @@ stop() { super.stop(); this.since = Date.now(); // force redraw at 100% brightness - drawMusic(); + this.redraw(); this.since = null; } brightness() { @@ -176,143 +111,62 @@ */ class Scroller extends Ticker { constructor() { - super(defaults.track.interval); + super(200); } tick() { - this.offset += defaults.track.step; - this.draw(); + this.offset += 10; + if (Bangle.isLCDOn()) { + this.redraw(); + } } - draw() { - const s = defaults.track; - g.setFont(s.font, s.size); - g.setColor(infoColor("track")); - const w = g.stringWidth(info.track)+s.space, - bottom = screen.height-s.bottom; - this.offset = this.offset%w; - g.setFontAlign(-1, 1); - g.clearRect(0, bottom-s.size, screen.width, bottom) - .drawString(info.track, -this.offset+s.space, screen.height-s.bottom) - .drawString(info.track, -this.offset+s.space+w, screen.height-s.bottom); + redraw() { + drawScroller(); } start() { this.offset = 0; super.start(); } - stop() { - if (this.active) { - const s = defaults.track, - bottom = screen.height-s.bottom; - g.clearRect(0, bottom-s.size, screen.width, bottom); - } - super.stop(); - } - } - - function drawInfo(name, options) { - drawText(name, info[name], Object.assign({ - color: infoColor(name), - size: infoSize(name), - force: fadeOut.active, - }, options)); - } - let oldText = {}, clear = {}; - function drawText(name, text, options) { - if (name in oldText && !(name in clear) && !(options || {}).force) { - return; // nothing to do - } - const s = Object.assign( - // deep clone defaults to prevent them being overwritten with options - JSON.parse(JSON.stringify(defaults[name])), - options || {}, - ); - g.setColor(s.color); - g.setFont(s.font, s.size); - const ax = "left" in s ? -1 : ("right" in s ? 1 : 0), - ay = "top" in s ? -1 : ("bottom" in s ? 1 : 0); - g.setFontAlign(ax, ay); - // drawString coordinates - const x = "left" in s ? s.left : ("right" in s ? screen.width-s.right : s.center), - y = "top" in s ? s.top : ("bottom" in s ? screen.height-s.bottom : s.middle); - // bounding rectangle - const w = g.stringWidth(text), h = g.getFontHeight(), - left = "left" in s ? x : ("right" in s ? x-w : x-w/2), - top = "top" in s ? y : ("bottom" in s ? y-h : y-h/2); - if (name in oldText) { - const old = oldText[name]; - // only clear if text/area has changed - if (name in clear || [left, top, w, h].toString()!==old.toString()) { - // left top left+w left+h - g.clearRect(old[0], old[1], old[0]+old[2], old[1]+old[3]); - } - } - delete clear[name]; - if (text.length) { - g.drawString(text, x, y); - // remember which rectangle to clear before next draw - oldText[name] = [left, top, w, h]; - } else { - delete oldText[name]; - } } /** - * - * @param text + * @param {string} text * @return {number} Maximum font size to make text fit on screen */ function fitText(text) { if (!text.length) { return Infinity; } - // Vector: make a guess, then shrink/grow until it fits - const getWidth = (size) => g.setFont("Vector", size).stringWidth(text) - , sw = screen.width; - let guess = Math.round(sw/(text.length*0.6)); - if (getWidth(guess)===sw) { // good guess! + // make a guess, then shrink/grow until it fits + const getWidth = (size) => g.setFont("Vector", size).stringWidth(text); + let guess = Math.floor(24000/getWidth(100)); + if (getWidth(guess)===240) { // good guess! return guess; } - if (getWidth(guess) target + // width > 240 do { guess--; - } while(getWidth(guess)>sw); + } while(getWidth(guess)>240); return guess; } - /** - * @param name - * @return {number} Font size to use for given info - */ - function infoSize(name) { - if (name==="num") { // fixed size - return defaults[name].size; - } - return Math.min( - defaults[name].size, - fitText(info[name]), - ); - } /** * @param name * @return {string} Semi-random color to use for given info */ - let infoColors = {}; function infoColor(name) { - if (name in infoColors && !fadeOut.active) { - return infoColors[name]; - } let h, s, v; if (name==="num") { // always white h = 0; s = 0; } else { - // complicated scheme to make color depend deterministically on info + // make color depend deterministically on info let code = 0; const textCode = t => { let c = 0; @@ -345,40 +199,156 @@ }; const rgb = hsv2rgb(h, s, v); const f2hex = (f) => ("00"+(Math.round(f*255)).toString(16)).substr(-2); - const color = "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); - infoColors[name] = color; - return color; + return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); + } + /** + * Remember track color until info changes + * Because we need this every time we move the scroller + * @return {string} + */ + function trackColor() { + if (!("track_color" in info) || fadeOut.active) { + info.track_color = infoColor("track"); + } + return info.track_color; } + //////////////////// + // Drawing functions + //////////////////// + /** + * Draw date and time + * @return {*} + */ + function drawDateTime() { + const now = new Date; + const l = require("locale"); + const is12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + let time; + if (is12hour) { + const date12 = new Date(now.getTime()); + const hours = date12.getHours(); + if (hours===0) { + date12.setHours(12); + } else if (hours>12) { + date12.setHours(hours-12); + } + time = l.time(date12, true)+l.meridian(now); + } else { + time = l.time(now, true); + } + g.reset(); + g.setFont("Vector", 24) + .setFontAlign(-1, -1) // top left + .clearRect(10, 30, 119, 54) + .drawString(time, 10, 30); + + const date = require("locale").date(now, true); + g.setFont("Vector", 16) + .setFontAlign(0, 1) // bottom center + .setClipRect(35, 198, 199, 214) + .clearRect(31, 198, 199, 214) + .drawString(date, 119, 240-26); + } + + /** + * Draw track number and total count + */ + function drawNum() { + let num = ""; + if ("n" in info && info.n>0) { + num = "#"+info.n; + if ("c" in info && info.c>0) { // I've seen { c:-1 } + num += "/"+info.c; + } + } + g.reset(); + g.setFont("Vector", 30) + .setFontAlign(1, -1) // top right + .setClipRect(225, 30, 120, 60) + .clearRect(225, 30, 120, 60) + .drawString(num, 225, 30); + } + /** + * Clear rectangle used by track title + */ + function clearTrack() { + g.clearRect(0, 60, 239, 119); + } + /** + * Draw track title + */ function drawTrack() { - // we try if we can squeeze this in with a slightly smaller font, but if - // the title is too long we start up the scroller instead - if (infoSize("track")40) { + size = 40; + } + if (size<25) { + // the title is too long: start up the scroller if (!scroller.active) { scroller.start(); } - } else { - if (scroller.active) { - scroller.stop(); - } - drawInfo("track"); + return; + } else if (scroller.active) { + scroller.stop(); + } + // stationary track + g.reset(); + g.setFont("Vector", size) + .setFontAlign(0, 1) // center bottom + .setColor(trackColor()); + clearTrack(); + g.drawString(info.track, 119, 109); + } + /** + * Draw scrolling track title + */ + function drawScroller() { + g.reset(); + g.setFont("Vector", 40); + const w = g.stringWidth(info.track)+40; + scroller.offset = scroller.offset%w; + g.setFontAlign(-1, 1) // left bottom + .setColor(trackColor()); + clearTrack(); + g.drawString(info.track, -scroller.offset+40, 109) + .drawString(info.track, -scroller.offset+40+w, 109); + } + + /** + * Draw track artist and album + */ + function drawArtistAlbum() { + // we just use small enough fonts to make these always fit + // calculate stuff before clear+redraw + const artistColor = infoColor("artist"); + const albumColor = infoColor("album"); + let artistSize = fitText(info.artist); + if (artistSize>30) { + artistSize = 30; + } + let albumSize = fitText(info.album); + if (albumSize>20) { + albumSize = 20; + } + g.reset(); + g.clearRect(0, 120, 240, 189); + let top = 124; + if (info.artist) { + g.setFont("Vector", artistSize) + .setFontAlign(0, -1) // center top + .setColor(artistColor) + .drawString(info.artist, 119, top); + top += artistSize+4; // fit album neatly under artist + } + if (info.album) { + g.setFont("Vector", albumSize) + .setFontAlign(0, -1) // center top + .setColor(albumColor) + .drawString(info.album, 119, top); } } - function drawArtistAlbum() { - // we just use small enough fonts to make these always fit - let album_middle = defaults.album.middle; - const artist_size = infoSize("artist"); - if (info.artist) { - album_middle += defaults.artist.size; - } - drawInfo("artist", { - size: artist_size, - }); - drawInfo("album", { - middle: album_middle, - }); - } const icons = { pause: function(x, y, s) { const w1 = s/3; @@ -412,16 +382,15 @@ }, }; function controlColor(control) { - const s = defaults.controls; if (volCmd && control===volCmd) { // volume button kept pressed down - return s.activeColor; + return "#ff0000"; } - return (control in tCommand) ? s.activeColor : s.color; + return (control in tCommand) ? "#ff0000" : "#008800"; } function drawControl(control, x, y) { g.setColor(controlColor(control)); - const s = defaults.controls.size; + const s = 20; if (state!==controlState) { g.clearRect(x, y, x+s, y+s); } @@ -429,44 +398,49 @@ } let controlState; function drawControls() { - const s = defaults.controls; + g.reset(); if (state==="play") { // left touch - drawControl("pause", s.left, screen.height-(s.bottom+s.size)); + drawControl("pause", 10, 190); // right touch - drawControl("next", screen.width-(s.right+s.size), screen.height-(s.bottom+s.size)); + drawControl("next", 200, 190); } else { - drawControl("previous", s.left, screen.height-(s.bottom+s.size)); - drawControl("play", screen.width-(s.right+s.size), screen.height-(s.bottom+s.size)); + drawControl("previous", 10, 190); + drawControl("play", 200, 190); } - g.setFont("6x8", s.volSize); + g.setFont("6x8", 2); // BTN1 g.setFontAlign(1, -1); g.setColor(controlColor("volumeup")); - g.drawString("+", screen.width, s.top); + g.drawString("+", 240, 30); // BTN2 g.setFontAlign(1, 1); g.setColor(controlColor("volumedown")); - g.drawString("-", screen.width, screen.height-s.bottom); + g.drawString("-", 240, 210); controlState = state; } - function setNumInfo() { - info.num = ""; - if ("n" in info && info.n>0) { - info.num = "#"+info.n; - if ("c" in info && info.c>0) { // I've seen { c:-1 } - info.num += "/"+info.c; - } - } - } function drawMusic() { - g.reset(); - setNumInfo(); - drawInfo("num"); + drawNum(); drawTrack(); drawArtistAlbum(); } + + ///////////////////////// + + /** + * Update music info + * @param event + */ + function setInfo(event) { + info = event; + delete (info.t); + scroller.offset = 0; + if (Bangle.isLCDOn()) { + drawMusic(); + } + } + let tQuit; function updateState() { // if paused for five minutes, load the clock @@ -475,6 +449,7 @@ clearTimeout(tQuit); } tQuit = null; + fadeOut.stop(); if (state!=="play" && autoClose) { if (state==="stop") { // never actually happens with my phone :-( load(); @@ -482,10 +457,10 @@ tQuit = setTimeout(load, TIMEOUT); fadeOut.start(); } - } else { - fadeOut.stop(); } - drawControls(); + if (Bangle.isLCDOn()) { + drawControls(); + } } // create tickers @@ -497,19 +472,6 @@ // Events //////////////////// - // pause timers while screen is off - Bangle.on("lcdPower", on => { - if (on) { - clock.resume(); - scroller.resume(); - fadeOut.resume(); - } else { - clock.pause(); - scroller.pause(); - fadeOut.pause(); - } - }); - let tLauncher; // we put starting of watches inside a function, so we can defer it until we // asked the user about autoStart @@ -545,7 +507,7 @@ tCommand[command] = setTimeout(function() { delete tCommand[command]; drawControls(); - }, defaults.controls.highlight); + }, 200); drawControls(); } @@ -629,6 +591,7 @@ startLauncherWatch(); startTouchWatches(); } + function start() { // start listening for music updates const _GB = global.GB; @@ -636,12 +599,7 @@ // we eat music events! switch(event.t) { case "musicinfo": - info = event; - infoColors = {}; - scroller.offset = 0; - delete (info.t); - clear.artist = clear.album = clear.title = clear.num = true; - drawMusic(); + setInfo(event); break; case "musicstate": state = event.state; @@ -652,7 +610,7 @@ if (_GB) { setTimeout(_GB, 0, event); } - return; // no drawMusic + return; } }; drawMusic(); @@ -660,6 +618,12 @@ startWatches(); clock.start(); startEmulator(); + Bangle.on("lcdPower", function(on) { + if (on) { + drawMusic(); + drawControls(); + } + }); } let saved = require("Storage").readJSON("gbmusic.load.json", true); From 2de7a2dea0f37f25d4f9937b9a7ea0c00aa96286 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Wed, 31 Mar 2021 03:22:58 +0200 Subject: [PATCH 4/5] gbmusic: simplify code some more Remove Ticker classes, shorten some variable names --- apps/gbmusic/app.js | 1098 +++++++++++++++++++++---------------------- 1 file changed, 535 insertions(+), 563 deletions(-) diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 757c4b732..22566c5b3 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -2,354 +2,313 @@ /** * Control the music on your Gadgetbridge-connected phone **/ -{ - let autoClose = false; // only if opened automatically - let state = ""; - let info = { - artist: "", - album: "", - track: "", - n: 0, - c: 0, - }; - const TIMEOUT = 5*1000*60; // auto close timeout: 5 minutes +let auto = false; // auto close if opened automatically +let stat = ""; +let info = { + artist: "", + album: "", + track: "", + n: 0, + c: 0, +}; +const TOUT = 300000; // auto close timeout: 5 minutes (in ms) - /** - * Base ticker class, needs children to implement `redraw` - */ - class Ticker { - constructor(ms) { - this.i = null; - this.ms = ms; - this.active = false; - this.onLCD = (on) => { - if (this.i) { - clearInterval(this.i); - this.i = null; - } - if (on) { - this.i = setInterval(() => {this.tick();}, this.ms); - this.redraw(); - } - }; - } - start() { - if (this.i) { - clearInterval(this.i); - } - this.i = setInterval(() => {this.tick();}, this.ms); - this.active = true; - Bangle.on("lcdPower", this.onLCD); - } - stop() { - if (this.i) { - clearInterval(this.i); - this.i = null; - } - this.active = false; - Bangle.removeListener("lcdPower", this.onLCD); - } - tick() { - // default: just redraw - if (Bangle.isLCDOn()) { - this.redraw(); - } - } +/////////////////////// +// Self-repeating timeouts +/////////////////////// + +// Clock +let tock = -1; +function tick() { + if (!Bangle.isLCDOn()) { + return; } - - /** - * Draw time and date - */ - class Clock extends Ticker { - constructor() { - super(1000); - this.lastTime = -1; - } - tick() { - // only redraw if time has changed - const now = new Date; - if (Bangle.isLCDOn() && now.getHours()*60+now.getMinutes()!==this.lastTime) { - this.redraw(); - this.lastTime = now.getHours()*60+now.getMinutes(); - } - } - redraw() { - drawDateTime(); - } + const now = new Date; + if (now.getHours()*60+now.getMinutes()!==tock) { + drawDateTime(); + tock = now.getHours()*60+now.getMinutes(); } + setTimeout(tick, 1000); // we only show minute precision anyway +} - /** - * Keep redrawing music while fading out - */ - class Fader extends Ticker { - constructor() { - super(500); - } - redraw() { - drawMusic(); - } - start() { - this.since = Date.now(); - super.start(); - } - stop() { - super.stop(); - this.since = Date.now(); // force redraw at 100% brightness - this.redraw(); - this.since = null; - } - brightness() { - if (!fadeOut.since) { - return 1; - } - return Math.max(0, 1-((Date.now()-fadeOut.since)/TIMEOUT)); - } +// Fade out while paused and auto closing +let fade = null; +function fadeOut() { + if (!Bangle.isLCDOn() || !fade) { + return; } - - /** - * Scroll long track names - */ - class Scroller extends Ticker { - constructor() { - super(200); - } - tick() { - this.offset += 10; - if (Bangle.isLCDOn()) { - this.redraw(); - } - } - redraw() { - drawScroller(); - } - start() { - this.offset = 0; - super.start(); - } + drawMusic(); + setTimeout(fadeOut, 500); +} +function brightness() { + if (!fade) { + return 1; } + return Math.max(0, 1-((Date.now()-fade)/TOUT)); +} - /** - * @param {string} text - * @return {number} Maximum font size to make text fit on screen - */ - function fitText(text) { - if (!text.length) { - return Infinity; +// Scroll long track names +// use an interval to get smooth movement +let offset = null, // scroll Offset: null = no scrolling + scrollI; +function scroll() { + offset += 10; + drawScroller(); +} +function scrollStart() { + if (offset!==null) { + return; // already started + } + offset = 0; + if (Bangle.isLCDOn()) { + if (!scrollI) { + scrollI = setInterval(scroll, 200); } - // make a guess, then shrink/grow until it fits - const getWidth = (size) => g.setFont("Vector", size).stringWidth(text); - let guess = Math.floor(24000/getWidth(100)); - if (getWidth(guess)===240) { // good guess! - return guess; - } - if (getWidth(guess)<240) { - do { - guess++; - } while(getWidth(guess)<=240); - return guess-1; - } - // width > 240 + drawScroller(); + } +} +function scrollStop() { + if (scrollI) { + clearInterval(scrollI); + scrollI = null; + } + offset = null; +} + +/** + * @param {string} text + * @return {number} Maximum font size to make text fit on screen + */ +function fitText(text) { + if (!text.length) { + return Infinity; + } + // make a guess, then shrink/grow until it fits + const test = (s) => g.setFont("Vector", s).stringWidth(text); + let best = Math.floor(24000/test(100)); + if (test(best)===240) { // good guess! + return best; + } + if (test(best)<240) { do { - guess--; - } while(getWidth(guess)>240); - return guess; + best++; + } while(test(best)<=240); + return best-1; } + // width > 240 + do { + best--; + } while(test(best)>240); + return best; +} - /** - * @param name - * @return {string} Semi-random color to use for given info - */ - function infoColor(name) { - let h, s, v; - if (name==="num") { - // always white - h = 0; - s = 0; - } else { - // make color depend deterministically on info - let code = 0; - const textCode = t => { - let c = 0; - for(let i = 0; i { - const f = (n) => { - const k = (n+h/60)%6; - return v-v*s*Math.max(Math.min(k, 4-k, 1), 0); - }; - return {r: f(5), g: f(3), b: f(1)}; - }; - const rgb = hsv2rgb(h, s, v); - const f2hex = (f) => ("00"+(Math.round(f*255)).toString(16)).substr(-2); - return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); +/** + * @param {string} text + * @return {number} Randomish but deterministic number from 0-360 for text + */ +function textCode(text) { + "ram"; + let code = 0; + for(let i = 0; i { + const k = (n+h/60)%6; + return v-v*s*Math.max(Math.min(k, 4-k, 1), 0); + }; + return {r: f(5), g: f(3), b: f(1)}; +} +function f2hex(f) { + return ("00"+(Math.round(f*255)).toString(16)).substr(-2); +} +/** + * @param name + * @return {string} Semi-random color to use for given info + */ +function infoColor(name) { + let h, s, v; + if (name==="num") { + // always white + h = 0; + s = 0; + } else { + // make color depend deterministically on info + let code = 0; + switch(name) { + case "track": + code += textCode(info.track); + // fallthrough: also use album+artist + case "album": + code += textCode(info.album); + // fallthrough: also use artist + default: + code += textCode(info[name]); } - return info.track_color; + h = code%360; + s = 0.7; } + v = brightness(); + const rgb = hsv2rgb(h, s, v); + return "#"+f2hex(rgb.r)+f2hex(rgb.g)+f2hex(rgb.b); +} +/** + * Remember track color until info changes + * Because we need this every time we move the scroller + * @return {string} + */ +function trackColor() { + if (!("track_color" in info) || fade) { + info.track_color = infoColor("track"); + } + return info.track_color; +} - //////////////////// - // Drawing functions - //////////////////// - /** - * Draw date and time - * @return {*} - */ - function drawDateTime() { - const now = new Date; - const l = require("locale"); - const is12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; - let time; - if (is12hour) { - const date12 = new Date(now.getTime()); - const hours = date12.getHours(); - if (hours===0) { - date12.setHours(12); - } else if (hours>12) { - date12.setHours(hours-12); - } - time = l.time(date12, true)+l.meridian(now); - } else { - time = l.time(now, true); +//////////////////// +// Drawing functions +//////////////////// +/** + * Draw date and time + * @return {*} + */ +function drawDateTime() { + const now = new Date; + const l = require("locale"); + const is12 = (require("Storage").readJSON("setting.json", 1) || {})["12hour"]; + let time; + if (is12) { + const d12 = new Date(now.getTime()); + const hour = d12.getHours(); + if (hour===0) { + d12.setHours(12); + } else if (hour>12) { + d12.setHours(hour-12); } - g.reset(); - g.setFont("Vector", 24) - .setFontAlign(-1, -1) // top left - .clearRect(10, 30, 119, 54) - .drawString(time, 10, 30); + time = l.time(d12, true)+l.meridian(now); + } else { + time = l.time(now, true); + } + g.reset(); + g.setFont("Vector", 24) + .setFontAlign(-1, -1) // top left + .clearRect(10, 30, 119, 54) + .drawString(time, 10, 30); - const date = require("locale").date(now, true); - g.setFont("Vector", 16) - .setFontAlign(0, 1) // bottom center - .setClipRect(35, 198, 199, 214) - .clearRect(31, 198, 199, 214) - .drawString(date, 119, 240-26); - } + const date = require("locale").date(now, true); + g.setFont("Vector", 16) + .setFontAlign(0, 1) // bottom center + .setClipRect(35, 198, 199, 214) + .clearRect(31, 198, 199, 214) + .drawString(date, 119, 240-26); +} - /** - * Draw track number and total count - */ - function drawNum() { - let num = ""; - if ("n" in info && info.n>0) { - num = "#"+info.n; - if ("c" in info && info.c>0) { // I've seen { c:-1 } - num += "/"+info.c; - } +/** + * Draw track number and total count + */ +function drawNum() { + let num = ""; + if ("n" in info && info.n>0) { + num = "#"+info.n; + if ("c" in info && info.c>0) { // I've seen { c:-1 } + num += "/"+info.c; } - g.reset(); - g.setFont("Vector", 30) - .setFontAlign(1, -1) // top right - .setClipRect(225, 30, 120, 60) - .clearRect(225, 30, 120, 60) - .drawString(num, 225, 30); } - /** - * Clear rectangle used by track title - */ - function clearTrack() { - g.clearRect(0, 60, 239, 119); + g.reset(); + g.setFont("Vector", 30) + .setFontAlign(1, -1) // top right + .clearRect(225, 30, 120, 60) + .drawString(num, 225, 30); +} +/** + * Clear rectangle used by track title + */ +function clearTrack() { + g.clearRect(0, 60, 239, 119); +} +/** + * Draw track title + */ +function drawTrack() { + let size = fitText(info.track); + if (size<25) { + // the title is too long: start the scroller + scrollStart(); + return; + } else { + scrollStop(); } - /** - * Draw track title - */ - function drawTrack() { - let size = fitText(info.track); - if (size>40) { - size = 40; - } - if (size<25) { - // the title is too long: start up the scroller - if (!scroller.active) { - scroller.start(); - } - return; - } else if (scroller.active) { - scroller.stop(); - } - // stationary track - g.reset(); - g.setFont("Vector", size) - .setFontAlign(0, 1) // center bottom - .setColor(trackColor()); - clearTrack(); - g.drawString(info.track, 119, 109); - } - /** - * Draw scrolling track title - */ - function drawScroller() { - g.reset(); - g.setFont("Vector", 40); - const w = g.stringWidth(info.track)+40; - scroller.offset = scroller.offset%w; - g.setFontAlign(-1, 1) // left bottom - .setColor(trackColor()); - clearTrack(); - g.drawString(info.track, -scroller.offset+40, 109) - .drawString(info.track, -scroller.offset+40+w, 109); + // stationary track + if (size>40) { + size = 40; } + g.reset(); + g.setFont("Vector", size) + .setFontAlign(0, 1) // center bottom + .setColor(trackColor()); + clearTrack(); + g.drawString(info.track, 119, 109); +} +/** + * Draw scrolling track title + */ +function drawScroller() { + g.reset(); + g.setFont("Vector", 40); + const w = g.stringWidth(info.track)+40; + offset = offset%w; + g.setFontAlign(-1, 1) // left bottom + .setColor(trackColor()); + clearTrack(); + g.drawString(info.track, -offset+40, 109) + .drawString(info.track, -offset+40+w, 109); +} - /** - * Draw track artist and album - */ - function drawArtistAlbum() { - // we just use small enough fonts to make these always fit - // calculate stuff before clear+redraw - const artistColor = infoColor("artist"); - const albumColor = infoColor("album"); - let artistSize = fitText(info.artist); - if (artistSize>30) { - artistSize = 30; - } - let albumSize = fitText(info.album); - if (albumSize>20) { - albumSize = 20; - } - g.reset(); - g.clearRect(0, 120, 240, 189); - let top = 124; - if (info.artist) { - g.setFont("Vector", artistSize) - .setFontAlign(0, -1) // center top - .setColor(artistColor) - .drawString(info.artist, 119, top); - top += artistSize+4; // fit album neatly under artist - } - if (info.album) { - g.setFont("Vector", albumSize) - .setFontAlign(0, -1) // center top - .setColor(albumColor) - .drawString(info.album, 119, top); - } +/** + * Draw track artist and album + */ +function drawArtistAlbum() { + // we just use small enough fonts to make these always fit + // calculate stuff before clear+redraw + const aCol = infoColor("artist"); + const bCol = infoColor("album"); + let aSiz = fitText(info.artist); + if (aSiz>30) { + aSiz = 30; } + let bSiz = fitText(info.album); + if (bSiz>20) { + bSiz = 20; + } + g.reset(); + g.clearRect(0, 120, 240, 189); + let top = 124; + if (info.artist) { + g.setFont("Vector", aSiz) + .setFontAlign(0, -1) // center top + .setColor(aCol) + .drawString(info.artist, 119, top); + top += aSiz+4; // fit album neatly under artist + } + if (info.album) { + g.setFont("Vector", bSiz) + .setFontAlign(0, -1) // center top + .setColor(bCol) + .drawString(info.album, 119, top); + } +} - const icons = { +/** + * + * @param {string} icon Icon name + * @param {number} x + * @param {number} y + * @param {number} s Icon size + */ +function drawIcon(icon, x, y, s) { + ({ pause: function(x, y, s) { const w1 = s/3; g.drawRect(x, y, x+w1, y+s); @@ -380,260 +339,271 @@ ], true); g.drawRect(x+w2, y, x+s, y+s); }, - }; - function controlColor(control) { - if (volCmd && control===volCmd) { - // volume button kept pressed down - return "#ff0000"; - } - return (control in tCommand) ? "#ff0000" : "#008800"; + })[icon](x, y, s); +} +function controlColor(ctrl) { + if (vCmd && ctrl===vCmd) { + // volume button kept pressed down + return "#ff0000"; } - function drawControl(control, x, y) { - g.setColor(controlColor(control)); - const s = 20; - if (state!==controlState) { - g.clearRect(x, y, x+s, y+s); - } - icons[control](x, y, s); + return (ctrl in tCommand) ? "#ff0000" : "#008800"; +} +function drawControl(ctrl, x, y) { + g.setColor(controlColor(ctrl)); + const s = 20; + if (stat!==controlState) { + g.clearRect(x, y, x+s, y+s); } - let controlState; - function drawControls() { - g.reset(); - if (state==="play") { - // left touch - drawControl("pause", 10, 190); - // right touch - drawControl("next", 200, 190); - } else { - drawControl("previous", 10, 190); - drawControl("play", 200, 190); - } - g.setFont("6x8", 2); - // BTN1 - g.setFontAlign(1, -1); - g.setColor(controlColor("volumeup")); - g.drawString("+", 240, 30); - // BTN2 - g.setFontAlign(1, 1); - g.setColor(controlColor("volumedown")); - g.drawString("-", 240, 210); - controlState = state; + drawIcon(ctrl, x, y, s); +} +let controlState; +function drawControls() { + g.reset(); + if (stat==="play") { + // left touch + drawControl("pause", 10, 190); + // right touch + drawControl("next", 200, 190); + } else { + drawControl("previous", 10, 190); + drawControl("play", 200, 190); } + g.setFont("6x8", 2); + // BTN1 + g.setFontAlign(1, -1); + g.setColor(controlColor("volumeup")); + g.drawString("+", 240, 30); + // BTN2 + g.setFontAlign(1, 1); + g.setColor(controlColor("volumedown")); + g.drawString("-", 240, 210); + controlState = stat; +} - function drawMusic() { - drawNum(); - drawTrack(); - drawArtistAlbum(); - } +function drawMusic() { + drawNum(); + drawTrack(); + drawArtistAlbum(); +} - ///////////////////////// - - /** - * Update music info - * @param event - */ - function setInfo(event) { - info = event; - delete (info.t); - scroller.offset = 0; - if (Bangle.isLCDOn()) { - drawMusic(); - } - } - - let tQuit; - function updateState() { - // if paused for five minutes, load the clock - // (but timeout resets if we get new info, even while paused) - if (tQuit) { - clearTimeout(tQuit); - } - tQuit = null; - fadeOut.stop(); - if (state!=="play" && autoClose) { - if (state==="stop") { // never actually happens with my phone :-( - load(); - } else { // also quit when paused for a long time - tQuit = setTimeout(load, TIMEOUT); - fadeOut.start(); - } - } - if (Bangle.isLCDOn()) { - drawControls(); - } - } - - // create tickers - const clock = new Clock(); - const fadeOut = new Fader(); - const scroller = new Scroller(); - - //////////////////// - // Events - //////////////////// - - let tLauncher; - // we put starting of watches inside a function, so we can defer it until we - // asked the user about autoStart - function startLauncherWatch() { - // long-press: launcher - // short-press: toggle play/pause - setWatch(function() { - if (tLauncher) { - clearTimeout(tLauncher); - } - tLauncher = setTimeout(Bangle.showLauncher, 1000); - }, BTN2, {repeat: true, edge: "rising"}); - setWatch(function() { - if (tLauncher) { - clearTimeout(tLauncher); - tLauncher = null; - } - togglePlay(); - }, BTN2, {repeat: true, edge: "falling"}); - } - - let tCommand = {}; - /** - * Send command and highlight corresponding control - * @param command "play/pause/next/previous/volumeup/volumedown" - */ - function sendCommand(command) { - Bluetooth.println(JSON.stringify({t: "music", n: command})); - // for controlColor - if (command in tCommand) { - clearTimeout(tCommand[command]); - } - tCommand[command] = setTimeout(function() { - delete tCommand[command]; - drawControls(); - }, 200); - drawControls(); - } - - // BTN1/3: volume control (with repeat after long-press) - let tVol, volCmd; - function volUp() { - volStart("up"); - } - function volDown() { - volStart("down"); - } - function volStart(dir) { - const command = "volume"+dir; - stopVol(); - sendCommand(command); - volCmd = command; - tVol = setTimeout(repeatVol, 500); - } - function repeatVol() { - sendCommand(volCmd); - tVol = setTimeout(repeatVol, 100); - } - function stopVol() { - if (tVol) { - clearTimeout(tVol); - tVol = null; - } - volCmd = null; - drawControls(); - } - function startVolWatches() { - setWatch(volUp, BTN1, {repeat: true, edge: "rising"}); - setWatch(stopVol, BTN1, {repeat: true, edge: "falling"}); - setWatch(volDown, BTN3, {repeat: true, edge: "rising"}); - setWatch(stopVol, BTN3, {repeat: true, edge: "falling"}); - } - - // touch/swipe: navigation - function togglePlay() { - sendCommand(state==="play" ? "pause" : "play"); - } - function startTouchWatches() { - Bangle.on("touch", function(side) { - switch(side) { - case 1: - sendCommand(state==="play" ? "pause" : "previous"); - break; - case 2: - sendCommand(state==="play" ? "next" : "play"); - break; - case 3: - togglePlay(); - } - }); - Bangle.on("swipe", function(dir) { - sendCommand(dir===1 ? "previous" : "next"); - }); - } - ///////////////////// - // Startup - ///////////////////// - // check for saved music state (by widget) to load - g.clear(); - global.gbmusic_active = true; // we don't need our widget - Bangle.loadWidgets(); - Bangle.drawWidgets(); - delete (global.gbmusic_active); - - function startEmulator() { - if (typeof Bluetooth==="undefined") { // emulator! - Bluetooth = { - println: (line) => {console.log("Bluetooth:", line);}, - }; - // some example info - GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); - GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); - } - } - function startWatches() { - startVolWatches(); - startLauncherWatch(); - startTouchWatches(); - } - - function start() { - // start listening for music updates - const _GB = global.GB; - global.GB = (event) => { - // we eat music events! - switch(event.t) { - case "musicinfo": - setInfo(event); - break; - case "musicstate": - state = event.state; - updateState(); - break; - default: - // pass on other events - if (_GB) { - setTimeout(_GB, 0, event); - } - return; - } - }; +//////////////////////// +// GB event handlers +/////////////////////// +/** + * Update music info + * @param e + */ +function musicInfo(e) { + info = e; + delete (info.t); + offset = null; + if (Bangle.isLCDOn()) { drawMusic(); - updateState(); - startWatches(); - clock.start(); - startEmulator(); - Bangle.on("lcdPower", function(on) { - if (on) { - drawMusic(); - drawControls(); - } - }); } +} +let tXit; +function musicState(e) { + stat = e.state; + // if paused for five minutes, load the clock + // (but timeout resets if we get new info, even while paused) + if (tXit) { + clearTimeout(tXit); + } + tXit = null; + fade = null; + delete info.track_color; + if (stat!=="play" && auto) { + if (stat==="stop") { // never actually happens with my phone :-( + load(); + } else { // also quit when paused for a long time + tXit = setTimeout(load, TOUT); + fade = Date.now(); + fadeOut(); + } + } + if (Bangle.isLCDOn()) { + drawControls(); + } +} + +//////////////////// +// Events +//////////////////// + +let tLauncher; +// we put starting of watches inside a function, so we can defer it until we +// asked the user about autoStart +function startLauncherWatch() { + // long-press: launcher + // short-press: toggle play/pause + setWatch(function() { + if (tLauncher) { + clearTimeout(tLauncher); + } + tLauncher = setTimeout(Bangle.showLauncher, 1000); + }, BTN2, {repeat: true, edge: "rising"}); + setWatch(function() { + if (tLauncher) { + clearTimeout(tLauncher); + tLauncher = null; + } + togglePlay(); + }, BTN2, {repeat: true, edge: "falling"}); +} + +let tCommand = {}; +/** + * Send command and highlight corresponding control + * @param command "play/pause/next/previous/volumeup/volumedown" + */ +function sendCommand(command) { + Bluetooth.println(JSON.stringify({t: "music", n: command})); + // for controlColor + if (command in tCommand) { + clearTimeout(tCommand[command]); + } + tCommand[command] = setTimeout(function() { + delete tCommand[command]; + drawControls(); + }, 200); + drawControls(); +} + +// BTN1/3: volume control (with repeat after long-press) +let tVol, vCmd; +function volUp() { + volStart("up"); +} +function volDown() { + volStart("down"); +} +function volStart(dir) { + const command = "volume"+dir; + stopVol(); + sendCommand(command); + vCmd = command; + tVol = setTimeout(repeatVol, 500); +} +function repeatVol() { + sendCommand(vCmd); + tVol = setTimeout(repeatVol, 100); +} +function stopVol() { + if (tVol) { + clearTimeout(tVol); + tVol = null; + } + vCmd = null; + drawControls(); +} +function startVolWatches() { + setWatch(volUp, BTN1, {repeat: true, edge: "rising"}); + setWatch(stopVol, BTN1, {repeat: true, edge: "falling"}); + setWatch(volDown, BTN3, {repeat: true, edge: "rising"}); + setWatch(stopVol, BTN3, {repeat: true, edge: "falling"}); +} + +// touch/swipe: navigation +function togglePlay() { + sendCommand(stat==="play" ? "pause" : "play"); +} +function startTouchWatches() { + Bangle.on("touch", function(side) { + switch(side) { + case 1: + sendCommand(stat==="play" ? "pause" : "previous"); + break; + case 2: + sendCommand(stat==="play" ? "next" : "play"); + break; + case 3: + togglePlay(); + } + }); + Bangle.on("swipe", function(dir) { + sendCommand(dir===1 ? "previous" : "next"); + }); +} +///////////////////// +// Startup +///////////////////// +// check for saved music stat (by widget) to load +g.clear(); +global.gbmusic_active = true; // we don't need our widget +Bangle.loadWidgets(); +Bangle.drawWidgets(); +delete (global.gbmusic_active); + +function startEmulator() { + if (typeof Bluetooth==="undefined") { // emulator! + Bluetooth = { + println: (line) => {console.log("Bluetooth:", line);}, + }; + // some example info + GB({"t": "musicinfo", "artist": "Some Artist Name", "album": "The Album Name", "track": "The Track Title Goes Here", "dur": 241, "c": 2, "n": 2}); + GB({"t": "musicstate", "state": "play", "position": 0, "shuffle": 1, "repeat": 1}); + } +} +function startWatches() { + startVolWatches(); + startLauncherWatch(); + startTouchWatches(); +} + +function start() { + // start listening for music updates + const _GB = global.GB; + global.GB = (event) => { + // we eat music events! + switch(event.t) { + case "musicinfo": + musicInfo(event); + break; + case "musicstate": + musicState(event); + break; + default: + // pass on other events + if (_GB) { + setTimeout(_GB, 0, event); + } + return; + } + }; + drawMusic(); + drawControls(); + startWatches(); + tick(); + startEmulator(); + Bangle.on("lcdPower", (on) => { + if (on) { + tick(); + drawMusic(); + drawControls(); + fadeOut(); + if (offset!==null) { + drawScroller(); + scrollI = setInterval(scroll, 200); + } + } else { + if (scrollI) { + clearInterval(scrollI); + scrollI = null; + } + } + }); +} + +function init() { let saved = require("Storage").readJSON("gbmusic.load.json", true); require("Storage").erase("gbmusic.load.json"); if (saved) { // autoloaded: load state was saved by widget info = saved.info; - state = saved.state; + stat = saved.state; delete (saved); - autoClose = true; + auto = true; start(); } else { const s = require("Storage").readJSON("gbmusic.json", 1) || {}; @@ -653,3 +623,5 @@ } } } +init(); + From 66c5284d23fc5936844c17e44c87dfd02770169a Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Sat, 10 Apr 2021 18:32:20 +0200 Subject: [PATCH 5/5] gbmusic: improve controls Using BTN3-rising for `volumedown` meant that everytime you hold BTN3 to reload the watch it first messes with your volume. So now we listen for falling edge only. Also add double/triple pressing BTN2 for next/previous. And fix a bug with the scroller interval. --- apps/gbmusic/ChangeLog | 2 +- apps/gbmusic/README.md | 10 ++- apps/gbmusic/app.js | 164 ++++++++++++++++++++--------------------- 3 files changed, 87 insertions(+), 89 deletions(-) diff --git a/apps/gbmusic/ChangeLog b/apps/gbmusic/ChangeLog index 25a6f010c..4fa99c934 100644 --- a/apps/gbmusic/ChangeLog +++ b/apps/gbmusic/ChangeLog @@ -1,2 +1,2 @@ 0.01: Initial version -0.02: Increase text brightness, try to improve memory usage \ No newline at end of file +0.02: Increase text brightness, improve controls, (try to) reduce memory usage \ No newline at end of file diff --git a/apps/gbmusic/README.md b/apps/gbmusic/README.md index acb5f5dfe..a5de044ed 100644 --- a/apps/gbmusic/README.md +++ b/apps/gbmusic/README.md @@ -23,9 +23,13 @@ You can change this under `Settings`->`App/Widget Settings`->`Music Controls`. ## Controls ### Buttons -* Button 1: Volume up (hold to repeat) -* Button 2: Toggle play/pause, long-press for menu -* Button 3: Volume down (hold to repeat, but remember that holding for too long resets your watch) +* Button 1: Volume up +* Button 2: + - Single press: toggle play/pause + - Double press: next song + - Triple press: previous song + - Long-press: open application launcher +* Button 3: Volume down ### Touch * Left: pause/previous song diff --git a/apps/gbmusic/app.js b/apps/gbmusic/app.js index 22566c5b3..7cfbb574a 100644 --- a/apps/gbmusic/app.js +++ b/apps/gbmusic/app.js @@ -50,7 +50,7 @@ function brightness() { // Scroll long track names // use an interval to get smooth movement let offset = null, // scroll Offset: null = no scrolling - scrollI; + iScroll; function scroll() { offset += 10; drawScroller(); @@ -61,16 +61,16 @@ function scrollStart() { } offset = 0; if (Bangle.isLCDOn()) { - if (!scrollI) { - scrollI = setInterval(scroll, 200); + if (!iScroll) { + iScroll = setInterval(scroll, 200); } drawScroller(); } } function scrollStop() { - if (scrollI) { - clearInterval(scrollI); - scrollI = null; + if (iScroll) { + clearInterval(iScroll); + iScroll = null; } offset = null; } @@ -342,10 +342,6 @@ function drawIcon(icon, x, y, s) { })[icon](x, y, s); } function controlColor(ctrl) { - if (vCmd && ctrl===vCmd) { - // volume button kept pressed down - return "#ff0000"; - } return (ctrl in tCommand) ? "#ff0000" : "#008800"; } function drawControl(ctrl, x, y) { @@ -431,26 +427,48 @@ function musicState(e) { // Events //////////////////// -let tLauncher; -// we put starting of watches inside a function, so we can defer it until we -// asked the user about autoStart -function startLauncherWatch() { - // long-press: launcher - // short-press: toggle play/pause - setWatch(function() { - if (tLauncher) { - clearTimeout(tLauncher); +// we put starting of watches inside a function, so we can defer it until +// we asked the user about autoStart +/** + * Start watching for BTN2 presses + */ +let tPress, nPress = 0; +function startButtonWatches() { + // BTN1/3: volume control + // Wait for falling edge to avoid messing with volume while long-pressing BTN3 + // to reload the watch (and same for BTN2 for consistency) + setWatch(() => { sendCommand("volumeup"); }, BTN1, {repeat: true, edge: "falling"}); + setWatch(() => { sendCommand("volumedown"); }, BTN3, {repeat: true, edge: "falling"}); + + // BTN2: long-press for launcher, otherwise depends on number of presses + setWatch(() => { + if (nPress===0) { + tPress = setTimeout(() => {Bangle.showLauncher();}, 3000); } - tLauncher = setTimeout(Bangle.showLauncher, 1000); }, BTN2, {repeat: true, edge: "rising"}); - setWatch(function() { - if (tLauncher) { - clearTimeout(tLauncher); - tLauncher = null; - } - togglePlay(); + setWatch(() => { + nPress++; + clearTimeout(tPress); + tPress = setTimeout(handleButton2Press, 500); }, BTN2, {repeat: true, edge: "falling"}); } +function handleButton2Press() { + tPress = null; + switch(nPress) { + case 1: + togglePlay(); + break; + case 2: + sendCommand("next"); + break; + case 3: + sendCommand("previous"); + break; + default: // invalid + Bangle.buzz(50); + } + nPress = 0; +} let tCommand = {}; /** @@ -470,46 +488,12 @@ function sendCommand(command) { drawControls(); } -// BTN1/3: volume control (with repeat after long-press) -let tVol, vCmd; -function volUp() { - volStart("up"); -} -function volDown() { - volStart("down"); -} -function volStart(dir) { - const command = "volume"+dir; - stopVol(); - sendCommand(command); - vCmd = command; - tVol = setTimeout(repeatVol, 500); -} -function repeatVol() { - sendCommand(vCmd); - tVol = setTimeout(repeatVol, 100); -} -function stopVol() { - if (tVol) { - clearTimeout(tVol); - tVol = null; - } - vCmd = null; - drawControls(); -} -function startVolWatches() { - setWatch(volUp, BTN1, {repeat: true, edge: "rising"}); - setWatch(stopVol, BTN1, {repeat: true, edge: "falling"}); - setWatch(volDown, BTN3, {repeat: true, edge: "rising"}); - setWatch(stopVol, BTN3, {repeat: true, edge: "falling"}); -} - // touch/swipe: navigation function togglePlay() { sendCommand(stat==="play" ? "pause" : "play"); } function startTouchWatches() { - Bangle.on("touch", function(side) { + Bangle.on("touch", side => { switch(side) { case 1: sendCommand(stat==="play" ? "pause" : "previous"); @@ -521,10 +505,34 @@ function startTouchWatches() { togglePlay(); } }); - Bangle.on("swipe", function(dir) { + Bangle.on("swipe", dir => { sendCommand(dir===1 ? "previous" : "next"); }); } +function startLCDWatch() { + Bangle.on("lcdPower", (on) => { + if (on) { + // redraw and resume scrolling + tick(); + drawMusic(); + drawControls(); + fadeOut(); + if (offset!==null) { + drawScroller(); + if (!iScroll) { + iScroll = setInterval(scroll, 200); + } + } + } else { + // pause scrolling + if (iScroll) { + clearInterval(iScroll); + iScroll = null; + } + } + }); +} + ///////////////////// // Startup ///////////////////// @@ -546,9 +554,9 @@ function startEmulator() { } } function startWatches() { - startVolWatches(); - startLauncherWatch(); + startButtonWatches(); startTouchWatches(); + startLCDWatch(); } function start() { @@ -576,23 +584,6 @@ function start() { startWatches(); tick(); startEmulator(); - Bangle.on("lcdPower", (on) => { - if (on) { - tick(); - drawMusic(); - drawControls(); - fadeOut(); - if (offset!==null) { - drawScroller(); - scrollI = setInterval(scroll, 200); - } - } else { - if (scrollI) { - clearInterval(scrollI); - scrollI = null; - } - } - }); } function init() { @@ -602,23 +593,26 @@ function init() { // autoloaded: load state was saved by widget info = saved.info; stat = saved.state; - delete (saved); + delete saved; auto = true; start(); } else { - const s = require("Storage").readJSON("gbmusic.json", 1) || {}; + delete saved; + let s = require("Storage").readJSON("gbmusic.json", 1) || {}; if (!("autoStart" in s)) { // user opened the app, but has not picked a setting yet // ask them about autoloading now E.showPrompt( "Automatically load\n"+ "when playing music?\n", - ).then(function(autoStart) { - s.autoStart = autoStart; + ).then(choice => { + s.autoStart = choice; require("Storage").writeJSON("gbmusic.json", s); + delete s; setTimeout(start, 0); }); } else { + delete s; start(); } }