mirror of https://github.com/espruino/BangleApps
Merge remote-tracking branch 'upstream/master'
commit
93cefbcd33
21
apps.json
21
apps.json
|
@ -616,7 +616,7 @@
|
|||
{ "id": "widbat",
|
||||
"name": "Battery Level Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "Show the current battery level and charging status in the top right of the clock",
|
||||
"tags": "widget,battery,b2",
|
||||
"type":"widget",
|
||||
|
@ -2409,10 +2409,10 @@
|
|||
"name": "Acceleration Logger",
|
||||
"shortName":"Accel Log",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"interface": "interface.html",
|
||||
"description": "Logs XYZ acceleration data to a CSV file that can be downloaded to your PC",
|
||||
"tags": "outdoor",
|
||||
"tags": "outdoor,b2",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"accellog.app.js","url":"app.js"},
|
||||
|
@ -3411,6 +3411,21 @@
|
|||
{"name":"thermomF.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "nixie",
|
||||
"name": "Nixie Clock",
|
||||
"shortName":"Nixie",
|
||||
"icon": "nixie.png",
|
||||
"version":"0.01",
|
||||
"description": "A nixie tube clock for both Bangle 1 and 2.",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"nixie.app.js","url":"app.js"},
|
||||
{"name":"nixie.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"m_vatch.js","url":"m_vatch.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "carcrazy",
|
||||
"name": "Car Crazy",
|
||||
"shortName":"Car Crazy",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Use the new multiplatform 'Layout' library
|
||||
|
|
|
@ -90,16 +90,26 @@ function startRecord(force) {
|
|||
// display
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
var w = g.getWidth();
|
||||
var h = g.getHeight();
|
||||
g.setColor("#ff0000").fillRect(0,h-48,w,h);
|
||||
g.setColor("#ffffff").setFont("6x8",2).setFontAlign(0,0).drawString("RECORDING", w/2,h-24);
|
||||
g.setFont("6x8").drawString("Samples:",w/2,h/3 - 20);
|
||||
g.setFont("6x8").drawString("Time:",w/2,h*2/3 - 20);
|
||||
g.setFont("6x8",2).setFontAlign(0,0,1).drawString("STOP",w-10,h/2);
|
||||
|
||||
var Layout = require("Layout");
|
||||
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:true},
|
||||
]
|
||||
},[ // Buttons...
|
||||
{label:"STOP", cb:()=>{
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
}}
|
||||
]);
|
||||
layout.update();
|
||||
layout.render();
|
||||
|
||||
// now start writing
|
||||
f = require("Storage").open(getFileName(fileNumber), "w");
|
||||
var f = require("Storage").open(getFileName(fileNumber), "w");
|
||||
f.write("Time (ms),X,Y,Z\n");
|
||||
var start = getTime();
|
||||
var sampleCount = 0;
|
||||
|
@ -113,17 +123,14 @@ function startRecord(force) {
|
|||
accel.z*8192].map(n=>Math.round(n)).join(",")+"\n");
|
||||
|
||||
sampleCount++;
|
||||
g.reset().setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString(" "+sampleCount+" ",w/2,h/3,true);
|
||||
g.drawString(" "+Math.round(t)+"s ",w/2,h*2/3,true);
|
||||
layout.samples.label = sampleCount;
|
||||
layout.time.label = Math.round(t)+"s";
|
||||
layout.render(layout.samples);
|
||||
layout.render(layout.time);
|
||||
}
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
Bangle.setPollInterval(80); // 12.5 Hz - the default
|
||||
Bangle.on('accel', accelHandler);
|
||||
setWatch(()=>{
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
showMenu();
|
||||
}, BTN2);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||

