1
0
Fork 0

Merge branch 'master' into quiet-mode

master
Gordon Williams 2021-04-13 08:34:28 +01:00 committed by GitHub
commit 7f0b323f3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 2137 additions and 1004 deletions

View File

@ -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)

View File

@ -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
View File

@ -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}
]
}
]

1
apps/fclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First published version of app

1
apps/fclock/app-icon.js Normal file

File diff suppressed because one or more lines are too long

BIN
apps/fclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

206
apps/fclock/fclock.app.js Normal file
View File

@ -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" });
}

View File

@ -1 +1,2 @@
0.01: Initial version
0.02: Increase text brightness, improve controls, (try to) reduce memory usage

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

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

@ -0,0 +1,25 @@
# Hour Strike
![icon](app-icon.png)
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).

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkGswAHogAEBxAAHsgXFowXPCwowQFwwwQCIUjn/zmQwPFwUj/4ACDAQwMBwNDCgPwh4DBmgwMFwU/C4vzGBgMBoRECC4f/kgwLIwgXFJBgLBl4XH+QXNLwQXFMAQXPmEDC6K8DiEBAoYXSgA1DI6MxgETL6gYBgIGBC5ynDCYMQAwKnOa4YABmTXQoQXEAAUkC5dkMAx2EowXJJBBGNAAMUBwMjMAgHBoIXLiIPBDAM/+YWCokRC5gwCAAtBC5owDAAgJBC5dBiAwGoMBigXLokQgIXFA4QXMoEAAANNqAECggXR7oXXAYQXSgvUC6sEC60N6oXW6AXUinu8IXTgPuAAMQC6UEC4SsDC58OC4XgC9RHXO66nCoLXVAAoXmABQX1A"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

48
apps/hourstrike/app.js Normal file
View File

@ -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();

39
apps/hourstrike/boot.js Normal file
View File

@ -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();
})();

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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}}

View File

@ -1 +1,2 @@
0.01: Initial Release
0.02: Color Themes, Smoother scrolling

View File

@ -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.
![](app.png)
## Usage
### Button 1
Use Button 1 (the top right button) to change the language
| English | English (Traditional) | French | Japanese (Romanji) |
| ---- | ---- | ---- | ---- |
| ![](./format-01.jpg) | ![](format-02.jpg) | ![](format-03.jpg) |![](format-04.jpg) |
### Button 3
Button 3 (bottom right button) is used to change the colour
| Black | Red | Gray | Purple |
| ---- | ---- | ---- | ---- |
| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) | ![](color-04.jpg) |
## 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -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;

View File

@ -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"});

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1 +1,2 @@
0.01: Initial Release
0.02: Added Colour Themes

View File

@ -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 |
| ---- | ---- | ---- |
| ![](./numeral-01.jpg) | ![](numeral-02.jpg) | ![](numeral-03.jpg) |
### Button 3
Button 3 (bottom right button) is used to change the colour
| Red | Grey | Purple |
| ---- | ---- | ---- |
| ![](./color-01.jpg) | ![](color-02.jpg) | ![](color-03.jpg) |
## 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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -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=="))

View File

@ -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"});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -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