Skip to content

The FreeInkUI backend

The Rust half of the FreeInkUI backend: the host a firmware installs, the platform trait it supplies input and the clock through, the call that points the C++ half at a framebuffer, and the macro that exports a screen for a C++ host to create.

The README wires it up and firmware.md adds the C++ half to a build. This page is what each piece does.

BackendThe backend, stateless because everything it needs lives on the C++ side, bound once by attach.
attachPoints the C++ side at the panel's framebuffer.
PlatformButtons, gestures and the clock, which FreeInkUI has no opinion about.
NoInputA platform with no input at all, for a panel that only ever displays.
register_screen!Exports a C factory for a screen, so a C++ host can create one by name.

The backend, stateless because everything it needs lives on the C++ side, bound once by attach.

pub struct Backend<P: Platform + 'static>

It implements all five host traits: Canvas, TextMetrics and Chrome by calling the C ABI, InputSource and Clock by forwarding to P. Because it holds only a &'static P, it can be a static itself, built in const context.

Example — wiring it up

Rust
use xpui_fui::{Backend, NoInput};
static PLATFORM: NoInput = NoInput;
static BACKEND: Backend<NoInput> = Backend::new(&PLATFORM);
let mut framebuffer = vec![0u8; (480 + 7) / 8 * 800];
// Safety: the buffer is the size `attach` asks for and outlives every draw.
unsafe { xpui_fui::attach(framebuffer.as_mut_ptr(), 480, 800) };
// Safety: one thread, and nothing has rendered yet.
unsafe { xpui::host::install(&BACKEND) };

xpui asks a backend for eight pieces of themed furniture. Where FreeInkUI has a component, the shim calls it. Where it has none, the shim draws it from DisplayTarget primitives. Calling the component is what makes a screen written against xpui and one written in C++ against FreeInkUI the same pixels.

Chrome callAsked for byDrawn by
draw_headerNavigationScreen, OverlayPanelfui::header — controls/header.h
draw_listListfui::Screen::list — lists/list.h, through FreeInkApp.h
draw_option_popup, option_popup_row_rectModalfui::optionDialog and fui::optionDialogHeight — overlays/option-dialog.h
draw_sliderSliderfui::slider — controls/slider.h
draw_progress_barProgressBarfui::progressBar — controls/progress-bar.h
draw_scroll_indicatorScrollViewfui::drawListScrollIndicator — a helper in lists/list.h, not a component
draw_sub_headerSectionthe shim — FreeInkUI has nothing for it
draw_button_hintsNavigationScreenthe shim — FreeInkUI has nothing for it

Text, fills, strokes, lines, the dither, the scrim and bitmaps land on freeink::ui::DisplayTarget directly, and so does clipping, which FreeInkUI does not have. This build ships no icons.

A backend forwarding input and the clock to platform.

pub const fn new(platform: &'static P) -> Self

See also: attach, Platform, lifecycle

Points the C++ side at the panel's framebuffer.

pub unsafe fn attach(framebuffer: *mut u8, width: i32, height: i32)

Call it once, before installing the backend. It forwards to xpui_fui_attach.

ParameterMeaning
framebuffer1 bit per pixel, MSB first, (width + 7) / 8 bytes per row, and a set bit is white.
width, heightThe panel in pixels, in the frame the UI lays out in: rotate on the way to the glass, not on the way in.

Buttons, gestures and the clock, which FreeInkUI has no opinion about.

pub trait Platform: Sync

A drawing library cannot tell whether a button was pressed. Whatever drives the panel already knows, a firmware's input manager or a GPIO poll, so it implements this and the backend forwards to it. Sync because the backend is a static. Implemented here by NoInput.

Example — a firmware's buttons

Rust
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use xpui::Button;
use xpui_fui::{Backend, Platform};
struct Firmware {
millis: AtomicU32,
confirm_down: AtomicBool,
confirm_edge: AtomicBool,
}
impl Platform for Firmware {
fn millis(&self) -> u32 {
self.millis.load(Ordering::Relaxed)
}
fn was_pressed(&self, button: Button) -> bool {
matches!(button, Button::Confirm) && self.confirm_edge.load(Ordering::Relaxed)
}
fn is_pressed(&self, button: Button) -> bool {
matches!(button, Button::Confirm) && self.confirm_down.load(Ordering::Relaxed)
}
fn was_released(&self, _button: Button) -> bool {
false
}
fn has_left_right_keys(&self) -> bool {
false // a device with only Confirm, Up and Down
}
}
static FIRMWARE: Firmware = Firmware {
millis: AtomicU32::new(0),
confirm_down: AtomicBool::new(false),
confirm_edge: AtomicBool::new(false),
};
static BACKEND: Backend<Firmware> = Backend::new(&FIRMWARE);

Milliseconds since some fixed point; only differences are read.

fn millis(&self) -> u32

Whether button went down this frame.

fn was_pressed(&self, button: Button) -> bool

Whether button is down, this frame included.

fn is_pressed(&self, button: Button) -> bool

Whether button came up this frame.

fn was_released(&self, button: Button) -> bool

Whether the device has a Left/Right pair to nudge a value with; see InputSource::has_left_right_keys.

fn has_left_right_keys(&self) -> bool

Required, unlike everything below it: no answer is safe to inherit, and only the firmware knows which keys its device carries. The xpui-boards-* crates answer it for every board they describe, as Board::has_left_right_keys.

Each answers "nothing happened" unless overridden, which is right for a device without a touchscreen.

Whether this frame carries a touch — not whether the device has a touchscreen.

fn has_touch(&self) -> bool

A completed tap, at the position the finger went down.

fn tap(&self) -> Option<Point>

Where the finger is while it is down.

fn touch_held(&self) -> Option<Point>

Whether a finger lifted this frame.

fn touch_released(&self) -> bool

A completed swipe, or SwipeDir::None.

fn swipe(&self) -> SwipeDir

The system back gesture.

fn was_back_gesture(&self) -> bool

The system home gesture, offered to the screen before the host acts.

fn was_home_gesture(&self) -> bool

Whether a swipe moves focus rather than dragging content; see InputSource::swipe_moves_selection.

fn swipe_moves_selection(&self) -> bool

A platform with no input at all, for a panel that only ever displays.

pub struct NoInput

Its clock stands at 0, no button is ever pressed, and it has no Left/Right pair. See the example under Backend.

Exports a C factory for a screen, so a C++ host can create one by name.

macro_rules! register_screen
register_screen!(ScreenType, factory_name);

It expands to a #[no_mangle] extern "C" fn factory_name() -> *mut c_void that builds the screen with ScreenType::new() and returns it as a lifecycle handle. The screen type never crosses the boundary, so the host needs no header describing it: adding a screen is this line plus one declaration on the C++ side.

Rust
use xpui::screen::Screen;
use xpui::{NavigationScreen, Text, View};
use xpui_fui::register_screen;
struct MainMenu;
impl MainMenu {
fn new() -> Self {
MainMenu // the macro calls this
}
}
impl Screen for MainMenu {
type Message = ();
fn body(&self) -> impl View<()> {
NavigationScreen::new(Text::new("Main menu")).title("Menu")
}
fn update(&mut self, _message: ()) {}
}
register_screen!(MainMenu, xpui_app_create_main_menu);
let handle = xpui_app_create_main_menu();
assert!(!handle.is_null());
// Safety: a live handle from the factory, destroyed exactly once.
unsafe { xpui_fui::lifecycle::xpui_screen_destroy(handle) };