← 都来码← 文档索引UI 开发原文English

UI 开发

VML 程序不是只能打印文字 —— 它有一套完整的手机界面接口:开窗、绘图、收触摸与按键、放声音、震动、存档。这一页把全部接口列出来,每个都带一句用法和一个例子。

最小程序

#include <vml_ui.h>

int main(void) {
    int w = ui_scr_get_w(), h = ui_scr_get_h();     /* ① 先问可用绘图区 */
    ui_win_open("演示", w, h);              /* ② 再按它开窗 */
    ui_2d_clear(0xFF101020);
    ui_2d_text(20, 40, "Hello", 0xFFFFFFFF, 20, VML_ANCHOR_CENTER);
    ui_2d_present();                           /* ③ 这一帧画完了 */

    int m[4];
    while (ui_win_is_closed() == 0) {          /* ④ 主循环:收消息 → 处理 → 重画 */
        if (ui_msg_wait(m, 0) == VML_MSG_TOUCHDOWN) { /* 处理 m[1], m[2] */ }
    }
    return 0;
}

四条骨架:先问尺寸 → 开窗 → 主循环(收消息 / 处理 / ui_2d_present)→ 用户关窗退出。下面按类别列出全部接口;不同语言调用方式一样,只是语法不同(见「27 种语言」)。

签名的权威来源是 Lib/c/vml_ui.h,实现在 Lib/shared/src/vmlui.c —— 27 个前端共用同一份实现。本页的签名就是从那个头文件取的,改了接口重跑生成器即可。

接口分几层

按用途分四层。写程序时按这张图找接口,比翻列表快:

┌─ 窗口 / 设备 ────────────────────────────────────────────────┐
│  开窗 ui_win_open(_ex / _pc)   问尺寸 ui_scr_get_w/h             │
│  设备方向 ui_scr_get_orientation       方向锁 ui_scr_lock_orientation         │
│  全屏 ui_scr_set_immersive             别熄屏 ui_scr_set_keep_on             │
│  传感器 ui_sensor_read(_available/_rate/_calibrate)               │
│  电量 ui_battery_read               省电 ui_power_is_saving           │
└───────────────────────────┬──────────────────────────────────┘
                            │ 开好窗之后
┌─ 输入 ────────────────────▼──────────────────────────────────┐
│  事件式(主循环)ui_msg_wait / ui_msg_read + ui_msg_get_a/b              │
│  轮询式(手感)  ui_touch_fetch / ui_key_is_down                │
└───────────────────────────┬──────────────────────────────────┘
                            │ 拿到输入 → 决定画什么
┌─ 绘图 ────────────────────▼──────────────────────────────────┐
│  图元   ui_2d_rect / ui_2d_circle / ui_2d_path / ui_2d_text …            │
│  绘图状态 ui_clip_* / ui_mask_* / ui_2d_set_alpha                   │
│  资源   ui_2d_create_block(精灵)/ ui_2d_gradient / ui_brush_*     │
│  像素   ui_2d_get_pixel / ui_2d_flood_fill / ui_scr_screenshot          │
└───────────────────────────┬──────────────────────────────────┘
                            │ 配合着用
┌─ 系统 ────────────────────▼──────────────────────────────────┐
│  对话框 ui_dlg_*    音效震动 ui_audio_beep / ui_vibrate_simple            │
│  存档 ui_store_*    参数 ui_arg_get_count / ui_arg_get                    │
│  剪贴板 ui_clipboard_*   分享 ui_share_text / ui_url_open    │
└─────────────────────────────────────────────────────────────┘

一句话记法:窗口决定你能画多大,输入告诉你用户干了什么, 绘图把东西画上去,系统是游戏之外的那些事(弹框、出声、存档)。

精灵为什么要用「矢量图块」

会动的东西(玩家、飞鸟、香蕉…)尽量做成图块(ui_2d_create_block → ui_2d_draw_block):

