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

go/ui/demo_ui.go · 11847 字节 · 下载原文

// demo_ui.go —— **最新 UI 接口**示范(Go)

// demo_ui.go -- the **newest UI interface** demo (Go)

//

// 四层示范的第四层:开 `ui_*` 绘图窗口 + **查屏幕方向** + 画图元 + `ui_2d_present` +

// The fourth of the four demo layers: open a `ui_*` drawing window + **query the screen orientation** + draw primitives + `ui_2d_present` +

// 处理触摸/按键,**有界**(收到任意键就退出,否则画满 60 帧自动退出),正常结束。

// handle touch/keys, **bounded** (exit on any key, otherwise exit automatically after 60 frames), then exit normally.

//

// 四条关键差别(与第三层 BGI 相比):① 开窗显式(宽高来自 `ui_scr_get_w/h`,不写死分辨率);

// Four key differences (compared with the third layer, BGI): ① opening the window is explicit (width/height come from `ui_scr_get_w/h`, no hard-coded resolution);

// ② 方向要问 `ui_scr_get_orientation()`(0=竖屏 / 1=横屏,**开窗前就能问**);

// ② the orientation must be asked for via `ui_scr_get_orientation()` (0=portrait / 1=landscape, **askable before the window opens**);

// ③ `ui_2d_present()` 才是"这一帧画完了"的帧边界;④ 输入是**统一消息队列**

// ③ `ui_2d_present()` is the frame boundary that says "this frame is done"; ④ input is a **unified message queue**

// (键盘/触摸/定时器/窗口事件同一个队列,`ui_msg_fetch_wait` / `ui_msg_get_a` / `ui_win_is_closed`)。

// (keyboard/touch/timer/window events share one queue: `ui_msg_fetch_wait` / `ui_msg_get_a` / `ui_win_is_closed`).

//

// ◆ 跑法与"画面怎么看"(桌面 vmlcli)

// ◆ How to run it and "how to look at the picture" (desktop vmlcli)

//

//   dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll \

//       third_party/vml/Examples/go/demo_ui.go --screen 480x640 --frames /tmp/out_frames/

//

// ⚠ **要看画面请用 `--frames 目录`,不要用 `--frame 单个文件`**:`ui_win_close()`(#521)

// ⚠ **To see the picture use `--frames <dir>`, not `--frame <single file>`**: `ui_win_close()` (#521)

//   在宿主侧把**整个场景置空**,程序跑完之后 `--frame` 那一刀已经无场景可取,只会打一行

//   **nulls out the whole scene** on the host side, so after the program finishes that `--frame` shot has no scene left and only prints

//   「没有可导出的帧(场景是空的)」。`--frames` 是每帧 present 当场拍快照,照常出图。

//   "no frame to export (the scene is empty)". `--frames` snapshots right at each present, so it still produces images.

//   这是桌面脚手架的行为,不是本程序写错了。

//   This is the desktop scaffolding's behavior, not a mistake in this program.

//

// 跑法:命令行页输入  vml run examples/go/demo_ui.go

// How to run: type this into the command-line page:  vml run examples/go/demo_ui.go

//

// 颜色:0xAARRGGBB 写**负数十进制**。

// Colors: 0xAARRGGBB is written as a **negative decimal**.


package main

// 显式引用 UI 库:`import "vmlui"` 会**真的去读** `Lib/go/vmlui/vmlui.go`

// Explicitly reference the UI library: `import "vmlui"` actually reads `Lib/go/vmlui/vmlui.go`

// (GenLib 生成、155 个 `ui_*` 声明),把符号登记进编译器的符号表。

// (generated by GenLib, 155 `ui_*` declarations), registering those symbols in the compiler's symbol table.

//

// ⚠ 删掉这一行就**编不过** —— 判据(实测):

// ⚠ Removing this line **fails to compile** - the measured criterion:

//   vmlcli <本文件>  ⇒  error: 未声明的函数 'ui_win_open'(它属于包 'vmlui'…)

//   vmlcli <this file>  =>  error: undeclared function 'ui_win_open' (it belongs to package 'vmlui' ...)

// 所以它是**强制**的,不是文档级的摆设。

// so it is **mandatory**, not documentation-only.

// ⚠ **2026-10-04 复验仍成立**:/tmp 副本删掉这行后实测 `✘ 编译失败`(本文件报「未声明的函数 'ui_scr_get_orientation'」)。

// ⚠ **still holds on 2026-10-04**: a /tmp copy with this line removed measured `✘ compile failed` (this file reports 「undefined function 'ui_scr_get_orientation'」).

