LUI添加3D
This commit is contained in:
parent
1223746dee
commit
1019179b8d
28
RenderPipelineFile/effects/lui_world_ui.yaml
Normal file
28
RenderPipelineFile/effects/lui_world_ui.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
# Scene-space effect for world-space LUI canvas textures.
|
||||
|
||||
fragment:
|
||||
defines: |
|
||||
#define DONT_FETCH_DEFAULT_TEXTURES 1
|
||||
#define DONT_SET_MATERIAL_PROPERTIES 1
|
||||
|
||||
inout: |
|
||||
uniform sampler2D p3d_Texture0;
|
||||
|
||||
material: |
|
||||
vec4 lui_color = texture(p3d_Texture0, texcoord);
|
||||
if (lui_color.a <= 0.001) {
|
||||
discard;
|
||||
}
|
||||
|
||||
#if IN_FORWARD_SHADER
|
||||
color_result = lui_color;
|
||||
return;
|
||||
#else
|
||||
m.shading_model = SHADING_MODEL_DEFAULT;
|
||||
m.basecolor = lui_color.rgb;
|
||||
m.normal = normalize(vOutput.normal);
|
||||
m.roughness = 0.65;
|
||||
m.specular_ior = 1.45;
|
||||
m.metallic = 0.0;
|
||||
m.shading_model_param0 = 0.0;
|
||||
#endif
|
||||
24
imgui.ini
24
imgui.ini
@ -31,25 +31,25 @@ DockId=0x0000000D,0
|
||||
|
||||
[Window][场景树]
|
||||
Pos=0,20
|
||||
Size=339,996
|
||||
Size=339,1023
|
||||
Collapsed=0
|
||||
DockId=0x00000007,0
|
||||
|
||||
[Window][属性面板]
|
||||
Pos=1504,20
|
||||
Size=346,498
|
||||
Pos=1574,20
|
||||
Size=346,512
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][控制台]
|
||||
Pos=341,617
|
||||
Size=1161,399
|
||||
Pos=341,644
|
||||
Size=1231,399
|
||||
Collapsed=0
|
||||
DockId=0x00000006,1
|
||||
|
||||
[Window][脚本管理]
|
||||
Pos=1504,520
|
||||
Size=346,496
|
||||
Pos=1574,534
|
||||
Size=346,509
|
||||
Collapsed=0
|
||||
DockId=0x00000004,0
|
||||
|
||||
@ -60,7 +60,7 @@ Collapsed=0
|
||||
|
||||
[Window][WindowOverViewport_11111111]
|
||||
Pos=0,20
|
||||
Size=1850,996
|
||||
Size=1920,1023
|
||||
Collapsed=0
|
||||
|
||||
[Window][测试窗口1]
|
||||
@ -99,8 +99,8 @@ Size=600,500
|
||||
Collapsed=0
|
||||
|
||||
[Window][资源管理器]
|
||||
Pos=341,617
|
||||
Size=1161,399
|
||||
Pos=341,644
|
||||
Size=1231,399
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
@ -150,7 +150,7 @@ Size=101,226
|
||||
Collapsed=0
|
||||
|
||||
[Window][LUI编辑器]
|
||||
Pos=160,44
|
||||
Pos=20,40
|
||||
Size=346,1331
|
||||
Collapsed=0
|
||||
|
||||
@ -225,7 +225,7 @@ Size=460,260
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X
|
||||
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,1023 Split=X
|
||||
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=2212,1012 Split=X
|
||||
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=339,1084 Selected=0xE0015051
|
||||
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1871,1084 Split=Y
|
||||
|
||||
10
main.py
10
main.py
@ -836,6 +836,10 @@ class MyWorld(PanelDelegates, CoreWorld):
|
||||
if imgui_captured:
|
||||
print("❌ ImGui处理了该事件,跳过3D场景选择")
|
||||
return
|
||||
|
||||
if hasattr(self, 'lui_manager') and self.lui_manager.handle_world_space_pointer_press():
|
||||
print("✓ LUI 3D UI 处理了该事件,跳过3D场景选择")
|
||||
return
|
||||
|
||||
# 调用事件处理器进行射线检测和选择
|
||||
if hasattr(self, 'event_handler'):
|
||||
@ -867,6 +871,12 @@ class MyWorld(PanelDelegates, CoreWorld):
|
||||
winWidth, winHeight = self.win.getSize()
|
||||
window_x = (mouse_x + 1) * 0.5 * winWidth
|
||||
window_y = (1 - mouse_y) * 0.5 * winHeight
|
||||
|
||||
if hasattr(self, 'lui_manager') and (
|
||||
self.lui_manager.is_world_space_pointer_over_ui()
|
||||
or self.lui_manager.has_world_space_pointer_capture()
|
||||
):
|
||||
return
|
||||
|
||||
# 调用事件处理器
|
||||
if hasattr(self, 'event_handler'):
|
||||
|
||||
@ -2584,6 +2584,10 @@ class SSBOEditor:
|
||||
process_imgui_click = getattr(self.base, "processImGuiMouseClick", None)
|
||||
if callable(process_imgui_click) and process_imgui_click(window_x, window_y):
|
||||
return
|
||||
lui_manager = getattr(self.base, "lui_manager", None)
|
||||
handle_lui_world = getattr(lui_manager, "handle_world_space_pointer_press", None)
|
||||
if callable(handle_lui_world) and handle_lui_world():
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
self._sync_pick_model_transform()
|
||||
|
||||
@ -81,6 +81,14 @@ class LUIFunctionComponentsMixin:
|
||||
'panel': canvas_panel,
|
||||
'visible': True,
|
||||
'background': bg,
|
||||
'render_mode': 'screen',
|
||||
'world_position': (0.0, 8.0, 2.0),
|
||||
'world_hpr': (25.0, 0.0, 0.0),
|
||||
'world_scale': (1.0, 1.0, 1.0),
|
||||
'world_pixels_per_unit': 280.0,
|
||||
'world_depth_test': True,
|
||||
'world_interactive': True,
|
||||
'world_two_sided': False,
|
||||
'fill_mode': True, # 默认开启填充模式
|
||||
'margins': {
|
||||
'left': 240,
|
||||
|
||||
@ -102,9 +102,74 @@ class LUIManagerEditorMixin:
|
||||
self._lui_structure_edit_sessions = {}
|
||||
return self._lui_structure_edit_sessions
|
||||
|
||||
def _triplet_or_default(self, value, fallback):
|
||||
if not isinstance(value, (list, tuple)) or len(value) < 3:
|
||||
return tuple(fallback)
|
||||
out = []
|
||||
for idx, default in enumerate(fallback):
|
||||
try:
|
||||
out.append(float(value[idx]))
|
||||
except Exception:
|
||||
out.append(float(default))
|
||||
return tuple(out)
|
||||
|
||||
def _ensure_canvas_world_defaults(self, canvas_data):
|
||||
if not canvas_data:
|
||||
return
|
||||
canvas_data["render_mode"] = str(canvas_data.get("render_mode") or "screen").lower().strip()
|
||||
if canvas_data["render_mode"] != "world":
|
||||
canvas_data["render_mode"] = "screen"
|
||||
canvas_data.setdefault("world_position", (0.0, 8.0, 2.0))
|
||||
canvas_data.setdefault("world_hpr", (25.0, 0.0, 0.0))
|
||||
canvas_data.setdefault("world_scale", (1.0, 1.0, 1.0))
|
||||
canvas_data.setdefault("world_pixels_per_unit", 280.0)
|
||||
canvas_data.setdefault("world_depth_test", True)
|
||||
canvas_data.setdefault("world_interactive", True)
|
||||
canvas_data.setdefault("world_two_sided", False)
|
||||
|
||||
def _copy_canvas_node_transform_to_canvas(self, canvas_data):
|
||||
node = (canvas_data or {}).get("node")
|
||||
if node is None:
|
||||
return
|
||||
try:
|
||||
if hasattr(node, "isEmpty") and node.isEmpty():
|
||||
return
|
||||
pos = node.getPos()
|
||||
hpr = node.getHpr()
|
||||
scale = node.getScale()
|
||||
canvas_data["world_position"] = (float(pos.x), float(pos.y), float(pos.z))
|
||||
canvas_data["world_hpr"] = (float(hpr.x), float(hpr.y), float(hpr.z))
|
||||
canvas_data["world_scale"] = (float(scale.x), float(scale.y), float(scale.z))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _apply_canvas_world_transform(self, canvas_data):
|
||||
node = (canvas_data or {}).get("node")
|
||||
if node is None:
|
||||
return
|
||||
try:
|
||||
if hasattr(node, "isEmpty") and node.isEmpty():
|
||||
return
|
||||
node.setPos(*self._triplet_or_default(canvas_data.get("world_position"), (0.0, 8.0, 2.0)))
|
||||
node.setHpr(*self._triplet_or_default(canvas_data.get("world_hpr"), (25.0, 0.0, 0.0)))
|
||||
node.setScale(*self._triplet_or_default(canvas_data.get("world_scale"), (1.0, 1.0, 1.0)))
|
||||
if canvas_data.get("render_mode") == "world":
|
||||
node.setTag("is_lui_canvas", "1")
|
||||
node.setTag("is_scene_element", "1")
|
||||
node.setTag("tree_item_type", "SCENE_NODE")
|
||||
else:
|
||||
for tag_name in ("is_lui_canvas", "is_scene_element", "tree_item_type"):
|
||||
if node.hasTag(tag_name):
|
||||
node.clearTag(tag_name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _capture_canvas_snapshot(self, canvas_data):
|
||||
if not canvas_data:
|
||||
return None
|
||||
self._ensure_canvas_world_defaults(canvas_data)
|
||||
if canvas_data.get("render_mode") == "world":
|
||||
self._copy_canvas_node_transform_to_canvas(canvas_data)
|
||||
|
||||
panel = canvas_data.get("panel")
|
||||
background = canvas_data.get("background")
|
||||
@ -121,6 +186,10 @@ class LUIManagerEditorMixin:
|
||||
top = float(pos.y)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
world_pixels_per_unit = float(canvas_data.get("world_pixels_per_unit", 280.0) or 280.0)
|
||||
except Exception:
|
||||
world_pixels_per_unit = 280.0
|
||||
|
||||
return {
|
||||
"name": canvas_data.get("name"),
|
||||
@ -128,6 +197,14 @@ class LUIManagerEditorMixin:
|
||||
"fill_mode": bool(canvas_data.get("fill_mode", False)),
|
||||
"fixed": bool(canvas_data.get("fixed", False)),
|
||||
"margins": copy.deepcopy(canvas_data.get("margins", {})),
|
||||
"render_mode": canvas_data.get("render_mode", "screen"),
|
||||
"world_position": self._triplet_or_default(canvas_data.get("world_position"), (0.0, 8.0, 2.0)),
|
||||
"world_hpr": self._triplet_or_default(canvas_data.get("world_hpr"), (25.0, 0.0, 0.0)),
|
||||
"world_scale": self._triplet_or_default(canvas_data.get("world_scale"), (1.0, 1.0, 1.0)),
|
||||
"world_pixels_per_unit": world_pixels_per_unit,
|
||||
"world_depth_test": bool(canvas_data.get("world_depth_test", True)),
|
||||
"world_interactive": bool(canvas_data.get("world_interactive", True)),
|
||||
"world_two_sided": bool(canvas_data.get("world_two_sided", False)),
|
||||
"left": left,
|
||||
"top": top,
|
||||
"width": width,
|
||||
@ -229,6 +306,12 @@ class LUIManagerEditorMixin:
|
||||
self._hide_resize_handles()
|
||||
except Exception:
|
||||
pass
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is not None:
|
||||
try:
|
||||
runtime.clear()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self, "debug_outlines") and isinstance(self.debug_outlines, dict):
|
||||
for borders in list(self.debug_outlines.values()):
|
||||
@ -315,11 +398,42 @@ class LUIManagerEditorMixin:
|
||||
canvas_data["fixed"] = bool(snapshot.get("fixed", canvas_data.get("fixed", False)))
|
||||
canvas_data["visible"] = bool(snapshot.get("visible", canvas_data.get("visible", True)))
|
||||
canvas_data["margins"] = copy.deepcopy(snapshot.get("margins", canvas_data.get("margins", {})))
|
||||
canvas_data["render_mode"] = str(snapshot.get("render_mode", canvas_data.get("render_mode", "screen")) or "screen").lower().strip()
|
||||
if canvas_data["render_mode"] != "world":
|
||||
canvas_data["render_mode"] = "screen"
|
||||
canvas_data["world_position"] = self._triplet_or_default(
|
||||
snapshot.get("world_position", canvas_data.get("world_position")),
|
||||
(0.0, 8.0, 2.0),
|
||||
)
|
||||
canvas_data["world_hpr"] = self._triplet_or_default(
|
||||
snapshot.get("world_hpr", canvas_data.get("world_hpr")),
|
||||
(25.0, 0.0, 0.0),
|
||||
)
|
||||
canvas_data["world_scale"] = self._triplet_or_default(
|
||||
snapshot.get("world_scale", canvas_data.get("world_scale")),
|
||||
(1.0, 1.0, 1.0),
|
||||
)
|
||||
try:
|
||||
canvas_data["world_pixels_per_unit"] = float(
|
||||
snapshot.get("world_pixels_per_unit", canvas_data.get("world_pixels_per_unit", 280.0)) or 280.0
|
||||
)
|
||||
except Exception:
|
||||
canvas_data["world_pixels_per_unit"] = 280.0
|
||||
canvas_data["world_depth_test"] = bool(
|
||||
snapshot.get("world_depth_test", canvas_data.get("world_depth_test", True))
|
||||
)
|
||||
canvas_data["world_interactive"] = bool(
|
||||
snapshot.get("world_interactive", canvas_data.get("world_interactive", True))
|
||||
)
|
||||
canvas_data["world_two_sided"] = bool(
|
||||
snapshot.get("world_two_sided", canvas_data.get("world_two_sided", False))
|
||||
)
|
||||
|
||||
panel = canvas_data.get("panel")
|
||||
background = canvas_data.get("background")
|
||||
title_label = canvas_data.get("title_label")
|
||||
canvas_node = canvas_data.get("node")
|
||||
self._apply_canvas_world_transform(canvas_data)
|
||||
|
||||
if canvas_node is not None and hasattr(canvas_node, "setName"):
|
||||
try:
|
||||
@ -366,9 +480,12 @@ class LUIManagerEditorMixin:
|
||||
pass
|
||||
|
||||
try:
|
||||
if canvas_data["visible"] and hasattr(panel, "show"):
|
||||
screen_visible = canvas_data["visible"] and canvas_data.get("render_mode") != "world"
|
||||
if hasattr(self, "_set_lui_canvas_screen_visible"):
|
||||
self._set_lui_canvas_screen_visible(canvas_data, screen_visible)
|
||||
elif screen_visible and hasattr(panel, "show"):
|
||||
panel.show()
|
||||
elif not canvas_data["visible"] and hasattr(panel, "hide"):
|
||||
elif (not screen_visible) and hasattr(panel, "hide"):
|
||||
panel.hide()
|
||||
except Exception:
|
||||
pass
|
||||
@ -402,6 +519,9 @@ class LUIManagerEditorMixin:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(canvas_index)
|
||||
|
||||
def _compute_lui_structure_absolute_position(self, index, component_snapshots, memo=None):
|
||||
if memo is None:
|
||||
memo = {}
|
||||
@ -1554,6 +1674,136 @@ class LUIManagerEditorMixin:
|
||||
# Recurse for grandchildren
|
||||
self._sync_canvas_children(child_idx)
|
||||
|
||||
def _set_canvas_render_mode(self, canvas, render_mode):
|
||||
if not canvas:
|
||||
return
|
||||
mode = "world" if str(render_mode).lower().strip() == "world" else "screen"
|
||||
self._ensure_canvas_world_defaults(canvas)
|
||||
canvas["render_mode"] = mode
|
||||
if mode == "world":
|
||||
current_hpr = self._triplet_or_default(canvas.get("world_hpr"), (0.0, 0.0, 0.0))
|
||||
if not canvas.get("_world_hpr_user_set") and all(abs(float(v)) < 1e-6 for v in current_hpr):
|
||||
canvas["world_hpr"] = (25.0, 0.0, 0.0)
|
||||
self._apply_canvas_world_transform(canvas)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
try:
|
||||
self.invalidate_world_space_canvas(self.canvases.index(canvas))
|
||||
except Exception:
|
||||
self.invalidate_world_space_canvas()
|
||||
if 0 <= getattr(self, "current_canvas_index", -1) < len(getattr(self, "canvases", [])):
|
||||
self.switch_canvas(self.current_canvas_index)
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is not None:
|
||||
try:
|
||||
runtime.sync()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(self.world, "updateSceneTree"):
|
||||
try:
|
||||
self.world.updateSceneTree()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _draw_canvas_render_mode_controls(self, canvas):
|
||||
self._ensure_canvas_world_defaults(canvas)
|
||||
mode_labels = ["屏幕 (Screen)", "场景3D (World)"]
|
||||
current_mode = canvas.get("render_mode", "screen")
|
||||
current_mode_index = 1 if current_mode == "world" else 0
|
||||
changed, new_mode_index = imgui.combo("渲染模式", current_mode_index, mode_labels)
|
||||
if changed:
|
||||
new_mode = "world" if new_mode_index == 1 else "screen"
|
||||
self._apply_lui_structure_change_with_history(
|
||||
lambda target_mode=new_mode: self._set_canvas_render_mode(canvas, target_mode)
|
||||
)
|
||||
|
||||
if canvas.get("render_mode") == "world":
|
||||
self._draw_world_canvas_controls(canvas)
|
||||
|
||||
def _draw_world_canvas_controls(self, canvas):
|
||||
self._copy_canvas_node_transform_to_canvas(canvas)
|
||||
imgui.separator()
|
||||
imgui.text("场景3D设置")
|
||||
|
||||
pos = self._triplet_or_default(canvas.get("world_position"), (0.0, 8.0, 2.0))
|
||||
changed, new_pos = imgui.drag_float3("3D位置", pos, 0.05, -10000.0, 10000.0)
|
||||
if imgui.is_item_activated():
|
||||
self._begin_lui_structure_snapshot_session("canvas_world_position")
|
||||
if changed:
|
||||
canvas["world_position"] = tuple(new_pos)
|
||||
self._apply_canvas_world_transform(canvas)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
self._finish_lui_structure_snapshot_session("canvas_world_position")
|
||||
|
||||
hpr = self._triplet_or_default(canvas.get("world_hpr"), (25.0, 0.0, 0.0))
|
||||
changed, new_hpr = imgui.drag_float3("3D旋转(HPR)", hpr, 0.5, -360.0, 360.0)
|
||||
if imgui.is_item_activated():
|
||||
self._begin_lui_structure_snapshot_session("canvas_world_hpr")
|
||||
if changed:
|
||||
canvas["world_hpr"] = tuple(new_hpr)
|
||||
canvas["_world_hpr_user_set"] = True
|
||||
self._apply_canvas_world_transform(canvas)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
self._finish_lui_structure_snapshot_session("canvas_world_hpr")
|
||||
|
||||
scale = self._triplet_or_default(canvas.get("world_scale"), (1.0, 1.0, 1.0))
|
||||
changed, new_scale = imgui.drag_float3("3D缩放", scale, 0.01, 0.01, 100.0)
|
||||
if imgui.is_item_activated():
|
||||
self._begin_lui_structure_snapshot_session("canvas_world_scale")
|
||||
if changed:
|
||||
canvas["world_scale"] = tuple(max(0.01, float(v)) for v in new_scale)
|
||||
self._apply_canvas_world_transform(canvas)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
self._finish_lui_structure_snapshot_session("canvas_world_scale")
|
||||
|
||||
ppu = float(canvas.get("world_pixels_per_unit", 280.0) or 280.0)
|
||||
changed, new_ppu = imgui.drag_float("像素/单位", ppu, 1.0, 10.0, 4000.0)
|
||||
if imgui.is_item_activated():
|
||||
self._begin_lui_structure_snapshot_session("canvas_world_ppu")
|
||||
if changed:
|
||||
canvas["world_pixels_per_unit"] = max(10.0, float(new_ppu))
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
self._finish_lui_structure_snapshot_session("canvas_world_ppu")
|
||||
|
||||
depth_test = bool(canvas.get("world_depth_test", True))
|
||||
changed, new_depth_test = imgui.checkbox("启用深度测试", depth_test)
|
||||
if changed:
|
||||
self._apply_lui_structure_change_with_history(
|
||||
lambda value=new_depth_test: canvas.__setitem__("world_depth_test", bool(value))
|
||||
)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
|
||||
interactive = bool(canvas.get("world_interactive", True))
|
||||
changed, new_interactive = imgui.checkbox("启用3D交互", interactive)
|
||||
if changed:
|
||||
self._apply_lui_structure_change_with_history(
|
||||
lambda value=new_interactive: canvas.__setitem__("world_interactive", bool(value))
|
||||
)
|
||||
|
||||
two_sided = bool(canvas.get("world_two_sided", False))
|
||||
changed, new_two_sided = imgui.checkbox("双面显示", two_sided)
|
||||
if changed:
|
||||
self._apply_lui_structure_change_with_history(
|
||||
lambda value=new_two_sided: canvas.__setitem__("world_two_sided", bool(value))
|
||||
)
|
||||
if hasattr(self, "invalidate_world_space_canvas"):
|
||||
self.invalidate_world_space_canvas(self.current_canvas_index)
|
||||
|
||||
debug_world_lui = bool(getattr(self, "debug_world_lui", False))
|
||||
changed, new_debug_world_lui = imgui.checkbox("输出3D LUI调试日志", debug_world_lui)
|
||||
if changed:
|
||||
self.debug_world_lui = bool(new_debug_world_lui)
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is not None:
|
||||
try:
|
||||
runtime._debug_force_next = True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def draw_editor(self):
|
||||
"""Draw the LUI Editor Window"""
|
||||
if hasattr(self, "world") and hasattr(self.world, "showLUIEditor"):
|
||||
@ -1651,10 +1901,15 @@ class LUIManagerEditorMixin:
|
||||
self._apply_lui_structure_change_with_history(
|
||||
lambda: (
|
||||
canvas.__setitem__('visible', visible),
|
||||
panel.show() if visible else panel.hide(),
|
||||
self._set_lui_canvas_screen_visible(
|
||||
canvas,
|
||||
visible and self._canvas_render_mode(canvas) != "world",
|
||||
) if hasattr(self, "_set_lui_canvas_screen_visible")
|
||||
else (panel.show() if visible else panel.hide()),
|
||||
)
|
||||
)
|
||||
|
||||
self._draw_canvas_render_mode_controls(canvas)
|
||||
|
||||
# Fill Mode & Margins
|
||||
is_fill_mode = canvas.get('fill_mode', False)
|
||||
|
||||
@ -69,14 +69,29 @@ class LUIManagerInteractionMixin:
|
||||
|
||||
self.current_canvas_index = index
|
||||
|
||||
# 显示选中的Canvas,隐藏其他Canvas
|
||||
# 显示选中的Canvas,隐藏其他Canvas。场景3D Canvas 只显示场景镜像,
|
||||
# 原始 overlay 仍作为编辑数据源,但不直接绘制在屏幕上。
|
||||
for i, canvas in enumerate(self.canvases):
|
||||
render_mode = "screen"
|
||||
try:
|
||||
render_mode = self._canvas_render_mode(canvas)
|
||||
except Exception:
|
||||
render_mode = str(canvas.get("render_mode") or "screen").lower().strip()
|
||||
screen_visible = (i == index and render_mode != "world")
|
||||
if i == index:
|
||||
canvas['panel'].show()
|
||||
canvas['visible'] = True
|
||||
else:
|
||||
canvas['panel'].hide()
|
||||
canvas['visible'] = False
|
||||
if hasattr(self, "_set_lui_canvas_screen_visible"):
|
||||
self._set_lui_canvas_screen_visible(canvas, screen_visible)
|
||||
else:
|
||||
try:
|
||||
if screen_visible:
|
||||
canvas['panel'].show()
|
||||
else:
|
||||
canvas['panel'].hide()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 显示/隐藏属于不同Canvas的组件(包括子组件)
|
||||
visible_count = 0
|
||||
@ -87,24 +102,43 @@ class LUIManagerInteractionMixin:
|
||||
comp_obj = comp['object']
|
||||
comp_name = comp.get('name', 'Unknown')
|
||||
|
||||
should_be_visible = (comp_canvas_index == index)
|
||||
canvas_render_mode = "screen"
|
||||
try:
|
||||
if comp_canvas_index is not None and 0 <= comp_canvas_index < len(self.canvases):
|
||||
canvas_render_mode = self._canvas_render_mode(self.canvases[comp_canvas_index])
|
||||
except Exception:
|
||||
canvas_render_mode = "screen"
|
||||
|
||||
should_be_visible = (comp_canvas_index == index and canvas_render_mode != "world")
|
||||
|
||||
if hasattr(self, "_set_lui_component_screen_visible"):
|
||||
self._set_lui_component_screen_visible(comp, should_be_visible)
|
||||
if should_be_visible:
|
||||
visible_count += 1
|
||||
else:
|
||||
hidden_count += 1
|
||||
|
||||
if hasattr(self, "_set_lui_component_screen_visible"):
|
||||
continue
|
||||
if should_be_visible:
|
||||
if hasattr(comp_obj, 'show'):
|
||||
comp_obj.show()
|
||||
elif hasattr(comp_obj, 'visible'):
|
||||
comp_obj.visible = True
|
||||
visible_count += 1
|
||||
else:
|
||||
if hasattr(comp_obj, 'hide'):
|
||||
comp_obj.hide()
|
||||
elif hasattr(comp_obj, 'visible'):
|
||||
comp_obj.visible = False
|
||||
hidden_count += 1
|
||||
|
||||
# 更新当前根节点
|
||||
if index >= 0:
|
||||
self.root = self.canvases[index]['panel']
|
||||
try:
|
||||
if self._canvas_render_mode(self.canvases[index]) == "world" and hasattr(self, "_hide_resize_handles"):
|
||||
self._hide_resize_handles()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 如果选中的组件不属于当前Canvas,取消选中
|
||||
if self.selected_index >= 0 and self.selected_index < len(self.components):
|
||||
@ -118,6 +152,24 @@ class LUIManagerInteractionMixin:
|
||||
|
||||
print(f"✓ Switched to Canvas: {self.canvases[index]['name']}")
|
||||
print(f" 显示组件: {visible_count}, 隐藏组件: {hidden_count}")
|
||||
try:
|
||||
if getattr(self, "debug_world_lui", False):
|
||||
active_canvas = self.canvases[index]
|
||||
active_mode = self._canvas_render_mode(active_canvas)
|
||||
active_panel = active_canvas.get("panel")
|
||||
parent_label = (
|
||||
self._debug_lui_parent_label(getattr(active_panel, "parent", None))
|
||||
if hasattr(self, "_debug_lui_parent_label")
|
||||
else str(getattr(active_panel, "parent", None))
|
||||
)
|
||||
print(
|
||||
"[LUI3D Debug] switch_canvas "
|
||||
f"idx={index} name='{active_canvas.get('name')}' mode={active_mode} "
|
||||
f"screen_visible={active_mode != 'world'} panel_parent={parent_label} "
|
||||
f"visible_components={visible_count} hidden_components={hidden_count}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _update_canvas_geometry(self, canvas_data):
|
||||
"""根据填充模式和边距更新Canvas几何信息"""
|
||||
@ -831,6 +883,14 @@ class LUIManagerInteractionMixin:
|
||||
|
||||
comp_data = self.components[self.selected_index]
|
||||
comp_type = comp_data['type']
|
||||
comp_canvas_index = comp_data.get('canvas_index')
|
||||
try:
|
||||
if comp_canvas_index is not None and 0 <= comp_canvas_index < len(self.canvases):
|
||||
if self._canvas_render_mode(self.canvases[comp_canvas_index]) == "world":
|
||||
self._hide_resize_handles()
|
||||
return task.cont
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 只对Frame、Button、Slider、InputField、Plane、Image显示resize handles
|
||||
if comp_type not in ['Frame', 'Button', 'Slider', 'InputField', 'Plane', 'Image', 'Checkbox', 'Text', 'Label', 'Video', 'Progressbar', 'Selectbox', 'ScrollableRegion', 'TabbedFrame', 'VerticalLayout', 'HorizontalLayout', 'HttpText']:
|
||||
@ -908,8 +968,12 @@ class LUIManagerInteractionMixin:
|
||||
continue
|
||||
|
||||
canvas = self.canvases[canvas_index]
|
||||
if not canvas.get('visible', True):
|
||||
# Hide outlines for components on hidden canvases
|
||||
try:
|
||||
is_world_canvas = self._canvas_render_mode(canvas) == "world"
|
||||
except Exception:
|
||||
is_world_canvas = str(canvas.get("render_mode") or "screen").lower().strip() == "world"
|
||||
if not canvas.get('visible', True) or is_world_canvas:
|
||||
# Hide screen-space editor outlines for hidden or scene-space canvases.
|
||||
borders = self.debug_outlines.get(idx)
|
||||
if borders:
|
||||
for b in borders:
|
||||
|
||||
1608
ui/LUI/lui_world_space.py
Normal file
1608
ui/LUI/lui_world_space.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,13 @@ from imgui_bundle import imgui, imgui_ctx
|
||||
|
||||
from ui.LUI.lui_manager_editor import LUIManagerEditorMixin
|
||||
from ui.LUI.lui_manager_interaction import LUIManagerInteractionMixin
|
||||
try:
|
||||
from ui.LUI.lui_world_space import WorldSpaceLUIRuntime
|
||||
except Exception as exc:
|
||||
WorldSpaceLUIRuntime = None
|
||||
LUI_WORLD_SPACE_IMPORT_ERROR = exc
|
||||
else:
|
||||
LUI_WORLD_SPACE_IMPORT_ERROR = None
|
||||
from ui.LUI.lui_shared import (
|
||||
CardMaker,
|
||||
LUIButton,
|
||||
@ -105,6 +112,10 @@ class LUIManager(LUIManagerInteractionMixin, LUIManagerEditorMixin):
|
||||
self.drag_states = {}
|
||||
self._last_interaction_frame = -1 # 记录最后一次交互的帧数,用于防止事件穿透
|
||||
self._input_enabled = True
|
||||
self.debug_world_lui = str(os.environ.get("EG_LUI_WORLD_DEBUG", "1")).lower() not in ("0", "false", "off", "no")
|
||||
self._debug_lui_visibility_state = {}
|
||||
self.world_space_runtime = None
|
||||
self._init_world_space_runtime()
|
||||
|
||||
# 创建resize handles和selection box(延迟创建,确保在Canvas之后)
|
||||
if hasattr(self.world, 'taskMgr'):
|
||||
@ -121,6 +132,226 @@ class LUIManager(LUIManagerInteractionMixin, LUIManagerEditorMixin):
|
||||
|
||||
print("✓ LUIManager initialized with complete functionality")
|
||||
|
||||
def _init_world_space_runtime(self):
|
||||
"""Attach the optional Panda3D scene-space LUI mirror."""
|
||||
if WorldSpaceLUIRuntime is None:
|
||||
print(f"⚠ LUI 3D 场景运行时不可用: {LUI_WORLD_SPACE_IMPORT_ERROR}")
|
||||
return
|
||||
try:
|
||||
self.world_space_runtime = WorldSpaceLUIRuntime(self)
|
||||
print("✓ LUI 3D 场景运行时已启用")
|
||||
except Exception as exc:
|
||||
self.world_space_runtime = None
|
||||
print(f"⚠ LUI 3D 场景运行时初始化失败: {exc}")
|
||||
|
||||
def _canvas_render_mode(self, canvas_data):
|
||||
mode = str((canvas_data or {}).get("render_mode") or "screen").lower().strip()
|
||||
return "world" if mode == "world" else "screen"
|
||||
|
||||
def _debug_world_lui_enabled(self):
|
||||
return bool(getattr(self, "debug_world_lui", False))
|
||||
|
||||
def _debug_lui_parent_label(self, obj):
|
||||
if obj is None:
|
||||
return "None"
|
||||
try:
|
||||
get_name = getattr(obj, "getName", None) or getattr(obj, "get_name", None)
|
||||
if callable(get_name):
|
||||
name = get_name()
|
||||
if name:
|
||||
return f"{obj.__class__.__name__}:{name}"
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
name = getattr(obj, "name", None)
|
||||
if name:
|
||||
return f"{obj.__class__.__name__}:{name}"
|
||||
except Exception:
|
||||
pass
|
||||
return f"{obj.__class__.__name__}@{id(obj):x}"
|
||||
|
||||
def _debug_lui_visible_value(self, obj):
|
||||
if obj is None:
|
||||
return None
|
||||
try:
|
||||
return getattr(obj, "visible", None)
|
||||
except Exception:
|
||||
return "?"
|
||||
|
||||
def _debug_lui_screen_visibility(self, canvas_data, requested_visible):
|
||||
if not self._debug_world_lui_enabled():
|
||||
return
|
||||
panel = (canvas_data or {}).get("panel")
|
||||
panel_parent = getattr(panel, "parent", None)
|
||||
parent_state = "None" if panel_parent is None else "parented"
|
||||
state = (
|
||||
bool(requested_visible),
|
||||
parent_state,
|
||||
self._debug_lui_visible_value(panel),
|
||||
self._debug_lui_visible_value((canvas_data or {}).get("background")),
|
||||
self._debug_lui_visible_value((canvas_data or {}).get("title_label")),
|
||||
self._canvas_render_mode(canvas_data),
|
||||
)
|
||||
key = id(canvas_data)
|
||||
if self._debug_lui_visibility_state.get(key) == state:
|
||||
return
|
||||
self._debug_lui_visibility_state[key] = state
|
||||
print(
|
||||
"[LUI3D Debug] screen overlay visibility "
|
||||
f"canvas='{(canvas_data or {}).get('name')}' "
|
||||
f"mode={state[5]} requested={state[0]} "
|
||||
f"panel_parent={self._debug_lui_parent_label(panel_parent)} "
|
||||
f"panel_parent_state={state[1]} panel_visible={state[2]} "
|
||||
f"bg_visible={state[3]} title_visible={state[4]}"
|
||||
)
|
||||
|
||||
def _set_lui_object_visible_deep(self, obj, visible, visited=None):
|
||||
if obj is None:
|
||||
return
|
||||
if visited is None:
|
||||
visited = set()
|
||||
obj_id = id(obj)
|
||||
if obj_id in visited:
|
||||
return
|
||||
visited.add(obj_id)
|
||||
|
||||
try:
|
||||
if visible and hasattr(obj, "show"):
|
||||
obj.show()
|
||||
elif (not visible) and hasattr(obj, "hide"):
|
||||
obj.hide()
|
||||
elif hasattr(obj, "visible"):
|
||||
obj.visible = bool(visible)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(obj, "visible"):
|
||||
obj.visible = bool(visible)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
child_attrs = (
|
||||
"_layout", "_label", "_text", "_shadow_text", "_knob",
|
||||
"_slider_bg", "_slider_fill", "_sprite_left", "_sprite_mid",
|
||||
"_sprite_right", "content_node", "root_layout", "header_bar",
|
||||
"main_frame", "label", "text_label", "sprite", "widget",
|
||||
"layout_obj", "object",
|
||||
)
|
||||
for attr_name in child_attrs:
|
||||
try:
|
||||
child = getattr(obj, attr_name, None)
|
||||
except Exception:
|
||||
child = None
|
||||
if child is not None:
|
||||
self._set_lui_object_visible_deep(child, visible, visited)
|
||||
|
||||
try:
|
||||
obj_dict = getattr(obj, "__dict__", {}) or {}
|
||||
except Exception:
|
||||
obj_dict = {}
|
||||
for value in obj_dict.values():
|
||||
if value is obj:
|
||||
continue
|
||||
if isinstance(value, dict):
|
||||
for nested in value.values():
|
||||
self._set_lui_object_visible_deep(nested, visible, visited)
|
||||
elif isinstance(value, (list, tuple, set)):
|
||||
for nested in value:
|
||||
self._set_lui_object_visible_deep(nested, visible, visited)
|
||||
elif value is not None and value.__class__.__name__.lower().startswith("lui"):
|
||||
self._set_lui_object_visible_deep(value, visible, visited)
|
||||
|
||||
def _set_lui_canvas_screen_visible(self, canvas_data, visible):
|
||||
panel = (canvas_data or {}).get("panel")
|
||||
if panel is None:
|
||||
return
|
||||
visible = bool(visible)
|
||||
try:
|
||||
if visible:
|
||||
parent = canvas_data.get("_screen_parent")
|
||||
if parent is None:
|
||||
parent = getattr(self, "overlay_root", None)
|
||||
if parent is not None and getattr(panel, "parent", None) is None:
|
||||
panel.parent = parent
|
||||
else:
|
||||
current_parent = getattr(panel, "parent", None)
|
||||
if current_parent is not None:
|
||||
canvas_data["_screen_parent"] = current_parent
|
||||
panel.parent = None
|
||||
self._set_lui_object_visible_deep(panel, visible)
|
||||
self._set_lui_object_visible_deep(canvas_data.get("background"), visible)
|
||||
self._set_lui_object_visible_deep(canvas_data.get("title_label"), visible)
|
||||
except Exception:
|
||||
pass
|
||||
self._debug_lui_screen_visibility(canvas_data, visible)
|
||||
|
||||
def _set_lui_component_screen_visible(self, comp_data, visible):
|
||||
comp_obj = (comp_data or {}).get("object")
|
||||
if comp_obj is None:
|
||||
return
|
||||
visible = bool(visible)
|
||||
try:
|
||||
if visible:
|
||||
parent = comp_data.get("_screen_parent")
|
||||
if parent is None:
|
||||
canvas_index = comp_data.get("canvas_index")
|
||||
if canvas_index is not None and 0 <= int(canvas_index) < len(getattr(self, "canvases", [])):
|
||||
parent = self.canvases[int(canvas_index)].get("panel")
|
||||
if parent is not None and getattr(comp_obj, "parent", None) is None:
|
||||
comp_obj.parent = parent
|
||||
else:
|
||||
current_parent = getattr(comp_obj, "parent", None)
|
||||
if current_parent is not None:
|
||||
comp_data["_screen_parent"] = current_parent
|
||||
comp_obj.parent = None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self._set_lui_object_visible_deep(comp_obj, visible)
|
||||
for key in (
|
||||
"widget", "sprite", "text_label", "layout_obj",
|
||||
"keep_alive", "keep_alive_node",
|
||||
):
|
||||
self._set_lui_object_visible_deep(comp_data.get(key), visible)
|
||||
|
||||
def is_world_space_pointer_over_ui(self):
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is None:
|
||||
return False
|
||||
try:
|
||||
return bool(runtime.is_pointer_over_ui())
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def has_world_space_pointer_capture(self):
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is None:
|
||||
return False
|
||||
return bool(getattr(runtime, "_active_hit", None))
|
||||
|
||||
def handle_world_space_pointer_press(self):
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is None:
|
||||
return False
|
||||
try:
|
||||
return bool(runtime.handle_pointer_press_from_event())
|
||||
except Exception as exc:
|
||||
print(f"LUI 3D UI pointer handling failed: {exc}")
|
||||
return False
|
||||
|
||||
def invalidate_world_space_canvas(self, canvas_index=None):
|
||||
runtime = getattr(self, "world_space_runtime", None)
|
||||
if runtime is None:
|
||||
return
|
||||
try:
|
||||
if canvas_index is None:
|
||||
runtime._signatures.clear()
|
||||
else:
|
||||
runtime._signatures.pop(int(canvas_index), None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_input_enabled(self, enabled):
|
||||
"""Enable/disable LUI input handling (used to avoid ImGui click-through)."""
|
||||
if not self.lui_enabled:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user