Merge branch 'espruino:master' into master

master
Hilmar Strauch 2022-03-22 10:37:23 +01:00 committed by GitHub
commit 442f23a5dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1684 additions and 122 deletions

View File

@ -72,6 +72,18 @@ try and keep filenames short to avoid overflowing the buffer.
},
```
### Screenshots
In the app `metadata.json` file you can add a list of screenshots with a line like: `"screenshots" : [ { url:"screenshot.png" } ],`
To get a screenshot you can:
* Type `g.dump()` in the left-hand side of the Web IDE when connected to a Bangle.js 2 - you can then
right-click and save the image shown in the terminal (this only works on Bangle.js 2 - Bangle.js 1 is
unable to read data back from the LCD controller).
* Run your code in the emulator and use the screenshot button in the bottom right of the window.
## Testing
### Online

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mUywkEIf4A/AHUBiAYWgcwDC0v+IYW///C6sC+c/kAYUj/xj/wDCgvBgfyVihhBAQQASh6TCMikvYoRkU/73CMicD+ZnFViJFBj5MBMiU/+IuBJoJkRCoUvfIPy/5kQVgM//7gBC4KCDFxSsDgTHCl8QWgaRKmBJBFIzmDSJXzYBECWobbJAAKNIMhYlBOoK/IMhZXCmYMLABAkCS4RkSXZoNJRBo/CgK6UBwTWBBIs/SJBAGl7UFegIXMaogHEehAAHj/yIYsfehAAGMQISFMRxbCiEDU4ZiQZY5iQZYpiSbQ8/cwzLOCiQA/AH4A1A"))
require("heatshrink").decompress(atob("mEkgIRO4AFJgPgAocDAoswAocHAokGjAFDhgFFhgFDjEOAoc4gxSE44FDuPjAod//+AAoXfn4FCgPMjJUCmIJBAoU7AoJUCv4CBsACBtwCBuACB4w3CEQIaCKgMBFgQFBgYFCLQMDMIfAg55D4BcDg/gNAcD+B0DSIMcOgiGEjCYEjgFEhhVCUgQ"))

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mUywIebg/4AocP//AAoUf//+BYgMDh/+j/8Dol/wEAgYFBg/wgEBFIV+AQIVCh4fBnwFBgISBj8AhgJCh+Ag4BB4ED8ED+ASCAYJDBnkAvkAIYIWBjw8B/EB8AcBn//gF4DwJdBAQMA/EP738FYM8g/nz+A+EPgHx8YKBgfAjF4sAKBHIItBBQJMBFoJEBHII1BIQIDCvAUCAYYUBHIIDBMIXACgQpBRAIUBMIIrBDAIWCVYaiBTYQJCn4FBQgIIBEYKrDQ4MBVYUf8CQCCoP/w6DBAAKIBAocHAoIwBBgb5DDoYAZA="))
require("heatshrink").decompress(atob("kkkwIEBgf8AYMB//4AgN///ggEf4E/wED+EACQN8C4Pgh4TBh8BCYMAvEcEoWD4AEBnk4gFggPHwAXBj1wgIwB88An/Ah3gg/+gF+gH/+EH8Ef/+ABAPvuAIBgnyCIQjBBAMAJAIIEuAICFgIIBh14BAMB8eAg0Ajk8KAXBKAU4jwDBg+ADoIXBg4NBnxPBEgPAgP8gZaBg//KoKLBKAIEBMQMAA"))

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mUyxH+AH4AG3YAGF1w0oExYykEZwyhEIyRJGUAfEYpgxjLxQNEGEajMGTohPGMBTQOZwwTGKoyXDASVWGSwtHKYYAJZbYVEGR7bSGKQWkDRQbOCAoxYRI4wMCIYxXXpQSYP6L4NCRLGXLZwdVMJwAWGKgwbD6aUTSzoRKfCAxbAogcJBxQx/GP4x/GP4xNAAoKKBxwxaGRQZPSqwZmGOZ7VY8oxnPZoJPGP57TBJavWGL7gRRaiPVGJxRGBJgxcACYxfHJIRLSrTHxGODHvGSgwcAEY="))
require("heatshrink").decompress(atob("kUw4MA///xP5gEH/AMBh//4AHBwF4gEDwEHgEB4fw8EAsf/jEAjPh80AhngjnAgcwAIMB5kA50A+cAmfAtnAhnYmc//8zhln/+c4YjBg0w440Bxk38EB/cP/0B//Dwf/+FxwEf8EGIAJGB2BkCnhiB4EPgF//EDFQIpB+HGgOMnkxwFjh8MsEY4YQHn/x//j//8n/wHYItBCAKFBhgKBKAIQBBgIQC4AQCmAQChkD/v8gcA/wCBBoMA7+39kAPwP/WIMP4aYBCAYhCCAkHAYOAA="))

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Fixed issue with wrong device informations
0.03: Ensure manufacturer:undefined doesn't overflow screen
0.04: Set Bangle.js 2 compatible, show widgets

View File

@ -5,6 +5,7 @@ let menu = {
function showMainMenu() {
menu["< Back"] = () => load();
Bangle.drawWidgets();
return E.showMenu(menu);
}
@ -55,5 +56,6 @@ function waitMessage() {
E.showMessage("scanning");
}
Bangle.loadWidgets();
scan();
waitMessage();

View File

@ -2,11 +2,11 @@
"id": "bledetect",
"name": "BLE Detector",
"shortName": "BLE Detector",
"version": "0.03",
"version": "0.04",
"description": "Detect BLE devices and show some informations.",
"icon": "bledetect.png",
"tags": "app,bluetooth,tool",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"bledetect.app.js","url":"bledetect.js"},

View File

@ -47,3 +47,5 @@
0.41: Add Keyboard and Mouse Bluetooth HID option
0.42: Sort *.boot.js files lexically and by optional numeric priority, e.g. appname.<priority>.boot.js
0.43: Fix Gadgetbridge handling with Programmable:off
0.44: Write .boot0 without ever having it all in RAM (fix Bangle.js 1 issues with BTHRM)
0.45: Fix 0.44 regression (auto-add semi-colon between each boot code chunk)

View File

@ -4,7 +4,7 @@ of the time. */
E.showMessage("Updating boot0...");
var s = require('Storage').readJSON('setting.json',1)||{};
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
var boot = "";
var boot = "", bootPost = "";
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
@ -15,6 +15,7 @@ if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't chang
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
boot += `E.setFlags({pretokenise:1});\n`;
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
if (s.ble!==false) {
if (s.HID) { // Human interface device
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
@ -195,8 +196,8 @@ if (!Bangle.appRect) { // added in 2v11 - polyfill for older firmwares
// Append *.boot.js files
// These could change bleServices/bleServiceOptions if needed
var getPriority = /.*\.(\d+)\.boot\.js$/;
require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
var bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
var getPriority = /.*\.(\d+)\.boot\.js$/;
var aPriority = a.match(getPriority);
var bPriority = b.match(getPriority);
if (aPriority && bPriority){
@ -207,17 +208,39 @@ require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
return 1;
}
return a==b ? 0 : (a>b ? 1 : -1);
}).forEach(bootFile=>{
});
// precalculate file size
var fileSize = boot.length + bootPost.length;
bootFiles.forEach(bootFile=>{
// match the size of data we're adding below in bootFiles.forEach
fileSize += 2+bootFile.length+1+require('Storage').read(bootFile).length+2;
});
// write file in chunks (so as not to use up all RAM)
require('Storage').write('.boot0',boot,0,fileSize);
var fileOffset = boot.length;
bootFiles.forEach(bootFile=>{
// we add a semicolon so if the file is wrapped in (function(){ ... }()
// with no semicolon we don't end up with (function(){ ... }()(function(){ ... }()
// which would cause an error!
boot += "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
// we write:
// "//"+bootFile+"\n"+require('Storage').read(bootFile)+";\n";
// but we need to do this without ever loading everything into RAM as some
// boot files seem to be getting pretty big now.
require('Storage').write('.boot0',"//"+bootFile+"\n",fileOffset);
fileOffset+=2+bootFile.length+1;
var bf = require('Storage').read(bootFile);
require('Storage').write('.boot0',bf,fileOffset);
fileOffset+=bf.length;
require('Storage').write('.boot0',";\n",fileOffset);
fileOffset+=2;
});
// update ble
boot += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`;
// write file
require('Storage').write('.boot0',boot);
require('Storage').write('.boot0',bootPost,fileOffset);
delete boot;
delete bootPost;
delete bootFiles;
delete fileSize;
delete fileOffset;
E.showMessage("Reloading...");
eval(require('Storage').read('.boot0'));
// .bootcde should be run automatically after if required, since

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.43",
"version": "0.45",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
0.03: Fix the clock for dark mode.

View File

@ -76,7 +76,7 @@ function draw_clock(){
// g.drawLine(clock_center.x - radius, clock_center.y, clock_center.x + radius, clock_center.y);
// g.drawLine(clock_center.x, clock_center.y - radius, clock_center.x, clock_center.y + radius);
g.setColor(g.theme.fg);
g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg);
let ticks = [0, 90, 180, 270];
ticks.forEach((item)=>{
let agl = item+180;
@ -92,13 +92,13 @@ function draw_clock(){
let minute_agl = minute_angle(date);
g.drawImage(hour_hand, hour_pos_x(hour_agl), hour_pos_y(hour_agl), {rotate:hour_agl*p180}); //
g.drawImage(minute_hand, minute_pos_x(minute_agl), minute_pos_y(minute_agl), {rotate:minute_agl*p180}); //
g.setColor(g.theme.fg);
g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg);
g.fillCircle(clock_center.x, clock_center.y, 6);
g.setColor(g.theme.bg);
g.setColor(g.theme.dark ? g.theme.fg : g.theme.bg);
g.fillCircle(clock_center.x, clock_center.y, 3);
// draw minute ticks. Takes long time to draw!
g.setColor(g.theme.fg);
g.setColor(g.theme.dark ? g.theme.bg : g.theme.fg);
for (var i=0; i<60; i++){
let agl = i*6+180;
g.drawImage(tick1.asImage(), rotate_around_x(big_wheel_x(i*6), agl, tick1), rotate_around_y(big_wheel_y(i*6), agl, tick1), {rotate:agl*p180});

View File

@ -1,7 +1,7 @@
{
"id": "crowclk",
"name": "Crow Clock",
"version": "0.02",
"version": "0.03",
"description": "A simple clock based on Bold Clock that has MST3K's Crow T. Robot for a face",
"icon": "crow_clock.png",
"screenshots": [{"url":"screenshot_crow.png"}],

View File

@ -8,3 +8,4 @@
0.08: Optimize line wrapping for Bangle 2
0.09: fix the trasparent widget bar if there are no widgets for Bangle 2
0.10: added "one click exit" setting for Bangle 2
0.11: Fix bangle.js 1 white icons not displaying

View File

@ -48,6 +48,7 @@ function draw_icon(p,n,selected) {
var x = (n%3)*80;
var y = n>2?130:40;
(selected?g.setColor(0.3,0.3,0.3):g.setColor(0,0,0)).fillRect(x,y,x+79,y+89);
g.setColor(g.theme.fg);
g.drawImage(s.read(apps[p*6+n].icon),x+10,y+10,{scale:1.25});
g.setColor(-1).setFontAlign(0,-1,0).setFont("6x8",1);
var txt = apps[p*6+n].name.split(" ");

View File

@ -1,7 +1,7 @@
{
"id": "dtlaunch",
"name": "Desktop Launcher",
"version": "0.10",
"version": "0.11",
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
"icon": "icon.png",

5
apps/game1024/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: Initial version
0.02: Temporary intermediate version
0.03: Basic colors
0.04: Bug fix score reset after Game Over, new icon
0.05: Chevron marker on the randomly added square

36
apps/game1024/README.md Normal file
View File

@ -0,0 +1,36 @@
# Play the game of 1024
Move the tiles by swiping to the lefthand, righthand or up- and downward side of the watch.
When two tiles with the same number are squashed together they will add up as exponentials:
**1 + 1 = 2** or **A + A = D** which is a representation of **2^1 + 2^1 = 2^1 = 4**
**2 + 2 = 3** or **B + B = C** which is a representation of **2^2 + 2^2 = 2^3 = 8**
**3 + 3 = 4** or **C + C = D** which is a representation of **2^3 + 2^3 = 2^4 = 16**
After each move a new tile will be added on a random empty square. The value can be 1 or 2, and will be marked with a chevron.
So you can continue till you reach **1024** which equals **2^(10)**. So when you reach tile **10** you have won.
The score is maintained by adding the outcome of the sum of all pairs of squashed tiles (4+16+4+8 etc.)
Use the side **BTN** to exit the game, score and tile positions will be saved.
## Buttons on the screen
- Button **U**: Undo the last move. There are currently a maximum of 4 undo levels. The level is indicated with a small number in the lower righthand corner of the Undo button
- Button **\***: Change the text on the tile to number, capitals or Roman numbers
- Button **R**: Reset the game. The Higscore will be remembered. You will be prompted first.
### Credits
Game 1024 is based on Saming's 2048 and Misho M. Petkovic 1024game.org and conceptually similar to Threes by Asher Vollmer.
In Dark theme with numbers:
![Screenshot from the Banglejs 2 watch with the game in dark theme](./game1024_sc_dump_dark.png)
In Light theme with characters:
![Screenshot from the Banglejs 2 watch with the game in light theme](./game1024_sc_dump_light.png)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBkQAWkAyVgQXx5gAMCQOqAAeiC/4X/AAXdC6HP7gECn///oXH///+QXEn4XC4f/mf/AwQXEmczmQXD74QD7/8AQZHLFIPfC4QzC4ZICC5XPngXD/4CB5oXNIYQXG+YXSCYQXKkQXWU4oXbL5mjC5M/R5evC5PfniwBa5Gvd4gXE5/z7s/DQIXGl6PJ5v//5eCC46/F4YXCAgMzAoYXFkYXFABTvMC/4X0ACkCC/4XJu4AMCQOIAAeCC+0///zC6dz/8z/83C6V/CgN/+4XSn4DCF6ZcGC6Hyv53V+Z3WCgR3OkQAWA="))

691
apps/game1024/app.js Normal file
View File

@ -0,0 +1,691 @@
const debugMode = 'off'; // valid values are: off, test, production, development
const middle = {x:Math.floor(g.getWidth()/2)-20, y: Math.floor(g.getHeight()/2)};
const rows = 4, cols = 4;
const borderWidth = 6;
const sqWidth = (Math.floor(Bangle.appRect.w - 48) / rows) - borderWidth;
const cellColors = [{bg:'#00FFFF', fg: '#000000'},
{bg:'#FF00FF', fg: '#000000'}, {bg:'#808000', fg: '#FFFFFF'}, {bg:'#0000FF', fg: '#FFFFFF'}, {bg:'#008000', fg: '#FFFFFF'},
{bg:'#800000', fg: '#FFFFFF'}, {bg:'#00FF00', fg: '#000000'}, {bg:'#000080', fg: '#FFFFFF'}, {bg:'#FFFF00', fg: '#000000'},
{bg:'#800080', fg: '#FFFFFF'}, {bg:'#FF0000', fg: '#FFFFFF'}];
const cellFonts = ["12x20", "12x20", "Vector:14"];
const cellChars = [
[0,1,2,3,4,5,6,7,8,9,10],
['0','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
['0','I', 'II', 'III', 'IV', 'V', 'VI', 'VII','VIII', 'IX', 'X']
];
// const numInitialCells = 2;
const maxUndoLevels = 4;
const noExceptions = true;
let charIndex = 0; // plain numbers on the grid
const themeBg = g.theme.bg;
const scores = {
currentScore: 0,
highScore: 0,
lastScores: [0],
add: function(val) {
this.currentScore = this.currentScore + Math.pow(2, val);
debug(() => console.log("new score=",this.currentScore));
},
addToUndo: function () {
this.lastScores.push(this.currentScore);
if (this.lastScores.length > maxUndoLevels) this.lastScores.shift();
},
undo: function () {
this.currentScore = this.lastScores.pop();
debug(() => console.log("undo score =", this.currentScore, "rest:", this.lastScores));
},
reset: function () {
this.currentScore = 0;
this.lastScores = [0];
},
draw: function () {
g.setColor(btnAtribs.fg);
let ulCorner = {x: Bangle.appRect.x + 6, y: Bangle.appRect.y2 -22 };
let lrCorner = {x: Bangle.appRect.x2, y: Bangle.appRect.y2 - 1};
g.fillRect(ulCorner.x, ulCorner.y, lrCorner.x, lrCorner.y)
.setFont12x20(1)
.setFontAlign(0,0,0);
let scrX = Math.floor((ulCorner.x + lrCorner.x)/3);
let scrY = Math.floor((ulCorner.y + lrCorner.y)/2) + 1;
g.setColor('#000000')
.drawString(this.currentScore, scrX+1, scrY+1)
.setColor(btnAtribs.bg)
.drawString(this.currentScore, scrX, scrY);
scrX = Math.floor(4*(ulCorner.x + lrCorner.x)/5);
g.setFont("6x8:1x2")
.drawString(this.highScore, btnAtribs.x + Math.floor(btnAtribs.w/2), scrY);
},
hsContents: function () {
return {"highScore": this.highScore, "lastScore": this.currentScore};
},
check: function () {
this.highScore = (this.currentScore > this.highScore) ? this.currentScore : this.highScore;
debug(() => console.log('highScore =', this.highScore));
}
};
// snapshot interval is the number of moves after wich a snapshot is wriiten to file
const snInterval = 1;
const snReadOnInit = true;
// a snapshot contains a json file dump of the last positions of the tiles on the board, including the scores
const snapshot = {
interval: snInterval,
snFileName: 'game1024.json',
counter: 0,
updCounter: function() {
this.counter = ++this.counter > this.interval ? 0 : this.counter;
},
dump: {gridsize: rows * cols, expVals: [], score: 0, highScore: 0, charIndex: charIndex},
write: function() {
require("Storage").writeJSON(this.snFileName, this.dump);
},
read: function () {
let sn = require("Storage").readJSON(this.snFileName, noExceptions);
if ((typeof sn == "undefined") || (sn.gridsize !== rows * cols)) {
require("Storage").writeJSON(this.snFileName, this.dump);
return false;
} else {
if ((typeof sn !== "undefined") && (sn.gridsize == rows * cols)){
this.dump = sn;
return true;
}
}
},
setDump: function () {
this.dump.expVals = [];
allSquares.forEach(sq => {
this.dump.expVals.push(sq.expVal);
});
this.dump.score = scores.currentScore;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
},
make: function () {
this.updCounter();
if (this.counter == this.interval) {
this.setDump();
this.write();
debug(() => console.log("snapped the state of the game:", this.dump));
}
},
recover: function () {
if (this.read()) {
this.dump.expVals.forEach((val, idx) => {
allSquares[idx].setExpVal(val);
});
scores.currentScore = this.dump.score ? this.dump.score : 0;
scores.highScore = this.dump.highScore ? this.dump.highScore : 0 ;
charIndex = this.dump.charIndex ? this.dump.charIndex : 0 ;
}
},
reset: function () {
this.dump.gridsize = rows * cols;
this.dump.expVals = [];
for (let i = 0; i< this.dump.gridsize; i++) {
this.dump.expVals[i] = 0;
}
this.dump.score = 0;
this.dump.highScore = scores.highScore;
this.dump.charIndex = charIndex;
this.write();
debug(() => console.log("reset D U M P E D!", this.dump));
}
};
const btnAtribs = {x: 134, w: 42, h: 42, fg:'#C0C0C0', bg:'#800000'};
const buttons = {
all: [],
draw: function () {
this.all.forEach(btn => {
btn.draw();
});
},
add: function(btn) {
this.all.push(btn);
}
};
/**
* to the right = -1
all tiles move to the left, begin with the outer righthand side tiles
moving 0 to max 3 places to the right
find first tile beginning with bottom row, righthand side
*/
const mover = {
direction: {
up: {name: 'up', step: 1, innerBegin: 0, innerEnd: rows-1, outerBegin: 0, outerEnd: cols-1, iter: rows -1,
sqIndex: function (m,n) {return m*(cols) + n;}, sqNextIndex: function (m,n) {return m < rows -1 ? (m+1)*(cols) + n : -1;}
},
down: {name: 'down', step:-1, innerBegin: rows-1, innerEnd: 0, outerBegin: cols-1, outerEnd: 0, iter: rows -1,
sqIndex: function (m,n) {return m*(cols) + n;}, sqNextIndex: function (m,n) {return m > 0 ? (m-1)*(cols) + n : -1;}
},
left: {name: 'left', step: 1, innerBegin: 0, innerEnd: cols-1, outerBegin: 0, outerEnd: rows-1, iter: cols -1,
sqIndex: function (m,n) {return n*(rows) + m;}, sqNextIndex: function (m,n) {return m < cols -1 ? n*(rows) + m +1 : -1;}
},
right: {name: 'right', step:-1, innerBegin: cols-1, innerEnd: 0, outerBegin: rows-1, outerEnd: 0, iter: cols -1,
sqIndex: function (m,n) {return n*(rows) + m;}, sqNextIndex: function (m,n) {return m > 0 ? n*(rows) + m -1: -1;}
}
},
anyLeft: function() {
let canContinue = false;
[this.direction.up,this.direction.left].forEach (dir => {
const step = dir.step;
// outer loop for all colums/rows
for (let n = dir.outerBegin; step*n <= step*dir.outerEnd; n=n+step) {
// lets move squares one position in a row or column, counting backwards starting from the and where the squares will end up
for (let m = dir.innerBegin; step*m <= step*dir.innerEnd; m=m+step) {
const idx = dir.sqIndex(m,n);
const nextIdx = dir.sqNextIndex(m,n);
if (allSquares[idx].expVal == 0) {
canContinue = true; // there is an empty cell found
break;
}
if (nextIdx >= 0) {
if (allSquares[idx].expVal == allSquares[nextIdx].expVal) {
canContinue = true; // equal adjacent cells > 0 found
break;
}
if (allSquares[nextIdx].expVal == 0) {
canContinue = true; // there is an empty cell found
break;
}
}
if (canContinue) break;
}
if (canContinue) break;
}
});
return canContinue;
},
nonEmptyCells: function (dir) {
debug(() => console.log("Move: ", dir.name));
const step = dir.step;
// outer loop for all colums/rows
for (let n = dir.outerBegin; step*n <= step*dir.outerEnd; n=n+step) {
// let rowStr = '| ';
// Move a number of iteration with the squares to move them all to one side
for (let iter = 0; iter < dir.iter; iter++) {
// lets move squares one position in a row or column, counting backwards starting from the and where the squares will end up
for (let m = dir.innerBegin; step*m <= step*dir.innerEnd; m=m+step) {
// get the array of squares index for current cell
const idx = dir.sqIndex(m,n);
const nextIdx = dir.sqNextIndex(m,n);
if (allSquares[idx].expVal == 0 && nextIdx >= 0) {
allSquares[idx].setExpVal(allSquares[nextIdx].expVal);
allSquares[nextIdx].setExpVal(0);
}
}
}
}
},
// add up the conjacent squares with identical values en set next square to empty in the process
mergeEqlCells: function(dir) {
const step = dir.step;
// outer loop for all colums/rows
for (let n = dir.outerBegin; step*n <= step*dir.outerEnd; n=n+step) {
// lets move squares one position in a row or column, counting backwards starting from the and where the squares will end up
for (let m = dir.innerBegin; step*m <= step*dir.innerEnd; m=m+step) {
const idx = dir.sqIndex(m,n);
const nextIdx = dir.sqNextIndex(m,n);
if ((allSquares[idx].expVal > 0) && nextIdx >= 0) {
if (allSquares[idx].expVal == allSquares[nextIdx].expVal) {
let expVal = allSquares[idx].expVal;
allSquares[idx].setExpVal(++expVal);
allSquares[idx].addToScore();
allSquares[nextIdx].setExpVal(0);
}
}
}
}
}
};
// Minimum number of pixels to interpret it as drag gesture
const dragThreshold = 10;
// Maximum number of pixels to interpret a click from a drag event series
const clickThreshold = 3;
let allSquares = [];
// let buttons = [];
class Button {
constructor(name, x0, y0, width, height, text, bg, fg, cb, enabled) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x0 + width;
this.y1 = y0 + height;
this.name = name;
this.cb = cb;
this.text = text;
this.bg = bg;
this.fg = fg;
this.font = "6x8:3";
this.enabled = enabled;
}
disable() {
this.enabled = false;
}
enable() {
this.enabled = true;
}
draw() {
g.setColor(this.bg)
.fillRect(this.x0, this.y0, this.x1, this.y1)
.setFont(this.font)
.setFontAlign(0,0,0);
let strX = Math.floor((this.x0+this.x1)/2);
let strY = Math.floor((this.y0+this.y1)/2);
g.setColor("#000000")
.drawString(this.text, strX+2, strY+2)
.setColor(this.fg)
.drawString(this.text, strX, strY);
// buttons.push(this);
}
onClick() {if (typeof this.cb === 'function' && this.enabled) {
this.cb(this);
}
}
}
class Cell {
constructor(x0, y0, width, idx, cb) {
this.x0 = x0;
this.y0 = y0;
this.x1 = x0 + width;
this.y1 = y0 + width;
this.expVal = 0;
this.previousExpVals=[];
this.idx = idx;
this.cb = cb;
this.isRndm = false;
this.ax = x0;
this.ay = Math.floor(0.2*width+y0);
this.bx = Math.floor(0.3*width+x0);
this.by = Math.floor(0.5*width+y0);
this.cx = x0;
this.cy = Math.floor(0.8*width+y0);
}
getColor(i) {
return cellColors[i >= cellColors.length ? cellColors.length -1 : i];
}
drawBg() {
debug(()=>console.log("Drawbg!!"));
if (this.isRndm == true) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(this.expVal).bg)
.fillRect(this.x0, this.y0, this.x1, this.y1)
.setColor(themeBg)
.fillPoly([this.cx,this.cy,this.bx,this.by,this.ax,this.ay]);
} else {
g.setColor(this.getColor(this.expVal).bg)
.fillRect(this.x0, this.y0, this.x1, this.y1);
}
}
drawNumber() {
if (this.expVal !== 0) {
g.setFont(cellFonts[charIndex])
.setFontAlign(0,0,0);
let char = cellChars[charIndex][this.expVal];
let strX = Math.floor((this.x0 + this.x1)/2);
let strY = Math.floor((this.y0 + this.y1)/2);
g.setColor(this.getColor(this.expVal).fg)
.drawString(char, strX, strY);
}
}
setExpVal(val) {
this.expVal = val;
}
getIdx() {return this.idx;}
pushToUndo() {
// remember this new step
this.previousExpVals.push(this.expVal);
// keep the undo list not longer than max undo levels
if (this.previousExpVals.length > maxUndoLevels) this.previousExpVals.shift();
}
popFromUndo() {
// take one step back
if (this.previousExpVals.length > 0) {
this.expVal = this.previousExpVals.pop();
}
}
removeUndo() {
this.previousExpVals=[0];
}
addToScore() {if (typeof this.cb === 'function') {
this.cb(this.expVal);
}
}
setRndmFalse() {
this.isRndm = false;
}
setRndmTrue() {
this.isRndm = true;
}
drawRndmIndicator(){
if (this.isRndm == true) {
debug(()=>console.log('Random: (ax)', this.ax));
g.setColor(this.getColor(0).bg)
.fillPoly(this.ax,this.ay,this.bx,this.by,this.cx,this.cy);
}
}
}
function undoGame() {
g.clear();
if (scores.lastScores.length > 0) {
allSquares.forEach(sq => {
sq.popFromUndo();
sq.drawBg();
sq.drawNumber();
});
scores.undo();
scores.draw();
buttons.draw();
updUndoLvlIndex();
snapshot.make();
}
Bangle.loadWidgets();
Bangle.drawWidgets();
}
function addToUndo() {
allSquares.forEach(sq => {
sq.pushToUndo();
});
scores.addToUndo();
}
function addToScore (val) {
scores.add(val);
if (val == 10) messageYouWin();
}
function createGrid () {
let cn =0;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
let x0 = borderWidth + c*(borderWidth + sqWidth) - (rows/2)*(2*borderWidth + sqWidth) + middle.x + Math.floor(sqWidth/3);
let y0 = borderWidth + r*(borderWidth + sqWidth) - (cols/2)*(2*borderWidth + sqWidth) + middle.y + Math.floor(sqWidth/3);
let cell = new Cell(x0, y0, sqWidth, c + r*cols, addToScore);
allSquares.push(cell);
}
}
}
function messageGameOver () {
const c = (g.theme.dark) ? {"fg": "#FFFFFF", "bg": "#808080"} : {"fg": "#FF0000", "bg": "#000000"};
g.setColor(c.bg)
.setFont12x20(2).setFontAlign(0,0,0)
.drawString("G A M E", middle.x+13, middle.y-24)
.drawString("O V E R !", middle.x+13, middle.y+24);
g.setColor(c.fg)
.drawString("G A M E", middle.x+12, middle.y-25)
.drawString("O V E R !", middle.x+12, middle.y+25);
}
function messageYouWin () {
g.setColor("#1a0d00")
.setFont12x20(2)
.setFontAlign(0,0,0)
.drawString("YOU HAVE", middle.x+18, middle.y-24)
.drawString("W O N ! !", middle.x+18, middle.y+24);
g.setColor("#FF0808")
.drawString("YOU HAVE", middle.x+17, middle.y-25)
.drawString("W O N ! !", middle.x+17, middle.y+25);
Bangle.buzz(200, 1);
}
function makeRandomNumber () {
return Math.ceil(2*Math.random());
}
function addRandomNumber() {
let emptySquaresIdxs = [];
allSquares.forEach(sq => {
if (sq.expVal == 0) emptySquaresIdxs.push(sq.getIdx());
});
if (emptySquaresIdxs.length > 0) {
let randomIdx = Math.floor( emptySquaresIdxs.length * Math.random() );
allSquares[emptySquaresIdxs[randomIdx]].setExpVal(makeRandomNumber());
allSquares[emptySquaresIdxs[randomIdx]].setRndmTrue();
}
}
function drawGrid() {
allSquares.forEach(sq => {
sq.drawBg();
// sq.drawRndmIndicator();
sq.drawNumber();
});
}
function initGame() {
g.clear();
// scores.read();
createGrid();
if (snReadOnInit) {
snapshot.recover();
debug(() => console.log("R E C O V E R E D !", snapshot.dump));
let sum = allSquares.reduce(function (tv, sq) {return (sq.expVal + tv) ;}, 0);
if (!sum) {
addRandomNumber();
}
} else {
addRandomNumber();
// addToUndo();
}
addRandomNumber();
drawGrid();
scores.draw();
buttons.draw();
// Clock mode allows short-press on button to exit
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
}
function drawPopUp(message,cb) {
g.setColor('#FFFFFF');
let rDims = Bangle.appRect;
g.fillPoly([rDims.x+10, rDims.y+20,
rDims.x+20, rDims.y+10,
rDims.x2-30, rDims.y+10,
rDims.x2-20, rDims.y+20,
rDims.x2-20, rDims.y2-40,
rDims.x2-30, rDims.y2-30,
rDims.x+20, rDims.y2-30,
rDims.x+10, rDims.y2-40
]);
buttons.all.forEach(btn => {btn.disable();});
const btnYes = new Button('yes', rDims.x+16, rDims.y2-80, 54, btnAtribs.h, 'YES', btnAtribs.fg, btnAtribs.bg, cb, true);
const btnNo = new Button('no', rDims.x2-80, rDims.y2-80, 54, btnAtribs.h, 'NO', btnAtribs.fg, btnAtribs.bg, cb, true);
btnYes.draw();
btnNo.draw();
g.setColor('#000000');
g.setFont12x20(1);
g.setFontAlign(-1,-1,0);
g.drawString(message, rDims.x+20, rDims.y+20);
buttons.add(btnYes);
buttons.add(btnNo);
}
function handlePopUpClicks(btn) {
const name = btn.name;
buttons.all.pop(); // remove the no button
buttons.all.pop(); // remove the yes button
buttons.all.forEach(b => {b.enable();}); // enable the remaining buttons again
debug(() => console.log("Button name =", name));
switch (name) {
case 'yes':
resetGame();
break;
default:
g.clear();
drawGrid();
scores.draw();
buttons.draw();
updUndoLvlIndex();
Bangle.loadWidgets();
Bangle.drawWidgets();
}
}
function resetGame() {
g.clear();
scores.reset();
allSquares.forEach(sq => {sq.setExpVal(0);sq.removeUndo();sq.setRndmFalse();});
addRandomNumber();
addRandomNumber();
drawGrid();
scores.draw();
buttons.draw();
Bangle.loadWidgets();
Bangle.drawWidgets();
}
/**
* Function that can be used in test or development environment, or production.
* Depends on global constant debugMode
* @param {function} func function to call like console.log()
*/
const debug = (func) => {
switch (debugMode) {
case "development":
if (typeof func === 'function') {
func();
}
break;
case "off":
default: break;
}
};
// Handle a "click" event (only needed for menu button)
function handleclick(e) {
buttons.all.forEach(btn => {
if ((e.x >= btn.x0) && (e.x <= btn.x1) && (e.y >= btn.y0) && (e.y <= btn.y1)) {
btn.onClick();
debug(() => console.log(btn.name));
}
});
}
// Handle a drag event (moving the stones around)
function handledrag(e) {
/*debug(Math.abs(e.dx) > Math.abs(e.dy) ?
(e.dx > 0 ? e => console.log('To the right') : e => console.log('To the left') ) :
(e.dy > 0 ? e => console.log('Move down') : e => console.log('Move up') ));
*/
// [move.right, move.left, move.up, move.down]
runGame((Math.abs(e.dx) > Math.abs(e.dy) ?
(e.dx > 0 ? mover.direction.right : mover.direction.left ) :
(e.dy > 0 ? mover.direction.down : mover.direction.up )));
}
// Evaluate "drag" events from the UI and call handlers for drags or clicks
// The UI sends a drag as a series of events indicating partial movements
// of the finger.
// This class combines such parts to a long drag from start to end
// If the drag is short, it is interpreted as click,
// otherwise as drag.
// The approprate method is called with the data of the drag.
class Dragger {
constructor(clickHandler, dragHandler, clickThreshold, dragThreshold) {
this.clickHandler = clickHandler;
this.dragHandler = dragHandler;
this.clickThreshold = (clickThreshold === undefined ? 3 : clickThreshold);
this.dragThreshold = (dragThreshold === undefined ? 10 : dragThreshold);
this.dx = 0;
this.dy = 0;
this.enabled = true;
}
// Enable or disable the Dragger
setEnabled(b) {
this.enabled = b;
}
// Handle a raw drag event from the UI
handleRawDrag(e) {
if (!this.enabled)
return;
this.dx += e.dx; // Always accumulate
this.dy += e.dy;
if (e.b === 0) { // Drag event ended: Evaluate full drag
if (Math.abs(this.dx) < this.clickThreshold && Math.abs(this.dy) < this.clickThreshold)
this.clickHandler({
x: e.x - this.dx,
y: e.y - this.dy
}); // take x and y from the drag start
else if (Math.abs(this.dx) > this.dragThreshold || Math.abs(this.dy) > this.dragThreshold)
this.dragHandler({
x: e.x - this.dx,
y: e.y - this.dy,
dx: this.dx,
dy: this.dy
});
this.dx = 0; // Clear the drag accumulator
this.dy = 0;
}
}
// Attach the drag evaluator to the UI
attach() {
Bangle.on("drag", e => this.handleRawDrag(e));
}
}
// Dragger is needed for interaction during the game
var dragger = new Dragger(handleclick, handledrag, clickThreshold, dragThreshold);
// Disable dragger as board is not yet initialized
dragger.setEnabled(false);
// Nevertheless attach it so that it is ready once the game starts
dragger.attach();
function runGame(dir){
addToUndo();
updUndoLvlIndex();
mover.nonEmptyCells(dir);
mover.mergeEqlCells(dir);
mover.nonEmptyCells(dir);
allSquares.forEach(sq => {sq.setRndmFalse();});
addRandomNumber();
drawGrid();
scores.check();
scores.draw();
// scores.write();
snapshot.make();
dragger.setEnabled(true);
if (!(mover.anyLeft())) {
debug(() => console.log("G A M E O V E R !!"));
snapshot.reset();
messageGameOver();
}
}
function updUndoLvlIndex() {
let x = 170;
let y = 60;
g.setColor(btnAtribs.fg)
.fillRect(x-6,y-6, 176, 67);
if (scores.lastScores.length > 0) {
g.setColor("#000000")
.setFont("4x6:2")
.drawString(scores.lastScores.length, x, y);
}
}
function incrCharIndex() {
charIndex++;
if (charIndex >= cellChars.length) charIndex = 0;
drawGrid();
}
buttons.add(new Button('undo', btnAtribs.x, 25, btnAtribs.w, btnAtribs.h, 'U', btnAtribs.fg, btnAtribs.bg, undoGame, true));
buttons.add(new Button('chars', btnAtribs.x, 71, btnAtribs.w, 31, '*', btnAtribs.fg, btnAtribs.bg, function(){incrCharIndex();}, true));
buttons.add(new Button('restart', btnAtribs.x, 106, btnAtribs.w, btnAtribs.h, 'R', btnAtribs.fg, btnAtribs.bg, function(){drawPopUp('Do you want\nto restart?',handlePopUpClicks);}, true));
initGame();
dragger.setEnabled(true);
E.on('kill',function() {
this.write();
debug(() => console.log("1024 game got killed!"));
});

View File

@ -0,0 +1,6 @@
require("Storage").write("timer.info",{
"id":"game1024",
"name":"1024 Game",
"src":"game1024.app.js",
"icon":"game1024.img"
});

View File

@ -0,0 +1 @@
{"gridsize": 16, "expVals": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "score": 0, "highScore": 0, "charIndex": 1}

BIN
apps/game1024/game1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,17 @@
{ "id": "game1024",
"name": "1024 Game",
"shortName" : "1024 Game",
"version": "0.05",
"icon": "game1024.png",
"screenshots": [ {"url":"screenshot.png" } ],
"readme":"README.md",
"description": "Swipe the squares up, down, to the left or right, join the numbers and get to the 10 (2^1024), J or X tile!",
"type": "app",
"tags": "game,puzzle",
"allow_emulator": true,
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"game1024.app.js","url":"app.js"},
{"name":"game1024.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEw4cBzsE/4AClMywH680rlOW9N9kmSpICnyBBBgQRMkBUDgIRKoBoGGRYAFHBGARpARHT5MJKxQAFLgzELCIlIBQkSCIsEPRKBHCIYbGoIRFiQRJhJgFCISeEBwMQOQykCCIqlBpMEBIgRHOQYRIYQbPDhAbBNwgRJVwOCTIgRFMAJKDgQRGOQprBCIMSGogHBJwwbBkC2FCJNbUgMNwHYBYPJCIhODju0yFNCIUGCJGCoE2NwO24EAmw1FHgWCpMGgQOBBIMwCJGSpMmyAjDCI6eBCIWAhu2I4IRCUIYREk+Ah3brEB2CzFAAIRCl3b23btsNCJckjoRC1h2CyAREtoNC9oDC2isCCIgHBjdt5MtCJj2CowjD2uyCIOSCI83lu123tAQIRI4EB28/++39/0mwRCoARCgbfByU51/3rev+mWCIQwCPok0EYIRB/gRDpJ+EcYQRJkARQdgq/Bl5HE7IRDZAltwAREyXbCIbIFgEfCIXsBwQCDQAYRNLgvfCIXtCI44Dm3JCIUlYoYCGkrjBk9bxMkyy9CChICFA="))
require("heatshrink").decompress(atob("mEw4cBzsE/4AClMywH680rlOW9N9kmSpICnyBBBgQRMkBUDgIRKoBoGGRYAFHBGARpARHT5MJKxQAFLgzELCIlIBQkSCIsEPRKBHCIYbGoIRFiQRJhJgFCISeEBwMQOQykCCIqlBpMEBIgRHOQYRIYQbPDhAbBNwgRJVwOCTIgRFMAJKDgQRGOQprBCIMSGogHBJwwbBkC2FCJNbUgMNwHYBYPJCIhODju0yFNCIUGCJGCoE2NwO24EAmw1FHgWCpMGgQOBBIMwCJGSpMmyAjDCI6eBCIWAhu2I4IRCUIYREk+Ah3brEB2CzFAAIRCl3b23btsNCJckjoRC1h2CyAREtoNC9oDC2isCCIgHBjdt5MtCJj2CowjD2uyCIOSCI83lu123tAQIRI4EB28/++39/0mwRCoARCgbfByU51/3rev+mWCIQwCPok0EYIRB/gRDpJ+EcYQRJkARQdgq/Bl5HE7IRDZAltwAREyXbCIbIFgEfCIXsBwQCDQAYRNLgvfCIXtCI44Dm3JCIUlYoYCGkrjBk9bxMkyy9CChICFA="))

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Make Bangle.js 2 compatible

View File

@ -1,7 +1,86 @@
var storage = require('Storage');
const storage = require('Storage');
const Layout = require("Layout");
const settings = storage.readJSON('setting.json',1) || { HID: false };
const BANGLEJS2 = process.env.HWVERSION == 2;
const sidebarWidth=18;
const buttonWidth = (Bangle.appRect.w-sidebarWidth)/2;
const buttonHeight = (Bangle.appRect.h-16)/2*0.85; // subtract text row and add a safety margin
var sendInProgress = false; // Only send one message at a time, do not flood
var touchBtn2 = 0;
var touchBtn3 = 0;
var touchBtn4 = 0;
var touchBtn5 = 0;
function renderBtnArrows(l) {
const d = g.getWidth() - l.width;
function c(a) {
return {
width: 8,
height: a.length,
bpp: 1,
buffer: (new Uint8Array(a)).buffer
};
}
g.drawImage(c([0,8,12,14,255,14,12,8]),d,g.getHeight()/2);
if (!BANGLEJS2) {
g.drawImage(c([16,56,124,254,16,16,16,16]),d,40);
g.drawImage(c([16,16,16,16,254,124,56,16]),d,194);
}
}
const layoutChilden = [];
if (BANGLEJS2) { // add virtual buttons in display
layoutChilden.push({type:"h", c:[
{type:"btn", width:buttonWidth, height:buttonHeight, label:"BTN2", id:"touchBtn2" },
{type:"btn", width:buttonWidth, height:buttonHeight, label:"BTN3", id:"touchBtn3" },
]});
}
layoutChilden.push({type:"h", c:[
{type:"txt", font:"6x8:2", label:"Joystick" },
]});
if (BANGLEJS2) { // add virtual buttons in display
layoutChilden.push({type:"h", c:[
{type:"btn", width:buttonWidth, height:buttonHeight, label:"BTN4", id:"touchBtn4" },
{type:"btn", width:buttonWidth, height:buttonHeight, label:"BTN5", id:"touchBtn5" },
]});
}
const layout = new Layout(
{type:"h", c:[
{type:"v", width:Bangle.appRect.w-sidebarWidth, c: layoutChilden},
{type:"custom", width:18, height: Bangle.appRect.h, render:renderBtnArrows }
]}
);
function isInBox(box, x, y) {
return x >= box.x && x < box.x+box.w && y >= box.y && y < box.y+box.h;
}
if (BANGLEJS2) {
Bangle.on('drag', function(event) {
if (event.b == 0) { // release
touchBtn2 = touchBtn3 = touchBtn4 = touchBtn5 = 0;
} else if (isInBox(layout.touchBtn2, event.x, event.y)) {
touchBtn2 = 1;
touchBtn3 = touchBtn4 = touchBtn5 = 0;
} else if (isInBox(layout.touchBtn3, event.x, event.y)) {
touchBtn3 = 1;
touchBtn2 = touchBtn4 = touchBtn5 = 0;
} else if (isInBox(layout.touchBtn4, event.x, event.y)) {
touchBtn4 = 1;
touchBtn2 = touchBtn3 = touchBtn5 = 0;
} else if (isInBox(layout.touchBtn5, event.x, event.y)) {
touchBtn5 = 1;
touchBtn2 = touchBtn3 = touchBtn4 = 0;
} else {
// outside any buttons, release all
touchBtn2 = touchBtn3 = touchBtn4 = touchBtn5 = 0;
}
});
}
const sendHid = function (x, y, btn1, btn2, btn3, btn4, btn5, cb) {
try {
@ -20,31 +99,17 @@ const sendHid = function (x, y, btn1, btn2, btn3, btn4, btn5, cb) {
function drawApp() {
g.clear();
g.setFont("6x8",2);
g.setFontAlign(0,0);
g.drawString("Joystick", 120, 120);
const d = g.getWidth() - 18;
function c(a) {
return {
width: 8,
height: a.length,
bpp: 1,
buffer: (new Uint8Array(a)).buffer
};
}
g.drawImage(c([16,56,124,254,16,16,16,16]),d,40);
g.drawImage(c([16,16,16,16,254,124,56,16]),d,194);
g.drawImage(c([0,8,12,14,255,14,12,8]),d,116);
Bangle.loadWidgets();
Bangle.drawWidgets();
layout.render();
}
function update() {
const btn1 = BTN1.read();
const btn2 = BTN2.read();
const btn3 = BTN3.read();
const btn4 = BTN4.read();
const btn5 = BTN5.read();
const btn1 = BTN1 ? BTN1.read() : 0;
const btn2 = !BANGLEJS2 ? BTN2.read() : touchBtn2;
const btn3 = !BANGLEJS2 ? BTN3.read() : touchBtn3;
const btn4 = !BANGLEJS2 ? BTN4.read() : touchBtn4;
const btn5 = !BANGLEJS2 ? BTN5.read() : touchBtn5;
const acc = Bangle.getAccel();
var x = acc.x*-127;
var y = acc.y*-127;

View File

@ -2,11 +2,11 @@
"id": "hidjoystick",
"name": "Bluetooth Joystick",
"shortName": "Joystick",
"version": "0.01",
"description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5.",
"version": "0.02",
"description": "Emulates a 2 axis/5 button Joystick using the accelerometer as stick input and buttons 1-3, touch left as button 4 and touch right as button 5. On Bangle.js 2 buttons 2-5 are emulated with the touchscreen.",
"icon": "app.png",
"tags": "bluetooth",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"hidjoystick.app.js","url":"app.js"},
{"name":"hidjoystick.img","url":"app-icon.js","evaluate":true}

View File

@ -15,4 +15,5 @@
0.15: Using wpedom to count steps.
0.16: Improved stability. Wind can now be shown.
0.17: Settings for mph/kph and other minor improvements.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.18: Fullscreen mode can now be enabled or disabled in the settings.
0.19: Alarms can not go bigger than 100.

View File

@ -626,7 +626,7 @@ Bangle.on('charging',function(charging) {
function increaseAlarm(){
if(isAlarmEnabled()){
if(isAlarmEnabled() && getAlarmMinutes() < 95){
settings.alarm += 5;
} else {
settings.alarm = getCurrentTimeInMinutes() + 5;

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.18",
"version":"0.19",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",

View File

@ -1 +1,2 @@
0.01: First release
0.02: Make sure to reset turns

View File

@ -3,7 +3,7 @@
"name": "Classic Mind Game",
"shortName":"Master Mind",
"icon": "mmind.png",
"version":"0.01",
"version":"0.02",
"description": "This is the classic game for masterminds",
"screenshots": [{"url":"screenshot_mmind.png"}],
"type": "app",

View File

@ -172,6 +172,7 @@ Bangle.on('touch', function(zone,e) {
break;
case 4:
//new game
turn = 0;
play = [-1,-1,-1,-1];
game = [];
endgame=false;
@ -189,10 +190,3 @@ Bangle.on('touch', function(zone,e) {
game = [];
get_secret();
draw();
//Bangle.loadWidgets();
//Bangle.drawWidgets();

View File

@ -219,7 +219,7 @@ function viewTrack(filename, info) {
f.erase();
viewTracks();
} else
viewTrack(n, info);
viewTrack(filename, info);
});
};
menu['< Back'] = () => { viewTracks(); };

View File

@ -8,3 +8,4 @@
0.07: Fix crash if an odd number of active boxes are configured (fix #1473)
0.08: Added support for notifications from exstats. Support all stats from exstats
0.09: Fix broken start/stop if recording not enabled (fix #1561)
0.10: Don't allow the same setting to be chosen for 2 boxes (fix #1578)

View File

@ -1,6 +1,6 @@
{ "id": "run",
"name": "Run",
"version":"0.09",
"version":"0.10",
"description": "Displays distance, time, steps, cadence, pace and more for runners.",
"icon": "app.png",
"tags": "run,running,fitness,outdoors,gps",

View File

@ -42,6 +42,11 @@
value: Math.max(statsIDs.indexOf(settings[boxID]),0),
format: v => statsList[v].name,
onchange: v => {
for (var i=1;i<=6;i++)
if (settings["B"+i]==statsIDs[v]) {
settings["B"+i]="";
boxMenu["Box "+i].value=0;
}
settings[boxID] = statsIDs[v];
saveSettings();
},
@ -60,7 +65,7 @@
'': { 'title': 'Run' },
'< Back': back,
};
if (WIDGETS["recorder"])
if (global.WIDGETS&&WIDGETS["recorder"])
menu[/*LANG*/"Record Run"] = {
value : !!settings.record,
format : v => v?/*LANG*/"Yes":/*LANG*/"No",
@ -87,7 +92,7 @@
notificationsMenu[/*LANG*/"Dist Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.dist.notifications))),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.dist.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]);
@ -97,7 +102,7 @@
notificationsMenu[/*LANG*/"Step Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.step.notifications))),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.step.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]);
@ -107,7 +112,7 @@
notificationsMenu[/*LANG*/"Time Pattern"] = {
value: Math.max(0,vibPatterns.findIndex((p) => JSON.stringify(p) === JSON.stringify(settings.notify.time.notifications))),
min: 0, max: vibPatterns.length,
format: v => vibPatterns[v]||"Off",
format: v => vibPatterns[v]||/*LANG*/"Off",
onchange: v => {
settings.notify.time.notifications = vibTimes[v];
sampleBuzz(vibTimes[v]);

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mUygP/AC5BlH4MAn/gAwN/4EP/AFBsEMhkBwEAjEDgYJBgEGgHA4EYDwOAmEwBIIYyj/wgf+AoMH/kA/4eBJXwYLVxgAjh//AC3w"))
require("heatshrink").decompress(atob("mEqgInkn/gg/8Ao/AjEYgYF/AoZT/Kb4AiA="))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,2 @@
0.01: Initial Release
0.02: Shrink hand images to save memory

View File

@ -0,0 +1,16 @@
# Seiko 5actus
![](screenshot.png)
This is built on the knowledge of what I gained through designing the rolex watch face and improves on it by getting more done right at the start.
This watch is modeled after one I personally own and love, I have spent quite a bit of time designing this in a pixel art editor to try and make it as clean as possible and am quite happy with how it came out.
This watch face works in both the light and dark themes but I personally think it looks a lot cleaner in the dark them.
This watch whilst technically designed in a way that would work with the BangleJs has been only listed to work with the BangleJs2, if someones wants to test it on a first gen and let me know if it works then i'll allow it to be installed on both devices but I assume with how the images have been designed it would look strange on a first gen watch.
Special thanks to:
* rozek (for his updated widget draw code for utilization with background images)
* Gordon Williams (Bangle.js, watchapps for reference code and documentation)
* The community (for helping drive such a wonderful project)

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkEBpMB+fziAjRCQQXBHyoXEgIRLgMwC5EAj8gC5MC+QXJn4XKBgJHJMhkfJAYXEh/xC5cDBofzJocvIxKiCHpBGNExMCIxi2KeRIJFgMiiYBCkQ1Jh67EAAMSCgICBiQjFn8xDYX/AgQANn4qEgf/JIcDkcxiUSiMRY4cv+ZaFj6bDgZGBkMRDIIXD/7CHn5TDFYIADFIcRSxgAvXQwAQgRyDACcje4wAESQ4RDmMQgSGBj8zAAnyTgauH/65Cj7EBkMicAPyBYIABCgcRkYWCmYvCewMhmUiiMyF4gUBDoXzn7/Dj4RBF4IXB+QrDj40DmJgBiEyBYMDL4qcEgUikYqDCgSGKAAUSn8hbh8BgICBl/yFggwEbhMC/4sHAAcTIhIsBGYLcJGAQOHgLAEGBM/Jg0vRgsQZAMQBAQUBif/AwLJDfoQ1DkDOBkIOCgQDBFAKCDaIRPGSwqGBgM/dwUDeQiZEEAowCgXxb5jGGgQ+GaAjaHeIbmISYToHRYTPKYQIODE4cfEA4AEPghtEgQJCI5Mv+AXHJAiIIBggXFj/xDBMCcAYXGga7LcYIXIUpY1GC4SiJVRAXCiAWJA"))

181
apps/seiko-5actus/app.js Normal file
View File

@ -0,0 +1,181 @@
var imgBg = {
width : 176, height : 176, bpp : 2,
transparent : 1,
buffer : require("heatshrink").decompress(atob("qoASv/8//1C6YASrorD6opjuorHBAIriQYwriV9FXFZldFjrSEFY6FjQcorPSYqtZFZaxaVoiDkZRArLv4rV/orT/5rGABtf/4rSq//+79UFaptIK5puHFZQUBFda5IABZuBCw4rKTAPVq4rUNw4rK/4rBroqRQAIrVQSd1N4QrQNZIAOFdbzBQ4IrOCQIIDWKQYFFZhqBHwaeBACBwBFazZQThQrJ/7CHABaSEFaTaWOIYrONJArTToorIdpDdRDQLJOCBDhRIxBoPABdXAwwrRVKQ+GZR7aZDYRHNM4IraOZyTQZbQrKaIwAKr7LOHRNdFaJzOr56RuppZACBgRAEtW1Wl1WqzWm1Nqy2qC5hyTrWq1IrFzWqyoXLv7mFQRlqqtp0tVy2V0uptNVA4JWK/72Fr4yFFY9VLINWyqJBFZtfFYwGGQZIrS////peF/6xM1KDDQQIrMKwJQFA4SEKQYLbGAoIrKv5PGGY4rGEYIAHyrZKQQoIDQhoARKwR6GBJIAXJpKECMIoAXUpaELBYQAICZR4CPYo3Kq4IBr7RITIzZFq7mOGwXXBYIjB//3/9drt/+5AGZ4RKHuoNEFZVdupQBuorBupjCJIyNIrqELr4mBr99vorEK476PBxAYC79//orCr4QBu5XIXAwiIcwxXCKYJlBFYSvIQRI7HTiLjBFYzZHAAzdBDA4AKDY6CMQgYQOABT8CEQjdJFTAAKuo8MADokkAH4A/AH4A/ABn1FldXFlfVTX4A/AH4A/AH4APr//ABVVrorcv4rL6tXFbgqL//1Qbv/1QAE1AED14re/wrK1Yr/Ff4rEcY3VFf4r/Ff4r/EaoAH/4rK14r/FZYALFb1/FZbTWAA9fFZYNBFjoA/AH4A/AH4A/vots+pu/AH4A/AH4A/ADdX6ousr4GFrohgABP/FbN/+o7O/6NZv5HOB4IrZ///LBlXB5wrO/qCNB5pzOOhgNBK7RICDhQNCX5P96td/91u4FBvpJLAoQPDrplEQRNdFYPVu93qvVO5JYJurZDSJV1FYP9FYP16oXBfJRKIbJpQB7vVv/3AwJvCbpTZVVIP9/9d6/9AALdTbJgAVEJDZMACoiCLAjZNAAqSKbpjZNSoo7PQg4zCIx9/FaBQBJ4rZRHqJQBFYzZRLCN/HooYRCQIRQYQ1dDCFVQR4A/ADFXCSK5RDJ40Iq6nPW6LcIq//fh39SrNf/6EN/47OFZp0NFbd/K5wPPI5obNM5F1FaYPNdYLcGfpA3IDQIrXABZ6FDSLcZTxAIBW4zcBFa4ZBFaLsNFZYZGFZBpIACCdBFZ7BFq7dSZJArOfQwAHHQYYBFaA+JABwhBIggrObirIJFZLuBbioXJFZI/JWJorBEJIJJS4KFRrqbKFZLvDupYSeZIrJbiwrBNwIrnTQYrReBIrNCpArKBRQAKIJQrLNhCvNJidXQSZYCPCiCTIQS6KEI4pVAAddFaF1FbAZHfioAVFYyUJWbRXHLrqxFFYhVeLI4rq6orCMAoAhFa4"))
};
/* Set hour hand image */
var imgHour = {
width : 14, height : 114, bpp : 2,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AH4A/AB8P/4DB//wAz8D//8BIIKBn4DB54CBACPzAQP8EoImBD4PAJkQG/A34GIgbUBA"))
};
/* Set minute hand image */
var imgMin = {
width : 4, height : 168, bpp : 2,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AH4AE/4A/AEI"))
};
/* Set second hand image */
var imgSec = {
width : 6, height : 176, bpp : 2,
transparent : 1,
buffer : require("heatshrink").decompress(atob("qoA/ADFf6v9AU1c6vNFlICWvtXAXlVA="))
};
/* Sets the font for the date at an appropriate size of 14 */
Graphics.prototype.setFontWorkSans = function(scale) {
// Actual height 12 (12 - 1)
this.setFontCustom(atob("AAAAAAADwAAAPAAAAAAAAAvAAC/0AH/gAL/QAD9AAAIAAAAACQAAL/9AC/r9APAA8A8ADwDwAfAH//wAH/8AAAAAADwAAAtAAAH//8Af//wAAAAAAAAAABwAUAfgHwDwA/APAP8A8DzwD/9PAD/Q8AAABQAAA0AHwDwA9AHwDw4PAPDw8A///gA/f8AAAAAAAAQAAAvAAAP8AALzwAD8PAAv//wB///AAAPAAAAUAAAAAAC/88AP/y8A8NDwDywPAPD18A8L/AAAGgAAAAAAC/8AA//9ALTx8A8ODwDw4PALz/8ALD/AAAAAAPAAAA8AAADwB/APC/8A9/gAD/AAAPgAAAAAAAAAAAAB8fwAf//wDw8PAPDw8A8PDwB///AC8vwAAAAAAAAAAD/DgAv/PQDwsPAPC08A8PDwB//8AB//QAAAAAAAAAAA8DwADwPAABAE"), 46, atob("BAYJBggICQgJCAkJBA=="), 14+(scale<<8)+(2<<16));
return this;
};
/* Sets the font for the day at an appropriate size of 12 */
Graphics.prototype.setFontWorkSansSmall = function(scale) {
// Actual height 12 (11 - 0)
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAABAP/zwGqjQAAAAP0AAEAAAP4AAGAAAAEoAAs/gL//QL88AAt/gL/+QK88AAYAAAUFAD+PAHPDgv//8fr70Hj7QCx+AAAAAC8AAL/AAPHBgL/PQB68AAPgAC9/APT7wEDjwAC/QAAAAAAuAC7/gL/DwPPywL9/gCwvAAD7wAABgAAAAP0AAEAAAACkAC//wP0C8sAAPYAAGPQA+D//0Af9ABQAADzAAB/AAv8AAB/AADyAABAAAACAAADgAADgAC//AAr5AADgAADQAAABAAAD9AAD0AAAAACwAACwAACwAACwAAAgAAABAAADwAADQAAAkAAv0Af9AL+AAvAAAAKgAD//ALgLgPADwPADwD//QA/9AAAAAAwAADwAAL//gL//gAAAAAgBQD4DwPAPwPA/wLnzwD/TwAUBQAAFADwPQLADwPDDwPvjwD+/AAQYAAAoAAD8AAv8AD08AP//gL//gAA8AAAAAL/PAPrDwPPDwPPDwPH/AAAoAAKgAC/+AHzrgPPDwPPDwH3/gBS+AKAAAPAAAPAbgPL/gP/QAPwAALAAAAAYAD+/ALvjwPLDwPLDwH//gBk+AAYAAD/PALTzwPDzwPDjwD//AAv8AAQBAA8DwA4DgAAAAAEBAAPD9AOD4AAAAABQAADwAAP4AANsAA8OAA4PABwHAAIUAAM8AAM8AAM8AAM8AAM8AAMoABgCAA4LAA8OAAdsAAP8AAHwAADgABgAADwAAPCzgPDzwLvBAD9AAAQAAABoAAv/wC0A8DD/OLPX3OMDjONHDHP/7Dfi5B4HQAP9AAACgAC/gB/8AP48AP48AB/8AAC/gAACgAAAAL//gP//wPDDwPDDwLv3gD+/AAAYAAGQAB/+AH0fQPADwPADwPADwDwPQBgNAAAAAL//gP//wPADwPADwHQDgD//AA/8AAAAAAAAAL//gP//wPDDwPDDwPDDwPADwAAAAAAAAL//gP//gPDAAPDAAPDAAPAAAAGQAB/+AD0fQPADwPCjwPDzwHz/QBy/gAAAAAAAAL//gL//gADAAADAAADAAL//gL//gAAAAAAAAL//gL//gAAAAAAeAAAvgAADwAADwL//gP//AAAAAAAAAL//gL//gAPAAA/0ADx/APAPwIABgAAAAL//gL//wAADwAADwAADwAACgAAAAL//gP6qQC/gAAH/QAAvwAv9AL9AAP//wGqqQAAAAL//gL6qQC+AAAP0AAB/AL//wL//gAAAAAGQAB/+AH0fQPADwPADwPADwD6vQB/9AAGQAAAAAL//gP//gPDwAPDwAH7gAD/AAAAAAAGQAB/+AH0fQPADwPAD9PAD/D6vbB/9PAGQAAAAAL//gP//gPDgAPD0ALr/AD/LwAUAgAUFAD+PALvDwPHDwPDjwLT7gDx/AAAAALAAAPAAAPAAAP//wPqqQPAAAPAAAAAAAP/+AGqvgAADwAADwAADwL//AL/4AAAAAKAAAL+AAAv9AAAvwAB/gAv8AL9AAKAAAKQAAL/QAAf/gAAPwAv/QL+AAL/gAAL/gAAvwC/+AP9AAEAAAEABgPgLwD9+AAfwAA/8AL0fgPADwOAAAL0AAB/AAAH/wA/qQL4AAPAAAFACgPAPwPA/wPHzwPvDwP4DwLQDwAAAAAAAAv///uqqvsAAPdAAAf4AAB/0AAC/wAAC4cAAKsAAPv///Kqqp"), 32, atob("BAMFCAgLCAMEBAcHAwYDBQgFBwcHBwcHBwcEBAcHBwcLCAgICQgHCQkEBwgHCgkJCAkICAcJCAwHBwgEBQQ="), 12+(scale<<8)+(2<<16));
return this;
};
/* Set variables to get screen width, height and center points */
let W = g.getWidth();
let H = g.getHeight();
let cx = W/2;
let cy = H/2;
let Timeout;
Bangle.loadWidgets();
/* Custom version of Bangle.drawWidgets (does not clear the widget areas) Thanks to rozek */
Bangle.drawWidgets = function () {
var w = g.getWidth(), h = g.getHeight();
var pos = {
tl:{x:0, y:0, r:0, c:0}, // if r==1, we're right->left
tr:{x:w-1, y:0, r:1, c:0},
bl:{x:0, y:h-24, r:0, c:0},
br:{x:w-1, y:h-24, r:1, c:0}
};
if (global.WIDGETS) {
for (var wd of WIDGETS) {
var p = pos[wd.area];
if (!p) continue;
wd.x = p.x - p.r*wd.width;
wd.y = p.y;
p.x += wd.width*(1-2*p.r);
p.c++;
}
g.reset(); // also loads the current theme
try {
for (var wd of WIDGETS) {
g.setClipRect(wd.x,wd.y, wd.x+wd.width-1,23);
wd.draw(wd);
}
} catch (e) { print(e); }
g.reset(); // clears the clipping rectangle!
}
};
/* Draws the clock hands and date */
function drawHands() {
let d = new Date();
let hour = d.getHours() % 12;
let min = d.getMinutes();
let sec = d.getSeconds();
let twoPi = 2*Math.PI;
let Pi = Math.PI;
let hourAngle = (hour+(min/60))/12 * twoPi - Pi;
let minAngle = (min/60) * twoPi - Pi;
let secAngle = (sec/60) * twoPi - Pi;
g.setFontWorkSans();
g.setColor(g.theme.bg);
g.setFontAlign(0,0,0);
g.drawString(d.getDate(),162,90);
g.setFontWorkSansSmall();
let weekDay = d.toString().split(" ");
if (weekDay[0] == "Sat"){g.setColor(0,0,1);}
else if (weekDay[0] == "Sun"){g.setColor(1,0,0);}
else {g.setColor(g.theme.bg);}
g.drawString(weekDay[0].toUpperCase(), 137, 90);
handLayers = [
{x:cx,
y:cy,
image:imgHour,
rotate:hourAngle,
center:true
},
{x:cx,
y:cy,
image:imgMin,
rotate:minAngle,
center:true
},
{x:cx,
y:cy,
image:imgSec,
rotate:secAngle,
center:true
}];
g.setColor(g.theme.fg);
g.drawImages(handLayers);
}
function drawBackground() {
g.clear(1);
g.setBgColor(g.theme.bg);
g.setColor(g.theme.fg);
bgLayer = [
{x:cx,
y:cy,
image:imgBg,
center:true
}];
g.drawImages(bgLayer);
g.reset();
}
/* Refresh the display every second */
function displayRefresh() {
g.clear(true);
drawBackground();
drawHands();
Bangle.drawWidgets();
let Pause = 1000 - (Date.now() % 1000);
Timeout = setTimeout(displayRefresh,Pause);
}
Bangle.on('lcdPower', (on) => {
if (on) {
if (Timeout != null) { clearTimeout(Timeout); Timeout = undefined;}
displayRefresh();
}
});
Bangle.setUI("clock");
// load widgets after 'setUI' so they're aware there is a clock active
Bangle.loadWidgets();
displayRefresh();

View File

@ -0,0 +1,17 @@
{ "id": "seiko-5actus",
"name": "Seiko 5actus",
"shortName":"5actus",
"icon": "seiko-5actus.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.02",
"description": "A watch designed after then Seiko 5actus from the 1970's",
"tags": "clock",
"type": "clock",
"supports":["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"seiko-5actus.app.js","url":"app.js"},
{"name":"seiko-5actus.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -29,11 +29,11 @@ function calc_ess(val) {
if (nonmot) {
slsnds+=1;
if (slsnds >= sleepthresh) {
return true; // awake
return true; // sleep
}
} else {
slsnds=0;
return false; // sleep
return false; // awake
}
}
}

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("oFAwhC/ABOIABgfymYAKD+Z/9hGDL5c4wAf/XzjASTxqgQhAfPMB2IPxiACIBo+BDxqACIBg+CLxpANHwQPBABgvCIBT8CJ5owDD5iPOOAQfLBojiDCYQGFGIQfICIQfdBYJNMOI6SHD8jeNOIYzID8hfRD9LfEAoTdFBIifLAAIffBoQRBAJpxMD84JCD+S/GL56fID8ALBb6ZhID8qtJCZ4fgT4YDBABq/PD7RNEL6IRKD8WID5pfCD5kzNhKSFmYfMBwSeOGBoPDABgvCJ5wAON5pADABivPIAIAOd5xABABweOD4J+OD58IQBj8LD/6gUDyAfhXzgfiP/wA2"))
require("heatshrink").decompress(atob("mEw4UA///7k8//GnldDZ9RosUqNABQsFqoACqALFg2qAAWQBaMVEYdUBYseC4e0BYsaBYekBYt6BYetBYouDAAIKEgPqC4erNgkFBQYABNgke2oiDrxIEvXUBYcXHgl7FIkB9oEDBYKYBTwILEi4LCoEBBYUQHQX7EYyRCBYJrF95ICBYNFBQdRBYcWEYwLDit7otUHQMVqIvDL4L5BgL8CI4YLDqILDO4gXGBQUEEZQ7CEYprEI4prFoLGBqkFoILFNZaPFF4ZHCR4hrFa5ILMfYJeDfYse2ovDrxGCAAMF1QAEMgIpD9QKD1Y1EgBfFBQg8BC4Y6EAAMaBYekBYseBYZGESIQuDfIYACgwXDyALRgojDNQhsCBYZqFABI="))

View File

@ -1,4 +1,6 @@
0.01: Initial version
0.02: Add battery level
0.03: Fix battery display when full
0.03: Fix battery display when full (incorporating code by Ronin0000)
0.04: Add support for settings
0.05: Add ability to change background (3bit or 4bit)
0.06: Replace battery text with image

View File

@ -4,13 +4,16 @@ Just a simple watch face for the Banglejs2.
It shows battery level in the upper left corner, date information in the upper right, and time information in the bottom.
![](screenshot.png)
![](screenshot0.png)
![](screenshot1.png)
## Settings
**Analog Clock:**
**Analog Clock:** *Not yet implemented.*
**Human Readable Date:** When the setting is on, the date is shown in a more human-friendly format (e.g. "Oct 2"), otherwise the date is shown in a standard format (e.g. "02/10"). Default is off.
**Background:** When the setting is set as "3bit", a background with more accurate colors is chosen for the watchface. Otherwise, it uses a background following the 16-bit Mac Color Palette.
**Date Format:** When the setting is set as "Long", the date is shown in a more human-friendly format (e.g. "Oct 2"), otherwise the date is shown in a standard format (e.g. "02/10"). Default is off.
**Show Week Info:** When the setting is on, the weekday and week number are shown in the upper right box. When the setting is off, the full year is shown instead. Default is off.
@ -20,4 +23,4 @@ It shows battery level in the upper left corner, date information in the upper r
Monogram Watch Face can be selected as the default clock or it can be run manually from the launcher. Its settings can be accessed and changed via the relevant menu.
Tapping on the "Alerts" area will replace the current time display with the time of the most immediate alert.
*Tapping on the "Alerts" area will replace the current time display with the time of the most immediate alert.* - *Feature not implemented yet.*

View File

@ -1,23 +1,23 @@
const SETTINGSFILE = "smclock.json";
const background = {
width: 176,
height: 176,
bpp: 3,
transparent: 1,
buffer: require("heatshrink").decompress(
atob(
"/4A/AH4ACUb8H9MkyVJAThB/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INP/AH4A/AAX8Yz4Afn5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/INI="
)
),
const image3bit = {
width : 176, height : 176, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("/4A/AH4AC23btoCct/pkmSpICcIP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5Bp/4A/AH4AC/kAAH0/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5B/IP5BpA="))
};
const image4bit = {
width : 176, height : 176, bpp : 4,
transparent : 1,
buffer : require("heatshrink").decompress(atob("/4A/AH4Au1QAp1/2swApK/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K/5X/K+//AH4A/AF8AAH4AUK/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/AA=="))
};
const monthName = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
const weekday = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// dynamic variables
var batLevel = -1;
var batColor = [0, 0, 0];
var batColor = "";
// settings variables
var backgroundImage;
var dateFormat;
var drawInterval;
var pollInterval;
@ -31,6 +31,7 @@ function loadSettings() {
function def(value, def) {return value !== undefined ? value : def;}
var settings = require("Storage").readJSON(SETTINGSFILE, true) || {};
backgroundImage = def(settings.backgroundImage, "3bit");
dateFormat = def(settings.dateFormat, "Short");
drawInterval = def(settings.drawInterval, 10);
pollInterval = def(settings.pollInterval, 60);
@ -67,23 +68,29 @@ function getBatteryColor(level) {
level = batLevel;
}
if (level > 80) {
color = [0, 0, 1];
color = "#00f";
} else if (level > 60) {
color = [0, 1, 1];
color = "#0ff";
} else if (level > 40) {
color = [0, 1, 0];
color = "#0f0";
} else if (level > 20) {
color = [1, 1, 0];
color = "#f40";
} else {
color = [1, 0, 0];
color = "f00";
}
return color;
}
function draw() {
var background;
if (backgroundImage == "3bit") {
background = image3bit;
} else {
background = image4bit;
}
g.drawImage(background);
const color = getBatteryColor(batLevel);
batColor = getBatteryColor(batLevel);
var bat = "";
const d = new Date();
const day = d.getDate();
@ -95,32 +102,38 @@ function draw() {
const m = d.getMinutes();
const time = d02(h) + ":" + d02(m);
if (E.getBattery() < 100) {
bat = d02(E.getBattery()) + "%";
} else {
bat = E.getBattery() + "%";
}
g.reset();
// draw battery info
g.setColor(1, 1, 1);
var x = 12;
var y = 16;
if (Bangle.isCharging()) {
g.setColor("#ff0").drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
} else {
g.clearRect(x,y,x+14,y+24);
g.setColor("#000").fillRect(x+2,y+2,x+12,y+22).clearRect(x+4,y+4,x+10,y+20).fillRect(x+5,y+1,x+9,y+2);
g.setColor(batColor).fillRect(x+4,y+20-(batLevel*16/100),x+10,y+20);
}
if (Bangle.isCharging()) {
g.setColor("#ff0");
} else {
g.setColor(batColor);
}
if (useVectorFont == true) {
g.setFont("Vector", 16);
g.drawString("Bat:", 12, 22, false);
} else {
g.setFont("4x6", 2);
g.drawString("Bat:", 10, 22, false);
g.setFont("4x6", 3);
}
g.setColor(color[0], color[1], color[2]);
if (batLevel < 100) {
g.drawString(bat, 52, 22, false);
bat = d02(batLevel) + "%";
g.drawString(bat, 50, 22, false);
} else {
g.drawString(bat, 46, 22, false);
bat = "100%";
g.drawString(bat, 40, 22, false);
}
// draw date info
g.setColor(0, 0, 0);
g.setColor("#000");
if (useVectorFont == true) {
g.setFont("Vector", 20);
} else {
@ -136,7 +149,7 @@ function draw() {
// draw week info
if (showWeekInfo == true) {
date2 = weekday[d.getDay()] + " " + d02(week)
date2 = weekday[d.getDay()] + " " + d02(week);
if (useVectorFont == true) {
g.setFont("Vector", 18);
} else {
@ -155,7 +168,7 @@ function draw() {
}
// draw time
g.setColor(1, 1, 1);
g.setColor("#fff");
if (useVectorFont == true) {
g.setFont("Vector", 60);
g.drawString(time, 10, 108, false);

View File

@ -3,13 +3,13 @@
"name": "Monogram Watch Face",
"shortName": "MonoClock",
"icon": "app.png",
"screenshots": [{ "url": "screenshot.png" }],
"screenshots": [{ "url": "screenshot0.png" }, {"url": "screenshot1.png" }],
"version": "0.04",
"description": "A simple watchface based on my stylised monogram.",
"type": "clock",
"tags": "clock",
"readme": "README.md",
"supports": ["BANGLEJS", "BANGLEJS2"],
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{ "name": "smclock.app.js", "url": "app.js" },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -52,6 +52,7 @@
writeSettings();
},
},
"Background": stringInSettings("backgroundImage", ["3bit", "4bit"]),
Date: stringInSettings("dateFormat", ["Long", "Short"]),
"Draw Interval": {
value: settings.drawInterval,

6
apps/sunclock/README.md Normal file
View File

@ -0,0 +1,6 @@
# Sun Clock
Clock showing date/time, sunset/sunrise, H = current sun height/noon sun height, Az = sun azimuth
![](screenshot_sunclock.png)
Location set with mylocation app, time zone set with settings app.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("kEgwhC/AC8N6APo7oPJBQndBQYPEhoaFAogZIEokO93u8AuGAAYOCCAgOLCBQOFAAIeNEBAPPBw4wHB5wuIGAwPthGIxwIC8UowUuB4eIwAPBxEk91CAgIGGwAhBBYeCAwMoA4ZwEBIIOCAxAA/ABwA="))

79
apps/sunclock/app.js Normal file
View File

@ -0,0 +1,79 @@
/* sclock.app.js for Bangle2
Peter Bernschneider 30.12.2021
Update current latitude and longitude in My Location app
Update current Timezone in Settings app, menu item "System"
Update for summer time by incrementing Timezone += 1 */
setting = require("Storage").readJSON("setting.json",1);
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
SunCalc = require("suncalc.js");
loc = require('locale');
const LOCATION_FILE = "mylocation.json";
const xyCenter = g.getWidth() / 2 + 3;
const yposTime = 60;
const yposDate = 100;
const yposRS = 135;
const yposPos = 160;
var rise = "07:00";
var set = "20:00";
var pos = {altitude: 20, azimuth: 135};
var noonpos = {altitude: 37, azimuth: 180};
let idTimeout = null;
function updatePos() {
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"};
pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
rise = times.sunrise.toString().split(" ")[4].substr(0,5);
set = times.sunset.toString().split(" ")[4].substr(0,5);
noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
}
function drawSimpleClock() {
var d = new Date(); // get date
var da = d.toString().split(" ");
g.clear();
Bangle.drawWidgets();
g.reset(); // default draw styles
g.setFontAlign(0, 0); // drawSting centered
var time = da[4].substr(0, 5); // draw time
g.setFont("Vector",60);
g.drawString(time, xyCenter, yposTime, true);
var date = [loc.dow(new Date(),1), loc.date(d,1)].join(" "); // draw day of week, date
g.setFont("Vector",24);
g.drawString(date, xyCenter, yposDate, true);
g.setFont("Vector",25);
g.drawString(`${rise} ${set}`, xyCenter, yposRS, true); // draw riseset
g.drawImage(require("Storage").read("sunrise.img"), xyCenter-16, yposRS-16);
g.setFont("Vector",21);
g.drawString(`H${pos.altitude}/${noonpos.altitude} Az${pos.azimuth}`, xyCenter, yposPos, true); // draw sun pos
let t = d.getSeconds()*1000 + d.getMilliseconds();
idTimeout = setTimeout(drawSimpleClock, 60000 - t); // time till next minute
}
// special function to handle display switch on
Bangle.on('lcdPower', function(on){
if (on) {
drawSimpleClock();
} else {
if(idTimeout) {
clearTimeout(idTimeout);
}
}
});
g.clear(); // clean app screen
Bangle.loadWidgets();
Bangle.drawWidgets();
setInterval(updatePos, 60*5E3); // refesh every 5 mins
updatePos();
drawSimpleClock(); // draw now
setWatch(Bangle.showLauncher, BTN1, { repeat: false, edge: "falling" }); // Show launcher when button pressed

BIN
apps/sunclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 B

View File

@ -0,0 +1,16 @@
{
"id": "sunclock",
"name": "Sun Clock",
"version": "0.01",
"description": "A clock with sunset/sunrise, sun height/azimuth",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"sunclock.app.js","url":"app.js"},
{"name":"sunclock.img","url":"app-icon.js","evaluate":true},
{"name":"suncalc.js","url":"suncalc.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

298
apps/sunclock/suncalc.js Normal file
View File

@ -0,0 +1,298 @@
/* Module suncalc.js
(c) 2011-2015, Vladimir Agafonkin
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
https://github.com/mourner/suncalc
PB: Usage:
E.setTimeZone(2); // 1 = MEZ, 2 = MESZ
SunCalc = require("suncalc.js");
pos = SunCalc.getPosition(Date.now(), 53.3, 10.1);
times = SunCalc.getTimes(Date.now(), 53.3, 10.1);
rise = times.sunrise; // Date object
rise_str = rise.getHours() + ':' + rise.getMinutes(); //hh:mm
*/
var exports={};
// shortcuts for easier to read formulas
var PI = Math.PI,
sin = Math.sin,
cos = Math.cos,
tan = Math.tan,
asin = Math.asin,
atan = Math.atan2,
acos = Math.acos,
rad = PI / 180;
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
// date/time constants and conversions
var dayMs = 1000 * 60 * 60 * 24,
J1970 = 2440588,
J2000 = 2451545;
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); } // PB: onece removed + 0.5; included it again 4 Jan 2021
function toDays(date) { return toJulian(date) - J2000; }
// general calculations for position
var e = rad * 23.4397; // obliquity of the Earth
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
function astroRefraction(h) {
if (h < 0) // the following formula works for positive altitudes only.
h = 0; // if h = -0.08901179 a div/0 would occur.
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
}
// general sun calculations
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
function eclipticLongitude(M) {
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + PI;
}
function sunCoords(d) {
var M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
return {
dec: declination(L, 0),
ra: rightAscension(L, 0)
};
}
// calculates sun position for a given date and latitude/longitude
exports.getPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = sunCoords(d),
H = siderealTime(d, lw) - c.ra;
return {
azimuth: Math.round((azimuth(H, phi, c.dec) / rad + 180) % 360), // PB: converted to deg
altitude: Math.round( altitude(H, phi, c.dec) / rad) // PB: converted to deg
};
};
// sun times configuration (angle, morning name, evening name)
var times = [
[-0.833, 'sunrise', 'sunset' ]
];
// calculations for sun times
var J0 = 0.0009;
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
// returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L) {
var w = hourAngle(h, phi, dec),
a = approxTransit(w, lw, n);
return solarTransitJ(a, M, L);
}
// calculates sun times for a given date, latitude/longitude, and, optionally,
// the observer height (in meters) relative to the horizon
exports.getTimes = function (date, lat, lng, height) {
height = height || 0;
var lw = rad * -lng,
phi = rad * lat,
dh = observerAngle(height),
d = toDays(date),
n = julianCycle(d, lw),
ds = approxTransit(0, lw, n),
M = solarMeanAnomaly(ds),
L = eclipticLongitude(M),
dec = declination(L, 0),
Jnoon = solarTransitJ(ds, M, L),
i, len, time, h0, Jset, Jrise;
var result = {
solarNoon: fromJulian(Jnoon),
nadir: fromJulian(Jnoon - 0.5)
};
for (i = 0, len = times.length; i < len; i += 1) {
time = times[i];
h0 = (time[0] + dh) * rad;
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
result[time[1]] = fromJulian(Jrise);
result[time[2]] = fromJulian(Jset);
}
return result;
};
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
M = rad * (134.963 + 13.064993 * d), // mean anomaly
F = rad * (93.272 + 13.229350 * d), // mean distance
l = L + rad * 6.289 * sin(M), // longitude
b = rad * 5.128 * sin(F), // latitude
dt = 385001 - 20905 * cos(M); // distance to the moon in km
return {
ra: rightAscension(l, b),
dec: declination(l, b),
dist: dt
};
}
getMoonPosition = function (date, lat, lng) {
var lw = rad * -lng,
phi = rad * lat,
d = toDays(date),
c = moonCoords(d),
H = siderealTime(d, lw) - c.ra,
h = altitude(H, phi, c.dec),
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
h = h + astroRefraction(h); // altitude correction for refraction
return {
azimuth: azimuth(H, phi, c.dec),
altitude: h,
distance: c.dist,
parallacticAngle: pa
};
};
// calculations for illumination parameters of the moon,
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
getMoonIllumination = function (date) {
var d = toDays(date || new Date()),
s = sunCoords(d),
m = moonCoords(d),
sdist = 149598000, // distance from Earth to Sun in km
phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
return {
fraction: (1 + cos(inc)) / 2,
phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
angle: angle
};
};
function hoursLater(date, h) {
return new Date(date.valueOf() + h * dayMs / 24);
}
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
getMoonTimes = function (date, lat, lng, inUTC) {
var t = new Date(date);
if (inUTC) t.setUTCHours(0, 0, 0, 0);
else t.setHours(0, 0, 0, 0);
var hc = 0.133 * rad,
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
for (var i = 1; i <= 24; i += 2) {
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
a = (h0 + h2) / 2 - h1;
b = (h2 - h0) / 2;
xe = -b / (2 * a);
ye = (a * xe + b) * xe + h1;
d = b * b - 4 * a * h1;
roots = 0;
if (d >= 0) {
dx = Math.sqrt(d) / (Math.abs(a) * 2);
x1 = xe - dx;
x2 = xe + dx;
if (Math.abs(x1) <= 1) roots++;
if (Math.abs(x2) <= 1) roots++;
if (x1 < -1) x1 = x2;
}
if (roots === 1) {
if (h0 < 0) rise = i + x1;
else set = i + x1;
} else if (roots === 2) {
rise = i + (ye < 0 ? x2 : x1);
set = i + (ye < 0 ? x1 : x2);
}
if (rise && set) break;
h0 = h2;
}
var result = {};
if (rise) result.rise = hoursLater(t, rise);
if (set) result.set = hoursLater(t, set);
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
return result;
};

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Rename "Activity" in "Motion" and display the true values for it
0.02: Rename "Activity" in "Motion" and display the true values for it
0.03: Add Banglejs 1 compatibility

View File

@ -1,16 +1,28 @@
var locale = require("locale");
var fontColor = g.theme.dark ? "#0f0" : "#000";
var paddingY = 2;
var font6x8At4Size = 32;
var font6x8At2Size = 18;
var heartRate = 0;
// handling the differents versions of the Banglejs smartwatch
if (process.env.HWVERSION == 1){
var paddingY = 3;
var font6x8At4Size = 48;
var font6x8At2Size = 27;
var font6x8FirstTextSize = 6;
var font6x8DefaultTextSize = 3;
}
else{
var paddingY = 2;
var font6x8At4Size = 32;
var font6x8At2Size = 18;
var font6x8FirstTextSize = 4;
var font6x8DefaultTextSize = 2;
}
function setFontSize(pos){
if(pos == 1)
g.setFont("6x8", 4);
g.setFont("6x8", font6x8FirstTextSize);
else
g.setFont("6x8", 2);
g.setFont("6x8", font6x8DefaultTextSize);
}
function clearField(pos){

View File

@ -3,11 +3,12 @@
"name": "Terminal Clock",
"shortName":"Terminal Clock",
"description": "A terminal cli like clock displaying multiple sensor data",
"version":"0.02",
"version":"0.03",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name": "terminalclock.app.js","url": "app.js"},

1
apps/widclose/ChangeLog Normal file
View File

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

7
apps/widclose/README.md Normal file
View File

@ -0,0 +1,7 @@
# Close Button
Adds a ![X](preview.png) button to close the current app and go back to the clock.
(Widget is not visible on the clock screen)
![Light theme screenshot](screenshot_light.png)
![Dark theme screenshot](screenshot_dark.png)

BIN
apps/widclose/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,15 @@
{
"id": "widclose",
"name": "Close Button",
"version": "0.01",
"description": "A button to close the current app",
"readme": "README.md",
"icon": "icon.png",
"type": "widget",
"tags": "widget,tools",
"supports": ["BANGLEJS2"],
"screenshots": [{"url":"screenshot_light.png"},{"url":"screenshot_dark.png"}],
"storage": [
{"name":"widclose.wid.js","url":"widget.js"}
]
}

BIN
apps/widclose/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

14
apps/widclose/widget.js Normal file
View File

@ -0,0 +1,14 @@
if (!Bangle.CLOCK) WIDGETS.close = {
area: "tr", width: 24, sortorder: 10, // we want the right-most spot please
draw: function() {
Bangle.removeListener("touch", this.touch);
Bangle.on("touch", this.touch);
g.reset().setColor("#f00").drawImage(atob( // hardcoded red to match setUI back button
// b/w version of preview.png, 24x24
"GBgBABgAAf+AB//gD//wH//4P//8P//8fn5+fjx+fxj+f4H+/8P//8P/f4H+fxj+fjx+fn5+P//8P//8H//4D//wB//gAf+AABgA"
), this.x, this.y);
}, touch: function(_, c) {
const w = WIDGETS.close;
if (w && c.x>=w.x && c.x<=w.x+24 && c.y>=w.y && c.y<=w.y+24) load();
}
};

View File

@ -22,10 +22,14 @@ function bangleDownload() {
var promise = Promise.resolve();
// Normal files
normalFiles.forEach((filename,n) => {
if (filename==".firmware") {
console.log("Ignoring .firmware file");
return;
}
promise = promise.then(() => {
Progress.hide({sticky: true});
var percent = n/fileCount;
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0});
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
return Comms.readFile(filename).then(data => zip.file(filename,data));
});
});
@ -36,7 +40,7 @@ function bangleDownload() {
promise = promise.then(() => {
Progress.hide({sticky: true});
var percent = (normalFiles.length+n)/fileCount;
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent,percent:0});
Progress.show({title:`Download ${filename}`,sticky:true,min:percent,max:percent+(1/fileCount),percent:0});
return Comms.readStorageFile(filename).then(data => zipStorageFiles.file(filename,data));
});
});

2
core

@ -1 +1 @@
Subproject commit 6e94cf77a2b355389dcd5efa55e2249a9b31983c
Subproject commit 27c7db6035832837ca3909ea52939f60803df72f

View File

@ -59,6 +59,7 @@ options is an object containing:
* `label` - the text on the button
* `cb` - a callback function
* `cbl` - a callback function for long presses
* `back` - a callback function, passed as `back` into Bangle.setUI
If automatic lazy rendering is enabled, calls to `layout.render()` will attempt to automatically
determine what objects have changed or moved, clear their previous locations, and re-render just those objects.
@ -89,7 +90,7 @@ function Layout(layout, options) {
options = options || {};
this.lazy = options.lazy || false;
var btnList;
var btnList, uiSet;
Bangle.setUI(); // remove all existing input handlers
if (process.env.HWVERSION!=2) {
// no touchscreen, find any buttons in 'layout'
@ -104,7 +105,7 @@ function Layout(layout, options) {
this.physBtns = 0;
this.buttons = btnList;
this.selectedButton = -1;
Bangle.setUI("updown", dir=>{
Bangle.setUI({mode:"updown", back:options.back}, dir=>{
var s = this.selectedButton, l=this.buttons.length;
if (dir===undefined && this.buttons[s])
return this.buttons[s].cb();
@ -119,8 +120,10 @@ function Layout(layout, options) {
}
this.selectedButton = s;
});
uiSet = true;
}
}
if (options.back && !uiSet) Bangle.setUI({mode: "custom", back: options.back});
if (options.btns) {
var buttons = options.btns;