-
• #2
Ah, I like it, just like the html flexible layout boxes?It's friendly to frontend developers!
-
• #3
I'm not using bangle.js yet (mostly because I'm stuck in a SEA country with unreliable shipping, though plan on getting one shortly -- maybe version 2? :P), but I feel this would be a real break through issue for Espruino. I often write code with display elements on say an OLED screen (128x64), but then after want to redeploy this code on a 160x128 or 240x240 or 320x240 screen -- you get the idea -- and it's a real pain.
So I'm suggesting it deserves some thought as it's a real opportunity. I like Flexbox and something similar might not be a bad first step!
I'm not sure what it would look like, but lines for example could be expressed in terms of percentage of screen (start at 10% and go to 80%), or text could be positioned at 1/3 of the screen or who knows. I guess this would break a little if aspect ratios changed. Some ST7735's are 128x128, vs 160x128. But even just that would make resolution independent display elements easier.
Some ideas...
-hfc -
• #4
I like that very much. What about position and color? I know about to make it lightweight as possible is a goal, but it should not be too limiting. First thing I notice is that position, length, color follow some rules which are for me a bit too limiting.
More later. Based on experience gained in https://www.espruino.com/ui (UI Framework for microcontrollers) - no visuals yet (my bad) - based on Modular and extensible UI framework and ui elements. with a lot of visuals - work that started 5+ years ago with last updates less than 2 years ago and was developed in an emulator in the browser... - I would say that layout should not go as crazy as that but presented specs are a Byte(! - not just a bit) frugal.
What I also can say that layouts need to be hw oriented... otherwise complexity ends up comparable with a (graphical) Web browser... and I would consider those be the most complex applications ever exist(ed).
PS: @MaBe worked recently thru some layout design and implementation exercise and may spare a cent or two on this... (sorry @MaBe, squeezing even more out of the already time-emtpy 24h day-lemon - :\ ).
-
• #5
Yes, I think this is a good opportunity, not just for Bangle.js but for Espruino in general.
And yes, as much as it'd be nice to follow a web browser API, I think we're going to have to stay pretty low-level to make this perform well. I think we should aim to handle 80% of what people generally want to do, and then if there's anything more fancy it can be handled in code with a
type:"custom"
handler.Good point about color - there could definitely be
color:
fields (or maybe just aconfig
field that contains a function that sets everything up?).And I think the ability to re-render just one part of the UI is pretty crucial as well, especially for Bangle.js where it's unbuffered. Being able to do the update in a more or less flicker-free way would be great.
-
• #6
This would be very nice, fiddling with UI positions is definitely one of my least favorite activities.
I think we also want
align:
, and a way to specify which part of the screen to use. a) so you can use it with or without widgets, and b) to test if a layout fits on a smaller screen.
(But default to just using the whole screen, or even check ifBangle.loadwidgets()
has been called?)It would also be nice if you could make one part get all "remaining" space, so you could do something like this:
var layout = ["vertical", {type:"text", font:"6x8", txt:"Temperature"}, // centered? at top of screen ["horizontal", // right below "Temperature" {type:"text", font:"30%", txt:"20.0"}, // against left screen edge {type:"text", font:"6x8", txt:"'C"} // against right screen edge ], {type:"custom", size:"fill", render:drawThermometer},//draw in middle of screen {type:"button", txt:"more...", touch:myTouchHandler}, // centered? at bottom ]; function drawThermometer(x,y,w,h) { // x,y,w,h: available space after other parts are placed }
Some more questions:
- What happens if you re-render just one "6x8" text, but it now has a different length?
- How to handle buttons on a Bangle.js 1.0?
- What happens if you re-render just one "6x8" text, but it now has a different length?
-
• #7
think we also want align
Me too,....
-
• #8
align
Yes, absolutely.
a way to specify which part of the screen to use
Yes - initially I was planning on just making it check if widgets were loaded and size accordingly, but hopefully it'd be flexible enough that you could specify a width and a height to fit to.
one part get all "remaining" space
Thanks - yes, that'd be a great idea. Just got to figure out how to handle that!
What happens if you re-render just one "6x8" text, but it now has a different length?
I was thinking
Layout.update
runs over everything, and if the size/position of anything gets changed, it is marked as 'dirty'. Then you can maybe choose to just render the bits that have changed?How to handle buttons on a Bangle.js 1.0?
I think we just use BTN1/BTN3 to move focus between them, and BTN2 to select - a bit like we do for showPrompt currently? It'd be one of the benefits of doing stuff this way as well.
-
• #9
What do you all think about something like this: https://www.espruino.com/ide/?emulator&upload&gist=8cad25a87c633e2c614d0eefa52fcd2e
The layout for an app might look something like this:
var layout = { type: "v", content: [ {type:"txt", font:"6x8", label:"Temperature", col:"#f00", halign:0, pad:4}, {type:"h", content: [ {type:"img", src: ()=>atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="), valign:0, pad:4}, {type:"txt", font:"30%", label:"20.0"}, {type:"txt", font:"6x8", label:"'C", valign:-1} ]}, {type:"txt", font:"4x6", label:"left", halign:-1}, {type:"txt", font:"4x6", label:"center"/*, halign:0*/}, {type:"txt", font:"4x6", label:"right", halign:1}, {type:"custom", _w:64, _h:64, render:myRenderer, fill:true}, {type:"btn", label:"More...", click:() => print("Hello")}, {type:"custom", _w:64, _h:64, render:myRenderer, fill:true} ] };
So you can have horizontal/vertical alignment, the ability to fill available space, padding and color. By putting in elements with
fill:true
you can pad out certain areas too,Nothing sorted for button handling yet, but so far the code seems like it'd be reasonably fast and compact.
Maybe the thing to do is to look at existing apps and see how easily they can be converted to use it.
-
• #10
A clock might end up looking like this:
var layout = { type: "v", content: [ {type:"txt", font:"30%", label:"12:00"}, {type:"txt", font:"6x8", label:"Date"}, ] }; function draw() { var d = new Date(); layout.content[0].label = require("locale").time(d,1); layout.content[1].label = require("locale").date(d); Layout.clear(layout); Layout.update(layout); Layout.render(layout); } g.clear(); draw(); setInterval(draw, 60000);
-
• #11
Right now the width and the height of items can't really be defined explicitly (other than for
custom
). Potentially we could add the ability to define a minimum size, and maybe even allow things like "50%" to make something take up 50% of the available screen height. -
• #12
Very cool !
-
• #13
Yeah that's great! I can already see myself building some menus with something like this ;) And it is more what I had in mind too -- I didn't mean to suggest we should implement a CSS style layout engine. This really hits the "elegant simplicity" zone.
-
• #14
I like the idea of relative heights/widths (DroidScript does this for Android: 1.0 = full width/height, so use 0.5 for half etc) as it should adjust to other screen sizes automatically. Also if fonts were (somehow) density-agnostic (like device independent pixels) so if I load font_small it's 4x6 on lower resolutions and 6x8 on higher.
I also like the layout manager approach: I'm guessing "v" is vertical here, so top -down is simple, then nesting an "h" inside the first "v" element gives you a grid, and you could have anchors like "ne", "n", "nw" to align to corners, top, bottom (in an abbreviated format)
also it would be nice if the Layout automagically knew to redraw when it's content changed so you wouldn't have to .clear() .update() ... just .render(). That last one may call g.flip() as needed too?
now mapping 12-bit colors down to 3-bit... ouch.
May be a good idea for someone with LOTS of time on their hands to go through the bangle apps and try to lay them out using these rules, see how complex it gets, see where the majority tend to pattern.
-
• #15
I'm in favour as long as the results look nice at the end of the day.
Nothing worse than being forced to build to a butt ugly ui layout.Also would be nice to keep it a simple as possible with sensible defaults but with the ability to augment for more complex things.
If BTN handling is going to be part of this can we have long press built into the firmware so we can assign a call back for a short press and long press to different functions. I do this through timers at the moment.
Really looking forward to the new watch going on Sale. I'd like to pre-order if I could.
-
• #16
I'm guessing "v" is vertical here, so top -down is simple, then nesting an "h" inside the first "v" element gives you a grid
That's right, yes.
and you could have anchors like "ne", "n", "nw" to align to corners, top, bottom
Not sure I understand this one? We have halign/valign - but maybe
ne/n/nw
is tidier?it would be nice if the Layout automagically knew to redraw
Yes, I was wondering about that. Thinking about even the clock example I feel there might be too many edge cases for us to be able to implement it easily.
now mapping 12-bit colors down to 3-bit... ouch.
If you use the
#rgb
color format it's not too painful - I've been going through some of the existing apps/widgets getting them working already. I've found that because you know it's going to round the values you can generally get something that ok on both. Having said that there's nowg.theme
(since the new 3 bit screen looks best with black on white, and Bangle is nicer with white on black), and that can mess things up!go through the bangle apps and try to lay them out using these rules
Absolutely, yes! As a first start I think i'll add this library to BangleApps and then see about modifying one or two apps.
Nothing worse than being forced to build to a butt ugly ui layout.
I'm open to ideas here to make it produce nice results. At the end of the day nobody is forced to use it - the plan is to make it easy to build common app layouts, but also to allow folks to render all or part of the screen themselves if they want to.
can we have long press built into the firmware
Maybe that's part of a bigger problem. I guess in a lot of apps you will want the 3 'hard' buttons to do specific tasks rather than having on-screen buttons.
So really you want:
- On Bangle.js 1.0: 3x labels down the right of the screen in line with the buttons - direct access from the Bangle's buttons
- On Bangle.js 2.0: 3x buttons down the right of the screen - touches on-screen to make them work
And that wants to be built into the layout library somehow. Adding a long-press callback to that should be pretty easy though
- On Bangle.js 1.0: 3x labels down the right of the screen in line with the buttons - direct access from the Bangle's buttons
-
• #17
it would be nice if the Layout automagically knew to redraw
Yes, I was wondering about that. Thinking about even the clock example I feel there might be too many edge cases for us to be able to implement it easily.
Not to be contrarian, but when using a using a g.flip() approach I always have a "screen redraw" function in my code after doing some updates. The Espruino app code is generally so small that hand crafted screen updates seem like the logical thing to do -- so I'm fine with calling a "layout.update()" function.
-hfc
-
• #18
Just pushed some updates to the Gist, with the ability to have buttons (which work on original Bangle as hard buttons or the new one as 'soft' buttons)
-
• #19
Wondering if the layout render will be smart enough to avoid flicker with the direct screen writes. I often write bits of code to reduce flicker by checking that the text I am about to g.drawString() is not just the same as the previous one I wrote. I usually keep a prevText variable and check before doing the clearRect() and drawString(). Now I know you can use an ArrayBuffer but I found they had a performance impact and that they caused memory fragmentation and then OOM errors when switching between watch faces in a multiclock format.
In terms of BTNs I think I might want to redesign my App to work with a single button. Probably use more swipes etc for some of the cycling through features I have done using BTN1 etc.
Hoping that we will still be free to code the old way if needed and that the layout manager is not the only option. The layout manager will need to be aware of parts of the screen where an ArrayBuffer might be used or a dynamic image (eg a rotating arrow for a compass) constructed from g.fillPoly() etc.
-
• #20
Wondering if the layout render will be smart enough to avoid flicker
It's able to clear just the area that changed and then render the text. It's what most people do and mostly avoids flicker. I guess usage of offscreen buffers could be an option but the idea of this is to be lightweight - not to grow into an all-encompassing UI library.
layout manager is not the only option
No, you can do whatever you want.
eg a rotating arrow for a compass
That's why we have the 'custom' element - so you can render just the bit that you need, but where it is gets handled for you.
-
• #21
Hi I was making a simple clock with the layout library. Here is my code:
require("Font7x11Numeric7Seg").add(Graphics); var Layout = require("Layout"); var layout = new Layout( {type:"h", c: [ {type:"txt", font:"7x11Numeric7Seg:2", label:"", id:"time"}, ]}, {btns:[ {label:"", cb: l=>print("Two")}, ], lazy:true}); layout.update(); layout.time.x = 170; layout.time.y = 10; function draw() { var currentDate = new Date(); var currentHour = currentDate.getHours(); var currentMinute = currentDate.getMinutes(); layout.time.label = currentHour+":"+currentMinute; layout.render(); } g.clear(); draw(); setInterval(draw, 5000);
The problem is I am using lazy rendering and the clock does not seem to automatically redraw. It just keeps drawing on top of itself. I am using the Bangle.js 1 emulator. I put a screenshot below of the emulator after I left it on for a while.
1 Attachment
-
• #22
I suspect the issue is that since
layout.time.label
is initially empty, and you don't calllayout.update
after setting it, the layout code thinks it still has zero width and thus tries to clear an area with zero width.Try either setting the label to a placeholder value of
"00:00"
when you construct the layout or calllayout.update()
before each render. -
• #23
@NebbishHacker Thanks! It works now.
Hi! As some of you know, I'm looking into launching a Bangle.js 2.0 soon which should resolve a lot of the niggles that folks have had with the original Bangle. One part of that is moving to a sunlight readable full touch LCD which has a different resolution (176x176) to the current Bangle's 240x240.
This presents an issue for pretty much all existing apps as they're all laid out for the 240x240 screen, and while they could be converted ideally we want the same app to be able to run on both devices.
However this feels like a an opportunity. In many cases all we want is a simple layout - sometimes just one line above another - so a library to handle this would solve a lot of issues and would clean up a lot of apps. There's also the possibility of easily handling touch input and maybe even having some layout tools.
Does anyone have any thoughts about this? Can you think of any nice solutions?
I'm imagining a simple thermometer app might look something like this:
But I'm very open to suggestions. We just need to keep things as simple and lightweight as possible.