class_name AboutScreen extends Control ## T-WIN-010:关于页面 + 开源声明(可滚动) ## T-WIN-012:关于清单展示当前版本号(文档 §11.1:Windows 端在「关于」清单) ## ## 文档 §8.3 要求 Windows 客户端与 Android App 的「关于」页展示同款声明。 ## ## 版本号**唯一来源**是 project.godot 的 application/config/version ## (与 project.yaml 的 version、导出产物名 opendance-windows-vX.Y.Z.zip、Git tag 一致), ## 本文件不再手写一份(文档 §11.2 第 1 条硬性要求)。 const UiKit = preload("res://src/ui/ui_kit.gd") const Credits = preload("res://src/ui/credits.gd") var _on_back: Callable = Callable() func _init(on_back: Callable = Callable()) -> void: _on_back = on_back ## 当前运行版本(构建期由 project.godot 注入,运行期读取) static func app_version() -> String: return str(ProjectSettings.get_setting("application/config/version", "0.0.0")) ## 取 AppConfig 自动加载实例。测试在 `--script` 模式下没有 autoload, ## 因此这里必须容错返回 null,而不是直接引用 AppConfig 造成解析期错误。 static func _app_config() -> Node: var loop := Engine.get_main_loop() if loop is SceneTree: return (loop as SceneTree).root.get_node_or_null("AppConfig") return null static func _config_call(method: String, fallback: String) -> String: var cfg := _app_config() if cfg != null and cfg.has_method(method): return str(cfg.call(method)) return fallback static func _config_property(name: String, fallback: String) -> String: var cfg := _app_config() if cfg != null: var value: Variant = cfg.get(name) if value != null: return str(value) return fallback ## 「关于」清单的一行行内容:版本 / 引擎 / 契约 / 运行地址 / 隐私 static func about_manifest() -> Array: return [ {"label": "客户端版本", "value": "v%s" % app_version()}, {"label": "运行引擎", "value": "Godot %s" % str(Engine.get_version_info().get("string", "?"))}, {"label": "契约版本", "value": Credits.summary()}, {"label": "接收协议", "value": "websocket-keypoints v%s(33 点 / 239 字节帧)" % Credits.version_of("websocket-keypoints")}, {"label": "接收地址", "value": _config_call("listen_url", "ws://0.0.0.0:8765/pose")}, {"label": "服务端地址", "value": _config_property("api_base_url", "http://127.0.0.1:3000")}, {"label": "隐私说明", "value": "仅接收关键点,不接收、不存储任何视频画面"}, ] func _ready() -> void: set_anchors_preset(Control.PRESET_FULL_RECT) var root := UiKit.vbox(10) root.set_anchors_preset(Control.PRESET_FULL_RECT) root.offset_left = 24 root.offset_top = 18 root.offset_right = -24 root.offset_bottom = -18 add_child(root) var header := UiKit.hbox(12) header.add_child(UiKit.title("关于 OpenDance Studio")) header.add_child(UiKit.expander()) var back := UiKit.button("返回主菜单", func(): if _on_back.is_valid(): _on_back.call()) header.add_child(back) root.add_child(header) var info := UiKit.panel() var info_box := UiKit.vbox(4) info_box.add_child(UiKit.heading("清单")) for item in about_manifest(): var row := UiKit.hbox(8) var key := UiKit.label("%s:" % str(item["label"]), UiKit.FONT_SMALL, UiKit.COLOR_MUTED) key.custom_minimum_size = Vector2(96, 0) row.add_child(key) var value := UiKit.label(str(item["value"]), UiKit.FONT_SMALL) value.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART value.size_flags_horizontal = Control.SIZE_EXPAND_FILL row.add_child(value) info_box.add_child(row) info_box.add_child(UiKit.spacer(4)) info_box.add_child(UiKit.muted("摄像头跳舞评分系统:接收 Android 辅助 App 通过 WebSocket 推送的 33 点骨骼关键点,驱动 VRM 角色并实时 DTW 评分。")) info.add_child(info_box) root.add_child(info) var credits_panel := UiKit.panel() var credits_box := UiKit.vbox(4) credits_box.add_child(UiKit.heading("开源项目声明")) credits_box.add_child(UiKit.muted("共 %d 项;与 contracts/open-source-credits.md 及 THIRD_PARTY.md 保持一致。" % Credits.entry_count())) var text := UiKit.rich_text() text.bbcode_enabled = true text.text = Credits.to_bbcode() text.custom_minimum_size = Vector2(0, 320) credits_box.add_child(text) credits_panel.add_child(credits_box) credits_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL root.add_child(credits_panel)