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);
Draw the first frame of an image at (x, y)
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| const gfx_img_t* | img | Image from the asset pipeline |
Note — Opaque, like gfx_draw_xbm(): the image's whole bounding box is overwritten.
Draw one frame of a multi-frame image
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | frame | Frame index; wrapped modulo the image's frame count, so a free-running counter is fine |
| const gfx_img_t* | img | Image 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.
Draw an image shrunk by an integer factor
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| const gfx_img_t* | img | Image from the asset pipeline |
| uint16_t | scale | Divisor: 2 is half size, 3 a third. 1 is 1:1. |
Warning — Shrinks only — see gfx_draw_xbm_scaled(), which this wraps.
Render a pre-encoded QR code
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | size | Pixels per QR module — the code ends up `size * modules` square |
| const uint8_t* | qr | Buffer 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.
Draw an indexed duration row — "3 1m 24s", or "3 <1s"
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Text baseline |
| float | len | Duration in seconds |
| uint16_t | idx_l | Index printed before the duration |
Move a position half-way toward a target — one step of an ease-out
| uint16_t* | pos | Position to advance, updated in place |
| uint16_t | target | Where 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.
ui_ease_position() with an adjustable step
| uint16_t* | pos | Position to advance, updated in place |
| uint16_t | target | Where it is heading |
| float | mod | Fraction 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
Vertical scrollbar with a proportional thumb
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge (the track is 8px wide) |
| uint16_t | y | Top edge |
| uint16_t | height | Track height in pixels |
| uint16_t | pos | Zero-based index of the selected item |
| uint16_t | total | Number of items; 0 draws the empty track |
Draw a dot positioned around a circle — a knob indicator
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Centre X of the circle |
| uint16_t | y | Centre Y of the circle |
| float | value | Position around the circle, 0.0-1.0 |
| uint_fast8_t | scale | Radius the dot orbits at |
| uint_fast8_t | size | Radius of the dot itself |
Rounded outline, with a double ring when selected
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Width in pixels |
| uint16_t | h | Height in pixels |
| uint16_t | r | Corner radius |
| bool | active | true adds two outer rings — the firmware's selection cue. They grow *outward*, so leave 2px of margin around the frame. |
Button-hint row: a label with a press/hold marker to its left
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge of the label |
| uint16_t | y | Text baseline |
| bool | hold | true draws the "hold" bar, false the "press" dot |
| const char* | name | Label text |
ui_draw_action() that can render selected, and reports its width
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge of the label |
| uint16_t | y | Text baseline |
| bool | hold | true draws the "hold" bar, false the "press" dot |
| const char* | name | Label text |
| bool | active | true fills a rounded plate behind it and inverts the contents |
| bool | tall | true 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
Fill a region with random pixels — TV static
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Width in pixels |
| uint16_t | h | Height in pixels |
| uint_fast8_t | pixel_size | Vertical 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.
Plot a float buffer as a dotted waveform
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | width | Plot width in pixels; the buffer is resampled to fit |
| uint16_t | height | Plot height in pixels — also sets the amplitude scale |
| const float* | buffer | Sample values, roughly -1.0 to 1.0. NULL is ignored. |
| size_t | size | Number of samples in the buffer |
Copy a string to upper case
| const char* | original_str | Source string |
| char* | uppper | Destination buffer |
| int8_t | len | Size of the destination buffer including the terminator |
Copy a string to lower case
| const char* | original_str | Source string |
| char* | upper | Destination buffer |
| int8_t | len | Size of the destination buffer including the terminator |
ui_draw_frame() over a cleared background — an opaque panel
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Width in pixels |
| uint16_t | h | Height in pixels |
| uint16_t | r | Corner radius |
| bool | active | true adds the outer selection rings |
Note — Use this for anything floating over other content; ui_draw_frame() alone leaves whatever was underneath showing through.
Horizontal bar meter over an integer range
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Outer width in pixels |
| uint16_t | h | Outer height in pixels |
| uint32_t | val | Current value |
| uint32_t | min | Range minimum |
| uint32_t | max | Range maximum |
Slim float bar meter, drawn in the lower half of its box
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Outer width in pixels |
| uint16_t | h | Outer height in pixels — the bar itself is about an eighth of it |
| float | val | Current value |
| float | min | Range minimum |
| float | max | Range maximum |
Rounded box with a black / white / black border stack
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Width in pixels |
| uint16_t | h | Height in pixels |
| uint16_t | round | Corner radius |
Note — Leaves the draw colour at 1.
Draw an icon offset by half its own size
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | x | Reference X |
| uint16_t | y | Reference Y |
| const gfx_img_t* | icon | Image 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.
Draw a string centred on the screen, nudged right if it has an icon
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | y | Text baseline |
| const char* | text | Label text |
| const gfx_img_t* | icon | Icon 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
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* restrict | buff | Destination buffer |
| uint32_t | size | Size of the destination buffer |
| float | val | Position in tape positions (SD sectors), as tape_get_position() / 64 returns |
| TimeFormatStyle | style | TIME_FORMAT_COMPACT or TIME_FORMAT_VERBOSE |
Note — Compact output drops leading zero fields — a sub-minute value comes out "SS.ss".
Fill a rectangle with a line/dot texture instead of a dither pattern
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | w | Width in pixels |
| uint16_t | h | Height in pixels |
| uint_fast8_t | pattern | Texture: 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_t | density | 1-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.
Single horizontal level bar
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | width | Full-scale width in pixels |
| uint16_t | height | Box height; the bar is half of it |
| const float* | values | Two floats 0.0-1.0; the louder of the two is drawn |
| bool | frame | Unused, kept for symmetry with ui_draw_volume_stereo() |
Two stacked level bars, left over right
| gfx_t* | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | width | Full-scale width in pixels |
| uint16_t | height | Box height; each bar is a third of it |
| const float* | values | Two floats 0.0-1.0 — `values[0]` left, `values[1]` right |
| bool | frame | true clears a backing plate first, so the meter stays legible over content |
Draw word-wrapped text
| gfx_t* restrict | gfx | Graphics context |
| const char * | str | Text to draw; "\n" forces a break |
| uint_fast8_t | max_chr_per_line | Wrap width in characters |
| uint32_t | x | Left edge |
| uint32_t | y | Baseline 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.
Count the lines ui_draw_str_multi_line() would produce
| const char* | str | Text to measure |
| uint_fast8_t | max_chr_per_line | Wrap width in characters |
Returns — Number of lines
Note — Use it to size a panel before you draw the text into it.
Word-wrapped text revealed a character at a time — the typewriter effect
| gfx_t* restrict | gfx | Graphics context |
| uint_fast8_t | x | Left edge |
| uint_fast8_t | y | Baseline of the first line |
| uint16_t | count | How many characters to reveal; raise it each frame to type the text out |
| uint16_t | max_chars_per_line | Wrap width in characters |
| const char* restrict | text | Text to draw |
Draw an empty speech bubble — rounded panel plus a tail below it
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | x | Left edge |
| uint16_t | y | Top edge |
| uint16_t | width | Bubble width in pixels |
| uint16_t | height | Bubble 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.
Filled button, 24px tall, with centred text or an icon
| gfx_t* restrict | gfx | Graphics context |
| const uint16_t | x | Left edge |
| const uint16_t | y | Top edge |
| const uint16_t | w | Button width in pixels |
| const char* | text | Label, centred; ignored when `icon` is non-NULL |
| const gfx_img_t* | icon | Icon to draw instead of the label, or NULL |
| const bool | pressed | true shifts the face 2px up-left for the pressed look |
ui_draw_button() with the fill and outline swapped
| gfx_t* restrict | gfx | Graphics context |
| const uint16_t | x | Left edge |
| const uint16_t | y | Top edge |
| const uint16_t | w | Button width in pixels |
| const char* | text | Label, centred; ignored when `icon` is non-NULL |
| const gfx_img_t* | icon | Icon to draw instead of the label, or NULL |
| const bool | pressed | true shifts the face 2px up-left for the pressed look |
On/off toggle
| gfx_t* restrict | gfx | Graphics context |
| uint16_t | x | Reference X — the switch body starts 25px to the right of it |
| uint16_t | y | Reference Y — the body sits *above* it, from y-17 to y+2 |
| bool | val | Switch state |
| bool | detailed | true marks the body O for off and I for on |