New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature request] Hideable widget bar #1466
Comments
This is an excellent idea, I used a watchface without a widget zone was disappointed that they weren't still running. Great idea! |
I wonder if this needs to be a library, or an installable boot code app that "just works" for all clocks? |
It's an idea - it's just getting clocks to work with it. Many of them will just assume there's always a widget bar and leave 24px spare all the time. Also, I guess we want it to only work for clocks, not all apps? |
I think it is also useful to extend it to apps. the downside is that the "Swipe-down" command cannot be used (unless overwritten) and I don't know if it could interfere with scrolling through the menus .... in any case, I think it is good to have a "standard" that manages the visibility of the widgetbar (personally I don't use it and the apps I use / have written don't display the widgetbar in order to have more screen space) |
Sure, but just having that empty space for a "cleaner" look would be a nice start, and then we can just convert clocks/apps to properly use
Maybe we could split it into two separate options?
That should be possible by just not installing any widgets: if code is doing something it should go into a For non-clock apps, I feel trading the widgetbar for screen space is fine, but for clocks it would be nice if they looked at |
Whoops, I was thinking of the Bangle.js 1, totally forgot that I've got some working code for Bangle.js 2, but it has no way to tell apps to repaint the top area after we show and hide the widget bar :-( // widhide.boot.js
Bangle.loadWidgets = (o => () => {
o();
if (Bangle.CLOCK) Bangle.appRect = {
// apps can use the complete screen now
x: 0, y: 0,
w: g.getWidth(), h: g.getHeight(),
x2: g.getWidth()-1, y2: g.getHeight()-1,
};
})(Bangle.loadWidgets);
Bangle.drawWidgets = (o => () => {
if (!Bangle.CLOCK) return o();
// draw widgets to buffer instead of screen
if (!Bangle.widgetBuffer) Bangle.widgetBuffer = Graphics.createArrayBuffer(
g.getWidth(),
24,
4,
{msb: true}
);
let _g = g;
g = Bangle.widgetBuffer;
o();
g = _g;
// draw the buffer at the offset position (0 = completely offscreen, 24 = fully visible)
g.drawImage(Bangle.widgetBuffer, 0, (Bangle.widgetBuffer.o|0)-24);
})(Bangle.drawWidgets);
Bangle.on("swipe", (_, d) => {
if (!Bangle.CLOCK || !d) return; // not a clock, or horizontal swipe
if (!Bangle.widgetBuffer) Bangle.drawWidgets();
let w = Bangle.widgetBuffer; // we also save 'o'ffset, 'm'ovement and a 't'imeout in the buffer object
if (w.m*d>0) return; // already moving/moved in this direction
w.m = 2*d; // movement: same direction as swipe
if (w.t) clearTimeout(w.t);
delete w.t;
function anim() {
w.o = Math.min(24, w.m+(w.o|0)); // calculate new offset (0 = 24 pixels above screen = hidden)
g.setClipRect(0, 0, g.getWidth(), 24) // make sure we can paint to the widget area
.drawImage(w, 0, w.o-24);
if (w.m>0) { // moving down: sliding into view
if (w.o<24) {
w.t=setTimeout(anim, 10);
} else w.t=setTimeout(() => {
// auto-hide after 10 seconds
if (w.o>=24 && w.m>=0) {
w.m = -2;
anim();
}
}, 10e3);
} else { // moving up: sliding out of view
g.clearRect(0, w.o, g.getWidth()-1, w.o-w.m);
if (w.o>0) w.t=setTimeout(anim, 10);
}
g.setClipRect(0, w.o, g.getWidth()-1, g.getHeight()-1); // prevent app from drawing over widgets
}
anim();
}); |
Yes, this is the problem... You can use Something like:
would work (ish) but it's pretty inefficient with memory, and any updates to the clock/app in the meantime would end up undithered. Also looks like your code doesn't override the widget draw, so widgets like battery that auto-update would still break things. I think this is one reason to have a library rather than boot code - the app can give the widget hider a callback for redraw, and could also avoid redraws itself when widgets were showing, so things would go a lot more smoothly. Otherwise I think to do this nicely there'd need to be some kind of firmware mod |
Yeah, I only thought about that later. Overriding If we get the widget side of things to work, I figured for app support there are two options:
I'm thinking we might do both: add a library, but if apps don't use it just leave the widgetbar blank? |
Could do, but personally it seems like if you have the space you should use it. I'm thinking maybe I should just add another 24px (or more) to the top of the offscreen buffer and allow setting the Bangle.js screen offset like we do on Bangle.js 1? In the long run that's probably easier. |
That's probably the way to go, I'm pretty much convinced now this needs firmware support. (To be honest I'm quite happy with always-visible widgets, this just looked like a interesting feature to code.) |
I just took a look at adding fw support, but there's something a bit odd going on and I can't figure it out at the moment. This is actually the code that I'd done before for this:
So you can hide and show widgets and the background is still kept around. BUT if the app tries to draw while the widgets are shown it'll overwrite them. |
Just a note on firmware support - while we could do a simple scroll I'm still in two minds about whether there should be to ability to do something a bit more flexible (like allowing notifications over the middle of the screen) as folks have requested that for alarms/notifications/etc - but I think that could be a minefield |
I like the sound of adding the extra 24px to the offscreen buffer, it gives us other ways to show the widgets then too, like shake to show widgets. |
I've had a go at converting the code from @gfwilliams and @rigrig above to use the new It also needs clocks that correctly use Bangle.appRect to set their view size. // widhide.boot.js
Bangle.widgetsShown = false;
Bangle.loadWidgets = (o => () => {
// console.log('loadWidgets');
o();
if (Bangle.CLOCK) Bangle.appRect = {
// apps can use the complete screen now
x: 0, y: 0,
w: g.getWidth(), h: g.getHeight(),
x2: g.getWidth()-1, y2: g.getHeight()-1,
};
})(Bangle.loadWidgets);
Bangle.drawWidgets = (o => () => {
if (!Bangle.CLOCK) return o();
// console.log('drawWidgets');
// draw widgets to buffer instead of screen
if (!Bangle.widgetBuffer) Bangle.widgetBuffer = Graphics.createArrayBuffer(
g.getWidth(), 24, 8, {msb: true}
);
let _g = g;
g = Bangle.widgetBuffer;
o();
g = _g;
if (Bangle.widgetsShown) Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, 0);
})(Bangle.drawWidgets);
Bangle.on("swipe", (_, d) => {
if (!Bangle.CLOCK || !d) return; // not a clock, or horizontal swipe
if (!Bangle.widgetBuffer) Bangle.drawWidgets();
if (Bangle.widgetBuffer.m*d>0) return; // already moving/moved in this direction
Bangle.widgetBuffer.m = d; // movement: same direction as swipe
if (d>0) {
// console.log('Show widgets');
Bangle.widgetsShown = true;
} else {
// console.log('Hide widgets');
Bangle.widgetsShown = false;
Bangle.setLCDOverlay();
}
Object.keys(WIDGETS).forEach(wi=>{
var w = WIDGETS[wi];
if (Bangle.widgetsShown) {
// console.log('Enable widget draw', wi);
if (w.olddraw) {
w.draw = w.olddraw;
delete w.olddraw;
}
} else {
// console.log('Disable widget draw', wi);
w.olddraw = w.draw;
w.draw = ()=>{};
}
});
if (Bangle.widgetsShown) Bangle.drawWidgets();
}); |
:o yes, don't do that! Instead of overwriting
|
'Why not' is always a good question! I didn't know how is the answer, but now you've shown me, here's a new version that seems to work better (I didn't use // widhide.boot.js
Bangle.widgetsShown = false;
Bangle.loadWidgets = (o => () => {
// console.log('loadWidgets');
o();
if (Bangle.CLOCK) {
Bangle.appRect = {
// apps can use the complete screen now
x: 0, y: 0,
w: g.getWidth(), h: g.getHeight(),
x2: g.getWidth()-1, y2: g.getHeight()-1,
};
Object.keys(WIDGETS).forEach(wi=>{
var w = WIDGETS[wi];
w.olddraw = w.draw;
w.draw = function(){
let _g = g;
g = Bangle.widgetBuffer;
this.olddraw(this);
g = _g;
if (Bangle.widgetsShown) Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, 0);
};
});
}
})(Bangle.loadWidgets);
Bangle.drawWidgets = (o => () => {
if (!Bangle.CLOCK) return o();
// console.log('drawWidgets');
Bangle.widgetBuffer = Graphics.createArrayBuffer(g.getWidth(), 24, 8, {msb: true});
o();
if (Bangle.widgetsShown) Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, 0);
})(Bangle.drawWidgets);
Bangle.on("swipe", (_, d) => {
if (!Bangle.CLOCK || !d) return; // not a clock, or horizontal swipe
if (!Bangle.widgetBuffer) Bangle.drawWidgets();
if (d>0 & !Bangle.widgetsShown) {
// console.log('Show widgets');
Bangle.widgetsShown = true;
Bangle.drawWidgets();
} else if (d<0 & Bangle.widgetsShown){
// console.log('Hide widgets');
Bangle.widgetsShown = false;
Bangle.setLCDOverlay();
}
}); |
Just because you end up redrawing all widgets all the time, when maybe you only wanted to change one part of one widget (eg to make it flash on and off).
Ahh - yes, the original drawWidgets just does a clearRect: https://github.com/espruino/Espruino/blob/master/libs/js/banglejs/Bangle_drawWidgets_Q3.js Personally I'd allocate the Graphics at the start and just do Only thing it's missing is slowly moving setLCDOverlay downwards so it does a nice animation ;) |
I hadn't spotted that. Turns out it still does, so I'll need to stop that.
Yeah, I've had a go, it's harder than it sounds! I had a look at it here: http://forum.espruino.com/conversations/379983 |
Ahh, that's a shame. That could well be a firmware issue then :) |
As a workaround, you could add another buffer... |
I've tweaked the code, now here: https://github.com/sir-indy/BangleApps/tree/widget-hide/apps/widhide
I think you're right, and I think it's here: https://github.com/espruino/Espruino/blob/5a99fbdafedf5b8a551ac53f2370c76f63be9523/libs/graphics/lcd_memlcd.c#L308-L309 |
That sounds like it - thanks! I've just pushed some changes (and tested) and with the latest cutting edge builds you should now be able to display the overlay with negative offsets |
Ah ha! With firmware 2v15.29, drawing off screen works! Thank you Gordon! Updated my repo above with animation code. It works! And looks good! |
The animation is pretty cool, I think it would look better if it weren't linear, so here's a modification of this which uses a quadratic. // widhide.boot.js
Bangle.widgetBuffer = Graphics.createArrayBuffer(g.getWidth(), 24, 8, {msb: true});
Bangle.widgetBuffer.shown = false;
Bangle.widgetBuffer.offset = -24;
Bangle.loadWidgets = (o => () => {
// console.log('loadWidgets');
o();
if (Bangle.CLOCK) {
Bangle.appRect = {
// apps can use the complete screen now
x: 0, y: 0,
w: g.getWidth(), h: g.getHeight(),
x2: g.getWidth()-1, y2: g.getHeight()-1,
};
Object.keys(WIDGETS).forEach(wi=>{
var w = WIDGETS[wi];
w.olddraw = w.draw;
w.draw = function(){
if (Bangle.widgetBuffer.shown) {
let _g = g;
g = Bangle.widgetBuffer;
this.olddraw(this);
g = _g;
Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, Bangle.widgetBuffer.offset);
}
};
});
}
})(Bangle.loadWidgets);
Bangle.drawWidgets = (o => () => {
if (!Bangle.CLOCK) return o();
// console.log('drawWidgets');
if (Bangle.widgetBuffer.shown) {
Bangle.widgetBuffer.clear(1);
let _g = g;
g = Bangle.widgetBuffer;
o();
g = _g;
Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, Bangle.widgetBuffer.offset);
}
})(Bangle.drawWidgets);
Bangle.on("swipe", (_, d) => {
if (!Bangle.CLOCK || !d) return; // not a clock, or horizontal swipe
if (!Bangle.widgetBuffer) Bangle.drawWidgets();
if (d>0 & !Bangle.widgetBuffer.shown) { // show widgets
if (typeof WIDGETS != 'object') Bangle.loadWidgets();
Bangle.widgetBuffer.shown = true;
Bangle.widgetBuffer.offset = -24;
Bangle.drawWidgets();
} else if (d<0 & Bangle.widgetBuffer.shown){ // hide widgets
Bangle.widgetBuffer.shown = false;
Bangle.widgetBuffer.offset = 0;
} else {
return;
}
function anim() {
Bangle.widgetBuffer.offset += d;
Bangle.widgetBuffer.rOffset = Math.pow(Bangle.widgetBuffer.offset / Math.sqrt(24) + Math.sqrt(24), 2) - 24;
Bangle.setLCDOverlay(Bangle.widgetBuffer, 0, Bangle.widgetBuffer.rOffset);
if (Bangle.widgetBuffer.offset >= 0 || Bangle.widgetBuffer.offset <= -24) {
clearTimeout(animTimeout);
return;
}
animTimeout = setTimeout(anim, 1);
}
var animTimeout = setTimeout(anim, 1);
}); This runs a formula on the offset, as if |
I've just been experimenting with making use of I initially thought it was my code but it looks like the MacWatch2 app does the same thing: when I first try to swipe down the widgets I get a blank bar (no widgets displayed at all), but now (not sure if it's just a timing thing, or because the screen locked/unlocked, or something else) I do see widgets when swiping but both the top and bottom widgets appear at the top with the top widgets drawn on top of the bottom widgets! I'm going to give up on the swipe feature for now but it seems like it would be really useful when it's working. |
Interesting - thanks! I was pretty sure this feature works ok - maybe you could file an issue... But please ensure you list what's installed... It works here with the default widgets so maybe it's an issue with the bottom widget bar? It could even be because of a broken widget.... |
Ah, thanks, good point, it was with my widget so that could definitely be broken. I'll investigate more... |
I tried with a different bottom widget (the bottom digital clock widget) and didn't see the top and bottom widgets shown on top of each other, which is good, so I guess I'm doing something wrong in the medical alert widget The bottom clock widget didn't show up at all though- is that expected? I like the hideable widget bar idea a lot but I'm also keen to keep the medical alert widget visible on the clock screen. I was wondering if it would be possible to use a system setting to control widget hiding (top/bottom widgets or both on clock/app screens or both) rather than leaving it to apps to code explicitly. Is that technically possible? I've opened #2474 for the odd blank bar issue - it's just a bit unexpected rather than a big problem. |
Ok, thanks - lets move discussion to #2474 then... |
Closing as completed - see #2474 / |
Just a thought here, but I see a lot of clock apps now having an option to hide the widgets.
Maybe we could have a library (in modules) that adjusts the widget draw call such that it renders widgets to an offscreen buffer. Swipe-down could then make the widget bar slide out of the top of the screen, and after a few secs it could pop back up.
Having it as a library would mean we could avoid a bunch of code duplication too.
The text was updated successfully, but these errors were encountered: