fortran/ui/demo_ui.f90 · 14738 字节 · 下载原文
! 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