Merge branch 'circlesclock_v0.04' of https://github.com/myxor/BangleApps into circlesclock_v0.04

master
Marco Heiming 2022-01-06 11:09:38 +01:00
commit 39ac9002f6
53 changed files with 2691 additions and 154 deletions

View File

@ -16,7 +16,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.39",
"version": "0.40",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",
@ -845,7 +845,7 @@
{
"id": "weather",
"name": "Weather",
"version": "0.14",
"version": "0.15",
"description": "Show Gadgetbridge weather report",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
@ -936,7 +936,7 @@
"id": "widbatpc",
"name": "Battery Level Widget (with percentage)",
"shortName": "Battery Widget",
"version": "0.15",
"version": "0.16",
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
"icon": "widget.png",
"type": "widget",
@ -1324,7 +1324,7 @@
"icon": "gesture.png",
"type": "app",
"tags": "gesture,ai",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"gesture.app.js","url":"gesture.js"},
{"name":".tfnames","url":"gesture-tfnames.js","evaluate":true},
@ -1501,7 +1501,7 @@
{
"id": "gpsinfo",
"name": "GPS Info",
"version": "0.06",
"version": "0.07",
"description": "An application that displays information about altitude, lat/lon, satellites and time",
"icon": "gps-info.png",
"type": "app",
@ -2430,7 +2430,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.05",
"version": "0.06",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
@ -3533,7 +3533,7 @@
"id": "mclockplus",
"name": "Morph Clock+",
"shortName": "Morph Clock+",
"version": "0.02",
"version": "0.03",
"description": "Morphing Clock with more readable seconds and date and additional stopwatch",
"icon": "mclockplus.png",
"type": "clock",
@ -3987,11 +3987,12 @@
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"doztime.app.js","url":"app.js"},
{"name":"doztime.app.js","url":"app-bangle1.js","supports":["BANGLEJS"]},
{"name":"doztime.app.js","url":"app-bangle2.js","supports":["BANGLEJS2"]},
{"name":"doztime.img","url":"app-icon.js","evaluate":true}
]
},
@ -4238,8 +4239,9 @@
{
"id": "antonclk",
"name": "Anton Clock",
"version": "0.03",
"description": "A simple clock using the bold Anton font.",
"version": "0.04",
"description": "A clock using the bold Anton font, optionally showing seconds and date in ISO-8601 format.",
"readme":"README.md",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
@ -4248,8 +4250,10 @@
"allow_emulator": true,
"storage": [
{"name":"antonclk.app.js","url":"app.js"},
{"name":"antonclk.settings.js","url":"settings.js"},
{"name":"antonclk.img","url":"app-icon.js","evaluate":true}
]
],
"data": [{"name":"antonclk.json"}]
},
{
"id": "waveclk",
@ -4488,7 +4492,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.08",
"version":"0.09",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",
@ -5122,6 +5126,24 @@
{"name":"ltherm.img","url":"icon.js","evaluate":true}
]
},
{
"id": "presentor",
"name": "Presentor",
"version": "3.0",
"description": "Use your Bangle to present!",
"icon": "app.png",
"type": "app",
"tags": "tool,bluetooth",
"interface": "interface.html",
"readme":"README.md",
"supports": ["BANGLEJS", "BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"presentor.app.js","url":"app.js"},
{"name":"presentor.img","url":"app-icon.js","evaluate":true},
{"name":"presentor.json","url":"settings.json"}
]
},
{
"id": "slash",
"name": "Slash Watch",
@ -5143,15 +5165,16 @@
{
"id": "promenu",
"name": "Pro Menu",
"version": "0.01",
"description": "Replace Bangle.js 1's built in menu function.",
"version": "0.02",
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
"icon": "icon.png",
"type": "boot",
"tags": "system",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"screenshots": [{"url":"pro-menu-screenshot.png"}],
"storage": [
{"name":"promenu.boot.js","url":"boot.js"},
{"name":"promenu.boot.js","url":"boot.js","supports": ["BANGLEJS"]},
{"name":"promenu.boot.js","url":"bootb2.js","supports": ["BANGLEJS2"]},
{"name":"promenu.img","url":"promenuIcon.js","evaluate":true}
]
},
@ -5389,5 +5412,41 @@
"storage": [
{"name":"touchmenu.boot.js","url":"touchmenu.boot.js"}
]
},
{
"id": "puzzle15",
"name": "15 puzzle",
"version": "0.05",
"description": "A 15 puzzle game with drag gesture interface",
"readme":"README.md",
"icon": "puzzle15.app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "app",
"tags": "game",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"puzzle15.app.js","url":"puzzle15.app.js"},
{"name":"puzzle15.settings.js","url":"puzzle15.settings.js"},
{"name":"puzzle15.img","url":"puzzle15.app-icon.js","evaluate":true}
],
"data": [{"name":"puzzle15.json"}]
},
{
"id": "flipper",
"name": "flipper",
"version": "0.01",
"description": "Switch between dark and light theme and vice versa, combine with pattern launcher and swipe to flip.",
"readme":"README.md",
"screenshots": [{"url":"flipper.png"}],
"icon": "flipper.png",
"type": "app",
"tags": "game",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"storage": [
{"name":"flipper.app.js","url":"flipper.app.js"},
{"name":"flipper.img","url":"flipper.icon.js","evaluate":true}
]
}
]

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Load widgets after setUI so widclk knows when to hide
0.03: Clock now shows day of week under date.
0.04: Clock can optionally show seconds, date optionally in ISO-8601 format, weekdays and uppercase configurable, too.

77
apps/antonclk/README.md Normal file
View File

@ -0,0 +1,77 @@
# Anton Clock - Large font digital watch with seconds and date
Anton clock uses the "Anton" bold font to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
## Features
The basic time representation only shows hours and minutes of the current time. However, Anton clock can show additional information:
* Seconds can be shown, either always or only if the screen is unlocked.
* To help easy recognition, the seconds can be coloured in blue on the Bangle.js 2.
* Date can be shown in three different formats:
* ISO-8601: 2021-12-19
* short local format: 19/12/2021, 19.12.2021
* long local format: DEC 19 2021
* Weekday can be shown (on seconds screen only instead of year)
## Usage
Install Anton clock through the Bangle.js app loader.
Configure it through the default Bangle.js configuration mechanism
(Settings app, "Apps" menu, "Anton clock" submenu).
If you like it, make it your default watch face
(Settings app, "System" menu, "Clock" submenu, select "Anton clock").
## Configuration
Anton clock is configured by the standard settings mechanism of Bangle.js's operating system:
Open the "Settings" app, then the "Apps" submenu and below it the "Anton clock" menu.
You configure Anton clock through several "on/off" switches in two menus.
### The main menu
The main menu contains several settings covering Anton clock in general.
* **Seconds...** - Opens the submenu for configuring the presentation of the current time's seconds.
* **Date** - Format of the date representation. Possible values are
* **Long** - "Long" date format in the current locale. Usually with the month as name, not number.
* **Short** - "Short" date format in the current locale. Usually with the month as number.
* **ISO8601** - Show the date in ISO-8601 format (YYYY-MM-DD), irrespective of the current locale.
* **Show Weekday** - Weekday is shown in the time presentation without seconds.
Weekday name depends on the current locale.
If seconds are shown, the weekday is never shown as there is not enough space on the watch face.
* **Uppercase** - Weekday name and month name in the long format are converted to upper case letters.
This can improve readability.
* **Vector font** - Use the built-in vector font for dates and weekday.
This can improve readability.
Otherwise, a scaled version of the built-in 6x8 pixels font is used.
### The "Seconds" submenu
The "Seconds" submenu configures how (and if) seconds are shown on the "Anton" watch face.
* **Show** - Configure when the seconds should be shown at all:
* **Never** - Seconds are never shown.
In this case, hour and minute are a bit more centered on the screen and the clock will always only update every minute.
This saves battery power.
* **Unlocked** - Seconds are shown if the display is unlocked.
On locked displays, only hour, minutes, date and optionally the weekday are shown.
_This option is highly recommended on the Bangle.js 2!_
* **Always** - Seconds are _always_ shown, irrespective of the display's unlock state.
_Enabling this option increases power consumption as the watch face will update once per second instead of once per minute._
* **With ":"** - If enabled, a colon ":" is prepended to the seconds.
This resembles the usual time representation "hh:mm:ss", even though the seconds are printed on a separate line.
* **Color** - If enabled, seconds are shown in blue instead of black.
If the date is shown on the seconds screen, it is colored read instead of black.
This make the visual orientation much easier on the watch face.
* **Date** - It is possible to show the date together with the seconds:
* **No** - Date is _not_ shown in the seconds screen.
In this case, the seconds are centered below hour and minute.
* **Year** - Date is shown with day, month, and year. If "Date" in the main settings is configured to _ISO8601_, this is used here, too. Otherwise, the short local format is used.
* **Weekday** - Date is shown with day, month, and weekday.
The date is coloured in red if the "Coloured" option is chosen.
## Compatibility
Anton clock makes use of core Bangle.js 2 features (coloured display, display lock state). It also runs on the Bangle.js 1 but these features are not available there due to hardware restrictions.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 1.6 KiB

99
apps/antonclk/settings.js Normal file
View File

