← 都来码 / Dolaima← 例程索引c/ui/shot.c原文

c/ui/shot.c · 7926 字节 · 下载原文

/* shot.c —— 主动截屏(`ui_scr_screenshot` / syscall #588)的示例与自检程序。
 * shot.c — example and self-check program for active screenshots (`ui_scr_screenshot` / syscall #588).
 *
 * ## 它演示什么
 * ## What it demonstrates
 *
 * 程序**自己**把当前画布截下来存成 PNG(不需要宿主帮忙、不需要用户按截屏键)。
 * The program captures the current canvas into a PNG **by itself** (no host help, no user pressing a screenshot key).
 * 存的是**你画的那张画面**(不含屏幕手柄与标题栏)—— 拿来当"战绩图 / 通关留念"正好。
 * It saves **the picture you drew** (without the on-screen gamepad or title bar) — perfect as a score shot or completion memento.
 * 实现走共享光栅器,四端同一份代码、不需要任何权限。
 * The implementation goes through the shared rasterizer: one codebase for all four ends, no permission needed.
 *
 * ## 三条要知道的
 * ## Three things to know
 *
 * 1. **路径是相对沙箱的**:空串用 `shot.png`、没扩展名自动补 `.png`、子目录会自动建。
 * 1. **Paths are sandbox-relative**: an empty string uses `shot.png`, a missing extension gets `.png`, subdirectories are created automatically.
 *    ⚠ 含 `..`/`.` 段或盘符(`C:\`、`http:`)的路径**直接失败**,什么都写不出来 ——
 *    ⚠ A path with a `..`/`.` segment or a drive prefix (`C:\`, `http:`) **fails outright**, nothing gets written —
 *    那是防写到你沙箱外面的唯一一道闸,故意不宽容。
 *    that is the only gate keeping writes inside your sandbox, deliberately strict.
 * 2. **会阻塞几十毫秒**(光栅化 + 编码 PNG)⇒ **别放进每帧循环**,
 * 2. **It blocks for tens of milliseconds** (rasterize + encode PNG) => **do not put it in a per-frame loop**,
 *    放在"这一局结束""按了分享键"这种一拍一次的地方。
 *    put it where it runs once per beat, such as "this round ended" or "the share key was pressed".
 * 3. **找不到文件别以为程序坏了**:文件落在**源文件旁边**(桌面端)或
 * 3. **If you cannot find the file, do not assume the program is broken**: it lands **next to the source file** (desktop) or
 *    **工作区根**(手机端,App 的「文件」页能看到)。桌面先用仓库里的 vmlcli 跑:
 *    at the **workspace root** (mobile; the App's Files page can see it). On desktop, run it with the repo's vmlcli first:
 *
 *        dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll Examples/c/shot.c
 *
 * ## C 前端的三条硬约束(与 mario.c / tetris.c 同源,踩过才写的)
 * ## Three hard constraints of the C frontend (same origin as mario.c / tetris.c, written after stepping on them)
 *
 * 1. ⚠ `${}` 占位符只认局部变量 ⇒ 调 syscall 一律走 `vml_ui.h` 的包装函数。
 * 1. ⚠ `${}` placeholders only accept local variables => always call syscalls through the `vml_ui.h` wrappers.
 * 2. 文件级全局变量是好的,可以随便用。
 * 2. File-level global variables are fine, use them freely.
 * 3. ~~⚠ `#define` 不支持反斜杠续行 ⇒ 宏一行写完。~~
 * 3. ~~⚠ `#define` does not support backslash line continuation => write the macro on one line.~~
 *    ✅ **已修好(2026-10-04 复验)**:实测 `#define ADD(a,b)` 后接反斜杠折行,`ADD(2,3)` 得 **5**;
 *    ✅ **fixed (re-checked 2026-10-04)**: `#define ADD(a,b)` continued with a backslash gives `ADD(2,3)` = **5**;
 *    多行宏 `do { … } while (0)` 得 **42**、字符串续行 `"he"`+`"llo"` 得 **hello** —— 续行/折行都能用。
 *    a multi-line `do { … } while (0)` macro gives **42** and a continued string literal `"he"`+`"llo"` gives **hello** -- continuation/line breaks both work.
 *    本文件为绕过它写的单行宏**照旧保留**(能跑就别动);**新写的代码不必再绕**。
 *    The one-line macros this file wrote to dodge it **stay as they are** (if it runs, do not touch it); **new code need not dodge**.
 */

