Merge branch 'master' into quiet-mode
|
@ -29,3 +29,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
|||
* Added progress bar on Bangle.js for uploads
|
||||
* Provide a proper error message in case JSON decode fails
|
||||
* Check you're connecting with a Bangle.js of the correct version
|
||||
* Allow 'data' style app files to be uploaded with the app (and switch over settings files for various apps)
|
||||
|
|
10
README.md
|
@ -249,14 +249,20 @@ and which gives information about the app for the Launcher.
|
|||
{"name":"appid.js", // filename to use in storage.
|
||||
// If name=='RAM', the code is sent directly to Bangle.js and is not saved to a file
|
||||
"url":"", // URL of file to load (currently relative to apps/)
|
||||
"content":"..." // if supplied, this content is loaded directly
|
||||
"evaluate":true // if supplied, data isn't quoted into a String before upload
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
"noOverwrite":true // if supplied, this file will not be overwritten if it
|
||||
// already exists
|
||||
},
|
||||
]
|
||||
"data": [ // list of files the app writes to
|
||||
{"name":"appid.data.json", // filename used in storage
|
||||
"storageFile":true // if supplied, file is treated as storageFile
|
||||
"url":"", // if supplied URL of file to load (currently relative to apps/)
|
||||
"content":"...", // if supplied, this content is loaded directly
|
||||
"evaluate":true, // if supplied, data isn't quoted into a String before upload
|
||||
// (eg it's evaluated as JS)
|
||||
},
|
||||
{"wildcard":"appid.data.*" // wildcard of filenames used in storage
|
||||
}, // this is mutually exclusive with using "name"
|
||||
|
|
121
apps.json
|
@ -180,6 +180,9 @@
|
|||
{"name":"setting.boot.js","url":"boot.js"},
|
||||
{"name":"setting.img","url":"settings-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"setting.json", "url":"settings.min.json","evaluate":true}
|
||||
],
|
||||
"sortorder" : -2
|
||||
},
|
||||
{ "id": "alarm",
|
||||
|
@ -216,24 +219,33 @@
|
|||
{ "id": "slidingtext",
|
||||
"name": "Sliding Clock",
|
||||
"icon": "slidingtext.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"custom":"custom.html",
|
||||
"storage": [
|
||||
{"name":"slidingtext.app.js","url":"slidingtext.js"},
|
||||
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}
|
||||
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
|
||||
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
|
||||
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
|
||||
{"name":"slidingtext.utils.en.js","url":"slidingtext.utils.en.js"},
|
||||
{"name":"slidingtext.locale.fr.js","url":"slidingtext.locale.fr.js"},
|
||||
{"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"},
|
||||
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "sweepclock",
|
||||
"name": "Sweep Clock",
|
||||
"icon": "sweepclock.png",
|
||||
"version":"0.01",
|
||||
"description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font",
|
||||
"version":"0.02",
|
||||
"description": "Smooth sweep secondhand with single hour numeral. Use button1 to toggle the numeral font and button3 to change the colour theme",
|
||||
"tags": "clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"sweepclock.app.js","url":"sweepclock.js"},
|
||||
{"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true}
|
||||
|
@ -438,14 +450,16 @@
|
|||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"gpsnav.app.js","url":"app.min.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"gpsnav.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"waypoints.json","url":"waypoints.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "heart",
|
||||
"name": "Heart Rate Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record your heart rate. Can run in background",
|
||||
"tags": "tool,health,widget",
|
||||
|
@ -1701,9 +1715,9 @@
|
|||
{
|
||||
"id": "rclock",
|
||||
"name": "Round clock with seconds, minutes and date",
|
||||
"shortName":"Round Clock",
|
||||
"shortName": "Round Clock",
|
||||
"icon": "app.png",
|
||||
"version":"0.04",
|
||||
"version": "0.05",
|
||||
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
|
@ -1712,6 +1726,20 @@
|
|||
{"name":"rclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fclock",
|
||||
"name": "fclock",
|
||||
"shortName": "F Clock",
|
||||
"icon": "app.png",
|
||||
"version": "0.01",
|
||||
"description": "Simple design of a digital clock",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"storage": [
|
||||
{"name":"fclock.app.js","url":"fclock.app.js"},
|
||||
{"name":"fclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "hamloc",
|
||||
"name": "QTH Locator / Maidenhead Locator System",
|
||||
"shortName": "QTH Locator",
|
||||
|
@ -2399,9 +2427,11 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"worldclock.app.js","url":"app.js"},
|
||||
{"name":"worldclock.settings.json"},
|
||||
{"name":"worldclock.img","url":"worldclock-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [
|
||||
{"name":"worldclock.settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "digiclock",
|
||||
"name": "Digital Clock Face",
|
||||
|
@ -2644,8 +2674,10 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"breath.app.js","url":"app.js"},
|
||||
{"name":"breath.settings.json","url":"settings.json"},
|
||||
{"name":"breath.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"breath.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "lazyclock",
|
||||
|
@ -2742,9 +2774,11 @@
|
|||
"storage": [
|
||||
{"name":"gpsservice.app.js","url":"app.js"},
|
||||
{"name":"gpsservice.settings.js","url":"settings.js"},
|
||||
{"name":"gpsservice.settings.json","url":"settings.json"},
|
||||
{"name":"gpsservice.wid.js","url":"widget.js"},
|
||||
{"name":"gpsservice.img","url":"gpsservice-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gpsservice.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "mclockplus",
|
||||
|
@ -2841,9 +2875,11 @@
|
|||
"storage": [
|
||||
{"name":"gpssetup","url":"gpssetup.js"},
|
||||
{"name":"gpssetup.settings.js","url":"settings.js"},
|
||||
{"name":"gpssetup.settings.json","url":"settings.json"},
|
||||
{"name":"gpssetup.app.js","url":"app.js"},
|
||||
{"name":"gpssetup.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gpssetup.settings.json","url":"settings.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "walkersclock",
|
||||
|
@ -2944,8 +2980,10 @@
|
|||
"interface":"waypoints.html",
|
||||
"storage": [
|
||||
{"name":"waypointer.app.js","url":"app.js"},
|
||||
{"name":"waypoints.json","url":"waypoints.json","evaluate":false},
|
||||
{"name":"waypointer.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"waypoints.json","url":"waypoints.json"}
|
||||
]
|
||||
},
|
||||
{ "id": "color_catalog",
|
||||
|
@ -3004,7 +3042,7 @@
|
|||
"name": "Gadgetbridge Music Controls",
|
||||
"shortName":"Music Controls",
|
||||
"icon": "icon.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Control the music on your Gadgetbridge-connected phone",
|
||||
"tags": "tools,bluetooth,gadgetbridge,music",
|
||||
"type":"app",
|
||||
|
@ -3045,7 +3083,7 @@
|
|||
{ "id": "kitchen",
|
||||
"name": "Kitchen Combo",
|
||||
"icon": "kitchen.png",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Combination of the stepo, walkersclock, arrow and waypointer apps into a multiclock format. 'Everything but the kitchen sink'. Requires firmware v2.08.167 or later",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"readme": "README.md",
|
||||
|
@ -3060,22 +3098,37 @@
|
|||
{"name":"kitchen.img","url":"kitchen.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "qmsched",
|
||||
"name": "Quiet Mode Schedule",
|
||||
"shortName":"Quiet Mode",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
"storage": [
|
||||
{"name":"qmsched","url":"lib.js"},
|
||||
{"name":"qmsched.app.js","url":"app.js"},
|
||||
{"name":"qmsched.boot.js","url":"boot.js"},
|
||||
{"name":"qmsched.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"qmsched.json"}
|
||||
]
|
||||
}
|
||||
{ "id": "qmsched",
|
||||
"name": "Quiet Mode Schedule",
|
||||
"shortName":"Quiet Mode",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Automatically turn Quiet Mode on or off at set times",
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
"storage": [
|
||||
{"name":"qmsched","url":"lib.js"},
|
||||
{"name":"qmsched.app.js","url":"app.js"},
|
||||
{"name":"qmsched.boot.js","url":"boot.js"},
|
||||
{"name":"qmsched.img","url":"icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"qmsched.json"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "hourstrike",
|
||||
"name": "Hour Strike",
|
||||
"shortName": "Hour Strike",
|
||||
"icon": "app-icon.png",
|
||||
"version": "0.07",
|
||||
"description": "Strike the clock on the hour. A great tool to remind you an hour has passed!",
|
||||
"tags": "tool,alarm",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"hourstrike.app.js","url":"app.js"},
|
||||
{"name":"hourstrike.boot.js","url":"boot.js"},
|
||||
{"name":"hourstrike.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: First published version of app
|
After Width: | Height: | Size: 6.4 KiB |
|
@ -0,0 +1,206 @@
|
|||
{
|
||||
var minutes;
|
||||
var seconds;
|
||||
var hours;
|
||||
var date;
|
||||
var first = true;
|
||||
var locale = require('locale');
|
||||
var _12hour = (require("Storage").readJSON("setting.json", 1) || {})["12hour"] || false;
|
||||
|
||||
//HR variables
|
||||
var id = 0;
|
||||
var grow = true;
|
||||
var size=10;
|
||||
|
||||
//Screen dimensions
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
};
|
||||
|
||||
// Ssettings
|
||||
const settings = {
|
||||
time: {
|
||||
color: '#dddddd',
|
||||
font: 'Vector',
|
||||
size: 100,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
},
|
||||
date: {
|
||||
color: '#dddddd',
|
||||
font: 'Vector',
|
||||
size: 15,
|
||||
middle: screen.height-17, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
circle: {
|
||||
colormin: '#ffffff',
|
||||
colorsec: '#ffffff',
|
||||
width: 10,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
height: screen.height
|
||||
},
|
||||
hr: {
|
||||
color: '#333333',
|
||||
size: 20,
|
||||
x: screen.center,
|
||||
y: screen.middle + 65
|
||||
}
|
||||
};
|
||||
|
||||
const dateStr = function (date) {
|
||||
return locale.date(new Date(), 1);
|
||||
};
|
||||
|
||||
const getFormated = function(val) {
|
||||
if (val<10) {
|
||||
val='0'+val;
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
const drawMin = function (sections, color) {
|
||||
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor('#000000');
|
||||
g.setFont(settings.time.font, settings.time.size/2);
|
||||
g.drawString(getFormated(sections-1), settings.time.center+50, settings.time.middle);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size/2);
|
||||
g.drawString(getFormated(sections), settings.time.center+50, settings.time.middle);
|
||||
};
|
||||
|
||||
const drawSec = function (sections, color) {
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor('#000000');
|
||||
g.setFont(settings.time.font, settings.time.size/4);
|
||||
g.drawString(getFormated(sections-1), settings.time.center+100, settings.time.middle);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size/4);
|
||||
g.drawString(getFormated(sections), settings.time.center+100, settings.time.middle);
|
||||
};
|
||||
|
||||
const drawClock = function () {
|
||||
|
||||
currentTime = new Date();
|
||||
|
||||
//Get date as a string
|
||||
date = dateStr(currentTime);
|
||||
|
||||
if(seconds==59) {
|
||||
g.clear();
|
||||
}
|
||||
|
||||
// Update minutes when needed
|
||||
if (minutes != currentTime.getMinutes()) {
|
||||
minutes = currentTime.getMinutes();
|
||||
drawMin(minutes, settings.circle.colormin);
|
||||
}
|
||||
|
||||
//Update seconds when needed
|
||||
if (seconds != currentTime.getSeconds()) {
|
||||
seconds = currentTime.getSeconds();
|
||||
drawSec(seconds, settings.circle.colorsec);
|
||||
}
|
||||
|
||||
//Write the time as configured in the settings
|
||||
hours = currentTime.getHours();
|
||||
if (_12hour && hours > 13) {
|
||||
hours = hours - 12;
|
||||
}
|
||||
|
||||
var meridian;
|
||||
|
||||
if (typeof locale.meridian === "function") {
|
||||
meridian = locale.meridian(new Date());
|
||||
} else {
|
||||
meridian = "";
|
||||
}
|
||||
|
||||
var timestr;
|
||||
|
||||
if (meridian.length > 0 && _12hour) {
|
||||
timestr = hours + " " + meridian;
|
||||
} else {
|
||||
timestr = hours;
|
||||
}
|
||||
g.setFontAlign(0, 0, 0);
|
||||
g.setColor(settings.time.color);
|
||||
g.setFont(settings.time.font, settings.time.size);
|
||||
g.drawString(timestr, settings.time.center-40, settings.time.middle);
|
||||
|
||||
//Write the date as configured in the settings
|
||||
g.setColor(settings.date.color);
|
||||
g.setFont(settings.date.font, settings.date.size);
|
||||
g.drawString(date, settings.date.center, settings.date.middle);
|
||||
};
|
||||
|
||||
//setInterval for HR visualisation
|
||||
const newBeats = function (hr) {
|
||||
if (id != 0) {
|
||||
changeInterval(id, 6e3 / hr.bpm);
|
||||
} else {
|
||||
id = setInterval(drawHR, 6e3 / hr.bpm);
|
||||
}
|
||||
};
|
||||
|
||||
//visualize HR with circles pulsating
|
||||
const drawHR = function () {
|
||||
if (grow && size < settings.hr.size) {
|
||||
size++;
|
||||
}
|
||||
|
||||
if (!grow && size > 3) {
|
||||
size--;
|
||||
}
|
||||
|
||||
if (size == settings.hr.size || size == 3) {
|
||||
grow = !grow;
|
||||
}
|
||||
|
||||
if (grow) {
|
||||
color = settings.hr.color;
|
||||
g.setColor(color);
|
||||
g.fillCircle(settings.hr.x, settings.hr.y, size);
|
||||
} else {
|
||||
color = "#000000";
|
||||
g.setColor(color);
|
||||
g.drawCircle(settings.hr.x, settings.hr.y, size);
|
||||
}
|
||||
};
|
||||
|
||||
// clean app screen
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
//manage when things should be enabled and not
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) {
|
||||
Bangle.setHRMPower(1);
|
||||
} else {
|
||||
Bangle.setHRMPower(0);
|
||||
}
|
||||
});
|
||||
|
||||
// refesh every second
|
||||
setInterval(drawClock, 1E3);
|
||||
|
||||
//start HR monitor and update frequency of update
|
||||
Bangle.setHRMPower(1);
|
||||
Bangle.on('HRM', function (d) {
|
||||
newBeats(d);
|
||||
});
|
||||
|
||||
// draw now
|
||||
drawClock();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial version
|
||||
0.02: Increase text brightness, improve controls, (try to) reduce memory usage
|
|
@ -23,9 +23,13 @@ You can change this under `Settings`->`App/Widget Settings`->`Music Controls`.
|
|||
## Controls
|
||||
|
||||
### Buttons
|
||||
* Button 1: Volume up (hold to repeat)
|
||||
* Button 2: Toggle play/pause, long-press for menu
|
||||
* Button 3: Volume down (hold to repeat, but remember that holding for too long resets your watch)
|
||||
* Button 1: Volume up
|
||||
* Button 2:
|
||||
- Single press: toggle play/pause
|
||||
- Double press: next song
|
||||
- Triple press: previous song
|
||||
- Long-press: open application launcher
|
||||
* Button 3: Volume down
|
||||
|
||||
### Touch
|
||||
* Left: pause/previous song
|
||||
|
|
1158
apps/gbmusic/app.js
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Don't overwrite existing settings on app update
|
||||
Clean up recordings on app removal
|
||||
0.03: added graphing feature of 164 latest measurements
|
||||
|
|
|
@ -1,8 +1,25 @@
|
|||
const GraphXZero = 40;
|
||||
const GraphYZero = 200;
|
||||
const GraphY100 = 80;
|
||||
|
||||
const GraphMarkerOffset = 5;
|
||||
const MaxValueCount = 164;
|
||||
const GraphXMax = GraphXZero + MaxValueCount;
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var settings = require("Storage").readJSON("heart.json",1)||{};
|
||||
|
||||
var globalSettings = require('Storage').readJSON('setting.json', true) || {timezone: 0};
|
||||
require('DateExt').locale({
|
||||
str: "0D.0M. 0h:0m",
|
||||
offset: [
|
||||
globalSettings.timezone * 60,
|
||||
globalSettings.timezone * 60
|
||||
]
|
||||
});
|
||||
|
||||
function getFileNbr(n) {
|
||||
return ".heart"+n.toString(36);
|
||||
}
|
||||
|
@ -36,6 +53,7 @@ function showMainMenu() {
|
|||
}
|
||||
},
|
||||
'View Records': viewRecords,
|
||||
'Graph Records': graphRecords,
|
||||
'< Back': ()=>{load();}
|
||||
};
|
||||
return E.showMenu(mainMenu);
|
||||
|
@ -97,4 +115,183 @@ function viewRecord(n) {
|
|||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function graphRecords() {
|
||||
const menu = {
|
||||
'': { 'title': 'Heart Records' }
|
||||
};
|
||||
var found = false;
|
||||
for (var n=0;n<36;n++) {
|
||||
var f = require("Storage").open(getFileNbr(n),"r");
|
||||
var line = f.readLine();
|
||||
if (line!==undefined) {
|
||||
menu["#"+n+" "+Date(line.split(",")[0]*1000).as().str] = graphRecord.bind(null,n);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
menu["No Records Found"] = function(){};
|
||||
menu['< Back'] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
// based on batchart
|
||||
function renderHomeIcon() {
|
||||
//Home for Btn2
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawLine(220, 118, 227, 110);
|
||||
g.drawLine(227, 110, 234, 118);
|
||||
|
||||
g.drawPoly([222,117,222,125,232,125,232,117], false);
|
||||
g.drawRect(226,120,229,125);
|
||||
}
|
||||
|
||||
function renderChart() {
|
||||
// Left Y axis (Battery)
|
||||
g.setColor(1, 1, 0);
|
||||
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
|
||||
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("150", 35, GraphY100 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
|
||||
|
||||
g.drawString("125", 35, GraphYZero - 110 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("100", 35, GraphYZero - 100 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("90", 35, GraphYZero - 90 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("80", 35, GraphYZero - 70 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("70", 35, GraphYZero - 50 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("60", 35, GraphYZero - 30 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("50", 35, GraphYZero - 20 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("40", 35, GraphYZero - 10 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 150, GraphXZero, 150);
|
||||
|
||||
g.drawString("30", 35, GraphYZero - GraphMarkerOffset);
|
||||
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
|
||||
|
||||
console.log("Finished drawing chart");
|
||||
}
|
||||
|
||||
// as drawing starts at 30 HRM decreasing measrure by 30
|
||||
// recalculate for range 110-150 as only 20 pixels are available
|
||||
function getY(measure) {
|
||||
positionY = GraphYZero - measure + 30;
|
||||
if (100 < measure < 150) {
|
||||
positionY = GraphYZero - ( 100 + Math.round((measure - 100)/2) ) + 30;
|
||||
g.setColor(1, 0, 0);
|
||||
} else if (60 < measrure < 100) {
|
||||
positionY = GraphYZero - ( 30 + Math.round((measure - 30)/2) ) + 30;
|
||||
g.setColor(0, 1, 0);
|
||||
}
|
||||
if (positionY > GraphYZero) {
|
||||
positionY = GraphYZero;
|
||||
g.setColor(1, 0, 0);
|
||||
}
|
||||
if (positionY < GraphY100) {
|
||||
positionY = GraphY100;
|
||||
g.setColor(1, 0, 0);
|
||||
}
|
||||
return positionY;
|
||||
}
|
||||
|
||||
function stop() {
|
||||
E.showMenu();
|
||||
load();
|
||||
}
|
||||
|
||||
function graphRecord(n) {
|
||||
E.showMenu({'': 'Heart Record '+n});
|
||||
E.showMessage(
|
||||
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
|
||||
'Heart Record '+n
|
||||
);
|
||||
g.setFont("Vector", 10);
|
||||
|
||||
var lastPixel;
|
||||
var lineCount = 0;
|
||||
var positionX = GraphXZero;
|
||||
var positionY = GraphYZero;
|
||||
var startLine = 1;
|
||||
var tempCount = 0;
|
||||
var f = require("Storage").open(getFileNbr(n),"r");
|
||||
var line = f.readLine();
|
||||
var times = Array(2);
|
||||
console.log("Counting lines");
|
||||
while (line !== undefined) {
|
||||
lineCount++;
|
||||
line = f.readLine();
|
||||
}
|
||||
console.log(`Line count: ${lineCount}`);
|
||||
if (lineCount > MaxValueCount) {
|
||||
startLine = lineCount - MaxValueCount;
|
||||
}
|
||||
console.log(`start: ${startLine}`);
|
||||
|
||||
f = require("Storage").open(getFileNbr(n),"r");
|
||||
line = f.readLine();
|
||||
while (line !== undefined) {
|
||||
currentLine = line;
|
||||
line = f.readLine();
|
||||
tempCount++;
|
||||
if (tempCount == startLine) {
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
renderHomeIcon();
|
||||
renderChart();
|
||||
} else if (tempCount > startLine) {
|
||||
positionX++;
|
||||
if (parseInt(currentLine.split(",")[2]) >= 70) {
|
||||
g.setColor(1, 1, 1);
|
||||
oldPositionY = positionY;
|
||||
positionY = getY(parseInt(currentLine.split(",")[1]));
|
||||
if (times[0] === undefined) {
|
||||
times[0] = parseInt(currentLine.split(",")[0]);
|
||||
}
|
||||
if (tempCount == startLine + 1) {
|
||||
g.setPixel(positionX, positionY);
|
||||
} else {
|
||||
g.drawLine(positionX - 1, oldPositionY, positionX, positionY);
|
||||
times[1] = parseInt(currentLine.split(",")[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
g.flip();
|
||||
}
|
||||
|
||||
g.setColor(1, 1, 0);
|
||||
g.setFont("Vector", 10);
|
||||
console.log('start: ' + times[0]);
|
||||
console.log('end: ' + times[1]);
|
||||
if (times[0] !== undefined) {
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
var startdate = new Date(times[0]*1000);
|
||||
g.drawString(startdate.local().as("0h:0m").str, 15, GraphYZero + 12);
|
||||
}
|
||||
if (times[1] !== undefined) {
|
||||
g.setFontAlign(1, -1, 0);
|
||||
var enddate = new Date(times[1]*1000);
|
||||
g.drawString(enddate.local().as().str, GraphXMax, GraphYZero + 12);
|
||||
}
|
||||
console.log("Finished rendering data");
|
||||
Bangle.buzz(200, 0.3);
|
||||
setWatch(stop, BTN2, {edge:"falling", debounce:50, repeat:false});
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
|
||||
// vim: et ts=2 sw=2
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
0.01: New App
|
||||
0.02: Add different strike intervals and support for quiet time
|
||||
0.03: Bug fixes for setting attributes
|
||||
0.04: Add more time to strike and the strength
|
||||
0.05: Add display for the next strike time
|
||||
0.06: Move the next strike time to the first row of display
|
||||
0.07: Change the boot function to avoid reloading the entire watch
|
|
@ -0,0 +1,25 @@
|
|||
# Hour Strike
|
||||
|
||||

|
||||
|
||||
Time passes too fast!
|
||||
|
||||
This app configures your `Bangle.js` so that it buzzes on the hour or on the half hour.
|
||||
|
||||
This app is slightly different from [Hour Chime](https://github.com/espruino/BangleApps/tree/master/apps/widchime). `Hour Chimee` runs as a widget but `Hour Strike` runs as a background task, without showing a widget.
|
||||
|
||||
## Features
|
||||
|
||||
- Strike the hour, the half hour, the quarter hour, and more
|
||||
- Set up a range of hours that clock will strike
|
||||
- Set up the strength of the strike
|
||||
- Preview when the next strike will happen
|
||||
|
||||
## Known Issues
|
||||
|
||||
- This app does not know or check whether your clock already chimes on the hour.
|
||||
|
||||
## Creator
|
||||
|
||||
[Weiming Hu](https://weiming-hu.github.io/), using coding from the [Default Alarm](https://github.com/espruino/BangleApps/tree/master/apps/alarm).
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkGswAHogAEBxAAHsgXFowXPCwowQFwwwQCIUjn/zmQwPFwUj/4ACDAQwMBwNDCgPwh4DBmgwMFwU/C4vzGBgMBoRECC4f/kgwLIwgXFJBgLBl4XH+QXNLwQXFMAQXPmEDC6K8DiEBAoYXSgA1DI6MxgETL6gYBgIGBC5ynDCYMQAwKnOa4YABmTXQoQXEAAUkC5dkMAx2EowXJJBBGNAAMUBwMjMAgHBoIXLiIPBDAM/+YWCokRC5gwCAAtBC5owDAAgJBC5dBiAwGoMBigXLokQgIXFA4QXMoEAAANNqAECggXR7oXXAYQXSgvUC6sEC60N6oXW6AXUinu8IXTgPuAAMQC6UEC4SsDC58OC4XgC9RHXO66nCoLXVAAoXmABQX1A"))
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,48 @@
|
|||
const storage = require('Storage');
|
||||
let settings;
|
||||
|
||||
function updateSettings() {
|
||||
storage.write('hourstrike.json', settings);
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
settings = {
|
||||
interval: 3600,
|
||||
start: 9,
|
||||
end: 21,
|
||||
vlevel: 0.5,
|
||||
next_hour: -1,
|
||||
next_minute: -1,
|
||||
};
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
settings = storage.readJSON('hourstrike.json', 1);
|
||||
if (!settings) resetSettings();
|
||||
|
||||
function showMainMenu() {
|
||||
var mode_txt = ['Off','1 min','5 min','10 min','1/4 h','1/2 h','1 h'];
|
||||
var mode_interval = [-1,60,300,600,900,1800,3600];
|
||||
const mainmenu = {'': { 'title': 'Hour Strike' }};
|
||||
mainmenu['Next strike '+settings.next_hour+':'+settings.next_minute] = function(){};
|
||||
mainmenu['Notify every'] = {
|
||||
value: mode_interval.indexOf(settings.interval),
|
||||
min: 0, max: 6, format: v => mode_txt[v],
|
||||
onchange: v => {
|
||||
settings.interval = mode_interval[v];
|
||||
if (v===0) {settings.next_hour = -1; settings.next_minute = -1;}
|
||||
updateSettings();}};
|
||||
mainmenu.Start = {
|
||||
value: settings.start, min: 0, max: 23, format: v=>v+':00',
|
||||
onchange: v=> {settings.start = v; updateSettings();}};
|
||||
mainmenu.End = {
|
||||
value: settings.end, min: 0, max: 23, format: v=>v+':59',
|
||||
onchange: v=> {settings.end = v; updateSettings();}};
|
||||
mainmenu.Strength = {
|
||||
value: settings.vlevel*10, min: 1, max: 10, format: v=>v/10,
|
||||
onchange: v=> {settings.vlevel = v/10; updateSettings();}};
|
||||
mainmenu['< Back'] = ()=>load();
|
||||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
||||
showMainMenu();
|
|
@ -0,0 +1,39 @@
|
|||
(function() {
|
||||
function setup () {
|
||||
var settings = require('Storage').readJSON('hourstrike.json',1)||[];
|
||||
var t = new Date();
|
||||
var t_min_sec = t.getMinutes()*60+t.getSeconds();
|
||||
var wait_msec = settings.interval>0?(settings.interval-t_min_sec%settings.interval)*1000:-1;
|
||||
if (wait_msec>0) {
|
||||
t.setMilliseconds(t.getMilliseconds()+wait_msec);
|
||||
var t_hour = t.getHours();
|
||||
if (t_hour<settings.start||t_hour>settings.end) {
|
||||
var strike = new Date(t.getTime());
|
||||
strike.setHours(settings.start);
|
||||
strike.setMinutes(0);
|
||||
if (t_hour>settings.end) {
|
||||
strike.setDate(strike.getDate()+1);
|
||||
}
|
||||
wait_msec += strike-t;
|
||||
settings.next_hour = strike.getHours();
|
||||
settings.next_minute = strike.getMinutes();
|
||||
} else {
|
||||
settings.next_hour = t_hour;
|
||||
settings.next_minute = t.getMinutes();
|
||||
}
|
||||
setTimeout(strike_func, wait_msec);
|
||||
} else {
|
||||
settings.next_hour = -1;
|
||||
settings.next_minute = -1;
|
||||
}
|
||||
require('Storage').write('hourstrike.json', settings);
|
||||
}
|
||||
function strike_func () {
|
||||
var setting = require('Storage').readJSON('hourstrike.json',1)||[];
|
||||
Bangle.buzz(200, setting.vlevel||0.5)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve,200)))
|
||||
.then(() => Bangle.buzz(200, setting.vlevel||0.5));
|
||||
setup();
|
||||
}
|
||||
setup();
|
||||
})();
|
|
@ -1 +1,3 @@
|
|||
0.02: Don't buzz for GPS fix in Quiet Mode
|
||||
0.01: First version
|
||||
0.02: compass disable BTN1,BTN2 while waiting for GPS to reach RUNNING status
|
||||
0.03: Don't buzz for GPS fix in Quiet Mode
|
|
@ -106,6 +106,8 @@
|
|||
}
|
||||
|
||||
function onButtonShort(btn) {
|
||||
log_debug("onButtonShort()");
|
||||
if (gpsObject.getState() !== gpsObject.GPS_RUNNING) return;
|
||||
switch(btn) {
|
||||
case 1:
|
||||
log_debug("prev waypoint");
|
||||
|
@ -128,7 +130,6 @@
|
|||
log_debug("markWaypoint()");
|
||||
if (btn !== 1) return;
|
||||
if (gpsObject.getState() !== gpsObject.GPS_RUNNING) return;
|
||||
|
||||
log_debug("markWaypoint()");
|
||||
|
||||
gpsObject.markWaypoint();
|
||||
|
@ -143,8 +144,8 @@
|
|||
wp_distance = gpsObject.getWPdistance();
|
||||
wp_bearing = gpsObject.getWPbearing();
|
||||
log_debug(wp);
|
||||
log_debug(wp_distance);
|
||||
log_debug(wp_bearing);
|
||||
log_debug("wp_distance:" + wp_distance);
|
||||
log_debug("wp_bearing:" + wp_bearing);
|
||||
}
|
||||
|
||||
// takes 32ms
|
||||
|
@ -205,16 +206,8 @@
|
|||
var d = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
heading = newHeading(d,heading);
|
||||
|
||||
if (gpsObject.getState() === gpsObject.GPS_RUNNING) {
|
||||
wp_dist = gpsObject.getWPdistance();
|
||||
wp_bearing = gpsObject.getWPbearing();
|
||||
bearing = wp_bearing;
|
||||
} else {
|
||||
bearing = 0;
|
||||
wp_distance = 0;
|
||||
wp_bearing = 0;
|
||||
}
|
||||
|
||||
getWaypoint();
|
||||
|
||||
var dir = bearing - heading;
|
||||
if (dir < 0) dir += 360;
|
||||
if (dir > 360) dir -= 360;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
0.01: First published version of app
|
||||
0.02: Added support for locale and 12H clock
|
||||
0.03: Added HR indication to clock
|
||||
0.04: Update font size and alignment
|
||||
0.04: Update font size and alignment
|
||||
0.05: Changes which circle show minutes and seconds
|
|
@ -23,29 +23,29 @@
|
|||
// Ssettings
|
||||
const settings = {
|
||||
time: {
|
||||
color: 0xD6ED17,
|
||||
color: '#D6ED17',
|
||||
font: 'Vector',
|
||||
size: 60,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
},
|
||||
date: {
|
||||
color: 0xD6ED17,
|
||||
color: '#D6ED17',
|
||||
font: 'Vector',
|
||||
size: 15,
|
||||
middle: screen.height-17, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
circle: {
|
||||
colormin: 0x606060,
|
||||
colorsec: 0x656565,
|
||||
colormin: '#ffffff',
|
||||
colorsec: '#ffffff',
|
||||
width: 10,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
height: screen.height
|
||||
},
|
||||
hr: {
|
||||
color: 0x333333,
|
||||
color: '#333333',
|
||||
size: 10,
|
||||
x: screen.center,
|
||||
y: screen.middle + 45
|
||||
|
@ -66,18 +66,6 @@
|
|||
};
|
||||
|
||||
const drawMinArc = function (sections, color) {
|
||||
g.setColor(color);
|
||||
rad = (settings.circle.height / 2) - 20;
|
||||
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
|
||||
g.setColor('#333333');
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4)
|
||||
};
|
||||
|
||||
const drawSecArc = function (sections, color) {
|
||||
g.setColor(color);
|
||||
rad = (settings.circle.height / 2) - 40;
|
||||
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
|
||||
|
@ -86,7 +74,19 @@
|
|||
//g.setPixel(r[0],r[1]);
|
||||
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
|
||||
g.setColor('#333333');
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4)
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4);
|
||||
};
|
||||
|
||||
const drawSecArc = function (sections, color) {
|
||||
g.setColor(color);
|
||||
rad = (settings.circle.height / 2) - 20;
|
||||
r1 = getArcXY(settings.circle.middle, settings.circle.center, rad, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
r2 = getArcXY(settings.circle.middle, settings.circle.center, rad - settings.circle.width, sections * (360 / 60) - 90);
|
||||
//g.setPixel(r[0],r[1]);
|
||||
g.drawLine(r1[0], r1[1], r2[0], r2[1]);
|
||||
g.setColor('#333333');
|
||||
g.drawCircle(settings.circle.middle, settings.circle.center, rad - settings.circle.width - 4);
|
||||
};
|
||||
|
||||
const drawClock = function () {
|
||||
|
@ -107,15 +107,13 @@
|
|||
first = false;
|
||||
}
|
||||
|
||||
// Reset seconds
|
||||
// Reset
|
||||
if (seconds == 59) {
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 40);
|
||||
}
|
||||
// Reset minutes
|
||||
if (minutes == 59 && seconds == 59) {
|
||||
g.setColor('#000000');
|
||||
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2) - 20);
|
||||
g.fillCircle(settings.circle.middle, settings.circle.center, (settings.circle.height / 2));
|
||||
for (count = 0; count <= minutes; count++) {
|
||||
drawMinArc(count, settings.circle.colormin);
|
||||
}
|
||||
}
|
||||
|
||||
//Get date as a string
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"ble":true,"blerepl":true,"log":false,"timeout":10,"vibrate":true,"beep":"vib","timezone":0,"HID":false,"clock":null,"12hour":false,"brightness":1,"options":{"wakeOnBTN1":true,"wakeOnBTN2":true,"wakeOnBTN3":true,"wakeOnFaceUp":false,"wakeOnTouch":false,"wakeOnTwist":true,"twistThreshold":819.2,"twistMaxY":-800,"twistTimeout":1000}}
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial Release
|
||||
0.02: Color Themes, Smoother scrolling
|
||||
|
|
|
@ -1,18 +1,34 @@
|
|||
# Sliding Text Clock - See the time in different languages
|
||||
|
||||
Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently only English, French and Japanese are supported
|
||||
Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Please use the upload page to choose which languages you want loaded.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
### Button 1
|
||||
|
||||
Use Button 1 (the top right button) to change the language
|
||||
|
||||
| English | English (Traditional) | French | Japanese (Romanji) |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  | |
|
||||
|
||||
### Button 3
|
||||
Button 3 (bottom right button) is used to change the colour
|
||||
|
||||
| Black | Red | Gray | Purple |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  |  |
|
||||
|
||||
## Further Details
|
||||
|
||||
For further details of design and working please visit [The Project Page](https://www.notion.so/adrianwkirk/Sliding-Text-Clock-a8fe556f03624a619656ddbc4f36f41b)
|
||||
|
||||
## Requests
|
||||
|
||||
[Reach out to Adrian](https://www.github.com/awkirk71) if you have feature requests or notice bugs.
|
||||
Reach out to adrian@adriankirk.com if you have feature requests or notice bugs.
|
||||
|
||||
## Creator
|
||||
|
||||
Made by [Adrian Kirk](https://www.github.com/awkirk71).
|
||||
Made by [Adrian Kirk](mailto:adrian@adriankirk.com)
|
||||
|
|
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 20 KiB |
|
@ -0,0 +1,69 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Please select watch languages</p>
|
||||
|
||||
<table id="language_selection">
|
||||
<tr>
|
||||
<th>Enabled</th>
|
||||
<th>Name</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var slidingtext_languages=[
|
||||
{name:"English", shortname:"en"},
|
||||
{name:"English(Traditional)",shortname:"en2"},
|
||||
{name:"French",shortname:"fr"},
|
||||
{name:"Japanese",shortname:"jp"}
|
||||
];
|
||||
var selected_languages = ["en","fr","jp"];
|
||||
try{
|
||||
var stored = localStorage.getItem('slidingtext_stored')
|
||||
if(stored) selected_languages = JSON.parse(stored);
|
||||
} catch(e){
|
||||
console.log("failed to load languages:" + e);
|
||||
}
|
||||
console.log("selected languages:" + selected_languages);
|
||||
var tbl=document.getElementById("language_selection");
|
||||
for (var i=0; i<slidingtext_languages.length; i++) {
|
||||
var curr_language = slidingtext_languages[i];
|
||||
var language_selected = selected_languages.includes(curr_language["shortname"])
|
||||
var $offset = document.createElement('tr')
|
||||
$offset.innerHTML = `
|
||||
<td><input type="checkbox" id="enabled_${i}" ${language_selected? "checked" : ""}></td>
|
||||
<td>${curr_language['name']}</td>`
|
||||
tbl.append($offset);
|
||||
}
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var new_selected_languages=[];
|
||||
for (var i=0; i<slidingtext_languages.length; i++) {
|
||||
var curr_language = slidingtext_languages[i];
|
||||
var checked=document.getElementById("enabled_"+i).checked;
|
||||
if (checked) {
|
||||
new_selected_languages.push(curr_language.shortname);
|
||||
}
|
||||
}
|
||||
console.log("new selected languages:" + new_selected_languages);
|
||||
localStorage.setItem('slidingtext_stored',JSON.stringify(new_selected_languages));
|
||||
// send finished app (in addition to contents of app.json)
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"slidingtext.languages.json", content:JSON.stringify(new_selected_languages)},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 27 KiB |
|
@ -0,0 +1,15 @@
|
|||
class DateFormatter {
|
||||
/**
|
||||
* A pure virtual class which all the other date formatters will
|
||||
* inherit from.
|
||||
* The name will be used to declare the date format when selected
|
||||
* and the date formatDate methid will return the time formated
|
||||
* to the lines of text on the screen
|
||||
*/
|
||||
name(){return "no name";}
|
||||
formatDate(date){
|
||||
return ["no","date","defined"];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DateFormatter;
|
|
@ -1,21 +1,102 @@
|
|||
/**
|
||||
* Adrian Kirk 2021-02
|
||||
* Sliding text clock inspired by the Pebble
|
||||
* clock with the same name
|
||||
*/
|
||||
* Adrian Kirk 2021-02
|
||||
* Sliding text clock inspired by the Pebble
|
||||
* clock with the same name
|
||||
*/
|
||||
|
||||
const color_schemes = [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
main_bar: [1.0,1.0,1.0],
|
||||
other_bars: [0.85,0.85,0.85],
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
background : [1.0,0.0,0.0],
|
||||
main_bar: [1.0,1.0,0.0],
|
||||
other_bars: [0.85,0.85,0.85]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
main_bar: [1.0,1.0,1.0],
|
||||
other_bars: [0.0,0.0,0.0],
|
||||
},
|
||||
{
|
||||
name: "purple",
|
||||
background : [1.0,0.0,1.0],
|
||||
main_bar: [1.0,1.0,0.0],
|
||||
other_bars: [0.85,0.85,0.85]
|
||||
},
|
||||
{
|
||||
name: "blue",
|
||||
background : [0.4,0.7,1.0],
|
||||
main_bar: [1.0,1.0,1.0],
|
||||
other_bars: [0.9,0.9,0.9]
|
||||
}
|
||||
];
|
||||
|
||||
let color_scheme_index = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The Watch Display
|
||||
*/
|
||||
|
||||
function bg_color(){
|
||||
return color_schemes[color_scheme_index].background;
|
||||
}
|
||||
|
||||
function main_color(){
|
||||
return color_schemes[color_scheme_index].main_bar;
|
||||
}
|
||||
|
||||
function other_color(){
|
||||
return color_schemes[color_scheme_index].other_bars;
|
||||
}
|
||||
|
||||
let command_stack_high_priority = [];
|
||||
let command_stack_low_priority = [];
|
||||
|
||||
function next_command(){
|
||||
command = command_stack_high_priority.pop();
|
||||
if(command == null){
|
||||
//console.log("Low priority command");
|
||||
command = command_stack_low_priority.pop();
|
||||
} else {
|
||||
//console.log("High priority command");
|
||||
}
|
||||
if(command != null){
|
||||
command.call();
|
||||
} else {
|
||||
//console.log("no command");
|
||||
}
|
||||
}
|
||||
|
||||
function reset_commands(){
|
||||
command_stack_high_priority = [];
|
||||
command_stack_low_priority = [];
|
||||
}
|
||||
|
||||
function has_commands(){
|
||||
return command_stack_high_priority.length > 0 ||
|
||||
command_stack_low_priority.lenth > 0;
|
||||
}
|
||||
|
||||
class ShiftText {
|
||||
/**
|
||||
* Class Responsible for shifting text around the screen
|
||||
*
|
||||
* This is a object that initializes itself with a position and
|
||||
* text after which you can tell it where you want to move to
|
||||
* using the moveTo method and it will smoothly move the text across
|
||||
* at the selected frame rate and speed
|
||||
*/
|
||||
* Class Responsible for shifting text around the screen
|
||||
*
|
||||
* This is a object that initializes itself with a position and
|
||||
* text after which you can tell it where you want to move to
|
||||
* using the moveTo method and it will smoothly move the text across
|
||||
* at the selected frame rate and speed
|
||||
*/
|
||||
constructor(x,y,txt,font_name,
|
||||
font_size,speed_x,speed_y,freq_millis, color){
|
||||
font_size,speed_x,speed_y,freq_millis,
|
||||
color,
|
||||
bg_color){
|
||||
this.x = x;
|
||||
this.tgt_x = x;
|
||||
this.init_x = x;
|
||||
|
@ -29,29 +110,44 @@ class ShiftText {
|
|||
this.speed_x = Math.abs(speed_x);
|
||||
this.speed_y = Math.abs(speed_y);
|
||||
this.freq_millis = freq_millis;
|
||||
this.colour = color;
|
||||
this.color = color;
|
||||
this.bg_color = bg_color;
|
||||
this.finished_callback=null;
|
||||
this.timeoutId = null;
|
||||
}
|
||||
setColor(color){
|
||||
this.color = color;
|
||||
}
|
||||
setBgColor(bg_color){
|
||||
this.bg_color = bg_color;
|
||||
}
|
||||
reset(){
|
||||
//console.log("reset");
|
||||
this.hide();
|
||||
this.x = this.init_x;
|
||||
this.y = this.init_y;
|
||||
this.txt = this.init_txt;
|
||||
this.show();
|
||||
if(this.timeoutId != null){
|
||||
clearTimeout(this.timeoutId);
|
||||
clearTimeout(this.timeoutId);
|
||||
}
|
||||
}
|
||||
show() {
|
||||
g.setFont(this.font_name,this.font_size);
|
||||
g.setColor(this.colour[0],this.colour[1],this.colour[2]);
|
||||
g.setColor(this.color[0],this.color[1],this.color[2]);
|
||||
g.drawString(this.txt, this.x, this.y);
|
||||
}
|
||||
hide(){
|
||||
g.setFont(this.font_name,this.font_size);
|
||||
g.setColor(0,0,0);
|
||||
//console.log("bgcolor:" + this.bg_color);
|
||||
g.setColor(this.bg_color[0],this.bg_color[1],this.bg_color[2]);
|
||||
g.drawString(this.txt, this.x, this.y);
|
||||
/*g.fillPoly([this.x - 1, this.y,
|
||||
240, this.y,
|
||||
240, this.y + this.font_size,
|
||||
this.x -1 , this.y + this.font_size,
|
||||
]);
|
||||
*/
|
||||
}
|
||||
setText(txt){
|
||||
this.txt = txt;
|
||||
|
@ -92,15 +188,15 @@ class ShiftText {
|
|||
this.finished_callback = finished_callback;
|
||||
}
|
||||
/**
|
||||
* private internal method for directing the text move.
|
||||
* It will see how far away we are from the target coords
|
||||
* and move towards the target at the defined speed.
|
||||
*/
|
||||
* private internal method for directing the text move.
|
||||
* It will see how far away we are from the target coords
|
||||
* and move towards the target at the defined speed.
|
||||
*/
|
||||
_doMove(){
|
||||
this.hide();
|
||||
// move closer to the target in the x direction
|
||||
diff_x = this.tgt_x - this.x;
|
||||
finished_x = false;
|
||||
var diff_x = this.tgt_x - this.x;
|
||||
var finished_x = false;
|
||||
if(Math.abs(diff_x) <= this.speed_x){
|
||||
this.x = this.tgt_x;
|
||||
finished_x = true;
|
||||
|
@ -112,8 +208,8 @@ class ShiftText {
|
|||
}
|
||||
}
|
||||
// move closer to the target in the y direction
|
||||
diff_y = this.tgt_y - this.y;
|
||||
finished_y = false;
|
||||
var diff_y = this.tgt_y - this.y;
|
||||
var finished_y = false;
|
||||
if(Math.abs(diff_y) <= this.speed_y){
|
||||
this.y = this.tgt_y;
|
||||
finished_y = true;
|
||||
|
@ -126,235 +222,90 @@ class ShiftText {
|
|||
}
|
||||
this.show();
|
||||
this.timeoutId = null;
|
||||
finished = finished_x & finished_y;
|
||||
var finished = finished_x & finished_y;
|
||||
if(!finished){
|
||||
this.timeoutId = setTimeout(this._doMove.bind(this), this.freq_millis);
|
||||
} else if(this.finished_callback != null){
|
||||
//console.log("finished - calling:" + this.finished_callback);
|
||||
this.finished_callback.call();
|
||||
this.finished_callback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatter {
|
||||
/**
|
||||
* A pure virtual class which all the other date formatters will
|
||||
* inherit from.
|
||||
* The name will be used to declare the date format when selected
|
||||
* and the date formatDate methid will return the time formated
|
||||
* to the lines of text on the screen
|
||||
*/
|
||||
name(){"no name";}
|
||||
formatDate(date){
|
||||
return ["","",""];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* English date formatting
|
||||
*/
|
||||
|
||||
// English String Numbers
|
||||
const numberStr = ["ZERO","ONE", "TWO", "THREE", "FOUR", "FIVE",
|
||||
"SIX", "SEVEN","EIGHT", "NINE", "TEN",
|
||||
"ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN",
|
||||
"FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN",
|
||||
"NINETEEN", "TWENTY"];
|
||||
const tensStr = ["ZERO", "TEN", "TWENTY", "THIRTY", "FOURTY",
|
||||
"FIFTY"];
|
||||
|
||||
function hoursToText(hours){
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return numberStr[hours];
|
||||
}
|
||||
|
||||
function numberToText(value){
|
||||
word1 = '';
|
||||
word2 = '';
|
||||
if(value > 20){
|
||||
tens = (value / 10 | 0);
|
||||
word1 = tensStr[tens];
|
||||
remainder = value - tens * 10;
|
||||
if(remainder > 0){
|
||||
word2 = numberStr[remainder];
|
||||
}
|
||||
} else if(value > 0) {
|
||||
word1 = numberStr[value];
|
||||
}
|
||||
return [word1,word2];
|
||||
}
|
||||
|
||||
class EnglishDateFormatter extends DateFormatter{
|
||||
name(){return "English";}
|
||||
formatDate(date){
|
||||
hours_txt = hoursToText(date.getHours());
|
||||
mins_txt = numberToText(date.getMinutes());
|
||||
return [hours_txt,mins_txt[0],mins_txt[1]];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* French date formatting
|
||||
*/
|
||||
const frenchNumberStr = [ "ZERO", "UNE", "DEUX", "TROIS", "QUATRE",
|
||||
"CINQ", "SIX", "SEPT", "HUIT", "NEUF", "DIX",
|
||||
"ONZE", "DOUZE", "TREIZE", "QUATORZE","QUINZE",
|
||||
"SEIZE", "DIX SEPT", "DIX HUIT","DIX NEUF", "VINGT",
|
||||
"VINGT ET UN", "VINGT DEUX", "VINGT TROIS",
|
||||
"VINGT QUATRE", "VINGT CINQ", "VINGT SIX",
|
||||
"VINGT SEPT", "VINGT HUIT", "VINGT NEUF"
|
||||
];
|
||||
|
||||
function frenchHoursToText(hours){
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return frenchNumberStr[hours];
|
||||
}
|
||||
|
||||
function frenchHeures(hours){
|
||||
if(hours % 12 == 1){
|
||||
return 'HEURE';
|
||||
} else {
|
||||
return 'HEURES';
|
||||
}
|
||||
}
|
||||
|
||||
class FrenchDateFormatter extends DateFormatter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
name(){return "French";}
|
||||
formatDate(date){
|
||||
hours = frenchHoursToText(date.getHours());
|
||||
heures = frenchHeures(date.getHours());
|
||||
mins = date.getMinutes();
|
||||
if(mins == 0){
|
||||
if(hours == 0){
|
||||
return ["MINUIT", "",""];
|
||||
} else if(hours == 12){
|
||||
return ["MIDI", "",""];
|
||||
} else {
|
||||
return [hours, heures,""];
|
||||
}
|
||||
} else if(mins == 30){
|
||||
return [hours, heures,'ET DEMIE'];
|
||||
} else if(mins == 15){
|
||||
return [hours, heures,'ET QUERT'];
|
||||
} else if(mins == 45){
|
||||
next_hour = date.getHours() + 1;
|
||||
hours = frenchHoursToText(next_hour);
|
||||
heures = frenchHeures(next_hour);
|
||||
return [hours, heures,"MOINS",'LET QUERT'];
|
||||
}
|
||||
if(mins > 30){
|
||||
to_mins = 60-mins;
|
||||
mins_txt = frenchNumberStr[to_mins];
|
||||
next_hour = date.getHours() + 1;
|
||||
hours = frenchHoursToText(next_hour);
|
||||
heures = frenchHeures(next_hour);
|
||||
return [ hours, heures , "MOINS", mins_txt ];
|
||||
} else {
|
||||
mins_txt = frenchNumberStr[mins];
|
||||
return [ hours, heures , mins_txt ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Japanese date formatting
|
||||
*/
|
||||
const japaneseHourStr = [ "ZERO", "ICHII", "NI", "SAN", "YO",
|
||||
"GO", "ROKU", "SHICHI", "HACHI", "KU", "JUU",
|
||||
'JUU ICHI', 'JUU NI'];
|
||||
const tensPrefixStr = [ "",
|
||||
"JUU",
|
||||
'NIJUU',
|
||||
'SAN JUU',
|
||||
'YON JUU',
|
||||
'GO JUU'];
|
||||
|
||||
const japaneseMinuteStr = [ ["", "PUN"],
|
||||
["IP","PUN" ],
|
||||
["NI", "FUN"],
|
||||
["SAN", "PUN"],
|
||||
["YON","FUN"],
|
||||
["GO", "HUN"],
|
||||
["RO", "PUN"],
|
||||
["NANA", "FUN"],
|
||||
["HAP", "PUN"],
|
||||
["KYU","FUN"],
|
||||
["JUP", "PUN"]
|
||||
];
|
||||
|
||||
function japaneseHoursToText(hours){
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return japaneseHourStr[hours];
|
||||
}
|
||||
|
||||
function japaneseMinsToText(mins){
|
||||
if(mins == 0){
|
||||
return ["",""];
|
||||
} else if(mins == 30)
|
||||
return ["HAN",""];
|
||||
else {
|
||||
units = mins % 10;
|
||||
mins_txt = japaneseMinuteStr[units];
|
||||
tens = mins /10 | 0;
|
||||
if(tens > 0){
|
||||
tens_txt = tensPrefixStr[tens];
|
||||
return [tens_txt + ' ' + mins_txt[0], mins_txt[1]];
|
||||
} else {
|
||||
return [mins_txt[0], mins_txt[1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JapaneseDateFormatter extends DateFormatter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
name(){return "Japanese (Romanji)";}
|
||||
formatDate(date){
|
||||
hours_txt = japaneseHoursToText(date.getHours());
|
||||
mins_txt = japaneseMinsToText(date.getMinutes());
|
||||
return [hours_txt,"JI", mins_txt[0], mins_txt[1] ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Watch Display
|
||||
*/
|
||||
|
||||
// a list of display rows
|
||||
let row_displays = [
|
||||
new ShiftText(240,60,'',"Vector",40,10,10,40,[1,1,1]),
|
||||
new ShiftText(240,100,'',"Vector",20,10,10,50,[0.85,0.85,0.85]),
|
||||
new ShiftText(240,120,'',"Vector",20,10,10,60,[0.85,0.85,0.85]),
|
||||
new ShiftText(240,140,'',"Vector",20,10,10,70,[0.85,0.85,0.85])
|
||||
const CLOCK_TEXT_SPEED_X = 10;
|
||||
// a list of display rows
|
||||
let row_displays = [
|
||||
new ShiftText(240,50,'',"Vector",40,CLOCK_TEXT_SPEED_X,1,10,main_color(),bg_color()),
|
||||
new ShiftText(240,90,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
|
||||
new ShiftText(240,120,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
|
||||
new ShiftText(240,150,'',"Vector",30,CLOCK_TEXT_SPEED_X,1,10,other_color(),bg_color()),
|
||||
new ShiftText(240,180,'',"Vector",40,CLOCK_TEXT_SPEED_X,1,10,main_color(),bg_color())
|
||||
];
|
||||
|
||||
// a list of the formatters to cycle through
|
||||
let date_formatters = [
|
||||
new EnglishDateFormatter(),
|
||||
new FrenchDateFormatter(),
|
||||
new JapaneseDateFormatter()
|
||||
];
|
||||
function nextColorTheme(){
|
||||
//console.log("next color theme");
|
||||
color_scheme_index += 1;
|
||||
if(color_scheme_index >= row_displays.length){
|
||||
color_scheme_index = 0;
|
||||
}
|
||||
var color_scheme = color_schemes[color_scheme_index];
|
||||
setColor(color_scheme.main_bar,
|
||||
color_scheme.other_bars,
|
||||
color_scheme.background);
|
||||
reset_clock();
|
||||
draw_clock();
|
||||
}
|
||||
|
||||
function setColor(main_color,other_color,bg_color){
|
||||
row_displays[0].setColor(main_color);
|
||||
row_displays[0].setBgColor(bg_color);
|
||||
for(var i=1; i<row_displays.length - 1; i++){
|
||||
row_displays[i].setColor(other_color);
|
||||
row_displays[i].setBgColor(bg_color);
|
||||
}
|
||||
row_displays[row_displays.length - 1].setColor(main_color);
|
||||
row_displays[row_displays.length - 1].setBgColor(bg_color);
|
||||
g.setColor(bg_color[0],bg_color[1],bg_color[2]);
|
||||
g.fillPoly([0,25,
|
||||
0,240,
|
||||
240,240,
|
||||
240,25
|
||||
]);
|
||||
}
|
||||
|
||||
// load the date formats required
|
||||
|
||||
LANGUAGES_FILE = "slidingtext.languages.json";
|
||||
var LANGUAGES_DEFAULT = ["en","en2"];
|
||||
var locales = null;
|
||||
try{
|
||||
locales = require("Storage").readJSON(LANGUAGES_FILE);
|
||||
if(locales != null){
|
||||
console.log("loaded languages:" + JSON.stringify(locales));
|
||||
} else {
|
||||
console.log("no languages loaded");
|
||||
locales = LANGUAGES_DEFAULT;
|
||||
}
|
||||
} catch(e){
|
||||
console.log("failed to load languages:" + e);
|
||||
}
|
||||
if(locales == null || locales.length == 0){
|
||||
locales = LANGUAGES_DEFAULT;
|
||||
console.log("defaulting languages to locale:" + locales);
|
||||
}
|
||||
|
||||
let date_formatters = [];
|
||||
for(var i=0; i< locales.length; i++){
|
||||
console.log("loading locale:" + locales[i]);
|
||||
var Formatter = require("slidingtext.locale." + locales[i] + ".js");
|
||||
date_formatters.push(new Formatter());
|
||||
}
|
||||
|
||||
// current index of the date formatter to display
|
||||
let date_formatter_idx = 0;
|
||||
let date_formatter = date_formatters[date_formatter_idx];
|
||||
|
||||
// The small display at the top which announces the date format
|
||||
let format_name_display = new ShiftText(55,0,'',"Vector",10,1,1,50,[1,1,1]);
|
||||
|
||||
function changeFormatter(){
|
||||
date_formatter_idx += 1;
|
||||
if(date_formatter_idx >= date_formatters.length){
|
||||
|
@ -364,61 +315,204 @@ function changeFormatter(){
|
|||
date_formatter = date_formatters[date_formatter_idx];
|
||||
reset_clock();
|
||||
draw_clock();
|
||||
// now announce the formatter by name
|
||||
format_name_display.setTextYPosition(date_formatter.name(),-10);
|
||||
format_name_display.moveToY(15);
|
||||
// and then move back
|
||||
format_name_display.onFinished(
|
||||
function(){
|
||||
format_name_display.moveToY(-10);
|
||||
command_stack_high_priority.unshift(
|
||||
function() {
|
||||
//console.log("move in new:" + txt);
|
||||
// first select the top or bottom to display the formatter name
|
||||
// We choose the first spare row without text
|
||||
var format_name_display = row_displays[row_displays.length - 1];
|
||||
if (format_name_display.txt != '') {
|
||||
format_name_display = row_displays[0];
|
||||
}
|
||||
if (format_name_display.txt != ''){
|
||||
return;
|
||||
}
|
||||
format_name_display.speed_x = 3;
|
||||
format_name_display.onFinished(function(){
|
||||
format_name_display.speed_x = CLOCK_TEXT_SPEED_X;
|
||||
console.log("return speed to:" + format_name_display.speed_x)
|
||||
next_command();
|
||||
});
|
||||
format_name_display.setTextXPosition(date_formatter.name(),220);
|
||||
format_name_display.moveToX(-date_formatter.name().length * format_name_display.font_size);
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function reset_clock(){
|
||||
//console.log("reset_clock");
|
||||
var i;
|
||||
for (i = 0; i < row_displays.length; i++) {
|
||||
for (var i = 0; i < row_displays.length; i++) {
|
||||
row_displays[i].speed_x = CLOCK_TEXT_SPEED_X;
|
||||
row_displays[i].reset();
|
||||
}
|
||||
reset_commands();
|
||||
}
|
||||
|
||||
let last_draw_time = null;
|
||||
const next_minute_boundary_secs = 7.5;
|
||||
|
||||
function draw_clock(){
|
||||
//console.log("draw_clock");
|
||||
date = new Date();
|
||||
rows = date_formatter.formatDate(date);
|
||||
var i;
|
||||
for (i = 0; i < rows.length; i++) {
|
||||
var date = new Date();
|
||||
if(last_draw_time != null &&
|
||||
date.getTime() - last_draw_time.getTime() < next_minute_boundary_secs * 1000 &&
|
||||
has_commands() ){
|
||||
console.log("skipping draw clock");
|
||||
return;
|
||||
} else {
|
||||
last_draw_time = date;
|
||||
}
|
||||
reset_commands();
|
||||
console.log("draw_clock:" + date.toISOString());
|
||||
// we don't want the time to be displayed
|
||||
// and then immediately be trigger another time
|
||||
if(date.getSeconds() > 60 - next_minute_boundary_secs){
|
||||
console.log("forwarding to next minute");
|
||||
date = new Date(date.getTime() + next_minute_boundary_secs * 1000);
|
||||
}
|
||||
//date.setMinutes(37);
|
||||
var rows = date_formatter.formatDate(date);
|
||||
var display;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
display = row_displays[i];
|
||||
txt = rows[i];
|
||||
var txt = rows[i];
|
||||
//console.log(i + "->" + txt);
|
||||
display_row(display,txt);
|
||||
}
|
||||
// If the dateformatter has not returned enough
|
||||
// If the dateformatter has not returned enough
|
||||
// rows then treat the reamining rows as empty
|
||||
for (j = i; j < row_displays.length; j++) {
|
||||
for (var j = i; j < row_displays.length; j++) {
|
||||
display = row_displays[j];
|
||||
//console.log(i + "->''(empty)");
|
||||
display_row(display,'');
|
||||
}
|
||||
next_command();
|
||||
//console.log(date);
|
||||
}
|
||||
|
||||
function display_row(display,txt){
|
||||
if(display.txt == ''){
|
||||
display.setTextXPosition(txt,240);
|
||||
display.moveToX(20);
|
||||
} else if(txt != display.txt){
|
||||
display.moveToX(-100);
|
||||
display.onFinished(
|
||||
function(){
|
||||
display.setTextXPosition(txt,240);
|
||||
display.moveToX(20);
|
||||
}
|
||||
if(display == null) {
|
||||
console.log("no display for text:" + txt)
|
||||
return;
|
||||
}
|
||||
|
||||
if(display.txt == null || display.txt == ''){
|
||||
if(txt != '') {
|
||||
command_stack_high_priority.unshift(
|
||||
function () {
|
||||
//console.log("move in new:" + txt);
|
||||
display.onFinished(next_command);
|
||||
display.setTextXPosition(txt, 240);
|
||||
display.moveToX(20);
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if(txt != display.txt && display.txt != null){
|
||||
command_stack_high_priority.push(
|
||||
function(){
|
||||
//console.log("move out:" + txt);
|
||||
display.onFinished(next_command);
|
||||
display.moveToX(-display.txt.length * display.font_size);
|
||||
}
|
||||
);
|
||||
command_stack_low_priority.push(
|
||||
function(){
|
||||
//console.log("move in:" + txt);
|
||||
display.onFinished(next_command);
|
||||
display.setTextXPosition(txt,240);
|
||||
display.moveToX(20);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
display.setTextXPosition(txt,20);
|
||||
command_stack_high_priority.push(
|
||||
function(){
|
||||
//console.log("move in2:" + txt);
|
||||
display.setTextXPosition(txt,20);
|
||||
next_command();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called from load_settings on startup to
|
||||
* set the color scheme to named value
|
||||
*/
|
||||
function set_colorscheme(colorscheme_name){
|
||||
console.log("setting color scheme:" + colorscheme_name);
|
||||
for (var i=0; i < color_schemes.length; i++) {
|
||||
if(color_schemes[i].name == colorscheme_name){
|
||||
color_scheme_index = i;
|
||||
console.log("match");
|
||||
var color_scheme = color_schemes[color_scheme_index];
|
||||
setColor(color_scheme.main_bar,
|
||||
color_scheme.other_bars,
|
||||
color_scheme.background);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_dateformat(dateformat_name){
|
||||
console.log("setting date format:" + dateformat_name);
|
||||
for (var i=0; i < date_formatters.length; i++) {
|
||||
if(date_formatters[i].name() == dateformat_name){
|
||||
date_formatter_idx = i;
|
||||
date_formatter = date_formatters[date_formatter_idx];
|
||||
console.log("match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PREFERENCE_FILE = "slidingtext.settings.json";
|
||||
/**
|
||||
* Called on startup to set the watch to the last preference settings
|
||||
*/
|
||||
function load_settings(){
|
||||
try{
|
||||
settings = require("Storage").readJSON(PREFERENCE_FILE);
|
||||
if(settings != null){
|
||||
console.log("loaded:" + JSON.stringify(settings));
|
||||
if(settings.color_scheme != null){
|
||||
set_colorscheme(settings.color_scheme);
|
||||
}
|
||||
if(settings.date_format != null){
|
||||
set_dateformat(settings.date_format);
|
||||
}
|
||||
} else {
|
||||
console.log("no settings to load");
|
||||
}
|
||||
} catch(e){
|
||||
console.log("failed to load settings:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on button press to save down the last preference settings
|
||||
*/
|
||||
function save_settings(){
|
||||
var settings = {
|
||||
date_format : date_formatter.name(),
|
||||
color_scheme : color_schemes[color_scheme_index].name,
|
||||
};
|
||||
console.log("saving:" + JSON.stringify(settings));
|
||||
require("Storage").writeJSON(PREFERENCE_FILE,settings);
|
||||
}
|
||||
|
||||
function button1pressed() {
|
||||
changeFormatter();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function button3pressed() {
|
||||
console.log("button3pressed");
|
||||
nextColorTheme();
|
||||
reset_clock();
|
||||
draw_clock();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
// The interval reference for updating the clock
|
||||
let intervalRef = null;
|
||||
|
||||
|
@ -430,9 +524,9 @@ function clearTimers(){
|
|||
}
|
||||
|
||||
function startTimers(){
|
||||
let date = new Date();
|
||||
let secs = date.getSeconds();
|
||||
let nextMinuteStart = 60 - secs;
|
||||
var date = new Date();
|
||||
var secs = date.getSeconds();
|
||||
var nextMinuteStart = 60 - secs;
|
||||
//console.log("scheduling clock draw in " + nextMinuteStart + " seconds");
|
||||
setTimeout(scheduleDrawClock,nextMinuteStart * 1000);
|
||||
draw_clock();
|
||||
|
@ -457,6 +551,7 @@ Bangle.on('lcdPower', (on) => {
|
|||
clearTimers();
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.on('faceUp',function(up){
|
||||
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
|
||||
if (up && !Bangle.isLCDOn()) {
|
||||
|
@ -467,9 +562,17 @@ Bangle.on('faceUp',function(up){
|
|||
});
|
||||
|
||||
g.clear();
|
||||
load_settings();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
startTimers();
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"});
|
||||
setWatch(changeFormatter, BTN1,{repeat:true,edge:"falling"});
|
||||
|
||||
|
||||
// Handle button 1 being pressed
|
||||
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
|
||||
|
||||
// Handle button 3 being pressed
|
||||
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
var DateFormatter = require("slidingtext.dtfmt.js");
|
||||
const hoursToText = require("slidingtext.utils.en.js").hoursToText;
|
||||
const numberToText = require("slidingtext.utils.en.js").numberToText;
|
||||
|
||||
class EnglishDateFormatter extends DateFormatter {
|
||||
constructor() { super();}
|
||||
name(){return "English";}
|
||||
formatDate(date){
|
||||
var hours_txt = hoursToText(date.getHours());
|
||||
var mins_txt = numberToText(date.getMinutes());
|
||||
return [hours_txt,mins_txt[0],mins_txt[1]];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EnglishDateFormatter;
|
|
@ -0,0 +1,53 @@
|
|||
var DateFormatter = require("slidingtext.dtfmt.js");
|
||||
const hoursToText = require("slidingtext.utils.en.js").hoursToText;
|
||||
const numberToText = require("slidingtext.utils.en.js").numberToText;
|
||||
|
||||
class EnglishTraditionalDateFormatter extends DateFormatter {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
name(){return "English (Traditional)";}
|
||||
formatDate(date){
|
||||
var mins = date.getMinutes();
|
||||
var hourOfDay = date.getHours();
|
||||
if(mins > 30){
|
||||
hourOfDay += 1;
|
||||
}
|
||||
var hours = hoursToText(hourOfDay);
|
||||
// Deal with the special times first
|
||||
if(mins == 0){
|
||||
return [hours,"", "O'","CLOCK"];
|
||||
} else if(mins == 30){
|
||||
return ["","HALF", "PAST", "", hours];
|
||||
} else if(mins == 15){
|
||||
return ["","QUARTER", "PAST", "", hours];
|
||||
} else if(mins == 45) {
|
||||
return ["", "QUARTER", "TO", "", hours];
|
||||
}
|
||||
var mins_txt;
|
||||
var from_to;
|
||||
var mins_value;
|
||||
if(mins > 30){
|
||||
mins_value = 60-mins;
|
||||
from_to = "TO";
|
||||
mins_txt = numberToText(mins_value);
|
||||
} else {
|
||||
mins_value = mins;
|
||||
from_to = "PAST";
|
||||
mins_txt = numberToText(mins_value);
|
||||
}
|
||||
if(mins_txt[1] != '') {
|
||||
return ['', mins_txt[0], mins_txt[1], from_to, hours];
|
||||
} else {
|
||||
if(mins_value % 5 == 0) {
|
||||
return ['', mins_txt[0], from_to, '', hours];
|
||||
} else if(mins_value == 1){
|
||||
return ['', mins_txt[0], 'MINUTE', from_to, hours];
|
||||
} else {
|
||||
return ['', mins_txt[0], 'MINUTES', from_to, hours];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EnglishTraditionalDateFormatter;
|
|
@ -0,0 +1,70 @@
|
|||
var DateFormatter = require("slidingtext.dtfmt.js");
|
||||
|
||||
/**
|
||||
* French date formatting
|
||||
*/
|
||||
const frenchNumberStr = [ "ZERO", "UNE", "DEUX", "TROIS", "QUATRE",
|
||||
"CINQ", "SIX", "SEPT", "HUIT", "NEUF", "DIX",
|
||||
"ONZE", "DOUZE", "TREIZE", "QUATORZE","QUINZE",
|
||||
"SEIZE", "DIX SEPT", "DIX HUIT","DIX NEUF", "VINGT",
|
||||
"VINGT ET UN", "VINGT DEUX", "VINGT TROIS",
|
||||
"VINGT QUATRE", "VINGT CINQ", "VINGT SIX",
|
||||
"VINGT SEPT", "VINGT HUIT", "VINGT NEUF"
|
||||
];
|
||||
|
||||
function frenchHoursToText(hours){
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return frenchNumberStr[hours];
|
||||
}
|
||||
|
||||
function frenchHeures(hours){
|
||||
if(hours % 12 == 1){
|
||||
return 'HEURE';
|
||||
} else {
|
||||
return 'HEURES';
|
||||
}
|
||||
}
|
||||
|
||||
class FrenchDateFormatter extends DateFormatter {
|
||||
constructor() { super(); }
|
||||
name(){return "French";}
|
||||
formatDate(date){
|
||||
var hours = frenchHoursToText(date.getHours());
|
||||
var heures = frenchHeures(date.getHours());
|
||||
var mins = date.getMinutes();
|
||||
if(mins == 0){
|
||||
if(hours == 0){
|
||||
return ["MINUIT", "",""];
|
||||
} else if(hours == 12){
|
||||
return ["MIDI", "",""];
|
||||
} else {
|
||||
return [hours, heures,""];
|
||||
}
|
||||
} else if(mins == 30){
|
||||
return [hours, heures,'ET DEMIE'];
|
||||
} else if(mins == 15){
|
||||
return [hours, heures,'ET QUERT'];
|
||||
} else if(mins == 45){
|
||||
var next_hour = date.getHours() + 1;
|
||||
hours = frenchHoursToText(next_hour);
|
||||
heures = frenchHeures(next_hour);
|
||||
return [hours, heures,"MOINS",'LET QUERT'];
|
||||
}
|
||||
if(mins > 30){
|
||||
var to_mins = 60-mins;
|
||||
var mins_txt = frenchNumberStr[to_mins];
|
||||
next_hour = date.getHours() + 1;
|
||||
hours = frenchHoursToText(next_hour);
|
||||
heures = frenchHeures(next_hour);
|
||||
return [ hours, heures , "MOINS", mins_txt ];
|
||||
} else {
|
||||
mins_txt = frenchNumberStr[mins];
|
||||
return [ hours, heures , mins_txt ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FrenchDateFormatter;
|
|
@ -0,0 +1,71 @@
|
|||
var DateFormatter = require("slidingtext.dtfmt.js");
|
||||
|
||||
/**
|
||||
* Japanese date formatting
|
||||
*/
|
||||
const japaneseHourStr = [ "ZERO", "ICHII", "NI", "SAN", "YO",
|
||||
"GO", "ROKU", "SHICHI", "HACHI", "KU", "JUU",
|
||||
'JUU ICHI', 'JUU NI'];
|
||||
const tensPrefixStr = [ "",
|
||||
"JUU",
|
||||
'NIJUU',
|
||||
'SAN JUU',
|
||||
'YON JUU',
|
||||
'GO JUU'];
|
||||
|
||||
const japaneseMinuteStr = [ ["", "PUN"],
|
||||
["IP","PUN" ],
|
||||
["NI", "FUN"],
|
||||
["SAN", "PUN"],
|
||||
["YON","FUN"],
|
||||
["GO", "HUN"],
|
||||
["RO", "PUN"],
|
||||
["NANA", "FUN"],
|
||||
["HAP", "PUN"],
|
||||
["KYU","FUN"],
|
||||
["JUP", "PUN"]
|
||||
];
|
||||
|
||||
function japaneseHoursToText(hours){
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return japaneseHourStr[hours];
|
||||
}
|
||||
|
||||
function japaneseMinsToText(mins){
|
||||
if(mins == 0){
|
||||
return ["",""];
|
||||
} else if(mins == 30)
|
||||
return ["HAN",""];
|
||||
else {
|
||||
var units = mins % 10;
|
||||
var mins_txt = japaneseMinuteStr[units];
|
||||
var tens = mins /10 | 0;
|
||||
if(tens > 0){
|
||||
var tens_txt = tensPrefixStr[tens];
|
||||
var minutes_txt;
|
||||
if(mins_txt[0] != ''){
|
||||
minutes_txt = mins_txt[0] + ' ' + mins_txt[1];
|
||||
} else {
|
||||
minutes_txt = mins_txt[1];
|
||||
}
|
||||
return [tens_txt, minutes_txt];
|
||||
} else {
|
||||
return [mins_txt[0], mins_txt[1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class JapaneseDateFormatter extends DateFormatter {
|
||||
constructor() { super(); }
|
||||
name(){return "Japanese (Romanji)";}
|
||||
formatDate(date){
|
||||
var hours_txt = japaneseHoursToText(date.getHours());
|
||||
var mins_txt = japaneseMinsToText(date.getMinutes());
|
||||
return [hours_txt,"JI", mins_txt[0], mins_txt[1] ];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JapaneseDateFormatter;
|
|
@ -0,0 +1,34 @@
|
|||
const numberStr = ["ZERO","ONE", "TWO", "THREE", "FOUR", "FIVE",
|
||||
"SIX", "SEVEN","EIGHT", "NINE", "TEN",
|
||||
"ELEVEN", "TWELVE", "THIRTEEN", "FOURTEEN",
|
||||
"FIFTEEN", "SIXTEEN", "SEVENTEEN", "EIGHTEEN",
|
||||
"NINETEEN", "TWENTY"];
|
||||
const tensStr = ["ZERO", "TEN", "TWENTY", "THIRTY", "FOURTY",
|
||||
"FIFTY"];
|
||||
|
||||
const hoursToText = (hours)=>{
|
||||
hours = hours % 12;
|
||||
if(hours == 0){
|
||||
hours = 12;
|
||||
}
|
||||
return numberStr[hours];
|
||||
}
|
||||
|
||||
const numberToText = (value)=> {
|
||||
var word1 = '';
|
||||
var word2 = '';
|
||||
if(value > 20){
|
||||
var tens = (value / 10 | 0);
|
||||
word1 = tensStr[tens];
|
||||
var remainder = value - tens * 10;
|
||||
if(remainder > 0){
|
||||
word2 = numberStr[remainder];
|
||||
}
|
||||
} else if(value > 0) {
|
||||
word1 = numberStr[value];
|
||||
}
|
||||
return [word1,word2];
|
||||
}
|
||||
|
||||
exports.hoursToText = hoursToText;
|
||||
exports.numberToText = numberToText;
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial Release
|
||||
0.02: Added Colour Themes
|
||||
|
|
|
@ -6,7 +6,24 @@ The Sweep Clock provides a clock with a perfectly smooth sweep second hand with
|
|||
|
||||
## Usage
|
||||
|
||||
Use Button 1 (the top right button) to change the numeral types (currently European and Roman)
|
||||
### Button 1
|
||||
|
||||
Use Button 1 (the top right button) to change the numeral type
|
||||
|
||||
| Default clock face | Roman Numeral Font | No Digits |
|
||||
| ---- | ---- | ---- |
|
||||
|  |  |  |
|
||||
|
||||
|
||||
|
||||
### Button 3
|
||||
Button 3 (bottom right button) is used to change the colour
|
||||
|
||||
| Red | Grey | Purple |
|
||||
| ---- | ---- | ---- |
|
||||
|  |  |  |
|
||||
|
||||
|
||||
|
||||
## Further Details
|
||||
|
||||
|
@ -14,8 +31,8 @@ For further details of design and working please visit [The Project Page](https:
|
|||
|
||||
## Requests
|
||||
|
||||
[Reach out to Adrian](https://www.github.com/awkirk71) if you have feature requests or notice bugs.
|
||||
Reach out to adrian@adriankirk.com if you have feature requests or notice bugs.
|
||||
|
||||
## Creator
|
||||
|
||||
Made by [Adrian Kirk](https://www.github.com/awkirk71).
|
||||
Made by [Adrian Kirk](mailto:adrian@adriankirk.com)
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 2.9 MiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 34 KiB |
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("lEowkE/4AdmU/CaMgCaUQCaPzgQmR+UDCaPxCaUwj525gJ2/CZ0vMSJ2SkEACaCJBgEPRKEAgJ3Q+cxE6Myn8yO6EggMjMSR3Q+ASBgCdRO4KJQCaMwCaPzCQQTPEwYTOJoYTO+cQCaKHCAAizLkEQYgQTF+YTHmMAkITEj//l8wl4UHkcwiIRBAQMD/8/CZKLBiUAiEigMCBAMzCwMyPI8zPQMzgMBn/yh/zgZSICoMxn5kC+ZmBPhY3CHAIAbA"))
|
||||
require("heatshrink").decompress(atob("lEowkA/4AGmYIHABHzmVCCaE0kUin4TPmUimQTQ+UzmcvJ6EjCaP/kYABCaEymYTl+Q7SMgITTmQTQPAK0RMgITm+QTS+ciPCcikQpPY4MjmYTO+czmcyHh4TCmcvJ54nCPCBjBJx4oECc8zJ6ATTn48RE4YTTHh4SDH4ImRFBwTGFBgTGFBgSGFBYmHUgITRmcyFBASFAoUjE5PzkQLBHJxiDAQP/GxAA=="))
|
||||
|
|
|
@ -5,10 +5,55 @@
|
|||
*/
|
||||
|
||||
const screen_center_x = g.getWidth()/2;
|
||||
const screen_center_y = g.getHeight()/2;
|
||||
const screen_center_y = 10 + g.getHeight()/2;
|
||||
|
||||
require("FontCopasetic40x58Numeric").add(Graphics);
|
||||
|
||||
const color_schemes = [
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
second_hand: [1.0,0.0,0.0],
|
||||
minute_hand: [1.0,1.0,1.0],
|
||||
hour_hand: [1.0,1.0,1.0],
|
||||
numeral:[1.0,1.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
background : [1.0,0.0,0.0],
|
||||
second_hand: [1.0,1.0,0.0],
|
||||
minute_hand: [1.0,1.0,1.0],
|
||||
hour_hand: [1.0,1.0,1.0],
|
||||
numeral:[1.0,1.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "grey",
|
||||
background : [0.5,0.5,0.5],
|
||||
second_hand: [0.0,0.0,0.0],
|
||||
minute_hand: [1.0,1.0,1.0],
|
||||
hour_hand: [1.0,1.0,1.0],
|
||||
numeral:[1.0,1.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "purple",
|
||||
background : [1.0,0.0,1.0],
|
||||
second_hand: [1.0,1.0,0.0],
|
||||
minute_hand: [1.0,1.0,1.0],
|
||||
hour_hand: [1.0,1.0,1.0],
|
||||
numeral:[1.0,1.0,1.0]
|
||||
},
|
||||
{
|
||||
name: "blue",
|
||||
background : [0.4,0.7,1.0],
|
||||
second_hand: [0.5,0.5,0.5],
|
||||
minute_hand: [1.0,1.0,1.0],
|
||||
hour_hand: [1.0,1.0,1.0],
|
||||
numeral:[1.0,1.0,1.0]
|
||||
}
|
||||
];
|
||||
|
||||
let color_scheme_index = 0;
|
||||
|
||||
class Hand {
|
||||
/**
|
||||
* Pure virtual class for all Hand classes to extend.
|
||||
|
@ -28,16 +73,12 @@ class ThinHand extends Hand {
|
|||
length,
|
||||
tolerance,
|
||||
draw_test,
|
||||
red,
|
||||
green,
|
||||
blue){
|
||||
color_theme){
|
||||
super();
|
||||
this.centerX = centerX;
|
||||
this.centerY = centerY;
|
||||
this.length = length;
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.color_theme = color_theme;
|
||||
// The last x and y coordinates (not the centre) of the last draw
|
||||
this.last_x = centerX;
|
||||
this.last_y = centerY;
|
||||
|
@ -60,13 +101,14 @@ class ThinHand extends Hand {
|
|||
// and then call the predicate to see if a redraw is needed
|
||||
this.draw_test(this.angle,this.last_draw_time) ){
|
||||
// rub out the old hand line
|
||||
g.setColor(0,0,0);
|
||||
background = color_schemes[color_scheme_index].background;
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
g.drawLine(this.centerX, this.centerY, this.last_x, this.last_y);
|
||||
// Now draw the new hand line
|
||||
g.setColor(this.red,this.green,this.blue);
|
||||
hand_color = color_schemes[color_scheme_index][this.color_theme];
|
||||
g.setColor(hand_color[0],hand_color[1],hand_color[2]);
|
||||
x2 = this.centerX + this.length*Math.sin(angle);
|
||||
y2 = this.centerY - this.length*Math.cos(angle);
|
||||
g.setColor(this.red,this.green,this.blue);
|
||||
g.drawLine(this.centerX, this.centerY, x2, y2);
|
||||
// and store the last draw details for the next call
|
||||
this.last_x = x2;
|
||||
|
@ -90,18 +132,14 @@ class ThickHand extends Hand {
|
|||
length,
|
||||
tolerance,
|
||||
draw_test,
|
||||
red,
|
||||
green,
|
||||
blue,
|
||||
color_theme,
|
||||
base_height,
|
||||
thickness){
|
||||
super();
|
||||
this.centerX = centerX;
|
||||
this.centerY = centerY;
|
||||
this.length = length;
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.color_theme = color_theme;
|
||||
this.thickness = thickness;
|
||||
this.base_height = base_height;
|
||||
// angle from the center to the top corners of the rectangle
|
||||
|
@ -132,7 +170,8 @@ class ThickHand extends Hand {
|
|||
// method to move the hand to a new angle
|
||||
moveTo(angle){
|
||||
if(Math.abs(angle - this.angle) > this.tolerance || this.draw_test(this.angle - this.delta_base,this.angle + this.delta_base ,this.last_draw_time) ){
|
||||
g.setColor(0,0,0);
|
||||
background = color_schemes[color_scheme_index].background;
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
g.fillPoly([this.last_x1,
|
||||
this.last_y1,
|
||||
this.last_x2,
|
||||
|
@ -142,7 +181,6 @@ class ThickHand extends Hand {
|
|||
this.last_x4,
|
||||
this.last_y4
|
||||
]);
|
||||
g.setColor(this.red,this.green,this.blue);
|
||||
// bottom left
|
||||
x1 = this.centerX +
|
||||
this.vertex_radius_base*Math.sin(angle - this.delta_base);
|
||||
|
@ -157,7 +195,8 @@ class ThickHand extends Hand {
|
|||
// top left
|
||||
x4 = this.centerX + this.vertex_radius_top*Math.sin(angle - this.delta_top);
|
||||
y4 = this.centerY - this.vertex_radius_top*Math.cos(angle - this.delta_top);
|
||||
g.setColor(this.red,this.green,this.blue);
|
||||
hand_color = color_schemes[color_scheme_index][this.color_theme];
|
||||
g.setColor(hand_color[0],hand_color[1],hand_color[2]);
|
||||
g.fillPoly([x1,y1,
|
||||
x2,y2,
|
||||
x3,y3,
|
||||
|
@ -184,10 +223,10 @@ let force_redraw = false;
|
|||
// The seconds hand is the main focus and is set to redraw on every cycle
|
||||
let seconds_hand = new ThinHand(screen_center_x,
|
||||
screen_center_y,
|
||||
100,
|
||||
95,
|
||||
0,
|
||||
(angle, last_draw_time) => false,
|
||||
1.0,0.0,0.0);
|
||||
"second_hand");
|
||||
// The minute hand is set to redraw at a 250th of a circle,
|
||||
// when the second hand is ontop or slighly overtaking
|
||||
// or when a force_redraw is called
|
||||
|
@ -201,7 +240,7 @@ let minutes_hand = new ThinHand(screen_center_x,
|
|||
80,
|
||||
2*Math.PI/250,
|
||||
minutes_hand_redraw,
|
||||
1.0,1.0,1.0);
|
||||
"minute_hand");
|
||||
// The hour hand is a thick hand so we have to redraw when the minute hand
|
||||
// overlaps from its behind andle coverage to its ahead angle coverage.
|
||||
let hour_hand_redraw = function(angle_from, angle_to, last_draw_time){
|
||||
|
@ -214,12 +253,13 @@ let hours_hand = new ThickHand(screen_center_x,
|
|||
40,
|
||||
2*Math.PI/600,
|
||||
hour_hand_redraw,
|
||||
1.0,1.0,1.0,
|
||||
"hour_hand",
|
||||
5,
|
||||
4);
|
||||
|
||||
function draw_clock(){
|
||||
date = new Date();
|
||||
draw_background();
|
||||
draw_hour_digit(date);
|
||||
draw_seconds(date);
|
||||
draw_mins(date);
|
||||
|
@ -242,7 +282,7 @@ function draw_mins(date,seconds_angle){
|
|||
mins_angle = 2*Math.PI*mins_frac;
|
||||
redraw = minutes_hand.moveTo(mins_angle);
|
||||
if(redraw){
|
||||
console.log("redraw mins");
|
||||
//console.log("redraw mins");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +292,7 @@ function draw_hours(date){
|
|||
hours_angle = 2*Math.PI*hours_frac;
|
||||
redraw = hours_hand.moveTo(hours_angle);
|
||||
if(redraw){
|
||||
console.log("redraw hours");
|
||||
//console.log("redraw hours");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,6 +315,18 @@ class NumeralFont {
|
|||
* method to draw text at the required coordinates
|
||||
*/
|
||||
draw(hour_txt,x,y){ return "";}
|
||||
/**
|
||||
* Called from the settings loader to identify the font
|
||||
*/
|
||||
getName(){return "";}
|
||||
}
|
||||
|
||||
class NoFont extends NumeralFont{
|
||||
constructor(){super();}
|
||||
getDimensions(hour){return [0,0];}
|
||||
hour_txt(hour){ return ""; }
|
||||
draw(hour_txt,x,y){ return "";}
|
||||
getName(){return "NoFont";}
|
||||
}
|
||||
|
||||
class CopasetFont extends NumeralFont{
|
||||
|
@ -314,6 +366,7 @@ class CopasetFont extends NumeralFont{
|
|||
g.setFontCopasetic40x58Numeric();
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Copaset";}
|
||||
}
|
||||
|
||||
|
||||
|
@ -358,6 +411,7 @@ class RomanNumeralFont extends NumeralFont{
|
|||
g.setFont("Vector",40);
|
||||
g.drawString(hour_txt,x,y);
|
||||
}
|
||||
getName(){return "Roman";}
|
||||
}
|
||||
|
||||
// The problem with the trig inverse functions on
|
||||
|
@ -419,11 +473,12 @@ class HourScriber {
|
|||
drawHour(hours){
|
||||
changed = false;
|
||||
if(this.curr_hours != hours || this.curr_numeral_font !=this.numeral_font){
|
||||
g.setColor(0,0,0);
|
||||
background = color_schemes[color_scheme_index].background;
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
this.curr_numeral_font.draw(this.curr_hour_str,
|
||||
this.curr_hour_x,
|
||||
this.curr_hour_y);
|
||||
console.log("erasing old hour");
|
||||
//console.log("erasing old hour");
|
||||
hours_frac = hours / 12;
|
||||
angle = 2*Math.PI*hours_frac;
|
||||
dimensions = this.numeral_font.getDimensions(hours);
|
||||
|
@ -477,15 +532,16 @@ class HourScriber {
|
|||
}
|
||||
if(changed ||
|
||||
this.draw_test(this.angle_from, this.angle_to, this.last_draw_time) ){
|
||||
g.setColor(1,1,1);
|
||||
numeral_color = color_schemes[color_scheme_index].numeral;
|
||||
g.setColor(numeral_color[0],numeral_color[1],numeral_color[2]);
|
||||
this.numeral_font.draw(this.curr_hour_str,this.curr_hour_x,this.curr_hour_y);
|
||||
this.last_draw_time = new Date();
|
||||
console.log("redraw digit");
|
||||
//console.log("redraw digit");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let numeral_fonts = [new CopasetFont(), new RomanNumeralFont()];
|
||||
let numeral_fonts = [new CopasetFont(), new RomanNumeralFont(), new NoFont()];
|
||||
let numeral_fonts_index = 0;
|
||||
/**
|
||||
* predicate for deciding when the digit has to be redrawn
|
||||
|
@ -540,7 +596,93 @@ function draw_hour_digit(date){
|
|||
hour_scriber.drawHour(hours);
|
||||
}
|
||||
|
||||
// Boiler plate code for setting up the clock
|
||||
function draw_background(){
|
||||
if(force_redraw){
|
||||
background = color_schemes[color_scheme_index].background;
|
||||
g.setColor(background[0],background[1],background[2]);
|
||||
g.fillPoly([0,25,
|
||||
0,240,
|
||||
240,240,
|
||||
240,25
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function next_colorscheme(){
|
||||
color_scheme_index += 1;
|
||||
color_scheme_index = color_scheme_index % color_schemes.length;
|
||||
//console.log("color_scheme_index=" + color_scheme_index);
|
||||
force_redraw = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* called from load_settings on startup to
|
||||
* set the color scheme to named value
|
||||
*/
|
||||
function set_colorscheme(colorscheme_name){
|
||||
console.log("setting color scheme:" + colorscheme_name);
|
||||
for (var i=0; i < color_schemes.length; i++) {
|
||||
if(color_schemes[i].name == colorscheme_name){
|
||||
color_scheme_index = i;
|
||||
force_redraw = true;
|
||||
console.log("match");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called from load_settings on startup
|
||||
* to set the font to named value
|
||||
*/
|
||||
function set_font(font_name){
|
||||
console.log("setting font:" + font_name);
|
||||
for (var i=0; i < numeral_fonts.length; i++) {
|
||||
if(numeral_fonts[i].getName() == font_name){
|
||||
numeral_fonts_index = i;
|
||||
force_redraw = true;
|
||||
console.log("match");
|
||||
hour_scriber.setNumeralFont(numeral_fonts[numeral_fonts_index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on startup to set the watch to the last preference settings
|
||||
*/
|
||||
function load_settings(){
|
||||
try{
|
||||
settings = require("Storage").readJSON("sweepclock.settings.json");
|
||||
if(settings != null){
|
||||
console.log("loaded:" + JSON.stringify(settings));
|
||||
if(settings.color_scheme != null){
|
||||
set_colorscheme(settings.color_scheme);
|
||||
}
|
||||
if(settings.font != null){
|
||||
set_font(settings.font);
|
||||
}
|
||||
} else {
|
||||
console.log("no settings to load");
|
||||
}
|
||||
} catch(e){
|
||||
console.log("failed to load settings:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on button press to save down the last preference settings
|
||||
*/
|
||||
function save_settings(){
|
||||
settings = {
|
||||
font : numeral_fonts[numeral_fonts_index].getName(),
|
||||
color_scheme : color_schemes[color_scheme_index].name,
|
||||
};
|
||||
console.log("saving:" + JSON.stringify(settings));
|
||||
require("Storage").writeJSON("sweepclock.settings.json",settings);
|
||||
}
|
||||
|
||||
// Boiler plate code for setting up the clock,
|
||||
// below
|
||||
let intervalRef = null;
|
||||
|
||||
|
@ -593,6 +735,7 @@ Bangle.on('faceUp',function(up){
|
|||
});
|
||||
|
||||
g.clear();
|
||||
load_settings();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
startTimers();
|
||||
|
@ -602,8 +745,17 @@ setWatch(Bangle.showLauncher, BTN2,{repeat:false,edge:"falling"});
|
|||
|
||||
function button1pressed(){
|
||||
next_font();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
function button3pressed(){
|
||||
next_colorscheme();
|
||||
save_settings();
|
||||
}
|
||||
|
||||
// Handle button 1 being pressed
|
||||
setWatch(button1pressed, BTN1,{repeat:true,edge:"falling"});
|
||||
|
||||
// Handle button 3 being pressed
|
||||
setWatch(button3pressed, BTN3,{repeat:true,edge:"falling"});
|
||||
|
||||
|
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.6 KiB |
|
@ -54,8 +54,8 @@ const APP_KEYS = [
|
|||
'sortorder', 'readme', 'custom', 'interface', 'storage', 'data', 'allow_emulator',
|
||||
'dependencies'
|
||||
];
|
||||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
|
||||
const DATA_KEYS = ['name', 'wildcard', 'storageFile'];
|
||||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite'];
|
||||
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
|
||||
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
|
||||
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
|
||||
|
||||
|
|
2
core
|
@ -1 +1 @@
|
|||
Subproject commit e65920a91f9f7178c9d8ed6551ac7d9af0a5d6e1
|
||||
Subproject commit 7d04c488496c873f392c5a068f72a6c75df40f70
|