|
||||
# Car Crazy
|
||||
Car crazy is a fun game where you tilt your wrist left and right to avoid incoming cars. If you get hit by a car you lose a heart. In the game you have three hearts, if you get hit 3 times you are sent to the game over screen. Your goal is to try to last as long as you can. Because this game is still in beta please report any bugs here: https://forms.office.com/r/HnwYzG9Sk7.
|
||||
|
||||
Form Link: https://forms.office.com/r/HnwYzG9Sk7
|
||||
### Images:
|
||||
(Coming Soon)
|
||||
|
||||
### Instructions:
|
||||
|
||||
BNT2: Hold down this button to start the game if you are on the starting page and game over page.
|
||||
|
||||
Tilting Left-Right: Tilt your wrist left and right to steer your car and try not to get hit by the enemy car.
|
||||
|
||||
### Feautures Coming Soon:
|
||||
0.02: Levels are creating making the game get harder as it goes along.
|
||||
|
||||
0.03: Optional soundtrack in settings. More levels.
|
||||
|
||||
0.04: With higher scores you can now unlock different colors of cars.
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,17 @@
|
|||
## Nixie clock
|
||||
|
||||
This clock displays the time in nixie-inspired numerals and works on both Bangle versions (1 and 2). It uses a generic
|
||||
coordinate system (0 <= width < 1) and has helper functions to use inline.
|
||||
|
||||
The app makes use of a module called "m_vatch" which manages all the timers, and makes calls to functions in the 'main' file
|
||||
to manage drawing the background, time, and any data like sensor info, step counters, battery, etc. The idea is that it is
|
||||
reusable if you write many watch apps... you just need to implement functions to draw the background (called on start, and every
|
||||
time the 'mode' changes (regular and night mode), the time (which gets a call every second), and the data (also every second,
|
||||
except not in night mode)).
|
||||
|
||||
Night mode is a mode that can be set manually or automatically, allowing the watch code to adjust colors and detail. Mainly,
|
||||
used as a night clock, you can draw no background, and use dim colors for your digits. If set to auto, the accelerometer is used so
|
||||
when the watch is placed on its side, it switches to night mode (your watch may need a tweak... and Bangle 2 is a different story!)
|
||||
|
||||
It also handles step counting so that it's stored on a daily
|
||||
basis - survives a system reset, zeroes when the date changes and keeps a record in a history file by day.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBIf4A/AH4A/AH4AtgtVAANQAwIFCAwYTGBIQDBqsF6AXCqFQroXDBQQXFAxNUBRAQKAAXVAYUEFBY7EBgtVC5UEDotdERwQBC4pGDTgQXJgoRDMoQGCqisFR5pICDQQwDCAxVJZAYXKQo7nEC6AtBCYgXGCYYDCJQYXBF5ThDKwoNCB4UMC4yyBToIGDAYNUDoRiBO5CyBLwi5Ea4RyGAH4A/AH4A/AH4A/ACQ="))
|
|
@ -0,0 +1,429 @@
|
|||
const EMULATOR = false;
|
||||
// which Bangle?
|
||||
const isB2 = g.getWidth() < 200;
|
||||
|
||||
// global coordinate system
|
||||
const wX = g.getWidth();
|
||||
const wY = g.getHeight();
|
||||
const midX = wX/2, midY = wY/2;
|
||||
// relative positioning: send 0 <= coord < 1
|
||||
function relX(x) { return Math.floor(x*wX); }
|
||||
function relY(y) { return Math.floor(y*wY); }
|
||||
|
||||
// colors
|
||||
const col_bg = 0;
|
||||
const col_nm =isB2 ? 1 :"#206040";
|
||||
const col_sep = isB2 ? 6 :"#202020";
|
||||
const col_off = isB2 ? 1 : "#202020";
|
||||
const col_shad1 = isB2 ? 4 :"#FF0000";
|
||||
const col_shad2 = isB2 ? 6 :"#FF6000";
|
||||
const col_hi =isB2 ? 7 : "#FFC000";
|
||||
const col_data = isB2 ? 6 :"#C06000";
|
||||
|
||||
g.setBgColor(col_bg);
|
||||
g.clear();
|
||||
|
||||
var imgTube = {
|
||||
width : 64, height : 128, bpp : 2,
|
||||
buffer : require("heatshrink").decompress(atob("AE9AB7sQD54AOiFQB5tVsgPN0uoBxkByEFB5kGyIPNhVVB5tpLwKAMoJuOgNQggMJgtVDhsVqtEZ5cVrWlEBcFtWq1WlJxUaBwOq1IgJgIdCqoABEBEC1WVBwTkGKgUGFYIOCgIRDC4kaFoVUOQQKCQ4IgCB4YKDCYIgCq2QgEqHwJLIEoOkgFqB4KaIEoNkB4Z7JHQVqquqD5EVDYQPCVRIPE1IPKgsAtJTCAA8GyEBD4TrKqAPOgNRB5sRB5wfPgAPOiA/RP4IPaiD6BB5oCBB5kAdQIPNH5wPCvIPMBgIPMR4QPcL4QPNgIPQvS/MqtAB59+B9cVB91VL91BF91RF79RB4OVD5wPsH59BB51FB5sQB/0AD7xvPV4elD5wPLqIPOgJPeX/6//X8YPMH5wPPL74PfN55PQB6TfPB5afDB51/D57P/Z/7P/B97vOB5kAB58VoAA="))
|
||||
};
|
||||
var imgTubeBW = {
|
||||
width : 46, height : 92, bpp : 1,
|
||||
buffer : require("heatshrink").decompress(atob("AD0EAomAAgcCBQkQEykwAgcP/gFD/wKECok4AgcB4A7DgwQEjAFEsYWExg2DhkgAoVAE4kA8AEDgZqEhw+JgA+DCwIKEhhrJCyJELFqBbQIiByLIk6gWZyC3WOSItWOVq3nCywA="))
|
||||
};
|
||||
|
||||
require("Font8x12").add(Graphics);
|
||||
g.setFont("8x12", 1);
|
||||
let interval = null;
|
||||
|
||||
let alarming = false;
|
||||
let nightMode = false;
|
||||
|
||||
// our scale factor
|
||||
let xs = 0.5 * wX/240;
|
||||
let ys = 0.75 * wY/240;
|
||||
|
||||
let prevH1 = -1;
|
||||
let prevH2 = -1;
|
||||
let prevM1 = -1;
|
||||
let prevM2 = -1;
|
||||
|
||||
|
||||
let points0 = new Uint8Array([
|
||||
0, 40,
|
||||
1, 35,
|
||||
7, 20,
|
||||
16, 8,
|
||||
28, 2,
|
||||
40, 0,
|
||||
|
||||
51, 2,
|
||||
63, 10,
|
||||
72, 20,
|
||||
77, 35,
|
||||
78, 40,
|
||||
|
||||
78, 59,
|
||||
77, 64,
|
||||
72, 79,
|
||||
63, 89,
|
||||
51, 97,
|
||||
|
||||
40, 99,
|
||||
28, 97,
|
||||
16, 91,
|
||||
7, 79,
|
||||
1, 64,
|
||||
0, 59,
|
||||
0, 40
|
||||
]);
|
||||
|
||||
let points1 = new Uint8Array([ 40, 99, 40, 0]);
|
||||
|
||||
let points2 = new Uint8Array([ 0, 25,
|
||||
2, 22,
|
||||
6, 13,
|
||||
17, 5,
|
||||
28, 2,
|
||||
40, 0,
|
||||
52, 2,
|
||||
63, 5,
|
||||
74, 13,
|
||||
79, 23,
|
||||
79, 28,
|
||||
74, 38,
|
||||
63, 46,
|
||||
51, 54,
|
||||
40, 58,
|
||||
29, 62,
|
||||
17, 68,
|
||||
8, 80,
|
||||
0, 99,
|
||||
79, 99
|
||||
]);
|
||||
|
||||
let points4 = new Uint8Array([ 60, 99, 60, 0, 0, 75, 79, 75 ]);
|
||||
|
||||
let points8 = new Uint8Array([
|
||||
40, 40,
|
||||
26, 42,
|
||||
15, 46,
|
||||
4, 56,
|
||||
1, 66,
|
||||
1, 77,
|
||||
6, 87,
|
||||
17, 94,
|
||||
28, 97,
|
||||
38, 99,
|
||||
42, 99,
|
||||
52, 97,
|
||||
63, 94,
|
||||
74, 87,
|
||||
79, 77,
|
||||
79, 66,
|
||||
75, 56,
|
||||
64, 46,
|
||||
54, 42,
|
||||
40, 40,
|
||||
|
||||
52, 39,
|
||||
62, 34,
|
||||
69, 29,
|
||||
72, 23,
|
||||
72, 19,
|
||||
69, 12,
|
||||
62, 6,
|
||||
52, 2,
|
||||
40, 0,
|
||||
|
||||
28, 2,
|
||||
18, 6,
|
||||
11, 12,
|
||||
8, 19,
|
||||
8, 23,
|
||||
11, 29,
|
||||
18, 34,
|
||||
28, 39,
|
||||
40, 40,
|
||||
]);
|
||||
|
||||
let points6 = new Uint8Array([
|
||||
50, 0,
|
||||
4, 56,
|
||||
1, 66,
|
||||
1, 77,
|
||||
6, 87,
|
||||
17, 94,
|
||||
28, 97,
|
||||
40, 99,
|
||||
52, 97,
|
||||
63, 94,
|
||||
74, 87,
|
||||
79, 77,
|
||||
79, 66,
|
||||
75, 56,
|
||||
64, 46,
|
||||
52, 42,
|
||||
40, 40,
|
||||
26, 42,
|
||||
15, 46,
|
||||
4, 56,
|
||||
]);
|
||||
|
||||
let points3 = new Uint8Array([
|
||||
1, 77,
|
||||
6, 87,
|
||||
17, 94,
|
||||
28, 97,
|
||||
40, 99,
|
||||
52, 97,
|
||||
63, 94,
|
||||
74, 87,
|
||||
79, 77,
|
||||
79, 66,
|
||||
75, 56,
|
||||
64, 46,
|
||||
52, 42,
|
||||
39, 40,
|
||||
79, 0,
|
||||
1, 0
|
||||
]);
|
||||
|
||||
let points7 = new Uint8Array([ 0, 0, 79, 0, 30, 99 ]);
|
||||
|
||||
let points9 = new Uint8Array(points6.length);
|
||||
let points5 = new Uint8Array([
|
||||
1, 77,
|
||||
6, 87,
|
||||
17, 94,
|
||||
28, 97,
|
||||
38, 99,
|
||||
42, 99,
|
||||
52, 97,
|
||||
63, 94,
|
||||
74, 87,
|
||||
79, 77,
|
||||
79, 66,
|
||||
75, 56,
|
||||
64, 46,
|
||||
54, 42,
|
||||
40, 40,
|
||||
26, 42,
|
||||
15, 46,
|
||||
27, 0,
|
||||
79, 0,
|
||||
]);
|
||||
|
||||
function drawPoints(points, x0, y0) {
|
||||
let x = points[0]*xs+x0, y = points[1]*ys+y0;
|
||||
//g.drawEllipse(x-2, y-2, x+2, y+2);
|
||||
g.moveTo(x, y);
|
||||
for(let idx=1; idx*2 < points.length; idx ++) {
|
||||
let x = points[idx*2]*xs+x0;
|
||||
let y = points[idx*2+1]*ys+y0;
|
||||
//g.drawEllipse(x-2, y-2, x+2, y+2);
|
||||
g.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/* create 5 from 2 */
|
||||
/* uncomment if you want the 5 to look more authentic (but uglier)
|
||||
for (let idx=0; idx*2 < points2.length; idx++) {
|
||||
points5[idx*2] = points2[idx*2];
|
||||
points5[idx*2+1] = 99-points2[idx*2+1];
|
||||
}
|
||||
*/
|
||||
/* create 9 from 6 */
|
||||
for (let idx=0; idx*2 < points6.length; idx++) {
|
||||
points9[idx*2] = 79-points6[idx*2];
|
||||
points9[idx*2+1] = 99-points6[idx*2+1];
|
||||
}
|
||||
|
||||
pointsArray = [points0, points1, points2, points3, points4, points5, points6, points7, points8, points9];
|
||||
|
||||
function eraseDigit(d, x, y) {
|
||||
if(d < 0 || d > 9) return;
|
||||
g.setColor(col_bg);
|
||||
if(nightMode) {
|
||||
drawPoints(pointsArray[d], x, y);
|
||||
return;
|
||||
}
|
||||
drawPoints(pointsArray[d], x-2, y-2);
|
||||
drawPoints(pointsArray[d], x+2, y-2);
|
||||
drawPoints(pointsArray[d], x-2, y+2);
|
||||
drawPoints(pointsArray[d], x+2, y+2);
|
||||
drawPoints(pointsArray[d], x-1, y-1);
|
||||
drawPoints(pointsArray[d], x+1, y-1);
|
||||
drawPoints(pointsArray[d], x-1, y+1);
|
||||
drawPoints(pointsArray[d], x+1, y+1);
|
||||
}
|
||||
|
||||
function drawDigit(d, x, y) {
|
||||
if(nightMode) {
|
||||
g.setColor(col_nm);
|
||||
drawPoints(pointsArray[d], x, y);
|
||||
return;
|
||||
}
|
||||
g.setColor(col_off);
|
||||
for (let idx = pointsArray.length - 1; idx >= 0 ; idx--) {
|
||||
if(idx == d) {
|
||||
g.setColor(col_shad1);
|
||||
drawPoints(pointsArray[d], x-2, y-2);
|
||||
drawPoints(pointsArray[d], x+2, y-2);
|
||||
drawPoints(pointsArray[d], x-2, y+2);
|
||||
drawPoints(pointsArray[d], x+2, y+2);
|
||||
g.setColor(col_shad2);
|
||||
drawPoints(pointsArray[d], x-1, y-1);
|
||||
drawPoints(pointsArray[d], x+1, y-1);
|
||||
drawPoints(pointsArray[d], x-1, y+1);
|
||||
drawPoints(pointsArray[d], x+1, y+1);
|
||||
|
||||
g.setColor(col_hi);
|
||||
drawPoints(pointsArray[d], x, y);
|
||||
|
||||
g.setColor(col_off);
|
||||
} else {
|
||||
drawPoints(pointsArray[idx], x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawBkgd(nm) {
|
||||
g.clear();
|
||||
prevH1=-1;prevH2=-1;prevM1=-1;prevM2=-1;
|
||||
if(nm) return;
|
||||
|
||||
if(!isB2) {
|
||||
// tube images
|
||||
g.setColor(col_shad2);
|
||||
|
||||
[relX(0),relX(0.25),relX(0.5),relX(0.75)].forEach((v,i,a) => {
|
||||
g.drawImage(imgTube,v,relY(0.225));
|
||||
});
|
||||
// something to sit on
|
||||
g.setColor(col_shad2);
|
||||
g.fillRect(0, relY(0.76),wX,relY(0.76));
|
||||
} else {
|
||||
// simple tubes
|
||||
[1,45,89,133].forEach((v,i,a) => {
|
||||
g.setColor(col_shad1);
|
||||
g.drawEllipse(v, 52, v+41, 90);
|
||||
g.drawRect(v,66,v+41,125);
|
||||
g.clearRect(v+1,66,v+40,124);
|
||||
});
|
||||
}
|
||||
g.setColor(col_shad2);
|
||||
g.moveTo(relX(0.125), 0);
|
||||
g.lineTo(relX(0.25), relY(0.125));
|
||||
g.lineTo(relX(0.75), relY(0.125));
|
||||
g.lineTo(relX(0.875),0);
|
||||
|
||||
g.moveTo(relX(0.125), wY);
|
||||
g.lineTo(relX(0.25), relY(0.875));
|
||||
g.lineTo(relX(0.75), relY(0.875));
|
||||
g.lineTo(relX(0.875), wY);
|
||||
|
||||
}
|
||||
|
||||
function drawTime(d,nm) {
|
||||
const dx = [relX(0.042), relX(0.29), relX(0.55), relX(0.791)]; //[ 10, 65, 135, 190];
|
||||
const dy = [relY(0.38),relY(0.38),relY(0.38),relY(0.38)];
|
||||
|
||||
let h1 = Math.floor(d.hour / 10);
|
||||
let h2 = d.hour % 10;
|
||||
let m1 = Math.floor(d.min / 10);
|
||||
let m2 = d.min % 10;
|
||||
|
||||
if(h1 == prevH1 && h2 == prevH2 && m1 == prevM1 && m2 == prevM2) {
|
||||
return;
|
||||
}
|
||||
nightMode = nm;
|
||||
|
||||
if(h1 != prevH1) {
|
||||
eraseDigit(prevH1, dx[0], dy[0]);
|
||||
drawDigit(h1, dx[0], dy[0]);
|
||||
}
|
||||
if(h2 != prevH2) {
|
||||
eraseDigit(prevH2, dx[1], dy[1]);
|
||||
drawDigit(h2, dx[1], dy[1]);
|
||||
}
|
||||
if(m1 != prevM1) {
|
||||
eraseDigit(prevM1, dx[2], dy[2]);
|
||||
drawDigit(m1, dx[2], dy[2]);
|
||||
}
|
||||
if(m2 != prevM2) {
|
||||
eraseDigit(prevM2, dx[3], dy[3]);
|
||||
drawDigit(m2, dx[3], dy[3]);
|
||||
}
|
||||
prevH1 = h1;
|
||||
prevH2 = h2;
|
||||
prevM1 = m1;
|
||||
prevM2 = m2;
|
||||
|
||||
}
|
||||
|
||||
function drawData(d) {
|
||||
if(!nightMode) {
|
||||
g.setColor(col_data);
|
||||
g.setFontAlign(0, -1);
|
||||
g.drawString(` ${d.dow}, ${d.mon3} ${d.date} `, wX/2, relX(0.042), true);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.drawString("STEP ", 0, relY(0.82), true);
|
||||
g.drawString(`${d.steps} `,0, relY(0.875), true);
|
||||
g.setFontAlign(1,-1);
|
||||
g.drawString(" BTY", relX(0.999), relY(0.82), true);
|
||||
g.drawString(` ${d.batt}`, relX(0.999), relY(0.875), true);
|
||||
g.setFontAlign(0,-1);
|
||||
g.setColor(col_shad2);
|
||||
g.drawString('BANGLE.JS', wX/2, relY(0.925));
|
||||
}
|
||||
}
|
||||
|
||||
//setWatch(E.showLauncher, BTN1, {repeat:true,edge:"falling"});
|
||||
if(EMULATOR) {
|
||||
let d = new Date();
|
||||
|
||||
let hour = d.getHours();
|
||||
let minute = d.getMinutes();
|
||||
|
||||
let h1 = Math.floor(hour / 10);
|
||||
let h2 = hour % 10;
|
||||
let m1 = Math.floor(minute / 10);
|
||||
let m2 = minute % 10;
|
||||
|
||||
let data = {
|
||||
h1: h1,
|
||||
h2: h2,
|
||||
m1: m1,
|
||||
m2: m2,
|
||||
hour: hour,
|
||||
min: minute,
|
||||
};
|
||||
|
||||
drawBkgd(nightMode);
|
||||
|
||||
drawTime(data, nightMode);
|
||||
const mstr="JanFebMarAprMayJunJulAugSepOctNovDec";
|
||||
const dowstr = "SunMonTueWedThuFriSat";
|
||||
|
||||
let month = d.getMonth();
|
||||
let dow = d.getDay();
|
||||
data.month = month;
|
||||
data.date = d.getDate();
|
||||
|
||||
data.mon3 = mstr.slice(month*3,month*3+3);
|
||||
data.dow = dowstr.substr(dow*3,3);
|
||||
data.dateStr = data.dow + " " + data.mon3 + " " + data.date;
|
||||
data.steps = 12345;
|
||||
data.batt = E.getBattery() + (Bangle.isCharging() ? "+" : "");
|
||||
data.charging = Bangle.isCharging();
|
||||
|
||||
drawData(data);
|
||||
} else {
|
||||
Bangle.setUI("clock");
|
||||
let v = require("m_vatch.js");
|
||||
v.setDrawTime(drawTime);
|
||||
v.setDrawBackground(drawBkgd);
|
||||
v.setDrawData(drawData);
|
||||
v.begin();
|
||||
}
|
|
@ -0,0 +1,317 @@
|
|||
const _Storage = require('Storage');
|
||||
|
||||
let interval = null;
|
||||
|
||||
let nightMode = false;
|
||||
|
||||
let stepFile = 'v.steps.json';
|
||||
let stepArchiveFile = 'v.stephist.json';
|
||||
|
||||
let _Options = {};
|
||||
let optsFile = 'm_vatch.opts.json';
|
||||
|
||||
let _Alarm = {
|
||||
inAlarm: false,
|
||||
reload: () => {},
|
||||
scheduleAlarms: () => {},
|
||||
showMsg: (title, msg) => {},
|
||||
showNotes: () => {},
|
||||
};
|
||||
|
||||
let _StepData = {};
|
||||
|
||||
const pad0 = (n) => (n > 9) ? n : ("0"+n);
|
||||
|
||||
const getToday = () => {
|
||||
let d = new Date();
|
||||
return d.getFullYear()+'-'+ pad0(d.getMonth()+1) + '-' + pad0(d.getDate());
|
||||
};
|
||||
|
||||
function reload() {
|
||||
_StepData = _Storage.readJSON(stepFile);
|
||||
if(!_StepData) {
|
||||
_StepData = {
|
||||
lastDate: '2020-01-01',
|
||||
stepCache: 0,
|
||||
lastStepCount: 0,
|
||||
updated: true,
|
||||
};
|
||||
}
|
||||
if(getToday() === _StepData.lastDate) {
|
||||
_StepData.stepCache += _StepData.lastStepCount;
|
||||
_StepData.lastStepCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function stringFromArray(data)
|
||||
{
|
||||
var count = data.length;
|
||||
var str = "";
|
||||
|
||||
for(var index = 0; index < count; index += 1)
|
||||
str += String.fromCharCode(data[index]);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function logD(str) {
|
||||
if(_Options.debug) console.log(str);
|
||||
}
|
||||
|
||||
|
||||
let lastH1 = -1;
|
||||
let lastH2 = -1;
|
||||
let lastM1 = -1;
|
||||
let lastM2 = -1;
|
||||
|
||||
|
||||
let drawBackground = () => {};
|
||||
let drawTime = () => {};
|
||||
let drawData = () => {};
|
||||
|
||||
|
||||
function timeCheck() {
|
||||
|
||||
if(_Alarm.inAlarm) return;
|
||||
|
||||
logD('Again, ' + JSON.stringify(_Options));
|
||||
logD('opt.nm = '+_Options.autoNightMode);
|
||||
if(_Options.autoNightMode) {
|
||||
// this may vary by Bangle.. adjust to taste
|
||||
let a = Bangle.getAccel();
|
||||
a.x = Math.floor(a.x * 100);
|
||||
logD('a.x = ' + a.x);
|
||||
if(a.x <= 101 && a.x >= 99) {
|
||||
if(!nightMode) {
|
||||
nightMode = ! nightMode;
|
||||
redrawScreen();
|
||||
}
|
||||
} else {
|
||||
if(nightMode) {
|
||||
nightMode = ! nightMode;
|
||||
redrawScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let d = new Date();
|
||||
|
||||
let hour = d.getHours();
|
||||
let minute = d.getMinutes();
|
||||
|
||||
let h1 = Math.floor(hour / 10);
|
||||
let h2 = hour % 10;
|
||||
let m1 = Math.floor(minute / 10);
|
||||
let m2 = minute % 10;
|
||||
|
||||
logD("lastH1 = "+lastH1+": lastM2 = "+lastM2);
|
||||
if(h1 == lastH1 && h2 == lastH2 && m1 == lastM1 && m2 == lastM2) {
|
||||
return;
|
||||
}
|
||||
|
||||
logD("drawing time");
|
||||
let data = {
|
||||
h1: h1,
|
||||
h2: h2,
|
||||
m1: m1,
|
||||
m2: m2,
|
||||
hour: hour,
|
||||
min: minute,
|
||||
};
|
||||
drawTime(data, nightMode);
|
||||
|
||||
lastH1 = h1;
|
||||
lastH2 = h2;
|
||||
lastM1 = m1;
|
||||
lastM2 = m2;
|
||||
|
||||
if(!nightMode && !_Alarm.inAlarm) {
|
||||
logD("drawing data...");
|
||||
const mstr="JanFebMarAprMayJunJulAugSepOctNovDec";
|
||||
const dowstr = "SunMonTueWedThuFriSat";
|
||||
|
||||
let month = d.getMonth();
|
||||
let dow = d.getDay();
|
||||
data.month = month;
|
||||
data.date = d.getDate();
|
||||
|
||||
data.mon3 = mstr.slice(month*3,month*3+3);
|
||||
data.dow = dowstr.substr(dow*3,3);
|
||||
data.dateStr = data.dow + " " + data.mon3 + " " + data.date;
|
||||
data.steps = _StepData.stepCache + _StepData.lastStepCount;
|
||||
data.batt = E.getBattery() + (Bangle.isCharging() ? "+" : "");
|
||||
data.charging = Bangle.isCharging();
|
||||
|
||||
drawData(data);
|
||||
}
|
||||
|
||||
if(_StepData.updated) {
|
||||
_Storage.writeJSON(stepFile, _StepData);
|
||||
logD(JSON.stringify(_StepData));
|
||||
_StepData.updated = false;
|
||||
}
|
||||
}
|
||||
|
||||
function stop () {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}
|
||||
|
||||
function start () {
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
// first time init
|
||||
interval = setInterval(timeCheck, 1000);
|
||||
timeCheck();
|
||||
}
|
||||
|
||||
|
||||
function btn1Func() {
|
||||
logD("btn1Func");
|
||||
|
||||
if(_Alarm.inAlarm ) {
|
||||
_Alarm.inAlarm = false;
|
||||
} else {
|
||||
if( ! _Options.autoNightMode) {
|
||||
nightMode = ! nightMode;
|
||||
logD('nm is '+nightMode);
|
||||
}
|
||||
}
|
||||
redrawScreen();
|
||||
}
|
||||
|
||||
function redrawScreen() {
|
||||
logD("redrawScreen");
|
||||
|
||||
if(nightMode) {
|
||||
g.setRotation(1,0);
|
||||
} else {
|
||||
g.setRotation(0,0);
|
||||
}
|
||||
lastM1 = -1;
|
||||
lastM2 = -1;
|
||||
lastH1 = -1;
|
||||
lastH2 = -1;
|
||||
drawBackground(nightMode);
|
||||
timeCheck();
|
||||
}
|
||||
|
||||
function btn2Func() {
|
||||
_Alarm.reload();
|
||||
_Alarm.scheduleAlarms();
|
||||
_Alarm.showNotes();
|
||||
}
|
||||
|
||||
Bangle.on('step', function(cnt) {
|
||||
if(!_StepData.lastDate) return;
|
||||
if(_StepData.lastDate !== getToday()) {
|
||||
// save previous day's step count
|
||||
try {
|
||||
let sf = _Storage.readJSON(stepArchiveFile);
|
||||
if(!sf) sf = [];
|
||||
logD('sf is '+ (typeof sf) +':'+sf);
|
||||
// trim to 30
|
||||
if(sf.length >= 30 ) sf.shift();
|
||||
let steps = _StepData.stepCache +_StepData.lastStepCount;
|
||||
let sd = `${_StepData.lastDate},${steps}`;
|
||||
sf.push(sd);
|
||||
_Storage.writeJSON(stepArchiveFile, sf);
|
||||
} catch (err) {
|
||||
_Storage.write('err.txt',err);
|
||||
}
|
||||
/* individual step files by date
|
||||
_Storage.write(_StepData.lastDate +'.steps', JSON.stringify(
|
||||
_StepData.stepCache +_StepData.lastStepCount
|
||||
));
|
||||
*/
|
||||
_StepData.stepCache = 0 - cnt;
|
||||
_StepData.lastDate = getToday();
|
||||
}
|
||||
_StepData.lastStepCount = cnt;
|
||||
_StepData.updated = true;
|
||||
});
|
||||
|
||||
/*
|
||||
** Advertise a writeable characteristic. Accepts text (in 20 char
|
||||
** chunks) terminated with __EOM__ by itself. If there's text, show
|
||||
** it (as an alarm), otherwise reload the alarm & msg files (empty
|
||||
** string signals another BLE process updated those files)
|
||||
*/
|
||||
/*
|
||||
var BLEMessage = "";
|
||||
NRF.setServices({
|
||||
"feb10001-f00d-ea75-7192-abbadabadebb": {
|
||||
"feb10002-f00d-ea75-7192-abbadabadebb": {
|
||||
value : [0],
|
||||
maxLen : 20,
|
||||
writable : true,
|
||||
onWrite : function(evt) {
|
||||
let str = stringFromArray(evt.data);
|
||||
if(str === "__EOM__") {
|
||||
if(BLEMessage) {
|
||||
showMsg('Message',BLEMessage);
|
||||
} else {
|
||||
reload();
|
||||
scheduleAlarms();
|
||||
showMsg('', 'Reloading...');
|
||||
}
|
||||
BLEMessage = '';
|
||||
} else {
|
||||
BLEMessage += str;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, { });
|
||||
*/
|
||||
|
||||
exports.setDrawBackground = function(dBkgd) {
|
||||
drawBackground = dBkgd;
|
||||
};
|
||||
exports.setDrawTime = function(dTime) {
|
||||
drawTime = dTime;
|
||||
};
|
||||
exports.setDrawData = function( dData) {
|
||||
drawData = dData;
|
||||
};
|
||||
exports.begin = function() {
|
||||
_Options = _Storage.readJSON(optsFile);
|
||||
if(!_Options) _Options = {
|
||||
autoNightMode: true,
|
||||
useAlarms: false,
|
||||
stepManager: true,
|
||||
debug: true,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(_Options));
|
||||
|
||||
if(_Options.useAlarms) {
|
||||
_Alarm = require('m_alarms');
|
||||
_Alarm.reload();
|
||||
_Alarm.scheduleAlarms();
|
||||
}
|
||||
// separate the Bangles now
|
||||
const isB2 = g.getWidth() < 200;
|
||||
|
||||
if(!isB2) {
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
setWatch(btn1Func, BTN1, {repeat:true,edge:"falling"});
|
||||
|
||||
if(_Options.useAlarms) {
|
||||
setWatch(btn2Func, BTN2, {repeat:true,edge:"falling"});
|
||||
}
|
||||
setWatch(Bangle.showLauncher, BTN3, {repeat:false,edge:"falling"});
|
||||
}
|
||||
reload();
|
||||
drawBackground(nightMode);
|
||||
start();
|
||||
};
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id":"jvNixie",
|
||||
"name":"Nixie Clock",
|
||||
"type":"clock",
|
||||
"src":"nixie.app.js",
|
||||
"icon": "nixie.img",
|
||||
"sortorder":1,
|
||||
"version":"1.1",
|
||||
"files":"nixie.info,nixie.app.js,nixie.img, m_vatch.js"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 401 B |
|
@ -4,3 +4,4 @@
|
|||
0.05: Fix regression stopping correct widget updates
|
||||
0.06: Use 'g.theme' (requires bootloader 0.23)
|
||||
0.07: Move CHARGING variable to more readable string
|
||||
0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
Bangle.drawWidgets(); // relayout widgets
|
||||
g.flip();
|
||||
});
|
||||
var batteryInterval;
|
||||
var batteryInterval = Bangle.isLCDOn() ? setInterval(()=>WIDGETS["bat"].draw(), 60000) : undefined;
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
WIDGETS["bat"].draw();
|
||||
|
|
|
@ -1,39 +1,101 @@
|
|||
if (!g.theme) {
|
||||
g.theme = {
|
||||
fg:-1,bg:0,fgH:-1,bgH:"#008"
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Usage:
|
||||
|
||||
var layout = new Layout( layoutObject, btns )
|
||||
layout.render(optionalObject);
|
||||
|
||||
layoutObject has:
|
||||
|
||||
* A `type` field of:
|
||||
* `undefined` - blank, can be used for padding
|
||||
* `"txt"` - a text label, with value `label` and `r` for text rotation
|
||||
* `"btn"` - a button, with value `label` and callback `cb`
|
||||
* `"img"` - an image where the function `src` is called to return an image to draw
|
||||
* `"custom"` - a custom block where `render(layoutObj)` is called to render
|
||||
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
|
||||
* `"v"` - Veritical layout, `c` is an array of more `layoutObject`
|
||||
* A `id` field. If specified the object is added with this name to the
|
||||
returned `layout` object, so can be referenced as `layout.foo`
|
||||
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height
|
||||
* A `col` field, eg `#f00` for red
|
||||
* A `bgCol` field for background color (will automatically fill on render)
|
||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
||||
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
||||
* A `pad` integer field to set pixels padding
|
||||
* A `fillx` boolean to choose if the object should fill available space in x
|
||||
* A `filly` boolean to choose if the object should fill available space in y
|
||||
* `width` and `height` fields to optionally specify minimum size
|
||||
|
||||
btns is an array of objects containing:
|
||||
|
||||
* `label` - the text on the button
|
||||
* `cb` - a callback function
|
||||
* `cbl` - a callback function for long presses
|
||||
|
||||
Once `layout.update()` is called, the following fields are added
|
||||
to each object:
|
||||
|
||||
* `x` and `y` for the top left position
|
||||
* `w` and `h` for the width and height
|
||||
* `_w` and `_h` for the **minimum** width and height
|
||||
|
||||
|
||||
Other functions:
|
||||
|
||||
* `layout.update()` - update positions of everything if contents have changed
|
||||
* `layout.debug(obj)` - draw outlines for objects on screen
|
||||
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
function Layout(layout, buttons) {
|
||||
this._l = this.l = layout;
|
||||
this.b = buttons;
|
||||
// Do we have physical buttons?
|
||||
this.physBtn = process.env.HWVERSION!=2;
|
||||
// Do we have >1 physical buttons?
|
||||
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
||||
this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0;
|
||||
|
||||
if (buttons) {
|
||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
||||
if (this.physBtn) {
|
||||
if (buttons) {
|
||||
if (this.physBtns >= buttons.length) {
|
||||
// enough physical buttons
|
||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns);
|
||||
if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch);
|
||||
Bangle.btnWatch = [];
|
||||
if (this.physBtns > 2 && buttons.length==1)
|
||||
buttons.unshift({label:""}); // pad so if we have a button in the middle
|
||||
while (this.physBtns > buttons.length)
|
||||
buttons.push({label:""});
|
||||
if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
|
||||
if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
|
||||
if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
|
||||
this._l.width = g.getWidth()-8; // text width
|
||||
this._l = {type:"h", content: [
|
||||
this._l = {type:"h", filly:1, c: [
|
||||
this._l,
|
||||
{type:"v", content: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
||||
{type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
||||
]};
|
||||
} else { // no physical buttons, use touchscreen
|
||||
} else {
|
||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
||||
this._l.width = g.getWidth()-20; // button width
|
||||
this._l = {type:"h", content: [
|
||||
this._l = {type:"h", c: [
|
||||
this._l,
|
||||
{type:"v", content: buttons.map(b=>(b.type="btn",b.height=btnHeight,b.width=32,b.r=1,b))}
|
||||
{type:"v", c: buttons.map(b=>(b.type="btn",b.h=btnHeight,b.w=32,b.r=1,b))}
|
||||
]};
|
||||
Bangle.touchHandler = (_,e) => touchHandler(this._l,e);
|
||||
Bangle.on('touch',Bangle.touchHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// add IDs
|
||||
var ll = this;
|
||||
function idRecurser(l) {
|
||||
if (l.id) ll[l.id] = l;
|
||||
if (l.c) l.c.forEach(idRecurser);
|
||||
}
|
||||
idRecurser(layout);
|
||||
this.update();
|
||||
}
|
||||
|
||||
Layout.prototype.remove = function (l) {
|
||||
|
@ -59,7 +121,7 @@ function pressHandler(btn,e) {
|
|||
function touchHandler(l,e) {
|
||||
if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h)
|
||||
l.cb(e);
|
||||
if (l.content) l.content.forEach(n => touchHandler(n,e));
|
||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,7 +130,13 @@ function updateMin(l) {
|
|||
case "txt": {
|
||||
if (l.font.endsWith("%"))
|
||||
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
||||
g.setFont(l.font);
|
||||
// Not needed in new firmwares - 'font' is enough
|
||||
if (l.font.includes(":")) {
|
||||
var f = l.font.split(":");
|
||||
l.font = f[0];
|
||||
l.fsz = f[1];
|
||||
}
|
||||
g.setFont(l.font,l.fsz);
|
||||
l._h = g.getFontHeight();
|
||||
l._w = g.stringWidth(l.label);
|
||||
break;
|
||||
|
@ -84,6 +152,7 @@ function updateMin(l) {
|
|||
l._w = im.charCodeAt(1);
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case "custom": {
|
||||
// size should already be set up in width/height
|
||||
l._w = 0;
|
||||
|
@ -91,17 +160,17 @@ function updateMin(l) {
|
|||
break;
|
||||
}
|
||||
case "h": {
|
||||
l.content.forEach(updateMin);
|
||||
l._h = l.content.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
||||
l._w = l.content.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
||||
l.fill |= l.content.some(c=>c.fill);
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
||||
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
||||
l.fillx |= l.c.some(c=>c.fillx);
|
||||
break;
|
||||
}
|
||||
case "v": {
|
||||
l.content.forEach(updateMin);
|
||||
l._h = l.content.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
||||
l._w = l.content.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
||||
l.fill |= l.content.some(c=>c.fill);
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
||||
l.filly |= l.c.some(c=>c.filly);
|
||||
break;
|
||||
}
|
||||
default: throw "Unknown item type "+l.type;
|
||||
|
@ -116,9 +185,10 @@ function render(l) {
|
|||
if (!l) l = this.l;
|
||||
g.reset();
|
||||
if (l.col) g.setColor(l.col);
|
||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h);
|
||||
switch (l.type) {
|
||||
case "txt":
|
||||
g.setFont(l.font).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1));
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/);
|
||||
break;
|
||||
case "btn":
|
||||
var poly = [
|
||||
|
@ -141,7 +211,7 @@ function render(l) {
|
|||
l.render(l);
|
||||
break;
|
||||
}
|
||||
if (l.content) l.content.forEach(render);
|
||||
if (l.c) l.c.forEach(render);
|
||||
}
|
||||
|
||||
Layout.prototype.render = function (l) {
|
||||
|
@ -152,23 +222,24 @@ Layout.prototype.render = function (l) {
|
|||
Layout.prototype.layout = function (l) {
|
||||
// l = current layout element
|
||||
// exw,exh = extra width/height available
|
||||
var fill = l.content.reduce((a,l)=>a+(0|l.fill),0);
|
||||
var fillx = l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
||||
var filly = l.c.reduce((a,l)=>a+(0|l.filly),0);
|
||||
switch (l.type) {
|
||||
case "h": {
|
||||
let x = l.x + (l.w-l._w)/2;
|
||||
if (fill) { x = l.x; }
|
||||
l.content.forEach(c => {
|
||||
c.w = c._w + (c.fill?(l.w-l._w)/fill:0);
|
||||
c.h = c.fill ? l.h : c._h;
|
||||
if (fillx) { x = l.x; }
|
||||
l.c.forEach(c => {
|
||||
c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0);
|
||||
c.h = c.filly ? l.h : c._h;
|
||||
c.x = x;
|
||||
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
||||
x += c.w;
|
||||
if (c.pad) {
|
||||
x += c.pad*2;
|
||||
c.x += c.pad;
|
||||
c.y += c.pad;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.content) {
|
||||
if (c.c) {
|
||||
this.layout(c);
|
||||
}
|
||||
});
|
||||
|
@ -176,19 +247,19 @@ Layout.prototype.layout = function (l) {
|
|||
}
|
||||
case "v": {
|
||||
let y = l.y + (l.h-l._h)/2;
|
||||
if (fill) { y = l.y; }
|
||||
l.content.forEach(c => {
|
||||
c.w = c.fill ? l.w : c._w;
|
||||
c.h = c._h + (c.fill?(l.h-l._h)/fill:0);
|
||||
if (filly) { y = l.y; }
|
||||
l.c.forEach(c => {
|
||||
c.w = c.fillx ? l.w : c._w;
|
||||
c.h = c._h + (c.filly?(l.h-l._h)/filly:0);
|
||||
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
||||
c.y = y;
|
||||
y += c.h;
|
||||
if (c.pad) {
|
||||
y += c.pad*2;
|
||||
c.x += c.pad;
|
||||
c.y += c.pad;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.content) this.layout(c);
|
||||
if (c.c) this.layout(c);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -199,7 +270,7 @@ Layout.prototype.debug = function(l,c) {
|
|||
c=c||1;
|
||||
g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c);
|
||||
c++;
|
||||
if (l.content) l.content.forEach(n => this.debug(n,c));
|
||||
if (l.c) l.c.forEach(n => this.debug(n,c));
|
||||
};
|
||||
Layout.prototype.update = function() {
|
||||
var l = this._l;
|
||||
|
@ -209,7 +280,7 @@ Layout.prototype.update = function() {
|
|||
// update sizes
|
||||
updateMin(l);
|
||||
// center
|
||||
if (l.fill) {
|
||||
if (l.fillx || l.filly) {
|
||||
l.w = w;
|
||||
l.h = h;
|
||||
l.x = 0;
|
||||
|
@ -226,7 +297,9 @@ Layout.prototype.update = function() {
|
|||
|
||||
Layout.prototype.clear = function(l) {
|
||||
if (!l) l = this._l;
|
||||
g.reset().clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
|
||||
g.reset();
|
||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol);
|
||||
g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
|
||||
};
|
||||
|
||||
exports = Layout;
|
||||
|
|
|
@ -0,0 +1,305 @@
|
|||
|
||||
/*
|
||||
|
||||
Usage:
|
||||
|
||||
var layout = new Layout( layoutObject, btns )
|
||||
layout.render(optionalObject);
|
||||
|
||||
layoutObject has:
|
||||
|
||||
* A `type` field of:
|
||||
* `undefined` - blank, can be used for padding
|
||||
* `"txt"` - a text label, with value `label` and `r` for text rotation
|
||||
* `"btn"` - a button, with value `label` and callback `cb`
|
||||
* `"img"` - an image where the function `src` is called to return an image to draw
|
||||
* `"custom"` - a custom block where `render(layoutObj)` is called to render
|
||||
* `"h"` - Horizontal layout, `c` is an array of more `layoutObject`
|
||||
* `"v"` - Veritical layout, `c` is an array of more `layoutObject`
|
||||
* A `id` field. If specified the object is added with this name to the
|
||||
returned `layout` object, so can be referenced as `layout.foo`
|
||||
* A `font` field, eg `6x8` or `30%` to use a percentage of screen height
|
||||
* A `col` field, eg `#f00` for red
|
||||
* A `bgCol` field for background color (will automatically fill on render)
|
||||
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
|
||||
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
|
||||
* A `pad` integer field to set pixels padding
|
||||
* A `fillx` boolean to choose if the object should fill available space in x
|
||||
* A `filly` boolean to choose if the object should fill available space in y
|
||||
* `width` and `height` fields to optionally specify minimum size
|
||||
|
||||
btns is an array of objects containing:
|
||||
|
||||
* `label` - the text on the button
|
||||
* `cb` - a callback function
|
||||
* `cbl` - a callback function for long presses
|
||||
|
||||
Once `layout.update()` is called, the following fields are added
|
||||
to each object:
|
||||
|
||||
* `x` and `y` for the top left position
|
||||
* `w` and `h` for the width and height
|
||||
* `_w` and `_h` for the **minimum** width and height
|
||||
|
||||
|
||||
Other functions:
|
||||
|
||||
* `layout.update()` - update positions of everything if contents have changed
|
||||
* `layout.debug(obj)` - draw outlines for objects on screen
|
||||
* `layout.clear(obj)` - clear the given object (you can also just specify `bgCol` to clear before each render)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
function Layout(layout, buttons) {
|
||||
this._l = this.l = layout;
|
||||
this.b = buttons;
|
||||
// Do we have >1 physical buttons?
|
||||
this.physBtns = (process.env.HWVERSION==2) ? 1 : 3;
|
||||
this.yOffset = Object.keys(global.WIDGETS).length ? 24 : 0;
|
||||
|
||||
if (buttons) {
|
||||
if (this.physBtns >= buttons.length) {
|
||||
// enough physical buttons
|
||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / this.physBtns);
|
||||
if (Bangle.btnWatch) Bangle.btnWatch.forEach(clearWatch);
|
||||
Bangle.btnWatch = [];
|
||||
if (this.physBtns > 2 && buttons.length==1)
|
||||
buttons.unshift({label:""}); // pad so if we have a button in the middle
|
||||
while (this.physBtns > buttons.length)
|
||||
buttons.push({label:""});
|
||||
if (buttons[0]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,0), BTN1, {repeat:true,edge:-1}));
|
||||
if (buttons[1]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,1), BTN2, {repeat:true,edge:-1}));
|
||||
if (buttons[2]) Bangle.btnWatch.push(setWatch(pressHandler.bind(this,2), BTN3, {repeat:true,edge:-1}));
|
||||
this._l.width = g.getWidth()-8; // text width
|
||||
this._l = {type:"h", filly:1, c: [
|
||||
this._l,
|
||||
{type:"v", pad:1, filly:1, c: buttons.map(b=>(b.type="txt",b.font="6x8",b.height=btnHeight,b.r=1,b))}
|
||||
]};
|
||||
} else {
|
||||
var btnHeight = Math.floor((g.getHeight()-this.yOffset) / buttons.length);
|
||||
this._l.width = g.getWidth()-20; // button width
|
||||
this._l = {type:"h", c: [
|
||||
this._l,
|
||||
{type:"v", c: buttons.map(b=>(b.type="btn",b.h=btnHeight,b.w=32,b.r=1,b))}
|
||||
]};
|
||||
Bangle.touchHandler = (_,e) => touchHandler(this._l,e);
|
||||
Bangle.on('touch',Bangle.touchHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// add IDs
|
||||
var ll = this;
|
||||
function idRecurser(l) {
|
||||
if (l.id) ll[l.id] = l;
|
||||
if (l.c) l.c.forEach(idRecurser);
|
||||
}
|
||||
idRecurser(layout);
|
||||
this.update();
|
||||
}
|
||||
|
||||
Layout.prototype.remove = function (l) {
|
||||
if (Bangle.btnWatch) {
|
||||
Bangle.btnWatch.forEach(clearWatch);
|
||||
delete Bangle.btnWatch;
|
||||
}
|
||||
if (Bangle.touchHandler) {
|
||||
Bangle.removeListener("touch",Bangle.touchHandler);
|
||||
delete Bangle.touchHandler;
|
||||
}
|
||||
};
|
||||
|
||||
// Handler for button watch events
|
||||
function pressHandler(btn,e) {
|
||||
if (e.time-e.lastTime > 0.75 && this.b[btn].cbl)
|
||||
this.b[btn].cbl(e);
|
||||
else
|
||||
if (this.b[btn].cb) this.b[btn].cb(e);
|
||||
}
|
||||
|
||||
// Handler for touch events
|
||||
function touchHandler(l,e) {
|
||||
if (l.type=="btn" && l.cb && e.x>=l.x && e.y>=l.y && e.x<=l.x+l.w && e.y<=l.y+l.h)
|
||||
l.cb(e);
|
||||
if (l.c) l.c.forEach(n => touchHandler(n,e));
|
||||
}
|
||||
|
||||
|
||||
function updateMin(l) {
|
||||
switch (l.type) {
|
||||
case "txt": {
|
||||
if (l.font.endsWith("%"))
|
||||
l.font = "Vector"+Math.round(g.getHeight()*l.font.slice(0,-1)/100);
|
||||
// Not needed in new firmwares - 'font' is enough
|
||||
if (l.font.includes(":")) {
|
||||
var f = l.font.split(":");
|
||||
l.font = f[0];
|
||||
l.fsz = f[1];
|
||||
}
|
||||
g.setFont(l.font,l.fsz);
|
||||
l._h = g.getFontHeight();
|
||||
l._w = g.stringWidth(l.label);
|
||||
break;
|
||||
}
|
||||
case "btn": {
|
||||
l._h = 24;
|
||||
l._w = 14 + l.label.length*8;
|
||||
break;
|
||||
}
|
||||
case "img": {
|
||||
var im = E.toString(l.src());
|
||||
l._h = im.charCodeAt(0);
|
||||
l._w = im.charCodeAt(1);
|
||||
break;
|
||||
}
|
||||
case undefined:
|
||||
case "custom": {
|
||||
// size should already be set up in width/height
|
||||
l._w = 0;
|
||||
l._h = 0;
|
||||
break;
|
||||
}
|
||||
case "h": {
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>Math.max(a,b._h+(b.pad<<1)),0);
|
||||
l._w = l.c.reduce((a,b)=>a+b._w+(b.pad<<1),0);
|
||||
l.fillx |= l.c.some(c=>c.fillx);
|
||||
break;
|
||||
}
|
||||
case "v": {
|
||||
l.c.forEach(updateMin);
|
||||
l._h = l.c.reduce((a,b)=>a+b._h+(b.pad<<1),0);
|
||||
l._w = l.c.reduce((a,b)=>Math.max(a,b._w+(b.pad<<1)),0);
|
||||
l.filly |= l.c.some(c=>c.filly);
|
||||
break;
|
||||
}
|
||||
default: throw "Unknown item type "+l.type;
|
||||
}
|
||||
if (l.r&1) { // rotation
|
||||
var t = l._w;l._w=l._h;l._h=t;
|
||||
}
|
||||
l._w = Math.max(l._w, 0|l.width);
|
||||
l._h = Math.max(l._h, 0|l.height);
|
||||
}
|
||||
function render(l) {
|
||||
if (!l) l = this.l;
|
||||
g.reset();
|
||||
if (l.col) g.setColor(l.col);
|
||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol).clearRect(l.x,l.y,l.x+l.w,l.y+l.h);
|
||||
switch (l.type) {
|
||||
case "txt":
|
||||
g.setFont(l.font,l.fsz).setFontAlign(0,0,l.r).drawString(l.label, l.x+(l.w>>1), l.y+(l.h>>1), true/*solid bg*/);
|
||||
break;
|
||||
case "btn":
|
||||
var poly = [
|
||||
l.x,l.y+4,
|
||||
l.x+4,l.y,
|
||||
l.x+l.w-5,l.y,
|
||||
l.x+l.w-1,l.y+4,
|
||||
l.x+l.w-1,l.y+l.h-5,
|
||||
l.x+l.w-5,l.y+l.h-1,
|
||||
l.x+4,l.y+l.h-1,
|
||||
l.x,l.y+l.h-5,
|
||||
l.x,l.y+4
|
||||
];
|
||||
g.setColor(g.theme.bgH).fillPoly(poly).setColor(l.selected ? g.theme.fgH : g.theme.fg).drawPoly(poly).setFont("4x6",2).setFontAlign(0,0,l.r).drawString(l.label,l.x+l.w/2,l.y+l.h/2);
|
||||
break;
|
||||
case "img":
|
||||
g.drawImage(l.src(), l.x, l.y);
|
||||
break;
|
||||
case "custom":
|
||||
l.render(l);
|
||||
break;
|
||||
}
|
||||
if (l.c) l.c.forEach(render);
|
||||
}
|
||||
|
||||
Layout.prototype.render = function (l) {
|
||||
if (!l) l = this._l;
|
||||
render(l);
|
||||
};
|
||||
|
||||
Layout.prototype.layout = function (l) {
|
||||
// l = current layout element
|
||||
// exw,exh = extra width/height available
|
||||
var fillx = l.c.reduce((a,l)=>a+(0|l.fillx),0);
|
||||
var filly = l.c.reduce((a,l)=>a+(0|l.filly),0);
|
||||
switch (l.type) {
|
||||
case "h": {
|
||||
let x = l.x + (l.w-l._w)/2;
|
||||
if (fillx) { x = l.x; }
|
||||
l.c.forEach(c => {
|
||||
c.w = c._w + (c.fillx?(l.w-l._w)/fillx:0);
|
||||
c.h = c.filly ? l.h : c._h;
|
||||
c.x = x;
|
||||
c.y = l.y + (1+(0|c.valign))*(l.h-c.h)/2;
|
||||
x += c.w;
|
||||
if (c.pad) {
|
||||
x += c.pad*2;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.c) {
|
||||
this.layout(c);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "v": {
|
||||
let y = l.y + (l.h-l._h)/2;
|
||||
if (filly) { y = l.y; }
|
||||
l.c.forEach(c => {
|
||||
c.w = c.fillx ? l.w : c._w;
|
||||
c.h = c._h + (c.filly?(l.h-l._h)/filly:0);
|
||||
c.x = l.x + (1+(0|c.halign))*(l.w-c.w)/2;
|
||||
c.y = y;
|
||||
y += c.h;
|
||||
if (c.pad) {
|
||||
y += c.pad*2;
|
||||
c.w += c.pad*2;
|
||||
c.h += c.pad*2;
|
||||
}
|
||||
if (c.c) this.layout(c);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
Layout.prototype.debug = function(l,c) {
|
||||
if (!l) l = this._l;
|
||||
c=c||1;
|
||||
g.setColor(c&1,c&2,c&4).drawRect(l.x+c-1, l.y+c-1, l.x+l.w-c, l.y+l.h-c);
|
||||
c++;
|
||||
if (l.c) l.c.forEach(n => this.debug(n,c));
|
||||
};
|
||||
Layout.prototype.update = function() {
|
||||
var l = this._l;
|
||||
var w = g.getWidth();
|
||||
var y = this.yOffset;
|
||||
var h = g.getHeight()-y;
|
||||
// update sizes
|
||||
updateMin(l);
|
||||
// center
|
||||
if (l.fillx || l.filly) {
|
||||
l.w = w;
|
||||
l.h = h;
|
||||
l.x = 0;
|
||||
l.y = y;
|
||||
} else {
|
||||
l.w = l._w;
|
||||
l.h = l._h;
|
||||
l.x = (w-l.w)/2;
|
||||
l.y = y+(h-l.h)/2;
|
||||
}
|
||||
// layout children
|
||||
this.layout(l);
|
||||
};
|
||||
|
||||
Layout.prototype.clear = function(l) {
|
||||
if (!l) l = this._l;
|
||||
g.reset();
|
||||
if (l.bgCol!==undefined) g.setBgColor(l.bgCol);
|
||||
g.clearRect(l.x,l.y,l.x+l.w-1,l.y+l.h-1);
|
||||
};
|
||||
|
||||
exports = Layout;
|
Loading…
Reference in New Issue