Merge remote-tracking branch 'upstream/master'
|
|
@ -7,4 +7,5 @@ appdates.csv
|
|||
.vscode
|
||||
.idea/
|
||||
_config.yml
|
||||
|
||||
tests/Layout/bin/tmp.*
|
||||
tests/Layout/testresult.bmp
|
||||
|
|
|
|||
40
apps.json
|
|
@ -94,7 +94,7 @@
|
|||
"name": "Notifications (default)",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.09",
|
||||
"version":"0.10",
|
||||
"description": "A handler for displaying notifications that displays them in a bar at the top of the screen",
|
||||
"tags": "widget",
|
||||
"type": "notify",
|
||||
|
|
@ -107,9 +107,9 @@
|
|||
"name": "Fullscreen Notifications",
|
||||
"shortName":"Notifications",
|
||||
"icon": "notify.png",
|
||||
"version":"0.10",
|
||||
"version":"0.11",
|
||||
"description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.",
|
||||
"tags": "widget",
|
||||
"tags": "widget,b2",
|
||||
"type": "notify",
|
||||
"storage": [
|
||||
{"name":"notify","url":"notify.js"}
|
||||
|
|
@ -155,7 +155,7 @@
|
|||
"icon": "app.png",
|
||||
"version":"0.24",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||
"tags": "tool,system,android,widget",
|
||||
"tags": "tool,system,android,widget,b2",
|
||||
"readme": "README.md",
|
||||
"type":"widget",
|
||||
"dependencies": { "notify":"type" },
|
||||
|
|
@ -571,7 +571,7 @@
|
|||
{ "id": "weather",
|
||||
"name": "Weather",
|
||||
"icon": "icon.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "Show Gadgetbridge weather report",
|
||||
"readme": "readme.md",
|
||||
"tags": "widget,outdoors",
|
||||
|
|
@ -1184,7 +1184,7 @@
|
|||
{ "id": "widpedom",
|
||||
"name": "Pedometer widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.18",
|
||||
"version":"0.19",
|
||||
"description": "Daily pedometer widget",
|
||||
"tags": "widget,b2",
|
||||
"type":"widget",
|
||||
|
|
@ -1670,7 +1670,7 @@
|
|||
"name": "Calculator",
|
||||
"shortName":"Calculator",
|
||||
"icon": "calculator.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||
"tags": "app,tool,b2",
|
||||
"storage": [
|
||||
|
|
@ -2508,7 +2508,7 @@
|
|||
"name": "Cycling speed sensor",
|
||||
"shortName":"CSCSensor",
|
||||
"icon": "icons8-cycling-48.png",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
|
||||
"tags": "outdoors,exercise,ble,bluetooth",
|
||||
"readme": "README.md",
|
||||
|
|
@ -3520,5 +3520,29 @@
|
|||
"data": [
|
||||
{"name":"pastel.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "antonclk",
|
||||
"name": "Anton Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A simple clock using the bold Anton font.",
|
||||
"tags":"clock,b2",
|
||||
"type":"clock",
|
||||
"storage": [
|
||||
{"name":"antonclk.app.js","url":"app.js"},
|
||||
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "waveclk",
|
||||
"name": "Wave Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A clock using a wave image by [Lillith May](https://www.instagram.com/_lilustrations_/). **Note: This requires a bugfix for #2049 on Bangle.js 1**",
|
||||
"tags":"clock,b2",
|
||||
"type":"clock",
|
||||
"storage": [
|
||||
{"name":"waveclk.app.js","url":"app.js"},
|
||||
{"name":"waveclk.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ function startRecord(force) {
|
|||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:true},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
]
|
||||
},[ // Buttons...
|
||||
{label:"STOP", cb:()=>{
|
||||
|
|
@ -105,7 +105,6 @@ function startRecord(force) {
|
|||
showMenu();
|
||||
}}
|
||||
]);
|
||||
layout.update();
|
||||
layout.render();
|
||||
|
||||
// now start writing
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA=="))
|
||||
|
After Width: | Height: | Size: 759 B |
|
After Width: | Height: | Size: 696 B |
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: fix precision rounding issue + no reset when equals pressed
|
||||
0.03: Support for different screen sizes and touchscreen
|
||||
0.04: Display current operation on LHS
|
||||
|
|
|
|||
|
|
@ -199,8 +199,7 @@ function doMath(x, y, operator) {
|
|||
function displayOutput(num) {
|
||||
var len;
|
||||
var minusMarge = 0;
|
||||
g.setColor(0);
|
||||
g.fillRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||
g.setColor(-1);
|
||||
if (num === Infinity || num === -Infinity || isNaN(num)) {
|
||||
// handle division by 0
|
||||
|
|
@ -244,6 +243,10 @@ function displayOutput(num) {
|
|||
}
|
||||
g.setFontAlign(1,0);
|
||||
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);
|
||||
if (operator) {
|
||||
g.setFont('Vector', 22).setFontAlign(1,0);
|
||||
g.drawString(operator, g.getWidth()-1, RESULT_HEIGHT/2);
|
||||
}
|
||||
}
|
||||
var wasPressedEquals = false;
|
||||
var hasPressedNumber = false;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
0.02: Add wheel circumference settings dialog
|
||||
0.03: Save total distance traveled
|
||||
0.04: Add sensor battery level indicator
|
||||
|
||||
0.05: Add cadence sensor support
|
||||
|
|
|
|||
|
|
@ -13,6 +13,6 @@ Currently the app displays the following data:
|
|||
|
||||
Button 1 resets all measurements except total distance traveled. The latter gets preserved by being written to storage every 0.1 miles and upon exiting the app.
|
||||
If the watch app has not received an update from the sensor for at least 10 seconds, pushing button 3 will attempt to reconnect to the sensor.
|
||||
Button 2 switches between the display for cycling speed and cadence.
|
||||
|
||||
I do not have access to a cadence sensor at the moment, so only the speed part is currently implemented. Values displayed are imperial or metric (depending on locale),
|
||||
the wheel circumference can be adjusted in the global settings app.
|
||||
Values displayed are imperial or metric (depending on locale), cadence is in RPM, the wheel circumference can be adjusted in the global settings app.
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ class CSCSensor {
|
|||
this.distFactor = this.qMetric ? 1.609344 : 1;
|
||||
this.screenInit = true;
|
||||
this.batteryLevel = -1;
|
||||
this.lastCrankTime = 0;
|
||||
this.lastCrankRevs = 0;
|
||||
this.showCadence = false;
|
||||
this.cadence = 0;
|
||||
}
|
||||
|
||||
reset() {
|
||||
|
|
@ -40,6 +44,11 @@ class CSCSensor {
|
|||
this.screenInit = true;
|
||||
}
|
||||
|
||||
toggleDisplayCadence() {
|
||||
this.showCadence = !this.showCadence;
|
||||
this.screenInit = true;
|
||||
}
|
||||
|
||||
setBatteryLevel(level) {
|
||||
if (level!=this.batteryLevel) {
|
||||
this.batteryLevel = level;
|
||||
|
|
@ -62,7 +71,7 @@ class CSCSensor {
|
|||
else g.setFontVector(14).setFontAlign(0, 0, 0).setColor(0xffff).drawString("?", 16, 66);
|
||||
}
|
||||
|
||||
updateScreen() {
|
||||
updateScreenRevs() {
|
||||
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
|
||||
var ddist = Math.round(100*dist)/100;
|
||||
var tdist = Math.round(this.distFactor*this.totaldist*10)/10;
|
||||
|
|
@ -108,45 +117,88 @@ class CSCSensor {
|
|||
g.setColor(0).fillRect(88, 209, 238, 238);
|
||||
g.setColor(0xffff).drawString(tdist + " " + this.distUnit, 92, 226);
|
||||
}
|
||||
|
||||
|
||||
updateScreenCadence() {
|
||||
if (this.screenInit) {
|
||||
for (var i=0; i<2; ++i) {
|
||||
if ((i&1)==0) g.setColor(0, 0, 0);
|
||||
else g.setColor(0x30cd);
|
||||
g.fillRect(0, 48+i*32, 86, 48+(i+1)*32);
|
||||
if ((i&1)==1) g.setColor(0);
|
||||
else g.setColor(0x30cd);
|
||||
g.fillRect(87, 48+i*32, 239, 48+(i+1)*32);
|
||||
g.setColor(0.5, 0.5, 0.5).drawRect(87, 48+i*32, 239, 48+(i+1)*32).drawLine(0, 239, 239, 239);//.drawRect(0, 48, 87, 239);
|
||||
g.moveTo(0, 80).lineTo(30, 80).lineTo(30, 48).lineTo(87, 48).lineTo(87, 239).lineTo(0, 239).lineTo(0, 80);
|
||||
}
|
||||
g.setFontAlign(1, 0, 0).setFontVector(19).setColor(1, 1, 0);
|
||||
g.drawString("Cadence:", 87, 98);
|
||||
this.drawBatteryIcon();
|
||||
this.screenInit = false;
|
||||
}
|
||||
g.setFontAlign(-1, 0, 0).setFontVector(26);
|
||||
g.setColor(0).fillRect(88, 81, 238, 111);
|
||||
g.setColor(0xffff).drawString(Math.round(this.cadence), 92, 98);
|
||||
}
|
||||
|
||||
updateScreen() {
|
||||
if (!this.showCadence) {
|
||||
this.updateScreenRevs();
|
||||
} else {
|
||||
this.updateScreenCadence();
|
||||
}
|
||||
}
|
||||
|
||||
updateSensor(event) {
|
||||
var qChanged = false;
|
||||
if (event.target.uuid == "0x2a5b") {
|
||||
var wheelRevs = event.target.value.getUint32(1, true);
|
||||
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
|
||||
if (dRevs>0) {
|
||||
qChanged = true;
|
||||
this.totaldist += dRevs*this.wheelCirc/63360.0;
|
||||
if ((this.totaldist-this.settings.totaldist)>0.1) {
|
||||
this.settings.totaldist = this.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
if (event.target.value.getUint8(0, true) & 0x2) {
|
||||
// crank revolution
|
||||
const crankRevs = event.target.value.getUint16(1, true);
|
||||
const crankTime = event.target.value.getUint16(3, true);
|
||||
if (crankTime > this.lastCrankTime) {
|
||||
this.cadence = (crankRevs-this.lastCrankRevs)/(crankTime-this.lastCrankTime)*(60*1024);
|
||||
qChanged = true;
|
||||
}
|
||||
}
|
||||
this.lastRevs = wheelRevs;
|
||||
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
|
||||
var wheelTime = event.target.value.getUint16(5, true);
|
||||
var dT = (wheelTime-this.lastTime)/1024;
|
||||
var dBT = (Date.now()-this.lastBangleTime)/1000;
|
||||
this.lastBangleTime = Date.now();
|
||||
if (dT<0) dT+=64;
|
||||
if (Math.abs(dT-dBT)>3) dT = dBT;
|
||||
this.lastTime = wheelTime;
|
||||
this.speed = this.lastSpeed;
|
||||
if (dRevs>0 && dT>0) {
|
||||
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
|
||||
this.speedFailed = 0;
|
||||
this.movingTime += dT;
|
||||
}
|
||||
else {
|
||||
this.speedFailed++;
|
||||
qChanged = false;
|
||||
if (this.speedFailed>3) {
|
||||
this.speed = 0;
|
||||
qChanged = (this.lastSpeed>0);
|
||||
this.lastCrankRevs = crankRevs;
|
||||
this.lastCrankTime = crankTime;
|
||||
} else {
|
||||
// wheel revolution
|
||||
var wheelRevs = event.target.value.getUint32(1, true);
|
||||
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
|
||||
if (dRevs>0) {
|
||||
qChanged = true;
|
||||
this.totaldist += dRevs*this.wheelCirc/63360.0;
|
||||
if ((this.totaldist-this.settings.totaldist)>0.1) {
|
||||
this.settings.totaldist = this.totaldist;
|
||||
storage.writeJSON(SETTINGS_FILE, this.settings);
|
||||
}
|
||||
}
|
||||
this.lastRevs = wheelRevs;
|
||||
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
|
||||
var wheelTime = event.target.value.getUint16(5, true);
|
||||
var dT = (wheelTime-this.lastTime)/1024;
|
||||
var dBT = (Date.now()-this.lastBangleTime)/1000;
|
||||
this.lastBangleTime = Date.now();
|
||||
if (dT<0) dT+=64;
|
||||
if (Math.abs(dT-dBT)>3) dT = dBT;
|
||||
this.lastTime = wheelTime;
|
||||
this.speed = this.lastSpeed;
|
||||
if (dRevs>0 && dT>0) {
|
||||
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
|
||||
this.speedFailed = 0;
|
||||
this.movingTime += dT;
|
||||
}
|
||||
else {
|
||||
this.speedFailed++;
|
||||
qChanged = false;
|
||||
if (this.speedFailed>3) {
|
||||
this.speed = 0;
|
||||
qChanged = (this.lastSpeed>0);
|
||||
}
|
||||
}
|
||||
this.lastSpeed = this.speed;
|
||||
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
|
||||
}
|
||||
this.lastSpeed = this.speed;
|
||||
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
|
||||
}
|
||||
if (qChanged && this.qUpdateScreen) this.updateScreen();
|
||||
}
|
||||
|
|
@ -199,6 +251,7 @@ connection_setup();
|
|||
setWatch(function() { mySensor.reset(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN1, {repeat:true, debounce:20});
|
||||
E.on('kill',()=>{ if (gatt!=undefined) gatt.disconnect(); mySensor.settings.totaldist = mySensor.totaldist; storage.writeJSON(SETTINGS_FILE, mySensor.settings); });
|
||||
setWatch(function() { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }, BTN3, {repeat:true, debounce:20});
|
||||
setWatch(function() { mySensor.toggleDisplayCadence(); g.clearRect(0, 48, 239, 239); mySensor.updateScreen(); }, BTN2, {repeat:true, debounce:20});
|
||||
NRF.on('disconnect', connection_setup);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@
|
|||
0.07: Auto-calculate height, and pad text down even when there's no title (so it stays on-screen)
|
||||
0.08: Don't turn on screen during Quiet Mode
|
||||
0.09: Add onHide callback
|
||||
0.10: Improvements to help notifications work with themes
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ A handler for displaying notifications that displays them in a bar at the top of
|
|||
This is not an app, but instead it is a library that can be used by
|
||||
other applications or widgets to display messages.
|
||||
|
||||
**Note:** There are other implementations of this library available such
|
||||
as `notifyfs` (Fullscreen Notifications). These can be used in the exact
|
||||
same way from code, but they look different to the user.
|
||||
|
||||
## Usage
|
||||
|
||||
```JS
|
||||
|
|
|
|||
|
|
@ -96,15 +96,17 @@ exports.show = function(options) {
|
|||
b = y+h-1, r = x+w-1; // bottom,right
|
||||
g.setClipRect(x,y, r,b);
|
||||
// clear area
|
||||
g.setColor(options.bgColor||0).fillRect(x,y, r,b);
|
||||
g.reset();
|
||||
if (options.bgColor!==undefined) g.setColor(options.bgColor);
|
||||
g.clearRect(x,y, r,b);
|
||||
// bottom border
|
||||
g.setColor(0x39C7).fillRect(0,b-1, r,b);
|
||||
g.setColor("#333").fillRect(0,b-1, r,b);
|
||||
b -= 2;h -= 2;
|
||||
// title bar
|
||||
if (options.title || options.src) {
|
||||
g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20);
|
||||
const title = options.title||options.src;
|
||||
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2);
|
||||
g.drawString(title.trim().substring(0, 13), x+25,y+3);
|
||||
if (options.title && options.src) {
|
||||
g.setFont("6x8", 1).setFontAlign(1, 1, 0);
|
||||
|
|
@ -122,7 +124,7 @@ exports.show = function(options) {
|
|||
}
|
||||
// body text
|
||||
if (options.body) {
|
||||
g.setColor(-1).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4);
|
||||
g.setColor(g.theme.fg).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4);
|
||||
}
|
||||
|
||||
if (options.render) {
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Don't turn on screen during Quiet Mode
|
||||
0.09: Add onHide callback
|
||||
0.10: Ensure dismissing a notification dismissal doesn't enter launcher if in clock mode
|
||||
0.11: Improvements to help notifications work with themes, Bangle.js 2 support
|
||||
|
|
|
|||
|
|
@ -50,22 +50,24 @@ exports.show = function(options) {
|
|||
if (options.on===undefined) options.on=true;
|
||||
id = ("id" in options)?options.id:null;
|
||||
let size = options.size||120;
|
||||
if (size>120) {size=120}
|
||||
Bangle.setLCDMode("direct");
|
||||
if (size>120) size=120;
|
||||
try { Bangle.setLCDMode("direct"); } catch(e) {} // not supported/needed on Bangle.js 2
|
||||
let x = 0,
|
||||
y = 40,
|
||||
w = 240,
|
||||
w = g.getWidth(),
|
||||
h = size;
|
||||
// clear screen
|
||||
g.setColor(options.bgColor||0).fillRect(0,0,g.getWidth(),g.getHeight());
|
||||
g.reset();
|
||||
if (options.bgColor!==undefined) g.setColor(options.bgColor);
|
||||
g.clearRect(0,0,g.getWidth(),g.getHeight());
|
||||
// top bar
|
||||
if (options.title||options.src) {
|
||||
const title = options.title || options.src
|
||||
g.setColor(options.titleBgColor||0x39C7).fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 3);
|
||||
const title = options.title || options.src;
|
||||
g.setColor(options.titleBgColor||"#333").fillRect(x, y, x+w-1, y+30);
|
||||
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 3);
|
||||
g.drawString(title.trim().substring(0, 13), x+5, y+3);
|
||||
if (options.title && options.src) {
|
||||
g.setColor(-1).setFontAlign(1, 1, 0).setFont("6x8", 2);
|
||||
g.setColor(g.theme.fg).setFontAlign(1, 1, 0).setFont("6x8", 2);
|
||||
// above drawing area, but we are fullscreen
|
||||
g.drawString(options.src.substring(0, 10), w-16, y-4);
|
||||
}
|
||||
|
|
@ -73,8 +75,8 @@ exports.show = function(options) {
|
|||
}
|
||||
if (options.icon) {
|
||||
let i = options.icon, iw,ih;
|
||||
if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1)}
|
||||
else {iw=i[0]; ih=i[1]}
|
||||
if ("string"==typeof i) {iw=i.charCodeAt(0); ih=i.charCodeAt(1);}
|
||||
else {iw=i[0]; ih=i[1];}
|
||||
const iy=y ? (y+4) : (h-ih)/2; // show below title bar if present, otherwise center vertically
|
||||
g.drawImage(i, x+4,iy);
|
||||
x += iw+4;w -= iw+4;
|
||||
|
|
@ -84,16 +86,13 @@ exports.show = function(options) {
|
|||
const maxRows = Math.floor((h-4)/16), // font=2*(6x8)
|
||||
maxChars = Math.floor((w-4)/12),
|
||||
text=fitWords(options.body, maxRows, maxChars);
|
||||
g.setColor(-1).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4);
|
||||
g.setColor(g.theme.fg).setFont("6x8", 2).setFontAlign(-1, -1, 0).drawString(text, x+4, y+4);
|
||||
}
|
||||
|
||||
if (options.render) {
|
||||
const area={x:x, y:y, w:w, h:h}
|
||||
options.render(area);
|
||||
}
|
||||
if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) {
|
||||
if (options.render)
|
||||
options.render({x:x, y:y, w:w, h:h});
|
||||
if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet)
|
||||
Bangle.setLCDPower(1); // light up
|
||||
}
|
||||
Bangle.on("touch", exports.hide);
|
||||
if (options.onHide)
|
||||
hideCallback = options.onHide;
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwge27dtAX4C+/dt+wFB/wCECIu3/dvBYNv34RC7/tCIu//99EYN9C4IpB74jG3379ovDFIIRBEYxHD/47D2wjHCIX+AQJHBCIIXBNZt/+5QBEZIgBAQX///9EZRWBBARHDEwhlC/9/EAJoBDQIOBNwyPCEYYCDJQ4CJSQ4CB0O2lojL2lwBIXFiwsK0f/KgUbuwRJo6cBPAO34cUmJHH7U/97tBgEGBIODEY/RXoOw7cAgHbtlxoojGx7hCjAjD20ANA1378MEIIAB4d0u5HGNAPYCAYAB2n2SQSPDjv3CIsF2lxEYto//+CoOGCIUt0O3EYtHvqMBvlw4UAgJQBqIjERgQDBsO+7FAhaMH64DB+4qB+3AgARG9uhIgQJD4dghd+7dLBQZoBaISwC4cArf27dpCIf/23f9uHCIQABhoNClsl20ttuwgYKBEAIAChOmCIOH/vx9ttwB2BDgMBAoIRBmnbpkbtk2ltsgAMCJQOwAgMBk+eq3AhiSBsE2GAX//4WCAAOBAoVbtt8mCJBgHHfYMHdgoRBott+zmDsEAn/tEwkC7UAVoYACgPbv4REAASTBEYY0BPIPwCJAjEu3Dvq8BCAnbsEwGgm2jbAC8EAjFvEAQREjuwDQXbvvx7cd2K/EgEb9oRCAoLOBjEAgk/A="))
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
function getImg() {
|
||||
return require("heatshrink").decompress(atob("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"));
|
||||
}
|
||||
var IMAGEWIDTH = 176;
|
||||
var IMAGEHEIGHT = 109;
|
||||
|
||||
Graphics.prototype.setFontZCOOL = function() {
|
||||
// Actual height 40 (46 - 7)
|
||||
var widths = atob("CxAhEh8hJCIjGSMdCw==");
|
||||
var font = atob("AAAAAAAAAAAAAAAEAAAAAAAPAAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP8AAAAAAB/AAAAAAAfAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAfgAAAAAAf4AAAAAA/+AAAAAA//AAAAAB/+AAAAAB/+AAAAAD/8AAAAAD/8AAAAAD/4AAAAAH/4AAAAAH/wAAAAAD/wAAAAAAfwAAAAAAHgAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAP8AAAAAB//gAAAAP//4AAAB////AAAP////wAAf///h8AAH//8AfAAD//gAHwAA/8AAB8AAPgAAAfAAD4AAAHwAAfAAAB+AAHwAAAPgAB8AAAD4AAfAAAA+AAHwAAAPgAB8AAAD4AAPgAAA+AAD4AAAPgAA+AAAD8AAPgAAAfAAD4AAAHwAA/////8AAH/////AAB/////wAAf////4AAD////8AAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAB8AAAAAAA/AAAAAAAfgAAAAAAH4AAAAAAD8AAAAAAB+AAAAAAA/AAAAAAAPwAAH/gAH4D///4AD/////+AB//////gAf/////4AD////gAAA//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAB8AAAGAAAfAAAD4AAPwAAB/AAH4AAA/wAB8AAAf8AA/AAAP/AAfgAAP/gAHwAAH/4AD8AAD++AB+AAB/PgAfAAA/j4AHwAAfw+AB+AAP4PgAfwAH8D4AD8AH+A+AAfgD/AfAAD8B/AHwAAfg/gB8AAD8fwAfAAA/v4AHwAAH/8AB8AAA/+AAfAAAH/AAHwAAA/AAB8AAAHgAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAAAAAAAAABAAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAHwAAMAAAB8AAfAAAAfAAHwAAAHwAB8AAAA+AAfAAAAPgAHwAfAD4AA+AP4A+AAPgD+APgAD4B/gB8AA+A/8AfAAPgf/AHwAD4H/wB8AA+D98AfgAHh+fgD4AB4/D4A+AAffg+A/gAH34Pg/4AB/8D8f8AAf+Aff+AAH/AH/+AAA/wB/+AAAP4Af+AAAD8AD/AAAA+AA/AAAAHgADAAAAAAAAAAAAAAAAAAAAAAAADgAAAAAAD+AAAAAAH/gAAAAAH/4AAAAAP/+AAAAAP//gAAAAf/z4AAAAf/w+AAAA//gPgAAA//gD4AAAf/AA+AAAH/AAPgAAB/AAD4AAAeAAA+AAACAAAPgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAP/+AAAAP///gAB/////4AAf////+AAH/////AAB///+AAAAf/APgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAAAAAAAAAAAAACAAAAAAAHwAAAAAAB8AAAAAAAfAAAAAAAH4AAAAAAA+AAAAAAAPgAAAAAAD4AAAAfwA+AAAH/+AHwAAf//gB8AAP//4AfAAH//+AHwAB/+PwB+AAfwB8APgAHwAfAD4AB8AHwA+AAfAB8APgAHwAfAB8AB8AHwAfAAfAB8AHwAHwAfAB8AB8AH4AfAAfAA+AH4AHwAPgH+AB8AD4H/gAfAA+H/wAHwAPv/4AB8AD//4AAfAA//wAAHwAP/wAAB8AD/gAAAAAAfgAAAAAABAAAAAAAAAAAAAAAAAHwAAAAAAH8AAAAAAD/gAAAAAD/4AAAAAD/+AAAAAD/3wAAAAD/x8AAAAD/4PgAAAD/4D4AAAD/4A+AAAB/4AHwAAB/4AB8AAB/4AAfAAB/4CAD4AB/8DgA+AAf8B8APgAD8AfAB8AA8AH4AfAAEAA+AH4AAAAPgA+AAAAD8AfgAAAAfAP4AAAAH4P8AAAAA+H+AAAAAPj+AAAAAB9/AAAAAAf/gAAAAAH/wAAAAAA/4AAAAAAP8AAAAAAB8AAAAAAAOAAAAAAAAAAAAAAAAAAAAD4AAAAAAA+AAAAAAAPwAAAAAAB8AAAAAAAfAAAAAAAHwAAAAAAB8AAAAAAAfAAAAAAAHwAAADAAB8AAAD4AAfAAAD+AAHwAAH/wAB8AAH/4AAfgAH/4AAH4AH/4AAA+AH/wAAAPgH/wAAAD4H/wAAAA+H/wAAAAPv/wAAAAD//wAAAAA//gAAAAAP/gAAAAAD/gAAAAAAfgAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAH4AAAAAAB+AAAHwAA/wAAB8AAf8AAA/gAP/AAAP8AH/wAAH/gD8+AAB/4B+PgAAf/A/j4AAPn4Pw+AAD4/H4HwAB+Pz8B8AAfB/+AfAAPwP/AHwAD4B/gB+AA+AfwAPgAfAP+AD4AHwD/wA+AB+B/+AfgAfw/PgPwAB/fj8D8AAP/wfh+AAB/4D8fAAAP+AfPwAAB/AH/4AAAPgA/8AAAAAAH/AAAAAAA/gAAAAAAPwAAAAAAB8AAAAAAAAAAAAAAAAAAAAAGAAAAAAAD4AAAAAAB/AAAAAAB/wAAAAAA/+AAAAAAf/gAAAAAf78AAAAAP8fAAAAAP+H4AAAAD+A+AAAAB/APgAAAAfAB8AAAAH4AfAAYAA+AD4AfAAPgA+AfwAD4APgf+AA+ABgf/AAHwAA//AAB8AA//AAAfAA//AAAHwA//AAAB+A/+AAAAPh/+AAAAD5/+AAAAA//+AAAAAP/+AAAAAB/8AAAAAAf8AAAAAAH8AAAAAAAAAAAAAAAAADgAOAAAAH4AfgAAAD+AP4AAAA/wD/AAAAH8AfwAAAB/AH8AAAAfwB/AAAADgAOAAAAAAAAAAAAAAAAA=");
|
||||
var scale = 1; // size multiplier for this font
|
||||
g.setFontCustom(font, 46, widths, 50+(scale<<8)+(1<<16));
|
||||
};
|
||||
|
||||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = 24+20;
|
||||
|
||||
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()-IMAGEHEIGHT);
|
||||
if (g.getWidth() == IMAGEWIDTH)
|
||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT);
|
||||
else {
|
||||
let scale = g.getWidth()/IMAGEWIDTH;
|
||||
y *= scale;
|
||||
g.drawImage(getImg(),0,g.getHeight()-IMAGEHEIGHT*scale,{scale:scale});
|
||||
}
|
||||
// work out locale-friendly date/time
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var dateStr = require("locale").date(date).toUpperCase();
|
||||
// draw time
|
||||
g.setFontAlign(0,0).setFont("ZCOOL");
|
||||
g.drawString(timeStr,x,y);
|
||||
// draw date
|
||||
y += 35;
|
||||
g.setFontAlign(0,0,1).setFont("6x8");
|
||||
g.drawString(dateStr,g.getWidth()-8,g.getHeight()/2);
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.setTheme({bg:"#f0f",fg:"#fff",dark:true}).clear();
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -3,4 +3,5 @@
|
|||
0.04: Adjust "weather unknown" message according to Bluetooth connection.
|
||||
0.05: Add wind direction.
|
||||
0.06: Use setUI for launcher.
|
||||
0.07: Add theme support and unknown icon.
|
||||
0.07: Add theme support and unknown icon.
|
||||
0.08: Refactor and reduce widget ram usage.
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
(() => {
|
||||
const weather = require('weather');
|
||||
let current = weather.get();
|
||||
|
||||
function formatDuration(millis) {
|
||||
let pluralize = (n, w) => n + " " + w + (n == 1 ? "" : "s");
|
||||
|
|
@ -10,16 +11,15 @@
|
|||
}
|
||||
|
||||
function draw() {
|
||||
let w = weather.current;
|
||||
g.reset();
|
||||
g.clearRect(0, 24, 239, 239);
|
||||
|
||||
weather.drawIcon(w.txt, 65, 90, 55);
|
||||
weather.drawIcon(current.txt, 65, 90, 55);
|
||||
const locale = require("locale");
|
||||
|
||||
g.reset();
|
||||
|
||||
const temp = locale.temp(w.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
|
||||
width += g.setFont("Vector", 20).stringWidth(temp[2]);
|
||||
g.setFont("Vector", 40).setFontAlign(-1, -1, 0);
|
||||
|
|
@ -31,19 +31,19 @@
|
|||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("Humidity", 135, 130);
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString(w.hum+"%", 225, 130);
|
||||
if ('wind' in w) {
|
||||
g.drawString(current.hum+"%", 225, 130);
|
||||
if ('wind' in current) {
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
g.drawString("Wind", 135, 142);
|
||||
g.setFontAlign(1, 0, 0);
|
||||
g.drawString(locale.speed(w.wind)+' '+w.wrose.toUpperCase(), 225, 142);
|
||||
g.drawString(locale.speed(current.wind)+' '+current.wrose.toUpperCase(), 225, 142);
|
||||
}
|
||||
|
||||
g.setFont("6x8", 2).setFontAlign(0, 0, 0);
|
||||
g.drawString(w.loc, 120, 170);
|
||||
g.drawString(current.loc, 120, 170);
|
||||
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||
g.drawString(w.txt.charAt(0).toUpperCase()+w.txt.slice(1), 120, 190);
|
||||
g.drawString(current.txt.charAt(0).toUpperCase()+current.txt.slice(1), 120, 190);
|
||||
|
||||
drawUpdateTime();
|
||||
|
||||
|
|
@ -51,8 +51,8 @@
|
|||
}
|
||||
|
||||
function drawUpdateTime() {
|
||||
if (!weather.current || !weather.current.time) return;
|
||||
let text = `Last update received ${formatDuration(Date.now() - weather.current.time)} ago`;
|
||||
if (!current || !current.time) return;
|
||||
let text = `Last update received ${formatDuration(Date.now() - current.time)} ago`;
|
||||
g.reset();
|
||||
g.clearRect(0, 202, 239, 210);
|
||||
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||
|
|
@ -60,8 +60,9 @@
|
|||
}
|
||||
|
||||
function update() {
|
||||
current = weather.get();
|
||||
NRF.removeListener("connect", update);
|
||||
if (weather.current) {
|
||||
if (current) {
|
||||
draw();
|
||||
} else if (NRF.getSecurityStatus().connected) {
|
||||
E.showMessage("Weather unknown\n\nIs Gadgetbridge\nweather reporting\nset up on your\nphone?");
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const storage = require('Storage');
|
||||
|
||||
let expiryTimeout = undefined;
|
||||
let expiryTimeout;
|
||||
function scheduleExpiry(json) {
|
||||
if (expiryTimeout) {
|
||||
clearTimeout(expiryTimeout);
|
||||
|
|
@ -9,53 +9,35 @@ function scheduleExpiry(json) {
|
|||
let expiry = "expiry" in json ? json.expiry : 2*3600000;
|
||||
if (json.weather && json.weather.time && expiry) {
|
||||
let t = json.weather.time + expiry - Date.now();
|
||||
expiryTimeout = setTimeout(() => {
|
||||
expiryTimeout = undefined;
|
||||
|
||||
let json = storage.readJSON('weather.json')||{};
|
||||
delete json.weather;
|
||||
storage.write('weather.json', json);
|
||||
|
||||
exports.current = undefined;
|
||||
exports.emit("update");
|
||||
}, t);
|
||||
expiryTimeout = setTimeout(update, t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert numeric direction into human-readable label
|
||||
*
|
||||
* @param {number} deg - Direction in degrees
|
||||
* @return {string|null} - Nearest compass point
|
||||
*/
|
||||
function compassRose(deg) {
|
||||
if (typeof deg === 'undefined') return null;
|
||||
while (deg<0 || deg>360) {
|
||||
deg = (deg+360)%360;
|
||||
}
|
||||
return ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)];
|
||||
}
|
||||
|
||||
function setCurrentWeather(json) {
|
||||
scheduleExpiry(json);
|
||||
exports.current = json.weather;
|
||||
}
|
||||
|
||||
function update(weatherEvent) {
|
||||
let weather = Object.assign({}, weatherEvent);
|
||||
weather.time = Date.now();
|
||||
if ('wdir' in weather) {
|
||||
weather.wrose = compassRose(weather.wdir);
|
||||
}
|
||||
delete weather.t;
|
||||
|
||||
let json = storage.readJSON('weather.json')||{};
|
||||
json.weather = weather;
|
||||
|
||||
if (weatherEvent) {
|
||||
let weather = weatherEvent.clone();
|
||||
delete weather.t;
|
||||
weather.time = Date.now();
|
||||
if (weather.wdir != null) {
|
||||
// Convert numeric direction into human-readable label
|
||||
let deg = weather.wdir;
|
||||
while (deg<0 || deg>360) {
|
||||
deg = (deg+360)%360;
|
||||
}
|
||||
weather.wrose = ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)];
|
||||
}
|
||||
|
||||
json.weather = weather;
|
||||
}
|
||||
else {
|
||||
delete json.weather;
|
||||
}
|
||||
|
||||
storage.write('weather.json', json);
|
||||
|
||||
setCurrentWeather(json);
|
||||
|
||||
exports.emit("update");
|
||||
scheduleExpiry(json);
|
||||
exports.emit("update", json.weather);
|
||||
}
|
||||
|
||||
const _GB = global.GB;
|
||||
|
|
@ -64,7 +46,11 @@ global.GB = (event) => {
|
|||
if (_GB) setTimeout(_GB, 0, event);
|
||||
};
|
||||
|
||||
setCurrentWeather(storage.readJSON('weather.json')||{});
|
||||
exports.get = function() {
|
||||
return storage.readJSON('weather.json').weather;
|
||||
}
|
||||
|
||||
scheduleExpiry(storage.readJSON('weather.json')||{});
|
||||
|
||||
exports.drawIcon = function(cond, x, y, r) {
|
||||
function drawSun(x, y, r) {
|
||||
|
|
|
|||
|
|
@ -1,45 +1,23 @@
|
|||
(() => {
|
||||
const weather = require('weather');
|
||||
|
||||
function draw() {
|
||||
const w = weather.current;
|
||||
if (!w) return;
|
||||
g.reset();
|
||||
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
||||
if (w.txt) {
|
||||
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
|
||||
}
|
||||
if (w.temp) {
|
||||
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
||||
t = t.match(/[\d\-]*/)[0]; // but we have no room for units
|
||||
g.reset();
|
||||
g.setFontAlign(0, 1); // center horizontally at bottom of widget
|
||||
g.setFont('6x8', 1);
|
||||
g.drawString(t, this.x+10, this.y+24);
|
||||
}
|
||||
}
|
||||
|
||||
var dirty = false;
|
||||
|
||||
function update() {
|
||||
if (!WIDGETS["weather"].width) {
|
||||
WIDGETS["weather"].width = 20;
|
||||
Bangle.drawWidgets();
|
||||
} else if (Bangle.isLCDOn()) {
|
||||
WIDGETS["weather"].draw();
|
||||
} else {
|
||||
dirty = true;
|
||||
weather.on("update", w => {
|
||||
if (w) {
|
||||
if (!WIDGETS["weather"].width) {
|
||||
WIDGETS["weather"].width = 20;
|
||||
Bangle.drawWidgets();
|
||||
} else if (Bangle.isLCDOn()) {
|
||||
WIDGETS["weather"].draw();
|
||||
} else {
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
WIDGETS["weather"].width = 0;
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
WIDGETS["weather"].width = 0;
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
weather.on("update", () => {
|
||||
if (weather.current) update();
|
||||
else hide();
|
||||
});
|
||||
|
||||
Bangle.on('lcdPower', on => {
|
||||
|
|
@ -51,7 +29,23 @@
|
|||
|
||||
WIDGETS["weather"] = {
|
||||
area: "tl",
|
||||
width: weather.current ? 20 : 0,
|
||||
draw: draw,
|
||||
width: weather.get() ? 20 : 0,
|
||||
draw: function() {
|
||||
const w = weather.get();
|
||||
if (!w) return;
|
||||
g.reset();
|
||||
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
|
||||
if (w.txt) {
|
||||
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
|
||||
}
|
||||
if (w.temp) {
|
||||
let t = require('locale').temp(w.temp-273.15); // applies conversion
|
||||
t = t.match(/[\d\-]*/)[0]; // but we have no room for units
|
||||
g.reset();
|
||||
g.setFontAlign(0, 1); // center horizontally at bottom of widget
|
||||
g.setFont('6x8', 1);
|
||||
g.drawString(t, this.x+10, this.y+24);
|
||||
}
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -15,3 +15,7 @@
|
|||
0.16: Settings option to show large digits in widget area
|
||||
0.17: Cope with 2v10+ firmware sometimes reporting >1 step
|
||||
0.18: Adjust widget width when displaying large text
|
||||
0.19: Allow goal in large font mode
|
||||
Stop goal drawing outside widget area
|
||||
Fix issue with widget overwrite in large font mode
|
||||
Memory usage enhancements
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
onchange: (g) => {
|
||||
s.goal = g
|
||||
s.progress = !!g
|
||||
save()
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Show Progress': {
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
format: () => (s.progress ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.progress = !s.progress
|
||||
save()
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Large Digits': {
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
format: () => (s.large ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.large = !s.large
|
||||
save()
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Hide Widget': {
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
format: () => (s.hide ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
s.hide = !s.hide
|
||||
save()
|
||||
save();
|
||||
},
|
||||
},
|
||||
'< Back': back,
|
||||
|
|
|
|||
|
|
@ -23,78 +23,6 @@
|
|||
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||
}
|
||||
|
||||
function drawProgress(stps) {
|
||||
if (setting('hide')) return;
|
||||
const width = 24, half = width/2;
|
||||
const goal = setting('goal'), left = Math.max(goal-stps,0);
|
||||
const c = left ? "#00f" : "#090"; // blue or dark green
|
||||
g.setColor(c).fillCircle(this.x + half, this.y + half, half);
|
||||
const TAU = Math.PI*2;
|
||||
if (left) {
|
||||
const f = left/goal; // fraction to blank out
|
||||
let p = [];
|
||||
p.push(half,half);
|
||||
p.push(half,0);
|
||||
if(f>1/8) p.push(0,0);
|
||||
if(f>2/8) p.push(0,half);
|
||||
if(f>3/8) p.push(0,width);
|
||||
if(f>4/8) p.push(half,width);
|
||||
if(f>5/8) p.push(width,width);
|
||||
if(f>6/8) p.push(width,half);
|
||||
if(f>7/8) p.push(width,0);
|
||||
p.push(half - Math.sin(f * TAU) * half);
|
||||
p.push(half - Math.cos(f * TAU) * half);
|
||||
for (let i = p.length; i; i -= 2) {
|
||||
p[i - 2] += this.x;
|
||||
p[i - 1] += this.y;
|
||||
}
|
||||
g.setColor(g.theme.bg).fillPoly(p);
|
||||
}
|
||||
}
|
||||
|
||||
// show the step count in the widget area in a readable sized font
|
||||
function draw_large(st) {
|
||||
this.width = 12 * st.length + 3;
|
||||
g.reset();
|
||||
g.clearRect(this.x, this.y, this.x + this.width, this.y + 16); // erase background
|
||||
g.setColor(g.theme.fg);
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(-1, -1);
|
||||
g.drawString(st, this.x + 4, this.y + 2);
|
||||
}
|
||||
|
||||
// draw your widget
|
||||
function draw() {
|
||||
if (setting('hide')) return;
|
||||
var width = 24;
|
||||
if (stp_today > 99999){
|
||||
stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters
|
||||
}
|
||||
let stps = stp_today.toString();
|
||||
if (setting('large')) {
|
||||
draw_large.call(this, stps);
|
||||
return;
|
||||
}
|
||||
g.reset().clearRect(this.x, this.y, this.x + width, this.y + 23); // erase background
|
||||
if (setting('progress')){ drawProgress.call(this, stps); }
|
||||
g.setColor(g.theme.fg);
|
||||
if (stps.length > 3){
|
||||
stps = stps.slice(0,-3) + "," + stps.slice(-3);
|
||||
g.setFont("4x6", 1); // if big, shrink text to fix
|
||||
} else {
|
||||
g.setFont("6x8", 1);
|
||||
}
|
||||
g.setFontAlign(0, 0); // align to x: center, y: center
|
||||
g.drawString(stps, this.x+width/2, this.y+19);
|
||||
// on low bpp screens, draw 1 bit. Currently there is no getBPP so we just do it based on resolution
|
||||
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
loadSettings()
|
||||
draw()
|
||||
}
|
||||
|
||||
Bangle.on('step', stepCount => {
|
||||
var steps = stepCount-lastStepCount;
|
||||
if (lastStepCount===undefined || steps<0) steps=1;
|
||||
|
|
@ -115,11 +43,11 @@
|
|||
}
|
||||
lastUpdate = date
|
||||
//console.log("up: " + up + " stp: " + stp_today + " " + date.toString());
|
||||
if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw();
|
||||
WIDGETS["wpedom"].redraw();
|
||||
});
|
||||
// redraw when the LCD turns on
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) WIDGETS["wpedom"].draw();
|
||||
if (on) WIDGETS["wpedom"].redraw();
|
||||
});
|
||||
// When unloading, save state
|
||||
E.on('kill', () => {
|
||||
|
|
@ -134,10 +62,80 @@
|
|||
|
||||
// add your widget
|
||||
WIDGETS["wpedom"]={area:"tl",width:26,
|
||||
draw:draw,
|
||||
reload:reload,
|
||||
getSteps:()=>stp_today
|
||||
};
|
||||
redraw:function() { // work out the width, and queue a full redraw if needed
|
||||
let stps = stp_today.toString();
|
||||
let newWidth = 24;
|
||||
if (setting('hide'))
|
||||
newWidth = 0;
|
||||
else {
|
||||
if (setting('large')) {
|
||||
newWidth = 12 * stps.length + 3;
|
||||
if (setting('progress'))
|
||||
newWidth += 24;
|
||||
}
|
||||
}
|
||||
if (newWidth!=this.width) {
|
||||
// width has changed, re-layout all widgets
|
||||
this.width = newWidth;
|
||||
Bangle.drawWidgets();
|
||||
} else {
|
||||
// width not changed - just redraw
|
||||
WIDGETS["wpedom"].draw();
|
||||
}
|
||||
},
|
||||
draw:function() {
|
||||
if (setting('hide')) return;
|
||||
if (stp_today > 99999)
|
||||
stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters
|
||||
let stps = stp_today.toString();
|
||||
g.reset().clearRect(this.x, this.y, this.x + this.width, this.y + 23); // erase background
|
||||
if (setting('progress')) {
|
||||
const width = 23, half = 11;
|
||||
const goal = setting('goal'), left = Math.max(goal-stps,0);
|
||||
// blue or dark green
|
||||
g.setColor(left ? "#08f" : "#080").fillCircle(this.x + half, this.y + half, half);
|
||||
if (left) {
|
||||
const TAU = Math.PI*2;
|
||||
const f = left/goal; // fraction to blank out
|
||||
let p = [];
|
||||
p.push(half,half);
|
||||
p.push(half,0);
|
||||
if(f>1/8) p.push(0,0);
|
||||
if(f>2/8) p.push(0,half);
|
||||
if(f>3/8) p.push(0,width);
|
||||
if(f>4/8) p.push(half,width);
|
||||
if(f>5/8) p.push(width,width);
|
||||
if(f>6/8) p.push(width,half);
|
||||
if(f>7/8) p.push(width,0);
|
||||
p.push(half - Math.sin(f * TAU) * half);
|
||||
p.push(half - Math.cos(f * TAU) * half);
|
||||
g.setColor(g.theme.bg).fillPoly(g.transformVertices(p,{x:this.x,y:this.y}));
|
||||
}
|
||||
g.reset();
|
||||
}
|
||||
if (setting('large')) {
|
||||
g.setFont("6x8",2);
|
||||
g.setFontAlign(-1, 0);
|
||||
g.drawString(stps, this.x + (setting('progress')?28:4), this.y + 12);
|
||||
} else {
|
||||
let w = 24;
|
||||
if (stps.length > 3){
|
||||
stps = stps.slice(0,-3) + "," + stps.slice(-3);
|
||||
g.setFont("4x6", 1); // if big, shrink text to fix
|
||||
} else {
|
||||
g.setFont("6x8", 1);
|
||||
}
|
||||
g.setFontAlign(0, 0); // align to x: center, y: center
|
||||
g.drawString(stps, this.x+w/2, this.y+19);
|
||||
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(w-10)/2,this.y+2);
|
||||
}
|
||||
},
|
||||
reload:function() {
|
||||
loadSettings();
|
||||
WIDGETS["wpedom"].redraw();
|
||||
},
|
||||
getSteps:()=>stp_today
|
||||
};
|
||||
// Load data at startup
|
||||
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
|
||||
if (pedomData) {
|
||||
|
|
|
|||
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 2aac601e38d659876eb7db5aebc7a12dd3c39da7
|
||||
Subproject commit 0fd608f085deff9b39f2db3559ecc88edb232aba
|
||||
|
|
@ -40,6 +40,10 @@
|
|||
<p id="requireHTTPS" class="hidden">
|
||||
<b>STOP!</b> This page <b>must</b> be served over HTTPS. Please <a>reload this page via HTTPS</a>.
|
||||
</p>
|
||||
<div class="toast toast-success flex-centered" id="bangle2ks">
|
||||
<img src="data:image/png;base64,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" />
|
||||
Bangle.js 2 is now on KickStarter! <a href="https://www.kickstarter.com/projects/gfw/banglejs-2-the-open-smart-watch" target="_blank">Check it out here</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -154,16 +154,20 @@ function touchHandler(l,e) {
|
|||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||
}
|
||||
|
||||
function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) {
|
||||
if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") {
|
||||
function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) {
|
||||
var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol);
|
||||
if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") {
|
||||
// Hash the layoutObject without including its children
|
||||
let c = l.c;
|
||||
var c = l.c;
|
||||
delete l.c;
|
||||
let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order
|
||||
var hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order
|
||||
if (c) l.c = c;
|
||||
|
||||
if (!delete rectsToClear[hash]) {
|
||||
rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]};
|
||||
rects[hash] = {
|
||||
bg: parentBg == null ? g.theme.bg : parentBg,
|
||||
r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]
|
||||
};
|
||||
if (drawList) {
|
||||
drawList.push(l);
|
||||
drawList = null; // Prevent children from being redundantly added to the drawList
|
||||
|
|
@ -171,7 +175,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) {
|
|||
}
|
||||
}
|
||||
|
||||
if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol);
|
||||
if (l.c) for (var ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, bgCol);
|
||||
}
|
||||
|
||||
Layout.prototype.render = function (l) {
|
||||
|
|
@ -189,20 +193,24 @@ Layout.prototype.render = function (l) {
|
|||
"txt":function(l){
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||
}, "btn":function(l){
|
||||
var x = l.x+(0|l.pad);
|
||||
var y = l.y+(0|l.pad);
|
||||
var w = l.w-(l.pad<<1);
|
||||
var h = l.h-(l.pad<<1);
|
||||
var poly = [
|
||||
l.x,l.y+4,
|
||||
l.x+4,l.y,
|
||||
l.x+l.w-5,l.y,
|
||||
l.x+l.w-1,l.y+4,
|
||||
l.x+l.w-1,l.y+l.h-5,
|
||||
l.x+l.w-5,l.y+l.h-1,
|
||||
l.x+4,l.y+l.h-1,
|
||||
l.x,l.y+l.h-5,
|
||||
l.x,l.y+4
|
||||
x,y+4,
|
||||
x+4,y,
|
||||
x+w-5,y,
|
||||
x+w-1,y+4,
|
||||
x+w-1,y+h-5,
|
||||
x+w-5,y+h-1,
|
||||
x+4,y+h-1,
|
||||
x,y+h-5,
|
||||
x,y+4
|
||||
];
|
||||
g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
||||
}, "img":function(l){
|
||||
g.drawImage(l.src(), l.x, l.y);
|
||||
g.drawImage(l.src(), l.x + (0|l.pad), l.y + (0|l.pad));
|
||||
}, "custom":function(l){
|
||||
l.render(l);
|
||||
},"h":function(l) { l.c.forEach(render); },
|
||||
|
|
@ -216,7 +224,7 @@ Layout.prototype.render = function (l) {
|
|||
if (!this.rects) this.rects = {};
|
||||
var rectsToClear = this.rects.clone();
|
||||
var drawList = [];
|
||||
prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor());
|
||||
prepareLazyRender(l, rectsToClear, drawList, this.rects, null);
|
||||
for (var h in rectsToClear) delete this.rects[h];
|
||||
var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored
|
||||
for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r);
|
||||
|
|
@ -231,39 +239,29 @@ Layout.prototype.layout = function (l) {
|
|||
// exw,exh = extra width/height available
|
||||
switch (l.type) {
|
||||
case "h": {
|
||||
let x = l.x + (l.w-l._w)/2;
|
||||
var x = l.x + (0|l.pad);
|
||||
var fillx = l.c && l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
||||
if (fillx) { x = l.x; }
|
||||
if (!fillx) { x += (l.w-l._w)/2; }
|
||||
l.c.forEach(c => {
|
||||
c.w = c._w + ((0|c.fillx)*(l.w-l._w)/(fillx||1));
|
||||
c.h = c.filly ? l.h : c._h;
|
||||
c.h = c.filly ? l.h - (l.pad<<1) : c._h;
|
||||
c.x = x;
|
||||
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
||||
c.y = l.y + (0|l.pad) + (1+(0|c.valign))*(l.h-(l.pad<<1)-c.h)/2;
|
||||
x += c.w;
|
||||
if (c.pad) {
|
||||
x += c.pad*2;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.c) this.layout(c);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "v": {
|
||||
let y = l.y + (l.h-l._h)/2;
|
||||
var y = l.y + (0|l.pad);;
|
||||
var filly = l.c && l.c.reduce((a,l)=>a+(0|l.filly),0);
|
||||
if (filly) { y = l.y; }
|
||||
if (!filly) { y += (l.h-l._h)/2 }
|
||||
l.c.forEach(c => {
|
||||
c.w = c.fillx ? l.w : c._w;
|
||||
c.w = c.fillx ? l.w - (l.pad<<1) : c._w;
|
||||
c.h = c._h + ((0|c.filly)*(l.h-l._h)/(filly||1));
|
||||
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
||||
c.y = y;
|
||||
c.x = l.x + (0|l.pad) + (1+(0|c.halign))*(l.w-(l.pad<<1)-c.w)/2;
|
||||
y += c.h;
|
||||
if (c.pad) {
|
||||
y += c.pad*2;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.c) this.layout(c);
|
||||
});
|
||||
break;
|
||||
|
|
@ -274,6 +272,8 @@ Layout.prototype.debug = function(l,c) {
|
|||
if (!l) l = this._l;
|
||||
c=c||1;
|
||||
g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c);
|
||||
if (l.pad)
|
||||
g.drawRect(l.x+l.pad-1, l.y+l.pad-1, l.x+l.w-l.pad, l.y+l.h-l.pad);
|
||||
c++;
|
||||
if (l.c) l.c.forEach(n => this.debug(n,c));
|
||||
};
|
||||
|
|
@ -288,8 +288,8 @@ Layout.prototype.update = function() {
|
|||
if (l.r&1) { // rotation
|
||||
var t = l._w;l._w=l._h;l._h=t;
|
||||
}
|
||||
l._w = Math.max(l._w, 0|l.width);
|
||||
l._h = Math.max(l._h, 0|l.height);
|
||||
l._w = Math.max(l._w + (l.pad<<1), 0|l.width);
|
||||
l._h = Math.max(l._h + (l.pad<<1), 0|l.height);
|
||||
}
|
||||
var cb = {
|
||||
"txt" : function(l) {
|
||||
|
|
@ -327,16 +327,16 @@ Layout.prototype.update = function() {
|
|||
l._h = 0;
|
||||
}, "h": function(l) {
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
||||
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
||||
if (l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||
if (l.c.some(c=>c.filly)) l.filly = 1;
|
||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h),0);
|
||||
l._w = l.c.reduce((a,b)=>a+b._w,0);
|
||||
if (l.fillx == null && l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||
if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1;
|
||||
}, "v": function(l) {
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
||||
if (l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||
if (l.c.some(c=>c.filly)) l.filly = 1;
|
||||
l._h = l.c.reduce((a,b)=>a+b._h,0);
|
||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w),0);
|
||||
if (l.fillx == null && l.c.some(c=>c.fillx)) l.fillx = 1;
|
||||
if (l.filly == null && l.c.some(c=>c.filly)) l.filly = 1;
|
||||
}
|
||||
};
|
||||
updateMin(l);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd `dirname $0`/..
|
||||
ls tests/*.js | xargs -I{} bin/runtest.sh {}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#!/bin/bash
|
||||
# Requires Linux x64 (for ./espruino)
|
||||
# Also imagemagick for display
|
||||
|
||||
cd `dirname $0`/..
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "USAGE:"
|
||||
echo " bin/runtest.sh tests/testxyz.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# temporary test files
|
||||
TESTJS=bin/tmp.js
|
||||
TESTBMP=bin/tmp.bmp
|
||||
# actual source files
|
||||
SRCDIR=tests
|
||||
SRCJS=$1
|
||||
SRCBMP=$SRCDIR/`basename $SRCJS .js`.bmp
|
||||
echo "TEST $SRCJS ($SRCBMP)"
|
||||
|
||||
cat ../../modules/Layout.js > $TESTJS
|
||||
echo 'Bangle = {};BTN1=0;process.env = process.env;process.env.HWVERSION=2;' >> $TESTJS
|
||||
echo 'g = Graphics.createArrayBuffer(176,176,4);' >> $TESTJS
|
||||
cat $SRCJS >> $TESTJS || exit 1
|
||||
echo 'layout.render()' >> $TESTJS
|
||||
#echo 'layout.debug()' >> $TESTJS
|
||||
echo 'require("fs").writeFileSync("'$TESTBMP'",g.asBMP())' >> $TESTJS
|
||||
|
||||
bin/espruino $TESTJS || exit 1
|
||||
if ! cmp $TESTBMP $SRCBMP >/dev/null 2>&1
|
||||
then
|
||||
echo =============================================
|
||||
echo $TESTBMP $SRCBMP differ
|
||||
echo ==============================================
|
||||
convert "+append" $TESTBMP $SRCBMP testresult.bmp
|
||||
display testresult.bmp
|
||||
exit 1
|
||||
else
|
||||
echo Files are the same
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,13 @@
|
|||
// Based on accellog usage
|
||||
var layout = new Layout({ type: "v", c: [
|
||||
{type:"txt", font:"6x8", label:"Samples", pad:2},
|
||||
{type:"txt", id:"samples", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", font:"6x8", label:"Time", pad:2},
|
||||
{type:"txt", id:"time", font:"6x8:2", label:" - ", pad:5},
|
||||
{type:"txt", font:"6x8:2", label:"RECORDING", bgCol:"#f00", pad:5, fillx:1},
|
||||
]
|
||||
},[ // Buttons...
|
||||
{label:"STOP", cb:()=>{}}
|
||||
]);
|
||||
layout.samples.label = "123";
|
||||
layout.time.label = "123s";
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,9 @@
|
|||
g.clear();
|
||||
var layout = new Layout({type:"h", filly: 0, c: [
|
||||
{type: "txt", font: "50%", label: "A"},
|
||||
{type:"v", c: [
|
||||
{type: "txt", font: "10%", label: "B"},
|
||||
{filly: 1},
|
||||
{type: "txt", font: "10%", label: "C"},
|
||||
]},
|
||||
]});
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,26 @@
|
|||
var img = () => ({
|
||||
width : 8, height : 8, bpp : 4,
|
||||
transparent : 1,
|
||||
buffer : E.toArrayBuffer(atob("Ee7uER7u7uHuDuDu7u7u7u7u7u7g7u4OHgAA4RHu7hE="))
|
||||
});
|
||||
|
||||
var layout = new Layout({type: "v", c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", pad: 5, label: "TEXT"},
|
||||
{type: "img", font: "6x8", bgCol: "#0F0", pad: 5, src: img},
|
||||
{type: "btn", font: "6x8", bgCol: "#00F", pad: 5, label: "BTN"},
|
||||
{type: "v", bgCol: "#F0F", pad: 2, c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", label: "v with children"},
|
||||
{type: "txt", font: "6x8", bgCol: "#0F0", halign: -1, label: "halign -1"},
|
||||
{type: "txt", font: "6x8", bgCol: "#00F", halign: 1, label: "halign 1"},
|
||||
]},
|
||||
{type: "h", bgCol: "#0FF", pad: 2, c: [
|
||||
{type: "txt", font: "6x8:2", bgCol: "#F00", label: "h"},
|
||||
{type: "txt", font: "6x8", bgCol: "#0F0", valign: -1, label: "valign -1"},
|
||||
{type: "txt", font: "6x8", bgCol: "#00F", valign: 1, label: "valign 1"},
|
||||
]},
|
||||
{type: "h", bgCol: "#FF0", pad: 2, c: [
|
||||
{type: "v", bgCol: "#0F0", pad: 2, c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", pad: 2, label: "nested"},
|
||||
]},
|
||||
]},
|
||||
]});
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
var layout = new Layout({type:"v", fillx: true, c: [
|
||||
{type: "txt", font: "10%", halign:1, pad: 20, label: "0123456789"},
|
||||
]});
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
var layout = new Layout({type: "v", c: [
|
||||
{type: "h", pad: 20, c: [
|
||||
{type: "txt", font: "10%", label: "abcd"},
|
||||
{fillx: 1},
|
||||
{type: "txt", font: "10%", label: "1234"},
|
||||
]},
|
||||
]});
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -0,0 +1,26 @@
|
|||
var img = () => ({
|
||||
width : 8, height : 8, bpp : 4,
|
||||
transparent : 1,
|
||||
buffer : E.toArrayBuffer(atob("Ee7uER7u7uHuDuDu7u7u7u7u7u7g7u4OHgAA4RHu7hE="))
|
||||
});
|
||||
|
||||
var layout = new Layout({type: "v", c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", fillx: 1, pad: 5, label: "TEXT"},
|
||||
{type: "img", font: "6x8", bgCol: "#0F0", filly: 1, fillx: 1,pad: 5, src: img},
|
||||
{type: "btn", font: "6x8", bgCol: "#00F", fillx: 1, pad: 5, label: "BTN"},
|
||||
{type: "v", bgCol: "#F0F", pad: 2, c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", fillx: 1, filly: 1, label: "v with children"},
|
||||
{type: "txt", font: "6x8", bgCol: "#0F0", halign: -1, label: "halign -1"},
|
||||
{type: "txt", font: "6x8", bgCol: "#00F", halign: 1, label: "halign 1"},
|
||||
]},
|
||||
{type: "h", bgCol: "#0FF", pad: 2, c: [
|
||||
{type: "txt", font: "6x8:2", bgCol: "#F00", fillx: 1, filly: 1, label: "h"},
|
||||
{type: "txt", font: "6x8", bgCol: "#0F0", valign: -1, label: "valign -1"},
|
||||
{type: "txt", font: "6x8", bgCol: "#00F", valign: 1, label: "valign 1"},
|
||||
]},
|
||||
{type: "h", bgCol: "#FF0", pad: 2, c: [
|
||||
{type: "v", bgCol: "#0F0", pad: 2, c: [
|
||||
{type: "txt", font: "6x8", bgCol: "#F00", fillx: 1, filly: 1, pad: 2, label: "nested"},
|
||||
]},
|
||||
]},
|
||||
]});
|
||||