! demo_ui.f90 —— Fortran 第四层：**最新 UI 接口**（宿主 `ui_*` 图元 + 绘图窗口）
! demo_ui.f90 -- Fortran layer four: **the newest UI interface** (host `ui_*` primitives + a drawing window)
!
! 这一层和 BGI 那一路刻意不同。BGI 是"固定分辨率 + 索引色"的老世界，
! This layer is deliberately different from the BGI path. BGI is the old world of "fixed resolution + indexed colors",
! 这一层是**宿主窗口 + 0xAARRGGBB 真彩 + 统一消息队列**。四条关键差别：
! while this layer is **a host window + 0xAARRGGBB true color + a unified message queue**. Four key differences:
!
!   ① **开窗显式**：`ui_win_open(标题, 宽, 高)`，宽高来自 `ui_scr_get_w()/ui_scr_get_h()`
!   ① **Opening the window is explicit**: `ui_win_open(title, width, height)`, where width/height come from `ui_scr_get_w()/ui_scr_get_h()`
!      （可用绘图区），不是写死的 640x480。
!      (the usable drawing area), not a hard-coded 640x480.
!   ② **屏幕方向要问**：`ui_scr_get_orientation()` 返回 0=竖屏 / 1=横屏 —— 程序据此决定
!   ② **The screen orientation must be asked**: `ui_scr_get_orientation()` returns 0=portrait / 1=landscape -- the program uses it to decide
!      "面板怎么摆"。这是**开窗前就能问**的（详见 `docs/VML宿主接口.md`）。
!      "how to lay the panel out". This is **askable before the window opens** (see the VML host interface doc under `docs/`).
!   ③ **显式呈现**：图元只是追加进场景，`ui_2d_present()` 才是"这一帧画完了"的帧边界。
!   ③ **Presentation is explicit**: primitives are only appended to the scene; `ui_2d_present()` is the frame boundary that says "this frame is done".
!   ④ **输入是消息队列**：`ui_msg_fetch_wait(超时) / ui_msg_get_a() / ui_win_is_closed()` 收
!   ④ **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 one and the same queue.
!      消息类型：1=KeyDown 2=KeyUp 3..5=鼠标 6..8=触摸 9=定时器 10=窗口关闭。
!      Message types: 1=KeyDown 2=KeyUp 3..5=mouse 6..8=touch 9=timer 10=window closed.
!
! 跑法（桌面 vmlcli）：
! How to run (desktop vmlcli):
!   dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll Examples/fortran/demo_ui.f90 \
!       --screen 480x640 --frames out_frames/
!
! ⚠ **要看画面请用 `--frames 目录`，不要用 `--frame` 单个文件**：`ui_win_close()`（#521）
! ⚠ **To see the picture use `--frames <dir>`, not `--frame` for a single file**: `ui_win_close()` (#521)
!   在宿主侧把整个场景置空（`VmlHostRuntime.WinClose` 的 `_scene = null`），
!   nulls out the whole scene on the host side (`_scene = null` in `VmlHostRuntime.WinClose`),
!   跑完之后 `--frame` 已经没场景可取；而 `--frames` 是每帧 present **当场拍快照**。
!   so after it finishes there is no scene for `--frame` to take; whereas `--frames` **snapshots right at each present**.
!
! ◆ **有界**（任务要求：按 N 帧或收到键就退出）—— 三条出口都写了：
! ◆ **Bounded** (a task requirement: exit after N frames or on a key press) -- all three exits are implemented:
!   · 收到**任意按键**（消息类型 1 = KeyDown）立刻退出
!   · exits immediately on **any key press** (message type 1 = KeyDown)
!   · 或画满 150 帧自动退出（每帧 `ui_msg_fetch_wait(30)` 最多等 30ms ⇒ 约 4.5 秒）
!   · or exits automatically after 150 drawn frames (each `ui_msg_fetch_wait(30)` waits at most 30ms => about 4.5 seconds)
!   · 或用户点了标题栏的返回箭头（`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。
!   `ui_win_close()` runs before exiting; the program ends normally with exit code 0.
!
! ═══════════════════════════════════════════════════════════════════════
!  ⚠ 三条本前端的写法要求（每条都来自实测，不是风格偏好）
!  ⚠ Three style requirements of this frontend (each from measurement, not style preference)
!
!  ① **调库函数一律写成函数调用表达式 `k = ui_2d_rect(...)`**。
!  ① **Always write a library call as the function-call expression `k = ui_2d_rect(...)`**.
!     写成 `call ui_2d_rect(...)` 会被编成 `CALL sub_ui_rect`，而**链接器只剥
!     Written as `call ui_2d_rect(...)` it compiles to `CALL sub_ui_rect`, and **the linker strips only
!     `func_`/`word_`/`method_`/`var_` 四种前缀、不含 `sub_`** ⇒ 碰不到 UI 库。
!     the four prefixes `func_`/`word_`/`method_`/`var_`, not `sub_`** => it never reaches the UI library.
!     （对照组：`call putchar(27)` 恰好能编成裸名 `CALL putchar`，所以那一句能跑
!     (Control case: `call putchar(27)` happens to compile to the bare name `CALL putchar`, so that one line works
!      —— 见 `demo_tty.f90`。**别把某个 `call` 能跑推广到所有 `call`**。）
!      -- see `demo_tty.f90`. **Do not generalize "one `call` works" to all `call`s**.)
!
!  ② **一个子程序都不用**。`contains` 里的内部子程序**传不进实参**、
!  ② **No subroutines at all**. A contained subroutine **cannot receive arguments**
!     也**看不见宿主程序的变量**（实测 `call show(97)` 里 `v` 读到 0）；
!     and **cannot see the host program's variables** (measured: inside `call show(97)` `v` reads 0);
!     外部子程序则直接「未定义的函数」。所以这份 demo 全部内联。
!     an external subroutine just reports "undefined function". So this demo is entirely inline.
!
!  ③ **正文里不出现数字**。本前端没有整数转串（`Str()` 没有、`//` 拼串没有、
!  ③ **No digits appear in the display text**. This frontend has no integer-to-string conversion (no `Str()`, no `//` concatenation,
!     `character` 变量存不住字符串）⇒ 状态一律用**形状与固定标签**表达：进度用 `ui_2d_rect`
!     and a `character` variable cannot hold a string) => state is expressed purely with **shapes and fixed labels**: progress through the width of
!     的宽度、方向用两个不同的词。这与同目录 `sokoban.f90` 的取舍一致。
!     a `ui_2d_rect`, orientation through two different words. This matches the trade-off in `sokoban.f90` in this directory.
!     （~~原先还有一条理由：`print` 浮点会打出整数的位模式（见 `demo_std.f90`）~~ ✅ **已修好（2026-10-04 复验）**：
!     （~~one more reason used to be that `print` on a float printed the integer bit pattern (see `demo_std.f90`)~~ ✅ **Fixed （re-verified 2026-10-04）**:
!      `print *, 2.5` ⇒ `2.5`、`print *, 2.5 * 4.0` ⇒ `10`；见 `demo_std.f90` 文件头。）
!      `print *, 2.5` => `2.5`, `print *, 2.5 * 4.0` => `10`; see the header of `demo_std.f90`.）
!
!  附：颜色用的是**十进制负数**（`0xAARRGGBB` 作为 32 位有符号整数会溢出）。
!  Note: colors are written as **negative decimals** (`0xAARRGGBB` overflows as a 32-bit signed integer).
!      每一条后面都标了它对应的十六进制值，改的时候照着改、别手算 ——
!      Every one carries the corresponding hex value; change it by copying, never by hand --
!      手算差了 1 个最低位，画出来"只差一点点"，肉眼根本看不出来。
!      being off by one least-significant bit draws "just a tiny bit different", which the eye simply cannot see.
! ═══════════════════════════════════════════════════════════════════════

program demo_ui
  implicit none
  integer :: w, h
  integer :: ori
  integer :: frame, maxf
  integer :: tid, msg, kc
  integer :: running
  integer :: k
  integer :: bx, by, bw, bh

  ! ① 开窗：尺寸传 0 ⇒ **交给宿主用真实可用区**。开窗**之前** `ui_scr_get_w/h` 只能估算，而估算与
  ! ① 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.
  k = ui_win_open('Fortran UI demo', 0, 0)

  ! ② 问可用绘图区（**开窗之后**问 —— 这时拿到的是真实画布）；拿不到就给个兜底尺寸
  ! ② Ask for the usable drawing area (**after** the open, when it is the real canvas); if unavailable fall back to a default size
  w = ui_scr_get_w()
  h = ui_scr_get_h()
  if (w <= 0) then
    w = 360
  end if
  if (h <= 0) then
    h = 620
  end if

  ! ③ 问屏幕方向：0 = 竖屏，1 = 横屏
  ! ③ Ask the screen orientation: 0 = portrait, 1 = landscape
  !    ⚠ 这里本来想先把文字存进 `character(len=40) :: label1` 再画 —— **做不到**：
  !    ⚠ The original plan was to store the text in `character(len=40) :: label1` and then draw it -- **impossible**:
  !    本前端的 `character(len=…)` 声明直接编译错（「期望变量名（得到 LParen）」；
  !    this frontend's `character(len=...)` declaration is a compile error ("expected variable name (got LParen)";
  !    2026-10-04 复验仍成立：`character(len=16) :: s` ⇒ `期望 Identifier，实际得到 LParen`），
  !    still true as of 2026-10-04: `character(len=16) :: s` => `expected Identifier, got LParen`),
  !    而不带长度的 `character :: s` 虽然编得过、却**存不住字符串**（`s = 'AB'` 读回来是 0）。
  !    and a length-less `character :: s` compiles but **cannot hold a string** (`s = 'AB'` reads back as 0).
  !    ⇒ 文字只能以**字面量**形式直接写在 `ui_2d_text(...)` 那一句里，所以下面的
  !    => the text can only be written as a **literal** directly in the `ui_2d_text(...)` call, so the
  !    if/else 是两份重复的绘制调用（而不是"选一句话再画一次"）。
  !    if/else below is two duplicated drawing calls (instead of "pick a sentence and draw once").
  ori = ui_scr_get_orientation()

  ! ④ 方向决定面板怎么摆：横屏把方块放右上，竖屏放右下
  ! ④ The orientation decides the panel layout: in landscape the block goes top right, in portrait bottom right
  bx = 24
  by = 24
  bw = 140
  bh = 100
  if (ori == 1) then
    bx = w - bw - 24
    by = 120
  else
    by = h - bh - 110
  end if

  maxf = 150
  frame = 0
  running = 1

  do while (running == 1)
    ! ── 每帧重画（保留模式：场景每帧从头追加）────────────────────
    ! -- Redraw every frame (retained mode: the scene is appended from scratch each frame) --
    k = ui_2d_clear(-15724520)                                   ! 0xFF101018 深底
    ! 0xFF101018 dark background

    ! 标题（方向那行只能按 if/else 各写一份字面量 —— 理由见上面第 ③ 条）
    ! Title (the orientation line can only be written as one literal per if/else branch -- see item ③ above)
    k = ui_2d_text(16, 16, 'Fortran UI demo', -6629121, 20, 0)   ! 0xFF9AD8FF 浅蓝
    ! 0xFF9AD8FF light blue
    if (ori == 1) then
      k = ui_2d_text(16, 44, 'orientation = LANDSCAPE (1)', -11446, 15, 0)   ! 0xFFFFD34A
    else
      k = ui_2d_text(16, 44, 'orientation = PORTRAIT (0)', -11446, 15, 0)    ! 0xFFFFD34A
    end if
    k = ui_2d_text(16, 66, 'ui_win_open / ui_2d_rect / ui_2d_circle / ui_2d_line / ui_2d_text / ui_2d_present', -7693656, 11, 0)  ! 0xFF8A9AA8

    ! 图元三件套：线 / 矩形 / 圆
    ! The primitive trio: line / rectangle / circle
    k = ui_2d_line(16, 92, w - 16, 92, -13414294, 1)             ! 0xFF33506A 分隔线
    ! 0xFF33506A separator line
    k = ui_2d_rect(bx, by, bw, bh, -13730510, 1, 0, 8)           ! 0xFF2E7D32 深绿实心圆角框
    ! 0xFF2E7D32 dark green filled rounded box
    k = ui_2d_rect(bx + 12, by + 12, bw - 24, bh - 24, -10044566, 0, 2, 0)  ! 0xFF66BB6A 浅绿空心
    ! 0xFF66BB6A light green hollow
    k = ui_2d_circle(w / 2, h / 2, 44, -1618884, 1, 0)           ! 0xFFE74C3C 红实心圆
    ! 0xFFE74C3C red filled circle
    k = ui_2d_circle(w / 2, h / 2, 20, -932849, 1, 0)            ! 0xFFF1C40F 黄内圆
    ! 0xFFF1C40F yellow inner circle

    ! 帧计数进度条（不写字：这里没有整数转串，见文件头第 ③ 条）
    ! Frame-count progress bar (no text: there is no integer-to-string here, see item ③ in the file header)
    k = ui_2d_rect(16, h - 62, w - 32, 14, -14536644, 1, 0, 3)   ! 0xFF22303C 进度条底
    ! 0xFF22303C progress bar background
    k = ui_2d_rect(16, h - 62, (w - 32) * frame / maxf, 14, -16727384, 1, 0, 3)  ! 0xFF00C2A8
    k = ui_2d_text(16, h - 42, 'press any key to exit', -7693656, 13, 0)
    k = ui_2d_text(w - 16, h - 42, 'ui_2d_present every frame', -7693656, 13, 2)

    ! ── 帧边界 ────────────────────────────────────────────────────
    ! -- Frame boundary --
    k = ui_2d_present()
    frame = frame + 1

    ! 出口 A：画满 maxf 帧
    ! Exit A: after maxf frames are drawn
    if (frame >= maxf) then
      running = 0
    end if

    ! 出口 B：收到任意按键（消息类型 1 = KeyDown）
    ! Exit B: any key press received (message type 1 = KeyDown)
    if (running == 1) then
      msg = ui_msg_fetch_wait(30)
      if (msg == 1) then
        kc = ui_msg_get_a()
        running = 0
      end if
    end if

    ! 出口 C：用户点了标题栏的返回箭头
    ! Exit C: the user tapped the back arrow in the title bar
    if (ui_win_is_closed() /= 0) then
      running = 0
    end if
  end do

  ! 正常收尾：关窗（定时器没用到就不必 kill）
  ! Normal wrap-up: close the window (no timer was used, so nothing needs killing)
  k = ui_win_close()
end program demo_ui
