msdeibel 2020-04-04 16:37:57 +02:00
commit 1f8c0ef685
17 changed files with 413 additions and 206 deletions

View File

@ -6,6 +6,10 @@ Bangle.js App Loader (and 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/)
**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?
* 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.app.js` is JS code for applications
* `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.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.
### 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
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"

View File

@ -78,13 +78,14 @@
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.04",
"version":"0.05",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
"storage": [
{"name":"welcome.js","url":"welcome.js"},
{"name":"welcome.app.js","url":"app.js"},
{"name":"welcome.settings.js","url":"settings.js"},
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
]
},
@ -117,7 +118,7 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.08",
"version":"0.09",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
@ -336,13 +337,16 @@
},
{ "id": "widbatpc",
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"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",
"tags": "widget,battery",
"type":"widget",
"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",
@ -906,7 +910,7 @@
{ "id": "marioclock",
"name": "Mario Clock",
"icon": "marioclock.png",
"version":"0.05",
"version":"0.06",
"description": "Animated Mario clock, jumps to change the time!",
"tags": "clock,mario,retro",
"type": "clock",
@ -1026,7 +1030,7 @@
"name": "Touch Launcher",
"shortName":"Menu",
"icon": "app.png",
"version":"0.02",
"version":"0.03",
"description": "Touch enable left to right launcher.",
"tags": "tool,system,launcher",
"type":"launch",
@ -1072,5 +1076,18 @@
"storage": [
{"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}
]
}
]

View File

@ -3,3 +3,4 @@
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.05: use 12/24 hour clock from settings
0.06: Performance refactor, and enhanced graphics!

View File

@ -3,6 +3,7 @@
+ 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.
+ Online Image convertor: https://www.espruino.com/Image+Converter
+ Images must be converted 1Bit White/Black !!! Not Black/White
**********************************/
const locale = require("locale");
@ -16,110 +17,32 @@ let W, H;
let intervalRef, displayTimeoutRef = null;
// Space to draw watch widgets (e.g battery, bluetooth status)
const WIDGETS_GUTTER = 10;
// Colours
const LIGHTEST = "#effedd";
const LIGHT = "#add795";
const DARK = "#588d77";
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 = {
frameIdx: 0,
frames: [
marioRunningImage1,
marioRunningImage2
],
negFrames: [
marioRunningImage1Neg,
marioRunningImage2Neg
],
x: 35,
y: 55,
jumpCounter: 0,
jumpIncrement: Math.PI / 10,
jumpIncrement: Math.PI / 6,
isJumping: false
};
const STATIC_TILES = {
"_": {img: floor, x: 16 * 8, y: 75},
"X": {img: sky, x: 0, y: 10},
"#": {img: brick, x: 0, y: 0},
const coinSprite = {
frameIdx: 0,
x: 34,
y: 18,
isAnimating: false,
yDefault: 18,
};
const TILES = {
"T": {img: pipe, x: 16 * 8, y: 69},
"^": {img: pyramid, x: 16 * 8, y: 55},
"*": {img: flower, x: 16 * 8, y: 68},
"V": {img: pipePlant, x: 16 * 8, y: 60}
const pyramidSprite = {
x: 90,
height: 34,
};
const ONE_SECOND = 1000;
@ -136,95 +59,133 @@ function incrementTimer() {
}
}
function drawTile(sprite) {
g.drawImage(sprite.img, sprite.x, sprite.y);
}
function drawBackground() {
// Clear screen
g.setColor(LIGHTEST);
g.fillRect(0, 10, W, H);
// draw floor
g.setColor(DARK);
for (var x = 0; x < 16; x++) {
var floorSprite = Object.assign({}, STATIC_TILES._, {x: x * 8});
drawTile(floorSprite);
}
// Date bar
g.setColor(DARKEST);
g.fillRect(0, 0, W, 9);
// draw sky
var skySprite = STATIC_TILES.X;
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() {
// new random sprite
const spriteKeys = Object.keys(TILES);
const key = spriteKeys[Math.floor(Math.random() * spriteKeys.length)];
let newSprite = Object.assign({}, TILES[key]);
function drawFloor() {
const fImg = require("heatshrink").decompress(atob("ikDxH+rgATCoIBQAQYDP")); // Floor image
for (let x = 0; x < 4; x++) {
g.drawImage(fImg, x * 20, g.getHeight() - 5);
}
}
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
let firstBackgroundSprite = backgroundArr[0];
if (firstBackgroundSprite) {
if (firstBackgroundSprite.x < -20) backgroundArr.splice(0, 1);
if (firstBackgroundSprite.x < -15) backgroundArr.splice(0, 1);
}
// set background sprite if array empty
var lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
let lastBackgroundSprite = backgroundArr[backgroundArr.length - 1];
if (!lastBackgroundSprite) {
lastBackgroundSprite = newSprite;
backgroundArr.push(lastBackgroundSprite);
}
// add random sprites
if (backgroundArr.length < 6 && lastBackgroundSprite.x < (16 * 7)) {
var randIdx = Math.floor(Math.random() * 25);
if (randIdx < spriteKeys.length - 1) {
if (backgroundArr.length < 2 && lastBackgroundSprite.x < (16 * 7)) {
const randIdx = Math.floor(Math.random() * 25);
if (randIdx < 2) {
backgroundArr.push(newSprite);
}
}
for (x = 0; x < backgroundArr.length; 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;
drawTile(scenerySprite);
drawTreesFrame(scenerySprite.x, scenerySprite.y);
}
}
function drawMario() {
// clear old mario frame
g.setColor(LIGHTEST);
g.drawImage(
marioSprite.negFrames[marioSprite.frameIdx],
marioSprite.x,
marioSprite.y
);
g.drawImage(
marioSprite.frames[marioSprite.frameIdx],
marioSprite.x,
marioSprite.y
);
function drawCoinFrame(x, y) {
const cImg = require("heatshrink").decompress(atob("hkPxH+AAcHAAQIEBIXWAAQNEBIWHAAdcBgQLBA4IODBYQKEBAQMDBelcBaJUBM4QRBNYx1EBQILDR4QHBBISdIBIoA==")); // Coin image
g.drawImage(cImg, x, 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
const t = new Date(),
seconds = t.getSeconds(),
milliseconds = t.getMilliseconds();
const seconds = date.getSeconds(),
milliseconds = date.getMilliseconds();
if (seconds == 59 && milliseconds > 800 && !marioSprite.isJumping) {
marioSprite.isJumping = true;
}
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;
if (parseInt(marioSprite.jumpCounter) === 2 && !coinSprite.isAnimating) {
coinSprite.isAnimating = true;
}
if (marioSprite.jumpCounter.toFixed(1) >= 4) {
marioSprite.jumpCounter = 0;
marioSprite.isJumping = false;
@ -232,51 +193,28 @@ function drawMario() {
}
// calculate animation timing
if (timer % 100 === 0) {
if (timer % 50 === 0) {
// shift to next frame
marioSprite.frameIdx ^= 1;
}
// colour in mario
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
);
drawMarioFrame(marioSprite.frameIdx, marioSprite.x, marioSprite.y);
}
function drawBrick(x, y) {
const brickSprite = Object.assign({}, STATIC_TILES['#'], {x: x, y: 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 drawBrickFrame(x, y) {
const brk = require("heatshrink").decompress(atob("ikQxH+/0HACASB6wAQCoPWw4AOrgT/Cf4T/Cb1cAB8H/wVBAB/+A"));
g.drawImage(brk, x, y);
}
function drawTime() {
function drawTime(date) {
// draw hour brick
drawBrick(20, 25);
drawBrickFrame(20, 25);
// draw minute brick
drawBrick(42, 25);
drawBrickFrame(42, 25);
const t = new Date();
const h = t.getHours();
const h = date.getHours();
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.setColor(DARKEST);
@ -284,25 +222,30 @@ function drawTime() {
g.drawString(mins, 47, 29);
}
function drawDate() {
function drawDate(date) {
g.setFont("6x8");
g.setColor(LIGHTEST);
let d = new Date();
let dateStr = locale.date(d, true);
dateStr = dateStr.replace(d.getFullYear(), "").trim().replace(/\/$/i,"");
dateStr = locale.dow(d, true) + " " + dateStr;
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 0, true);
let dateStr = locale.date(date, true);
dateStr = dateStr.replace(date.getFullYear(), "").trim().replace(/\/$/i,"");
dateStr = locale.dow(date, true) + " " + dateStr;
g.drawString(dateStr, (W - g.stringWidth(dateStr))/2, 1);
}
function redraw() {
const date = new Date();
// Update timers
incrementTimer();
// Draw frame
drawScenery();
drawTime();
drawDate();
drawMario();
drawBackground();
drawFloor();
drawPyramid();
drawTrees();
drawTime(date);
drawDate(date);
drawMario(date);
drawCoin();
// Render new frame
g.flip();
@ -335,7 +278,6 @@ function startTimers(){
resetDisplayTimeout();
drawBackground();
redraw();
}
@ -345,7 +287,6 @@ function init() {
// Initialise display
Bangle.setLCDMode("80x80");
g.clear();
// Store screen dimensions
W = g.getWidth();
@ -381,4 +322,4 @@ function init() {
}
// Initialise!
init();
init();

1
apps/minionclk/ChangeLog Executable file
View File

@ -0,0 +1 @@
0.01: First release

1
apps/minionclk/app-icon.js Executable file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwhAaXlZEolVVvOj0mq1XOv9/qtWFb8rquj1XV5wBDAA2jvMqNLMqwAsCABBhBAIujqpbWvIsCLowBHMg0qFyVVFQJNDK4YoEAYxjFvIuQwHP6ur5+rDgfU5wBDMI2qCYOsC4XV0bFOwIWBAAeBAIOrMYYsF54sCCIWswQDB52AGBcrFIOtF4gAEMoJfDAAOrCYQuDAIowKFwQWIBIeA52pGQPWLYgAFA4YDBGA4uDEwQYFGQvPL4IuKC4wwHFglWAIQYIYoQuFCYwDGqrqFF4YYCYBKeHHwgRLlZhCLowMBKIIubWAtWF4PXBQRdEBAIBHGAoTRCIRfCDQQvBLofXB4NVBIQcDFweAOYdWp9WqwrDGA8ABoRJFAAOswFOqtUwBNFeQYVCwMqp4ABqwbDCowvCAgKOGf4N5aAIwKKIVPpwRBGQOBFw5fCIgZfGwFVlT/BqtVBQQwFUAVWFwMAlYvCL6mBqkqDgNUF4RLGAgVPFwMAklPwD0GX5gOCXoReBJgSvDIwWrAoN6py/Cp58DCYxQBVIYwHqyQBbgL6EX4qQDFwRdFLAifBaoQaEAJIuDCYWrEgoTIGAerWIJfHGRZdECZ5fD0bQBIwJgEIoynEGBJxIAAYPBwHNlUq6owBMIZ4OMKQPC0eiqsr53PX4guOwBhOComk0ejqpfB53OJZAeDU4lVvN5OQQXKBIeA0RfBvNV5wwBMI4uD1oLDFoN4AQJEMBYWl0fOL4NUL4QwCPw4BEwN4AAejvGACZaMBLoRfGAIWr1Z8HwGkFQRIBDgekwAVGFoOAB4QTDqgvBFwQDD1Wk1el1YsBv4oDAIoAB0d/GQOlAIV/CpF51ZfFAIgAEUoOiAJYmEAAoHDvOBFxWqAIWpFxwnDOJABBquJ1QwM5oDCMYYDDAIN/5+r6CABHRKOBmVewIwCYY4sC0bGB678B1ekZYIAB0ulwOlvWkIQLJCMY0yq2sr2sMJYABp96vQnB0tPz4FBBANzAAWj5pdI0dWq0zr2Ir2rMJKQCvNIp9PudIuYDBAIV0FwSMFL4d/LoMzL4WBwIwD6hhH5ujuZXBFAIDDAIOdFwPNL4hdCwBdCq15AgMrAQLDB52pRYYACBIWjvGdK4NJAQOdvIlB5oXB0QwBLoWjqsrAAaSCGANVGAJHBSQjBEAAINBewIDCLYIBCAAJfDv5dCLAIvBvIEDAAJoBwGqDQSUBY4htDBwIBBAoIwDL4WjvNdhAvCAAYFGhDGCY4IAB1QABFwQwDv4BB1V/0eA0mALINdP4IpGMYcAAIQABGAIxDAAIFBruCroAGq1eFALkDmdeD4IjDGQYCCBQYDBCgIqBAJAoBAQIDCmeBCoIDCGgIfBLooADL4YBCJAIiCAANdAIQoCAI4ABAYdWKQQDDMooEBlVPBwJfCGAwABFxABDSAZYGLo1OvFOIgQaBLYZhFAIsyFgYAEFAUqpxUBFYQ="))

68
apps/minionclk/app.js Executable file
View File

@ -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"});

BIN
apps/minionclk/minionclk.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -5,3 +5,4 @@
0.06: Remove distance setting as there's a separate app for Locale now
0.07: Added vibrate as beep workaround
0.08: Add support for app/widget settings
0.09: Move Welcome into App/widget settings

View File

@ -95,14 +95,6 @@ function showMainMenu() {
}
}
},
'Welcome App': {
value: !settings.welcomed,
format: boolFormat,
onchange: v => {
settings.welcomed = v?undefined:true;
updateSettings();
}
},
'Locale': showLocaleMenu,
'Select Clock': showClockMenu,
'HID': {

View File

@ -1,2 +1,3 @@
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

View File

@ -127,4 +127,8 @@ Bangle.on('touch', function(button){
Bangle.on('swipe', dir => {
if(dir == 1) prev();
else next();
});
Bangle.on('lcdPower', function(on) {
if(!on) return load();
});

View File

@ -2,3 +2,4 @@
0.02: Animate balloon intro
0.03: BTN3 now won't restart when at the end
0.04: Fix regression after tweaks to Storage.readJSON
0.05: Move configuration into App/widget settings

16
apps/welcome/settings.js Normal file
View File

@ -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,
})
})

View File

@ -3,3 +3,4 @@
0.04: Ensure redrawing works with variable size widget system
0.05: Change color depending on battery level, cloned from widbat
0.06: Show battery percentage as text
0.07: Add settings: percentage/color/charger icon

58
apps/widbatpc/settings.js Normal file
View File

@ -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)
})

View File

@ -1,20 +1,62 @@
(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) => {
if (Bangle.isCharging()) return 0x07E0; // "Green"
if (l >= 50) return 0x05E0; // slightly darker green
if (l >= 15) return 0xFD20; // "Orange"
return 0xF800; // "Red"
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
const green = setting('percentage') ? COLORS.high : COLORS.charging
switch (setting('color')) {
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() {
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
WIDGETS["batpc"].width = 40;
if (Bangle.isCharging() && setting('charger')) {
WIDGETS["batpc"].width += 16;
}
}
function draw() {
var s = 39;
var x = this.x, y = this.y;
const l = E.getBattery(), c = levelColor(l);
if (Bangle.isCharging()) {
g.setColor(c).drawImage(atob(
if (Bangle.isCharging() && setting('charger')) {
g.setColor(chargerColor()).drawImage(atob(
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
x+=16;
}
@ -22,8 +64,13 @@ function draw() {
g.fillRect(x,y+2,x+s-4,y+21);
g.clearRect(x+2,y+4,x+s-6,y+19);
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(-1);
if (!setting('percentage')) {
return;
}
g.setFontAlign(-1,-1);
if (l >= 100) {
g.setFont('4x6', 2);
@ -34,6 +81,16 @@ function draw() {
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) {
if(charging) Bangle.buzz();
setWidth();
@ -43,7 +100,7 @@ Bangle.on('charging',function(charging) {
var batteryInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
WIDGETS["bat"].draw();
WIDGETS["batpc"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
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();
})()