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

java/ui/demo_ui.java · 20284 字节 · 下载原文

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

// demo_ui.java — **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.

//

// ## ⚠ 这份例程的两条写法都是**被 Java 前端的缺口逼出来的**,不是风格

// ## ⚠ Both style choices in this example were **forced by gaps in the Java frontend**, not style

//

// ### ① ~~不用 `static` 标量字段,也不定义常量~~ —— 2026-10-04 复验**已修好**,但本文件保持原样

// ### ① ~~No `static` scalar fields and no constants~~ -- **fixed**, re-verified 2026-10-04; this file keeps its original form

//

// ~~实测(2026-09-24):Java 的类字段读出来是它的地址、不是它的值~~(`static int A = 9` 读回 1024、

// ~~Measured (2026-09-24): a Java class field reads back as its address, not its value~~ (`static int A = 9` read back 1024,

// `static final int B = 9` 读回 1028、`static int C; C = 9` 读回 1032)。根因是数据段标量读取用了"取地址",2026-09-30 已修。

// `static final int B = 9` read 1028, `static int C; C = 9` read 1032). The data-section scalar read took the address; fixed 2026-09-30.

//

// 实测(2026-10-04):`static int A = 9` / `static final int B = 9` / `static int C; C = 9` 全部读回 **9**,

// Measured (2026-10-04): `static int A = 9` / `static final int B = 9` / `static int C; C = 9` all read back **9**,

// `if (t == MSG_TIMER)` 成立(主循环不再变死循环);跨方法读写 `static` 标量也对(setter 写 42 → getter 读到 42)。

// `if (t == MSG_TIMER)` holds (no more infinite loop); cross-method read/write of a `static` scalar is correct too (setter writes 42 -> getter reads 42).

// ⇒ 本文件仍把状态放在 `static int[] A`(能跑就别动;新写的代码可以用 `static` 标量与 `static final` 常量)。

// => this file still keeps its state in `static int[] A` (leave working code alone; new code may use `static` scalars and `static final` constants).

//

// ⚠ `static int[] A` **能用**(数组字段要的正好是一个地址,而字段读出来的就是地址):

// ⚠ `static int[] A` **works** (an array field wants exactly an address, and the field reads back as one):

//   `catch.java` 一直用这个模式,本文件照它写。

//   `catch.java` has always used this pattern, and this file follows it.

//

// ### ② 不把数组传给库函数 —— 用标量版的消息读取

// ### ② Don't pass arrays to library functions — use the scalar message readers

//

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

// When Java passes an `int[]` to a library `int*` parameter the pointer lands **4 bytes before** the data start (the "length header"):

//

//   · **写**:库写 `msg[0]` 落在数组的 `msg[-1]`,我们读到的 `msg[0]` 是库写的 `msg[1]`

//   · **Writes**: the library writing `msg[0]` lands on the array's `msg[-1]`, so the `msg[0]` we read is the library's `msg[1]`

//     (实测 `ui_msg_wait(msg,…)`:库里 `msg=(类型,A,B,时间戳)`,Java 读到 `(A,B,时间戳,旧值)`)。

//     (measured with `ui_msg_wait(msg,…)`: the library writes `msg=(type,A,B,timestamp)`, Java reads `(A,B,timestamp,old value)`).

//   · **读**:库读到的 `pts[0]` 是数组的长度头 ⇒ `callwithint8({1,11,22,…})`

//   · **Reads**: what the library reads as `pts[0]` is the array's length header ⇒ `callwithint8({1,11,22,…})`

//     C 侧返回 `84197531`、Java 侧返回 **-6**(宿主"没有这个调用号")。

//     the C side returns `84197531`, the Java side returns **-6** (the host says "no such call number").

//

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

// So on Java 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, …)` 这类收数组的图元在 Java 里不能用,本 demo 不碰。

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

// ✅ 2026-10-04 复验仍成立:`callwithint8({1,11,…,77})` Java 侧得 **-6**、C 侧得 84197531(数组传给库的 `int*` 形参仍然对不上)。

// ✅ Re-verified 2026-10-04, still holds: `callwithint8({1,11,…,77})` gives **-6** on the Java side vs. 84197531 on the C side (arrays passed to a library `int*` parameter are still misaligned).

//

// ## 退出是有界的

// ## 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/java/demo_ui.java`;

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

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

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


#include "../../../Lib/java/vml_ui.java"
public class DemoUi {

    // ── 窗体 ──

    // ── The window ──

    static native int  ui_win_open_ex(String title, int w, int h, int rotatable, int gamepad);
    static native int  ui_win_close();
    static native int  ui_win_is_closed();
    static native int  ui_scr_get_w();
    static native int  ui_scr_get_h();
    static native int  ui_scr_get_orientation();
    static native int  ui_scr_get_language();

