Merge branch 'espruino:master' into add_waternet

pull/3021/head
Willems Davy 2023-09-15 04:28:14 +02:00 committed by GitHub
commit 05142fc8cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 3801 additions and 309 deletions

View File

@ -31,3 +31,4 @@
0.30: Send firmware and hardware versions on connection 0.30: Send firmware and hardware versions on connection
Allow alarm enable/disable Allow alarm enable/disable
0.31: Implement API for activity fetching 0.31: Implement API for activity fetching
0.32: Added support for loyalty cards from gadgetbridge

View File

@ -236,6 +236,11 @@
event.t="remove"; event.t="remove";
} }
require("messages").pushMessage(event); require("messages").pushMessage(event);
},
"cards" : function() {
// we receive all, just override what we have
if (Array.isArray(event.d))
require("Storage").writeJSON("android.cards.json", event.d);
} }
}; };
var h = HANDLERS[event.t]; var h = HANDLERS[event.t];

View File

@ -2,7 +2,7 @@
"id": "android", "id": "android",
"name": "Android Integration", "name": "Android Integration",
"shortName": "Android", "shortName": "Android",
"version": "0.31", "version": "0.32",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge", "tags": "tool,system,messages,notifications,gadgetbridge",
@ -15,6 +15,6 @@
{"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.img","url":"app-icon.js","evaluate":true},
{"name":"android.boot.js","url":"boot.js"} {"name":"android.boot.js","url":"boot.js"}
], ],
"data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}],
"sortorder": -8 "sortorder": -8
} }

View File

@ -31,7 +31,7 @@
<div class="form-group"> <div class="form-group">
<label class="form-label">Select which GNSS system you want.</label> <label class="form-label">Select which GNSS system you want.</label>
<label class="form-radio"> <label class="form-radio">
<input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS <input type="radio" name="gnss_select" value="1" checked><i class="form-icon"></i> GPS (fastest to get a fix)
</label> </label>
<label class="form-radio"> <label class="form-radio">
<input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS <input type="radio" name="gnss_select" value="2"><i class="form-icon"></i> BDS

View File

@ -1,2 +1,5 @@
0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast 0.02: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
0.03: Bangle 2 support 0.03: Bangle 2 support
0.04: Increase size of ship, asteroids and fonts for better readability
0.05: improve collision detect for larger ship v astroid
0.06: added, 7 point asteroid ploygon, made ship solid, rather than outline

View File

@ -18,6 +18,22 @@ if (process.env.HWVERSION==2) {
} }
var W = g.getWidth(); var W = g.getWidth();
var H = g.getHeight(); var H = g.getHeight();
var SS = W/11; // ship back length
var SL = W/15; // ship side length
var AS = W/18; // asteroid radius
// radius of ship, assumed a circle inside equilateral traingle of side SS
// r = a / root 3 where a is length of equilateral triangle
var SR = SS / Math.sqrt(3);
var AST = [ // asteroid polygon as X/Y pairs
0 ,-1.5,
1 , 0,
0.5, 0,
0.5, 0.5,
0 , 1,
-1 , 0,
-1 , -1
];
g.clear().setFontAlign(0,-1); g.clear().setFontAlign(0,-1);
function newAst(x,y) { function newAst(x,y) {
@ -25,7 +41,7 @@ function newAst(x,y) {
x:x,y:y, x:x,y:y,
vx:Math.random()-0.5, vx:Math.random()-0.5,
vy:Math.random()-0.5, vy:Math.random()-0.5,
rad:3+Math.random()*5 rad:3+Math.random()*AS
}; };
return a; return a;
} }
@ -41,8 +57,9 @@ var lastFrame;
function gameStop() { function gameStop() {
running = false; running = false;
g.clear(); g.setFont('Vector', W/7);
g.drawString("Game Over!",120,(H-6)/2); g.setFontAlign(0,0);
g.drawString("Game Over", W/2, H/2);
g.flip(); g.flip();
} }
@ -104,12 +121,13 @@ function onFrame() {
} }
g.clear(); g.clear();
g.drawString(score,W-20,0); g.setFont('Vector', 16);
g.drawString(score,W-20,16);
var rs = Math.PI*0.8; var rs = Math.PI*0.8;
g.drawPoly([ g.fillPoly([
ship.x+Math.cos(ship.r)*4, ship.y+Math.sin(ship.r)*4, ship.x+Math.cos(ship.r)*SS, ship.y+Math.sin(ship.r)*SS,
ship.x+Math.cos(ship.r+rs)*3, ship.y+Math.sin(ship.r+rs)*3, ship.x+Math.cos(ship.r+rs)*SL, ship.y+Math.sin(ship.r+rs)*SL,
ship.x+Math.cos(ship.r-rs)*3, ship.y+Math.sin(ship.r-rs)*3, ship.x+Math.cos(ship.r-rs)*SL, ship.y+Math.sin(ship.r-rs)*SL,
],true); ],true);
var na = []; var na = [];
ammo.forEach(function(a) { ammo.forEach(function(a) {
@ -137,7 +155,10 @@ function onFrame() {
ast.forEach(function(a) { ast.forEach(function(a) {
a.x += a.vx*d; a.x += a.vx*d;
a.y += a.vy*d; a.y += a.vy*d;
g.drawCircle(a.x, a.y, a.rad); //g.drawCircle(a.x, a.y, a.rad);
// a 7 point asteroid with rough circle radius of scale 2
g.drawPoly(g.transformVertices(AST,{x:a.x,y:a.y,scale:a.rad,rotate:t}),true);
if (a.x<0) a.x+=W; if (a.x<0) a.x+=W;
if (a.y<0) a.y+=H; if (a.y<0) a.y+=H;
if (a.x>=W) a.x-=W; if (a.x>=W) a.x-=W;
@ -165,7 +186,7 @@ function onFrame() {
var dx = a.x-ship.x; var dx = a.x-ship.x;
var dy = a.y-ship.y; var dy = a.y-ship.y;
var d = Math.sqrt(dx*dx+dy*dy); var d = Math.sqrt(dx*dx+dy*dy);
if (d < a.rad) crashed = true; if (d < a.rad + SR) crashed = true;
}); });
ast=na; ast=na;
if (!ast.length) { if (!ast.length) {

View File

@ -1,10 +1,10 @@
{ {
"id": "astroid", "id": "astroid",
"name": "Asteroids!", "name": "Asteroids!",
"version": "0.03", "version": "0.06",
"description": "Retro asteroids game", "description": "Retro asteroids game",
"icon": "asteroids.png", "icon": "asteroids.png",
"screenshots": [{"url":"screenshot_asteroids.png"}], "screenshots": [{"url":"screenshot.png"}],
"tags": "game", "tags": "game",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true, "allow_emulator": true,

BIN
apps/astroid/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

35
apps/cards/Barcode.js Normal file
View File

@ -0,0 +1,35 @@
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
class Barcode{
constructor(data, options){
this.data = data;
this.text = options.text || data;
this.options = options;
}
}
module.exports = Barcode;

1
apps/cards/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Simple app to display loyalty cards

54
apps/cards/README.md Normal file
View File

@ -0,0 +1,54 @@
# Cards
Simple app to display loyalty cards synced from Catima through GadgetBridge.
The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported.
To come back to the visualization of the card's details from the code view, simply press the button.
Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes).
### Supported codes types
* `CODE_39`
* `CODABAR`
* `QR_CODE`
### Disclaimer
This app is a proof of concept, many codes are too complex to be rendered by the bangle's screen or hardware (at least with the current logic), keep that in mind.
### How to sync
_WIP: we currently cannot synchronize cards, a PR is under review in GadgetBridge repo, soon we will see support on nightly builds_
You can test it by sending on your bangle a file like this:
_android.cards.json_
```json
[
{
"id": 1,
"name": "First card",
"value": "01234",
"note": "Some stuff",
"type": "CODE_39",
"balance": "15 EUR",
"expiration": "1691102081"
},
{
"id": 2,
"name": "Second card",
"value": "Hello world",
"note": "This is a qr generated on the bangle!",
"type": "QR_CODE",
"balance": "2 P"
}
]
```
### Credits
Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode)
QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo"))

210
apps/cards/app.js Normal file
View File

@ -0,0 +1,210 @@
/* CARDS is a list of:
{id:int,
name,
value,
type,
expiration,
color,
balance,
note,
...
}
*/
Bangle.loadWidgets();
Bangle.drawWidgets();
//may make it configurable in the future
const WHITE=-1
const BLACK=0
var FILE = "android.cards.json";
var Locale = require("locale");
var fontSmall = "6x8";
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
var CARDS = require("Storage").readJSON("android.cards.json",true)||[];
var settings = require("Storage").readJSON("cards.settings.json",true)||{};
function getDate(timestamp) {
return new Date(timestamp*1000);
}
function formatDay(date) {
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
if (!settings.useToday) {
return formattedDate;
}
const today = new Date(Date.now());
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
return /*LANG*/"Today ";
else {
const tomorrow = new Date(Date.now() + 86400 * 1000);
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
return /*LANG*/"Tomorrow ";
}
return formattedDate;
}
}
function getColor(intColor) {
return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0");
}
function isLight(color) {
var r = +("0x"+color.slice(1,3));
var g = +("0x"+color.slice(3,5));
var b = +("0x"+color.slice(5,7));
var threshold = 0x88 * 3;
return (r+g+b) > threshold;
}
function printSquareCode(binary, size) {
var padding = 5;
var ratio = (g.getWidth()-(2*padding))/size;
for (var y = 0; y < size; y++) {
for (var x = 0; x < size; x++) {
if (binary[x + y * size]) {
g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
} else {
g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio});
}
}
}
}
function printLinearCode(binary) {
var yFrom = 15;
var yTo = 28;
var width = g.getWidth()/binary.length;
for(var b = 0; b < binary.length; b++){
var x = b * width;
if(binary[b] === "1"){
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
else if(binary[b]){
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
}
}
function showCode(card) {
E.showScroller();
// keeping it on rising edge would come back twice..
setWatch(()=>showCard(card), BTN, {edge:"falling"});
// theme independent
g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight());
switch (card.type) {
case "QR_CODE": {
const getBinaryQR = require("cards.qrcode.js");
let code = getBinaryQR(card.value);
printSquareCode(code.data, code.size);
break;
}
case "CODE_39": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const CODE39 = require("cards.code39.js");
let code = new CODE39(card.value, {});
printLinearCode(code.encode().data);
break;
}
case "CODABAR": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const codabar = require("cards.codabar.js");
let code = new codabar(card.value, {});
printLinearCode(code.encode().data);
break;
}
default:
g.clear(true);
g.setFont("Vector:30");
g.setFontAlign(0,0);
g.drawString(card.value, g.getWidth()/2, g.getHeight()/2);
}
}
function showCard(card) {
var lines = [];
var bodyFont = fontBig;
if(!card) return;
g.setFont(bodyFont);
//var lines = [];
if (card.name) lines = g.wrapString(card.name, g.getWidth()-10);
var titleCnt = lines.length;
var start = getDate(card.expiration);
var includeDay = true;
lines = lines.concat("", /*LANG*/"View code");
var valueLine = lines.length - 1;
if (card.expiration)
lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10));
if(card.balance)
lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10));
if(card.note && card.note.trim())
lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10));
lines = lines.concat("",/*LANG*/"< Back");
var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2;
var titleColor = g.theme.fg2;
if (card.color)
titleColor = isLight(titleBgColor) ? BLACK : WHITE;
E.showScroller({
h : g.getFontHeight(), // height of each menu item in pixels
c : lines.length, // number of menu items
// a function to draw a menu item
draw : function(idx, r) {
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
g.setBgColor(idx<titleCnt ? titleBgColor : g.theme.bg).
setColor(idx<titleCnt ? titleColor : g.theme.fg).
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
}, select : function(idx) {
if (idx>=lines.length-2)
showList();
else if (idx==valueLine)
showCode(card);
},
back : () => showList()
});
}
// https://github.com/metafloor/bwip-js
// https://github.com/lindell/JsBarcode
function showList() {
if(CARDS.length == 0) {
E.showMessage(/*LANG*/"No cards");
return;
}
E.showScroller({
h : 52,
c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
draw : function(idx, r) {"ram"
var card = CARDS[idx];
g.setColor(g.theme.fg);
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (!card) return;
var isPast = false;
var x = r.x+2, name = card.name;
var body = card.expiration ? formatDay(getDate(card.expiration)) : "";
if (card.balance) body += "\n" + card.balance;
if (name) g.setFontAlign(-1,-1).setFont(fontBig)
.setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2);
if (body) {
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
g.drawString(body, x+10,r.y+20);
}
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
if(card.color) {
g.setColor(getColor(card.color));
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
}
},
select : idx => showCard(CARDS[idx]),
back : () => load()
});
}
showList();

BIN
apps/cards/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

88
apps/cards/codabar.js Normal file
View File

@ -0,0 +1,88 @@
// Encoding specification:
// http://www.barcodeisland.com/codabar.phtml
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
const Barcode = require("cards.Barcode.js");
class codabar extends Barcode{
constructor(data, options){
if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) {
data = "A" + data + "A";
}
super(data.toUpperCase(), options);
this.text = this.options.text || this.text.replace(/[A-D]/g, '');
}
valid(){
return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data)
}
encode(){
var result = [];
var encodings = this.getEncodings();
for(var i = 0; i < this.data.length; i++){
result.push(encodings[this.data.charAt(i)]);
// for all characters except the last, append a narrow-space ("0")
if (i !== this.data.length - 1) {
result.push("0");
}
}
return {
text: this.text,
data: result.join('')
};
}
getEncodings(){
return {
"0": "101010011",
"1": "101011001",
"2": "101001011",
"3": "110010101",
"4": "101101001",
"5": "110101001",
"6": "100101011",
"7": "100101101",
"8": "100110101",
"9": "110100101",
"-": "101001101",
"$": "101100101",
":": "1101011011",
"/": "1101101011",
".": "1101101101",
"+": "1011011011",
"A": "1011001001",
"B": "1001001011",
"C": "1010010011",
"D": "1010011001"
};
}
}
module.exports = codabar

130
apps/cards/code39.js Normal file
View File