这些接口长在哪一层(出了问题去哪找)

   你的程序(C / BASIC / Python / … 27 种语言)
        │  #include <vml_ui.h>
        ▼
   Lib/c/vml_ui.h          ← 签名的权威来源
   Lib/shared/src/vmlui.c       ← C 包装:一行 asm("SYSCALL #5xx")
        │  syscall 500–599
        ▼
   VmlHostRuntime(UI/Shared)  ← **逻辑全在这一份**
        │   手机与桌面**编同一份**:坐标钳位、消息队列、绘图状态机…
        │  IVmlHost(只有平台真的不一样的那十几件事)
        ▼
   手机 MauiVmlHost          │  桌面 CliVmlHost(scripts/vmlcli)
   弹原生框 / 存档 / 音频     │  脚本化输入 / 写 PNG

知道这个有什么用:

窗口

开窗、问尺寸、关窗、屏幕方向 —— [help:vml/ui/window](help:vml/ui/window)

接口一句话
ui_scr_get_orientation设备方向:VML_ORIENT_PORTRAIT(0) / VML_ORIENT_LANDSCAPE(1)。
ui_scr_get_language界面语言:VML_LANG_ZH(0) / VML_LANG_EN(1),跟随系统语言(不是 App 内开关,也没有第三档)。
ui_lang按宿主语言在两种文案里二选一 —— 返回 zh 或 en 的指针(不动字符串本身)。
ui_scr_get_h可用绘图区的高。
ui_scr_get_w可用绘图区的宽(开窗前也能问)。
ui_scr_get_h_exui_scr_get_w_ex 的高 —— 同一套「先声明配置、再问尺寸」。
ui_scr_get_w_ex按声明的 chrome 配置问可用绘图区的宽(开窗前问)。chrome 是位掩码:
ui_win_close关掉窗口(程序自己结束用)。
ui_win_is_closed用户是不是已经关窗了(主循环的退出条件)。
ui_win_open开一个绘图窗口。先问 ui_scr_get_w/h() 拿可用绘图区,再按它开 —— 写死尺寸在小屏上会溢出。
ui_win_open_ex同上,另加两个开窗前就生效的声明:转屏策略、要不要手柄区。
ui_win_open_pc开一个 “电脑屏”窗口:老 DOS/BGI 程序那种“字符网格 + 鼠标”的模型(要键盘/鼠标,不要手机手柄)。
/* 例:ui_scr_get_orientation */
if (ui_scr_get_orientation() == VML_ORIENT_LANDSCAPE) { /* 横排 */ }

收消息

触摸 / 按键 / 定时器消息怎么收 —— [help:vml/ui/messages](help:vml/ui/messages)

接口一句话
ui_msg_get_a当前消息的第一个参数(触摸的 x、按键的键码、定时器的 id…)。
ui_msg_get_b当前消息的第二个参数(触摸的 y…)。
ui_msg_clear清空消息队列(切场景 / 重开一局时用,免得把上一局的按键吃进来)。
ui_msg_drop丢掉队列里某一类还没被消费的消息 → 丢掉的条数(-1 = 类别认不出,什么也没丢)。
ui_msg_get_count队列里还积着几条(想丢掉积压时可以看一眼)。
ui_msg_get_type当前消息的类型(省得把 msg[0] 记在脑子里)。
ui_msg_read不等:没有消息就返回 VML_MSG_NONE。连续动画用这个(配自己的节拍);
ui_msg_read_exui_msg_read 的带「读完后留不留」版本。
ui_msg_fetch不碰指针的取消息版本(拿不到数组指针的语言用):没有就返回 VML_MSG_NONE,
ui_msg_wait等一条消息,参数是 int msg[4]。返回消息类型(见下表);timeout=0 表示一直等。
ui_msg_wait_ex同上,第三个参数决定读完之后留不留这条消息(VML_MSG_KEEP / VML_MSG_CONSUME)。
ui_msg_fetch_wait不碰指针的等消息版本:等到就返回类型、超时返回 VML_MSG_NONE。
/* 例:ui_msg_get_a */
int x = ui_msg_get_a();

多点触控与设备

