The board's 4.2" e-paper panel: its size, its fonts and its packed formats.
The panel is owned by BB.NSK.Display.FrameSink and drawn by
BB.NSK.Display.Viewport. Everything here is pure, so screens can be built
and inspected from the host without hardware.
Nothing here rasterises. Emerge renders a screen with Skia and packs it to one or two bits per pixel on the Rust side, so the Elixir side never handles a pixel. That matters more than it sounds: the panel is 120,000 pixels and 15,000 bytes, and anything that walks that in Elixir costs about as long as the panel takes to show it.
Summary
Functions
The fonts the panel's screens are drawn with, as an Emerge asset config.
The renderer and driver settings for a mode, and the frame geometry to expect back from it.
The modes the panel can be driven in.
The robot's name, as a screen shows it.
Wrap a screen in the panel's paper and orientation.
The panel's size in pixels, as {width, height}.
Functions
@spec fonts() :: [keyword()]
The fonts the panel's screens are drawn with, as an Emerge asset config.
Registered once against the renderer when the viewport mounts, and asked for by family in a screen. Handing them to a render call instead re-reads and re-parses both files every time, which costs ninety times what drawing the whole dashboard does.
@spec mode(:bw1 | :gray2) :: map()
The renderer and driver settings for a mode, and the frame geometry to expect back from it.
@spec modes() :: [:bw1 | :gray2]
The modes the panel can be driven in.
:bw1 is one bit per pixel and is what the panel has always been sent.
:gray2 is two bits per pixel and four levels, for twice the bytes.
@spec name() :: String.t()
The robot's name, as a screen shows it.
BB.NSK.name/0 rather than written down, so that a robot built from this
wears its own name without anybody remembering to edit a screen. The
dashboard's heading and the test card both ask for it, and before this they
were two literals free to disagree.
@spec paper(Emerge.tree(), boolean()) :: Emerge.tree()
Wrap a screen in the panel's paper and orientation.
Two things happen at this edge so that no screen has to know about either.
Skia starts a surface transparent, which quantises to black, so a screen that painted no background of its own would cost a full black refresh on a panel that takes three seconds to change its mind. The paper is painted here.
The panel is mounted upside down on the robot, so upside_down turns the
whole screen half a turn. Skia does it as part of the paint, so it costs
nothing and nothing upstream has to know — screens stay in the orientation a
person reading the panel sees.
@spec size() :: {pos_integer(), pos_integer()}
The panel's size in pixels, as {width, height}.