← Dolaima← Docs indexUI development原文中文

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 is Lib/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):

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:

Window

Opening, asking the size, closing, screen orientation - [help:vml/ui/window](help:vml/ui/window)

InterfaceIn one line
ui_scr_get_orientationDevice orientation: VML_ORIENT_PORTRAIT(0) / VML_ORIENT_LANDSCAPE(1).
ui_scr_get_languageInterface 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_hThe height of the usable drawing area.
ui_scr_get_wThe width of the usable drawing area (you can ask before opening a window too).
ui_scr_get_h_exThe height counterpart of ui_scr_get_w_ex - the same "declare the configuration first, then ask for the size".
ui_scr_get_w_exAsk for the width of the usable drawing area under a declared chrome configuration (askable before opening a window). chrome is a bitmask:
ui_win_closeClose the window (for a program that ends on its own).
ui_win_is_closedWhether the user has already closed the window (the exit condition of the main loop).
ui_win_openOpen 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_exSame 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_pcOpen 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)

InterfaceIn one line
ui_msg_get_aThe 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_bThe second argument of the current message (the y of a touch, ...).
ui_msg_clearClear 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_dropDrop one kind of unconsumed message from the queue -> the number discarded (-1 = unknown kind, nothing discarded).
ui_msg_get_countHow many messages are still queued up (worth a glance when you want to drop a backlog).
ui_msg_get_typeThe type of the current message (so you do not have to keep msg[0] in your head).
ui_msg_readDoes not wait: returns VML_MSG_NONE when there is no message. Use this for continuous animation (together with your own pacing);
ui_msg_read_exThe version of ui_msg_read that takes "keep it after reading or not".
ui_msg_fetchThe 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_waitWait 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_exSame 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_waitThe 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)

InterfaceIn one line
ui_audio_is_playingIs the background BGM still playing -> 1/0 (never played, finished playing and stopped all give 0).
ui_scr_set_immersiveHide / restore the status bar and navigation bar (full-screen games).
ui_key_is_downWhether a given key is held down right now ("hold left to keep moving" without maintaining your own state table).
ui_scr_lock_orientationLock the screen orientation: VML_LOCK_PORTRAIT(0) / VML_LOCK_LANDSCAPE(1) / VML_LOCK_AUTO(2).
ui_touch_readQuery 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_downThe "pressed" value (1/0) cached by the last ui_touch_fetch.
ui_touch_fetchFor 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_xThe x cached by the last ui_touch_fetch.
ui_touch_get_yThe y cached by the last ui_touch_fetch.
ui_sensor_readSensors: read the accelerometer / gyroscope / fused attitude into out[0..2]. For tilt controls use the accelerometer.
ui_sensor_is_availableWhether this device has that sensor -> 1/0 (you can ask before opening a window).
ui_sensor_set_rateSet the sampling interval in milliseconds - a sensor draws battery the moment it is on.
ui_sensor_calibrateTreat the current attitude as zero (when the player is lying down, "level" is wrong).
ui_sensor_fetchQuery 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_xThe x cached by the last ui_sensor_fetch.
ui_sensor_get_yThe y cached by the last ui_sensor_fetch.
ui_sensor_get_zThe z cached by the last ui_sensor_fetch.
ui_battery_readBattery and charging: out[0] = 0-100, out[1] = charging. Returning 0 means this device has no battery.
ui_battery_fetchQuery 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_levelThe level cached by the last ui_battery_fetch (0-100).
ui_battery_is_chargingThe charging flag cached by the last ui_battery_fetch (1/0).
ui_power_is_savingIs the system power-saving mode on -> 1/0 (not the same thing as a low battery).
ui_power_readGeneric entry: op is VML_POWER_OP_BATTERY(0) / VML_POWER_OP_SAVER(1), and the result goes into out.
ui_camera_captureTake a photo; writes a sandbox-relative path into buf and returns the byte count. Cancel / no permission / no camera all give 0.
ui_camera_pickPick one from the gallery - same semantics and return as ui_camera_capture (the system photo picker needs no camera permission).
ui_dlg_file_openSystem 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_saveLet 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_availableCan this device locate at all -> 1/0 (askable before fetching; no permission counts as 0).
ui_gps_fetchFetch a fix once (timeout_ms milliseconds, 0 = platform default) -> 1 = got one / 0 = did not (timeout / no permission / no GPS).
ui_gps_get_lat_e6Latitude 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_e6Longitude x 1e6 - same as above.
ui_gps_get_alt_mAltitude in integer metres - same as above (0 when there is no altitude data).
ui_gps_get_accuracy_mHorizontal 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)

InterfaceIn one line
ui_error_get_codeError code of the most recent failure (VML_E_*); **0 (VML_E_OK) when there is no error**.
ui_error_get_messageA 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:

