Merge branch 'espruino:master' into dev_0.5

pull/1597/head
Jelco Huijser 2022-03-18 00:50:28 +01:00 committed by GitHub
commit 72fd3ec544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1222 additions and 178 deletions

View File

@ -0,0 +1 @@
0.01: New App!

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

12
apps/bikespeedo/README.md Normal file
View File

@ -0,0 +1,12 @@
## GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude...
![](Hochrad120px.png)...all taken from internal sources.
#### To speed-up GPS reception it is strongly recommended to upload AGPS data with ["Assisted GPS Update"](https://banglejs.com/apps/?id=assistedgps)
#### If "CALIB!" is shown on the display or the compass heading differs too much from GPS heading, compass calibration should be done with the ["Navigation Compass" App](https://banglejs.com/apps/?id=magnav)
**Credits:**<br>
Bike Speedometer App by <i>github.com/HilmarSt</i><br>
Big parts of the software are based on <i>github.com/espruino/BangleApps/tree/master/apps/speedalt</i><br>
Compass and Compass Calibration based on <i>github.com/espruino/BangleApps/tree/master/apps/magnav</i>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+64A/AC+sF1uBgAwsq1W1krGEmswIFDlcAFoMrqyGjlcrGAQDB1guBBQJghKYZZCMYhqBlYugFAesgAuFYgQIHAE2sYMZDfwIABbgIuowMAqwABb4wAjFVQAEqyMrF4cAlYABqwypR4RgBwIyplYnF1hnBGIo8BAAQvhGIj6C1hpBgChBGCqGBqwdCRQQnCB4gJBGAgtWc4WBPoi9JH4ILBGYQATPoRHJRYoACwLFBLi4tGLIyLEA5QuPCoYpEMhBBBGDIuFgArIYQIUHA4b+GABLUBAwoQIXorDGI5RNGCB9WRQ0AJwwHGDxChOH4oDCRI4/GXpAaB1gyLEwlWKgTrBT46ALCogQKZoryFCwzgGBgz/NZpaQHHBCdEF5hKBBxWBUwoGBgEAEoIyHHYesBg7aBJQ7SBBAIvEIIJCBD4IFBgBIGEAcAUA8rGAIWHS4QvDCAJAHG4JfRCgKCFeAovCdRIiBDYq/NABi0Cfo5IEBgjUGACZ6BqwcGwLxBFYRsEHIKBIJwLkBNoIHDF468GYgIBBXY4EDE4IHDYwSwCN4IGBCIp5CJYtWgBZBHAgFEMoRjEE4QDCLYJUEUoaCBPYoQCgA4FGozxFLYwfEQgqrGexIYFBoxbDS4YHCIAYVEEAZcCYwwvGfoQHEcwQHHIg9WIAS9BIoYYESoowIABQuBUgg1DVwwACEpIwBChDLFDQ5JLlZnHJAajBQwgLEO4LDBHKAhBFxQxFCIIACAwadLHgJJBAAUrQJxYFAAbKPCwRGCCqAAm"))

546
apps/bikespeedo/app.js Normal file
View File

