Bedtime. /UI Components

UI Components

Ready-made widgets — buttons, meters, frames, images — drawn in the firmware's style

These sit one level above Graphics API: each is a small pile of primitives the firmware's own screens use, exported so a tapp looks like it belongs on the device. They take the same gfx_t* and obey the same colour and clip state.

Sizes are mostly fixed by the design rather than by your arguments — ui_draw_button() is always 24px tall, ui_draw_scrollbar()'s track always 8px wide — so x/y place a known shape rather than defining one. Where a widget does take w/h, they are the outer bounds.

Images and animations

gfx_img_t comes out of the asset pipeline: drop a PNG or GIF in your tapp's assets/ folder and tapp-build compiles it into a gfx_img_t plus an extern declaration in the generated assets header. A GIF becomes a multi-frame image; ui_draw_anim() takes the frame index directly and wraps it, so drive it from ui_get_frame().

ui_draw_img(gfx, 20, 20, &asset_logo);
ui_draw_anim(gfx, 120, 60, ui_get_frame(gfx) >> 2, &asset_dance_party);
void ui_draw_img(gfx_t* gfx, uint16_t x, uint16_t y, const gfx_img_t* img)

Draw the first frame of an image at (x, y)

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
const gfx_img_t*imgImage from the asset pipeline

Note — Opaque, like gfx_draw_xbm(): the image's whole bounding box is overwritten.

void ui_draw_anim(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t frame, const gfx_img_t* img)

Draw one frame of a multi-frame image

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_tframeFrame index; wrapped modulo the image's frame count, so a free-running counter is fine
const gfx_img_t*imgImage from the asset pipeline

Note — There is no img_animation_* API and no playback clock — you pick the frame each redraw. `ui_get_frame(gfx) >> n` is the usual source; bigger `n` is slower.

void ui_draw_img_scaled(gfx_t* gfx, uint16_t x, uint16_t y, const gfx_img_t* img, uint16_t scale)

Draw an image shrunk by an integer factor

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
const gfx_img_t*imgImage from the asset pipeline
uint16_tscaleDivisor: 2 is half size, 3 a third. 1 is 1:1.

Warning — Shrinks only — see gfx_draw_xbm_scaled(), which this wraps.

void ui_draw_qrcode(gfx_t* restrict gfx, uint16_t x, uint16_t y, uint16_t size, const uint8_t* qr)

Render a pre-encoded QR code

gfx_t* restrictgfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_tsizePixels per QR module — the code ends up `size * modules` square
const uint8_t*qrBuffer in qrcodegen format

Warning — The encoder is not part of the TAPP API: only this renderer is exported, so you have to produce the qrcodegen buffer yourself (bundle the encoder in your tapp) or bake it in as a constant.

void ui_draw_time_entry(gfx_t* gfx, uint16_t x, uint16_t y, float len, uint16_t idx_l)

Draw an indexed duration row — "3 1m 24s", or "3 <1s"

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyText baseline
floatlenDuration in seconds
uint16_tidx_lIndex printed before the duration
bool ui_ease_position(uint16_t* pos, uint16_t target)

Move a position half-way toward a target — one step of an ease-out

uint16_t*posPosition to advance, updated in place
uint16_ttargetWhere it is heading

Returns — true once `*pos` has arrived (and nothing was changed)

if(!ui_ease_position(&m->scroll_y, m->scroll_target)) m->dirty = true;

Note — Call it once per frame and redraw while it returns false. Integer halving, so it lands exactly rather than creeping.

bool ui_ease_position_mod(uint16_t* pos, uint16_t target, float mod)

ui_ease_position() with an adjustable step

uint16_t*posPosition to advance, updated in place
uint16_ttargetWhere it is heading
floatmodFraction of the half-step to take: 1.0 matches ui_ease_position(), smaller is slower. Progress of at least one unit per call is guaranteed.

Returns — true once `*pos` has arrived

void ui_draw_scrollbar(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t height, uint16_t pos, uint16_t total)

Vertical scrollbar with a proportional thumb

gfx_t*gfxGraphics context
uint16_txLeft edge (the track is 8px wide)
uint16_tyTop edge
uint16_theightTrack height in pixels
uint16_tposZero-based index of the selected item
uint16_ttotalNumber of items; 0 draws the empty track
void ui_draw_on_circle(gfx_t* gfx, uint16_t x, uint16_t y, float value, uint_fast8_t scale, uint_fast8_t size)

Draw a dot positioned around a circle — a knob indicator

gfx_t*gfxGraphics context
uint16_txCentre X of the circle
uint16_tyCentre Y of the circle
floatvaluePosition around the circle, 0.0-1.0
uint_fast8_tscaleRadius the dot orbits at
uint_fast8_tsizeRadius of the dot itself
void ui_draw_frame(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, bool active)

Rounded outline, with a double ring when selected

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twWidth in pixels
uint16_thHeight in pixels
uint16_trCorner radius
boolactivetrue adds two outer rings — the firmware's selection cue. They grow *outward*, so leave 2px of margin around the frame.
void ui_draw_action(gfx_t* gfx, uint16_t x, uint16_t y, bool hold, const char* name)

