mirror of https://github.com/espruino/BangleApps
commit
4408bf8597
13
apps.json
13
apps.json
|
@ -213,6 +213,19 @@
|
|||
{"name":"wclock.img","url":"clock-word-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "slidingtext",
|
||||
"name": "Sliding Clock",
|
||||
"icon": "slidingtext.png",
|
||||
"version":"0.01",
|
||||
"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,
|
||||
"storage": [
|
||||
{"name":"slidingtext.app.js","url":"slidingtext.js"},
|
||||
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "imgclock",
|
||||
"name": "Image background clock",
|
||||
"shortName":"Image Clock",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial Release
|
|
@ -0,0 +1,18 @@
|
|||
# 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
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Use Button 1 (the top right button) to change the language
|
||||
|
||||
|
||||
## Requests
|
||||
|
||||
[Reach out to Adrian](https://www.github.com/awkirk71) if you have feature requests or notice bugs.
|
||||
|
||||
## Creator
|
||||
|
||||
Made by [Adrian Kirk](https://www.github.com/awkirk71).
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("lEowkE/4A/AH//+czCSKbBgXziYhKEQXxgMQgERgASIkf/CgM/+UBiISBCZAUBn4DBmcybAUTHZIUEmEQkcgl5gLJ4XygQCBj50MAYUQCZ3/HgXwkcyiZqBJxQoC+UD+cPXBIIBBQcwHIMBNwpNFDocQBoMwM4IUEn4kBMIZ3DAAMgQYoZCCYg6CVhKJFEQJyIn4VBkZrFDAgIJBxCqHO5DmJT4v/mQSIKxA+DDIIADCRJEDZgRCKIgjUHHJAPCPhprFExwSDJRgPDN5oUHCJ4A/AH4AIA=="))
|
|
@ -0,0 +1,475 @@
|
|||
/**
|
||||
* Adrian Kirk 2021-02
|
||||
* Sliding text clock inspired by the Pebble
|
||||
* clock with the same name
|
||||
*/
|
||||
|
||||
|
||||
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
|
||||
*/
|
||||
constructor(x,y,txt,font_name,
|
||||
font_size,speed_x,speed_y,freq_millis, color){
|
||||
this.x = x;
|
||||
this.tgt_x = x;
|
||||
this.init_x = x;
|
||||
this.y = y;
|
||||
this.tgt_y = y;
|
||||
this.init_y = y;
|
||||
this.txt = txt;
|
||||
this.init_txt = txt;
|
||||
this.font_name = font_name;
|
||||
this.font_size = font_size;
|
||||
this.speed_x = Math.abs(speed_x);
|
||||
this.speed_y = Math.abs(speed_y);
|
||||
this.freq_millis = freq_millis;
|
||||
this.colour = color;
|
||||
this.finished_callback=null;
|
||||
this.timeoutId = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
show() {
|
||||
g.setFont(this.font_name,this.font_size);
|
||||
g.setColor(this.colour[0],this.colour[1],this.colour[2]);
|
||||
g.drawString(this.txt, this.x, this.y);
|
||||
}
|
||||
hide(){
|
||||
g.setFont(this.font_name,this.font_size);
|
||||
g.setColor(0,0,0);
|
||||
g.drawString(this.txt, this.x, this.y);
|
||||
}
|
||||
setText(txt){
|
||||
this.txt = txt;
|
||||
}
|
||||
setTextPosition(txt,x,y){
|
||||
this.hide();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.txt = txt;
|
||||
this.show();
|
||||
}
|
||||
setTextXPosition(txt,x){
|
||||
this.hide();
|
||||
this.x = x;
|
||||
this.txt = txt;
|
||||
this.show();
|
||||
}
|
||||
setTextYPosition(txt,y){
|
||||
this.hide();
|
||||
this.y = y;
|
||||
this.txt = txt;
|
||||
this.show();
|
||||
}
|
||||
moveTo(new_x,new_y){
|
||||
this.tgt_x = new_x;
|
||||
this.tgt_y = new_y;
|
||||
this._doMove();
|
||||
}
|
||||
moveToX(new_x){
|
||||
this.tgt_x = new_x;
|
||||
this._doMove();
|
||||
}
|
||||
moveToY(new_y){
|
||||
this.tgt_y = new_y;
|
||||
this._doMove();
|
||||
}
|
||||
onFinished(finished_callback){
|
||||
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.
|
||||
*/
|
||||
_doMove(){
|
||||
this.hide();
|
||||
// move closer to the target in the x direction
|
||||
diff_x = this.tgt_x - this.x;
|
||||
finished_x = false;
|
||||
if(Math.abs(diff_x) <= this.speed_x){
|
||||
this.x = this.tgt_x;
|
||||
finished_x = true;
|
||||
} else {
|
||||
if(diff_x > 0){
|
||||
this.x += this.speed_x;
|
||||
} else {
|
||||
this.x -= this.speed_x;
|
||||
}
|
||||
}
|
||||
// move closer to the target in the y direction
|
||||
diff_y = this.tgt_y - this.y;
|
||||
finished_y = false;
|
||||
if(Math.abs(diff_y) <= this.speed_y){
|
||||
this.y = this.tgt_y;
|
||||
finished_y = true;
|
||||
} else {
|
||||
if(diff_y > 0){
|
||||
this.y += this.speed_y;
|
||||
} else {
|
||||
this.y -= this.speed_y;
|
||||
}
|
||||
}
|
||||
this.show();
|
||||
this.timeoutId = null;
|
||||
finished = finished_x & finished_y;
|
||||
if(!finished){
|
||||
this.timeoutId = setTimeout(this._doMove.bind(this), this.freq_millis);
|
||||
} else if(this.finished_callback != null){
|
||||
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])
|
||||
];
|
||||
|
||||
// a list of the formatters to cycle through
|
||||
let date_formatters = [
|
||||
new EnglishDateFormatter(),
|
||||
new FrenchDateFormatter(),
|
||||
new JapaneseDateFormatter()
|
||||
];
|
||||
|
||||
// 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){
|
||||
date_formatter_idx = 0;
|
||||
}
|
||||
console.log("changing to formatter " + date_formatter_idx);
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function reset_clock(){
|
||||
//console.log("reset_clock");
|
||||
var i;
|
||||
for (i = 0; i < row_displays.length; i++) {
|
||||
row_displays[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
function draw_clock(){
|
||||
//console.log("draw_clock");
|
||||
date = new Date();
|
||||
rows = date_formatter.formatDate(date);
|
||||
var i;
|
||||
for (i = 0; i < rows.length; i++) {
|
||||
display = row_displays[i];
|
||||
txt = rows[i];
|
||||
display_row(display,txt);
|
||||
}
|
||||
// If the dateformatter has not returned enough
|
||||
// rows then treat the reamining rows as empty
|
||||
for (j = i; j < row_displays.length; j++) {
|
||||
display = row_displays[j];
|
||||
display_row(display,'');
|
||||
}
|
||||
//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);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
display.setTextXPosition(txt,20);
|
||||
}
|
||||
}
|
||||
|
||||
// The interval reference for updating the clock
|
||||
let intervalRef = null;
|
||||
|
||||
function clearTimers(){
|
||||
if(intervalRef) {
|
||||
clearInterval(intervalRef);
|
||||
intervalRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startTimers(){
|
||||
let date = new Date();
|
||||
let secs = date.getSeconds();
|
||||
let nextMinuteStart = 60 - secs;
|
||||
//console.log("scheduling clock draw in " + nextMinuteStart + " seconds");
|
||||
setTimeout(scheduleDrawClock,nextMinuteStart * 1000);
|
||||
draw_clock();
|
||||
}
|
||||
|
||||
function scheduleDrawClock(){
|
||||
//console.log("scheduleDrawClock");
|
||||
if(intervalRef) clearTimers();
|
||||
intervalRef = setInterval(draw_clock, 60*1000);
|
||||
draw_clock();
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
console.log("lcdPower: on");
|
||||
Bangle.drawWidgets();
|
||||
reset_clock();
|
||||
startTimers();
|
||||
} else {
|
||||
console.log("lcdPower: off");
|
||||
reset_clock();
|
||||
clearTimers();
|
||||
}
|
||||
});
|
||||
Bangle.on('faceUp',function(up){
|
||||
//console.log("faceUp: " + up + " LCD: " + Bangle.isLCDOn());
|
||||
if (up && !Bangle.isLCDOn()) {
|
||||
//console.log("faceUp and LCD off");
|
||||
clearTimers();
|
||||
Bangle.setLCDPower(true);
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
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"});
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Loading…
Reference in New Issue