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.
master
Richard de Boer 2021-04-10 18:32:20 +02:00
parent 2de7a2dea0
commit 66c5284d23
3 changed files with 87 additions and 89 deletions

View File

@ -1,2 +1,2 @@
0.01: Initial version 0.01: Initial version
0.02: Increase text brightness, try to improve memory usage 0.02: Increase text brightness, improve controls, (try to) reduce memory usage

View File

@ -23,9 +23,13 @@ You can change this under `Settings`->`App/Widget Settings`->`Music Controls`.
## Controls ## Controls
### Buttons ### Buttons
* Button 1: Volume up (hold to repeat) * Button 1: Volume up
* Button 2: Toggle play/pause, long-press for menu * Button 2:
* Button 3: Volume down (hold to repeat, but remember that holding for too long resets your watch) - Single press: toggle play/pause
- Double press: next song
- Triple press: previous song
- Long-press: open application launcher
* Button 3: Volume down
### Touch ### Touch
* Left: pause/previous song * Left: pause/previous song

View File

@ -50,7 +50,7 @@ function brightness() {
// Scroll long track names // Scroll long track names
// use an interval to get smooth movement // use an interval to get smooth movement
let offset = null, // scroll Offset: null = no scrolling let offset = null, // scroll Offset: null = no scrolling
scrollI; iScroll;
function scroll() { function scroll() {
offset += 10; offset += 10;
drawScroller(); drawScroller();
@ -61,16 +61,16 @@ function scrollStart() {
} }
offset = 0; offset = 0;
if (Bangle.isLCDOn()) { if (Bangle.isLCDOn()) {
if (!scrollI) { if (!iScroll) {
scrollI = setInterval(scroll, 200); iScroll = setInterval(scroll, 200);
} }
drawScroller(); drawScroller();
} }
} }
function scrollStop() { function scrollStop() {
if (scrollI) { if (iScroll) {
clearInterval(scrollI); clearInterval(iScroll);
scrollI = null; iScroll = null;
} }
offset = null; offset = null;
} }
@ -342,10 +342,6 @@ function drawIcon(icon, x, y, s) {
})[icon](x, y, s); })[icon](x, y, s);
} }
function controlColor(ctrl) { function controlColor(ctrl) {
if (vCmd && ctrl===vCmd) {
// volume button kept pressed down
return "#ff0000";
}
return (ctrl in tCommand) ? "#ff0000" : "#008800"; return (ctrl in tCommand) ? "#ff0000" : "#008800";
} }
function drawControl(ctrl, x, y) { function drawControl(ctrl, x, y) {
@ -431,26 +427,48 @@ function musicState(e) {
// Events // Events
//////////////////// ////////////////////
let tLauncher; // we put starting of watches inside a function, so we can defer it until
// we put starting of watches inside a function, so we can defer it until we // we asked the user about autoStart
// asked the user about autoStart /**
function startLauncherWatch() { * Start watching for BTN2 presses
// long-press: launcher */
// short-press: toggle play/pause let tPress, nPress = 0;
setWatch(function() { function startButtonWatches() {
if (tLauncher) { // BTN1/3: volume control
clearTimeout(tLauncher); // 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"}); }, BTN2, {repeat: true, edge: "rising"});
setWatch(function() { setWatch(() => {
if (tLauncher) { nPress++;
clearTimeout(tLauncher); clearTimeout(tPress);
tLauncher = null; tPress = setTimeout(handleButton2Press, 500);
}
togglePlay();
}, BTN2, {repeat: true, edge: "falling"}); }, 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 = {}; let tCommand = {};
/** /**
@ -470,46 +488,12 @@ function sendCommand(command) {
drawControls(); 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 // touch/swipe: navigation
function togglePlay() { function togglePlay() {
sendCommand(stat==="play" ? "pause" : "play"); sendCommand(stat==="play" ? "pause" : "play");
} }
function startTouchWatches() { function startTouchWatches() {
Bangle.on("touch", function(side) { Bangle.on("touch", side => {
switch(side) { switch(side) {
case 1: case 1:
sendCommand(stat==="play" ? "pause" : "previous"); sendCommand(stat==="play" ? "pause" : "previous");
@ -521,10 +505,34 @@ function startTouchWatches() {
togglePlay(); togglePlay();
} }
}); });
Bangle.on("swipe", function(dir) { Bangle.on("swipe", dir => {
sendCommand(dir===1 ? "previous" : "next"); 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 // Startup
///////////////////// /////////////////////
@ -546,9 +554,9 @@ function startEmulator() {
} }
} }
function startWatches() { function startWatches() {
startVolWatches(); startButtonWatches();
startLauncherWatch();
startTouchWatches(); startTouchWatches();
startLCDWatch();
} }
function start() { function start() {
@ -576,23 +584,6 @@ function start() {
startWatches(); startWatches();
tick(); tick();
startEmulator(); 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() { function init() {
@ -602,23 +593,26 @@ function init() {
// autoloaded: load state was saved by widget // autoloaded: load state was saved by widget
info = saved.info; info = saved.info;
stat = saved.state; stat = saved.state;
delete (saved); delete saved;
auto = true; auto = true;
start(); start();
} else { } else {
const s = require("Storage").readJSON("gbmusic.json", 1) || {}; delete saved;
let s = require("Storage").readJSON("gbmusic.json", 1) || {};
if (!("autoStart" in s)) { if (!("autoStart" in s)) {
// user opened the app, but has not picked a setting yet // user opened the app, but has not picked a setting yet
// ask them about autoloading now // ask them about autoloading now
E.showPrompt( E.showPrompt(
"Automatically load\n"+ "Automatically load\n"+
"when playing music?\n", "when playing music?\n",
).then(function(autoStart) { ).then(choice => {
s.autoStart = autoStart; s.autoStart = choice;
require("Storage").writeJSON("gbmusic.json", s); require("Storage").writeJSON("gbmusic.json", s);
delete s;
setTimeout(start, 0); setTimeout(start, 0);
}); });
} else { } else {
delete s;
start(); start();
} }
} }