import "vmlui"

func main() {
	// ── 开窗之前就问方向(这一层与 BGI 最"新"的一条)────────────

	// -- Ask the orientation before opening the window (the "newest" item of this layer vs. BGI) --

	ori := ui_scr_get_orientation()
	lang := ui_scr_get_language()

	// 开窗:尺寸传 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.

	if lang == 0 { ui_win_open("UI 接口演示", 0, 0) } else { ui_win_open("UI API demo", 0, 0) }
	ui_scr_set_keep_on(1)

	// 尺寸:开窗之后再问一次(拿不到就退到竖屏默认值)

	// Size: re-queried after the open (falling back to the portrait default if unavailable)

	w := ui_scr_get_w()
	h := ui_scr_get_h()
	if w <= 0 {
		w = 360
	}
	if h <= 0 {
		h = 620
	}
	tid := ui_timer_set(30, 1)

	frames := 0
	keys := 0
	tx := 0
	ty := 0
	touched := 0
	running := 1

	for running != 0 {
		if ui_win_is_closed() != 0 {
			break
		}
		if frames >= 60 {
			break
		}

		// ── ① 底色 + 标题(居中锚点 = 1)────────────────────────

		// -- ① Background color + title (center anchor = 1) --

		ui_2d_clear(-15724520)
		if lang == 0 { ui_2d_text(w/2, 26, "Dolaima  ui_*  接口演示 (Go)", -6643536, 16, 1) } else { ui_2d_text(w/2, 26, "Dolaima  ui_*  API demo (Go)", -6643536, 16, 1) }
		if lang == 0 { ui_2d_text(w/2, 50, "开窗 / 方向 / 图元 / present / 消息队列", -6643536, 12, 1) } else { ui_2d_text(w/2, 50, "window / orientation / primitives / present / message queue", -6643536, 12, 1) }

		// ── ② 屏幕方向(0=竖屏 1=横屏)──────────────────────────

		// -- ② Screen orientation (0=portrait 1=landscape) --

		if lang == 0 { ui_2d_text(14, 80, "屏幕方向 =", -11409298, 13, 0) } else { ui_2d_text(14, 80, "Orientation =", -11409298, 13, 0) }
		ui_2d_text(96, 80, int_to_str(ori), -11409298, 13, 0)
		if ori == 1 {
			if lang == 0 { ui_2d_text(126, 80, "(横屏)", -11409298, 13, 0) } else { ui_2d_text(126, 80, "(LANDSCAPE)", -11409298, 13, 0) }
		} else {
			if lang == 0 { ui_2d_text(126, 80, "(竖屏)", -11409298, 13, 0) } else { ui_2d_text(126, 80, "(PORTRAIT)", -11409298, 13, 0) }
		}

		// ── ③ 图元:矩形(实心 / 空心 / 圆角)──────────────────

		// -- ③ Primitives: rectangles (filled / hollow / rounded) --

		if lang == 0 { ui_2d_text(14, 108, "图元:矩形", -6643536, 13, 0) } else { ui_2d_text(14, 108, "Primitives: rect", -6643536, 13, 0) }
		ui_2d_rect(14, 126, 84, 46, -11409298, 1, 0, 0)
		ui_2d_rect(108, 126, 84, 46, -131246, 0, 2, 0)
		ui_2d_rect(202, 126, 84, 46, -63488, 1, 0, 12)

		// ── ④ 图元:圆 / 椭圆 / 线 ──────────────────────────────

		// -- ④ Primitives: circle / ellipse / line --

		if lang == 0 { ui_2d_text(14, 196, "图元:圆 / 椭圆 / 线", -6643536, 13, 0) } else { ui_2d_text(14, 196, "Primitives: circle / ellipse / line", -6643536, 13, 0) }
		ui_2d_circle(46, 250, 30, -131246, 1, 0)
		ui_2d_circle(120, 250, 30, -11409298, 0, 3)
		ui_2d_ellipse(210, 250, 44, 26, -63488, 1, 0)
		i := 0
		for i < 8 {
			ui_2d_line(14, 300+i*6, w-14, 300+i*6, -15263713, 2)
			i = i + 1
		}

		// ── ⑤ 8 级灰度色带(0xAARRGGBB 真彩)───────────────────

		// -- ⑤ An 8-step gray ramp (0xAARRGGBB true color) --

		if lang == 0 { ui_2d_text(14, 358, "真彩(0xAARRGGBB):", -6643536, 13, 0) } else { ui_2d_text(14, 358, "Truecolor (0xAARRGGBB):", -6643536, 13, 0) }
		s := 0
		for s < 8 {
			if s == 0 {
				ui_2d_rect(14+s*40, 376, 36, 26, -16777216, 1, 0, 0)
			}
			if s == 1 {
				ui_2d_rect(14+s*40, 376, 36, 26, -14671840, 1, 0, 0)
			}
			if s == 2 {
				ui_2d_rect(14+s*40, 376, 36, 26, -12566464, 1, 0, 0)
			}
			if s == 3 {
				ui_2d_rect(14+s*40, 376, 36, 26, -10066330, 1, 0, 0)
			}
			if s == 4 {
				ui_2d_rect(14+s*40, 376, 36, 26, -8355712, 1, 0, 0)
			}
			if s == 5 {
				ui_2d_rect(14+s*40, 376, 36, 26, -6250336, 1, 0, 0)
			}
			if s == 6 {
				ui_2d_rect(14+s*40, 376, 36, 26, -4210753, 1, 0, 0)
			}
			if s == 7 {
				ui_2d_rect(14+s*40, 376, 36, 26, -1, 1, 0, 0)
			}
			s = s + 1
		}

		// ── ⑥ 动画 + 帧进度条(证明每帧确实在重画)─────────────

		// -- ⑥ Animation + frame progress bar (proof that every frame really redraws) --

		col := 14 + frames - (frames/20)*20
		col = 14 + col*10
		if col > w-46 {
			col = 14
		}
		ui_2d_circle(col, 448, 18, -63488, 1, 0)
		ui_2d_rect(14, 480, (w-28)*frames/60, 10, -11409298, 1, 0, 4)

		// ── ⑦ 消息队列:帧数 / 最后一次按键 / 最后一次触摸 ──────

		// -- ⑦ Message queue: frame count / last key / last touch --

		if lang == 0 { ui_2d_text(14, 506, "已画帧数 =", -6643536, 13, 0) } else { ui_2d_text(14, 506, "Frames drawn =", -6643536, 13, 0) }
		ui_2d_text(112, 506, int_to_str(frames), -6643536, 13, 0)
		if lang == 0 { ui_2d_text(14, 528, "最后一次按键 =", -6643536, 13, 0) } else { ui_2d_text(14, 528, "Last key =", -6643536, 13, 0) }
		ui_2d_text(140, 528, int_to_str(keys), -6643536, 13, 0)
		if touched == 1 {
			if lang == 0 { ui_2d_text(14, 550, "最后一次触摸 =", -6643536, 13, 0) } else { ui_2d_text(14, 550, "Last touch =", -6643536, 13, 0) }
			ui_2d_text(140, 550, int_to_str(tx), -6643536, 13, 0)
			ui_2d_text(190, 550, int_to_str(ty), -6643536, 13, 0)
			ui_2d_circle(tx, ty, 26, -131246, 0, 3)
		} else {
			if lang == 0 { ui_2d_text(14, 550, "最后一次触摸 = 无(点一下试试)", -6643536, 13, 0) } else { ui_2d_text(14, 550, "Last touch = none (try tapping)", -6643536, 13, 0) }
		}

		if lang == 0 { ui_2d_text(w/2, h-24, "按任意键退出,或画满 60 帧自动退出", -6643536, 12, 1) } else { ui_2d_text(w/2, h-24, "Press any key to exit, or it exits after 60 frames", -6643536, 12, 1) }

		// ── ⑧ 帧边界 ────────────────────────────────────────────

		// -- ⑧ Frame boundary --

		ui_2d_present()

		// ── ⑨ 收一条消息(最多等 30ms,保证循环有界)────────────

		// -- ⑨ Take one message (wait at most 30ms, keeping the loop bounded) --

		t := ui_msg_fetch_wait(30)
		if t == 10 {
			running = 0
		} else if t == 1 {
			keys = ui_msg_get_a()
			running = 0
		} else if t == 6 {
			tx = ui_msg_get_a()
			ty = ui_msg_get_b()
			touched = 1
		} else if t == 4 {
			tx = ui_msg_get_a()
			ty = ui_msg_get_b()
			touched = 1
		}

		frames = frames + 1
	}

	// ── 收尾:关定时器、放开屏幕常亮、关窗 ──────────────────────

	// -- Wrap-up: stop the timer, release keep-screen-on, close the window --

	ui_timer_kill(tid)
	ui_scr_set_keep_on(0)
	ui_win_close()
}