@ -0,0 +1,99 @@
// Settings menu for the enhanced Anton clock
(function(back) {
var FILE = "antonclk.json";
// Load settings
var settings = Object.assign({
secondsOnUnlock: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Helper method which uses int-based menu item for set of string values
function stringItems(startvalue, writer, values) {
return {
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => values[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
writer(values[v]);
writeSettings();
}
};
}
// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values) {
return stringItems(settings[name], v => settings[name] = v, values);
}
var mainmenu = {
"": {
"title": "Anton clock"
},
"< Back": () => back(),
"Seconds...": () => E.showMenu(secmenu),
"Date": stringInSettings("dateOnMain", ["Short", "Long", "ISO8601"]),
"Show Weekday": {
value: (settings.weekDay !== undefined ? settings.weekDay : true),
format: v => v ? "On" : "Off",
onchange: v => {
settings.weekDay = v;
writeSettings();
}
},
"Uppercase": {
value: (settings.upperCase !== undefined ? settings.upperCase : false),
format: v => v ? "On" : "Off",
onchange: v => {
settings.upperCase = v;
writeSettings();
}
},
"Vector font": {
value: (settings.vectorFont !== undefined ? settings.vectorFont : false),
format: v => v ? "On" : "Off",
onchange: v => {
settings.vectorFont = v;
writeSettings();
}
},
};
// Submenu
var secmenu = {
"": {
"title": "Show seconds..."
},
"< Back": () => E.showMenu(mainmenu),
"Show": stringInSettings("secondsMode", ["Never", "Unlocked", "Always"]),
"With \":\"": {
value: (settings.secondsWithColon !== undefined ? settings.secondsWithColon : false),
format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsWithColon = v;
writeSettings();
}
},
"Color": {
value: (settings.secondsColoured !== undefined ? settings.secondsColoured : false),
format: v => v ? "On" : "Off",
onchange: v => {
settings.secondsColoured = v;
writeSettings();
}
},
"Date": stringInSettings("dateOnSecs", ["No", "Year", "Weekday"])
};
// Actually display the menu
E.showMenu(mainmenu);
});
// end of file

View File

