Merge branch 'master' into master

pull/1011/head
Gordon Williams 2021-12-06 08:52:28 +00:00 committed by GitHub
commit 3690d56e1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1677 additions and 56 deletions

114
apps.json
View File

@ -29,6 +29,31 @@
],
"sortorder": -10
},
{
"id": "hebrew_calendar",
"name": "Hebrew Calendar",
"shortName": "HebCal",
"version": "0.03",
"description": "lists the date according to the hebrew calendar",
"icon": "app.png",
"tags": "",
"supports": [
"BANGLEJS",
"BANGLEJS2"
],
"readme": "README.md",
"storage": [
{
"name": "hebrew_calendar.app.js",
"url": "app.js"
},
{
"name": "hebrew_calendar.img",
"url": "app-icon.js",
"evaluate": true
}
]
},
{
"id": "messages",
"name": "Messages",
@ -104,7 +129,7 @@
"id": "launch",
"name": "Launcher",
"shortName": "Launcher",
"version": "0.09",
"version": "0.10",
"description": "This is needed to display a menu allowing you to choose your own applications. You can replace this with a customised launcher.",
"icon": "app.png",
"type": "launch",
@ -112,8 +137,10 @@
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"launch.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]}
{"name":"launch.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
{"name":"launch.settings.js","url":"settings.js","supports":["BANGLEJS2"]}
],
"data": [{"name":"launch.json"}],
"sortorder": -10
},
{
@ -463,22 +490,22 @@
]
},
{
"id": "mandlebrotclock",
"name": "Mandlebrot Clock",
"id": "mandelbrotclock",
"name": "Mandelbrot Clock",
"version": "0.01",
"description": "A mandlebrot set themed clock cool",
"icon": "mandlebrotclock.png",
"screenshots": [{ "url": "screenshot_mandlebrotclock.png" }],
"description": "A mandelbrot set themed clock cool",
"icon": "mandelbrotclock.png",
"screenshots": [{ "url": "screenshot_mandelbrotclock.png" }],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{ "name": "mandlebrotclock.app.js", "url": "mandlebrotclock.js" },
{ "name": "mandelbrotclock.app.js", "url": "mandelbrotclock.js" },
{
"name": "mandlebrotclock.img",
"url": "mandlebrotclock-icon.js",
"name": "mandelbrotclock.img",
"url": "mandelbrotclock-icon.js",
"evaluate": true
}
]
@ -2100,6 +2127,19 @@
{"name":"snake.img","url":"snake-icon.js","evaluate":true}
]
},
{ "id": "snek",
"name": "The snek game",
"shortName":"Snek",
"version": "0.01",
"description": "A snek game where you control a snek to eat all the apples!",
"icon": "snek-icon.js",
"supports": ["BANGLEJS2"],
"tags": "game,fun",
"storage": [
{"name":"snek.app.js","url":"snek.js"},
{"name":"snek.img","url":"snek-icon.js","evaluate":true}
]
},
{
"id": "calculator",
"name": "Calculator",
@ -4449,7 +4489,7 @@
"shortName": "AuthWatch",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"version": "0.03",
"version": "0.04",
"description": "Google Authenticator compatible tool.",
"tags": "tool",
"interface": "interface.html",
@ -4531,7 +4571,7 @@
"shortName":"93 Dub",
"icon": "93dub.png",
"screenshots": [{"url":"screenshot.png"}],
"version":"0.03",
"version":"0.04",
"description": "Fan recreation of orviwan's 91 Dub app for the Pebble smartwatch. Uses assets from his 91-Dub-v2.0 repo",
"tags": "clock",
"type": "clock",
@ -4591,9 +4631,9 @@
},
{
"id":"a_speech_timer",
"name":"A Speech Timer",
"name":"Speech Timer",
"icon": "app.png",
"version":"1.00",
"version":"1.01",
"description": "A timer designed to help keeping your speeches and presentations to time.",
"tags": "tool,timer",
"readme":"README.md",
@ -4691,6 +4731,52 @@
"storage": [
{"name":"weatherClock.app.js","url":"app.js"},
{"name":"weatherClock.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "menuwheel",
"name": "Wheel Menus",
"version": "0.01",
"description": "Replace Bangle.js 2's menus with a version that contains variable-size text and a back button",
"readme": "README.md",
"icon": "icon.png",
"screenshots": [
{"url":"screenshot_b1_dark.png"},{"url":"screenshot_b1_edit.png"},{"url":"screenshot_b1_light.png"},
{"url":"screenshot_b2_dark.png"},{"url":"screenshot_b2_edit.png"},{"url":"screenshot_b2_light.png"}
],
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"menuwheel.boot.js","url":"boot.js"}
]
},
{ "id": "widChargingStatus",
"name": "Charging Status",
"shortName":"ChargingStatus",
"icon": "widget.png",
"version":"0.1",
"type": "widget",
"description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.",
"tags": "widget",
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"widChargingStatus.wid.js","url":"widget.js"}
]
},
{
"id": "flow",
"name": "FLOW",
"shortName": "FLOW",
"version": "0.01",
"description": "A game where you have to help a flow avoid white obstacles thing by tapping! This is a demake of an app which I forgot the name of. Press BTN(1) to restart. See if you can get to 2500 score!",
"icon": "app.png",
"tags": "game",
"supports" : ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name": "flow.app.js", "url": "app.js" },
{"name": "flow.img", "url": "app-icon.js","evaluate": true }
]
}
]

View File

@ -1,3 +1,4 @@
0.01: Initial version for upload
0.02: DiscoMinotaur's adjustments (removed battery and adjusted spacing)
0.03: Code style cleanup
0.04: Set 00:00 to 12:00 for 12 hour time

View File

@ -9,3 +9,4 @@ Leer10
Orviwan (original watchface and assets)
Gordon Williams (Bangle.js, watchapps for reference code and documentation)
DiscoMinotaur (adjustments)
Ray Holder (minor 12 hour time rendering adjustment)

View File

@ -78,6 +78,9 @@ function draw(){
} else {
h = " " + h;
}
} else if (h === 0) {
// display 12:00 instead of 00:00 for 12 hr mode
h = "12";
}
//draw separator

View File

@ -1 +1,2 @@
1.00: Release (2021/12/01)
1.01: Grey font when timer is frozen (2021/12/04)

View File