@ -0,0 +1,546 @@
// Bike Speedometer by https://github.com/HilmarSt
// Big parts of this software are based on https://github.com/espruino/BangleApps/tree/master/apps/speedalt
// Compass and Compass Calibration based on https://github.com/espruino/BangleApps/tree/master/apps/magnav
const BANGLEJS2 = 1;
const screenH = g.getHeight();
const screenYstart = 24; // 0..23 for widgets
const screenY_Half = screenH / 2 + screenYstart;
const screenW = g.getWidth();
const screenW_Half = screenW / 2;
const fontFactorB2 = 2/3;
const colfg=g.theme.fg, colbg=g.theme.bg;
const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
var altiGPS=0, altiBaro=0;
var hdngGPS=0, hdngCompass=0, calibrateCompass=false;
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
var KalmanFilter = (function () {
'use strict';
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
/**
* KalmanFilter
* @class
* @author Wouter Bulten
* @see {@link http://github.com/wouterbulten/kalmanjs}
* @version Version: 1.0.0-beta
* @copyright Copyright 2015-2018 Wouter Bulten
* @license MIT License
* @preserve
*/
var KalmanFilter =
/*#__PURE__*/
function () {
/**
* Create 1-dimensional kalman filter
* @param {Number} options.R Process noise
* @param {Number} options.Q Measurement noise
* @param {Number} options.A State vector
* @param {Number} options.B Control vector
* @param {Number} options.C Measurement vector
* @return {KalmanFilter}
*/
function KalmanFilter() {
var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref$R = _ref.R,
R = _ref$R === void 0 ? 1 : _ref$R,
_ref$Q = _ref.Q,
Q = _ref$Q === void 0 ? 1 : _ref$Q,
_ref$A = _ref.A,
A = _ref$A === void 0 ? 1 : _ref$A,
_ref$B = _ref.B,
B = _ref$B === void 0 ? 0 : _ref$B,
_ref$C = _ref.C,
C = _ref$C === void 0 ? 1 : _ref$C;
_classCallCheck(this, KalmanFilter);
this.R = R; // noise power desirable
this.Q = Q; // noise power estimated
this.A = A;
this.C = C;
this.B = B;
this.cov = NaN;
this.x = NaN; // estimated signal without noise
}
/**
* Filter a new value
* @param {Number} z Measurement
* @param {Number} u Control
* @return {Number}
*/
_createClass(KalmanFilter, [{
key: "filter",
value: function filter(z) {
var u = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (isNaN(this.x)) {
this.x = 1 / this.C * z;
this.cov = 1 / this.C * this.Q * (1 / this.C);
} else {
// Compute prediction
var predX = this.predict(u);
var predCov = this.uncertainty(); // Kalman gain
var K = predCov * this.C * (1 / (this.C * predCov * this.C + this.Q)); // Correction
this.x = predX + K * (z - this.C * predX);
this.cov = predCov - K * this.C * predCov;
}
return this.x;
}
/**
* Predict next value
* @param {Number} [u] Control
* @return {Number}
*/
}, {
key: "predict",
value: function predict() {
var u = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
return this.A * this.x + this.B * u;
}
/**
* Return uncertainty of filter
* @return {Number}
*/
}, {
key: "uncertainty",
value: function uncertainty() {
return this.A * this.cov * this.A + this.R;
}
/**
* Return the last filtered measurement
* @return {Number}
*/
}, {
key: "lastMeasurement",
value: function lastMeasurement() {
return this.x;
}
/**
* Set measurement noise Q
* @param {Number} noise
*/
}, {
key: "setMeasurementNoise",
value: function setMeasurementNoise(noise) {
this.Q = noise;
}
/**
* Set the process noise R
* @param {Number} noise
*/
}, {
key: "setProcessNoise",
value: function setProcessNoise(noise) {
this.R = noise;
}
}]);
return KalmanFilter;
}();
return KalmanFilter;
}());
//==================================== MAIN ====================================
var lf = {fix:0,satellites:0};
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
var canDraw = 1;
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
var sec; // actual seconds for testing purposes
var max = {};
max.spd = 0;
max.alt = 0;
max.n = 0; // counter. Only start comparing for max after a certain number of fixes to allow kalman filter to have smoohed the data.
var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values;
var wp = {}; // Waypoint to use for distance from cur position.
var SATinView = 0;
function radians(a) {
return a*Math.PI/180;
}
function distance(a,b){
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
var y = radians(b.lat-a.lat);
// Distance in selected units
var d = Math.sqrt(x*x + y*y) * 6371000;
d = (d/parseFloat(cfg.dist)).toFixed(2);
if ( d >= 100 ) d = parseFloat(d).toFixed(1);
if ( d >= 1000 ) d = parseFloat(d).toFixed(0);
return d;
}
function drawFix(dat) {
if (!canDraw) return;
g.clearRect(0,screenYstart,screenW,screenH);
var v = '';
var u='';
// Primary Display
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
// Primary Units
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
drawPrimary(v,u);
// Secondary Display
v = (cfg.primSpd)?dat.alt.toString():dat.speed.toString();
// Secondary Units
u = (cfg.primSpd)?dat.alt_units:cfg.spd_unit;
drawSecondary(v,u);
// Time
drawTime();
//Sats
if ( dat.age > 10 ) {
if ( dat.age > 90 ) dat.age = '>90';
drawSats('Age:'+dat.age);
}
else if (!BANGLEJS2) {
drawSats('Sats:'+dat.sats);
} else {
if (lf.fix) {
drawSats('Sats:'+dat.sats);
} else {
drawSats('View:' + SATinView);
}
}
g.reset();
}
function drawClock() {
if (!canDraw) return;
g.clearRect(0,screenYstart,screenW,screenH);
drawTime();
g.reset();
}
function drawPrimary(n,u) {
//if(emulator)console.log("\n1: " + n +" "+ u);
var s=40; // Font size
var l=n.length;
if ( l <= 7 ) s=48;
if ( l <= 6 ) s=55;
if ( l <= 5 ) s=66;
if ( l <= 4 ) s=85;
if ( l <= 3 ) s=110;
// X -1=left (default), 0=center, 1=right
// Y -1=top (default), 0=center, 1=bottom
g.setFontAlign(0,-1); // center, top
if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
if (BANGLEJS2) s *= fontFactorB2;
g.setFontVector(s);
g.drawString(n, screenW_Half - 10, screenYstart);
// Primary Units
s = 35; // Font size
g.setFontAlign(1,-1,3); // right, top, rotate
g.setColor(col1);
if (BANGLEJS2) s = 20;
g.setFontVector(s);
g.drawString(u, screenW - 20, screenYstart + 2);
}
function drawSecondary(n,u) {
//if(emulator)console.log("2: " + n +" "+ u);
if (calibrateCompass) hdngCompass = "CALIB!";
else hdngCompass +="°";
g.setFontAlign(0,1);
g.setColor(col1);
g.setFontVector(12).drawString("Altitude GPS / Barometer", screenW_Half - 5, screenY_Half - 10);
g.setFontVector(20);
g.drawString(n+" "+u+" / "+altiBaro+" "+u, screenW_Half, screenY_Half + 11);
g.setFontVector(12).drawString("Heading GPS / Compass", screenW_Half - 10, screenY_Half + 26);
g.setFontVector(20);
g.drawString(hdngGPS+"° / "+hdngCompass, screenW_Half, screenY_Half + 47);
}
function drawTime() {
var x = 0, y = screenH;
g.setFontAlign(-1,1); // left, bottom
g.setFont("6x8", 2);
g.setColor(colbg);
g.drawString(time,x+1,y); // clear old time
time = require("locale").time(new Date(),1);
g.setColor(colfg); // draw new time
g.drawString(time,x+2,y);
}
function drawSats(sats) {
g.setColor(col1);
g.setFont("6x8", 2);
g.setFontAlign(1,1); //right, bottom
g.drawString(sats,screenW,screenH);
g.setFontVector(18);
g.setColor(col1);
if ( cfg.modeA == 1 ) {
if ( showMax ) {
g.setFontAlign(0,1); //centre, bottom
g.drawString('MAX',120,164);
}
}
}
function onGPS(fix) {
if ( emulator ) {
fix.fix = 1;
fix.speed = Math.random()*30; // calmed by Kalman filter if cfg.spdFilt
fix.alt = Math.random()*200 -20; // calmed by Kalman filter if cfg.altFilt
fix.lat = 50.59; // google.de/maps/@50.59,8.53,17z
fix.lon = 8.53;
fix.course = 365;
fix.satellites = sec;
fix.time = new Date();
fix.smoothed = 0;
}
var m;
var sp = '---';
var al = '---';
var di = '---';
var age = '---';
if (fix.fix) lf = fix;
hdngGPS = lf.course;
if (isNaN(hdngGPS)) hdngGPS = "---";
else if (0 == hdngGPS) hdngGPS = "0?";
else hdngGPS = hdngGPS.toFixed(0);
if (emulator) hdngCompass = hdngGPS;
if (emulator) altiBaro = lf.alt.toFixed(0);
if (lf.fix) {
if (BANGLEJS2 && !emulator) Bangle.removeListener('GPS-raw', onGPSraw);
// Smooth data
if ( lf.smoothed !== 1 ) {
if ( cfg.spdFilt ) lf.speed = spdFilter.filter(lf.speed);
if ( cfg.altFilt ) lf.alt = altFilter.filter(lf.alt);
lf.smoothed = 1;
if ( max.n <= 15 ) max.n++;
}
// Speed
if ( cfg.spd == 0 ) {
m = require("locale").speed(lf.speed).match(/([0-9,\.]+)(.*)/); // regex splits numbers from units
sp = parseFloat(m[1]);
cfg.spd_unit = m[2];
}
else sp = parseFloat(lf.speed)/parseFloat(cfg.spd); // Calculate for selected units
if ( sp < 10 ) sp = sp.toFixed(1);
else sp = Math.round(sp);
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
// Altitude
al = lf.alt;
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al);
// Distance to waypoint
di = distance(lf,wp);
if (isNaN(di)) di = 0;
// Age of last fix (secs)
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
}
if ( cfg.modeA == 1 ) {
if ( showMax )
drawFix({
speed:max.spd,
sats:lf.satellites,
alt:max.alt,
alt_units:cfg.alt_unit,
age:age,
fix:lf.fix
}); // Speed and alt maximums
else
drawFix({
speed:sp,
sats:lf.satellites,
alt:al,
alt_units:cfg.alt_unit,
age:age,
fix:lf.fix
}); // Show speed/altitude
}
}
function setButtons(){
setWatch(_=>load(), BTN1);
onGPS(lf);
}
function updateClock() {
if (!canDraw) return;
drawTime();
g.reset();
if ( emulator ) {
max.spd++; max.alt++;
d=new Date(); sec=d.getSeconds();
onGPS(lf);
}
}
//###
let cfg = {};
cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
cfg.spd_unit = 'km/h'; // Displayed speed unit
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
cfg.dist = 1000; // Multiplier for distnce unit conversions.
cfg.dist_unit = 'km'; // Displayed distnce units
cfg.modeA = 1;
cfg.primSpd = 1; // 1 = Spd in primary, 0 = Spd in secondary
cfg.spdFilt = false;
cfg.altFilt = false;
if ( cfg.spdFilt ) var spdFilter = new KalmanFilter({R: 0.1 , Q: 1 });
if ( cfg.altFilt ) var altFilter = new KalmanFilter({R: 0.01, Q: 2 });
function onGPSraw(nmea) {
var nofGP = 0, nofBD = 0, nofGL = 0;
if (nmea.slice(3,6) == "GSV") {
// console.log(nmea.slice(1,3) + " " + nmea.slice(11,13));
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$GLGSV,") nofGL = Number(nmea.slice(11,13));
SATinView = nofGP + nofBD + nofGL;
} }
if(BANGLEJS2) Bangle.on('GPS-raw', onGPSraw);
function onPressure(dat) { altiBaro = dat.altitude.toFixed(0); }
Bangle.setBarometerPower(1); // needs some time...
g.clearRect(0,screenYstart,screenW,screenH);
onGPS(lf);
Bangle.setGPSPower(1);
Bangle.on('GPS', onGPS);
Bangle.on('pressure', onPressure);
Bangle.setCompassPower(1);
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
if (!CALIBDATA) calibrateCompass = true;
function Compass_tiltfixread(O,S){
"ram";
//console.log(O.x+" "+O.y+" "+O.z);
var m = Bangle.getCompass();
var g = Bangle.getAccel();
m.dx =(m.x-O.x)*S.x; m.dy=(m.y-O.y)*S.y; m.dz=(m.z-O.z)*S.z;
var d = Math.atan2(-m.dx,m.dy)*180/Math.PI;
if (d<0) d+=360;
var phi = Math.atan(-g.x/-g.z);
var cosphi = Math.cos(phi), sinphi = Math.sin(phi);
var theta = Math.atan(-g.y/(-g.x*sinphi-g.z*cosphi));
var costheta = Math.cos(theta), sintheta = Math.sin(theta);
var xh = m.dy*costheta + m.dx*sinphi*sintheta + m.dz*cosphi*sintheta;
var yh = m.dz*sinphi - m.dx*cosphi;
var psi = Math.atan2(yh,xh)*180/Math.PI;
if (psi<0) psi+=360;
return psi;
}
var Compass_heading = 0;
function Compass_newHeading(m,h){
var s = Math.abs(m - h);
var delta = (m>h)?1:-1;
if (s>=180){s=360-s; delta = -delta;}
if (s<2) return h;
var hd = h + delta*(1 + Math.round(s/5));
if (hd<0) hd+=360;
if (hd>360)hd-= 360;
return hd;
}
function Compass_reading() {
"ram";
var d = Compass_tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
Compass_heading = Compass_newHeading(d,Compass_heading);
hdngCompass = Compass_heading.toFixed(0);
}
if (!calibrateCompass) setInterval(Compass_reading,200);
setButtons();
if (emulator) setInterval(updateClock, 2000);
else setInterval(updateClock, 10000);
Bangle.loadWidgets();
Bangle.drawWidgets();

