BangleApps/apps/toucher/app.js

310 lines
7.8 KiB
JavaScript
Raw Normal View History

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();
}
2020-04-02 18:36:19 +00:00
g.clear();
g.flip();
2020-04-02 18:36:19 +00:00
let icons = {};
2020-04-02 18:36:19 +00:00
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
};
2020-04-02 18:36:19 +00:00
function getPosition(index){
return (index*HALF);
2020-04-02 18:36:19 +00:00
}
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.name<b.name) return -1;
if (a.name>b.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();
2020-04-08 16:42:47 +00:00
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);
2020-04-02 18:36:19 +00:00
}
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 );
2020-04-08 16:42:47 +00:00
let cycle = 0;
2024-03-04 20:34:50 +00:00
//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);
2020-04-08 16:42:47 +00:00
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 });
}
*/
});
2020-04-02 18:36:19 +00:00
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);
2020-04-02 18:36:19 +00:00
}
function animateTo(index){
STATE.index = index;
STATE.target = getPosition(index);
render();
2020-04-02 18:36:19 +00:00
}
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);
2020-04-02 18:36:19 +00:00
}
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);
}
2020-04-02 18:36:19 +00:00
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();
2020-04-02 18:36:19 +00:00
g.clear();
g.flip();
E.showMessage(/*LANG*/"Loading...");
load(app.src);
2020-04-02 18:36:19 +00:00
}
}
2020-04-02 20:29:37 +00:00
if (process.env.HWVERSION == 1) {
// Screen event
Bangle.on('touch', function(button){
if(STATE.settings_open) return;
switch(button){
2020-04-02 20:29:37 +00:00
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();
});
}
2020-04-02 20:29:37 +00:00
Bangle.on('swipe', dir => {
if(STATE.settings_open) return;
2020-04-02 20:29:37 +00:00
if(dir == 1) prev();
else next();
});
2021-12-16 10:20:37 +00:00
// 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);