@ -63,7 +63,7 @@ function countDown() {
Bangle.on('touch',(touchside, touchdata)=>{
if (!islocked && istimeron && touchdata.y > (100+10)) {
Bangle.buzz(40);
Bangle.buzz(40);
istimeron = false;
clearInterval(timerinterval);
} else if (touchdata.y > 24 && touchdata.y < (100-10)) {
@ -134,20 +134,20 @@ function draw() {
else if (current_value >= current_from) { g.setBgColor("#8F8"); }
g.clearRect(0,24,176,176);
g.reset();
g.setFontAlign(0, 0);
g.reset().setFontAlign(0, 0).setColor(istimeron ? "#000" : "#444");
g.setFont("Michroma36").drawString(timeToString(current_value), 88, 62);
g.reset().setFontAlign(0, 0);
g.setFont("HaxorNarrow7x17");
g.drawString(timeToString(current_from), 44, 62+26);
g.drawString(timeToString(current_mid), 88, 62+26);
g.drawString(timeToString(current_to), 132, 62+26);
if (current_value >= current_from) { g.drawRect(44-1,62+26+9,44+1,62+26+9+1); }
if (current_value >= current_mid) { g.drawRect(88-1,62+26+9,88+1,62+26+9+1); }
if (current_value >= current_to) { g.drawRect(132-1,62+26+9,132+1,62+26+9+1); }
if (showInstructions) {
g.setFont("6x8").drawString("Tapping timer locks buttons", 88, 100+5);
g.setFont("6x8").drawString("<= Swipe to change time =>", 88, 168);
@ -159,7 +159,7 @@ function draw() {
g.drawString(timeToString(newtimer_left_to), 44, 138+9);
g.drawString(timeToString(newtimer_right_from), 132, 138-9);
g.drawString(timeToString(newtimer_right_to), 132, 138+9);
g.drawRect(0+8,138-24, 88-9+1, 138+22+1);
g.drawRect(0+8,138-24, 88-9, 138+22);
g.drawRect(88+8,138-24, 176-10+1, 138+22+1);

View File

@ -1,3 +1,4 @@
0.04: Fix tapping at very bottom of list, exit on inactivity
0.03: Add "Calculating" placeholder, update JSON save format
0.02: Fix JSON save format
0.01: First release

View File

@ -10,6 +10,8 @@ const calculating = "Calculating";
const notokens = "No tokens";
const notsupported = "Not supported";
// sample settings:
// {tokens:[{"algorithm":"SHA1","digits":6,"period":30,"issuer":"","account":"","secret":"Bbb","label":"Aaa"}],misc:{}}
var settings = require("Storage").readJSON("authentiwatch.json", true) || {tokens:[],misc:{}};
if (settings.data ) tokens = settings.data ; /* v0.02 settings */
if (settings.tokens) tokens = settings.tokens; /* v0.03+ settings */
@ -146,14 +148,14 @@ function drawToken(id, r) {
// counter - draw triangle as swipe hint
let yc = (y1 + y2) / 2;
g.fillPoly([0, yc, 10, yc - 10, 10, yc + 10, 0, yc]);
adj = 5;
adj = 10;
}
// digits just below label
sz = 30;
do {
g.setFont("Vector", sz--);
} while (g.stringWidth(state.otp) > (r.w - adj));
g.drawString(state.otp, (x1 + x2) / 2 + adj, y1 + 16, false);
g.drawString(state.otp, (x1 + adj + x2) / 2, y1 + 16, false);
}
// shaded lines top and bottom
g.setColor(0.5, 0.5, 0.5);
@ -163,6 +165,8 @@ function drawToken(id, r) {
}
function draw() {
var timerfn = exitApp;
var timerdly = 10000;
var d = new Date();
if (state.curtoken != -1) {
var t = tokens[state.curtoken];
@ -203,17 +207,13 @@ function draw() {
y += tokenentryheight;
}
if (drewcur) {
// the current token has been drawn - draw it again in 1sec
if (state.drawtimer) {
clearTimeout(state.drawtimer);
}
var dly;
// the current token has been drawn - schedule a redraw
if (tokens[state.curtoken].period > 0) {
dly = (state.otp == calculating) ? 1 : 1000;
timerdly = (state.otp == calculating) ? 1 : 1000; // timed
} else {
dly = state.nexttime - d.getTime();
timerdly = state.nexttime - d.getTime(); // counter
}
state.drawtimer = setTimeout(draw, dly);
timerfn = draw;
if (tokens[state.curtoken].period <= 0) {
state.hide = 0;
}
@ -230,12 +230,16 @@ function draw() {
g.setFontAlign(0, 0, 0);
g.drawString(notokens, Bangle.appRect.x + Bangle.appRect.w / 2, Bangle.appRect.y + Bangle.appRect.h / 2, false);
}
if (state.drawtimer) {
clearTimeout(state.drawtimer);
}
state.drawtimer = setTimeout(timerfn, timerdly);
}
function onTouch(zone, e) {
if (e) {
var id = Math.floor((state.listy + (e.y - Bangle.appRect.y)) / tokenentryheight);
if (id == state.curtoken || tokens.length == 0) {
if (id == state.curtoken || tokens.length == 0 || id >= tokens.length) {
id = -1;
}
if (state.curtoken != id) {
@ -254,26 +258,20 @@ function onTouch(zone, e) {
state.nextTime = 0;
state.curtoken = id;
state.hide = 2;
draw();
}
}
draw();
}
function onDrag(e) {
if (e.x > g.getWidth() || e.y > g.getHeight()) return;
if (e.dx == 0 && e.dy == 0) return;
var newy = Math.min(state.listy - e.dy, tokens.length * tokenentryheight - Bangle.appRect.h);
newy = Math.max(0, newy);
if (newy != state.listy) {
state.listy = newy;
draw();
}
state.listy = Math.max(0, newy);
draw();
}
function onSwipe(e) {
if (e == 1) {
Bangle.showLauncher();
}
if (e == -1 && state.curtoken != -1 && tokens[state.curtoken].period <= 0) {
tokens[state.curtoken].period--;
let newsettings={tokens:tokens,misc:settings.misc};
@ -281,8 +279,8 @@ function onSwipe(e) {
state.nextTime = 0;
state.otp = "";
state.hide = 2;
draw();
}
draw();
}
function bangle1Btn(e) {
@ -302,16 +300,22 @@ function bangle1Btn(e) {
state.curtoken = -1;
state.nextTime = 0;
onTouch(0, fakee);
} else {
draw(); // resets idle timer
}
}
function exitApp() {
Bangle.showLauncher();
}
Bangle.on('touch', onTouch);
Bangle.on('drag' , onDrag );
Bangle.on('swipe', onSwipe);
if (typeof BTN2 == 'number') {
setWatch(function(){bangle1Btn(-1); }, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){Bangle.showLauncher();}, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn( 1); }, BTN3, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn(-1);}, BTN1, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){exitApp(); }, BTN2, {edge:"rising", debounce:50, repeat:true});
setWatch(function(){bangle1Btn( 1);}, BTN3, {edge:"rising", debounce:50, repeat:true});
}
Bangle.loadWidgets();

12
apps/flow/README.md Normal file
View File

@ -0,0 +1,12 @@
# FLOW
This is a game where you have to help a flow avoid white obstacles thing by tapping!
This is a demake of an app which I forgot the name of.
Press BTN(1) to restart.
See if you can get to 2500 score!
## Screenshots
![](screenshot1.png)
![](screenshot2.png)
![](screenshot3.png)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4X/AwX48EHgEC1WgCQkVqoDBBfuqBQcBqoLagEqGAguBqALaGAOoAoQuEBbEAKgIMBBQNUBbgMCyoKHBbBVBBYIKGBbEBtNVrQLfOgNaT4gLagp0CPQOABbcBFwNAgEKBgILbitVqAFClWq0ALZFwTDFGAQLZFwYwDBfg"))

220
apps/flow/app.js Normal file
View File

@ -0,0 +1,220 @@
const isB2 = process.env.HWVERSION === 2;
// Bangle.js 1 runs just too fast in direct mode??? (also no getPixel)
if (!isB2) Bangle.setLCDMode("120x120");
const options = Bangle.getOptions();
options.lockTimeout = 0;
options.lcdPowerTimeout = 0;
Bangle.setOptions(options);
g.reset();
g.setBgColor(0, 0, 0);
g.setColor(255, 255, 255);
g.clear();
const h = g.getHeight();
function trigToCoord(ret) {
return ((ret + 1) * h) / 2;
}
function trigToLen(ret) {
return (ret * h) / 2;
}
let i = 0.2;
let speedCoef = 0.014;
let flowFile = require("Storage").readJSON("flow.json");
let highestI = (flowFile && flowFile.hiscore) || 0.1;
let colorA = [255, 255, 0];
let colorB = [0, 255, 255];
let x = 0;
let xt = 0;
let safeMode = false;
let lost = false;
function offsetRect(g, x, y, w) {
g.fillRect(x, y, x + w, y + w);
}
function getColor(num) {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 1, 0],
[0, 1, 1],
[1, 0, 1],
[0.5, 0.5, 1],
[1, 0.5, 0],
[0, 1, 0.5],
[0.5, 0.5, 0.5],
][num];
}
function calculateColor(num) {
colorA = getColor(Math.floor((num % 1) * 10));
colorB = getColor(Math.floor((num % 10) - (num % 1)));
}
calculateColor(highestI);
Bangle.on("touch", () => (safeMode = !safeMode));
function resetGame() {
x = xt = 0;
safeMode = lost = false;
i = 0.2;
speedCoef = 0.014;
obstaclePeriod = 150;
obstacleMode = 1;
g.clear();
shownScore = false;
intervalId = setInterval(draw);
}
function checkCollision() {
lost = g.getPixel(trigToCoord(+x), (h * 2) / 3 - 4) !== 0;
if (lost) {
scoringI = i;
speedCoef = Math.min(speedCoef, 0.02);
g.setFont(isB2 ? "6x15" : "4x6", 3);
g.setColor(colorA[0], colorA[1], colorA[2])
.drawString(
"Game over",
trigToCoord(0) - g.stringWidth("Game over") / 2,
trigToCoord(0)
)
.setColor(1, 1, 1);
}
}
function drawPlayer() {
if (!safeMode) xt = Math.cos(i * Math.PI * 4) / 7.5;
else xt = -Math.cos(i * Math.PI * 2) / 20 + 0.35;
x = x * 0.8 + xt * 0.2;
if (highestI > 250) calculateColor(i);
g.setColor(colorA[0], colorA[1], colorA[2]);
offsetRect(g, trigToCoord(+x), (h * 2) / 3, 3);
g.setColor(colorB[0], colorB[1], colorB[2]);
offsetRect(g, trigToCoord(-x), (h * 2) / 3, 3);
}
let obstaclePeriod = 150;
let obstacleMode = 1;
function drawObstracle() {
g.setColor(1, 1, 1);
switch (obstacleMode) {
case 0:
offsetRect(g, trigToCoord(-0.15), 0, trigToLen(0.3));
break;
case 1:
offsetRect(g, trigToCoord(0.2), 0, trigToLen(0.2));
offsetRect(g, trigToCoord(-0.4), 0, trigToLen(0.2));
break;
case 2:
break;
}
obstaclePeriod--;
if (obstaclePeriod <= 0) {
// If we are off cooldown mode, pick a random actual mode
if (obstacleMode === 2) {
obstaclePeriod = Math.random() * 50 + 50;
obstacleMode = Math.round(Math.random());
} else if (Math.random() > 0.5) {
// Give it a chance to repeat with no cooldown
obstaclePeriod = 25 + 2.5 * speedCoef;
obstacleMode = 2;
}
}
}
let shownScore = false;
let scoringI = 0;
function draw() {
if (!lost) {
drawPlayer();
checkCollision();
speedCoef *= 1.0005;
drawObstracle();
} else {
speedCoef /= 1.05;
if (speedCoef <= 0.005) {
clearInterval(intervalId);
i -= speedCoef;
g.setFont(isB2 ? "6x15" : "4x6", 1);
const str = "Hiscore: " + Math.round(highestI * 10);
g.setColor(
scoringI > highestI ? 0 : 255,
0,
scoringI > highestI ? 255 : 0
)
.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0)
)
.setColor(255, 255, 255);
if (scoringI > highestI) {
highestI = scoringI;
require("Storage").writeJSON("flow.json", {
hiscore: highestI,
});
calculateColor(highestI);
}
setTimeout(resetGame, 3000);
} else if (speedCoef <= 0.01 && !shownScore) {
shownScore = true;
g.setFont(isB2 ? "6x15" : "4x6", 2);
const str = "Score: " + Math.round(scoringI * 10);
g.setColor(colorB[0], colorB[1], colorB[2])
.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0)
)
.setColor(1, 1, 1);
}
}
i += speedCoef;
g.scroll(0, speedCoef * h);
g.flip();
}
let intervalId;
if (BTN.read()) {
for (let i = 0; i < 10; i++) {
color = getColor(i);
g.setColor(color[0], color[1], color[2]);
g.fillRect((i / 10) * h, 0, ((i + 1) / 10) * h, h);
}
g.setColor(0);
g.setFont("Vector", 9);
let str = "Welcome to the debug screen!";
g.drawString(
str,
trigToCoord(0) - g.stringWidth(str) / 2,
trigToCoord(0) - 9
);
str = "Don't hold BTN while opening to play!";
g.drawString(str, trigToCoord(0) - g.stringWidth(str) / 2, trigToCoord(0));
g.flip();
setInterval(() => {
g.scroll(0, 0.014 * h);
i += 0.014;
calculateColor(i);
g.setColor(colorA[0], colorA[1], colorA[2]);
g.fillRect(0, 0, trigToCoord(0), 0.014 * h);
g.setColor(colorB[0], colorB[1], colorB[2]);
g.fillRect(trigToCoord(0), 0, trigToCoord(1), 0.014 * h);
}, 1000 / 30);
} else intervalId = setInterval(draw, 1000 / 30);

BIN
apps/flow/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 B

BIN
apps/flow/screenshot1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

BIN
apps/flow/screenshot2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
apps/flow/screenshot3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,3 @@
0.01: New App!
0.02: using TS and rollup to bundle
0.03: bug fixes and support bangle 1

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-20 Ionică Bizău <bizauionica@gmail.com> (https://ionicabizau.net)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,26 @@
# Hebrew Calendar
Displays the current hebrew calendar date
Add screen shots (if possible) to the app folder and link then into this file with ![](<name>.png)
## Usage
Open the app, and it shows a menu with the date components
## Features
Shows the hebrew date, month, and year; alongside the gregorian date
## Controls
Name the buttons and what they are used for
## Requests
Michael Salaverry (github.com/barakplasma)
## Creator
Michael Salaverry
with help from https://github.com/IonicaBizau/hebrew-date (MIT license)
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a> from <a href="https://www.flaticon.com/" title="Flaticon">[www.flaticon.com](https://www.flaticon.com/premium-icon/calendar_3130060?term=jewish&page=1&position=10&page=1&position=10&related_id=3130060&origin=tag)</a></div>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,17 @@
!function(){"use strict";
/*!
* This script was taked from this page and ported to Node.js by Ionic Bizu
* http://www.shamash.org/help/javadate.shtml
*
* This script was adapted from C sources written by
* Scott E. Lee, which contain the following copyright notice:
*
* Copyright 1993-1995, Scott E. Lee, all rights reserved.
* Permission granted to use, copy, modify, distribute and sell so long as
* the above copyright and this permission statement are retained in all
* copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
*
* Bill Hastings
* RBI Software Systems
* bhastings@rbi.com
*/var t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};var i=new function(t,i,e,o,r,n,h,a,s,f,u,l,v,c){this[0]=t,this[1]=i,this[2]=e,this[3]=o,this[4]=r,this[5]=n,this[6]=h,this[7]=a,this[8]=s,this[9]=f,this[10]=u,this[11]=l,this[12]=v,this[13]=c}("Tishri","Heshvan","Kislev","Tevet","Shevat","AdarI","AdarII","Nisan","Iyyar","Sivan","Tammuz","Av","Elul"),e=new function(t,i,e,o,r,n,h,a,s,f,u,l,v,c,y,d,m,M,b){this[0]=t,this[1]=i,this[2]=e,this[3]=o,this[4]=r,this[5]=n,this[6]=h,this[7]=a,this[8]=s,this[9]=f,this[10]=u,this[11]=l,this[12]=v,this[13]=c,this[14]=y,this[15]=d,this[16]=m,this[17]=M,this[18]=b}(12,12,13,12,12,13,12,13,12,12,13,12,12,13,12,12,13,12,13);g.clear();let o=new Date,r=function(o){var r,n,h=0,a=0,s=0,f=0,u=0,l=0,v=0;function c(t){var i,o,r,n;for(f=Math.floor((t+310)/6940),r=void 0,n=void 0,r=31524,n=(r+=45971*f)>>16,n+=2744*f,o=Math.floor(n/25920),r=(n-=25920*o)<<16|65535&r,i=Math.floor(r/25920),l=o<<16|i,v=r-=25920*i;l<t-6940+310;)f++,v+=179876755,l+=Math.floor(v/25920),v%=25920;for(u=0;u<18&&!(l>t-74);u++)v+=765433*e[u],l+=Math.floor(v/25920),v%=25920}function y(t,i,e){var o=i,r=o%7;return(e>=19440||!(2==t||5==t||7==t||10==t||13==t||16==t||18==t)&&2==r&&e>=9924||(3==t||6==t||8==t||11==t||14==t||17==t||0==t)&&1==r&&e>=16789)&&(o++,7==++r&&(r=0)),3!=r&&5!=r&&0!=r||o++,o}var d=o;return"object"===(void 0===d?"undefined":t(d))&&(r=o.getMonth()+1,n=o.getDate(),d=o.getFullYear()),function(t){var i,o=0,r=0,n=t-347997;if(c(n),n>=(o=y(u,l,v))){if(s=19*f+u+1,n<o+59)return void(n<o+30?(h=1,a=n-o+1):(h=2,a=n-o-29));v+=765433*e[u],l+=Math.floor(v/25920),r=y((u+1)%19,l,v%=25920)}else{if(s=19*f+u,n>=o-177)return void(n>o-30?(h=13,a=n-o+30):n>o-60?(h=12,a=n-o+60):n>o-89?(h=11,a=n-o+89):n>o-119?(h=10,a=n-o+119):n>o-148?(h=9,a=n-o+148):(h=8,a=n-o+178));if(13==e[(s-1)%19]){if(h=7,(a=n-o+207)>0)return;if(h--,(a+=30)>0)return;h--,a+=30}else{if(h=6,(a=n-o+207)>0)return;h--,a+=30}if(a>0)return;if(h--,(a+=29)>0)return;r=o,c(l-365),o=y(u,l,v)}if(l=n-o-29,355==(i=r-o)||385==i){if(l<=30)return h=2,void(a=l);l-=30}else{if(l<=29)return h=2,void(a=l);l-=29}h=3,a=l}(function(t,i,e){var o=0,r=0,n=void 0;return o=t<0?t+4801:t+4800,i>2?r=i-3:(r=i+9,o--),n=Math.floor(146097*Math.floor(o/100)/4),n+=Math.floor(o%100*1461/4),n+=Math.floor((153*r+2)/5),n+=e-32045}(d,r,n)),{year:s,month:h,date:a,month_name:i[h-1]}}(o);var n={"":{title:"Hebrew Date"},cal:{value:require("locale").date(o,1),onchange:()=>{}},date:{value:r.date,onchange:()=>{}},month:{value:r.month_name,onchange:()=>{}},year:{value:r.year,onchange:()=>{}}};E.showMenu(n)}();

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,23 @@
{
"name": "hebrew_calendar",
"version": "0.0.3",
"description": "Bangle.js app for seeing hebrew calendar",
"main": "app.js",
"types": "app.d.ts",
"scripts": {
"build": "rollup -c"
},
"author": {
"name": "Michael Salaverry",
"url": "https://github.com/barakplasma"
},
"license": "MIT",
"devDependencies": {
"@rollup/plugin-typescript": "^4.1.1",
"rollup": "^2.10.2",
"rollup-plugin-terser": "^5.3.0",
"terser": "^4.7.0",
"tslib": "^2.0.0",
"typescript": "^3.9.2"
}
}

View File

@ -0,0 +1,15 @@
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
export default {
input: './src/app.ts',
output: {
dir: '.',
format: 'iife',
name: 'hebrew_calendar'
},
plugins: [
typescript(),
terser(),
]
};

View File

@ -0,0 +1,34 @@
declare var Bangle: any;
declare var g: any;
declare var E: any;
declare var require: any;
g.clear();
let now = new Date();
import { hebrewDate } from "./hebrewDate";
let today = hebrewDate(now);
var mainmenu = {
"" : {
"title" : "Hebrew Date"
},
cal: {
value: require('locale').date(now,1),
onchange : () => {}
},
date: {
value : today.date,
onchange : () => {}
},
month: {
value : today.month_name,
onchange : () => {}
},
year: {
value : today.year,
onchange : () => {}
}
};
E.showMenu(mainmenu);

View File

@ -0,0 +1,358 @@
/*!
* This script was taked from this page and ported to Node.js by Ionic Bizu
* http://www.shamash.org/help/javadate.shtml
*
* This script was adapted from C sources written by
* Scott E. Lee, which contain the following copyright notice:
*
* Copyright 1993-1995, Scott E. Lee, all rights reserved.
* Permission granted to use, copy, modify, distribute and sell so long as
* the above copyright and this permission statement are retained in all
* copies. THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
*
* Bill Hastings
* RBI Software Systems
* bhastings@rbi.com
*/
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var GREG_SDN_OFFSET = 32045,
DAYS_PER_5_MONTHS = 153,
DAYS_PER_4_YEARS = 1461,
DAYS_PER_400_YEARS = 146097;
var HALAKIM_PER_HOUR = 1080,
HALAKIM_PER_DAY = 25920,
HALAKIM_PER_LUNAR_CYCLE = 29 * HALAKIM_PER_DAY + 13753,
HALAKIM_PER_METONIC_CYCLE = HALAKIM_PER_LUNAR_CYCLE * (12 * 19 + 7);
var HEB_SDN_OFFSET = 347997,
NEW_MOON_OF_CREATION = 31524,
NOON = 18 * HALAKIM_PER_HOUR,
AM3_11_20 = 9 * HALAKIM_PER_HOUR + 204,
AM9_32_43 = 15 * HALAKIM_PER_HOUR + 589;
var SUN = 0,
MON = 1,
TUES = 2,
WED = 3,
THUR = 4,
FRI = 5,
SAT = 6;
function weekdayarr(d0, d1, d2, d3, d4, d5, d6) {
this[0] = d0;
this[1] = d1;
this[2] = d2;
this[3] = d3;
this[4] = d4;
this[5] = d5;
this[6] = d6;
}
function gregmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
}
function hebrewmontharr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13?: any) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
this[12] = m12;
this[13] = m13;
}
function monthsperyeararr(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18) {
this[0] = m0;
this[1] = m1;
this[2] = m2;
this[3] = m3;
this[4] = m4;
this[5] = m5;
this[6] = m6;
this[7] = m7;
this[8] = m8;
this[9] = m9;
this[10] = m10;
this[11] = m11;
this[12] = m12;
this[13] = m13;
this[14] = m14;
this[15] = m15;
this[16] = m16;
this[17] = m17;
this[18] = m18;
}
var gWeekday = new weekdayarr("Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur"),
gMonth = new gregmontharr("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"),
hMonth = new hebrewmontharr("Tishri", "Heshvan", "Kislev", "Tevet", "Shevat", "AdarI", "AdarII", "Nisan", "Iyyar", "Sivan", "Tammuz", "Av", "Elul"),
mpy = new monthsperyeararr(12, 12, 13, 12, 12, 13, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 13);
/**
* hebrewDate
* Convert the Gregorian dates into Hebrew calendar dates.
*
* @name hebrewDate
* @function
* @param {Date|Number} inputDate The date object (representing the Gregorian date) or the year.
* @return {Object} An object containing:
*
* - `year`: The Hebrew year.
* - `month`: The Hebrew month.
* - `month_name`: The Hebrew month name.
* - `date`: The Hebrew date.
*/
export const hebrewDate = function (inputDateOrYear: Date) {
var inputMonth, inputDate;
var hebrewMonth = 0,
hebrewDate = 0,
hebrewYear = 0,
metonicCycle = 0,
metonicYear = 0,
moladDay = 0,
moladHalakim = 0;
function GregorianToSdn(inputYear, inputMonth, inputDay) {
var year = 0,
month = 0,
sdn = void 0;
// Make year a positive number
if (inputYear < 0) {
year = inputYear + 4801;
} else {
year = inputYear + 4800;
}
// Adjust the start of the year
if (inputMonth > 2) {
month = inputMonth - 3;
} else {
month = inputMonth + 9;
year--;
}
sdn = Math.floor(Math.floor(year / 100) * DAYS_PER_400_YEARS / 4);
sdn += Math.floor(year % 100 * DAYS_PER_4_YEARS / 4);
sdn += Math.floor((month * DAYS_PER_5_MONTHS + 2) / 5);
sdn += inputDay - GREG_SDN_OFFSET;
return sdn;
}
function SdnToHebrew(sdn) {
var tishri1 = 0,
tishri1After = 0,
yearLength = 0,
inputDay = sdn - HEB_SDN_OFFSET;
FindTishriMolad(inputDay);
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
if (inputDay >= tishri1) {
// It found Tishri 1 at the start of the year.
hebrewYear = metonicCycle * 19 + metonicYear + 1;
if (inputDay < tishri1 + 59) {
if (inputDay < tishri1 + 30) {
hebrewMonth = 1;
hebrewDate = inputDay - tishri1 + 1;
} else {
hebrewMonth = 2;
hebrewDate = inputDay - tishri1 - 29;
}
return;
}
// We need the length of the year to figure this out,so find Tishri 1 of the next year.
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
tishri1After = Tishri1((metonicYear + 1) % 19, moladDay, moladHalakim);
} else {
// It found Tishri 1 at the end of the year.
hebrewYear = metonicCycle * 19 + metonicYear;
if (inputDay >= tishri1 - 177) {
// It is one of the last 6 months of the year.
if (inputDay > tishri1 - 30) {
hebrewMonth = 13;
hebrewDate = inputDay - tishri1 + 30;
} else if (inputDay > tishri1 - 60) {
hebrewMonth = 12;
hebrewDate = inputDay - tishri1 + 60;
} else if (inputDay > tishri1 - 89) {
hebrewMonth = 11;
hebrewDate = inputDay - tishri1 + 89;
} else if (inputDay > tishri1 - 119) {
hebrewMonth = 10;
hebrewDate = inputDay - tishri1 + 119;
} else if (inputDay > tishri1 - 148) {
hebrewMonth = 9;
hebrewDate = inputDay - tishri1 + 148;
} else {
hebrewMonth = 8;
hebrewDate = inputDay - tishri1 + 178;
}
return;
} else {
if (mpy[(hebrewYear - 1) % 19] == 13) {
hebrewMonth = 7;
hebrewDate = inputDay - tishri1 + 207;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
} else {
hebrewMonth = 6;
hebrewDate = inputDay - tishri1 + 207;
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 30;
}
if (hebrewDate > 0) return;
hebrewMonth--;
hebrewDate += 29;
if (hebrewDate > 0) return;
// We need the length of the year to figure this out,so find Tishri 1 of this year.
tishri1After = tishri1;
FindTishriMolad(moladDay - 365);
tishri1 = Tishri1(metonicYear, moladDay, moladHalakim);
}
}
yearLength = tishri1After - tishri1;
moladDay = inputDay - tishri1 - 29;
if (yearLength == 355 || yearLength == 385) {
// Heshvan has 30 days
if (moladDay <= 30) {
hebrewMonth = 2;
hebrewDate = moladDay;
return;
}
moladDay -= 30;
} else {
// Heshvan has 29 days
if (moladDay <= 29) {
hebrewMonth = 2;
hebrewDate = moladDay;
return;
}
moladDay -= 29;
}
// It has to be Kislev.
hebrewMonth = 3;
hebrewDate = moladDay;
}
function FindTishriMolad(inputDay) {
// Estimate the metonic cycle number. Note that this may be an under
// estimate because there are 6939.6896 days in a metonic cycle not
// 6940,but it will never be an over estimate. The loop below will
// correct for any error in this estimate.
metonicCycle = Math.floor((inputDay + 310) / 6940);
// Calculate the time of the starting molad for this metonic cycle.
MoladOfMetonicCycle();
// If the above was an under estimate,increment the cycle number until
// the correct one is found. For modern dates this loop is about 98.6%
// likely to not execute,even once,because the above estimate is
// really quite close.
while (moladDay < inputDay - 6940 + 310) {
metonicCycle++;
moladHalakim += HALAKIM_PER_METONIC_CYCLE;
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
}
// Find the molad of Tishri closest to this date.
for (metonicYear = 0; metonicYear < 18; metonicYear++) {
if (moladDay > inputDay - 74) break;
moladHalakim += HALAKIM_PER_LUNAR_CYCLE * mpy[metonicYear];
moladDay += Math.floor(moladHalakim / HALAKIM_PER_DAY);
moladHalakim = moladHalakim % HALAKIM_PER_DAY;
}
}
function MoladOfMetonicCycle() {
var r1 = void 0,
r2 = void 0,
d1 = void 0,
d2 = void 0;
// Start with the time of the first molad after creation.
r1 = NEW_MOON_OF_CREATION;
// Calculate gMetonicCycle * HALAKIM_PER_METONIC_CYCLE. The upper 32
// bits of the result will be in r2 and the lower 16 bits will be in r1.
r1 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE & 0xFFFF);
r2 = r1 >> 16;
r2 += metonicCycle * (HALAKIM_PER_METONIC_CYCLE >> 16 & 0xFFFF);
// Calculate r2r1 / HALAKIM_PER_DAY. The remainder will be in r1,the
// upper 16 bits of the quotient will be in d2 and the lower 16 bits
// will be in d1.
d2 = Math.floor(r2 / HALAKIM_PER_DAY);
r2 -= d2 * HALAKIM_PER_DAY;
r1 = r2 << 16 | r1 & 0xFFFF;
d1 = Math.floor(r1 / HALAKIM_PER_DAY);
r1 -= d1 * HALAKIM_PER_DAY;
moladDay = d2 << 16 | d1;
moladHalakim = r1;
}
function Tishri1(metonicYear, moladDay, moladHalakim) {
var tishri1 = moladDay,
dow = tishri1 % 7,
leapYear = metonicYear == 2 || metonicYear == 5 || metonicYear == 7 || metonicYear == 10 || metonicYear == 13 || metonicYear == 16 || metonicYear == 18,
lastWasLeapYear = metonicYear == 3 || metonicYear == 6 || metonicYear == 8 || metonicYear == 11 || metonicYear == 14 || metonicYear == 17 || metonicYear == 0;
// Apply rules 2,3 and 4
if (moladHalakim >= NOON || !leapYear && dow == TUES && moladHalakim >= AM3_11_20 || lastWasLeapYear && dow == MON && moladHalakim >= AM9_32_43) {
tishri1++;
dow++;
if (dow == 7) dow = 0;
}
// Apply rule 1 after the others because it can cause an additional delay of one day.
if (dow == WED || dow == FRI || dow == SUN) {
tishri1++;
}
return tishri1;
}
var inputYear: Date | number = inputDateOrYear;
if ((typeof inputYear === "undefined" ? "undefined" : _typeof(inputYear)) === "object") {
inputMonth = inputDateOrYear.getMonth() + 1;
inputDate = inputDateOrYear.getDate();
inputYear = inputDateOrYear.getFullYear();
}
SdnToHebrew(GregorianToSdn(inputYear, inputMonth, inputDate));
return {
year: hebrewYear,
month: hebrewMonth,
date: hebrewDate,
month_name: hMonth[hebrewMonth - 1]
};
};

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"module": "es2015",
"noImplicitAny": false,
"target": "es2015"
},
"include": [
"src"
]
}

