diff --git a/apps.json b/apps.json index 8c00cfec8..03278f45d 100644 --- a/apps.json +++ b/apps.json @@ -1506,6 +1506,23 @@ } ] }, + { "id": "dane_tcr", + "name": "DANE Touch Launcher", + "shortName":"DANE Toucher", + "icon": "app.png", + "version":"0.03", + "description": "Touch enable left to right launcher in the style of the DANE Watchface", + "tags": "tool,system,launcher", + "type":"launch", + "data": [ + {"name":"dane_tcr.json"} + ], + "storage": [ + {"name":"dane_tcr.app.js","url":"app.js"}, + {"name":"dane_tcr.settings.js","url":"settings.js"} + ], + "sortorder" : -10 + }, { "id": "buffgym", "name": "BuffGym", diff --git a/apps/dane/app.js b/apps/dane/app.js index 78126316f..103bb21a5 100644 --- a/apps/dane/app.js +++ b/apps/dane/app.js @@ -34,18 +34,6 @@ let count = 100; let oldCount = count; -// function getImg() { -// return atob("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"); -// } - -// function makeImg() { -// return { -// width : 120, height : 120, bpp : 8, -// transparent : 254, -// buffer : require("heatshrink").decompress(getImg()) -// } -// } - function drawTopLeftCorner(x, y) { g.setColor(mainColor); diff --git a/apps/dane_tcr/ChangeLog b/apps/dane_tcr/ChangeLog new file mode 100644 index 000000000..b2cf10a5f --- /dev/null +++ b/apps/dane_tcr/ChangeLog @@ -0,0 +1,3 @@ +0.01: Fork Toucher and change looks to match with DANE Watchface +0.02: Add Frames +0.03: Add LowRes Support \ No newline at end of file diff --git a/apps/dane_tcr/app.js b/apps/dane_tcr/app.js new file mode 100644 index 000000000..d545ea016 --- /dev/null +++ b/apps/dane_tcr/app.js @@ -0,0 +1,374 @@ + +const yOffset = 23; +const width = g.getWidth(); +const height = g.getHeight(); +const xyCenter = width / 2 + 4; +const cornerSize = 14; +const cornerOffset = 3; +const borderWidth = 1; +const mainColor = "#26dafd"; +const mainColorDark = "#029dbb"; +// const mainColorLight = "#8bebfe"; + +const secondaryColor = "#df9527"; +const secondaryColorDark = "#8b5c15"; +// const secondaryColorLight = "#ecc180"; + +const success = "#00ff00"; +// const successDark = "#000900"; +// const successLight = "#060f06"; + +const alert = "#ff0000"; +// const alertDark = "#090000"; +// const alertLight = "#0f0606"; + +function drawTopLeftCorner(x, y) { + g.setColor(mainColor); + const x1 = x - cornerOffset; + const y1 = y - cornerOffset; + g.fillRect(x1, y1, x1 + cornerSize, y1 + cornerSize); + g.setColor("#000000"); + g.fillRect(x, y, x + cornerSize - cornerOffset, y + cornerSize - cornerOffset); +} + +function drawTopRightCorner(x, y) { + g.setColor(mainColor); + const x1 = x + cornerOffset; + const y1 = y - cornerOffset; + g.fillRect(x1, y1, x1 - cornerSize, y1 + cornerSize); + g.setColor("#000000"); + g.fillRect(x, y, x - cornerSize - cornerOffset, y + cornerSize - cornerOffset); +} + +function drawBottomLeftCorner(x, y) { + g.setColor(mainColor); + const x1 = x - cornerOffset; + const y1 = y + cornerOffset; + g.fillRect(x1, y1, x1 + cornerSize, y1 - cornerSize); + g.setColor("#000000"); + g.fillRect(x, y, x + cornerSize - cornerOffset, y - cornerSize + cornerOffset); +} + +function drawBottomRightCorner(x, y) { + g.setColor(mainColor); + const x1 = x + cornerOffset; + const y1 = y + cornerOffset; + g.fillRect(x1, y1, x1 - cornerSize, y1 - cornerSize); + g.setColor("#000000"); + g.fillRect(x, y, x - cornerSize + cornerOffset, y - cornerSize + cornerOffset); +} + +function drawFrame(x1, y1, x2, y2) { + drawTopLeftCorner(x1, y1); + drawTopRightCorner(x2, y1); + drawBottomLeftCorner(x1, y2); + drawBottomRightCorner(x2, y2); + g.setColor(mainColorDark); + g.drawRect(x1, y1, x2, y2); + g.setColor("#000000"); + g.fillRect(x1 + borderWidth, y1 + borderWidth, x2 - borderWidth, y2 - borderWidth); +} +function drawFrameNoCorners(x1, y1, x2, y2) { + g.setColor(mainColorDark); + g.drawRect(x1, y1, x2, y2); + g.setColor("#000000"); + g.fillRect(x1 + borderWidth, y1 + borderWidth, x2 - borderWidth, y2 - borderWidth); +} + +function drawTopFrame(x1, y1, x2, y2) { + + drawBottomLeftCorner(x1, y2); + drawBottomRightCorner(x2, y2); + g.setColor(mainColorDark); + g.drawRect(x1, y1, x2, y2); + g.setColor("#000000"); + g.fillRect(x1 + borderWidth, y1 + borderWidth, x2 - borderWidth, y2 - borderWidth); +} + +function drawBottomFrame(x1,y1,x2,y2) { + drawTopLeftCorner(x1,y1); + drawTopRightCorner(x2,y1); + g.setColor(mainColorDark); + g.drawRect(x1,y1,x2,y2); + g.setColor("#000000"); + g.fillRect(x1+borderWidth,y1+borderWidth,x2-borderWidth,y2-borderWidth); +} + + +const Storage = require("Storage"); +const filename = 'dane_tcr.json'; +let settings = Storage.readJSON(filename,1) || { + hightres: true, + animation : true, + frame : 3, + debug: false +}; + +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: 240, + 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(alert); + 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(); + // drawFrame(3, 3, width - 3, height - 3); + // drawFrame(3, 10, width - 3, height - 3); + + + const visibleApps = APPS.filter(app => app.x >= STATE.offset-HALF && app.x <= STATE.offset+WIDTH-HALF ); + + visibleApps.forEach(app => { + + + + const x = app.x+HALF-STATE.offset; + const y = HALF - (HALF*0.3); + + let dist = HALF - x; + if(dist < 0) dist *= -1; + + const scale = 1 - (dist / HALF); + + if(!scale) return; + + if(app.special){ + const font = settings.highres ? '6x8' : '4x6'; + const fontSize = settings.highres ? 2 : 1; + const h = (settings.highres ?8:6)*fontSize + const w = ((settings.highres ?6:2)*fontSize)*app.name.length + if(settings.hightres) + drawFrame(HALF-w, HALF-h, HALF+w, HALF+h); + else + drawFrame(HALF-w-2, HALF-h, HALF+w, HALF+h); + g.setFont(font, fontSize); + g.setColor(alert); + 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 { + const rescale = settings.highres ? scale*ORIGINAL_ICON_SIZE : (scale*(ORIGINAL_ICON_SIZE/2)); + const imageScale = settings.highres ? scale*2 : scale; + + if(settings.hightres) + drawFrame(x-rescale-5, y-rescale-5, x+rescale+5, y+rescale+5); + else + drawFrame(x-rescale-2-2, y-rescale-1, x+rescale+2, y+rescale+1); + + + + g.drawImage(icon, x-rescale, y-rescale, { scale: imageScale }); + + } catch(e){ + noIcon(x, y, scale); + } + }else{ + noIcon(x, y, scale); + } + + //draw text + + if(scale > 0.1){ + const font = settings.highres ? '6x8': '4x6'; + const fontSize = settings.highres ? 2 : 1; + const h = (settings.highres ?8:6)*fontSize + const w = ((settings.highres ?6:2)*fontSize)*10//app.name.length + if(settings.highres) + drawFrame(36, HEIGHT/4*3-(fontSize*8), 204, HEIGHT/4*3+(fontSize*8)); + else + drawTopFrame(HALF-w-2, HEIGHT/4*3-h, HALF+w, HEIGHT/4*3+h); + g.setColor(mainColor); + g.setFont(font, fontSize); + g.setFontAlign(0,0); + g.drawString(app.name, HALF, HEIGHT/4*3); + } + + if(settings.highres){ + const type = app.type ? app.type : 'App'; + const version = app.version ? app.version : '0.00'; + const info = type+' v'+version; + const textWidth = (info.length*(6*1.5)) + drawTopFrame(HALF-textWidth/2, 210-(1.5*8)-2, HALF+textWidth/2, 210+(1.5*8)-2); + g.setFontAlign(0,1); + g.setFont('6x8', 1.5); + g.setColor(secondaryColor); + g.drawString(info, HALF, 210, { scale: scale }); + } + + }); + + const duration = Math.floor(Date.now()-start); + if(settings.debug){ + g.setFontAlign(0,1); + g.setColor(0, 1, 0); + const fontSize = settings.highres ? 2 : 1; + g.setFont('4x6',fontSize); + 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 { + Bangle.setLCDMode(); + g.clear(); + g.flip(); + E.showMessage("Loading..."); + load(app.src); + } + +} + +// 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; + } +}); + +Bangle.on('swipe', dir => { + if(STATE.settings_open) return; + if(dir == 1) prev(); + else next(); +}); + +// close launcher when lcd is off +Bangle.on('lcdPower', on => { + if(!on) return load(); +}); + + +setWatch(prev, BTN1, { repeat: true }); +setWatch(next, BTN3, { repeat: true }); +setWatch(run, BTN2, { repeat:true }); + +jumpTo(1); \ No newline at end of file diff --git a/apps/dane_tcr/app.png b/apps/dane_tcr/app.png new file mode 100644 index 000000000..582cb2e08 Binary files /dev/null and b/apps/dane_tcr/app.png differ diff --git a/apps/dane_tcr/settings.js b/apps/dane_tcr/settings.js new file mode 100644 index 000000000..9d28d1b30 --- /dev/null +++ b/apps/dane_tcr/settings.js @@ -0,0 +1,59 @@ +(function(back) { + + const Storage = require("Storage"); + const filename = 'dane_tcr.json'; + let settings = Storage.readJSON(filename,1)|| null; + + function getSettings(){ + return { + highres: true, + animation : true, + frame : 3, + debug: false + }; + } + + function updateSettings() { + require("Storage").writeJSON(filename, settings); + Bangle.buzz(); + } + + if(!settings){ + settings = getSettings(); + updateSettings(); + } + + function saveChange(name){ + return function(v){ + settings[name] = v; + updateSettings(); + } + } + + E.showMenu({ + '': { 'title': 'DANE Toucher settings' }, + "Resolution" : { + value : settings.highres, + format : v => v?"High":"Low", + onchange: v => { + saveChange('highres')(!settings.highres); + } + }, + "Animation" : { + value : settings.animation, + format : v => v?"On":"Off", + onchange : saveChange('animation') + }, + "Frame rate" : { + value : settings.frame, + min: 1, max: 10, step: 1, + onchange : saveChange('frame') + }, + "Debug" : { + value : settings.debug, + format : v => v?"On":"Off", + onchange : saveChange('debug') + }, + '< Back': back + }); +}); \ No newline at end of file