多指查询、按住判断、方向锁、全屏 —— [help:vml/ui/device](help:vml/ui/device)

接口一句话
ui_audio_is_playing后台 BGM 还在放吗 → 1/0(没放过、已放完、被停掉都是 0)。
ui_scr_set_immersive隐藏 / 恢复状态栏与导航栏(全屏游戏)。
ui_key_is_down某个键此刻按住没有(“持续按住左移”不必自己维护状态表)。
ui_scr_lock_orientation锁定屏幕方向:VML_LOCK_PORTRAIT(0) / VML_LOCK_LANDSCAPE(1) / VML_LOCK_AUTO(2)。
ui_touch_read查第 slot 根手指(0..9):写进 out[0]=x out[1]=y out[2]=按下,返回 1 有效(槽位越界 0)。
ui_touch_is_down上一次 ui_touch_fetch 缓存里的“按下”(1/0)。
ui_touch_fetch拿不到指针的语言用这个:查一次并缓存,下面三个读缓存(与 ui_msg_fetch_wait + ui_msg_get_a/b 同一套分工)。
ui_touch_get_x上一次 ui_touch_fetch 缓存里的 x。
ui_touch_get_y上一次 ui_touch_fetch 缓存里的 y。
ui_sensor_read传感器:读加速度计 / 陀螺仪 / 融合姿态,写进 out[0..2]。做倾斜控制用加速度计。
ui_sensor_is_available这台设备有没有该传感器 → 1/0(开窗之前就能问)。
ui_sensor_set_rate设采样间隔(毫秒)—— 传感器一开就在耗电。
ui_sensor_calibrate把当前姿态当零点(玩家躺着玩时"水平"是错的)。
ui_sensor_fetch查一次并缓存(1 = 有效、0 = 没有该传感器),下面三个读缓存(与 ui_touch_fetch 那一套分工相同)。
ui_sensor_get_x上一次 ui_sensor_fetch 缓存里的 x。
ui_sensor_get_y上一次 ui_sensor_fetch 缓存里的 y。
ui_sensor_get_z上一次 ui_sensor_fetch 缓存里的 z。
ui_battery_read电量与充电:out[0]=0–100、out[1]=充电中。返回 0 = 这台设备没有电池。
ui_battery_fetch查一次并缓存电量(1 = 有效、0 = 没有电池),下面两个读缓存(与 ui_touch_fetch 那一套分工相同)。
ui_battery_get_level上一次 ui_battery_fetch 缓存里的电量(0–100)。
ui_battery_is_charging上一次 ui_battery_fetch 缓存里的充电标志(1/0)。
ui_power_is_saving系统省电模式开着吗 → 1/0(与"电量低"不是一回事)。
ui_power_read通用入口:op 取 VML_POWER_OP_BATTERY(0) / VML_POWER_OP_SAVER(1),结果写进 out。
ui_camera_capture拍照,返回沙箱相对路径写入 buf(返回写入的字节数);取消 / 没权限 / 没有相机一律 0。
ui_camera_pick从相册选一张,语义与返回值同 ui_camera_capture。
ui_dlg_file_open弹系统文件选择器,把用户选中的文件解析进沙箱,回沙箱相对路径(返回写入的字节数);取消 / 不支持 0。
ui_dlg_file_save让用户选保存位置(suggested = 建议文件名,可传空串),回沙箱相对路径。
ui_gps_is_available这台设备能不能定位 → 1/0(取数之前就能问;没权限也算 0)。
ui_gps_fetch取一次定位(timeout_ms 毫秒,传 0 = 平台默认)→ 1 = 拿到了 / 0 = 没拿到(超时 / 没权限 / 没有 GPS)。
ui_gps_get_lat_e6纬度 × 1e6(31523000 = 31.523°)——读上一次 ui_gps_fetch 的结果;没取过 → 0。
ui_gps_get_lon_e6经度 × 1e6 —— 同上。
ui_gps_get_alt_m海拔(米,整数)—— 同上。没有海拔数据时是 0。
ui_gps_get_accuracy_m水平精度(米,整数)—— 同上。数越小越准,可以拿它决定「要不要在地图上标一圈」。
/* 例:ui_audio_is_playing */
ui_audio_play("bgm.mp3", 0);
while (ui_audio_is_playing()) { ui_msg_wait(msg, 200); }