#include <vml_ui.h>
#include <stdlib.h>

#define BG_COLOR 0xFF102030
#define FRAME_COLOR 0xFFFF0000
#define DISC_COLOR 0xFF00FF00
#define TEXT_COLOR 0xFFFFFFFF

int main(void)
{
    int w;
    int h;
    int n;
    int lang;                   /* 界面语言:开局查一次(ui_scr_get_language 是 syscall,别每帧调) */
                                /* UI language: queried once at start (ui_scr_get_language is a syscall, do not call it every frame) */

    lang = ui_scr_get_language();

    /* 不要手柄区:把画布让给自己(手柄会占掉一百多 dp) */
    /* No gamepad area: give the canvas to ourselves (the gamepad would eat 100+ dp) */
    if (ui_win_open_ex("shot", 0, 0, VML_WIN_ROTATABLE, VML_WIN_NO_GAMEPAD) < 0) {
        ui_dlg_msg(lang == 0 ? "失败" : "Failed", lang == 0 ? "开窗失败" : "Could not open the window", VML_DLG_ERROR);
        return 1;
    }
    w = ui_scr_get_w();
    h = ui_scr_get_h();

    ui_2d_clear(BG_COLOR);
    ui_2d_rect(10, 10, w - 20, h - 20, FRAME_COLOR, 0, 6, 0);   /* 描边方框 */
                                                             /* Stroked rectangle */
    ui_2d_circle(w / 2, h / 2, 60, DISC_COLOR, 1, 0);           /* 实心圆 */
                                                             /* Filled circle */
    ui_2d_text(20, 40, "SCREENSHOT DEMO", TEXT_COLOR, 20, VML_ANCHOR_LEFT);
    ui_2d_text(20, 70, "ui_scr_screenshot(\"\")", TEXT_COLOR, 14, VML_ANCHOR_LEFT);
    ui_2d_present();

    /* ① **推荐用法:传空串** ⇒ 自动命名成 shot/<窗口标题>_<日期>_<时间>.png
     * 1) **Recommended usage: pass an empty string** => auto-named shot/<window title>_<date>_<time>.png
     *   (如 shot/shot_20260924_102801.png)。连存几张不会互相覆盖。
     *   (e.g. shot/shot_20260924_102801.png). Saving several in a row will not overwrite each other. */
    n = ui_scr_screenshot("");

    /* ② 非法路径(回退段)⇒ -1,且盘上不该多出任何东西。
     * 2) An illegal path (escape branch) => -1, and nothing extra should appear on disk.
     *    这一条是"防逃逸"的现场证据:注释掉上面那句、只留这句,程序应当什么也不产出。
     *    This is the on-site evidence for "escape prevention": comment out the line above, keep only this one, and the program should produce nothing. */
    ui_scr_screenshot("../escape.png");

    /* 报数不用 printf —— 本环境的 puts/printf 在字面量多的程序里会串行/重复,
     * Do not report the number with printf — in this environment puts/printf serialize or repeat in programs with many literals,
     * 会把 stdio 的毛病看成代码的毛病(见 docs/VML游戏开发指南.md §7)。
     * which makes stdio bugs look like code bugs (see the VML game dev guide under docs, section 7).
     * 用 ui_dlg_msg 直接把返回值写出来看。
     * Use ui_dlg_msg to print the return value directly and look at it. */
    if (n > 0) {
        ui_dlg_msg(lang == 0 ? "截屏成功" : "Screenshot saved",
                   lang == 0 ? "已自动命名存进 shot/ 目录(手机:工作区根;桌面:源文件旁边)"
                             : "Auto-named into shot/ (mobile: workspace root; desktop: next to the source file)",
                   VML_DLG_INFO);
    } else {
        ui_dlg_msg(lang == 0 ? "截屏失败" : "Screenshot failed",
                   lang == 0 ? "ui_scr_screenshot 返回 -1:路径非法 / 还没有画布 / 写不进去"
                             : "ui_scr_screenshot returned -1: illegal path / no canvas yet / cannot write",
                   VML_DLG_ERROR);
    }

    ui_win_close();
    return 0;
}