1
0
Fork 0

Merge pull request #673 from awkirk71/master

Sliding Text Clock
master
Gordon Williams 2021-02-18 09:02:50 +00:00 committed by GitHub
commit 4408bf8597
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 508 additions and 0 deletions

View File

@ -213,6 +213,19 @@
{"name":"wclock.img","url":"clock-word-icon.js","evaluate":true} {"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", { "id": "imgclock",
"name": "Image background clock", "name": "Image background clock",
"shortName":"Image Clock", "shortName":"Image Clock",

View File

@ -0,0 +1 @@
0.01: Initial Release

View File

@ -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
![](app.png)
## 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).

BIN
apps/slidingtext/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("lEowkE/4A/AH//+czCSKbBgXziYhKEQXxgMQgERgASIkf/CgM/+UBiISBCZAUBn4DBmcybAUTHZIUEmEQkcgl5gLJ4XygQCBj50MAYUQCZ3/HgXwkcyiZqBJxQoC+UD+cPXBIIBBQcwHIMBNwpNFDocQBoMwM4IUEn4kBMIZ3DAAMgQYoZCCYg6CVhKJFEQJyIn4VBkZrFDAgIJBxCqHO5DmJT4v/mQSIKxA+DDIIADCRJEDZgRCKIgjUHHJAPCPhprFExwSDJRgPDN5oUHCJ4A/AH4AIA=="))

View File

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