69 points BoxOfRain 3 days ago 13 comments
I've recently switched to a black-and-white e-ink smartphone with the motivation of withdrawing from the attention economy somewhat and it's a genuinely cool piece of hardware. While the majority of my needs are met by this device there are a few things I'd like to have which don't work terribly well with the e-ink screen. I'm planning to implement a couple of projects to fill these gaps, at the moment I'm planning a Lemmy frontend and an OpenRouter frontend specifically for e-ink. Both are to be browser-based rather than native, to maximise compatibility and because I'm much more familiar with the web than Android development.
I would like to study the principles of sound e-ink UI design before approaching these projects to avoid creating unusable slop, in particular I am not entirely sure how to approach treating the refreshes as a first-class aspect of the design when I can't control them from the browser, and how to apply comprehensible UI conventions when a greyscale, high-contrast display is the target.
Some specific problems I have out of the gate are:
* Streaming LLM output to the screen is basically the worst-case scenario for e-ink, I need to buffer it and paint it in chunks without this becoming horrible to use.
* Ghosting is a serious problem, browsing HN on the device is a particularly obvious example. Ideally I want to avoid scrolling as far as possible and rely on pagination instead, which I feel has the potential to become annoying if not done well.
* Given I must rely exclusively on layout and type to carry the UI, what design languages emphasise these qualities best? My gut says the early Mac OS versions wouldn't be a bad place to start, this seems relevant given the display constraints of the early macs.
I would greatly appreciate any advice on the design and implementation of e-ink UIs from people with practical experience in this area. This is purely to scratch a personal itch, once they're nailed down I'll put them out in the wild under the GPL.
jeffnash 1 day ago | parent
You obviously cannot always avoid this, so when there is a hard requirement for continuous feedback, use a degraded/cheap transient representation and refresh at commit of that action. With E-Ink, every interaction has to be a trade off between ghosting and latency. One common pattern I ended up coming back to is intermediate states that optimize for latency at the expense of ghosting until the UI action is 'complete'. This will allow refreshes to be associated with the satisfaction of finality. Funny enough, many of them harken back to old UI paradigms from when computer graphics weren't as powerful as today. When resizing, I simply draw the outline of the shape with a fast update mode that tolerates more ghosting until the user releases their finger or stylus, at which point, after a small lag to account for an 'oops, just a tiny bit bigger/smaller', I do a localized refresh of the union of the old + new area.
The outline itself is deliberately faint, so the refresh doesn't have to be as intense if its a small delta.
For your streaming LLM case, I would recommend doing it in chunks, like you said, perhaps doing a hard refresh of everything but the input box + moving the tail of last sent message + beginning of stream to the top upon send. Of course, this implies that you know exactly how long the response will be, which you don't. The challenge will be coming up with a UI paradigm that doesn't make it look awkward if the response only goes down to the middle of the screen while also nicely doing a clean refresh pre-emptively if it's clear it will overflow and need to move to the top. For the former case, find some way where it looks not completely awkward if it's partial and then do refresh of the chat history area and move it to its proper fitted position once the user clicks on the input box again to type their next reply. For the latter, perhaps embedding some kind of remaining space meter at the bottom that indicates when you'd need to do this move + refresh action (don't directly label it that way) would enable the user to anticipate it more and be less frustrated when it happens, as they are psychologically awaiting the next part of the answer/reasoning, not surprised at a sudden flash.
For the actual streaming, you'll have to ensure that your text alignment works in such a way that once a line is rendered to the screen, its placement is final (in other words, no streaming intra-word, as you need to know if the word will fit in remaining space on the line before rendering it). Basically, avoid retroactive reflow like a PDF document with a fixed layout, not like this text box I am typing in with a resize handle at the lower right. You'll also probably want to disable scrolling during generation as well, or at least make some sort of discrete pagination mechanism for going back and forth.
In any event, I look forward to seeing whatever it is you're building!
freeone3000 2 hours ago | parent
https://www.mediapoint.com.au/design-tips/composition-layout... might be moderately helpful.
But my advice: do not count on anything refreshing ever. Do not have animations, or transitions. Preload things to the correct spaces, then fill in. Reserve fixed white areas for output, and then fill in with black. (This works much better than the reverse!)
For quite long running processes, it might be worth having it behind a second page load.
rafabulsing 33 minutes ago | parent
a2ff6eeb0 2 minutes ago | parent
longnguyen 2 hours ago | parent
[0]: https://inka.page
exe34 1 hour ago | parent
philosopherNoob 2 hours ago | parent
When scrolling arbitrary text on a computer via keyboard, I generally become mildly frustrated hitting Spacebar, PageUp, and/or PageDown because I lose continuity with what I’m reading. (Perhaps the momentary 60hz blur makes it worse?) I attribute that to having a hard time identifying where I last read.
If there’s a dedicated button to scrolling, consider having an adjustable distance. EX: 100% screen height down, 95% screen width down (keeps a sliver of the last page), or 80% screen width down (keeps a chunk of the last page).
If you’re working with a touch display, perhaps a hold-and-drag-to-scroll feature could work? Have no indicator when pressed and only refresh the screen when the user lifts their finger. (It’s certainly not ideal to provide no feedback that it’s being used. But it may be useful to a user aware of how it works. Would probably need a training demonstration in-device like what older operating systems had.)
Perhaps a dedicated scroll undo button? For when one makes a mis-input and wants it back where it was. I know I’ve seen my grandma get frustrated at that sort of thing on desktop/phone where it’s quick to fix.
skydhash 1 hour ago | parent
There can be also a configurable wait time for refreshing the screen (like top) instead of assuming 60hz
seemack 1 hour ago | parent
ianbicking 47 minutes ago | parent
It could work well with e-ink because it's basically not interactive; you don't press and hold and move around to adjust it just right, instead you can confidently get an exact scroll position with one tap.
tracker1 1 hour ago | parent
For that matter, look at good TUI applications... (not the render path, but the result as a whole) ... blocking/spacing and clear controls, etc... though you'll be more touch, less tab/enter, etc. A TUI App renders by characters, but you can still learn from the overall layouts. Simplified menu lists with fewer items in a hierarchy, etc.
If you can and have physical buttons, think long and hard about their functions, especially if you have a limited number... these should do your most important things.
ramses0 1 hour ago | parent
Specifically for your example of maps, I would stick mostly to border buttons or UDLR gestures to "page" around rather than than trying to show a smoothly refreshing drag area.
Since you can _see_ the screen refreshing/drawing, and assuming you're using +/- buttons instead of "pinch to zoom", try drawing animations "in layers", ie: spend 100-300ms slapping "zoom tunnel boxes" trying to keep the user oriented while interacting.
Break the tiles into "thick => thin => letters => detail" and wait for them to quiesce before stacking more layers.
Rule #1 of optimization, you can never do something faster, you can only "do less"!
Rule #2 of optimization (amdahls law?)- optimize the slowest parts before you optimize the faster parts (and there's a limit to each!).
...from an end-user perspective, I would love if you respected my time and concentration when doing something "interactive".
Imagine scrolling through an e-ink address book. Imagine you spam only the (properly placed / laid out) first letter of first name and first letter of last name, and then after ~500ms of no movement, only THEN write out the remaining letters, phone numbers, icons, etc.
For e-ink interactive: every (slow, expensive) pixel that you write that gets scrolled off the screen is wasted time.
#1 - do less. In interactive cases, draw less pixels.
#2 - keep context. Use proxies/wireframes, "first letters", and "bold strokes".
#3 - fill in layers. Think "iterative Mona Lisa" and "progressive jpeg"
#4 - actually being slower is okay if you end up "feeling faster" or "seeming cooler"
Lean in to the defects and quirks of e-ink, make them feel intentional rather than trying to duplicate 60fps 16M colors.
DANmode 36 minutes ago | parent
Not kidding.