View File

@ -8,3 +8,4 @@
0.08: Merge Bangle.js 1 and 2 launchers
0.09: Bangle.js 2 - pressing the button goes back to clock (fix #971)
After 10s of being locked, the launcher goes back to the clock screen
0.10: added in selectable font in settings including scalable vector font

View File

@ -1,4 +1,22 @@
var s = require("Storage");
let fonts = g.getFonts();
var scaleval = 1;
var vectorval = 20;
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
let settings = require('Storage').readJSON("launch.json", true) || {};
if ("vectorsize" in settings) {
vectorval = parseInt(settings.vectorsize);
}
if ("font" in settings){
if(settings.font == "Vector"){
scaleval = vectorval/20;
font = "Vector"+(vectorval).toString();
}
else{
font = settings.font;
scaleval = (font.split('x')[1])/20;
}
}
var apps = s.list(/\.info$/).map(app=>{var a=s.readJSON(app,1);return a&&{name:a.name,type:a.type,icon:a.icon,sortorder:a.sortorder,src:a.src};}).filter(app=>app && (app.type=="app" || app.type=="clock" || !app.type));
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
@ -11,8 +29,6 @@ apps.forEach(app=>{
if (app.icon)
app.icon = s.read(app.icon); // should just be a link to a memory area
});
// FIXME: not needed after 2v11
var font = g.getFonts().includes("12x20") ? "12x20" : "6x8:2";
// FIXME: check not needed after 2v11
if (g.wrapString) {
g.setFont(font);
@ -22,9 +38,9 @@ if (g.wrapString) {
function drawApp(i, r) {
var app = apps[i];
if (!app) return;
g.clearRect(r.x,r.y,r.x+r.w-1, r.y+r.h-1);
g.setFont(font).setFontAlign(-1,0).drawString(app.name,64,r.y+32);
if (app.icon) try {g.drawImage(app.icon,8,r.y+8);} catch(e){}
g.clearRect((r.x),(r.y),(r.x+r.w-1), (r.y+r.h-1));
g.setFont(font).setFontAlign(-1,0).drawString(app.name,64*scaleval,r.y+(32*scaleval));
if (app.icon) try {g.drawImage(app.icon,8*scaleval, r.y+(8*scaleval), {scale: scaleval});} catch(e){}
}
g.clear();
@ -32,7 +48,7 @@ Bangle.loadWidgets();
Bangle.drawWidgets();
E.showScroller({
h : 64, c : apps.length,
h : 64*scaleval, c : apps.length,
draw : drawApp,
select : i => {
var app = apps[i];

25
apps/launch/settings.js Normal file
View File

@ -0,0 +1,25 @@
// make sure to enclose the function in parentheses
(function(back) {
let settings = require('Storage').readJSON('launch.json',1)||{};
let fonts = g.getFonts();
function save(key, value) {
settings[key] = value;
require('Storage').write('launch.json',settings);
}
const appMenu = {
'': {'title': 'Launcher Settings'},
'< Back': back,
'Font': {
value: fonts.includes(settings.font)? fonts.indexOf(settings.font) : fonts.indexOf("12x20"),
min:0, max:fonts.length-1, step:1,wrap:true,
onchange: (m) => {save('font', fonts[m])},
format: v => fonts[v]
},
'Vector font size': {
value: settings.vectorsize || 10,
min:10, max: 20,step:1,wrap:true,
onchange: (m) => {save('vectorsize', m)}
}
};
E.showMenu(appMenu);
});

View File

@ -1,6 +1,6 @@
# Mandlebrot Clock
# Mandelbrot Clock
A simple clock themed on the mandlebrot set.
A simple clock themed on the mandelbrot set.
Written by [James Milner](https://www.github.com/jameslmilner)

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -1,6 +1,6 @@
// MIT License - James Milner 2021
const mandlebrotBmp = {
const mandelbrotBmp = {
width: 176,
height: 176,
bpp: 8,
@ -13,7 +13,7 @@ const mandlebrotBmp = {
};
function draw() {
g.drawImage(mandlebrotBmp);
g.drawImage(mandelbrotBmp);
// work out how to display the current time
const d = new Date();
const h = d.getHours(),

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

1
apps/menuwheel/ChangeLog Normal file
View File

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

25
apps/menuwheel/README.md Normal file
View File

@ -0,0 +1,25 @@
# Wheel Menu
Replace Bangle.js 2's menus with a version that contains variable-size text and a back button.
Bangle.js 1:
![Dark Mode Screenshot](screenshot_b1_dark.png)
![Light Mode Screenshot](screenshot_b1_light.png)
Bangle.js 2:
![Dark Mode Screenshot](screenshot_b2_dark.png)
![Editing Screenshot](screenshot_b2_edit.png)
![Light Mode Screenshot](screenshot_b2_light.png)
## Features
If the menu contains "Back" or "Exit", it is shown as a button instead.
The menu wraps around, with a divider between the last and first items.
## Controls
Bangle.js 1: Use BTN1/BTN3 to scroll through items, BTN2 to open/edit the selected item.
Bangle.js 2: Swipe up/down to scroll through items, tap/BTN to open/edit the selected item.
Press the back button (if present) to go back.

213
apps/menuwheel/boot.js Normal file
View File

@ -0,0 +1,213 @@
E.showMenu = function(items) {
g.clearRect(Bangle.appRect); // clear screen if no menu supplied
// clean up back button listener
if (Bangle.backHandler) Bangle.removeListener('touch', Bangle.backHandler)
delete Bangle.backHandler;
if (!items) {
Bangle.setUI();
return;
}
var B2 = process.env.HWVERSION===2,
loc = require("locale"),
menuItems = Object.keys(items),
options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
// show "< Back" item (or similar) as button instead (i.e. remove from the menu)
var back,backLbl;
for (var b of ['Back', 'Exit', 'Cancel']) {
if (!items[b] && items['< '+b]) b = '< '+b;
back = items[b];
if (typeof back === "function") {
backLbl = loc.translate(b);
menuItems.splice(menuItems.indexOf(b),1);
break;
}
else back = undefined;
}
// font sizes
var small = B2?15:22,
large = B2?30:45;
if (options.selected === undefined) options.selected = 0;
var ar = Bangle.appRect,
x = ar.x,
x2 = ar.x2,
w = ar.w,
y = ar.y,
y2 = ar.y2;
if (options.title) y += 22;
var wrap = menuItems.length>3; // don't wrap if all items are always in view anyway
var vc=Math.round((y+y2)/2), // vertical center
hc = Math.round((x+x2)/2), // horizontal center
ih = large+small*2; // active item height
var getItem = idx => {
// we wrap out-of-range indexes
while (idx<0) idx+=menuItems.length;
idx = idx%menuItems.length;
var name = menuItems[idx];
var item = items[name];
var v;
if ("object"== typeof item) {
v = item.value;
if (item.format) v = item.format(v);
v = loc.translate(""+v);
}
return {lbl: loc.translate(name), v: v};
};
var l = {
lastIdx : null, // we want a complete redraw on first run
draw : function() {
var idx = options.selected,
edit = l.selectEdit;
g.reset();
// don't highlight whole item when editing
g.setColor(edit?g.theme.fg:g.theme.fgH)
.setBgColor(edit?g.theme.bg:g.theme.bgH)
.setFont('Vector', large);
var item = getItem(idx),
lw = g.stringWidth(item.lbl)+2;
if (lw+2 >= w) { // label width doesn't fit at large size: scale it down
g.setFont('Vector', Math.floor(large*ar.w/lw));
}
g.clearRect(x,vc-ih/2,x2,vc+ih/2)
.setFontAlign(0,0,0).drawString(item.lbl,hc,vc);
if (item.v !== undefined) {
g.setColor(g.theme.fgH).setBgColor(g.theme.bgH) // always highlighted: either as part of item, or while editing
.setFontAlign(0,1,0)
.setFont('Vector', small)
.clearRect(x,vc+ih/2-small-2,x2,vc+ih/2)
.drawString(item.v,hc,vc+ih/2-1);
if (edit) {
g.drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",x2-23,vc+ih/2-small+(B2?1:5),{scale:2});
}
}
if (l.lastIdx !== idx) {
// we scrolled: redraw all
l.lastIdx=idx;
g.reset();
if (options.title) {
if (B2) g.setFont('12x20');
else g.setFont('6x8',2);
g.drawLine(x, y-2, x2, y-2)
.setFontAlign(0,1,0)
.drawString(options.title, (x+x2)/2, y-2);
}
// clear prev/next items area
g.clearRect(x,y,x2,vc-ih/2-1)
.clearRect(x,vc+ih/2+1,x2,y2);
// get display label by index
var lbl = idx => {
var item = getItem(idx);
if (item.v !== undefined) item.lbl+=': '+item.v;
return item.lbl;
}
// previous two items
g.setFontAlign(0, 1)
if (wrap||idx>0) g.setFont('Vector', small).drawString(lbl(idx-1), hc, vc-ih/2-5);
if (wrap||idx>1) g.setFont('Vector', small/2).drawString(lbl(idx-2), hc, vc-ih/2-small-10);
// next two items
g.setFontAlign(0, -1);
if (wrap||idx<menuItems.length-1) g.setFont('Vector', small).drawString(lbl(idx+1), hc, vc+ih/2+5);
if (wrap||idx<menuItems.length-2) g.setFont('Vector', small/2).drawString(lbl(idx+2), hc, vc+ih/2+small+10);
if (wrap) {
// draw divider between first/last items
var div = y => g.drawLine(x, y, x2, y);
if (idx===0) div(vc-ih/2-1);
if (idx===1) div(vc-ih/2-small-8);
// if (s === 2) div(vc-ih/2-small*1.5-13);
if (idx===menuItems.length-1) div(vc+ih/2+1);
if (idx===menuItems.length-2) div(vc+ih/2+small+6);
// if (s === 2) div(vc+ih/2+small*1.5+13);
}
if (back) {
g.setBgColor(g.theme.bg2)
.setFont('Vector', small);
var bw=g.stringWidth(backLbl);
g.clearRect(x,y, x+bw+2, y+small+2);
var bx1=x, by1=y, bx2=x+bw+2, by2=y+small+2;
// g.drawRect(x,y, x+bw+2, y+small+2);
var poly = [ // button outline
bx1+2,by1,
bx2-2,by1,
bx2, by1+2,
bx2, by2-2,
bx2-2,by2,
bx1+2,by2,
bx1, by2-2,
bx1, by1+2,
]
g.setColor(g.theme.bg2).fillPoly(poly, true)
.setColor(g.theme.fg2).drawPoly(poly, true)
.setFontAlign(-1,-1,0).drawString(backLbl, x+2,y+2);
}
}
g.flip();
},
select : function() { // same as default menu
var item = items[menuItems[options.selected]];
if ("function" == typeof item) {l.lastIdx=null; item(l);} // force a redraw after callback
else if ("object" == typeof item) {
// if a number, go into 'edit mode'
if ("number" == typeof item.value)
l.selectEdit = l.selectEdit?undefined:item;
else { // else just toggle bools
if ("boolean" == typeof item.value) item.value=!item.value;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
}
l.draw();
}
},
move : function(dir) {
if (l.selectEdit) { // same as default menu
var item = l.selectEdit;
item.value -= (dir||1)*(item.step||1);
if (item.min!==undefined && item.value<item.min) item.value = item.wrap ? item.max : item.min;
if (item.max!==undefined && item.value>item.max) item.value = item.wrap ? item.min : item.max;
if (item.onchange) {l.lastIdx=null; item.onchange(item.value);} // force a redraw after callback
} else {
if (B2) dir=-dir; // swipe vs button scrolling
if (!wrap && (options.selected+dir<0 || options.selected+dir>=menuItems.length)) {
return;
}
options.selected = (options.selected+dir+menuItems.length)%menuItems.length;
}
l.draw();
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
if (back) {
// we have a back button: check touches before passing them to setUI's touchHandler
if (B2) {
Bangle.removeListener('touch', Bangle.touchHandler);
Bangle.backHandler = (b, xy) => {
// anywhere top-left (but above the active item) = back button
if (xy.x<hc && xy.y<vc-ih/2) back();
else Bangle.touchHandler(b, xy);
}
} else {
// Bangle.js 1 has no touchHandler
Bangle.backHandler = (b) => {
// left side = back button
if (b===1) back();
}
}
// note: backHandler is cleaned up at the top of this file
Bangle.on('touch', Bangle.backHandler);
}
return l;
};

BIN
apps/menuwheel/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 880 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

1
apps/snek/snek-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("pFIwkB+MRAEH/EUIA7i93u9xEUIABEf4AC+93/4CBETpDBv//gEHEf6MB9wkB/8HSDl3vwjCAAfnErIjiEQYdBAAXuAoSNXEYIdDAAoj4j/3DpN3v6NWAA3/fDYjgRgIjLu9xESj2BAAN/SQwLBEe4XDdwghDBQQjSCgN+C5D9FEebTDEZJEWEQSDVEdZpDZYPnETYhDAAhpeEbzREI0rTbEdXuETb4Bvz1BAYIj/EYxrg9yQDv/3JoS9WEcoaGAAQtBOwYABEaSMBWoYeFJKgjjiIUD9ySEEjwAJFogj0SQgAFBQ4jRABcfXoQj/TowjCOgIkeEf7lHvz+CEb93Ef4jHR8Rr/fY4jCuIifAAQj/EIojbeohGeEcQhHfLRFDeoIicEcQbDv3uEYiNXIgnu87SgEcf/DKnxBJEf/4ACESf/EcYA=="))

469
apps/snek/snek.js Normal file
View File

@ -0,0 +1,469 @@
function init() {
this.titleScreen = true;
this.min = 0;
this.max = 160;
this.step = 20;
this.scoreMultiplier = 25;
this.totalGrid = this.max / this.step;
if (g.theme.dark) {
this.textColor = 1;
} else {
this.textColor = 0;
}
this.getNewPosistion = () => {
let newPos;
while (!newPos) {
// random bonus points for bad luck / lag
if (currentPosition.length > 10) {
this.score += 1;
}
const x = Math.floor(Math.random() * this.totalGrid + 1) * this.step;
const y = Math.floor(Math.random() * this.totalGrid + 1) * this.step;
const found = currentPosition.find(pos => {
return pos.x === x && pos.y === y;
});
if (!found) {
newPos = {x: x, y: y};
}
}
return newPos;
};
this.restart = () => {
g.clear();
this.titleScreen = false;
this.score = 0;
this.paused = false;
this.currentPosition = [{x: 2 * step, y: 3 * step},{x: 1 * step, y: 3 * step}];
this.death = false;
this.gameSpeed = 200;
this.directionSet = null;
this.direction = 1;
this.createApple();
};
const game = () => {
if (this.death && !this.paused) {
g.clear();
this.showDeathScreen();
} else if (this.titleScreen && !this.paused) {
this.showTitleScreen();
} else if (!this.paused) {
g.clear();
this.drawApple();
this.drawSnake();
this.boundries();
}
setTimeout(() => {
game();
}, this.gameSpeed);
};
this.increaseDifficulity = () => {
if (gameSpeed > 59) {
gameSpeed -= 10;
}
};
this.createApple = () => {
this.applePosition = getNewPosistion();
};
this.drawApple = () => {
g.setColor(0, 1, 0);
g.drawImage(this.appleLeaf, this.applePosition.x - 15, this.applePosition.y - 10);
g.setColor(1, 0, 0);
g.drawImage(this.apple, this.applePosition.x - 15, this.applePosition.y - 2);
};
this.checkmax = (x) => {
if (x > this.max) {
return this.min;
} else if (x < this.min) {
return this.max;
}
return x;
};
this.movement = (lastItem) => {
let newPosition;
switch(this.direction) {
case 3:
newPosition = {
x: checkmax(lastItem.x + this.step),
y: lastItem.y
};
break;
case 1:
newPosition = {
x: checkmax(lastItem.x - this.step),
y: lastItem.y
};
break;
case 2:
newPosition = {
x: lastItem.x,
y: checkmax(lastItem.y + this.step)
};
break;
case 0:
newPosition = {
x: lastItem.x,
y: checkmax(lastItem.y - this.step)
};
break;
}
this.directionSet = false;
this.checkDeath(newPosition);
this.currentPosition.push(newPosition);
};
this.snakeHead = (props) => {
switch (this.direction) {
case 0:
return [this.snakeUp, props.x - 9, props.y - 12];
case 1:
return [this.snakeLeft, props.x - 20, props.y - 10];
case 3:
return [this.snakeRight, props.x - 12, props.y - 12];
case 2:
return [this.snakeDown, props.x - 12, props.y - 7];
default:
return [this.snakeDown, props.x - 12, props.y - 7];
}
};
this.drawSnake = () => {
const totalItems = this.currentPosition.length - 1;
g.setColor(0, 1, 0);
this.movement(this.currentPosition[totalItems]);
this.currentPosition.forEach((props, index) => {
if (index-1 === totalItems) {
const head = this.snakeHead(props);
g.drawImage(head[0], head[1], head[2]);
} else {
g.fillCircle(props.x, props.y, 10);
}
});
if (this.currentPosition[totalItems].x === this.applePosition.x && this.currentPosition[totalItems].y === this.applePosition.y) {
this.createApple();
this.increaseDifficulity();
} else {
this.currentPosition.shift();
}
};
this.checkDeath = (newPos) => {
const found = this.currentPosition.find((oldPos) => {
return newPos.x === oldPos.x && newPos.y === oldPos.y;
});
if (found) {
Bangle.buzz();
g.clear();
this.death = true;
}
};
this.boundries = () => {
if (this.currentPosition.x >= this.maxPx) {
this.currentPosition.x = this.maxPx;
}
else if (this.currentPosition.x < 10) {
this.currentPosition.x = 10;
}
if ( this.currentPosition.y >= this.maxPy) {
this.currentPosition.y = this.maxPy;
} else if (this.currentPosition.y < 10) {
this.currentPosition.y = 10;
}
};
this.creatTopScrore = () => {
require("Storage").writeJSON("snek_jd", {
topScore: this.calculateScore()
});
};
this.calculateScore = () => {
return currentPosition.length * this.scoreMultiplier + this.score;
};
this.showDeathScreen = () => {
this.paused = true;
g.setFont('Vector', 25);
g.setColor(1, 0, 0);
g.drawString("GAME OVER",15, 50, "solid");
g.setFont('Vector', 15);
g.setColor(this.textColor, this.textColor, this.textColor);
g.drawString("Score : " + this.calculateScore(), 50, 78, "solid");
let storage = require("Storage").readJSON("snek_jd");
if (storage && storage.topScore) {
if (storage.topScore < this.calculateScore()) {
g.setColor(0, 1, 1);
g.drawString("New top score!", 20, 95, "solid");
g.setFont('Vector', 22);
g.drawString(this.calculateScore(), 20, 115, "solid");
this.creatTopScrore();
} else {
g.setColor(this.textColor, this.textColor, this.textColor);
g.drawString("Top score : " + storage.topScore, 20, 95, "solid");
}
} else {
this.creatTopScrore();
}
g.setFont('Vector', 25);
};
/* Events */
Bangle.on('tap', (data) => {
Bangle.setLCDPower(true);
if (this.death) {
this.showTitleScreen();
} else if (this.titleScreen || this.paused) {
this.restart();
}
});
Bangle.on('accel', (xyz) => {
if (Math.abs(xyz.x) > Math.abs(xyz.y)) {
if (xyz.x < 0) {
if (!this.directionSet && this.direction !== 1) {
Bangle.setLCDPower(true);
this.direction = 3;
}
} else {
if (!this.directionSet && this.direction !== 3) {
Bangle.setLCDPower(true);
this.direction = 1;
}
}
} else {
if (xyz.y < 0) {
if (!this.directionSet && this.direction !== 0) {
Bangle.setLCDPower(true);
this.direction = 2;
}
} else {
if (!this.directionSet && this.direction !== 2) {
Bangle.setLCDPower(true);
this.direction = 0;
}
}
}
this.directionSet = true;
});
this.showTitleScreen = () => {
this.death = false;
g.clear();
g.setColor(0, 1, 0);
g.setFont('Vector', 50);
g.drawString("nek", 70, 15, "solid");
g.drawImage(this.titleScreenImg, 20, 20);
g.fillPoly([
15, 66,
152, 70,
159, 79,
21, 71 ]);
g.setColor(this.textColor, this.textColor, this.textColor);
g.setFont('Vector', 15);
g.drawString("Tilt to turn", 20, 100, "solid");
g.drawString("Tap to start", 20, 120, "solid");
g.setColor(0, 1, 0);
g.setFont('4x6', 3);
g.drawString("Jason de Belle", 5, 145, "solid");
};
/* Graphics */
this.snakeUp = Graphics.createImage(`
XX XX
xx xx
xx xx
xx
xx
xx
xx
xx
xxxxxxxx
xxxx xxxx
xxxxxx xxxxxxx
xxxxxxxxxxxxxxxx
xxxxx xXXx xxxxx
xxxxx XX xxxxx
xxxxx XX xxxxx
xxxxxx xxxx xxxxx
xxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxx
`);
this.snakeDown = Graphics.createImage(`
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxx xxxx xxxxxx
xxxxxx xxxx xxxxxx
xxxx XX xxxxx
xxxx XX xxxxx
xxxx xXXx xxxx
xxxx xXXx xxxx
xXxxxxxxxxxxxx
xXxxxxxxxxxxxx
xxx xxx
xxxx xxxx
xxxx
xx
xx
xx
xx
x x
xx xx
xx xx
`);
this.snakeRight = Graphics.createImage(`
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xXxxxxxx xxxx
xXxxxxxx xxxx
xxxxxxxX xxx
xxxxxxxX xxx xxxx
xxxxxxxxxxXX xxxxxx xxxx
xxxxxxxxxxXX xxxxxx xx
xxxxxxxxxxXXxxxxx xxxxxxxx
xxxxxxxxxxXXxxxxx xxxxxxxx
xxxxxxxxxxxx xxxxx xx
xxxxxxxxxxxx xxxxx xxx
xxxxxxxx xx xxxx
xxxxxxxx xx
xxxxxxxx xxx
xxxxxxxx xxx
xxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxx
xxxxxxxxx
`);
this.snakeLeft = Graphics.createImage(`
xxxxxxxxxx
xxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xXxxxxxxxxxxxx
xX xxxxxxxxxx
x xx xXxxxxxxxx
xx xx xXxxxxxxxx
xx xx xxxx xxXXxxxxxxxx
xxxxxxx xxxxxXXxxxxxxxx
xxxxxxx xxxxxXXxxxxxxxx
xx xx xxxx xxXXxxxxxxxx
xx xxx xxxxxxxxxx
x xxx xxxxxxxxx
xxxx xxxxxxxxx
xxxx xxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxx
xxxxxxxxx
`);
this.apple = Graphics.createImage(`
xxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxx
`);
this.appleLeaf = Graphics.createImage(`
xxxxxx
xxxxxx
xxxx
XXxxxxxxxx
xx
xx
xx
xx
xx
`);
this.titleScreenImg = Graphics.createImage(`
sxxxxxxxs
xxsxxx xxxxxs
xxxxxxxxsxx xxxxsx
xxxxxxxxxxxxxxxsxxs xxxxsxx
xxxxxxxxxxxxxxxxxxxxxsxxxsssxxxxxxsxxxx
xxxxxxxxxxxxxxxxxxxsxxxxsxxs xxsxxx
xxxxxxxxxxxxxxxxxxxxxxxxxsxx xxxsxx
xxxxxxxxxxxxxxxxxxxxx sxxx ssxxsx
xxxxxxxxxxxxxxx xxxxxxs
xxxxxxxxxxxx ssss
xxxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxxxx
xxxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxxxx
xxxxxxxxxx
xxxxxxx
xxx
`);
game();
}
init();

BIN
apps/snek/snek.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -0,0 +1 @@
0.1: First release.

View File

@ -0,0 +1,31 @@
(() => {
const icon = require("heatshrink").decompress(atob("ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA"));
const iconWidth = 18;
function draw() {
g.reset();
if (Bangle.isCharging()) {
g.setColor("#FD0");
g.drawImage(icon, this.x + 1, this.y + 1, {
scale: 0.6875
});
}
}
WIDGETS.chargingStatus = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', (charging) => {
if (charging) {
Bangle.buzz();
WIDGETS.chargingStatus.width = iconWidth;
} else {
WIDGETS.chargingStatus.width = 0;
}
Bangle.drawWidgets(); // re-layout widgets
g.flip();
});
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

2
core

@ -1 +1 @@
Subproject commit 23854083e0c3f83c649073a2d85e8079efc471d3
Subproject commit 59f80bb52a38da12cb272f9106cb3951b49dab2e