Skip to content

Plain chrome

xpui deliberately has no opinion about what a list row looks like. It asks, and a backend answers through its Chrome implementation. A backend sitting on a component library answers by calling that library, and a Rust screen ends up pixel-identical to a native one. A backend sitting on a drawing library has nothing to call, and this macro is what it answers with: one invocation writes the whole implementation from four things the backend passes in.

A whole panel painted by the chrome this crate writes: a Settings header reading 12, a Display group of three rows with Frontlight focused, a Reading group with a slider and a progress bar, a scroll indicator down the right edge, and Back, Select, Up and Down along the bottom

The host contract is what a backend implements, and Chrome is the fifth of its traits. This page is the macro that writes that one. Why it is a macro rather than a blanket implementation is in design.md.

plain_chrome!Writes a complete Chrome implementation for a backend that supplies only primitives.

Writes a complete Chrome implementation for a backend that supplies only primitives.

macro_rules! plain_chrome
plain_chrome! {
for Backend,
metrics: |backend| metrics,
labels: |backend| labels,
keys: |backend| keys,
request_update: |backend| expression,
}
plain_chrome! {
generic: [parameters],
for Backend<parameters>,
metrics: |backend| metrics,
labels: |backend| labels,
keys: |backend| keys,
request_update: |backend| expression,
}

The arguments are given in this order, and the trailing comma is optional.

ArgumentMeaning
genericOptional. The implementation's generic parameters with their bounds, [D: DrawTarget], spliced into impl<…>.
forThe backend type the implementation is for. It must implement Canvas and TextMetrics, because everything painted goes through them.
metricsA &Metrics: every number the components paint to. See Metrics.
labelsA &Labels: the words on the hint bar. See Labels.
keysA &KeyRow: which key sits in each slot of the hint bar, left to right. See KeyRow.
request_updateAn expression of type () that gets pixels onto the panel.

It asks for four things, because four parties own them. The metrics size the chrome and come from whoever knows the panel. The labels are words, and only an application knows what language its user reads. The key row says which word sits over which key, which is a fact about the hardware. And request_update is there because only a backend knows how to get pixels onto a panel: it has no sensible default.

All four are written as closures over the backend, not plain values, so a backend carrying its own can answer |backend| &backend.metrics. Two backends in one process may be driving two different panels, in two different languages, with two different key rows, and globals would give them one of each between them. Why metrics, labels and keys are separate from each other is on draw_button_hints.

One impl Chrome for Backend, whose eleven methods each call this crate:

Chrome methodAnswered by
metricMetrics::metric, on what metrics returns
draw_headerdraw_header
draw_sub_headerdraw_sub_header
draw_button_hintsdraw_button_hints, with metrics, labels and keys
draw_progress_bardraw_progress_bar
draw_sliderdraw_slider
draw_scroll_indicatordraw_scroll_indicator
draw_listdraw_list
draw_option_popupdraw_option_popup
option_popup_row_rectoption_popup_row_rect
request_updatethe request_update expression

The implementation needs no use in the backend's crate: the macro names every type through this crate.

Taking only some. Nine of the trait's eleven methods are also plain functions, so a backend with some components of its own can take only the ones it lacks rather than the whole implementation. The other two are not on offer: metric is answered by Metrics::metric, and request_update is one of the four things a backend passes in. No backend takes that route yet: xpui-embedded-graphics takes all nine through the macro, and the FreeInkUI backend answers Chrome over its own C ABI without depending on this crate.

Example — values in statics

Rust
use xpui_chrome::{KeyRow, Labels, Metrics};
static METRICS: Metrics = Metrics::DEFAULT;
static LABELS: Labels = Labels::ENGLISH;
static KEYS: KeyRow = KeyRow::READER;
xpui_chrome::plain_chrome! {
for MyBackend,
metrics: |_backend| &METRICS,
labels: |_backend| &LABELS,
keys: |_backend| &KEYS,
request_update: |backend| backend.flush(),
}

Example — a backend carrying its own

Rust
use core::cell::Cell;
use xpui::host::{Chrome, ThemeMetric};
use xpui_chrome::{KeyRow, Labels, Metrics};
struct Badge {
metrics: Metrics,
labels: Labels,
keys: KeyRow,
dirty: Cell<bool>,
}
xpui_chrome::plain_chrome! {
for Badge,
metrics: |badge| &badge.metrics,
labels: |badge| &badge.labels,
keys: |badge| &badge.keys,
request_update: |badge| badge.dirty.set(true),
}
let badge = Badge {
metrics: Metrics::for_panel(296, 128),
labels: Labels::for_panel(296, 128),
keys: KeyRow::READER,
dirty: Cell::new(false),
};
assert_eq!(badge.metric(ThemeMetric::ListRowHeight), 24);
badge.request_update();
assert!(badge.dirty.get());

Example — a generic backend

Rust
use xpui_chrome::{KeyRow, Labels, Metrics};
/// A backend over any display type.
struct Panel<D> {
display: D,
metrics: Metrics,
}
static LABELS: Labels = Labels::ENGLISH;
static KEYS: KeyRow = KeyRow::READER;
xpui_chrome::plain_chrome! {
generic: [D: Send],
for Panel<D>,
metrics: |panel| &panel.metrics,
labels: |_panel| &LABELS,
keys: |_panel| &KEYS,
request_update: |_panel| {},
}

Example — what the written implementation paints

It paints through the installed host, so a call on the backend lands on whichever canvas is installed: here, xpui's recording fake.

Rust
use xpui::host::{Chrome, Hint};
use xpui_chrome::{KeyRow, Labels, Metrics};
struct Plain;
xpui_chrome::plain_chrome! {
for Plain,
metrics: |_backend| &Metrics::DEFAULT,
labels: |_backend| &Labels::ENGLISH,
keys: |_backend| &KeyRow::READER,
request_update: |_backend| {},
}
xpui::testing::install();
xpui::testing::reset();
Plain.draw_button_hints(&Hint::Standard, &Hint::Done, &Hint::None, &Hint::None);
let text = xpui::testing::drawn_text();
let words: Vec<&str> = text.iter().map(|(_, _, word, _, _)| word.as_str()).collect();
assert_eq!(words, ["Back", "Done"]);

See also: Metrics, Labels, draw_button_hints, painting