Merge branch 'master' of github.com:espruino/BangleApps

pull/3545/head
frederic wagner 2024-08-26 10:55:24 +02:00
commit b667ace938
82 changed files with 1896 additions and 288 deletions

BIN
apps/8ball/8ball.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 983 B

1
apps/8ball/ChangeLog Normal file
View File

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

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

@ -0,0 +1 @@
atob("MDCBAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/4AAAAB4AeAAAAHgAHgAAAOAABwAAAcAAA4AAA4AMAcAABwAMAOAABgA/AGAADAAeADAAHAAMADgAGAAAABgAGAAAABgAMAAAAAwAMBAAAAwAMDAAAAwAMHwAAAwAMHwAAAwAMDAACAwAMBAADAwAMAAAPgwAMAAAPgwAMAAADAwAGAAACBgAGAAAABgAHAAAADgADAAAADAADgAAAHAAB////+AAB////+AABgAAAGAABgAAAGAABgAAAGAADAAAADAADAAAADAADAAAADAAGAAAABgAGAAAABgAH/////gAP/////wAYAAAAAYAYAAAAAYAf/////4AP/////wAAAAAAAAAAAAAAAAA==")

92
apps/8ball/app.js Normal file
View File

@ -0,0 +1,92 @@
var keyboard = "textinput";
var Name = "";
Bangle.setLCDTimeout(0);
var menuOpen = 1;
var answers = new Array("no", "yes","WHAT????","What do you think", "That was a bad question", "YES!!!", "NOOOOO!!", "nope","100%","yup","why should I answer that?","think for yourself","ask again later, I'm busy", "what Was that horrible question","how dare you?","you wanted to hear yes? okay, yes", "Don't get angry when I say no","you are 100% wrong","totally, for sure","hmmm... I'll ponder it and get back to you later","wow, you really have a lot of questions", "NOPE","is the sky blue, hmmm...","I don't have time to answer","How many more questions before you change my name?","theres this thing called wikipedia","hmm... I don't seem to be able to reach the internet right now","if you phrase it like that, yes","Huh, never thought so hard in my life","The winds of time say no");
var consonants = new Array("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z");
var vowels = new Array("a","e","i","o","u");
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
function generateName()
{
Name = "";
var nameLength = Math.round(Math.random()*5);
for(var i = 0; i < nameLength; i++){
var cosonant = consonants[Math.round(Math.random()*consonants.length/2)];
var vowel = vowels[Math.round(Math.random()*vowels.length/2)];
Name = Name + cosonant + vowel;
if(Name == "")
{
generateName();
}
}
}
generateName();
function menu()
{
g.clear();
E.showMenu();
menuOpen = 1;
E.showMenu({
"" : { title : Name },
"< Back" : () => menu(),
"Start" : () => {
E.showMenu();
g.clear();
menuOpen = 0;
Drawtext("ask " + Name + " a yes or no question");
},
"regenerate name" : () => {
menu();
generateName();
},
"show answers" : () => {
var menu = new Array([]);
for(var i = 0; i < answers.length; i++){
menu.push({title : answers[i]});
}
E.showMenu(menu);
},
"Add answer" : () => {
E.showMenu();
keyboard.input({}).then(result => {if(result != ""){answers.push(result);} menu();});
},
"Edit name" : () => {
E.showMenu();
keyboard.input({}).then(result => {if(result != ""){Name = result;} menu();});
},
"Exit" : () => load(),
});
}
menu();
var answer;
function Drawtext(text)
{
g.clear();
g.setFont("Vector", 20);
g.drawString(g.wrapString(text, g.getWidth(), -20).join("\n"));
}
function WriteAnswer()
{
if (menuOpen == 0)
{
var randomnumber = Math.round(Math.random()*answers.length);
answer = answers[randomnumber];
Drawtext(answer);
setTimeout(function() {
Drawtext("ask " + Name + " a yes or no question");
}, 3000);
}
}
setWatch(function() {
menu();
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true, edge:"falling"});
Bangle.on('touch', function(button, xy) { WriteAnswer(); });

19
apps/8ball/metadata.json Normal file
View File

@ -0,0 +1,19 @@
{ "id": "8ball",
"name": "Magic 8 ball",
"shortName":"8ball",
"icon": "8ball.png",
"version":"0.01",
"screenshots": [
{"url":"screenshot.png"},
{"url":"screenshot-1.png"},
{"url":"screenshot-2.png"}
],
"allow_emulator": true,
"description": "A very sarcastic magic 8ball",
"tags": "game",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"8ball.app.js","url":"app.js"},
{"name":"8ball.img","url":"app-icon.js","evaluate":true}
]
}

BIN
apps/8ball/screenshot-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
apps/8ball/screenshot-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
apps/8ball/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

1
apps/blc/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.10: New app introduced to the app loader!

13
apps/blc/README.md Normal file
View File

@ -0,0 +1,13 @@
# Binary LED Clock
A binary watch with LEDs, showing time and date.
From top to bottom the watch face shows four rows of leds:
* hours (red leds)
* minutes (green leds)
* day (yellow leds, top row)
* month (yellow leds, bottom row)
As usual, luminous leds represent a logical one, dark leds a logcal '0'.
Widgets aren't affected and are shown as normal.

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4n/AAIHBqut8FPgH4sspk1T885/feoMI74TB1Fc51Dmfg28gKmMCrNSAgMlyo5BgV7uQIKgEhiMRkECAYMSgErolLBBIXBqIKBqEFAYMVgF0olEuAIIC4ORBQOQhIDBjMA2gOB2AIIF7JfXR67X0lvdHwQII7vSa4/TmYKBBBEtmc9a40NmYKBBBIbBmfQa4oOEBBAXFF65fXR64A/AG8IvN4AgOG62ABAuHy4IGgEHiMXAgNu91gBAtxiNwBAsAhMRjIEB73ucIIIEyMRyAIFF7BfXAH6/IttoKxRoIgEG93mQxSYIgEN93tWxTIIF7BfXAH4AGw93u/A44IDhl8vQRFBogXB0ECuGoBAcKxRxBC53Hhlyk8ggVyuQGBvlwhgNBk98BAN6I4UgC4N4BwWgAwWsC4fAk4IB0AvBAgIQBBwUIkQOBAwQXCJIIEBI4UAkQXE48sAwgXJF40mgAvDvRtCC4pfEC4WCPYJdBDYNyC4wAX"))

BIN
apps/blc/blc-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

136
apps/blc/blc.js Normal file
View File

