pascal/ui/demo_ui.pas · 13527 字节 · 下载原文
(* demo_ui.pas —— Pascal 第四层:**最新 UI 接口**(宿主 `ui_*` 图元 + 绘图窗口)
demo_ui.pas -- Pascal layer 4: the newest UI interface (host ui_* primitives + draw window)
这一层和第三层(BGI/`uses Graph`)刻意不同。BGI 是"固定分辨率 + 索引色"的老世界,
This layer is deliberately different from layer 3 (BGI / uses Graph). BGI is the old world
这一层是**宿主窗口 + 0xAARRGGBB 真彩 + 统一消息队列**。四条关键差别:
of fixed resolution + indexed colour; this one is a host window + 0xAARRGGBB true colour + a single message queue. Four key differences:
① **开窗显式**:`ui_win_open(标题, 宽, 高)`,宽高来自 `ui_scr_get_w()/ui_scr_get_h()`
1) Window opening is explicit: ui_win_open(title, w, h); w/h come from ui_scr_get_w()/ui_scr_get_h()
(可用绘图区),不是写死的 640x480。
(the usable draw area), not a hardcoded 640x480.
② **屏幕方向要问**:`ui_scr_get_orientation()` 返回 0=竖屏 / 1=横屏 —— 程序据此决定
2) Ask for the screen orientation: ui_scr_get_orientation() returns 0 = portrait / 1 = landscape; the program
"面板怎么摆"。这是**开窗前就能问**的(详见 `docs/VML宿主接口.md`)。
uses it to decide the panel layout. This can be asked before the window is opened (see the VML host interface doc).
③ **显式呈现**:图元只是追加进场景,`ui_2d_present()` 才是"这一帧画完了"的帧边界。
3) Explicit present: primitives are only appended to the scene; ui_2d_present() is the frame boundary meaning this frame is done.
④ **输入是消息队列**:`ui_msg_fetch_wait(超时) / ui_msg_get_a() / ui_win_is_closed()` 收
4) Input is a message queue: ui_msg_fetch_wait(timeout) / ui_msg_get_a() / ui_win_is_closed() receive
键盘、触摸、定时器、窗口事件 —— 全部走同一个队列。
keyboard, touch, timer and window events -- all through the same queue.
跑法(桌面 vmlcli):
How to run (desktop vmlcli):
dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll Examples/pascal/demo_ui.pas \
--screen 480x640 --frames out_frames/
⚠ **要看画面请用 `--frames 目录`,不要用 `--frame 单个文件`**。原因不在本程序:
WARNING: to see images use --frames DIR, not --frame SINGLE-FILE. The reason is not this program:
`ui_win_close()`(#521)在宿主侧把**整个场景置空**(`VmlHostRuntime.WinClose`
ui_win_close() (#521) clears the whole scene on the host side (VmlHostRuntime.WinClose
的 `_scene = null`),于是程序跑完之后 `--frame` 那一刀已经没有场景可取,
sets _scene = null), so once the program has finished there is no scene left for --frame,
只会打一行「没有可导出的帧(场景是空的)」。`--frames` 是**每帧 present 当场拍快照**,
it just prints "no frame to export (the scene is empty)". --frames snapshots on each present,
所以照常出图。这是桌面脚手架的行为,不是本程序写错了。
so images still come out. This is desktop-scaffold behaviour, not a mistake in this program.
◆ **有界**(任务要求:按 N 帧或收到键就退出)—— 三条出口都写了:
* Bounded (task requirement: exit after N frames or on a key) -- all three exits are written:
· 收到**任意按键**(消息类型 1 = KeyDown)立刻退出
. any key press (message type 1 = KeyDown) exits immediately
· 或画满 150 帧自动退出(每帧 `ui_msg_fetch_wait(30)` 最多等 30ms ⇒ 约 4.5 秒)
. or auto-exit once 150 frames are drawn (each frame ui_msg_fetch_wait(30) waits up to 30ms => about 4.5 s)
· 或用户点了标题栏的返回箭头(`ui_win_is_closed() <> 0`)
. or the user taps the back arrow in the title bar (ui_win_is_closed() <> 0)
退出前 `ui_win_close()`,程序正常结束、退出码 0。
Before exiting it calls ui_win_close(); the program ends normally with exit code 0.
◆ 三条本前端的写法要求
* Three coding rules for this frontend
① **裸调库函数不写声明**(同目录 `catch.pas` 的约定):`Lib/shared/src/vmlui.c`
1) Call library functions without declaring them (the convention of catch.pas in this directory): Lib/shared/src/vmlui.c
编出来的 `vmlui.vml` 由 `vmltool.config.xml` 挂上来,直接写 `ui_2d_rect(...)` 即可。
is compiled to vmlui.vml, hooked up by vmltool.config.xml; just write ui_2d_rect(...) directly.
② **颜色用 Pascal 的十六进制字面量 `$FF101018`**(`$` 是 Pascal 的数字前缀)。
2) Use Pascal hex literals for colours: $FF101018 ($ is the Pascal number prefix).
文档给的记法是 `0xAARRGGBB`,而它作为 32 位有符号整数**会溢出** —— 写成
The docs write it as 0xAARRGGBB, but as a 32-bit signed integer it overflows; writing
十进制负数当然也行,但**极容易算错**:本文件第一版手算了 11 条,错了 5 条,
a decimal negative also works, but is very easy to get wrong: the first version of this file hand-computed 11 values and got 5 wrong,
画出来的颜色只是"差一点点"(比如该 `#2E7D32` 画成了 `#2F7D32`),
the colours came out only slightly off (e.g. #2E7D32 drawn as #2F7D32),
肉眼根本看不出,是靠 `--frames` 出图后**逐像素比色**才发现的。
invisible to the eye; it was found only by comparing pixels in the --frames output.
③ **正文里不用数字**。Pascal 的 `Str()`(整数转串)本前端没有,而字符串 `+`
3) No numbers in body text. This frontend has no Pascal Str() (int to string), and string +
又不是拼接(见 `demo_std.pas` 的说明)⇒ **没有现成的"拼一串带数字的话"这条路**。
is not concatenation (see demo_std.pas) => there is no ready way to build a string containing numbers.
所以状态一律用**形状与固定标签**表达(进度条用 `ui_2d_rect` 的宽度、方向用
So state is always expressed with shapes and fixed labels (progress bar via ui_2d_rect width, direction via
两个不同的词)—— 这与同目录 `Examples/fortran/sokoban.f90` 的取舍一致。
two different words) -- the same trade-off as Examples/fortran/sokoban.f90.
⚠ 另外:**Pascal 的两种块注释都不嵌套**(花括号那种与括号星号那种都是),
WARNING: also, neither of Pascal's two block comment forms nests (neither the brace form nor the paren-star form),
所以本文件里提到注释符号一律用文字说,不写真符号。
so this file always names comment symbols in words and never writes the real symbols.
*)
program DemoUi;
{ `ui_*`(本平台的**宿主 UI 库**:窗口 / 图元 / 文本 / 帧呈现)全部声明在
The ui_* host UI library (window / primitives / text / present) is declared in
`Lib/pascal/vml_ui.pas` 里,`uses vml_ui` 把它拉进来 —— 与 `uses Graph` 拉 BGI、
Lib/pascal/vml_ui.pas; `uses vml_ui` pulls it in, same mechanism as `uses Graph`
`uses Crt` 拉终端是同一套机制。
for BGI and `uses Crt` for the terminal.
⚠ **这条 `uses` 不是装饰,是必需的**:Pascal 里"零参函数"与"变量"**语法形状完全一样**
WARNING: this `uses` is required, not decoration: in Pascal a zero-arg function and a
(都不带括号),`w := ui_scr_get_w;` 到底是一次调用还是一个变量,只能靠**声明**分。
variable have exactly the same syntax, so only a *declaration* tells them apart.
裸写成 `ui_scr_get_w` / `ui_2d_present`(不带括号)时,删掉这一行就报「未声明的变量」(实测);
A bare ui_scr_get_w / ui_2d_present (no parentheses) reports an undeclared variable without it
带括号的调用不受影响(本文件的 `ui_*` 恰好全带括号)。
(measured); parenthesised calls are unaffected -- every call here is parenthesised.
`ui_*` 的实现在 `Lib/shared/vmlui.vml`(由 `Lib/shared/src/vmlui.c` 生成),
The implementation lives in Lib/shared/vmlui.vml (generated from Lib/shared/src/vmlui.c);
`uses vml_ui` 会经 `AutoLinkUnit` 把它链上。
`uses vml_ui` links it in via AutoLinkUnit. }
uses vml_ui;
var
w, h, ori, frame, maxf, msg, kc, running: integer;
bx, by, bw, bh: integer;
label1, label2: string;
begin
{ ① 开窗:尺寸传 0 ⇒ **交给宿主用真实可用区**。开窗**之前** `ui_scr_get_w/h` 只能估算,而估算与 }
{ 1) Open the window: pass size 0 ⇒ **the host uses the real available area**. **Before** the open, }
{ 实际总有出入(这就是"内容偏上"的根因)⇒ 所以尺寸改在**开窗之后**问(那时才是真实画布)。 }
{ `ui_scr_get_w/h` can only estimate and the estimate differs from reality (exactly why the content sat high) ⇒ the size is now queried **after** the open, when it is the real canvas. }
ui_win_open('Pascal UI demo', 0, 0);
{ ② 问可用绘图区(**开窗之后**问 —— 这时拿到的是真实画布);拿不到就给个兜底尺寸 }
{ 2) ask for the usable draw area (**after** the open, when it is the real canvas); if unavailable use a fallback size }
w := ui_scr_get_w();
h := ui_scr_get_h();
if w <= 0 then
begin
w := 360;
end;
if h <= 0 then
begin
h := 620;
end;
{ ③ 问屏幕方向:0 = 竖屏,1 = 横屏 }
{ 3) ask the screen orientation: 0 = portrait, 1 = landscape }
ori := ui_scr_get_orientation();
if ori = 1 then
begin
label1 := 'orientation = LANDSCAPE (1)';
end
else
begin
label1 := 'orientation = PORTRAIT (0)';
end;
label2 := 'ui_win_open / ui_2d_rect / ui_2d_circle / ui_2d_line / ui_2d_text / ui_2d_present';
{ ④ 方向决定面板怎么摆:横屏把方块放右上,竖屏放右下 }
{ 4) orientation decides the panel layout: landscape puts the box top-right, portrait bottom-right }
bx := 24;
by := 24;
bw := 140;
bh := 100;
if ori = 1 then
begin
bx := w - bw - 24;
by := 120;
end
else
begin
by := h - bh - 110;
end;
maxf := 150;
frame := 0;
running := 1;
while running = 1 do
begin
{ ── 每帧重画(保留模式:场景每帧从头追加)────────────────── }
{ -- redraw every frame (retained mode: the scene is appended from scratch each frame) --- }
ui_2d_clear($FF101018); { 深底 }
{ dark background }
{ 标题 }
{ title }
ui_2d_text(16, 16, 'Pascal UI demo', $FF9AD8FF, 20, VML_ANCHOR_LEFT); { 浅蓝 }
{ light blue }
ui_2d_text(16, 44, label1, $FFFFD34A, 15, VML_ANCHOR_LEFT); { 黄 }
{ yellow }
ui_2d_text(16, 66, label2, $FF8A9AA8, 11, VML_ANCHOR_LEFT); { 灰 }
{ grey }
{ 图元三件套:线 / 矩形 / 圆 }
{ the primitive trio: line / rect / circle }
ui_2d_line(16, 92, w - 16, 92, $FF33506A, 1); { 分隔线 }
{ separator line }
ui_2d_rect(bx, by, bw, bh, $FF2E7D32, 1, 0, 8); { 深绿实心圆角框 }
{ dark green filled rounded box }
ui_2d_rect(bx + 12, by + 12, bw - 24, bh - 24, $FF66BB6A, 0, 2, 0); { 浅绿空心框 }
{ light green hollow box }
ui_2d_circle(w div 2, h div 2, 44, $FFE74C3C, 1, 0); { 红实心圆 }
{ red filled circle }
ui_2d_circle(w div 2, h div 2, 20, $FFF1C40F, 1, 0); { 黄内圆 }
{ yellow inner circle }
{ 帧计数进度条(**不写字**:这里没有整数转串,见文件头第 ③ 条) }
{ frame counter progress bar (no text: there is no int-to-string here, see rule 3 in the file header) }
ui_2d_rect(16, h - 62, w - 32, 14, $FF22303C, 1, 0, 3); { 进度条底 }
{ progress bar background }
ui_2d_rect(16, h - 62, (w - 32) * frame div maxf, 14, $FF00C2A8, 1, 0, 3); { 进度条 }
{ progress bar }
ui_2d_text(16, h - 42, 'press any key to exit', $FF8A9AA8, 13, VML_ANCHOR_LEFT);
ui_2d_text(w - 16, h - 42, 'ui_2d_present every frame', $FF8A9AA8, 13, VML_ANCHOR_RIGHT);
{ ── 帧边界 ────────────────────────────────────────────────── }
{ -- frame boundary --- }
ui_2d_present();
frame := frame + 1;
{ 出口 A:画满 maxf 帧 }
{ exit A: maxf frames drawn }
if frame >= maxf then
begin
running := 0;
end;
{ 出口 B:收到任意按键(消息类型 1 = KeyDown) }
{ exit B: any key received (message type 1 = KeyDown) }
if running = 1 then
begin
msg := ui_msg_fetch_wait(30);
if msg = 1 then
begin
kc := ui_msg_get_a();
running := 0;
end;
end;
{ 出口 C:用户点了标题栏的返回箭头 }
{ exit C: the user tapped the back arrow in the title bar }
if ui_win_is_closed() <> 0 then
begin
running := 0;
end;
end;
{ 正常收尾:关窗 }
{ normal finish: close the window }
ui_win_close();
end.