The embedded-graphics backend
The host itself: a Backend over any embedded-graphics DrawTarget, the
input a caller feeds it each frame, the two colours ink and background land
as, and the wrapper a parallel-bus panel needs. Everything a screen draws goes
through one of these, and everything it hears about buttons and fingers comes
in through one.
The crate's README wires one into a loop from nothing, and
design.md says why it has the shape it does. This page is
what each piece does.
Topics
Section titled “Topics”Backend | An xpui host over an embedded-graphics display. |
DisplayLoan | A display that has been taken out of its backend, and goes back on drop. |
InputState | One frame of input, as the event source writes it and the framework reads it. |
PacedFill | Wraps a display, replacing its repeated-pixel path with a per-pixel one. |
Palette | The two colours ink and background map onto. |
| Re-exports | DrawTarget, Labels and Metrics, from the crates this one is built on. |
xpui_eg::Backend
Section titled “xpui_eg::Backend”An xpui host over an embedded-graphics display.
pub struct Backend<D: DrawTarget>D is the display: a panel driver, an OLED, a colour TFT, or
xpui_screenshot::Framebuffer in a test. The backend supplies Canvas and
TextMetrics itself, InputSource and Clock from what the caller feeds it,
and Chrome from xpui-chrome, so a list, a dialog and a slider look like
something without anyone drawing one.
Canvas::clear fills the whole panel and ignores any clip. It writes the
background straight to the display, however small a clip is set at the time;
every other primitive draws through the clip.
| Builder | Sets | When not called |
|---|---|---|
with_metrics | the measurements chrome is painted to | Metrics::DEFAULT |
with_fonts | the family, and the size each role wants | Fonts::DEFAULT |
with_labels | the words in the hint bar | Labels::ENGLISH |
with_keys | what the keys along the bottom edge mean | KeyRow::READER |
with_left_right_keys | whether the device has a Left/Right pair | false |
The caller drives the frame. A loop calls
begin_frame with the clock, reports whatever
input arrived, ticks the App, renders if a repaint was asked for, and pushes
the pixels with with_display or
loan_display.
Example — a frame, driven by hand
use xpui::{App, Button, NavigationScreen, Screen, Text, View};use xpui_eg::{Backend, Palette};use xpui_screenshot::Framebuffer;
struct Hello;
impl Screen for Hello { type Message = ();
fn body(&self) -> impl View<()> { NavigationScreen::new(Text::new("Hello")) }
fn update(&mut self, _message: ()) {}}
let backend = Backend::leak(Framebuffer::new(480, 800), Palette::INK_IS_ON);// Safety: one thread, and nothing has rendered on this backend yet.unsafe { xpui::host::install(backend) };
let mut app = App::new(Hello);backend.begin_frame(0);backend.press(Button::Down); // from wherever input comes fromapp.tick();if app.render_if_dirty() { backend.clear_dirty(); backend.with_display(|frame| assert!(frame.ink_count() > 0));}Example — a small panel, configured
use xpui::host::KeyRow;use xpui_eg::{Backend, Fonts, Labels, Metrics, Palette};use xpui_screenshot::Framebuffer;
let backend = Backend::new(Framebuffer::new(296, 128), Palette::INK_IS_OFF) .with_metrics(Metrics::SMALL) .with_fonts(Fonts::SMALL) .with_labels(Labels::ENGLISH_SHORT) .with_keys(KeyRow::READER) .with_left_right_keys(true);
assert!(backend.left_right_keys());assert_eq!(backend.fonts().ui, Fonts::SMALL.ui);Creating a backend
Section titled “Creating a backend”xpui_eg::Backend::new
Section titled “xpui_eg::Backend::new”A backend over display, painting ink and background as palette says, with the default metrics, English labels and a reader's key row.
pub fn new(display: D, palette: Palette<D::Color>) -> SelfThe panel's size is read once, here, so it can still be answered while the display is on loan.
xpui_eg::Backend::leak
Section titled “xpui_eg::Backend::leak”Leaks the backend so it can be installed.
pub fn leak(display: D, palette: Palette<D::Color>) -> &'static Self where D: 'static,xpui::host::install takes a &'static, and a backend lives as long as the
program that draws with it, so this is one allocation that was never going to
be freed anyway.
xpui_eg::Backend::leaked
Section titled “xpui_eg::Backend::leaked”Leaks a backend a caller has finished building.
pub fn leaked(self) -> &'static Self where D: 'static,leak builds and leaks in one step, which leaves
nowhere for the builders. This is the other order:
Backend::new(display, palette).with_metrics(m).leaked().
Configuring it
Section titled “Configuring it”xpui_eg::Backend::with_metrics
Section titled “xpui_eg::Backend::with_metrics”Paints chrome to these measurements instead of the default.
pub fn with_metrics(self, metrics: Metrics) -> SelfPer backend rather than global, because a 296x128 panel and a 480x800 one need different numbers, and a process can drive both.
xpui_eg::Backend::with_fonts
Section titled “xpui_eg::Backend::with_fonts”Sets the type the framework's roles resolve through: a family, and the size each role wants.
pub fn with_fonts(self, fonts: Fonts) -> SelfFor a backend nothing will re-set. set_family
replaces it while running. See Fonts.
xpui_eg::Backend::with_labels
Section titled “xpui_eg::Backend::with_labels”The words its hint bar shows.
pub fn with_labels(self, labels: Labels) -> SelfEnglish until an application says so.
xpui_eg::Backend::with_keys
Section titled “xpui_eg::Backend::with_keys”What the keys along the device's bottom edge mean, left to right.
pub fn with_keys(self, keys: KeyRow) -> Selfxpui_eg::Backend::with_left_right_keys
Section titled “xpui_eg::Backend::with_left_right_keys”Says the device has a Left/Right pair.
pub fn with_left_right_keys(self, present: bool) -> SelfA fact about the hardware, so it is told rather than derived: the backend has no idea what is around the display it was handed.
Reading its configuration
Section titled “Reading its configuration”xpui_eg::Backend::metrics
Section titled “xpui_eg::Backend::metrics”The measurements every chrome painter lays out against.
pub fn metrics(&self) -> &Metricsxpui_eg::Backend::fonts
Section titled “xpui_eg::Backend::fonts”The type in use, as with_fonts or a switch left it.
pub fn fonts(&self) -> Fontsxpui_eg::Backend::labels
Section titled “xpui_eg::Backend::labels”The words this backend paints hints with.
pub fn labels(&self) -> &Labelsxpui_eg::Backend::keys
Section titled “xpui_eg::Backend::keys”What the keys along the device's bottom edge mean.
pub fn keys(&self) -> &KeyRowxpui_eg::Backend::left_right_keys
Section titled “xpui_eg::Backend::left_right_keys”Whether it was told the device has a Left/Right pair.
pub fn left_right_keys(&self) -> boolChanging the type while it runs
Section titled “Changing the type while it runs”xpui_eg::Backend::set_family
Section titled “xpui_eg::Backend::set_family”Sets the type in another family, and asks for a repaint.
pub fn set_family(&self, family: &'static Family)The sizes are re-derived from the chrome, not carried over: each role keeps
the line height these metrics call for, and the new family answers with the
tier it was cut in. Every font id changes with it, since an id is a hash of the
bytes. A screen that wants every backend to follow calls
request_family instead.
use xpui_eg::{Backend, Fonts, HELVETICA, Palette};use xpui_screenshot::Framebuffer;
let backend = Backend::new(Framebuffer::new(480, 800), Palette::INK_IS_ON);backend.clear_dirty();backend.set_family(&HELVETICA);assert!(backend.is_dirty());assert_eq!(backend.fonts().ui, Fonts::for_metrics(backend.metrics()).ui);Driving a frame
Section titled “Driving a frame”xpui_eg::Backend::begin_frame
Section titled “xpui_eg::Backend::begin_frame”Starts a frame: advances the clock and clears one-frame input.
pub fn begin_frame(&self, millis: u32)A family a screen asked for with request_family lands here, between frames,
before anything has been measured against the type it replaces.
xpui_eg::Backend::is_dirty
Section titled “xpui_eg::Backend::is_dirty”Whether the framework has asked for a repaint since clear_dirty.
pub fn is_dirty(&self) -> boolxpui_eg::Backend::clear_dirty
Section titled “xpui_eg::Backend::clear_dirty”Marks the pending repaint as pushed; call after presenting the frame.
pub fn clear_dirty(&self)Feeding input
Section titled “Feeding input”Each of these is an edge, true for the frame it is reported in and cleared by
the next begin_frame.
xpui_eg::Backend::press
Section titled “xpui_eg::Backend::press”Reports button as pressed this frame.
pub fn press(&self, button: Button)xpui_eg::Backend::release
Section titled “xpui_eg::Backend::release”Reports button as released this frame.
pub fn release(&self, button: Button)xpui_eg::Backend::tap
Section titled “xpui_eg::Backend::tap”Reports a completed tap at at.
pub fn tap(&self, at: Point)xpui_eg::Backend::swipe
Section titled “xpui_eg::Backend::swipe”Reports a completed swipe.
pub fn swipe(&self, direction: SwipeDir)xpui_eg::Backend::input
Section titled “xpui_eg::Backend::input”Feeds the frame, for a caller that has its own event source.
pub fn input(&self, feed: impl FnOnce(&mut InputState))The closure runs with the backend's state borrowed, so it must not call back
into the backend: no drawing, no screen_size. Feed input and return. What it
can write is InputState.
Reaching the panel
Section titled “Reaching the panel”xpui_eg::Backend::with_display
Section titled “xpui_eg::Backend::with_display”Borrows the display, for pushing the framebuffer to a panel.
pub fn with_display<R>(&self, body: impl FnOnce(&mut D) -> R) -> RThe closure holds the backend's state, so flush the panel, read the pixels, and return without drawing through the backend.
xpui_eg::Backend::loan_display
Section titled “xpui_eg::Backend::loan_display”Takes the display out of the backend, until the loan is dropped.
pub fn loan_display(&self) -> Option<DisplayLoan<'_, D>>For a flush that suspends. with_display holds the guard for as long as its
closure runs, and a borrow held across an await is a second task finding the
state already borrowed. None when the display is already out. While it is out,
anything that paints is discarded and screen_size still answers.
use xpui_eg::{Backend, Palette};use xpui_screenshot::Framebuffer;
let backend = Backend::new(Framebuffer::new(480, 800), Palette::INK_IS_ON);let loan = backend.loan_display().expect("the display is in");assert!(backend.loan_display().is_none(), "it is out until the loan drops");assert_eq!(loan.width, 480); // a loan derefs to the displaydrop(loan);assert!(backend.loan_display().is_some());See also: DisplayLoan, InputState, Palette, Fonts
xpui_eg::DisplayLoan
Section titled “xpui_eg::DisplayLoan”A display that has been taken out of its backend, and goes back on drop.
pub struct DisplayLoan<'a, D: DrawTarget>Returned by Backend::loan_display. It derefs,
mutably too, to the display, and it borrows the backend only shared, so a loan
of a Sync backend is Send and can be held across an await, which is what
it is for.
xpui_eg::InputState
Section titled “xpui_eg::InputState”One frame of input, as the event source writes it and the framework reads it.
pub struct InputStateReached through Backend::input. Every event here is
an edge, cleared by begin_frame; what survives it is the held set of buttons,
the finger's position until touch_up, and swipe_moves_selection, which is a
setting.
| Field | Meaning |
|---|---|
xpui_eg::InputState::swipe_moves_selection | Whether a swipe up should move focus up rather than dragging content. |
Example — a touch driver's frame
use xpui::host::InputSource;use xpui::{Button, Point, SwipeDir};use xpui_eg::{Backend, Palette};use xpui_screenshot::Framebuffer;
let backend = Backend::new(Framebuffer::new(480, 800), Palette::INK_IS_ON);backend.begin_frame(16);backend.input(|state| { state.press(Button::Confirm); state.touch_down(Point::new(40, 120)); state.swipe(SwipeDir::Up);});assert!(backend.was_pressed(Button::Confirm));
backend.begin_frame(32);assert!(!backend.was_pressed(Button::Confirm), "an edge lasts one frame");assert!(backend.is_pressed(Button::Confirm), "the held state survives");Starting a frame
Section titled “Starting a frame”xpui_eg::InputState::begin_frame
Section titled “xpui_eg::InputState::begin_frame”Clears everything that lasts one frame.
pub fn begin_frame(&mut self)Held buttons survive; that is the difference between "is down" and "went
down". Backend::begin_frame calls it.
Buttons
Section titled “Buttons”xpui_eg::InputState::press
Section titled “xpui_eg::InputState::press”A button went down: reports both the edge and the held state.
pub fn press(&mut self, button: Button)xpui_eg::InputState::release
Section titled “xpui_eg::InputState::release”A button came up.
pub fn release(&mut self, button: Button)xpui_eg::InputState::tap
Section titled “xpui_eg::InputState::tap”A completed tap, at the position the finger went down.
pub fn tap(&mut self, at: Point)xpui_eg::InputState::touch_down
Section titled “xpui_eg::InputState::touch_down”A finger is down at at.
pub fn touch_down(&mut self, at: Point)Reported every frame it stays down, which is the signal a slider drag needs.
xpui_eg::InputState::touch_up
Section titled “xpui_eg::InputState::touch_up”The finger came up.
pub fn touch_up(&mut self)xpui_eg::InputState::swipe
Section titled “xpui_eg::InputState::swipe”A completed swipe in direction.
pub fn swipe(&mut self, direction: SwipeDir)Gestures
Section titled “Gestures”xpui_eg::InputState::back_gesture
Section titled “xpui_eg::InputState::back_gesture”The system back gesture, this frame.
pub fn back_gesture(&mut self)xpui_eg::InputState::home_gesture
Section titled “xpui_eg::InputState::home_gesture”The system home gesture, this frame.
pub fn home_gesture(&mut self)xpui_eg::PacedFill
Section titled “xpui_eg::PacedFill”Wraps a display, replacing its repeated-pixel path with a per-pixel one.
pub struct PacedFill<D>(D)Wrap a parallel-bus panel in PacedFill. An 8080-style parallel panel
driven through mipidsi shortens a run of one colour into a loop on the write
strobe alone, and on a fast microcontroller that loop outruns the controller's
minimum write cycle, so the fill lands as noise. It triggers whenever a pixel's
two bytes are identical, and ink and background are two such colours, so every
filled rectangle and every clear breaks while text, drawn pixel by pixel, comes
out perfectly. A panel showing crisp type over static is this fault.
PacedFill routes fill_solid and clear through fill_contiguous, which has
a colour for every pixel and so no run to shorten. It is opt-in: an SPI panel
clocks its own bytes and a framebuffer has no timing, so neither needs it.
hardware.md has the whole account, with the timings.
use embedded_graphics::pixelcolor::BinaryColor;use embedded_graphics::prelude::*;use xpui_eg::{Backend, PacedFill, Palette};use xpui_screenshot::Framebuffer;
let mut panel = PacedFill::new(Framebuffer::new(320, 240));panel.clear(BinaryColor::On).unwrap();let frame = panel.into_inner();assert_eq!(frame.ink_count(), 320 * 240);
// In a firmware, the wrapped panel is what the backend is built over.let backend = Backend::new(PacedFill::new(frame), Palette::INK_IS_ON);xpui_eg::PacedFill::new
Section titled “xpui_eg::PacedFill::new”Wraps a display so its solid fills go out a pixel at a time.
pub fn new(display: D) -> Selfxpui_eg::PacedFill::into_inner
Section titled “xpui_eg::PacedFill::into_inner”The display back, for a caller that has finished pacing it.
pub fn into_inner(self) -> Dxpui_eg::Palette
Section titled “xpui_eg::Palette”The two colours ink and background map onto.
pub struct Palette<C>xpui paints in ink and background, and a palette maps the two onto any
PixelColor, so a colour TFT runs the same screens looking monochrome.
| Field | Meaning |
|---|---|
xpui_eg::Palette::ink | What ink paints as. |
xpui_eg::Palette::background | What background paints as. |
Example — a colour panel
use embedded_graphics::pixelcolor::Rgb565;use embedded_graphics::prelude::RgbColor;use xpui_eg::Palette;
let tft = Palette::new(Rgb565::BLACK, Rgb565::WHITE);assert_eq!(tft.ink, Rgb565::BLACK);xpui_eg::Palette::new
Section titled “xpui_eg::Palette::new”A palette painting ink as ink and background as background.
pub fn new(ink: C, background: C) -> Selfxpui_eg::Palette::INK_IS_ON
Section titled “xpui_eg::Palette::INK_IS_ON”Ink is On.
pub const INK_IS_ON: Self = PaletteThe common case, and what the simulator uses.
xpui_eg::Palette::INK_IS_OFF
Section titled “xpui_eg::Palette::INK_IS_OFF”Ink is Off.
pub const INK_IS_OFF: Self = PaletteSome 1-bit panels invert: uc8151, the Badger 2040's controller, maps Off to
black so bitmaps load unmirrored.
Re-exports
Section titled “Re-exports”Re-exported so a caller never needs a second dependency that could drift to another version of the same type.
| Name | What it is |
|---|---|
xpui_eg::DrawTarget | embedded-graphics' drawing trait, the bound on a Backend's display. A caller writing its own fn wire<D: DrawTarget> names this one. |
xpui_eg::Labels | xpui-chrome's hint-bar words, for with_labels. |
xpui_eg::Metrics | xpui-chrome's chrome measurements, for with_metrics. |