← 都来码 / Dolaima← 例程索引csharp/ui/demo_ui.cs原文

csharp/ui/demo_ui.cs · 15560 字节 · 下载原文

// demo_ui.cs —— **第 4 层:最新 UI 接口**(`ui_*` / `Lib/c/vml_ui.h`)

// demo_ui.cs — **Layer 4: the newest UI interface** (`ui_*` / `Lib/c/vml_ui.h`)

//

// 与第 3 层(BGI)正好相反:

// Exactly the opposite of layer 3 (BGI):

//

//   · **没有固定分辨率**:画布多大由宿主给(`ui_scr_get_w()` / `ui_scr_get_h()`),程序现排版。

//   · **No fixed resolution**: how big the canvas is comes from the host (`ui_scr_get_w()` / `ui_scr_get_h()`), and the program lays out on the fly.

//   · **真彩 0xAARRGGBB**,不是 16 个索引色。

//   · **True color 0xAARRGGBB**, not 16 indexed colors.

//   · **有消息循环**:触摸 / 按键 / 定时器 / 窗口被关 / 被转屏都从消息里出来。

//   · **There is a message loop**: touch / key / timer / window closed / rotated all arrive as messages.

//

// ## ⚠ 消息用**标量**版读,不把数组传给库函数

// ## ⚠ Read messages with the **scalar** version; don't pass arrays to library functions

//

// C# 的 `int[]` 传给库里 `int*` 形参时**指针落在数据起点前 4 字节**("长度头")上。

// When a C# `int[]` is passed to a library `int*` parameter the **pointer lands 4 bytes before the data start** (the "length header").

// 实测(2026-09-24):`callwithint8({1,11,22,…})` —— C 侧返回 `84197531`、

// Measured (2026-09-24): `callwithint8({1,11,22,…})` — the C side returns `84197531`,

// **C# 侧返回 -6**(宿主"没有这个调用号" ⇒ 它读到的调用号不是 1)。同理 `ui_msg_wait(msg,…)`

// **the C# side returns -6** (the host says "no such call number" ⇒ the call number it read wasn't 1). Likewise the

// 里宿主写的 `msg=(类型,A,B,时间戳)` 会被错位读到。

// `msg=(type,A,B,timestamp)` the host writes in `ui_msg_wait(msg,…)` is read back shifted.

//

// 所以这一层在 C# 里只用**标量**版:`ui_msg_fetch_wait` / `ui_msg_get_a` / `ui_msg_get_b`

// So on C# this layer only uses the **scalar** versions: `ui_msg_fetch_wait` / `ui_msg_get_a` / `ui_msg_get_b`

// —— `vml_ui.h` 里那组"不碰指针的消息读取(非 C 语言用)"正是为这个准备的。

// —— That set of "pointer-free message readers (for non-C languages)" in `vml_ui.h` exists precisely for this.

// 同理 `ui_2d_polygon(int* pts, …)` 这类收数组的图元在 C# 里不能用,本 demo 不碰。

// Likewise array-taking primitives such as `ui_2d_polygon(int* pts, …)` cannot be used from C#, and this demo does not touch them.

//

// ## 退出是有界的

// ## Exit is bounded

//

// 主循环数**定时器拍数**,到 60 拍自己停;按任意键 / 点任意处也能提前退。

// The main loop counts **timer ticks** and stops itself at 60; any keypress / any tap also exits early.

//

// 跑法:手机 `vml run examples/csharp/demo_ui.cs`;

// How to run: on the phone `vml run examples/csharp/demo_ui.cs`;

//       桌面 `vmlcli Examples/csharp/demo_ui.cs --frames /tmp/fr`

//       on the desktop `vmlcli Examples/csharp/demo_ui.cs --frames /tmp/fr`


// 显式引用 UI 库:`ui_*` 的声明在 `Lib/csharp/VmlUi.cs`(tools/GenLib 生成,别手改)。

// Explicit reference to the UI library: `ui_*` is declared in `Lib/csharp/VmlUi.cs` (generated by tools/GenLib; don't hand-edit).

using Vml.Ui;

class DemoUi
{
    // ── 消息类型 / 锚点 / 方向 / 字体样式(源:Lib/c/vml_ui.h)──

    // ── Message types / anchors / orientation / font styles (source: Lib/c/vml_ui.h) ──

