diff --git a/apps.json b/apps.json index 040ee6e22..7369516f6 100644 --- a/apps.json +++ b/apps.json @@ -845,7 +845,7 @@ { "id": "weather", "name": "Weather", - "version": "0.13", + "version": "0.14", "description": "Show Gadgetbridge weather report", "icon": "icon.png", "screenshots": [{"url":"screenshot.png"}], @@ -5374,5 +5374,19 @@ {"name":"sonicclk.app.js","url":"app.js"}, {"name":"sonicclk.img","url":"app-icon.js","evaluate":true} ] + }, + { + "id": "touchmenu", + "name": "TouchMenu", + "version": "0.01", + "description": "Redesigned menu that uses the full touchscreen on the Bangle.js 2", + "screenshots": [{"url":"touchmenu.gif"}], + "icon": "touchmenu.png", + "type": "bootloader", + "tags": "tool", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"touchmenu.boot.js","url":"touchmenu.js"}, + ] } ] diff --git a/apps/banglerun/interface.html b/apps/banglerun/interface.html index 403f28258..6388d3b65 100644 --- a/apps/banglerun/interface.html +++ b/apps/banglerun/interface.html @@ -68,7 +68,7 @@ ${track.map(pt=>` ${pt.distance}\n`).join("")} function saveGPX(track, title) { var gpx = ` - + diff --git a/apps/recorder/interface.html b/apps/recorder/interface.html index 42aa4e16d..0535b2d51 100644 --- a/apps/recorder/interface.html +++ b/apps/recorder/interface.html @@ -16,7 +16,7 @@ function saveKML(track,title) { ${track[0].Heartrate!==undefined ? ` Heart Rate - `:``} + `:``} ${track[0].Steps!==undefined ? ` Step Count `:``} @@ -25,7 +25,7 @@ ${track[0].Core!==undefined ? ` `:``} ${track[0].Skin!==undefined ? ` Skin Temp - `:``} + `:``} @@ -49,7 +49,7 @@ ${track.map(pt=>` ${0|pt.Core}\n`).join("")} `:``} ${track[0].Skin!==undefined ? ` ${track.map(pt=>` ${0|pt.Skin}\n`).join("")} - `:``} + `:``} @@ -72,8 +72,7 @@ ${track.map(pt=>` ${0|pt.Skin}\n`).join("")} function saveGPX(track, title) { var gpx = ` - - + diff --git a/apps/touchmenu/ChangeLog b/apps/touchmenu/ChangeLog new file mode 100644 index 000000000..c5277e465 --- /dev/null +++ b/apps/touchmenu/ChangeLog @@ -0,0 +1 @@ +0.01: App launched diff --git a/apps/touchmenu/README.md b/apps/touchmenu/README.md new file mode 100644 index 000000000..0e81f3755 --- /dev/null +++ b/apps/touchmenu/README.md @@ -0,0 +1,40 @@ +# TouchMenu + +A redesign of the built-in `E.showMenu()` to take advantage of the full touch screen on the Bangle.js 2. + +![screenshot](touchmenu.gif) + +## Features + +- All of the features of the built-in `E.showMenu()` +- Icon support for menu items: + ```javascript + menu.items[0].icon = Graphics.createImage(...); + ``` +- Custom accent colors: + ```javascript + E.showMenu({ + "": { + cAB: g.theme.bg2, // Accent background + cAF: g.theme.fg2 // Accent foreground + } + }) + ``` +- Automatic back button detection - name a button `< Back` and it will be given a special position and icon + +## Controls + +- Scroll through the options +- Tap on an option to select it +- Tap on a button again to use it +- Tap on a selected Boolean to toggle it +- Tap on a selected number to change - tap the right side of the screen to decrease, left side to increase +- If detected, tap on the back button in the upper left to go back + +## Requests + +Contact information is on my website: [kyleplo](https://kyleplo.com) + +## Creator + +[kyleplo](https://kyleplo.com) diff --git a/apps/touchmenu/touchmenu.boot.js b/apps/touchmenu/touchmenu.boot.js new file mode 100644 index 000000000..93a0ba1c8 --- /dev/null +++ b/apps/touchmenu/touchmenu.boot.js @@ -0,0 +1,197 @@ +E.showMenu = function(items) { + const gw = g.getWidth(); + const gh = g.getHeight(); + Bangle.removeAllListeners("drag"); + if(!items){ + delete m; + g.clearRect(0, 30, gw, gh - 30); + return false; + } + var loc = require("locale"); + var m = { + info: { + title: "Menu", + cB: g.theme.bg, + cF: g.theme.fg, + cHB: g.theme.bgH, + cHF: g.theme.fgH, + cAB: g.theme.bg2, + cAF: g.theme.fg2, + predraw : () => {}, + preflip : () => {} + }, + scroll: 0, + items: [], + selected: -1, + draw: () => { + g.reset().setFont('12x20'); + m.info.predraw(g); + g.setColor(m.info.cB).fillRect(0, 50, gw, gh - 30).setColor(m.info.cF); + m.items.forEach((e, i) => { + const s = (i * 48) - m.scroll + 50; + if(s < 30 || s > gh - 74){ + return false; + } + if(i == m.selected){ + g.setColor(m.info.cHB).fillRect(0, s, gw, Math.min(s + 48, gh - 30)).setColor(m.info.cHF); + }else{ + g.setColor(m.info.cF); + } + g.drawString(e.title, (e.icon ? 30 : 10), s + 5); + if(e.icon){ + g.drawImage(e.icon, 5, s + 5); + } + if(e.type && s < gh - 72){ + if(e.format){ + g.setFontAlign(1, -1, 0).drawString(e.format(e.value), gw - 10, s + 25).setFontAlign(-1, -1, 0); + }else{ + g.setFontAlign(1, -1, 0).drawString(e.value, gw - 10, s + 25).setFontAlign(-1, -1, 0); + } + } + }); + g.setColor(m.info.cAB).fillRect(0, 30, gw, 50); + g.setColor(m.info.cAF).drawString(m.info.title, (m.back ? 30 : 10), 32); + if(m.back){ + g.drawLine(5, 40, 20, 40); + g.drawLine(5, 40, 15, 33); + g.drawLine(5, 40, 15, 47); + } + m.info.preflip(g, m.scroll > 0, m.scroll < (m.items.length - 1) * 48); + }, + select: (x, y) => { + if(m.selected == -1 || m.selected !== Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0)){ + if(y){ + if(y < 50 || y > gh - 30){ + return false; + }else{ + m.selected = Math.max(Math.min(Math.floor((y + m.scroll - 50) / 48), m.items.length - 1), 0); + } + }else{ + m.selected = Math.floor(m.scroll / 48); + } + m.draw(); + }else{ + if(m.items[m.selected].type && m.items[m.selected].type === "boolean"){ + m.items[m.selected].value = !m.items[m.selected].value; + m.items[m.selected].onchange(m.items[m.selected].value); + m.draw(); + }else if(m.items[m.selected].type && m.items[m.selected].type === "number"){ + if(x && x < (gw / 2)){ + m.items[m.selected].value = m.items[m.selected].value - (m.items[m.selected].step ? m.items[m.selected].step : 1); + }else{ + m.items[m.selected].value = m.items[m.selected].value + (m.items[m.selected].step ? m.items[m.selected].step : 1); + } + if(m.items[m.selected].value > (m.items[m.selected].max ? m.items[m.selected].max : Infinity)){ + m.items[m.selected].value = m.items[m.selected].min ? m.items[m.selected].min : 0; + } + if(m.items[m.selected].value < (m.items[m.selected].min ? m.items[m.selected].min : 0)){ + m.items[m.selected].value = m.items[m.selected].max ? m.items[m.selected].max : 10; + } + m.items[m.selected].onchange(m.items[m.selected].value); + m.draw(); + }else{ + if(m.items[m.selected]){ + m.items[m.selected](); + } + } + } + }, + move: d => { + m.scroll += (d * 48); + m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48); + m.selected = Math.max(Math.min(Math.floor((m.scroll - 50) / 48), m.items.length - 1), 0); + m.draw(); + }, + }; + Object.keys(items).forEach(i => { + if(i == ""){ + m.info = Object.assign(m.info, items[i]); + }else if(i === "< Back" && items[i]){ + m.back = items[i]; + }else if(items[i]){ + m.items.push(items[i]); + m.items[m.items.length - 1].title = loc.translate(i); + if(items[i].hasOwnProperty("value")){ + if(typeof items[i].value === "boolean"){ + m.items[m.items.length - 1].type = "boolean"; + }else{ + m.items[m.items.length - 1].type = "number"; + } + } + } + }); + m.info.title = loc.translate(m.info.title); + m.draw(); + Bangle.on("drag", d => { + if(!d.b){ + return false; + } + if(d.dx == 0 && d.dy == 0){ + if(d.x < 30 && d.y < 50){ + m.back(); + return false; + } + m.select(d.x, d.y); + }else{ + m.selected = -1; + m.scroll -= d.dy; + m.scroll = Math.min(Math.max(m.scroll, 0), (m.items.length - 1) * 48); + m.draw(); + } + }); + return m; +}; + +E.showAlert = function (e, t){ + if(!e){ + E.showMenu(); + return false; + } + return new Promise(r => { + const menu = { + "": { + "title": (t ? t : "Alert") + }, + Ok: () => { + E.showMenu(); + r(); + } + }; + menu[e] = () => {}; + E.showMenu(menu); + }); +}; +E.showMessage = E.showAlert; + +E.showPrompt = function (e, t){ + if(!e){ + E.showMenu(); + return false; + } + return new Promise(r => { + const menu = { + "": { + "title": (t && t.title ? t.title : "Choose") + } + }; + menu[e] = () => {}; + if(t && t.buttons){ + Object.keys(t.buttons).forEach(b => { + menu[b] = () => { + E.showMenu(); + r(t.buttons[b]); + }; + }); + }else{ + menu.Yes = () => { + E.showMenu(); + r(true); + }; + menu.No = () => { + E.showMenu(); + r(false); + }; + } + E.showMenu(menu); + }); +}; diff --git a/apps/touchmenu/touchmenu.gif b/apps/touchmenu/touchmenu.gif new file mode 100644 index 000000000..3df4b3462 Binary files /dev/null and b/apps/touchmenu/touchmenu.gif differ diff --git a/apps/touchmenu/touchmenu.png b/apps/touchmenu/touchmenu.png new file mode 100644 index 000000000..58733cbc7 Binary files /dev/null and b/apps/touchmenu/touchmenu.png differ diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index fb6b28bf6..910cd4658 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -10,3 +10,4 @@ 0.11: Bangle.js 2 support 0.12: Allow hiding the widget 0.13: Tweak Bangle.js 2 light theme colors +0.14: Use weather condition code for icon selection diff --git a/apps/weather/app.js b/apps/weather/app.js index 8c8526fbd..efd9b0209 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -9,7 +9,7 @@ var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [ {filly: 1}, {type: "h", filly: 0, c: [ {type: "custom", width: g.getWidth()/2, height: g.getWidth()/2, valign: -1, txt: "unknown", id: "icon", - render: l => weather.drawIcon(l.txt, l.x+l.w/2, l.y+l.h/2, l.w/2-5)}, + render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/2-5)}, {type: "v", fillx: 1, c: [ {type: "h", pad: 2, c: [ {type: "txt", font: "18%", id: "temp", label: "000"}, @@ -47,6 +47,7 @@ function formatDuration(millis) { function draw() { layout.icon.txt = current.txt; + layout.icon.code = current.code; const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); layout.temp.label = temp[1]; layout.tempUnit.label = temp[2]; diff --git a/apps/weather/lib.js b/apps/weather/lib.js index 7cb9a9f9b..8afdfe6df 100644 --- a/apps/weather/lib.js +++ b/apps/weather/lib.js @@ -16,7 +16,7 @@ function scheduleExpiry(json) { function update(weatherEvent) { let json = storage.readJSON('weather.json')||{}; - + if (weatherEvent) { let weather = weatherEvent.clone(); delete weather.t; @@ -55,7 +55,7 @@ scheduleExpiry(storage.readJSON('weather.json')||{}); exports.drawIcon = function(cond, x, y, r) { var palette; - + if (B2) { if (g.theme.dark) { palette = { @@ -101,7 +101,7 @@ exports.drawIcon = function(cond, x, y, r) { }; } } - + function drawSun(x, y, r) { g.setColor(palette.sun); g.fillCircle(x, y, r); @@ -280,5 +280,44 @@ exports.drawIcon = function(cond, x, y, r) { return drawUnknown; } - chooseIcon(cond)(x, y, r); + /* + * Choose weather icon to display based on weather conditition code + * https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2 + */ + function chooseIconByCode(code) { + const codeGroup = Math.round(code / 100); + switch (codeGroup) { + case 2: return drawThunderstorm; + case 3: return drawRain; + case 5: + switch (code) { + case 511: return drawSnow; + case 520: return drawShowerRain; + case 521: return drawShowerRain; + case 522: return drawShowerRain; + case 531: return drawShowerRain; + default: return drawRain; + } + break; + case 6: return drawSnow; + case 7: return drawMist; + case 8: + switch (code) { + case 800: return drawSun; + case 801: return drawFewClouds; + case 802: return drawCloud; + default: return drawBrokenClouds; + } + break; + default: return drawUnknown; + } + } + + if (cond.code && cond.code > 0) { + chooseIconByCode(cond.code)(x, y, r); + } else { + chooseIcon(cond.txt)(x, y, r); + } + + };