BIN
apps/bikespeedo/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

View File

@ -0,0 +1,18 @@
{
"id": "bikespeedo",
"name": "Bike Speedometer (beta)",
"shortName": "Bike Speedomet.",
"version": "0.01",
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
"icon": "app.png",
"screenshots": [{"url":"Screenshot.png"}],
"type": "app",
"tags": "tool,cycling,bicycle,outdoors,sport",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"bikespeedo.app.js","url":"app.js"},
{"name":"bikespeedo.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -47,3 +47,4 @@
0.41: Add Keyboard and Mouse Bluetooth HID option 0.41: Add Keyboard and Mouse Bluetooth HID option
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js 0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
0.43: Fix Gadgetbridge handling with Programmable:off 0.43: Fix Gadgetbridge handling with Programmable:off
0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM)

View File

@ -4,7 +4,7 @@ of the time. */
E.showMessage("Updating boot0..."); E.showMessage("Updating boot0...");
var s = require('Storage').readJSON('setting.json',1)||{}; var s = require('Storage').readJSON('setting.json',1)||{};
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2 var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
var boot = ""; var boot = "", bootPost = "";
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT); var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`; boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
@ -15,6 +15,7 @@ if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't chang
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`; boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
boot += `E.setFlags({pretokenise:1});\n`; boot += `E.setFlags({pretokenise:1});\n`;
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`; boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
if (s.ble!==false) { if (s.ble!==false) {
if (s.HID) { // Human interface device if (s.HID) { // Human interface device
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`; if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
@ -195,8 +196,8 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
// Append *.boot.js files // Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed // These could change bleServices/bleServiceOptions if needed
var getPriority = /.*\.(\d+)\.boot\.js$/; var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
require('Storage').list(/\.boot\.js$/).sort((a,b)=>{ var getPriority = /.*\.(\d+)\.boot\.js$/;
var aPriority = a.match(getPriority); var aPriority = a.match(getPriority);
var bPriority = b.match(getPriority); var bPriority = b.match(getPriority);
if (aPriority && bPriority){ if (aPriority && bPriority){
@ -207,17 +208,39 @@ require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
return 1; return 1;
} }
return a==b ? 0 : (a>b ? 1 : -1); return a==b ? 0 : (a>b ? 1 : -1);
}).forEach(bootFile=>{ });
// precalculate file size
var fileSize = boot.length + bootPost.length;
bootFiles.forEach(bootFile=>{
// match the size of data we're adding below in bootFiles.forEach
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+1;
});
// write file in chunks (so as not to use up all RAM)
require('Storage').write('.boot0',boot,0,fileSize);
var fileOffset = boot.length;
bootFiles.forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }() // we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }() // with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
// which would cause an error! // which would cause an error!
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n"; // we write:
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
// but we need to do this without ever loading everything into RAM as some
// boot files seem to be getting pretty big now.
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
fileOffset+=2+bootFile.length+1;
var bf = require('Storage').read(bootFile);
require('Storage').write('.boot0',bf,fileOffset);
fileOffset+=bf.length;
require('Storage').write('.boot0',"\n",fileOffset);
fileOffset+=1;
}); });
// update ble require('Storage').write('.boot0',bootPost,fileOffset);
boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`;
// write file
require('Storage').write('.boot0',boot);
delete boot; delete boot;
delete bootPost;
delete bootFiles;
delete fileSize;
delete fileOffset;
E.showMessage("Reloading..."); E.showMessage("Reloading...");
eval(require('Storage').read('.boot0')); eval(require('Storage').read('.boot0'));
// .bootcde should be run automatically after if required, since // .bootcde should be run automatically after if required, since

View File

@ -1,7 +1,7 @@
{ {
"id": "boot", "id": "boot",
"name": "Bootloader", "name": "Bootloader",
"version": "0.43", "version": "0.44",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png", "icon": "bootloader.png",
"type": "bootloader", "type": "bootloader",

17
apps/bordle/README.md Normal file
View File

@ -0,0 +1,17 @@
# Bordle
The Bangle version of a popular word guessing game. The goal is to guess a 5 letter word in 6 tries or less. After each guess, the letters in the guess are
marked in colors: yellow for a letter that appears in the to-be-guessed word, but in a different location and green for a letter in the correct position.
Only words contained in the internal dictionary are allowed as valid guesses. At app launch, a target word is picked from the dictionary at random.
On startup, a grid of 6 lines with 5 (empty) letter boxes is displayed. Swiping left or right at any time switches between grid view and keyboard view.
The keyboad was inspired by the 'Scribble' app (it is a simplified version using the layout library). The letter group "Z ..." contains the delete key and
the enter key. Hitting enter after the 5th letter will add the guess to the grid view and color mark it.
The (English language) dictionary was derived from the the Unix ispell word list by filtering out plurals and past particples (and some hand editing) from all 5 letter words.
It is contained in the file 'wordlencr.txt' which contains one long string (no newline characters) of all the words concatenated. It would not be too difficult to swap it
out for a different language version. The keyboard currently only supports the 26 characters of the latin alphabet (no accents or umlauts).

1
apps/bordle/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AA/TADwoIFkYyOF0owIF04wGUSqvVBZQtZGJYJIFzomKF0onIF07EKF0owLF9wNEnwACE6oZILxovbMBov/F/4v/C54uWF/4vKBQQLLF/4YPFwYMLF7AZGF5Y5KF5xJIFwoMJD44vaBhwvcLQpgHF8gGRF6xYNBpQvTXBoNOF65QJBIgvjBywvUV5YOOF64OIB54v/cQwAKB5ov/F84wKADYuIF+AwkFIwwnE45hmExCSlEpTEiERr3KADw+PF0ownUSoseA=="))

BIN
apps/bordle/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

159
apps/bordle/bordle.app.js Normal file
View File

@ -0,0 +1,159 @@
var Layout = require("Layout");
var gameState = 0;
var keyState = 0;
var keyStateIdx = 0;
function buttonPushed(b) {
if (keyState==0) {
keyState++;
keyStateIdx = b;
if (b<6) {
for (i=1; i<=5; ++i) {
var c = String.fromCharCode(i+64+(b-1)*5);
layout["bt"+i.toString()].label = c;
layout["bt"+i.toString()].bgCol = wordle.keyColors[c]||g.theme.bg;
}
layout.bt6.label = "<";
}
else {
layout.bt1.label = "Z";
layout.bt1.bgCol = wordle.keyColors.Z||g.theme.bg;
layout.bt2.label = "<del>";
layout.bt4.label = "<ent>";
layout.bt3.label = layout.bt5.label = " ";
layout.bt6.label = "<";
}
}
else { // actual button pushed
inp = layout.input.label;
if (b!=6) {
if ((keyStateIdx<=5 || b<=1) && inp.length<5) inp += String.fromCharCode(b+(keyStateIdx-1)*5+64);
else if (layout.input.label.length>0 && b==2) inp = inp.slice(0,-1);
layout.input.label = inp;
}
layout = getKeyLayout(inp);
keyState = 0;
if (inp.length==5 && keyStateIdx==6 && b==4) {
rc = wordle.addGuess(inp);
layout.input.label = "";
layout.update();
gameState = 0;
if (rc>0) return;
g.clear();
wordle.render();
return;
}
}
layout.update();
g.clear();
layout.render();
}
function getKeyLayout(text) {
return new Layout( {
type: "v", c: [
{type:"txt", font:"6x8:2", id:"input", label:text, pad: 3},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt1", label:"ABCDE", cb: l=>buttonPushed(1), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt2", label:"FGHIJ", cb: l=>buttonPushed(2), pad:4, filly:1, fillx:1 },
]},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt3", label:"KLMNO", cb: l=>buttonPushed(3), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt4", label:"PQRST", cb: l=>buttonPushed(4), pad:4, filly:1, fillx:1 },
]},
{type: "h", c: [
{type:"btn", font:"6x8:2", id:"bt5", label:"UVWXY", cb: l=>buttonPushed(5), pad:4, filly:1, fillx:1 },
{type:"btn", font:"6x8:2", id:"bt6", label:"Z ...", cb: l=>buttonPushed(6), pad:4, filly:1, fillx:1 },
]}
]});
}
class Wordle {
constructor(word) {
this.word = word;
this.guesses = [];
this.guessColors = [];
this.keyColors = [];
this.nGuesses = -1;
if (word == "rnd") {
this.words = require("Storage").read("wordlencr.txt");
i = Math.floor(Math.floor(this.words.length/5)*Math.random())*5;
this.word = this.words.slice(i, i+5).toUpperCase();
}
console.log(this.word);
}
render(clear) {
h = g.getHeight();
bh = Math.floor(h/6);
bbh = Math.floor(0.85*bh);
w = g.getWidth();
bw = Math.floor(w/5);
bbw = Math.floor(0.85*bw);
if (clear) g.clear();
g.setFont("Vector", Math.floor(bbh*0.95)).setFontAlign(0,0);
g.setColor(g.theme.fg);
for (i=0; i<6; ++i) {
for (j=0; j<5; ++j) {
if (i<=this.nGuesses) {
g.setColor(this.guessColors[i][j]).fillRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
g.setColor(g.theme.fg).drawString(this.guesses[i][j], 2+j*bw+bw/2, 2+i*bh+bh/2);
}
g.setColor(g.theme.fg).drawRect(j*bw+(bw-bbw)/2, i*bh+(bh-bbh)/2, (j+1)*bw-(bw-bbw)/2, (i+1)*bh-(bh-bbh)/2);
}
}
}
addGuess(w) {
if ((this.words.indexOf(w.toLowerCase())%5)!=0) {
E.showAlert(w+"\nis not a word", "Invalid word").then(function() {
layout = getKeyLayout("");
wordle.render(true);
});
return 3;
}
this.guesses.push(w);
this.nGuesses++;
this.guessColors.push([]);
correct = 0;
var sol = this.word;
for (i=0; i<w.length; ++i) {
c = w[i];
col = g.theme.bg;
if (sol[i]==c) {
sol = sol.substr(0,i) + '?' + sol.substr(i+1);
col = "#0f0";
++correct;
}
else if (sol.includes(c)) col = "#ff0";
if (col!=g.theme.bg) this.keyColors[c] = this.keyColors[c] || col;
else this.keyColors[c] = "#00f";
this.guessColors[this.nGuesses].push(col);
}
if (correct==5) {
E.showAlert("The word is\n"+this.word, "You won in "+(this.nGuesses+1)+" guesses!").then(function(){load();});
return 1;
}
if (this.nGuesses==5) {
E.showAlert("The word was\n"+this.word, "You lost!").then(function(){load();});
return 2;
}
}
}
wordle = new Wordle("rnd");
layout = getKeyLayout("");
wordle.render(true);
Bangle.on('swipe', function (dir) {
if (dir==1 || dir==-1) {
g.clear();
if (gameState==0) {
layout.render();
gameState = 1;
}
else if (gameState==1) {
wordle.render();
gameState = 0;
}
}
});

15
apps/bordle/metadata.json Normal file
View File

@ -0,0 +1,15 @@
{ "id": "bordle",
"name": "Bordle",
"shortName":"Bordle",
"icon": "app.png",
"version":"0.01",
"description": "Bangle version of a popular word search game",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"tags": "game, text",
"storage": [
{"name":"bordle.app.js","url":"bordle.app.js"},
{"name":"wordlencr.txt","url":"wordlencr.txt"},
{"name":"bordle.img","url":"app-icon.js","evaluate":true}
]
}

File diff suppressed because one or more lines are too long

View File

@ -2,3 +2,6 @@
0.02: added emulator capability and display of widgets 0.02: added emulator capability and display of widgets
0.03: bug of advancing time fixed; doztime now correct within ca. 1 second 0.03: bug of advancing time fixed; doztime now correct within ca. 1 second
0.04: changed time colour from slightly off white to pure white 0.04: changed time colour from slightly off white to pure white
0.05: extraneous comments and code removed
display improved
now supports Adjust Clock widget, if installed

View File

@ -1,23 +1,23 @@
// Positioning values for graphics buffers // Positioning values for graphics buffers
const g_height = 80; // total graphics height const g_height = 80; // total graphics height
const g_x_off = 0; // position from left was 16, then 8 here const g_x_off = 0; // position from left
const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240 const g_y_off = (180 - g_height)/2; // vertical center for graphics region
const g_width = 240 - 2 * g_x_off; // total graphics width const g_width = 240 - 2 * g_x_off; // total graphics width
const g_height_d = 28; // height of date region was 32 const g_height_d = 28; // height of date region
const g_y_off_d = 0; // y position of date region within graphics region const g_y_off_d = 0; // y position of date region within graphics region
const spacing = 0; // space between date and time in graphics region const spacing = 6; // space between date and time in graphics region
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
const g_height_t = 44; // height of time region was 48 const g_height_t = 44; // height of time region
// Other vars // Other vars
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30]; const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30]; const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30]; const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
const timeColour = "#ffffff"; const timeColour = "#ffffff";
const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#8383ff","#ff00ff","#ff0080"]; //blue was 0000ff const dateColours = ["#ff0000","#ff8000","#ffff00","#00ff00","#0080ff","#ff00ff","#ffffff"];
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20 const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20 const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line
const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30 const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line
const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30 const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
const baseYear = 11584; const baseYear = 11584;
const baseDate = Date(2020,11,21); // month values run from 0 to 11 const baseDate = Date(2020,11,21); // month values run from 0 to 11
@ -42,28 +42,25 @@ var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
g.clear(); // start with blank screen g.clear(); // start with blank screen
g.flip = function() g.flip = function()
{ {
g.setBgColor(0,0,0); g.setBgColor(0,0,0);
g.setColor(dateColour); g.setColor(dateColour);
g.drawImage( g.drawImage(
{ {
width:g_width, width:g_width,
height:g_height_d, height:g_height_d,
buffer:g_d.buffer buffer:g_d.buffer
}, g_x_off, g_y_off + g_y_off_d); }, g_x_off, g_y_off + g_y_off_d);
g.setColor(timeColour2); g.setColor(timeColour2);
g.drawImage( g.drawImage(
{ {
width:g_width, width:g_width,
height:g_height_t, height:g_height_t,
buffer:g_t.buffer buffer:g_t.buffer
}, g_x_off, g_y_off + g_y_off_t); }, g_x_off, g_y_off + g_y_off_t);
}; };
setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1 setWatch(function(){ modeTime(); }, BTN, {repeat:true} );
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2 setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" });
//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
Bangle.on('touch', function(button, xy) { //from Gordon Williams Bangle.on('touch', function(button, xy) { //from Gordon Williams
if (button==1) toggleTimeDigits(); if (button==1) toggleTimeDigits();
@ -71,10 +68,10 @@ Bangle.on('touch', function(button, xy) { //from Gordon Williams
}); });
function buildSequence(targ){ function buildSequence(targ){
for(let i=0;i<targ.length;++i){ for(let i=0;i<targ.length;++i){
sequence.push(new Date(accum.getTime())); sequence.push(new Date(accum.getTime()));
accum.setDate(accum.getDate()+targ[i]); accum.setDate(accum.getDate()+targ[i]);
} }
} }
buildSequence(B2); buildSequence(B2);
buildSequence(B2); buildSequence(B2);
@ -91,154 +88,137 @@ buildSequence(B1);
buildSequence(B2); buildSequence(B2);
function getDate(dt){ function getDate(dt){
let index = sequence.findIndex(n => n > dt)-1; let index = sequence.findIndex(n => n > dt)-1;
let year = baseYear+parseInt(index/12); let year = baseYear+parseInt(index/12);
let month = index % 12; let month = index % 12;
let day = parseInt((dt-sequence[index])/86400000); let day = parseInt((dt-sequence[index])/86400000);
let colour = dateColours[day % 6]; let colour = dateColours[day % 6];
if(day==30){ colour=dateColours[6]; } if(day==30){ colour=dateColours[6]; }
return({"year":year,"month":month,"day":day,"colour":colour}); return({"year":year,"month":month,"day":day,"colour":colour});
} }
function toggleTimeDigits(){ function toggleTimeDigits(){
addTimeDigit = !addTimeDigit; addTimeDigit = !addTimeDigit;
modeTime(); modeTime();
} }
function toggleDateFormat(){ function toggleDateFormat(){
dateFormat = !dateFormat; dateFormat = !dateFormat;
modeTime(); modeTime();
} }
function formatDate(res,dateFormat){ function formatDate(res,dateFormat){
let yyyy = res.year.toString(12); let yyyy = res.year.toString(12);
calenDef = calen10; calenDef = calen10;
if(!dateFormat){ //ordinal format if(!dateFormat){ //ordinal format
let mm = ("0"+(res.month+1).toString(12)).substr(-2); let mm = ("0"+(res.month+1).toString(12)).substr(-2);
let dd = ("0"+(res.day+1).toString(12)).substr(-2); let dd = ("0"+(res.day+1).toString(12)).substr(-2);
if(res.day==30){ if(res.day==30){
calenDef = calen7; calenDef = calen7;
let m = ((res.month+1).toString(12)).substr(-2); let m = ((res.month+1).toString(12)).substr(-2);
return(yyyy+"-"+"S"+m); // ordinal format return(yyyy+"-"+"S"+m); // ordinal format
} }
return(yyyy+"-"+mm+"-"+dd); return(yyyy+"-"+mm+"-"+dd);
} }
let m = res.month.toString(12); // cardinal format let m = res.month.toString(12); // cardinal format
let w = parseInt(res.day/6); let w = parseInt(res.day/6);
let d = res.day%6; let d = res.day%6;
//return(yyyy+"-"+res.month+"-"+w+"-"+d); //return(yyyy+"-"+res.month+"-"+w+"-"+d);
return(yyyy+"-"+m+"-"+w+"-"+d); return(yyyy+"-"+m+"-"+w+"-"+d);
} }
function writeDozTime(text,def){ function writeDozTime(text,def){
let pts = def.pts; let pts = def.pts;
let x=def.pt0[0]; let x=def.pt0[0];
let y=def.pt0[1]; let y=def.pt0[1];
g_t.clear(); g_t.clear();
g_t.setFont("Vector",def.size); g_t.setFont("Vector",def.size);
for(let i in text){ for(let i in text){
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); }
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); }
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); } else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
x = x+def.step[0]; x = x+def.step[0];
y = y+def.step[1]; y = y+def.step[1];
} }
} }
function writeDozDate(text,def,colour){ function writeDozDate(text,def,colour){
dateColour = colour; dateColour = colour;
let pts = def.pts; let pts = def.pts;
let x=def.pt0[0]; let x=def.pt0[0];
let y=def.pt0[1]; let y=def.pt0[1];
g_d.clear(); g_d.clear();
g_d.setFont("Vector",def.size); g_d.setFont("Vector",def.size);
for(let i in text){ for(let i in text){
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); }
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); }
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); } else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
x = x+def.step[0]; x = x+def.step[0];
y = y+def.step[1]; y = y+def.step[1];
} }
} }
Bangle.loadWidgets();
//for malaire's Adjust Clock widget, if used
function adjustedNow() {
return WIDGETS.adjust ? new Date(WIDGETS.adjust.now()) : new Date();
}
Bangle.drawWidgets();
// Functions for time mode // Functions for time mode
function drawTime() function drawTime()
{ {
let dt = new Date(); let dt = adjustedNow();
let date = ""; let date = "";
let timeDef; let timeDef;
let x = 0; let x = 0;
dt.setDate(dt.getDate()); dt.setDate(dt.getDate());
if(addTimeDigit){ if(addTimeDigit){
x = x =
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds(); 10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
let msg = "00000"+Math.floor(x).toString(12); let msg = "00000"+Math.floor(x).toString(12);
let time = msg.substr(-5,3)+"."+msg.substr(-2); let time = msg.substr(-5,3)+"."+msg.substr(-2);
let wait = 347*(1-(x%1)); let wait = 347*(1-(x%1));
timeDef = time6; timeDef = time6;
} else { } else {
x = x =
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds(); 864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
let msg = "0000"+Math.floor(x).toString(12); let msg = "0000"+Math.floor(x).toString(12);
let time = msg.substr(-4,3)+"."+msg.substr(-1); let time = msg.substr(-4,3)+"."+msg.substr(-1);
let wait = 4167*(1-(x%1)); let wait = 4167*(1-(x%1));
timeDef = time5; timeDef = time5;
} }
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
date = formatDate(res,dateFormat); date = formatDate(res,dateFormat);
if(dt<timeActiveUntil) if(dt<timeActiveUntil)
{ {
// Write to background buffers, then display on screen // Write to background buffers, then display on screen
writeDozDate(date,calenDef,res.colour); writeDozDate(date,calenDef,res.colour);
writeDozTime(time,timeDef); writeDozTime(time,timeDef);
g.flip(); g.flip();
// Ready next interval // Ready next interval
drawtime_timeout = setTimeout(drawTime,wait); drawtime_timeout = setTimeout(drawTime,wait);
} }
else else
{ {
// Clear screen // Clear screen
g_d.clear(); g_d.clear();
g_t.clear(); g_t.clear();
g.flip(); g.flip();
} }
lastX = x; lastX = x;
} }
function modeTime() function modeTime()
{ {
timeActiveUntil = new Date(); timeActiveUntil = adjustedNow();
timeActiveUntil.setDate(timeActiveUntil.getDate()); timeActiveUntil.setDate(timeActiveUntil.getDate());
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+86400); timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+604800);
if (typeof drawtime_timeout !== 'undefined') if (typeof drawtime_timeout !== 'undefined')
{ {
clearTimeout(drawtime_timeout); clearTimeout(drawtime_timeout);
} }
drawTime(); drawTime();
} }
Bangle.loadWidgets();
Bangle.drawWidgets();
// Functions for weather mode - TODO
// function drawWeather() {}
// function modeWeather() {}
// Start time on twist // Start time on twist
Bangle.on('twist',function() { Bangle.on('twist',function() {
modeTime(); modeTime();
}); });
// Time fix with GPS
function fixTime() {
Bangle.on("GPS",function cb(g) {
Bangle.setGPSPower(0,"time");
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()>2200)) {
} else {
// We have a GPS time. Set time
setTime(g.time.getTime()/1000);
}
});
Bangle.setGPSPower(1,"time");
setTimeout(fixTime, 10*60*1000); // every 10 minutes
}
// Start time fixing with GPS on next 10 minute interval
setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000);

View File

@ -2,7 +2,7 @@
"id": "doztime", "id": "doztime",
"name": "Dozenal Time", "name": "Dozenal Time",
"shortName": "Dozenal Time", "shortName": "Dozenal Time",
"version": "0.04", "version": "0.05",
"description": "A dozenal Holocene calendar and dozenal diurnal clock", "description": "A dozenal Holocene calendar and dozenal diurnal clock",
"icon": "app.png", "icon": "app.png",
"type": "clock", "type": "clock",

View File

@ -8,3 +8,4 @@
0.07: Fix crash if an odd number of active boxes are configured (fix #1473) 0.07: Fix crash if an odd number of active boxes are configured (fix #1473)
0.08: Added support for notifications from exstats. Support all stats from exstats 0.08: Added support for notifications from exstats. Support all stats from exstats
0.09: Fix broken start/stop if recording not enabled (fix #1561) 0.09: Fix broken start/stop if recording not enabled (fix #1561)
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)

View File

@ -1,6 +1,6 @@
{ "id": "run", { "id": "run",
"name": "Run", "name": "Run",
"version":"0.09", "version":"0.10",
"description": "Displays distance, time, steps, cadence, pace and more for runners.", "description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png", "icon": "app.png",
"tags": "run,running,fitness,outdoors,gps", "tags": "run,running,fitness,outdoors,gps",

View File

@ -42,6 +42,11 @@
value: Math.max(statsIDs.indexOf(settings[boxID]),0), value: Math.max(statsIDs.indexOf(settings[boxID]),0),
format: v => statsList[v].name, format: v => statsList[v].name,
onchange: v => { onchange: v => {
for (var i=1;i<=6;i++)
if (settings["B"+i]==statsIDs[v]) {
settings["B"+i]="";
boxMenu["Box "+i].value=0;
}
settings[boxID] = statsIDs[v]; settings[boxID] = statsIDs[v];
saveSettings(); saveSettings();
}, },
@ -60,7 +65,7 @@
'': { 'title': 'Run' }, '': { 'title': 'Run' },
'< Back': back, '< Back': back,
}; };
if (WIDGETS["recorder"]) if (global.WIDGETS&&WIDGETS["recorder"])
menu[/*LANG*/"Record Run"] = { menu[/*LANG*/"Record Run"] = {
value : !!settings.record, value : !!settings.record,
format : v => v?/*LANG*/"Yes":/*LANG*/"No", format : v => v?/*LANG*/"Yes":/*LANG*/"No",
@ -87,7 +92,7 @@
notificationsMenu[/*LANG*/"Dist Pattern"] = { notificationsMenu[/*LANG*/"Dist Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))), value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
min: 0, max: vibPatterns.length, min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off", format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => { onchange: v => {
settings.notify.dist.notifications = vibTimes[v]; settings.notify.dist.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]); sampleBuzz(vibTimes[v]);
@ -97,7 +102,7 @@
notificationsMenu[/*LANG*/"Step Pattern"] = { notificationsMenu[/*LANG*/"Step Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))), value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
min: 0, max: vibPatterns.length, min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off", format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => { onchange: v => {
settings.notify.step.notifications = vibTimes[v]; settings.notify.step.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]); sampleBuzz(vibTimes[v]);
@ -107,7 +112,7 @@
notificationsMenu[/*LANG*/"Time Pattern"] = { notificationsMenu[/*LANG*/"Time Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))), value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
min: 0, max: vibPatterns.length, min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off", format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => { onchange: v => {
settings.notify.time.notifications = vibTimes[v]; settings.notify.time.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]); sampleBuzz(vibTimes[v]);

View File

@ -0,0 +1 @@
0.01: Initial Release

View File

@ -0,0 +1,16 @@
# Seiko 5actus
![](screenshot.png)
This is built on the knowledge of what I gained through designing the rolex watch face and improves on it by getting more done right at the start.
This watch is modeled after one I personally own and love, I have spent quite a bit of time designing this in a pixel art editor to try and make it as clean as possible and am quite happy with how it came out.
This watch face works in both the light and dark themes but I personally think it looks a lot cleaner in the dark them.
This watch whilst technically designed in a way that would work with the BangleJs has been only listed to work with the BangleJs2, if someones wants to test it on a first gen and let me know if it works then i'll allow it to be installed on both devices but I assume with how the images have been designed it would look strange on a first gen watch.
Special thanks to:
* rozek (for his updated widget draw code for utilization with background images)
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
* The community (for helping drive such a wonderful project)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEBpMB+fziAjRCQQXBHyoXEgIRLgMwC5EAj8gC5MC+QXJn4XKBgJHJMhkfJAYXEh/xC5cDBofzJocvIxKiCHpBGNExMCIxi2KeRIJFgMiiYBCkQ1Jh67EAAMSCgICBiQjFn8xDYX/AgQANn4qEgf/JIcDkcxiUSiMRY4cv+ZaFj6bDgZGBkMRDIIXD/7CHn5TDFYIADFIcRSxgAvXQwAQgRyDACcje4wAESQ4RDmMQgSGBj8zAAnyTgauH/65Cj7EBkMicAPyBYIABCgcRkYWCmYvCewMhmUiiMyF4gUBDoXzn7/Dj4RBF4IXB+QrDj40DmJgBiEyBYMDL4qcEgUikYqDCgSGKAAUSn8hbh8BgICBl/yFggwEbhMC/4sHAAcTIhIsBGYLcJGAQOHgLAEGBM/Jg0vRgsQZAMQBAQUBif/AwLJDfoQ1DkDOBkIOCgQDBFAKCDaIRPGSwqGBgM/dwUDeQiZEEAowCgXxb5jGGgQ+GaAjaHeIbmISYToHRYTPKYQIODE4cfEA4AEPghtEgQJCI5Mv+AXHJAiIIBggXFj/xDBMCcAYXGga7LcYIXIUpY1GC4SiJVRAXCiAWJA"))

181
apps/seiko-5actus/app.js Normal file
View File

@ -0,0 +1,181 @@
var imgBg = {
width : 176, height : 176, bpp : 2,
transparent : 1,
buffer : require("heatshrink").decompress(atob("qoASv/8//1C6YASrorD6opjuorHBAIriQYwriV9FXFZldFjrSEFY6FjQcorPSYqtZFZaxaVoiDkZRArLv4rV/orT/5rGABtf/4rSq//+79UFaptIK5puHFZQUBFda5IABZuBCw4rKTAPVq4rUNw4rK/4rBroqRQAIrVQSd1N4QrQNZIAOFdbzBQ4IrOCQIIDWKQYFFZhqBHwaeBACBwBFazZQThQrJ/7CHABaSEFaTaWOIYrONJArTToorIdpDdRDQLJOCBDhRIxBoPABdXAwwrRVKQ+GZR7aZDYRHNM4IraOZyTQZbQrKaIwAKr7LOHRNdFaJzOr56RuppZACBgRAEtW1Wl1WqzWm1Nqy2qC5hyTrWq1IrFzWqyoXLv7mFQRlqqtp0tVy2V0uptNVA4JWK/72Fr4yFFY9VLINWyqJBFZtfFYwGGQZIrS////peF/6xM1KDDQQIrMKwJQFA4SEKQYLbGAoIrKv5PGGY4rGEYIAHyrZKQQoIDQhoARKwR6GBJIAXJpKECMIoAXUpaELBYQAICZR4CPYo3Kq4IBr7RITIzZFq7mOGwXXBYIjB//3/9drt/+5AGZ4RKHuoNEFZVdupQBuorBupjCJIyNIrqELr4mBr99vorEK476PBxAYC79//orCr4QBu5XIXAwiIcwxXCKYJlBFYSvIQRI7HTiLjBFYzZHAAzdBDA4AKDY6CMQgYQOABT8CEQjdJFTAAKuo8MADokkAH4A/AH4A/ABn1FldXFlfVTX4A/AH4A/AH4APr//ABVVrorcv4rL6tXFbgqL//1Qbv/1QAE1AED14re/wrK1Yr/Ff4rEcY3VFf4r/Ff4r/EaoAH/4rK14r/FZYALFb1/FZbTWAA9fFZYNBFjoA/AH4A/AH4A/vots+pu/AH4A/AH4A/ADdX6ousr4GFrohgABP/FbN/+o7O/6NZv5HOB4IrZ///LBlXB5wrO/qCNB5pzOOhgNBK7RICDhQNCX5P96td/91u4FBvpJLAoQPDrplEQRNdFYPVu93qvVO5JYJurZDSJV1FYP9FYP16oXBfJRKIbJpQB7vVv/3AwJvCbpTZVVIP9/9d6/9AALdTbJgAVEJDZMACoiCLAjZNAAqSKbpjZNSoo7PQg4zCIx9/FaBQBJ4rZRHqJQBFYzZRLCN/HooYRCQIRQYQ1dDCFVQR4A/ADFXCSK5RDJ40Iq6nPW6LcIq//fh39SrNf/6EN/47OFZp0NFbd/K5wPPI5obNM5F1FaYPNdYLcGfpA3IDQIrXABZ6FDSLcZTxAIBW4zcBFa4ZBFaLsNFZYZGFZBpIACCdBFZ7BFq7dSZJArOfQwAHHQYYBFaA+JABwhBIggrObirIJFZLuBbioXJFZI/JWJorBEJIJJS4KFRrqbKFZLvDupYSeZIrJbiwrBNwIrnTQYrReBIrNCpArKBRQAKIJQrLNhCvNJidXQSZYCPCiCTIQS6KEI4pVAAddFaF1FbAZHfioAVFYyUJWbRXHLrqxFFYhVeLI4rq6orCMAoAhFa4"))
};
/* Set hour hand image */
var imgHour = {
width : 16, height : 176, bpp : 2,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AH4A/AH4A/AEk//gDp///gEDAYPAh4DB+E/AYP8AaYbDEYYrDLdgD/Af4DXh/wAYIA/AGwA="))
};
/* Set minute hand image */
var imgMin = {
width : 8, height : 176, bpp : 2,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AH4A/AB8P+AB/AP4B/AIcA4DPHA="))
};
/* Set second hand image */
var imgSec = {
width : 6, height : 176, bpp : 2,
transparent : 1,
buffer : require("heatshrink").decompress(atob("qoA/ADFf6v9AU1c6vNFlICWvtXAXlVA="))
};
/* Sets the font for the date at an appropriate size of 14 */
Graphics.prototype.setFontWorkSans = function(scale) {
// Actual height 12 (12 - 1)
this.setFontCustom(atob("AAAAAAADwAAAPAAAAAAAAAvAAC/0AH/gAL/QAD9AAAIAAAAACQAAL/9AC/r9APAA8A8ADwDwAfAH//wAH/8AAAAAADwAAAtAAAH//8Af//wAAAAAAAAAABwAUAfgHwDwA/APAP8A8DzwD/9PAD/Q8AAABQAAA0AHwDwA9AHwDw4PAPDw8A///gA/f8AAAAAAAAQAAAvAAAP8AALzwAD8PAAv//wB///AAAPAAAAUAAAAAAC/88AP/y8A8NDwDywPAPD18A8L/AAAGgAAAAAAC/8AA//9ALTx8A8ODwDw4PALz/8ALD/AAAAAAPAAAA8AAADwB/APC/8A9/gAD/AAAPgAAAAAAAAAAAAB8fwAf//wDw8PAPDw8A8PDwB///AC8vwAAAAAAAAAAD/DgAv/PQDwsPAPC08A8PDwB//8AB//QAAAAAAAAAAA8DwADwPAABAE"), 46, atob("BAYJBggICQgJCAkJBA=="), 14+(scale<<8)+(2<<16));
return this;
};
/* Sets the font for the day at an appropriate size of 12 */
Graphics.prototype.setFontWorkSansSmall = function(scale) {
// Actual height 12 (11 - 0)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAABAP/zwGqjQAAAAP0AAEAAAP4AAGAAAAEoAAs/gL//QL88AAt/gL/+QK88AAYAAAUFAD+PAHPDgv//8fr70Hj7QCx+AAAAAC8AAL/AAPHBgL/PQB68AAPgAC9/APT7wEDjwAC/QAAAAAAuAC7/gL/DwPPywL9/gCwvAAD7wAABgAAAAP0AAEAAAACkAC//wP0C8sAAPYAAGPQA+D//0Af9ABQAADzAAB/AAv8AAB/AADyAABAAAACAAADgAADgAC//AAr5AADgAADQAAABAAAD9AAD0AAAAACwAACwAACwAACwAAAgAAABAAADwAADQAAAkAAv0Af9AL+AAvAAAAKgAD//ALgLgPADwPADwD//QA/9AAAAAAwAADwAAL//gL//gAAAAAgBQD4DwPAPwPA/wLnzwD/TwAUBQAAFADwPQLADwPDDwPvjwD+/AAQYAAAoAAD8AAv8AD08AP//gL//gAA8AAAAAL/PAPrDwPPDwPPDwPH/AAAoAAKgAC/+AHzrgPPDwPPDwH3/gBS+AKAAAPAAAPAbgPL/gP/QAPwAALAAAAAYAD+/ALvjwPLDwPLDwH//gBk+AAYAAD/PALTzwPDzwPDjwD//AAv8AAQBAA8DwA4DgAAAAAEBAAPD9AOD4AAAAABQAADwAAP4AANsAA8OAA4PABwHAAIUAAM8AAM8AAM8AAM8AAM8AAMoABgCAA4LAA8OAAdsAAP8AAHwAADgABgAADwAAPCzgPDzwLvBAD9AAAQAAABoAAv/wC0A8DD/OLPX3OMDjONHDHP/7Dfi5B4HQAP9AAACgAC/gB/8AP48AP48AB/8AAC/gAACgAAAAL//gP//wPDDwPDDwLv3gD+/AAAYAAGQAB/+AH0fQPADwPADwPADwDwPQBgNAAAAAL//gP//wPADwPADwHQDgD//AA/8AAAAAAAAAL//gP//wPDDwPDDwPDDwPADwAAAAAAAAL//gP//gPDAAPDAAPDAAPAAAAGQAB/+AD0fQPADwPCjwPDzwHz/QBy/gAAAAAAAAL//gL//gADAAADAAADAAL//gL//gAAAAAAAAL//gL//gAAAAAAeAAAvgAADwAADwL//gP//AAAAAAAAAL//gL//gAPAAA/0ADx/APAPwIABgAAAAL//gL//wAADwAADwAADwAACgAAAAL//gP6qQC/gAAH/QAAvwAv9AL9AAP//wGqqQAAAAL//gL6qQC+AAAP0AAB/AL//wL//gAAAAAGQAB/+AH0fQPADwPADwPADwD6vQB/9AAGQAAAAAL//gP//gPDwAPDwAH7gAD/AAAAAAAGQAB/+AH0fQPADwPAD9PAD/D6vbB/9PAGQAAAAAL//gP//gPDgAPD0ALr/AD/LwAUAgAUFAD+PALvDwPHDwPDjwLT7gDx/AAAAALAAAPAAAPAAAP//wPqqQPAAAPAAAAAAAP/+AGqvgAADwAADwAADwL//AL/4AAAAAKAAAL+AAAv9AAAvwAB/gAv8AL9AAKAAAKQAAL/QAAf/gAAPwAv/QL+AAL/gAAL/gAAvwC/+AP9AAEAAAEABgPgLwD9+AAfwAA/8AL0fgPADwOAAAL0AAB/AAAH/wA/qQL4AAPAAAFACgPAPwPA/wPHzwPvDwP4DwLQDwAAAAAAAAv///uqqvsAAPdAAAf4AAB/0AAC/wAAC4cAAKsAAPv///Kqqp"), 32, atob("BAMFCAgLCAMEBAcHAwYDBQgFBwcHBwcHBwcEBAcHBwcLCAgICQgHCQkEBwgHCgkJCAkICAcJCAwHBwgEBQQ="), 12+(scale<<8)+(2<<16));
return this;
};
/* Set variables to get screen width, height and center points */
let W = g.getWidth();
let H = g.getHeight();
let cx = W/2;
let cy = H/2;
let Timeout;
Bangle.loadWidgets();
/* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */
Bangle.drawWidgets = function () {
var w = g.getWidth(), h = g.getHeight();
var pos = {
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
tr:{x:w-1, y:0, r:1, c:0},
bl:{x:0, y:h-24, r:0, c:0},
br:{x:w-1, y:h-24, r:1, c:0}
};
if (global.WIDGETS) {
for (var wd of WIDGETS) {
var p = pos[wd.area];
if (!p) continue;
wd.x = p.x - p.r*wd.width;
wd.y = p.y;
p.x += wd.width*(1-2*p.r);
p.c++;
}
g.reset(); // also loads the current theme
try {
for (var wd of WIDGETS) {
g.setClipRect(wd.x,wd.y, wd.x+wd.width-1,23);
wd.draw(wd);
}
} catch (e) { print(e); }
g.reset(); // clears the clipping rectangle!
}
};
/* Draws the clock hands and date */
function drawHands() {
let d = new Date();
let hour = d.getHours() % 12;
let min = d.getMinutes();
let sec = d.getSeconds();
let twoPi = 2*Math.PI;
let Pi = Math.PI;
let hourAngle = (hour+(min/60))/12 * twoPi - Pi;
let minAngle = (min/60) * twoPi - Pi;
let secAngle = (sec/60) * twoPi - Pi;
g.setFontWorkSans();
g.setColor(g.theme.bg);
g.setFontAlign(0,0,0);
g.drawString(d.getDate(),162,90);
g.setFontWorkSansSmall();
let weekDay = d.toString().split(" ");
if (weekDay[0] == "Sat"){g.setColor(0,0,1);}
else if (weekDay[0] == "Sun"){g.setColor(1,0,0);}
else {g.setColor(g.theme.bg);}
g.drawString(weekDay[0].toUpperCase(), 137, 90);
handLayers = [
{x:cx,
y:cy,
image:imgHour,
rotate:hourAngle,
center:true
},
{x:cx,
y:cy,
image:imgMin,
rotate:minAngle,
center:true
},
{x:cx,
y:cy,
image:imgSec,
rotate:secAngle,
center:true
}];
g.setColor(g.theme.fg);
g.drawImages(handLayers);
}
function drawBackground() {
g.clear(1);
g.setBgColor(g.theme.bg);
g.setColor(g.theme.fg);
bgLayer = [
{x:cx,
y:cy,
image:imgBg,
center:true
}];
g.drawImages(bgLayer);
g.reset();
}
/* Refresh the display every second */
function displayRefresh() {
g.clear(true);
drawBackground();
drawHands();
Bangle.drawWidgets();
let Pause = 1000 - (Date.now() % 1000);
Timeout = setTimeout(displayRefresh,Pause);
}
Bangle.on('lcdPower', (on) => {
if (on) {
if (Timeout != null) { clearTimeout(Timeout); Timeout = undefined;}
displayRefresh();
}
});
Bangle.setUI("clock");
// load widgets after 'setUI' so they're aware there is a clock active
Bangle.loadWidgets();
displayRefresh();

View File

@ -0,0 +1,17 @@
{ "id": "seiko-5actus",
"name": "Seiko 5actus",
"shortName":"5actus",
"icon": "seiko-5actus.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.01",
"description": "A watch designed after then Seiko 5actus from the 1970's",
"tags": "clock",
"type": "clock",
"supports":["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"seiko-5actus.app.js","url":"app.js"},
{"name":"seiko-5actus.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

1
apps/widclose/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New widget!

7
apps/widclose/README.md Normal file
View File

@ -0,0 +1,7 @@
# Close Button
Adds a ![X](preview.png) button to close the current app and go back to the clock.
(Widget is not visible on the clock screen)
![Light theme screenshot](screenshot_light.png)
![Dark theme screenshot](screenshot_dark.png)

BIN
apps/widclose/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
{
"id": "widclose",
"name": "Close Button",
"version": "0.01",
"description": "A button to close the current app",
"readme": "README.md",
"icon": "icon.png",
"type": "widget",
"tags": "widget,tools",
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot_light.png"},{"url":"screenshot_dark.png"}],
"storage": [
{"name":"widclose.wid.js","url":"widget.js"}
]
}

BIN
apps/widclose/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

14
apps/widclose/widget.js Normal file
View File

@ -0,0 +1,14 @@
if (!Bangle.CLOCK) WIDGETS.close = {
area: "tr", width: 24, sortorder: 10, // we want the right-most spot please
draw: function() {
Bangle.removeListener("touch", this.touch);
Bangle.on("touch", this.touch);
g.reset().setColor("#f00").drawImage(atob( // hardcoded red to match setUI back button
// b/w version of preview.png, 24x24
"GBgBABgAAf+AB//gD//wH//4P//8P//8fn5+fjx+fxj+f4H+/8P//8P/f4H+fxj+fjx+fn5+P//8P//8H//4D//wB//gAf+AABgA"
), this.x, this.y);
}, touch: function(_, c) {
const w = WIDGETS.close;
if (w && c.x>=w.x && c.x<=w.x+24 && c.y>=w.y && c.y<=w.y+24) load();
}
};

View File

@ -22,10 +22,14 @@ function bangleDownload() {
var promise = Promise.resolve(); var promise = Promise.resolve();
// Normal files // Normal files
normalFiles.forEach((filename,n) => { normalFiles.forEach((filename,n) => {
if (filename==".firmware") {
console.log("Ignoring .firmware file");
return;
}
promise = promise.then(() => { promise = promise.then(() => {
Progress.hide({sticky: true}); Progress.hide({sticky: true});
var percent = n/fileCount; var percent = n/fileCount;
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0}); Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
return Comms.readFile(filename).then(data => zip.file(filename,data)); return Comms.readFile(filename).then(data => zip.file(filename,data));
}); });
}); });
@ -36,7 +40,7 @@ function bangleDownload() {
promise = promise.then(() => { promise = promise.then(() => {
Progress.hide({sticky: true}); Progress.hide({sticky: true});
var percent = (normalFiles.length+n)/fileCount; var percent = (normalFiles.length+n)/fileCount;
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0}); Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data)); return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data));
}); });
}); });

2
core

@ -1 +1 @@
Subproject commit 6e94cf77a2b355389dcd5efa55e2249a9b31983c Subproject commit 27c7db6035832837ca3909ea52939f60803df72f

View File

@ -59,6 +59,7 @@ options is an object containing:
* `label` - the text on the button * `label` - the text on the button
* `cb` - a callback function * `cb` - a callback function
* `cbl` - a callback function for long presses * `cbl` - a callback function for long presses
* `back` - a callback function, passed as `back` into Bangle.setUI
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
determine what objects have changed or moved, clear their previous locations, and re-render just those objects. determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
@ -89,7 +90,7 @@ function Layout(layout, options) {
options = options || {}; options = options || {};
this.lazy = options.lazy || false; this.lazy = options.lazy || false;
var btnList; var btnList, uiSet;
Bangle.setUI(); // remove all existing input handlers Bangle.setUI(); // remove all existing input handlers
if (process.env.HWVERSION!=2) { if (process.env.HWVERSION!=2) {
// no touchscreen, find any buttons in 'layout' // no touchscreen, find any buttons in 'layout'
@ -104,7 +105,7 @@ function Layout(layout, options) {
this.physBtns = 0; this.physBtns = 0;
this.buttons = btnList; this.buttons = btnList;
this.selectedButton = -1; this.selectedButton = -1;
Bangle.setUI("updown", dir=>{ Bangle.setUI({mode:"updown", back:options.back}, dir=>{
var s = this.selectedButton, l=this.buttons.length; var s = this.selectedButton, l=this.buttons.length;
if (dir===undefined && this.buttons[s]) if (dir===undefined && this.buttons[s])
return this.buttons[s].cb(); return this.buttons[s].cb();
@ -119,8 +120,10 @@ function Layout(layout, options) {
} }
this.selectedButton = s; this.selectedButton = s;
}); });
uiSet = true;
} }
} }
if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back});
if (options.btns) { if (options.btns) {
var buttons = options.btns; var buttons = options.btns;