    const int MSG_NONE = 0;
    const int MSG_KEYDOWN = 1;
    const int MSG_TOUCHDOWN = 6;
    const int MSG_MOUSEDOWN = 4;
    const int MSG_TIMER = 9;
    const int MSG_WINDOWCLOSE = 10;
    const int MSG_WINDOWRESIZE = 11;
    const int MSG_WINDOWORIENT = 12;

    const int ANCHOR_LEFT = 0;
    const int ANCHOR_CENTER = 1;
    const int FONT_BOLD = 1;
    const int ORIENT_LANDSCAPE = 1;
    const int WIN_ROTATABLE = 1;
    const int WIN_NEED_GAMEPAD = 1;
    const int MAX_FRAMES = 60;             // 到点自己停:60 拍 × 60ms ≈ 3.6 秒

    // stops by itself when the time is up: 60 ticks × 60ms ≈ 3.6 seconds


    // ── 颜色(`0xAARRGGBB` 的十进制负数形式,与仓库里其它语言例程一致)──

    // ── Colors (the decimal-negative form of `0xAARRGGBB`, consistent with the other language examples in the repo) ──

    const int BG = -15724520;              // 0xFF101018

    const int PANEL = -15066588;           // 0xFF1A1A24

    const int BLUE = -11890471;            // 0xFF4A90D9

    const int ORANGE = -2069424;           // 0xFFE06C50

    const int YELLOW = -2509750;           // 0xFFD9B44A

    const int GREEN = -11483016;           // 0xFF50C878

    const int TITLE = -1513232;            // 0xFFE8E8F0

    const int ACCENT = -11409298;          // 0xFF51E86E

    const int MUTED = -6643536;            // 0xFF9AA0B0


    // ── 状态 ──

    // ── State ──

    static int sw;
    static int sh;
    static int gy;
    static int frames;
    static int keys;
    static int touches;
    static int orient;
    static int tx = -1;
    static int ty = -1;
    static int tid;
    static int lang;                 // 界面语言:0=中文 1=英文(开局问一次宿主,见 Main)

    // UI language: 0=Chinese 1=English (asked of the host once at the start, see Main)


    static void relayout()
    {
        sw = ui_scr_get_w();
        sh = ui_scr_get_h();
        if (sw <= 0) sw = 360;
        if (sh <= 0) sh = 620;
        // 内容整块**在画布内垂直居中**(用户 2026-10-07 报的「很多程序文字没居中、很靠上」):

        // 这一份的控件是**从 gy 往下逐项堆**的,最后一行文字落在 gy+278 ⇒ 整块高约 300

        // (与样板 `c/ui/demo_ui.c` 取**同一个数**,两处布局不要各算各的)。

        // ⚠ 按键 / 触摸的标记圆画在 gy+306,但它们**只在有输入时才画**(`if (keys > 0)`)——

        //    无输入时根本不在画面上,**不是稳态画面的一部分 ⇒ 不计入总高**(算进去会把整块顶高 30px)。

        // 原先写死 `gy = 14` ⇒ 内容只占画布上部三成。`sh` 是**画布**高(见上面两行)。

        // ⚠ 下限保 14:画布特别矮时(横屏分屏)别把内容推出上边缘。

        // Center the whole block vertically (user report, 2026-10-07). The widgets are stacked

        // downward from gy and the last text row sits at gy+278 => the block is about 300 tall, the

        // same number the reference sample `c/ui/demo_ui.c` uses. The key / touch marker circles at

        // gy+306 are drawn **only when there is input**, so they are not part of the steady-state

        // block and are excluded. Clamp at 14 so a short canvas cannot push it off the top.

        gy = (sh - 300) / 2;
        if (gy < 14) gy = 14;
    }