错误处理

上一次调用为什么失败(取消 / 没权限 / 这一端没有这个能力) —— [help:vml/ui/device](help:vml/ui/device)

接口一句话
ui_error_get_code最近一次失败的错误码(VML_E_*);没有错误就是 0(VML_E_OK)。
ui_error_get_message最近一次失败的人话描述 → 写进 buf(最多 cap 字节,含结尾 0),返回写入的字节数;没有错误就写空串、返回 0。
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));   /* 人话:例如「没有权限」 */
    ui_dlg_msg("取图失败", why, VML_DLG_WARN);
}

定时器与随机数

重复定时器、取时间、随机数 —— [help:vml/ui/timer](help:vml/ui/timer)

接口一句话
ui_math_rand随机数:ui_math_rand(n) → 0..n-1(n ≤ 0 时返回 1,不会崩)。
ui_time_get_ms开机以来的毫秒数(自己算帧间隔、做动画用)。
ui_timer_kill停掉一个定时器。
ui_timer_set起一个重复定时器,每 N 毫秒发一条 VML_MSG_TIMER(msg[1] 是你给的 id)。
/* 例:ui_math_rand */
int n = ui_math_rand(6);      /* 0..5 */
int side = ui_math_rand(2);   /* 0 或 1 */

绘图

点线面、多边形、路径、渐变、贴图 —— [help:vml/ui/draw](help:vml/ui/draw)

接口一句话
ui_brush_linear造一个线性渐变刷子。几何是千分之一(0..1000),相对形状自己的包围盒:0,0,1000,0 = 从左到右。
ui_brush_named按名字引用一个已经 ui_2d_gradient 定义过的渐变 → 句柄。
ui_brush_radial造一个径向渐变刷子(中心 → 四周)。cx cy r 同样千分之一,500,500,500 = 居中。
ui_brush_solid造一个纯色刷子。返回句柄(≥1;0 = 失败),交给 ui_brush_set_fill / ui_brush_set_pen 用。
ui_2d_circle画圆,fill 非 0 填充。
ui_2d_circle_grad渐变的圆(渐变先用 ui_2d_gradient 起个名字)。
ui_2d_clear整屏填一个色(每帧开头调)。颜色一律 0xAARRGGBB。
ui_2d_shape_circle用当前刷子画圆。
ui_2d_shape_ellipse用当前刷子画椭圆。
ui_2d_shape_heart用当前刷子画心形。
ui_2d_shape_line用当前画笔画直线。
ui_2d_shape_path用当前刷子画 SVG 路径(M L C Q A Z,大小写区分绝对/相对;多子路径按奇偶规则挖洞)。
ui_2d_shape_pie用当前刷子画扇形:半径 / 起始角 / 结束角(度)。
ui_2d_shape_poly用当前刷子画多边形(close=1 自动闭合)或折线(close=0)。pts 每两个 int 一个点,count 是点数。
ui_2d_shape_rect用当前刷子画矩形(radius > 0 即圆角)。
ui_2d_shape_regular用当前刷子画正多边形:半径 / 边数 / 旋转角(度)。
ui_2d_shape_ring用当前刷子画圆环(外半径 / 内半径,中间的洞靠奇偶规则挖)。
ui_2d_shape_star用当前刷子画星形:外半径 / 内半径 / 角数 / 旋转角(度)。
ui_2d_shape_text用当前文字属性(ui_text_set_font)画一行字。
ui_2d_ellipse画椭圆(rx / ry 两个半径)。
ui_2d_ellipse_grad渐变填充的椭圆。与 ui_2d_rect_grad / ui_2d_circle_grad 是一组(那批接口当时漏了椭圆)。
ui_2d_gradient定义一个渐变刷子并起个名字(字符串 id);之后 ui_2d_rect_grad / ui_2d_circle_grad / ui_2d_path 按名字引用它。
ui_2d_icon画一个内置图标(按名字取,省得自己画)。
ui_2d_image在指定位置画一张图(PNG / JPG / BMP),w / h 传 0 按原尺寸。
ui_2d_line画线,lw 是线宽。
ui_2d_path按 SVG 路径语法画(M L H V C S Q T A Z,大小写区分绝对/相对)。
ui_2d_pixel画一个点。
ui_2d_polygon画多边形(自动闭合)。pts 是 int 数组、每两个 int 一个点;count 是点数。
ui_2d_polyline折线(不闭合)。参数含义同 ui_2d_polygon(stroke 是颜色、width 是线宽)。
ui_2d_present这一帧画完了。整个循环里最关键的一句 —— 不调它屏幕不更新。
ui_2d_rect画矩形。fill 非 0 填充、radius 是圆角半径。
ui_2d_rect_grad带渐变的矩形(渐变先用 ui_2d_gradient 定义)。
ui_brush_set_fill设置填充刷子。传刷子句柄或颜色都行;传 0 = 不填充(空心)。
ui_brush_set_pen设置画笔(描边)= 刷子 + 线宽 + 线帽 + 虚线 + 箭头。传 0 = 不描边。
ui_text_set_brush设置文字刷子(配合 ui_text_set_font + ui_2d_shape_text)。传 0 = 回到 ui_text_set_font 给的颜色。
ui_color_rgb把 r / g / b 打包成 ui_* 要的 0xAARRGGBB(alpha 强制 FF = 不透明);分量会被钳到 0..255。
ui_color_argb同上,另给 alpha(0..255,0xFF = 不透明、0x00 = 全透明)。
/* 例:ui_brush_linear */
int b = ui_brush_linear(0xFFFF3020, 0xFF2050FF, 0, 0, 1000, 0);
ui_brush_set_fill(b);

