217 lines
8.4 KiB
Python
217 lines
8.4 KiB
Python
import os
|
||
|
||
import panda3d.core as p3d
|
||
from imgui_bundle import imgui, imgui_ctx
|
||
|
||
from ui.LUI.lui_manager_editor import LUIManagerEditorMixin
|
||
from ui.LUI.lui_manager_interaction import LUIManagerInteractionMixin
|
||
from ui.LUI.lui_shared import (
|
||
CardMaker,
|
||
LUIButton,
|
||
LUIDefaultSkin,
|
||
LUIFrame,
|
||
LUIInputField,
|
||
LUIInputHandler,
|
||
LUILabel,
|
||
LUIObject,
|
||
LUIRegion,
|
||
LUISlider,
|
||
LUISprite,
|
||
NodePath,
|
||
Path,
|
||
lui,
|
||
)
|
||
from ui.lui_function import luiFunction
|
||
|
||
|
||
class LUIManager(LUIManagerInteractionMixin, LUIManagerEditorMixin):
|
||
"""Manager for LUI system integration"""
|
||
def __init__(self, world):
|
||
self.world = world
|
||
self.lui_enabled = (lui is not None)
|
||
# Always initialize selection fields, even when LUI is unavailable.
|
||
self.selected_index = -1
|
||
self._last_selected_index = -1
|
||
|
||
self.luiFunction = luiFunction
|
||
|
||
# Editor State
|
||
self.show_editor = True
|
||
self.play_mode = False
|
||
self._selected_type_index = 0
|
||
|
||
if not self.lui_enabled:
|
||
print("LUI is not available, LUIManager will be disabled.")
|
||
return
|
||
|
||
# 1. 优先注册中文字体
|
||
self._register_chinese_font()
|
||
|
||
# 2. 初始化核心 LUI 区域
|
||
self._init_ui_regions()
|
||
|
||
# 3. 加载皮肤资源
|
||
self.lui_skin = LUIDefaultSkin()
|
||
self.lui_skin.load()
|
||
|
||
# 3.1 皮肤会覆盖 default/label/header 字体,这里再次注册中文字体
|
||
# 以避免 LUIText 输出中文时出现 “Font does not support character ...”
|
||
self._register_chinese_font()
|
||
|
||
# 4. 状态管理 - 与engine保持一致
|
||
self.canvases = []
|
||
self.current_canvas_index = -1
|
||
self.canvas_counter = 0
|
||
self.components = []
|
||
self.root_order = []
|
||
self._tree_drag_src = None
|
||
self.selected_index = -1
|
||
self._last_selected_index = -1
|
||
|
||
# 拖拽和选择状态
|
||
self.dragging_comp = None
|
||
self.pending_drag_comp = None
|
||
self.pending_drag_index = -1
|
||
self.pending_drag_start_mouse = None
|
||
self.pending_drag_start_abs = None
|
||
self.pending_drag_start_parent = None
|
||
self.dragging_index = -1
|
||
self.drag_start_threshold = 6.0
|
||
self.drag_offset = (0, 0)
|
||
|
||
# 锚点功能相关变量
|
||
self.anchor_popup_open = False
|
||
self.pending_child_creation = False
|
||
self.pending_parent_index = -1
|
||
|
||
# 子父对齐功能
|
||
self.child_parent_alignment_enabled = True
|
||
|
||
# Resize handles 相关变量
|
||
self.resize_handles = []
|
||
self.selection_box = []
|
||
self.debug_outlines = {} # comp_index -> [border sprites]
|
||
self.resizing_handle = None
|
||
self.resize_start_pos = (0, 0)
|
||
self.resize_start_bounds = {}
|
||
self.min_size = 20
|
||
|
||
# 使用外部字典管理状态,避免修改 C++ 对象属性导致 AttributeError
|
||
self.drag_states = {}
|
||
self._last_interaction_frame = -1 # 记录最后一次交互的帧数,用于防止事件穿透
|
||
self._input_enabled = True
|
||
|
||
# 创建resize handles和selection box(延迟创建,确保在Canvas之后)
|
||
if hasattr(self.world, 'taskMgr'):
|
||
self.world.taskMgr.doMethodLater(0.1, self._delayed_create_resize_handles, "delayed_create_resize_handles")
|
||
else:
|
||
self._create_resize_handles()
|
||
|
||
# 添加任务来处理拖动和resize handles更新
|
||
if hasattr(self.world, 'taskMgr'):
|
||
self.world.taskMgr.add(self._update_drag, "update_lui_drag")
|
||
self.world.taskMgr.add(self._update_resize_handles, "update_resize_handles")
|
||
self.world.taskMgr.add(self._update_component_outlines, "update_lui_outlines")
|
||
self.world.taskMgr.add(self._update_http_components_task, "update_http_components")
|
||
|
||
print("✓ LUIManager initialized with complete functionality")
|
||
|
||
def set_input_enabled(self, enabled):
|
||
"""Enable/disable LUI input handling (used to avoid ImGui click-through)."""
|
||
if not self.lui_enabled:
|
||
return
|
||
|
||
if enabled == self._input_enabled:
|
||
return
|
||
|
||
self._input_enabled = enabled
|
||
|
||
try:
|
||
if enabled:
|
||
if hasattr(self, "overlay_handler_node") and self.overlay_handler_node:
|
||
self.overlay_handler_node.reparent_to(self.world.mouseWatcher)
|
||
if hasattr(self, "overlay_region") and self.overlay_region:
|
||
self.overlay_region.set_input_handler(self.overlay_handler)
|
||
else:
|
||
if hasattr(self, "overlay_region") and self.overlay_region:
|
||
self.overlay_region.set_input_handler(None)
|
||
if hasattr(self, "overlay_handler_node") and self.overlay_handler_node:
|
||
self.overlay_handler_node.detach_node()
|
||
except Exception as e:
|
||
print(f"Warning: Failed to toggle LUI input state: {e}")
|
||
|
||
def _register_chinese_font(self):
|
||
"""注册支持中文的字体"""
|
||
from panda3d.lui import LUIFontPool
|
||
import os
|
||
|
||
# 尝试常见的系统路径,确保使用完整的 OS 路径
|
||
candidate_fonts = [
|
||
"C:/Windows/Fonts/msyh.ttc",
|
||
"C:/Windows/Fonts/msyhbd.ttc",
|
||
"C:/Windows/Fonts/simhei.ttf",
|
||
"C:/Windows/Fonts/simsun.ttc",
|
||
"C:/Windows/Fonts/arialuni.ttf",
|
||
"C:/Windows/Fonts/arial.ttf"
|
||
]
|
||
|
||
font_registered = False
|
||
for fpath in candidate_fonts:
|
||
if os.path.exists(fpath):
|
||
try:
|
||
font_default = self.world.loader.loadFont(fpath)
|
||
font_label = self.world.loader.loadFont(fpath)
|
||
font_header = self.world.loader.loadFont(fpath)
|
||
|
||
if font_default and font_label and font_header:
|
||
if hasattr(font_label, "setPixelsPerUnit"):
|
||
font_label.setPixelsPerUnit(32)
|
||
if hasattr(font_header, "setPixelsPerUnit"):
|
||
font_header.setPixelsPerUnit(80)
|
||
|
||
font_pool = LUIFontPool.get_global_ptr()
|
||
font_pool.register_font("default", font_default)
|
||
font_pool.register_font("label", font_label)
|
||
font_pool.register_font("header", font_header)
|
||
print(f"✓ LUI 成功注册中文字体: {fpath}")
|
||
font_registered = True
|
||
break
|
||
except Exception as e:
|
||
print(f"LUI 字体注册失败 {fpath}: {e}")
|
||
|
||
if not font_registered:
|
||
# 如果没有系统字体,至少注册一个默认的避免崩溃
|
||
print("⚠️ 警告: 未能找到合适的 LUI 中文字体")
|
||
|
||
def _init_ui_regions(self):
|
||
"""初始化 LUI 区域 (Overlay, Camera, WorldSpace)"""
|
||
# 渲染层级说明(参考engine/main.py的设置):
|
||
# - 3D场景(render)默认sort=0
|
||
# - LUI组件设置sort=5,在3D场景之上
|
||
# - pixel2d (ImGui) 默认sort=20,在LUI之上
|
||
# 这样:3D场景 < LUI组件 < ImGui面板
|
||
|
||
self.overlay_region = LUIRegion.make("OverlayUI", self.world.win)
|
||
self.overlay_handler = LUIInputHandler()
|
||
self.overlay_handler_node = self.world.mouseWatcher.attach_new_node(self.overlay_handler)
|
||
self.overlay_region.set_input_handler(self.overlay_handler)
|
||
self.overlay_region.set_sort(5) # 在3D场景之上,在ImGui之下(与engine保持一致)
|
||
self.overlay_root = self.overlay_region.root
|
||
|
||
# 调试输出:确认层级设置
|
||
actual_sort = self.overlay_region.getSort()
|
||
print(f"✓ LUI Overlay Region 层级设置: {actual_sort}")
|
||
|
||
# 显示所有DisplayRegion的层级信息
|
||
num_regions = self.world.win.getNumDisplayRegions()
|
||
print(f"✓ 窗口总共有 {num_regions} 个DisplayRegion:")
|
||
for i in range(num_regions):
|
||
dr = self.world.win.getDisplayRegion(i)
|
||
sort_value = dr.getSort()
|
||
camera = dr.getCamera()
|
||
camera_name = camera.getName() if not camera.isEmpty() else "无摄像机"
|
||
print(f" Region {i}: sort={sort_value}, camera={camera_name}")
|
||
|
||
# 当前操作的根节点
|
||
self.root = self.overlay_root
|