const Storage = require("Storage"); const filename = 'toucher.json'; let settings = Storage.readJSON(filename,1) || { hightres: true, animation : true, frame : 3, debug: false }; // this means that setFont('6x8',1) is actually setFont('6x8',3) if (process.env.HWVERSION == 1) { if(!settings.highres) Bangle.setLCDMode("80x80"); else Bangle.setLCDMode(); } g.clear(); g.flip(); let icons = {}; const HEIGHT = g.getHeight(); const WIDTH = g.getWidth(); const HALF = WIDTH/2; const ORIGINAL_ICON_SIZE = 48; const STATE = { settings_open: false, index: 0, target: g.getWidth(), offset: 0 }; function getPosition(index){ return (index*HALF); } function getApps(){ const exit_app = { name: 'Exit', special: true }; const raw_apps = Storage.list(/\.info$/).filter(app => app.endsWith('.info')).map(app => Storage.readJSON(app,1) || { name: "DEAD: "+app.substr(1) }) .filter(app=>app.type=="app" || app.type=="clock" || !app.type) .sort((a,b)=>{ var n=(0|a.sortorder)-(0|b.sortorder); if (n) return n; // do sortorder first if (a.nameb.name) return 1; return 0; }).map(raw => ({ name: raw.name, src: raw.src, icon: raw.icon, version: raw.version })); const apps = [Object.assign({}, exit_app)].concat(raw_apps); apps.push(exit_app); return apps.map((app, i) => { app.x = getPosition(i); return app; }); } const APPS = getApps(); function noIcon(x, y, scale){ if(scale < 0.2) return; g.setColor(g.theme.fg); g.setFontAlign(0,0); g.setFont('6x8',settings.highres ? 6:3); g.drawString('x_x', x+1.5, y); const h = (ORIGINAL_ICON_SIZE/3); g.drawRect(x-h, y-h, x+h, y+h); } function render(){ const start = Date.now(); const ANIMATION_FRAME = settings.frame; const ANIMATION_STEP = Math.floor(HALF / ANIMATION_FRAME); const THRESHOLD = ANIMATION_STEP - 1; g.clear(); const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); let cycle = 0; let lastCycle = visibleApps.length; visibleApps.forEach(app => { cycle++; const x = app.x + HALF - STATE.offset; const y = HALF; let dist = HALF - x; if(dist < 0) dist *= -1; const scale = 1 - (dist / HALF); if(!scale) return; if(app.special){ const fontSize = (process.env.HWVERSION == 2) ? 4 : (settings.highres ? 6 : 2); g.setFont('6x8', fontSize); g.setColor(g.theme.fg); g.setFontAlign(0,0); g.drawString(app.name, HALF, HALF); return; } //draw icon const icon = app.icon ? icons[app.name] ? icons[app.name] : Storage.read(app.icon) : null; if(icon){ icons[app.name] = icon; try { let rescale; let imageScale; if (process.env.HWVERSION == 1) { // on a bangle 1 !highres means 80x80 rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); imageScale = settings.highres ? scale*2 : scale; } else { // !highres mode is meaningless on a bangle 2 at present rescale = 1.25*scale*ORIGINAL_ICON_SIZE; imageScale = 2.5*scale; } g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); } catch(e) { noIcon(x, y, scale); } } else { noIcon(x, y, scale); } //draw text g.setColor(g.theme.fg); if (cycle == 2 && scale > 0.1) { let fontSize = (process.env.HWVERSION == 2) ? 2 : 1; if (process.env.HWVERSION == 1) { fontSize = (settings.highres) ? 3 : 1; } if (app.name.length <= 12) { g.setFont("6x8", fontSize); g.setFontAlign(0,1); g.drawString(app.name, HALF, HEIGHT); } else { // some app names are too long for one line var name = app.name; var first = name.substring(0, name.indexOf(" ")); var last = name.substring(name.indexOf(" ") + 1, name.length); // all this to handle long names like // Simple 7 Segment Clock if (last.length > 12 && process.env.HWVERSION == 1) { g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); } else { g.setFont("6x8", fontSize); } g.setFontAlign(0,-1); g.drawString(first, HALF, 0); if (last.length > 12 && process.env.HWVERSION == 1) { g.setFont((settings.highres ? "6x8" : "4x6"),(settings.highres ? 2 : 1) ); } else { g.setFont("6x8", fontSize); } g.setFontAlign(0,1); g.drawString(last, HALF, HEIGHT); } } /* if(settings.highres){ const type = app.type ? app.type : 'App'; const version = app.version ? app.version : '0.00'; const info = type+' v'+version; g.setFontAlign(0,1); g.setFont('6x8', 1.5); g.setColor(scale,scale,scale); g.drawString(info, HALF, HEIGHT/8*7, { scale: scale }); } */ }); const duration = Math.floor(Date.now()-start); if(settings.debug){ g.setFontAlign(0,1); g.setColor(g.theme.fgH); const fontSize = (process.env.HWVERSION == 2) ? 2 : (settings.highres ? 2 : 1); g.setFont(((process.env.HWVERSION == 2) ? '6x8' : (settings.highres ? '6x8' :'4x6')), fontSize); // steal the bottom line, and print the duration g.clearRect(0, HEIGHT - (process.env.HWVERSION == 1 && !settings.highres ? 8 : 24), WIDTH, HEIGHT); g.drawString('Render: '+duration+' ms', HALF, HEIGHT); } g.flip(); if(STATE.offset == STATE.target) return; if(STATE.offset < STATE.target) STATE.offset += ANIMATION_STEP; else if(STATE.offset > STATE.target) STATE.offset -= ANIMATION_STEP; if(STATE.offset >= STATE.target-THRESHOLD && STATE.offset < STATE.target) STATE.offset = STATE.target; if(STATE.offset <= STATE.target+THRESHOLD && STATE.offset > STATE.target) STATE.offset = STATE.target; setTimeout(render, 0); } function animateTo(index){ STATE.index = index; STATE.target = getPosition(index); render(); } function jumpTo(index){ STATE.index = index; STATE.target = getPosition(index); STATE.offset = STATE.target; render(); } function prev(){ if(STATE.settings_open) return; if(STATE.index == 0) jumpTo(APPS.length-1); setTimeout(() => { if(!settings.animation) jumpTo(STATE.index-1); else animateTo(STATE.index-1); },1); } function next(){ if(STATE.settings_open) return; if(STATE.index == APPS.length-1) jumpTo(0); setTimeout(() => { if(!settings.animation) jumpTo(STATE.index+1); else animateTo(STATE.index+1); },1); } function run(){ const app = APPS[STATE.index]; if(app.name == 'Exit') return load(); if (Storage.read(app.src)===undefined) { E.showMessage("App Source\nNot found"); setTimeout(render, 2000); } else { if (process.env.HWVERSION == 1) Bangle.setLCDMode(); g.clear(); g.flip(); E.showMessage(/*LANG*/"Loading..."); load(app.src); } } if (process.env.HWVERSION == 1) { // Screen event Bangle.on('touch', function(button){ if(STATE.settings_open) return; switch(button){ case 1: prev(); break; case 2: next(); break; case 3: run(); break; } }); } if (process.env.HWVERSION == 2) { // tap at top 1/3 of screen to launch app Bangle.on('touch', function(button, xy) { if (xy.y < HEIGHT / 3) run(); }); } Bangle.on('swipe', dir => { if(STATE.settings_open) return; if(dir == 1) prev(); else next(); }); // close launcher when screen is locked Bangle.on('lock', on => { if(on) return load(); }); if (process.env.HWVERSION == 1) { setWatch(prev, BTN1, { repeat: true }); setWatch(next, BTN3, { repeat: true }); setWatch(run, BTN2, { repeat:true }); } else { setWatch(run, BTN1, { repeat:true }); } jumpTo(1);