绘图状态

裁剪、蒙版(含布尔运算)、透明度、资源计数 —— [help:vml/ui/gfx](help:vml/ui/gfx)

接口一句话
ui_2d_set_alpha全局透明度 0..255,对之后画的图元生效。
ui_brush_reset释放本窗口累积的全部画刷 / 渐变定义(只能整体重置 —— 句柄就是表的下标,删单个会让后面全部错位)。
ui_clip_pop弹出一级裁剪(栈空了再弹是空操作)。
ui_clip_push压入一级矩形裁剪:之后画的东西只在这个矩形里可见;可以嵌套(与上一级求交)。
ui_clip_reset清空整个裁剪栈(不是弹一级)—— 给“出错恢复”用:一进主循环调一次,回到干净的整屏状态。
ui_layer_begin开始图层收集:这期间画的图元先落在临时画布上,等 ui_layer_end 整层合成。
ui_layer_end结束收集并整层按 alpha(0..255)合成上去。
ui_mask_begin开始收集蒙版形状:这期间画的形状不上屏,只当蒙版用。配 ui_mask_end / ui_mask_end_ex 收尾。
ui_mask_clear取消蒙版:之后的图元全部可见。
ui_mask_end收下蒙版并取代当前蒙版。inside:1 = 只在形状里画 / 0 = 只在形状外画。
ui_mask_end_ex收下蒙版并与当前蒙版按 op 做布尔运算 —— 老式“圆环”得画两遍,现在一次就够。
ui_mask_path把第 seg 段第 idx 个形状导出成 SVG 路径写进 buf —— 程序能拿它描洞口的边、做外发光。
ui_mask_get_seg_count当前蒙版有几段(没有蒙版返回 0)。
ui_mask_get_seg_op第 seg 段用的运算符(越界 -1)。
ui_mask_get_shape_count第 seg 段里有几个形状(越界 0)。
ui_mask_is_inside蒙版当碰撞体:这一点在不在当前蒙版里(1/0)。
ui_res_get_count查当前占用:0 图元 / 1 图像 / 2 矢量图块 / 3 画刷渐变;未知返回 -1。
/* 例:ui_2d_set_alpha */
ui_2d_set_alpha(120);
ui_2d_rect(10, 10, 100, 60, 0xFF000000, 1, 0, 0);   /* 半透明黑 */
ui_2d_set_alpha(255);

