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");
}
}