diff --git a/apps.json b/apps.json index 6fcd930c2..02da8534c 100644 --- a/apps.json +++ b/apps.json @@ -185,7 +185,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.28", + "version":"0.30", "description": "A menu for setting up Bangle.js", "tags": "tool,system,b2", "readme": "README.md", @@ -199,11 +199,11 @@ "sortorder" : -2 }, { "id": "alarm", - "name": "Default Alarm", + "name": "Default Alarm & Timer", "shortName":"Alarms", "icon": "app.png", - "version":"0.12", - "description": "Set and respond to alarms", + "version":"0.13", + "description": "Set and respond to alarms and timers", "tags": "tool,alarm,widget,b2", "storage": [ {"name":"alarm.app.js","url":"app.js"}, @@ -585,7 +585,7 @@ { "id": "weather", "name": "Weather", "icon": "icon.png", - "version":"0.09", + "version":"0.10", "description": "Show Gadgetbridge weather report", "readme": "readme.md", "tags": "widget,outdoors", @@ -1187,7 +1187,7 @@ { "id": "widclk", "name": "Digital clock widget", "icon": "widget.png", - "version":"0.05", + "version":"0.06", "description": "A simple digital clock widget", "tags": "widget,clock", "type":"widget", @@ -1236,9 +1236,9 @@ { "id": "demoapp", "name": "Demo Loop", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "Simple demo app - displays Bangle.js, JS logo, graphics, and Bangle.js information", - "tags": "", + "tags": "bno2", "type":"app", "allow_emulator":true, "storage": [ @@ -1328,14 +1328,13 @@ "id": "grocery", "name": "Grocery", "icon": "grocery.png", - "version":"0.01", + "version":"0.02", "description": "Simple grocery (shopping) list - Display a list of product and track if you already put them in your cart.", "tags": "tool,outdoors,shopping,list", "type": "app", "custom":"grocery.html", "storage": [ - {"name":"grocery"}, - {"name":"grocery.app.js"}, + {"name":"grocery.app.js", "url":"app.js"}, {"name":"grocery.img","url":"grocery-icon.js","evaluate":true} ] }, @@ -2414,7 +2413,7 @@ "name": "Acceleration Logger", "shortName":"Accel Log", "icon": "app.png", - "version":"0.02", + "version":"0.03", "interface": "interface.html", "description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC", "tags": "outdoor,b2", @@ -3537,10 +3536,11 @@ { "id": "antonclk", "name": "Anton Clock", "icon": "app.png", - "version":"0.01", + "version":"0.02", "description": "A simple clock using the bold Anton font.", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"antonclk.app.js","url":"app.js"}, {"name":"antonclk.img","url":"app-icon.js","evaluate":true} @@ -3549,13 +3549,27 @@ { "id": "waveclk", "name": "Wave Clock", "icon": "app.png", - "version":"0.01", - "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**", + "version":"0.02", + "description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", "tags":"clock,b2", "type":"clock", + "allow_emulator":true, "storage": [ {"name":"waveclk.app.js","url":"app.js"}, {"name":"waveclk.img","url":"app-icon.js","evaluate":true} ] +}, +{ "id": "floralclk", + "name": "Floral Clock", + "icon": "app.png", + "version":"0.01", + "description": "A clock with a flower background by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires firmware 2v11 or later Bangle.js 1**", + "tags":"clock,b2", + "type":"clock", + "allow_emulator":true, + "storage": [ + {"name":"floralclk.app.js","url":"app.js"}, + {"name":"floralclk.img","url":"app-icon.js","evaluate":true} + ] } ] diff --git a/apps/accellog/ChangeLog b/apps/accellog/ChangeLog index 084f26aff..c0097db80 100644 --- a/apps/accellog/ChangeLog +++ b/apps/accellog/ChangeLog @@ -1,2 +1,3 @@ 0.01: New App! 0.02: Use the new multiplatform 'Layout' library +0.03: Exit as first menu option, dont show decimal places for seconds diff --git a/apps/accellog/app.js b/apps/accellog/app.js index 3ee88059b..fdb4d52be 100644 --- a/apps/accellog/app.js +++ b/apps/accellog/app.js @@ -8,6 +8,9 @@ function getFileName(n) { function showMenu() { var menu = { "" : { title : "Accel Logger" }, + "Exit" : function() { + load(); + }, "File No" : { value : fileNumber, min : 0, @@ -21,9 +24,6 @@ function showMenu() { "View Logs" : function() { viewLogs(); }, - "Exit" : function() { - load(); - }, }; E.showMenu(menu); } @@ -34,7 +34,7 @@ function viewLog(n) { var records = 0, l = "", ll=""; while ((l=f.readLine())!==undefined) {records++;ll=l;} var length = 0; - if (ll) length = (ll.split(",")[0]|0)/1000; + if (ll) length = Math.round( (ll.split(",")[0]|0)/1000 ); var menu = { "" : { title : "Log "+n } diff --git a/apps/alarm/ChangeLog b/apps/alarm/ChangeLog index 4084a7d3f..fce54f273 100644 --- a/apps/alarm/ChangeLog +++ b/apps/alarm/ChangeLog @@ -10,3 +10,5 @@ 0.10: Fix auto-snooze option (this stopped new alarms being added) (fix #506) 0.11: Respect Quiet Mode 0.12: Fix widget for bangle 2, now uses theme + Widgets now shown on Alarm screen +0.13: Alarm widget state now updates when setting/resetting an alarm diff --git a/apps/alarm/alarm.js b/apps/alarm/alarm.js index 26345e887..bb5722106 100644 --- a/apps/alarm/alarm.js +++ b/apps/alarm/alarm.js @@ -18,8 +18,10 @@ function showAlarm(alarm) { var buzzCount = 10; if (alarm.msg) msg += "\n"+alarm.msg; + Bangle.loadWidgets(); + Bangle.drawWidgets(); E.showPrompt(msg,{ - title:"ALARM!", + title:alarm.timer ? "TIMER!" : "ALARM!", buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins }).then(function(sleep) { buzzCount = 0; diff --git a/apps/alarm/app.js b/apps/alarm/app.js index b6019ca08..fdb7784f7 100644 --- a/apps/alarm/app.js +++ b/apps/alarm/app.js @@ -9,6 +9,7 @@ var alarms = require("Storage").readJSON("alarm.json",1)||[]; last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day! rp : true, // repeat as : false, // auto snooze + timer : 5, // OPTIONAL - if set, this is a timer and it's the time in minutes } ];*/ @@ -18,6 +19,12 @@ function formatTime(t) { return hrs+":"+("0"+mins).substr(-2); } +function formatMins(t) { + mins = (0|t)%60; + hrs = 0|(t/60); + return hrs+":"+("0"+mins).substr(-2); +} + function getCurrentHr() { var time = new Date(); return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600); @@ -25,17 +32,24 @@ function getCurrentHr() { function showMainMenu() { const menu = { - '': { 'title': 'Alarms' }, - 'New Alarm': ()=>editAlarm(-1) + '': { 'title': 'Alarm/Timer' }, + 'New Alarm': ()=>editAlarm(-1), + 'New Timer': ()=>editTimer(-1) }; alarms.forEach((alarm,idx)=>{ - txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr); - if (alarm.rp) txt += " (repeat)"; + if (alarm.timer) { + txt = "TIMER "+(alarm.on?"on ":"off ")+formatMins(alarm.timer); + } else { + txt = "ALARM "+(alarm.on?"on ":"off ")+formatTime(alarm.hr); + if (alarm.rp) txt += " (repeat)"; + } menu[txt] = function() { - editAlarm(idx); + if (alarm.timer) editTimer(idx); + else editAlarm(idx); }; }); menu['< Back'] = ()=>{load();}; + if (WIDGETS["alarm"]) WIDGETS["alarm"].reload(); return E.showMenu(menu); } @@ -55,7 +69,7 @@ function editAlarm(alarmIndex) { as = a.as; } const menu = { - '': { 'title': 'Alarms' }, + '': { 'title': 'Alarm' }, 'Hours': { value: hrs, onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' @@ -109,4 +123,59 @@ function editAlarm(alarmIndex) { return E.showMenu(menu); } +function editTimer(alarmIndex) { + var newAlarm = alarmIndex<0; + var hrs = 0; + var mins = 5; + var en = true; + if (!newAlarm) { + var a = alarms[alarmIndex]; + mins = (0|a.timer)%60; + hrs = 0|(a.timer/60); + en = a.on; + } + const menu = { + '': { 'title': 'Timer' }, + 'Hours': { + value: hrs, + onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Minutes': { + value: mins, + onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this' + }, + 'Enabled': { + value: en, + format: v=>v?"On":"Off", + onchange: v=>en=v + } + }; + function getTimer() { + var d = new Date(Date.now() + ((hrs*60)+mins)*60000); + var hr = d.getHours() + (d.getMinutes()/60) + (d.getSeconds()/3600); + // Save alarm + return { + on : en, + timer : (hrs*60)+mins, + hr : hr, + rp : false, as: false + }; + } + menu["> Save"] = function() { + if (newAlarm) alarms.push(getTimer()); + else alarms[alarmIndex] = getTimer(); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + if (!newAlarm) { + menu["> Delete"] = function() { + alarms.splice(alarmIndex,1); + require("Storage").write("alarm.json",JSON.stringify(alarms)); + showMainMenu(); + }; + } + menu['< Back'] = showMainMenu; + return E.showMenu(menu); +} + showMainMenu(); diff --git a/apps/alarm/widget.js b/apps/alarm/widget.js index 7b9a88bb9..e8bb79fc7 100644 --- a/apps/alarm/widget.js +++ b/apps/alarm/widget.js @@ -1,11 +1,7 @@ -(() => { - var alarms = require('Storage').readJSON('alarm.json',1)||[]; - alarms = alarms.filter(alarm=>alarm.on); - if (!alarms.length) return; // no alarms, no widget! - delete alarms; - // add the widget - WIDGETS["alarm"]={area:"tl",width:24,draw:function() { - g.setColor(g.theme.fg); - g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); - }}; -})() +WIDGETS["alarm"]={area:"tl",width:0,draw:function() { + if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y); + },reload:function() { + WIDGETS["alarm"].width = (require('Storage').readJSON('alarm.json',1)||[]).some(alarm=>alarm.on) ? 24 : 0; + } +}; +WIDGETS["alarm"].reload(); diff --git a/apps/antonclk/ChangeLog b/apps/antonclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/antonclk/ChangeLog +++ b/apps/antonclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/antonclk/app.js b/apps/antonclk/app.js index 1b92d4a8b..f6fcf1708 100644 --- a/apps/antonclk/app.js +++ b/apps/antonclk/app.js @@ -19,7 +19,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = g.getHeight()/2; - g.reset(); + g.reset(); var date = new Date(); var timeStr = require("locale").time(date,1); var dateStr = require("locale").date(date).toUpperCase(); @@ -33,7 +33,7 @@ function draw() { g.clearRect(0,y-8,g.getWidth(),y+8); // clear the background g.drawString(dateStr,x,y); // queue draw in one minute - queueDraw(); + queueDraw(); } // Clear the screen once, at startup @@ -49,9 +49,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); - diff --git a/apps/demoapp/ChangeLog b/apps/demoapp/ChangeLog index 5560f00bc..53e9cf268 100644 --- a/apps/demoapp/ChangeLog +++ b/apps/demoapp/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Minor adjustment to fix out of memory errors diff --git a/apps/demoapp/app.js b/apps/demoapp/app.js index 13c043587..f1cf5af07 100644 --- a/apps/demoapp/app.js +++ b/apps/demoapp/app.js @@ -60,8 +60,8 @@ var scenes = [ }; }, function() { + Bangle.setLCDMode("120x120"); var img = require("heatshrink").decompress(atob("oNBxH+5wA/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHGpAAoQKv4ADCBQAeqsrAAejBw9/B4oABqt/IGepHw5CEQspALH5hBC5pAvv4/MAALFkIBWpPI6IHqpAu0Z3GfYOpRYdPQEhALYIp2FBYNVI4JAvvL4LH0yBYAFJAQQQ5Ay1JAFftBAQBYxCDv+qIGiCHIQiGnIBfOv5BJIQRAyIJkrvKEkIBrFBB4qEGIGRCNYsZAQIQV/IZDEiICRCDQVJAUIQVPC4lVIF6yJQYpAZ5t/FYvNIBepqtVIJGjIDoqBDY2pdYo3DfAhBIQLmpvIcDvIrC5oJEIAhTCGQmj5qgEC4t5e7YrBqt5BI6UFBg15v4XHbQwAQb4oAKv7NKABdVRoYATUAwnICqjZFIMdVE4+jXI4XGYCxBFFZN/M5OpCxUrvJ/ZFYmjvNVAAY+KCwpDBC6YAV5vNC9oA/AH4A/AHYA==")); - g.clear(); y = 0; var step = 4; @@ -70,8 +70,7 @@ var scenes = [ g.clear(); g.drawImage(img,60,60,{rotate:Math.sin(y*0.03)*0.5}); g.flip(); - }, 20); - Bangle.setLCDMode("120x120"); + }, 20); return function() { if (i) clearInterval(i); }; diff --git a/apps/floralclk/ChangeLog b/apps/floralclk/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/floralclk/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/floralclk/app-icon.js b/apps/floralclk/app-icon.js new file mode 100644 index 000000000..4dfc57191 --- /dev/null +++ b/apps/floralclk/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwhE+sVin0/tVjsdim84sdro1GAQNrAAlHAYVqABk/FINosc/AoNpF4cTGoMTnIhBo1qEgIvFABJACoAvEFwJaDGoNjnFpn5eCik/DYQwBAAQwOFIMUDYKBBLwQwBnwoBBAM3GIIEBhs5E4RLBPYqMKFwU4AAM+nwCBF4SJCAwMxXII2BnBeDJogAGNQIAFBIJMBRIYvCLQK+Bow7BhsNCINjm45BXwZgHF5ITBigoBF4NpQoIwBLwLJBn8Oh0NBoU4F4J6CF4RiKR47iCtIrBiaGBEgdknMOnBABiYKBtNkKoaUIdo5hCQoKvBYgKGBGAIJBMANjhqfBHgLQBNgKcBEpAxBBA9HHoiwBF4S3BcoM4Nwdim83sVEGAINBMQIfBEASYGLII4ECISFBnEyFgKHBGwRsDHYKfBaQOGrifCXw4qBNgIEBXoQHBCQZXBnArCAQNpnBWBFwUTBINiwGkwFcsbzDEwJcFG4pcCAAMUik/EIKJBn6JBMYNpnzABsY0BwGeAAN6wLnCEQQACF4ztCF4UUJ4QNDGAKTCtMTnASBHwOezAwCveIP4ReEeQzNDFwgvDDQU/oDlDJYVkF4e8z2Hx2Px2IAAKKEGo1qnAuBtLYBBwRJCAoIuCbgVqxAsCAQWB1mBwN6AANWmSwBJwRcCDIIuDnxAGAYU5HQmPF4W84QBBvWlGoOY4TIBmMxnJGCYYc5ik+coNjn8UhsUiqRDGQQUBQQJUBz3CzAxBYYYADvWGsTZBDoNHcQUTXgU+n1pB4LmBFQSUCAoNkw4uCF4QBBF4QFBAAIFBF4IjBoBMCn84nwtCMAIABm8TnLREXYd7KQSLBzoBDAQJlBBQN5w1osU/VAQuBnCLBGQNpGYM4R4LRCAATTBRgJcCy4kBz3I5HO5HHy4JBYwRfBcYIfBoE4m5YCho0BA4M4FwzzCxCMBEIO73guB5wAC5BgBSoWAF4KQBsdkKoKNChoACik4nIuHF4i0BdQOdF4XNAQK8Cz2lqzjCnCIBFwTnBS4IrBTQQuBdoLuCBAWOdoOYWgRfC5ovDy4vDriDCAAS8CFQYADdQgvCowvDSAK0CF4SPC4QwCvVcmUymMxFwSUBFwQoDFQToBAoIsBBoSQBMASRBy6QDXwIJCSIWAFwjzDWowAKGYOHwIhBYIOezooC3YuBF4d6GAK/BYAVkF6CUExBiBYQQCBFQIvCzAvCAYM3LoUTLwIeDF5pBDSQRgCLQYDBZQZrBz1cw9iiZeCWoQvWeYQuBfIIxCLwd6w1inEULyQvFtYvFEwOB0uBz4zCX4QuBnDsCDortNCQNHL4mYFwN7ZAOIfgN6AANcsc+m6NBDoRgQFwNGo5FBx2HKoZeBHYNqsg7BqtVsS8BRoReCL6AgBSYQ1CwJWBveHZYlkmMxLwM4h05sgADPwRRCF5ouDMIKKCxwPDsk4mM4XgMTXwLvBikOYYQvOBoQOBoE/JA4DBn8UFwNim8NF4QABhsNnIvQUgVAnMOVoQ4CAANqscUidiRoMNm4zBAAQHBF6CLDO4JIBGAVHXgYiBn1jn0NGYVoAAIvBIwYvOBgM/hyxBAAQXBHYU5RANjscTLwNjLgIuBny+FF5xeBhtcPYU+DYJeDRog0CCIYSBoAvRGAMUmOHJgcbF4QuBFIUNmIBBeYItCIIRNBd54ABisUVgNED4QJBn69Dm4uBh0OnIsBoArCFBoPDHgNqoAvBL4YvCb4JeBnxiCslkDogvRNQVGGALrBVobwBfAMNXoMTigsHDINHAAIvJGIdGn9ro4FBscNMANpF4LoBm4DChq1BFAJDBDobmMMIgvDA4UULwKHBMoLlBG4MynBeBCYQfFF56MBoAbDMAKzBnETm7oBGoM4hxeCQoJfCcJC/KAgIvFMAMNAASNBsQ1BLwVqFwIeELppCBF4dq")) diff --git a/apps/floralclk/app.js b/apps/floralclk/app.js new file mode 100644 index 000000000..5fb9303a8 --- /dev/null +++ b/apps/floralclk/app.js @@ -0,0 +1,76 @@ +function getImg() { + return require("heatshrink").decompress(atob("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")); +} +var IMAGEWIDTH = 176; +var IMAGEHEIGHT = 109; + + +Graphics.prototype.setFontDancingScript = function() { + // Actual height 44 (44 - 1) + var widths = atob("DBIhFB4bGRoeFhweDQ=="); + var font = atob("AAAAAAAAAAAAAAMAAAAAAAHgAAAAAAD4AAAAAAA+AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAAAAAD8AAAAAAD+AAAAAAD/AAAAAAD/gAAAAAH/gAAAAAH/wAAAAAH/gAAAAAP/gAAAAAP/gAAAAAf/AAAAAAf/AAAAAA/+AAAAAA/+AAAAAB/+AAAAAB/8AAAAAB/8AAAAAA/4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8AAAAAf//4AAAAf///AAAAf///4AAAf////AAAf/8Af4AAP/gAA/AAP/AAADwAH+AAAAcAD+AAAAHAA+AAAABwAfAAAAAcAPAAAAAHADgAAAABwB4AAAAA8AcAAAAAOAHAAAAAHgBgAAAAHwAYAAAAD4AGAAAAD+ABwAAAD/AAcAAAD/gAHgAAH/wAA+AAP/wAAH+H//4AAB////4AAAP///4AAAA///4AAAAD//gAAAAAD8AAAAAAAAAAAGAAAAAAABwAAAAAAAcAAAAAAAHAAAAAAABwAAAAAAAcAAGAAAAPAADwAAA/wAA4AAB/8AAeAAP//AAPAD//7wAHwf//w8AB////gPAA///+ABwAf//4AAcAP/+AAADAH/wAAAAQB8AAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAB8AAAAAAAfAAAAAAAGwAAAAAABsAAAAAAAfAAAAAAAHwAAP4AAB4AAP/AAAeAAH/wAAHAAD/+AAD4AB+AgAB+AA8AAAA/gAOAAAAf8AHAAAAPvABwAAAPzwAYAAAH4eAGAAAH8HgBgAAD+B8AcAAD/AfAHAAH/AHwB4AH/gB8AP4//gAfAD///wAHwAf//wAB8AB//gAAeAAP/gAAHAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAP+AAAAAAH/wAAAAAB/+AAAAAAcfgAAAAAEB8AB/AAAAPAA/4AAABwAf+AAAAcAP/gAAAHADwQAAABwAwABAAAcAcAAYAAHAHAAOAADwBgAHwAB4AYAD+AB+AHAB/4B/ABwB+///wAeB/P//4AH//h//8AA//4P/+AAH/4B/+AAA/8AH/AAAH8AAEAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAA4AAAAAAAeAAAAAAAPAAAAAAAHwAAAAAAD8AAAAAAB/AAAAAAA/wAAAAAAe8AAAAAAPPAAAAAAHjwAAAAADw8AAAAAB4PAAwAAA8DwP+AAAeA///wAAPAP//8AAHh////AAH3////wAD////AAAB///wAAAA//88AAAAf/gPAAAAf8ADgAAAHgAA4AAAAAAAMAAAAAAAAAfwAAAAAAP+AAAAAAH/wAAAAAB/+AAAAAA8PgAAAAwEA8AAAH8AAHAAAP/AABwAA//gAAcAD/4YAAHAH/gGAABwB/ABgAAcAfgAYAAPAD4AHAADwA+ABwAB4APgAeAB+AD4AH4B/AA+AA///wAPgAP//4AD4AB//8AB+AAP/+AAfgAB//AAHwAAH/AAB8AAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4AAAAAAf/4AAAAA///gAAAB///+AAAB////wAAB////+AAA//+B/gAA//8AD8AAf+OAAPAAP8HAABwAH8BgAAcAD8A4AAHAB+AOAABwAeADgAAcAPAA4AAHADgAOAADwBwADgAA8AcAA8AA+AGAAPgAfgBgAD+A/wAYAAf//4AGAAH//8ABwAA//+AAfgAH//AAD4AAf/AAAcAAA+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAHAA/AAAABwB/wAwAA8B/8AeAAPB//AHwADx//gB8AA9//wAfAAP/4IAHwAD/wAAB8AB/wAAAPAB/gAAADwB/wAAAA8A/8AAAAPA/PAAAADw/DwAAAA8/A8AAAAP/AOAAAAD/ADgAAAB/gAwAAAAAAAAAAAAAAAAABAAAAAAAH/AAAAAAD/4AAAAAB//AAAAAA//4AAOAAfA+AAf8APgHwAP/wHgA8AH/+DwAHAD//48ABwA///eAAcAeAf/AAHAHAB/gABwBwAP4AA8AYAB/AAPAGAAf4AHgBgAP/AD4AcAHv8B8AHgD5///AA8H8H//gAP/+A//wAB/+AH/4AAP/AAf8AAA/AAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAAAAAP/gAAeAAP/+AAPgAH//wAD8AD//+AAPAB///gAAwAfwD8AAMAPwAfAADADwADwAAwB4AAeAAcAcAAHgAHAHAAA4ADgBwAAOAB4AcAADAB8AHAAAwA/ABwAAcA/gAeAAGB/wAHwADh/4AA+AB//8AAP+D//8AAB////+AAAP///+AAAB///+AAAAP//8AAAAAf/4AAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAA4AAAAAAAeAAAAADAHgAAAAB4BwAAAAAeAIAAAAAHgAAAAAABwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + var scale = 1; // size multiplier for this font + g.setFontCustom(font, 46, widths, 50+(scale<<8)+(1<<16)); +} + +// timeout used to update every minute +var drawTimeout; + +// schedule a draw for the next minute +function queueDraw() { + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = setTimeout(function() { + drawTimeout = undefined; + draw(); + }, 60000 - (Date.now() % 60000)); +} + +function draw() { + var x = g.getWidth()/2; + var y = 50; + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); + if (g.getWidth() == IMAGEWIDTH) + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); + else { + let scale = g.getWidth()/IMAGEWIDTH; + y *= scale; + g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT*scale,{scale:scale}); + } + + var date = new Date(); + var dateStr = require("locale").date(date); + // draw time + g.setFont("DancingScript").setFontAlign(0,0).setColor("#f00"); + g.drawString(date.getHours(), x,y); + y += 43; + g.drawString(date.getMinutes().toString().padStart(2,0), x,y); + // draw date + y += 22; + g.setFontAlign(0,0).setFont("6x8"); + var p = g.getWidth()-60; + g.clearRect(p,y-4,g.getWidth()-p,y+3); // clear the background + g.drawString(dateStr,x,y); + // queue draw in one minute + queueDraw(); +} + +// Stop updates when LCD is off, restart when on +Bangle.on('lcdPower',on=>{ + if (on) { + draw(); // draw immediately, queue redraw + } else { // stop draw timer + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + } +}); +// set background colour +g.setTheme({bg:"#0ff"}); +// Clear the screen once, at startup +g.clear(); +// draw immediately at first, queue update +draw(); +// Show launcher when middle button pressed +Bangle.setUI("clock"); +// Load widgets +Bangle.loadWidgets(); +Bangle.drawWidgets(); diff --git a/apps/floralclk/app.png b/apps/floralclk/app.png new file mode 100644 index 000000000..a0284226e Binary files /dev/null and b/apps/floralclk/app.png differ diff --git a/apps/grocery/ChangeLog b/apps/grocery/ChangeLog index 5560f00bc..906046782 100644 --- a/apps/grocery/ChangeLog +++ b/apps/grocery/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Refactor code to store grocery list in separate file diff --git a/apps/grocery/app.js b/apps/grocery/app.js new file mode 100644 index 000000000..481efc3d9 --- /dev/null +++ b/apps/grocery/app.js @@ -0,0 +1,29 @@ +var filename = 'grocery_list.json'; +var settings = require("Storage").readJSON(filename,1)|| { products: [] }; + +function updateSettings() { + require("Storage").writeJSON(filename, settings); + Bangle.buzz(); +} + +function twoChat(n){ + if(n<10) return '0'+n; + return ''+n; +} + +const mainMenu = settings.products.reduce(function(m, p, i){ + const name = twoChat(p.quantity)+' '+p.name; + m[name] = { + value: p.ok, + format: v => v?'[x]':'[ ]', + onchange: v => { + settings.products[i].ok = v; + updateSettings(); + } + }; + return m; +}, { + '': { 'title': 'Grocery list' } +}); +mainMenu['< Back'] = ()=>{load();}; +E.showMenu(mainMenu); diff --git a/apps/grocery/grocery.html b/apps/grocery/grocery.html index 14c406d75..e717dee2e 100644 --- a/apps/grocery/grocery.html +++ b/apps/grocery/grocery.html @@ -105,56 +105,9 @@ } document.getElementById("upload").addEventListener("click", function() { - - - var app = ` -var newTime = ${Date.now()} -var products = ${JSON.stringify(products)} -var newTime = newTime; -var filename = 'grocery'; -var settings = require("Storage").readJSON(filename,1)|| null; -function getSettings(){ - return { - products : products, - date: newTime - }; -} -if(!settings || !settings.date || settings.date < newTime){ - settings = getSettings(); - Bangle.buzz(500); -} -function updateSettings() { - require("Storage").writeJSON(filename, settings); - Bangle.buzz(); -} -function twoChat(n){ - if(n<10) return '0'+n; - return ''+n; -} -const mainMenu = settings.products.reduce(function(m, p, i){ - const name = twoChat(p.quantity)+' '+p.name; - m[name] = { - value: p.ok, - format: v => v?'[x]':'[ ]', - onchange: v => { - settings.products[i].ok = v; - updateSettings(); - } - }; - return m; -}, { - '': { 'title': 'Grocery list' } -}); -mainMenu['< Back'] = ()=>{load();}; -E.showMenu(mainMenu); -`; - - var icon = `require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQ0QACF1nGAAIxpFoYwqFwwwnRggwGB4eFAggACLzwHCMAeF1WGAgOGw2x2IGCLzYGEF4YpBwotCFwJfWFwo1GSAYtBAIIABRq4vFMhAwBzoAFdzIuKAAOc4IAGGC4qEMZOiF44wXFxovleBYvIGCwmB0WjE4V/AgfG1IvCzujFQOjwoECF6WFwovBDYOFEwN/AgIwCAgOFBwYrBBAQEBzodCF6AAHww1CBpIODAAYvRDAWG2IEBAYYJFBxICCF6Ox1WxAAQfBAYQlCAAIOJAQIvUADQvn1WGR4RfbP4gAFBwgFCF7a5EdwQADF46/cL9wAQF94AGF85bB1TvmF47vdJ4bvFF8qPRFgLv/L7jPCaQq/fYYrvgJgoAGd/7v/F/4v/F5oAdF54weFyAA/AH4A3A="))`; sendCustomizedApp({ storage:[ - {name:"grocery.app.js", url:"app.js", content:app}, - {name:"grocery.img", content:icon, evaluate:true}, - {name:"grocery"} + { name:"grocery_list.json", content: JSON.stringify({products: products}) } ] }); }); diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 5a96451f2..49915ee21 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -31,3 +31,5 @@ 0.26: Use Bangle.softOff if available as this keeps the time 0.27: Add Theme menu 0.28: Update Quiet Mode widget (if present) +0.29: Add Customize to Theme menu +0.30: Move '< Back' to the top of menus \ No newline at end of file diff --git a/apps/setting/README.md b/apps/setting/README.md index f81f3fb05..1875fc3b0 100644 --- a/apps/setting/README.md +++ b/apps/setting/README.md @@ -15,6 +15,7 @@ This is Bangle.js's settings menu * **NOTE:** on some platforms enabling HID can cause you problems when trying to connect to Bangle.js to upload apps. * **Set Time** Configure the current time - Note that this can be done much more easily by choosing 'Set Time' from the App Loader * **LCD** Configure settings about the screen. How long it stays on, how bright it is, and when it turns on - see below. +* **Theme** Adjust the colour scheme * **Reset Settings** Reset the settings to defaults * **Turn Off** Turn Bangle.js off diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 1b1cc5478..a0e535df7 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -75,6 +75,7 @@ function showMainMenu() { var beepN = ["Off", "Piezo", "Vibrate"]; const mainmenu = { '': { 'title': 'Settings' }, + '< Back': ()=>load(), 'Make Connectable': ()=>makeConnectable(), 'App/Widget Settings': ()=>showAppSettingsMenu(), 'BLE': ()=>showBLEMenu(), @@ -117,7 +118,6 @@ function showMainMenu() { 'Theme': ()=>showThemeMenu(), 'Reset Settings': ()=>showResetMenu(), 'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }, - '< Back': ()=>load() }; return E.showMenu(mainmenu); } @@ -126,6 +126,7 @@ function showBLEMenu() { var hidV = [false, "kbmedia", "kb", "joy"]; var hidN = ["Off", "Kbrd & Media", "Kbrd","Joystick"]; E.showMenu({ + '< Back': ()=>showMainMenu(), 'BLE': { value: settings.ble, format: boolFormat, @@ -158,8 +159,7 @@ function showBLEMenu() { 'Whitelist': { value: settings.whitelist?(settings.whitelist.length+" devs"):"off", onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call - }, - '< Back': ()=>showMainMenu() + } }); } @@ -178,6 +178,8 @@ function showThemeMenu() { m.draw(); } var m = E.showMenu({ + '':{title:'Theme'}, + '< Back': ()=>showMainMenu(), 'Dark BW': ()=>{ upd({ fg:cl("#fff"), bg:cl("#000"), @@ -194,12 +196,69 @@ function showThemeMenu() { dark:false }); }, - '< Back': ()=>showMainMenu() + 'Customize': ()=>showCustomThemeMenu(), }); + + function showCustomThemeMenu() { + function cv(x) { return g.setColor(x).getColor(); } + function setT(t, v) { + let th = g.theme; + th[t] = v; + if (t==="bg") { + th['dark'] = (v===cv("#000")); + } + upd(th); + } + const rgb = { + black: "#000", white: "#fff", + red: "#f00", green: "#0f0", blue: "#00f", + cyan: "#0ff", magenta: "#f0f", yellow: "#ff0", + }; + let colors = [], names = []; + for(const c in rgb) { + names.push(c); + colors.push(cv(rgb[c])); + } + function cn(v) { + const i = colors.indexOf(v); + return i!== -1 ? names[i] : v; // another color: just show value + } + let menu = { + '':{title:'Custom Theme'}, + "< Back": () => showThemeMenu() + }; + const labels = { + fg: 'Foreground', bg: 'Background', + fg2: 'Foreground 2', bg2: 'Background 2', + fgH: 'Highlight FG', bgH: 'Highlight BG', + }; + ["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => { + menu[labels[t]] = { + value: colors.indexOf(g.theme[t]), + format: () => cn(g.theme[t]), + onchange: function(v) { + // wrap around + if (v>=colors.length) {v = 0;} + if (v<0) {v = colors.length-1;} + this.value = v; + const c = colors[v]; + // if we select the same fg and bg: set the other to the old color + // e.g. bg=black;fg=white, user selects fg=black -> bg changes to white automatically + // so users don't end up with a black-on-black menu + if (t === 'fg' && g.theme.bg === c) setT('bg', g.theme.fg); + if (t === 'bg' && g.theme.fg === c) setT('fg', g.theme.bg); + setT(t, c); + }, + }; + }); + menu["< Back"] = () => showThemeMenu(); + m = E.showMenu(menu); + } } function showPasskeyMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.passkey = undefined; updateSettings(); @@ -220,12 +279,12 @@ function showPasskeyMenu() { } }; })(i); - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } function showWhitelistMenu() { var menu = { + "< Back" : ()=>showBLEMenu(), "Disable" : () => { settings.whitelist = undefined; updateSettings(); @@ -257,7 +316,6 @@ function showWhitelistMenu() { showWhitelistMenu(); }); }; - menu['< Back']=()=>showBLEMenu(); E.showMenu(menu); } diff --git a/apps/slidingtext/slidingtext.locale.de.js b/apps/slidingtext/slidingtext.locale.de.js index 11124c24a..3cb178232 100644 --- a/apps/slidingtext/slidingtext.locale.de.js +++ b/apps/slidingtext/slidingtext.locale.de.js @@ -1,15 +1,15 @@ var DateFormatter = require("slidingtext.dtfmt.js"); -const germanNumberStr = [ ["ZERO",""], // 0 +const germanNumberStr = [ ["NULL",""], // 0 ["EINS",""], // 1 ["ZWEI",""], //2 ["DREI",''], //3 ["VIER",''], //4 ["FÜNF",''], //5 ["SECHS",''], //6 - ["SEIBEN",''], //7 + ["SIEBEN",''], //7 ["ACHT",''], //8 - ["NUEN",''], // 9, + ["NEUN",''], // 9, ["ZEHN",''], // 10 ["ELF",''], // 11, ["ZWÖLF",''], // 12 @@ -22,7 +22,7 @@ const germanNumberStr = [ ["ZERO",""], // 0 ["NEUN",'ZEHN'], // 19 ]; -const germanTensStr = ["ZERO",//0 +const germanTensStr = ["NULL",//0 "ZEHN",//10 "ZWANZIG",//20 "DREIßIG",//30 @@ -38,7 +38,7 @@ const germanUnit = ["",//0 "VIERUND", //4 "FÜNFUND", //5 "SECHSUND", //6 - "SEIBENUND", //7 + "SIEBENUND", //7 "ACHTUND", //8 "NEUNUND" //9 ] @@ -91,4 +91,4 @@ class GermanDateFormatter extends DateFormatter { } } -module.exports = GermanDateFormatter; \ No newline at end of file +module.exports = GermanDateFormatter; diff --git a/apps/waveclk/ChangeLog b/apps/waveclk/ChangeLog index 5560f00bc..8c2a33143 100644 --- a/apps/waveclk/ChangeLog +++ b/apps/waveclk/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Load widgets after setUI so widclk knows when to hide diff --git a/apps/waveclk/app.js b/apps/waveclk/app.js index 7e1870aa7..f1c67ce2f 100644 --- a/apps/waveclk/app.js +++ b/apps/waveclk/app.js @@ -28,7 +28,7 @@ function queueDraw() { function draw() { var x = g.getWidth()/2; var y = 24+20; - + g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT); if (g.getWidth() == IMAGEWIDTH) g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT); @@ -65,8 +65,8 @@ Bangle.on('lcdPower',on=>{ drawTimeout = undefined; } }); +// Show launcher when middle button pressed +Bangle.setUI("clock"); // Load widgets Bangle.loadWidgets(); Bangle.drawWidgets(); -// Show launcher when middle button pressed -Bangle.setUI("clock"); \ No newline at end of file diff --git a/apps/weather/ChangeLog b/apps/weather/ChangeLog index 09e159045..8f997a83e 100644 --- a/apps/weather/ChangeLog +++ b/apps/weather/ChangeLog @@ -5,4 +5,5 @@ 0.06: Use setUI for launcher. 0.07: Add theme support and unknown icon. 0.08: Refactor and reduce widget ram usage. -0.09: Fix crash when weather.json is absent. \ No newline at end of file +0.09: Fix crash when weather.json is absent. +0.10: Use new Layout library \ No newline at end of file diff --git a/apps/weather/app.js b/apps/weather/app.js index 9d64583e9..6dba14143 100644 --- a/apps/weather/app.js +++ b/apps/weather/app.js @@ -1,96 +1,106 @@ -(() => { - const weather = require('weather'); - let current = weather.get(); +const Layout = require('Layout'); +const locale = require('locale'); +const weather = require('weather'); +let current = weather.get(); - function formatDuration(millis) { - let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); - if (millis < 60000) return "< 1 minute"; - if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); - if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); - return pluralize(Math.floor(millis/86400000), "day"); - } +Bangle.loadWidgets(); - function draw() { - g.reset(); - g.clearRect(0, 24, 239, 239); +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)}, + {type: "v", fillx: 1, c: [ + {type: "h", pad: 2, c: [ + {type: "txt", font: "18%", id: "temp", label: "000"}, + {type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"}, + ]}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: 1, label: "Humidity"}, + {type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"}, + {filly: 1}, + {type: "txt", font: "6x8", pad: 2, halign: -1, label: "Wind"}, + {type: "h", halign: -1, c: [ + {type: "txt", font: "9%", pad: 2, id: "wind", label: "00"}, + {type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"}, + ]}, + ]}, + ]}, + {filly: 1}, + {type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: "Weather condition"}, + {filly: 1}, + {type: "h", c: [ + {type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"}, + {fillx: 1}, + {type: "txt", font: "6x8", pad: 4, id: "updateTime", label: "15 minutes ago"}, + ]}, + {filly: 1}, +]}, null, {lazy: true}); - weather.drawIcon(current.txt, 65, 90, 55); - const locale = require("locale"); +function formatDuration(millis) { + let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s"); + if (millis < 60000) return "< 1 minute"; + if (millis < 3600000) return pluralize(Math.floor(millis/60000), "minute"); + if (millis < 86400000) return pluralize(Math.floor(millis/3600000), "hour"); + return pluralize(Math.floor(millis/86400000), "day"); +} - g.reset(); +function draw() { + layout.icon.txt = current.txt; + const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); + layout.temp.label = temp[1]; + layout.tempUnit.label = temp[2]; + layout.hum.label = current.hum+"%"; + const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/); + layout.wind.label = wind[1]; + layout.windUnit.label = wind[2] + " " + current.wrose.toUpperCase(); + layout.cond.label = current.txt.charAt(0).toUpperCase()+current.txt.slice(1); + layout.loc.label = current.loc; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/); - let width = g.setFont("Vector", 40).stringWidth(temp[1]); - width += g.setFont("Vector", 20).stringWidth(temp[2]); - g.setFont("Vector", 40).setFontAlign(-1, -1, 0); - g.drawString(temp[1], 180-width/2, 70); - g.setFont("Vector", 20).setFontAlign(1, -1, 0); - g.drawString(temp[2], 180+width/2, 70); +function drawUpdateTime() { + if (!current || !current.time) return; + layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; + layout.update(); + layout.render(); +} - g.setFont("6x8", 1); - g.setFontAlign(-1, 0, 0); - g.drawString("Humidity", 135, 130); - g.setFontAlign(1, 0, 0); - g.drawString(current.hum+"%", 225, 130); - if ('wind' in current) { - g.setFontAlign(-1, 0, 0); - g.drawString("Wind", 135, 142); - g.setFontAlign(1, 0, 0); - g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142); - } - - g.setFont("6x8", 2).setFontAlign(0, 0, 0); - g.drawString(current.loc, 120, 170); - - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190); - - drawUpdateTime(); - - g.flip(); - } - - function drawUpdateTime() { - if (!current || !current.time) return; - let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`; - g.reset(); - g.clearRect(0, 202, 239, 210); - g.setFont("6x8", 1).setFontAlign(0, 0, 0); - g.drawString(text, 120, 206); - } - - function update() { - current = weather.get(); - NRF.removeListener("connect", update); - if (current) { - draw(); - } else if (NRF.getSecurityStatus().connected) { - E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?"); +function update() { + current = weather.get(); + NRF.removeListener("connect", update); + if (current) { + draw(); + } else { + layout.forgetLazyState(); + if (NRF.getSecurityStatus().connected) { + E.showMessage("Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?"); } else { - E.showMessage("Weather unknown\n\nGadgetbridge\nnot connected"); + E.showMessage("Weather\nunknown\n\nGadgetbridge\nnot connected"); NRF.on("connect", update); } } +} - let interval = setInterval(drawUpdateTime, 60000); - Bangle.on('lcdPower', (on) => { - if (interval) { - clearInterval(interval); - interval = undefined; - } - if (on) { - drawUpdateTime(); - interval = setInterval(drawUpdateTime, 60000); - } - }); +let interval = setInterval(drawUpdateTime, 60000); +Bangle.on('lcdPower', (on) => { + if (interval) { + clearInterval(interval); + interval = undefined; + } + if (on) { + drawUpdateTime(); + interval = setInterval(drawUpdateTime, 60000); + } +}); - weather.on("update", update); +weather.on("update", update); - update(); +update(); - // Show launcher when middle button pressed - Bangle.setUI("clock"); +// Show launcher when middle button pressed +Bangle.setUI("clock"); - Bangle.loadWidgets(); - Bangle.drawWidgets(); -})() +Bangle.drawWidgets(); diff --git a/apps/widclk/ChangeLog b/apps/widclk/ChangeLog index 655679515..c74857ab4 100644 --- a/apps/widclk/ChangeLog +++ b/apps/widclk/ChangeLog @@ -2,3 +2,4 @@ 0.03: Ensure redrawing works with variable size widget system 0.04: Fix regression stopping correct widget updates 0.05: Don't show clock widget if already showing clock app +0.06: Use 7 segment font, update *on* the minute, use less memory diff --git a/apps/widclk/widget.js b/apps/widclk/widget.js index cd4b29367..9e035ca9a 100644 --- a/apps/widclk/widget.js +++ b/apps/widclk/widget.js @@ -1,30 +1,16 @@ -(function() { - // don't show widget if we know we have a clock app running - if (Bangle.CLOCK) return; +/* Simple clock that appears in the widget bar if no other clock +is running. We update once per minute, but don't bother stopping +if the */ - let intervalRef = null; - var width = 5 * 6*2 - - function draw() { - g.reset().setFont("6x8", 2).setFontAlign(-1, 0); - var time = require("locale").time(new Date(),1); - g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60 - } - function clearTimers(){ - if(intervalRef) { - clearInterval(intervalRef); - intervalRef = null; - } - } - function startTimers(){ - intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); - WIDGETS["wdclk"].draw(); - } - Bangle.on('lcdPower', (on) => { - clearTimers(); - if (on) startTimers(); - }); - - WIDGETS["wdclk"]={area:"tr",width:width,draw:draw}; - if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000); -})() +// don't show widget if we know we have a clock app running +if (!Bangle.CLOCK) WIDGETS["wdclk"]={area:"tl",width:52/* g.stringWidth("00:00") */,draw:function() { + g.reset().setFontCustom(atob("AAAAAAAAAAIAAAQCAQAAAd0BgMBdwAAAAAAAdwAB0RiMRcAAAERiMRdwAcAQCAQdwAcERiMRBwAd0RiMRBwAAEAgEAdwAd0RiMRdwAcERiMRdwAFAAd0QiEQdwAdwRCIRBwAd0BgMBAAABwRCIRdwAd0RiMRAAAd0QiEQAAAAAAAAAA="), 32, atob("BgAAAAAAAAAAAAAAAAYCAAYGBgYGBgYGBgYCAAAAAAAABgYGBgYG"), 512+9); + var time = require("locale").time(new Date(),1); + g.drawString(time, this.x, this.y+3, true); // 5 * 6*2 = 60 + // queue draw in one minute + if (this.drawTimeout) clearTimeout(this.drawTimeout); + this.drawTimeout = setTimeout(()=>{ + this.drawTimeout = undefined; + this.draw(); + }, 60000 - (Date.now() % 60000)); +}}; diff --git a/bin/build_bangle2_c.js b/bin/firmwaremaker_c.js similarity index 86% rename from bin/build_bangle2_c.js rename to bin/firmwaremaker_c.js index 5b4464691..2cb993d00 100755 --- a/bin/build_bangle2_c.js +++ b/bin/firmwaremaker_c.js @@ -11,16 +11,35 @@ var SETTINGS = { pretokenise : true }; +var DEVICE = process.argv[2]; + var path = require('path'); var ROOTDIR = path.join(__dirname, '..'); var APPDIR = ROOTDIR+'/apps'; var APPJSON = ROOTDIR+'/apps.json'; -var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); -var APPS = [ // IDs of apps to install - "boot","launchb2","s7clk","setting", - "about","alarm","widlock","widbat","widbt" -]; var MINIFY = true; +var OUTFILE, APPS; + +if (DEVICE=="BANGLEJS") { + var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs1_storage_default.c'); + var APPS = [ // IDs of apps to install + "boot","launch","mclock","setting", + "about","alarm","widbat","widbt","welcome" + ]; +} else if (DEVICE=="BANGLEJS2") { + var OUTFILE = path.join(ROOTDIR, '../Espruino/libs/banglejs/banglejs2_storage_default.c'); + var APPS = [ // IDs of apps to install + "boot","launchb2","s7clk","setting", + "about","alarm","widlock","widbat","widbt" + ]; +} else { + console.log("USAGE:"); + console.log(" bin/firmwaremaker_c.js BANGLEJS"); + console.log(" bin/firmwaremaker_c.js BANGLEJS2"); + process.exit(1); +} +console.log("Device = ",DEVICE); + var fs = require("fs"); global.Const = { diff --git a/modules/Layout.js b/modules/Layout.js index 5ac0cab16..09e2a3d8c 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -36,6 +36,8 @@ layoutObject has: * A `id` field. If specified the object is added with this name to the returned `layout` object, so can be referenced as `layout.foo` * A `font` field, eg `6x8` or `30%` to use a percentage of screen height +* A `wrap` field to enable line wrapping. Requires some combination of `width`/`height` + and `fillx`/`filly` to be set. Not compatible with text rotation. * A `col` field, eg `#f00` for red * A `bgCol` field for background color (will automatically fill on render) * A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center @@ -71,6 +73,7 @@ Other functions: * `layout.update()` - update positions of everything if contents have changed * `layout.debug(obj)` - draw outlines for objects on screen * `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render) +* `layout.forgetLazyState()` - if lazy rendering is enabled, makes the next call to `render()` perform a full re-render */ @@ -138,7 +141,7 @@ function Layout(layout, buttons, options) { if (l.c) l.c.forEach(idRecurser); } idRecurser(layout); - this.update(); + this.updateNeeded = true; } Layout.prototype.remove = function (l) { @@ -152,6 +155,24 @@ Layout.prototype.remove = function (l) { } }; +function wrappedLines(str, maxWidth) { + var lines = []; + for (var unwrappedLine of str.split("\n")) { + var words = unwrappedLine.split(" "); + var line = words.shift(); + for (var word of words) { + if (g.stringWidth(line + " " + word) > maxWidth) { + lines.push(line); + line = word; + } else { + line += " " + word; + } + } + lines.push(line); + } + return lines; +} + function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { @@ -176,6 +197,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { Layout.prototype.render = function (l) { if (!l) l = this._l; + if (this.updateNeeded) this.update(); function render(l) {"ram" g.reset(); @@ -187,7 +209,14 @@ Layout.prototype.render = function (l) { var cb = { "":function(){}, "txt":function(l){ - g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + if (l.wrap) { + g.setFont(l.font,l.fsz).setFontAlign(0,-1); + var lines = wrappedLines(l.label, l.w); + var y = l.y+((l.h-g.getFontHeight()*lines.length)>>1); + lines.forEach((line, i) => g.drawString(line, l.x+(l.w>>1), y+g.getFontHeight()*i)); + } else { + g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1)); + } }, "btn":function(l){ var x = l.x+(0|l.pad); var y = l.y+(0|l.pad); @@ -230,6 +259,10 @@ Layout.prototype.render = function (l) { } }; +Layout.prototype.forgetLazyState = function () { + this.rects = {}; +} + Layout.prototype.layout = function (l) { // l = current layout element // exw,exh = extra width/height available @@ -282,6 +315,7 @@ Layout.prototype.debug = function(l,c) { if (l.c) l.c.forEach(n => this.debug(n,c)); }; Layout.prototype.update = function() { + delete this.updateNeeded; var l = this._l; var w = g.getWidth(); var y = this.yOffset; @@ -305,9 +339,13 @@ Layout.prototype.update = function() { l.font = f[0]; l.fsz = f[1]; } - g.setFont(l.font,l.fsz); - l._h = g.getFontHeight(); - l._w = g.stringWidth(l.label); + if (l.wrap) { + l._h = l._w = 0; + } else { + g.setFont(l.font,l.fsz); + l._h = g.getFontHeight(); + l._w = g.stringWidth(l.label); + } }, "btn": function(l) { l._h = 24; l._w = 14 + l.label.length*8; diff --git a/tests/Layout/tests/wrapping.bmp b/tests/Layout/tests/wrapping.bmp new file mode 100644 index 000000000..a0d80cc5b Binary files /dev/null and b/tests/Layout/tests/wrapping.bmp differ diff --git a/tests/Layout/tests/wrapping.js b/tests/Layout/tests/wrapping.js new file mode 100644 index 000000000..652530f9c --- /dev/null +++ b/tests/Layout/tests/wrapping.js @@ -0,0 +1,7 @@ +var layout = new Layout({type:"v", c: [ + {type:"h", c: [ + {type:"txt", font:"10%", wrap: true, fillx: true, filly: true, label:"This is wrapping text that fills remaining space"}, + {type:"txt", font:"6x8", wrap: true, width: 60, filly: true, label:"This is wrapping text in a narrow column"}, + ]}, + {type:"txt", font:"6x8", wrap: true, fillx: true, height: 20, label:"This doesn't need to wrap"}, +]}); \ No newline at end of file