BangleApps/apps/presentor/app.js

414 lines
10 KiB
JavaScript

// Presentor by 7kasper (Kasper Müller)
// Version 0.14
// Imports
const bt = require("ble_hid_combo");
const Layout = require("Layout");
const Locale = require("locale");
// App Layout
let mainLayout = new Layout({
'type': 'v',
filly: 1,
c: [
{
type: 'txt',
font: '6x8',
label: 'Presentor',
valign: -1,
halign: 0,
col: g.theme.fg1,
// bgCol: g.theme.bg2,
bgCol: '#00F',
fillx: 1,
}, {
type: 'h',
fillx: 1,
c: [
{
type: 'txt',
font: '15%',
label: '00:00',
id: 'Time',
halign: -1,
pad: 3
}, {
fillx: 1
}, {
type: 'txt',
font: '15%',
label: '00:00',
id: 'Timer',
halign: 1,
pad: 3
}
]
}, {
type: 'txt',
font: '10%',
label: '+00:00',
id: 'RestTime',
col: '#fff'
}, {
type: 'txt',
font: '10%',
label: '--------------'
}, {
type: 'txt',
font: '15%',
label: 'Presenting',
id: 'Subject'
}, {
type: 'txt',
font: '6x8',
label: 'Swipe up to start the time.',
id: 'Notes',
col: '#ff0',
fillx: 1,
filly: 1,
valign: 1
}
]
}, {lazy:true});
let settings = {pparts: [], sversion: 0};
let HIDenabled = true;
// Application variables
let pparti = -1;
let ppartBuzzed = false;
let restBuzzed = false;
let lastx = 0;
let lasty = 0;
// Mouse states
let holding = false;
let trackPadMode = false;
let focusMode = false;
// Timeout IDs.
let timeoutId = -1;
let timeoutHolding = -1;
let timeoutDraw = -1;
let timeoutSendMouse = -1;
let homeRoll = 0;
let homePitch = 0;
let mCal = 0;
let mttl = 0;
let cttl = 0;
let bttl = 0;
// BT helper.
let clearToSend = true;
// Presentation Timers
let ptimers = [];
function delay(t, v) {
return new Promise((resolve) => {
setTimeout(resolve, t);
});
}
function formatTimePart(time) {
time = Math.floor(Math.abs(time));
return time < 10 ? `0${time}` : `${time}`;
}
function formatTime(time, doPlus) {
if (time == Infinity) return ' --:-- ';
return `${time < 0 ? '-' : (doPlus ? '+' : '')}${formatTimePart(time/60)}:${formatTimePart(time%60)}`;
}
function loadSettings() {
settings = require("Storage").readJSON('presentor.json');
for (let i = 0; i < settings.pparts.length; i++) {
ptimers[i] = {
active: false,
tracked: -1,
left: settings.pparts[i].minutes * 60 + settings.pparts[i].seconds
};
}
}
function getCurrentTimer() {
if (pparti < 0) return Infinity;
if (!settings.pparts || pparti >= settings.pparts.length) return Infinity;
if (ptimers[pparti].tracked == -1) return Infinity;
ptimers[pparti].left -= (getTime() - ptimers[pparti].tracked);
ptimers[pparti].tracked = getTime();
// if we haven't buzzed yet and timer became negative just buzz here.
// TODO better place?
if (ptimers[pparti].left <= 0 && !ppartBuzzed) {
Bangle.buzz(400)
.then(() => delay(400))
.then(() => Bangle.buzz(400));
ppartBuzzed = true;
}
return ptimers[pparti].left;
}
function getRestTime() {
let rem = 0;
// Add all remaining time from previous presentation parts.
for (let i = 0; i < pparti; i++) {
rem += ptimers[i].left;
}
if (pparti >= 0 && pparti < ptimers.length && ptimers[pparti].left < 0) {
rem += ptimers[pparti].left;
}
// if we haven't buzzed yet and timer became negative just buzz here.
// TODO better place?
if (rem < 0 && !restBuzzed) {
Bangle.buzz(200)
.then(() => delay(400))
.then(() => Bangle.buzz(200))
.then(() => delay(400))
.then(() => Bangle.buzz(200));
restBuzzed = true;
}
return rem;
}
function drawMainFrame() {
var d = new Date();
// update time
mainLayout.Time.label = Locale.time(d,1);
// update timer
mainLayout.Timer.label = formatTime(getCurrentTimer());
let restTime = getRestTime();
mainLayout.RestTime.label = formatTime(restTime, true);
mainLayout.RestTime.col = restTime < 0 ? '#f00' : (restTime > 0 ? '#0f0' : '#fff');
mainLayout.render();
// schedule a draw for the next minute
if (timeoutDraw != -1) clearTimeout(timeoutDraw);
timeoutDraw = setTimeout(function() {
timeoutDraw = -1;
drawMainFrame();
}, 1000 - (Date.now() % 1000));
}
function drawMain() {
g.clear();
mainLayout.forgetLazyState();
drawMainFrame();
// mainLayout.render();
// E.showMessage('Presentor');
}
function doPPart(r) {
pparti += r;
if (settings.pparts.length == 0) {
mainLayout.Subject.label = 'PRESENTOR';
mainLayout.Notes.label = '';
return;
}
if (pparti < 0) {
pparti = -1;
mainLayout.Subject.label = 'PAUSED';
mainLayout.Notes.label = 'Swipe up to start again.';
return;
}
if (!settings.pparts || pparti >= settings.pparts.length) {
pparti = settings.pparts.length;
mainLayout.Subject.label = 'FINISHED';
mainLayout.Notes.label = 'Good Job!';
return;
}
let ppart = settings.pparts[pparti];
mainLayout.Subject.label = ppart.subject;
mainLayout.Notes.label = ppart.notes;
ptimers[pparti].tracked = getTime();
// We haven't buzzed if there was time left.
ppartBuzzed = ptimers[pparti].left <= 0;
// Always reset buzzstate for the rest timer.
restBuzzed = getRestTime() < 0;
drawMainFrame();
}
// Turn on Bluetooth as presentor.
NRF.setServices(undefined, { hid : bt.report });
NRF.on('HID', function() {
if (!HIDenabled) {
Bangle.buzz(200);
HIDenabled = true;
}
});
//
NRF.setAdvertising([
{}, // include original Advertising packet
[ // second packet containing 'appearance'
2, 1, 6, // standard Bluetooth flags
3,3,0x12,0x18, // HID Service
3,0x19,0xCA,0x03 // Appearance: Presentation Remote
]
]);
// function getSign(x) {
// return ((x > 0) - (x < 0)) || +x;
// }
function handleAcc(acc) {
let rRoll = acc.y * -50;
let rPitch = acc.x * -100;
if (mCal > 10) {
//console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch));
bt.moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch);
} else {
//console.log("homeroll: " +homeRoll +"homepitch: " + homePitch);
homeRoll = rRoll * 0.7 + homeRoll * 0.3;
homePitch = rPitch * 0.7 + homePitch * 0.3;
mCal = mCal + 1;
}
}
Bangle.on('lock', function(on) {
if (on && (holding || trackPadMode)) {
Bangle.setLocked(false);
Bangle.setLCDPower(1);
}
});
function startHolding() {
bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT);
holding = true;
focusMode = true;
Bangle.buzz();
E.showMessage('Holding');
Bangle.on('accel', handleAcc);
Bangle.setLCDPower(1);
}
function stopHolding() {
if (holding) {
bt.tapKey(bt.KEY.F10);
// bt.tapKey(bt.KEY.F10);
homePitch = 0;
homeRoll = 0;
holding = false;
focusMode = false;
mCal = 0;
Bangle.removeListener('accel', handleAcc);
Bangle.buzz();
drawMain();
}
// else {
// timeoutId = setTimeout(drawMain, 1000);
// }
clearTimeout(timeoutHolding);
timeoutHolding = -1;
}
Bangle.on('drag', function(e) {
if (cttl == 0) { cttl = getTime(); }
if (trackPadMode) {
if (lastx + lasty == 0) {
lastx = e.x;
lasty = e.y;
mttl = getTime();
}
if (clearToSend) {
clearToSend = false;
let difX = e.x - lastx, difY = e.y - lasty;
let dT = getTime() - mttl;
let vX = difX / dT, vY = difY / dT;
//let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2);
//let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2);
let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY;
bt.moveMouse(qX, qY, 0, 0, 0, function() {
timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50);
});
lastx = e.x;
lasty = e.y;
mttl = getTime();
console.log("Dx: " + (qX) + " Dy: " + (qY));
} else if (timeoutSendMouse == -1) { // Can happen perhaps on single bluetooth failure.
timeoutSendMouse = setTimeout(function() {clearToSend = true; timeoutSendMouse = -1;}, 50);
}
if (!e.b) {
if (!focusMode) {
// short press
if (getTime() - cttl < 0.2) {
bt.clickButton(bt.BUTTON.LEFT);
console.log("click left");
}
// longer press in center
else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) {
bt.clickButton(bt.BUTTON.RIGHT);
console.log("click right");
}
}
cttl = 0;
lastx = 0;
lasty = 0;
}
} else {
if(!e.b){
Bangle.buzz(100);
if(lasty > 40){
doPPart(-1);
// E.showMessage('down');
} else if(lasty < -40){
doPPart(1);
// E.showMessage('up');
} else if(lastx > 40){
// E.showMessage('right');
//kb.tap(kb.KEY.RIGHT, 0);
bt.scroll(-1);
} else if(lastx < -40){
// E.showMessage('left');
//kb.tap(kb.KEY.LEFT, 0);
bt.scroll(1);
}
// Todo re-implement? Seems bit buggy or unnecessary for now.
// else if(lastx==0 && lasty==0 && holding == false){
// // E.showMessage('press');
// bt.clickButton(bt.BUTTON.LEFT);
// }
stopHolding();
lastx = 0;
lasty = 0;
} else{
lastx = lastx + e.dx;
lasty = lasty + e.dy;
if (timeoutHolding == -1) {
timeoutHolding = setTimeout(startHolding, 500);
}
}
}
});
function onBtn() {
if (trackPadMode) {
if ((getTime() - bttl < 0.4 && !focusMode)) {
E.showMessage('Pointer');
focusMode = true;
bt.tapKey(bt.KEY.F10, bt.MODIFY.SHIFT);
} else {
trackPadMode = false;
stopHolding();
drawMain();
if (focusMode) {
bt.tapKey(bt.KEY.F10);
focusMode = false;
}
}
} else {
stopHolding();
clearToSend = true;
trackPadMode = true;
E.showMessage('Mouse');
// Also skip drawing thingy for now.
if (timeoutDraw != -1) {
clearTimeout(timeoutDraw);
timeoutDraw = -1;
}
bttl = getTime();
}
Bangle.buzz();
}
setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true});
// Start App
loadSettings();
drawMain();