EG/ui/lui_manager.py
2026-02-27 16:52:00 +08:00

217 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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