mirror of https://github.com/espruino/BangleApps
Merge branch 'master' into auto-on-settings
commit
d325be9b57
47
README.md
47
README.md
|
@ -6,6 +6,10 @@ Bangle.js App Loader (and Apps)
|
||||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||||
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
||||||
|
|
||||||
|
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||||
|
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||||
|
and that it is not licensed in another way that would make this impossible.
|
||||||
|
|
||||||
## How does it work?
|
## How does it work?
|
||||||
|
|
||||||
* A list of apps is in `apps.json`
|
* A list of apps is in `apps.json`
|
||||||
|
@ -32,6 +36,7 @@ easily distinguish between file types, we use the following:
|
||||||
* `stuff.img` is an image
|
* `stuff.img` is an image
|
||||||
* `stuff.app.js` is JS code for applications
|
* `stuff.app.js` is JS code for applications
|
||||||
* `stuff.wid.js` is JS code for widgets
|
* `stuff.wid.js` is JS code for widgets
|
||||||
|
* `stuff.settings.js` is JS code for the settings menu
|
||||||
* `stuff.boot.js` is JS code that automatically gets run at boot time
|
* `stuff.boot.js` is JS code that automatically gets run at boot time
|
||||||
* `stuff.json` is used for JSON settings for an app
|
* `stuff.json` is used for JSON settings for an app
|
||||||
|
|
||||||
|
@ -314,6 +319,48 @@ the data you require from Bangle.js.
|
||||||
|
|
||||||
See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
|
See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
|
||||||
|
|
||||||
|
### Adding configuration to the "Settings" menu
|
||||||
|
|
||||||
|
Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings".
|
||||||
|
To do so, the app needs to include a `settings.js` file, containing a single function
|
||||||
|
that handles configuring the app.
|
||||||
|
When the app settings are opened, this function is called with one
|
||||||
|
argument, `back`: a callback to return to the settings menu.
|
||||||
|
|
||||||
|
Example `settings.js`
|
||||||
|
```js
|
||||||
|
// make sure to enclose the function in parentheses
|
||||||
|
(function(back) {
|
||||||
|
let settings = require('Storage').readJSON('app.settings.json',1)||{};
|
||||||
|
function save(key, value) {
|
||||||
|
settings[key] = value;
|
||||||
|
require('Storage').write('app.settings.json',settings);
|
||||||
|
}
|
||||||
|
const appMenu = {
|
||||||
|
'': {'title': 'App Settings'},
|
||||||
|
'< Back': back,
|
||||||
|
'Monkeys': {
|
||||||
|
value: settings.monkeys||12,
|
||||||
|
onchange: (m) => {save('monkeys', m)}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
E.showMenu(appMenu)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
In this example the app needs to add both `app.settings.js` and
|
||||||
|
`app.settings.json` to `apps.json`:
|
||||||
|
```json
|
||||||
|
{ "id": "app",
|
||||||
|
...
|
||||||
|
"storage": [
|
||||||
|
...
|
||||||
|
{"name":"app.settings.js","url":"settings.js"},
|
||||||
|
{"name":"app.settings.json","content":"{}"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
```
|
||||||
|
That way removing the app also cleans up `app.settings.json`.
|
||||||
|
|
||||||
## Coding hints
|
## Coding hints
|
||||||
|
|
||||||
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
|
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
|
||||||
|
|
57
apps.json
57
apps.json
|
@ -78,13 +78,14 @@
|
||||||
{ "id": "welcome",
|
{ "id": "welcome",
|
||||||
"name": "Welcome",
|
"name": "Welcome",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.04",
|
"version":"0.05",
|
||||||
"description": "Appears at first boot and explains how to use Bangle.js",
|
"description": "Appears at first boot and explains how to use Bangle.js",
|
||||||
"tags": "start,welcome",
|
"tags": "start,welcome",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"welcome.js","url":"welcome.js"},
|
{"name":"welcome.js","url":"welcome.js"},
|
||||||
{"name":"welcome.app.js","url":"app.js"},
|
{"name":"welcome.app.js","url":"app.js"},
|
||||||
|
{"name":"welcome.settings.js","url":"settings.js"},
|
||||||
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
|
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -117,7 +118,7 @@
|
||||||
{ "id": "setting",
|
{ "id": "setting",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"icon": "settings.png",
|
"icon": "settings.png",
|
||||||
"version":"0.08",
|
"version":"0.10",
|
||||||
"description": "A menu for setting up Bangle.js",
|
"description": "A menu for setting up Bangle.js",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -337,13 +338,16 @@
|
||||||
},
|
},
|
||||||
{ "id": "widbatpc",
|
{ "id": "widbatpc",
|
||||||
"name": "Battery Level Widget (with percentage)",
|
"name": "Battery Level Widget (with percentage)",
|
||||||
|
"shortName": "Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.06",
|
"version":"0.07",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||||
"tags": "widget,battery",
|
"tags": "widget,battery",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widbatpc.wid.js","url":"widget.js"}
|
{"name":"widbatpc.wid.js","url":"widget.js"},
|
||||||
|
{"name":"widbatpc.settings.js","url":"settings.js"},
|
||||||
|
{"name":"widbatpc.settings.json","content": "{}"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "widbt",
|
{ "id": "widbt",
|
||||||
|
@ -663,7 +667,7 @@
|
||||||
{ "id": "miclock",
|
{ "id": "miclock",
|
||||||
"name": "Mixed Clock",
|
"name": "Mixed Clock",
|
||||||
"icon": "clock-mixed.png",
|
"icon": "clock-mixed.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "A mix of analog and digital Clock",
|
"description": "A mix of analog and digital Clock",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"type":"clock",
|
"type":"clock",
|
||||||
|
@ -865,6 +869,19 @@
|
||||||
{"name":"torch.img","url":"app-icon.js","evaluate":true}
|
{"name":"torch.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ "id": "wohrm",
|
||||||
|
"name": "Workout HRM",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.05",
|
||||||
|
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
|
||||||
|
"tags": "hrm,workout",
|
||||||
|
"type": "app",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"wohrm.app.js","url":"app.js"},
|
||||||
|
{"name":"wohrm.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "id": "widid",
|
{ "id": "widid",
|
||||||
"name": "Bluetooth ID Widget",
|
"name": "Bluetooth ID Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
|
@ -894,7 +911,7 @@
|
||||||
{ "id": "marioclock",
|
{ "id": "marioclock",
|
||||||
"name": "Mario Clock",
|
"name": "Mario Clock",
|
||||||
"icon": "marioclock.png",
|
"icon": "marioclock.png",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"description": "Animated Mario clock, jumps to change the time!",
|
"description": "Animated Mario clock, jumps to change the time!",
|
||||||
"tags": "clock,mario,retro",
|
"tags": "clock,mario,retro",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -942,6 +959,19 @@
|
||||||
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
|
{"name":"barclock.img","url":"clock-bar-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ "id": "dotclock",
|
||||||
|
"name": "Dot Clock",
|
||||||
|
"icon": "clock-dot.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A Minimal Dot Analog Clock",
|
||||||
|
"tags": "clock",
|
||||||
|
"type":"clock",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"dotclock.app.js","url":"clock-dot.js"},
|
||||||
|
{"name":"dotclock.img","url":"clock-dot-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "id": "widtbat",
|
{ "id": "widtbat",
|
||||||
"name": "Tiny Battery Widget",
|
"name": "Tiny Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
|
@ -1001,7 +1031,7 @@
|
||||||
"name": "Touch Launcher",
|
"name": "Touch Launcher",
|
||||||
"shortName":"Menu",
|
"shortName":"Menu",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.02",
|
"version":"0.03",
|
||||||
"description": "Touch enable left to right launcher.",
|
"description": "Touch enable left to right launcher.",
|
||||||
"tags": "tool,system,launcher",
|
"tags": "tool,system,launcher",
|
||||||
"type":"launch",
|
"type":"launch",
|
||||||
|
@ -1047,5 +1077,18 @@
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"widmp.wid.js","url":"widget.js"}
|
{"name":"widmp.wid.js","url":"widget.js"}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{ "id": "minionclk",
|
||||||
|
"name": "Minion clock",
|
||||||
|
"icon": "minionclk.png",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Minion themed clock.",
|
||||||
|
"tags": "clock,minion",
|
||||||
|
"type": "clock",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"minionclk.app.js","url":"app.js"},
|
||||||
|
{"name":"minionclk.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: Based on the Analog Clock app, minimal dot interface
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkBIf4A/AGUJyAXtACeZBCAOJh/wC6IADC4gA/XEINJC64A/AHcP+ACD/4CBTB0Ph8A+ACBAIKoKC65HKC4gA/AAfACysM5gvjTBgNKC64A/AEWZBCAXdADa4XaH4A/AAgA=="))
|
|
@ -0,0 +1,162 @@
|
||||||
|
let g;
|
||||||
|
let Bangle;
|
||||||
|
|
||||||
|
const locale = require('locale');
|
||||||
|
const p = Math.PI / 2;
|
||||||
|
const pRad = Math.PI / 180;
|
||||||
|
const faceWidth = 100; // watch face radius
|
||||||
|
let timer = null;
|
||||||
|
let currentDate = new Date();
|
||||||
|
let hourRadius = 60;
|
||||||
|
let minRadius = 80;
|
||||||
|
const centerPx = g.getWidth() / 2;
|
||||||
|
|
||||||
|
const seconds = (angle) => {
|
||||||
|
const a = angle * pRad;
|
||||||
|
const x = centerPx + Math.sin(a) * faceWidth;
|
||||||
|
const y = centerPx - Math.cos(a) * faceWidth;
|
||||||
|
|
||||||
|
// if 15 degrees, make hour marker larger
|
||||||
|
const radius = (angle % 15) ? 2 : 4;
|
||||||
|
g.fillCircle(x, y, radius);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hourDot = (angle,radius) => {
|
||||||
|
const a = angle * pRad;
|
||||||
|
const x = centerPx + Math.sin(a) * hourRadius;
|
||||||
|
const y = centerPx - Math.cos(a) * hourRadius;
|
||||||
|
g.fillCircle(x, y, radius);
|
||||||
|
};
|
||||||
|
|
||||||
|
const minDot = (angle,radius) => {
|
||||||
|
const a = angle * pRad;
|
||||||
|
const x = centerPx + Math.sin(a) * minRadius;
|
||||||
|
const y = centerPx - Math.cos(a) * minRadius;
|
||||||
|
g.fillCircle(x, y, radius);
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawAll = () => {
|
||||||
|
g.clear();
|
||||||
|
currentDate = new Date();
|
||||||
|
// draw hands first
|
||||||
|
onMinute();
|
||||||
|
// draw seconds
|
||||||
|
const currentSec = currentDate.getSeconds();
|
||||||
|
// draw all secs
|
||||||
|
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
if (i > currentSec) {
|
||||||
|
g.setColor(0, 0, 0.6);
|
||||||
|
} else {
|
||||||
|
g.setColor(0.3, 0.3, 1);
|
||||||
|
}
|
||||||
|
seconds((360 * i) / 60);
|
||||||
|
}
|
||||||
|
onSecond();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetSeconds = () => {
|
||||||
|
g.setColor(0, 0, 0.6);
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
seconds((360 * i) / 60);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawMin = () => {
|
||||||
|
g.setColor(0.5, 0.5, 0.5);
|
||||||
|
for (let i = 0; i < 60; i++) {
|
||||||
|
minDot((360 * i) / 60,1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawHour = () => {
|
||||||
|
g.setColor(0.5, 0.5, 0.5);
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
hourDot((360 * 5 * i) / 60,1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSecond = () => {
|
||||||
|
g.setColor(0.3, 0.3, 1);
|
||||||
|
seconds((360 * currentDate.getSeconds()) / 60);
|
||||||
|
if (currentDate.getSeconds() === 59) {
|
||||||
|
resetSeconds();
|
||||||
|
onMinute();
|
||||||
|
}
|
||||||
|
g.setColor(1, 0.7, 0.2);
|
||||||
|
currentDate = new Date();
|
||||||
|
seconds((360 * currentDate.getSeconds()) / 60);
|
||||||
|
g.setColor(1, 1, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawDate = () => {
|
||||||
|
g.reset();
|
||||||
|
g.setColor(1, 1, 1);
|
||||||
|
g.setFont('6x8', 2);
|
||||||
|
|
||||||
|
const dayString = locale.dow(currentDate, true);
|
||||||
|
// pad left date
|
||||||
|
const dateString = ((currentDate.getDate() < 10) ? '0' : '') + currentDate.getDate().toString();
|
||||||
|
const dateDisplay = `${dayString} ${dateString}`;
|
||||||
|
// console.log(`${dayString}|${dateString}`);
|
||||||
|
// center date
|
||||||
|
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
||||||
|
const t = centerPx - 6 ;
|
||||||
|
g.drawString(dateDisplay, l, t);
|
||||||
|
// console.log(l, t);
|
||||||
|
};
|
||||||
|
const onMinute = () => {
|
||||||
|
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
|
||||||
|
g.clear();
|
||||||
|
resetSeconds();
|
||||||
|
}
|
||||||
|
// clear existing hands
|
||||||
|
g.setColor(0, 0, 0);
|
||||||
|
hourDot((360 * currentDate.getHours()) / 12,4);
|
||||||
|
minDot((360 * currentDate.getMinutes()) / 60,3);
|
||||||
|
|
||||||
|
// Hour
|
||||||
|
drawHour();
|
||||||
|
// Minute
|
||||||
|
drawMin();
|
||||||
|
|
||||||
|
// get new date, then draw new hands
|
||||||
|
currentDate = new Date();
|
||||||
|
g.setColor(1, 0, 0);
|
||||||
|
// Hour
|
||||||
|
hourDot((360 * currentDate.getHours()) / 12,4);
|
||||||
|
g.setColor(1, 0.9, 0.9);
|
||||||
|
// Minute
|
||||||
|
minDot((360 * currentDate.getMinutes()) / 60,3);
|
||||||
|
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||||
|
Bangle.buzz();
|
||||||
|
}
|
||||||
|
drawDate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const startTimers = () => {
|
||||||
|
timer = setInterval(onSecond, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.on('lcdPower', (on) => {
|
||||||
|
if (on) {
|
||||||
|
// g.clear();
|
||||||
|
drawAll();
|
||||||
|
startTimers();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
} else {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
resetSeconds();
|
||||||
|
startTimers();
|
||||||
|
drawAll();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,4 +1,4 @@
|
||||||
|
/* jshint esversion: 6 */
|
||||||
const distanceUnits = { // how many meters per X?
|
const distanceUnits = { // how many meters per X?
|
||||||
"m" : 1,
|
"m" : 1,
|
||||||
"yd" : 0.9144,
|
"yd" : 0.9144,
|
||||||
|
@ -387,4 +387,21 @@ var locales = {
|
||||||
abday: "Dom,Seg,Ter,Qua,Qui,Sex,Sab",
|
abday: "Dom,Seg,Ter,Qua,Qui,Sex,Sab",
|
||||||
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
|
day: "Domingo,Segunda-feira,Terça-feira,Quarta-feira,Quinta-feira,Sexta-feira,Sábado",
|
||||||
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "certo", on: "ligado", off: "desligado" }},
|
trans: { yes: "sim", Yes: "Sim", no: "não", No: "Não", ok: "certo", on: "ligado", off: "desligado" }},
|
||||||
|
"cs_CZ": {
|
||||||
|
lang: "cs_CZ",
|
||||||
|
decimal_point: ",",
|
||||||
|
thousands_sep: " ",
|
||||||
|
currency_symbol: "Kč",
|
||||||
|
int_curr_symbol: " CZK",
|
||||||
|
speed: 'kmh',
|
||||||
|
distance: { "0": "m", "1": "km" },
|
||||||
|
temperature: '°C',
|
||||||
|
ampm: {0:"dop",1:"odp"},
|
||||||
|
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||||
|
datePattern: { 0: "%d. %b %Y", 1: "%d.%m.%Y" }, // "30. led 2020" // "30.01.2020"(short)
|
||||||
|
abmonth: "led,úno,bře,dub,kvě,čvn,čvc,srp,zář,říj,lis,pro",
|
||||||
|
month: "leden,únor,březen,duben,květen,červen,červenec,srpen,září,říjen,listopad,prosinec",
|
||||||
|
abday: "ne,po,út,st,čt,pá,so",
|
||||||
|
day: "neděle,pondělí,úterý,středa,čtvrtek,pátek,sobota",
|
||||||
|
trans: { yes: "tak", Yes: "Tak", no: "nie", No: "Nie", ok: "ok", on: "na", off: "poza" }}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: use short date format from locale, take timeout from settings
|
0.03: use short date format from locale, take timeout from settings
|
||||||
0.04: modify date to display to be more at the original idea but still localized
|
0.04: modify date to display to be more at the original idea but still localized
|
||||||
0.05: use 12/24 hour clock from settings
|
0.05: use 12/24 hour clock from settings
|
||||||
|
0.06: Performance refactor, and enhanced graphics!
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
+ Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock
|
+ Based on Espruino Mario Clock V3 https://github.com/paulcockrell/espruino-mario-clock
|
||||||
+ Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP.
|
+ Converting images to 1bit BMP: Image > Mode > Indexed and tick the "Use black and white (1-bit) palette", Then export as BMP.
|
||||||
+ Online Image convertor: https://www.espruino.com/Image+Converter
|
+ Online Image convertor: https://www.espruino.com/Image+Converter
|
||||||
|
+ Images must be converted 1Bit White/Black !!! Not Black/White
|
||||||
**********************************/
|
**********************************/
|
||||||
|
|
||||||
const locale = require("locale");
|
const locale = require("locale");
|
||||||
|
@ -16,110 +17,32 @@ let W, H;
|
||||||
|
|
||||||
let intervalRef, displayTimeoutRef = null;
|
let intervalRef, displayTimeoutRef = null;
|
||||||
|
|
||||||
// Space to draw watch widgets (e.g battery, bluetooth status)
|
|
||||||
const WIDGETS_GUTTER = 10;
|
|
||||||
|
|
||||||
// Colours
|
// Colours
|
||||||
const LIGHTEST = "#effedd";
|
const LIGHTEST = "#effedd";
|
||||||
const LIGHT = "#add795";
|
const LIGHT = "#add795";
|
||||||
const DARK = "#588d77";
|
const DARK = "#588d77";
|
||||||
const DARKEST = "#122d3e";
|
const DARKEST = "#122d3e";
|
||||||
|
|
||||||
// Mario Images
|
|
||||||
const marioRunningImage1 = {
|
|
||||||
width : 15, height : 20, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("B8AfwH+B/8f/z4M+KExcnAUSCw87w4L8CJQRNB/YH+AxgCEAPAA="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const marioRunningImage1Neg = {
|
|
||||||
width : 15, height : 20, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAHwB0DOgY/jt8PDAPAEAB2gOyAAgAAAOAB4AAAA="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const marioRunningImage2 = {
|
|
||||||
width : 15, height : 20, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("B8AfwH+B/8f/z4M+KExcnAUSCw87w4J6BEsPnSfyT+S+OMAAAAA="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const marioRunningImage2Neg = {
|
|
||||||
width : 15, height : 20, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAHwB0DOgY/jt8PDAPAGEA7QAYhgMMBhAAAAAAAA="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const pyramid = {
|
|
||||||
width : 20, height : 20, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAkAAQgAIEAEAgCAEBAAggAEQAAoAAE="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const pipe = {
|
|
||||||
width : 9, height : 6, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("/8BxaCRSCA=="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const floor = {
|
|
||||||
width : 8, height : 3, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("/6pE"))
|
|
||||||
};
|
|
||||||
|
|
||||||
const sky = {
|
|
||||||
width : 128, height : 30, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("VVVVVVVVVVVVVVVVVVVVVQAAAAAAAAAAAAAAAAAAAABVVVVVVVVVVVVVVVVVVVVVIiIiIiIiIiIiIiIiIiIiIlVVVVVVVVVVVVVVVVVVVVWIiIiIiIiIiIiIiIiIiIiIVVVVVVVVVVVVVVVVVVVVVSIiIiIiIiICIiIiIiIiIiJVVVVVVVVVAVVVVVVVVVVViIiIiIiIiACIiIiIiIiIiFVVVVVVVVQAVVVVVVVVVVUiIiIiIiIgACIiIiIiIiIiVVVVVVVVUAAVVVUBVVVVUKqqqqqqqggAKCqqAKqqqoBVUBVVVVQAABAVVABVVVUAIiACIiIgAAAgAiAAIiIiAFVABVVVUAAAUAVUABRVVQCqgAKCqqAAACAAAAAgCqoAVUABAVVAAAAAAAAAAAVQAKqAAACqoAAAAAAAAAACgABAAAAAUEAAAAAAAAAABQAAgAAAACAAAAAAAAAAAAIAAAAAAABQAAAAAAAAAAAEAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"))
|
|
||||||
};
|
|
||||||
|
|
||||||
const brick = {
|
|
||||||
width : 21, height : 15, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("f//0AABoAAsAABgAAMAABgAAMAABgAAMAABgAAMAABoAAsAABf//wA=="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const flower = {
|
|
||||||
width : 7, height : 7, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("fY3wjW+OAA=="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const pipePlant = {
|
|
||||||
width : 9, height : 15, bpp : 1,
|
|
||||||
transparent : 0,
|
|
||||||
buffer : E.toArrayBuffer(atob("FBsNhsHDWPn/gOLQSKQSKQQ="))
|
|
||||||
};
|
|
||||||
|
|
||||||
const marioSprite = {
|
const marioSprite = {
|
||||||
frameIdx: 0,
|
frameIdx: 0,
|
||||||
frames: [
|
|
||||||
marioRunningImage1,
|
|
||||||
marioRunningImage2
|
|
||||||
],
|
|
||||||
negFrames: [
|
|
||||||
marioRunningImage1Neg,
|
|
||||||
marioRunningImage2Neg
|
|
||||||
],
|
|
||||||
x: 35,
|
x: 35,
|
||||||
y: 55,
|
y: 55,
|
||||||
jumpCounter: 0,
|
jumpCounter: 0,
|
||||||
jumpIncrement: Math.PI / 10,
|
jumpIncrement: Math.PI / 6,
|
||||||
isJumping: false
|
isJumping: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATIC_TILES = {
|
const coinSprite = {
|
||||||
"_": {img: floor, x: 16 * 8, y: 75},
|
frameIdx: 0,
|
||||||
"X": {img: sky, x: 0, y: 10},
|
x: 34,
|
||||||
"#": {img: brick, x: 0, y: 0},
|
y: 18,
|
||||||
|
isAnimating: false,
|
||||||
|
yDefault: 18,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TILES = {
|
const pyramidSprite = {
|
||||||
"T": {img: pipe, x: 16 * 8, y: 69},
|
x: 90,
|
||||||
"^": {img: pyramid, x: 16 * 8, y: 55},
|
height: 34,
|
||||||
"*": {img: flower, x: 16 * 8, y: 68},
|
|
||||||
"V": {img: pipePlant, x: 16 * 8, y: 60}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ONE_SECOND = 1000;
|
const ONE_SECOND = 1000;
|
||||||
|
@ -136,95 +59,133 @@ function incrementTimer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTile(sprite) {
|
|
||||||
g.drawImage(sprite.img, sprite.x, sprite.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function drawBackground() {
|
function drawBackground() {
|
||||||
|
// Clear screen
|
||||||
g.setColor(LIGHTEST);
|
g.setColor(LIGHTEST);
|
||||||
g.fillRect(0, 10, W, H);
|
g.fillRect(0, 10, W, H);
|
||||||
|
|
||||||
// draw floor
|
// Date bar
|
||||||
g.setColor(DARK);
|
g.setColor(DARKEST);
|
||||||
for (var x = 0; x < 16; x++) {
|
g.fillRect(0, 0, W, 9);
|
||||||
var floorSprite = Object.assign({}, STATIC_TILES._, {x: x * 8});
|
|
||||||
drawTile(floorSprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw sky
|
// draw sky
|
||||||
var skySprite = STATIC_TILES.X;
|
|
||||||
g.setColor(LIGHT);
|
g.setColor(LIGHT);
|
||||||
drawTile(skySprite);
|
g.fillRect(0, 10, g.getWidth(), 15);
|
||||||
|
g.fillRect(0, 17, g.getWidth(), 17);
|
||||||
|
g.fillRect(0, 19, g.getWidth(), 19);
|
||||||
|
g.fillRect(0, 21, g.getWidth(), 21);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawScenery() {
|
function drawFloor() {
|
||||||
// new random sprite
|
const fImg = require("heatshrink").decompress(atob("ikDxH+rgATCoIBQAQYDP")); // Floor image
|
||||||
const spriteKeys = Object.keys(TILES);
|
for (let x = 0; x < 4; x++) {
|
||||||
const key = spriteKeys[Math.floor(Math.random() * spriteKeys.length)];
|
g.drawImage(fImg, x * 20, g.getHeight() - 5);
|
||||||
let newSprite = Object.assign({}, TILES[key]);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawPyramid() {
|
||||||
|
const pPol = [pyramidSprite.x + 10, H - 6, pyramidSprite.x + 50, pyramidSprite.height, pyramidSprite.x + 90, H - 6]; // Pyramid poly
|
||||||
|
|
||||||
|
g.setColor(LIGHT);
|
||||||
|
g.fillPoly(pPol);
|
||||||
|
|
||||||
|
pyramidSprite.x -= 1;
|
||||||
|
// Reset and randomize pyramid if off-screen
|
||||||
|
if (pyramidSprite.x < - 100) {
|
||||||
|
pyramidSprite.x = 90;
|
||||||
|
pyramidSprite.height = Math.floor(Math.random() * (60 /* max */ - 25 /* min */ + 1) + 25 /* min */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTreesFrame(x, y) {
|
||||||
|
const tImg = require("heatshrink").decompress(atob("h8GxH+AAMHAAIFCAxADEBYgDCAQYAFCwobOAZAEFBxo=")); // Tree image
|
||||||
|
|
||||||
|
g.drawImage(tImg, x, y);
|
||||||
|
g.setColor(DARKEST);
|
||||||
|
g.drawLine(x + 6 /* Match stalk to palm tree */, y + 6 /* Match stalk to palm tree */, x + 6, H - 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTrees() {
|
||||||
|
let newSprite = {x: 90, y: Math.floor(Math.random() * (40 /* max */ - 5 /* min */ + 1) + 15 /* min */)};
|
||||||
|
|
||||||
// remove first sprite if offscreen
|
// remove first sprite if offscreen
|
||||||
let firstBackgroundSprite = backgroundArr[0];
|
let firstBackgroundSprite = backgroundArr[0];
|
||||||
if (firstBackgroundSprite) {
|
if (firstBackgroundSprite) {
|
||||||
if (firstBackgroundSprite.x < -20) backgroundArr.splice(0, 1);
|
if (firstBackgroundSprite.x < -15) backgroundArr.splice(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set background sprite if array empty
|
// set background sprite if array empty
|
||||||
var lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
|
let lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
|
||||||
if (!lastBackgroundSprite) {
|
if (!lastBackgroundSprite) {
|
||||||
lastBackgroundSprite = newSprite;
|
lastBackgroundSprite = newSprite;
|
||||||
backgroundArr.push(lastBackgroundSprite);
|
backgroundArr.push(lastBackgroundSprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add random sprites
|
// add random sprites
|
||||||
if (backgroundArr.length < 6 && lastBackgroundSprite.x < (16 * 7)) {
|
if (backgroundArr.length < 2 && lastBackgroundSprite.x < (16 * 7)) {
|
||||||
var randIdx = Math.floor(Math.random() * 25);
|
const randIdx = Math.floor(Math.random() * 25);
|
||||||
if (randIdx < spriteKeys.length - 1) {
|
if (randIdx < 2) {
|
||||||
backgroundArr.push(newSprite);
|
backgroundArr.push(newSprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (x = 0; x < backgroundArr.length; x++) {
|
for (x = 0; x < backgroundArr.length; x++) {
|
||||||
let scenerySprite = backgroundArr[x];
|
let scenerySprite = backgroundArr[x];
|
||||||
|
|
||||||
// clear sprite at previous position
|
|
||||||
g.setColor(LIGHTEST);
|
|
||||||
drawTile(scenerySprite);
|
|
||||||
|
|
||||||
// draw sprite in new position
|
|
||||||
g.setColor(LIGHT);
|
|
||||||
scenerySprite.x -= 5;
|
scenerySprite.x -= 5;
|
||||||
drawTile(scenerySprite);
|
drawTreesFrame(scenerySprite.x, scenerySprite.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMario() {
|
function drawCoinFrame(x, y) {
|
||||||
// clear old mario frame
|
const cImg = require("heatshrink").decompress(atob("hkPxH+AAcHAAQIEBIXWAAQNEBIWHAAdcBgQLBA4IODBYQKEBAQMDBelcBaJUBM4QRBNYx1EBQILDR4QHBBISdIBIoA==")); // Coin image
|
||||||
g.setColor(LIGHTEST);
|
g.drawImage(cImg, x, y);
|
||||||
g.drawImage(
|
}
|
||||||
marioSprite.negFrames[marioSprite.frameIdx],
|
|
||||||
marioSprite.x,
|
|
||||||
marioSprite.y
|
|
||||||
);
|
|
||||||
g.drawImage(
|
|
||||||
marioSprite.frames[marioSprite.frameIdx],
|
|
||||||
marioSprite.x,
|
|
||||||
marioSprite.y
|
|
||||||
);
|
|
||||||
|
|
||||||
|
function drawCoin() {
|
||||||
|
if (!coinSprite.isAnimating) return;
|
||||||
|
|
||||||
|
coinSprite.y -= 8;
|
||||||
|
if (coinSprite.y < (0 - 15 /*Coin sprite height*/)) {
|
||||||
|
coinSprite.isAnimating = false;
|
||||||
|
coinSprite.y = coinSprite.yDefault;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCoinFrame(coinSprite.x, coinSprite.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMarioFrame(idx, x, y) {
|
||||||
|
const mFr1 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw52FSg2HAAIoDAgIOMB5AAFGQTtKeBLuNcQwOJFwgJFA=")); // Mario Frame 1
|
||||||
|
const mFr2 = require("heatshrink").decompress(atob("h8UxH+AAkHAAYKFBolcAAIPIBgYPDBpgfGFIY7EA4YcEBIPWAAYdDC4gLDAII5ECoYOFDogODFgoJCBwYZCAQYOFBAhAFFwZKGHQpMDw+HCQYEBSowOBBQIdCCgTOIFgiVHFwYCBUhA9FBwz8HAo73GACQA=")); // Mario frame 2
|
||||||
|
|
||||||
|
switch(idx) {
|
||||||
|
case 0:
|
||||||
|
g.drawImage(mFr1, x, y);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
g.drawImage(mFr2, x, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMario(date) {
|
||||||
// calculate jumping
|
// calculate jumping
|
||||||
const t = new Date(),
|
const seconds = date.getSeconds(),
|
||||||
seconds = t.getSeconds(),
|
milliseconds = date.getMilliseconds();
|
||||||
milliseconds = t.getMilliseconds();
|
|
||||||
|
|
||||||
if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) {
|
if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) {
|
||||||
marioSprite.isJumping = true;
|
marioSprite.isJumping = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (marioSprite.isJumping) {
|
if (marioSprite.isJumping) {
|
||||||
marioSprite.y = (Math.sin(marioSprite.jumpCounter) * -10) + 50 /* Mario Y base value */;
|
marioSprite.y = (Math.sin(marioSprite.jumpCounter) * -12) + 50 /* Mario Y base value */;
|
||||||
marioSprite.jumpCounter += marioSprite.jumpIncrement;
|
marioSprite.jumpCounter += marioSprite.jumpIncrement;
|
||||||
|
|
||||||
|
if (parseInt(marioSprite.jumpCounter) === 2 && !coinSprite.isAnimating) {
|
||||||
|
coinSprite.isAnimating = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (marioSprite.jumpCounter.toFixed(1) >= 4) {
|
if (marioSprite.jumpCounter.toFixed(1) >= 4) {
|
||||||
marioSprite.jumpCounter = 0;
|
marioSprite.jumpCounter = 0;
|
||||||
marioSprite.isJumping = false;
|
marioSprite.isJumping = false;
|
||||||
|
@ -232,51 +193,28 @@ function drawMario() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate animation timing
|
// calculate animation timing
|
||||||
if (timer % 100 === 0) {
|
if (timer % 50 === 0) {
|
||||||
// shift to next frame
|
// shift to next frame
|
||||||
marioSprite.frameIdx ^= 1;
|
marioSprite.frameIdx ^= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// colour in mario
|
drawMarioFrame(marioSprite.frameIdx, marioSprite.x, marioSprite.y);
|
||||||
g.setColor(LIGHT);
|
|
||||||
g.drawImage(
|
|
||||||
marioSprite.negFrames[marioSprite.frameIdx],
|
|
||||||
marioSprite.x,
|
|
||||||
marioSprite.y
|
|
||||||
);
|
|
||||||
|
|
||||||
// draw mario
|
|
||||||
g.setColor(DARKEST);
|
|
||||||
g.drawImage(
|
|
||||||
marioSprite.frames[marioSprite.frameIdx],
|
|
||||||
marioSprite.x,
|
|
||||||
marioSprite.y
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawBrickFrame(x, y) {
|
||||||
function drawBrick(x, y) {
|
const brk = require("heatshrink").decompress(atob("ikQxH+/0HACASB6wAQCoPWw4AOrgT/Cf4T/Cb1cAB8H/wVBAB/+A"));
|
||||||
const brickSprite = Object.assign({}, STATIC_TILES['#'], {x: x, y: y});
|
g.drawImage(brk, x, y);
|
||||||
|
|
||||||
// draw brick background colour
|
|
||||||
g.setColor(LIGHT);
|
|
||||||
g.fillRect(x, y, x + 20, y+14);
|
|
||||||
|
|
||||||
// draw brick sprite
|
|
||||||
g.setColor(DARK);
|
|
||||||
drawTile(brickSprite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawTime() {
|
function drawTime(date) {
|
||||||
// draw hour brick
|
// draw hour brick
|
||||||
drawBrick(20, 25);
|
drawBrickFrame(20, 25);
|
||||||
// draw minute brick
|
// draw minute brick
|
||||||
drawBrick(42, 25);
|
drawBrickFrame(42, 25);
|
||||||
|
|
||||||
const t = new Date();
|
const h = date.getHours();
|
||||||
const h = t.getHours();
|
|
||||||
const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
|
const hours = ("0" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
|
||||||
const mins = ("0" + t.getMinutes()).substr(-2);
|
const mins = ("0" + date.getMinutes()).substr(-2);
|
||||||
|
|
||||||
g.setFont("6x8");
|
g.setFont("6x8");
|
||||||
g.setColor(DARKEST);
|
g.setColor(DARKEST);
|
||||||
|
@ -284,25 +222,30 @@ function drawTime() {
|
||||||
g.drawString(mins, 47, 29);
|
g.drawString(mins, 47, 29);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawDate() {
|
function drawDate(date) {
|
||||||
g.setFont("6x8");
|
g.setFont("6x8");
|
||||||
g.setColor(LIGHTEST);
|
g.setColor(LIGHTEST);
|
||||||
let d = new Date();
|
let dateStr = locale.date(date, true);
|
||||||
let dateStr = locale.date(d, true);
|
dateStr = dateStr.replace(date.getFullYear(), "").trim().replace(/\/$/i,"");
|
||||||
dateStr = dateStr.replace(d.getFullYear(), "").trim().replace(/\/$/i,"");
|
dateStr = locale.dow(date, true) + " " + dateStr;
|
||||||
dateStr = locale.dow(d, true) + " " + dateStr;
|
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 1);
|
||||||
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function redraw() {
|
function redraw() {
|
||||||
|
const date = new Date();
|
||||||
|
|
||||||
// Update timers
|
// Update timers
|
||||||
incrementTimer();
|
incrementTimer();
|
||||||
|
|
||||||
// Draw frame
|
// Draw frame
|
||||||
drawScenery();
|
drawBackground();
|
||||||
drawTime();
|
drawFloor();
|
||||||
drawDate();
|
drawPyramid();
|
||||||
drawMario();
|
drawTrees();
|
||||||
|
drawTime(date);
|
||||||
|
drawDate(date);
|
||||||
|
drawMario(date);
|
||||||
|
drawCoin();
|
||||||
|
|
||||||
// Render new frame
|
// Render new frame
|
||||||
g.flip();
|
g.flip();
|
||||||
|
@ -335,7 +278,6 @@ function startTimers(){
|
||||||
|
|
||||||
resetDisplayTimeout();
|
resetDisplayTimeout();
|
||||||
|
|
||||||
drawBackground();
|
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +287,6 @@ function init() {
|
||||||
|
|
||||||
// Initialise display
|
// Initialise display
|
||||||
Bangle.setLCDMode("80x80");
|
Bangle.setLCDMode("80x80");
|
||||||
g.clear();
|
|
||||||
|
|
||||||
// Store screen dimensions
|
// Store screen dimensions
|
||||||
W = g.getWidth();
|
W = g.getWidth();
|
||||||
|
@ -381,4 +322,4 @@ function init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise!
|
// Initialise!
|
||||||
init();
|
init();
|
|
@ -1,2 +1,3 @@
|
||||||
0.02: Modified for use with new bootloader and firmware
|
0.02: Modified for use with new bootloader and firmware
|
||||||
0.03: Localization
|
0.03: Localization
|
||||||
|
0.04: move jshint to the top
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var locale = require("locale");
|
|
||||||
/* jshint esversion: 6 */
|
/* jshint esversion: 6 */
|
||||||
|
var locale = require("locale");
|
||||||
const Radius = { "center": 8, "hour": 78, "min": 95, "dots": 102 };
|
const Radius = { "center": 8, "hour": 78, "min": 95, "dots": 102 };
|
||||||
const Center = { "x": 120, "y": 132 };
|
const Center = { "x": 120, "y": 132 };
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First release
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwhAaXlZEolVVvOj0mq1XOv9/qtWFb8rquj1XV5wBDAA2jvMqNLMqwAsCABBhBAIujqpbWvIsCLowBHMg0qFyVVFQJNDK4YoEAYxjFvIuQwHP6ur5+rDgfU5wBDMI2qCYOsC4XV0bFOwIWBAAeBAIOrMYYsF54sCCIWswQDB52AGBcrFIOtF4gAEMoJfDAAOrCYQuDAIowKFwQWIBIeA52pGQPWLYgAFA4YDBGA4uDEwQYFGQvPL4IuKC4wwHFglWAIQYIYoQuFCYwDGqrqFF4YYCYBKeHHwgRLlZhCLowMBKIIubWAtWF4PXBQRdEBAIBHGAoTRCIRfCDQQvBLofXB4NVBIQcDFweAOYdWp9WqwrDGA8ABoRJFAAOswFOqtUwBNFeQYVCwMqp4ABqwbDCowvCAgKOGf4N5aAIwKKIVPpwRBGQOBFw5fCIgZfGwFVlT/BqtVBQQwFUAVWFwMAlYvCL6mBqkqDgNUF4RLGAgVPFwMAklPwD0GX5gOCXoReBJgSvDIwWrAoN6py/Cp58DCYxQBVIYwHqyQBbgL6EX4qQDFwRdFLAifBaoQaEAJIuDCYWrEgoTIGAerWIJfHGRZdECZ5fD0bQBIwJgEIoynEGBJxIAAYPBwHNlUq6owBMIZ4OMKQPC0eiqsr53PX4guOwBhOComk0ejqpfB53OJZAeDU4lVvN5OQQXKBIeA0RfBvNV5wwBMI4uD1oLDFoN4AQJEMBYWl0fOL4NUL4QwCPw4BEwN4AAejvGACZaMBLoRfGAIWr1Z8HwGkFQRIBDgekwAVGFoOAB4QTDqgvBFwQDD1Wk1el1YsBv4oDAIoAB0d/GQOlAIV/CpF51ZfFAIgAEUoOiAJYmEAAoHDvOBFxWqAIWpFxwnDOJABBquJ1QwM5oDCMYYDDAIN/5+r6CABHRKOBmVewIwCYY4sC0bGB678B1ekZYIAB0ulwOlvWkIQLJCMY0yq2sr2sMJYABp96vQnB0tPz4FBBANzAAWj5pdI0dWq0zr2Ir2rMJKQCvNIp9PudIuYDBAIV0FwSMFL4d/LoMzL4WBwIwD6hhH5ujuZXBFAIDDAIOdFwPNL4hdCwBdCq15AgMrAQLDB52pRYYACBIWjvGdK4NJAQOdvIlB5oXB0QwBLoWjqsrAAaSCGANVGAJHBSQjBEAAINBewIDCLYIBCAAJfDv5dCLAIvBvIEDAAJoBwGqDQSUBY4htDBwIBBAoIwDL4WjvNdhAvCAAYFGhDGCY4IAB1QABFwQwDv4BB1V/0eA0mALINdP4IpGMYcAAIQABGAIxDAAIFBruCroAGq1eFALkDmdeD4IjDGQYCCBQYDBCgIqBAJAoBAQIDCmeBCoIDCGgIfBLooADL4YBCJAIiCAANdAIQoCAI4ABAYdWKQQDDMooEBlVPBwJfCGAwABFxABDSAZYGLo1OvFOIgQaBLYZhFAIsyFgYAEFAUqpxUBFYQ="))
|
|
@ -0,0 +1,68 @@
|
||||||
|
const bob = require("heatshrink").decompress(atob("nk8hAaXlYLWAEsqvN/0gBBql5lQ2tquj1XV5wBJ52j0hACPsdP1QsBAQQAGBIIBF51/P8OkN5R1GIxF5HLmAFgoDLPZfOpzmZ6vPFwomCPaA6DAYOjeq2A1YyCdI4HGQJQ8F1T2SJ4Oq1XW1es1mtAQOrPoPUAIh3J54ZHIAR5S62s64cBwIBGQIOqHQK4HKQYVDAAIFC1g+BHh9VHAQAFDwQDDHoJ5E54BB6AaBKQ5YGqo6MwJzGHQ4BDeIj/BR4JxDABY8BvI6OOYgaEHwZADHgQ6BZA42GAIusPJNW64eFqzJDlcrERA8BHQI2FqwaBDYYGBPI45GCoIgCLoVWQ5NWXA2rKhaiGLAwOGEAmADxJPDVA51ElQaMC4ouEWALdEHRg8Dc4woCDJo8EAIYxCHQQIFHiwaRegJ5EcYWsHgbrKbBA8GDSrNDO4wfRKgR3FDSh3CN4UrdwZbSHYZ5DHajMFHYQGCHalWO4jtQDQwABwAGCAAQfTKoK0EHahwCeARdFHakASIZWVZ4Q8CO4YgWO4QbCO6hWGEIKYZKzZ3DLog7UG4I6C1lWDSdWO4bpCO4bwUwKYEHajMDwOAlUkLojTUd4gaTZoRWC0YIB1eJLqo4EWiqRE0mjlcr1QkEeKFWOooBCHiB2CC4WA5wzB52rEQgfPHQwABAYJXOHQ2iO4XO6omFEJh1BEAgBGPJlWDIQbC0ej50qgHV1XPEwohKcwRbEvJ3EBQTrLFomkOwOjlR3C5w8GMAR8ClYuBLIgOCvN4HgIZFDQYbBlaOBR4YNCwA5B0XOpzvBHgWqTw4AFxB1EvQ6BAAI8GDZILEdgQBCqp3DPIRfIEQwABvJ1CvGkvGiwA6IAYoBCv6wCAAVOlQ6DAIWkL5ABEwN40Z1CAYJfCv7zEAJNWOYZ3KAIWq5yYHFYLOBLIrVCAIh7BOIzpECoYDDpw7BHAQDG1WqwGkAIN/CwIABLY4LDAAZ9BwAABvLCBC5IBBvOrO44BEAAmjAIPN0XOAJyHIAAgPEquBHBi4BAId+HwWiHxqJDRpYBDq2I1R3LIAQ6BAAOpPogABGgIDDOomk0nP5pIGd42Aq1ewI8CeZI6CHAJhCEAIDCdIo2B0er1esAQIZBC4Z9JlVW1leeKGp0es5+s6+s6ABB0oFBAIervWr0ulub7OqtWmdexJ4BGxB5G0V6pF6wItB0t6p9PvQABvINBudJudPzwXCHQwBDlY6BO4WIwPPPJbvDvA0BFgNzAAIDGugGCzrtHdYh1DAIOBrzxBPIgAIeIXNHgoBCGwYADzoVB0fNOpMyHQdW5+Bro7BHgQAB6jxJAAOjOYhxCAIukOoPN5ujdZFWqyyD0d6AwUrquBwAuB1I1FHgRJBMoQ9BWg1zzw0BDYI6B0R3DAAJ1BvMyp8rAAV4IQWBIodewAeCHAZ5IAAJoBAAXHHAJWDO4TtEdYQvEHgejAwIKClcqIQRdDXYbzFeoQBIGwIDDOot/VgQ6FAAIGBlgBCAAMzmZPBF4LzDACB1FAAOi1WjvFVr0zGYQ7GAAMAAYpPBwNeAIOIwOrfYOA1eA1WkAIWjAIekv4PBwCVBruBq5eBEYIABlcBF4wCEHw55CHwQABIQQBBABkzAILlCHQR1CFYavEPgsAAAIDEDQNdAwQAaHQNWEwQ0DHAh3KleBLoI7dHQKuFWQo0EAIsISoKdBHbyyHNgwADlVVpwEBDANWro7fd4Q6HO495vF5QgIYCd75eBHYUINAN5lQ3EA"));
|
||||||
|
|
||||||
|
const locale = require("locale");
|
||||||
|
|
||||||
|
const black = 0x0000;
|
||||||
|
const white = 0xFFFF;
|
||||||
|
|
||||||
|
let hour;
|
||||||
|
let minute;
|
||||||
|
let date;
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
const d = new Date();
|
||||||
|
|
||||||
|
const newHour = ('0' + d.getHours()).substr(-2);
|
||||||
|
const newMinute = ('0' + d.getMinutes()).substr(-2);
|
||||||
|
const newDate = locale.date(d).trim();
|
||||||
|
|
||||||
|
g.setFontAlign(0, 0, 0);
|
||||||
|
|
||||||
|
if (newHour !== hour) {
|
||||||
|
g.setFontVector(48);
|
||||||
|
g.setColor(black);
|
||||||
|
g.drawString(hour, 64, 92);
|
||||||
|
g.setColor(white);
|
||||||
|
g.drawString(newHour, 64, 92);
|
||||||
|
hour = newHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newMinute !== minute) {
|
||||||
|
g.setFontVector(48);
|
||||||
|
g.setColor(black);
|
||||||
|
g.drawString(minute, 172, 92);
|
||||||
|
g.setColor(white);
|
||||||
|
g.drawString(newMinute, 172, 92);
|
||||||
|
minute = newMinute;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newDate !== date) {
|
||||||
|
g.setFontVector(12);
|
||||||
|
g.setColor(black);
|
||||||
|
g.drawString(date, 120, 228);
|
||||||
|
g.setColor(0xFFFF);
|
||||||
|
g.drawString(newDate, 120, 228);
|
||||||
|
date = newDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawAll() {
|
||||||
|
hour = '';
|
||||||
|
minute = '';
|
||||||
|
date = '';
|
||||||
|
g.drawImage(bob, 0, 0, { scale: 4 });
|
||||||
|
draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('lcdPower', function(on) {
|
||||||
|
if (on) {
|
||||||
|
drawAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
setInterval(draw, 1000);
|
||||||
|
drawAll();
|
||||||
|
|
||||||
|
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
Binary file not shown.
After Width: | Height: | Size: 5.5 KiB |
|
@ -4,4 +4,9 @@
|
||||||
0.05: Fix Settings json
|
0.05: Fix Settings json
|
||||||
0.06: Remove distance setting as there's a separate app for Locale now
|
0.06: Remove distance setting as there's a separate app for Locale now
|
||||||
0.07: Added vibrate as beep workaround
|
0.07: Added vibrate as beep workaround
|
||||||
0.08: Added LCD wake-up settings
|
<<<<<<< HEAD
|
||||||
|
0.08: Added LCD wake-up settings
|
||||||
|
=======
|
||||||
|
0.08: Add support for app/widget settings
|
||||||
|
0.09: Move Welcome into App/widget settings
|
||||||
|
>>>>>>> master
|
||||||
|
|
|
@ -121,14 +121,6 @@ function showMainMenu() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Welcome App': {
|
|
||||||
value: !settings.welcomed,
|
|
||||||
format: boolFormat,
|
|
||||||
onchange: v => {
|
|
||||||
settings.welcomed = v ? undefined : true;
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'Locale': showLocaleMenu,
|
'Locale': showLocaleMenu,
|
||||||
'Select Clock': showClockMenu,
|
'Select Clock': showClockMenu,
|
||||||
'HID': {
|
'HID': {
|
||||||
|
@ -141,6 +133,7 @@ function showMainMenu() {
|
||||||
},
|
},
|
||||||
'Set Time': showSetTimeMenu,
|
'Set Time': showSetTimeMenu,
|
||||||
'LCD Wake-Up': showWakeUpMenu,
|
'LCD Wake-Up': showWakeUpMenu,
|
||||||
|
'App/widget settings': showAppSettingsMenu,
|
||||||
'Reset Settings': showResetMenu,
|
'Reset Settings': showResetMenu,
|
||||||
'Turn Off': Bangle.off,
|
'Turn Off': Bangle.off,
|
||||||
'< Back': () => { load(); }
|
'< Back': () => { load(); }
|
||||||
|
@ -408,4 +401,48 @@ function showSetTimeMenu() {
|
||||||
return E.showMenu(timemenu);
|
return E.showMenu(timemenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showAppSettingsMenu() {
|
||||||
|
let appmenu = {
|
||||||
|
'': { 'title': 'App Settings' },
|
||||||
|
'< Back': showMainMenu,
|
||||||
|
}
|
||||||
|
const apps = storage.list(/\.info$/)
|
||||||
|
.map(app => storage.readJSON(app, 1))
|
||||||
|
.filter(app => app && app.settings)
|
||||||
|
.sort((a, b) => a.sortorder - b.sortorder)
|
||||||
|
if (apps.length === 0) {
|
||||||
|
appmenu['No app has settings'] = () => { };
|
||||||
|
}
|
||||||
|
apps.forEach(function (app) {
|
||||||
|
appmenu[app.name] = () => { showAppSettings(app) };
|
||||||
|
})
|
||||||
|
E.showMenu(appmenu)
|
||||||
|
}
|
||||||
|
function showAppSettings(app) {
|
||||||
|
const showError = msg => {
|
||||||
|
E.showMessage(`${app.name}:\n${msg}!\n\nBTN1 to go back`);
|
||||||
|
setWatch(showAppSettingsMenu, BTN1, { repeat: false });
|
||||||
|
}
|
||||||
|
let appSettings = storage.read(app.settings);
|
||||||
|
if (!appSettings) {
|
||||||
|
return showError('Missing settings');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
appSettings = eval(appSettings);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`${app.name} settings error:`, e)
|
||||||
|
return showError('Error in settings');
|
||||||
|
}
|
||||||
|
if (typeof appSettings !== "function") {
|
||||||
|
return showError('Invalid settings');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// pass showAppSettingsMenu as "back" argument
|
||||||
|
appSettings(showAppSettingsMenu);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`${app.name} settings error:`, e)
|
||||||
|
return showError('Error in settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Add swipe support and doucle tap to run application
|
0.02: Add swipe support and doucle tap to run application
|
||||||
|
0.03: Close launcher when lcd turn off
|
|
@ -127,4 +127,8 @@ Bangle.on('touch', function(button){
|
||||||
Bangle.on('swipe', dir => {
|
Bangle.on('swipe', dir => {
|
||||||
if(dir == 1) prev();
|
if(dir == 1) prev();
|
||||||
else next();
|
else next();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.on('lcdPower', function(on) {
|
||||||
|
if(!on) return load();
|
||||||
});
|
});
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Animate balloon intro
|
0.02: Animate balloon intro
|
||||||
0.03: BTN3 now won't restart when at the end
|
0.03: BTN3 now won't restart when at the end
|
||||||
0.04: Fix regression after tweaks to Storage.readJSON
|
0.04: Fix regression after tweaks to Storage.readJSON
|
||||||
|
0.05: Move configuration into App/widget settings
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
// The welcome app is special, and gets to use global settings
|
||||||
|
(function(back) {
|
||||||
|
let settings = require('Storage').readJSON('setting.json', 1) || {}
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'Welcome App' },
|
||||||
|
'Run again': {
|
||||||
|
value: !settings.welcomed,
|
||||||
|
format: v => v ? 'Yes' : 'No',
|
||||||
|
onchange: v => {
|
||||||
|
settings.welcomed = v ? undefined : true
|
||||||
|
require('Storage').write('setting.json', settings)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'< Back': back,
|
||||||
|
})
|
||||||
|
})
|
|
@ -3,3 +3,4 @@
|
||||||
0.04: Ensure redrawing works with variable size widget system
|
0.04: Ensure redrawing works with variable size widget system
|
||||||
0.05: Change color depending on battery level, cloned from widbat
|
0.05: Change color depending on battery level, cloned from widbat
|
||||||
0.06: Show battery percentage as text
|
0.06: Show battery percentage as text
|
||||||
|
0.07: Add settings: percentage/color/charger icon
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
// This file should contain exactly one function, which shows the app's settings
|
||||||
|
/**
|
||||||
|
* @param {function} back Use back() to return to settings menu
|
||||||
|
*/
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = 'widbatpc.settings.json'
|
||||||
|
const COLORS = ['By Level', 'Green', 'Monochrome']
|
||||||
|
|
||||||
|
// initialize with default settings...
|
||||||
|
let s = {
|
||||||
|
'color': COLORS[0],
|
||||||
|
'percentage': true,
|
||||||
|
'charger': true,
|
||||||
|
}
|
||||||
|
// ...and overwrite them with any saved values
|
||||||
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
const storage = require('Storage')
|
||||||
|
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||||
|
for (const key in saved) {
|
||||||
|
s[key] = saved[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||||
|
function save(key) {
|
||||||
|
return function (value) {
|
||||||
|
s[key] = value
|
||||||
|
storage.write(SETTINGS_FILE, s)
|
||||||
|
WIDGETS["batpc"].reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onOffFormat = b => (b ? 'on' : 'off')
|
||||||
|
const menu = {
|
||||||
|
'': { 'title': 'Battery Widget' },
|
||||||
|
'< Back': back,
|
||||||
|
'Percentage': {
|
||||||
|
value: s.percentage,
|
||||||
|
format: onOffFormat,
|
||||||
|
onchange: save('percentage'),
|
||||||
|
},
|
||||||
|
'Charging Icon': {
|
||||||
|
value: s.charger,
|
||||||
|
format: onOffFormat,
|
||||||
|
onchange: save('charger'),
|
||||||
|
},
|
||||||
|
'Color': {
|
||||||
|
format: () => s.color,
|
||||||
|
onchange: function () {
|
||||||
|
// cycles through options
|
||||||
|
const oldIndex = COLORS.indexOf(s.color)
|
||||||
|
const newIndex = (oldIndex + 1) % COLORS.length
|
||||||
|
s.color = COLORS[newIndex]
|
||||||
|
save('color')(s.color)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
E.showMenu(menu)
|
||||||
|
})
|
|
@ -1,20 +1,62 @@
|
||||||
(function(){
|
(function(){
|
||||||
|
const DEFAULTS = {
|
||||||
|
'color': 'By Level',
|
||||||
|
'percentage': true,
|
||||||
|
'charger': true,
|
||||||
|
}
|
||||||
|
const COLORS = {
|
||||||
|
'white': -1,
|
||||||
|
'charging': 0x07E0, // "Green"
|
||||||
|
'high': 0x05E0, // slightly darker green
|
||||||
|
'ok': 0xFD20, // "Orange"
|
||||||
|
'low':0xF800, // "Red"
|
||||||
|
}
|
||||||
|
const SETTINGS_FILE = 'widbatpc.settings.json'
|
||||||
|
|
||||||
|
let settings
|
||||||
|
function loadSettings() {
|
||||||
|
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}
|
||||||
|
}
|
||||||
|
function setting(key) {
|
||||||
|
if (!settings) { loadSettings() }
|
||||||
|
return (key in settings) ? settings[key] : DEFAULTS[key]
|
||||||
|
}
|
||||||
|
|
||||||
const levelColor = (l) => {
|
const levelColor = (l) => {
|
||||||
if (Bangle.isCharging()) return 0x07E0; // "Green"
|
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
|
||||||
if (l >= 50) return 0x05E0; // slightly darker green
|
const green = setting('percentage') ? COLORS.high : COLORS.charging
|
||||||
if (l >= 15) return 0xFD20; // "Orange"
|
switch (setting('color')) {
|
||||||
return 0xF800; // "Red"
|
case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
|
||||||
|
case 'Green': return green;
|
||||||
|
case 'By Level': // fall through
|
||||||
|
default:
|
||||||
|
if (setting('charger')) {
|
||||||
|
// charger icon -> always make percentage readable
|
||||||
|
if (Bangle.isCharging() || l >= 50) return green;
|
||||||
|
} else {
|
||||||
|
// no icon -> brightest green to indicate charging, even when showing percentage
|
||||||
|
if (Bangle.isCharging()) return COLORS.charging;
|
||||||
|
if (l >= 50) return COLORS.high;
|
||||||
|
}
|
||||||
|
if (l >= 15) return COLORS.ok;
|
||||||
|
return COLORS.low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const chargerColor = () => {
|
||||||
|
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWidth() {
|
function setWidth() {
|
||||||
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
|
WIDGETS["batpc"].width = 40;
|
||||||
|
if (Bangle.isCharging() && setting('charger')) {
|
||||||
|
WIDGETS["batpc"].width += 16;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function draw() {
|
function draw() {
|
||||||
var s = 39;
|
var s = 39;
|
||||||
var x = this.x, y = this.y;
|
var x = this.x, y = this.y;
|
||||||
const l = E.getBattery(), c = levelColor(l);
|
if (Bangle.isCharging() && setting('charger')) {
|
||||||
if (Bangle.isCharging()) {
|
g.setColor(chargerColor()).drawImage(atob(
|
||||||
g.setColor(c).drawImage(atob(
|
|
||||||
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
||||||
x+=16;
|
x+=16;
|
||||||
}
|
}
|
||||||
|
@ -22,8 +64,13 @@ function draw() {
|
||||||
g.fillRect(x,y+2,x+s-4,y+21);
|
g.fillRect(x,y+2,x+s-4,y+21);
|
||||||
g.clearRect(x+2,y+4,x+s-6,y+19);
|
g.clearRect(x+2,y+4,x+s-6,y+19);
|
||||||
g.fillRect(x+s-3,y+10,x+s,y+14);
|
g.fillRect(x+s-3,y+10,x+s,y+14);
|
||||||
|
const l = E.getBattery(),
|
||||||
|
c = levelColor(l);
|
||||||
g.setColor(c).fillRect(x+4,y+6,x+4+l*(s-12)/100,y+17);
|
g.setColor(c).fillRect(x+4,y+6,x+4+l*(s-12)/100,y+17);
|
||||||
g.setColor(-1);
|
g.setColor(-1);
|
||||||
|
if (!setting('percentage')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
g.setFontAlign(-1,-1);
|
g.setFontAlign(-1,-1);
|
||||||
if (l >= 100) {
|
if (l >= 100) {
|
||||||
g.setFont('4x6', 2);
|
g.setFont('4x6', 2);
|
||||||
|
@ -34,6 +81,16 @@ function draw() {
|
||||||
g.drawString(l, x + 6, y + 4);
|
g.drawString(l, x + 6, y + 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// reload widget, e.g. when settings have changed
|
||||||
|
function reload() {
|
||||||
|
loadSettings()
|
||||||
|
// need to redraw all widgets, because changing the "charger" setting
|
||||||
|
// can affect the width and mess with the whole widget layout
|
||||||
|
setWidth()
|
||||||
|
g.clear();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
}
|
||||||
|
|
||||||
Bangle.on('charging',function(charging) {
|
Bangle.on('charging',function(charging) {
|
||||||
if(charging) Bangle.buzz();
|
if(charging) Bangle.buzz();
|
||||||
setWidth();
|
setWidth();
|
||||||
|
@ -43,7 +100,7 @@ Bangle.on('charging',function(charging) {
|
||||||
var batteryInterval;
|
var batteryInterval;
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
WIDGETS["bat"].draw();
|
WIDGETS["batpc"].draw();
|
||||||
// refresh once a minute if LCD on
|
// refresh once a minute if LCD on
|
||||||
if (!batteryInterval)
|
if (!batteryInterval)
|
||||||
batteryInterval = setInterval(draw, 60000);
|
batteryInterval = setInterval(draw, 60000);
|
||||||
|
@ -54,6 +111,6 @@ Bangle.on('lcdPower', function(on) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
WIDGETS["bat"]={area:"tr",width:40,draw:draw};
|
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
|
||||||
setWidth();
|
setWidth();
|
||||||
})()
|
})()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
0.01: Only tested on the emulator.
|
||||||
|
0.02: Adapted to new App code layout
|
||||||
|
0.03: Optimized rendering for the background
|
||||||
|
0.04: Only buzz on high confidence (>85%)
|
||||||
|
0.05: Improved buzz timing and rendering
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AH4AVgnd5tABI3c7oJGAAUs5gAC4gJDpgJD4QWGhoMDAAPQBJYADBgoABBJYAChgJD5oDC4AJEAAfAC4fcBIfUDYYJEEogWCgQJEoYSHAAsgIw3MmYqIn89JAoXFn5DH4f/+YXFWQnE/4GEAAXP///ZgooE4X/ngvMPAQXEBoIXHHIJfDC4ss5nf+f9OosjFwgXF5oTBp8z+gMBMQPTn5dBNIgXCAwPDEQM/mQmCJQNP/8zDIJRDO4SnB6fz7k/poXEJwIJBmanGhvMl//loxC7nE/jUCon/6gzBC4PQC4MDKIJFDn9M4YXB5nUKYbACmAXBgE/+YMBOoMvngXDJIKDB6YvBOwRgDaoINB788p5wDn7HELwQABghWCBoPD/s/YwNN5i+Bc4dAC4bBCC4fyPIPU+Z0BDAZGEJAffYgPC+ZxBG4KkB6f/C4JGEAAQsBcIX/+QEBCgP9A4IXBCwwwB5pxDPYJoDcgIuIGASJH5rvBAwIWIeYQABl5jBAAXDIwLrCABCcC76gDAoP0RgwAFYYJ7DJAcsFxYABaYJ7DAAXECxhJEAAgWOPQgACIpoADUwb1BCyBJERZgYKkAXUglACygA/AH4AFA=="))
|
|
@ -0,0 +1,325 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
const Setter = {
|
||||||
|
NONE: "none",
|
||||||
|
UPPER: 'upper',
|
||||||
|
LOWER: 'lower'
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortBuzzTimeInMs = 80;
|
||||||
|
const longBuzzTimeInMs = 400;
|
||||||
|
|
||||||
|
let upperLimit = 130;
|
||||||
|
let upperLimitChanged = true;
|
||||||
|
|
||||||
|
let lowerLimit = 100;
|
||||||
|
let lowerLimitChanged = true;
|
||||||
|
|
||||||
|
let limitSetter = Setter.NONE;
|
||||||
|
|
||||||
|
let currentHeartRate = 0;
|
||||||
|
let hrConfidence = -1;
|
||||||
|
let hrChanged = true;
|
||||||
|
let confidenceChanged = true;
|
||||||
|
|
||||||
|
let setterHighlightTimeout;
|
||||||
|
|
||||||
|
function renderUpperLimitBackground() {
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.fillRect(125,40, 210, 70);
|
||||||
|
g.fillRect(180,70, 210, 200);
|
||||||
|
|
||||||
|
//Round top left corner
|
||||||
|
g.fillEllipse(115,40,135,70);
|
||||||
|
|
||||||
|
//Round top right corner
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(205,40, 210, 45);
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.fillEllipse(190,40,210,50);
|
||||||
|
|
||||||
|
//Round inner corner
|
||||||
|
g.fillRect(174,71, 179, 76);
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillEllipse(160,71,179,82);
|
||||||
|
|
||||||
|
//Round bottom
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.fillEllipse(180,190, 210, 210);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLowerLimitBackground() {
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillRect(10, 180, 100, 210);
|
||||||
|
g.fillRect(10, 50, 40, 180);
|
||||||
|
|
||||||
|
//Rounded top
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillEllipse(10,40, 40, 60);
|
||||||
|
|
||||||
|
//Round bottom right corner
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillEllipse(90,180,110,210);
|
||||||
|
|
||||||
|
//Round inner corner
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillRect(40,175,45,180);
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillEllipse(41,170,60,179);
|
||||||
|
|
||||||
|
//Round bottom left corner
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(10,205, 15, 210);
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillEllipse(10,200,30,210);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawTrainingHeartRate() {
|
||||||
|
//Only redraw if the display is on
|
||||||
|
if (Bangle.isLCDOn()) {
|
||||||
|
renderUpperLimit();
|
||||||
|
|
||||||
|
renderCurrentHeartRate();
|
||||||
|
|
||||||
|
renderLowerLimit();
|
||||||
|
|
||||||
|
renderConfidenceBars();
|
||||||
|
}
|
||||||
|
|
||||||
|
buzz();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUpperLimit() {
|
||||||
|
if(!upperLimitChanged) { return; }
|
||||||
|
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.fillRect(125,40, 210, 70);
|
||||||
|
|
||||||
|
if(limitSetter === Setter.UPPER){
|
||||||
|
g.setColor(255,255, 0);
|
||||||
|
} else {
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
}
|
||||||
|
g.setFontVector(13);
|
||||||
|
g.drawString("Upper : " + upperLimit, 130,50);
|
||||||
|
|
||||||
|
upperLimitChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCurrentHeartRate() {
|
||||||
|
if(!hrChanged) { return; }
|
||||||
|
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
g.fillRect(55, 110, 165, 150);
|
||||||
|
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.setFontVector(24);
|
||||||
|
g.setFontAlign(1, -1, 0);
|
||||||
|
g.drawString(currentHeartRate, 130, 117);
|
||||||
|
|
||||||
|
//Reset alignment to defaults
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
|
||||||
|
hrChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLowerLimit() {
|
||||||
|
if(!lowerLimitChanged) { return; }
|
||||||
|
|
||||||
|
g.setColor(0,0,1);
|
||||||
|
g.fillRect(10, 180, 100, 210);
|
||||||
|
|
||||||
|
if(limitSetter === Setter.LOWER){
|
||||||
|
g.setColor(255,255, 0);
|
||||||
|
} else {
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
}
|
||||||
|
g.setFontVector(13);
|
||||||
|
g.drawString("Lower : " + lowerLimit, 20,190);
|
||||||
|
|
||||||
|
lowerLimitChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderConfidenceBars(){
|
||||||
|
if(!confidenceChanged) { return; }
|
||||||
|
|
||||||
|
if(hrConfidence >= 85){
|
||||||
|
g.setColor(0, 255, 0);
|
||||||
|
} else if (hrConfidence >= 50) {
|
||||||
|
g.setColor(255, 255, 0);
|
||||||
|
} else if(hrConfidence >= 0){
|
||||||
|
g.setColor(255, 0, 0);
|
||||||
|
} else {
|
||||||
|
g.setColor(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
g.fillRect(45, 110, 55, 150);
|
||||||
|
g.fillRect(165, 110, 175, 150);
|
||||||
|
|
||||||
|
confidenceChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderButtonIcons() {
|
||||||
|
g.setColor(255,255,255);
|
||||||
|
g.setFontVector(14);
|
||||||
|
|
||||||
|
//+ for Btn1
|
||||||
|
g.drawString("+", 222,50);
|
||||||
|
|
||||||
|
//Home for Btn2
|
||||||
|
g.drawLine(220, 118, 227, 110);
|
||||||
|
g.drawLine(227, 110, 234, 118);
|
||||||
|
|
||||||
|
g.drawPoly([222,117,222,125,232,125,232,117], false);
|
||||||
|
g.drawRect(226,120,229,125);
|
||||||
|
|
||||||
|
//- for Btn3
|
||||||
|
g.drawString("-", 222,165);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buzz()
|
||||||
|
{
|
||||||
|
// Do not buzz if not confident
|
||||||
|
if(hrConfidence < 85) { return; }
|
||||||
|
|
||||||
|
if(currentHeartRate > upperLimit)
|
||||||
|
{
|
||||||
|
Bangle.buzz(shortBuzzTimeInMs);
|
||||||
|
setTimeout(() => { Bangle.buzz(shortBuzzTimeInMs); }, shortBuzzTimeInMs * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentHeartRate < lowerLimit)
|
||||||
|
{
|
||||||
|
Bangle.buzz(longBuzzTimeInMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHrm(hrm){
|
||||||
|
if(currentHeartRate !== hrm.bpm){
|
||||||
|
currentHeartRate = hrm.bpm;
|
||||||
|
hrChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hrConfidence !== hrm.confidence) {
|
||||||
|
hrConfidence = hrm.confidence;
|
||||||
|
confidenceChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLimitSetterToLower() {
|
||||||
|
resetHighlightTimeout();
|
||||||
|
|
||||||
|
limitSetter = Setter.LOWER;
|
||||||
|
console.log("Limit setter is lower");
|
||||||
|
|
||||||
|
upperLimitChanged = true;
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
|
||||||
|
renderUpperLimit();
|
||||||
|
renderLowerLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLimitSetterToUpper() {
|
||||||
|
resetHighlightTimeout();
|
||||||
|
|
||||||
|
limitSetter = Setter.UPPER;
|
||||||
|
console.log("Limit setter is upper");
|
||||||
|
|
||||||
|
upperLimitChanged = true;
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
|
||||||
|
renderLowerLimit();
|
||||||
|
renderUpperLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLimitSetterToNone() {
|
||||||
|
limitSetter = Setter.NONE;
|
||||||
|
console.log("Limit setter is none");
|
||||||
|
|
||||||
|
upperLimitChanged = true;
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
|
||||||
|
renderLowerLimit();
|
||||||
|
renderUpperLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementLimit(){
|
||||||
|
resetHighlightTimeout();
|
||||||
|
|
||||||
|
if (limitSetter === Setter.UPPER) {
|
||||||
|
upperLimit++;
|
||||||
|
renderUpperLimit();
|
||||||
|
console.log("Upper limit: " + upperLimit);
|
||||||
|
upperLimitChanged = true;
|
||||||
|
} else if(limitSetter === Setter.LOWER) {
|
||||||
|
lowerLimit++;
|
||||||
|
renderLowerLimit();
|
||||||
|
console.log("Lower limit: " + lowerLimit);
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrementLimit(){
|
||||||
|
resetHighlightTimeout();
|
||||||
|
|
||||||
|
if (limitSetter === Setter.UPPER) {
|
||||||
|
upperLimit--;
|
||||||
|
renderUpperLimit();
|
||||||
|
console.log("Upper limit: " + upperLimit);
|
||||||
|
upperLimitChanged = true;
|
||||||
|
} else if(limitSetter === Setter.LOWER) {
|
||||||
|
lowerLimit--;
|
||||||
|
renderLowerLimit();
|
||||||
|
console.log("Lower limit: " + lowerLimit);
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHighlightTimeout() {
|
||||||
|
if (setterHighlightTimeout) {
|
||||||
|
clearTimeout(setterHighlightTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
setterHighlightTimeout = setTimeout(setLimitSetterToNone, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
function switchOffApp(){
|
||||||
|
Bangle.setHRMPower(0);
|
||||||
|
Bangle.showLauncher();
|
||||||
|
}
|
||||||
|
|
||||||
|
// special function to handle display switch on
|
||||||
|
Bangle.on('lcdPower', (on) => {
|
||||||
|
g.clear();
|
||||||
|
if (on) {
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
renderButtonIcons();
|
||||||
|
// call your app function here
|
||||||
|
renderLowerLimitBackground();
|
||||||
|
renderUpperLimitBackground();
|
||||||
|
lowerLimitChanged = true;
|
||||||
|
upperLimitChanged = true;
|
||||||
|
drawTrainingHeartRate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Bangle.setHRMPower(1);
|
||||||
|
Bangle.on('HRM', onHrm);
|
||||||
|
|
||||||
|
setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
setWatch(decrementLimit, BTN3, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
setWatch(setLimitSetterToLower, BTN4, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
setWatch(setLimitSetterToUpper, BTN5, { edge: "rising", debounce: 50, repeat: true });
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
//drawTrainingHeartRate();
|
||||||
|
|
||||||
|
renderButtonIcons();
|
||||||
|
renderLowerLimitBackground();
|
||||||
|
renderUpperLimitBackground();
|
||||||
|
|
||||||
|
// refesh every sec
|
||||||
|
setInterval(drawTrainingHeartRate, 1000);
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -60,6 +60,8 @@ var AppInfo = {
|
||||||
if (app.type && app.type!="app") json.type = app.type;
|
if (app.type && app.type!="app") json.type = app.type;
|
||||||
if (fileContents.find(f=>f.name==app.id+".app.js"))
|
if (fileContents.find(f=>f.name==app.id+".app.js"))
|
||||||
json.src = app.id+".app.js";
|
json.src = app.id+".app.js";
|
||||||
|
if (fileContents.find(f=>f.name==app.id+".settings.js"))
|
||||||
|
json.settings = app.id+".settings.js";
|
||||||
if (fileContents.find(f=>f.name==app.id+".img"))
|
if (fileContents.find(f=>f.name==app.id+".img"))
|
||||||
json.icon = app.id+".img";
|
json.icon = app.id+".img";
|
||||||
if (app.sortorder) json.sortorder = app.sortorder;
|
if (app.sortorder) json.sortorder = app.sortorder;
|
||||||
|
|
Loading…
Reference in New Issue