    // ── 绘图 ──

    // ── Drawing ──

    static native void ui_2d_clear(int color);
    static native void ui_2d_rect(int x, int y, int w, int h, int color, int fill, int lw, int radius);
    static native void ui_2d_circle(int cx, int cy, int r, int color, int fill, int lw);
    static native void ui_2d_ellipse(int cx, int cy, int rx, int ry, int color, int fill, int lw);
    static native void ui_2d_line(int x1, int y1, int x2, int y2, int color, int lw);
    static native void ui_2d_text(int x, int y, String s, int color, int size, int anchor);
    static native void ui_2d_text_styled(int x, int y, String s, int color, int size, int anchor, int style);
    static native void ui_2d_present();

    // ── 消息(**标量**版,见文件头 ②)──

    // ── Messages (**scalar** version, see header ②) ──

    static native int  ui_msg_fetch_wait(int timeout_ms);
    static native int  ui_msg_get_a();
    static native int  ui_msg_get_b();
    static native int  ui_timer_set(int interval_ms, int tag);
    static native int  ui_timer_kill(int id);

    // ── 全部状态放这一个数组(见文件头 ①)──

    //   0=sw 1=sh 2=gy 3=frames 4=keys 5=touches 6=orient 7=tx 8=ty 9=tid 10=lang

    static int[] A = new int[11];

    // 消息类型 / 锚点 / 方向 / 字体样式(源:Lib/c/vml_ui.h;这里只能写字面量)

    //   MSG_NONE=0 KEYDOWN=1 TIMER=9 TOUCHDOWN=6 MOUSEDOWN=4

    //   WINDOWCLOSE=10 WINDOWRESIZE=11 WINDOWORIENT=12

    //   ANCHOR_LEFT=0 CENTER=1 FONT_BOLD=1 ORIENT_PORTRAIT=0 LANDSCAPE=1

    //   WIN_ROTATABLE=1 WIN_NEED_GAMEPAD=1      MAX_FRAMES=60

    //

    // 颜色一律写**负数十进制**(能跑就别动)。~~Java 前端对十六进制字面量有解读差异(见 catch.java 的注记)~~ —— 2026-10-04 复验**已修**:

    // Colors are always written as **negative decimals** (leave working code alone). ~~the Java frontend interprets hex literals differently (see catch.java)~~ -- **fixed**, re-verified 2026-10-04:

    //   `0xFF` 得 255、`0xFF101018` 得 -15724520、`0xFFFFE060` 得 -8096;新写的代码可以直接用十六进制。

    //   `0xFF` gives 255, `0xFF101018` gives -15724520, `0xFFFFE060` gives -8096; new code may use hex directly.

    // 下面是本文件用到的那几个,名字 ↔ 0xAARRGGBB ↔ 十进制 三者对照:

    //   BG     0xFF101018 = -15724520      PANEL  0xFF1A1A24 = -15066588

    //   BLUE   0xFF4A90D9 = -11890471      ORANGE 0xFFE06C50 =  -2069424

    //   YELLOW 0xFFD9B44A =  -2509750      GREEN  0xFF50C878 = -11483016

    //   TITLE  0xFFE8E8F0 =  -1513232      ACCENT 0xFF51E86E = -11409298

    //   MUTED  0xFF9AA0B0 =  -6643536      GOLD   0xFFFFE060 =    -8096


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

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

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

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

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

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

        // ⚠ 下限保 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.

        A[2] = (A[1] - 300) / 2;        // GY