像素读回

读像素、灌色、抓图贴图、截屏 —— [help:vml/ui/pixel](help:vml/ui/pixel)

接口一句话
ui_2d_flood_fill从 (x,y) 灌色,碰到 border 色就停(四连通)。返回落笔的矩形条数,0 = 没填
ui_2d_get_image存一块画面 → 句柄(≥1),失败 0。老程序的 malloc(imagesize(...)) 照写不误,只是那块内存我们不用。
ui_2d_free_image释放一张 ui_2d_get_image 存下的图像(句柄回收再用)。返回 1 成功 / 0 失败。
ui_2d_get_pixel读一个像素的颜色(0xRRGGBB;越界 -1)。
ui_2d_put_image把句柄那块贴到 (x,y)。mode:0 = COPY 直接贴 / 1 = XOR 异或(异或要先读目的像素,慢一些)。
ui_2d_put_qb_bitmap把一块**手打包在 DATA 里的 QBasic 位图贴到 (x,y)。老 BASIC 游戏的精灵不是 GET 抓的** —— 它们按 QBasic 的 GET/PUT 数组格式打进 DATA、READ 进长整型数组再 PUT(GORILLA.BAS 的 LBan& 就是),而 ui_2d_put_image 只认句柄 ⇒ 这类精灵一个都画不出来。
ui_scr_screenshot把当前窗口存成 PNG(路径相对工作区;不给路径就自动取名落在 shot/ 下)。返回 1 成功。
/* 例:ui_2d_flood_fill */
ui_2d_flood_fill(50, 50, 0xFF00FF00, 0xFFFF0000);   /* 在红框里灌绿 */

矢量图块

录一次、带旋转缩放地反复贴(精灵用这个) —— [help:vml/ui/block](help:vml/ui/block)

接口一句话
ui_2d_create_block开始录制一个图块。录的是绘图指令、不是像素 —— 贴的时候能旋转缩放,放大也不糊。
ui_2d_draw_block贴一个图块。(x,y) 是块的中心、绕中心旋转;缩放是千分比(1000 = 原尺寸)、角度是度。
ui_2d_draw_block_at同上,但 (x,y) 是块的左上角、绕左上角转。
ui_2d_end_block结束录制,返回图块句柄(≥1;0 = 没录成)。
ui_2d_free_block释放一个图块(句柄回收再用)。返回 1 成功 / 0 失败(句柄不存在或已释放)。
/* 例:ui_2d_create_block */
bid = ui_2d_create_block(30, 20, 0);   /* 30×20 的块 */

文字

写字、字体、锚点 —— [help:vml/ui/text](help:vml/ui/text)

接口一句话
ui_text_set_font设一次字体,后面所有 ui_2d_text_cur 都用它(省得每次重复传四个参数)。
ui_text_set_valign设置默认垂直对齐(之后所有文字生效)。
ui_2d_text在 (x,y) 写一行字。size 是字号;anchor 决定 (x,y) 指文字的哪一边(VML_ANCHOR_LEFT / CENTER / RIGHT)。
ui_2d_text_cur用 ui_text_set_font 设好的字体写字。
ui_2d_text_styled同上,另加样式(粗体 / 斜体 / 下划线)。
ui_2d_text_v带垂直对齐的文字(VML_VALIGN_*)—— ui_2d_text 的 y 是基线,这个可以按顶/中/底对齐。
/* 例:ui_text_set_font */
ui_text_set_font(18, VML_FONT_BOLD, 0xFFFFFFFF, VML_ANCHOR_CENTER);
ui_2d_text_cur(180, 40, "按方向键退出");

方块贴图

把一张图切成小格反复贴 —— [help:vml/ui/piece](help:vml/ui/piece)