ValueNameMeaning
0VML_E_OKno error (the last call succeeded)
1VML_E_CANCELLEDthe user cancelled - not a failure
2VML_E_PERMISSIONpermission denied (or "do not ask again")
3VML_E_UNSUPPORTEDthis platform has no such capability (no camera / GPS / sensor)
4VML_E_TIMEOUTtimed out
5VML_E_FAILEDanything 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)

InterfaceIn one line
ui_math_randRandom number: ui_math_rand(n) -> 0..n-1 (returns 1 when n <= 0; it does not crash).
ui_time_get_msMilliseconds since boot (for computing frame intervals and driving animation yourself).
ui_timer_killStop a timer.
ui_timer_setStart 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)

InterfaceIn one line
ui_brush_linearBuild 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_namedGet a handle to a gradient already defined with ui_2d_gradient, looked up by name.
ui_brush_radialBuild a radial gradient brush (center -> edges). cx cy r are in thousandths too; 500,500,500 = centered.
ui_brush_solidBuild a solid-color brush. Returns a handle (>= 1; 0 = failure) for ui_brush_set_fill / ui_brush_set_pen to use.
ui_2d_circleDraw a circle; a non-zero fill fills it.
ui_2d_circle_gradA gradient circle (define the gradient with ui_2d_gradient first).
ui_2d_clearFill the whole screen with one color (call it at the start of each frame). Colors are always 0xAARRGGBB.
ui_2d_shape_circleDraw a circle with the current brush.
ui_2d_shape_ellipseDraw an ellipse with the current brush.
ui_2d_shape_heartDraw a heart with the current brush.
ui_2d_shape_lineDraw a straight line with the current pen.
ui_2d_shape_pathDraw 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_pieDraw a pie wedge with the current brush: radius / start angle / end angle in degrees.
ui_2d_shape_polyDraw 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_rectDraw a rectangle with the current brush (radius > 0 gives rounded corners).
ui_2d_shape_regularDraw a regular polygon with the current brush: radius / number of sides / rotation in degrees.
ui_2d_shape_ringDraw 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_starDraw a star with the current brush: outer radius / inner radius / number of points / rotation in degrees.
ui_2d_shape_textDraw one line of text with the current text attributes (ui_text_set_font).
ui_2d_ellipseDraw an ellipse (rx / ry are the two radii).
ui_2d_ellipse_gradAn 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_gradientDefine 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_iconDraw a built-in icon (looked up by name, so you do not have to draw it yourself).
ui_2d_imageDraw an image (PNG / JPG / BMP) at the given position; pass 0 for w / h to use the original size.
ui_2d_lineDraw a line; lw is the line width.
ui_2d_pathDraw using SVG path syntax (M L H V C S Q T A Z; upper case is absolute and lower case relative).
ui_2d_pixelDraw a single point.
ui_2d_polygonDraw a polygon (closed automatically). pts is an int array with one point per two ints; count is the number of points.
ui_2d_polylineA polyline (not closed). The arguments mean the same as ui_2d_polygon (stroke is the color, width the line width).
ui_2d_presentThis frame is finished. The single most important call in the whole loop - nothing on screen updates without it.
ui_2d_rectDraw a rectangle. A non-zero fill fills it; radius is the corner radius.
ui_2d_rect_gradA rectangle with a gradient (define the gradient with ui_2d_gradient first).
ui_brush_set_fillSet the fill brush. You can pass a brush handle or a color; 0 = do not fill (hollow).
ui_brush_set_penSet the pen (the stroke) = brush + width + line cap + dash + arrow. 0 = do not stroke.
ui_text_set_brushSet 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_rgbPack r / g / b into the 0xAARRGGBB the ui_* functions want (alpha forced to FF = opaque); the components are clamped to 0..255.
ui_color_argbSame 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)

InterfaceIn one line
ui_2d_set_alphaGlobal alpha 0..255, in effect for shapes drawn after it.
ui_brush_resetRelease 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_popPop one level of clipping (popping an empty stack does nothing).
ui_clip_pushPush 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_resetClear 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_beginStart collecting a layer: shapes drawn meanwhile land on a temporary canvas, and the whole layer is composited when ui_layer_end arrives.
ui_layer_endFinish collecting and composite the whole layer with alpha (0..255).
ui_mask_beginStart 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_clearCancel the mask: everything drawn afterwards is visible.
ui_mask_endAccept the mask and make it the current one. inside: 1 = draw only inside the shape / 0 = only outside it.
ui_mask_end_exAccept 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_pathExport 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_countHow many segments the current mask has (0 when there is no mask).
ui_mask_get_seg_opThe operator used by segment seg (-1 when out of range).
ui_mask_get_shape_countHow many shapes there are in segment seg (0 when out of range).
ui_mask_is_insideUse the mask as a collider: is this point inside the current mask (1/0).
ui_res_get_countQuery 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)

