Merge branch 'espruino:master' into master
54
apps.json
|
@ -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"}],
|
||||
|
@ -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},
|
||||
|
@ -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}
|
||||
]
|
||||
},
|
||||
|
@ -4488,7 +4489,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 +5123,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",
|
||||
|
@ -5389,5 +5408,24 @@
|
|||
"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"}]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
@ -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);
|
|
@ -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.
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.4 KiB |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!
|
|
@ -0,0 +1,2 @@
|
|||
# Presentor
|
||||
Use your Bangle to present!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4ASlgADGmIxwLV4wqGQowfWZQwjKw4wJF7ghBmVWmQlELYoweFwYABGAwrHF7IuFGAwrIF7AuHMJADGF0AwHAYovWFxaSHADQuDEgIADYZYucAQOB1fQ1eBmQwiFwlX6AAE1gqBGD6MEmQqBwIICwIGB0rDeWYksFAIuBz+fvQHC6D0dcQssEwIuB4fC4V0M4VXF7YuFDYLqBlnDF4WeHAYugL5N6L4I4BF0IvB0vQvdQGAJeBY4YucmQxEAgJgBvYOBdwYQDFzUzmZ/EllXFIKKBAYVXBwSMaller7fFAoKSBAAOBFQK7dPoJfFEoYADRjgmEeIzFFdUQAOdUIumdRLtDAAIufdRQKCr8zCQjqmE4NeF4YudWxpqCFyovBK5LqiF6DqdR6D0BdTYwJGg5sBdTQwKEAIwHdTQwMSpQueYaAufGBouiGBYukGBIumGA4uoGAouqGAYABF1QAl"))
|
|
@ -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();
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -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>
|
|
@ -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}
|
|
@ -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
|
|
@ -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!
|
After Width: | Height: | Size: 3.6 KiB |
|
@ -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="))
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -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
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit ae9586977948279d267f2749bf3a48d3aa753c11
|
||||
Subproject commit b05af96b2522a7a7225a56d804faf9383f8a8f97
|
|
@ -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
|
||||
|
|