Button-hint row: a label with a press/hold marker to its left

gfx_t*gfxGraphics context
uint16_txLeft edge of the label
uint16_tyText baseline
boolholdtrue draws the "hold" bar, false the "press" dot
const char*nameLabel text
uint16_t ui_draw_action_act(gfx_t* gfx, uint16_t x, uint16_t y, bool hold, const char* name, bool active, bool tall)

ui_draw_action() that can render selected, and reports its width

gfx_t*gfxGraphics context
uint16_txLeft edge of the label
uint16_tyText baseline
boolholdtrue draws the "hold" bar, false the "press" dot
const char*nameLabel text
boolactivetrue fills a rounded plate behind it and inverts the contents
booltalltrue makes the plate 22px taller, for two-line rows

Returns — Width of the label text in pixels — use it to lay the next action out

void ui_draw_noise_zone(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint_fast8_t pixel_size)

Fill a region with random pixels — TV static

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twWidth in pixels
uint16_thHeight in pixels
uint_fast8_tpixel_sizeVertical step between noise rows; 1 is every row

Warning — Currently draws nothing: the firmware offsets x by the display width, putting every pixel outside the clip window. Build static out of gfx_draw_pixel() until that is fixed.

void ui_draw_buffer_dots(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const float* buffer, size_t size)

Plot a float buffer as a dotted waveform

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twidthPlot width in pixels; the buffer is resampled to fit
uint16_theightPlot height in pixels — also sets the amplitude scale
const float*bufferSample values, roughly -1.0 to 1.0. NULL is ignored.
size_tsizeNumber of samples in the buffer
void ui_string_upper(const char* original_str, char* uppper, int8_t len)

Copy a string to upper case

const char*original_strSource string
char*uppperDestination buffer
int8_tlenSize of the destination buffer including the terminator
void ui_string_lower(const char* original_str, char* upper, int8_t len)

Copy a string to lower case

const char*original_strSource string
char*upperDestination buffer
int8_tlenSize of the destination buffer including the terminator
void ui_draw_frame_wback(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, bool active)

ui_draw_frame() over a cleared background — an opaque panel

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twWidth in pixels
uint16_thHeight in pixels
uint16_trCorner radius
boolactivetrue adds the outer selection rings

Note — Use this for anything floating over other content; ui_draw_frame() alone leaves whatever was underneath showing through.

void ui_draw_value_bar(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint32_t val, uint32_t min, uint32_t max)

Horizontal bar meter over an integer range

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twOuter width in pixels
uint16_thOuter height in pixels
uint32_tvalCurrent value
uint32_tminRange minimum
uint32_tmaxRange maximum
void ui_draw_value_bar_smallf(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, float val, float min, float max)

Slim float bar meter, drawn in the lower half of its box

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twOuter width in pixels
uint16_thOuter height in pixels — the bar itself is about an eighth of it
floatvalCurrent value
floatminRange minimum
floatmaxRange maximum
void ui_draw_3layer_box(gfx_t* restrict gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t round)

Rounded box with a black / white / black border stack

gfx_t* restrictgfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twWidth in pixels
uint16_thHeight in pixels
uint16_troundCorner radius

Note — Leaves the draw colour at 1.

void ui_draw_icon_centered(gfx_t* restrict gfx, uint16_t x, uint16_t y, const gfx_img_t* icon)

Draw an icon offset by half its own size

gfx_t* restrictgfxGraphics context
uint16_txReference X
uint16_tyReference Y
const gfx_img_t*iconImage to draw; NULL is ignored

Note — The offset is added, not subtracted: the icon lands with its top-left half a width/height *past* (x, y). Pass the top-left of the box you want it centred in.

uint16_t ui_draw_text_centered_with_icon(gfx_t* restrict gfx, uint16_t y, const char* text, const gfx_img_t* icon)

Draw a string centred on the screen, nudged right if it has an icon

gfx_t* restrictgfxGraphics context
uint16_tyText baseline
const char*textLabel text
const gfx_img_t*iconIcon that will sit to the left of the text; NULL for text alone

Returns — X the text was drawn at — draw the icon relative to it

void ui_format_time(char* restrict buff, uint32_t size, float val, TimeFormatStyle style)