InterfaceIn one line
ui_2d_flood_fillFlood from (x,y) and stop at the border color (four-connected). Returns the number of rectangle runs written; 0 = nothing filled
ui_2d_get_imageSave 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_imageRelease an image stored by ui_2d_get_image (the handle is recycled). Returns 1 on success / 0 on failure.
ui_2d_get_pixelRead the color of one pixel (0xRRGGBB; -1 when out of range).
ui_2d_put_imagePaste 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_bitmapPaste 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_screenshotSave 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)

InterfaceIn one line
ui_2d_create_blockStart 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_blockPaste 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_atSame as above, but (x,y) is the block's top-left corner and it rotates about that corner.
ui_2d_end_blockFinish recording and return the block handle (>= 1; 0 = nothing was recorded).
ui_2d_free_blockRelease 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)

InterfaceIn one line
ui_text_set_fontSet the font once; every later ui_2d_text_cur uses it (so you do not repeat four arguments every time).
ui_text_set_valignSet the default vertical alignment (in effect for all text after it).
ui_2d_textWrite 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_curWrite text with the font set by ui_text_set_font.
ui_2d_text_styledSame as above, plus styles (bold / italic / underline).
ui_2d_text_vText 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)

InterfaceIn one line
ui_piece_get_cellGet one cell of a piece: pid is the piece number, rot the rotation, which which cell.
ui_piece_initInitialize 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)

InterfaceIn one line
ui_grid_clearClear 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_getRead one cell; returns 0 if it was never written.
ui_grid_setWrite 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)

InterfaceIn one line
ui_dlg_inputAsk for one line of text input. The result is written into the buffer you pass.
ui_dlg_msgShow 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_multiA multiple-choice dialog: opts is a string of options, n the number of options. Returns the number selected.
ui_dlg_selectA 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)

InterfaceIn one line
ui_audio_beepSynthesize 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_onPolyphonic: start a note (note number 0-127, A4 = 69). Start several channels at once and you get a chord.
ui_tone_on_msNote-on with a duration: it releases automatically after ms milliseconds (saves a ui_tone_off, and covers "forgot to stop it").
ui_tone_offStop one note (note = -1 stops every note on that channel).
ui_tone_off_allStop everything (with a fade-out).
ui_tone_get_voicesHow many voices are sounding right now - use it to check whether your chords are stacking up.
ui_tone_panicStop immediately (no fade-out) - for a mute button or a forced stop.
ui_tone_set_waveSet 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_voicesThe maximum number of simultaneous voices (1-32, default 32).
ui_sfx_addSequencer: 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_tickCall once per frame; it advances by real elapsed time (put it in the main loop).
ui_sfx_stepAdvance 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_panicMute immediately and clear the table (on exit or when starting a new round).
ui_sfx_get_activeHow many slots are still occupied - after a round finishes this should be back to 0, so use it as a self-check.
ui_sfx_resetClear 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_playPlay an audio file (mp3/wav/...); a non-zero loop repeats it (for BGM).
ui_audio_stopStop the audio that is playing.
ui_audio_set_volumeOverall volume 0-100 (in effect for audio played after it).
ui_audio_is_playingIs the background BGM still playing -> 1/0.
ui_scr_set_keep_onScreen-awake switch (every game should turn this on).
ui_vibrate_simpleVibrate: ms milliseconds, strength intensity.
ui_vibrate_patternVibrate 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)

InterfaceIn one line
ui_clipboard_setWrite the system clipboard: 1 = handed to the system / 0 = this platform has no clipboard.
ui_clipboard_getRead the clipboard: returns the number of bytes written / -1 = empty or unsupported.
ui_share_textShow the system share sheet (an empty title string means none). Warning: it does not block.
ui_url_openOpen 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)

InterfaceIn one line
ui_store_getRead a value: it is written into the buffer you pass and the length is returned (-1 when the key does not exist).
ui_store_setStore 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_delDelete 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)

InterfaceIn one line
ui_call_jsonTwo strings in, one JSON string out - use it to query device information and to reach the host's miscellaneous capabilities,
ui_call_json_get_atGet byte number i of the previous result.
ui_call_json_get_lenHow long the result of the last ui_call_json_s is.
ui_call_json_printPrint 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_sSame 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)

InterfaceIn one line
callwithdouble4Same as above, with 3 double arguments (passed in D1..D3); the return covers D0.
callwithfloat8Same as above, with 7 float arguments (passed in F1..F7); the return covers F0.
callwithint8Call a host function by numeric id: v[0] is the call number (see the VML_CALL_* macros), v[1..7] are the arguments,
callwithlong4Same 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)

InterfaceIn one line
ui_arg_getCopy 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_countThe 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 */ }