接口一句话
ui_piece_get_cell取某个棋子的某一格:pid 棋子号、rot 旋转、which 第几格。
ui_piece_init初始化棋子贴图表(无参版本;具体形态见 Lib/shared/src/vmlui.c)。
/* 例:ui_piece_get_cell */
ui_piece_get_cell(0, 0, 3);   /* 0 号棋子、不旋转、第 3 格 */

整数网格

棋盘 / 地图这种二维状态 —— [help:vml/ui/grid](help:vml/ui/grid)

接口一句话
ui_grid_clear清空网格。棋盘 / 地图这种二维状态用它 —— 比语言自带的数组可靠(有的前端数组写入读不回来,见「27 种语言」里各语言的坑)。
ui_grid_get读一格,没写过返回 0。
ui_grid_set写一格,i 是一维下标(row * 宽 + col)。
/* 例:ui_grid_clear */
ui_grid_clear();

对话框

提示、单选、多选、输入 —— [help:vml/ui/dialog](help:vml/ui/dialog)

接口一句话
ui_dlg_input要一行文字输入。结果写进你给的缓冲区。
ui_dlg_msg弹一个提示框(style 传 0 即可)。会阻塞到用户点掉 —— 游戏结束时报个结果正好。
ui_dlg_multi多选对话框:opts 选项串、n 选项个数。返回选中的个数。
ui_dlg_select单选对话框:opts 是选项串、n 是选项个数、def 是默认选中项。返回选中下标(-1 = 取消)。
/* 例:ui_dlg_input */
char buf[64];
ui_dlg_input("改名", "新名字:", buf, 64);

音效与触感

合成音、音频文件、震动、屏幕常亮 —— [help:vml/ui/feel](help:vml/ui/feel)

接口一句话
ui_audio_beep现场合成一个音(不用带音频文件):freq 赫兹、ms 毫秒。单通道,连发只听见最后一个 —— 这正是不破音的原因:一个事件永远只有一个音在响。游戏音效用它。
ui_tone_on复音:起一个音(音符号 0–127,A4=69)。几个通道同时起就是和弦。
ui_tone_on_ms带时长的起音:ms 毫秒到点自动进释放(省掉一次 ui_tone_off,也兜住「忘了关」)。
ui_tone_off关一个音(note 传 -1 = 该通道全部)。
ui_tone_off_all全关(走淡出)。
ui_tone_get_voices此刻在响的声部数 —— 查和弦叠起来没有。
ui_tone_panic立刻全停(不进淡出)—— 一键静音 / 强制停止用。
ui_tone_set_wave设某通道的波形(正弦/方波/锯齿/三角)—— 和弦里给低音换三角波就不糊。
ui_tone_set_max_voices同时允许的声部上限(1–32,默认 32)。
ui_sfx_add音序器:塞一个音(delay 拍后响、响 dur 拍)。⚠ 游戏音效别用它(多声部叠加 / 长音拖尾真机破音,本仓 28 个游戏已全量退回 ui_audio_beep)—— 见 音效用哪个。
ui_sfx_tick每帧调一次,按真实流逝时间推进(放主循环里)。
ui_sfx_step推进一拍:delay 到了就 note_on、dur 响完就 note_off 并腾出槽位。
ui_sfx_panic立刻静音 + 清表(退出 / 重开一局时)。
ui_sfx_get_active还有几个槽占着 —— 放完一轮应当回到 0,拿它做自检。
ui_sfx_reset清表(不发声)。⚠ 只清表不关音 = 已经在响的那些从此没人管、一直响到程序结束 ⇒ 要「静音」请用 ui_sfx_panic()(它先把在响的全关掉、再清表,顺序不能反)。
ui_audio_play播放一个音频文件(mp3/wav…),loop 非 0 = 循环(BGM 用)。
ui_audio_stop停掉正在播的音频。
ui_audio_set_volume整体音量 0–100(对之后播放的音生效)。
ui_audio_is_playing后台 BGM 还在放吗 → 1/0。
ui_scr_set_keep_on屏幕常亮开关(玩游戏的都该开)。
ui_vibrate_simple震动:ms 毫秒,strength 强度。
ui_vibrate_pattern按节奏震动(int 数组:奇数下标静、偶数下标动)。
/* 例:ui_audio_beep */
ui_audio_beep(880, 80);      /* 消一行 */
ui_audio_beep(1568, 160);    /* 消四行,音更高 */