Output style for ui_format_time() */ typedef enum { TIME_FORMAT_COMPACT, /**< "MM:SS.ss" or "HH:MM:SS.ss" — timeline readouts */ TIME_FORMAT_VERBOSE, /**< "Xh Ym Zs" — menu labels */ } TimeFormatStyle; /**

char* restrictbuffDestination buffer
uint32_tsizeSize of the destination buffer
floatvalPosition in tape positions (SD sectors), as tape_get_position() / 64 returns
TimeFormatStylestyleTIME_FORMAT_COMPACT or TIME_FORMAT_VERBOSE

Note — Compact output drops leading zero fields — a sub-minute value comes out "SS.ss".

void ui_draw_shade_pattern(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint_fast8_t pattern, uint_fast8_t density)

Fill a rectangle with a line/dot texture instead of a dither pattern

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twWidth in pixels
uint16_thHeight in pixels
uint_fast8_tpatternTexture: 1 vertical stripes, 2 horizontal stripes, 3 cross-hatch, 4 checkerboard, 5 dot grid, 6 diagonal, 7 diagonal cross, 8 fine cross-hatch, 9 large checker, 10 staggered dots, 11 vertical dashes, 12 horizontal dashes, 13 dense dots, 14 stipple, 15 solid
uint_fast8_tdensity1-14, spacing between texture elements — bigger is tighter. 0 draws nothing at all.

Note — Distinct from the dithered fills: this is a geometric texture at your chosen spacing, not a grey level. Clipped to the screen internally.

void ui_draw_volume_mono(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const float* values, bool frame)

Single horizontal level bar

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twidthFull-scale width in pixels
uint16_theightBox height; the bar is half of it
const float*valuesTwo floats 0.0-1.0; the louder of the two is drawn
boolframeUnused, kept for symmetry with ui_draw_volume_stereo()
void ui_draw_volume_stereo(gfx_t* gfx, uint16_t x, uint16_t y, uint16_t width, uint16_t height, const float* values, bool frame)

Two stacked level bars, left over right

gfx_t*gfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twidthFull-scale width in pixels
uint16_theightBox height; each bar is a third of it
const float*valuesTwo floats 0.0-1.0 — `values[0]` left, `values[1]` right
boolframetrue clears a backing plate first, so the meter stays legible over content
uint32_t ui_draw_str_multi_line(gfx_t* restrict gfx, const char *str, uint_fast8_t max_chr_per_line, uint32_t x, uint32_t y)

Draw word-wrapped text

gfx_t* restrictgfxGraphics context
const char *strText to draw; "\n" forces a break
uint_fast8_tmax_chr_per_lineWrap width in characters
uint32_txLeft edge
uint32_tyBaseline of the first line

Returns — Baseline y of the last line drawn

Note — Wraps on character count, not pixel width, so pick the limit for the font you have selected.

Warning — `max_chr_per_line` must stay at or below 100 — the firmware assembles each line in a 101-byte stack buffer and does not bounds-check the limit you pass.

uint32_t ui_count_string_lines(const char* str, uint_fast8_t max_chr_per_line)

Count the lines ui_draw_str_multi_line() would produce

const char*strText to measure
uint_fast8_tmax_chr_per_lineWrap width in characters

Returns — Number of lines

Note — Use it to size a panel before you draw the text into it.

void ui_draw_dialogue_multiline_string(gfx_t* restrict gfx, uint_fast8_t x, uint_fast8_t y, uint16_t count, uint16_t max_chars_per_line, const char* restrict text)

Word-wrapped text revealed a character at a time — the typewriter effect

gfx_t* restrictgfxGraphics context
uint_fast8_txLeft edge
uint_fast8_tyBaseline of the first line
uint16_tcountHow many characters to reveal; raise it each frame to type the text out
uint16_tmax_chars_per_lineWrap width in characters
const char* restricttextText to draw
void ui_draw_dialogue(gfx_t* restrict gfx, uint16_t x, uint16_t y, uint16_t width, uint16_t height)

Draw an empty speech bubble — rounded panel plus a tail below it

gfx_t* restrictgfxGraphics context
uint16_txLeft edge
uint16_tyTop edge
uint16_twidthBubble width in pixels
uint16_theightBubble height in pixels; the tail hangs about 15px below that

Note — Draws the container only. Follow it with ui_draw_dialogue_multiline_string() for the text.

void ui_draw_button(gfx_t* restrict gfx, const uint16_t x, const uint16_t y, const uint16_t w, const char* text, const gfx_img_t* icon, const bool pressed)

Filled button, 24px tall, with centred text or an icon

gfx_t* restrictgfxGraphics context
const uint16_txLeft edge
const uint16_tyTop edge
const uint16_twButton width in pixels
const char*textLabel, centred; ignored when `icon` is non-NULL
const gfx_img_t*iconIcon to draw instead of the label, or NULL
const boolpressedtrue shifts the face 2px up-left for the pressed look
void ui_draw_button_inverted(gfx_t* restrict gfx, const uint16_t x, const uint16_t y, const uint16_t w, const char* text, const gfx_img_t* icon, const bool pressed)

ui_draw_button() with the fill and outline swapped

gfx_t* restrictgfxGraphics context
const uint16_txLeft edge
const uint16_tyTop edge
const uint16_twButton width in pixels
const char*textLabel, centred; ignored when `icon` is non-NULL
const gfx_img_t*iconIcon to draw instead of the label, or NULL
const boolpressedtrue shifts the face 2px up-left for the pressed look
void ui_draw_switch(gfx_t* restrict gfx, uint16_t x, uint16_t y, bool val, bool detailed)

On/off toggle

gfx_t* restrictgfxGraphics context
uint16_txReference X — the switch body starts 25px to the right of it
uint16_tyReference Y — the body sits *above* it, from y-17 to y+2
boolvalSwitch state
booldetailedtrue marks the body O for off and I for on