UI development
A VML program is not limited to printing text - it has a complete set of phone UI interfaces: open a window, draw, receive touch and key input, play sound, vibrate, save data. This page lists every interface, each with a one-line description of how to use it and an example.
The smallest program
#include <vml_ui.h>
int main(void) {
int w = ui_scr_get_w(), h = ui_scr_get_h(); /* (1) ask for the usable drawing area first */
ui_win_open("Demo", w, h); /* (2) then open the window at that size */
ui_2d_clear(0xFF101020);
ui_2d_text(20, 40, "Hello", 0xFFFFFFFF, 20, VML_ANCHOR_CENTER);
ui_2d_present(); /* (3) this frame is finished */
int m[4];
while (ui_win_is_closed() == 0) { /* (4) main loop: receive -> handle -> redraw */
if (ui_msg_wait(m, 0) == VML_MSG_TOUCHDOWN) { /* handle m[1], m[2] */ }
}
return 0;
}
Four bones: ask for the size -> open the window -> main loop (receive / handle / ui_2d_present) -> the user closes the window and you exit. Below, every interface is listed by category; the way you call them is the same in every language, only the syntax differs (see "27 languages").
The authoritative source for the signatures is
Lib/c/vml_ui.h, and the implementation isLib/shared/src/vmlui.c- all 27 front ends share one implementation. The signatures on this page come from that header; change an interface and re-running the generator is enough.
How the interfaces are layered
They fall into four layers by purpose. When you are writing a program, finding an interface from this diagram is faster than scanning the list:
┌─ Window / device ────────────────────────────────────────────────┐
│ open a window ui_win_open(_ex / _pc) ask size ui_scr_get_w/h │
│ orientation ui_scr_get_orientation lock ui_scr_lock_orientation │
│ immersive ui_scr_set_immersive keep awake ui_scr_set_keep_on │
│ sensors ui_sensor_read(_available/_rate / _calibrate) │
│ battery ui_battery_read power saving ui_power_is_saving │
└───────────────────────────▼──────────────────────────────────────┘
│ after the window is open
┌─ Input ──────────────────────────────────────────────────────────┐
│ event-driven (main loop) ui_msg_wait / ui_msg_read + ui_msg_get_a/b │
│ polling (feel) ui_touch_fetch / ui_key_is_down │
└───────────────────────────▼──────────────────────────────────────┘
│ input arrives -> decide what to draw
┌─ Drawing ────────────────────────────────────────────────────────┐
│ shapes ui_2d_rect / ui_2d_circle / ui_2d_path / ui_2d_text ... │
│ drawing state ui_clip_* / ui_mask_* / ui_2d_set_alpha │
│ resources ui_2d_create_block (sprites) / ui_2d_gradient / ui_brush_* │
│ pixels ui_2d_get_pixel / ui_2d_flood_fill / ui_scr_screenshot │
└───────────────────────────▼──────────────────────────────────────┘
│ used together
┌─ System ─────────────────────────────────────────────────────────┐
│ dialogs ui_dlg_* sound & vibration ui_audio_beep / ui_vibrate_simple │
│ save data ui_store_* arguments ui_arg_get_count / ui_arg_get │
│ clipboard ui_clipboard_* share ui_share_text / ui_url_open │
└──────────────────────────────────────────────────────────────────┘
A one-line mnemonic: Window decides how large you can draw, Input tells you what the user did, Drawing puts things on the screen, and System covers everything outside the game itself (dialogs, sound, save data).
Why sprites should be "vector blocks"
Anything that moves (the player, a bird, a banana, ...) is best made into a block (ui_2d_create_block -> ui_2d_draw_block):
- The shape is written once, and it can be rotated, scaled and mirrored when pasted (a negative scale is a mirror);
- Per frame, "a dozen drawing calls" collapse into "one block paste". Measured with 300 sprites x 60 frames: drawing directly 3531ms -> pasting blocks 2945ms (a 17% gain), and the gap grows the more sprites there are.
Which layer these interfaces live in (where to look when something breaks)
your program (C / BASIC / Python / ... 27 languages)
│ #include <vml_ui.h>
▼
Lib/c/vml_ui.h <- the authoritative source of the signatures
Lib/shared/src/vmlui.c <- the C wrapper: one line of asm("SYSCALL #5xx")
│ syscall 500-599
▼
VmlHostRuntime (UI/Shared) <- **all of the logic lives in this one file**
│ the phone and the desktop **compile the same file**: coordinate clamping,
│ the message queue, the drawing state machine, ...
│ IVmlHost (only the dozen or so things that really differ per platform)
▼
phone MauiVmlHost │ desktop CliVmlHost (scripts/vmlcli)
native dialogs / save │ scripted input / write PNG
data / audio │
What is this good for:
- The two platforms behave identically (one implementation) => what you verified on the desktop is exactly what you get on the phone;
- If "right on the phone, wrong on the desktop" ever happens, it is certainly in the **
IVmlHost** layer (the bottom cell), and there is no need to dig through the drawing code; - The desktop
scripts/vmlcliis an acceptance harness - run a change there first (it takes seconds); do not jump straight to building an APK (a compile on the phone takes over a minute).
Window
Opening, asking the size, closing, screen orientation - [help:vml/ui/window](help:vml/ui/window)
| Interface | In one line |
|---|---|
| ui_scr_get_orientation | Device orientation: VML_ORIENT_PORTRAIT(0) / VML_ORIENT_LANDSCAPE(1). |
| ui_scr_get_language | Interface language: VML_LANG_ZH(0) / VML_LANG_EN(1); it follows the system language (not an in-app switch, and there is no third option). |
| ui_scr_get_h | The height of the usable drawing area. |
| ui_scr_get_w | The width of the usable drawing area (you can ask before opening a window too). |
| ui_scr_get_h_ex | The height counterpart of ui_scr_get_w_ex - the same "declare the configuration first, then ask for the size". |
| ui_scr_get_w_ex | Ask for the width of the usable drawing area under a declared chrome configuration (askable before opening a window). chrome is a bitmask: |
| ui_win_close | Close the window (for a program that ends on its own). |
| ui_win_is_closed | Whether the user has already closed the window (the exit condition of the main loop). |
| ui_win_open | Open a drawing window. Ask ui_scr_get_w/h() for the usable drawing area first and open at that size - a hard-coded size overflows on a small screen. |
| ui_win_open_ex | Same as above, plus two declarations that take effect before the window opens: the rotation policy, and whether you want a gamepad area. |
| ui_win_open_pc | Open a "computer screen" window: the old DOS/BGI model of "character grid + mouse" (it wants a keyboard and a mouse, not the phone gamepad). |
/* Example: ui_scr_get_orientation */
if (ui_scr_get_orientation() == VML_ORIENT_LANDSCAPE) { /* lay it out sideways */ }
Receiving messages
How touch / key / timer messages are received - [help:vml/ui/messages](help:vml/ui/messages)
| Interface | In one line |
|---|---|
| ui_msg_get_a | The first argument of the current message (the x of a touch, the key code of a key, the id of a timer, ...). |
| ui_msg_get_b | The second argument of the current message (the y of a touch, ...). |
| ui_msg_clear | Clear the message queue (use it when switching scenes or starting a new round, so a keypress from the previous round is not swallowed). |
| ui_msg_drop | Drop one kind of unconsumed message from the queue -> the number discarded (-1 = unknown kind, nothing discarded). |
| ui_msg_get_count | How many messages are still queued up (worth a glance when you want to drop a backlog). |
| ui_msg_get_type | The type of the current message (so you do not have to keep msg[0] in your head). |
| ui_msg_read | Does not wait: returns VML_MSG_NONE when there is no message. Use this for continuous animation (together with your own pacing); |
| ui_msg_read_ex | The version of ui_msg_read that takes "keep it after reading or not". |
| ui_msg_fetch | The pointer-free way to fetch a message (for languages that cannot take an array pointer): returns VML_MSG_NONE when there is none, |
| ui_msg_wait | Wait for one message; the argument is int msg[4]. Returns the message type (see the table below); timeout=0 means wait forever. |
| ui_msg_wait_ex | Same as above, but the third argument decides whether the message stays in the queue after it is read (VML_MSG_KEEP / VML_MSG_CONSUME). |
| ui_msg_fetch_wait | The pointer-free way to wait for a message: returns the type when one arrives, VML_MSG_NONE on timeout. |
/* Example: ui_msg_get_a */
int x = ui_msg_get_a();
Multi-touch and the device
Multi-finger queries, hold detection, orientation lock, full screen - [help:vml/ui/device](help:vml/ui/device)
| Interface | In one line |
|---|---|
| ui_audio_is_playing | Is the background BGM still playing -> 1/0 (never played, finished playing and stopped all give 0). |
| ui_scr_set_immersive | Hide / restore the status bar and navigation bar (full-screen games). |
| ui_key_is_down | Whether a given key is held down right now ("hold left to keep moving" without maintaining your own state table). |
| ui_scr_lock_orientation | Lock the screen orientation: VML_LOCK_PORTRAIT(0) / VML_LOCK_LANDSCAPE(1) / VML_LOCK_AUTO(2). |
| ui_touch_read | Query finger number slot (0..9): writes out[0]=x out[1]=y out[2]=pressed and returns 1 when valid (0 when the slot is out of range). |
| ui_touch_is_down | The "pressed" value (1/0) cached by the last ui_touch_fetch. |
| ui_touch_fetch | For languages that cannot take a pointer: query once and cache it; the three below read that cache (the same division of labor as ui_msg_fetch_wait + ui_msg_get_a/b). |
| ui_touch_get_x | The x cached by the last ui_touch_fetch. |
| ui_touch_get_y | The y cached by the last ui_touch_fetch. |
| ui_sensor_read | Sensors: read the accelerometer / gyroscope / fused attitude into out[0..2]. For tilt controls use the accelerometer. |
| ui_sensor_is_available | Whether this device has that sensor -> 1/0 (you can ask before opening a window). |
| ui_sensor_set_rate | Set the sampling interval in milliseconds - a sensor draws battery the moment it is on. |
| ui_sensor_calibrate | Treat the current attitude as zero (when the player is lying down, "level" is wrong). |
| ui_sensor_fetch | Query once and cache it (1 = valid, 0 = that sensor is absent); the three below read the cache (the same division of labor as ui_touch_fetch). |
| ui_sensor_get_x | The x cached by the last ui_sensor_fetch. |
| ui_sensor_get_y | The y cached by the last ui_sensor_fetch. |
| ui_sensor_get_z | The z cached by the last ui_sensor_fetch. |
| ui_battery_read | Battery and charging: out[0] = 0-100, out[1] = charging. Returning 0 means this device has no battery. |
| ui_battery_fetch | Query the battery once and cache it (1 = valid, 0 = no battery); the two below read the cache (the same division of labor as ui_touch_fetch). |
| ui_battery_get_level | The level cached by the last ui_battery_fetch (0-100). |
| ui_battery_is_charging | The charging flag cached by the last ui_battery_fetch (1/0). |
| ui_power_is_saving | Is the system power-saving mode on -> 1/0 (not the same thing as a low battery). |
| ui_power_read | Generic entry: op is VML_POWER_OP_BATTERY(0) / VML_POWER_OP_SAVER(1), and the result goes into out. |
| ui_camera_capture | Take a photo; writes a sandbox-relative path into buf and returns the byte count. Cancel / no permission / no camera all give 0. |
| ui_camera_pick | Pick one from the gallery - same semantics and return as ui_camera_capture (the system photo picker needs no camera permission). |
| ui_dlg_file_open | System file picker; the chosen file is copied into the sandbox and the sandbox-relative path is returned (byte count); 0 on cancel / unsupported. |
| ui_dlg_file_save | Let the user choose where to save (suggested is a suggested name, may be empty) and return a sandbox-relative path. It does not write the file. |
| ui_gps_is_available | Can this device locate at all -> 1/0 (askable before fetching; no permission counts as 0). |
| ui_gps_fetch | Fetch a fix once (timeout_ms milliseconds, 0 = platform default) -> 1 = got one / 0 = did not (timeout / no permission / no GPS). |
| ui_gps_get_lat_e6 | Latitude x 1e6 (31523000 = 31.523 deg) - the last ui_gps_fetch result; 0 if never fetched. Fixed point, not a float. |
| ui_gps_get_lon_e6 | Longitude x 1e6 - same as above. |
| ui_gps_get_alt_m | Altitude in integer metres - same as above (0 when there is no altitude data). |
| ui_gps_get_accuracy_m | Horizontal accuracy in integer metres - same as above (smaller is better). |
/* Example: ui_audio_is_playing */
ui_audio_play("bgm.mp3", 0);
while (ui_audio_is_playing()) { ui_msg_wait(msg, 200); }
Error handling
Why the last call failed (cancelled / no permission / this platform does not have the capability) - [help:vml/ui/device](help:vml/ui/device)
| Interface | In one line |
|---|---|
| ui_error_get_code | Error code of the most recent failure (VML_E_*); **0 (VML_E_OK) when there is no error**. |
| ui_error_get_message | A human-readable description of the most recent failure, written into buf (at most cap bytes including the terminating 0); returns the byte count, or 0 and an empty string when there is no error. cap = 0 asks for the length only. |
char img[256];
if (ui_camera_capture(img, sizeof(img)) > 0) {
ui_2d_image(0, 0, img, 320, 240);
} else if (ui_error_get_code() != VML_E_CANCELLED) {
char why[64];
ui_error_get_message(why, sizeof(why)); /* plain words, e.g. "no permission" */
ui_dlg_msg("Could not get an image", why, VML_DLG_WARN);
}
Camera / gallery, the file pickers and GPS all return 0 on any failure, which used to conflate "the user cancelled", "there is no permission" and "this platform does not have it". Read the code with ui_error_get_code() to tell them apart:
| Value | Name | Meaning |
|---|---|---|
| 0 | VML_E_OK | no error (the last call succeeded) |
| 1 | VML_E_CANCELLED | the user cancelled - not a failure |
| 2 | VML_E_PERMISSION | permission denied (or "do not ask again") |
| 3 | VML_E_UNSUPPORTED | this platform has no such capability (no camera / GPS / sensor) |
| 4 | VML_E_TIMEOUT | timed out |
| 5 | VML_E_FAILED | anything else (host error / exception / missing file) |
Note: the slot describes "the call just made" - any other ui_* call clears it back to 0, so read it immediately after the failure. The two readers above never clear it themselves, so asking for the code and then for the message still describes the same failure.
Timers and random numbers
Repeating timers, reading the time, random numbers - [help:vml/ui/timer](help:vml/ui/timer)
| Interface | In one line |
|---|---|
| ui_math_rand | Random number: ui_math_rand(n) -> 0..n-1 (returns 1 when n <= 0; it does not crash). |
| ui_time_get_ms | Milliseconds since boot (for computing frame intervals and driving animation yourself). |
| ui_timer_kill | Stop a timer. |
| ui_timer_set | Start a repeating timer that sends a VML_MSG_TIMER every N milliseconds (msg[1] is the id you gave). |
/* Example: ui_math_rand */
int n = ui_math_rand(6); /* 0..5 */
int side = ui_math_rand(2); /* 0 or 1 */
Drawing
Points, lines, shapes, polygons, paths, gradients, images - [help:vml/ui/draw](help:vml/ui/draw)
| Interface | In one line |
|---|---|
| ui_brush_linear | Build a linear gradient brush. The geometry is in thousandths (0..1000) relative to the shape's own bounding box: 0,0,1000,0 = left to right. |
| ui_brush_named | Get a handle to a gradient already defined with ui_2d_gradient, looked up by name. |
| ui_brush_radial | Build a radial gradient brush (center -> edges). cx cy r are in thousandths too; 500,500,500 = centered. |
| ui_brush_solid | Build a solid-color brush. Returns a handle (>= 1; 0 = failure) for ui_brush_set_fill / ui_brush_set_pen to use. |
| ui_2d_circle | Draw a circle; a non-zero fill fills it. |
| ui_2d_circle_grad | A gradient circle (define the gradient with ui_2d_gradient first). |
| ui_2d_clear | Fill the whole screen with one color (call it at the start of each frame). Colors are always 0xAARRGGBB. |
| ui_2d_shape_circle | Draw a circle with the current brush. |
| ui_2d_shape_ellipse | Draw an ellipse with the current brush. |
| ui_2d_shape_heart | Draw a heart with the current brush. |
| ui_2d_shape_line | Draw a straight line with the current pen. |
| ui_2d_shape_path | Draw an SVG path with the current brush (M L C Q A Z; upper case is absolute and lower case relative; multiple subpaths punch holes by the even-odd rule). |
| ui_2d_shape_pie | Draw a pie wedge with the current brush: radius / start angle / end angle in degrees. |
| ui_2d_shape_poly | Draw a polygon with the current brush (close=1 closes it automatically) or a polyline (close=0). pts holds one point per two ints and count is the number of points. |
| ui_2d_shape_rect | Draw a rectangle with the current brush (radius > 0 gives rounded corners). |
| ui_2d_shape_regular | Draw a regular polygon with the current brush: radius / number of sides / rotation in degrees. |
| ui_2d_shape_ring | Draw a ring with the current brush (outer radius / inner radius; the hole in the middle is punched by the even-odd rule). |
| ui_2d_shape_star | Draw a star with the current brush: outer radius / inner radius / number of points / rotation in degrees. |
| ui_2d_shape_text | Draw one line of text with the current text attributes (ui_text_set_font). |
| ui_2d_ellipse | Draw an ellipse (rx / ry are the two radii). |
| ui_2d_ellipse_grad | An ellipse with a gradient fill. It belongs with ui_2d_rect_grad / ui_2d_circle_grad (that batch of interfaces missed the ellipse at the time). |
| ui_2d_gradient | Define a gradient brush under a name (a string id); afterwards ui_2d_rect_grad / ui_2d_circle_grad / ui_2d_path refer to it by that name. |
| ui_2d_icon | Draw a built-in icon (looked up by name, so you do not have to draw it yourself). |
| ui_2d_image | Draw an image (PNG / JPG / BMP) at the given position; pass 0 for w / h to use the original size. |
| ui_2d_line | Draw a line; lw is the line width. |
| ui_2d_path | Draw using SVG path syntax (M L H V C S Q T A Z; upper case is absolute and lower case relative). |
| ui_2d_pixel | Draw a single point. |
| ui_2d_polygon | Draw a polygon (closed automatically). pts is an int array with one point per two ints; count is the number of points. |
| ui_2d_polyline | A polyline (not closed). The arguments mean the same as ui_2d_polygon (stroke is the color, width the line width). |
| ui_2d_present | This frame is finished. The single most important call in the whole loop - nothing on screen updates without it. |
| ui_2d_rect | Draw a rectangle. A non-zero fill fills it; radius is the corner radius. |
| ui_2d_rect_grad | A rectangle with a gradient (define the gradient with ui_2d_gradient first). |
| ui_brush_set_fill | Set the fill brush. You can pass a brush handle or a color; 0 = do not fill (hollow). |
| ui_brush_set_pen | Set the pen (the stroke) = brush + width + line cap + dash + arrow. 0 = do not stroke. |
| ui_text_set_brush | Set the text brush (together with ui_text_set_font + ui_2d_shape_text). 0 = go back to the color given to ui_text_set_font. |
| ui_color_rgb | Pack r / g / b into the 0xAARRGGBB the ui_* functions want (alpha forced to FF = opaque); the components are clamped to 0..255. |
| ui_color_argb | Same as above, with an alpha as well (0..255; 0xFF = opaque, 0x00 = fully transparent). |
/* Example: ui_brush_linear */
int b = ui_brush_linear(0xFFFF3020, 0xFF2050FF, 0, 0, 1000, 0);
ui_brush_set_fill(b);
Drawing state
Clipping, masks (with boolean operations), alpha, resource counts - [help:vml/ui/gfx](help:vml/ui/gfx)
| Interface | In one line |
|---|---|
| ui_2d_set_alpha | Global alpha 0..255, in effect for shapes drawn after it. |
| ui_brush_reset | Release every brush / gradient definition this window has accumulated (it can only reset as a whole - a handle is an index into the table, so deleting one entry shifts every entry after it). |
| ui_clip_pop | Pop one level of clipping (popping an empty stack does nothing). |
| ui_clip_push | Push one level of rectangular clipping: from then on, what you draw is only visible inside that rectangle; levels can nest (intersected with the one above). |
| ui_clip_reset | Clear the entire clipping stack (not pop one level) - for "recovering from an error": call it once on entering the main loop and you are back to a clean full-screen state. |
| ui_layer_begin | Start collecting a layer: shapes drawn meanwhile land on a temporary canvas, and the whole layer is composited when ui_layer_end arrives. |
| ui_layer_end | Finish collecting and composite the whole layer with alpha (0..255). |
| ui_mask_begin | Start collecting mask shapes: shapes drawn meanwhile do not go to the screen, they only serve as the mask. Finish with ui_mask_end / ui_mask_end_ex. |
| ui_mask_clear | Cancel the mask: everything drawn afterwards is visible. |
| ui_mask_end | Accept the mask and make it the current one. inside: 1 = draw only inside the shape / 0 = only outside it. |
| ui_mask_end_ex | Accept the mask and combine it with the current one by the boolean op - the old way needed two passes for a "ring", now one is enough. |
| ui_mask_path | Export shape number idx of segment seg as an SVG path into buf - a program can use it to stroke the rim of a hole or to draw an outer glow. |
| ui_mask_get_seg_count | How many segments the current mask has (0 when there is no mask). |
| ui_mask_get_seg_op | The operator used by segment seg (-1 when out of range). |
| ui_mask_get_shape_count | How many shapes there are in segment seg (0 when out of range). |
| ui_mask_is_inside | Use the mask as a collider: is this point inside the current mask (1/0). |
| ui_res_get_count | Query current usage: 0 shapes / 1 images / 2 vector blocks / 3 brushes and gradients; -1 when unknown. |
/* Example: ui_2d_set_alpha */
ui_2d_set_alpha(120);
ui_2d_rect(10, 10, 100, 60, 0xFF000000, 1, 0, 0); /* semi-transparent black */
ui_2d_set_alpha(255);
Reading pixels back
Read pixels, flood fill, capture and paste images, screenshots - [help:vml/ui/pixel](help:vml/ui/pixel)
| Interface | In one line |
|---|---|
| ui_2d_flood_fill | Flood from (x,y) and stop at the border color (four-connected). Returns the number of rectangle runs written; 0 = nothing filled |
| ui_2d_get_image | Save a piece of the screen -> a handle (>= 1), 0 on failure. An old program's malloc(imagesize(...)) can still be written as before; we just do not use that memory. |
| ui_2d_free_image | Release an image stored by ui_2d_get_image (the handle is recycled). Returns 1 on success / 0 on failure. |
| ui_2d_get_pixel | Read the color of one pixel (0xRRGGBB; -1 when out of range). |
| ui_2d_put_image | Paste the handle's content at (x,y). mode: 0 = COPY, paste directly / 1 = XOR (XOR has to read the destination pixels first, so it is slower). |
| ui_2d_put_qb_bitmap | Paste a **QBasic bitmap hand-packed into DATA** at (x,y). Sprites in old BASIC games were not captured with GET - they are packed into DATA in QBasic's GET/PUT array format, READ into a long-integer array and then PUT (GORILLA.BAS's LBan& is exactly this), and ui_2d_put_image accepts only handles => not one of them can be drawn. |
| ui_scr_screenshot | Save the current window as a PNG (the path is relative to the workspace; without a path a name is generated under shot/). Returns 1 on success. |
/* Example: ui_2d_flood_fill */
ui_2d_flood_fill(50, 50, 0xFF00FF00, 0xFFFF0000); /* flood green inside a red frame */
Vector blocks
Record once, paste over and over with rotation and scaling (this is what sprites use) - [help:vml/ui/block](help:vml/ui/block)
| Interface | In one line |
|---|---|
| ui_2d_create_block | Start recording a block. What is recorded is drawing commands, not pixels - so it can be rotated and scaled when pasted, and does not blur when enlarged. |
| ui_2d_draw_block | Paste a block. (x,y) is the center of the block and it rotates about that center; the scale is in thousandths (1000 = original size) and the angle is in degrees. |
| ui_2d_draw_block_at | Same as above, but (x,y) is the block's top-left corner and it rotates about that corner. |
| ui_2d_end_block | Finish recording and return the block handle (>= 1; 0 = nothing was recorded). |
| ui_2d_free_block | Release a block (the handle is recycled). Returns 1 on success / 0 on failure (the handle does not exist or was already released). |
/* Example: ui_2d_create_block */
bid = ui_2d_create_block(30, 20, 0); /* a 30x20 block */
Text
Writing, fonts, anchors - [help:vml/ui/text](help:vml/ui/text)
| Interface | In one line |
|---|---|
| ui_text_set_font | Set the font once; every later ui_2d_text_cur uses it (so you do not repeat four arguments every time). |
| ui_text_set_valign | Set the default vertical alignment (in effect for all text after it). |
| ui_2d_text | Write one line of text at (x,y). size is the font size; anchor decides which side of the text (x,y) refers to (VML_ANCHOR_LEFT / CENTER / RIGHT). |
| ui_2d_text_cur | Write text with the font set by ui_text_set_font. |
| ui_2d_text_styled | Same as above, plus styles (bold / italic / underline). |
| ui_2d_text_v | Text with vertical alignment (VML_VALIGN_*) - the y of ui_2d_text is the baseline, while this one can align to top / middle / bottom. |
/* Example: ui_text_set_font */
ui_text_set_font(18, VML_FONT_BOLD, 0xFFFFFFFF, VML_ANCHOR_CENTER);
ui_2d_text_cur(180, 40, "Press a direction key to exit");
Piece tiles
Cut an image into cells and paste them over and over - [help:vml/ui/piece](help:vml/ui/piece)
| Interface | In one line |
|---|---|
| ui_piece_get_cell | Get one cell of a piece: pid is the piece number, rot the rotation, which which cell. |
| ui_piece_init | Initialize the piece tile table (the no-argument version; for the actual shapes see Lib/shared/src/vmlui.c). |
/* Example: ui_piece_get_cell */
ui_piece_get_cell(0, 0, 3); /* piece 0, no rotation, cell 3 */
Integer grid
Two-dimensional state such as a board or a map - [help:vml/ui/grid](help:vml/ui/grid)
| Interface | In one line |
|---|---|
| ui_grid_clear | Clear the grid. Use it for two-dimensional state such as a board or a map - it is more reliable than the language's own arrays (some front ends cannot read back what they wrote; see the per-language pitfalls in "27 languages"). |
| ui_grid_get | Read one cell; returns 0 if it was never written. |
| ui_grid_set | Write one cell; i is a one-dimensional index (row * width + col). |
/* Example: ui_grid_clear */
ui_grid_clear();
Dialogs
Messages, single choice, multiple choice, text input - [help:vml/ui/dialog](help:vml/ui/dialog)
| Interface | In one line |
|---|---|
| ui_dlg_input | Ask for one line of text input. The result is written into the buffer you pass. |
| ui_dlg_msg | Show a message box (pass 0 for style). It blocks until the user dismisses it - just right for reporting a result at the end of a game. |
| ui_dlg_multi | A multiple-choice dialog: opts is a string of options, n the number of options. Returns the number selected. |
| ui_dlg_select | A single-choice dialog: opts is a string of options, n the number of options, def the default selection. Returns the selected index (-1 = cancelled). |
/* Example: ui_dlg_input */
char buf[64];
ui_dlg_input("Rename", "New name:", buf, 64);
Sound and haptics
Synthesized tones, audio files, vibration, keeping the screen awake - [help:vml/ui/feel](help:vml/ui/feel)
| Interface | In one line |
|---|---|
| ui_audio_beep | Synthesize a tone on the spot (no audio file needed): freq in hertz, ms in milliseconds. Single channel, so firing several in a row leaves only the last one audible - and that is exactly why it never crackles: an event always has exactly one tone sounding. Use this for game sound effects. |
| ui_tone_on | Polyphonic: start a note (note number 0-127, A4 = 69). Start several channels at once and you get a chord. |
| ui_tone_on_ms | Note-on with a duration: it releases automatically after ms milliseconds (saves a ui_tone_off, and covers "forgot to stop it"). |
| ui_tone_off | Stop one note (note = -1 stops every note on that channel). |
| ui_tone_off_all | Stop everything (with a fade-out). |
| ui_tone_get_voices | How many voices are sounding right now - use it to check whether your chords are stacking up. |
| ui_tone_panic | Stop immediately (no fade-out) - for a mute button or a forced stop. |
| ui_tone_set_wave | Set the waveform of a channel (sine / square / saw / triangle) - give the bass a triangle wave in a chord and it stops sounding muddy. |
| ui_tone_set_max_voices | The maximum number of simultaneous voices (1-32, default 32). |
| ui_sfx_add | Sequencer: enqueue a note (it sounds after delay beats and lasts dur beats). Warning: do not use this for game sound effects (stacked voices and long tails crackle on real devices; all 28 games in this repository have been moved back to ui_audio_beep) - see which sound effect to use. |
| ui_sfx_tick | Call once per frame; it advances by real elapsed time (put it in the main loop). |
| ui_sfx_step | Advance one beat: when delay reaches 0 it sends note_on, and when dur runs out it sends note_off and frees the slot. |
| ui_sfx_panic | Mute immediately and clear the table (on exit or when starting a new round). |
| ui_sfx_get_active | How many slots are still occupied - after a round finishes this should be back to 0, so use it as a self-check. |
| ui_sfx_reset | Clear the table (makes no sound). Only clearing without stopping the notes = whatever is sounding is left unattended until the program ends; to mute, use ui_sfx_panic() (it stops everything first, then clears - the order must not be reversed). |
| ui_audio_play | Play an audio file (mp3/wav/...); a non-zero loop repeats it (for BGM). |
| ui_audio_stop | Stop the audio that is playing. |
| ui_audio_set_volume | Overall volume 0-100 (in effect for audio played after it). |
| ui_audio_is_playing | Is the background BGM still playing -> 1/0. |
| ui_scr_set_keep_on | Screen-awake switch (every game should turn this on). |
| ui_vibrate_simple | Vibrate: ms milliseconds, strength intensity. |
| ui_vibrate_pattern | Vibrate in a rhythm (an int array: odd indices are silent, even indices are on). |
/* Example: ui_audio_beep */
ui_audio_beep(880, 80); /* one line cleared */
ui_audio_beep(1568, 160); /* four lines cleared, a higher pitch */
Clipboard and sharing
Share a score, store and load a save code (encode a whole game state into a short piece of text so the player can keep it in their notes and continue on another device) - [help:vml/ui/device](help:vml/ui/device)
| Interface | In one line |
|---|---|
| ui_clipboard_set | Write the system clipboard: 1 = handed to the system / 0 = this platform has no clipboard. |
| ui_clipboard_get | Read the clipboard: returns the number of bytes written / -1 = empty or unsupported. |
| ui_share_text | Show the system share sheet (an empty title string means none). Warning: it does not block. |
| ui_url_open | Open a link in the system browser. Only http/https. |
char code[64];
ui_clipboard_set("SAVE-1a2b3c"); /* hand the save code to the player */
if (ui_clipboard_get(code, 64) > 0) { /* read it back and carry on playing */ }
ui_share_text("I scored 8 points in the game!", "Share my score");
Warning: returning 0 means "this platform does not have that capability" (the desktop has no share sheet), not that it failed - the game should carry on as usual. Both writing the clipboard and showing the share sheet are asynchronous on the platform, and the host does not wait for a result.
Local save data
Small data such as a high score - [help:vml/ui/store](help:vml/ui/store)
| Interface | In one line |
|---|---|
| ui_store_get | Read a value: it is written into the buffer you pass and the length is returned (-1 when the key does not exist). |
| ui_store_set | Store a value. The value is a string too - to store a number, convert it to a string yourself (there is no sprintf available here). |
| ui_store_del | Delete one entry of save data (for example, "clear the high score"). |
/* Example: ui_store_get */
char buf[16];
if (ui_store_get("high", buf, 16) > 0) best = atoi(buf);
The all-purpose interface
Two strings in, one JSON out - [help:vml/ui/json](help:vml/ui/json)
| Interface | In one line |
|---|---|
| ui_call_json | Two strings in, one JSON string out - use it to query device information and to reach the host's miscellaneous capabilities, |
| ui_call_json_get_at | Get byte number i of the previous result. |
| ui_call_json_get_len | How long the result of the last ui_call_json_s is. |
| ui_call_json_print | Print the whole result of the last ui_call_json_s to stdout (with a newline at the end) - the least hassle for debugging, |
| ui_call_json_s | Same as above, but without a buffer (suits languages that cannot take a pointer); read the result with _len / _at. |
/* Example: ui_call_json */
char buf[512];
ui_call_json("sysinfo", "", buf, 512);
puts(buf); /* {"ok":true,"result":{...}} */
Calling the host
Call a host function by numeric id (typed, with no encode/decode) - [help:vml/ui/call](help:vml/ui/call)
| Interface | In one line |
|---|---|
| callwithdouble4 | Same as above, with 3 double arguments (passed in D1..D3); the return covers D0. |
| callwithfloat8 | Same as above, with 7 float arguments (passed in F1..F7); the return covers F0. |
| callwithint8 | Call a host function by numeric id: v[0] is the call number (see the VML_CALL_* macros), v[1..7] are the arguments, |
| callwithlong4 | Same as above, with 3 long arguments (passed in L1..L3); the return covers L0. |
/* Example: callwithdouble4 */
double v[4];
v[0] = VML_CALL_ECHO_DOUBLE;
v[1] = 1.0; v[2] = 2.0;
double r = callwithdouble4(v);
Command-line arguments
The program's own switches, such as -l - [help:vml/ui/args](help:vml/ui/args)
| Interface | In one line |
|---|---|
| ui_arg_get | Copy argument i into buf and return its length (-1 when out of range). argv[0] is the program name, so the user's first argument is ui_arg_get(1,...). |
| ui_arg_get_count | The number of arguments (including the program name; always >= 1). |
/* Example: ui_arg_get */
char buf[64];
if (ui_arg_get(1, buf, sizeof(buf)) > 0) { /* a -l style switch was used */ }