diff --git a/apps.json b/apps.json index 7a0fde795..4ff6ba9c2 100644 --- a/apps.json +++ b/apps.json @@ -319,6 +319,20 @@ {"name":"sweepclock.img","url":"sweepclock-icon.js","evaluate":true} ] }, + { "id": "matrixclock", + "name": "Matrix Clock", + "icon": "matrixclock.png", + "version":"0.01", + "description": "inspired by The Matrix, a clock of the same style", + "tags": "clock", + "type":"clock", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"matrixclock.app.js","url":"matrixclock.js"}, + {"name":"matrixclock.img","url":"matrixclock-icon.js","evaluate":true} + ] + }, { "id": "imgclock", "name": "Image background clock", "shortName":"Image Clock", diff --git a/apps/matrixclock/ChangeLog b/apps/matrixclock/ChangeLog new file mode 100644 index 000000000..d53df991b --- /dev/null +++ b/apps/matrixclock/ChangeLog @@ -0,0 +1 @@ +0.01: Initial Release diff --git a/apps/matrixclock/README.md b/apps/matrixclock/README.md new file mode 100644 index 000000000..010524b60 --- /dev/null +++ b/apps/matrixclock/README.md @@ -0,0 +1,11 @@ +# Matrix Clock + +![](app.png) + +## Requests + +Please reach out to adrian@adriankirk.com if you have feature requests or notice bugs. + +## Creator + +Made by [Adrian Kirk](mailto:adrian@adriankirk.com) diff --git a/apps/matrixclock/app.png b/apps/matrixclock/app.png new file mode 100644 index 000000000..bc135c3ee Binary files /dev/null and b/apps/matrixclock/app.png differ diff --git a/apps/matrixclock/matrixclock-icon.js b/apps/matrixclock/matrixclock-icon.js new file mode 100644 index 000000000..5d5e9cd67 --- /dev/null +++ b/apps/matrixclock/matrixclock-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("lEowkBBpNgEKV3Bhd2CZEGFZAgGAwUHuEGuAMIs4JFCYIBBBItmFRA7BCY4+Gs5MDCbZ3CHwQTNJgwTPB4h3LHYQTQG44Tfd4zsCCZJ3GBwQTMCwYTEgwTSbBVnCYZPDdhZSICbo7EMZbbGRZivDT54AJHIITHdYoAGBgxjCHYYnEO5QyGJpgMDbZgTLHpITJT50GOQKfTCaoMRRdITRPQQAJBgZyRC4oAFA")) diff --git a/apps/matrixclock/matrixclock.js b/apps/matrixclock/matrixclock.js new file mode 100644 index 000000000..0bf33fd68 --- /dev/null +++ b/apps/matrixclock/matrixclock.js @@ -0,0 +1,259 @@ +/** + * Adrian Kirk 2021-10 + * + * Matrix Clock + * + * A simple clock inspired by the movie. + * Text shards move down the screen as a background to the + * time and date + **/ +const Locale = require('locale'); + +const SHARD_COLOR =[0,1.0,0]; +const SHARD_FONT_SIZE = 12; +const SHARD_Y_START = 30; +/** +* The text shard object is responsible for creating the +* shards of text that move down the screen. As the +* shard moves down the screen the latest character added +* is brightest with characters being coloured darker and darker +* going back to the eldest +*/ +class TextShard { + + constructor(x,y,length){ + // The x and y coords of the first character of the shard + this.x = x; + this.y = y; + // The visible length of the shard. We don't make the + // whole chain visible just to save on cpu time + this.length = length; + // the list of characters making up this shard + this.txt = []; + } + /** + * The add method call adds another random character to + * the chain + */ + add(){ + this.txt.push(randomChar()); + } + /** + * The show method displays the latest shard image to the + * screen with the following rules: + * - latest addition is brightest, oldest is darker + * - display up to defined length of characters only + * of the shard to save cpu + */ + show(){ + g.setFontAlign(-1,-1,0); + for(var i=0; i this.length - 2){ + color_strength = 0; + } + g.setColor(color_strength*SHARD_COLOR[0], + color_strength*SHARD_COLOR[1], + color_strength*SHARD_COLOR[2]); + g.setFont("Vector",SHARD_FONT_SIZE); + g.drawString(this.txt[idx], this.x, this.y + idx*SHARD_FONT_SIZE); + } + } + /** + * Method tests to see if any part of the shard chain is still + * visible on the screen + */ + isVisible(){ + return (this.y + (this.txt.length - this.length - 2)*SHARD_FONT_SIZE < g.getHeight()); + } + /** + * resets the shard back to the top of the screen + */ + reset(){ + this.y = SHARD_Y_START; + this.txt = []; + } +} + +/** +* random character chooser to be called by the shard when adding characters +*/ +const CHAR_CODE_START = 33; +const CHAR_CODE_LAST = 126; +const CHAR_CODE_LENGTH = CHAR_CODE_LAST - CHAR_CODE_START; +function randomChar(){ + return String.fromCharCode(Math.floor(Math.random() * CHAR_CODE_LENGTH)+ CHAR_CODE_START); +} + +// Now set up the shards +// we are going to have a limited no of shards (to save cpu) +// but randomize the x value and length every reset to make it look as if there +// are more +var shards = []; +const NO_SHARDS = 3; +const channel_width = g.getWidth()/NO_SHARDS; + +function shard_x(i){ + return i*channel_width + Math.random() * channel_width; +} + +function shard_length(){ + return Math.floor(Math.random()*5) + 3; +} + +for(var i=0; i RESET_PROBABILITY){ + shards[i].reset(); + shards[i].length = shard_length(); + shards[i].x = shard_x(i); + if(shards[i].x > DATE_X_COORD - 20){ + shards[i].y = 50; + } + } + // If its still visble then add to the shard and show to screen + if(visible){ + shards[i].add(); + } + // we still have to show the shard even though it may be off the screen to keep the speed constant + shards[i].show(); + } + var now = new Date(); + // draw time. Have to draw time on every loop + g.setFont("Vector",45); + g.setFontAlign(-1,-1,0); + if(last_draw_time == null || now.getMinutes() != last_draw_time.getMinutes()){ + g.setColor(0,0,0); + g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + timeStr = format_time(now); + } + g.setColor(SHARD_COLOR[0], + SHARD_COLOR[1], + SHARD_COLOR[2]); + g.drawString(timeStr, TIME_X_COORD, TIME_Y_COORD); + // + // draw date when it changes + g.setFont("Vector",15); + g.setFontAlign(-1,-1,0); + if(last_draw_time == null || now.getDate() != last_draw_time.getDate()){ + g.setColor(0,0,0); + g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + dateStr = format_date(now); + g.setColor(SHARD_COLOR[0], + SHARD_COLOR[1], + SHARD_COLOR[2]); + g.drawString(dateStr, DATE_X_COORD, DATE_Y_COORD); + } + last_draw_time = now; +} + +function format_date(now){ + return Locale.dow(now,1) + " " + format00(now.getDate()); +} + + +function format_time(now){ + var time = new Date(now.getTime()); + var hours = time.getHours() % 12; + if(hours < 1){ + hours = 12; + } + var am_pm; + if(time.getHours() < 12){ + am_pm = "AM"; + } else { + am_pm = "PM"; + } + return format00(hours) + ":" + format00(time.getMinutes()) + " "+ am_pm; +} + +function format00(num){ + var value = (num | 0); + if(value > 99 || value < 0) + throw "must be between in range 0-99"; + if(value < 10) + return "0" + value.toString(); + else + return value.toString(); +} + +// The interval reference for updating the clock +let intervalRef = null; + +function clearTimers(){ + if(intervalRef != null) { + clearInterval(intervalRef); + intervalRef = null; + } +} + +function shouldRedraw(){ + return Bangle.isLCDOn(); +} + +function startTimers(){ + clearTimers(); + if (Bangle.isLCDOn()) { + intervalRef = setInterval(() => { + if (!shouldRedraw()) { + //console.log("draw clock callback - skipped redraw"); + } else { + draw_clock(); + } + }, 100 + ); + draw_clock(); + } else { + console.log("scheduleDrawClock - skipped not visible"); + } +} + + +Bangle.on('lcdPower', (on) => { + if (on) { + console.log("lcdPower: on"); + startTimers(); + } else { + console.log("lcdPower: off"); + 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(); +Bangle.setUI("clock"); + + diff --git a/apps/matrixclock/matrixclock.png b/apps/matrixclock/matrixclock.png new file mode 100644 index 000000000..634253674 Binary files /dev/null and b/apps/matrixclock/matrixclock.png differ