    static void draw()
    {
        int cx = sw / 2;
        int pad = sw / 16;
        int i = 0;
        int w;

        ui_2d_clear(BG);

        ui_2d_text_styled(cx, gy, lang == 0 ? "UI 接口 / demo_ui.cs" : "UI interface / demo_ui.cs", TITLE, 16, ANCHOR_CENTER, FONT_BOLD);
        if (orient == ORIENT_LANDSCAPE)
        {
            ui_2d_text(cx, gy + 26, lang == 0 ? "屏幕方向 = 横屏 (LANDSCAPE)" : "Orientation = LANDSCAPE", ACCENT, 13, ANCHOR_CENTER);
        }
        else
        {
            ui_2d_text(cx, gy + 26, lang == 0 ? "屏幕方向 = 竖屏 (PORTRAIT)" : "Orientation = PORTRAIT", ACCENT, 13, ANCHOR_CENTER);
        }
        ui_2d_text(cx, gy + 46, lang == 0 ? "画布按宿主给的尺寸现排(旋转后跟着变)" : "Canvas laid out from the host size (follows rotation)", MUTED, 12, ANCHOR_CENTER);

        // 跟随尺寸的方框:旋转后跟着变宽变矮

        // A size-following box: on rotation it follows and gets wider and shorter

        ui_2d_rect(pad, gy + 70, sw - pad * 2, 90, PANEL, 1, 0, 10);
        ui_2d_rect(pad + 6, gy + 76, sw - pad * 2 - 12, 30, BLUE, 1, 0, 6);
        ui_2d_text(pad + 16, gy + 84, lang == 0 ? "rect / round-rect(随屏宽伸缩)" : "rect / round-rect (stretches with the width)", BG, 12, ANCHOR_LEFT);

        ui_2d_circle(cx - sw / 6, gy + 140, sw / 12, ORANGE, 1, 0);
        ui_2d_ellipse(cx + sw / 6, gy + 140, sw / 9, sw / 18, YELLOW, 1, 0);
        ui_2d_line(pad, gy + 172, sw - pad, gy + 172, GREEN, 3);

        // 8 格真彩色带

        // 8-cell true-color bar

        while (i < 8)
        {
            w = (sw - pad * 2) / 8;
            if (i % 2 == 0) ui_2d_rect(pad + i * w, gy + 186, w - 2, 16, ORANGE, 1, 0, 2);
            else            ui_2d_rect(pad + i * w, gy + 186, w - 2, 16, BLUE, 1, 0, 2);
            i = i + 1;
        }
        ui_2d_text(cx, gy + 210, lang == 0 ? "真彩 0xAARRGGBB(不是索引色)" : "True color 0xAARRGGBB (not indexed)", MUTED, 12, ANCHOR_CENTER);

        // 进度用**条形长度**表达(C# 前端没有 int→string,见 demo_std.cs);

        // Progress is expressed as a **bar length** (the C# frontend has no int→string, see demo_std.cs);

        // 具体数字走 stdout —— 那边 Console 打印是好的。

        // the actual numbers go to stdout — printing there with Console works.

        ui_2d_text(pad, gy + 236, lang == 0 ? "已跑帧数(条形)" : "Frames run (bar)", MUTED, 12, ANCHOR_LEFT);
        ui_2d_rect(pad, gy + 254, sw - pad * 2, 14, PANEL, 1, 0, 4);
        ui_2d_rect(pad, gy + 254, (sw - pad * 2) * frames / MAX_FRAMES, 14, BLUE, 1, 0, 4);

        ui_2d_text(pad, gy + 278, lang == 0 ? "按键 / 触摸来了就画一个标记" : "A marker is drawn on key / touch", MUTED, 12, ANCHOR_LEFT);
        if (keys > 0) ui_2d_circle(pad + 20, gy + 306, 14, YELLOW, 1, 0);
        if (touches > 0) ui_2d_circle(pad + 60, gy + 306, 14, ACCENT, 1, 0);

        // 触摸标记:点哪儿就在哪儿留个圈(证明坐标真能用)

        // Touch marker: a circle is left wherever you tap (proof the coordinates really work)

        if (tx >= 0)
        {
            ui_2d_circle(tx, ty, 18, YELLOW, 0, 2);
            ui_2d_circle(tx, ty, 4, YELLOW, 1, 0);
            ui_2d_text(cx, sh - 44, lang == 0 ? "触摸坐标已经用上了" : "Touch coordinates are in use", YELLOW, 12, ANCHOR_CENTER);
        }
        else
        {
            ui_2d_text(cx, sh - 44, lang == 0 ? "点一下屏幕 / 按任意键退出" : "Tap the screen / press any key to exit", MUTED, 12, ANCHOR_CENTER);
        }

        ui_2d_text(cx, sh - 24, lang == 0 ? "退出:按任意键或点任意处(或等 N 帧到点)" : "Exit: any key or any tap (or wait out the frame count)", MUTED, 12, ANCHOR_CENTER);

        ui_2d_present();
    }

