Notify: Add Bangle.js 2 support with Bangle.setLCDOverlay

pull/2467/head
Gordon Williams 2023-01-06 13:20:51 +00:00
parent 9c783a1f91
commit be8b402dc7
5 changed files with 181 additions and 5 deletions

View File

@ -8,3 +8,4 @@
0.09: Add onHide callback 0.09: Add onHide callback
0.10: Improvements to help notifications work with themes 0.10: Improvements to help notifications work with themes
0.11: Fix regression that caused no notifications and corrupted background 0.11: Fix regression that caused no notifications and corrupted background
0.12: Add Bangle.js 2 support with Bangle.setLCDOverlay

View File

@ -2,14 +2,17 @@
"id": "notify", "id": "notify",
"name": "Notifications (default)", "name": "Notifications (default)",
"shortName": "Notifications", "shortName": "Notifications",
"version": "0.11", "version": "0.12",
"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", "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", "icon": "notify.png",
"type": "notify", "type": "notify",
"tags": "widget", "tags": "widget",
"supports": ["BANGLEJS"], "supports": ["BANGLEJS","BANGLEJS2"],
"provides_modules" : ["notify"],
"default" : true,
"readme": "README.md", "readme": "README.md",
"storage": [ "storage": [
{"name":"notify","url":"notify.js"} {"name":"notify","url":"notify_bjs1.js", "supports": ["BANGLEJS"]},
{"name":"notify","url":"notify_bjs2.js", "supports": ["BANGLEJS2"]}
] ]
} }

View File

@ -96,7 +96,7 @@ exports.show = function(options) {
b = y+h-1, r = x+w-1; // bottom,right b = y+h-1, r = x+w-1; // bottom,right
// clear area // clear area
g.reset().setClipRect(x,y, r,b); 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); g.clearRect(x,y, r,b);
// bottom border // bottom border
g.setColor("#333").fillRect(0,b-1, r,b); g.setColor("#333").fillRect(0,b-1, r,b);

171
apps/notify/notify_bjs2.js Normal file
View File

@ -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();
};

View File

@ -8,6 +8,7 @@
"type": "notify", "type": "notify",
"tags": "widget", "tags": "widget",
"supports": ["BANGLEJS","BANGLEJS2"], "supports": ["BANGLEJS","BANGLEJS2"],
"provides_modules" : ["notify"],
"storage": [ "storage": [
{"name":"notify","url":"notify.js"} {"name":"notify","url":"notify.js"}
] ]