diff --git a/apps.json b/apps.json index 36e2cf94e..65a50a6c8 100644 --- a/apps.json +++ b/apps.json @@ -95,7 +95,7 @@ "shortName":"Notifications", "icon": "notify.png", "version":"0.11", - "description": "A handler for displaying notifications that displays them in a bar at the top of the screen", + "description": "Provides the default `notify` module used by applications to display notifications in a bar at the top of the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen", "tags": "widget", "type": "notify", "readme": "README.md", @@ -108,7 +108,7 @@ "shortName":"Notifications", "icon": "notify.png", "version":"0.11", - "description": "A handler for displaying notifications that displays them fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notifications library.", + "description": "Provides a replacement for the `Notifications (default)` `notify` module. This version is used by applications to display notifications fullscreen. This may not fully restore the screen after on some apps. See `Notifications (default)` for more information about the notify module.", "tags": "widget,b2", "type": "notify", "storage": [ @@ -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", @@ -3507,7 +3521,7 @@ "name": "Pastel Clock", "shortName": "Pastel", "icon": "pastel.png", - "version":"0.01", + "version":"0.02", "description": "A Configurable clock with custom fonts and background", "tags": "clock,b2", "type":"clock", diff --git a/apps/antonclk/app-icon.js b/apps/antonclk/app-icon.js index 00892e9c3..fad03d50f 100644 --- a/apps/antonclk/app-icon.js +++ b/apps/antonclk/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwge27dtAX4C+/dt+wFB/wCECIu3/dvBYNv34RC7/tCIu//99EYN9C4IpB74jG3379ovDFIIRBEYxHD/47D2wjHCIX+AQJHBCIIXBNZt/+5QBEZIgBAQX///9EZRWBBARHDEwhlC/9/EAJoBDQIOBNwyPCEYYCDJQ4CJSQ4CB0O2lojL2lwBIXFiwsK0f/KgUbuwRJo6cBPAO34cUmJHH7U/97tBgEGBIODEY/RXoOw7cAgHbtlxoojGx7hCjAjD20ANA1378MEIIAB4d0u5HGNAPYCAYAB2n2SQSPDjv3CIsF2lxEYto//+CoOGCIUt0O3EYtHvqMBvlw4UAgJQBqIjERgQDBsO+7FAhaMH64DB+4qB+3AgARG9uhIgQJD4dghd+7dLBQZoBaISwC4cArf27dpCIf/23f9uHCIQABhoNClsl20ttuwgYKBEAIAChOmCIOH/vx9ttwB2BDgMBAoIRBmnbpkbtk2ltsgAMCJQOwAgMBk+eq3AhiSBsE2GAX//4WCAAOBAoVbtt8mCJBgHHfYMHdgoRBott+zmDsEAn/tEwkC7UAVoYACgPbv4REAASTBEYY0BPIPwCJAjEu3Dvq8BCAnbsEwGgm2jbAC8EAjFvEAQREjuwDQXbvvx7cd2K/EgEb9oRCAoLOBjEAgk/A=")) +require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA==")) 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 diff --git a/apps/notify/README.md b/apps/notify/README.md index f186aaab2..7b2473015 100644 --- a/apps/notify/README.md +++ b/apps/notify/README.md @@ -1,9 +1,8 @@ # Notifications (default) -A handler for displaying notifications that displays them in a bar at the top of the screen +The default version of the `notify` module for displaying notifications in a bar at the top of the screen -This is not an app, but instead it is a library that can be used by -other applications or widgets to display messages. +This module is installed by default by client applications such as Gadgetbridge. **Note:** There are other implementations of this library available such as `notifyfs` (Fullscreen Notifications). These can be used in the exact diff --git a/apps/pastel/ChangeLog b/apps/pastel/ChangeLog index 7b83706bf..4b99fd7c1 100644 --- a/apps/pastel/ChangeLog +++ b/apps/pastel/ChangeLog @@ -1 +1,2 @@ 0.01: First release +0.02: Display 12 hour clock as 12:xx not 00:xx when just into PM diff --git a/apps/pastel/pastel.app.js b/apps/pastel/pastel.app.js index 0c1d56118..9f9efd171 100644 --- a/apps/pastel/pastel.app.js +++ b/apps/pastel/pastel.app.js @@ -73,7 +73,8 @@ function draw() { // fix hh for 12hr clock var h2 = "0" + parseInt(hh) % 12 || 12; - hh = h2.substr(h2.length -2); + if (parseInt(hh) > 12) + hh = h2.substr(h2.length -2); var w = g.getWidth(); var h = g.getHeight(); diff --git a/apps/waveclk/app-icon.js b/apps/waveclk/app-icon.js index fad03d50f..00892e9c3 100644 --- a/apps/waveclk/app-icon.js +++ b/apps/waveclk/app-icon.js @@ -1 +1 @@ -require("heatshrink").decompress(atob("mEwgX/AH4A/AAf+14BBAoPq/Wq1QFB+EP+kLAoNA+CdB3//yEP6ALB/sABYf8gEMgALB6ALJqoLB+tVgH//kQhkQhUABYImCIwPwh3whcA94UCgJHD+AXB/4LCcQQLCKYQLB+EDBZQFCBY/Qh4LJ4ALLl4LFioLCgE/KZMAv4XFBYimBC4/+BYw7DRwQLIV4ILWTQQLDOIUA/ALDAC+t1uv/263/6/Pq/YLC3vv//vM4Oq33rBYP6/u//2/C4Pr1YXC12vBwO+BYO69XmJDQA/ACIA==")) +require("heatshrink").decompress(atob("mEwge27dtAX4C+/dt+wFB/wCECIu3/dvBYNv34RC7/tCIu//99EYN9C4IpB74jG3379ovDFIIRBEYxHD/47D2wjHCIX+AQJHBCIIXBNZt/+5QBEZIgBAQX///9EZRWBBARHDEwhlC/9/EAJoBDQIOBNwyPCEYYCDJQ4CJSQ4CB0O2lojL2lwBIXFiwsK0f/KgUbuwRJo6cBPAO34cUmJHH7U/97tBgEGBIODEY/RXoOw7cAgHbtlxoojGx7hCjAjD20ANA1378MEIIAB4d0u5HGNAPYCAYAB2n2SQSPDjv3CIsF2lxEYto//+CoOGCIUt0O3EYtHvqMBvlw4UAgJQBqIjERgQDBsO+7FAhaMH64DB+4qB+3AgARG9uhIgQJD4dghd+7dLBQZoBaISwC4cArf27dpCIf/23f9uHCIQABhoNClsl20ttuwgYKBEAIAChOmCIOH/vx9ttwB2BDgMBAoIRBmnbpkbtk2ltsgAMCJQOwAgMBk+eq3AhiSBsE2GAX//4WCAAOBAoVbtt8mCJBgHHfYMHdgoRBott+zmDsEAn/tEwkC7UAVoYACgPbv4REAASTBEYY0BPIPwCJAjEu3Dvq8BCAnbsEwGgm2jbAC8EAjFvEAQREjuwDQXbvvx7cd2K/EgEb9oRCAoLOBjEAgk/A=")) diff --git a/modules/Layout.js b/modules/Layout.js index 2caa11c97..9f3a805be 100644 --- a/modules/Layout.js +++ b/modules/Layout.js @@ -154,16 +154,20 @@ function touchHandler(l,e) { if (l.c) l.c.forEach(n => touchHandler(n,e)); } -function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { - if ((l.bgCol != null && l.bgCol != bgCol) || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { +function prepareLazyRender(l, rectsToClear, drawList, rects, parentBg) { + var bgCol = l.bgCol == null ? parentBg : g.toColor(l.bgCol); + if (bgCol != parentBg || l.type == "txt" || l.type == "btn" || l.type == "img" || l.type == "custom") { // Hash the layoutObject without including its children - let c = l.c; + var c = l.c; delete l.c; - let hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order + var hash = "H"+E.CRC32(E.toJS(l)); // String keys maintain insertion order if (c) l.c = c; if (!delete rectsToClear[hash]) { - rects[hash] = {bg: bgCol, r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1]}; + rects[hash] = { + bg: parentBg == null ? g.theme.bg : parentBg, + r: [l.x,l.y,l.x+l.w-1,l.y+l.h-1] + }; if (drawList) { drawList.push(l); drawList = null; // Prevent children from being redundantly added to the drawList @@ -171,7 +175,7 @@ function prepareLazyRender(l, rectsToClear, drawList, rects, bgCol) { } } - if (l.c) for (let ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, l.bgCol == null ? bgCol : l.bgCol); + if (l.c) for (var ch of l.c) prepareLazyRender(ch, rectsToClear, drawList, rects, bgCol); } Layout.prototype.render = function (l) { @@ -220,7 +224,7 @@ Layout.prototype.render = function (l) { if (!this.rects) this.rects = {}; var rectsToClear = this.rects.clone(); var drawList = []; - prepareLazyRender(l, rectsToClear, drawList, this.rects, g.getBgColor()); + prepareLazyRender(l, rectsToClear, drawList, this.rects, null); for (var h in rectsToClear) delete this.rects[h]; var clearList = Object.keys(rectsToClear).map(k=>rectsToClear[k]).reverse(); // Rects are cleared in reverse order so that the original bg color is restored for (var r of clearList) g.setBgColor(r.bg).clearRect.apply(g, r.r);