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

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.