    static void Main()
    {
        int done = 0;
        int t;
        int w;
        int h;

        // 界面语言:开局查一次(`ui_scr_get_language` 是 syscall)—— 文案按它取

        // UI language: queried once at startup (`ui_scr_get_language` is a syscall) — the strings are picked by it

        lang = ui_scr_get_language();

        // ── ① 开窗**之前**就问屏幕方向 ──

        // ── ① Ask the screen orientation **before** opening the window ──

        orient = ui_scr_get_orientation();

        // 开窗:尺寸传 0 ⇒ **交给宿主用真实可用区**(开窗前 `ui_scr_get_w/h` 只能估算,

        // Open the window: pass size 0 ⇒ **the host uses the real available area** (before the open,

        // 估算与实际有出入 —— 那正是"内容偏上"的根因;开窗后 `relayout()` 再问一次拿到的是真实画布)。

        // `ui_scr_get_w/h` can only estimate -- exactly why the content sat high; `relayout()` re-queries the real canvas after the open).

        // 其余声明照旧:"支持旋转 + 要手柄"(转屏时宿主会把新坐标空间整个给过来)。

        // The rest is unchanged: "rotation supported + gamepad wanted" (on rotation the host hands over the whole new coordinate space).

        // ⚠ 下面这两句是**开窗前的估算值**,只用于兜底默认尺寸;开窗后 `relayout()` 会重新问一次。

        // ⚠ The two reads below are the **pre-open estimate**, kept only for the fallback default; `relayout()` re-queries after the open.

        w = ui_scr_get_w();
        h = ui_scr_get_h();
        if (w <= 0) w = 360;
        if (h <= 0) h = 620;
        ui_win_open_ex("demo_ui.cs", 0, 0, WIN_ROTATABLE, WIN_NEED_GAMEPAD);
        relayout();
        draw();

        // ── 消息循环:定时器驱动重画,**有界退出** ──

        // ── Message loop: the timer drives redraws, **exit is bounded** ──

        tid = ui_timer_set(60, 1);
        while (done == 0 && ui_win_is_closed() == 0)
        {
            t = ui_msg_fetch_wait(200);
            if (t == MSG_NONE) continue;

            if (t == MSG_TIMER)
            {
                frames = frames + 1;
                draw();
                if (frames >= MAX_FRAMES) done = 1;     // 有界:到点自己停

                // bounded: stops by itself when the time is up

                continue;
            }

            if (t == MSG_KEYDOWN)
            {
                keys = keys + 1;
                draw();
                done = 1;                              // 有界:收到键就退

                // bounded: exits as soon as a key arrives

                continue;
            }

            if (t == MSG_TOUCHDOWN || t == MSG_MOUSEDOWN)
            {
                touches = touches + 1;
                tx = ui_msg_get_a();                       // A=x B=y

                ty = ui_msg_get_b();
                draw();
                done = 1;                              // 有界:点到就退

                // bounded: exits as soon as a tap arrives

                continue;
            }

            if (t == MSG_WINDOWORIENT)
            {
                orient = ui_msg_get_a();                   // A = 新方向

                // A = the new orientation

                continue;
            }

            if (t == MSG_WINDOWRESIZE)
            {
                relayout();                            // 换坐标系了 ⇒ 重排 + 重画

                // the coordinate space changed ⇒ re-layout + redraw

                draw();
                continue;
            }

            if (t == MSG_WINDOWCLOSE) break;
        }

        ui_timer_kill(tid);
        ui_win_close();

        // 给无头验证留确定性判据(图形只能肉眼看,这几个数能自动比)

        // Deterministic checkpoints for headless verification (graphics can only be eyeballed, but these numbers can be compared automatically)

        if (orient == ORIENT_LANDSCAPE) println_str("demo_ui: orient=LANDSCAPE");
        else                            println_str("demo_ui: orient=PORTRAIT");
        print_str("demo_ui: canvas="); print_int(sw); print_str("x"); println_int(sh);
        print_str("demo_ui: frames="); println_int(frames);
        print_str("demo_ui: keys=");   println_int(keys);
        print_str("demo_ui: touches="); println_int(touches);
        println_str("demo_ui: done");
    }
}