@ -43,3 +43,4 @@
0.37: Remove Quiet Mode settings: now handled by Quiet Mode Schedule app
0.38: Option to log to file if settings.log==2
0.39: Fix passkey support (fix https://github.com/espruino/Espruino/issues/2035)
0.40: Bootloader now rebuilds for new firmware versions

View File

@ -6,11 +6,11 @@ var s = require('Storage').readJSON('setting.json',1)||{};
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
var boot = "";
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/);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)!=${CRC})`;
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})`;
} else {
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/));
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))!=${CRC})`;
var CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
}
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
boot += `E.setFlags({pretokenise:1});\n`;

View File

@ -3,3 +3,4 @@
0.03: Add setting to start week on Sunday
0.04: Add setting to switch color schemes. On Bangle 2 non-dithering colors will be used by default. Use localized names for months and days of the week (Language app needed).
0.05: Update calendar weekend colors for start on Sunday
0.06: Use larger font for dates

View File

@ -206,6 +206,8 @@ function drawCalendar(date) {
y2 - 1
);
}
require("Font8x12").add(Graphics);
g.setFont("8x12", fontSize);
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
g.drawString(
(day > 50 ? day - 50 : day).toString(),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -11,4 +11,4 @@ The year itself begins on the December solstice. Because that always happens, th
The epoch (year numbering) begins in the last year when the perihelion coincided with the June solstice, near the beginning of the Holocene era. That astronomical basis makes the calendar free from politics, religion, or geography.
While the year number remains cardinal, BTN5 toggles between cardinal and ordinal for the rest of the calendar segments. BTN4 adds or removes a quickly changing digit to or from the clock.
While the year number remains cardinal, tapping on the right side of the watch face toggles between cardinal and ordinal for the rest of the calendar segments. Tapping on the left adds or removes a quickly changing digit to or from the clock.

244
apps/doztime/app-bangle2.js Normal file
View File

@ -0,0 +1,244 @@
// Positioning values for graphics buffers
const g_height = 80; // total graphics height
const g_x_off = 0; // position from left was 16, then 8 here
const g_y_off = (184 - g_height)/2; // vertical center for graphics region was 240
const g_width = 240 - 2 * g_x_off; // total graphics width
const g_height_d = 28; // height of date region was 32
const g_y_off_d = 0; // y position of date region within graphics region
const spacing = 0; // space between date and time in graphics region
const g_y_off_t = g_y_off_d + g_height_d + spacing; // y position of time within graphics region
const g_height_t = 44; // height of time region was 48
// Other vars
const A1 = [30,30,30,30,31,31,31,31,31,31,30,30];
const B1 = [30,30,30,30,30,31,31,31,31,31,30,30];
const B2 = [30,30,30,30,31,31,31,31,31,30,30,30];
const timeColour = "#ffffff";
const dateColours = ["#ff0000","#ffa500","#ffff00","#00b800","#8383ff","#ff00ff","#ff0080"]; //blue was 0000ff
const calen10 = {"size":26,"pt0":[18-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for usual calendar line ft w 32, 32-g, step 20
const calen7 = {"size":26,"pt0":[48-g_x_off,16],"step":[16,0],"dx":-4.5,"dy":-4.5}; // positioning for S-day calendar line ft w 32, 62-g, step 20
const time5 = {"size":42,"pt0":[39-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for lull time line ft w 48, 64-g, step 30
const time6 = {"size":42,"pt0":[26-g_x_off,24],"step":[26,0],"dx":-6.5,"dy":-6.5}; // positioning for twinkling time line ft w 48, 48-g, step 30
const baseYear = 11584;
const baseDate = Date(2020,11,21); // month values run from 0 to 11
let accum = new Date(baseDate.getTime());
let sequence = [];
let timeActiveUntil;
let addTimeDigit = false;
let dateFormat = false;
let lastX = 999999999;
let res = {};
//var last_time_log = 0;
var drawtime_timeout;
// Date and time graphics buffers
var dateColour = "#ffffff"; // override later
var timeColour2 = timeColour;
var g_d = Graphics.createArrayBuffer(g_width,g_height_d,1,{'msb':true});
var g_t = Graphics.createArrayBuffer(g_width,g_height_t,1,{'msb':true});
// Set screen mode and function to write graphics buffers
//Bangle.setLCDMode();
g.clear(); // start with blank screen
g.flip = function()
{
g.setBgColor(0,0,0);
g.setColor(dateColour);
g.drawImage(
{
width:g_width,
height:g_height_d,
buffer:g_d.buffer
}, g_x_off, g_y_off + g_y_off_d);
g.setColor(timeColour2);
g.drawImage(
{
width:g_width,
height:g_height_t,
buffer:g_t.buffer
}, g_x_off, g_y_off + g_y_off_t);
};
setWatch(function(){ modeTime(); }, BTN, {repeat:true} ); //was BTN1
setWatch(function(){ Bangle.showLauncher(); }, BTN, { repeat: false, edge: "falling" }); //was BTN2
//setWatch(function(){ modeWeather(); }, BTN3, {repeat:true});
//setWatch(function(){ toggleTimeDigits(); }, BTN4, {repeat:true});
//setWatch(function(){ toggleDateFormat(); }, BTN5, {repeat:true});
Bangle.on('touch', function(button, xy) { //from Gordon Williams
if (button==1) toggleTimeDigits();
if (button==2) toggleDateFormat();
});
function buildSequence(targ){
for(let i=0;i<targ.length;++i){
sequence.push(new Date(accum.getTime()));
accum.setDate(accum.getDate()+targ[i]);
}
}
buildSequence(B2);
buildSequence(B2);
buildSequence(A1);
buildSequence(B1);
buildSequence(B2);
buildSequence(B2);
buildSequence(A1);
buildSequence(B1);
buildSequence(B2);
buildSequence(B2);
buildSequence(A1);
buildSequence(B1);
buildSequence(B2);
function getDate(dt){
let index = sequence.findIndex(n => n > dt)-1;
let year = baseYear+parseInt(index/12);
let month = index % 12;
let day = parseInt((dt-sequence[index])/86400000);
let colour = dateColours[day % 6];
if(day==30){ colour=dateColours[6]; }
return({"year":year,"month":month,"day":day,"colour":colour});
}
function toggleTimeDigits(){
addTimeDigit = !addTimeDigit;
modeTime();
}
function toggleDateFormat(){
dateFormat = !dateFormat;
modeTime();
}
function formatDate(res,dateFormat){
let yyyy = res.year.toString(12);
calenDef = calen10;
if(!dateFormat){ //ordinal format
let mm = ("0"+(res.month+1).toString(12)).substr(-2);
let dd = ("0"+(res.day+1).toString(12)).substr(-2);
if(res.day==30){
calenDef = calen7;
let m = ((res.month+1).toString(12)).substr(-2);
return(yyyy+"-"+"S"+m); // ordinal format
}
return(yyyy+"-"+mm+"-"+dd);
}
let m = res.month.toString(12); // cardinal format
let w = parseInt(res.day/6);
let d = res.day%6;
//return(yyyy+"-"+res.month+"-"+w+"-"+d);
return(yyyy+"-"+m+"-"+w+"-"+d);
}
function writeDozTime(text,def){
let pts = def.pts;
let x=def.pt0[0];
let y=def.pt0[1];
g_t.clear();
g_t.setFont("Vector",def.size);
for(let i in text){
if(text[i]=="a"){ g_t.setFontAlign(0,0,2); g_t.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s are new
else if(text[i]=="b"){ g_t.setFontAlign(0,0,2); g_t.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s are new
else{ g_t.setFontAlign(0,0,0); g_t.drawString(text[i],x,y); }
x = x+def.step[0];
y = y+def.step[1];
}
}
function writeDozDate(text,def,colour){
dateColour = colour;
let pts = def.pts;
let x=def.pt0[0];
let y=def.pt0[1];
g_d.clear();
g_d.setFont("Vector",def.size);
for(let i in text){
if(text[i]=="a"){ g_d.setFontAlign(0,0,2); g_d.drawString("2",x+2+def.dx,y+1+def.dy); } //+1s new
else if(text[i]=="b"){ g_d.setFontAlign(0,0,2); g_d.drawString("3",x+2+def.dx,y+1+def.dy); } //+1s new
else{ g_d.setFontAlign(0,0,0); g_d.drawString(text[i],x,y); }
x = x+def.step[0];
y = y+def.step[1];
}
}
// Functions for time mode
function drawTime()
{
let dt = new Date();
let date = "";
let timeDef;
let x = 0;
dt.setDate(dt.getDate());
if(addTimeDigit){
x =
10368*dt.getHours()+172.8*dt.getMinutes()+2.88*dt.getSeconds()+0.00288*dt.getMilliseconds();
let msg = "00000"+Math.floor(x).toString(12);
let time = msg.substr(-5,3)+"."+msg.substr(-2);
let wait = 347*(1-(x%1));
timeDef = time6;
} else {
x =
864*dt.getHours()+14.4*dt.getMinutes()+0.24*dt.getSeconds()+0.00024*dt.getMilliseconds();
let msg = "0000"+Math.floor(x).toString(12);
let time = msg.substr(-4,3)+"."+msg.substr(-1);
let wait = 4167*(1-(x%1));
timeDef = time5;
}
if(lastX > x){ res = getDate(dt); } // calculate date once at start-up and once when turning over to a new day
date = formatDate(res,dateFormat);
if(dt<timeActiveUntil)
{
// Write to background buffers, then display on screen
writeDozDate(date,calenDef,res.colour);
writeDozTime(time,timeDef);
g.flip();
// Ready next interval
drawtime_timeout = setTimeout(drawTime,wait);
}
else
{
// Clear screen
g_d.clear();
g_t.clear();
g.flip();
}
lastX = x;
}
function modeTime()
{
timeActiveUntil = new Date();
timeActiveUntil.setDate(timeActiveUntil.getDate());
timeActiveUntil.setSeconds(timeActiveUntil.getSeconds()+86400);
if (typeof drawtime_timeout !== 'undefined')
{
clearTimeout(drawtime_timeout);
}
drawTime();
}
Bangle.loadWidgets();
Bangle.drawWidgets();
// Functions for weather mode - TODO
// function drawWeather() {}
// function modeWeather() {}
// Start time on twist
Bangle.on('twist',function() {
modeTime();
});
// Time fix with GPS
function fixTime() {
Bangle.on("GPS",function cb(g) {
Bangle.setGPSPower(0,"time");
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()>2200)) {
} else {
// We have a GPS time. Set time
setTime(g.time.getTime()/1000);
}
});
Bangle.setGPSPower(1,"time");
setTimeout(fixTime, 10*60*1000); // every 10 minutes
}
// Start time fixing with GPS on next 10 minute interval
setTimeout(fixTime, ((60-(new Date()).getMinutes()) % 10) * 60 * 1000);

1
apps/flipper/ChangeLog Normal file
View File

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

20
apps/flipper/README.md Normal file
View File

@ -0,0 +1,20 @@
# Flipper
![](flipper.png)
*A utility to switch from the dark to the light theme and vice versa*
* If the current theme is dark it will switch to the light theme
* If the current theme is light it will switch to the dark theme
* Combine with the awesome pattern launcher and it saves loads of time
## Demo Video
There's no screenshot but there is a [demo video.](https://espruino.microco.sm/api/v1/files/9caa1afef7e4cce1d9b518af2dd271f1a57c5ecc.mp4)
## Future Enhancements
* Nothing left to add
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

View File

@ -0,0 +1,39 @@
const storage = require('Storage');
let settings = storage.readJSON('setting.json', 1);
function cl(x) { return g.setColor(x).getColor(); }
function upd(th) {
g.theme = th;
settings.theme = th;
storage.write('setting.json', settings);
delete g.reset;
g._reset = g.reset;
g.reset = function(n) { return g._reset().setColor(th.fg).setBgColor(th.bg); };
g.clear = function(n) { if (n) g.reset(); return g.clearRect(0,0,g.getWidth(),g.getHeight()); };
g.clear(1);
};
function flipTheme() {
if (!g.theme.dark) {
upd({
fg:cl("#fff"), bg:cl("#000"),
fg2:cl("#0ff"), bg2:cl("#000"),
fgH:cl("#fff"), bgH:cl("#00f"),
dark:true
});
} else {
upd({
fg:cl("#000"), bg:cl("#fff"),
fg2:cl("#000"), bg2:cl("#cff"),
fgH:cl("#000"), bgH:cl("#0ff"),
dark:false
});
}
}
Bangle.loadWidgets();
Bangle.drawWidgets();
flipTheme();
setTimeout(load, 20);

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4X/AAO/mMUzs975K+ggLKysUAYNVqoLFitUoAKBqtQBYkJBIQABqwLEgQLEqtABggJDqkVBaoNCBZQwEgILWgoJENYsVBIcVBYpDEgpSIBYMBKQg6CuogCBY1UgoLCXAQLDqAsDBYhSBqEJHAoLDoEBcQ4LBEwILIMooLdIg4LaVoyaGERLcFao4LIdRAACYYUQBY5RKAH4Ar"));

BIN
apps/flipper/flipper.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 B

View File

@ -2,4 +2,5 @@
0.03: Show number of satellites while waiting for fix
0.04: Add Maidenhead readout of GPS location
0.05: Refactor to use 'layout' library for multi-device support
0.06: Added number of satellites in view and fixed crash with GPS time
0.06: Add number of satellites in view and fix crash with GPS time
0.07: Resolve one FIFO_FULL case and exit App with button press

View File

@ -19,6 +19,7 @@ var lastFix = {
var SATinView = 0;
var nofBD = 0;
var nofGP = 0;
var listenerGPSraw = 1;
function formatTime(now) {
if (now == undefined) {
@ -93,6 +94,10 @@ function onGPS(fix) {
}
lastFix = fix;
if (fix.fix) {
if (listenerGPSraw == 1) {
Bangle.removeListener('GPS-raw', onGPSraw);
listenerGPSraw = 0;
}
var locale = require("locale");
var satellites = fix.satellites;
var maidenhead = getMaidenHead(fix.lat,fix.lon);
@ -104,6 +109,10 @@ function onGPS(fix) {
layout.sat.label = "Satellites: "+satellites;
layout.maidenhead.label = "Maidenhead: "+maidenhead;
} else {
if (listenerGPSraw == 0) {
Bangle.on('GPS-raw', onGPSraw);
listenerGPSraw = 1;
}
layout.sat.label = fix.satellites;
layout.progress.label = "in view: " + SATinView;
}
@ -111,15 +120,26 @@ function onGPS(fix) {
}
function onGPSraw(nmea) {
if (nmea.slice(3,6) == "GSV") {
// console.log(nmea);
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
SATinView = nofBD + nofGP;
}
if (nmea.slice(0,7) == "$BDGSV,") nofBD = Number(nmea.slice(11,13));
if (nmea.slice(0,7) == "$GPGSV,") nofGP = Number(nmea.slice(11,13));
SATinView = nofBD + nofGP;
}
Bangle.loadWidgets();
Bangle.drawWidgets();
Bangle.on('GPS', onGPS);
Bangle.on('GPS-raw', onGPSraw);
function exitApp() {
Bangle.setGPSPower(0, "app");
Bangle.removeListener('GPS-raw', onGPSraw);
Bangle.removeListener('GPS', onGPS);
load();
}
setWatch(_=>exitApp(), BTN1);
if (global.BTN2) {
setWatch(_=>exitApp(), BTN2);
setWatch(_=>exitApp(), BTN3);
}

View File

@ -5,4 +5,5 @@
0.05: Additional icons for (1) charging and (2) bat < 30%.
0.06: Fix - Alarm disabled, if clock was closed.
0.07: Added settings to adjust data that is shown for each row.
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
0.09: Tab anywhere to open the launcher.

View File

@ -8,13 +8,15 @@ To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerda
* LCARS Style watch face.
* Full screen mode - widgets are still loaded.
* Supports multiple screens with different data.
* Tab anywhere to open the launcher.
* [Screen 1] Date + Time + Lock status.
* [Screen 1] Shows randomly images of real planets.
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
* [Screen 1] Swipe up/down to activate an alarm.
* [Screen 1] Shows 3 customizable datapoints on the first screen.
* [Screen 1] The lower orange line indicates the battery level.
* [Screen 2] Display month graphs for steps + hrm on the second screen.
* [Screen 2] Display graphs for steps + hrm on the second screen.
* [Screen 2] Switch between day/month via swipe up/down.
## Multiple screens support

View File

@ -24,6 +24,7 @@ let cOrange = "#FF9900";
let cPurple = "#FF00DC";
let cWhite = "#FFFFFF";
let cBlack = "#000000";
let cGrey = "#9E9E9E";
/*
* Global lcars variables
@ -147,14 +148,17 @@ function printData(key, y, c){
}
g.setColor(c);
g.fillRect(79, y-2, 87 ,y+18);
g.setFontAlign(1,-1,0);
g.drawString(value, 131, y);
g.setColor(c);
g.setFontAlign(-1,-1,0);
g.fillRect(133, y-2, 165 ,y+18);
g.fillCircle(161, y+8, 10);
g.setColor(cBlack);
g.drawString(text, 135, y);
g.setColor(c);
g.setFontAlign(1,-1,0);
g.drawString(value, 130, y);
}
function drawHorizontalBgLine(color, x1, x2, y, h){
@ -191,13 +195,14 @@ function drawState(){
return;
}
g.clearRect(20, 93, 77, 170);
g.setColor(cWhite);
var bat = E.getBattery();
var current = new Date();
var hours = current.getHours();
g.clearRect(20, 93, 75, 170);
g.setFontAlign(0, 0, 0);
g.setFontAntonioMedium();
if(!isAlarmEnabled()){
var bat = E.getBattery();
var current = new Date();
var hours = current.getHours();
var iconImg =
Bangle.isCharging() ? iconCharging :
bat < 30 ? iconNoBattery :
@ -206,16 +211,16 @@ function drawState(){
hours % 4 == 1 ? iconMars :
hours % 4 == 2 ? iconMoon :
iconEarth;
g.drawImage(iconImg, 29, 104);
g.drawImage(iconImg, 24, 118);
g.setColor(cWhite);
g.drawString("STATUS", 24+25, 108);
} else {
// Alarm within symbol
g.setFontAntonioMedium();
g.setFontAlign(0, 0, 0);
g.setColor(cOrange);
g.drawString("ALARM", 29+25, 107);
g.drawString("ALARM", 24+25, 108);
g.setColor(cWhite);
g.setFontAntonioLarge();
g.drawString(getAlarmMinutes(), 29+25, 107+35);
g.drawString(getAlarmMinutes(), 24+25, 108+35);
}
g.setFontAlign(-1, -1, 0);
@ -236,7 +241,7 @@ function drawPosition0(){
var bat = E.getBattery() / 100.0;
var batX2 = parseInt((172 - 35) * bat + 35);
drawHorizontalBgLine(cOrange, 35, batX2, 171, 5);
drawHorizontalBgLine(cPurple, batX2+10, 172, 171, 5);
drawHorizontalBgLine(cGrey, batX2+10, 172, 171, 5);
// Draw logo
drawLock();
@ -247,7 +252,7 @@ function drawPosition0(){
var currentDate = new Date();
var timeStr = locale.time(currentDate,1);
g.setFontAntonioLarge();
g.drawString(timeStr, 28, 10);
g.drawString(timeStr, 29, 10);
// Write date
g.setColor(cWhite);
@ -255,7 +260,7 @@ function drawPosition0(){
var dayStr = locale.dow(currentDate, true).toUpperCase();
dayStr += " " + currentDate.getDate();
dayStr += " " + currentDate.getFullYear();
g.drawString(dayStr, 29, 56);
g.drawString(dayStr, 32, 56);
// Draw data
g.setFontAlign(-1, -1, 0);
@ -401,7 +406,7 @@ function draw(){
* Step counter via widget
*/
function getSteps() {
var steps = 0
var steps = 0;
try {
health = require("health");
} catch(ex) {
@ -553,6 +558,10 @@ Bangle.on("drag", e => {
}
});
Bangle.on("touch", e => {
Bangle.showLauncher();
});
/*
* Lets start widgets, listen for btn etc.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,2 +1,3 @@
0.01: Created app
0.02: Use Bangle.setUI for button/launcher handling
0.03: Allow widgets to detect this is a clock

View File

@ -304,15 +304,14 @@ Bangle.on('lcdPower',function(on) {
});
g.clear();
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
Bangle.drawWidgets();
// Update time once a second
timeInterval = setInterval(showTime, 1000);
showTime();
// Show launcher when button pressed
Bangle.setUI("clock");
// Start stopwatch when BTN3 is pressed
setWatch(() => {swInterval=setInterval(stopWatch, 1000);stopWatch();}, BTN3, {repeat:false,edge:"falling"});
B3 = 1; // BTN3 is bound to start the stopwatch

8
apps/presentor/ChangeLog Normal file
View File

@ -0,0 +1,8 @@
0.1: Start of app.
0.5: BLE keyboard functionality.
1.0: BLE mouse functionality to scroll back/forward.
1.5: Added accelerator style mouse.
2.0: Added touchpad style mouse.
2.1: Initial internal git(hub) release. Added icon and such.
2.2: Begin work on presentation parts.
3.0: Presentation parts!

2
apps/presentor/README.md Normal file
View File

@ -0,0 +1,2 @@
# Presentor
Use your Bangle to present!

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4ASlgADGmIxwLV4wqGQowfWZQwjKw4wJF7ghBmVWmQlELYoweFwYABGAwrHF7IuFGAwrIF7AuHMJADGF0AwHAYovWFxaSHADQuDEgIADYZYucAQOB1fQ1eBmQwiFwlX6AAE1gqBGD6MEmQqBwIICwIGB0rDeWYksFAIuBz+fvQHC6D0dcQssEwIuB4fC4V0M4VXF7YuFDYLqBlnDF4WeHAYugL5N6L4I4BF0IvB0vQvdQGAJeBY4YucmQxEAgJgBvYOBdwYQDFzUzmZ/EllXFIKKBAYVXBwSMaller7fFAoKSBAAOBFQK7dPoJfFEoYADRjgmEeIzFFdUQAOdUIumdRLtDAAIufdRQKCr8zCQjqmE4NeF4YudWxpqCFyovBK5LqiF6DqdR6D0BdTYwJGg5sBdTQwKEAIwHdTQwMSpQueYaAufGBouiGBYukGBIumGA4uoGAouqGAYABF1QAl"))

471
apps/presentor/app.js Normal file
View File

@ -0,0 +1,471 @@
// Presentor by 7kasper (Kasper Müller)
// Version 3.0
const SpecialReport = new Uint8Array([
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x02, // USAGE (Mouse)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // REPORT_ID (1)
0x09, 0x01, // USAGE (Pointer)
0xa1, 0x00, // COLLECTION (Physical)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x05, // USAGE_MAXIMUM (Button 5)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x38, // USAGE (Wheel)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x03, // REPORT_COUNT (3)
0x81, 0x06, // INPUT (Data,Var,Rel)
0x05, 0x0c, // USAGE_PAGE (Consumer Devices)
0x0a, 0x38, 0x02, // USAGE (AC Pan)
0x15, 0x81, // LOGICAL_MINIMUM (-127)
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x06, // INPUT (Data,Var,Rel)
0xc0, // END_COLLECTION
0xc0, // END_COLLECTION
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x02, // REPORT_ID (2)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x75, 0x08, // REPORT_SIZE (8)
0x95, 0x01, // REPORT_COUNT (1)
0x81, 0x01, // INPUT (Cnst,Ary,Abs)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x73, // USAGE_MAXIMUM (Keyboard F24)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x73, // LOGICAL_MAXIMUM (115)
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x00, // INPUT (Data,Ary,Abs)
0xc0 // END_COLLECTION
]);
const MouseButton = {
NONE : 0,
LEFT : 1,
RIGHT : 2,
MIDDLE : 4,
BACK : 8,
FORWARD: 16
};
const kb = require("ble_hid_keyboard");
const Layout = require("Layout");
const Locale = require("locale");
let mainLayout = new Layout({
'type': 'v',
filly: 1,
c: [
{
type: 'txt',
font: '6x8',
label: 'Presentor',
valign: -1,
halign: 0,
col: g.theme.fg1,
// bgCol: g.theme.bg2,
bgCol: '#00F',
fillx: 1,
}, {
type: 'h',
fillx: 1,
c: [
{
type: 'txt',
font: '15%',
label: '00:00',
id: 'Time',
halign: -1,
pad: 3
}, {
fillx: 1
}, {
type: 'txt',
font: '15%',
label: '00:00',
id: 'Timer',
halign: 1,
pad: 3
}
]
}, {
type: 'txt',
font: '10%',
label: '+00:00',
id: 'RestTime',
col: '#fff'
}, {
type: 'txt',
font: '10%',
label: '--------------'
}, {
type: 'txt',
font: '15%',
label: 'Presenting',
id: 'Subject'
}, {
type: 'txt',
font: '6x8',
label: 'Swipe up to start the time.',
id: 'Notes',
col: '#ff0',
fillx: 1,
filly: 1,
valign: 1
}
]
}, {lazy:true});
let settings = {pparts: [], sversion: 0};
let HIDenabled = true;
// Application variables
let pparti = -1;
let ppartBuzzed = false;
let restBuzzed = false;
let lastx = 0;
let lasty = 0;
// Mouse states
let holding = false;
let trackPadMode = false;
// Timeout IDs.
let timeoutId = -1;
let timeoutHolding = -1;
let timeoutDraw = -1;
let homeRoll = 0;
let homePitch = 0;
let mCal = 0;
let mttl = 0;
let cttl = 0;
// BT helper.
let clearToSend = true;
// Presentation Timers
let ptimers = [];
function delay(t, v) {
return new Promise((resolve) => {
setTimeout(resolve, t)
});
}
function formatTimePart(time) {
time = Math.floor(Math.abs(time));
return time < 10 ? `0${time}` : `${time}`;
}
function formatTime(time, doPlus) {
if (time == Infinity) return ' --:-- ';
return `${time < 0 ? '-' : (doPlus ? '+' : '')}${formatTimePart(time/60)}:${formatTimePart(time%60)}`;
}
function loadSettings() {
settings = require("Storage").readJSON('presentor.json');
for (let i = 0; i < settings.pparts.length; i++) {
ptimers[i] = {
active: false,
tracked: -1,
left: settings.pparts[i].minutes * 60 + settings.pparts[i].seconds
};
}
}
function getCurrentTimer() {
if (pparti < 0) return Infinity;
if (!settings.pparts || pparti >= settings.pparts.length) return Infinity;
if (ptimers[pparti].tracked == -1) return Infinity;
ptimers[pparti].left -= (getTime() - ptimers[pparti].tracked);
ptimers[pparti].tracked = getTime();
// if we haven't buzzed yet and timer became negative just buzz here.
// TODO better place?
if (ptimers[pparti].left <= 0 && !ppartBuzzed) {
Bangle.buzz(400)
.then(() => delay(400))
.then(() => Bangle.buzz(400));
ppartBuzzed = true;
}
return ptimers[pparti].left;
}
function getRestTime() {
let rem = 0;
// Add all remaining time from previous presentation parts.
for (let i = 0; i < pparti; i++) {
rem += ptimers[i].left;
}
if (pparti >= 0 && pparti < ptimers.length && ptimers[pparti].left < 0) {
rem += ptimers[pparti].left;
}
// if we haven't buzzed yet and timer became negative just buzz here.
// TODO better place?
if (rem < 0 && !restBuzzed) {
Bangle.buzz(200)
.then(() => delay(400))
.then(() => Bangle.buzz(200))
.then(() => delay(400))
.then(() => Bangle.buzz(200));
restBuzzed = true;
}
return rem;
}
function drawMainFrame() {
var d = new Date();
// update time
mainLayout.Time.label = Locale.time(d,1);
// update timer
mainLayout.Timer.label = formatTime(getCurrentTimer());
let restTime = getRestTime();
mainLayout.RestTime.label = formatTime(restTime, true);
mainLayout.RestTime.col = restTime < 0 ? '#f00' : (restTime > 0 ? '#0f0' : '#fff');
mainLayout.render();
// schedule a draw for the next minute
if (timeoutDraw != -1) clearTimeout(timeoutDraw);
timeoutDraw = setTimeout(function() {
timeoutDraw = -1;
drawMainFrame();
}, 1000 - (Date.now() % 1000));
}
function drawMain() {
g.clear();
mainLayout.forgetLazyState();
drawMainFrame();
// mainLayout.render();
// E.showMessage('Presentor');
}
function doPPart(r) {
pparti += r;
if (pparti < 0) {
pparti = -1;
mainLayout.Subject.label = 'PAUSED';
mainLayout.Notes.label = 'Swipe up to start again.';
return;
}
if (!settings.pparts || pparti >= settings.pparts.length) {
pparti = settings.pparts.length;
mainLayout.Subject.label = 'FINISHED';
mainLayout.Notes.label = 'Good Job!';
return;
}
let ppart = settings.pparts[pparti];
mainLayout.Subject.label = ppart.subject;
mainLayout.Notes.label = ppart.notes;
ptimers[pparti].tracked = getTime();
// We haven't buzzed if there was time left.
ppartBuzzed = ptimers[pparti].left <= 0;
// Always reset buzzstate for the rest timer.
restBuzzed = getRestTime() < 0;
drawMainFrame();
}
NRF.setServices(undefined, { hid : SpecialReport });
// TODO: figure out how to detect HID.
NRF.on('HID', function() {
HIDenabled = true;
});
function moveMouse(x,y,b,wheel,hwheel,callback) {
if (!HIDenabled) return;
if (!b) b = 0;
if (!wheel) wheel = 0;
if (!hwheel) hwheel = 0;
NRF.sendHIDReport([1,b,x,y,wheel,hwheel,0,0], function() {
if (callback) callback();
});
}
// function getSign(x) {
// return ((x > 0) - (x < 0)) || +x;
// }
function scroll(wheel,hwheel,callback) {
moveMouse(0,0,0,wheel,hwheel,callback);
}
// Single click a certain button (immidiatly release).
function clickMouse(b, callback) {
if (!HIDenabled) return;
NRF.sendHIDReport([1,b,0,0,0,0,0,0], function() {
NRF.sendHIDReport([1,0,0,0,0,0,0,0], function() {
if (callback) callback();
});
});
}
function pressKey(keyCode, modifiers, callback) {
if (!HIDenabled) return;
if (!modifiers) modifiers = 0;
NRF.sendHIDReport([2, modifiers,0,keyCode,0,0,0,0], function() {
NRF.sendHIDReport([2,0,0,0,0,0,0,0], function() {
if (callback) callback();
});
});
}
function handleAcc(acc) {
let rRoll = acc.y * -50;
let rPitch = acc.x * -100;
if (mCal > 10) {
//console.log("x: " + (rRoll - homeRoll) + " y:" + (rPitch - homePitch));
moveMouse(acc.y * -50 - homeRoll, acc.x * -100 - homePitch);
} else {
//console.log("homeroll: " +homeRoll +"homepitch: " + homePitch);
homeRoll = rRoll * 0.7 + homeRoll * 0.3;
homePitch = rPitch * 0.7 + homePitch * 0.3;
mCal = mCal + 1;
}
}
Bangle.on('lock', function(on) {
if (on && holding) {
Bangle.setLocked(false);
Bangle.setLCDPower(1);
}
});
function startHolding() {
pressKey(kb.KEY.F10);
holding = true;
Bangle.buzz();
E.showMessage('Holding');
Bangle.on('accel', handleAcc);
Bangle.setLCDPower(1);
}
function stopHolding() {
clearTimeout(timeoutId);
if (holding) {
pressKey(kb.KEY.F10);
homePitch = 0;
homeRoll = 0;
holding = false;
mCal = 0;
Bangle.removeListener('accel', handleAcc);
Bangle.buzz();
drawMain();
} else {
timeoutId = setTimeout(drawMain, 1000);
}
clearTimeout(timeoutHolding);
timeoutHolding = -1;
}
Bangle.on('drag', function(e) {
if (cttl == 0) { cttl = getTime(); }
if (trackPadMode) {
if (lastx + lasty == 0) {
lastx = e.x;
lasty = e.y;
mttl = getTime();
}
if (clearToSend) {
clearToSend = false;
let difX = e.x - lastx, difY = e.y - lasty;
let dT = getTime() - mttl;
let vX = difX / dT, vY = difY / dT;
//let qX = getSign(difX) * Math.pow(Math.abs(difX), 1.2);
//let qY = getSign(difY) * Math.pow(Math.abs(difY), 1.2);
let qX = difX + 0.02 * vX, qY = difY + 0.02 * vY;
moveMouse(qX, qY, 0, 0, 0, function() {
setTimeout(function() {clearToSend = true;}, 50);
});
lastx = e.x;
lasty = e.y;
mttl = getTime();
console.log("Dx: " + (qX) + " Dy: " + (qY));
}
if (!e.b) {
// short press
if (getTime() - cttl < 0.2) {
clickMouse(MouseButton.LEFT);
console.log("click left");
}
// longer press in center
else if (getTime() - cttl < 0.6 && e.x > g.getWidth()/4 && e.x < 3 * g.getWidth()/4 && e.y > g.getHeight() / 4 && e.y < 3 * g.getHeight() / 4) {
clickMouse(MouseButton.RIGHT);
console.log("click right");
}
cttl = 0;
lastx = 0;
lasty = 0;
}
} else {
if(!e.b){
Bangle.buzz(100);
if(lasty > 40){
doPPart(-1);
// E.showMessage('down');
} else if(lasty < -40){
doPPart(1);
// E.showMessage('up');
} else if(lastx > 40){
// E.showMessage('right');
//kb.tap(kb.KEY.RIGHT, 0);
scroll(-1);
} else if(lastx < -40){
// E.showMessage('left');
//kb.tap(kb.KEY.LEFT, 0);
scroll(1);
} else if(lastx==0 && lasty==0 && holding == false){
// E.showMessage('press');
clickMouse(MouseButton.LEFT);
}
stopHolding();
lastx = 0;
lasty = 0;
} else{
lastx = lastx + e.dx;
lasty = lasty + e.dy;
if (timeoutHolding == -1) {
timeoutHolding = setTimeout(startHolding, 500);
}
}
}
});
function onBtn() {
if (trackPadMode) {
trackPadMode = false;
stopHolding();
drawMain();
} else {
clearToSend = true;
trackPadMode = true;
E.showMessage('Mouse');
}
Bangle.buzz();
}
setWatch(onBtn, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat: true});
loadSettings();
drawMain();

BIN
apps/presentor/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,285 @@
<!-- Presentor by 7kasper (Kasper Müller) -->
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0"/>
<meta charset="UTF-8">
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
<style>
.qcent {
display: flex;
width: 100%;
justify-content: space-evenly;
}
h1 {
margin-bottom: 0;
}
.iwrap {
max-height: 100%;
}
.iwrap img {
height: 100%;
padding: 10%;
}
hr {
border: 0;
height: 2px;
background-image: -webkit-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
background-image: -moz-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
background-image: -ms-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
background-image: -o-linear-gradient(left, #f0f0f0, #8c8b8b, #f0f0f0);
}
.fullbtn button {
flex-grow: 1;
margin-left: 1rem;
margin-right: 1rem;
}
.ppartrow {
display: flex;
width: 100%;
justify-content: space-between;
font-weight: bolder;
}
.timerselector {
display: flex;
justify-content: space-between;
inset: 1px solid black;
height: 1.5rem;
}
.timerselector input {
width: 45%;
}
.pp-order {
width: calc(150% / 14);
display: flex;
justify-content: space-between;
align-items: center;
}
.pp-order-r {
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.pp-subject {
width: calc(300% / 14);
}
.pp-timer {
width: calc(250% / 14);
}
.pp-notes {
width: calc(700% / 14);
}
input {
height: 1.5rem;
}
.icon-cross {
color: #e85600;
cursor: pointer;
transition: transform 0.2s ease-in-out;
}
.icon-cross:hover {
transform: scale(1.1);
}
.draghandle {
cursor: grab;
}
footer {
display: inline-block;
width: 100%;
text-align: center;
font-size: 0.6rem;
color: grey;
}
</style>
</head>
<body>
<header class="qcent">
<h1>Presentor</h1>
<div class="iwrap"><img src="app.png"/></div>
</header>
<hr/>
<div class="form-group" id="subber">
<div id="pparthdr" class="ppartrow">
<div class="pp-order">#</div>
<div class="pp-subject">Subject</div>
<div class="pp-timer">Time</div>
<div class="pp-notes">Notes</div>
</div>
<div id="loader">Loading...</div>
<div id="subber-data"></div>
</div>
<hr/>
<div class="qcent fullbtn">
<button class="btn btn-default" id="btnSave">Save</button>
<button class="btn btn-error" id="btnClear">Clear</button>
</div>
<footer>Presentor by <a href="https://kaspermuller.nl" target="_blank">Kasper Müller</a></footer>
<script src="../../core/lib/interface.js"></script>
<script>
const subber = document.getElementById('subber-data');
let cmpStr = ''; //compare string to see if there are changes with previous save.
function ppartsAreFilled() {
let pparts = subber.children;
for (let i = 0; i < pparts.length; i++) {
if (!pparts[i].getElementsByClassName('pp-subject')[0].value) return false;
}
return true;
}
function getJSON() {
let ret = {
pparts: [],
sversion: 2.2
}
let jparts = [];
let pparts = subber.children;
for (let i = 0; i < pparts.length; i++) {
let rpart = {};
let ppart = pparts[i];
rpart.subject = ppart.getElementsByClassName('pp-subject')[0].value;
if (!rpart.subject) continue;
rpart.minutes = ppart.getElementsByClassName('pp-timer')[0].children[0].value | 0;
rpart.seconds = ppart.getElementsByClassName('pp-timer')[0].children[2].value | 0;
rpart.notes = ppart.getElementsByClassName('pp-notes')[0].value;
jparts.push(rpart);
}
ret.pparts = jparts;
return JSON.stringify(ret);
}
function save() {
let savestr = getJSON();
Util.showModal('Saving...');
Puck.write(`\x10require('Storage').writeJSON(${JSON.stringify('presentor.json')},${savestr})\n`,()=>{
Util.hideModal();
});
cmpStr = savestr;
}
function loadJSON(str) {
cmpStr = str;
let settings = JSON.parse(str);
let jparts = settings.pparts;
for (let i = 0; i < jparts.length; i++) {
addFormPPart(jparts[i]);
}
addFormPPart(); // add empty element on startup
}
function load() {
Util.showModal('Loading...');
Puck.eval(`require('Storage').read(${JSON.stringify('presentor.json')})`,data => {
Util.hideModal();
loadJSON(data);
});
}
function addFormPPart(partData = {}) {
let part = document.createElement('div');
part.classList.add('ppartrow');
let orderThing = document.createElement('div');
orderThing.classList.add('pp-order', 'pp-order-r');
let orderItem = document.createElement('i');
orderItem.classList.add('icon', 'icon-menu', 'draghandle');
orderThing.appendChild(orderItem);
let deleteItem = document.createElement('i');
deleteItem.classList.add('icon', 'icon-cross');
deleteItem.onclick = () => {
subber.removeChild(part);
// Make sure there stays one form thing left.
if (!subber.hasChildNodes()) addFormPPart();
}
orderThing.appendChild(deleteItem);
part.appendChild(orderThing);
let subjectField = document.createElement('input');
subjectField.classList.add('pp-subject');
subjectField.type = 'text';
subjectField.placeholder = 'Subject';
if (partData.subject) subjectField.value = partData.subject;
part.appendChild(subjectField);
let timeField = document.createElement('div');
timeField.classList.add('pp-timer', 'timerselector');
let minSelector = document.createElement('input');
minSelector.type = 'number';
minSelector.min = 0;
minSelector.step = 1
minSelector.placeholder = 'mm';
if (partData.minutes !== undefined) minSelector.value = partData.minutes;
let colon = document.createElement('p');
colon.innerText = ':';
let secSelector = document.createElement('input');
secSelector.type = 'number';
secSelector.min = 0;
secSelector.max = 59;
secSelector.step = 1
secSelector.placeholder = 'ss';
if (partData.seconds !== undefined) secSelector.value = partData.seconds;
timeField.appendChild(minSelector);
timeField.appendChild(colon);
timeField.appendChild(secSelector);
part.appendChild(timeField);
let notesField = document.createElement('input');
notesField.classList.add('pp-notes');
notesField.type = 'text';
notesField.placeholder = 'Notes (optional)';
if (partData.notes !== undefined) notesField.value = partData.notes;
part.appendChild(notesField);
subber.appendChild(part);
}
Sortable.create(subber, {
handle: '.draghandle',
animation: 150
});
document.getElementById('btnSave').onclick = () => {
save();
alert(getJSON());
}
document.getElementById('btnClear').onclick = () => {
while (subber.firstChild) {
subber.removeChild(subber.lastChild);
}
addFormPPart();
}
document.addEventListener('keyup', () => {
if (ppartsAreFilled()) {
addFormPPart();
}
});
// Simple thing to check if we need to save (TODO optimise me in the future?)
setInterval(() => {
if (cmpStr == getJSON()) {
document.getElementById('btnSave').classList.remove('btn-primary');
document.getElementById('btnSave').classList.add('btn-default');
} else {
document.getElementById('btnSave').classList.remove('btn-default');
document.getElementById('btnSave').classList.add('btn-primary');
}
}, 1000);
document.getElementById('loader').style.display = 'none';
function onInit() {
load();
}
// load from watch first.
// let qq = `[{"subject":"Hello","minutes":55,"seconds":4,"notes":""},{"subject":"dsfafds","minutes":4,"seconds":33,"notes":"fdasdfsafasfsd"},{"subject":"dsadsf","minutes":0,"seconds":4,"notes":""},{"subject":"sdasf","minutes":0,"seconds":0,"notes":""}]`;
// loadJSON(qq);
</script>
</body>
</html>

View File

@ -0,0 +1 @@
{"pparts":[{"subject":"#1","minutes":10,"seconds":0,"notes":"This is a note."},{"subject":"#2","minutes":2,"seconds":50,"notes":"Change in the app!"}],"sversion":2.2}

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Add Bangle.js 2 Support

View File

@ -70,7 +70,7 @@ E.showMenu = function(items) {
if(g.theme.dark){
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+20);
}else{
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-10);
fillRectRnd(x+2,iy+1,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-20);
}
g.setColor(hl ? g.theme.fgH : g.theme.fg);
g.setFontAlign(-1,-1);

142
apps/promenu/bootb2.js Normal file
View File

@ -0,0 +1,142 @@
E.showMenu = function(items) {
function RectRnd(x1,y1,x2,y2,r) {
pp = [];
pp.push.apply(pp,g.quadraticBezier([x2-r,y1, x2,y1,x2,y1+r]));
pp.push.apply(pp,g.quadraticBezier([x2,y2-r,x2,y2,x2-r,y2]));
pp.push.apply(pp,g.quadraticBezier([x1+r,y2,x1,y2,x1,y2-r]));
pp.push.apply(pp,g.quadraticBezier([x1,y1+r,x1,y1,x1+r,y1]));
return pp;
}
function fillRectRnd(x1,y1,x2,y2,r,c) {
g.setColor(c);
g.fillPoly(RectRnd(x1,y1,x2,y2,r),1);
g.setColor(255,255,255);
}
function drawRectRnd(x1,y1,x2,y2,r,c) {
g.setColor(c);
g.drawPoly(RectRnd(x1,y1,x2,y2,r),1);
g.setColor(255,255,255);
}
g.reset().clearRect(Bangle.appRect); // clear if no menu supplied
Bangle.setLCDPower(1); // ensure screen is on
if (!items) {
Bangle.setUI();
return;
}
var menuItems = Object.keys(items);
var options = items[""];
if (options) menuItems.splice(menuItems.indexOf(""),1);
if (!(options instanceof Object)) options = {};
options.fontHeight = options.fontHeight||25;
if (options.selected === undefined)
options.selected = 0;
var ar = Bangle.appRect;
var x = ar.x;
var x2 = ar.x2;
var y = ar.y;
var y2 = ar.y2 - 12; // padding at end for arrow
if (options.title)
y += 22;
var loc = require("locale");
var l = {
lastIdx : 0,
draw : function(rowmin,rowmax) {
var rows = 0|Math.min((y2-y) / options.fontHeight,menuItems.length);
var idx = E.clip(options.selected-( rows>>1),0,menuItems.length-rows);
if (idx!=l.lastIdx) rowmin=undefined; // redraw all if we scrolled
l.lastIdx = idx;
var iy = y;
g.reset().setFontAlign(0,-1,0).setFont('12x20');
if (options.predraw) options.predraw(g);
if (rowmin===undefined && options.title)
g.drawString(options.title,(x+x2)/2,y-21).drawLine(x,y-2,x2,y-2).
setColor(g.theme.fg).setBgColor(g.theme.bg);
iy += 4;
if (rowmin!==undefined) {
if (idx<rowmin) {
iy += options.fontHeight*(rowmin-idx);
idx=rowmin;
}
if (idx+rows>rowmax) {
rows = 1+rowmax-rowmin;
}
}
while (rows--) {
var name = menuItems[idx];
var item = items[name];
var hl = (idx==options.selected && !l.selectEdit);
if(g.theme.dark){
fillRectRnd(x,iy,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg+40);
}else{
fillRectRnd(x,iy,x2,iy+options.fontHeight-3,7,hl ? g.theme.bgH : g.theme.bg-20);
}
g.setColor(hl ? g.theme.fgH : g.theme.fg);
g.setFontAlign(-1,-1);
var v = item.value;
v = loc.translate(""+v);
if(loc.translate(name).length >= 17-v.length && "object" == typeof item){
if (item.format) v=item.format(v);
g.drawString(loc.translate(name).substring(0, 12-v.length)+"...",x+3.7,iy+2.7);
}else{
if(loc.translate(name).length >= 15){
g.drawString(loc.translate(name).substring(0, 15)+"...",x+3.7,iy+2.7);
}else{
g.drawString(loc.translate(name),x+3.7,iy+2.7);
}
}
if ("object" == typeof item) {
var xo = x2;
var v = item.value;
if (item.format) v=item.format(v);
v = loc.translate(""+v);
if (l.selectEdit && idx==options.selected) {
xo -= 24 + 1;
g.setColor(g.theme.fgH).drawImage("\x0c\x05\x81\x00 \x07\x00\xF9\xF0\x0E\x00@",xo,iy+(options.fontHeight-10)/2,{scale:2});
}
g.setFontAlign(1,-1);
g.drawString(v,xo-2,iy+1);
}
g.setColor(g.theme.fg);
iy += options.fontHeight;
idx++;
}
g.setFontAlign(-1,-1);
g.setColor((idx<menuItems.length)?g.theme.fg:g.theme.bg).fillPoly([72,166,104,166,88,174]);
g.flip();
},
select : function() {
var item = items[menuItems[options.selected]];
if ("function" == typeof item) item(l);
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) item.onchange(item.value);
}
l.draw();
}
},
move : function(dir) {
var item = l.selectEdit;
if (item) {
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) item.onchange(item.value);
l.draw(options.selected,options.selected);
} else {
var lastSelected=options.selected;
options.selected = (dir+options.selected+menuItems.length)%menuItems.length;
l.draw(Math.min(lastSelected,options.selected), Math.max(lastSelected,options.selected));
}
}
};
l.draw();
Bangle.setUI("updown",dir => {
if (dir) l.move(dir);
else l.select();
});
return l;
};

5
apps/puzzle15/ChangeLog Normal file
View File

@ -0,0 +1,5 @@
0.01: Initial version, UI mechanics ready, no real game play so far
0.02: Lots of enhancements, menu system not yet functional, but packaging should be now...
0.03: Menu logic now generally functioning, splash screen added. The first really playable version!
0.04: Settings dialog, about screen
0.05: Central game end function

57
apps/puzzle15/README.md Normal file
View File

@ -0,0 +1,57 @@
# Puzzle15 - A 15-puzzle for the Bangle.js 2
This is a Bangle.js 2 adoption of the famous 15 puzzle.
## The game
A board of _n_ by _n_ fields is filled with _n^2-1_ numbered stones. So, one field, the "gap", is left free.
Bring them in the correct order so that the gap is finally at the bottom right of the playing field.
The less moves you need, the better you are.
If _n_ is 4, the number of stones is _16-1=15_. Hence the name of the game.
## How to play
If you start the game, it shows a splash screen and then generates a shuffled 4x4 board with a 15 puzzle.
Move the stones with drag gestures on the screen.
If you want to move the stone below the gap upward, drag from the bottom of the screen upward.
The drag gestures can be performed anywhere on the screen, there is no need to start or end them on the stone to be moved.
If you managed to order the stones correctly, a success message appears.
You can continue with another game, go to the game's main menu, or quit the game entirely.
There is a grey menu button right of the board containing the well-known three-bar menu symbol ("Hamburger menu").
It opens the game's main menu directly from within the game.
## The main menu
Puzzle15 has a main menu which can be reached from the in-game menu button or the end-of-game message window.
It features the following options:
* **Continue** - Continue the currently running game. _This option is only shown if the main menu is opened during an open game._
* **Start 3x3**, **Start 4x4**, **Start 5x5** - Start a new game on a board with the respective dimension. Any currently open game is dropped.
* **About** Show a small "About" info box.
* **Exit** Exit Puzzle15 and return to the default watch face.
## Game settings
The game has some global settings which can be accessed on the usual way through the Bangle.js' app settings user interface.
Currently it has the following options:
* **Splash** - Define whether the game should open with a splash screen. **long** shows the splash screen for five seconds, **short** shows it for two seconds. **off** starts the app _without_ a splash screen, it directly comes up with whatever the "Start with" option says.
* **Start with** - What should happen after the splash screen (or, if it is disabled, directly at app start): **3x3**, **4x4** and **5x5** start the game with a board of the respective dimension, **menu** shows the main menu which allows to select the board size.
## Implementation notes
The game engine always generates puzzles which can be solved.
Solvability is detected by counting inversions,
i.e. pairs of stones where the stone at the earlier field (row-wise, left to right, top to bottom) has a number _greater than_ the stone on the later field, with all pairs of stones compared.
The algorithm is described at https://www.geeksforgeeks.org/check-instance-15-puzzle-solvable/ .
## The splash screen
The Splash screen shows a part of the illustration "The 14-15-puzzle in puzzleland" from Sam Loyd. Other than Puzzle15, it depicts a 15 puzzle with the stones "14" and "15" swapped. This puzzle is indeed *not* solvable.
Have fun!

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwgn/AC3+7oAD7e7AAW8BQndBQe79/9DomgHocH74KD/RJE34Xax4XDtvoC4fJ54XDluAC4f2z4XDzm/C4ett4XD34OBF4e/I4m+C4f8r4XChHuC5U98oXEF4cP7/AC5O9mYXC/2/F4cGtwvE/SsBC4Ws7gvD7YCBL4ULO4i/u1QAD7QED1e6AoetCAnf/YeE1wpD/lgBQcKIAgXG14LD/twC5kL3Z+BC4P+LgIXBg272wXD7wXEh7eCC4PWzIXChHtOoIXB/WX54XDh3KmAXC1oLBI4UD+AXC+/rdIIvD5wvD3O4C4cJ4AXC/dUI4kJhgMBC4Ov+AXDh9QC4X2/gvEhvvoAXC81dC4duR4f8wSncC6v8u4AD3ndAAXcy4KDtYKD7vf/oGE2wRDvPNBQfLFAnP/o2EVIIACg7yBAATZBAAe/C7P9g4XCx+wn/6C4Op//AC4MK+cI/+QC4X2/fPC4PM2HKh8H7vpewIXBhvThV5+AXC+/5C4UL2HHC4Pf/P/AIJHB6cAj2wC4X+3AXPhADBF4fX94XB1va1vOC4PXAIX6hfrxvb0CPD7p3C1e6hW2C4LOBAIIXB3eJ3YXEX78GM4IAC9QXG1QAD7QEDJYIFD14oE//7DwgME/twBQcPC70G6EG5dQ1/8VYPtC4ObgfM5IXHr/whvO4Gvy6LBtX9vfugnr3AXHkXggGOC4P97/43X9ukOgnv6BfIC4Oe2AXC6+nI4MOgfI9QXJhssF4f91AXCgnA9IXHr3u1HusGv3Ob//s/t693l3xHJX9v+3YAD7oAE5YKD34XFAC4="))

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,50 @@
// Settings menu for the Puzzle15 app
(function(back) {
var FILE = "puzzle15.json";
// Load settings
var settings = Object.assign({
splashMode: "long",
startWith: "4x4"
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
// Helper method which uses int-based menu item for set of string values
function stringItems(startvalue, writer, values) {
return {
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => values[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
writer(values[v]);
writeSettings();
}
};
}
// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values) {
return stringItems(settings[name], v => settings[name] = v, values);
}
var mainmenu = {
"": {
"title": "15 Puzzle"
},
"< Back": () => back(),
"Splash": stringInSettings("splashMode", ["long", "short", "off"]),
"Start with": stringInSettings("startWith", ["3x3", "4x4", "5x5", "menu"])
};
// Actually display the menu
E.showMenu(mainmenu);
});
// end of file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -11,3 +11,4 @@
0.12: Allow hiding the widget
0.13: Tweak Bangle.js 2 light theme colors
0.14: Use weather condition code for icon selection
0.15: Fix widget icon

View File

@ -53,6 +53,16 @@ exports.get = function() {
scheduleExpiry(storage.readJSON('weather.json')||{});
/**
*
* @param cond Weather condition, as one of:
* {number} code: (Preferred form) https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
* {string} weather description (in English: breaks for other languages!)
* {object} use cond.code if present, or fall back to cond.txt
* @param x Left
* @param y Top
* @param r Icon Size
*/
exports.drawIcon = function(cond, x, y, r) {
var palette;
@ -249,32 +259,35 @@ exports.drawIcon = function(cond, x, y, r) {
g.setColor(g.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
}
function chooseIcon(condition) {
if (!condition) return () => {};
condition = condition.toLowerCase();
if (condition.includes("thunderstorm")) return drawThunderstorm;
if (condition.includes("freezing")||condition.includes("snow")||
condition.includes("sleet")) {
/*
* Choose weather icon to display based on weather description
*/
function chooseIconByTxt(txt) {
if (!txt) return () => {};
txt = txt.toLowerCase();
if (txt.includes("thunderstorm")) return drawThunderstorm;
if (txt.includes("freezing")||txt.includes("snow")||
txt.includes("sleet")) {
return drawSnow;
}
if (condition.includes("drizzle")||
condition.includes("shower")) {
if (txt.includes("drizzle")||
txt.includes("shower")) {
return drawRain;
}
if (condition.includes("rain")) return drawShowerRain;
if (condition.includes("clear")) return drawSun;
if (condition.includes("few clouds")) return drawFewClouds;
if (condition.includes("scattered clouds")) return drawCloud;
if (condition.includes("clouds")) return drawBrokenClouds;
if (condition.includes("mist") ||
condition.includes("smoke") ||
condition.includes("haze") ||
condition.includes("sand") ||
condition.includes("dust") ||
condition.includes("fog") ||
condition.includes("ash") ||
condition.includes("squalls") ||
condition.includes("tornado")) {
if (txt.includes("rain")) return drawShowerRain;
if (txt.includes("clear")) return drawSun;
if (txt.includes("few clouds")) return drawFewClouds;
if (txt.includes("scattered clouds")) return drawCloud;
if (txt.includes("clouds")) return drawBrokenClouds;
if (txt.includes("mist") ||
txt.includes("smoke") ||
txt.includes("haze") ||
txt.includes("sand") ||
txt.includes("dust") ||
txt.includes("fog") ||
txt.includes("ash") ||
txt.includes("squalls") ||
txt.includes("tornado")) {
return drawMist;
}
return drawUnknown;
@ -298,7 +311,6 @@ exports.drawIcon = function(cond, x, y, r) {
case 531: return drawShowerRain;
default: return drawRain;
}
break;
case 6: return drawSnow;
case 7: return drawMist;
case 8:
@ -308,16 +320,21 @@ exports.drawIcon = function(cond, x, y, r) {
case 802: return drawCloud;
default: return drawBrokenClouds;
}
break;
default: return drawUnknown;
}
}
if (cond.code && cond.code > 0) {
chooseIconByCode(cond.code)(x, y, r);
} else {
chooseIcon(cond.txt)(x, y, r);
function chooseIcon(cond) {
if (typeof (cond)==="object") {
if ("code" in cond) return chooseIconByCode(cond.code);
if ("txt" in cond) return chooseIconByTxt(cond.txt);
} else if (typeof (cond)==="number") {
return chooseIconByCode(cond.code);
} else if (typeof (cond)==="string") {
return chooseIconByTxt(cond.txt);
}
return drawUnknown;
}
chooseIcon(cond)(x, y, r);
};

View File

@ -52,8 +52,8 @@
if (!w) return;
g.reset();
g.clearRect(this.x, this.y, this.x+this.width-1, this.y+23);
if (w.txt) {
weather.drawIcon(w.txt, this.x+10, this.y+8, 7.5);
if (w.code||w.txt) {
weather.drawIcon(w, this.x+10, this.y+8, 7.5);
}
if (w.temp) {
let t = require('locale').temp(w.temp-273.15); // applies conversion

View File

@ -12,3 +12,4 @@
0.13: Fillbar setting added, see README
0.14: Fix drawing the bar when charging
0.15: Added option to always display the icon when charging (useful if 'hide if charge greater than' is enabled)
0.16: Increase screen update rate when charging

View File

@ -1,6 +1,9 @@
(function(){
const intervalLow = 60000; // update time when not charging
const intervalHigh = 2000; // update time when charging
let COLORS = {};
if (process.env.HWVERSION == 1) {
COLORS = {
'white': -1, // White
@ -17,13 +20,13 @@
'high': "#0f0", // Green
'ok': "#ff0", // Orange
'low': "#f00", // Red
};
};
}
const SETTINGS_FILE = 'widbatpc.json'
const SETTINGS_FILE = 'widbatpc.json';
let settings
let settings;
function loadSettings() {
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {}
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
const DEFAULTS = {
'color': 'By Level',
'percentage': true,
@ -32,17 +35,17 @@
'alwaysoncharge': false,
};
Object.keys(DEFAULTS).forEach(k=>{
if (settings[k]===undefined) settings[k]=DEFAULTS[k]
if (settings[k]===undefined) settings[k]=DEFAULTS[k];
});
}
function setting(key) {
if (!settings) { loadSettings() }
if (!settings) { loadSettings(); }
return settings[key];
}
const levelColor = (l) => {
// "charging" is very bright -> percentage is hard to read, "high" is ok(ish)
const green = setting('percentage') ? COLORS.high : COLORS.charging
const green = setting('percentage') ? COLORS.high : COLORS.charging;
switch (setting('color')) {
case 'Monochrome': return COLORS.white; // no chance of reading the percentage here :-(
case 'Green': return green;
@ -59,10 +62,11 @@
if (l >= 15) return COLORS.ok;
return COLORS.low;
}
}
};
const chargerColor = () => {
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging
}
return (setting('color') === 'Monochrome') ? COLORS.white : COLORS.charging;
};
// sets width, returns true if it changed
function setWidth() {
var w = 40;
@ -77,6 +81,7 @@
WIDGETS["batpc"].width = w;
return changed;
}
function draw() {
// if hidden, don't draw
if (!WIDGETS["batpc"].width) return;
@ -106,11 +111,11 @@
if (!setting('percentage')) {
return;
}
let gfx = g
let gfx = g;
if (setting('color') === 'Monochrome') {
// draw text inverted on battery level
gfx = Graphics.createCallback(g.getWidth(),g.getHeight(), 1,
(x,y) => {g.setPixel(x,y,x<=xl?0:-1)})
(x,y) => {g.setPixel(x,y,x<=xl?0:-1);});
}
gfx.setFontAlign(-1,-1);
if (l >= 100) {
@ -122,19 +127,24 @@
gfx.drawString(l, x + 6, y + 4);
}
}
// reload widget, e.g. when settings have changed
function reload() {
loadSettings()
loadSettings();
// need to redraw all widgets, because changing the "charger" setting
// can affect the width and mess with the whole widget layout
setWidth()
setWidth();
g.clear();
Bangle.drawWidgets();
}
// update widget - redraw just widget, or all widgets if size changed
function update() {
if (setWidth()) Bangle.drawWidgets();
else WIDGETS["batpc"].draw();
if (Bangle.isCharging()) changeInterval(id, intervalHigh);
else changeInterval(id, intervalLow);
}
Bangle.on('charging',function(charging) {
@ -142,20 +152,13 @@
update();
g.flip();
});
var batteryInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
update();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(update, 60000);
} else {
if (batteryInterval) {
clearInterval(batteryInterval);
batteryInterval = undefined;
}
}
if (on) update();
});
var id = setInterval(()=>WIDGETS["batpc"].draw(), intervalLow);
WIDGETS["batpc"]={area:"tr",width:40,draw:draw,reload:reload};
setWidth();
})()
})();

2
core

@ -1 +1 @@
Subproject commit ae9586977948279d267f2749bf3a48d3aa753c11
Subproject commit b05af96b2522a7a7225a56d804faf9383f8a8f97

View File

@ -42,8 +42,8 @@ layoutObject has:
and `fillx`/`filly` to be set. Not compatible with text rotation.
* A `col` field, eg `#f00` for red
* A `bgCol` field for background color (will automatically fill on render)
* A `halign` field to set horizontal alignment. `-1`=left, `1`=right, `0`=center
* A `valign` field to set vertical alignment. `-1`=top, `1`=bottom, `0`=center
* A `halign` field to set horizontal alignment WITHIN a `v` container. `-1`=left, `1`=right, `0`=center
* A `valign` field to set vertical alignment WITHIN a `h` container. `-1`=top, `1`=bottom, `0`=center
* A `pad` integer field to set pixels padding
* A `fillx` int to choose if the object should fill available space in x. 0=no, 1=yes, 2=2x more space
* A `filly` int to choose if the object should fill available space in y. 0=no, 1=yes, 2=2x more space