        if (A[2] < 14) { A[2] = 14; }
    }

    static void draw() {
        int lang = A[10];
        int sw = A[0];
        int sh = A[1];
        int gy = A[2];
        int cx = sw / 2;
        int pad = sw / 16;
        int i;
        int w;

        ui_2d_clear(-15724520);                                    // 0xFF101018


        ui_2d_text_styled(cx, gy, lang == 0 ? "UI 接口 / demo_ui.java" : "UI interface / demo_ui.java", -1513232, 16, 1, 1);
        if (A[6] == 1) {                                        // ORIENT_LANDSCAPE

            ui_2d_text(cx, gy + 26, lang == 0 ? "屏幕方向 = 横屏 (LANDSCAPE)" : "Orientation = LANDSCAPE", -11409298, 13, 1);
        } else {
            ui_2d_text(cx, gy + 26, lang == 0 ? "屏幕方向 = 竖屏 (PORTRAIT)" : "Orientation = PORTRAIT", -11409298, 13, 1);
        }
        ui_2d_text(cx, gy + 46, lang == 0 ? "画布按宿主给的尺寸现排(旋转后跟着变)" : "Canvas laid out from the host size (follows rotation)", -6643536, 12, 1);

        // 跟随尺寸的方框:旋转后跟着变宽变矮(这就是"不写死坐标"的证明)

        // A size-following box: on rotation it follows and gets wider and shorter (this is the proof of "no hardcoded coordinates")

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

        ui_2d_circle(cx - sw / 6, gy + 140, sw / 12, -2069424, 1, 0);
        ui_2d_ellipse(cx + sw / 6, gy + 140, sw / 9, sw / 18, -2509750, 1, 0);
        ui_2d_line(pad, gy + 172, sw - pad, gy + 172, -11483016, 3);

        // 8 格真彩色带

        // 8-cell true-color bar

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

        // ⚠ 屏上不写数字:这里用**条形长度**表达进度,具体数字走 stdout。

        // ⚠ No numbers on screen: progress is expressed here as a **bar length** and the actual numbers go to stdout.

        //   ~~Java 侧 `int` → `String` 要靠 `+` 拼接,而那个是坏的~~ —— 2026-10-04 复验**已修**

        //   ~~on the Java side `int` → `String` needs `+` concatenation, and that is broken~~ -- **fixed**, re-verified 2026-10-04

        //   (`"n=" + n` 得 `n=7`、`"score " + a + " pts"` 得 `score 3 pts`)。本文件保持原样(能跑就别动)。

        //   (`"n=" + n` gives `n=7`, `"score " + a + " pts"` gives `score 3 pts`). This file keeps its original form (leave working code alone).

        ui_2d_text(pad, gy + 236, lang == 0 ? "已跑帧数(条形)" : "Frames run (bar)", -6643536, 12, 0);
        ui_2d_rect(pad, gy + 254, sw - pad * 2, 14, -15066588, 1, 0, 4);
        ui_2d_rect(pad, gy + 254, (sw - pad * 2) * A[3] / 60, 14, -11483016, 1, 0, 4);

        ui_2d_text(pad, gy + 278, lang == 0 ? "按键 / 触摸来了就画一个标记" : "A marker is drawn on key / touch", -6643536, 12, 0);
        if (A[4] > 0) { ui_2d_circle(pad + 20, gy + 306, 14, -2509750, 1, 0); }
        if (A[5] > 0) { ui_2d_circle(pad + 60, gy + 306, 14, -11409298, 1, 0); }

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

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

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

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

        ui_2d_present();
    }

    public static void main(String[] args) {
        int done = 0;
        int t;
        int w;
        int h;

        A[7] = -1;                      // gTx = -1(还没点过)

        // gTx = -1 (not tapped yet)

        A[3] = 0;
        A[4] = 0;
        A[5] = 0;
        A[10] = ui_scr_get_language();

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

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

        A[6] = 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.java", 0, 0, 1, 1);
        relayout();
        draw();

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

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

        A[9] = ui_timer_set(60, 1);
        while (done == 0 && ui_win_is_closed() == 0) {
            t = ui_msg_fetch_wait(200);
            if (t == 0) { continue; }                  // MSG_NONE


            if (t == 9) {                              // MSG_TIMER

                A[3] = A[3] + 1;
                draw();
                if (A[3] >= 60) { done = 1; }          // 有界:到点自己停

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

                continue;
            }

            if (t == 1) {                              // MSG_KEYDOWN

                A[4] = A[4] + 1;
                draw();
                done = 1;                              // 有界:收到键就退

                // bounded: exits as soon as a key arrives

                continue;
            }

            if (t == 6 || t == 4) {                    // MSG_TOUCHDOWN / MSG_MOUSEDOWN

                A[5] = A[5] + 1;
                A[7] = ui_msg_get_a();                     // A=x B=y

                A[8] = ui_msg_get_b();
                draw();
                done = 1;                              // 有界:点到就退

                // bounded: exits as soon as a tap arrives

                continue;
            }

            if (t == 12) {                             // MSG_WINDOWORIENT

                A[6] = ui_msg_get_a();                     // A = 新方向

                // A = the new orientation

                continue;
            }

            if (t == 11) {                             // MSG_WINDOWRESIZE

                relayout();                            // 换坐标系了 ⇒ 重排 + 重画

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

                draw();
                continue;
            }

            if (t == 10) { break; }                    // MSG_WINDOWCLOSE

        }

        ui_timer_kill(A[9]);
        ui_win_close();

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

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

        if (A[6] == 1) {
            System.out.println("demo_ui: orient=LANDSCAPE");
        } else {
            System.out.println("demo_ui: orient=PORTRAIT");
        }
        System.out.print("demo_ui: canvas="); System.out.print(A[0]);
        System.out.print("x");                 System.out.println(A[1]);
        System.out.print("demo_ui: frames=");  System.out.println(A[3]);
        System.out.print("demo_ui: keys=");    System.out.println(A[4]);
        System.out.print("demo_ui: touches="); System.out.println(A[5]);
        System.out.println("demo_ui: done");
    }
}