@ -0,0 +1,136 @@
//Binary LED Clock (BLC) by aeMKai
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let drawTimeout;
// Actually draw the watch face
let draw = function()
{
// Bangle.js2 -> 176x176
var x_rgt = g.getWidth();
var y_bot = g.getHeight();
//var x_cntr = x_rgt / 2;
var y_cntr = y_bot / 18*7; // not to high because of widget-field (1/3 is to high)
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
let white = [1,1,1];
let red = [1,0,0];
let green = [0,1,0];
//let blue = [0,0,1];
let yellow = [1,1,0];
//let magenta = [1,0,1];
//let cyan = [0,1,1];
let black = [0,0,0];
let bord_col = white;
let col_off = black;
var col = new Array(red, green, yellow, yellow); // [R,G,B]
let pot_2 = [1, 2, 4, 8, 16, 32]; // array with powers of two, because power-op (**)
// doesn't work -> maybe also faster
var nr_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
// Arrays: [hr, min, day, mon]
//No of Bits: 5 6 5 4
let msbits = [4, 5, 4, 3]; // MSB = No bits - 1
let rad = [12, 12, 8, 8]; // radiuses for each row
var x_dist = 28;
let y_dist = [0, 30, 60, 85]; // y-position from y_centr for each row from top
// don't calc. automatic as for x, because of different spaces
var x_offs_rgt = 16; // distance from right border (layout)
// Date-Time-Array: 4x6 Bit
//var idx_hr = 0;
//var idx_min = 1;
//var idx_day = 2;
//var idx_mon = 3;
var dt_bit_arr = [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]];
var date_time = new Date();
var hr = date_time.getHours(); // 0..23
var min = date_time.getMinutes(); // 0..59
var day = date_time.getDate(); // 1..31
var mon = date_time.getMonth() + 1; // GetMonth() -> 0..11
let dt_array = [hr, min, day, mon];
////////////////////////////////////////
// compute bit-pattern from time/date and draw leds
////////////////////////////////////////
var line_cnt = 0;
var cnt = 0;
var bit_cnt = 0;
while (line_cnt < nr_lines)
{
////////////////////////////////////////
// compute bit-pattern
bit_cnt = msbits[line_cnt];
while (bit_cnt >= 0)
{
if (dt_array[line_cnt] >= pot_2[bit_cnt])
{
dt_array[line_cnt] -= pot_2[bit_cnt];
dt_bit_arr[line_cnt][bit_cnt] = 1;
}
else
{
dt_bit_arr[line_cnt][bit_cnt] = 0;
}
bit_cnt--;
}
////////////////////////////////////////
// draw leds (first white border for black screen, then led itself)
cnt = 0;
while (cnt <= msbits[line_cnt])
{
g.setColor(bord_col[0], bord_col[1], bord_col[2]);
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]);
if (dt_bit_arr[line_cnt][cnt] == 1)
{
g.setColor(col[line_cnt][0], col[line_cnt][1], col[line_cnt][2]);
}
else
{
g.setColor(col_off[0], col_off[1], col_off[2]);
}
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]-1);
cnt++;
}
line_cnt++;
}
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function()
{
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
// Show launcher when middle button pressed
Bangle.setUI(
{
mode : "clock",
remove : function()
{
// Called to unload all of the clock app
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
// Load widgets
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets,0);
}

17
apps/blc/metadata.json Normal file
View File

@ -0,0 +1,17 @@
{
"id":"blc",
"name":"Binary LED Clock",
"version": "0.10",
"description": "Binary LED Clock with date",
"icon":"blc-icon.png",
"screenshots": [{"url":"screenshot_blc.bmp"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"blc.app.js","url":"blc.js"},
{"name":"blc.img","url":"blc-icon.js","evaluate":true}
]
}

BIN
apps/blc/screenshot_blc.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -3,3 +3,4 @@
0.03: Allows showing the month in short or long format by setting `"shortMonth"` to true or false 0.03: Allows showing the month in short or long format by setting `"shortMonth"` to true or false
0.04: Improves touchscreen drag handling for background apps such as Pattern Launcher 0.04: Improves touchscreen drag handling for background apps such as Pattern Launcher
0.05: Fixes step count not resetting after a new day starts 0.05: Fixes step count not resetting after a new day starts
0.06 Added clockbackground app functionality

View File

@ -5,11 +5,11 @@
* --------------------------------------------------------------- * ---------------------------------------------------------------
*/ */
let background = require("clockbg");
let storage = require("Storage"); let storage = require("Storage");
let locale = require("locale"); let locale = require("locale");
let widgets = require("widget_utils"); let widgets = require("widget_utils");
let date = new Date(); let date = new Date();
let bgImage;
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0; let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json'; let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
// Add a condition to check if the file exists, if it does not, default to 'boxclk.json' // Add a condition to check if the file exists, if it does not, default to 'boxclk.json'
@ -71,14 +71,6 @@
* --------------------------------------------------------------- * ---------------------------------------------------------------
*/ */
for (let key in boxesConfig) {
if (key === 'bg' && boxesConfig[key].img) {
bgImage = storage.read(boxesConfig[key].img);
} else if (key !== 'selectedConfig') {
boxes[key] = Object.assign({}, boxesConfig[key]);
}
}
let boxKeys = Object.keys(boxes); let boxKeys = Object.keys(boxes);
boxKeys.forEach((key) => { boxKeys.forEach((key) => {
@ -224,9 +216,7 @@
return function(boxes) { return function(boxes) {
date = new Date(); date = new Date();
g.clear(); g.clear();
if (bgImage) { background.fillRect(Bangle.appRect);
g.drawImage(bgImage, 0, 0);
}
if (boxes.time) { if (boxes.time) {
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0)); boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0));
updatePerMinute = isBool(boxes.time.short, true); updatePerMinute = isBool(boxes.time.short, true);
@ -412,4 +402,4 @@
widgets.swipeOn(); widgets.swipeOn();
modSetColor(); modSetColor();
setup(); setup();
} }

Binary file not shown.

View File

@ -4,6 +4,7 @@
"version": "0.05", "version": "0.05",
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background", "description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
"icon": "app.png", "icon": "app.png",
"dependencies" : { "clockbg":"module" },
"screenshots": [ "screenshots": [
{"url":"screenshot.png"}, {"url":"screenshot.png"},
{"url":"screenshot-1.png"}, {"url":"screenshot-1.png"},

1
apps/dedreckon/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: attempt to import

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

@ -0,0 +1,20 @@
# Ded Reckon
Dead Reckoning using compass and step counter.
This allows logging track using "dead reckoning" -- that's logging
angles from compass and distances from step counter. You need to mark
turns, and point watch to direction of the turn. Simultaneously, it
tries to log positions using GPS. You can use it to calibrate your
step length by comparing GPS and step counter data. It can also get
pretty accurate recording of track walked in right circumstances.
Tap bottom part of the screen to select display (text or map for
now). Point watch to new direction, then tap top left part of screen
to indicate a turn.
Map shows blue line for track from dead reckonging, and green line for
track from GPS.
You probably want magnav installed (and calibrated) for useful
results, as it provides library with better compass.

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AFsAFtoADF1wwqF4wwhEI5goGGIjFYN4wFF1KbHGUolIMc4lGSdIwJd9DstAH7FrBywwgad4veDwojJBIIvcFwIACGBYICGDYvEGBYvdFwqyLL8i+LF7oxFRxgveGAQ0EF5IwfMY4vpL5AFLAEYv/F8owoE44vrAY4vmAQIEEF85dGGE0AE4gvoFwpmHd0oINAH4A/AH4AvA"))

BIN
apps/dedreckon/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,442 @@
/* Ded Reckon */
/* eslint-disable no-unused-vars */
/* fmt library v0.1.3 */
let fmt = {
icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
icon_km : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
icon_kph : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3",
icon_c : "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
/* 0 .. DD.ddddd
1 .. DD MM.mmm'
2 .. DD MM'ss"
*/
geo_mode : 1,
init: function() {},
fmtDist: function(km) {
if (km >= 1.0) return km.toFixed(1) + this.icon_km;
return (km*1000).toFixed(0) + this.icon_m;
},
fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); },
fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; },
draw_dot : 1,
add0: function(i) {
if (i > 9) {
return ""+i;
} else {
return "0"+i;
}
},
fmtTOD: function(now) {
this.draw_dot = !this.draw_dot;
let dot = ":";
if (!this.draw_dot)
dot = ".";
return now.getHours() + dot + this.add0(now.getMinutes());
},
fmtNow: function() { return this.fmtTOD(new Date()); },
fmtTimeDiff: function(d) {
if (d < 180)
return ""+d.toFixed(0);
d = d/60;
return ""+d.toFixed(0)+"m";
},
fmtAngle: function(x) {
switch (this.geo_mode) {
case 0:
return "" + x;
case 1: {
let d = Math.floor(x);
let m = x - d;
m = m*60;
return "" + d + " " + m.toFixed(3) + "'";
}
case 2: {
let d = Math.floor(x);
let m = x - d;
m = m*60;
let mf = Math.floor(m);
let s = m - mf;
s = s*60;
return "" + d + " " + mf + "'" + s.toFixed(0) + '"';
}
}
return "bad mode?";
},
fmtPos: function(pos) {
let x = pos.lat;
let c = "N";
if (x<0) {
c = "S";
x = -x;
}
let s = c+this.fmtAngle(x) + "\n";
c = "E";
if (x<0) {
c = "W";
x = -x;
}
return s + c + this.fmtAngle(x);
},
fmtFix: function(fix, t) {
if (fix && fix.fix && fix.lat) {
return this.fmtSpeed(fix.speed) + " " +
this.fmtAlt(fix.alt);
} else {
return "N/FIX " + this.fmtTimeDiff(t);
}
},
fmtSpeed: function(kph) {
return kph.toFixed(1) + this.icon_kph;
},
};
/* gps library v0.1.1 */
let gps = {
emulator: -1,
init: function(x) {
this.emulator = (process.env.BOARD=="EMSCRIPTEN"
|| process.env.BOARD=="EMSCRIPTEN2")?1:0;
},
state: {},
on_gps: function(f) {
let fix = this.getGPSFix();
f(fix);
/*
"lat": number, // Latitude in degrees
"lon": number, // Longitude in degrees
"alt": number, // altitude in M
"speed": number, // Speed in kph
"course": number, // Course in degrees
"time": Date, // Current Time (or undefined if not known)
"satellites": 7, // Number of satellites
"fix": 1 // NMEA Fix state - 0 is no fix
"hdop": number, // Horizontal Dilution of Precision
*/
this.state.timeout = setTimeout(this.on_gps, 1000, f);
},
off_gps: function() {
clearTimeout(this.state.timeout);
},
getGPSFix: function() {
if (!this.emulator)
return Bangle.getGPSFix();
let fix = {};
fix.fix = 1;
fix.lat = 50;
fix.lon = 14-(getTime()-this.gps_start) / 1000; /* Go West! */
fix.alt = 200;
fix.speed = 5;
fix.course = 30;
fix.time = Date();
fix.satellites = 5;
fix.hdop = 12;
return fix;
},
gps_start : -1,
start_gps: function() {
Bangle.setGPSPower(1, "libgps");
this.gps_start = getTime();
},
stop_gps: function() {
Bangle.setGPSPower(0, "libgps");
},
};
/* ui library 0.1 */
let ui = {
display: 0,
numScreens: 2,
drawMsg: function(msg) {
g.reset().setFont("Vector", 35)
.setColor(1,1,1)
.fillRect(0, this.wi, 176, 176)
.setColor(0,0,0)
.drawString(msg, 5, 30);
},
drawBusy: function() {
this.drawMsg("\n.oO busy");
},
nextScreen: function() {
print("nextS");
this.display = this.display + 1;
if (this.display == this.numScreens)
this.display = 0;
this.drawBusy();
},
prevScreen: function() {
print("prevS");
this.display = this.display - 1;
if (this.display < 0)
this.display = this.numScreens - 1;
this.drawBusy();
},
onSwipe: function(dir) {
this.nextScreen();
},
h: 176,
w: 176,
wi: 32,
last_b: 0,
touchHandler: function(d) {
let x = Math.floor(d.x);
let y = Math.floor(d.y);
if (d.b != 1 || this.last_b != 0) {
this.last_b = d.b;
return;
}
print("touch", x, y, this.h, this.w);
/*
if ((x<this.h/2) && (y<this.w/2)) {
}
if ((x>this.h/2) && (y<this.w/2)) {
}
*/
if ((x<this.h/2) && (y>this.w/2)) {
print("prev");
this.prevScreen();
}
if ((x>this.h/2) && (y>this.w/2)) {
print("next");
this.nextScreen();
}
},
init: function() {
}
};
var last_steps = Bangle.getStepCount(), last_time = getTime(), speed = 0, step_phase = 0;
var mpstep = 0.719 * 1.15;
function updateSteps() {
if (step_phase ++ > 9) {
step_phase =0;
let steps = Bangle.getStepCount();
let time = getTime();
speed = 3.6 * mpstep * ((steps-last_steps) / (time-last_time));
last_steps = steps;
last_time = time;
}
return "" + fmt.fmtSpeed(speed) + " " + step_phase + "\n" + fmt.fmtDist(log_dist/1000) + " " + fmt.fmtDist(log_last/1000);
}
/* compensated compass */
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
const tiltfixread = require("magnav").tiltfixread;
var heading;
var cancel_gps = false;
function drawStats() {
let fix = gps.getGPSFix();
let msg = fmt.fmtFix(fix, getTime() - gps.gps_start);
msg += "\n" + fmt.fmtDist(gps_dist/1000) + " " + fmt.fmtDist(gps_last/1000) + "\n" + updateSteps();
let c = Bangle.getCompass();
if (c) msg += "\n" + c.heading.toFixed(0) + "/" + heading.toFixed(0) + "deg " + log.length + "\n";
g.reset().clear().setFont("Vector", 31)
.setColor(1,1,1)
.fillRect(0, 24, 176, 100)
.setColor(0,0,0)
.drawString(msg, 3, 25);
}
function updateGps() {
if (cancel_gps)
return;
heading = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
if (ui.display == 0) {
setTimeout(updateGps, 1000);
drawLog();
drawStats();
}
if (ui.display == 1) {
setTimeout(updateGps, 1000);
drawLog();
}
}
function stopGps() {
cancel_gps=true;
gps.stop_gps();
}
var log = [], log_dist = 0, gps_dist = 0;
var log_last = 0, gps_last = 0;
function logEntry() {
let e = {};
e.time = getTime();
e.fix = gps.getGPSFix();
e.steps = Bangle.getStepCount();
if (0) {
let c = Bangle.getCompass();
if (c)
e.dir = c.heading;
else
e.dir = -1;
} else {
e.dir = heading;
}
return e;
}
function onTurn() {
let e = logEntry();
log.push(e);
}
function radians(a) { return a*Math.PI/180; }
function degrees(a) { return a*180/Math.PI; }
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
// https://www.movable-type.co.uk/scripts/latlong.html
// (Equirectangular approximation)
function calcDistance(a,b) {
var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2));
var y = radians(b.lat-a.lat);
return Math.sqrt(x*x + y*y) * 6371000;
}
var dn, de;
function initConv(fix) {
let n = { lat: fix.lat+1, lon: fix.lon };
let e = { lat: fix.lat, lon: fix.lon+1 };
dn = calcDistance(fix, n);
de = calcDistance(fix, e);
print("conversion is ", dn, 108000, de, 50000);
}
function toM(start, fix) {
return { x: (fix.lon - start.lon) * de, y: (fix.lat - start.lat) * dn };
}
var mpp = 4;
function toPix(q) {
let p = { x: q.x, y: q.y };
p.x /= mpp; /* 10 m / pix */
p.y /= -mpp;
p.x += 85;
p.y += 85;
return p;
}
function drawLog() {
let here = logEntry();
if (!here.fix.lat) {
here.fix.lat = 50;
here.fix.lon = 14;
}
initConv(here.fix);
log.push(here);
let l = log;
log_dist = 0;
log_last = -1;
gps_last = -1;
g.reset().clear();
g.setColor(0, 0, 1);
let last = { x: 0, y: 0 };
for (let i = l.length - 2; i >= 0; i--) {
let next = {};
let m = (l[i+1].steps - l[i].steps) * mpstep;
let dir = radians(180 + l[i].dir);
next.x = last.x + m * Math.sin(dir);
next.y = last.y + m * Math.cos(dir);
print(dir, m, last, next);
let lp = toPix(last);
let np = toPix(next);
g.drawLine(lp.x, lp.y, np.x, np.y);
g.drawCircle(np.x, np.y, 3);
last = next;
if (log_last == -1)
log_last = m;
log_dist += m;
}
g.setColor(0, 1, 0);
last = { x: 0, y: 0 };
gps_dist = 0;
for (let i = l.length - 2; i >= 0; i--) {
let fix = l[i].fix;
if (fix.fix && fix.lat) {
let next = toM(here.fix, fix);
let lp = toPix(last);
let np = toPix(next);
let d = Math.sqrt((next.x-last.x)*(next.x-last.x)+(next.y-last.y)*(next.y-last.y));
if (gps_last == -1)
gps_last = d;
gps_dist += d;
g.drawLine(lp.x, lp.y, np.x, np.y);
g.drawCircle(np.x, np.y, 3);
last = next;
}
}
log.pop();
}
function testPaint() {
let pos = gps.getGPSFix();
log = [];
let e = { fix: pos, steps: 100, dir: 0 };
log.push(e);
e = { fix: pos, steps: 200, dir: 90 };
log.push(e);
e = { fix: pos, steps: 300, dir: 0 };
log.push(e);
print(log, log.length, log[0], log[1]);
drawLog();
}
function touchHandler(d) {
let x = Math.floor(d.x);
let y = Math.floor(d.y);
if (d.b != 1 || ui.last_b != 0) {
ui.last_b = d.b;
return;
}
if ((x<ui.h/2) && (y<ui.w/2)) {
ui.drawMsg("Turn");
onTurn();
}
if ((x>ui.h/2) && (y<ui.w/2)) {
ui.drawMsg("Writing");
require('Storage').writeJSON("speedstep."+getTime()+".json", log);
ui.drawMsg("Wrote");
}
ui.touchHandler(d);
}
fmt.init();
gps.init();
ui.init();
ui.drawBusy();
gps.start_gps();
Bangle.setCompassPower(1, "speedstep");
Bangle.on("drag", touchHandler);
Bangle.setUI({
mode : "custom",
swipe : (s) => ui.onSwipe(s),
clock : 0
});
if (0)
testPaint();
if (1) {
g.reset();
updateGps();
}

View File

@ -0,0 +1,13 @@
{ "id": "dedreckon",
"name": "Ded Reckon",
"version": "0.01",
"description": "Dead Reckoning using compass and step counter",
"icon": "app.png",
"readme": "README.md",
"supports" : ["BANGLEJS2"],
"tags": "outdoors",
"storage": [
{"name":"dedreckon.app.js","url":"dedreckon.app.js"},
{"name":"dedreckon.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -4,3 +4,4 @@
Also avoid polluting global scope. Also avoid polluting global scope.
0.04: Enhance menu: enable bluetooth, visit settings & visit recovery 0.04: Enhance menu: enable bluetooth, visit settings & visit recovery
0.05: Enhance menu: permit toggling bluetooth 0.05: Enhance menu: permit toggling bluetooth
0.06: Display clock in green when charging, with "charging" text

View File

@ -37,14 +37,19 @@ var draw = function () {
require("locale").dow(date, 0).toUpperCase(); require("locale").dow(date, 0).toUpperCase();
var x2 = x + 6; var x2 = x + 6;
var y2 = y + 66; var y2 = y + 66;
var charging = Bangle.isCharging();
g.reset() g.reset()
.clearRect(Bangle.appRect) .clearRect(Bangle.appRect)
.setFont("Vector", 55) .setFont("Vector", 55)
.setFontAlign(0, 0) .setFontAlign(0, 0)
.setColor(charging ? "#0f0" : g.theme.fg)
.drawString(timeStr, x, y) .drawString(timeStr, x, y)
.setFont("Vector", 24) .setFont("Vector", 24)
.drawString(dateStr, x2, y2) .drawString(dateStr, x2, y2);
.drawString("".concat(E.getBattery(), "%"), x2, y2 + 48); if (charging)
g.drawString("charging: ".concat(E.getBattery(), "%"), x2, y2 + 48);
else
g.drawString("".concat(E.getBattery(), "%"), x2, y2 + 48);
if (nextDraw) if (nextDraw)
clearTimeout(nextDraw); clearTimeout(nextDraw);
nextDraw = setTimeout(function () { nextDraw = setTimeout(function () {
@ -96,8 +101,10 @@ function drainedRestore() {
load(); load();
} }
var checkCharge = function () { var checkCharge = function () {
if (E.getBattery() < restore) if (E.getBattery() < restore) {
draw();
return; return;
}
drainedRestore(); drainedRestore();
}; };
if (Bangle.isCharging()) if (Bangle.isCharging())

View File

@ -54,15 +54,21 @@ const draw = () => {
require("locale").dow(date, 0).toUpperCase(); require("locale").dow(date, 0).toUpperCase();
const x2 = x + 6; const x2 = x + 6;
const y2 = y + 66; const y2 = y + 66;
const charging = Bangle.isCharging();
g.reset() g.reset()
.clearRect(Bangle.appRect) .clearRect(Bangle.appRect)
.setFont("Vector", 55) .setFont("Vector", 55)
.setFontAlign(0, 0) .setFontAlign(0, 0)
.setColor(charging ? "#0f0" : g.theme.fg)
.drawString(timeStr, x, y) .drawString(timeStr, x, y)
.setFont("Vector", 24) .setFont("Vector", 24)
.drawString(dateStr, x2, y2) .drawString(dateStr, x2, y2);
.drawString(`${E.getBattery()}%`, x2, y2 + 48);
if(charging)
g.drawString(`charging: ${E.getBattery()}%`, x2, y2 + 48);
else
g.drawString(`${E.getBattery()}%`, x2, y2 + 48);
if(nextDraw) clearTimeout(nextDraw); if(nextDraw) clearTimeout(nextDraw);
nextDraw = setTimeout(() => { nextDraw = setTimeout(() => {
@ -125,7 +131,10 @@ function drainedRestore() { // "public", to allow users to call
} }
const checkCharge = () => { const checkCharge = () => {
if(E.getBattery() < restore) return; if(E.getBattery() < restore) {
draw();
return;
}
drainedRestore(); drainedRestore();
}; };

View File

@ -1,7 +1,7 @@
{ {
"id": "drained", "id": "drained",
"name": "Drained", "name": "Drained",
"version": "0.05", "version": "0.06",
"description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals", "description": "Switches to displaying a simple clock when the battery percentage is low, and disables some peripherals",
"readme": "README.md", "readme": "README.md",
"icon": "icon.png", "icon": "icon.png",

View File

@ -1,3 +1,4 @@
0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock 0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock
0.02: Fix weather icon for languages other than English

View File

@ -1,5 +1,5 @@
const w = require("weather"); const w = require("weather");
//const locale = require("locale"); const locale = require("locale");
// Weather icons from https://icons8.com/icon/set/weather/color // Weather icons from https://icons8.com/icon/set/weather/color
function getSun() { function getSun() {
@ -67,6 +67,33 @@ function chooseIcon(condition) {
return getPartSun; return getPartSun;
} else return getErr; } else return getErr;
} }
/*
* Choose weather icon to display based on weather conditition code
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
*/
function chooseIconByCode(code) {
const codeGroup = Math.round(code / 100);
switch (codeGroup) {
case 2: return getStorm;
case 3: return getRain;
case 5:
switch (code) {
case 511: return getSnow;
default: return getRain;
}
case 6: return getSnow;
case 7: return getPartSun;
case 8:
switch (code) {
case 800: return getSun;
case 804: return getCloud;
default: return getPartSun;
}
default: return getCloud;
}
}
/*function condenseWeather(condition) { /*function condenseWeather(condition) {
condition = condition.toLowerCase(); condition = condition.toLowerCase();
if (condition.includes("thunderstorm") || if (condition.includes("thunderstorm") ||
@ -143,8 +170,17 @@ const clock = new ClockFace({
//let cWea =(curr === "no data" ? "no data" : curr.txt); //let cWea =(curr === "no data" ? "no data" : curr.txt);
let cTemp= (curr === "no data" ? 273 : curr.temp); let cTemp= (curr === "no data" ? 273 : curr.temp);
// const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/); // const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/);
let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt );
//let w_icon = chooseIcon(curr.txt); let w_icon = getErr;
if (locale.name === "en" || locale.name === "en_GB" || locale.name === "en_US") {
w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt);
} else {
// cannot use condition string to determine icon if language is not English; use weather code instead
const code = curr.code || -1;
if (code > 0) {
w_icon = chooseIconByCode(curr.code);
}
}
g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale); g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale);
g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale); g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale);

View File

@ -1,7 +1,7 @@
{ {
"id": "ffcniftyapp", "id": "ffcniftyapp",
"name": "Nifty-A Clock ++", "name": "Nifty-A Clock ++",
"version": "0.01", "version": "0.02",
"description": "A nifty clock with time and date and more", "description": "A nifty clock with time and date and more",
"dependencies": {"weather":"app"}, "dependencies": {"weather":"app"},
"icon": "app.png", "icon": "app.png",

View File

@ -106,6 +106,7 @@ function onInit(device) {
else if (crcs[0] == 3816337552) version = "2v21"; else if (crcs[0] == 3816337552) version = "2v21";
else if (crcs[0] == 3329616485) version = "2v22"; else if (crcs[0] == 3329616485) version = "2v22";
else if (crcs[0] == 1569433504) version = "2v23"; else if (crcs[0] == 1569433504) version = "2v23";
else if (crcs[0] == 680675961) version = "2v24";
else { // for other versions all 7 pages are used, check those else { // for other versions all 7 pages are used, check those
var crc = crcs[1]; var crc = crcs[1];
if (crc==1339551013) { version = "2v10.219"; ok = false; } if (crc==1339551013) { version = "2v10.219"; ok = false; }

View File

@ -138,4 +138,6 @@
0.25: Minor code improvements 0.25: Minor code improvements
0.26: Support for large paths (grid sizes > 65k) 0.26: Add option to plot openstmap if installed
0.27: Support for large paths (grid sizes > 65k)

View File

@ -666,11 +666,11 @@ class Status {
towards = next_point; towards = next_point;
} }
let diff = towards.minus(this.projected_point); let diff = towards.minus(this.projected_point);
direction = Math.atan2(diff.lat, diff.lon); const direction = Math.atan2(diff.lat, diff.lon);
let full_angle = direction - this.angle; let full_angle = direction - this.angle;
c = this.projected_point.coordinates( const c = this.projected_point.coordinates(
this.displayed_position, this.displayed_position,
this.adjusted_cos_direction, this.adjusted_cos_direction,
this.adjusted_sin_direction, this.adjusted_sin_direction,
@ -1400,7 +1400,7 @@ function ask_options(fn) {
g.flip(); g.flip();
function options_select(b, xy) { function options_select(b, xy) {
end = false; let end = false;
if (xy.y < height / 2 - 10) { if (xy.y < height / 2 - 10) {
g.setColor(0, 0, 0).fillRect(10, 10, width - 10, height / 2 - 10); g.setColor(0, 0, 0).fillRect(10, 10, width - 10, height / 2 - 10);
g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4); g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4);
@ -1480,6 +1480,98 @@ function start_gipy(path, maps, interests, heights) {
} }
}, },
}; };
try {
// plot openstmap option if installed
const osm = require("openstmap");
menu[/*LANG*/"Plot OpenStMap"] = function() {
E.showMenu(); // remove menu
// compute min/max coordinates
const fix = Bangle.getGPSFix();
let minLat = fix.lat ? fix.lat : 90;
let maxLat = fix.lat ? fix.lat : -90;
let minLong = fix.lon ? fix.lon : 180;
let maxLong = fix.lon ? fix.lon : -180;
for(let i=0; i<path.len; i++) {
const point = path.point(i);
if(point.lat>maxLat) maxLat=point.lat; if(point.lat<minLat) minLat=point.lat;
if(point.lon>maxLong) maxLong=point.lon; if(point.lon<minLong) minLong=point.lon;
}
const max = Bangle.project({lat: maxLat, lon: maxLong});
const min = Bangle.project({lat: minLat, lon: minLong});
const scaleX = (max.x-min.x)/Bangle.appRect.w;
const scaleY = (max.y-min.y)/Bangle.appRect.h;
// openstmap initialization
osm.scale = Math.ceil((scaleX > scaleY ? scaleX : scaleY)*1.1); // add 10% margin
osm.lat = (minLat+maxLat)/2.0;
osm.lon = (minLong+maxLong)/2.0;
const drawOpenStmap = () => {
g.clearRect(Bangle.appRect);
osm.draw();
// draw track
g.setColor("#f09");
for(let i=0; i<path.len; i++) {
const point = path.point(i);
const mp = osm.latLonToXY(point.lat, point.lon);
if (i == 0) {
g.moveTo(mp.x,mp.y);
} else {
g.lineTo(mp.x,mp.y);
}
g.fillCircle(mp.x,mp.y,2); // make the track more visible
}
// draw current position
g.setColor("#000");
if (fix.lat && fix.lon) {
const icon = require("heatshrink").decompress(atob("jEYwYPMyVJkgHEkgICyAHCgIIDyQIChIIEoAIDC4IIEBwOAgEEyVIBAY4DBD4sGHxBQIMRAIIPpAyCHAYILUJEAiVJkAIFgVJXo5fCABQA==")); // 24x24px
const mp = osm.latLonToXY(fix.lat, fix.lon);
g.drawImage(icon, mp.x, mp.y);
}
// labels
g.setFont("6x8",2);
g.setFontAlign(0,0,3);
g.drawString(/*LANG*/"Back", g.getWidth() - 10, g.getHeight()/2);
g.drawString("+", g.getWidth() - 10, g.getHeight()/4);
g.drawString("-", g.getWidth() - 10, g.getHeight()/4*3);
};
drawOpenStmap();
let startDrag = 0;
Bangle.setUI({
mode: "custom",
btn: (n) => { // back handling
g.clearRect(0, 0, g.getWidth(), g.getHeight());
E.showMenu(menu);
},
drag: (ev) => { // zoom, move
if (ev.b) {
osm.scroll(ev.dx, ev.dy);
if (!startDrag) {
startDrag = getTime();
}
} else {
if (getTime() - startDrag < 0.2) {
// tap
if (ev.y > g.getHeight() / 2) {
osm.scale *= 2;
} else {
osm.scale /= 2;
}
}
startDrag = 0;
drawOpenStmap();
}
},
});
};
} catch (ex) {
// openstmap not available.
}
E.showMenu(menu); E.showMenu(menu);
}, },
BTN1, BTN1,

View File

@ -1 +1,2 @@
0.01: attempt to import 0.01: attempt to import
0.02: implement colors and lines

View File

@ -3,5 +3,18 @@
Bitmap editor suitable for creating icons and fonts for BangleJS2. Bitmap editor suitable for creating icons and fonts for BangleJS2.
You'll want to run a copy of this in simulator, and another one on You'll want to run a copy of this in simulator, and another one on
watch to view the results. Draw using the provided tools, then press watch to view the results.
the button, and you'll get result on the console.
Draw using the provided tools, then press the button, and you'll get
result on the console; you can also use "dump();" on command
line. show_icon() takes same parameter as is used in app-icon.js
files, you can just copy&paste it to get an icon. By using
"for_screen();" command, then taking a screenshot, you can easily
generate app.png file.
It is also possible to load existing icon into editor, using
"load_icon("");" command. At the end of iconbits.app.js file there are
more utility functions.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -10,14 +10,16 @@
let kule = [0, 0, 0]; // R, G, B let kule = [0, 0, 0]; // R, G, B
var font_height = 22, font_width = 8; var font_height = 22, font_width = 8;
var zoom_x = 64, zoom_y = 24, zoom_f = 6; var zoom_x = 64, zoom_y = 24, zoom_f = 6;
var color = true;
let oldLock = false; let oldLock = false;
let sg = null; let sg = null;
const top_bar = 20;
function clear(m) { function clear(m) {
sg.setColor(1,1,1).fillRect(0,0, font_width, font_height); sg.setColor(1,1,1).fillRect(0,0, font_width, font_height);
} }
function setup(m) { function __setup(m) {
mode = m; mode = m;
switch (m) { switch (m) {
case 'font': case 'font':
@ -37,19 +39,32 @@
zoom_f = 2; zoom_f = 2;
break; break;
} }
}
function setup(m) {
__setup(m);
sg = Graphics.createArrayBuffer(font_width, font_height, 8, {}); sg = Graphics.createArrayBuffer(font_width, font_height, 8, {});
clear(); clear();
} }
function icon_big() {
zoom_x = 16;
zoom_y = 25;
zoom_f = 3;
}
function icon_small() {
__setup("icon");
}
function updateLock() { function updateLock() {
if (oldLock) { if (oldLock) {
return; return;
} }
g.setColor('#fff'); g.setColor('#fff');
g.fillRect(0, 0, g.getWidth(), 20); g.fillRect(0, 0, g.getWidth(), 20);
g.setFont('6x8', 2); g.setFont('Vector', 22);
g.setColor('#000'); g.setColor('#000');
g.drawString('PLEASE UNLOCK', 10, 2); g.drawString('PLEASE\nUNLOCK', 10, 2);
oldLock = true; oldLock = true;
} }
Bangle.on("lock", function() { Bangle.on("lock", function() {
@ -60,17 +75,20 @@ Bangle.on("lock", function() {
drawUtil(); drawUtil();
} }
}); });
function nextColor () { function nextColor() {
kule[0] = Math.random(); kule[0] = Math.random();
kule[1] = Math.random(); kule[1] = Math.random();
kule[2] = Math.random(); kule[2] = Math.random();
} }
function selectColor (x) { function selectColor(x) {
let c; if (color) {
let i = Math.floor((x - 32) / 4);
kule = toColor(i);
return;
}
let c = 255;
if (x < g.getWidth()/2) { if (x < g.getWidth()/2) {
c = 0; c = 0;
} else {
c = 255;
} }
kule[0] = c; kule[0] = c;
kule[1] = c; kule[1] = c;
@ -79,8 +97,8 @@ Bangle.on("lock", function() {
function nextPen () { function nextPen () {
switch (pen) { switch (pen) {
case 'circle': pen = 'pixel'; break; case 'circle': pen = 'pixel'; break;
case 'pixel': pen = 'crayon'; break; case 'pixel': pen = 'line'; break;
case 'crayon': pen = 'square'; break; case 'line': pen = 'square'; break;
case 'square': pen = 'circle'; break; case 'square': pen = 'circle'; break;
default: pen = 'pixel'; break; default: pen = 'pixel'; break;
} }
@ -89,8 +107,8 @@ Bangle.on("lock", function() {
discard = setTimeout(function () { oldX = -1; oldY = -1; console.log('timeout'); discard = null; }, 500); discard = setTimeout(function () { oldX = -1; oldY = -1; console.log('timeout'); discard = null; }, 500);
} }
var oldX = -1; var oldX = -1, oldY = -1;
var oldY = -1; var line_from = null;
function drawBrushIcon () { function drawBrushIcon () {
const w = g.getWidth(); const w = g.getWidth();
@ -110,13 +128,17 @@ Bangle.on("lock", function() {
g.drawLine(w - 14, 6, w - 10, 12); g.drawLine(w - 14, 6, w - 10, 12);
g.drawLine(w - 6, 6, w - 10, 12); g.drawLine(w - 6, 6, w - 10, 12);
break; break;
case 'line':
g.drawLine(w - 5, 5, w - 15, 15);
break;
} }
} }
function drawArea () { function drawArea() {
g.clear(); g.clear();
if (mode == "draw") if (mode == "draw")
return; return;
const w = g.getWidth;
g.setColor(0, 0, 0.5); g.setColor(0, 0, 0.5);
g.fillRect(0, 0, g.getWidth(), g.getHeight()); g.fillRect(0, 0, g.getWidth(), g.getHeight());
g.setColor(1, 1, 1); g.setColor(1, 1, 1);
@ -129,13 +151,28 @@ Bangle.on("lock", function() {
update(); update();
} }
function drawUtil () { function toColor(i) {
let r = [0, 0, 0];
r[0] = (i % 3) / 2;
i = Math.floor(i / 3);
r[1] = (i % 3) / 2;
i = Math.floor(i / 3);
r[2] = (i % 3) / 2;
return r;
}
function drawUtil() {
if (Bangle.isLocked()) { if (Bangle.isLocked()) {
updateLock(); updateLock();
} }
// titlebar // titlebar
g.setColor(kule[0], kule[1], kule[2]); g.setColor(kule[0], kule[1], kule[2]);
g.fillRect(0, 0, g.getWidth(), 20); g.fillRect(0, 0, g.getWidth(), top_bar);
for (let i = 0; i < 3*3*3; i++) {
let r = toColor(i);
g.setColor(r[0], r[1], r[2]);
g.fillRect(32+4*i, 12, 32+4*i+3, top_bar);
}
// clear button // clear button
g.setColor('#000'); // black g.setColor('#000'); // black
g.fillCircle(10, 10, 8, 8); g.fillCircle(10, 10, 8, 8);
@ -149,7 +186,7 @@ Bangle.on("lock", function() {
drawBrushIcon(); drawBrushIcon();
} }
function transform (p) { function transform(p) {
if (p.x < zoom_x || p.y < zoom_y) if (p.x < zoom_x || p.y < zoom_y)
return p; return p;
p.x = ((p.x - zoom_x) / zoom_f); p.x = ((p.x - zoom_x) / zoom_f);
@ -159,8 +196,12 @@ Bangle.on("lock", function() {
return p; return p;
} }
function __draw (g, from, to) { function __draw(g, from, to) {
let XS = (to.x - from.x) / 32;
let YS = (to.y - from.y) / 32;
switch (pen) { switch (pen) {
case 'line':
case 'pixel': case 'pixel':
g.drawLine(from.x, from.y, to.x, to.y); g.drawLine(from.x, from.y, to.x, to.y);
break; break;
@ -170,27 +211,25 @@ Bangle.on("lock", function() {
g.drawLine(from.x + 2, from.y + 2, to.x, to.y + 2); g.drawLine(from.x + 2, from.y + 2, to.x, to.y + 2);
break; break;
case 'circle': case 'circle':
var XS = (to.x - from.x) / 32;
var YS = (to.y - from.y) / 32;
for (let i = 0; i < 32; i++) { for (let i = 0; i < 32; i++) {
g.fillCircle(from.x + (i * XS), from.y + (i * YS), 4, 4); g.fillCircle(from.x + (i * XS), from.y + (i * YS), 2, 2);
} }
break; break;
case 'square': case 'square':
var XS = (to.x - from.x) / 32;
var YS = (to.y - from.y) / 32;
for (let i = 0; i < 32; i++) { for (let i = 0; i < 32; i++) {
const posX = from.x + (i * XS); const posX = from.x + (i * XS);
const posY = from.y + (i * YS); const posY = from.y + (i * YS);
g.fillRect(posX - 10, posY - 10, posX + 10, posY + 10); g.fillRect(posX - 4, posY - 4, posX + 4, posY + 4);
} }
break; break;
default:
print("Unkown pen ", pen);
} }
} }
function update() { function update() {
g.drawImage(sg, 0, 64, {}); if (zoom_f < 3)
g.drawImage(sg, 4, 64, {});
g.drawImage(sg, zoom_x, zoom_y, { scale: zoom_f }); g.drawImage(sg, zoom_x, zoom_y, { scale: zoom_f });
} }
@ -227,7 +266,7 @@ Bangle.on("lock", function() {
}, 100); }, 100);
// tap and hold the clear button // tap and hold the clear button
if (tap.x < 32 && tap.y < 32) { if (tap.x < 32 && tap.y < top_bar) {
if (tap.b === 1) { if (tap.b === 1) {
if (tapTimer === null) { if (tapTimer === null) {
tapTimer = setTimeout(function () { tapTimer = setTimeout(function () {
@ -244,7 +283,7 @@ Bangle.on("lock", function() {
} }
return; return;
} }
if (tap.x > g.getWidth() - 32 && tap.y < 32) { if (tap.x > g.getWidth() - 32 && tap.y < top_bar) {
if (tap.b === 1) { if (tap.b === 1) {
if (tapTimer === null) { if (tapTimer === null) {
tapTimer = setTimeout(function () { tapTimer = setTimeout(function () {
@ -264,7 +303,7 @@ Bangle.on("lock", function() {
} }
drawUtil(); drawUtil();
return; return;
} else if (tap.y < 32) { } else if (tap.y < top_bar) {
if (mode == "draw") if (mode == "draw")
nextColor(); nextColor();
else else
@ -272,20 +311,31 @@ Bangle.on("lock", function() {
drawUtil(); drawUtil();
return; return;
} }
oldX = to.x;
oldY = to.y;
sg.setColor(kule[0], kule[1], kule[2]); sg.setColor(kule[0], kule[1], kule[2]);
g.setColor(kule[0], kule[1], kule[2]); g.setColor(kule[0], kule[1], kule[2]);
oldX = to.x;
oldY = to.y;
do_draw(from, to); if (pen != "line") {
do_draw(from, to);
} else {
if (tap.b == 1) {
print(line_from);
if (!line_from) {
line_from = to;
} else {
do_draw(line_from, to);
line_from = null;
}
}
}
drawUtil(); drawUtil();
} }
function on_btn(n) {
function dump(n) {
function f(i) { function f(i) {
return "\\x" + i.toString(16).padStart(2, '0'); return "\\x" + i.toString(16).padStart(2, '0');
} }
print("on_btn", n);
print(g.getPixel(0, 0));
let s = f(0) + f(font_width) + f(font_height) + f(1); let s = f(0) + f(font_width) + f(font_height) + f(1);
// 0..black, 65535..white // 0..black, 65535..white
for (let y = 0; y < font_height; y++) { for (let y = 0; y < font_height; y++) {
@ -296,41 +346,55 @@ Bangle.on("lock", function() {
} }
s += f(v); s += f(v);
} }
print("Manual bitmap\n"); if (mode == "font")
print('ft("' + s + '");'); print('show_font("' + s + '");');
if (1) { var im = sg.asImage("string");
s = ""; //print('show_unc_icon("'+btoa(im)+'");');
var im = sg.asImage("string"); print('show_icon("'+btoa(require('heatshrink').compress(im))+'");');
for (var v of im) {
//print("val", v, typeof v);
s += f(v);
}
//print("wh", im, typeof im, im[0], typeof im[0]);
//print("Image:", im.length, s);
print('fi("'+btoa(im)+'");');
}
} }
setup("icon"); setup("icon");
drawArea(); drawArea();
Bangle.setUI({ Bangle.setUI({
"mode": "custom", "mode": "custom",
"drag": on_drag, "drag": on_drag,
"btn": on_btn, "btn": dump,
}); });
drawUtil(); drawUtil();
function show_font(icon) {
function ft(icon) {
g.reset().clear(); g.reset().clear();
g.setFont("Vector", 26).drawString("Hellord" + icon, 0, 0); g.setFont("Vector", 26).drawString("Hellord" + icon, 0, 0);
} }
function fi(icon) { function show_bin_icon(icon) {
g.reset().clear(); g.reset().clear();
g.drawImage(atob(icon), 40, 40); g.drawImage(icon, 40, 40);
}
function show_unc_icon(icon) {
show_bin_icon(atob(icon));
}
function show_icon(icon) {
let unc = require("heatshrink").decompress(atob(icon));
show_bin_icon(unc);
}
function load_bin_icon(i) {
sg.reset().clear();
sg.drawImage(i, 0, 0);
drawArea();
}
function load_icon(icon) {
let unc = require("heatshrink").decompress(atob(icon));
load_bin_icon(unc);
}
function for_screen() {
g.reset().clear();
icon_big();
update();
} }
//ft(icon_10 + "23.1" + icon_hpa);

View File

@ -1,6 +1,6 @@
{ "id": "iconbits", { "id": "iconbits",
"name": "Icon bits", "name": "Icon bits",
"version": "0.01", "version": "0.02",
"description": "Bitmap editor suitable for creating icons", "description": "Bitmap editor suitable for creating icons",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",

View File

@ -1,3 +1,4 @@
0.01: first release 0.01: first release
0.02: Use clock_info module as an app 0.02: Use clock_info module as an app
0.03: clock_info now uses app name to maintain settings specifically for this clock face 0.03: clock_info now uses app name to maintain settings specifically for this clock face
0.04: add optional date display, and a settings page to configure it

View File

@ -5,6 +5,7 @@ A simple clock with the Lato font, with fast load and clock_info
![](screenshot1.png) ![](screenshot1.png)
![](screenshot2.png) ![](screenshot2.png)
![](screenshot3.png) ![](screenshot3.png)
![](screenshot4.png)
This clock is a Lato version of Simplest++. Simplest++ provided the This clock is a Lato version of Simplest++. Simplest++ provided the
smallest example of a clock that supports 'fast load' and 'clock smallest example of a clock that supports 'fast load' and 'clock
@ -25,6 +26,8 @@ Pastel Clock.
* Settings are saved automatically and reloaded along with the clock. * Settings are saved automatically and reloaded along with the clock.
* Date display can be enabled and disabled, along with format choice in the app settings
## About Clock Info's ## About Clock Info's
* The clock info modules enable all clocks to add the display of information to the clock face. * The clock info modules enable all clocks to add the display of information to the clock face.
@ -52,3 +55,5 @@ Pastel Clock.
Written by: [Hugh Barney](https://github.com/hughbarney) For support Written by: [Hugh Barney](https://github.com/hughbarney) For support
and discussion please post in the [Bangle JS and discussion please post in the [Bangle JS
Forum](http://forum.espruino.com/microcosms/1424/) Forum](http://forum.espruino.com/microcosms/1424/)
Date functionality added by [Septolum](https://github.com/Septolum)

View File

@ -38,6 +38,11 @@ Graphics.prototype.setFontLatoSmall = function(scale) {
// must be inside our own scope here so that when we are unloaded everything disappears // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let settings = Object.assign({
dateDisplay: false,
dateFormat: 0,
}, require("Storage").readJSON("lato.json", true) || {});
let draw = function() { let draw = function() {
var date = new Date(); var date = new Date();
var timeStr = require("locale").time(date,1); var timeStr = require("locale").time(date,1);
@ -53,6 +58,25 @@ Graphics.prototype.setFontLatoSmall = function(scale) {
g.setFontAlign(0, 0); g.setFontAlign(0, 0);
g.setColor(g.theme.fg); g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2); g.drawString(timeStr, w/2, h/2);
if (settings.dateDisplay) {
switch (settings.dateFormat) {
case 1:
var dateStr = require("locale").date(date,1);
break;
case 2:
var dateStr = require("locale").date(date);
break;
default:
var dateStr = require("locale").dow(date,1) + ', ' + date.getDate() + ' ' + require("locale").month(date,1);
break;
}
g.setFontVector(16);
g.drawString(dateStr, w/2, h/4 -4);
}
clockInfoMenu.redraw(); // clock_info_support clockInfoMenu.redraw(); // clock_info_support
// schedule a draw for the next minute // schedule a draw for the next minute

View File

@ -1,7 +1,7 @@
{ {
"id": "lato", "id": "lato",
"name": "Lato", "name": "Lato",
"version": "0.03", "version": "0.04",
"description": "A Lato Font clock with fast load and clock_info", "description": "A Lato Font clock with fast load and clock_info",
"readme": "README.md", "readme": "README.md",
"icon": "app.png", "icon": "app.png",
@ -12,6 +12,10 @@
"dependencies" : { "clock_info":"module" }, "dependencies" : { "clock_info":"module" },
"storage": [ "storage": [
{"name":"lato.app.js","url":"app.js"}, {"name":"lato.app.js","url":"app.js"},
{"name":"lato.img","url":"icon.js","evaluate":true} {"name":"lato.img","url":"icon.js","evaluate":true},
{"name":"lato.settings.js","url":"settings.js"}
],
"data": [
{"name":"lato.json"}
] ]
} }

BIN
apps/lato/screenshot4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

24
apps/lato/settings.js Normal file
View File

@ -0,0 +1,24 @@
(function(back) {
let settings = require('Storage').readJSON('lato.json',1)||{};
if (typeof settings.dateDisplay !== "boolean") settings.dateDisplay = false; // default value
if (typeof settings.dateFormat !== "number") settings.dateFormat = 0; // default value
function save(key, value) {
settings[key] = value;
require('Storage').write('lato.json', settings);
}
const appMenu = {
'': {'title': 'Lato'},
'< Back': back,
'Display Date?': {
value: settings.dateDisplay,
onchange: (v) => {save('dateDisplay', v)}
},
"Date Format": {
value: settings.dateFormat,
min: 0, max: 2,
format: v => ["DoW, dd MMM","Locale Short","Locale Long"][v],
onchange: (v) => {save('dateFormat', v)}
}
};
E.showMenu(appMenu)
})

View File

@ -196,12 +196,6 @@ module.exports = {
"no-undef" "no-undef"
] ]
}, },
"apps/sixths/sixths.app.js": {
"hash": "2a4676828bdf78df052df402de34e6f1abd1c847ebe0d193fc789cd6e9dd0e5c",
"rules": [
"no-undef"
]
},
"apps/scribble/app.js": { "apps/scribble/app.js": {
"hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd", "hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd",
"rules": [ "rules": [
@ -1021,12 +1015,6 @@ module.exports = {
"no-undef" "no-undef"
] ]
}, },
"apps/gipy/app.js": {
"hash": "41f342e8ef6f2a87b3aea19b75ee45cfdfeff723b94281049e3ae0ec89cddba5",
"rules": [
"no-undef"
]
},
"apps/geissclk/precompute.js": { "apps/geissclk/precompute.js": {
"hash": "2317812a9e348e7883e93a4be9e294ad7accd4dc3f0e31ee00343e2412030f98", "hash": "2317812a9e348e7883e93a4be9e294ad7accd4dc3f0e31ee00343e2412030f98",
"rules": [ "rules": [
@ -1249,12 +1237,6 @@ module.exports = {
"no-undef" "no-undef"
] ]
}, },
"apps/accelrec/app.js": {
"hash": "b5369a60afc8f360f0b33f71080eb3f5d09a1bf3703acfcf07cd80dd19f1997d",
"rules": [
"no-undef"
]
},
"apps/BLEcontroller/app-joy.js": { "apps/BLEcontroller/app-joy.js": {
"hash": "e4f34bb1bc11b52c3d7a1c537a140b0e23ccef82694dcd602cb517a8ba342898", "hash": "e4f34bb1bc11b52c3d7a1c537a140b0e23ccef82694dcd602cb517a8ba342898",
"rules": [ "rules": [

View File

@ -1,10 +1,12 @@
<html> <!DOCTYPE html>
<html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="../../css/spectre.min.css"> <link rel="stylesheet" href="../../css/spectre.min.css">
<style> <style>
table { width:100%;} table {width:100%;margin-top:3%;}
.table_t {font-weight:bold;width:40%;}; .table_t {font-weight:bold;width:40%;}
.form-group > * {display:block;}
</style> </style>
</head> </head>
<body> <body>
@ -15,18 +17,20 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<input id="translations" type="checkbox" /> <label for="translations">Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label> <label><input id="translations" type="checkbox" /> Add common language translations like "Yes", "No", "On", "Off"<br/><i>(Not recommended. For translations use the option under <code>More...</code> in the app loader.</i></label>
<label><input id="customize" type="checkbox" /> Advanced: Customize the date and time formats.</label>
</div> </div>
<p> <p>
<table id="examples"> <span id="customize-warning"></span>
<table id="examples-short-long"></table>
<table id="examples"></table>
</p> </p>
</table>
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p> <p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../core/lib/customize.js"></script> <script src="../../core/lib/customize.js"></script>
<script src="../../core/js/utils.js"></script> <script src="../../core/js/utils.js"></script>
<script src="locales.js" charset="utf-8"></script> <script src="locales.js"></script>
<script> <script>
/* /*
@ -125,15 +129,14 @@ exports = { name : "system", currencySym:"£",
}); });
function createLocaleModule(lang) { function createLocaleModule() {
console.log(`Language ${lang}`); console.log(`Language ${lang}`);
const translations = document.getElementById('translations').checked; const translations = document.getElementById('translations').checked;
console.log(`Translations: ${translations}`); console.log(`Translations: ${translations}`);
const locale = locales[lang];
if (!locale) { if (!locale) {
alert(`Language ${lang} not found!`); alert(`Locale not set for language ${lang}!`);
return; return;
} }
@ -185,16 +188,10 @@ exports = { name : "system", currencySym:"£",
"%P": `d.getHours()<12?${js(locale.ampm[0].toLowerCase())}:${js(locale.ampm[1].toLowerCase())}` "%P": `d.getHours()<12?${js(locale.ampm[0].toLowerCase())}:${js(locale.ampm[1].toLowerCase())}`
}; };
var timeN = locale.timePattern[0]; var timeN = patternToCode(locale.timePattern[0]);
var timeS = locale.timePattern[1]; var timeS = patternToCode(locale.timePattern[1]);
var dateN = locale.datePattern[0]; var dateN = patternToCode(locale.datePattern[0]);
var dateS = locale.datePattern[1]; var dateS = patternToCode(locale.datePattern[1]);
Object.keys(replaceList).forEach(e => {
timeN = timeN.replace(e,"${"+replaceList[e]+"}");
timeS = timeS.replace(e,"${"+replaceList[e]+"}");
dateN = dateN.replace(e,"${"+replaceList[e]+"}");
dateS = dateS.replace(e,"${"+replaceList[e]+"}");
});
var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't'; var temperature = locale.temperature=='°F' ? '(t*9/5)+32' : 't';
function getLocaleModule(isLocal) { function getLocaleModule(isLocal) {
@ -246,46 +243,200 @@ exports = {
eval(getLocaleModule(true)); eval(getLocaleModule(true));
console.log("exports:",exports); console.log("exports:",exports);
function patternToCode(pattern){
for(const symbol of Object.keys(replaceList)){
pattern = pattern.replaceAll(symbol,"${"+replaceList[symbol]+"}");
}
return pattern;
}
function patternToOutput(pattern){
const code = patternToCode(pattern);
const result = eval(`let d = new Date();\`${code}\``);
return result;
}
function dataList(id, options, formatter){
let output = `<datalist id="${id}">`;
for(const option of options){
const formatted = formatter?.(option) || option;
output+=`\n<option value="${option}">${formatted}</option>`
}
output += "\n</datalist>";
return output;
}
var date = new Date(); var date = new Date();
document.getElementById("examples").innerHTML = ` // TODO: This warning should have a link to an article explaining how the formats work, and how long they are allowed to be
document.getElementById("customize-warning").innerText = customizeLocale ? "⚠️ If you make the formats too long, some apps will not work!" : "";
document.getElementById("examples-short-long").innerHTML = `
<tr><td class="table_t"></td><td style="font-weight:bold">Short</td><td style="font-weight:bold">Long</td></tr> <tr><td class="table_t"></td><td style="font-weight:bold">Short</td><td style="font-weight:bold">Long</td></tr>
<tr><td class="table_t">Day</td><td>${exports.dow(date,1)}</td><td>${exports.dow(date,0)}</td></tr> <tr><td class="table_t">Day</td><td>${exports.dow(date,1)}</td><td>${exports.dow(date,0)}</td></tr>
<tr><td class="table_t">Month</td><td>${exports.month(date,1)}</td><td>${exports.month(date,0)}</td></tr> <tr><td class="table_t">Month</td><td>${exports.month(date,1)}</td><td>${exports.month(date,0)}</td></tr>
<tr><td class="table_t">Date</td><td>${exports.date(date,1)}</td><td>${exports.date(date,0)}</td></tr> <tr><td class="table_t">Date</td>
<tr><td class="table_t">Time</td><td>${exports.time(date,1)}</td><td>${exports.time(date,0)}</td></tr> <td id="short-date-pattern-output">${exports.date(date,1)}</td>
<tr><td class="table_t">Number</td><td>${exports.number(12.3456789)}</td><td>${exports.number(12.3456789,4)}</td></tr> <td id="long-date-pattern-output">${exports.date(date,0)}</td>
<tr><td class="table_t">Distance</td><td>${exports.distance(12.34,0)}</td><td>${exports.distance(12345.6,1)}</td></tr> </tr>
<tr><td class="table_t">Speed</td><td></td><td>${exports.speed(123)}</td></tr> ${customizeLocale ? `<tr><td class="table_t">Date format</td>
<tr><td class="table_t">Temperature</td><td></td><td>${exports.temp(12,0)}</td></tr> <td>
<input type=text id="short-date-pattern" list="short-date-patterns" value="${locale?.datePattern["1"]}"/>
${dataList("short-date-patterns", [locale?.datePattern["1"], "%-d.%-m.%y", "%-d/%-m/%y", "%d/%m/%Y"], patternToOutput)}
</td>
<td>
<input type=text id="long-date-pattern" list="long-date-patterns" value="${locale?.datePattern["0"]}"/>
${dataList("long-date-patterns", [locale?.datePattern["0"], "%-d. %b %Y", "%b %d, %Y"], patternToOutput)}
</td>
</td>`
: ""}
<tr><td class="table_t">Time</td>
<td id="short-time-pattern-output">${exports.time(date,1)}</td>
<td id="long-time-pattern-output">${exports.time(date,0)}</td>
</tr>
${customizeLocale ? `<tr><td class="table_t">Time format</td>
<td>
<input type=text id="short-time-pattern" list="short-time-patterns" value="${locale?.timePattern["1"]}"/>
${dataList("short-time-patterns", [ "%HH.%MM", "%HH:%MM"], patternToOutput)}
</td>
<td>
<input type=text id="long-time-pattern" list="long-time-patterns" value="${locale?.timePattern["0"]}"/>
${dataList("long-time-patterns", [locale?.timePattern["0"], "%HH.%MM.%SS", "%HH:%MM:%SS"], patternToOutput)}
</td>
</td>`
: ""}
<tr><td class="table_t">Number</td><td>${exports.number(12.3456789)}</td><td>${exports.number(12.3456789,4)}</td></tr>
<tr><td class="table_t">Distance</td><td>${exports.distance(12.34,0)}</td><td>${exports.distance(12345.6,1)}</td></tr>
`; `;
document.getElementById("examples").innerHTML = `
<tr><td class="table_t">Meridian</td><td>
<span id="meridian-am-output">${exports.meridian(new Date(0))}</span> /
<span id="meridian-pm-output">${exports.meridian(new Date(43200000))}</span>
</td></tr>
${customizeLocale ? `<tr><td class="table_t">Meridian names</td>
<td>
<input type=text id="meridian-am" list="meridian-ams" value="${locale?.ampm["0"]}"/>
${dataList("meridian-ams", [locale?.ampm["0"], "AM"])}
</td>
<td>
<input type=text id="meridian-pm" list="meridian-pms" value="${locale?.ampm["1"]}"/>
${dataList("meridian-pms", [locale?.ampm["1"], "PM"])}
</td>
</tr>`
: ""}
<tr><td class="table_t">Speed</td><td>${exports.speed(123)}</td></tr>
<tr><td class="table_t">Temperature</td><td>${exports.temp(12,0)}</td></tr>
`;
if(customizeLocale){
document.querySelector("input#short-date-pattern").addEventListener("input", event => {
locale.datePattern["1"] = event.target.value;
document.querySelector("td#short-date-pattern-output").innerText = patternToOutput(event.target.value);
});
document.querySelector("input#long-date-pattern").addEventListener("input", event => {
locale.datePattern["0"] = event.target.value;
document.querySelector("td#long-date-pattern-output").innerText = patternToOutput(event.target.value);
});
document.querySelector("input#short-time-pattern").addEventListener("input", event => {
locale.timePattern["1"] = event.target.value;
document.querySelector("td#short-time-pattern-output").innerText = patternToOutput(event.target.value);
});
document.querySelector("input#long-time-pattern").addEventListener("input", event => {
locale.timePattern["0"] = event.target.value;
document.querySelector("td#long-time-pattern-output").innerText = patternToOutput(event.target.value);
});
document.querySelector("input#meridian-am").addEventListener("input", event => {
locale.ampm["0"] = event.target.value;
document.querySelector("span#meridian-am-output").innerText = event.target.value;
});
document.querySelector("input#meridian-pm").addEventListener("input", event => {
locale.ampm["1"] = event.target.value;
document.querySelector("span#meridian-pm-output").innerText = event.target.value;
});
}
return getLocaleModule(false); return getLocaleModule(false);
} }
const lastUploadedLocaleID = "last-uploaded-locale";
let lastUploadedLocale;
try{
lastUploadedLocale = JSON.parse(localStorage?.getItem(lastUploadedLocaleID));
}catch(error){
console.warn("Unable to load last uploaded locale", error);
}
if(lastUploadedLocale){
if(!lastUploadedLocale.lang){
lastUploadedLocale = undefined;
console.warn("Unable to load last uploaded locale, it is missing the lang entry");
}else if(lastUploadedLocale.custom){
// Make sure to add any missing data from the original lang
// We don't know if fx a new entry has been added after the locale was last saved
const originalLocale = structuredClone(locales[lastUploadedLocale.lang]);
lastUploadedLocale = {...originalLocale, ...lastUploadedLocale};
// Add a special entry for the custom locale, put it first in the list
locales = {[lastUploadedLocaleID]: lastUploadedLocale, ...locales};
}
}
var lang;
var locale;
var customizeLocale = false;
var languageSelector = document.getElementById("languages"); var languageSelector = document.getElementById("languages");
var customizeSelector = document.getElementById('customize');
languageSelector.innerHTML = Object.keys(locales).map(l=>{ languageSelector.innerHTML = Object.keys(locales).map(l=>{
var locale = locales[l]; var locale = locales[l];
var localeParts = l.split("_"); // en_GB -> ["en","GB"] var name = l === lastUploadedLocaleID ? `Custom locale based on ${locale.lang}` : locale.lang;
var localeParts = locale.lang.split("_"); // en_GB -> ["en","GB"]
var icon = ""; var icon = "";
// If we have a 2 char ISO country code, use it to get the unicode flag // If we have a 2 char ISO country code, use it to get the unicode flag
if (locale.icon) if (locale.icon)
icon = locale.icon+" "; icon = locale.icon+" ";
else if (localeParts[1] && localeParts[1].length==2) else if (localeParts[1] && localeParts[1].length==2)
icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" "; icon = localeParts[1].toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0)+127397) )+" ";
return `<option value="${l}">${icon}${l}${locale.notes?" - "+locale.notes:""}</option>` return `<option value="${l}">${icon}${name}${locale.notes?" - "+locale.notes:""}</option>`
}).join("\n"); }).join("\n");
languageSelector.addEventListener('change', function() { if(lastUploadedLocale){
const lang = languageSelector.options[languageSelector.selectedIndex].value; if(lastUploadedLocale.custom){
createLocaleModule(lang); // If the last uploaded locale was customized, choose the custom locale as default value
}); languageSelector.value = lastUploadedLocaleID;
// initial value }else{
createLocaleModule(languageSelector.options[languageSelector.selectedIndex].value); // If the last uploaded locale was not customized, choose the existing locale in the list as the default value
languageSelector.value = lastUploadedLocale.lang;
}
}
languageSelector.addEventListener('change', handleLanguageChange);
function handleLanguageChange(){
lang = languageSelector.value;
locale = structuredClone(locales[lang]);
// If the locale is customized, make sure the customization option is activated. If not, disable it.
if(Boolean(customizeSelector.checked) !== Boolean(locale.custom)){
customizeSelector.checked = Boolean(locale.custom);
handleCustomizeChange();
}else{
createLocaleModule();
}
}
customizeSelector.addEventListener('change', handleCustomizeChange);
function handleCustomizeChange(){
customizeLocale = customizeSelector.checked;
// If the user no longer wants to customize, make sure to return to the default lang entry
if(!customizeLocale){
languageSelector.value = locales[lang].lang;
handleLanguageChange();
}else{
createLocaleModule();
}
}
// set initial values
handleLanguageChange();
document.getElementById("upload").addEventListener("click", function() { document.getElementById("upload").addEventListener("click", function() {
const lang = languageSelector.options[languageSelector.selectedIndex].value; var localeModule = createLocaleModule();
var localeModule = createLocaleModule(lang);
// Save the locale data to make it easier to upload the same locale next time.
// If the locale is not customized, only save the lang. The rest of the data will be added when the page loads next time.
const savedLocaleData = customizeLocale ? {...locale, custom: true} : {lang: locale.lang};
localStorage?.setItem(lastUploadedLocaleID, JSON.stringify(savedLocaleData));
console.log("Locale Module is:",localeModule); console.log("Locale Module is:",localeModule);
sendCustomizedApp({ sendCustomizedApp({

View File

@ -1,2 +1,3 @@
0.1: Initial release 0.1: Initial release
0.2: Draw line for 3d effect, fix number alignment 0.2: Draw line for 3d effect, fix number alignment
0.3: Fix day-end overflowing hour calculation

View File

@ -78,6 +78,19 @@
return lineEndFull - 5; return lineEndFull - 5;
}; };
let drawHourString = function(hour, yLines) {
var hourForDrawing = 0;
if (hour < 0) {
// a negative hour => (+ and - = -)
hourForDrawing = 24 + hour;
} else if (hour >= 24) {
hourForDrawing = hour - 24;
} else {
hourForDrawing = hour;
}
g.drawString(hourForDrawing, hourStringXOffset(hourForDrawing), yLines, true);
};
let drawTime = function () { let drawTime = function () {
g.clear(); g.clear();
var d = new Date(); var d = new Date();
@ -101,12 +114,12 @@
switch (yTopLines - 88 + mins) { switch (yTopLines - 88 + mins) {
case -60: case -60:
lineEnd = lineEndFull; lineEnd = lineEndFull;
g.drawString(d.getHours()-1, hourStringXOffset(d.getHours()-1), yTopLines, true); drawHourString(d.getHours() - 1, yTopLines);
break; break;
case 0: case 0:
case 60: case 60:
lineEnd = lineEndFull; lineEnd = lineEndFull;
g.drawString(d.getHours(), hourStringXOffset(d.getHours()), yTopLines, true); drawHourString(d.getHours(), yTopLines);
break; break;
case 45: case 45:
case -45: case -45:
@ -136,11 +149,11 @@
case 0: case 0:
case 60: case 60:
lineEnd = lineEndFull; lineEnd = lineEndFull;
g.drawString(d.getHours() + 1, hourStringXOffset(d.getHours()+1), yBottomLines, true); drawHourString(d.getHours() + 1, yBottomLines);
break; break;
case 120: case 120:
lineEnd = lineEndFull; lineEnd = lineEndFull;
g.drawString(d.getHours() + 2, hourStringXOffset(d.getHours()+2), yBottomLines, true); drawHourString(d.getHours() + 2, yBottomLines);
break; break;
case 15: case 15:
case 75: case 75:

View File

@ -1,7 +1,7 @@
{ {
"id": "measuretime", "id": "measuretime",
"name": "Measure Time", "name": "Measure Time",
"version": "0.2", "version": "0.3",
"description": "Measure Time in a fancy way.", "description": "Measure Time in a fancy way.",
"icon": "measuretime_icon.png", "icon": "measuretime_icon.png",
"screenshots": [ "screenshots": [

14
apps/pomodo/README.md Normal file
View File

@ -0,0 +1,14 @@
# Pomodoro
> The Pomodoro Technique is a time management method developed by Francesco Cirillo in the late 1980s. It uses a kitchen timer to break work into intervals, typically 25 minutes in length, separated by short breaks. Each interval is known as a pomodoro, from the Italian word for tomato, after the tomato-shaped kitchen timer Cirillo used as a university student.
>
> The original technique has six steps:
>
> Decide on the task to be done.
> Set the Pomodoro timer (typically for 25 minutes).
> Work on the task.
> End work when the timer rings and take a short break (typically 510 minutes).
> Go back to Step 2 and repeat until you complete four pomodori.
> After four pomodori are done, take a long break (typically 20 to 30 minutes) instead of a short break. Once the long break is finished, return to step 2.
*Description gathered from https://en.wikipedia.org/wiki/Pomodoro_Technique*

View File

@ -7,6 +7,7 @@
"type": "app", "type": "app",
"tags": "pomodoro,cooking,tools", "tags": "pomodoro,cooking,tools",
"supports": ["BANGLEJS", "BANGLEJS2"], "supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true, "allow_emulator": true,
"screenshots": [{"url":"bangle2-pomodoro-screenshot.png"}], "screenshots": [{"url":"bangle2-pomodoro-screenshot.png"}],
"storage": [ "storage": [

View File

@ -8,3 +8,4 @@
0.05: Fix display of final menu item when no options are given and 0.05: Fix display of final menu item when no options are given and
handling of E.showMenu() with no arguments handling of E.showMenu() with no arguments
0.06: Fix lower bounding of numeric values 0.06: Fix lower bounding of numeric values
0.07: Fix bug with alarms app (scroller) and correctly show images

View File

@ -28,6 +28,9 @@ E.showMenu = function (items) {
y += 22; y += 22;
var lastIdx = 0; var lastIdx = 0;
var selectEdit = undefined; var selectEdit = undefined;
var scroller = {
scroll: selected,
};
var l = { var l = {
draw: function (rowmin, rowmax) { draw: function (rowmin, rowmax) {
var rows = 0 | Math.min((y2 - y) / fontHeight, menuItems.length); var rows = 0 | Math.min((y2 - y) / fontHeight, menuItems.length);
@ -76,10 +79,11 @@ E.showMenu = function (items) {
v = ""; v = "";
} }
{ {
if (name.length >= 17 - v.length && typeof item === "object") { var vplain = v.indexOf("\0") < 0;
if (vplain && name.length >= 17 - v.length && typeof item === "object") {
g.drawString(name.substring(0, 12 - v.length) + "...", x + 3.7, iy + 2.7); g.drawString(name.substring(0, 12 - v.length) + "...", x + 3.7, iy + 2.7);
} }
else if (name.length >= 15) { else if (vplain && name.length >= 15) {
g.drawString(name.substring(0, 15) + "...", x + 3.7, iy + 2.7); g.drawString(name.substring(0, 15) + "...", x + 3.7, iy + 2.7);
} }
else { else {
@ -138,9 +142,11 @@ E.showMenu = function (items) {
else { else {
var lastSelected = selected; var lastSelected = selected;
selected = (selected + dir + menuItems.length) % menuItems.length; selected = (selected + dir + menuItems.length) % menuItems.length;
scroller.scroll = selected;
l.draw(Math.min(lastSelected, selected), Math.max(lastSelected, selected)); l.draw(Math.min(lastSelected, selected), Math.max(lastSelected, selected));
} }
}, },
scroller: scroller,
}; };
l.draw(); l.draw();
var back = options.back; var back = options.back;

View File

@ -35,6 +35,10 @@ E.showMenu = (items?: Menu): MenuInstance => {
let lastIdx = 0; let lastIdx = 0;
let selectEdit: undefined | ActualMenuItem = undefined; let selectEdit: undefined | ActualMenuItem = undefined;
const scroller = {
scroll: selected,
};
const l = { const l = {
draw: (rowmin?: number, rowmax?: number) => { draw: (rowmin?: number, rowmax?: number) => {
let rows = 0|Math.min((y2 - y) / fontHeight, menuItems.length); let rows = 0|Math.min((y2 - y) / fontHeight, menuItems.length);
@ -83,9 +87,10 @@ E.showMenu = (items?: Menu): MenuInstance => {
} }
/*???*/{ /*???*/{
if(name.length >= 17 - v.length && typeof item === "object"){ const vplain = v.indexOf("\0") < 0;
if(vplain && name.length >= 17 - v.length && typeof item === "object"){
g.drawString(name.substring(0, 12 - v.length) + "...", x + 3.7, iy + 2.7); g.drawString(name.substring(0, 12 - v.length) + "...", x + 3.7, iy + 2.7);
}else if(name.length >= 15){ }else if(vplain && name.length >= 15){
g.drawString(name.substring(0, 15) + "...", x + 3.7, iy + 2.7); g.drawString(name.substring(0, 15) + "...", x + 3.7, iy + 2.7);
}else{ }else{
g.drawString(name, x + 3.7, iy + 2.7); g.drawString(name, x + 3.7, iy + 2.7);
@ -156,9 +161,11 @@ E.showMenu = (items?: Menu): MenuInstance => {
} else { } else {
const lastSelected = selected; const lastSelected = selected;
selected = (selected + dir + /*keep +ve*/menuItems.length) % menuItems.length; selected = (selected + dir + /*keep +ve*/menuItems.length) % menuItems.length;
scroller.scroll = selected;
l.draw(Math.min(lastSelected, selected), Math.max(lastSelected, selected)); l.draw(Math.min(lastSelected, selected), Math.max(lastSelected, selected));
} }
}, },
scroller,
}; };
l.draw(); l.draw();

View File

@ -1,7 +1,7 @@
{ {
"id": "promenu", "id": "promenu",
"name": "Pro Menu", "name": "Pro Menu",
"version": "0.06", "version": "0.07",
"description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.", "description": "Replace the built in menu function. Supports Bangle.js 1 and Bangle.js 2.",
"icon": "icon.png", "icon": "icon.png",
"type": "bootloader", "type": "bootloader",

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4X/AwX48AFCqoAEC4oL/Bf4L/Bf4LTAH4A/ADGqAAIL/Bf4LD"))

142
apps/quarterclock/app.js Normal file
View File

@ -0,0 +1,142 @@
{
const minute_boxes = [
{x:0.5, y:0},
{x:0.5, y:0.5},
{x:0, y:0.5},
{x:0, y:0},
];
const hour_boxes = [
{x:0.5, y:0},
{x:0.75, y:0},
{x:0.75, y:0.25},
{x:0.75, y:0.5},
{x:0.75, y:0.75},
{x:0.5, y:0.75},
{x:0.25, y:0.75},
{x:0, y:0.75},
{x:0, y:0.5},
{x:0, y:0.25},
{x:0, y:0},
{x:0.25, y:0},
];
let drawTimeout;
// schedule a draw for the next 15 minute period
let queueDraw = function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, (60000 * 15) - (Date.now() % (60000 * 15)));
};
// Main draw function
let draw = function draw() {
var d = new Date();
var h = d.getHours(), m = d.getMinutes();
g.setBgColor(settings.backgroundColour);
g.clearRect(Bangle.appRect);
if (settings.showBattery) {
drawBattery();
}
// Draw minute box
drawBox(Math.floor(m/15), minute_boxes, Bangle.appRect.h/2, settings.minuteColour);
// Draw an hour box or write the number
if (settings.digital) {
g.setColor(settings.hourColour);
g.setFont("Vector:60");
g.setFontAlign(0,0);
g.drawString(h, Bangle.appRect.x + Bangle.appRect.w/2, Bangle.appRect.y + Bangle.appRect.h/2);
} else {
drawBox(h % 12, hour_boxes, Bangle.appRect.h/4, settings.hourColour);
}
queueDraw();
};
// Draw battery box
let drawBattery = function drawBattery() {
// Round battery up to 10% interval
let battery = Math.min((Math.floor(E.getBattery()/10)+1)/10, 1);
// Maximum battery box
let batterySize = 30;
// Draw outer box at full brightness
g.setColor(settings.batteryColour);
g.drawRect(
(Bangle.appRect.w / 2) - batterySize,
(Bangle.appRect.h / 2) - batterySize + Bangle.appRect.y,
(Bangle.appRect.w / 2) + batterySize,
(Bangle.appRect.h / 2) + batterySize + Bangle.appRect.y
);
// Fade battery colour and draw inner box
g.setColor(settings.batteryColour.split('').map((c) => {
return c=='f' ? Math.ceil(15 * battery).toString(16) : c;
}).join(''));
g.fillRect(
(Bangle.appRect.w / 2) - (batterySize * battery),
(Bangle.appRect.h / 2) - (batterySize * battery) + Bangle.appRect.y,
(Bangle.appRect.w / 2) + (batterySize * battery),
(Bangle.appRect.h / 2) + (batterySize * battery) + Bangle.appRect.y
);
};
// Draw hour or minute boxes
let drawBox = function drawBox(current, boxes, size, colour) {
let x1 = (boxes[current].x * Bangle.appRect.h) + (Bangle.appRect.y/2);
let y1 = (boxes[current].y * Bangle.appRect.h) + Bangle.appRect.y;
let x2 = x1 + size;
let y2 = y1 + size;
g.setColor(colour);
g.fillRect(x1, y1, x2, y2);
};
let settings = Object.assign({
// Default values
minuteColour: '#f00',
hourColour: '#ff0',
backgroundColour: 'theme',
showWidgets: true,
showBattery: true,
digital: false,
batteryColour: '#0f0'
}, require('Storage').readJSON('quarterclock.json', true) || {});
if (settings.backgroundColour == 'theme') {
settings.backgroundColour = g.theme.bg;
}
// Set minuteColour to a darker shade if same as hourColour
if (settings.minuteColour == settings.hourColour) {
settings.minuteColour = settings.minuteColour.split('').map((c) => {
return c=='f' ? '7' : c;
}).join('');
}
// Show launcher when middle button pressed
// Remove handler to allow fast loading
Bangle.setUI({mode:"clock", remove:function() {
if (drawTimeout) clearTimeout(drawTimeout);
require("widget_utils").show();
}});
// Load and display widgets
Bangle.loadWidgets();
if (settings.showWidgets) {
require("widget_utils").show();
} else {
require("widget_utils").hide();
}
// draw initial boxes and queue subsequent redraws
draw();
}

BIN
apps/quarterclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

@ -0,0 +1,20 @@
{
"id": "quarterclock",
"name": "Quarter Clock",
"shortName":"Quarter Clock",
"icon": "app.png",
"screenshots" : [ { "url":"screenshot.png" } ],
"version":"0.01",
"description": "For those lazy days when the exact time doesn't matter. Small square shows the hour, large square shows the fifteen minute period, and centre square shows the battery level.",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"quarterclock.app.js","url":"app.js"},
{"name":"quarterclock.settings.js","url":"settings.js"},
{"name":"quarterclock.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"quarterclock.json"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,66 @@
(function(back) {
var FILE = 'quarterclock.json';
// Load settings
var settings = Object.assign({
minuteColour: '#f00',
hourColour: '#ff0',
backgroundColour: 'theme',
showWidgets: true,
showBattery: true,
digital: false,
batteryColour: '#0f0',
}, require('Storage').readJSON(FILE, true) || {});
function setSetting(key,value) {
settings[key] = value;
require('Storage').writeJSON(FILE, settings);
}
// Helper method which uses int-based menu item for set of string values and their labels
function stringItems(key, startvalue, values, labels) {
return {
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
format: v => labels[v],
min: 0,
max: values.length - 1,
wrap: true,
step: 1,
onchange: v => {
setSetting(key,values[v]);
}
};
}
// Helper method which breaks string set settings down to local settings object
function stringInSettings(name, values, labels) {
return stringItems(name,settings[name], values, labels);
}
// Show the menu
E.showMenu({
'' : { 'title' : 'Quarter Clock' },
'< Back' : () => back(),
'Hour Colour': stringInSettings('hourColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
'Minute Colour': stringInSettings('minuteColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
'Background Colour': stringInSettings('backgroundColour', ['theme', '#000', '#fff'],['theme', 'Black', 'White']),
'Digital': {
value: !!settings.digital, // !! converts undefined to false
onchange: v => {
setSetting('digital', v);
},
},
'Show Widgets': {
value: !!settings.showWidgets,
onchange: v => {
setSetting('showWidgets', v);
},
},
'Show Battery': {
value: !!settings.showBattery,
onchange: v => {
setSetting('showBattery', v);
},
},
'Battery Colour': stringInSettings('batteryColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
});
})

View File

@ -651,11 +651,11 @@ function showUtilMenu() {
E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.'); E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.');
Bangle.setLCDTimeout(0); Bangle.setLCDTimeout(0);
Bangle.setLCDPower(1); Bangle.setLCDPower(1);
Bangle.setLCDBrightness(1);
if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat"); if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat");
if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat"); if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat");
if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat"); if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat");
if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat"); if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat");
if (Bangle.setHRMPower) Bangle.setGPSPower(1,"flat");
setInterval(function() { setInterval(function() {
var i=1000;while (i--); var i=1000;while (i--);
}, 1); }, 1);

View File

@ -1,2 +1,3 @@
0.01: attempt to import 0.01: attempt to import
0.02: Minor code improvements 0.02: Minor code improvements
0.03: big rewrite, adding time-adjust and altitude-adjust functionality

View File

@ -1,6 +1,6 @@
{ "id": "skyspy", { "id": "skyspy",
"name": "Sky Spy", "name": "Sky Spy",
"version": "0.02", "version": "0.03",
"description": "Application for debugging GPS problems", "description": "Application for debugging GPS problems",
"icon": "app.png", "icon": "app.png",
"readme": "README.md", "readme": "README.md",

View File

@ -1,20 +1,121 @@
/* Sky spy */ /* Sky spy */
/* 0 .. DD.ddddd
1 .. DD MM.mmm' /* fmt library v0.1 */
2 .. DD MM'ss" let fmt = {
*/ icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
var mode = 1; icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
icon_km : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
icon_kph : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3",
icon_c : "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
/* 0 .. DD.ddddd
1 .. DD MM.mmm'
2 .. DD MM'ss"
*/
geo_mode : 1,
init: function() {},
fmtDist: function(km) { return km.toFixed(1) + this.icon_km; },
fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); },
fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; },
fmtTimeDiff: function(d) {
if (d < 180)
return ""+d.toFixed(0);
d = d/60;
return ""+d.toFixed(0)+"m";
},
fmtAngle: function(x) {
switch (this.geo_mode) {
case 0:
return "" + x;
case 1: {
let d = Math.floor(x);
let m = x - d;
m = m*60;
return "" + d + " " + m.toFixed(3) + "'";
}
case 2: {
let d = Math.floor(x);
let m = x - d;
m = m*60;
let mf = Math.floor(m);
let s = m - mf;
s = s*60;
return "" + d + " " + mf + "'" + s.toFixed(0) + '"';
}
}
return "bad mode?";
},
fmtPos: function(pos) {
let x = pos.lat;
let c = "N";
if (x<0) {
c = "S";
x = -x;
}
let s = c+this.fmtAngle(pos.lat) + "\n";
c = "E";
if (x<0) {
c = "W";
x = -x;
}
return s + c + this.fmtAngle(pos.lon);
},
};
/* gps library v0.1 */
let gps = {
emulator: -1,
init: function(x) {
this.emulator = (process.env.BOARD=="EMSCRIPTEN"
|| process.env.BOARD=="EMSCRIPTEN2")?1:0;
},
state: {},
on_gps: function(f) {
let fix = this.getGPSFix();
f(fix);
/*
"lat": number, // Latitude in degrees
"lon": number, // Longitude in degrees
"alt": number, // altitude in M
"speed": number, // Speed in kph
"course": number, // Course in degrees
"time": Date, // Current Time (or undefined if not known)
"satellites": 7, // Number of satellites
"fix": 1 // NMEA Fix state - 0 is no fix
"hdop": number, // Horizontal Dilution of Precision
*/
this.state.timeout = setTimeout(this.on_gps, 1000, f);
},
off_gps: function() {
clearTimeout(this.state.timeout);
},
getGPSFix: function() {
if (!this.emulator)
return Bangle.getGPSFix();
let fix = {};
fix.fix = 1;
fix.lat = 50;
fix.lon = 14;
fix.alt = 200;
fix.speed = 5;
fix.course = 30;
fix.time = Date();
fix.satellites = 5;
fix.hdop = 12;
return fix;
}
};
var display = 0; var display = 0;
var debug = 0; var debug = 0;
var gps_start;
var cancel_gps, gps_start;
var cur_altitude; var cur_altitude;
var wi = 24; var wi = 24;
var h = 176-wi, w = 176; var h = 176-wi, w = 176;
var fix; var fix;
var adj_time = 0, adj_alt = 0;
function radA(p) { return p*(Math.PI*2); } function radA(p) { return p*(Math.PI*2); }
function radD(d) { return d*(h/2); } function radD(d) { return d*(h/2); }
@ -27,26 +128,7 @@ function radY(p, d) {
return h/2 - Math.cos(a)*radD(d) + wi; return h/2 - Math.cos(a)*radD(d) + wi;
} }
function format(x) { var qalt = -1, min_dalt, max_dalt, step;
switch (mode) {
case 0:
return "" + x;
case 1:
d = Math.floor(x);
m = x - d;
m = m*60;
return "" + d + " " + m.toFixed(3) + "'";
case 2:
d = Math.floor(x);
m = x - d;
m = m*60;
mf = Math.floor(m);
s = m - mf;
s = s*60;
return "" + d + " " + mf + "'" + s.toFixed(0) + '"';
}
}
var qalt = -1;
function resetAlt() { function resetAlt() {
min_dalt = 9999; max_dalt = -9999; step = 0; min_dalt = 9999; max_dalt = -9999; step = 0;
} }
@ -64,65 +146,96 @@ function calcAlt(alt, cur_altitude) {
return ddalt; return ddalt;
} }
function updateGps() { function updateGps() {
let /*have = false,*/ lat = "lat", lon = "lon", alt = "alt", let lat = "lat ", alt = "?",
speed = "speed", hdop = "hdop"; // balt = "balt"; speed = "speed ", hdop = "?", adelta = "adelta ",
tdelta = "tdelta ";
if (cancel_gps) fix = gps.getGPSFix();
return; if (adj_time) {
fix = Bangle.getGPSFix(); print("Adjusting time");
setTime(fix.time.getTime()/1000);
adj_time = 0;
}
if (adj_alt) {
print("Adjust altitude");
if (qalt < 5) {
let rest_altitude = fix.alt;
let alt_adjust = cur_altitude - rest_altitude;
let abs = Math.abs(alt_adjust);
print("adj", alt_adjust);
let o = Bangle.getOptions();
if (abs > 10 && abs < 150) {
let a = 0.01;
// FIXME: draw is called often compared to alt reading
if (cur_altitude > rest_altitude)
a = -a;
o.seaLevelPressure = o.seaLevelPressure + a;
Bangle.setOptions(o);
}
msg = o.seaLevelPressure.toFixed(1) + "hPa";
print(msg);
}
}
try { try {
Bangle.getPressure().then((x) => { Bangle.getPressure().then((x) => {
cur_altitude = x.altitude; cur_altitude = x.altitude;
}, print); }, print);
} catch (e) { } catch (e) {
print("Altimeter error", e); //print("Altimeter error", e);
} }
speed = getTime() - gps_start;
//print(fix);
if (fix && fix.time) {
tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0);
}
if (fix && fix.fix && fix.lat) { if (fix && fix.fix && fix.lat) {
lat = "" + format(fix.lat); lat = "" + fmt.fmtPos(fix);
lon = "" + format(fix.lon); alt = "" + fix.alt.toFixed(0);
alt = "" + fix.alt.toFixed(1); adelta = "" + (cur_altitude - fix.alt).toFixed(0);
speed = "" + fix.speed.toFixed(1); speed = "" + fix.speed.toFixed(1);
hdop = "" + fix.hdop.toFixed(1); hdop = "" + fix.hdop.toFixed(0);
//have = true; } else {
lat = "NO FIX\n"
+ "" + (getTime() - gps_start).toFixed(0) + "s "
+ sats_used + "/" + snum;
if (cur_altitude)
adelta = "" + cur_altitude.toFixed(0);
} }
let ddalt = calcAlt(alt, cur_altitude); let ddalt = calcAlt(alt, cur_altitude);
if (display == 1) let msg = "";
g.reset().setFont("Vector", 20) if (display == 1) {
.setColor(1,1,1) msg = lat +
.fillRect(0, wi, 176, 176) "\ne" + hdop + "m "+tdelta+"s\n" +
.setColor(0,0,0) speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere";
.drawString("Acquiring GPS", 0, 30) }
.drawString(lat, 0, 50)
.drawString(lon, 0, 70)
.drawString("alt "+alt, 0, 90)
.drawString("speed "+speed, 0, 110)
.drawString("hdop "+hdop, 0, 130)
.drawString("balt" + cur_altitude, 0, 150);
if (display == 2) { if (display == 2) {
g.reset().setFont("Vector", 20) /* qalt is altitude quality estimate -- over ten seconds,
.setColor(1,1,1) computes differences between GPS and barometric altitude.
.fillRect(0, wi, 176, 176) The lower the better.
.setColor(0,0,0)
.drawString("GPS status", 0, 30) ddalt is just a debugging -- same estimate, but without
.drawString("speed "+speed, 0, 50) waiting 10 seconds, so will be always optimistic at start
.drawString("hdop "+hdop, 0, 70) of the cycle */
.drawString("dd "+qalt.toFixed(0) + " (" + ddalt.toFixed(0) + ")", 0, 90) msg = speed + "km/h\n" +
.drawString("alt "+alt, 0, 110) "e"+hdop + "m"
.drawString("balt " + cur_altitude, 0, 130) +"\ndd "+qalt.toFixed(0) + "\n(" + step + "/" + ddalt.toFixed(0) + ")" +
.drawString(step, 0, 150); "\n"+alt + "m+" + adelta;
}
step++; step++;
if (step == 10) { if (step == 10) {
qalt = max_dalt - min_dalt; qalt = max_dalt - min_dalt;
resetAlt(); resetAlt();
} }
if (display > 0) {
g.reset().setFont("Vector", 31)
.setColor(1,1,1)
.fillRect(0, wi, 176, 176)
.setColor(0,0,0)
.drawString(msg, 3, 25);
} }
if (debug > 0) if (debug > 0)
print(fix); print(fix);
setTimeout(updateGps, 1000); setTimeout(updateGps, 1000);
@ -184,7 +297,7 @@ function drawSats(sats) {
var sats = []; var sats = [];
var snum = 0; var snum = 0;
//var sats_receiving = 0; var sats_used = 0;
function parseRaw(msg, lost) { function parseRaw(msg, lost) {
if (lost) if (lost)
@ -199,6 +312,7 @@ function parseRaw(msg, lost) {
if (s[2] == "1") { if (s[2] == "1") {
snum = 0; snum = 0;
sats = []; sats = [];
sats_used = 0;
} }
let view = 1 * s[3]; let view = 1 * s[3];
@ -217,6 +331,8 @@ function parseRaw(msg, lost) {
sat.ele = 1*s[i++]; sat.ele = 1*s[i++];
sat.azi = 1*s[i++]; sat.azi = 1*s[i++];
sat.snr = s[i++]; sat.snr = s[i++];
if (sat.snr != "")
sats_used++;
if (debug > 0) if (debug > 0)
print(" ", sat); print(" ", sat);
sats[snum++] = sat; sats[snum++] = sat;
@ -231,30 +347,80 @@ function parseRaw(msg, lost) {
} }
} }
function stopGps() {
cancel_gps=true;
Bangle.setGPSPower(0, "skyspy");
}
function markGps() { function markGps() {
cancel_gps = false;
Bangle.setGPSPower(1, "skyspy"); Bangle.setGPSPower(1, "skyspy");
Bangle.on('GPS-raw', parseRaw); Bangle.on('GPS-raw', parseRaw);
gps_start = getTime(); gps_start = getTime();
updateGps(); updateGps();
} }
function drawMsg(msg) {
function onSwipe(dir) { g.reset().setFont("Vector", 35)
display = display + 1; .setColor(1,1,1)
if (display == 3) .fillRect(0, wi, 176, 176)
display = 0; .setColor(0,0,0)
.drawString(msg, 5, 30);
}
function drawBusy() {
drawMsg("\n.oO busy");
} }
var numScreens = 3;
function nextScreen() {
display = display + 1;
if (display == numScreens)
display = 0;
drawBusy();
}
function prevScreen() {
display = display - 1;
if (display < 0)
display = numScreens - 1;
drawBusy();
}
function onSwipe(dir) {
nextScreen();
}
var last_b = 0;
function touchHandler(d) {
let x = Math.floor(d.x);
let y = Math.floor(d.y);
if (d.b != 1 || last_b != 0) {
last_b = d.b;
return;
}
last_b = d.b;
if ((x<h/2) && (y<w/2)) {
drawMsg("Clock\nadjust");
adj_time = 1;
}
if ((x>h/2) && (y<w/2)) {
drawMsg("Alt\nadjust");
adj_alt = 1;
}
if ((x<h/2) && (y>w/2))
prevScreen();
if ((x>h/2) && (y>w/2))
nextScreen();
}
gps.init();
fmt.init();
Bangle.on("drag", touchHandler);
Bangle.setUI({ Bangle.setUI({
mode : "custom", mode : "custom",
swipe : onSwipe, swipe : onSwipe,
clock : 0 clock : 0
}); });
Bangle.loadWidgets(); Bangle.loadWidgets();
Bangle.drawWidgets(); Bangle.drawWidgets();
drawBusy();
markGps(); markGps();

View File

@ -10,4 +10,5 @@
0.08: Stability improvements - ensure we continue even if a flat string can't be allocated 0.08: Stability improvements - ensure we continue even if a flat string can't be allocated
Stop ClockInfo text drawing outside the allocated area Stop ClockInfo text drawing outside the allocated area
0.09: Use clock_info module as an app 0.09: Use clock_info module as an app
0.10: Option to hide widgets, tweak top widget width to avoid overlap with hour text at 9am 0.10: Option to hide widgets, tweak top widget width to avoid overlap with hour text at 9am
0.11: Avoid rendering clkinfo in the same colour as the background

View File

@ -86,6 +86,7 @@ let draw = function() {
let isAnimIn = true; let isAnimIn = true;
let animInterval; let animInterval;
let minuteX;
// Draw *just* the minute image // Draw *just* the minute image
let drawMinute = function() { let drawMinute = function() {
var yo = slopeBorder + offsy + y - 2*slope*minuteX/R.w; var yo = slopeBorder + offsy + y - 2*slope*minuteX/R.w;
@ -128,9 +129,9 @@ let clockInfoDraw = (itm, info, options) => {
let texty = options.y+41; let texty = options.y+41;
// set a cliprect to stop us drawing outside our box // set a cliprect to stop us drawing outside our box
g.reset().setClipRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1); g.reset().setClipRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1);
g.setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty); g.setFont("6x15").setBgColor(options.bg).clearRect(options.x, texty-15, options.x+options.w-2, texty);
if (options.focus) g.setColor(options.hl); g.setColor(options.focus ? options.hl : options.fg);
if (options.x < g.getWidth()/2) { // left align if (options.x < g.getWidth()/2) { // left align
let x = options.x+2; let x = options.x+2;
if (info.img) g.clearRect(x, options.y, x+23, options.y+23).drawImage(info.img, x, options.y); if (info.img) g.clearRect(x, options.y, x+23, options.y+23).drawImage(info.img, x, options.y);
@ -150,7 +151,7 @@ let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { // t
}); });
let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { // bottom left let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { // bottom left
app:"slopeclockpp",x:0, y:115, w:50, h:40, app:"slopeclockpp",x:0, y:115, w:50, h:40,
draw : clockInfoDraw, bg : bgColor, fg : g.theme.bg, hl : (bgColor=="#000")?"#f00"/*red*/:g.theme.fg draw : clockInfoDraw, bg : bgColor, fg : g.theme.bg, hl : (g.theme.fg===g.toColor(bgColor))?"#f00"/*red*/:g.theme.fg
}); });
// Show launcher when middle button pressed // Show launcher when middle button pressed
@ -175,4 +176,4 @@ Bangle.loadWidgets();
if (settings.hideWidgets) require("widget_utils").swipeOn(); if (settings.hideWidgets) require("widget_utils").swipeOn();
else setTimeout(Bangle.drawWidgets,0); else setTimeout(Bangle.drawWidgets,0);
draw(); draw();
} }

View File

@ -1,6 +1,6 @@
{ "id": "slopeclockpp", { "id": "slopeclockpp",
"name": "Slope Clock ++", "name": "Slope Clock ++",
"version":"0.10", "version":"0.11",
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.", "description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"}], "screenshots": [{"url":"screenshot.png"}],

View File

@ -7,3 +7,4 @@
0.07: Update clock_info to avoid a redraw 0.07: Update clock_info to avoid a redraw
0.08: Timer ClockInfo now updates once a minute 0.08: Timer ClockInfo now updates once a minute
0.09: Timer ClockInfo resets to timer menu when blurred 0.09: Timer ClockInfo resets to timer menu when blurred
0.10: Timer ClockInfo now uses +- icons, and changes timer from 'T-5 min' to just '5 min' to aid readability

View File

@ -28,7 +28,7 @@
var min = getAlarmMinutes(); var min = getAlarmMinutes();
if(min < 0) if(min < 0)
return "OFF"; return "OFF";
return "T-" + String(min)+ " min"; return min + " min";
} }
function increaseAlarm(t){ function increaseAlarm(t){
@ -80,7 +80,7 @@
offsets.forEach((o, i) => { offsets.forEach((o, i) => {
smpltmrItems.items = smpltmrItems.items.concat({ smpltmrItems.items = smpltmrItems.items.concat({
name: null, name: null,
get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }), get: () => ({ text: (o > 0 ? "+" : "") + o + " min", img: (o>0)?atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgYGBgYGDAYDDAYDDH/jDH/jDAYDDAYDBgYGBgYGAwAMA4AcAeB4AH/gAB+AA=="):atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgAGBgAGDAADDAADDH/jDH/jDAADDAADBgAGBgAGAwAMA4AcAeB4AH/gAB+AA==") }),
show: function() { }, show: function() { },
hide: function() { }, hide: function() { },
blur: restoreMainItem, blur: restoreMainItem,

View File

@ -2,7 +2,7 @@
"id": "smpltmr", "id": "smpltmr",
"name": "Simple Timer", "name": "Simple Timer",
"shortName": "Simple Timer", "shortName": "Simple Timer",
"version": "0.09", "version": "0.10",
"description": "A very simple app to start a timer.", "description": "A very simple app to start a timer.",
"icon": "app.png", "icon": "app.png",
"tags": "tool,alarm,timer,clkinfo", "tags": "tool,alarm,timer,clkinfo",

View File

@ -1 +1,2 @@
0.01: New App! 0.01: New App!
0.02: Adjusted main font size to fit nicely even at 8pm, minor tweaks

View File

@ -1,5 +1,5 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears { // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are globalj // we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let removeHasNotRun = true; let removeHasNotRun = true;
let drawTimeout; let drawTimeout;
@ -37,19 +37,19 @@ let drawSplashScreen = function (frame, total) {
// for fast startup-feeling, draw the splash screen once directly once // for fast startup-feeling, draw the splash screen once directly once
g.clear() g.clear()
drawSplashScreen(0, 20); drawSplashScreen(0, 15);
let splashScreen = function () { let splashScreen = function () {
g.clearRect(R.x,R.y, R.x2, R.y2); g.clearRect(R.x,R.y, R.x2, R.y2);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let frame = 0; let frame = 0;
function tick() { function tick() {
if (removeHasNotRun) drawSplashScreen(frame, 20); if (removeHasNotRun) drawSplashScreen(frame, 15);
frame += 1; frame += 1;
if (!removeHasNotRun) { if (!removeHasNotRun) {
reject(); reject();
} else if (frame < 20) { } else if (frame < 15) {
setTimeout(tick, 50); setTimeout(tick, 30);
} else { } else {
resolve(); resolve();
} }
@ -62,13 +62,13 @@ let splashScreen = function () {
Graphics.prototype.setFontPlayfairDisplay = function() { Graphics.prototype.setFontPlayfairDisplay = function() {
// https://www.espruino.com/Font+Converter // https://www.espruino.com/Font+Converter
// <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital@0;1&display=swap" rel="stylesheet"> // <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital@0;1&display=swap" rel="stylesheet">
// 60pt, 2bpp, Numeric // Actual height 58 (61 - 4)
// Actual height 62 (67 - 6) // 1 BPP
return this.setFontCustom( return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AD8/A40B/4IGh/8DI/4BA3/8AHFg//wAIFj/+ES8DEQ5FIj4ZGBAKdzhwIHNA8f4BoGUpBwGg/wRQ7HHPA0BFJA6HJY9/FI46GgLWHMiF/Mi8Dbo8MbuYALv6VGg//BA1/BAwQB/51Fn4IBNokBA4P/UAkPBASYEFQIABDI7DEDIY9EDIY9DDIY0EJoICDJoYNCFYgnDAYcDJQUBMAbKDBgcAuADCgw8DBgcYBg0AkAMGgAVDsADCgQiDLYYVDg4ZDCocOBgYiDnwDGNocHNAh/CRYy3Gj6uLcYgZHW4f8BAYrDDIjaDA4Y0DGYjJBbIoIDFQhGDCAoRBCAwAqcYcEBAbNDiDNHoCLDZoUDEQ8MBAccAYVwOAigCOQbaBToylDPYicC//waI4iVegYiDdYYiEdYYiEHgYiECAZFEDobbGRYoADRYgADVwgADVwYAjcYUDaoQAB8ACBjxcEAYX4BAc4DIQUCSgJtCj4iDhwDC/wZGg4iDgIeCn4iDGYS6BEQc8agQiEVQV/EQcDHgMDv4iDh4CBnIiEnwqB84iDgIeBj8fEQcH+AKBEQkf+E/8IiEv7qBg4iEfYQiFBAPgEQoIBNAs/DIIiEgAZCEQkDBAI3BRYn//AiFFYPAEQs/AoIiED4KVBEQg0BwAiFdASuFCYYiEJAYiEPwYHGAFsDAYUOBAcIAYVwBAdgCgRtDgIECjgQDgwDCMgkYVwSHEEQQZBCwQnDUgM4IIkD4AkDvACBh4WDgINBgE8gEMBoYLBEQMwBoY8B4AWCBoTfBwEPEQICCb4MAZ4V+EQX/h/cGwLSCg///98gE/HgUf//9/gMBNYU///nCYLsDAoOfAQJiCeIP8v4IBCAUP//wA4P8BAQrBwYZEJwIyC/6hDDIITBDIZXBwA/BDIZbCGYgRBCYRNDCYYqEDgoAXgYIHd4IAGXwQAEZgIIGYYIqGOAYADj4iH/4iGVAIQGuYiGgePEQ0cdQaVD4AiGhxFHuEPEQsDYAIiFjBOBFQwiGhxXBEQtwgAiFZ4IACCQbyBAAQSCdIIADQAgACGod/EQ0HDIgiCFQgiCFQoiCDIp7GaI5oGH4TRGFwIZHEQ9/EQwZBEQwAcggIHiAIHoA7EwBzBVwn+AgMMaAfAmAFBAQRlCDIMAVwfwgyiCEQQZBjAEBjgiDgHgAoNwEQcDHgQlCEQMOAgMeEQk4AgN4EQ0DEoQiCIQMfDIQiBh5rBXAYiBn7bEEQX/PgYiDfgJ8CEQYIBaQYiCBAJ5CEQYZEEQIpB//4EQkHDIjxCWAhaBEQIrBGYd/LYN/JoYAD/5nDbYiBCAAgQGAFReBHYp4CLwZ6CBAysCDQp6BRQghDAAJ5DXoQABDIaIBWwoqDRYgqDbIoADGgTFCGgoZBD4MHFYYeBKgMBcQQMBgZSCMAUf4EYBAQiCDoNgCwRNC8AZCgEMDIQEDgEgAQN4gE4EQkDKIIwChwCDEgIFBIoQXBh5uBj5RCDIK3CNAQ/CRYpUBSoaLCDgKEDHgSeEQQRUCcYpZCaIzbDTgbKEfog0DA4gICM4QIFFQgAih4pHn6ICAAn/VwReFEQ4ZHn5uFEQTCBESIMEEQd/TwYiCQgIVGYQIVCEQSwCLYQiCaYR1CEQLKFEQSFBEQzkCLYQiBaQRFFfwoiBAIPwgxoEj7iFEQJ7G//HV4ogBnzRHYA0/IIbRMCA8PZA8/A40ACA4AUvDlKAAl/BAcHAgKlBRgYNCcIiBBX4a+Cj4WBX4ThCv4WBDILhEQQICBGgQZBVwLQEDIP/z/gv6XBgLwCBwMfFYK1BAAIWBjySCFAk4AQPgIwQFBsA8BCQQoCEQMMBARdBgQTBkA+CBwMIBAINBGgYOBEQJHBMwQiDQgcGH4aBBBAMYBAJCBGgIDBH4MD/h7C/EPEQKZCMQQtCLwSFCRYnhPgS3CAgKoCVwi/DPgQFB8CXCDIQFBXQbrCj4VBSwiyDRoYAEv4zCBApnBAAp4CAExZCAApeECAgIGSwJWFSYTJBAAbADDIyXBMQYZCQQVgDIg0BgKRBDIY0BhwHBgIQCFYMwJogrBgKnCn4DBv+Ag48CIQU+gE8HgQuCnEAWAUcCgXgg4NCJARDBDIRIDg0B+D+CDIUwh48CEQfARod8DIUPQgaRCnL+DQQJrCDIZoDTwk/BAYZCRYYZERYf/JoSuE/5bCFYjSEW4aBCGgraHcZAqDBAYQFEYQHFAHh2Cj5XDg5UBS4JVETIMHUocARAU/NIcHO4SUEj4WBWILIECwKxBZAgiCW4YiIh4iDJwZFIv4NBh7rDNAiRkA=='))), E.toString(require('heatshrink').decompress(atob('ADv8AwsB/4PG/+AA43gA4t/+AHFn/4A4sfGA0P/wHFg44GIBF/IA0fPL8OA40/PI5IGMA0HPA0f4BXNO40DR40PU40/Ew5FWEw5FNgJFGgAmGAEbpBJYr5BMYoHB/5UEh4HBDAgHCKogHCMogHCEAgnCEAgHDNwYHDIIcDA4QoDA4OALQK6GeggkCgYwDuADGmADCjAHGhgLGgQLGgIDCgxtDNIUDA4ZACgJEDsBAKngDCTQZdCfAkPOwMfQJZ+BWQ1/BAQHDn4HGYQYHDFAbKDFAbzEEAQGDEAYHEDAI/EDARXDADKiDhAvDIoUEVwzKDVwa+EnAbFgEeB4TGDj5xC8CJG+AHCg4HCYQaRDNQa6IA4SKEA4ZADZQZADZQZADA4ZADKAZADAAccA40GA40BUw7jEAC8gAQMMA4cwAQMOA40PA444DYQUPPIYHDPIc8A4R5DNoUPPIY0Ch66DGgUPXQcHGgLdBaQY0Bhy6DgI0Bj1/IAUBFgM+n5ADEgM/j5ADv/B/5AEZQUHIAbKCwZADHoP/wJAEfIRAEA4RADgAHB4BAEv4HBIAgwB4BAEGAPAIAgwB4EDIAYgB4AzBA4aSBA4L7FDQQHEB4JADACkCeYoiBdYoHBHIQPDgA5CB4cAsAHGCgQHEjACBjkAhgEDcALqBAgICCOAMHAgYFBwEDHoIKCgaIBA4QCBgfggJFBg4CBgPwAIIMCDAP8gK4BA4d/wfuC4LLCn//++Ag7LCaQP7/EfA4UH//5doLTCW4P8A4i3B/giBbYYEBEQIHDn/+eoIHDj/+EQLrDg/+EQIHDgIUBv77EnhLCbApLBA4oaCABqjCA4rhCA4iQCA4iQCHAiICA4iACA4hAGgxQGh1/IAsOn5AFh0fIAsMh5AFjhAGjkDIAscQI0YYoIHEnCqBIAgHBIArhBdYgHEFIbIBAAQHHFIQHEFIQGD/5qCA4hqCC4hqFQI0AQIzCIQIzCIg7CGgJXDACsBBA4hDgZXCoAGCMwdgPIX8YYMAmB5C4EIZwaxB8EMewR+C+EGAgMOPwX4g5jCAQX8gY9BA4UD/0BW4MHBQJuBgBIBgbCDwFwKYhABngXBA4RABj4HBWYRABh/gXYZACaQhACA4K7CIAQHEIAQHEIAU//7LDIAMfA4vgGAIHD//wGAIPEHgIHEAAU/eY43DAAcgeS4AMJ4KHCAARfFIwR4BXARZCTAhpCAAIYEA4SMBA4zJCU4QABHIQHEVIgoGA4YoDIwQbBJIX/+IEBfQbBBBgQwCv8AA4UYM4UAoAEBhgCBn0AsAEBgQCBj0AmD/CA4ccKoRABh0Ah4PCFYIFBHoQCDBgJxEEQJyDA4QiBA4SiBFQSyCVQQqBYQKJDJwLSBA4b+BFATTFA44oBA4ogBWIYYDcQj8CUAQARH4IWGdAYADv4PGn4HGj4XGh5GGg5WGgZmDBYRABBYX/UAJABAYMPJgd/Z4RzDIASsBEARACSYhACYgRAEA4QsBIAV/A4ZACA4Q0BIATsCJARABRYpABBoZADaIpABQQsH/qSFOoKiFIAI8CYQguEIATbGn4HGjz4TACsPdo0fOQYMCYIJTCBgQHBNYQEB4ACBQYQEB+DZFAgLpBaIQECAQT+D//PZIcHAgP38EHHgKgBCod4A4glBjioBaAIdCgwKBIwQdCA4NgDIJOCEQMgXQJvCoEAjCyBGAQNBhhKCN4MQFQQgBM4IVBgYUBIIQVBgPwdYIYBhzpC//vDALeCFwPzLYhPCNYg2B+ASBMYRXCRgQHBFAP4PgS6Cv5nBA4kPA4IgBA4UDP4JiDAAZSBA4ojBF4QASg6iCAAZjBL4IADH4I7BA4qaBA4qACA4pAEA4RQBmAvDbgS7BA4YoBXYJvCFAS7CA4eAg5XC/+DGAIHCgYtBg/gcIUBHoXwgYEBA4c4A4UAAQUMgKbCsACBgwkCKYcCW4UAjgzCA4cPGYMDZ4TLDge/A4TICKYKTDMAQHEv4HGQIQHEPIYHDgYHC/yqDB4yyDA4ggCA4ggCegpBBdYpXBbQgAon50CToIHCGwMfIIc/AgMfKIYECh55Dj5mBKQJwDBgJrBFAQMCXoJiCBgbFBTIYMBv44Dv4MBIAkfA4MPA4cHA4MBSQoAE'))),
46, 46,
atob("ExouGyYjJiEoISgoFQ=="), atob("EBYpGCEfIh4lHyQjEQ=="),
70|65536 65|65536
); );
} }
@ -95,11 +95,10 @@ let draw = function() {
g.setColor(g.theme.fg) g.setColor(g.theme.fg)
// Time // Time
const yt = R.y + 92 - 20 - 30 + 6 + 10; const yt = R.y + 92 - 20 - 30 + 6 + 10;
const xt = R.w/2 - 5; const xt = R.w/2;
let hours = date.getHours()+''; let hours = date.getHours()+'';
g.setFontAlign(1, 0).setFontPlayfairDisplay().drawString(hours, xt - 8, yt);
g.setFontAlign(0, 0).setFontPlayfairDisplay().drawString(':', xt, yt); g.setFontAlign(0, 0).setFontPlayfairDisplay().drawString(hours + ':' + minutes, xt, yt);
g.setFontAlign(-1, 0).setFontPlayfairDisplay().drawString(minutes, xt + 8, yt);
// logo // logo
g.drawImage(supaClockImg, R.x2 - supaClockImg.width - 2, R.y + 2); g.drawImage(supaClockImg, R.x2 - supaClockImg.width - 2, R.y + 2);
// dow + date // dow + date
@ -174,16 +173,15 @@ splashScreen().then(() => {
draw(); draw();
Bangle.drawWidgets(); Bangle.drawWidgets();
// Allocate and draw clockinfos // Allocate and draw clockinfos
g.setFontAlign(1, 1).setFont('6x8').drawString('Loading Clock Info Modules...', R.x + 10, upperCI);
setTimeout(() => { setTimeout(() => {
// delay loading of clock info, so that the clock face appears quicker // delay loading of clock info, so that the clock face appears quicker
g.clearRect(R.x, upperCI, R.x2, upperCI+10); // clear loading text g.clearRect(R.x, upperCI, R.x2, upperCI+10); // clear loading text
try { try {
clockInfoItems = require("clock_info").load(); clockInfoItems = require("clock_info").load();
clockInfoMenu1 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x+1, y:upperCI, w:midX-2, h:28, draw : clockInfoDraw}); clockInfoMenu1 = require("clock_info").addInteractive(clockInfoItems, { app:"supaclk", x:R.x+1, y:upperCI, w:midX-2, h:28, draw : clockInfoDraw});
clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:upperCI, w:midX-2, h:28, draw : clockInfoDrawR}); clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"supaclk", x:midX+1, y:upperCI, w:midX-2, h:28, draw : clockInfoDrawR});
clockInfoMenu3 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x+1, y:lowerCI, w:midX-2, h:28, draw : clockInfoDraw}); clockInfoMenu3 = require("clock_info").addInteractive(clockInfoItems, { app:"supaclk", x:R.x+1, y:lowerCI, w:midX-2, h:28, draw : clockInfoDraw});
clockInfoMenu4 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+1, y:lowerCI, w:midX-2, h:28, draw : clockInfoDrawR}); clockInfoMenu4 = require("clock_info").addInteractive(clockInfoItems, { app:"supaclk", x:midX+1, y:lowerCI, w:midX-2, h:28, draw : clockInfoDrawR});
} catch(err) { } catch(err) {
if ((err + '').includes('Module "clock_info" not found' )) { if ((err + '').includes('Module "clock_info" not found' )) {
g.setFont('6x8').drawString('Please install\nclockinfo module!', R.x + 10, upperCI); g.setFont('6x8').drawString('Please install\nclockinfo module!', R.x + 10, upperCI);

View File

@ -1,6 +1,6 @@
{ "id": "supaclk", { "id": "supaclk",
"name": "SUPACLOCK Pro ULTRA", "name": "SUPACLOCK Pro ULTRA",
"version": "0.01", "version": "0.02",
"description": "SUPACLOCK Pro ULTRA, with four ClockInfo areas at the bottom. Tap them and swipe up/down and left/right to toggle between different information.", "description": "SUPACLOCK Pro ULTRA, with four ClockInfo areas at the bottom. Tap them and swipe up/down and left/right to toggle between different information.",
"icon": "app.png", "icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}], "screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],

View File

@ -1 +1,2 @@
0.01: New Clock! 0.01: New Clock!
0.02: Clockinfos now save under correct name, and wrap correctly to >1 line

View File

@ -134,8 +134,8 @@ for (var i=0;i<10;i++)
if (g.stringWidth(txt) > options.w) // if too big, smaller font if (g.stringWidth(txt) > options.w) // if too big, smaller font
g.setFont("LECO1976Regular14"); g.setFont("LECO1976Regular14");
if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines
var l = g.wrapString(txt, options.w); var l = g.wrapString(txt, options.w-4);
txt = l.slice(0,2).join("\n") + (l.length>2)?"...":""; txt = l.slice(0,2).join("\n") + ((l.length>2)?"...":"");
} }
var x = options.x+options.w/2, y = options.y+54; var x = options.x+options.w/2, y = options.y+54;
g.setColor(g.theme.bg).drawString(txt, x-2, y). // draw the text background g.setColor(g.theme.bg).drawString(txt, x-2, y). // draw the text background
@ -147,12 +147,12 @@ for (var i=0;i<10;i++)
}; };
clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, { clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, {
app:"pebblepp", app:"twotwoclock",
x : g.getWidth()-clockInfoW, y: 0, w: clockInfoW, h:clockInfoH, x : g.getWidth()-clockInfoW, y: 0, w: clockInfoW, h:clockInfoH,
draw : clockInfoDraw draw : clockInfoDraw
}); });
clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, { clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, {
app:"pebblepp", app:"twotwoclock",
x : g.getWidth()-clockInfoW, y: clockInfoH, w: clockInfoW, h:clockInfoH, x : g.getWidth()-clockInfoW, y: clockInfoH, w: clockInfoW, h:clockInfoH,
draw : clockInfoDraw draw : clockInfoDraw
}); });

View File

@ -1,7 +1,7 @@
{ "id": "twotwoclock", { "id": "twotwoclock",
"name": "TwoTwo Clock", "name": "TwoTwo Clock",
"shortName":"22 Clock", "shortName":"22 Clock",
"version":"0.01", "version":"0.02",
"description": "A clock with the time split over two lines, with custom backgrounds and two ClockInfos", "description": "A clock with the time split over two lines, with custom backgrounds and two ClockInfos",
"icon": "icon.png", "icon": "icon.png",
"type": "clock", "type": "clock",

2
core

@ -1 +1 @@
Subproject commit 1cdcb3405f78ef35f231b9c3df501721bda75525 Subproject commit 4f07b72ce2bdac4a8da6bfa3da3e2152370446fc

View File

@ -16,7 +16,7 @@ if (window.location.host=="banglejs.com") {
'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.'; 'This is not the official Bangle.js App Loader - you can try the <a href="https://banglejs.com/apps/">Official Version</a> here.';
} }
var RECOMMENDED_VERSION = "2v23"; var RECOMMENDED_VERSION = "2v24";
// could check http://www.espruino.com/json/BANGLEJS.json for this // could check http://www.espruino.com/json/BANGLEJS.json for this
// We're only interested in Bangles // We're only interested in Bangles

View File

@ -90,19 +90,17 @@ exports.swipeOn = function(autohide) {
function queueDraw() { function queueDraw() {
const o = exports.offset; const o = exports.offset;
Bangle.appRect.y = o+24;
Bangle.appRect.h = 1 + Bangle.appRect.y2 - Bangle.appRect.y;
if (o>-24) { if (o>-24) {
Bangle.appRect.y = o+24; Bangle.setLCDOverlay(og, 0, o, {
Bangle.appRect.h = 1 + Bangle.appRect.y2 - Bangle.appRect.y; id:"widget_utils",
if (o>-24) { remove:()=>{
Bangle.setLCDOverlay(og, 0, o, { require("widget_utils").cleanupOverlay();
id:"widget_utils", }
remove:()=>{ });
require("widget_utils").cleanupOverlay(); } else {
} Bangle.setLCDOverlay(undefined, {id: "widget_utils"});
});
} else {
Bangle.setLCDOverlay(undefined, {id: "widget_utils"});
}
} }
} }