@ -0,0 +1,130 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_39#Encoding
/*
* JS source adapted from https://github.com/lindell/JsBarcode
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
const Barcode = require("cards.Barcode.js");
class CODE39 extends Barcode {
constructor(data, options){
data = data.toUpperCase();
// Calculate mod43 checksum if enabled
if(options.mod43){
data += getCharacter(mod43checksum(data));
}
super(data, options);
}
encode(){
// First character is always a *
var result = getEncoding("*");
// Take every character and add the binary representation to the result
for(let i = 0; i < this.data.length; i++){
result += getEncoding(this.data[i]) + "0";
}
// Last character is always a *
result += getEncoding("*");
return {
data: result,
text: this.text
};
}
valid(){
return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data);
}
}
// All characters. The position in the array is the (checksum) value
var characters = [
"0", "1", "2", "3",
"4", "5", "6", "7",
"8", "9", "A", "B",
"C", "D", "E", "F",
"G", "H", "I", "J",
"K", "L", "M", "N",
"O", "P", "Q", "R",
"S", "T", "U", "V",
"W", "X", "Y", "Z",
"-", ".", " ", "$",
"/", "+", "%", "*"
];
// The decimal representation of the characters, is converted to the
// corresponding binary with the getEncoding function
var encodings = [
20957, 29783, 23639, 30485,
20951, 29813, 23669, 20855,
29789, 23645, 29975, 23831,
30533, 22295, 30149, 24005,
21623, 29981, 23837, 22301,
30023, 23879, 30545, 22343,
30161, 24017, 21959, 30065,
23921, 22385, 29015, 18263,
29141, 17879, 29045, 18293,
17783, 29021, 18269, 17477,
17489, 17681, 20753, 35770
];
// Get the binary representation of a character by converting the encodings
// from decimal to binary
function getEncoding(character){
return getBinary(characterValue(character));
}
function getBinary(characterValue){
return encodings[characterValue].toString(2);
}
function getCharacter(characterValue){
return characters[characterValue];
}
function characterValue(character){
return characters.indexOf(character);
}
function mod43checksum(data){
var checksum = 0;
for(let i = 0; i < data.length; i++){
checksum += characterValue(data[i]);
}
checksum = checksum % 43;
return checksum;
}
module.exports = CODE39;

21
apps/cards/metadata.json Normal file
View File

@ -0,0 +1,21 @@
{
"id": "cards",
"name": "Cards",
"version": "0.01",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
"tags": "cards",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"cards.app.js","url":"app.js"},
{"name":"cards.settings.js","url":"settings.js"},
{"name":"cards.Barcode.js","url":"Barcode.js"},
{"name":"cards.qrcode.js","url":"qrcode.js"},
{"name":"cards.codabar.js","url":"codabar.js"},
{"name":"cards.code39.js","url":"code39.js"},
{"name":"cards.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"cards.settings.json"}]
}

705
apps/cards/qrcode.js Normal file
View File

@ -0,0 +1,705 @@
/*
* C source adapted from https://github.com/ricmoo/QRCode
*
* The MIT License (MIT)
*
* This library is written and maintained by Richard Moore.
* Major parts were derived from Project Nayuki's library.
*
* Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode)
* Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
var c = E.compiledC(`
// int get_qr(int, int)
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
typedef __int8_t int8_t;
typedef __int16_t int16_t;
typedef __int32_t int32_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
typedef __uint32_t uint32_t;
typedef struct QRCode {
uint8_t version;
uint8_t size;
uint8_t ecc;
uint8_t mode;
uint8_t mask;
uint8_t *modules;
} QRCode;
uint16_t qrcode_getBufferSize(uint8_t version);
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372},
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750},
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430},
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040},
};
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49},
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25},
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81},
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68},
};
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
};
static int max(int a, int b) {
if (a > b) { return a; }
return b;
}
static int abs(int value) {
if (value < 0) { return -value; }
return value;
}
static void *memset(void *s, int c, int n) {
char *arr = (char *)s;
for (int i = 0; i<n; i++) {
arr[i] = c;
}
return arr;
}
static void *memcpy(void *dest, const void *src, int n) {
char *arr1 = (char *)dest;
const char *arr2 = (char *)src;
for (int i = 0; i<n; i++) {
arr1[i] = arr2[i];
}
return arr1;
}
int strlen(const char *s) {
int i = 0;
while (s[i]) ++i;
return i;
}
static int8_t getAlphanumeric(char c) {
if (c >= '0' && c <= '9') { return (c - '0'); }
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
switch (c) {
case ' ': return 36;
case '$': return 37;
case '%': return 38;
case '*': return 39;
case '+': return 40;
case '-': return 41;
case '.': return 42;
case '/': return 43;
case ':': return 44;
}
return -1;
}
static bool isAlphanumeric(const char *text, uint16_t length) {
while (length != 0) {
if (getAlphanumeric(text[--length]) == -1) { return false; }
}
return true;
}
static bool isNumeric(const char *text, uint16_t length) {
while (length != 0) {
char c = text[--length];
if (c < '0' || c > '9') { return false; }
}
return true;
}
static char getModeBits(uint8_t version, uint8_t mode) {
unsigned int modeInfo = 0x7bbb80a;
if (version > 9) { modeInfo >>= 9; }
if (version > 26) { modeInfo >>= 9; }
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
if (result == 15) { result = 16; }
return result;
}
typedef struct BitBucket {
uint32_t bitOffsetOrWidth;
uint16_t capacityBytes;
uint8_t *data;
} BitBucket;
static uint16_t bb_getGridSizeBytes(uint8_t size) {
return (((size * size) + 7) / 8);
}
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
return ((bits + 7) / 8);
}
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
bitBuffer->bitOffsetOrWidth = 0;
bitBuffer->capacityBytes = capacityBytes;
bitBuffer->data = data;
memset(data, 0, bitBuffer->capacityBytes);
}
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
bitGrid->bitOffsetOrWidth = size;
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
bitGrid->data = data;
memset(data, 0, bitGrid->capacityBytes);
}
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
uint32_t offset = bitBuffer->bitOffsetOrWidth;
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
bitBuffer->bitOffsetOrWidth = offset;
}
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
if (on) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
if (on ^ invert) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (bb_getBit(isFunction, x, y)) { continue; }
bool invert = 0;
switch (mask) {
case 0: invert = (x + y) % 2 == 0; break;
case 1: invert = y % 2 == 0; break;
case 2: invert = x % 3 == 0; break;
case 3: invert = (x + y) % 3 == 0; break;
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
}
bb_invertBit(modules, x, y, invert);
}
}
}
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
bb_setBit(modules, x, y, on);
bb_setBit(isFunction, x, y, true);
}
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
uint8_t size = modules->bitOffsetOrWidth;
for (int8_t i = -4; i <= 4; i++) {
for (int8_t j = -4; j <= 4; j++) {
uint8_t dist = max(abs(i), abs(j));
int16_t xx = x + j, yy = y + i;
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
}
}
}
}
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
for (int8_t i = -2; i <= 2; i++) {
for (int8_t j = -2; j <= 2; j++) {
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
}
}
}
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
uint32_t data = ecc << 3 | mask;
uint32_t rem = data;
for (int i = 0; i < 10; i++) {
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
}
data = data << 10 | rem;
data ^= 0x5412;
for (uint8_t i = 0; i <= 5; i++) {
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
for (int8_t i = 9; i < 15; i++) {
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 0; i <= 7; i++) {
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 8; i < 15; i++) {
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, size - 8, true);
}
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
int8_t size = modules->bitOffsetOrWidth;
if (version < 7) { return; }
uint32_t rem = version;
for (uint8_t i = 0; i < 12; i++) {
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
}
uint32_t data = version << 12 | rem;
for (uint8_t i = 0; i < 18; i++) {
bool bit = ((data >> i) & 1) != 0;
uint8_t a = size - 11 + i % 3, b = i / 3;
setFunctionModule(modules, isFunction, a, b, bit);
setFunctionModule(modules, isFunction, b, a, bit);
}
}
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t i = 0; i < size; i++) {
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
}
drawFinderPattern(modules, isFunction, 3, 3);
drawFinderPattern(modules, isFunction, size - 4, 3);
drawFinderPattern(modules, isFunction, 3, size - 4);
if (version > 1) {
uint8_t alignCount = version / 7 + 2;
uint8_t step;
if (version != 32) {
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2;
} else {
step = 26;
}
uint8_t alignPositionIndex = alignCount - 1;
uint8_t alignPosition[alignCount];
alignPosition[0] = 6;
uint8_t l_size = version * 4 + 17;
for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) {
alignPosition[alignPositionIndex--] = pos;
}
for (uint8_t i = 0; i < alignCount; i++) {
for (uint8_t j = 0; j < alignCount; j++) {
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
continue;
} else {
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
}
}
}
}
drawFormatBits(modules, isFunction, ecc, 0);
drawVersion(modules, isFunction, version);
}
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
uint32_t bitLength = codewords->bitOffsetOrWidth;
uint8_t *data = codewords->data;
uint8_t size = modules->bitOffsetOrWidth;
uint32_t i = 0;
for (int16_t right = size - 1; right >= 1; right -= 2) {
if (right == 6) { right = 5; }
for (uint8_t vert = 0; vert < size; vert++) {
for (int j = 0; j < 2; j++) {
uint8_t x = right - j;
bool upwards = ((right & 2) == 0) ^ (x < 6);
uint8_t y = upwards ? size - 1 - vert : vert;
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
i++;
}
}
}
}
}
static uint32_t getPenaltyScore(BitBucket *modules) {
uint32_t result = 0;
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
bool colorX = bb_getBit(modules, 0, y);
for (uint8_t x = 1, runX = 1; x < size; x++) {
bool cx = bb_getBit(modules, x, y);
if (cx != colorX) {
colorX = cx;
runX = 1;
} else {
runX++;
if (runX == 5) {
result += 3;
} else if (runX > 5) {
result++;
}
}
}
}
for (uint8_t x = 0; x < size; x++) {
bool colorY = bb_getBit(modules, x, 0);
for (uint8_t y = 1, runY = 1; y < size; y++) {
bool cy = bb_getBit(modules, x, y);
if (cy != colorY) {
colorY = cy;
runY = 1;
} else {
runY++;
if (runY == 5) {
result += 3;
} else if (runY > 5) {
result++;
}
}
}
}
uint16_t black = 0;
for (uint8_t y = 0; y < size; y++) {
uint16_t bitsRow = 0, bitsCol = 0;
for (uint8_t x = 0; x < size; x++) {
bool color = bb_getBit(modules, x, y);
if (x > 0 && y > 0) {
bool colorUL = bb_getBit(modules, x - 1, y - 1);
bool colorUR = bb_getBit(modules, x, y - 1);
bool colorL = bb_getBit(modules, x - 1, y);
if (color == colorUL && color == colorUR && color == colorL) {
result += 3;
}
}
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
if (x >= 10) {
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
result += 40;
}
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
result += 40;
}
}
if (color) { black++; }
}
}
uint16_t total = size * size;
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
result += 10;
}
return result;
}
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
uint16_t z = 0;
for (int8_t i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >> 7) * 0x11D);
z ^= ((y >> i) & 1) * x;
}
return z;
}
static void rs_init(uint8_t degree, uint8_t *coeff) {
memset(coeff, 0, degree);
coeff[degree - 1] = 1;
uint16_t root = 1;
for (uint8_t i = 0; i < degree; i++) {
for (uint8_t j = 0; j < degree; j++) {
coeff[j] = rs_multiply(coeff[j], root);
if (j + 1 < degree) {
coeff[j] ^= coeff[j + 1];
}
}
root = (root << 1) ^ ((root >> 7) * 0x11D);
}
}
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
for (uint8_t i = 0; i < length; i++) {
uint8_t factor = data[i] ^ result[0];
for (uint8_t j = 1; j < degree; j++) {
result[(j - 1) * stride] = result[j * stride];
}
result[(degree - 1) * stride] = 0;
for (uint8_t j = 0; j < degree; j++) {
result[j * stride] ^= rs_multiply(coeff[j], factor);
}
}
}
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
int8_t mode = 2;
if (isNumeric((char*)text, length)) {
mode = 0;
bb_appendBits(dataCodewords, 1 << 0, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 0));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 10 + ((char)(text[i]) - '0');
accumCount++;
if (accumCount == 3) {
bb_appendBits(dataCodewords, accumData, 10);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
}
} else if (isAlphanumeric((char*)text, length)) {
mode = 1;
bb_appendBits(dataCodewords, 1 << 1, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 1));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
accumCount++;
if (accumCount == 2) {
bb_appendBits(dataCodewords, accumData, 11);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, 6);
}
} else {
bb_appendBits(dataCodewords, 1 << 2, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 2));
for (uint16_t i = 0; i < length; i++) {
bb_appendBits(dataCodewords, (char)(text[i]), 8);
}
}
return mode;
}
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint8_t blockEccLen = totalEcc / numBlocks;
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
uint8_t result[data->capacityBytes];
memset(result, 0, sizeof(result));
uint8_t coeff[blockEccLen];
rs_init(blockEccLen, coeff);
uint16_t offset = 0;
uint8_t *dataBytes = data->data;
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
uint16_t index = i;
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == numShortBlocks) { stride++; }
index += stride;
}
}
{
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == 0) { stride++; }
index += stride;
}
}
uint8_t blockSize = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
if (blockNum == numShortBlocks) { blockSize++; }
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
dataBytes += blockSize;
}
memcpy(data->data, result, data->capacityBytes);
data->bitOffsetOrWidth = moduleCount;
}
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
uint16_t qrcode_getBufferSize(uint8_t version) {
return bb_getGridSizeBytes(4 * version + 17);
}
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
uint8_t size = version * 4 + 17;
qrcode->version = version;
qrcode->size = size;
qrcode->ecc = ecc;
qrcode->modules = modules;
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
struct BitBucket codewords;
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
if (mode < 0) { return -1; }
qrcode->mode = mode;
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
if (padding > 4) { padding = 4; }
bb_appendBits(&codewords, 0, padding);
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
bb_appendBits(&codewords, padByte, 8);
}
BitBucket modulesGrid;
bb_initGrid(&modulesGrid, modules, size);
BitBucket isFunctionGrid;
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
performErrorCorrection(version, eccFormatBits, &codewords);
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
uint8_t mask = 0;
int32_t minPenalty = (2147483647);
for (uint8_t i = 0; i < 8; i++) {
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
applyMask(&modulesGrid, &isFunctionGrid, i);
int penalty = getPenaltyScore(&modulesGrid);
if (penalty < minPenalty) {
mask = i;
minPenalty = penalty;
}
applyMask(&modulesGrid, &isFunctionGrid, i);
}
qrcode->mask = mask;
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
applyMask(&modulesGrid, &isFunctionGrid, mask);
return 0;
}
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
}
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x >= qrcode->size || y >= qrcode->size) {
return false;
}
uint32_t offset = y * qrcode->size + x;
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
int get_qr (char *string, uint8_t *qrcodeBitmap) {
// The structure to manage the QR code
QRCode qrcode;
// Allocate a chunk of memory to store the QR code
uint8_t qrcodeBytes[qrcode_getBufferSize(3)];
qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string);
for (uint8_t y = 0; y < qrcode.size; y++) {
for (uint8_t x = 0; x < qrcode.size; x++) {
qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y);
}
}
return qrcode.size;
}
`);
function getBinaryQR (value) {
var qrcodeBitmap = new Uint8Array(850);
var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0)));
var valueAddr = E.getAddressOf(flatValue, true);
var qrAddr = E.getAddressOf(qrcodeBitmap, true);
if (valueAddr == 0 || qrAddr == 0) {
console.log ("Failed to get flat arrays..");
//return;
}
var qrsize = c.get_qr(valueAddr, qrAddr);
return { data: qrcodeBitmap, size: qrsize };
}
module.exports = getBinaryQR;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

19
apps/cards/settings.js Normal file
View File

@ -0,0 +1,19 @@
(function(back) {
var settings = require("Storage").readJSON("cards.settings.json",1)||{};
function updateSettings() {
require("Storage").writeJSON("cards.settings.json", settings);
}
var mainmenu = {
"" : { "title" : "Cards" },
"< Back" : back,
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
/*LANG*/"Use 'Today',..." : {
value : !!settings.useToday,
onchange: v => {
settings.useToday = v;
updateSettings();
}
},
};
E.showMenu(mainmenu);
})

View File

