Port 2V0-f window input and UI render command bridge

This commit is contained in:
shen
2026-09-22 20:34:45 -07:00
parent 9c30f06083
commit 5590714b16
22 changed files with 693 additions and 182 deletions
+100
View File
@@ -11,6 +11,8 @@
# 桌面:python_host_test.gd(--script);真机:MT_TEST_MODE=python 从 client_main.gd 进来。
extends RefCounted
const PythonUISurface = preload("res://python_ui_surface.gd")
const EXPECTED_STOP := "Sorry, Your system does not support 3D graphics"
var fail := 0
@@ -54,6 +56,104 @@ func run() -> int:
"system.py 进入 prototype.RunApp(),Create() 报告待实现的图形设备")
_ck(Metin2PythonHost.run_line("import net, player, item; assert net.__port_stub__ == 1 and player.__port_stub__ == 1 and item.__port_stub__ == 1") == "",
"2V0 玩法模块桩已注册,调用会记录到 stderr")
# 真正从 Godot 入口转发事件给 40250 的 CWindowManager,再由窗口树回调 Python。
Metin2PythonHost.set_ui_size(800, 600)
var probe_source := """
import wndMgr
_ui_events = []
class _UIProbe:
def OnMouseOverIn(self): _ui_events.append('in')
def OnMouseOverOut(self): _ui_events.append('out')
def OnMouseOver(self): pass
def OnMouseLeftButtonDown(self): _ui_events.append('down')
def OnMouseLeftButtonUp(self): _ui_events.append('up'); return 1
def OnSetFocus(self): _ui_events.append('focus')
def OnKillFocus(self): _ui_events.append('blur')
def OnUpdate(self): _ui_events.append('update')
def OnKeyDown(self, key): _ui_events.append('key:%d' % key); return 1
def OnKeyUp(self, key): return 1
_ui_probe = _UIProbe()
_ui_window = wndMgr.Register(_ui_probe, 'UI')
wndMgr.SetWindowSize(_ui_window, 100, 50)
wndMgr.SetWindowPosition(_ui_window, 10, 20)
wndMgr.Show(_ui_window)
wndMgr.SetFocus(_ui_window)
"""
_ck(Metin2PythonHost.run_line(probe_source) == "", "40250 窗口测试对象已在 Godot 进程中创建")
Metin2PythonHost.ui_mouse_move(15, 25)
Metin2PythonHost.ui_mouse_button(1, true, 15, 25)
Metin2PythonHost.ui_mouse_button(1, false, 15, 25)
Metin2PythonHost.ui_key(42, true)
Metin2PythonHost.ui_update()
_ck(Metin2PythonHost.run_line("assert 'in' in _ui_events and 'down' in _ui_events and 'up' in _ui_events and 'focus' in _ui_events and 'key:42' in _ui_events and 'update' in _ui_events") == "",
"Godot 输入、焦点和更新事件抵达 40250 Python 窗口")
_ck(Metin2PythonHost.run_line("_ui_bar = wndMgr.RegisterBar(_UIProbe(), 'UI'); wndMgr.SetWindowSize(_ui_bar, 20, 10); wndMgr.SetWindowPosition(_ui_bar, 30, 40); wndMgr.SetColor(_ui_bar, 0x7f336699); wndMgr.Show(_ui_bar)") == "",
"40250 Bar 窗口已创建")
var draws: Array = Metin2PythonHost.ui_render_commands()
var has_bar := false
for draw in draws:
if draw["kind"] == "bar" and draw["x1"] == 30.0 and draw["y1"] == 40.0 and draw["x2"] == 50.0 and draw["y2"] == 50.0 and draw["argb"] == 0x7f336699:
has_bar = true
_ck(has_bar, "40250 Bar 绘制调用进入 Godot Canvas 命令队列")
var clip_source := """
import grp
class _ClipProbe(_UIProbe):
def OnRender(self):
_ui_events.append('renderclip')
grp.SetViewport(0.0, 0.0, 0.05, 0.08)
_ui_clip_probe = _ClipProbe()
_ui_clip = wndMgr.Register(_ui_clip_probe, 'UI')
wndMgr.SetWindowSize(_ui_clip, 800, 600)
wndMgr.SetParent(_ui_bar, _ui_clip)
wndMgr.Show(_ui_clip)
"""
_ck(Metin2PythonHost.run_line(clip_source) == "", "40250 裁剪窗口已创建")
draws = Metin2PythonHost.ui_render_commands()
_ck(Metin2PythonHost.run_line("assert 'renderclip' in _ui_events") == "", "父窗口 OnRender 已执行")
var has_clip := false
for draw in draws:
if draw["kind"] == "bar" and draw["clip_x2"] == 40.0 and draw["clip_y2"] == 48.0:
has_clip = true
_ck(has_clip, "40250 viewport 裁剪范围传给 Godot Canvas")
var surface := PythonUISurface.new()
var segment: PackedVector2Array = surface._clip_line(Vector2(-10, 10), Vector2(50, 10), Rect2(0, 0, 30, 30))
_ck(segment.size() == 2 and segment[0] == Vector2(0, 10) and segment[1] == Vector2(30, 10),
"Godot Canvas 线段裁剪保留可见区间")
surface.free()
_ck(Metin2PythonHost.run_line("wndMgr.Destroy(_ui_bar)") == "", "Bar 窗口已销毁")
_ck(Metin2PythonHost.run_line("grp.RestoreViewport(); wndMgr.Destroy(_ui_clip)") == "", "裁剪窗口已销毁")
_ck(Metin2PythonHost.run_line("_ui_text_probe = _UIProbe(); _ui_text = wndMgr.RegisterTextLine(_ui_text_probe, 'UI'); wndMgr.SetWindowPosition(_ui_text, 70, 80); wndMgr.SetText(_ui_text, 'Hello'); wndMgr.SetFontColor(_ui_text, 1.0, 0.5, 0.0); wndMgr.Show(_ui_text)") == "",
"40250 TextLine 已创建")
draws = Metin2PythonHost.ui_render_commands()
var has_text := false
for draw in draws:
if draw["kind"] == "text" and draw["text"] == "Hello" and draw["x1"] == 70.0 and draw["y1"] == 80.0:
has_text = true
_ck(has_text, "40250 TextLine 文本进入 Godot Canvas 命令队列")
_ck(Metin2PythonHost.run_line("wndMgr.Destroy(_ui_text)") == "", "TextLine 已销毁")
var image_path := "locale/en/ui/vkey/key_backspace.tga"
if Metin2Pack.exists(image_path):
var packed_texture: Texture2D = preload("res://ui/ui_assets.gd").load_tex(AssetRoot.path(), image_path)
_ck(packed_texture != null and packed_texture.get_width() > 0,
"40250 pack 图片可解码为 Godot 纹理")
var image_source := "_ui_image_probe = _UIProbe(); _ui_image = wndMgr.RegisterImageBox(_ui_image_probe, 'UI'); wndMgr.LoadImage(_ui_image, '%s'); wndMgr.SetWindowPosition(_ui_image, 120, 130); wndMgr.Show(_ui_image)" % image_path
_ck(Metin2PythonHost.run_line(image_source) == "", "40250 pack 图片已加载到 ImageBox")
draws = Metin2PythonHost.ui_render_commands()
var has_image := false
for draw in draws:
if draw["kind"] == "image" and String(draw["text"]).replace("\\", "/") == image_path and draw["x1"] == 120.0 and draw["y1"] == 130.0 and draw["x2"] > draw["x1"] and draw["y2"] > draw["y1"]:
has_image = true
_ck(has_image, "40250 ImageBox 图片进入 Godot Canvas 命令队列")
_ck(Metin2PythonHost.run_line("wndMgr.Destroy(_ui_image)") == "", "ImageBox 已销毁")
var sub_path := "d:/ymir work/ui/cursor/cursor.sub"
if Metin2Pack.exists(sub_path):
_ck(Metin2PythonHost.run_line("import grpImage; _ui_sub = grpImage.Generate('%s'); assert _ui_sub; grpImage.Delete(_ui_sub)" % sub_path) == "",
"40250 .sub 子图可创建并释放")
_ck(Metin2PythonHost.run_line("_ui_expanded = grpImage.GenerateExpanded('%s'); assert _ui_expanded; grpImage.DeleteExpanded(_ui_expanded)" % image_path) == "",
"40250 ExpandedImage 可创建并释放")
else:
print("SKIP: 40250 image test(pack 缺少 %s)" % image_path)
_ck(Metin2PythonHost.run_line("wndMgr.Destroy(_ui_window)") == "", "窗口已销毁")
else:
print("SKIP: system.py 这一段(没有 40250 Client/pack:%s)" % AssetRoot.client_path())