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