From be8b402dc79c631db2f430819c141ed991d85d8a Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 6 Jan 2023 13:20:51 +0000 Subject: [PATCH] Notify: Add Bangle.js 2 support with Bangle.setLCDOverlay --- apps/notify/ChangeLog | 1 + apps/notify/metadata.json | 11 +- apps/notify/{notify.js => notify_bjs1.js} | 2 +- apps/notify/notify_bjs2.js | 171 ++++++++++++++++++++++ apps/notifyfs/metadata.json | 1 + 5 files changed, 181 insertions(+), 5 deletions(-) rename apps/notify/{notify.js => notify_bjs1.js} (98%) create mode 100644 apps/notify/notify_bjs2.js diff --git a/apps/notify/ChangeLog b/apps/notify/ChangeLog index 8803b82b6..d7b754ff9 100644 --- a/apps/notify/ChangeLog +++ b/apps/notify/ChangeLog @@ -8,3 +8,4 @@ 0.09: Add onHide callback 0.10: Improvements to help notifications work with themes 0.11: Fix regression that caused no notifications and corrupted background +0.12: Add Bangle.js 2 support with Bangle.setLCDOverlay diff --git a/apps/notify/metadata.json b/apps/notify/metadata.json index e92d5e0e4..1cc8f52c1 100644 --- a/apps/notify/metadata.json +++ b/apps/notify/metadata.json @@ -2,14 +2,17 @@ "id": "notify", "name": "Notifications (default)", "shortName": "Notifications", - "version": "0.11", - "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", + "version": "0.12", + "description": "Provides the default `notify` module used by applications to display notifications on 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", "icon": "notify.png", "type": "notify", "tags": "widget", - "supports": ["BANGLEJS"], + "supports": ["BANGLEJS","BANGLEJS2"], + "provides_modules" : ["notify"], + "default" : true, "readme": "README.md", "storage": [ - {"name":"notify","url":"notify.js"} + {"name":"notify","url":"notify_bjs1.js", "supports": ["BANGLEJS"]}, + {"name":"notify","url":"notify_bjs2.js", "supports": ["BANGLEJS2"]} ] } diff --git a/apps/notify/notify.js b/apps/notify/notify_bjs1.js similarity index 98% rename from apps/notify/notify.js rename to apps/notify/notify_bjs1.js index 332c301d5..fb56e4bbc 100644 --- a/apps/notify/notify.js +++ b/apps/notify/notify_bjs1.js @@ -96,7 +96,7 @@ exports.show = function(options) { b = y+h-1, r = x+w-1; // bottom,right // clear area g.reset().setClipRect(x,y, r,b); - if (options.bgColor!==undefined) g.setColor(options.bgColor); + if (options.bgColor!==undefined) g.setBgColor(options.bgColor); g.clearRect(x,y, r,b); // bottom border g.setColor("#333").fillRect(0,b-1, r,b); diff --git a/apps/notify/notify_bjs2.js b/apps/notify/notify_bjs2.js new file mode 100644 index 000000000..c202e8c55 --- /dev/null +++ b/apps/notify/notify_bjs2.js @@ -0,0 +1,171 @@ +let pos = 0, size = 0; +let id = null; +let hideCallback = undefined; +let overlayImage = undefined; + +/** + * Fit text into area, trying to insert newlines between words + * Appends "..." if more text was present but didn't fit + * + * @param {string} text + * @param {number} rows Maximum number of rows + * @param {number} width Maximum line length, in characters + */ +function fitWords(text,rows,width) { + // We never need more than rows*width characters anyway, split by any whitespace + const words = text.trim().substr(0,rows*width).split(/\s+/); + let row=1,len=0,limit=width; + let result = ""; + for (let word of words) { + // len==0 means first word of row, after that we also add a space + if ((len?len+1:0)+word.length > limit) { + if (row>=rows) { + result += "..."; + break; + } + result += "\n"; + len=0; + row++; + if (row===rows) limit -= 3; // last row needs space for "..." + } + result += (len?" ":"") + word; + len += (len?1:0) + word.length; + } + return result; +} + +/** + options = { + on : bool // turn screen on, default true + size : int // height of notification, default 80 (max) + title : string // optional title + id // optional notification ID, used with hide() + src : string // optional source name + body : string // optional body text + icon : string // optional icon (image string) + render : function(y) // function callback to render + bgColor : int/string // optional background color (default black) + titleBgColor : int/string // optional background color for title (default black) + onHide : function() // callback when notification is hidden + } +*/ +/* + The screen is 240x240px, but has a 240x320 buffer, used like this: + 0,0: top-left ... 239,0: top-right + [Normal screen contents: lines 0-239] + 239,0: bottom-left ... 239,239: bottom-right + [Usually off-screen: lines 240-319] + 319,0: last line in buffer ... 319,239: last pixel in buffer + + When moving the display area, the buffer wraps around + + So we draw notifications at the end of the buffer, + then shift the display down to show them without touching regular content. + Apps don't know about this, so can just keep updating the usual display area. + + For example, a size 40 notification: + - Draws in bottom 40 buffer lines (279-319) + - Shifts display down by 40px + Display now shows buffer lines 279-319,0-199 + Apps/widgets keep drawing to buffer line 0-239 like nothing happened + */ +exports.show = function(options) { + options = options || {}; + if (options.on===undefined) options.on = true; + id = ("id" in options)?options.id:null; + let w = g.getWidth(); + let text = []; + size = options.size; + if (options.body) { + const bh = (size || 80) - 20, + maxRows=Math.floor((bh-4)/8), // font=6x8 + maxChars=Math.floor(w/6)-2; + text=fitWords(options.body, maxRows, maxChars); + // set size based on newlines + if (!size) size = 28 + (text.match(/\n/g).length+1)*8; + } else size = 20; + if (size>80) size = 80; + + let gg = Graphics.createArrayBuffer(w,size,16); + gg.setBgColor(g.theme.bg); + overlayImage = { width : gg.getWidth(), height : gg.getHeight(), bpp : 16, buffer:gg.buffer }; + + // drawing area + let x = 0, + y = 0, + h = size, + b = y+h-1, r = x+w-1; // bottom,right + // clear area + if (options.bgColor!==undefined) gg.setBgColor(options.bgColor); + gg.clearRect(x,y, r,b); + // title bar + if (options.title || options.src) { + gg.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20); + const title = options.title||options.src; + gg.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2); + gg.drawString(title.trim().substring(0, 13), x+25,y+3); + if (options.title && options.src) { + gg.setFont("6x8", 1).setFontAlign(1, 1, 0); + gg.drawString(options.src.substring(0, 10), gg.getWidth()-23,y+18); + } + } + y += 20; h -= 20; + if (options.icon) { + let i = options.icon, iw; + gg.drawImage(i, x,y+4); + if ("string"==typeof i) iw = i.charCodeAt(0); + else iw = i[0]; + x += iw;w -= iw; + } + // body text + if (options.body) { + gg.setColor(g.theme.fg).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4); + } + + if (options.render) { + options.render({x:x, y:y, w:w, h:h}); + } + + if (options.on && !(require('Storage').readJSON('setting.json',1)||{}).quiet) { + Bangle.setLCDPower(1); // light up + } + + + function anim() { + pos -= 2; + if (pos < -size) { + pos = -size; + } + Bangle.setLCDOverlay(overlayImage,0,-(pos+size)); + if (pos > -size) setTimeout(anim, 15); + } + anim(); + Bangle.on("touch", exports.hide); + if (options.onHide) + hideCallback = options.onHide; +}; + +/** + options = { + id // optional, only hide if current notification has this ID + } +*/ +exports.hide = function(options) { + options = options||{}; + if ("id" in options && options.id!==id) return; + if (hideCallback) hideCallback({id:id}); + hideCallback = undefined; + id = null; + Bangle.removeListener("touch", exports.hide); + function anim() { + pos += 4; + if (pos > 0) { + pos = 0; + overlayImage = undefined; + Bangle.setLCDOverlay(); + } else + Bangle.setLCDOverlay(overlayImage,0,-(pos+size)); + if (pos < 0) setTimeout(anim, 10); + } + anim(); +}; diff --git a/apps/notifyfs/metadata.json b/apps/notifyfs/metadata.json index dea8cb022..003b62429 100644 --- a/apps/notifyfs/metadata.json +++ b/apps/notifyfs/metadata.json @@ -8,6 +8,7 @@ "type": "notify", "tags": "widget", "supports": ["BANGLEJS","BANGLEJS2"], + "provides_modules" : ["notify"], "storage": [ {"name":"notify","url":"notify.js"} ]