c/app/gallery.c · 27327 字节 · 下载原文
/* gallery.c —— 常用小程序⑧:**拍照 / 相册**(第 4 层:`ui_*` 图形界面)
* gallery.c -- Everyday utility #8: **camera / photo gallery** (layer 4: the `ui_*` graphical interface)
*
* 干什么:一屏两个按钮 ——「拍照」走 `ui_camera_capture`、「从相册选」走 `ui_camera_pick`。
* What it does: two buttons -- "Take a photo" goes through `ui_camera_capture` and "Pick from the
* 两者都往同一个缓冲里写**沙箱相对路径**并返回**字节数**(0 = 取消 / 没权限 / 这一端没有相机)。
* gallery" through `ui_camera_pick`. Both write a **sandbox-relative path** into one buffer and
* 拿到图就用 `ui_2d_image` 画出来(按图框**等比缩放**,不拉伸),图下面两行写**文件名**与**像素尺寸**。
* return its **byte count** (0 = cancelled / no permission / this end has no camera). A picture that
* 像素尺寸是**自己读 PNG 头部 `IHDR` 得来的**(见 `probeImageSize`)—— 拿不到就写「尺寸未知」。
* comes back is drawn with `ui_2d_image`, scaled to fit the box while keeping the aspect ratio; two
* lines underneath show the **file name** and the **pixel size**, read from the PNG header (unreadable
* files fall back to "size unknown").
*
* ## ⚠ 手机上才有 —— 桌面(vmlcli)上「本机没有相机/相册」才是**正确输出**
* ## ⚠ Phone-only -- on the desktop (vmlcli) "no camera or gallery on this device" is the **correct output**
*
* 桌面脚手架是**无头进程**:没有 GUI 可以弹相机 / 相册,`ui_camera_*` 一律返回 0。
* The desktop scaffold is a **headless process**: there is no GUI to take a photo or open a gallery, so
* 而 **0 不是错误** —— 取消、没权限、这一端没有该能力,三者都是 0,程序该照常继续。
* `ui_camera_*` always returns 0. That **0 is not an error**: a cancel, a missing permission and a
* 所以本文件的第一条分支不是"图画得对不对",而是"**这台机器根本没有相机**",界面上必须**明说**,
* missing capability all look the same, and the program should carry on. So the first branch here is
* 而不是留一块空白(空白会被当成"我刚才点歪了")。
* not "is the picture right" but "**this machine has no camera at all**", and the screen must **say so**
* instead of leaving an empty box (an empty box reads as "I must have missed the button").
*
* ## 桌面怎么看到"有相机"那一半:宿主自带的输入脚本注入,**不用改一行源码**
* ## Seeing the camera branch on the desktop: the host's input-script injection, with **no source change**
*
* printf '0 camera .resource/tetris.appicon.png\n300 keydown 13\n360 keyup 13\n' > in.txt
* vmlcli gallery.c --vml-home third_party/vml --input in.txt --timeout 40 --frame gallery.png
*
* 第一条把"相机返回的路径"喂给宿主(`CliVmlHost.InjectCamera`),后两条是**按一下「拍照」按钮**
* The first line feeds the path the camera would return into the host (`CliVmlHost.InjectCamera`); the
* (回车 13 = 那个按钮),于是注入的那张图就被真的画出来了 —— 换一张图就是另一张画面。
* other two press the "Take a photo" button (virtual key 13 = Enter), so the injected picture really is
* ⚠ 注入只影响**脚手架**:手机端那条真实现(FilePicker / MediaPicker + 权限)一个字节都不动。
* drawn. The injection only affects the **scaffold**; the phone's real implementation is untouched.
*
* ## 踩到的坑(都是这一轮真撞上的,写在这里省得下一个人再撞)
* ## Pitfalls (all hit for real while writing this file; recorded so the next person does not)
*
* ① ⚠ **`ui_2d_image` 的路径是宿主**按进程工作目录**解的,而程序自己的 `fopen` 是按**沙箱根**解的**
* ① ⚠ **The host resolves the `ui_2d_image` path against the process working directory, while the
* (`#110` 走 `FileSystemRoot`,见 `VMLRuntime.Syscall.OS.cs`)。两者**不是同一套尺子** ——
* program's own `fopen` resolves against the sandbox root** (`#110` goes through `FileSystemRoot`).
* 实测(2026-10-07):在仓库根跑同一个相对路径,图**画不出来**、而 `fopen` 照样成功;
* They are not the same ruler: measured on 2026-10-07, running from the repository root with one
* 把工作目录换成源文件所在目录,两边就都对上了。所以本文件的跑法要求**进程工作目录 = 源文件目录**
* relative path made the picture silently disappear while `fopen` still succeeded; changing the
* (手机端两者天然一致:都是工作区根)。
* working directory to the source directory made both agree. Hence the run recipe keeps the process's
* working directory equal to the source directory (on the phone the two coincide by construction).
* ② ⚠ **`fopen` 的第二个参数是 `int`,不是 `"r"`**(`Lib/shared/src/file.c` 的签名是
* ② ⚠ **`fopen`'s second argument is an `int`, not `"r"`** (the signature in
* `fopen(const char *path, int mode)`,0 = 只读 / 1 = 只写 / 2 = 读写)。`stdio.h` 里
* `Lib/shared/src/file.c` is `fopen(const char *path, int mode)`: 0 = read, 1 = write, 2 = read/write).
* 声明的却是 `FILE *fopen(const char *, const char *)` —— 照那个签名写 `fopen(p, "r")`,
* `stdio.h` declares `FILE *fopen(const char *, const char *)` instead, so writing `fopen(p, "r")`
* 模式收到一个**指针值**,`#110` 直接回 `-2`(`INVALID_PARAMETER`)**且不报错**。
* hands the syscall a **pointer value** as the mode and `#110` answers `-2` (`INVALID_PARAMETER`)
* 本文件**不 include `stdio.h`**,自己按真签名声明(也顺带避开 `fgets` —— 那个函数在库里
* with no message at all. This file therefore **does not include `stdio.h`** and declares the real
* **根本没有实现**,链上去会报未定义)。
* signatures itself (which also avoids `fgets`, which has **no implementation** in the library and
* would fail to link).
* ③ ⚠ **文件级标量别用负值初值**(本仓刚归档的 E187):`static int x = -1;` 读到的是 **0**。
* ③ ⚠ **Never give a file-scope scalar a negative initializer** (this repo's E187): `static int x = -1;`
* 所以下面所有哨兵量一律用**正数**(0 = 没有/未知,1 = 有),别拿 -1 当"还没有"。
* reads back as **0**. Every sentinel below is therefore a **positive** number instead (0 = absent,
* 1 = present); `-1` is never used to mean "not yet".
* ④ ⚠ **"这一端没有"每拍都要重问一次**(与 `level.c` 坑 ② 同源):`--input` 的事件由另一个线程按
* ④ ⚠ **"This end has it" must be re-asked every tick** (same as pitfall 2 in `level.c`): the `--input`
* 时刻投递,程序可能在注入落地之前就点过按钮了。所以按钮**一直可点**,点一次问一次,
* events are delivered by another thread on a timer, and the button may be pressed before the
* "没有"只是这一次的结果,不是永久结论。
* injection lands. So the button stays live, each press asks again, and "absent" is the result of
* one attempt rather than a permanent verdict.
*
* 版面:尺寸一律从宿主问(`ui_scr_get_w()/_h()`),颜色是真彩 `0xAARRGGBB`,画完必须 `ui_2d_present()`。
* Layout: every size is asked of the host (`ui_scr_get_w()/_h()`), colours are true colour
* 循环**必须有界**(例程要能被自动验收):空闲 10 秒自己退出,`Esc`/`q` 也退出。
* `0xAARRGGBB`, and the frame must end with `ui_2d_present()`. The loop **must be bounded** (samples have
* 写真正的程序时把 `IDLE_EXIT_MS` 去掉即可。
* to be automatically verifiable): it exits after 10 s of idling, and `Esc`/`q` quits too. Drop
* `IDLE_EXIT_MS` when you write a real program.
*/
#include <vml_ui.h>
#include <stdlib.h> /* itoa --- digits to text via the library */
#define IDLE_EXIT_MS 10000 /* 空闲 10 秒自己退出(验收用) */
/* idle exit after 10 s (for automated checks) */
#define PATH_CAP 200 /* 宿主写回来的路径缓冲区(一个路径,不是一张图) */
/* the buffer the host writes the path into (a path, not a picture) */
#define BTN_H 44 /* 两个按钮的高度 */
/* height of the two buttons */
/* ⚠ 自己声明 `fopen` 家族(**真签名是 `int mode`**,见文件头坑 ②)——
* ⚠ Declare the `fopen` family here (the **real signature takes an `int mode`**, pitfall 2) --
* 不 include `stdio.h` 就没人和这份声明打架。
* with no `stdio.h` included, nothing can contradict these declarations.
*/
int fopen(const char *path, int mode); /* 0 = 只读 */
/* 0 = read only */
int fclose(int handle);
int fread(int handle, void *buf, int count);
/* 配色(真彩 AA RRGGBB)*/
/* Colours (true colour AA RRGGBB) */
#define C_BG 0xFF0E1218
#define C_BOX 0xFF161B24
#define C_RING 0xFF3C4658
#define C_TITLE 0xFFE8E8F0
#define C_DIM 0xFF8A94A8
#define C_ERR 0xFFFF8A80 /* 没有这项能力 */
/* capability absent */
#define C_NAME 0xFF6FC3FF /* 文件名 */
/* the file name */
#define C_SIZE 0xFFFFD166 /* 像素尺寸 */
/* the pixel size */
#define C_BTN 0xFF233046
#define C_BTNHOT 0xFF2F4060
/* ── 全局状态 ───────────────────────────────────────────────────────── */
/* -- Global state -- */
static int SW; /* 画布宽 */
/* canvas width */
static int SH; /* 画布高 */
/* canvas height */
static int g_pad; /* 页边距 */
/* page margin */
static int lang; /* 界面语言:0 = 中文,1 = English */
/* UI language: 0 = Chinese, 1 = English */
static char g_path[PATH_CAP]; /* 宿主写回来的路径(空串 = 还没有图)*/
/* the path the host wrote back (empty = no picture yet) */
static int g_have; /* 1 = 手上有一张能画的图 */
/* 1 = we hold a picture we can draw */
static int g_imgW; /* 图的像素宽(0 = 读不出来)*/
/* the picture's pixel width (0 = unreadable) */
static int g_imgH; /* 图的像素高(0 = 读不出来)*/
/* the picture's pixel height (0 = unreadable) */
static int g_none; /* 1 = 上一次要图时这一端**没有**相机/相册 */
/* 1 = the last attempt hit an end **without** a camera or gallery */
static int g_asked; /* 1 = 至少点过一次按钮(首屏提示靠它收起来)*/
/* 1 = the button has been pressed at least once */
/* 版面(画与命中判定**必须用同一份**,见 `hitButton`)*/
/* Layout (drawing and hit testing **must share this one copy**, see `hitButton`) */
static int boxX;
static int boxY;
static int boxW;
static int boxH; /* 图框 */
/* the picture box */
static int b1X;
static int b1Y;
static int b1W;
static int b1H; /* 「拍照」按钮 */
/* the "Take a photo" button */
static int b2X;
static int b2Y;
static int b2W;
static int b2H; /* 「从相册选」按钮 */
/* the "Pick from the gallery" button */
static int infoY; /* 图框下面两行文字(文件名 / 尺寸)*/
/* the two lines under the box (name / size) */
/* ── 小工具 ─────────────────────────────────────────────────────────── */
/* -- Small helpers -- */
/* 整数 → 十进制串(`ui_2d_text` 只收字符串;第 4 层没有 `print_int` 那种东西)。
* Integer to a decimal string (`ui_2d_text` takes strings only; layer 4 has no `print_int`).
* ⚠ 返回的是**同一个静态缓冲** —— 别在一个表达式里连用两次。
* ⚠ It returns **one static buffer** -- never use it twice in one expression.
*/
static char g_i10[16];
static char *numstr(int v) {
itoa(v, g_i10);
return g_i10;
}
/* 大端 4 字节(PNG 头里所有长度都是大端)。用乘法而不是 `<< 24`:后者在最高位为 1 时
* A big-endian 4-byte value (every length in a PNG header is big-endian). Multiplication rather
* 会得到负数(C 的 int 是 32 位有符号),而宽高永远不该是负的。
* than `<< 24`, which turns negative once the top bit is set (C's int is 32-bit signed).
*/
static int be32(char *b, int at) {
return (b[at] & 0xFF) * 16777216 + (b[at + 1] & 0xFF) * 65536
+ (b[at + 2] & 0xFF) * 256 + (b[at + 3] & 0xFF);
}
/* 读 PNG 头里的像素尺寸。**只认 PNG**:别的格式头部各有各的读法,认不出就把宽高留 0
* Read the pixel size out of the PNG header. **PNG only**: every other format has its own header
* (界面显示「尺寸未知」)—— 宁可承认不知道,也不要猜一个数出来。
* layout, and an unreadable file leaves both at 0 (the UI prints "size unknown") -- better to admit
* we do not know than to invent a number.
*/
static void probeImageSize(void) {
int h;
int n;
char hdr[32];
g_imgW = 0;
g_imgH = 0;
if (g_path[0] == 0) return;
h = fopen(g_path, 0);
if (h < 0) return; /* 打不开(越界 / 不存在)就当读不出来 */
/* cannot open (out of sandbox / missing) -> unknown */
n = fread(h, hdr, 26);
fclose(h);
if (n < 26) return;
if ((hdr[0] & 0xFF) != 0x89) return; /* 魔数 89 50 4E 47 = "\x89PNG" */
if ((hdr[1] & 0xFF) != 0x50) return; /* magic 89 50 4E 47 is "\x89PNG" */
if ((hdr[2] & 0xFF) != 0x4E) return;
if ((hdr[3] & 0xFF) != 0x47) return;
if ((hdr[12] & 0xFF) != 0x49) return; /* 偏移 12 起的 4 字节是块名,必须 "IHDR" */
if ((hdr[13] & 0xFF) != 0x48) return; /* the 4 bytes at offset 12 are the chunk name ("IHDR") */
if ((hdr[14] & 0xFF) != 0x44) return;
if ((hdr[15] & 0xFF) != 0x52) return;
g_imgW = be32(hdr, 16); /* 宽在 16,高在 20(大端 4 字节)*/
/* width at 16, height at 20 (big-endian, 4 bytes) */
g_imgH = be32(hdr, 20);
}
/* 文件名(只要末段)—— 注入给的是路径,界面上写成整条会挤爆一行 */
/* The file name (last segment only) -- the injected value is a path, and printing it whole overflows the line */
static char g_base[64];
static char *baseName(void) {
int i;
int last;
int n;
last = -1;
i = 0;
while (g_path[i] != 0) {
if (g_path[i] == '/') last = i;
i = i + 1;
}
n = 0;
i = last + 1;
while (g_path[i] != 0 && n < 40) { g_base[n] = g_path[i]; i = i + 1; n = n + 1; }
g_base[n] = 0;
return g_base;
}
/* ── 拿图 ───────────────────────────────────────────────────────────── */
/* -- Getting a picture -- */
/* 要一张图。`fromGallery` 0 = 拍照,1 = 相册。
* Ask for a picture. `fromGallery` 0 = take a photo, 1 = pick from the gallery.
* ⚠ **0 不是错误**(取消 / 没权限 / 这一端没有相机,三者都是 0)——
* ⚠ **0 is not an error** (a cancel, a missing permission and a missing camera all give 0) --
* 这里把"没有"记下来由界面**明说**,而不是静默什么都不做。
* so "absent" is recorded and the UI **says so**, instead of silently doing nothing.
*/
static void requestImage(int fromGallery) {
int n;
g_asked = 1;
n = 0;
if (fromGallery != 0) n = ui_camera_pick(g_path, PATH_CAP);
else n = ui_camera_capture(g_path, PATH_CAP);
if (n <= 0) {
g_have = 0;
g_none = 1;
g_path[0] = 0;
g_imgW = 0;
g_imgH = 0;
return;
}
g_none = 0;
g_have = 1;
/* 宿主保证末尾写了 0,但长度就是字节数,自己再补一次更稳(多余的字节不漏出去)。 */
/* The host does terminate the string, but the length it returned is the byte count; writing the
* terminator again is cheaper than trusting a buffer we do not own. */
g_path[n] = 0;
probeImageSize();
}
/* ── 版面 ───────────────────────────────────────────────────────────── */
/* -- Layout -- */
static void layout(void) {
SW = ui_scr_get_w();
SH = ui_scr_get_h();
if (SW <= 0) SW = 360;
if (SH <= 0) SH = 620;
g_pad = SW / 18;
if (g_pad < 10) g_pad = 10;
/* 两个按钮并排钉在底部,图框吃掉中间剩下的全部高度 —— 先算按钮,图框才有上界 */
/* The two buttons sit side by side at the bottom and the box takes whatever height is left, so the
* buttons are laid out first and the box is bounded by them. */
b1H = BTN_H;
b2H = BTN_H;
b1Y = SH - g_pad - BTN_H;
b2Y = b1Y;
b1W = (SW - g_pad * 2 - 10) / 2;
b2W = b1W;
if (b1W < 60) b1W = 60;
b2W = b1W;
b1X = g_pad;
b2X = SW - g_pad - b2W;
/* 图框下面两行(文件名 / 尺寸):行高跟着字号走,两行都钉在按钮之上 */
/* Two lines under the box (name / size): the row height follows the font, and both sit above the buttons */
infoY = b1Y - 44;
boxX = g_pad;
boxW = SW - g_pad * 2;
boxY = g_pad + 42; /* 标题两行之下 */
/* below the two heading lines */
boxH = infoY - boxY - 10;
if (boxH < 80) boxH = 80;
}
/* ── 绘制 ───────────────────────────────────────────────────────────── */
/* -- Drawing -- */
/* 把图按**等比缩放**摆进图框(宽高各让一步,取更小的那个倍率 ⇒ 不拉伸也不出血)。
* Fit the picture into the box while **keeping the aspect ratio** (both dimensions get a vote and the
* 拿不到像素尺寸时退回"铺满图框":那至少能看见图片本身。
* smaller ratio wins, so nothing is stretched or cropped). With an unknown size it falls back to
* filling the box, which at least shows the picture.
*/
static void drawPicture(void) {
int dw;
int dh;
int x;
int y;
int availW;
int availH;
dw = g_imgW;
dh = g_imgH;
availW = boxW - 16;
availH = boxH - 16;
if (availW < 8) availW = 8;
if (availH < 8) availH = 8;
if (dw <= 0 || dh <= 0) {
dw = availW;
dh = availH;
} else if (dw * availH > dh * availW) {
/* ⚠ 先乘后除:`dw * availH / dh` 写成 `dw / dh * availH` 会先整除成 0(整数除法)*/
/* ⚠ Multiply before dividing: `dw / dh * availH` truncates to 0 first (integer division) */
dh = dh * availW / dw;
dw = availW;
} else {
dw = dw * availH / dh;
dh = availH;
}
if (dw < 1) dw = 1;
if (dh < 1) dh = 1;
x = boxX + (boxW - dw) / 2;
y = boxY + (boxH - dh) / 2;
ui_2d_image(x, y, g_path, dw, dh);
}
static void drawBox(void) {
int cy;
ui_2d_rect(boxX, boxY, boxW, boxH, C_BOX, 1, 0, 8);
ui_2d_rect(boxX, boxY, boxW, boxH, C_RING, 0, 1, 8);
cy = boxY + boxH / 2;
if (g_have != 0) {
drawPicture();
return;
}
if (g_none != 0) {
/* ⚠ **降级分支**:明说"这台机器没有",别留一块空白。 */
/* ⚠ **The degraded branch**: say plainly that this machine lacks it instead of showing a blank box. */
ui_2d_text(SW / 2, cy - 24, lang == 0 ? "本机没有相机 / 相册" : "no camera or gallery here",
C_ERR, 17, VML_ANCHOR_CENTER);
ui_2d_text(SW / 2, cy + 2, lang == 0 ? "(桌面上就是这样,手机上有)" : "(that is the desktop; phones have both)",
C_ERR, 12, VML_ANCHOR_CENTER);
ui_2d_text(SW / 2, cy + 26, lang == 0 ? "再点一次也还是 0:这一端没有这个能力" : "pressing again still gives 0: this end lacks the capability",
C_DIM, 11, VML_ANCHOR_CENTER);
return;
}
ui_2d_text(SW / 2, cy - 20, lang == 0 ? "点下面的按钮要一张图" : "press a button below to get a picture",
C_DIM, 15, VML_ANCHOR_CENTER);
ui_2d_text(SW / 2, cy + 4, lang == 0 ? "「拍照」或「从相册选」" : "\"Take a photo\" or \"Pick from the gallery\"",
C_DIM, 12, VML_ANCHOR_CENTER);
}
static void drawInfo(void) {
char buf[96];
if (g_have == 0) return;
buf[0] = 0;
strcat(buf, lang == 0 ? "文件:" : "file: ");
strcat(buf, baseName());
ui_2d_text(SW / 2, infoY, buf, C_NAME, 13, VML_ANCHOR_CENTER);
buf[0] = 0;
if (g_imgW > 0 && g_imgH > 0) {
strcat(buf, numstr(g_imgW));
strcat(buf, " x ");
strcat(buf, numstr(g_imgH));
strcat(buf, lang == 0 ? " 像素" : " px");
} else {
strcat(buf, lang == 0 ? "尺寸未知(不是 PNG,或读不到文件)" : "size unknown (not a PNG, or unreadable)");
}
ui_2d_text(SW / 2, infoY + 20, buf, C_SIZE, 12, VML_ANCHOR_CENTER);
}
static void drawButton(int x, int y, int w, int h, char *label, char *key) {
char buf[64];
ui_2d_rect(x, y, w, h, C_BTN, 1, 0, h / 2);
ui_2d_rect(x, y, w, h, C_BTNHOT, 0, 1, h / 2);
buf[0] = 0;
strcat(buf, label);
strcat(buf, " ");
strcat(buf, key);
ui_2d_text(x + w / 2, y + (h - 15) / 2, buf, C_TITLE, 15, VML_ANCHOR_CENTER);
}
static void drawButtons(void) {
drawButton(b1X, b1Y, b1W, b1H, lang == 0 ? "拍照" : "Photo", "Enter");
drawButton(b2X, b2Y, b2W, b2H, lang == 0 ? "从相册选" : "Gallery", "P");
}
static void draw(void) {
ui_2d_clear(C_BG);
ui_2d_text_styled(SW / 2, g_pad, lang == 0 ? "相机 / 相册" : "Camera / Gallery",
C_TITLE, 18, VML_ANCHOR_CENTER, VML_FONT_BOLD);
if (g_have != 0) {
ui_2d_text(SW / 2, g_pad + 24, lang == 0 ? "ui_2d_image 按画布等比缩放" : "ui_2d_image, scaled to fit the box",
C_DIM, 11, VML_ANCHOR_CENTER);
} else if (g_none != 0) {
ui_2d_text(SW / 2, g_pad + 24, lang == 0 ? "这一端没有相机(返回 0,照常继续)" : "this end has no camera (0, carry on)",
C_ERR, 11, VML_ANCHOR_CENTER);
} else {
ui_2d_text(SW / 2, g_pad + 24, lang == 0 ? "手机上才有;桌面上会如实报「没有」" : "phones only; the desktop reports \"absent\"",
C_DIM, 11, VML_ANCHOR_CENTER);
}
drawBox();
drawInfo();
drawButtons();
ui_2d_text(SW / 2, SH - 14, lang == 0 ? "「拍照」回车 / 「相册」P Esc / q 退出"
: "Enter = photo, P = gallery Esc / q quits",
C_DIM, 11, VML_ANCHOR_CENTER);
ui_2d_present(); /* ⚠ 不调这句 = 黑屏 */
/* without this call you get a black screen */
}
/* ── 命中判定 ───────────────────────────────────────────────────────── */
/* -- Hit testing -- */
/* 点在哪个按钮里:0 = 没点着,1 = 拍照,2 = 相册(与 `drawButtons` 用的是**同一组**坐标)*/
/* Which button a tap landed on: 0 = none, 1 = photo, 2 = gallery (the **same** geometry `drawButtons` uses) */
static int hitButton(int px, int py) {
if (px >= b1X && px < b1X + b1W && py >= b1Y && py < b1Y + b1H) return 1;
if (px >= b2X && px < b2X + b2W && py >= b2Y && py < b2Y + b2H) return 2;
return 0;
}
/* ── 主流程 ─────────────────────────────────────────────────────────── */
/* -- Main flow -- */
int main(void) {
int msg[4];
int t;
int t0;
int done;
int hit;
lang = ui_scr_get_language(); /* 开局查一次,别每帧调 */
/* queried once, not every frame */
layout();
ui_win_open_ex(lang == 0 ? "相机 / 相册" : "Camera / Gallery",
0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD);
layout(); /* 开窗之后画布尺寸才是准的,再算一次版面 */
/* the canvas size is only final after the window opens, so lay out again */
draw();
t0 = ui_time_get_ms();
done = 0;
while (!done && ui_win_is_closed() == 0) {
t = ui_msg_wait(msg, 200);
if (t == VML_MSG_NONE) {
if (ui_time_get_ms() - t0 > IDLE_EXIT_MS) done = 1;
continue;
}
t0 = ui_time_get_ms();
if (t == VML_MSG_KEYDOWN) {
int k = msg[1];
if (k == VML_KEY_ESCAPE || k == 'q' || k == 'Q') done = 1;
else if (k == VML_KEY_ENTER || k == VML_KEY_SPACE) { requestImage(0); draw(); }
else if (k == 'p' || k == 'P') { requestImage(1); draw(); }
continue;
}
if (t == VML_MSG_TOUCHDOWN || t == VML_MSG_MOUSEDOWN) {
hit = hitButton(msg[1], msg[2]);
if (hit == 1) { requestImage(0); draw(); }
else if (hit == 2) { requestImage(1); draw(); }
continue;
}
if (t == VML_MSG_WINDOWORIENT || t == VML_MSG_WINDOWRESIZE) {
layout();
draw();
continue;
}
if (t == VML_MSG_WINDOWCLOSE) done = 1;
}
return 0;
}