notify: Update notify API for #527

Also some cleanup:
 - fall back to "src" if title is missing
 - move message-splitting into separate function
master
Richard de Boer 2020-07-19 21:57:49 +02:00
parent 8df352cca9
commit 7bb98d38e0
4 changed files with 97 additions and 52 deletions

View File

@ -80,7 +80,7 @@
"name": "Notifications (default)", "name": "Notifications (default)",
"shortName":"Notifications", "shortName":"Notifications",
"icon": "notify.png", "icon": "notify.png",
"version":"0.02", "version":"0.03",
"description": "A handler for displaying notifications that displays them in a bar at the top of the screen", "description": "A handler for displaying notifications that displays them in a bar at the top of the screen",
"tags": "widget", "tags": "widget",
"type": "notify", "type": "notify",

View File

@ -1,2 +1,3 @@
0.01: New Library! 0.01: New Library!
0.02: Add notification ID option 0.02: Add notification ID option
0.03: Pass `area{x,y,w,h}` to render callback instead of just `y`

View File

@ -16,7 +16,7 @@ options = {
src : string, // optional source name src : string, // optional source name
body : string, // optional body text body : string, // optional body text
icon : string, // optional icon (image string) icon : string, // optional icon (image string)
render function(y) {} // function callback to render render function(area) {} // function callback to render in area{x,y,w,h}
}; };
// eg... show notification // eg... show notification
require("notify").show({title:"Test", body:"Hello"}); require("notify").show({title:"Test", body:"Hello"});

View File

@ -1,5 +1,36 @@
var pos = 0; let pos = 0;
var id = null; let id = null;
/**
* 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 = { options = {
@ -13,77 +44,90 @@ var id = null;
render function(y) // function callback to render render function(y) // function callback to render
} }
*/ */
/*
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) { exports.show = function(options) {
options = options || {}; options = options || {};
if (options.on===undefined) options.on = true; if (options.on===undefined) options.on = true;
id = ("id" in options)?options.id:null; id = ("id" in options)?options.id:null;
var h = options.size||80; let size = options.size || 80;
var oldMode = Bangle.getLCDMode(); if (size>80) {size = 80}
const oldMode = Bangle.getLCDMode();
// TODO: throw exception if double-buffered? // TODO: throw exception if double-buffered?
// TODO: throw exception if size>80?
Bangle.setLCDMode("direct"); Bangle.setLCDMode("direct");
var y = 320-h; // drawing area
var x = 4; let x = 0,
g.setClipRect(0, y, 239, 319); y = 320-size,
w = 240,
h = size,
b = y+h-1, r = x+w-1; // bottom,right
g.setClipRect(x,y, r,b);
// clear area // clear area
g.setColor(0).fillRect(0, y+1, 239, 317); g.setColor(0).fillRect(x,y, r,b);
// border // bottom border
g.setColor(0x39C7).fillRect(0, 318, 239, 319); g.setColor(0x39C7).fillRect(0,b-1, r,b);
// top bar b -= 2;h -= 2;
var top = 0; // title bar
if (options.title) { if (options.title || options.src) {
g.setColor(0x39C7).fillRect(0, y, 239, y+20); g.setColor(0x39C7).fillRect(x,y, r,y+20);
const title = options.title||options.src;
g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2); g.setColor(-1).setFontAlign(-1, -1, 0).setFont("6x8", 2);
g.drawString(options.title.trim().substring(0, 13), 25, y+3); g.drawString(title.trim().substring(0, 13), x+25,y+3);
y+=20; if (options.title && options.src) {
g.setFont("6x8", 1);
g.drawString(options.src.substring(0, 10), x+215,y+5);
} }
if (options.src) { y += 20;h -= 20;
g.setColor(-1).setFontAlign(1, -1, 0).setFont("6x8", 1);
g.drawString(options.src.substring(0, 10), 215, 322-h);
} }
if (options.icon) { if (options.icon) {
let i = options.icon; let i = options.icon, iw;
g.drawImage(i, x,y+4); g.drawImage(i, x,y+4);
if ("string"==typeof i) x += i.charCodeAt(0); if ("string"==typeof i) {iw = i.charCodeAt(0)}
else x += i[0]; else {iw = i[0]}
x += iw;w -= iw;
} }
// body text // body text
if (options.body) { if (options.body) {
var body = options.body; const maxRows=Math.floor((h-4)/8), // font=6x8
const maxChars = Math.floor((300-x)/8); maxChars=Math.floor(w/6)-2,
var limit = maxChars; text=fitWords(options.body, maxRows, maxChars);
let row = 1; g.setColor(-1).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(text, x+6,y+4);
let words = body.trim().replace("\n", " ").split(" ");
body = "";
for (var i = 0; i < words.length; i++) {
if (body.length + words[i].length + 1 > limit) {
if (row>=5) {
body += "...";
break;
}
body += "\n " + words[i];
row++;
limit += maxChars;
if (row==5) limit -= 4;
} else {
body += " " + words[i];
}
}
g.setColor(-1).setFont("6x8", 1).setFontAlign(-1, -1, 0).drawString(body, x-4, y+4);
} }
if (options.render) options.render(320 - h); if (options.render) {
options.render({x:x, y:y, w:w, h:h});
}
if (options.on) Bangle.setLCDPower(1); // light up if (options.on) Bangle.setLCDPower(1); // light up
Bangle.setLCDMode(oldMode); // clears cliprect Bangle.setLCDMode(oldMode); // clears cliprect
function anim() { function anim() {
pos -= 2; pos -= 2;
if (pos < -h) { if (pos < -size) {
pos = -h; pos = -size;
} }
Bangle.setLCDOffset(pos); Bangle.setLCDOffset(pos);
if (pos > -h) setTimeout(anim, 15); if (pos > -size) setTimeout(anim, 15);
} }
anim(); anim();
Bangle.on("touch", exports.hide); Bangle.on("touch", exports.hide);