Merge branch 'master' into layout-img-object
|
|
@ -7,3 +7,4 @@ appdates.csv
|
||||||
.vscode
|
.vscode
|
||||||
.idea/
|
.idea/
|
||||||
_config.yml
|
_config.yml
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ easily distinguish between file types, we use the following:
|
||||||
|
|
||||||
## Adding your app to the menu
|
## Adding your app to the menu
|
||||||
|
|
||||||
* Come up with a unique (all lowercase, nu spaces) name, we'll assume `7chname`. Bangle.js
|
* Come up with a unique (all lowercase, no spaces) name, we'll assume `7chname`. Bangle.js
|
||||||
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
|
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
|
||||||
try and keep filenames short to avoid overflowing the buffer.
|
try and keep filenames short to avoid overflowing the buffer.
|
||||||
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
|
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
|
||||||
|
|
|
||||||
142
apps.json
|
|
@ -118,7 +118,7 @@
|
||||||
{ "id": "welcome",
|
{ "id": "welcome",
|
||||||
"name": "Welcome",
|
"name": "Welcome",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"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,
|
||||||
|
|
@ -136,7 +136,7 @@
|
||||||
"name": "Customised Welcome",
|
"name": "Customised Welcome",
|
||||||
"shortName": "My Welcome",
|
"shortName": "My Welcome",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
|
"description": "Appears at first boot and explains how to use Bangle.js. Like 'Welcome', but can be customised with a greeting",
|
||||||
"tags": "start,welcome",
|
"tags": "start,welcome",
|
||||||
"custom":"custom.html",
|
"custom":"custom.html",
|
||||||
|
|
@ -153,7 +153,7 @@
|
||||||
{ "id": "gbridge",
|
{ "id": "gbridge",
|
||||||
"name": "Gadgetbridge",
|
"name": "Gadgetbridge",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.23",
|
"version":"0.24",
|
||||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||||
"tags": "tool,system,android,widget",
|
"tags": "tool,system,android,widget",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
|
|
@ -532,7 +532,7 @@
|
||||||
{ "id": "heart",
|
{ "id": "heart",
|
||||||
"name": "Heart Rate Recorder",
|
"name": "Heart Rate Recorder",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.05",
|
"version":"0.06",
|
||||||
"interface": "interface.html",
|
"interface": "interface.html",
|
||||||
"description": "Application that allows you to record your heart rate. Can run in background",
|
"description": "Application that allows you to record your heart rate. Can run in background",
|
||||||
"tags": "tool,health,widget",
|
"tags": "tool,health,widget",
|
||||||
|
|
@ -571,7 +571,7 @@
|
||||||
{ "id": "weather",
|
{ "id": "weather",
|
||||||
"name": "Weather",
|
"name": "Weather",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"version":"0.05",
|
"version":"0.07",
|
||||||
"description": "Show Gadgetbridge weather report",
|
"description": "Show Gadgetbridge weather report",
|
||||||
"readme": "readme.md",
|
"readme": "readme.md",
|
||||||
"tags": "widget,outdoors",
|
"tags": "widget,outdoors",
|
||||||
|
|
@ -639,10 +639,11 @@
|
||||||
"name": "Battery Level Widget (with percentage)",
|
"name": "Battery Level Widget (with percentage)",
|
||||||
"shortName": "Battery Widget",
|
"shortName": "Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.11",
|
"version":"0.12",
|
||||||
"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,b2",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
"readme": "README.md",
|
||||||
"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.js","url":"settings.js"}
|
||||||
|
|
@ -721,7 +722,7 @@
|
||||||
{ "id": "widhrm",
|
{ "id": "widhrm",
|
||||||
"name": "Simple Heart Rate widget",
|
"name": "Simple Heart Rate widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.",
|
"description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.",
|
||||||
"tags": "health,widget",
|
"tags": "health,widget",
|
||||||
"type": "widget",
|
"type": "widget",
|
||||||
|
|
@ -1287,7 +1288,7 @@
|
||||||
{ "id": "wohrm",
|
{ "id": "wohrm",
|
||||||
"name": "Workout HRM",
|
"name": "Workout HRM",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.07",
|
"version":"0.08",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
|
"description": "Workout heart rate monitor notifies you with a buzz if your heart rate goes above or below the set limits.",
|
||||||
"tags": "hrm,workout",
|
"tags": "hrm,workout",
|
||||||
|
|
@ -1466,7 +1467,7 @@
|
||||||
"id": "balltastic",
|
"id": "balltastic",
|
||||||
"name": "Balltastic",
|
"name": "Balltastic",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Simple but fun ball eats dots game.",
|
"description": "Simple but fun ball eats dots game.",
|
||||||
"tags": "game,fun",
|
"tags": "game,fun",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
@ -1517,7 +1518,7 @@
|
||||||
"name": "OpenStreetMap",
|
"name": "OpenStreetMap",
|
||||||
"shortName":"OpenStMap",
|
"shortName":"OpenStMap",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
|
"description": "[BETA] Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are",
|
||||||
"tags": "outdoors,gps,b2",
|
"tags": "outdoors,gps,b2",
|
||||||
"custom": "custom.html", "customConnect":true,
|
"custom": "custom.html", "customConnect":true,
|
||||||
|
|
@ -1895,7 +1896,7 @@
|
||||||
{ "id": "ballmaze",
|
{ "id": "ballmaze",
|
||||||
"name": "Ball Maze",
|
"name": "Ball Maze",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"version": "0.01",
|
"version": "0.02",
|
||||||
"description": "Navigate a ball through a maze by tilting your watch.",
|
"description": "Navigate a ball through a maze by tilting your watch.",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"tags": "game",
|
"tags": "game",
|
||||||
|
|
@ -1944,26 +1945,16 @@
|
||||||
"id": "largeclock",
|
"id": "largeclock",
|
||||||
"name": "Large Clock",
|
"name": "Large Clock",
|
||||||
"icon": "largeclock.png",
|
"icon": "largeclock.png",
|
||||||
"version": "0.09",
|
"version": "0.10",
|
||||||
"description": "A readable and informational digital watch, with date, seconds and moon phase",
|
"description": "A readable and informational digital watch, with date, seconds and moon phase",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"tags": "clock",
|
"tags": "clock",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
"allow_emulator": true,
|
"allow_emulator": true,
|
||||||
"storage": [
|
"storage": [
|
||||||
{
|
{"name": "largeclock.app.js", "url": "largeclock.js"},
|
||||||
"name": "largeclock.app.js",
|
{"name": "largeclock.img", "url": "largeclock-icon.js", "evaluate": true},
|
||||||
"url": "largeclock.js"
|
{"name": "largeclock.settings.js", "url": "settings.js"}
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "largeclock.img",
|
|
||||||
"url": "largeclock-icon.js",
|
|
||||||
"evaluate": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "largeclock.settings.js",
|
|
||||||
"url": "settings.js"
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
{"name":"largeclock.json"}
|
{"name":"largeclock.json"}
|
||||||
|
|
@ -3265,14 +3256,15 @@
|
||||||
"name": "Hour Strike",
|
"name": "Hour Strike",
|
||||||
"shortName": "Hour Strike",
|
"shortName": "Hour Strike",
|
||||||
"icon": "app-icon.png",
|
"icon": "app-icon.png",
|
||||||
"version": "0.07",
|
"version": "0.08",
|
||||||
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
|
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
|
||||||
"tags": "tool,alarm",
|
"tags": "tool,alarm",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"hourstrike.app.js","url":"app.js"},
|
{"name":"hourstrike.app.js","url":"app.js"},
|
||||||
{"name":"hourstrike.boot.js","url":"boot.js"},
|
{"name":"hourstrike.boot.js","url":"boot.js"},
|
||||||
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
|
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"hourstrike.json","url":"hourstrike.json"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "whereworld",
|
{ "id": "whereworld",
|
||||||
|
|
@ -3430,13 +3422,103 @@
|
||||||
"name": "Car Crazy",
|
"name": "Car Crazy",
|
||||||
"shortName":"Car Crazy",
|
"shortName":"Car Crazy",
|
||||||
"icon": "carcrash.png",
|
"icon": "carcrash.png",
|
||||||
"version":"0.01",
|
"version":"0.03",
|
||||||
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
|
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
|
||||||
"tags": "game",
|
"tags": "game",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"carcrazy.app.js","url":"app.js"},
|
{"name":"carcrazy.app.js","url":"app.js"},
|
||||||
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true}
|
{"name":"carcrazy.img","url":"app-icon.js","evaluate":true},
|
||||||
|
{"name":"carcrazy.settings.js","url":"settings.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"app.json"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "shortcuts",
|
||||||
|
"name": "Shortcuts",
|
||||||
|
"shortName":"Shortcuts",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Quickly load your favourite apps from (almost) any watch face.",
|
||||||
|
"tags": "tool",
|
||||||
|
"type": "bootloader",
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"shortcuts.boot.js","url":"boot.js"},
|
||||||
|
{"name":"shortcuts.settings.js","url":"settings.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"shortcuts.json"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "vectorclock",
|
||||||
|
"name": "Vector Clock",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version": "0.02",
|
||||||
|
"description": "A digital clock that uses the built-in vector font.",
|
||||||
|
"tags": "clock",
|
||||||
|
"type": "clock",
|
||||||
|
"allow_emulator": true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"vectorclock.app.js","url":"app.js"},
|
||||||
|
{"name":"vectorclock.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "fd6fdetect",
|
||||||
|
"name": "fd6fdetect",
|
||||||
|
"shortName":"fd6fdetect",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.1",
|
||||||
|
"description": "Allows you to see 0xFD6F beacons near you.",
|
||||||
|
"tags": "tool",
|
||||||
|
"storage": [
|
||||||
|
{"name":"fd6fdetect.app.js","url":"app.js"},
|
||||||
|
{"name":"fd6fdetect.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "choozi",
|
||||||
|
"name": "Choozi",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Choose people or things at random using Bangle.js.",
|
||||||
|
"tags": "tool",
|
||||||
|
"readme": "README.md",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"choozi.app.js","url":"app.js"},
|
||||||
|
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "widclkbttm",
|
||||||
|
"name": "Digital clock (Bottom) widget",
|
||||||
|
"shortName":"Digital clock Bottom Widget",
|
||||||
|
"icon": "widclkbttm.png",
|
||||||
|
"version":"0.03",
|
||||||
|
"description": "Displays time in the bottom area.",
|
||||||
|
"readme": "README.md",
|
||||||
|
"tags": "widget",
|
||||||
|
"type": "widget",
|
||||||
|
"storage": [
|
||||||
|
{"name":"widclkbttm.wid.js","url":"widclkbttm.wid.js"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "pastel",
|
||||||
|
"name": "Pastel Clock",
|
||||||
|
"shortName": "Pastel",
|
||||||
|
"icon": "pastel.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "A Configurable clock with custom fonts and background",
|
||||||
|
"tags": "clock,b2",
|
||||||
|
"type":"clock",
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"pastel.app.js","url":"pastel.app.js"},
|
||||||
|
{"name":"pastel.img","url":"pastel.icon.js","evaluate":true},
|
||||||
|
{"name":"pastel.settings.js","url":"pastel.settings.js"}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name":"pastel.json"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
(() => {
|
(() => {
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
let intervalID;
|
let intervalID;
|
||||||
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
let settings = require("Storage").readJSON("ballmaze.json",true) || {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: Initial version of Balltastic released! Happy!
|
0.01: Initial version of Balltastic released! Happy!
|
||||||
|
0.02: Set LCD timeout for Espruino 2v10 compatibility
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
Bangle.setLCDBrightness(1);
|
Bangle.setLCDBrightness(1);
|
||||||
Bangle.setLCDMode("doublebuffered");
|
Bangle.setLCDMode("doublebuffered");
|
||||||
|
Bangle.setLCDTimeout(0);
|
||||||
|
|
||||||
let points = 0;
|
let points = 0;
|
||||||
let level = 1;
|
let level = 1;
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
0.01: Car Crazy is now avialable for testing in beta!
|
0.01: Car Crazy is now avialable for testing in beta!
|
||||||
|
0.02: 10 Levels are now added making the game harder as it goes along. Some of the levels include multiple cars and faster cars. More levels coming soon.
|
||||||
|
0.03: Settings are now added so that you can reset your high score.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Car Crazy
|
# Car Crazy
|
||||||
Car crazy is a fun game where you tilt your wrist left and right to avoid incoming cars. If you get hit by a car you lose a heart. In the game you have three hearts, if you get hit 3 times you are sent to the game over screen. Your goal is to try to last as long as you can. Because this game is still in beta please report any bugs here: https://forms.office.com/r/HnwYzG9Sk7.
|
Car crazy is a fun game where you tilt your wrist left and right to avoid incoming cars. If you get hit by a car you lose a heart. In the game you have three hearts, if you get hit 3 times you are sent to the game over screen. Recently levels have been added making the game get harder as you play. Your goal is to try to last as long as you can. Because this game is still in production please report any bugs here: https://forms.office.com/r/HnwYzG9Sk7.
|
||||||
|
|
||||||
### Images:
|
### Images:
|
||||||
(Coming Soon)
|
(Coming Soon)
|
||||||
|
|
@ -11,8 +11,50 @@ BNT2: Hold down this button to start the game if you are on the starting page an
|
||||||
Tilting Left-Right: Tilt your wrist left and right to steer your car and try not to get hit by the enemy car.
|
Tilting Left-Right: Tilt your wrist left and right to steer your car and try not to get hit by the enemy car.
|
||||||
|
|
||||||
### Feautures Coming Soon:
|
### Feautures Coming Soon:
|
||||||
0.02: Levels are creating making the game get harder as it goes along.
|
0.02: Levels are creating making the game get harder as it goes along. (Completed)
|
||||||
|
|
||||||
0.03: Optional soundtrack in settings. More levels.
|
0.03: Setting for reseting high score. (Completed)
|
||||||
|
|
||||||
0.04: With higher scores you can now unlock different colors of cars.
|
0.04: Optional soundtrack in settings. More levels.
|
||||||
|
|
||||||
|
0.05: With higher scores you can now unlock different colors of cars. More settings.
|
||||||
|
|
||||||
|
0.06: Car selector at game over screen and start screen.
|
||||||
|
|
||||||
|
0.07 More levels.
|
||||||
|
|
||||||
|
0.08 More types of Cars.
|
||||||
|
|
||||||
|
### Levels
|
||||||
|
Here is a list of all the levels:
|
||||||
|
|
||||||
|
##### Level 1:
|
||||||
|
Basic single car moving at 10 speed.
|
||||||
|
|
||||||
|
##### Level 2:
|
||||||
|
A single car can come from any position at the top of the screen.
|
||||||
|
|
||||||
|
##### Level 3:
|
||||||
|
A single car are coming at 12 speed and can come from any position at the top of the screen.
|
||||||
|
|
||||||
|
##### Level 4:
|
||||||
|
2 cars come at 8 speed.
|
||||||
|
|
||||||
|
##### Level 5:
|
||||||
|
2 cars come at 9 speed.
|
||||||
|
|
||||||
|
##### Level 6:
|
||||||
|
2 cars come at 9.5 and 8 speed.
|
||||||
|
|
||||||
|
##### Level 7:
|
||||||
|
2 cars come at 10 and 8 speed.
|
||||||
|
|
||||||
|
##### Level 8:
|
||||||
|
2 cars come at 11.5 speed.
|
||||||
|
|
||||||
|
##### Level 9 and Above:
|
||||||
|
2 cars come at 13 and 14 speed.
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
-Settings are now avialable for resetting your high score.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ var PurpleCar = require("heatshrink").decompress(atob("ol74UBitg///BIP/7lVqtUDJU
|
||||||
|
|
||||||
var LightGreenCar = require("heatshrink").decompress(atob("ol74UBocF///BIP1z9VqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14QxKjGqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAQGBCAOo6te1QQB///lQPC1AGBCAOqyoQQlAuBCAXPAQIAD/Q2BlGoCAgAGCAUqCBnyCAVS1WrB5AAB9RUDCB1WCB0VVIIQMXQYQ/CAbFBBw/8BQIQFn4QewDNBCQgOCgEACDADBAAMC1T5BCAwABCQIACA4QQZABAQ/CH4Q/CH4QC1APKhQQPlQQUxWgGJOCCAcCCBWACAmqGhAQFwEqCA8qFYIQTuWohWqAAIQE1WghUFCANVD4JFBAAqzBlQPCCCNyHIIQGMoONCAdWQ5WVCCjNJZIYQM1WtCAt6KYwAB0oQFIoIPGIIoQTqhDG6oMDA"));
|
var LightGreenCar = require("heatshrink").decompress(atob("ol74UBocF///BIP1z9VqtUDJUVBwIABq2qABOVCCkolQJC0AwDgWolAQD1EqwBCH0EqCCdKxQuEAAkKwGhCH4Q/CGD7ECDINEAAoQIwBACgQQYwQ+EAAcC1EACAeAlQfDAAheBCG2oCBUCCB8qCCiQBdp3+fxW/CH4Q/CH4QxgQQKwAQD1QQK1QQUmQQKxiH/CBGqCB3/5WACA+j/4Qf14QxKjGqRI0CCAv+x4QBEYcC1Wo/SpFCBtUlQQBBgISBAAQGBCAOo6te1QQB///lQPC1AGBCAOqyoQQlAuBCAXPAQIAD/Q2BlGoCAgAGCAUqCBnyCAVS1WrB5AAB9RUDCB1WCB0VVIIQMXQYQ/CAbFBBw/8BQIQFn4QewDNBCQgOCgEACDADBAAMC1T5BCAwABCQIACA4QQZABAQ/CH4Q/CH4QC1APKhQQPlQQUxWgGJOCCAcCCBWACAmqGhAQFwEqCA8qFYIQTuWohWqAAIQE1WghUFCANVD4JFBAAqzBlQPCCCNyHIIQGMoONCAdWQ5WVCCjNJZIYQM1WtCAt6KYwAB0oQFIoIPGIIoQTqhDG6oMDA"));
|
||||||
|
|
||||||
|
function consoleDebug(message) {
|
||||||
|
//console.log(message);
|
||||||
|
}
|
||||||
|
|
||||||
function getRandomInt(min, max) {
|
function getRandomInt(min, max) {
|
||||||
min = Math.ceil(min);
|
min = Math.ceil(min);
|
||||||
max = Math.floor(max);
|
max = Math.floor(max);
|
||||||
|
|
@ -30,21 +34,55 @@ function getRandomInt(min, max) {
|
||||||
|
|
||||||
function moveEnemyPosition(){
|
function moveEnemyPosition(){
|
||||||
score += 1;
|
score += 1;
|
||||||
|
checkForNextLevel();
|
||||||
|
if(level == 1){
|
||||||
randomRoadPositionIndicator = getRandomInt(1, 4);
|
randomRoadPositionIndicator = getRandomInt(1, 4);
|
||||||
if ((randomRoadPositionIndicator == 1)) {
|
if ((randomRoadPositionIndicator == 1)) {
|
||||||
randomRoadPosition = 85;
|
enemyPositonCenterX = 85;
|
||||||
}else if((randomRoadPositionIndicator == 2)){
|
}else if((randomRoadPositionIndicator == 2)){
|
||||||
randomRoadPosition = 120;
|
enemyPositonCenterX = 120;
|
||||||
}else {
|
}else {
|
||||||
randomRoadPosition = 155;
|
enemyPositonCenterX = 155;
|
||||||
|
}
|
||||||
|
}else if(level == 2||level==3){
|
||||||
|
enemyPositonCenterX = getRandomInt(85, 155);
|
||||||
|
}else if(level == 4 || level == 5 || level == 6 || level == 8 || level == 9 || level == 10 || level > 10){
|
||||||
|
do{
|
||||||
|
randomRoadPositionIndicator = getRandomInt(1, 4);
|
||||||
|
randomRoadPositionIndicator2 = getRandomInt(1, 4);
|
||||||
|
}while(randomRoadPositionIndicator==randomRoadPositionIndicator2);
|
||||||
|
|
||||||
|
if ((randomRoadPositionIndicator == 1)) {
|
||||||
|
enemyPositonCenterX = 85;
|
||||||
|
}else if((randomRoadPositionIndicator == 2)){
|
||||||
|
enemyPositonCenterX = 120;
|
||||||
|
}else if((randomRoadPositionIndicator == 3)){
|
||||||
|
enemyPositonCenterX = 155;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((randomRoadPositionIndicator2 == 1)) {
|
||||||
|
enemyPositonCenterX2 = 85;
|
||||||
|
}else if((randomRoadPositionIndicator2 == 2)){
|
||||||
|
enemyPositonCenterX2 = 120;
|
||||||
|
}else if((randomRoadPositionIndicator2 == 3)){
|
||||||
|
enemyPositonCenterX2 = 155;
|
||||||
|
}else if(level == 7||level == 8){
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function collision(){
|
function collision(){
|
||||||
if(gameStatus == GAMEPLAYING){
|
if(gameStatus == GAMEPLAYING){
|
||||||
|
consoleDebug("Px:"+playerCarLeftX+", "+playerCarRightX);
|
||||||
|
consoleDebug("1x:"+enemyCarLeftX+", "+enemyCarRightX);
|
||||||
|
consoleDebug("2x:"+enemyCarLeftX2+", "+enemyCarRightX2);
|
||||||
|
consoleDebug("Py:"+playerCarFrontY);
|
||||||
|
consoleDebug("1y:"+enemyCarFrontY);
|
||||||
|
consoleDebug("2y:"+enemyCarFrontY2);
|
||||||
if
|
if
|
||||||
(
|
(
|
||||||
(enemyCarFrontY > playerCarFrontY)
|
(enemyCarFrontY < 300 && enemyCarFrontY > playerCarFrontY)
|
||||||
&&
|
&&
|
||||||
(
|
(
|
||||||
(enemyCarLeftX > playerCarLeftX && enemyCarLeftX < playerCarRightX)
|
(enemyCarLeftX > playerCarLeftX && enemyCarLeftX < playerCarRightX)
|
||||||
|
|
@ -52,51 +90,105 @@ function collision(){
|
||||||
(enemyCarRightX > playerCarLeftX && enemyCarRightX < playerCarRightX)
|
(enemyCarRightX > playerCarLeftX && enemyCarRightX < playerCarRightX)
|
||||||
)
|
)
|
||||||
){
|
){
|
||||||
// hit
|
// hit car 1
|
||||||
setTimeout(collision, 2500); // wait 2.5 second for the function to actiavte agian.
|
consoleDebug("1 HIT");
|
||||||
|
enemyPositonCenterY = 300;
|
||||||
numberofHearts -= 1;
|
numberofHearts -= 1;
|
||||||
score -= 1;
|
Bangle.buzz(50,50);
|
||||||
Bangle.buzz();
|
}else if
|
||||||
}else{
|
(
|
||||||
// miss
|
(enemyCarFrontY2 < 300 && enemyCarFrontY2 > playerCarFrontY)
|
||||||
setTimeout(collision, 1); // try again in 1 milliseconds.
|
&&
|
||||||
|
(
|
||||||
|
(enemyCarLeftX2 > playerCarLeftX && enemyCarLeftX2 < playerCarRightX)
|
||||||
|
||
|
||||||
|
(enemyCarRightX2 > playerCarLeftX && enemyCarRightX2 < playerCarRightX)
|
||||||
|
)
|
||||||
|
){
|
||||||
|
// hit car 2
|
||||||
|
consoleDebug("2 HIT");
|
||||||
|
enemyPositonCenterY2 = 300;
|
||||||
|
numberofHearts -= 1;
|
||||||
|
Bangle.buzz(50,50);
|
||||||
}
|
}
|
||||||
|
setTimeout(collision, 50); // try again in 50 milliseconds.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function storeMyData(data) {
|
function checkForNextLevel(){
|
||||||
// ensure there are less than 500 elements in the array
|
if(score < 10){
|
||||||
while (log.length >= 500) log.shift();
|
level = 1;
|
||||||
// append a new item to the array
|
}else if(score >= 10 && score < 20){
|
||||||
log.push(data);
|
level = 2;
|
||||||
|
}else if(score >= 20 && score < 30){
|
||||||
|
level = 3;
|
||||||
|
}else if(score >= 30 && score < 40){
|
||||||
|
level = 4;
|
||||||
|
}else if(score >= 40 && score < 50){
|
||||||
|
level = 5;
|
||||||
|
}else if(score >= 50 && score < 60){
|
||||||
|
level = 6;
|
||||||
|
}else if(score >= 60 && score < 70){
|
||||||
|
level = 7;
|
||||||
|
}else if(score >= 70 && score < 80){
|
||||||
|
level = 8;
|
||||||
|
}else if(score >= 80 && score < 90){
|
||||||
|
level = 9;
|
||||||
|
}else if(score >= 90){
|
||||||
|
level = 10;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accel = Bangle.getAccel();
|
||||||
|
|
||||||
var file = require("Storage").open("CarCrazy.csv","r");
|
var file = require("Storage").open("CarCrazy.csv","r");
|
||||||
var currentHighScore = file.readLine();
|
var currentHighScore = file.readLine();
|
||||||
if (currentHighScore == undefined) currentHighScore = 0;
|
if (currentHighScore == undefined) currentHighScore = 0;
|
||||||
|
|
||||||
var BackgroundStartingPosition = 75;
|
var BackgroundStartingPosition = 75;
|
||||||
var carScale = 0.5;
|
|
||||||
var accel = Bangle.getAccel();
|
|
||||||
var playerCarPosition = 120-accel.x*40;
|
|
||||||
var BackgroundYPosition = BackgroundStartingPosition;
|
var BackgroundYPosition = BackgroundStartingPosition;
|
||||||
var randomRoadPositionIndicator = getRandomInt(1, 3);
|
|
||||||
var randomRoadPosition = 120;
|
var randomRoadPositionIndicator;
|
||||||
var enemyPositonY = 30;
|
var randomRoadPositionIndicator2;
|
||||||
|
var enemyPositonCenterX;
|
||||||
|
var enemyPositonCenterX2;
|
||||||
|
|
||||||
|
var carScale = 0.5;
|
||||||
var carWidth = 30;
|
var carWidth = 30;
|
||||||
var carHeight = 60;
|
var carHeight = 60;
|
||||||
var playerCarY = 130;
|
|
||||||
var enemyCarLeftX;
|
var playerCarCenterY = 130;
|
||||||
var enemyCarRightX;
|
var playerCarCenterX;
|
||||||
|
|
||||||
|
var enemyPositonCenterY = 0 - carHeight/2;
|
||||||
|
var enemyPositonCenterY2 = 0 - carHeight/2;
|
||||||
|
|
||||||
var playerCarLeftX;
|
var playerCarLeftX;
|
||||||
var playerCarRightX;
|
var playerCarRightX;
|
||||||
var enemyCarFrontY;
|
|
||||||
var playerCarFrontY;
|
var playerCarFrontY;
|
||||||
|
|
||||||
|
var playerCarFrontY;
|
||||||
|
var playerCarBackY;
|
||||||
|
var playerCarLeftX;
|
||||||
|
var playerCarRightX;
|
||||||
|
|
||||||
|
var enemyCarFrontY;
|
||||||
|
var enemyCarBackY;
|
||||||
|
var enemyCarLeftX;
|
||||||
|
var enemyCarRightX;
|
||||||
|
|
||||||
|
var enemyCarFrontY2;
|
||||||
|
var enemyCarBackY2;
|
||||||
|
var enemyCarLeftX2;
|
||||||
|
var enemyCarRightX2;
|
||||||
|
|
||||||
var GAMEPLAYING = 1;
|
var GAMEPLAYING = 1;
|
||||||
var GAMEOVER = 2;
|
var GAMEOVER = 2;
|
||||||
var GAMESTART = 3;
|
var GAMESTART = 3;
|
||||||
var gameStatus = GAMESTART;
|
var gameStatus = GAMESTART;
|
||||||
var score = 0;
|
var score = 0;
|
||||||
|
var level = 1;
|
||||||
|
|
||||||
|
|
||||||
moveEnemyPosition();
|
moveEnemyPosition();
|
||||||
collision();
|
collision();
|
||||||
|
|
@ -114,11 +206,16 @@ function draw(){
|
||||||
if(gameStatus == GAMEPLAYING){
|
if(gameStatus == GAMEPLAYING){
|
||||||
BackgroundYPosition += 10;
|
BackgroundYPosition += 10;
|
||||||
accel = Bangle.getAccel();
|
accel = Bangle.getAccel();
|
||||||
playerCarPosition = 120-accel.x*40;
|
playerCarCenterX = Math.round(120-accel.x*120);
|
||||||
|
if (playerCarCenterX > 170) { playerCarCenterX = 170; }
|
||||||
|
if (playerCarCenterX < 70) { playerCarCenterX = 70; }
|
||||||
g.flip();
|
g.flip();
|
||||||
g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0});
|
g.drawImage(backgroundImage,125,BackgroundYPosition, {scale:13,rotate:0});
|
||||||
g.drawImage(RedCar,playerCarPosition,playerCarY, {scale:carScale,rotate:3.142});
|
g.drawImage(RedCar,playerCarCenterX,playerCarCenterY, {scale:carScale,rotate:3.142});
|
||||||
g.drawImage(OrangeCar,randomRoadPosition,enemyPositonY, {scale:carScale,rotate:0});
|
g.drawImage(OrangeCar,enemyPositonCenterX,enemyPositonCenterY, {scale:carScale,rotate:0});
|
||||||
|
if(level>=4){
|
||||||
|
g.drawImage(OrangeCar,enemyPositonCenterX2,enemyPositonCenterY2, {scale:carScale,rotate:0});
|
||||||
|
}
|
||||||
|
|
||||||
if(numberofHearts==3){
|
if(numberofHearts==3){
|
||||||
g.drawImage(heartImage,10,10, {scale:2,rotate:0});
|
g.drawImage(heartImage,10,10, {scale:2,rotate:0});
|
||||||
|
|
@ -141,23 +238,38 @@ function draw(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playerCarFrontY = playerCarY-carHeight/2;
|
playerCarFrontY = playerCarCenterY-carHeight/2;
|
||||||
playerCarBackY = playerCarY+carHeight/2;
|
playerCarBackY = playerCarCenterY+carHeight/2;
|
||||||
playerCarLeftX = playerCarPosition-carWidth/2;
|
playerCarLeftX = playerCarCenterX-carWidth/2;
|
||||||
playerCarRightX = playerCarPosition+carWidth/2;
|
playerCarRightX = playerCarCenterX+carWidth/2;
|
||||||
|
|
||||||
enemyCarFrontY = enemyPositonY+carHeight/2;
|
enemyCarFrontY = enemyPositonCenterY+carHeight/2;
|
||||||
enemyCarBackY = enemyPositonY-carHeight/2;
|
enemyCarBackY = enemyPositonCenterY-carHeight/2;
|
||||||
enemyCarLeftX = randomRoadPosition-carWidth/2;
|
enemyCarLeftX = enemyPositonCenterX-carWidth/2;
|
||||||
enemyCarRightX = randomRoadPosition+carWidth/2;
|
enemyCarRightX = enemyPositonCenterX+carWidth/2;
|
||||||
|
|
||||||
|
enemyCarFrontY2 = enemyPositonCenterY2+carHeight/2;
|
||||||
|
enemyCarBackY2 = enemyPositonCenterY2-carHeight/2;
|
||||||
|
enemyCarLeftX2 = enemyPositonCenterX2-carWidth/2;
|
||||||
|
enemyCarRightX2 = enemyPositonCenterX2+carWidth/2;
|
||||||
|
|
||||||
|
g.setColor(255,0,0);
|
||||||
//g.drawRect(playerCarLeftX, playerCarFrontY, playerCarRightX, playerCarBackY);
|
//g.drawRect(playerCarLeftX, playerCarFrontY, playerCarRightX, playerCarBackY);
|
||||||
//g.drawRect(enemyCarLeftX, enemyCarFrontY, enemyCarRightX, enemyCarBackY);
|
//g.drawRect(enemyCarLeftX, enemyCarFrontY, enemyCarRightX, enemyCarBackY);
|
||||||
|
//g.drawRect(enemyCarLeftX2, enemyCarFrontY2, enemyCarRightX2, enemyCarBackY2);
|
||||||
|
|
||||||
g.setColor(0,0,0);
|
g.setColor(0,0,0);
|
||||||
g.drawString("Score: "+score,180,5);
|
g.drawString("Score: "+score,180,5);
|
||||||
g.drawString("HighScore:",178,15);
|
g.drawString("HighScore:",178,15);
|
||||||
g.drawString(currentHighScore,205,25);
|
g.drawString(currentHighScore,205,25);
|
||||||
|
g.drawString("Level: "+level,180,150);
|
||||||
|
|
||||||
|
//g.drawString("P:"+playerCarLeftX+", "+playerCarRightX,180,90);
|
||||||
|
//g.drawString("1:"+enemyCarLeftX+", "+enemyCarRightX,180,100);
|
||||||
|
//g.drawString("2:"+enemyCarLeftX2+", "+enemyCarRightX2,180,110);
|
||||||
|
//g.drawString("P:"+playerCarFrontY,180,120);
|
||||||
|
//g.drawString("1:"+enemyCarFrontY,180,130);
|
||||||
|
//g.drawString("2:"+enemyCarFrontY2,180,140);
|
||||||
|
|
||||||
if(BackgroundYPosition > 170){
|
if(BackgroundYPosition > 170){
|
||||||
BackgroundYPosition = BackgroundStartingPosition;
|
BackgroundYPosition = BackgroundStartingPosition;
|
||||||
|
|
@ -200,41 +312,75 @@ function draw(){
|
||||||
g.drawString("2 To Start",10,130);
|
g.drawString("2 To Start",10,130);
|
||||||
g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3});
|
g.drawImage(LightGreenCar,180,115, {scale:0.5,rotate:3});
|
||||||
g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3});
|
g.drawImage(PurpleCar,215,115, {scale:0.5,rotate:3});
|
||||||
//setTimeout(displayPopup, 3000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInterval(draw ,10);
|
setInterval(draw, 50);
|
||||||
|
|
||||||
|
|
||||||
function moveEnemyCar(){
|
function moveEnemyCar(){
|
||||||
if(gameStatus == GAMEPLAYING){
|
if(gameStatus == GAMEPLAYING){
|
||||||
enemyPositonY = enemyPositonY + 10;
|
if(level==1||level==2){
|
||||||
if((enemyPositonY > 200)){
|
enemyPositonCenterY = enemyPositonCenterY + 10;
|
||||||
enemyPositonY = 30;
|
enemyPositonCenterY2 = 0;
|
||||||
|
}else if(level==3){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 12;
|
||||||
|
enemyPositonCenterY2 = 0;
|
||||||
|
}else if(level==4){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 8;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 8;
|
||||||
|
}else if(level==5){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 9;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 9;
|
||||||
|
}else if(level==6){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 9.5;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 8;
|
||||||
|
}else if(level==7){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 10;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 8;
|
||||||
|
}else if(level==8){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 11.5;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 11.5;
|
||||||
|
}else if(level>=9){
|
||||||
|
enemyPositonCenterY = enemyPositonCenterY + 13;
|
||||||
|
enemyPositonCenterY2 = enemyPositonCenterY2 + 14;
|
||||||
|
}
|
||||||
|
if(enemyPositonCenterY > 200){
|
||||||
|
enemyPositonCenterY = 300;
|
||||||
|
}
|
||||||
|
if(enemyPositonCenterY2 > 200){
|
||||||
|
enemyPositonCenterY2 = 300;
|
||||||
|
}
|
||||||
|
if(enemyPositonCenterY > 200 && (enemyPositonCenterY2 > 200 || level < 4)){
|
||||||
|
enemyPositonCenterY = 0 - carHeight/2;
|
||||||
|
if (level >= 4) { enemyPositonCenterY2 = 0 - carHeight/2; }
|
||||||
moveEnemyPosition();
|
moveEnemyPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInterval(moveEnemyCar,10);
|
setInterval(moveEnemyCar,50);
|
||||||
|
|
||||||
setWatch(() => {
|
setWatch(() => {
|
||||||
if(gameStatus == GAMESTART){
|
if(gameStatus == GAMESTART){
|
||||||
gameStatus = GAMEPLAYING;
|
gameStatus = GAMEPLAYING;
|
||||||
collision();
|
collision();
|
||||||
enemyPositonY = 0;
|
numberofHearts = 3;
|
||||||
|
enemyPositonCenterX = 120;
|
||||||
|
enemyPositonCenterY = 0 - carHeight/2;
|
||||||
|
enemyPositonCenterX2 = 120;
|
||||||
|
enemyPositonCenterY2 = 0 - carHeight/2;
|
||||||
score = 0;
|
score = 0;
|
||||||
|
level = 1;
|
||||||
|
checkForNextLevel();
|
||||||
}else if(gameStatus == GAMEOVER){
|
}else if(gameStatus == GAMEOVER){
|
||||||
gameStatus = GAMEPLAYING;
|
gameStatus = GAMEPLAYING;
|
||||||
collision();
|
collision();
|
||||||
enemyPositonY = 0;
|
enemyPositonCenterX = 120;
|
||||||
|
enemyPositonCenterY = 0 - carHeight/2;
|
||||||
|
enemyPositonCenterX2 = 120;
|
||||||
|
enemyPositonCenterY2 = 0 - carHeight/2;
|
||||||
numberofHearts = 3;
|
numberofHearts = 3;
|
||||||
score = 0;
|
score = 0;
|
||||||
|
level = 1;
|
||||||
|
checkForNextLevel();
|
||||||
}
|
}
|
||||||
}, BTN2, {repeat:true});
|
}, BTN2, {repeat:true});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
(function (back) {
|
||||||
|
const menu = {
|
||||||
|
'': { 'title': 'Car Crazy' },
|
||||||
|
'< Back': back,
|
||||||
|
'Reset Highscore': () => {
|
||||||
|
E.showPrompt('Reset Highscore?').then((v) => {
|
||||||
|
let delay = 50;
|
||||||
|
if (v) {
|
||||||
|
delay = 500;
|
||||||
|
E.showMessage('Resetting');
|
||||||
|
var f = require('Storage').open('CarCrazy.csv', 'w');
|
||||||
|
f.write('0\n');
|
||||||
|
}
|
||||||
|
setTimeout(() => E.showMenu(menu), delay);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
E.showMenu(menu);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# Choozi
|
||||||
|
|
||||||
|
Choose people or things at random using Bangle.js.
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/4cqOLNM5ei8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can use Choozi to pick a person to play first in a board game. With all
|
||||||
|
the players seated in a circle, set the number of segments equal to the number
|
||||||
|
of players, ensure that each person knows which colour represents them, and then
|
||||||
|
choose a segment. After a short animation, the chosen segment will fill the screen.
|
||||||
|
|
||||||
|
You can use Choozi to randomly select an element from any set with 2 to 13 members,
|
||||||
|
as long as you can define a bijection between members of the set and coloured
|
||||||
|
segments on the Bangle.js display.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
BTN1: increase the number of segments
|
||||||
|
BTN2: choose a segment at random
|
||||||
|
BTN3: decrease the number of segments
|
||||||
|
|
||||||
|
## Creator
|
||||||
|
|
||||||
|
James Stanley
|
||||||
|
September 2021
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
|
||||||
|
/* Choozi - Choose people or things at random using Bangle.js.
|
||||||
|
* Inspired by the "Chwazi" Android app
|
||||||
|
*
|
||||||
|
* James Stanley 2021
|
||||||
|
*/
|
||||||
|
|
||||||
|
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
|
||||||
|
|
||||||
|
var stepAngle = 0.18; // radians - resolution of polygon
|
||||||
|
var gapAngle = 0.035; // radians - gap between segments
|
||||||
|
var perimMin = 110; // px - min. radius of perimeter
|
||||||
|
var perimMax = 120; // px - max. radius of perimeter
|
||||||
|
|
||||||
|
var segmentMax = 106; // px - max radius of filled-in segment
|
||||||
|
var segmentStep = 5; // px - step size of segment fill animation
|
||||||
|
var circleStep = 4; // px - step size of circle fill animation
|
||||||
|
|
||||||
|
// rolling ball animation:
|
||||||
|
var maxSpeed = 0.08; // rad/sec
|
||||||
|
var minSpeed = 0.001; // rad/sec
|
||||||
|
var animStartSteps = 300; // how many steps before it can start slowing?
|
||||||
|
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
|
||||||
|
var ballSize = 3; // px - ball radius
|
||||||
|
var ballTrack = 100; // px - radius of ball path
|
||||||
|
|
||||||
|
var centreX = 120; // px - centre of screen
|
||||||
|
var centreY = 120; // px - centre of screen
|
||||||
|
|
||||||
|
var fontSize = 50; // px
|
||||||
|
|
||||||
|
var radians = 2*Math.PI; // radians per circle
|
||||||
|
|
||||||
|
var defaultN = 3; // default value for N
|
||||||
|
var minN = 2;
|
||||||
|
var maxN = colours.length;
|
||||||
|
var N;
|
||||||
|
var arclen;
|
||||||
|
|
||||||
|
// https://www.frankmitchell.org/2015/01/fisher-yates/
|
||||||
|
function shuffle (array) {
|
||||||
|
var i = 0
|
||||||
|
, j = 0
|
||||||
|
, temp = null;
|
||||||
|
|
||||||
|
for (i = array.length - 1; i > 0; i -= 1) {
|
||||||
|
j = Math.floor(Math.random() * (i + 1));
|
||||||
|
temp = array[i];
|
||||||
|
array[i] = array[j];
|
||||||
|
array[j] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw an arc between radii minR and maxR, and between
|
||||||
|
// angles minAngle and maxAngle
|
||||||
|
function arc(minR, maxR, minAngle, maxAngle) {
|
||||||
|
var step = stepAngle;
|
||||||
|
var angle = minAngle;
|
||||||
|
var inside = [];
|
||||||
|
var outside = [];
|
||||||
|
var c, s;
|
||||||
|
while (angle < maxAngle) {
|
||||||
|
c = Math.cos(angle);
|
||||||
|
s = Math.sin(angle);
|
||||||
|
inside.push(centreX+c*minR); // x
|
||||||
|
inside.push(centreY+s*minR); // y
|
||||||
|
// outside coordinates are built up in reverse order
|
||||||
|
outside.unshift(centreY+s*maxR); // y
|
||||||
|
outside.unshift(centreX+c*maxR); // x
|
||||||
|
angle += step;
|
||||||
|
}
|
||||||
|
c = Math.cos(maxAngle);
|
||||||
|
s = Math.sin(maxAngle);
|
||||||
|
inside.push(centreX+c*minR);
|
||||||
|
inside.push(centreY+s*minR);
|
||||||
|
outside.unshift(centreY+s*maxR);
|
||||||
|
outside.unshift(centreX+c*maxR);
|
||||||
|
|
||||||
|
var vertices = inside.concat(outside);
|
||||||
|
g.fillPoly(vertices, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the arc segments around the perimeter
|
||||||
|
function drawPerimeter() {
|
||||||
|
g.clear();
|
||||||
|
for (var i = 0; i < N; i++) {
|
||||||
|
g.setColor(colours[i%colours.length]);
|
||||||
|
var minAngle = (i/N)*radians;
|
||||||
|
arc(perimMin,perimMax,minAngle,minAngle+arclen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// animate a ball rolling around and settling at "target" radians
|
||||||
|
function animateChoice(target) {
|
||||||
|
var angle = 0;
|
||||||
|
var speed = 0;
|
||||||
|
var oldx = -10;
|
||||||
|
var oldy = -10;
|
||||||
|
var decelFromAngle = -1;
|
||||||
|
var allowDecel = false;
|
||||||
|
for (var i = 0; true; i++) {
|
||||||
|
angle = angle + speed;
|
||||||
|
if (angle > radians) angle -= radians;
|
||||||
|
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
|
||||||
|
speed = speed + accel;
|
||||||
|
if (speed > maxSpeed) {
|
||||||
|
speed = maxSpeed;
|
||||||
|
/* when we reach max speed, we know how long it takes
|
||||||
|
* to accelerate, and therefore how long to decelerate, so
|
||||||
|
* we can work out what angle to start decelerating from */
|
||||||
|
if (decelFromAngle < 0) {
|
||||||
|
decelFromAngle = target-angle;
|
||||||
|
while (decelFromAngle < 0) decelFromAngle += radians;
|
||||||
|
while (decelFromAngle > radians) decelFromAngle -= radians;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
|
||||||
|
if (allowDecel) speed = speed - accel;
|
||||||
|
if (speed < minSpeed) speed = minSpeed;
|
||||||
|
if (speed == minSpeed && angle < target && angle+speed >= target) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = i/2;
|
||||||
|
if (r > ballTrack) r = ballTrack;
|
||||||
|
var x = centreX+Math.cos(angle)*r;
|
||||||
|
var y = centreY+Math.sin(angle)*r;
|
||||||
|
g.setColor('#000000');
|
||||||
|
g.fillCircle(oldx,oldy,ballSize+1);
|
||||||
|
g.setColor('#ffffff');
|
||||||
|
g.fillCircle(x, y, ballSize);
|
||||||
|
oldx=x;
|
||||||
|
oldy=y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose a winning segment and animate its selection
|
||||||
|
function choose() {
|
||||||
|
var chosen = Math.floor(Math.random()*N);
|
||||||
|
var minAngle = (chosen/N)*radians;
|
||||||
|
var maxAngle = minAngle + arclen;
|
||||||
|
animateChoice((minAngle+maxAngle)/2);
|
||||||
|
g.setColor(colours[chosen%colours.length]);
|
||||||
|
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
|
||||||
|
arc(i, perimMax, minAngle, maxAngle);
|
||||||
|
arc(0, perimMax, minAngle, maxAngle);
|
||||||
|
for (var r = 1; r < segmentMax; r += circleStep)
|
||||||
|
g.fillCircle(centreX,centreY,r);
|
||||||
|
g.fillCircle(centreX,centreY,segmentMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the current value of N in the middle of the screen, with
|
||||||
|
// up/down arrows
|
||||||
|
function drawN() {
|
||||||
|
g.setColor('#ffffff');
|
||||||
|
g.setFont("Vector",fontSize);
|
||||||
|
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
|
||||||
|
if (N < maxN)
|
||||||
|
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
|
||||||
|
if (N > minN)
|
||||||
|
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update number of segments, with min/max limit, "arclen" update,
|
||||||
|
// and screen reset
|
||||||
|
function setN(n) {
|
||||||
|
N = n;
|
||||||
|
if (N < minN) N = minN;
|
||||||
|
if (N > maxN) N = maxN;
|
||||||
|
arclen = radians/N - gapAngle;
|
||||||
|
drawPerimeter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// save N to choozi.txt
|
||||||
|
function writeN() {
|
||||||
|
var file = require("Storage").open("choozi.txt","w");
|
||||||
|
file.write(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load N from choozi.txt
|
||||||
|
function readN() {
|
||||||
|
var file = require("Storage").open("choozi.txt","r");
|
||||||
|
var n = file.readLine();
|
||||||
|
if (n !== undefined) setN(parseInt(n));
|
||||||
|
else setN(defaultN);
|
||||||
|
}
|
||||||
|
|
||||||
|
shuffle(colours); // is this really best?
|
||||||
|
Bangle.setLCDMode("direct");
|
||||||
|
Bangle.setLCDTimeout(0); // keep screen on
|
||||||
|
readN();
|
||||||
|
drawN();
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
setN(N+1);
|
||||||
|
drawN();
|
||||||
|
}, BTN1, {repeat:true});
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
writeN();
|
||||||
|
drawPerimeter();
|
||||||
|
choose();
|
||||||
|
}, BTN2, {repeat:true});
|
||||||
|
|
||||||
|
setWatch(() => {
|
||||||
|
setN(N-1);
|
||||||
|
drawN();
|
||||||
|
}, BTN3, {repeat:true});
|
||||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
0.1: Added source code
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwIjgg/gAp0IgfAiAFBjkP+E4AoM8n/8ngFBvn//8+AoP//Ef/4FBv/Agf+AoMPwEB+AFCjEYAoUenk8vAvCAoIvCnAFBjgFCC4IFCCgUeEQNwAoMO+EPuPD4eOAoPz8fH54FH+IRBx4FBDogpFGoxBFJopZFMopxFPoqJFSoqhFVoq5FgAFBa6gAW"))
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
g.clear();
|
||||||
|
let amount = 'global value';
|
||||||
|
function FindFD6FBeacons() {
|
||||||
|
NRF.findDevices(function(devices) {
|
||||||
|
g.setFont('Vector', 75);
|
||||||
|
g.setFontAlign(0,0);
|
||||||
|
var amount = devices.length;
|
||||||
|
g.clear();
|
||||||
|
g.drawString(amount, 125, 100);
|
||||||
|
if (amount == 1) {
|
||||||
|
g.setFont('Vector', 25);
|
||||||
|
g.drawString('FD6F', 125, 150);
|
||||||
|
g.drawString('beacon', 125, 175);
|
||||||
|
g.drawString('nearby', 125, 200);
|
||||||
|
} else{
|
||||||
|
g.setFont('Vector', 25);
|
||||||
|
g.drawString('FD6F', 125, 150);
|
||||||
|
g.drawString('beacons', 125, 175);
|
||||||
|
g.drawString('nearby', 125, 200);
|
||||||
|
}
|
||||||
|
}, {timeout : 1000, filters : [{services: ['fd6f'] }] });
|
||||||
|
}
|
||||||
|
setInterval(FindFD6FBeacons, 2000);
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -23,3 +23,4 @@
|
||||||
0.21: Fix HRM setting
|
0.21: Fix HRM setting
|
||||||
0.22: Respect Quiet Mode
|
0.22: Respect Quiet Mode
|
||||||
0.23: Allow notification dismiss to remove from phone too
|
0.23: Allow notification dismiss to remove from phone too
|
||||||
|
0.24: tag HRM power requests to allow this to work alongside other widgets/apps (fix #799)
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
if (activityInterval)
|
if (activityInterval)
|
||||||
clearInterval(activityInterval);
|
clearInterval(activityInterval);
|
||||||
activityInterval = undefined;
|
activityInterval = undefined;
|
||||||
if (s.hrm) Bangle.setHRMPower(1);
|
if (s.hrm) Bangle.setHRMPower(1,"gbr");
|
||||||
if (s.hrm) {
|
if (s.hrm) {
|
||||||
if (realtime) {
|
if (realtime) {
|
||||||
// if realtime reporting, leave HRM on and use that to trigger events
|
// if realtime reporting, leave HRM on and use that to trigger events
|
||||||
|
|
@ -138,7 +138,7 @@
|
||||||
hrmTimeout = 5;
|
hrmTimeout = 5;
|
||||||
activityInterval = setInterval(function() {
|
activityInterval = setInterval(function() {
|
||||||
hrmTimeout = 5;
|
hrmTimeout = 5;
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1,"gbr");
|
||||||
}, interval*1000);
|
}, interval*1000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -281,7 +281,7 @@
|
||||||
if (hrmTimeout!==undefined) hrmTimeout--;
|
if (hrmTimeout!==undefined) hrmTimeout--;
|
||||||
if (ok || hrmTimeout<=0) {
|
if (ok || hrmTimeout<=0) {
|
||||||
if (hrmTimeout!==undefined)
|
if (hrmTimeout!==undefined)
|
||||||
Bangle.setHRMPower(0);
|
Bangle.setHRMPower(0,"gbr");
|
||||||
sendActivity(hrm.confidence>20 ? hrm.bpm : -1);
|
sendActivity(hrm.confidence>20 ? hrm.bpm : -1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,4 @@
|
||||||
Reduce memory usage by ~30%
|
Reduce memory usage by ~30%
|
||||||
Generate scale based on defined minimum and maximum measurement
|
Generate scale based on defined minimum and maximum measurement
|
||||||
Added background line on 50% to ease estimation of drawn values
|
Added background line on 50% to ease estimation of drawn values
|
||||||
|
0.06: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,12 @@
|
||||||
if (settings.isRecording) {
|
if (settings.isRecording) {
|
||||||
WIDGETS["heart"].width = 24;
|
WIDGETS["heart"].width = 24;
|
||||||
Bangle.on('HRM',onHRM);
|
Bangle.on('HRM',onHRM);
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1,"heart");
|
||||||
var n = settings.fileNbr.toString(36);
|
var n = settings.fileNbr.toString(36);
|
||||||
recFile = require("Storage").open(".heart"+n,"a");
|
recFile = require("Storage").open(".heart"+n,"a");
|
||||||
} else {
|
} else {
|
||||||
WIDGETS["heart"].width = 0;
|
WIDGETS["heart"].width = 0;
|
||||||
Bangle.setHRMPower(0);
|
Bangle.setHRMPower(0,"heart");
|
||||||
recFile = undefined;
|
recFile = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,4 @@
|
||||||
0.05: Add display for the next strike time
|
0.05: Add display for the next strike time
|
||||||
0.06: Move the next strike time to the first row of display
|
0.06: Move the next strike time to the first row of display
|
||||||
0.07: Change the boot function to avoid reloading the entire watch
|
0.07: Change the boot function to avoid reloading the entire watch
|
||||||
|
0.08: Default to no strikes. Fix file-not-found issue during the first boot. Add data file.
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,10 @@
|
||||||
const storage = require('Storage');
|
const storage = require('Storage');
|
||||||
let settings;
|
var settings = storage.readJSON('hourstrike.json', 1);
|
||||||
|
|
||||||
function updateSettings() {
|
function updateSettings() {
|
||||||
storage.write('hourstrike.json', settings);
|
storage.write('hourstrike.json', settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSettings() {
|
|
||||||
settings = {
|
|
||||||
interval: 3600,
|
|
||||||
start: 9,
|
|
||||||
end: 21,
|
|
||||||
vlevel: 0.5,
|
|
||||||
next_hour: -1,
|
|
||||||
next_minute: -1,
|
|
||||||
};
|
|
||||||
updateSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = storage.readJSON('hourstrike.json', 1);
|
|
||||||
if (!settings) resetSettings();
|
|
||||||
|
|
||||||
function showMainMenu() {
|
function showMainMenu() {
|
||||||
var mode_txt = ['Off','1 min','5 min','10 min','1/4 h','1/2 h','1 h'];
|
var mode_txt = ['Off','1 min','5 min','10 min','1/4 h','1/2 h','1 h'];
|
||||||
var mode_interval = [-1,60,300,600,900,1800,3600];
|
var mode_interval = [-1,60,300,600,900,1800,3600];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
(function() {
|
(function() {
|
||||||
function setup () {
|
function setup () {
|
||||||
var settings = require('Storage').readJSON('hourstrike.json',1)||[];
|
var settings = require('Storage').readJSON('hourstrike.json',1);
|
||||||
var t = new Date();
|
var t = new Date();
|
||||||
var t_min_sec = t.getMinutes()*60+t.getSeconds();
|
var t_min_sec = t.getMinutes()*60+t.getSeconds();
|
||||||
var wait_msec = settings.interval>0?(settings.interval-t_min_sec%settings.interval)*1000:-1;
|
var wait_msec = settings.interval>0?(settings.interval-t_min_sec%settings.interval)*1000:-1;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"interval":-1,"start":9,"end":21,"vlevel":0.5,"next_hour":-1,"next_minute":-1}
|
||||||
|
|
@ -7,3 +7,4 @@
|
||||||
0.07: Don't clear all intervals during initialisation
|
0.07: Don't clear all intervals during initialisation
|
||||||
0.08: Use Bangle.setUI for button/launcher handling
|
0.08: Use Bangle.setUI for button/launcher handling
|
||||||
0.09: fix font size for latest firmwares
|
0.09: fix font size for latest firmwares
|
||||||
|
0.10: Configure the side text direction based on the wrist on which you wear your watch
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ A readable and informational digital watch, with date, seconds and moon phase an
|
||||||
- Readable
|
- Readable
|
||||||
- Informative: hours, minutes, secondsa, date, year and moon phase
|
- Informative: hours, minutes, secondsa, date, year and moon phase
|
||||||
- Pairs nicely with any other apps: in setting > large clock any installed app can be assigned to BTN1 and BTN3 in order to open it easily directly from the watch, without the hassle of passing trough the launcher. For example BTN1 can be assigned to alarm and BTN3 to chronometer.
|
- Pairs nicely with any other apps: in setting > large clock any installed app can be assigned to BTN1 and BTN3 in order to open it easily directly from the watch, without the hassle of passing trough the launcher. For example BTN1 can be assigned to alarm and BTN3 to chronometer.
|
||||||
|
- Configure the text direction on the side depending on the wrist on which you wear your watch.
|
||||||
|
|
||||||
## How to use it
|
## How to use it
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ const settings = require("Storage").readJSON("largeclock.json", 1)||{};
|
||||||
const BTN1app = settings.BTN1 || "";
|
const BTN1app = settings.BTN1 || "";
|
||||||
const BTN3app = settings.BTN3 || "";
|
const BTN3app = settings.BTN3 || "";
|
||||||
|
|
||||||
|
const right_hand = !!settings.right_hand;
|
||||||
|
const rotation = right_hand ? 3 : 1;
|
||||||
|
|
||||||
function drawMoon(d) {
|
function drawMoon(d) {
|
||||||
const BLACK = 0,
|
const BLACK = 0,
|
||||||
MOON = 0x41f,
|
MOON = 0x41f,
|
||||||
|
|
@ -145,9 +148,9 @@ function drawTime(d) {
|
||||||
g.setColor(1, 50, 1);
|
g.setColor(1, 50, 1);
|
||||||
g.drawString(minutes, 40, 130, true);
|
g.drawString(minutes, 40, 130, true);
|
||||||
g.setFont("Vector", 20);
|
g.setFont("Vector", 20);
|
||||||
g.setRotation(3);
|
g.setRotation(rotation);
|
||||||
g.drawString(`${dow} ${day} ${month}`, 60, 10, true);
|
g.drawString(`${dow} ${day} ${month}`, 60, right_hand?10:205, true);
|
||||||
g.drawString(year, is12Hour ? 46 : 75, 205, true);
|
g.drawString(year, is12Hour?(right_hand?56:120):(right_hand?85:115), right_hand?205:10, true);
|
||||||
lastMinutes = minutes;
|
lastMinutes = minutes;
|
||||||
}
|
}
|
||||||
g.setRotation(0);
|
g.setRotation(0);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
"BTN1": "",
|
"BTN1": "",
|
||||||
"BTN3": ""
|
"BTN3": "",
|
||||||
|
"right_hand": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@
|
||||||
|
|
||||||
const settings = s.readJSON("largeclock.json", 1) || {
|
const settings = s.readJSON("largeclock.json", 1) || {
|
||||||
BTN1: "",
|
BTN1: "",
|
||||||
BTN3: ""
|
BTN3: "",
|
||||||
|
right_hand: false
|
||||||
};
|
};
|
||||||
|
|
||||||
function showApps(btn) {
|
function showApps(btn) {
|
||||||
|
|
@ -67,10 +68,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainMenu = {
|
const mainMenu = {
|
||||||
"": { title: "Large Clock Settings" },
|
"": { title: "Large Clock" },
|
||||||
"< Back": back,
|
"< Back": back,
|
||||||
"BTN1 app": () => showApps("BTN1"),
|
"BTN1 app": () => showApps("BTN1"),
|
||||||
"BTN3 app": () => showApps("BTN3")
|
"BTN3 app": () => showApps("BTN3"),
|
||||||
|
"On right hand": {
|
||||||
|
value: !!settings.right_hand,
|
||||||
|
format: v=>v?"Yes":"No",
|
||||||
|
onchange: v=>{
|
||||||
|
settings.right_hand = v;
|
||||||
|
s.writeJSON("largeclock.json", settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
E.showMenu(mainMenu);
|
E.showMenu(mainMenu);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@
|
||||||
BTN2 now goes to menu on release
|
BTN2 now goes to menu on release
|
||||||
0.10: Add birthday style
|
0.10: Add birthday style
|
||||||
0.11: Skip double buffering, use 240x240 size
|
0.11: Skip double buffering, use 240x240 size
|
||||||
|
0.12: Fix swipe direction (#800)
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,7 @@ function move(dir) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Bangle.on('swipe',move);
|
Bangle.on('swipe', dir => move(-dir));
|
||||||
setWatch(()=>move(1), BTN3, {repeat:true});
|
setWatch(()=>move(1), BTN3, {repeat:true});
|
||||||
setWatch(()=>{
|
setWatch(()=>{
|
||||||
// If we're on the last page
|
// If we're on the last page
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@
|
||||||
0.06: Add support for scrolling, option for 3 bit maps
|
0.06: Add support for scrolling, option for 3 bit maps
|
||||||
0.07: Move to 96px tiles - less files (64 -> 25) and speed up rendering
|
0.07: Move to 96px tiles - less files (64 -> 25) and speed up rendering
|
||||||
0.08: Update for drag event refactor
|
0.08: Update for drag event refactor
|
||||||
|
0.09: Use current theme cols when drawing GPS info
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,7 @@ function drawMarker() {
|
||||||
var fix;
|
var fix;
|
||||||
Bangle.on('GPS',function(f) {
|
Bangle.on('GPS',function(f) {
|
||||||
fix=f;
|
fix=f;
|
||||||
g.clearRect(0,y1,240,y1+8);
|
g.reset().clearRect(0,y1,240,y1+8).setFont("6x8").setFontAlign(0,0);
|
||||||
g.setColor(1,1,1);
|
|
||||||
g.setFont("6x8");
|
|
||||||
g.setFontAlign(0,0);
|
|
||||||
var txt = fix.satellites+" satellites";
|
var txt = fix.satellites+" satellites";
|
||||||
if (!fix.fix)
|
if (!fix.fix)
|
||||||
txt += " - NO FIX";
|
txt += " - NO FIX";
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: First release
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Pastel Clock - a configurable clock with custom fonts and background
|
||||||
|
|
||||||
|
* Designed specifically for Bangle 1 and Bangle 2
|
||||||
|
* A choice of 5 different custom fonts
|
||||||
|
* Supports the Light and Dark themes
|
||||||
|
* Has a settings menu, change font, enable/disable the grid and the date display
|
||||||
|
|
||||||
|
|
||||||
|
I came up with the name Pastel due to the shade of the grid background.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4A1PYIqqAAUqp0kGMopCuVOvHHAAV4GEcAFQgAFuQwhLoIuJAAMqGEEAzovL44vgkguM49OGD0ApwvNzovfdpSQjF+FyEwskewyPFgAADX7kkA414EwIqCp14zmcuUkGJ5FEkgnFpwHHFgXBTQ0kFhpSBvAABD4KHHEY0ATAw7EFpTnHRA8Azg2FFxIvMCxGcHI0qG4kAlQuJF5gXJcIIwEvARDlSVGfZAwJuQWKSQwuCRpQAB4IvK/0kC5K0BGAouHzucvAIFL5bvHvAbCGAMkEAQuEzi0BAAZ9FX5QuH48kDgILB4IFCAAPB4IsCklySZIvKUxWdLYYABpyhBuUkIxAADHoJfSJI140SOBzgTNFxQwPQ4QuBAgK1FOoyiCF5QwBpwaIzgZBzlydgT2BCZGcHwMrmMHGJjnDAAd4JAImCvDRDGAIKBvF4uTJBA4MrrtjrljrowOAAIZCEQPBFQTuBHgpXBCYkGnBcCmVjsaTOJYOcFgZ+BeAXHkiRBAAprBrkxFAM4MAMyMgIvMkjaGKQICBMQUqfxInBlZ+DMIIGBF5QgGgAHBWQLCGAAnBgC5BFIUrR4KQNF4wGCki+DgCQHBwMySQNcFQMHSQKPTFQ4vJlS/BrhaBMgIwBd5ofHXwYALXALuCGQcxHAIvLznBD4q6JX41jmIvCdoKOBMB0ASQtyTJIADp0GdIVjgwvBMYQvMGISzEL5ucRIQAIg4vOzmdzpdBM4NOZA1OlUqBoUrFIZeCAAUrF5qSCAAIGDlSIEBgoNBGAVdBQMGmJoBgwvOG5JZBFgoNFLoIMCJgwxXO5wpYAH4A/AH4A/ABwA="))
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
require("Storage").write("pastel.info",{
|
||||||
|
"id":"pastel",
|
||||||
|
"name":"Configurable clock with custom fonts and background",
|
||||||
|
"src":"pastel.app.js",
|
||||||
|
"icon":"pastel.img",
|
||||||
|
"type":"clock"
|
||||||
|
});
|
||||||
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -0,0 +1,56 @@
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = "pastel.json";
|
||||||
|
|
||||||
|
// initialize with default settings...
|
||||||
|
let s = {
|
||||||
|
'grid': false,
|
||||||
|
'date': false,
|
||||||
|
'font': "Lato"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and overwrite them with any saved values
|
||||||
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
const storage = require('Storage')
|
||||||
|
let settings = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||||
|
const saved = settings || {}
|
||||||
|
for (const key in saved) {
|
||||||
|
s[key] = saved[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
settings = s
|
||||||
|
storage.write(SETTINGS_FILE, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
var font_options = ["Lato","Architect","GochiHand","CabinSketch","Orbitron"];
|
||||||
|
|
||||||
|
E.showMenu({
|
||||||
|
'': { 'title': 'Pastel Clock' },
|
||||||
|
'Font': {
|
||||||
|
value: 0 | font_options.indexOf(s.font),
|
||||||
|
min: 0, max: 4,
|
||||||
|
format: v => font_options[v],
|
||||||
|
onchange: v => {
|
||||||
|
s.font = font_options[v];
|
||||||
|
save();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Show Grid': {
|
||||||
|
value: s.grid,
|
||||||
|
format: () => (s.grid ? 'Yes' : 'No'),
|
||||||
|
onchange: () => {
|
||||||
|
s.grid = !s.grid
|
||||||
|
save()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Show Date': {
|
||||||
|
value: s.date,
|
||||||
|
format: () => (s.date ? 'Yes' : 'No'),
|
||||||
|
onchange: () => {
|
||||||
|
s.date = !s.date
|
||||||
|
save()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'< Back': back,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
|
@ -0,0 +1,6 @@
|
||||||
|
const SETTINGS_FILE = "pastel.json";
|
||||||
|
let settings = {"grid":false, "date":false, "font":"Lato"}
|
||||||
|
|
||||||
|
require("Storage").write(SETTINGS_FILE, settings);
|
||||||
|
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{};
|
||||||
|
console.log(settings);
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Shortcuts
|
||||||
|
|
||||||
|
Any installed app can be assigned to BTN1 and BTN3 and launched directly from compatible watch faces. This works with any watch face that uses `Bangle.setUI("clock")`.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
<a target="_blank" href="https://icons8.com/icon/i1z7pQ2orcJk/shortcut">Shortcut</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
|
||||||
|
After Width: | Height: | Size: 767 B |
|
|
@ -0,0 +1,15 @@
|
||||||
|
(function() {
|
||||||
|
var sui = Bangle.setUI;
|
||||||
|
Bangle.setUI = function(mode, cb) {
|
||||||
|
if (mode!="clock") return sui(mode,cb);
|
||||||
|
return sui("clockupdown", (dir) => {
|
||||||
|
let settings = require("Storage").readJSON("shortcuts.json", 1)||{};
|
||||||
|
if (dir == -1) {
|
||||||
|
if (settings.BTN1) load(settings.BTN1);
|
||||||
|
} else if (dir == 1) {
|
||||||
|
if (settings.BTN3) load(settings.BTN3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
(function(back) {
|
||||||
|
const s = require("Storage");
|
||||||
|
const apps = s
|
||||||
|
.list(/\.info$/)
|
||||||
|
.map(app => {
|
||||||
|
var a = s.readJSON(app, 1);
|
||||||
|
return a && (a.type=="app" || a.type=="clock" || !a.type) && {n: a.name, src: a.src};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
apps.sort((a, b) => {
|
||||||
|
if (a.n < b.n) return -1;
|
||||||
|
if (a.n > b.n) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
apps.push({n: "NONE", src: null});
|
||||||
|
|
||||||
|
const settings = s.readJSON("shortcuts.json", 1) || {
|
||||||
|
BTN1: null,
|
||||||
|
BTN3: null
|
||||||
|
};
|
||||||
|
|
||||||
|
function showApps(btn) {
|
||||||
|
function format(v) {
|
||||||
|
return v === settings[btn] ? "*" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onchange(v) {
|
||||||
|
settings[btn] = v;
|
||||||
|
s.writeJSON("shortcuts.json", settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnMenu = {
|
||||||
|
"": {
|
||||||
|
title: `Apps for ${btn}`
|
||||||
|
},
|
||||||
|
"< Back": () => E.showMenu(mainMenu)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (apps.length > 0) {
|
||||||
|
for (let a of apps) {
|
||||||
|
btnMenu[a.n] = {
|
||||||
|
value: a.src,
|
||||||
|
format: format,
|
||||||
|
onchange: onchange
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
btnMenu["...No Apps..."] = {
|
||||||
|
value: undefined,
|
||||||
|
format: () => "",
|
||||||
|
onchange: () => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu(btnMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainMenu = {
|
||||||
|
"": { title: "Shortcuts Settings" },
|
||||||
|
"< Back": back,
|
||||||
|
"BTN1 app": () => showApps("BTN1"),
|
||||||
|
"BTN3 app": () => showApps("BTN3")
|
||||||
|
};
|
||||||
|
E.showMenu(mainMenu);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.1: New watch face
|
||||||
|
0.2: Use Bangle.setUI for button/launcher handling
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwkEIf4AxgMhgUAiIHCmYKCmcgBAUCmQDEgUzkcg+QJBl//AYfzDgX///wAYcCmMzmQXC+c/AYM/kU/iEAgfzkfyAYcCAYM/HIUwC4QxCkEhgcwEYIDDgUwgcSC4QsBLQf/iATEAYcBiB3FC4cvKAIvINAMxgSGDC4UT/4ICC5EDkcjI40/mIHCgaNBC4IDCBAMDmUiXwU/mcQ/8zAYMyQ4M/RYQDBC4yxBIgIDDE4M//5FBAYasBifxf5QA/AC0iAALFDA4ICBgMhC5SBB//wA4gCBUoIXLXYKaBAAUjC54KJC6Ejmcxe4MAiczC4IJBmEjNwILBL4b5Bl7vCD4IEB+cCNgUP+A3EL4MyC4IkBiE/BoMD+cP+MvCoPygfxI4wXBA4UD+QZBh8wgacB/4ODC5YvC+EfC4JVBiAXLgP/n5JBZgcPSwgXIRYPz+cBQoIXBLwgARgU/c4gAQJYJeDF6TXBAH5OMmUBmcQkcgicxBQMTkBbDBIMCSAcTl8jmcxmXymczBQIECCIQEBkbDBAAUfFgMwgPymUDiUg+EjiUwgUhBIMQC4cCfgIXBeAINBicwC4JBBgY7BgcAC4cfkEDkUx+UAmUjBQPxmZZDBIQXDl//kQBC+UvDQIKBmM//4FCBYP/bX4A/ACIA="))
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
const is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||||
|
const locale = require("locale");
|
||||||
|
|
||||||
|
function padNum(n, l) {
|
||||||
|
return ("0".repeat(l)+n).substr(-l);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rects = {};
|
||||||
|
let rectsToClear = {};
|
||||||
|
let commands = [];
|
||||||
|
|
||||||
|
function pushCommand(command) {
|
||||||
|
let hash = E.CRC32(E.toJS(arguments));
|
||||||
|
if (!delete rectsToClear[hash]) {
|
||||||
|
commands.push({hash: hash, command: Function.apply.bind(command, null, arguments.slice(1))});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeCommands() {
|
||||||
|
"ram";
|
||||||
|
for (let hash in rectsToClear) delete rects[hash];
|
||||||
|
for (let r of rectsToClear) if (r) g.clearRect(r.x1, r.y1, r.x2, r.y2);
|
||||||
|
g.getModified(true);
|
||||||
|
for (let c of commands) {
|
||||||
|
c.command();
|
||||||
|
rects[c.hash] = g.getModified(true);
|
||||||
|
}
|
||||||
|
rectsToClear = Object.assign({}, rects);
|
||||||
|
commands = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawVectorText(text, size, x, y, alignX, alignY) {
|
||||||
|
g.setFont("Vector", size).setFontAlign(alignX, alignY).drawString(text, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset();
|
||||||
|
|
||||||
|
let d = new Date();
|
||||||
|
let hours = is12Hour ? ((d.getHours() + 11) % 12) + 1 : d.getHours();
|
||||||
|
let timeText = `${hours}:${padNum(d.getMinutes(), 2)}`;
|
||||||
|
let meridian = is12Hour ? ((d.getHours() < 12) ? "AM" : "PM") : "";
|
||||||
|
let secondsText = padNum(d.getSeconds(), 2);
|
||||||
|
let dowText = locale.dow(d);
|
||||||
|
let dateText = locale.date(d, true);
|
||||||
|
|
||||||
|
g.setFont("Vector", 256);
|
||||||
|
let timeFontSize = g.getWidth() / ((g.stringWidth(timeText) / 256) + (Math.max(g.stringWidth(meridian), g.stringWidth(secondsText)) / 512 * 9 / 10));
|
||||||
|
let dowFontSize = g.getWidth() / (g.stringWidth(dowText) / 256);
|
||||||
|
let dateFontSize = g.getWidth() / (g.stringWidth(dateText) / 256);
|
||||||
|
|
||||||
|
let timeHeight = g.setFont("Vector", timeFontSize).getFontHeight() * 9 / 10;
|
||||||
|
let dowHeight = g.setFont("Vector", dowFontSize).getFontHeight();
|
||||||
|
let dateHeight = g.setFont("Vector", dateFontSize).getFontHeight();
|
||||||
|
|
||||||
|
let remainingHeight = g.getHeight() - 24 - timeHeight - dowHeight - dateHeight;
|
||||||
|
let spacer = remainingHeight / 4;
|
||||||
|
|
||||||
|
let y = 24 + spacer;
|
||||||
|
|
||||||
|
pushCommand(drawVectorText, timeText, timeFontSize, 0, y, -1, -1);
|
||||||
|
pushCommand(drawVectorText, meridian, timeFontSize*9/20, g.getWidth(), y, 1, -1);
|
||||||
|
pushCommand(drawVectorText, secondsText, timeFontSize*9/20, g.getWidth(), y + timeHeight, 1, 1);
|
||||||
|
y += timeHeight + spacer;
|
||||||
|
|
||||||
|
pushCommand(drawVectorText, dowText, dowFontSize, g.getWidth()/2, y, 0, -1);
|
||||||
|
y += dowHeight + spacer;
|
||||||
|
|
||||||
|
pushCommand(drawVectorText, dateText, dateFontSize, g.getWidth()/2, y, 0, -1);
|
||||||
|
|
||||||
|
executeCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout;
|
||||||
|
|
||||||
|
function tick() {
|
||||||
|
draw();
|
||||||
|
timeout = setTimeout(tick, 1000 - getTime() % 1 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.on('lcdPower', function(on) {
|
||||||
|
if (timeout) clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
if (on) tick();
|
||||||
|
});
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
tick();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
Bangle.setUI("clock");
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -2,3 +2,5 @@
|
||||||
0.03: Fix flickering last updated time.
|
0.03: Fix flickering last updated time.
|
||||||
0.04: Adjust "weather unknown" message according to Bluetooth connection.
|
0.04: Adjust "weather unknown" message according to Bluetooth connection.
|
||||||
0.05: Add wind direction.
|
0.05: Add wind direction.
|
||||||
|
0.06: Use setUI for launcher.
|
||||||
|
0.07: Add theme support and unknown icon.
|
||||||
|
|
@ -12,12 +12,12 @@
|
||||||
function draw() {
|
function draw() {
|
||||||
let w = weather.current;
|
let w = weather.current;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(0).fillRect(0, 24, 239, 239);
|
g.clearRect(0, 24, 239, 239);
|
||||||
|
|
||||||
weather.drawIcon(w.txt, 65, 90, 55);
|
weather.drawIcon(w.txt, 65, 90, 55);
|
||||||
const locale = require("locale");
|
const locale = require("locale");
|
||||||
|
|
||||||
g.setColor(-1);
|
g.reset();
|
||||||
|
|
||||||
const temp = locale.temp(w.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
const temp = locale.temp(w.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||||
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
|
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
|
||||||
|
|
@ -54,8 +54,8 @@
|
||||||
if (!weather.current || !weather.current.time) return;
|
if (!weather.current || !weather.current.time) return;
|
||||||
let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`;
|
let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(0).fillRect(0, 202, 239, 210);
|
g.clearRect(0, 202, 239, 210);
|
||||||
g.setColor(-1).setFont("6x8", 1).setFontAlign(0, 0, 0);
|
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||||
g.drawString(text, 120, 206);
|
g.drawString(text, 120, 206);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
update();
|
update();
|
||||||
|
|
||||||
// Show launcher when middle button pressed
|
// Show launcher when middle button pressed
|
||||||
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'});
|
Bangle.setUI("clock");
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,13 @@ setCurrentWeather(storage.readJSON('weather.json')||{});
|
||||||
|
|
||||||
exports.drawIcon = function(cond, x, y, r) {
|
exports.drawIcon = function(cond, x, y, r) {
|
||||||
function drawSun(x, y, r) {
|
function drawSun(x, y, r) {
|
||||||
g.setColor("#FF7700");
|
g.setColor(g.theme.dark ? "#FE0" : "#FC0");
|
||||||
g.fillCircle(x, y, r);
|
g.fillCircle(x, y, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCloud(x, y, r, c) {
|
function drawCloud(x, y, r, c) {
|
||||||
const u = r/12;
|
const u = r/12;
|
||||||
if (c==null) c = "#EEEEEE";
|
if (c==null) c = g.theme.dark ? "#BBB" : "#AAA";
|
||||||
g.setColor(c);
|
g.setColor(c);
|
||||||
g.fillCircle(x-8*u, y+3*u, 4*u);
|
g.fillCircle(x-8*u, y+3*u, 4*u);
|
||||||
g.fillCircle(x-4*u, y-2*u, 5*u);
|
g.fillCircle(x-4*u, y-2*u, 5*u);
|
||||||
|
|
@ -91,7 +91,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBrokenClouds(x, y, r) {
|
function drawBrokenClouds(x, y, r) {
|
||||||
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777777");
|
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777");
|
||||||
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,24 +101,25 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawRainLines(x, y, r) {
|
function drawRainLines(x, y, r) {
|
||||||
g.setColor("#FFFFFF");
|
g.setColor(g.theme.dark ? "#0CF" : "#07F");
|
||||||
const y1 = y+1/2*r;
|
const y1 = y+1/2*r;
|
||||||
const y2 = y+1*r;
|
const y2 = y+1*r;
|
||||||
g.fillPoly([
|
|
||||||
x-6/12*r+1, y1,
|
g.fillPolyAA([
|
||||||
x-8/12*r+1, y2,
|
x-6/12*r, y1,
|
||||||
|
x-8/12*r, y2,
|
||||||
x-7/12*r, y2,
|
x-7/12*r, y2,
|
||||||
x-5/12*r, y1,
|
x-5/12*r, y1,
|
||||||
]);
|
]);
|
||||||
g.fillPoly([
|
g.fillPolyAA([
|
||||||
x-2/12*r+1, y1,
|
x-2/12*r, y1,
|
||||||
x-4/12*r+1, y2,
|
x-4/12*r, y2,
|
||||||
x-3/12*r, y2,
|
x-3/12*r, y2,
|
||||||
x-1/12*r, y1,
|
x-1/12*r, y1,
|
||||||
]);
|
]);
|
||||||
g.fillPoly([
|
g.fillPolyAA([
|
||||||
x+2/12*r+1, y1,
|
x+2/12*r, y1,
|
||||||
x+0/12*r+1, y2,
|
x+0/12*r, y2,
|
||||||
x+1/12*r, y2,
|
x+1/12*r, y2,
|
||||||
x+3/12*r, y1,
|
x+3/12*r, y1,
|
||||||
]);
|
]);
|
||||||
|
|
@ -136,7 +137,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
|
|
||||||
function drawThunderstorm(x, y, r) {
|
function drawThunderstorm(x, y, r) {
|
||||||
function drawLightning(x, y, r) {
|
function drawLightning(x, y, r) {
|
||||||
g.setColor("#FF7700");
|
g.setColor(g.theme.dark ? "#FE0" : "#FC0");
|
||||||
g.fillPoly([
|
g.fillPoly([
|
||||||
x-2/6*r, y-r,
|
x-2/6*r, y-r,
|
||||||
x-4/6*r, y+1/6*r,
|
x-4/6*r, y+1/6*r,
|
||||||
|
|
@ -164,7 +165,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.setColor("#FFFFFF");
|
g.setColor(g.theme.dark ? "#FFF" : "#CCC");
|
||||||
const w = 1/12*r;
|
const w = 1/12*r;
|
||||||
for(let i = 0; i<=6; ++i) {
|
for(let i = 0; i<=6; ++i) {
|
||||||
const points = [
|
const points = [
|
||||||
|
|
@ -199,7 +200,7 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
[-0.2, 0.3],
|
[-0.2, 0.3],
|
||||||
];
|
];
|
||||||
|
|
||||||
g.setColor("#FFFFFF");
|
g.setColor(g.theme.dark ? "#FFF" : "#CCC");
|
||||||
for(let i = 0; i<5; ++i) {
|
for(let i = 0; i<5; ++i) {
|
||||||
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
||||||
y+(0.4*i-0.7)*r-1);
|
y+(0.4*i-0.7)*r-1);
|
||||||
|
|
@ -208,6 +209,11 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drawUnknown(x, y, r) {
|
||||||
|
drawCloud(x, y, r, "#777");
|
||||||
|
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
||||||
|
}
|
||||||
|
|
||||||
function chooseIcon(condition) {
|
function chooseIcon(condition) {
|
||||||
if (!condition) return () => {};
|
if (!condition) return () => {};
|
||||||
condition = condition.toLowerCase();
|
condition = condition.toLowerCase();
|
||||||
|
|
@ -225,8 +231,19 @@ exports.drawIcon = function(cond, x, y, r) {
|
||||||
if (condition.includes("few clouds")) return drawFewClouds;
|
if (condition.includes("few clouds")) return drawFewClouds;
|
||||||
if (condition.includes("scattered clouds")) return drawCloud;
|
if (condition.includes("scattered clouds")) return drawCloud;
|
||||||
if (condition.includes("clouds")) return drawBrokenClouds;
|
if (condition.includes("clouds")) return drawBrokenClouds;
|
||||||
|
if (condition.includes("mist") ||
|
||||||
|
condition.includes("smoke") ||
|
||||||
|
condition.includes("haze") ||
|
||||||
|
condition.includes("sand") ||
|
||||||
|
condition.includes("dust") ||
|
||||||
|
condition.includes("fog") ||
|
||||||
|
condition.includes("ash") ||
|
||||||
|
condition.includes("squalls") ||
|
||||||
|
condition.includes("tornado")) {
|
||||||
return drawMist;
|
return drawMist;
|
||||||
}
|
}
|
||||||
|
return drawUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
chooseIcon(cond)(x, y, r);
|
chooseIcon(cond)(x, y, r);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,16 @@
|
||||||
const w = weather.current;
|
const w = weather.current;
|
||||||
if (!w) return;
|
if (!w) return;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setColor(0).fillRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
||||||
if (w.txt) {
|
if (w.txt) {
|
||||||
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
|
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
|
||||||
}
|
}
|
||||||
if (w.temp) {
|
if (w.temp) {
|
||||||
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
||||||
t = t.match(/[\d\-]*/)[0]; // but we have no room for units
|
t = t.match(/[\d\-]*/)[0]; // but we have no room for units
|
||||||
|
g.reset();
|
||||||
g.setFontAlign(0, 1); // center horizontally at bottom of widget
|
g.setFontAlign(0, 1); // center horizontally at bottom of widget
|
||||||
g.setFont('6x8', 1);
|
g.setFont('6x8', 1);
|
||||||
g.setColor(-1);
|
|
||||||
g.drawString(t, this.x+10, this.y+24);
|
g.drawString(t, this.x+10, this.y+24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@
|
||||||
BTN2 now goes to menu on release
|
BTN2 now goes to menu on release
|
||||||
0.10: Tweaks to reduce memory usage
|
0.10: Tweaks to reduce memory usage
|
||||||
0.11: Fix initial screen fill colour
|
0.11: Fix initial screen fill colour
|
||||||
|
0.12: Fix swipe direction (#800)
|
||||||
|
|
|
||||||
|
|
@ -280,7 +280,7 @@ function move(dir) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Bangle.on('swipe',move);
|
Bangle.on('swipe', dir => move(-dir));
|
||||||
setWatch(()=>move(1), BTN3, {repeat:true});
|
setWatch(()=>move(1), BTN3, {repeat:true});
|
||||||
setWatch(()=>{
|
setWatch(()=>{
|
||||||
// If we're on the last page
|
// If we're on the last page
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,4 @@
|
||||||
0.09: Fix regression stopping correct widget updates
|
0.09: Fix regression stopping correct widget updates
|
||||||
0.10: Add 'hide if charge greater than'
|
0.10: Add 'hide if charge greater than'
|
||||||
0.11: Don't overwrite existing settings on app update
|
0.11: Don't overwrite existing settings on app update
|
||||||
|
0.12: Fixed for Bangle 2
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Battery Widget
|
||||||
|
|
||||||
|
Show the current battery level and charging status in the top right of the clock, with charge percentage
|
||||||
|
|
||||||
|
Works with Bangle 1 and Bangle 2
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -1,10 +1,23 @@
|
||||||
(function(){
|
(function(){
|
||||||
const COLORS = {
|
let COLORS = {};
|
||||||
'white': -1,
|
|
||||||
'charging': 0x07E0, // "Green"
|
if (process.env.HWVERSION == 1) {
|
||||||
'high': 0x05E0, // slightly darker green
|
COLORS = {
|
||||||
'ok': 0xFD20, // "Orange"
|
'white': -1, // White
|
||||||
'low':0xF800, // "Red"
|
'charging': 0x07E0, // Green
|
||||||
|
'high': 0x05E0, // lightly darker green
|
||||||
|
'ok': 0xFD20, // Orange
|
||||||
|
'low': 0xF800, // Red
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// bangle 2 is only 7 bit colors
|
||||||
|
COLORS = {
|
||||||
|
'white': "#fff", // White
|
||||||
|
'charging': "#0f0", // Green
|
||||||
|
'high': "#0f0", // Green
|
||||||
|
'ok': "#ff0", // Orange
|
||||||
|
'low': "#f00", // Red
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const SETTINGS_FILE = 'widbatpc.json'
|
const SETTINGS_FILE = 'widbatpc.json'
|
||||||
|
|
||||||
|
|
@ -66,29 +79,28 @@
|
||||||
// else...
|
// else...
|
||||||
var s = 39;
|
var s = 39;
|
||||||
var x = this.x, y = this.y;
|
var x = this.x, y = this.y;
|
||||||
const l = E.getBattery(),
|
const l = E.getBattery();
|
||||||
c = levelColor(l);
|
|
||||||
const xl = x+4+l*(s-12)/100
|
const xl = x+4+l*(s-12)/100
|
||||||
|
c = levelColor(l);
|
||||||
|
|
||||||
if (Bangle.isCharging() && setting('charger')) {
|
if (Bangle.isCharging() && setting('charger')) {
|
||||||
g.setColor(chargerColor()).drawImage(atob(
|
g.setColor(chargerColor()).drawImage(atob(
|
||||||
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
"DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
|
||||||
x+=16;
|
x+=16;
|
||||||
}
|
}
|
||||||
g.setColor(-1);
|
g.setColor(g.theme.fg);
|
||||||
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);
|
||||||
|
|
||||||
g.setColor(c).fillRect(x+4,y+6,xl,y+17);
|
g.setColor(c).fillRect(x+4,y+6,xl,y+17);
|
||||||
g.setColor(-1);
|
g.setColor(g.theme.fg);
|
||||||
if (!setting('percentage')) {
|
if (!setting('percentage')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let gfx = g
|
let gfx = g
|
||||||
if (setting('color') === 'Monochrome') {
|
if (setting('color') === 'Monochrome') {
|
||||||
// draw text inverted on battery level
|
// draw text inverted on battery level
|
||||||
gfx = Graphics.createCallback(240, 240, 1,
|
gfx = Graphics.createCallback(g.getWidth(),g.getHeight(), 1,
|
||||||
(x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
|
(x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
|
||||||
}
|
}
|
||||||
gfx.setFontAlign(-1,-1);
|
gfx.setFontAlign(-1,-1);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
0.01: Fork of widclk v0.04 github.com/espruino/BangleApps/tree/master/apps/widclk
|
||||||
|
0.02: Modification for bottom widget area and text color
|
||||||
|
0.03: based in widclk v0.05 compatible at same time, bottom area and color
|
||||||
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Digital clock widget (bottom widget area)
|
||||||
|
This very basic widget clock allows to test the unfrequently used widget bottom area.
|
||||||
|
|
||||||
|
forked from
|
||||||
|
https://github.com/espruino/BangleApps/tree/master/apps/widclk
|
||||||
|
|
||||||
|
## Photo
|
||||||
|
|
||||||
|
Example of usage
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Upload the widget file
|
||||||
|
Open an app that supports displaying widgets
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
This app is so basic that probably the easiest is to just edit the code ;)
|
||||||
|
|
||||||
|
Otherwise you can contact me [here](https://github.com/dapgo)
|
||||||
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 660 B |
|
|
@ -0,0 +1,31 @@
|
||||||
|
(function() {
|
||||||
|
// don't show widget if we know we have a clock app running
|
||||||
|
if (Bangle.CLOCK) return;
|
||||||
|
|
||||||
|
let intervalRef = null;
|
||||||
|
var width = 5 * 6*2;
|
||||||
|
var text_color=0x07FF;//cyan
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
g.reset().setFont("6x8", 2).setFontAlign(-1, 0).setColor(text_color);
|
||||||
|
var time = require("locale").time(new Date(),1);
|
||||||
|
g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60
|
||||||
|
}
|
||||||
|
function clearTimers(){
|
||||||
|
if(intervalRef) {
|
||||||
|
clearInterval(intervalRef);
|
||||||
|
intervalRef = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function startTimers(){
|
||||||
|
intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000);
|
||||||
|
WIDGETS["wdclkbttm"].draw();
|
||||||
|
}
|
||||||
|
Bangle.on('lcdPower', (on) => {
|
||||||
|
clearTimers();
|
||||||
|
if (on) startTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
WIDGETS["wdclkbttm"]={area:"br",width:width,draw:draw};
|
||||||
|
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclkbttm"].draw(), 60*1000);
|
||||||
|
})()
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New Widget!
|
0.01: New Widget!
|
||||||
0.02: Tweaks for variable size widget system
|
0.02: Tweaks for variable size widget system
|
||||||
0.03: Ensure redrawing works with variable size widget system
|
0.03: Ensure redrawing works with variable size widget system
|
||||||
|
0.04: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,12 @@
|
||||||
// redraw when the LCD turns on
|
// redraw when the LCD turns on
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1,"widhrm");
|
||||||
firstBPM = true;
|
firstBPM = true;
|
||||||
currentBPM = undefined;
|
currentBPM = undefined;
|
||||||
WIDGETS["hrm"].draw();
|
WIDGETS["hrm"].draw();
|
||||||
} else {
|
} else {
|
||||||
Bangle.setHRMPower(0);
|
Bangle.setHRMPower(0,"widhrm");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
}
|
}
|
||||||
WIDGETS["hrm"].draw();
|
WIDGETS["hrm"].draw();
|
||||||
});
|
});
|
||||||
Bangle.setHRMPower(Bangle.isLCDOn());
|
Bangle.setHRMPower(Bangle.isLCDOn(),"widhrm");
|
||||||
|
|
||||||
// add your widget
|
// add your widget
|
||||||
WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
|
WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,4 @@
|
||||||
0.05: Improved buzz timing and rendering
|
0.05: Improved buzz timing and rendering
|
||||||
0.06: Removed debug outputs, fixed rendering for upper limit, improved rendering for +/- icons, changelog version order fixed
|
0.06: Removed debug outputs, fixed rendering for upper limit, improved rendering for +/- icons, changelog version order fixed
|
||||||
0.07: Home button fixed and README added
|
0.07: Home button fixed and README added
|
||||||
|
0.08: tag HRM power requests to allow this ot work alongside other widgets/apps (fix #799)
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,7 @@ function resetHighlightTimeout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchOffApp(){
|
function switchOffApp(){
|
||||||
Bangle.setHRMPower(0);
|
Bangle.setHRMPower(0,"wohrm");
|
||||||
Bangle.showLauncher();
|
Bangle.showLauncher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,7 +306,7 @@ Bangle.on('lcdPower', (on) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.setHRMPower(1);
|
Bangle.setHRMPower(1,"wohrm");
|
||||||
Bangle.on('HRM', onHrm);
|
Bangle.on('HRM', onHrm);
|
||||||
|
|
||||||
setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
|
setWatch(incrementLimit, BTN1, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
|
|
||||||
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit 39f89acf1468dc7d58ac54d15a092b2bd6d73cce
|
Subproject commit 2aac601e38d659876eb7db5aebc7a12dd3c39da7
|
||||||
|
|
@ -11,7 +11,7 @@ if (window.location.host=="banglejs.com") {
|
||||||
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
|
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
|
||||||
}
|
}
|
||||||
|
|
||||||
var RECOMMENDED_VERSION = "2v09";
|
var RECOMMENDED_VERSION = "2v10";
|
||||||
// could check http://www.espruino.com/json/BANGLEJS.json for this
|
// could check http://www.espruino.com/json/BANGLEJS.json for this
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
```
|
```
|
||||||
var Layout = require("Layout");
|
var Layout = require("Layout");
|
||||||
var layout = new Layout( layoutObject, btns )
|
var layout = new Layout( layoutObject, btns, options )
|
||||||
layout.render(optionalObject);
|
layout.render(optionalObject);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -42,8 +41,8 @@ layoutObject has:
|
||||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
||||||
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
||||||
* A `pad` integer field to set pixels padding
|
* A `pad` integer field to set pixels padding
|
||||||
* A `fillx` boolean to choose if the object should fill available space in x
|
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
|
||||||
* A `filly` boolean to choose if the object should fill available space in y
|
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space
|
||||||
* `width` and `height` fields to optionally specify minimum size
|
* `width` and `height` fields to optionally specify minimum size
|
||||||
|
|
||||||
btns is an array of objects containing:
|
btns is an array of objects containing:
|
||||||
|
|
@ -52,6 +51,13 @@ btns is an array of objects containing:
|
||||||
* `cb` - a callback function
|
* `cb` - a callback function
|
||||||
* `cbl` - a callback function for long presses
|
* `cbl` - a callback function for long presses
|
||||||
|
|
||||||
|
options is an object containing:
|
||||||
|
|
||||||
|
* `lazy` - a boolean specifying whether to enable automatic lazy rendering
|
||||||
|
|
||||||
|
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
|
||||||
|
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
|
||||||
|
|
||||||
Once `layout.update()` is called, the following fields are added
|
Once `layout.update()` is called, the following fields are added
|
||||||
to each object:
|
to each object:
|
||||||
|
|
||||||
|
|
@ -69,17 +75,20 @@ Other functions:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
function Layout(layout, buttons) {
|
function Layout(layout, buttons, options) {
|
||||||
this._l = this.l = layout;
|
this._l = this.l = layout;
|
||||||
this.b = buttons;
|
this.b = buttons;
|
||||||
// Do we have >1 physical buttons?
|
// Do we have >1 physical buttons?
|
||||||
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
||||||
this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0;
|
this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
this.lazy = options.lazy || false;
|
||||||
|
|
||||||
if (buttons) {
|
if (buttons) {
|
||||||
if (this.physBtns >= buttons.length) {
|
if (this.physBtns >= buttons.length) {
|
||||||
// enough physical buttons
|
// enough physical buttons
|
||||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns);
|
let btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns);
|
||||||
if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch);
|
if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch);
|
||||||
Bangle.btnWatch = [];
|
Bangle.btnWatch = [];
|
||||||
if (this.physBtns > 2 && buttons.length==1)
|
if (this.physBtns > 2 && buttons.length==1)
|
||||||
|
|
@ -95,7 +104,7 @@ function Layout(layout, buttons) {
|
||||||
{type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
{type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
||||||
]};
|
]};
|
||||||
} else {
|
} else {
|
||||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
let btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
||||||
this._l.width = g.getWidth()-20; // button width
|
this._l.width = g.getWidth()-20; // button width
|
||||||
this._l = {type:"h", c: [
|
this._l = {type:"h", c: [
|
||||||
this._l,
|
this._l,
|
||||||
|
|
@ -104,7 +113,7 @@ function Layout(layout, buttons) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (process.env.HWVERSION==2) {
|
if (process.env.HWVERSION==2) {
|
||||||
Bangle.touchHandler = (_,e) => touchHandler(layout,e);
|
Bangle.touchHandler = function(_,e){touchHandler(layout,e)};
|
||||||
Bangle.on('touch',Bangle.touchHandler);
|
Bangle.on('touch',Bangle.touchHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,6 +121,7 @@ function Layout(layout, buttons) {
|
||||||
var ll = this;
|
var ll = this;
|
||||||
function idRecurser(l) {
|
function idRecurser(l) {
|
||||||
if (l.id) ll[l.id] = l;
|
if (l.id) ll[l.id] = l;
|
||||||
|
if (!l.type) l.type="";
|
||||||
if (l.c) l.c.forEach(idRecurser);
|
if (l.c) l.c.forEach(idRecurser);
|
||||||
}
|
}
|
||||||
idRecurser(layout);
|
idRecurser(layout);
|
||||||
|
|
@ -144,79 +154,41 @@ function touchHandler(l,e) {
|
||||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) {
|
||||||
|
if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") {
|
||||||
|
// Hash the layoutObject without including its children
|
||||||
|
let c = l.c;
|
||||||
|
delete l.c;
|
||||||
|
let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order
|
||||||
|
if (c) l.c = c;
|
||||||
|
|
||||||
function updateMin(l) {
|
if (!delete rectsToClear[hash]) {
|
||||||
switch (l.type) {
|
rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]};
|
||||||
case "txt": {
|
if (drawList) {
|
||||||
if (l.font.endsWith("%"))
|
drawList.push(l);
|
||||||
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
drawList = null; // Prevent children from being redundantly added to the drawList
|
||||||
// FIXME ':'/fsz not needed in new firmwares - it's handled internally
|
|
||||||
if (l.font.includes(":")) {
|
|
||||||
var f = l.font.split(":");
|
|
||||||
l.font = f[0];
|
|
||||||
l.fsz = f[1];
|
|
||||||
}
|
}
|
||||||
g.setFont(l.font,l.fsz);
|
|
||||||
l._h = g.getFontHeight();
|
|
||||||
l._w = g.stringWidth(l.label);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "btn": {
|
|
||||||
l._h = 24;
|
|
||||||
l._w = 14 + l.label.length*8;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "img": {
|
|
||||||
var src = l.src();
|
if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol);
|
||||||
if (typeof(src) === 'object') {
|
|
||||||
l._h = ("width" in src) ? src.width : src.getWidth();
|
|
||||||
l._w = ("height" in src) ? src.height : src.getHeight();
|
|
||||||
} else {
|
|
||||||
var im = E.toString(src);
|
|
||||||
l._h = im.charCodeAt(0);
|
|
||||||
l._w = im.charCodeAt(1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case undefined:
|
|
||||||
case "custom": {
|
|
||||||
// size should already be set up in width/height
|
|
||||||
l._w = 0;
|
|
||||||
l._h = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "h": {
|
|
||||||
l.c.forEach(updateMin);
|
|
||||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
|
||||||
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
|
||||||
l.fillx |= l.c.some(c=>c.fillx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "v": {
|
|
||||||
l.c.forEach(updateMin);
|
|
||||||
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
|
||||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
|
||||||
l.filly |= l.c.some(c=>c.filly);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: throw "Unknown item type "+l.type;
|
|
||||||
}
|
|
||||||
if (l.r&1) { // rotation
|
|
||||||
var t = l._w;l._w=l._h;l._h=t;
|
|
||||||
}
|
|
||||||
l._w = Math.max(l._w, 0|l.width);
|
|
||||||
l._h = Math.max(l._h, 0|l.height);
|
|
||||||
}
|
}
|
||||||
function render(l) {
|
|
||||||
if (!l) l = this.l;
|
Layout.prototype.render = function (l) {
|
||||||
|
if (!l) l = this._l;
|
||||||
|
|
||||||
|
function render(l) {"ram"
|
||||||
g.reset();
|
g.reset();
|
||||||
if (l.col) g.setColor(l.col);
|
if (l.col) g.setColor(l.col);
|
||||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h);
|
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
|
||||||
switch (l.type) {
|
cb[l.type](l);
|
||||||
case "txt":
|
}
|
||||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/);
|
|
||||||
break;
|
var cb = {
|
||||||
case "btn":
|
"":function(){},
|
||||||
|
"txt":function(l){
|
||||||
|
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||||
|
}, "btn":function(l){
|
||||||
var poly = [
|
var poly = [
|
||||||
l.x,l.y+4,
|
l.x,l.y+4,
|
||||||
l.x+4,l.y,
|
l.x+4,l.y,
|
||||||
|
|
@ -229,33 +201,41 @@ function render(l) {
|
||||||
l.x,l.y+4
|
l.x,l.y+4
|
||||||
];
|
];
|
||||||
g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
||||||
break;
|
}, "img":function(l){
|
||||||
case "img":
|
|
||||||
g.drawImage(l.src(), l.x, l.y);
|
g.drawImage(l.src(), l.x, l.y);
|
||||||
break;
|
}, "custom":function(l){
|
||||||
case "custom":
|
|
||||||
l.render(l);
|
l.render(l);
|
||||||
break;
|
},"h":function(l) { l.c.forEach(render); },
|
||||||
}
|
"v":function(l) { l.c.forEach(render); }
|
||||||
if (l.c) l.c.forEach(render);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
Layout.prototype.render = function (l) {
|
if (this.lazy) {
|
||||||
if (!l) l = this._l;
|
// we have to use 'var' here not 'let', otherwise the minifier
|
||||||
|
// renames vars to the same name, which causes problems as Espruino
|
||||||
|
// doesn't yet honour the scoping of 'let'
|
||||||
|
if (!this.rects) this.rects = {};
|
||||||
|
var rectsToClear = this.rects.clone();
|
||||||
|
var drawList = [];
|
||||||
|
prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor());
|
||||||
|
for (var h in rectsToClear) delete this.rects[h];
|
||||||
|
var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored
|
||||||
|
for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r);
|
||||||
|
drawList.forEach(render);
|
||||||
|
} else { // non-lazy
|
||||||
render(l);
|
render(l);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Layout.prototype.layout = function (l) {
|
Layout.prototype.layout = function (l) {
|
||||||
// l = current layout element
|
// l = current layout element
|
||||||
// exw,exh = extra width/height available
|
// exw,exh = extra width/height available
|
||||||
var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
|
||||||
var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0);
|
|
||||||
switch (l.type) {
|
switch (l.type) {
|
||||||
case "h": {
|
case "h": {
|
||||||
let x = l.x + (l.w-l._w)/2;
|
let x = l.x + (l.w-l._w)/2;
|
||||||
|
var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
||||||
if (fillx) { x = l.x; }
|
if (fillx) { x = l.x; }
|
||||||
l.c.forEach(c => {
|
l.c.forEach(c => {
|
||||||
c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0);
|
c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1));
|
||||||
c.h = c.filly ? l.h : c._h;
|
c.h = c.filly ? l.h : c._h;
|
||||||
c.x = x;
|
c.x = x;
|
||||||
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
||||||
|
|
@ -265,18 +245,17 @@ Layout.prototype.layout = function (l) {
|
||||||
c.w += c.pad*2;
|
c.w += c.pad*2;
|
||||||
c.h += c.pad*2;
|
c.h += c.pad*2;
|
||||||
}
|
}
|
||||||
if (c.c) {
|
if (c.c) this.layout(c);
|
||||||
this.layout(c);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "v": {
|
case "v": {
|
||||||
let y = l.y + (l.h-l._h)/2;
|
let y = l.y + (l.h-l._h)/2;
|
||||||
|
var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0);
|
||||||
if (filly) { y = l.y; }
|
if (filly) { y = l.y; }
|
||||||
l.c.forEach(c => {
|
l.c.forEach(c => {
|
||||||
c.w = c.fillx ? l.w : c._w;
|
c.w = c.fillx ? l.w : c._w;
|
||||||
c.h = c._h + (c.filly?(l.h-l._h)/filly:0);
|
c.h = c._h + ((0|c.filly)*(l.h-l._h)/(filly||1));
|
||||||
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
||||||
c.y = y;
|
c.y = y;
|
||||||
y += c.h;
|
y += c.h;
|
||||||
|
|
@ -304,6 +283,62 @@ Layout.prototype.update = function() {
|
||||||
var y = this.yOffset;
|
var y = this.yOffset;
|
||||||
var h = g.getHeight()-y;
|
var h = g.getHeight()-y;
|
||||||
// update sizes
|
// update sizes
|
||||||
|
function updateMin(l) {"ram"
|
||||||
|
cb[l.type](l);
|
||||||
|
if (l.r&1) { // rotation
|
||||||
|
var t = l._w;l._w=l._h;l._h=t;
|
||||||
|
}
|
||||||
|
l._w = Math.max(l._w, 0|l.width);
|
||||||
|
l._h = Math.max(l._h, 0|l.height);
|
||||||
|
}
|
||||||
|
var cb = {
|
||||||
|
"txt" : function(l) {
|
||||||
|
if (l.font.endsWith("%"))
|
||||||
|
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
||||||
|
// FIXME ':'/fsz not needed in new firmwares - it's handled internally
|
||||||
|
if (l.font.includes(":")) {
|
||||||
|
var f = l.font.split(":");
|
||||||
|
l.font = f[0];
|
||||||
|
l.fsz = f[1];
|
||||||
|
}
|
||||||
|
g.setFont(l.font,l.fsz);
|
||||||
|
l._h = g.getFontHeight();
|
||||||
|
l._w = g.stringWidth(l.label);
|
||||||
|
}, "btn": function(l) {
|
||||||
|
l._h = 24;
|
||||||
|
l._w = 14 + l.label.length*8;
|
||||||
|
}, "img": function(l) {
|
||||||
|
var src = l.src();
|
||||||
|
if (typeof(src) === 'object') {
|
||||||
|
l._h = ("width" in src) ? src.width : src.getWidth();
|
||||||
|
l._w = ("height" in src) ? src.height : src.getHeight();
|
||||||
|
} else {
|
||||||
|
var im = E.toString(src);
|
||||||
|
l._h = im.charCodeAt(0);
|
||||||
|
l._w = im.charCodeAt(1);
|
||||||
|
}
|
||||||
|
}, "": function(l) {
|
||||||
|
// size should already be set up in width/height
|
||||||
|
l._w = 0;
|
||||||
|
l._h = 0;
|
||||||
|
}, "custom": function(l) {
|
||||||
|
// size should already be set up in width/height
|
||||||
|
l._w = 0;
|
||||||
|
l._h = 0;
|
||||||
|
}, "h": function(l) {
|
||||||
|
l.c.forEach(updateMin);
|
||||||
|
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
||||||
|
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
||||||
|
if (l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||||
|
if (l.c.some(c=>c.filly)) l.filly = 1;
|
||||||
|
}, "v": function(l) {
|
||||||
|
l.c.forEach(updateMin);
|
||||||
|
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
||||||
|
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
||||||
|
if (l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||||
|
if (l.c.some(c=>c.filly)) l.filly = 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
updateMin(l);
|
updateMin(l);
|
||||||
// center
|
// center
|
||||||
if (l.fillx || l.filly) {
|
if (l.fillx || l.filly) {
|
||||||
|
|
|
||||||
|
|
@ -1,325 +0,0 @@
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```
|
|
||||||
var Layout = require("Layout");
|
|
||||||
var layout = new Layout( layoutObject, btns )
|
|
||||||
layout.render(optionalObject);
|
|
||||||
```
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```
|
|
||||||
var Layout = require("Layout");
|
|
||||||
var layout = new Layout( {
|
|
||||||
type:"v", c: [
|
|
||||||
{type:"txt", font:"20%", label:"12:00" },
|
|
||||||
{type:"txt", font:"6x8", label:"The Date" }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
g.clear();
|
|
||||||
layout.render();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
layoutObject has:
|
|
||||||
|
|
||||||
* A `type` field of:
|
|
||||||
* `undefined` - blank, can be used for padding
|
|
||||||
* `"txt"` - a text label, with value `label` and `r` for text rotation. 'font' is required
|
|
||||||
* `"btn"` - a button, with value `label` and callback `cb`
|
|
||||||
* `"img"` - an image where the function `src` is called to return an image to draw
|
|
||||||
* `"custom"` - a custom block where `render(layoutObj)` is called to render
|
|
||||||
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
|
|
||||||
* `"v"` - Veritical layout, `c` is an array of more `layoutObject`
|
|
||||||
* A `id` field. If specified the object is added with this name to the
|
|
||||||
returned `layout` object, so can be referenced as `layout.foo`
|
|
||||||
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height
|
|
||||||
* A `col` field, eg `#f00` for red
|
|
||||||
* A `bgCol` field for background color (will automatically fill on render)
|
|
||||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
|
||||||
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
|
||||||
* A `pad` integer field to set pixels padding
|
|
||||||
* A `fillx` boolean to choose if the object should fill available space in x
|
|
||||||
* A `filly` boolean to choose if the object should fill available space in y
|
|
||||||
* `width` and `height` fields to optionally specify minimum size
|
|
||||||
|
|
||||||
btns is an array of objects containing:
|
|
||||||
|
|
||||||
* `label` - the text on the button
|
|
||||||
* `cb` - a callback function
|
|
||||||
* `cbl` - a callback function for long presses
|
|
||||||
|
|
||||||
Once `layout.update()` is called, the following fields are added
|
|
||||||
to each object:
|
|
||||||
|
|
||||||
* `x` and `y` for the top left position
|
|
||||||
* `w` and `h` for the width and height
|
|
||||||
* `_w` and `_h` for the **minimum** width and height
|
|
||||||
|
|
||||||
|
|
||||||
Other functions:
|
|
||||||
|
|
||||||
* `layout.update()` - update positions of everything if contents have changed
|
|
||||||
* `layout.debug(obj)` - draw outlines for objects on screen
|
|
||||||
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
function Layout(layout, buttons) {
|
|
||||||
this._l = this.l = layout;
|
|
||||||
this.b = buttons;
|
|
||||||
// Do we have >1 physical buttons?
|
|
||||||
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
|
||||||
this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0;
|
|
||||||
|
|
||||||
if (buttons) {
|
|
||||||
if (this.physBtns >= buttons.length) {
|
|
||||||
// enough physical buttons
|
|
||||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns);
|
|
||||||
if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch);
|
|
||||||
Bangle.btnWatch = [];
|
|
||||||
if (this.physBtns > 2 && buttons.length==1)
|
|
||||||
buttons.unshift({label:""}); // pad so if we have a button in the middle
|
|
||||||
while (this.physBtns > buttons.length)
|
|
||||||
buttons.push({label:""});
|
|
||||||
if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
|
|
||||||
if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
|
|
||||||
if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
|
|
||||||
this._l.width = g.getWidth()-8; // text width
|
|
||||||
this._l = {type:"h", filly:1, c: [
|
|
||||||
this._l,
|
|
||||||
{type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
|
||||||
]};
|
|
||||||
} else {
|
|
||||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
|
||||||
this._l.width = g.getWidth()-20; // button width
|
|
||||||
this._l = {type:"h", c: [
|
|
||||||
this._l,
|
|
||||||
{type:"v", c: buttons.map(b=>(b.type="btn",b.h=btnHeight,b.w=32,b.r=1,b))}
|
|
||||||
]};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (process.env.HWVERSION==2) {
|
|
||||||
Bangle.touchHandler = (_,e) => touchHandler(layout,e);
|
|
||||||
Bangle.on('touch',Bangle.touchHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add IDs
|
|
||||||
var ll = this;
|
|
||||||
function idRecurser(l) {
|
|
||||||
if (l.id) ll[l.id] = l;
|
|
||||||
if (l.c) l.c.forEach(idRecurser);
|
|
||||||
}
|
|
||||||
idRecurser(layout);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.prototype.remove = function (l) {
|
|
||||||
if (Bangle.btnWatch) {
|
|
||||||
Bangle.btnWatch.forEach(clearWatch);
|
|
||||||
delete Bangle.btnWatch;
|
|
||||||
}
|
|
||||||
if (Bangle.touchHandler) {
|
|
||||||
Bangle.removeListener("touch",Bangle.touchHandler);
|
|
||||||
delete Bangle.touchHandler;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handler for button watch events
|
|
||||||
function pressHandler(btn,e) {
|
|
||||||
if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
|
|
||||||
this.b[btn].cbl(e);
|
|
||||||
else
|
|
||||||
if (this.b[btn].cb) this.b[btn].cb(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for touch events
|
|
||||||
function touchHandler(l,e) {
|
|
||||||
if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h)
|
|
||||||
l.cb(e);
|
|
||||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function updateMin(l) {
|
|
||||||
switch (l.type) {
|
|
||||||
case "txt": {
|
|
||||||
if (l.font.endsWith("%"))
|
|
||||||
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
|
||||||
// FIXME ':'/fsz not needed in new firmwares - it's handled internally
|
|
||||||
if (l.font.includes(":")) {
|
|
||||||
var f = l.font.split(":");
|
|
||||||
l.font = f[0];
|
|
||||||
l.fsz = f[1];
|
|
||||||
}
|
|
||||||
g.setFont(l.font,l.fsz);
|
|
||||||
l._h = g.getFontHeight();
|
|
||||||
l._w = g.stringWidth(l.label);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "btn": {
|
|
||||||
l._h = 24;
|
|
||||||
l._w = 14 + l.label.length*8;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "img": {
|
|
||||||
var im = E.toString(l.src());
|
|
||||||
l._h = im.charCodeAt(0);
|
|
||||||
l._w = im.charCodeAt(1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case undefined:
|
|
||||||
case "custom": {
|
|
||||||
// size should already be set up in width/height
|
|
||||||
l._w = 0;
|
|
||||||
l._h = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "h": {
|
|
||||||
l.c.forEach(updateMin);
|
|
||||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
|
||||||
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
|
||||||
l.fillx |= l.c.some(c=>c.fillx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "v": {
|
|
||||||
l.c.forEach(updateMin);
|
|
||||||
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
|
||||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
|
||||||
l.filly |= l.c.some(c=>c.filly);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: throw "Unknown item type "+l.type;
|
|
||||||
}
|
|
||||||
if (l.r&1) { // rotation
|
|
||||||
var t = l._w;l._w=l._h;l._h=t;
|
|
||||||
}
|
|
||||||
l._w = Math.max(l._w, 0|l.width);
|
|
||||||
l._h = Math.max(l._h, 0|l.height);
|
|
||||||
}
|
|
||||||
function render(l) {
|
|
||||||
if (!l) l = this.l;
|
|
||||||
g.reset();
|
|
||||||
if (l.col) g.setColor(l.col);
|
|
||||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h);
|
|
||||||
switch (l.type) {
|
|
||||||
case "txt":
|
|
||||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/);
|
|
||||||
break;
|
|
||||||
case "btn":
|
|
||||||
var poly = [
|
|
||||||
l.x,l.y+4,
|
|
||||||
l.x+4,l.y,
|
|
||||||
l.x+l.w-5,l.y,
|
|
||||||
l.x+l.w-1,l.y+4,
|
|
||||||
l.x+l.w-1,l.y+l.h-5,
|
|
||||||
l.x+l.w-5,l.y+l.h-1,
|
|
||||||
l.x+4,l.y+l.h-1,
|
|
||||||
l.x,l.y+l.h-5,
|
|
||||||
l.x,l.y+4
|
|
||||||
];
|
|
||||||
g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
|
||||||
break;
|
|
||||||
case "img":
|
|
||||||
g.drawImage(l.src(), l.x, l.y);
|
|
||||||
break;
|
|
||||||
case "custom":
|
|
||||||
l.render(l);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (l.c) l.c.forEach(render);
|
|
||||||
}
|
|
||||||
|
|
||||||
Layout.prototype.render = function (l) {
|
|
||||||
if (!l) l = this._l;
|
|
||||||
render(l);
|
|
||||||
};
|
|
||||||
|
|
||||||
Layout.prototype.layout = function (l) {
|
|
||||||
// l = current layout element
|
|
||||||
// exw,exh = extra width/height available
|
|
||||||
var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
|
||||||
var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0);
|
|
||||||
switch (l.type) {
|
|
||||||
case "h": {
|
|
||||||
let x = l.x + (l.w-l._w)/2;
|
|
||||||
if (fillx) { x = l.x; }
|
|
||||||
l.c.forEach(c => {
|
|
||||||
c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0);
|
|
||||||
c.h = c.filly ? l.h : c._h;
|
|
||||||
c.x = x;
|
|
||||||
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
|
||||||
x += c.w;
|
|
||||||
if (c.pad) {
|
|
||||||
x += c.pad*2;
|
|
||||||
c.w += c.pad*2;
|
|
||||||
c.h += c.pad*2;
|
|
||||||
}
|
|
||||||
if (c.c) {
|
|
||||||
this.layout(c);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "v": {
|
|
||||||
let y = l.y + (l.h-l._h)/2;
|
|
||||||
if (filly) { y = l.y; }
|
|
||||||
l.c.forEach(c => {
|
|
||||||
c.w = c.fillx ? l.w : c._w;
|
|
||||||
c.h = c._h + (c.filly?(l.h-l._h)/filly:0);
|
|
||||||
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
|
||||||
c.y = y;
|
|
||||||
y += c.h;
|
|
||||||
if (c.pad) {
|
|
||||||
y += c.pad*2;
|
|
||||||
c.w += c.pad*2;
|
|
||||||
c.h += c.pad*2;
|
|
||||||
}
|
|
||||||
if (c.c) this.layout(c);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Layout.prototype.debug = function(l,c) {
|
|
||||||
if (!l) l = this._l;
|
|
||||||
c=c||1;
|
|
||||||
g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c);
|
|
||||||
c++;
|
|
||||||
if (l.c) l.c.forEach(n => this.debug(n,c));
|
|
||||||
};
|
|
||||||
Layout.prototype.update = function() {
|
|
||||||
var l = this._l;
|
|
||||||
var w = g.getWidth();
|
|
||||||
var y = this.yOffset;
|
|
||||||
var h = g.getHeight()-y;
|
|
||||||
// update sizes
|
|
||||||
updateMin(l);
|
|
||||||
// center
|
|
||||||
if (l.fillx || l.filly) {
|
|
||||||
l.w = w;
|
|
||||||
l.h = h;
|
|
||||||
l.x = 0;
|
|
||||||
l.y = y;
|
|
||||||
} else {
|
|
||||||
l.w = l._w;
|
|
||||||
l.h = l._h;
|
|
||||||
l.x = (w-l.w)/2;
|
|
||||||
l.y = y+(h-l.h)/2;
|
|
||||||
}
|
|
||||||
// layout children
|
|
||||||
this.layout(l);
|
|
||||||
};
|
|
||||||
|
|
||||||
Layout.prototype.clear = function(l) {
|
|
||||||
if (!l) l = this._l;
|
|
||||||
g.reset();
|
|
||||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol);
|
|
||||||
g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports = Layout;
|
|
||||||