@ -100,7 +100,8 @@ function onInit(device) {
if (crc==2560806221) version = "2v15"; if (crc==2560806221) version = "2v15";
if (crc==2886730689) version = "2v16"; if (crc==2886730689) version = "2v16";
if (crc==156320890) version = "2v17"; if (crc==156320890) version = "2v17";
if (crc==4012421318) version = "2v18"; if (crc==4012421318) version = "2v18";
if (crc==1856454048) version = "2v19";
if (!ok) { if (!ok) {
version += `(&#9888; update required)`; version += `(&#9888; update required)`;
} }

View File

@ -19,7 +19,7 @@ It provides the following features :
- display a local map around you, downloaded from openstreetmap - display a local map around you, downloaded from openstreetmap
- detects and buzzes if you leave the path - detects and buzzes if you leave the path
- (optional) buzzes before sharp turns - (optional) buzzes before sharp turns
- (optional) buzzes before waypoints - (optional) buzzes before waypoints
(for example when you need to turn in https://mapstogpx.com/) (for example when you need to turn in https://mapstogpx.com/)
- display instant / average speed - display instant / average speed
- display distance to next point - display distance to next point
@ -114,7 +114,7 @@ slopes between path points. Don't expect to see small bumps on the road.
Few settings for now (feel free to suggest me more) : Few settings for now (feel free to suggest me more) :
- buzz on turns : should the watch buzz when reaching a waypoint ? - buzz on turns : should the watch buzz when reaching a waypoint ?
- disable bluetooth : turn bluetooth off completely to try to save some power. - disable bluetooth : turn bluetooth off completely to try to save some power.
- lost distance : at which distance from path are you considered to be lost ? - lost distance : at which distance from path are you considered to be lost ?
- wake-up speed : if you drive below this speed powersaving will disable itself - wake-up speed : if you drive below this speed powersaving will disable itself
- active-time : how long (in seconds) the screen should be turned on if activated before going back to sleep. - active-time : how long (in seconds) the screen should be turned on if activated before going back to sleep.
@ -133,7 +133,7 @@ There are now two display modes :
The algorithm works in the following ways : The algorithm works in the following ways :
- some events will *activate* : the display will turn *active* - some events will *activate* : the display will turn *active*
- if no activation event occur for at least 10 seconds (or *active-time* setting) we switch back to *inactive* - if no activation event occur for at least 10 seconds (or *active-time* setting) we switch back to *inactive*
Activation events are the following : Activation events are the following :
@ -167,8 +167,8 @@ I had to go back uphill by quite a distance.
Feel free to give me feedback : is it useful for you ? what other features would you like ? Feel free to give me feedback : is it useful for you ? what other features would you like ?
If you want to raise issues the main repository is [https://github.com/wagnerf42/BangleApps](here) and If you want to raise issues the main repository is [here](https://github.com/wagnerf42/BangleApps) and
the rust code doing the actual map computations is located [https://github.com/wagnerf42/gps](here). the rust code doing the actual map computations is located [here](https://github.com/wagnerf42/gps).
You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/) You can try the cutting edge version at [https://wagnerf42.github.io/BangleApps/](https://wagnerf42.github.io/BangleApps/)
frederic.wagner@imag.fr frederic.wagner@imag.fr

View File

@ -23,17 +23,7 @@ function saveKML(track,title) {
</Placemark> </Placemark>
</Document> </Document>
</kml>`; </kml>`;
var a = document.createElement("a"), Util.saveFile(title+".kml", "application/vnd.google-earth.kml+xml", kml);
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".kml";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} }
function saveGPX(track, title) { function saveGPX(track, title) {
@ -56,17 +46,7 @@ function saveGPX(track, title) {
</trkseg> </trkseg>
</trk> </trk>
</gpx>`; </gpx>`;
var a = document.createElement("a"), Util.saveFile(title+".gpx", "application/gpx+xml", gpx);
file = new Blob([gpx], {type: "application/gpx+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".gpx";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} }
function trackLineToObject(l, hasTrackNumber) { function trackLineToObject(l, hasTrackNumber) {

View File

@ -28,6 +28,10 @@ The CSV data contains the following columns:
* PPG_o - `e.vcPPGoffs` from the `Bangle.on("HRM-raw"` event. This is the PPG offset used to map `e.vcPPG` to `e.raw` so there are no glitches when the exposure values in the sensor change. * PPG_o - `e.vcPPGoffs` from the `Bangle.on("HRM-raw"` event. This is the PPG offset used to map `e.vcPPG` to `e.raw` so there are no glitches when the exposure values in the sensor change.
* BTHRM - BPM figure from external Bluetooth HRM device (this is our reference BPM) * BTHRM - BPM figure from external Bluetooth HRM device (this is our reference BPM)
## FIXME
The `custom.html` for the app uses the Puck.js lib directly when it should just use `customize.js` - it won't work well under Gadgetbridge and may fail on other platforms too
## Creator ## Creator
[halemmerich](https://github.com/halemmerich) [halemmerich](https://github.com/halemmerich)

View File

@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
</head> </head>
<body> <body>
<script src="../../core/lib/customize.js"></script>
<script src="https://www.puck-js.com/puck.js"></script> <script src="https://www.puck-js.com/puck.js"></script>
<p> <p>
<div class="form-group"> <div class="form-group">
@ -23,20 +24,6 @@
<p id="result"></p> <p id="result"></p>
<script> <script>
function saveCSV(filename, csvData) {
let a = document.createElement("a"),
file = new Blob([csvData], {type: "Comma-separated value file"});
let url = URL.createObjectURL(file);
a.href = url;
a.download = filename+".csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function createCode(){ function createCode(){
//modes: 1 BT, 2 File //modes: 1 BT, 2 File
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw` return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
@ -48,7 +35,7 @@ function createCode(){
var gotBTHRM = false; var gotBTHRM = false;
var gotHRM = false; var gotHRM = false;
var gotAcc = false; var gotAcc = false;
var events = -1; var events = -1;
var hrmRaw,hrmPulse,bthrmPulse var hrmRaw,hrmPulse,bthrmPulse
@ -59,9 +46,9 @@ function createCode(){
let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {}); let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {});
Bangle.setHRMPower(1); Bangle.setHRMPower(1);
if (bthrmSettings.replace) Bangle.origSetHRMPower(1); if (bthrmSettings.replace) Bangle.origSetHRMPower(1);
if (Bangle.setBTHRMPower){ if (Bangle.setBTHRMPower){
Bangle.setBTHRMPower(1); Bangle.setBTHRMPower(1);
} else { } else {
@ -166,7 +153,7 @@ function createCode(){
drawStatus(free>0.25*process.env.STORAGE, h++, Math.floor(free/1024) + "K"); drawStatus(free>0.25*process.env.STORAGE, h++, Math.floor(free/1024) + "K");
} }
} }
var intervalId = -1; var intervalId = -1;
g.setFont12x20(); g.setFont12x20();
@ -181,7 +168,7 @@ function createCode(){
drawStatusText("Events", h++); drawStatusText("Events", h++);
if (method == 2) drawStatusText("Storage", h++); if (method == 2) drawStatusText("Storage", h++);
updateStatus(); updateStatus();
intervalId = setInterval(()=>{ intervalId = setInterval(()=>{
updateStatus(); updateStatus();
}, 1000); }, 1000);
@ -221,8 +208,8 @@ document.getElementById("chkLocal").addEventListener("click", function() {
window.addEventListener("message", function(event) { window.addEventListener("message", function(event) {
let msg = event.data; let msg = event.data;
if (msg.type=="readstoragefilersp") { if (msg.type=="readstoragefilersp") {
saveCSV("log.csv", msg.data); Util.saveCSV("log", msg.data);
} }
}, false); }, false);
@ -240,7 +227,7 @@ document.getElementById("btnDownload").addEventListener("click", function() {
}); });
document.getElementById("btnSave").addEventListener("click", function() { document.getElementById("btnSave").addEventListener("click", function() {
saveCSV("log.csv", localStorage.getItem("data")); Util.saveCSV("log", localStorage.getItem("data"));
}); });
function reset(){ function reset(){
@ -289,7 +276,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
l.forEach(onLine); l.forEach(onLine);
}); });
connection.write("reset();\n", function() { connection.write("reset();\n", function() {
setTimeout(function() { setTimeout(function() {
connection.write("\x03\x10if(1){"+createCode()+"}\n", connection.write("\x03\x10if(1){"+createCode()+"}\n",
function() { console.log("Ready..."); }); function() { console.log("Ready..."); });
}, 1500); }, 1500);

View File

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

View File

@ -0,0 +1,19 @@
# LCD Clock Plus
A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.
Forked from [LCD Clock](https://banglejs.com/apps/?id=lcdclock), inspired by [Cassio Watch](https://banglejs.com/apps/?id=cassioWatch).
---
Recommended to be used together with the [Casio Logo Widget](https://banglejs.com/apps/?id=widcasiologo).
---
Differences from the original LCD Clock:
* Two extra (4 total) ClockInfo areas at the bottom of the screen. ClockInfo areas are now smaller and only include the icon and the data.
* AM/PM labels moved to the right of the time and are only visible when 12-hour mode is enabled in the system locale settings.
* Month name label added.
* Background color changed to better mimic old-style LCD screens.
* Various small alignment ajustments.

View File

@ -0,0 +1 @@
atob("MDCI/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA5OTk5OUBAQEBAQEBAQEBAQEBAQEBAQDlAOTlAQEBAQEBAQEBAQEBAQEBAQEBABxU5BwAAB0BHR0dHR0dHR0dHR0dHR0dHR0BAOTJHR0dHR0dHR0dHR0dHR0dHR0dHRzkAAAAAAABHR0dAOUBHR0dAQDlAR0dHRzk5OTI5MjJHR0dAQEBHQEA5OUBAR0BHRwcAAAAAAABHR0dHRzJHR0A5DkdHR0dHRxU5OTlAQDJHR0cOQEBHOTlHQEAyRzlHRwcAAAAAAABHR0c5MkdHR0dAQEdHR0dHRzlAOTk5OUBHR0dAQDIyQEAyOUdAMjJHRw4AAAAAAABHR0A5R0dHR0A5DkdHR0dHRzk5OTk5DkdHR0cOQEBHOTlHR0cyRzlHR0AHAAAAAA5HR0c5DkdHR0dHQA45R0dHR0BHRzkVDkdHR0dAR0dHQEBHR0dAR0BHR0BHOTk5QEdHR0dHR0dHR0dHR0dHR0dHR0BHR0dAQEdHR0dHR0dHR0dHR0dHR0dHRw4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODkBHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BAOUBAR0dAQDlAR0dHR0dHR0dHR0dHRzI5R0c5OUdAOTlAR0dHR0dAR0A5OUdHR0BHRw45QEA5MkdHR0dHR0dHR0dHR0dHRzJHR0A5R0c5QEA5R0dHR0cOR0dHMkdHR0BHR0BAR0dAQA45R0dHR0dHR0dHR0dHR0AOQEcODkdADjlHR0dHR0dARzkOQEdHR0BHRw45QEA5MkdHR0dHR0dHR0dHR0dHR0dHMkA5R0c5QEdHR0dHR0cORzJHR0dHR0BHR0BHOTlHQDI5R0dHR0dHR0dHR0dHR0AyQEc5MkdHR0dHR0dHR0dARzkyR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHQAAAAABHR0dHR0dHR0AAAAAAQEdHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHOTk5MkdHR0cHQEdHR0dHR0dHR0dHB0BHR0dHR0AHR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEcyBzlHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEcOADJHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjI5K0dHR0cAQEdAOUBHR0dHR0dHB0BHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHQEBAOTIyMjI5R0dHR0dHR0cyMjIyOUBHR0dHR0c5R0dHR0dHR0BHR0dHR0dHR0dHQEBHQA4ODg45R0dHR0dHQDkODg4OR0dHR0dHR0c5R0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEdAOUBHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEcOADJHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHDjJHR0dHR0cAQEcOBzlHQAdHR0dHR0dHR0dHR0AAR0dHR0dHR0BHR0dHR0dHR0dHFTlHR0dHR0cHQEdHR0dHQAdHR0dHR0dHR0dHR0AHR0dHR0dHR0BHR0dHR0dHR0dHR0dHQAAAAABHR0dHR0dHR0AAAAAAQEdHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHRzlAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBVAOUBAQEBAQEBAQEBAQEBAQEBAQEBAQBVAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEA5AEBHR0dHR0dHR0dHR0dHR0dHR0dHR0BHRzlAR0dHR0dHR0dHR0dHR0dHR0dHR0A5ABVAOUdHQDk5Rzk5QEc5OUBHOTlHR0BHRzlAR0dHR0BAOTlAR0dHOTlARzk5R0A5ADkOAEBHR0cOR0dHOUdHQEBHRzlHR0BHR0BHR0dHRw45QEA5Rw5HR0A5R0c5QEBHDjkHAEdHQA5AR0dHQEdHQEBHDg5HR0BAQEBHOUdHR0BHOQ5AR0BAMhVHQA4VR0BHFRU5DkdHR0cOR0dHOUdHQEBHRzlHR0BHQAAHR0dHRw5HR0A5RxU5QEdHMkdHR0BHR0cAOUdHQDJAR0dHQEdHR0dHMjlHR0A5MjIyMkBHR0BHOTlHR0dHOTlHQDI5RzlHR0dAR0dHR0dHR0dHR0dHR0dHR0dHR0BHR0dHR0dHR0dHR0dHR0dHR0dHR0dHRwAyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMg4yMjIyMjIyMjIyMjIyMjIyMjIyMjIyBwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")

108
apps/lcdclockplus/app.js Normal file
View File

@ -0,0 +1,108 @@
Graphics.prototype.setFont7Seg = function() {
return this.setFontCustom(atob("AAAAAAAAAAAACAQCAAAAAAIAd0BgMBdwAAAAAAAADuAAAB0RiMRcAAAAAiMRiLuAAAcAQCAQdwAADgiMRiIOAAAd0RiMRBwAAAAgEAgDuAAAd0RiMRdwAADgiMRiLuAAAABsAAAd0QiEQdwAADuCIRCIOAAAd0BgMBAAAAAOCIRCLuAAAd0RiMRAAAADuiEQiAAAAAd0BgMBBwAADuCAQCDuAAAdwAAAAAAAAAAAIBALuAAAdwQCAQdwAADuAIBAIAAAAd0AgEAcEAgEAdwAd0AgEAdwAADugMBgLuAAAd0QiEQcAAADgiEQiDuAAAd0AgEAAAAADgiMRiIOAAAAEAgEAdwAADuAIBALuAAAdwBAIBdwAADuAIBAIOAIBALuADuCAQCDuAAAcAQCAQdwAAAOiMRiLgAAAA=="), 32, atob("BwAAAAAAAAAAAAAAAAcCAAcHBwcHBwcHBwcEAAAAAAAABwcHBwcHBwcHBwcHCgcHBwcHBwcHBwoHBwc="), 9);
};
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let drawTimeout;
require("Font7x11Numeric7Seg").add(Graphics);
const is12Hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"];
// Actually draw the watch face
let draw = function() {
var x = R.x + R.w/2;
var y = R.y + 48;
g.reset().setColor(g.theme.bg).setBgColor(g.theme.fg);
g.clearRect(R.x,bar1Y+2,R.x2,bar2Y-2);
var date = new Date();
var timeStr = require("locale").time(date, 1); // Hour and minute
// Time
if (is12Hour) {
g.setFontAlign(-1, 0).setFont("7x11Numeric7Seg:4").drawString(timeStr, R.x, y+39);
g.setFontAlign(1, 0).setFont("7Seg:2").drawString(require("locale").meridian(date).toUpperCase(), R.x2, y+30+(date.getHours() >= 12 ? 20 : 0));
} else {
g.setFontAlign(0, 0).setFont("7x11Numeric7Seg:4").drawString(timeStr, x, y+39);
}
// Day of week
g.setFontAlign(-1, 0).setFont("7Seg:2").drawString(require("locale").dow(date, 1).toUpperCase(), R.x+2, y);
// Date
g.setFontAlign(-1, 0).setFont("7Seg:2").drawString(require("locale").month(new Date(), 2).toUpperCase(), x, y);
g.setFontAlign(1, 0).setFont("7Seg:2").drawString(date.getDate(), R.x2 - 6, y);
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
let clockInfoDraw = (itm, info, options) => {
let texty = options.y+41;
g.reset().setFont("7Seg").setColor(g.theme.bg).setBgColor(g.theme.fg);
if (options.focus) g.setBgColor("#FF0");
g.clearRect({x:options.x,y:options.y,w:options.w,h:options.h,r:8});
if (info.img) {
g.drawImage(info.img, options.x+1,options.y+2);
}
var text = info.text.toString().toUpperCase();
if (g.setFont("7Seg:2").stringWidth(text)+24-2>options.w) g.setFont("7Seg");
g.setFontAlign(0,-1).drawString(text, options.x+options.w/2+13, options.y+6);
};
// Show launcher when middle button pressed
Bangle.setUI({
mode : "clock",
remove : function() {
// Called to unload all of the clock app
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
delete Graphics.prototype.setFont7Seg;
// remove info menu
clockInfoMenu.remove();
delete clockInfoMenu;
clockInfoMenu2.remove();
delete clockInfoMenu2;
clockInfoMenu3.remove();
delete clockInfoMenu3;
clockInfoMenu4.remove();
delete clockInfoMenu4;
// reset theme
g.setTheme(oldTheme);
}});
// Load widgets
Bangle.loadWidgets();
// Work out sizes
let R = Bangle.appRect;
R.x+=1;
R.y+=1;
R.x2-=1;
R.y2-=1;
R.w-=1;
R.h-=1;
let midX = R.x+R.w/2;
let bar1Y = R.y+30;
let bar2Y = R.y2-30;
// Clear the screen once, at startup
let oldTheme = g.theme;
g.setTheme({bg:"#000",fg:"#3ff",dark:true}).clear(1);
g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8})
.clearRect(R.x,bar1Y,R.w,bar1Y+1)
.clearRect(midX,R.y,midX,bar1Y)
.clearRect(R.x,bar2Y,R.w,bar2Y+1)
.clearRect(midX,bar2Y,midX,R.y2+1);
draw();
Bangle.drawWidgets();
// Allocate and draw clockinfos
let clockInfoItems = require("clock_info").load();
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:R.y, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:R.y, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
let clockInfoMenu3 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:bar2Y+2, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
let clockInfoMenu4 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:bar2Y+2, w:midX-2, h:bar1Y-R.y-2, draw : clockInfoDraw});
}

BIN
apps/lcdclockplus/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,16 @@
{ "id": "lcdclockplus",
"name": "LCD Clock Plus",
"version":"0.01",
"description": "A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
"type": "clock",
"tags": "clock,clkinfo",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"dependencies" : { "clock_info":"module" },
"storage": [
{"name":"lcdclockplus.app.js","url":"app.js"},
{"name":"lcdclockplus.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -5,3 +5,5 @@
0.05: Prevent drawing into app area. 0.05: Prevent drawing into app area.
0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden) 0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden)
0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler. 0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler.
0.08: Ensure boot code doesn't allocate and leave a gloval variable named 'settings'
0.09: Handle lightswitch logic running before its widget has loaded

View File

@ -1,5 +1,6 @@
{
// load settings // load settings
var settings = Object.assign({ let settings = Object.assign({
value: 1, value: 1,
isOn: true isOn: true
}, require("Storage").readJSON("lightswitch.json", true) || {}); }, require("Storage").readJSON("lightswitch.json", true) || {});
@ -12,6 +13,4 @@ Bangle.removeListener("tap", require("lightswitch.js").tapListener);
// add tap listener to unlock and/or flash backlight // add tap listener to unlock and/or flash backlight
if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener); if (settings.unlockSide || settings.tapSide) Bangle.on("tap", require("lightswitch.js").tapListener);
}
// clear variable
settings = undefined;

View File

@ -6,7 +6,7 @@ exports = {
// check for double tap and direction // check for double tap and direction
if (data.double) { if (data.double) {
// setup shortcut to this widget or load from storage // setup shortcut to this widget or load from storage
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({ var w = global.WIDGETS && WIDGETS.lightswitch || Object.assign({
unlockSide: "", unlockSide: "",
tapSide: "right", tapSide: "right",
tapOn: "always", tapOn: "always",
@ -31,7 +31,7 @@ exports = {
// function to flash backlight // function to flash backlight
flash: function(tOut) { flash: function(tOut) {
// setup shortcut to this widget or load from storage // setup shortcut to this widget or load from storage
var w = global.WIDGETS ? WIDGETS.lightswitch : Object.assign({ var w = global.WIDGETS && WIDGETS.lightswitch || Object.assign({
tOut: 3000, tOut: 3000,
minFlash: 0.2, minFlash: 0.2,
value: 1, value: 1,

View File

@ -2,7 +2,7 @@
"id": "lightswitch", "id": "lightswitch",
"name": "Light Switch Widget", "name": "Light Switch Widget",
"shortName": "Light Switch", "shortName": "Light Switch",
"version": "0.07", "version": "0.09",
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.", "description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
"icon": "images/app.png", "icon": "images/app.png",
"screenshots": [ "screenshots": [

View File

@ -2,6 +2,10 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
<style>
table { width:100%;}
.table_t {font-weight:bold;width:40%;};
</style>
</head> </head>
<body> <body>
@ -13,6 +17,11 @@
<div class="form-group"> <div class="form-group">
<input id="translations" type="checkbox" /> <label for="translations">Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label> <input id="translations" type="checkbox" /> <label for="translations">Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label>
</div> </div>
<p>
<table id="examples">
</p>
</table>
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p> <p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
@ -93,21 +102,8 @@ exports = { name : "en_GB", currencySym:"£",
checkChars(locale,localeName); checkChars(locale,localeName);
}); });
var languageSelector = document.getElementById("languages");
languageSelector.innerHTML = Object.keys(locales).map(l=>{
var localeParts = l.split("_"); // en_GB -> ["en","GB"]
var icon = "";
// If we have a 2 char ISO country code, use it to get the unicode flag
if (localeParts[1] && localeParts[1].length==2)
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
if (localeParts[1]=="NAV")
icon = "&#9973;&#9992;&#65039; ";
return `<option value="${l}">${icon}${l}</option>`
}).join("\n");
document.getElementById("upload").addEventListener("click", function() { function createLocaleModule(lang) {
const lang = languageSelector.options[languageSelector.selectedIndex].value;
console.log(`Language ${lang}`); console.log(`Language ${lang}`);
const translations = document.getElementById('translations').checked; const translations = document.getElementById('translations').checked;
@ -151,7 +147,7 @@ exports = { name : "en_GB", currencySym:"£",
var replaceList = { var replaceList = {
"%Y": "d.getFullYear()", "%Y": "d.getFullYear()",
"%y": "(d.getFullYear().toString()).slice(-2)", "%y": "d.getFullYear().toString().slice(-2)",
"%m": "('0'+(d.getMonth()+1).toString()).slice(-2)", "%m": "('0'+(d.getMonth()+1).toString()).slice(-2)",
"%-m": "d.getMonth()+1", "%-m": "d.getMonth()+1",
"%d": "('0'+d.getDate()).slice(-2)", "%d": "('0'+d.getDate()).slice(-2)",
@ -182,16 +178,17 @@ exports = { name : "en_GB", currencySym:"£",
`exports.number(n) + ${js(locale.currency_symbol)}`; `exports.number(n) + ${js(locale.currency_symbol)}`;
var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't'; var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't';
var localeModule = ` function getLocaleModule(isLocal) {
return `
function round(n, dp) { function round(n, dp) {
if (dp===undefined) dp=0; if (dp===undefined) dp=0;
var p = Math.min(dp,dp - Math.floor(Math.log(n)/Math.log(10))); var p = Math.max(0,Math.min(dp,dp - Math.floor(Math.log(n)/Math.log(10))));
return n.toFixed(p); return n.toFixed(p);
} }
var is12; var is12;
function getHours(d) { function getHours(d) {
var h = d.getHours(); var h = d.getHours();
if (is12 === undefined) is12 = (require('Storage').readJSON('setting.json', 1) || {})["12hour"]; if (is12 === undefined) is12 = ${isLocal ? "false" : `(require('Storage').readJSON('setting.json', 1) || {})["12hour"]`};
if (!is12) return ('0' + h).slice(-2); if (!is12) return ('0' + h).slice(-2);
return ((h % 12 == 0) ? 12 : h % 12).toString(); return ((h % 12 == 0) ? 12 : h % 12).toString();
} }
@ -224,7 +221,54 @@ exports = {
time: (d,short) => short ? \`${timeS}\` : \`${timeN}\`, time: (d,short) => short ? \`${timeS}\` : \`${timeN}\`,
meridian: d => d.getHours() < 12 ? ${js(locale.ampm[0])}:${js(locale.ampm[1])}, meridian: d => d.getHours() < 12 ? ${js(locale.ampm[0])}:${js(locale.ampm[1])},
}; };
`.trim(); `.trim()
};
var exports;
eval(getLocaleModule(true));
console.log("exports:",exports);
var date = new Date();
document.getElementById("examples").innerHTML = `
<tr><td class="table_t"></td><td style="font-weight:bold">Short</td><td style="font-weight:bold">Long</td></tr>
<tr><td class="table_t">Day</td><td>${exports.dow(date,1)}</td><td>${exports.dow(date,0)}</td></tr>
<tr><td class="table_t">Month</td><td>${exports.month(date,1)}</td><td>${exports.month(date,0)}</td></tr>
<tr><td class="table_t">Date</td><td>${exports.date(date,1)}</td><td>${exports.date(date,0)}</td></tr>
<tr><td class="table_t">Time</td><td>${exports.time(date,1)}</td><td>${exports.time(date,0)}</td></tr>
<tr><td class="table_t">Number</td><td>${exports.number(12.3456789)}</td><td>${exports.number(12.3456789,4)}</td></tr>
<tr><td class="table_t">Currency</td><td></td><td>${exports.currency(12.34)}</td></tr>
<tr><td class="table_t">Distance</td><td>${exports.distance(12.34,0)}</td><td>${exports.distance(12345.6,1)}</td></tr>
<tr><td class="table_t">Speed</td><td></td><td>${exports.speed(123)}</td></tr>
<tr><td class="table_t">Temperature</td><td></td><td>${exports.temp(12,0)}</td></tr>
`;
return getLocaleModule(false);
}
var languageSelector = document.getElementById("languages");
languageSelector.innerHTML = Object.keys(locales).map(l=>{
var locale = locales[l];
var localeParts = l.split("_"); // en_GB -> ["en","GB"]
var icon = "";
// If we have a 2 char ISO country code, use it to get the unicode flag
if (locale.icon)
icon = locale.icon+" ";
else if (localeParts[1] && localeParts[1].length==2)
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
return `<option value="${l}">${icon}${l}${locale.notes?" - "+locale.notes:""}</option>`
}).join("\n");
languageSelector.addEventListener('change', function() {
const lang = languageSelector.options[languageSelector.selectedIndex].value;
createLocaleModule(lang);
});
// initial value
createLocaleModule(languageSelector.options[languageSelector.selectedIndex].value);
document.getElementById("upload").addEventListener("click", function() {
const lang = languageSelector.options[languageSelector.selectedIndex].value;
var localeModule = createLocaleModule(lang);
console.log("Locale Module is:",localeModule); console.log("Locale Module is:",localeModule);
sendCustomizedApp({ sendCustomizedApp({

View File

@ -79,6 +79,44 @@ var locales = {
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday", day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english... // No translation for english...
}, },
"en_US": {
lang: "en_US",
notes: "USA with MM/DD/YY date",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$", currency_first: true,
int_curr_symbol: "USD",
speed: "mph",
distance: { 0: "ft", 1: "mi" },
temperature: "°F",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%b %d, %Y", 1: "%m/%d/%y" },
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english...
},
"en_US 2": {
lang: "en_US 2", icon:"🇺🇸",
notes: "USA with YYYY-MM-DD date",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$", currency_first: true,
int_curr_symbol: "USD",
speed: "mph",
distance: { 0: "ft", 1: "mi" },
temperature: "°F",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%b %d, %Y", 1: "%Y-%m-%d" },
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english...
},
"en_IN": { "en_IN": {
lang: "en_IN", lang: "en_IN",
decimal_point: ".", decimal_point: ".",
@ -118,7 +156,7 @@ var locales = {
// No translation for english... // No translation for english...
}, },
"en_NAV": { // navigation units nautical miles and knots "en_NAV": { // navigation units nautical miles and knots
lang: "en_NAV", lang: "en_NAV", icon: "&#9973;&#9992;&#65039;",
decimal_point: ".", decimal_point: ".",
thousands_sep: ",", thousands_sep: ",",
currency_symbol: "£", currency_first: true, currency_symbol: "£", currency_first: true,
@ -154,24 +192,6 @@ var locales = {
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus", trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus",
"< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" } "< Back": "< Zurück", "Delete": "Löschen", "Mark Unread": "Als ungelesen markieren" }
}, },
"en_US": {
lang: "en_US",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$", currency_first: true,
int_curr_symbol: "USD",
speed: "mph",
distance: { 0: "ft", 1: "mi" },
temperature: "°F",
ampm: { 0: "am", 1: "pm" },
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%b %d, %Y", 1: "%m/%d/%y" },
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
// No translation for english...
},
"en_JP": { // we do not have the font, so it is not ja_JP "en_JP": { // we do not have the font, so it is not ja_JP
lang: "en_JP", lang: "en_JP",
decimal_point: ".", decimal_point: ".",

View File

@ -95,17 +95,7 @@
Util.showModal("Downloading..."); Util.showModal("Downloading...");
let medicalInfoJson = getEditableContent(); let medicalInfoJson = getEditableContent();
if (isJsonString(medicalInfoJson)) { if (isJsonString(medicalInfoJson)) {
var a = document.createElement("a"), Util.saveFile(medicalInfoFile, "application/json", medicalInfoJson);
file = new Blob([medicalInfoJson], { type: "application/json" });
var url = URL.createObjectURL(file);
a.href = url;
a.download = medicalInfoFile;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} else { } else {
document.getElementById("info").innerHTML = errorFormat(); document.getElementById("info").innerHTML = errorFormat();
} }

View File

@ -99,4 +99,5 @@
0.71: Cancel buzzing when watch unlocked, or when different messages viewed 0.71: Cancel buzzing when watch unlocked, or when different messages viewed
On 2v18.64+ firmware, 'No Messages' now has a 'back' button On 2v18.64+ firmware, 'No Messages' now has a 'back' button
0.72: Nav message updastes don't automatically launch navigation menu unless they're new 0.72: Nav message updastes don't automatically launch navigation menu unless they're new
0.73: Add sharp left+right nav icons 0.73: Add sharp left+right nav icons
0.74: Add option for driving on left (affects roundabout icons in navigation)

View File

@ -98,6 +98,7 @@ function showMapMessage(msg) {
}else }else
target = instr; target = instr;
} }
var carIsRHD = !!settings.carIsRHD;
switch (msg.action) { switch (msg.action) {
case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break; case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break;
case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break; case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break;
@ -111,12 +112,11 @@ function showMapMessage(msg) {
case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break; case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break;
case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break; case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break;
case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break; case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break;
case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break; case "roundabout_left": img = carIsRHD ? "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=" : "HRYCAAPAAAAAAAAD/AAD//AAA/8AD///AAP/AA////AD/wAD/wP/A/8AA/wAP8P/////AAP//////8AA///////AAD/P////8AAP8P/AABUAD/AP/AAFUA/8AP/AAFX//AAP/AAFf/wAAP8AAB/8AAAPAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAA==";break;
case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break; case "roundabout_right": img = carIsRHD ? "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=" : "HBYCAAAAAAPAAABVQAAP8AAFVVQAD/wAFVVVAAP/ABUAFQAA/8BUAAVAAD/wVAAP/////FAAD/////9QAA//////VAAP/////FQAP8AAP/AVAP/AAP/AFX//AAP/AAV//AAP/AAAf/AAD/AAAD/AAAPAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAA==";break;
case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break; case "roundabout_straight": img = carIsRHD ? "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==" : "EBsCAAPAAAAP8AAAP/wAAP//AAP//8AP///wP8/z/P8P8P/8D/A/MA/wDABf/wABX//ABV//8BVAD/wVAAP8FQAA/BUAAPwVAAD8FQAA/BUAA/wVQA/8BV//8AFf/8AAX/8AAA/wAAAP8AAAD/AA";break;
case "roundabout_uturn": img = "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA";break; case "roundabout_uturn": img = carIsRHD ? "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA" : "ICABAAAAAAAAAAAAAAAAAAfwAAAP+AAAH/wAAD4eAAA8DwAAOA8AAHgHAAB4BwAAOA8AADwPAAA+HgAAHzwAAA84AAAPIAAADwAAAY8YAAPPPAAB73wAAf/4AAD/8AAAP+gAAB/YAAAPuAAADzgAAAY4AAAAAAAAAAAAAAAAAAAAAAA=";break;
} }
//FIXME: what about countries where we drive on the right? How will we know to flip the icons?
layout = new Layout({ type:"v", c: [ layout = new Layout({ type:"v", c: [
{type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 }, {type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 },

View File

@ -2,7 +2,7 @@
"id": "messagegui", "id": "messagegui",
"name": "Message UI", "name": "Message UI",
"shortName": "Messages", "shortName": "Messages",
"version": "0.73", "version": "0.74",
"description": "Default app to display notifications from iOS and Gadgetbridge/Android", "description": "Default app to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png", "icon": "app.png",
"type": "app", "type": "app",

View File

@ -5,3 +5,4 @@
0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours. 0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours.
0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called 0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called
0.61: Add repeatCalls option to allow different repeat settings for messages vs calls 0.61: Add repeatCalls option to allow different repeat settings for messages vs calls
0.62: Add option for driving on left (affects roundabout icons in navigation)

View File

@ -43,6 +43,28 @@ with a method called `open` that will cause it to be opened, with the
optionally supplied message. See `apps/messagegui/lib.js` for an example. optionally supplied message. See `apps/messagegui/lib.js` for an example.
## Settings
You can configure settings by going to `Settings -> Apps -> Messages`
There are several options to choose from:
* **Vibrate** : Vibration pattern to use for messages
* **Vibrate for calls** : Vibration pattern to use for calls
* **Repeat** : How many times to vibrate for messages
* **Repeat for calls** : How many times to vibrate for calls
* **Vibrate timer** : How many seconds should we vibrate for?
* **Unread timer** : How long should the Messages app show an unread message for before going back to the clock?
* **Min Font** : Minimum font size for messages
* **Auto-Open Music** : Should the messages music screen auto open when music is played?
* **Unlock Watch** : When a message arrives should the watch be unlocked?
* **Flash Icon** : Should the messages icon in the widget flash when a message arrives?
* **Quiet mode disables auto-open** : When in quiet mode, should we not open the messages app for new messages?
* **Disable auto-open** : Should we not open the messages app for new messages?
* **Widget messages** : How many message icons should the widget show?
* **Icon color mode** : Should icons in widgets be coloured?
* **Car driver pos** : What side of the car is the driver on? This affects navigation icons for roundabouts
## Requests ## Requests
Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=[messages]%20library Please file any issues on https://github.com/espruino/BangleApps/issues/new?title=[messages]%20library

View File

@ -1,7 +1,7 @@
{ {
"id": "messages", "id": "messages",
"name": "Messages", "name": "Messages",
"version": "0.61", "version": "0.62",
"description": "Library to handle, load and store message events received from Android/iOS", "description": "Library to handle, load and store message events received from Android/iOS",
"icon": "app.png", "icon": "app.png",
"type": "module", "type": "module",

View File

@ -89,7 +89,12 @@
min: 0, max: iconColorModes.length - 1, min: 0, max: iconColorModes.length - 1,
format: v => iconColorModes[v], format: v => iconColorModes[v],
onchange: v => updateSetting("iconColorMode", iconColorModes[v]) onchange: v => updateSetting("iconColorMode", iconColorModes[v])
} },
/*LANG*/'Car driver pos': { // used by messagegui
value:!!settings().carIsRHD,
format: v => v ? /*LANG*/"Right" :/*LANG*/"Left",
onchange: v => updateSetting("carIsRHD", v)
},
}; };
E.showMenu(mainmenu); E.showMenu(mainmenu);
}); });

View File

@ -96,17 +96,7 @@
Util.showModal("Downloading..."); Util.showModal("Downloading...");
let csvTimes = getEditableContent(); let csvTimes = getEditableContent();
if (isCorrectCsvString(csvTimes)) { if (isCorrectCsvString(csvTimes)) {
var a = document.createElement("a"), Util.saveFile(filePresentationTimer, "text/csv", csvTimes);
file = new Blob([csvTimes], { type: "text/csv" });
var url = URL.createObjectURL(file);
a.href = url;
a.download = filePresentationTimer;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} else { } else {
document.getElementById("info").innerHTML = errorFormat(); document.getElementById("info").innerHTML = errorFormat();
} }

View File

@ -75,17 +75,7 @@ ${track.map(pt=>` <gx:value>${0|pt.Skin}</gx:value>\n`).join("")}
</Folder> </Folder>
</Document> </Document>
</kml>`; </kml>`;
var a = document.createElement("a"), Util.saveFile(title+".kml", "application/vnd.google-earth.kml+xml", kml);
file = new Blob([kml], {type: "application/vnd.google-earth.kml+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".kml";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
showToast("Download finished.", "success"); showToast("Download finished.", "success");
} }
@ -122,17 +112,7 @@ function saveGPX(track, title) {
</trkseg> </trkseg>
</trk> </trk>
</gpx>`; </gpx>`;
var a = document.createElement("a"), Util.saveFile(title+".gpx", "application/gpx+xml", gpx);
file = new Blob([gpx], {type: "application/gpx+xml"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = title+".gpx";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
showToast("Download finished.", "success"); showToast("Download finished.", "success");
} }

View File

@ -3,3 +3,4 @@
0.03: Update help screen with more details. 0.03: Update help screen with more details.
0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game. 0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game.
0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one. 0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one.
0.06: fix AI logic typo and add prompt to show what AI played each turn.

View File

@ -2,7 +2,7 @@
"name": "Red 7 Card Game", "name": "Red 7 Card Game",
"shortName" : "Red 7", "shortName" : "Red 7",
"icon": "icon.png", "icon": "icon.png",
"version":"0.05", "version":"0.06",
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!", "description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
"tags": "game", "tags": "game",
"supports":["BANGLEJS2"], "supports":["BANGLEJS2"],

View File

@ -17,6 +17,9 @@ class Card {
//this.rect = {}; //this.rect = {};
this.clippedRect = {}; this.clippedRect = {};
} }
get description() {
return this.cardColor+" "+this.cardNum;
}
get number() { get number() {
return this.cardNum; return this.cardNum;
} }
@ -514,7 +517,7 @@ class AI {
//Play card that wins //Play card that wins
this.palette.addCard(c); this.palette.addCard(c);
this.hand.removeCard(c); this.hand.removeCard(c);
return true; return { winning: true, paletteAdded: c };
} }
clonePalette.removeCard(c); clonePalette.removeCard(c);
} }
@ -524,26 +527,26 @@ class AI {
//Play rule card that wins //Play rule card that wins
ruleStack.addCard(c); ruleStack.addCard(c);
this.hand.removeCard(c); this.hand.removeCard(c);
return true; return { winning: true, ruleAdded: c };
} else { } else {
//Check if any palette play can win with rule. //Check if any palette play can win with rule.
for(let h of this.hand.handCards) { for(let h of this.hand.handCards) {
if(h === c) {} if(h === c) {}
else { else {
clonePalette.addCard(c); clonePalette.addCard(h);
if(isWinningCombo(c, clonePalette, otherPalette)) { if(isWinningCombo(c, clonePalette, otherPalette)) {
ruleStack.addCard(c); ruleStack.addCard(c);
this.hand.removeCard(c); this.hand.removeCard(c);
this.palette.addCard(h); this.palette.addCard(h);
this.hand.removeCard(h); this.hand.removeCard(h);
return true; return { winning: true, ruleAdded: c, paletteAdded: h };
} }
clonePalette.removeCard(c); clonePalette.removeCard(h);
} }
} }
} }
} }
return false; return { winning: false };
} }
} }
@ -726,18 +729,20 @@ function finishTurn() {
if(AIhand.handCards.length === 0) { if(AIhand.handCards.length === 0) {
drawGameOver(true); drawGameOver(true);
} else { } else {
var takenTurn = aiPlayer.takeTurn(ruleCards, playerPalette); var aiResult = aiPlayer.takeTurn(ruleCards, playerPalette);
//Check if game over conditions met. E.showPrompt("AI played: " + ("paletteAdded" in aiResult ? aiResult["paletteAdded"].description+" to pallete. ":"") + ("ruleAdded" in aiResult ? aiResult["ruleAdded"].description+" to rules.":""),{buttons: {"Ok":0}}).then(function(){
if(!takenTurn) { //Check if game over conditions met.
drawGameOver(true); if(!aiResult["winning"]) {
} else if(playerHand.handCards.length === 0) { drawGameOver(true);
drawGameOver(false); } else if(playerHand.handCards.length === 0) {
} else if(!canPlay(playerHand, playerPalette, AIPalette)) { drawGameOver(false);
drawGameOver(false); } else if(!canPlay(playerHand, playerPalette, AIPalette)) {
} else { drawGameOver(false);
E.showMenu(); } else {
drawScreen1(); E.showMenu();
} drawScreen1();
}
});
} }
} }
@ -843,4 +848,3 @@ drawMainMenu();
setWatch(function(){ setWatch(function(){
drawMainMenu(); drawMainMenu();
},BTN, {edge: "rising", debounce: 50, repeat: true}); },BTN, {edge: "rising", debounce: 50, repeat: true});

2
apps/spacew/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: use binary format, include world map

View File

@ -38,6 +38,8 @@ Directories at the end of .mtar should be hashed, not linear searched.
Geojson is not really suitable as it takes a lot of storage. Geojson is not really suitable as it takes a lot of storage.
It would be nice to support polygons. Web-based tool for preparing maps would be nice.
Web-based tool for preparing maps would be nice. Storing 12bit coordinates, but only using 8bits.
Polygons should go first to get proper z-order.

View File

@ -386,7 +386,7 @@ function emptyMap() {
m.scale = 2; m.scale = 2;
g.reset().clearRect(R); g.reset().clearRect(R);
redraw(18); redraw(18);
print("Benchmark done (31 sec)"); print("Benchmark done");
} }
}; };
if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{ if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{
@ -400,36 +400,52 @@ function emptyMap() {
var gjson = null; var gjson = null;
function stringFromArray(data) {
var count = data.length;
var str = "";
for(var index = 0; index < count; index += 1)
str += String.fromCharCode(data[index]);
return str;
}
const st = require('Storage');
const hs = require('heatshrink');
function readTarFile(tar, f) { function readTarFile(tar, f) {
const st = require('Storage'); let json_off = st.read(tar, 0, 16) * 1;
json_off = st.read(tar, 0, 16) * 1;
if (isNaN(json_off)) { if (isNaN(json_off)) {
print("Don't have archive", tar); print("Don't have archive", tar);
return undefined; return undefined;
} }
while (1) { while (1) {
json_len = st.read(tar, json_off, 6) * 1; let json_len = st.read(tar, json_off, 6) * 1;
if (json_len == -1) if (json_len == -1)
break; break;
json_off += 6; json_off += 6;
json = st.read(tar, json_off, json_len); let json = st.read(tar, json_off, json_len);
//print("Have directory, ", json.length, "bytes"); //print("Have directory, ", json.length, "bytes");
//print(json); let files = JSON.parse(json);
files = JSON.parse(json); let rec = files[f];
//print(files); if (rec) {
rec = files[f]; let cs = st.read(tar, rec.st, rec.si);
if (rec) if (rec.comp == "hs") {
return st.read(tar, rec.st, rec.si); let d = stringFromArray(hs.decompress(cs));
//print("Decompressed", d);
return d;
}
return cs;
}
json_off += json_len; json_off += json_len;
} }
return undefined; return undefined;
} }
function loadVector(name) { function loadVector(name) {
var t1 = getTime(); var t1 = getTime();
print(".. Read", name); print(".. Read", name);
//s = require("Storage").read(name); //s = require("Storage").read(name);
var s = readTarFile("delme.mtar", name); var s = readTarFile("world.mtar", name);
if (s == undefined) { if (s == undefined) {
print("Don't have file", name); print("Don't have file", name);
return null; return null;
@ -438,8 +454,7 @@ function loadVector(name) {
print(".... Read and parse took ", getTime()-t1); print(".... Read and parse took ", getTime()-t1);
return r; return r;
} }
function drawPoint(a) { /* FIXME: let... */
function drawPoint(a) {
lon = a.geometry.coordinates[0]; lon = a.geometry.coordinates[0];
lat = a.geometry.coordinates[1]; lat = a.geometry.coordinates[1];
@ -459,7 +474,6 @@ function drawPoint(a) {
g.drawString(a.properties.name, p.x, p.y); g.drawString(a.properties.name, p.x, p.y);
points ++; points ++;
} }
function drawLine(a, qual) { function drawLine(a, qual) {
lon = a.geometry.coordinates[0][0]; lon = a.geometry.coordinates[0][0];
lat = a.geometry.coordinates[0][1]; lat = a.geometry.coordinates[0][1];
@ -485,38 +499,196 @@ function drawLine(a, qual) {
i = len-1; i = len-1;
points ++; points ++;
p1 = p2; p1 = p2;
g.flip();
} }
} }
function drawPolygon(a, qual) {
lon = a.geometry.coordinates[0][0];
lat = a.geometry.coordinates[0][1];
i = 1;
step = 1;
len = a.geometry.coordinates.length;
if (len > 62) {
step = log2(len) - 5;
step = 1<<step;
}
step = step * qual;
var p1 = m.latLonToXY(lat, lon);
let pol = [p1.x, p1.y];
while (i < len) {
lon = a.geometry.coordinates[i][0];
lat = a.geometry.coordinates[i][1];
var p2 = m.latLonToXY(lat, lon);
function drawVector(gjson, qual) { pol.push(p2.x, p2.y);
if (i == len-1)
break;
i = i + step;
if (i>len)
i = len-1;
points ++;
}
if (a.properties.fill) {
g.setColor(a.properties.fill);
} else {
g.setColor(.75, .75, 1);
}
g.fillPoly(pol, true);
if (a.properties.stroke) {
g.setColor(a.properties.stroke);
} else {
g.setColor(0,0,0)
}
g.drawPoly(pol, true);
}
function toScreen(tile, xy) {
// w, s, e, n, (x,y in 0..4096 range)
let x = xy[0];
let y = xy[1];
let r = {};
r.x = ((x/4096) * (tile[2]-tile[0])) + tile[0];
r.y = ((1-(y/4096)) * (tile[3]-tile[1])) + tile[1];
return r;
}
var d_off = 1;
function getBin(bin, i, prev) {
let x = bin[i*3 + d_off ]<<4;
let y = bin[i*3 + d_off+1]<<4;
//print("Point", x, y, bin);
return [x, y];
}
function getBinLength(bin) {
return (bin.length-d_off) / 3;
}
function newPoint(tile, a, rec, bin) {
var p = toScreen(tile, getBin(bin, 0, null));
var sz = 2;
if (a.properties) {
if (a.properties["marker-color"]) {
g.setColor(a.properties["marker-color"]);
}
if (a.properties.marker_size == "small")
sz = 1;
if (a.properties.marker_size == "large")
sz = 4;
}
g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz);
if (rec.tags) {
g.setColor(0,0,0);
g.setFont("Vector", 18).setFontAlign(-1,-1);
g.drawString(rec.tags.name, p.x, p.y);
}
points ++;
}
function newLine(tile, a, bin) {
let xy = getBin(bin, 0, null);
let i = 1;
let step = 1;
let len = getBinLength(bin);
let p1 = toScreen(tile, xy);
if (a.properties && a.properties.stroke) {
g.setColor(a.properties.stroke);
}
while (i < len) {
xy = getBin(bin, i, xy);
var p2 = toScreen(tile, xy);
//print(p1.x, p1.y, p2.x, p2.y);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
if (i == len-1)
break;
i = i + step;
if (i>len)
i = len-1;
points ++;
p1 = p2;
}
}
function newPolygon(tile, a, bin) {
let xy = getBin(bin, 0, null);
i = 1;
step = 1;
len = getBinLength(bin);
if (len > 62) {
step = log2(len) - 5;
step = 1<<step;
}
var p1 = toScreen(tile, xy);
let pol = [p1.x, p1.y];
while (i < len) {
xy = getBin(bin, i, xy); // FIXME... when skipping
var p2 = toScreen(tile, xy);
pol.push(p2.x, p2.y);
if (i == len-1)
break;
i = i + step;
if (i>len)
i = len-1;
points ++;
}
if (a.properties && a.properties.fill) {
g.setColor(a.properties.fill);
} else {
g.setColor(.75, .75, 1);
}
g.fillPoly(pol, true);
if (a.properties && a.properties.stroke) {
g.setColor(a.properties.stroke);
} else {
g.setColor(0,0,0)
}
g.drawPoly(pol, true);
}
function newVector(tile, rec) {
let bin = E.toUint8Array(atob(rec.b));
a = meta.attrs[bin[0]];
if (a.type == 1) {
newPoint(tile, a, rec, bin);
} else if (a.type == 2) {
newLine(tile, a, bin);
} else if (a.type == 3) {
newPolygon(tile, a, bin);
} else print("Unknown record", a);
g.flip();
}
function drawVector(gjson, tile, qual) {
var d = gjson; var d = gjson;
points = 0; points = 0;
var t1 = getTime(); var t1 = getTime();
for (var a of d.features) { let xy1 = m.latLonToXY(tile[1], tile[0]);
if (a.type != "Feature") let xy2 = m.latLonToXY(tile[3], tile[2]);
print("Expecting feature"); let t2 = [ xy1.x, xy1.y, xy2.x, xy2.y ];
print(t2);
for (var a of d) { // d.features for geojson
g.setColor(0,0,0); g.setColor(0,0,0);
if (a.type != "Feature") {
newVector(t2, a);
continue;
}
// marker-size, marker-color, stroke // marker-size, marker-color, stroke
if (qual < 32 && a.geometry.type == "Point") if (qual < 32 && a.geometry.type == "Point")
drawPoint(a); drawPoint(a);
if (qual < 8 && a.geometry.type == "LineString") if (qual < 8 && a.geometry.type == "LineString")
drawLine(a, qual); drawLine(a, qual);
if (qual < 8 && a.geometry.type == "Polygon")
drawPolygon(a, qual);
} }
print("....", points, "painted in", getTime()-t1, "sec"); print("....", points, "painted in", getTime()-t1, "sec");
} }
function fname(lon, lat, zoom) { function fname(lon, lat, zoom) {
var bbox = [lon, lat, lon, lat]; var bbox = [lon, lat, lon, lat];
var r = xyz(bbox, 13, false, "WGS84"); var r = xyz(bbox, 13, false, "WGS84");
//console.log('fname', r); //console.log('fname', r);
return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json'; return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json';
} }
function fnames(zoom) { function fnames(zoom) {
var bb = [m.lon, m.lat, m.lon, m.lat]; var bb = [m.lon, m.lat, m.lon, m.lat];
var r = xyz(bb, zoom, false, "WGS84"); var r = xyz(bb, zoom, false, "WGS84");
let maxt = 16;
while (1) { while (1) {
var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84"); var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84");
var os = m.latLonToXY(bb2[3], bb2[0]); var os = m.latLonToXY(bb2[3], bb2[0]);
@ -525,6 +697,9 @@ function fnames(zoom) {
else if (os.y >= 0) else if (os.y >= 0)
r.minY -= 1; r.minY -= 1;
else break; else break;
if (!maxt)
break;
maxt--;
} }
while (1) { while (1) {
var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84"); var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84");
@ -534,13 +709,16 @@ function fnames(zoom) {
else if (os.y <= g.getHeight()) else if (os.y <= g.getHeight())
r.maxY += 1; r.maxY += 1;
else break; else break;
if (!maxt)
break;
maxt--;
} }
if (!maxt)
print("!!! Too many tiles, not painting some");
print(".. paint range", r); print(".. paint range", r);
return r; return r;
} }
function log2(x) { return Math.log(x) / Math.log(2); } function log2(x) { return Math.log(x) / Math.log(2); }
function getZoom(qual) { function getZoom(qual) {
var z = 16-Math.round(log2(m.scale)); var z = 16-Math.round(log2(m.scale));
z += qual; z += qual;
@ -551,7 +729,6 @@ function getZoom(qual) {
return meta.max_zoom; return meta.max_zoom;
return z; return z;
} }
function drawDebug(text, perc) { function drawDebug(text, perc) {
g.setClipRect(0,0,R.x2,R.y); g.setClipRect(0,0,R.x2,R.y);
g.reset(); g.reset();
@ -564,7 +741,6 @@ function drawDebug(text, perc) {
g.setClipRect(R.x,R.y,R.x2,R.y2); g.setClipRect(R.x,R.y,R.x2,R.y2);
g.flip(); g.flip();
} }
function drawAll(qual) { function drawAll(qual) {
var zoom = getZoom(qual); var zoom = getZoom(qual);
var t1 = getTime(); var t1 = getTime();
@ -583,7 +759,7 @@ function drawAll(qual) {
var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json'; var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json';
var gjson = loadVector(n); var gjson = loadVector(n);
if (!gjson) break; if (!gjson) break;
drawVector(gjson, 1); drawVector(gjson, bbox(x, y, zoom, false, "WGS84"), 1);
} }
num++; num++;
drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles); drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles);
@ -611,7 +787,7 @@ function introScreen() {
} }
m.scale = 76; m.scale = 76000;
m.lat = 50.001; m.lat = 50.001;
m.lon = 14.759; m.lon = 14.759;

126
apps/spacew/bench.js Normal file
View File

@ -0,0 +1,126 @@
R = Bangle.appRect;
function introScreen() {
g.reset().clearRect(R);
g.setColor(0,0,0).setFont("Vector",25);
g.setFontAlign(0,0);
g.drawString("Benchmark", 85,35);
g.setColor(0,0,0).setFont("Vector",18);
g.drawString("Press button", 85,55);
}
function lineBench() {
/* 500 lines a second on hardware, 125 lines with flip */
for (let i=0; i<1000; i++) {
let x1 = Math.random() * 160;
let y1 = Math.random() * 160;
let x2 = Math.random() * 160;
let y2 = Math.random() * 160;
g.drawLine(x1, y1, x2, y2);
//g.flip();
}
}
function polyBench() {
/* 275 hollow polygons a second on hardware, 99 with flip */
/* 261 filled polygons a second on hardware, 99 with flip */
for (let i=0; i<1000; i++) {
let x1 = Math.random() * 160;
let y1 = Math.random() * 160;
let x2 = Math.random() * 160;
let y2 = Math.random() * 160;
let c = Math.random();
g.setColor(c, c, c);
g.fillPoly([80, x1, y1, 80, 80, x2, y2, 80], true);
//g.flip();
}
}
function checksum(d) {
let sum = 0;
for (i=0; i<d.length; i++) {
sum += (d[i]*1);
}
return sum;
}
function linearRead() {
/* 10000b block -> 8.3MB/sec, 781..877 IOPS
1000b block -> 920K/sec, 909 IOPS, 0.55 sec
100b block -> 100K/sec
10b block -> 10K/sec, 1020 IOPS, 914 IOPS with ops counting
1000b block backwards -- 0.59 sec.
100b block -- 5.93.
backwards -- 6.27
random -- 7.13
checksum 5.97 -> 351 seconds with checksum. 1400bytes/second
*/
let size = 500000;
let block = 100;
let i = 0;
let ops = 0;
let sum = 0;
while (i < size) {
//let pos = Math.random() * size;
let pos = i;
//let pos = size-i;
let d = require("Storage").read("delme.mtar", pos, block);
//sum += checksum(E.toUint8Array(d));
i += block;
ops ++;
}
print(ops, "ops", sum);
}
function drawBench(name) {
g.setColor(0,0,0).setFont("Vector",25);
g.setFontAlign(0,0);
g.drawString(name, 85,35);
g.setColor(0,0,0).setFont("Vector",18);
g.drawString("Running", 85,55);
g.flip();
}
function runBench(b, name) {
drawBench(name);
g.reset().clearRect(R);
let t1 = getTime();
print("--------------------------------------------------");
print("Running",name);
b();
let m = (getTime()-t1) + " sec";
print("..done in", m);
drawBench(name);
g.setColor(0,0,0).setFont("Vector",18);
g.drawString(m, 85,85);
}
function redraw() {
//runBench(lineBench, "Lines");
runBench(polyBench, "Polygons");
//runBench(linearRead, "Linear read");
}
function showMap() {
g.reset().clearRect(R);
redraw();
emptyMap();
}
function emptyMap() {
Bangle.setUI({mode:"custom",drag:e=>{
g.reset().clearRect(R);
redraw();
}, btn: btn=>{
mapVisible = false;
var menu = {"":{title:"Benchmark"},
"< Back": ()=> showMap(),
/*LANG*/"Run": () =>{
showMap();
}};
E.showMenu(menu);
}});
}
const st = require('Storage');
const hs = require('heatshrink');
introScreen();
emptyMap();

View File

@ -1,6 +1,6 @@
{ "id": "spacew", { "id": "spacew",
"name": "Space Weaver", "name": "Space Weaver",
"version":"0.01", "version":"0.02",
"description": "Application for displaying vector maps", "description": "Application for displaying vector maps",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",
@ -8,6 +8,7 @@
"tags": "outdoors,gps,osm", "tags": "outdoors,gps,osm",
"storage": [ "storage": [
{"name":"spacew.app.js","url":"app.js"}, {"name":"spacew.app.js","url":"app.js"},
{"name":"spacew.img","url":"app-icon.js","evaluate":true} {"name":"spacew.img","url":"app-icon.js","evaluate":true},
{"name":"world.mtar","url":"world.mtar"}
] ]
} }

View File

@ -1,7 +1,8 @@
#!/usr/bin/nodejs #!/usr/bin/nodejs
// https://stackoverflow.com/questions/49129643/how-do-i-merge-an-array-of-uint8arrays
var pc = 1; var pc = 1;
var hack = 0;
const hs = require('./heatshrink.js'); const hs = require('./heatshrink.js');
if (pc) { if (pc) {
@ -23,19 +24,21 @@ function writeTar(tar, dir) {
var h_len = 16; var h_len = 16;
var cur = h_len; var cur = h_len;
files = fs.readdirSync(dir); files = fs.readdirSync(dir);
data = ''; let data = [];
var directory = ''; var directory = '';
var json = {}; var json = {};
for (f of files) { for (f of files) {
let f_rec = {};
d = fs.readFileSync(dir+f); d = fs.readFileSync(dir+f);
cs = d; if (0) {
//cs = String.fromCharCode.apply(null, hs.compress(d)) cs = hs.compress(d);
f_rec.comp = "hs";
} else
cs = d;
print("Processing", f, cur, d.length, cs.length); print("Processing", f, cur, d.length, cs.length);
//if (d.length == 42) continue; data.push(cs);
data = data + cs;
var f_rec = {};
f_rec.st = cur; f_rec.st = cur;
var len = d.length; var len = cs.length;
f_rec.si = len; f_rec.si = len;
cur = cur + len; cur = cur + len;
json[f] = f_rec; json[f] = f_rec;
@ -53,10 +56,10 @@ function writeTar(tar, dir) {
while (header.length < h_len) { while (header.length < h_len) {
header = header+' '; header = header+' ';
} }
if (!hack) fs.writeFileSync(tar, header);
fs.writeFileSync(tar, header+data+directory); for (d of data)
else fs.appendFileSync(tar, Buffer.from(d));
fs.writeFileSync(tar, directory); fs.appendFileSync(tar, directory);
} }
function readTarFile(tar, f) { function readTarFile(tar, f) {
@ -70,7 +73,7 @@ function readTarFile(tar, f) {
} }
if (pc) if (pc)
writeTar("delme.mtaz", "delme/"); writeTar("delme.mtar", "delme/");
else { else {
print(readTarFile("delme.mtar", "ahoj")); print(readTarFile("delme.mtar", "ahoj"));
print(readTarFile("delme.mtar", "nazdar")); print(readTarFile("delme.mtar", "nazdar"));

View File

@ -14,5 +14,5 @@
"linear_tags": true, "linear_tags": true,
"area_tags": false, "area_tags": false,
"exclude_tags": [], "exclude_tags": [],
"include_tags": [ "place", "name", "landuse", "highway" ] "include_tags": [ "place", "name", "landuse", "highway", "natural" ]
} }

View File

@ -1,17 +1,49 @@
#!/bin/bash #!/bin/bash
if [ ".$1" == "-f" ]; then
# http://bboxfinder.com/#0.000000,0.000000,0.000000,0.000000
Z=
# Czech republic -- hitting internal limit in nodejs
#BBOX=10,60,20,30
#Z="--maxz 9"
# No Moravia -- ascii conversion takes 43min, "Error: Cannot create a string longer than 0x3fffffe7 characters"
#BBOX=10,60,17.75,30
#Z="--maxz 9"
# Just Moravia -- 266MB delme.pbf -- FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed
#BBOX=16.13,60,35,30
#Z="--maxz 9"
# Roudnice az Kutna hora -- band 1.1 deg -- 145MB delme.pbf, 5m cstocs, 40+m split.
# -- band 0.1 deg -- 13MB delme.pbf, 13s split, 21k result.
#BBOX=14.20,50.45,15.32,49.20
#Z="--maxz 9"
# Roudnice az... -- band 0.5 deg -- 91MB delme.pbf, 200k result.
# Roudnice az... -- band 0.8 deg -- 120MB delme.pbf, 2.5GB while splitting, 260k result.
#BBOX=14.20,50.45,15.0,49.20
#Z="--maxz 9"
# Prague; 1.2MB map, not really useful
#BBOX=14.25,50.17,14.61,49.97
#Z="--maxz 14"
# Zernovka small -- 3.5 delme.pbf, ~850K result.
BBOX=14.7,49.9,14.8,50.1
# Zernovka big
#BBOX=14.6,49.7,14.9,50.1
if [ ".$1" == ".-f" ]; then
I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf
#I=/data/gis/osm/dumps/zernovka.osm.bz2 #I=/data/gis/osm/dumps/zernovka.osm.bz2
O=cr.geojson O=cr.geojson
rm delme.pbf $O rm delme.pbf $O
time osmium extract $I --bbox 14.7,49.9,14.8,50.1 -f pbf -o delme.pbf ls -alh $I
time osmium extract $I --bbox $BBOX -f pbf -o delme.pbf
ls -alh delme.pbf
time osmium export delme.pbf -c prepare.json -o $O time osmium export delme.pbf -c prepare.json -o $O
ls -alh $O
# ~.5G in 15min
echo "Converting to ascii" echo "Converting to ascii"
time cstocs utf8 ascii cr.geojson > cr_ascii.geojson time cstocs utf8 ascii cr.geojson > cr_ascii.geojson
mv -f cr_ascii.geojson delme.json mv -f cr_ascii.geojson delme.json
fi fi
rm -r delme/; mkdir delme rm -r delme/; mkdir delme
./split.js time ./split.js $Z
./minitar.js ./minitar.js
ls -lS delme/*.json | head -20 ls -lS delme/*.json | head -20
cat delme/* | wc -c cat delme/* | wc -c

View File

@ -6,11 +6,11 @@
const fs = require('fs'); const fs = require('fs');
const sphm = require('./sphericalmercator.js'); const sphm = require('./sphericalmercator.js');
var split = require('geojson-vt') var split = require('geojson-vt');
const process = require('process');
// delme.json needs to be real file, symlink to geojson will not work // delme.json needs to be real file, symlink to geojson will not work
console.log("Loading json");
var gjs = require("./delme.json");
function tileToLatLon(x, y, z, x_, y_) { function tileToLatLon(x, y, z, x_, y_) {
var [ w, s, e, n ] = merc.bbox(x, y, z); var [ w, s, e, n ] = merc.bbox(x, y, z);
@ -30,9 +30,39 @@ function convGeom(tile, geom) {
return g; return g;
} }
function clamp(i) {
if (i<0)
return 0;
if (i>4095)
return 4095;
return i;
}
function binGeom(tile, geom) {
let off = 1;
let r = new Uint8Array(geom.length * 3 + off);
let j = off;
for (i = 0; i< geom.length; i++) {
let x = geom[i][0];
let y = geom[i][1];
x = clamp(x);
y = clamp(y);
r[j++] = x >> 4;
r[j++] = y >> 4;
r[j++] = (x & 0x0f) + ((y & 0x0f) << 4);
}
return r;
}
function zoomPoint(tags) { function zoomPoint(tags) {
var z = 99; var z = 99;
if (tags.featurecla == "Admin-0 scale ranksscalerank") z = 2;
if (tags.featurecla == "Admin-0 capital") z = 3;
if (tags.featurecla == "Admin-1 capital") z = 4;
if (tags.featurecla == "Populated place") z = 5;
if (tags.place == "city") z = 4; if (tags.place == "city") z = 4;
if (tags.place == "town") z = 8; if (tags.place == "town") z = 8;
if (tags.place == "village") z = 10; if (tags.place == "village") z = 10;
@ -40,10 +70,59 @@ function zoomPoint(tags) {
return z; return z;
} }
var meta = {};
var ac = -1;
meta.attrs = [];
var a_town = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 1;
var a_village = ++ac
meta.attrs[ac] = {};
meta.attrs[ac].type = 1;
meta.attrs[ac].properties = {};
meta.attrs[ac].properties["marker-color"] = "#800000";
var a_way = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 2;
var a_secondary = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 2;
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.stroke = "#000040";
var a_tertiary = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 2;
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.stroke = "#000080";
var a_track = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 2;
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.stroke = "#404040";
var a_path = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 2;
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.stroke = "#408040";
var a_polygon = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].type = 3;
var a_forest = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.fill = "#c0ffc0";
meta.attrs[ac].type = 3;
var a_water = ++ac;
meta.attrs[ac] = {};
meta.attrs[ac].properties = {};
meta.attrs[ac].properties.fill = "#c0c0ff";
meta.attrs[ac].type = 3;
function paintPoint(tags) { function paintPoint(tags) {
var p = {}; var p = {};
if (tags.place == "village") p["marker-color"] = "#ff0000"; if (tags.place == "city" || tags.place == "town") { p.attr = a_town; }
if (tags.place == "village") { p.attr = a_village; p["marker-color"] = "#ff0000"; }
return p; return p;
} }
@ -51,15 +130,17 @@ function paintPoint(tags) {
function zoomWay(tags) { function zoomWay(tags) {
var z = 99; var z = 99;
if (tags.scalerank == 0) z = 0;
if (tags.highway == "motorway") z = 7; if (tags.highway == "motorway") z = 7;
if (tags.highway == "primary") z = 9; if (tags.highway == "primary") z = 9;
if (tags.highway == "secondary") z = 13; if (tags.highway == "secondary") z = 13;
if (tags.highway == "tertiary") z = 14; if (tags.highway == "tertiary") z = 14;
if (tags.highway == "unclassified") z = 16; if (tags.highway == "unclassified") z = 15;
if (tags.highway == "residential") z = 17; if (tags.highway == "residential") z = 15;
if (tags.highway == "track") z = 17; if (tags.highway == "track") z = 15;
if (tags.highway == "path") z = 17; if (tags.highway == "path") z = 16;
if (tags.highway == "footway") z = 17; if (tags.highway == "footway") z = 16;
return z; return z;
} }
@ -67,52 +148,120 @@ function zoomWay(tags) {
function paintWay(tags) { function paintWay(tags) {
var p = {}; var p = {};
p.attr = a_way;
if (tags.highway == "motorway" || tags.highway == "primary") /* ok */; if (tags.highway == "motorway" || tags.highway == "primary") /* ok */;
if (tags.highway == "secondary" || tags.highway == "tertiary") p.stroke = "#0000ff"; if (tags.highway == "secondary" || tags.highway == "tertiary") { p.stroke = "#0000ff"; p.attr = a_secondary; }
if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") p.stroke = "#00ff00"; if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") { p.stroke = "#00ff00"; p.attr = a_tertiary; }
if (tags.highway == "track") p.stroke = "#ff0000"; if (tags.highway == "track") { p.stroke = "#ff0000"; p.attr = a_track; }
if (tags.highway == "path" || tags.highway == "footway") p.stroke = "#800000"; if (tags.highway == "path" || tags.highway == "footway") { p.stroke = "#800000"; p.attr = a_path; }
return p;
}
function zoomPolygon(tags) {
var z = 99;
if (tags.scalerank == 0) z = 0;
if (tags.landuse == "forest") z = 16;
if (tags.natural == "water") z = 16;
return z;
}
function paintPolygon(tags) {
var p = {};
p.attr = a_polygon;
if (tags.landuse == "forest") { p.fill = "#c0ffc0"; p.attr = a_forest; }
if (tags.natural == "water") { p.fill = "#c0c0ff"; p.attr = a_water; }
if (tags.featurecla == "Admin-0 sovereignty") p.attr = a_way;
return p; return p;
} }
function writeFeatures(name, feat) function writeFeatures(name, feat)
{ {
var n = {}; if (0) {
n.type = "FeatureCollection"; var n = {};
n.features = feat; n.type = "FeatureCollection";
n.features = feat;
fs.writeFile(name+'.json', JSON.stringify(n), on_error); fs.writeFile(name+'.json', JSON.stringify(n), on_error);
} else {
if (feat.length > 0)
fs.writeFile(name+'.json', JSON.stringify(feat), on_error);
}
} }
function btoa(s) {
return Buffer.from(s).toString('base64');
}
// E.toString()
function toGjson(name, d, tile) { function toGjson(name, d, tile) {
var cnt = 0; var cnt = 0;
var feat = []; var feat = [];
for (var a of d) { for (var a of d) {
var f = {}; let f = {}; // geojson output
let b = {}; // moving towards binary output
var zoom = 99; var zoom = 99;
var p = {}; var p = {};
var bin = [];
if (!a.tags)
a.tags = a.properties;
f.properties = a.tags; f.properties = a.tags;
f.type = "Feature"; f.type = "Feature";
f.geometry = {}; f.geometry = {};
if (a.type == 1) { if (a.type == 1) {
f.geometry.type = "Point"; f.geometry.type = "Point";
f.geometry.coordinates = convGeom(tile, a.geometry)[0]; f.geometry.coordinates = convGeom(tile, a.geometry)[0];
bin = binGeom(tile, a.geometry);
zoom = zoomPoint(a.tags); zoom = zoomPoint(a.tags);
p = paintPoint(a.tags); p = paintPoint(a.tags);
} else if (a.type == 2) { } else if (a.type == 2) {
f.geometry.type = "LineString"; f.geometry.type = "LineString";
f.geometry.coordinates = convGeom(tile, a.geometry[0]); f.geometry.coordinates = convGeom(tile, a.geometry[0]);
bin = binGeom(tile, a.geometry[0]);
zoom = zoomWay(a.tags); zoom = zoomWay(a.tags);
p = paintWay(a.tags); p = paintWay(a.tags);
if (zoom == 99) {
f.geometry.type = "Polygon";
zoom = zoomPolygon(a.tags);
p = paintPolygon(a.tags);
}
} else if (a.type == 3) {
f.geometry.type = "Polygon";
f.geometry.coordinates = convGeom(tile, a.geometry[0]);
bin = binGeom(tile, a.geometry[0]);
zoom = zoomPolygon(a.tags);
p = paintPolygon(a.tags);
} else { } else {
//console.log("Unknown type", a.type); console.log("Unknown type", a.type);
} }
//zoom -= 4; // Produces way nicer map, at expense of space. //zoom -= 4; // Produces way nicer map, at expense of space.
if (tile.z < zoom) if (tile.z < zoom)
continue; continue;
f.properties = Object.assign({}, f.properties, p); f.properties = Object.assign({}, f.properties, p);
feat.push(f); //feat.push(f); FIXME
bin[0] = p.attr;
b.b = btoa(bin);
b.tags = {};
if (a.tags.name)
b.tags.name = a.tags.name;
if (a.tags.nameascii)
b.tags.name = a.tags.nameascii;
if (a.tags.sr_subunit)
b.tags.name = a.tags.sr_subunit;
//delete(a.tags.highway);
//delete(a.tags.landuse);
//delete(a.tags.natural);
//delete(a.tags.place);
// b.properties = p
feat.push(b);
var s = JSON.stringify(feat); var s = JSON.stringify(feat);
if (s.length > 6000) { if (s.length > 6000) {
console.log("tile too big, splitting", cnt); console.log("tile too big, splitting", cnt);
@ -135,16 +284,41 @@ var merc = new sphm({
}); });
console.log("Splitting data"); console.log("Splitting data");
var meta = {}
meta.min_zoom = 0; meta.min_zoom = 0;
meta.max_zoom = 17; // HERE meta.max_zoom = 16; // HERE
// = 16 ... split3 takes > 30 minutes // = 16 ... split3 takes > 30 minutes
// = 13 ... 2 minutes // = 13 ... 2 minutes
if (process.argv[2] == "-h") {
console.log("help here");
process.exit(0);
}
if (process.argv[2] == "--maxz") {
meta.max_zoom = 1*process.argv[3];
console.log("... max zoom", meta.max_zoom);
}
if (process.argv[2] == "--world") {
console.log("Loading world");
meta.max_zoom = 4;
var g_sovereign = require("./ne_10m_admin_0_sovereignty.json");
var g_labels = require("./ne_10m_admin_0_label_points.json");
var g_places = require("./ne_10m_populated_places_simple.json");
gjs = {}
gjs.type = "FeatureCollection";
//gjs.features = g_sovereign.features + g_labels.features + g_places.features;
gjs.features = g_sovereign.features.concat(g_labels.features).concat(g_places.features);
console.log(gjs);
} else {
console.log("Loading json");
gjs = require("./delme.json");
}
var index = split(gjs, Object.assign({ var index = split(gjs, Object.assign({
maxZoom: meta.max_zoom, maxZoom: meta.max_zoom,
indexMaxZoom: meta.max_zoom, indexMaxZoom: meta.max_zoom,
indexMaxPoints: 0, indexMaxPoints: 0,
tolerance: 30, tolerance: 10,
buffer: 0,
}), {}); }), {});
console.log("Producing output"); console.log("Producing output");

View File

@ -1,8 +1,7 @@
#!/bin/bash #!/bin/bash
zoom() { zoom() {
echo "Zoom $1" VAL=`cat delme/z$1-* | wc -c`
cat delme/z$1-* | wc -c echo "Zoom $1 -- " $[$VAL/1024]
echo "M..k..."
} }
echo "Total data" echo "Total data"
@ -16,7 +15,17 @@ zoom 14
zoom 13 zoom 13
zoom 12 zoom 12
zoom 11 zoom 11
zoom 10 zoom 9
zoom 8
zoom 7
zoom 6
zoom 5
zoom 4
zoom 3
zoom 2
zoom 1
zoom 0
echo "Zoom 1..9" echo "Zoom 1..9"
cat delme/z?-* | wc -c cat delme/z?-* | wc -c
echo "M..k..." echo "M..k..."

14
apps/spacew/prep/world.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
if [ ".$1" == ".-f" ]; then
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_admin_0_sovereignty.json
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_admin_0_label_points.json
wget https://raw.githubusercontent.com/martynafford/natural-earth-geojson/master/10m/cultural/ne_10m_populated_places_simple.json
fi
rm delme.json
rm -r delme/; mkdir delme
./split.js --world
./minitar.js
ls -lS delme/*.json | head -20
cat delme/* | wc -c
ls -l delme.mtar

1411
apps/spacew/world.mtar Normal file

File diff suppressed because one or more lines are too long

View File

@ -75,7 +75,7 @@ function getLapTimes() {
}); });
} }
if (task=="download") { if (task=="download") {
Util.saveCSV(lap.n.slice(0,-5)+".csv", lap.d.map((d,n)=>[n+1,d].join(",")).join("\n")); Util.saveCSV(lap.n.slice(0,-5), lap.d.map((d,n)=>[n+1,d].join(",")).join("\n"));
} }
}); });
} }

View File

@ -32,7 +32,7 @@ function getData() {
<th>Month</th> <th>Month</th>
<th>Day</th> <th>Day</th>
<th>Time HH:mm</th> <th>Time HH:mm</th>
<th>temperature</th> <th>temperature</th>
</tr>`+data.trim().split("\n").map(l=>{ </tr>`+data.trim().split("\n").map(l=>{
l = l.split(";"); l = l.split(";");
return `<tr> return `<tr>
@ -47,7 +47,7 @@ function getData() {
// You can call a utility function to save the data // You can call a utility function to save the data
document.getElementById("btnSave").addEventListener("click", function() { document.getElementById("btnSave").addEventListener("click", function() {
Util.saveCSV("temphistory.csv", csvData); Util.saveCSV("temphistory", csvData);
}); });
// Or you can also delete the file // Or you can also delete the file
document.getElementById("btnDelete").addEventListener("click", function() { document.getElementById("btnDelete").addEventListener("click", function() {

View File

@ -95,17 +95,7 @@
Util.showModal("Downloading..."); Util.showModal("Downloading...");
let jsonTodos = getEditableContent(); let jsonTodos = getEditableContent();
if (isJsonString(jsonTodos)) { if (isJsonString(jsonTodos)) {
var a = document.createElement("a"), Util.saveFile(fileTodoList, "application/json", jsonTodos);
file = new Blob([jsonTodos], { type: "application/json" });
var url = URL.createObjectURL(file);
a.href = url;
a.download = fileTodoList;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
} else { } else {
document.getElementById("info").innerHTML = errorFormat(); document.getElementById("info").innerHTML = errorFormat();
} }

View File

@ -8,3 +8,4 @@
0.09: Misc speed/memory tweaks 0.09: Misc speed/memory tweaks
0.10: Color changes due to the battery level 0.10: Color changes due to the battery level
0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible 0.11: Change level for medium charge (50% -> 40%), and darken color on light themes as yellow was almost invisible
0.12: Use black flash instead of green fork to indicate charging

View File

@ -1,7 +1,7 @@
{ {
"id": "widbat", "id": "widbat",
"name": "Battery Level Widget", "name": "Battery Level Widget",
"version": "0.11", "version": "0.12",
"description": "Show the current battery level and charging status in the top right of the clock", "description": "Show the current battery level and charging status in the top right of the clock",
"icon": "widget.png", "icon": "widget.png",
"type": "widget", "type": "widget",

View File

@ -1,14 +1,10 @@
(function(){ {
function setWidth() {
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
}
Bangle.on('charging',function(charging) { Bangle.on('charging',function(charging) {
if(charging) Bangle.buzz(); if(charging) Bangle.buzz();
setWidth(); WIDGETS["bat"].draw();
Bangle.drawWidgets(); // re-layout widgets
g.flip(); g.flip();
}); });
var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined; let batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
Bangle.on('lcdPower', function(on) { Bangle.on('lcdPower', function(on) {
if (on) { if (on) {
WIDGETS["bat"].draw(); WIDGETS["bat"].draw();
@ -23,19 +19,14 @@
} }
}); });
WIDGETS["bat"]={area:"tr",width:40,draw:function() { WIDGETS["bat"]={area:"tr",width:40,draw:function() {
var s = 39;
var x = this.x, y = this.y; var x = this.x, y = this.y;
g.reset(); g.reset().setColor(g.theme.fg).fillRect(x,y+2,x+35,y+21).clearRect(x+2,y+4,x+33,y+19).fillRect(x+36,y+10,x+39,y+14);
if (Bangle.isCharging()) {
g.setColor("#0f0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
x+=16;
}
g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
var battery = E.getBattery(); var battery = E.getBattery();
if(battery < 20) {g.setColor("#f00");} if(battery < 20) g.setColor("#f00");
else if (battery < 40) {g.setColor(g.theme.dark ? "#ff0" : "#f80");} else if (battery < 40) g.setColor(g.theme.dark ? "#ff0" : "#f80");
else {g.setColor("#0f0");} else g.setColor("#0f0");
g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17); g.fillRect(x+4,y+6,x+4+battery*27/100,y+17);
if (Bangle.isCharging())
g.reset().drawImage(atob("FAqBAAHAAA8AAPwAB/D4f8P+Hw/gAD8AAPAAA4A="),x+8,y+7);
}}; }};
setWidth(); }
})()

View File

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

View File

@ -0,0 +1,13 @@
{
"id": "widcasiologo",
"name": "Casio Logo Widget",
"version": "0.01",
"description": "A simple widget that displays Casio logo when clock screen is active.",
"icon": "widget.png",
"type": "widget",
"tags": "widget,clock",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"widcasiologo.wid.js","url":"widget.js"}
]
}

View File

@ -0,0 +1,16 @@
WIDGETS["casiologo"]={
area:"tl",
width: Bangle.CLOCK?70:0,
draw: function () {
if (!Bangle.CLOCK != !this.width) { // if we're the wrong size for if we have a clock or not...
this.width = Bangle.CLOCK?70:0;
return setTimeout(Bangle.drawWidgets,1); // widget changed size - redraw
}
if (!this.width) return;
g.reset().setColor(g.theme.dark?"#fff":"#000").drawImage(
atob("OgwCAAAAAAAAAAAAAAAAAAAA///wA//AD//8D8D///A////AP/wD///w/D///8P9B/wL//A/AD8Pw/wC/D8AD8D8PwPwAAD8PwAPw/AAAA/D8D///A/D8AD8PwAAA/gPwP//8Pw/AA/D8AD8P//8AAA/T8PwAPw/wD/P///z8AP0/D/Af8P///z/AL8///8Pw////A///w/AA/T///D8D///AAAAAAAAAAAAAAAAAAAA"),
this.x+10,
this.y+6)
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

1
apps/widhr/ChangeLog Normal file
View File

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

10
apps/widhr/README.md Normal file
View File

@ -0,0 +1,10 @@
# Last announced heartrate BPM Widget
* Displays the last announced bpm measurement from Bangle.on('HRM', ...);
* it does not enable the heartrate sensor to do measurements it waits on such annoucement. I use it to view the last read value when letting the system take a reading every 10 minutes.
* saves last read value to a file so it can display that last value between app starts / reboots
![](screenshot_widhr.png)
Code based on Lato Pedometer Written by: [Hugh Barney](https://github.com/hughbarney)
Code Modified by: [Willems Davy](https://github.com/joyrider3774)

19
apps/widhr/metadata.json Normal file
View File

@ -0,0 +1,19 @@
{
"id": "widhr",
"name": "Last announced heartrate BPM Widget",
"shortName":"Last Heartrate BPM",
"icon": "widhr.icon.png",
"screenshots": [{"url":"screenshot_widhr.png"}],
"version":"0.01",
"type": "widget",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"description": "Displays the last announced heartrate BPM from `Bangle.on(\"HRM\", ...);` (it does not enable the hrm sensor it expects the system or other app todo so)",
"tags": "widget",
"data": [
{"name":"widhr.data.json"}
],
"storage": [
{"name":"widhr.wid.js","url":"widhr.wid.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
apps/widhr/widhr.icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

30
apps/widhr/widhr.wid.js Normal file
View File

@ -0,0 +1,30 @@
function widhr_hrm(hrm) {
require("Storage").writeJSON("widhr.data.json", {"bpm":hrm.bpm});
WIDGETS["widhr"].draw();
}
Bangle.on('HRM', widhr_hrm);
function widhr_draw() {
var json = require("Storage").readJSON("widhr.data.json");
var bpm = json === undefined ? 0 : json.bpm;
//3x6 from bpm text in 6x8 font
var w = (bpm.toString().length)*8 > 3 * 6 ? (bpm.toString().length)*8 : 3 * 6;
if (w != this.width)
{
this.width = w;
setTimeout(() => Bangle.drawWidgets(),10); return;
}
g.reset();
g.setColor(g.theme.bg);
g.fillRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
g.setColor(g.theme.fg);
g.setFont("6x8:1");
g.setFontAlign(-1, 0);
g.drawString("bpm", this.x, this.y + 5);
g.setFont("4x6:2");
g.setFontAlign(-1, 0);
g.drawString(bpm, this.x, this.y + 17);
}
WIDGETS["widhr"]={area:"tl",sortorder:-1,width:13,draw:widhr_draw};

2
core

@ -1 +1 @@
Subproject commit 431a3fb743da5c370729ab748cb2c177e70a345b Subproject commit 71813fe2eaf19987cec07db850ab9d1959694f96

View File

@ -16,7 +16,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 = "2v18"; var RECOMMENDED_VERSION = "2v19";
// could check http://www.espruino.com/json/BANGLEJS.json for this // could check http://www.espruino.com/json/BANGLEJS.json for this
// We're only interested in Bangles // We're only interested in Bangles
@ -236,7 +236,7 @@ window.addEventListener('load', (event) => {
}); });
}); });
// Button to install all default apps in one go // Button to install all default apps in one go
el = document.getElementById("installdefault"); el = document.getElementById("installdefault");
if (el) el.addEventListener("click", event=>{ if (el) el.addEventListener("click", event=>{
@ -262,7 +262,7 @@ window.addEventListener('load', (event) => {
showToast("Settings reset!", "success"); showToast("Settings reset!", "success");
}, function() { /* cancelled */ }); }, function() { /* cancelled */ });
}); });
// BLE Compatibility // BLE Compatibility
var selectBLECompat = document.getElementById("settings-ble-compat"); var selectBLECompat = document.getElementById("settings-ble-compat");

16
modules/Layout.min.js vendored
View File

@ -4,11 +4,11 @@ h,b,f,a){var e=null==d.bgCol?a:g.toColor(d.bgCol);if(e!=a||"txt"==d.type||"btn"=
if(void 0===h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75<f.time-f.lastTime&&this.b[b].cbl?this.b[b].cbl(f):this.b[b].cb&&this.b[b].cb(f)}Bangle.btnWatches&& if(void 0===h&&this.buttons[b])return this.buttons[b].cb();this.buttons[b]&&(delete this.buttons[b].selected,this.render(this.buttons[b]));b=(b+f+h)%f;this.buttons[b]&&(this.buttons[b].selected=1,this.render(this.buttons[b]));this.selectedButton=b}),d=!0);!this.options.back&&!this.options.remove||d||Bangle.setUI({mode:"custom",back:this.options.back,remove:this.options.remove});if(this.b){function h(b,f){.75<f.time-f.lastTime&&this.b[b].cbl?this.b[b].cbl(f):this.b[b].cb&&this.b[b].cb(f)}Bangle.btnWatches&&
Bangle.btnWatches.forEach(clearWatch);Bangle.btnWatches=[];this.b[0]&&Bangle.btnWatches.push(setWatch(h.bind(this,0),BTN1,{repeat:!0,edge:-1}));this.b[1]&&Bangle.btnWatches.push(setWatch(h.bind(this,1),BTN2,{repeat:!0,edge:-1}));this.b[2]&&Bangle.btnWatches.push(setWatch(h.bind(this,2),BTN3,{repeat:!0,edge:-1}))}if(2==process.env.HWVERSION){function h(b,f){b.cb&&f.x>=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler= Bangle.btnWatches.forEach(clearWatch);Bangle.btnWatches=[];this.b[0]&&Bangle.btnWatches.push(setWatch(h.bind(this,0),BTN1,{repeat:!0,edge:-1}));this.b[1]&&Bangle.btnWatches.push(setWatch(h.bind(this,1),BTN2,{repeat:!0,edge:-1}));this.b[2]&&Bangle.btnWatches.push(setWatch(h.bind(this,2),BTN3,{repeat:!0,edge:-1}))}if(2==process.env.HWVERSION){function h(b,f){b.cb&&f.x>=b.x&&f.y>=b.y&&f.x<=b.x+b.w&&f.y<=b.y+b.h&&(2==f.type&&b.cbl?b.cbl(f):b.cb&&b.cb(f));b.c&&b.c.forEach(a=>h(a,f))}Bangle.touchHandler=
(b,f)=>h(this._l,f);Bangle.on("touch",Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>> (b,f)=>h(this._l,f);Bangle.on("touch",Bangle.touchHandler)}};p.prototype.render=function(d){function h(c){"ram";b.reset();void 0!==c.col&&b.setColor(c.col);void 0!==c.bgCol&&b.setBgColor(c.bgCol).clearRect(c.x,c.y,c.x+c.w-1,c.y+c.h-1);f[c.type](c)}d||(d=this._l);this.updateNeeded&&this.update();var b=g,f={"":function(){},txt:function(c){"ram";if(c.wrap){var m=b.setFont(c.font).setFontAlign(0,-1).wrapString(c.label,c.w),n=c.y+(c.h-b.getFontHeight()*m.length>>1);b.drawString(m.join("\n"),c.x+(c.w>>
1),n)}else b.setFont(c.font).setFontAlign(0,0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=c.selected?b.theme.bgH:b.theme.bg2;b.setColor(n).fillPoly(m).setColor(c.selected?b.theme.fgH:b.theme.fg2).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==typeof c.src?c.src():c.src,c.x+ 1),n)}else b.setFont(c.font).setFontAlign(0,0,c.r).drawString(c.label,c.x+(c.w>>1),c.y+(c.h>>1))},btn:function(c){"ram";var m=c.x+(0|c.pad),n=c.y+(0|c.pad),q=c.w-(c.pad<<1),r=c.h-(c.pad<<1);m=[m,n+4,m+4,n,m+q-5,n,m+q-1,n+4,m+q-1,n+r-5,m+q-5,n+r-1,m+4,n+r-1,m,n+r-5,m,n+4];n=void 0!==c.bgCol?c.bgCol:b.theme.bg2;q=void 0!==c.btnBorder?c.btnBorder:b.theme.fg2;c.selected&&(n=b.theme.bgH,q=b.theme.fgH);b.setColor(n).fillPoly(m).setColor(q).drawPoly(m);void 0!==c.col&&b.setColor(c.col);c.src?b.setBgColor(n).drawImage("function"==
c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),e=[];t(d,a,e,this.rects, typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)}):b.setFont(c.font||"6x8:2").setFontAlign(0,0,c.r).drawString(c.label,c.x+c.w/2,c.y+c.h/2)},img:function(c){"ram";b.drawImage("function"==typeof c.src?c.src():c.src,c.x+c.w/2,c.y+c.h/2,{scale:c.scale||void 0,rotate:.5*Math.PI*(c.r||0)})},custom:function(c){"ram";c.render(c)},h:function(c){"ram";c.c.forEach(h)},v:function(c){"ram";c.c.forEach(h)}};if(this.lazy){this.rects||(this.rects={});var a=this.rects.clone(),
null);for(var l in a)delete this.rects[l];d=Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-(b.pad<<1):k._h);k.y=0| e=[];t(d,a,e,this.rects,null);for(var l in a)delete this.rects[l];d=Object.keys(a).map(c=>a[c]).reverse();for(var k of d)b.setBgColor(k.bg).clearRect.apply(g,k);e.forEach(h)}else h(d)};p.prototype.forgetLazyState=function(){this.rects={}};p.prototype.layout=function(d){var h={h:function(b){"ram";var f=b.x+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.fillx),0);e||(f+=b.w-b._w>>1,e=1);var l=f;b.c.forEach(k=>{k.x=0|l;f+=k._w;a+=0|k.fillx;l=f+Math.floor(a*(b.w-b._w)/e);k.w=0|l-k.x;k.h=0|(k.filly?b.h-
b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<<1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};if(h[d.type])h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);h=h||1;g.setColor(h& (b.pad<<1):k._h);k.y=0|b.y+(0|b.pad)+((1+(0|k.valign))*(b.h-(b.pad<<1)-k.h)>>1);if(k.c)h[k.type](k)})},v:function(b){"ram";var f=b.y+(0|b.pad),a=0,e=b.c&&b.c.reduce((k,c)=>k+(0|c.filly),0);e||(f+=b.h-b._h>>1,e=1);var l=f;b.c.forEach(k=>{k.y=0|l;f+=k._h;a+=0|k.filly;l=f+Math.floor(a*(b.h-b._h)/e);k.h=0|l-k.y;k.w=0|(k.fillx?b.w-(b.pad<<1):k._w);k.x=0|b.x+(0|b.pad)+((1+(0|k.halign))*(b.w-(b.pad<<1)-k.w)>>1);if(k.c)h[k.type](k)})}};if(h[d.type])h[d.type](d)};p.prototype.debug=function(d,h){d||(d=this._l);
1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h);d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100)); h=h||1;g.setColor(h&1,h&2,h&4).drawRect(d.x+h-1,d.y+h-1,d.x+d.w-h,d.y+d.h-h);d.pad&&g.drawRect(d.x+d.pad-1,d.y+d.pad-1,d.x+d.w-d.pad,d.y+d.h-d.pad);h++;d.c&&d.c.forEach(b=>this.debug(b,h))};p.prototype.update=function(){function d(a){"ram";b[a.type](a);if(a.r&1){var e=a._w;a._w=a._h;a._h=e}a._w=Math.max(a._w+(a.pad<<1),0|a.width);a._h=Math.max(a._h+(a.pad<<1),0|a.height)}delete this.updateNeeded;var h=g,b={txt:function(a){"ram";a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,
if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label);a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*l}, -1)/100));if(a.wrap)a._h=a._w=0;else{var e=g.setFont(a.font).stringMetrics(a.label);a._w=e.width;a._h=e.height}},btn:function(a){"ram";a.font&&a.font.endsWith("%")&&(a.font="Vector"+Math.round(h.getHeight()*a.font.slice(0,-1)/100));var e=a.src?h.imageMetrics("function"==typeof a.src?a.src():a.src):h.setFont(a.font||"6x8:2").stringMetrics(a.label);a._h=16+e.height;a._w=20+e.width},img:function(a){"ram";var e=h.imageMetrics("function"==typeof a.src?a.src():a.src),l=a.scale||1;a._w=e.width*l;a._h=e.height*
"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0},h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=this._l; l},"":function(a){"ram";a._w=0;a._h=0},custom:function(a){"ram";a._w=0;a._h=0},h:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>Math.max(e,l._h),0);a._w=a.c.reduce((e,l)=>e+l._w,0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)},v:function(a){"ram";a.c.forEach(d);a._h=a.c.reduce((e,l)=>e+l._h,0);a._w=a.c.reduce((e,l)=>Math.max(e,l._w),0);null==a.fillx&&a.c.some(e=>e.fillx)&&(a.fillx=1);null==a.filly&&a.c.some(e=>e.filly)&&(a.filly=1)}},f=
d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h,f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p this._l;d(f);delete b;f.fillx||f.filly?(f.w=Bangle.appRect.w,f.h=Bangle.appRect.h,f.x=Bangle.appRect.x,f.y=Bangle.appRect.y):(f.w=f._w,f.h=f._h,f.x=Bangle.appRect.w-f.w>>1,f.y=Bangle.appRect.y+(Bangle.appRect.h-f.h>>1));this.layout(f)};p.prototype.clear=function(d){d||(d=this._l);g.reset();void 0!==d.bgCol&&g.setBgColor(d.bgCol);g.clearRect(d.x,d.y,d.x+d.w-1,d.y+d.h-1)};exports=p