剪贴板与分享

分享成绩、存取档码(把整局状态编成一小段文本,玩家自己存到备忘录里换设备接着玩) —— [help:vml/ui/device](help:vml/ui/device)

接口一句话
ui_clipboard_set写系统剪贴板:1=已交给系统 / 0=这一端没有剪贴板。
ui_clipboard_get读剪贴板:返回写入的字节数 / -1=空的或不支持。
ui_share_text弹系统分享面板(标题传空串 = 不带)。⚠ 不阻塞。
ui_url_open用系统浏览器打开链接。只认 http/https。
char code[64];
ui_clipboard_set("SAVE-1a2b3c");              /* 把存档码交给玩家 */
if (ui_clipboard_get(code, 64) > 0) { /* 读回来接着玩 */ }
ui_share_text("我在拧螺丝里拧了 8 圈!", "分享战绩");

⚠ 返回 0 是"这一端没有这个能力"(桌面就没有分享面板),不是失败 —— 游戏该照常继续。写剪贴板/弹面板在平台上都是异步的,宿主不等结果。

本地存档

存最高分这类小数据 —— [help:vml/ui/store](help:vml/ui/store)

接口一句话
ui_store_get读一个值:写进你给的缓冲区、返回长度(没有这条键返回 -1)。
ui_store_set存一个值。值也是字符串 —— 存数字要先自己转成字符串(这里没有 sprintf 可用)。
ui_store_del删掉一条存档(例如"清空最高分")。
/* 例:ui_store_get */
char buf[16];
if (ui_store_get("high", buf, 16) > 0) best = atoi(buf);

全能接口

两个字符串进、一个 JSON 出 —— [help:vml/ui/json](help:vml/ui/json)

接口一句话
ui_call_json两个字符串进、一个 JSON 字符串出 —— 查设备信息、调宿主的杂项能力都走它,
ui_call_json_get_at取上一次结果的第 i 个字节。
ui_call_json_get_len上一次 ui_call_json_s 的结果有多长。
ui_call_json_print把上一次 ui_call_json_s 的结果整份打到 stdout(末尾补换行)—— 调试最省事,
ui_call_json_s同上,但不用给缓冲区(适合拿不到指针的语言),配 _len / _at 读结果。
/* 例:ui_call_json */
char buf[512];
ui_call_json("sysinfo", "", buf, 512);
puts(buf);   /* {"ok":true,"result":{…}} */

调用宿主

按数字 id 调宿主函数(带类型、零编解码) —— [help:vml/ui/call](help:vml/ui/call)

接口一句话
callwithdouble4同上,参数是 3 个 double(走 D1..D3),返回覆盖 D0。
callwithfloat8同上,参数是 7 个 float(走 F1..F7),返回覆盖 F0。
callwithint8按数字 id 调宿主的函数:v[0] 是调用号(见 VML_CALL_* 宏)、v[1..7] 是参数,
callwithlong4同上,参数是 3 个 long(走 L1..L3),返回覆盖 L0。
/* 例:callwithdouble4 */
double v[4];
v[0] = VML_CALL_ECHO_DOUBLE;
v[1] = 1.0; v[2] = 2.0;
double r = callwithdouble4(v);

命令行参数

程序自己的 -l 这类开关 —— [help:vml/ui/args](help:vml/ui/args)

接口一句话
ui_arg_get把第 i 个参数拷进 buf,返回长度(越界 -1)。argv[0] 是程序名 ⇒ 用户给的第一个是 ui_arg_get(1,…)。
ui_arg_get_count参数个数(含程序名,恒 ≥ 1)。
/* 例:ui_arg_get */
char buf[64];
if (ui_arg_get(1, buf, sizeof(buf)) > 0) { /* 用了 -l 这类开关 */ }