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):
- 形状只写一遍,贴的时候能旋转、缩放、镜像(缩放取负值就是镜像);
- 每一帧从"十几次绘图调用"压成"一次贴图块"。实测 300 个精灵 × 60 帧:直接画 3531ms → 贴图块 2945ms(快 17%),精灵越多差距越大。
这些接口长在哪一层(出了问题去哪找)
你的程序(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
知道这个有什么用:
- 两端行为一致(同一份实现)⇒ 桌面上验证过的,手机上就是那个行为;
- 万一出现「手机上对、桌面上错」,那一定在 **
IVmlHost** 那一层(最下面那格),不用去翻绘图的代码; - 桌面的
scripts/vmlcli是验收脚手架 —— 改完程序先在那儿跑(秒级),别一上来就打 APK(手机上编译要一分多钟)。
窗口
开窗、问尺寸、关窗、屏幕方向 —— [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_ex | ui_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_ex | ui_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 这类开关 */ }