import os
import sys
from pathlib import Path
import panda3d.core as p3d
from panda3d.core import NodePath, CardMaker
from imgui_bundle import imgui, imgui_ctx

# Ensure ui directory is in path and lui is correctly patched
UI_DIR = Path(__file__).resolve().parent
if str(UI_DIR) not in sys.path:
    sys.path.insert(0, str(UI_DIR))

# Add Builtin to path
BUILTIN_DIR = UI_DIR / "Builtin"
if str(BUILTIN_DIR) not in sys.path:
    sys.path.insert(0, str(BUILTIN_DIR))

try:
    import lui
    # Monkey patch for Builtin components
    import panda3d
    panda3d.lui = lui
    sys.modules["panda3d.lui"] = lui
    
    from Builtin.LUIRegion import LUIRegion
    from Builtin.LUIInputHandler import LUIInputHandler
    from Builtin.LUIButton import LUIButton
    from Builtin.LUILabel import LUILabel
    from Builtin.LUIInputField import LUIInputField
    from Builtin.LUISlider import LUISlider
    from Builtin.LUIFrame import LUIFrame
    from Builtin.LUISkin import LUIDefaultSkin
    from Builtin.LUISprite import LUISprite
    from Builtin.LUIObject import LUIObject
    from Builtin.LUICheckbox import LUICheckbox
    from Builtin.LUIProgressbar import LUIProgressbar
    from Builtin.LUISelectbox import LUISelectbox
    from Builtin.LUIScrollableRegion import LUIScrollableRegion
    from Builtin.LUITabbedFrame import LUITabbedFrame
    from Builtin.LUIVerticalLayout import LUIVerticalLayout
    from Builtin.LUIHorizontalLayout import LUIHorizontalLayout
except ImportError as e:
    print(f"Error: Failed to import LUI: {e}")
    lui = None

class luiFunction:

    def create_canvas(manager, name=None):
        """创建一个可视化的 UI 面板作为画布"""
        manager.canvas_counter += 1
        canvas_name = name or f"{manager.canvas_counter}"
        
        # 获取窗口尺寸
        win_width = 800
        win_height = 600
        if hasattr(manager.world, 'win'):
            win_width = manager.world.win.getXSize()
            win_height = manager.world.win.getYSize()

        # 计算场景窗口区域（排除Hierarchy面板）
        left_offset = 0
        if hasattr(manager.world, 'hierarchy_size'):
            left_offset = manager.world.hierarchy_size.x
            
        target_width = win_width - left_offset
        target_height = win_height
        
        canvas_panel = LUIObject(parent=manager.overlay_root, x=left_offset, y=0)
        
        # 显式设置尺寸
        canvas_panel.width = target_width
        canvas_panel.height = target_height
        canvas_panel.solid = True # 接收事件
        
        # 创建背景 Sprite
        bg = LUISprite(canvas_panel, "blank", "skin")
        bg.width = target_width
        bg.height = target_height
        bg.color = (0.5, 0.5, 0.5, 1.0)  # 半透明灰色背景，可见但不遮挡ImGui
        # Canvas背景在LUI内部的底层
        if hasattr(bg, 'set_sort'):
            bg.set_sort(-1)  # LUI内部的底层
            
        # Canvas面板使用默认层级（继承自LUI Region的sort=5）
        # 不需要额外设置sort，会自动继承LUI Region的层级
        canvas_panel.solid = True  # 接收事件，用于Canvas交互
        
        # 创建Canvas的NodePath节点用于层级树显示
        canvas_node = NodePath(canvas_name)
        canvas_node.reparent_to(manager.world.render)
        
        # 存储Canvas数据
        canvas_data = {
            'name': canvas_name,
            'node': canvas_node,
            'panel': canvas_panel,
            'visible': True,
            'background': bg,
            'fill_mode': True,  # 默认开启填充模式
            'margins': {
                'left': 240,
                'right': 480,  # 默认右侧面板宽度
                'top': 89,
                'bottom': 220  # 默认底部面板高度
            }
        }
        
        # 应用初始几何计算 (调用 manager 的方法)
        # 注意：这里需要 manager 是 public 的或者我们访问 protected 方法
        if hasattr(manager, '_update_canvas_geometry'):
            manager._update_canvas_geometry(canvas_data)
        
        # 添加面板标题
        title_text = f"Canvas: {canvas_name}"
        title = LUILabel(text=title_text, font_size=16)
        title.set_parent(canvas_panel)
        title.set_pos(10, 10) # 内部 padding
        if hasattr(title, 'set_color'):
            title.set_color((0.8, 0.8, 0.8, 1.0))
        
        # 存储标题引用以便后续修改
        canvas_data['title_label'] = title
        
        # 启用Canvas拖动
        if hasattr(manager, '_make_canvas_draggable'):
            manager._make_canvas_draggable(canvas_panel)
        
        manager.canvases.append(canvas_data)
        manager.current_canvas_index = len(manager.canvases) - 1
        manager.switch_canvas(manager.current_canvas_index)
        
        # 调试输出：确认Canvas层级
        print(f"✓ Created Canvas: {canvas_name}")
        print(f"  Canvas panel sort: {getattr(canvas_panel, 'sort', 'N/A')}")
        print(f"  Background sprite sort: {getattr(bg, 'sort', 'N/A')}")
        
        if hasattr(manager.world, 'updateSceneTree'):
            manager.world.updateSceneTree()
        return canvas_data

    def create_button(manager, text="Button", x=100, y=100, parent=None):
        """Create a LUI Button"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        btn = LUIButton(parent=parent, text=text, x=x, y=y)

        # Ensure size is initialized so selection bounds match the actual button
        default_w = 100
        default_h = 30
        try:
            if not hasattr(btn, 'width') or btn.width is None or btn.width <= 0:
                btn.width = default_w
            if not hasattr(btn, 'height') or btn.height is None or btn.height <= 0:
                btn.height = default_h
        except Exception:
            pass

        width = getattr(btn, 'width', default_w) or default_w
        height = getattr(btn, 'height', default_h) or default_h

        
        # 设置组件层级，在Canvas背景之上
        if hasattr(btn, 'set_sort'):
            btn.set_sort(1)  # 在Canvas背景之上
        
        comp_data = {
            'type': 'Button',
            'object': btn,
            'widget': btn,
            'text': text,
            'left': x,
            'top': y,
            'width': float(width),
            'height': float(height),
            'canvas_index': manager.current_canvas_index,
            'name': f"Button_{len(manager.components)}",
            'color': (1.0, 1.0, 1.0, 1.0),
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        if hasattr(manager, '_setup_component_drag'):
            manager._setup_component_drag(comp_data, comp_index)
        
        if hasattr(manager, '_add_to_scene_tree'):
             manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return btn

    def create_label(manager,text="Text", x=100, y=100, font_size=14, parent=None):
            """Create a LUI Text"""
            if not manager.lui_enabled: return None
            
            # 如果没有指定parent，使用当前Canvas的panel作为parent
            if parent is None and manager.current_canvas_index >= 0:
                parent = manager.canvases[manager.current_canvas_index]['panel']
            elif parent is None:
                parent = manager.overlay_root
                
            lbl = LUILabel(parent=parent, text=text, x=x, y=y, solid=True, font_size=font_size)
            
            # 设置组件层级，在Canvas背景之上
            if hasattr(lbl, 'set_sort'):
                lbl.set_sort(1)  # 在Canvas背景之上
            
            comp_data = {
                'type': 'Text',
                'object': lbl,
                'text': text,
                'left': x,
                'top': y,
                'width': 80,
                'height': 20,
                'canvas_index': manager.current_canvas_index,
                'name': f"Text_{len(manager.components)}",
                'color': (1, 1, 1, 1),
                'font_size': font_size,
                'sort': 1
            }
            
            # 设置拖拽功能
            comp_index = len(manager.components)
            manager._setup_component_drag(comp_data, comp_index)
            
            # 添加到层级树
            manager._add_to_scene_tree(comp_data)
            
            manager.components.append(comp_data)
            return lbl

    def create_input_field(manager,text="", x=100, y=100, width=200, parent=None):
            """Create a LUI InputField"""
            if not manager.lui_enabled: return None
            
            # 如果没有指定parent，使用当前Canvas的panel作为parent
            if parent is None and manager.current_canvas_index >= 0:
                parent = manager.canvases[manager.current_canvas_index]['panel']
            elif parent is None:
                parent = manager.overlay_root
                
            inf = LUIInputField(parent=parent, value=text, x=x, y=y, width=width)
            
            comp_data = {
                'type': 'InputField',
                'object': inf,
                'text': text,
                'left': x,
                'top': y,
                'width': width,
                'height': 24,
                'canvas_index': manager.current_canvas_index,
                'name': f"InputField_{len(manager.components)}",
                'value': text,
                'placeholder': "输入文本...",
                'sort': 1
            }
            
            # 设置拖拽功能
            comp_index = len(manager.components)
            manager._setup_component_drag(comp_data, comp_index)
            
            # 添加到层级树
            manager._add_to_scene_tree(comp_data)
            
            manager.components.append(comp_data)
            return inf

    def create_slider(manager,x=100, y=100, width=200, parent=None):
        """Create a LUI Slider"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        sld = LUISlider(parent=parent, x=x, y=y, width=width)
        if hasattr(sld, 'set_height'):
            sld.set_height(16)
        if hasattr(sld, 'height'):
            sld.height = 16
        
        comp_data = {
            'type': 'Slider',
            'object': sld,
            'left': x,
            'top': y,
            'width': width,
            'height': 16,
            'canvas_index': manager.current_canvas_index,
            'name': f"Slider_{len(manager.components)}",
            'min_value': 0.0,
            'max_value': 100.0,
            'value': 50.0,
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return sld

    def create_frame(manager,x=100, y=100, width=300, height=200, parent=None):
        """Create a LUI Frame"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        frm = LUIFrame(parent=parent, x=x, y=y, width=width, height=height)
        
        comp_data = {
            'type': 'Frame',
            'object': frm,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"Frame_{len(manager.components)}",
            'color': (0.7, 0.7, 0.7, 0.8),
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return frm


        

    def create_vertical_layout(manager, x=100, y=100, width=300, height=200, spacing=0.0, parent=None):
        """Create a LUI Vertical Layout"""
        if not manager.lui_enabled: return None

        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root

        container = LUIObject(parent=parent, x=x, y=y)
        if hasattr(container, 'width'):
            container.width = width
        if hasattr(container, 'height'):
            container.height = height
        if hasattr(container, 'solid'):
            container.solid = True

        layout = LUIVerticalLayout(parent=container, spacing=spacing)

        padding_left = 0.0
        padding_right = 0.0
        padding_top = 0.0
        padding_bottom = 0.0

        inner_w = max(1.0, float(width) - (padding_left + padding_right))
        inner_h = max(1.0, float(height) - (padding_top + padding_bottom))

        if hasattr(layout, 'left'):
            layout.left = padding_left
        if hasattr(layout, 'top'):
            layout.top = padding_top
        if hasattr(layout, 'width'):
            layout.width = inner_w
        if hasattr(layout, 'height'):
            layout.height = inner_h

        comp_data = {
            'type': 'VerticalLayout',
            'object': container,
            'layout_obj': layout,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'layout_spacing': spacing,
            'layout_padding_left': padding_left,
            'layout_padding_right': padding_right,
            'layout_padding_top': padding_top,
            'layout_padding_bottom': padding_bottom,
            'layout_align': 'start',
            'layout_wrap': True,
            'layout_line_spacing': 0.0,
            'canvas_index': manager.current_canvas_index,
            'name': f"VerticalLayout_{len(manager.components)}",
            'sort': 1,
            'draggable': True
        }

        comp_index = len(manager.components)
        if hasattr(manager, '_setup_component_drag'):
            manager._setup_component_drag(comp_data, comp_index)

        if hasattr(manager, '_add_to_scene_tree'):
            manager._add_to_scene_tree(comp_data)

        manager.components.append(comp_data)
        return container

    def create_horizontal_layout(manager, x=100, y=100, width=300, height=200, spacing=0.0, parent=None):
        """Create a LUI Horizontal Layout"""
        if not manager.lui_enabled: return None

        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root

        container = LUIObject(parent=parent, x=x, y=y)
        if hasattr(container, 'width'):
            container.width = width
        if hasattr(container, 'height'):
            container.height = height
        if hasattr(container, 'solid'):
            container.solid = True

        layout = LUIHorizontalLayout(parent=container, spacing=spacing)

        padding_left = 0.0
        padding_right = 0.0
        padding_top = 0.0
        padding_bottom = 0.0

        inner_w = max(1.0, float(width) - (padding_left + padding_right))
        inner_h = max(1.0, float(height) - (padding_top + padding_bottom))

        if hasattr(layout, 'left'):
            layout.left = padding_left
        if hasattr(layout, 'top'):
            layout.top = padding_top
        if hasattr(layout, 'width'):
            layout.width = inner_w
        if hasattr(layout, 'height'):
            layout.height = inner_h

        comp_data = {
            'type': 'HorizontalLayout',
            'object': container,
            'layout_obj': layout,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'layout_spacing': spacing,
            'layout_padding_left': padding_left,
            'layout_padding_right': padding_right,
            'layout_padding_top': padding_top,
            'layout_padding_bottom': padding_bottom,
            'layout_align': 'start',
            'layout_wrap': True,
            'layout_line_spacing': 0.0,
            'canvas_index': manager.current_canvas_index,
            'name': f"HorizontalLayout_{len(manager.components)}",
            'sort': 1,
            'draggable': True
        }

        comp_index = len(manager.components)
        if hasattr(manager, '_setup_component_drag'):
            manager._setup_component_drag(comp_data, comp_index)

        if hasattr(manager, '_add_to_scene_tree'):
            manager._add_to_scene_tree(comp_data)

        manager.components.append(comp_data)
        return container

    def create_checkbox(manager, label="Checkbox", x=100, y=100, parent=None):
        """Create a LUI Checkbox"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        chk = LUICheckbox(parent=parent, label=label, x=x, y=y)
        
        comp_data = {
            'type': 'Checkbox',
            'object': chk,
            'text': label,
            'left': x,
            'top': y,
            'width': 120,
            'height': 20,
            'canvas_index': manager.current_canvas_index,
            'name': f"Checkbox_{len(manager.components)}",
            'checked': False,
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return chk

    def create_plane(manager, x=100, y=100, width=100, height=100, color=(1,1,1,1), parent=None):
        """Create a LUI Plane (using Sprite with blank texture)"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        # 创建LUIObject容器
        obj = LUIObject(parent=parent, x=x, y=y)
        obj.width = width
        obj.height = height
        obj.solid = True  # 重要：设置为solid才能接收鼠标事件
        
        # 设置组件层级，在Canvas背景之上 (Plane)
        if hasattr(obj, 'set_sort'):
            obj.set_sort(1)  # 在Canvas背景之上
        
        # 创建Sprite作为平面背景
        spr = LUISprite(obj, "blank", "skin")
        spr.width = width
        spr.height = height
        spr.color = color
        spr.left = 0
        spr.top = 0
        
        comp_data = {
            'type': 'Plane',
            'object': obj,
            'sprite': spr,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"Plane_{len(manager.components)}",
            'color': color,
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return obj

    def create_image(manager, texture_path="blank", x=100, y=100, width=100, height=100, parent=None):
        """Create a LUI Image"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        # 创建LUIObject容器
        obj = LUIObject(parent=parent, x=x, y=y)
        obj.width = width
        obj.height = height
        obj.solid = True  # 重要：设置为solid才能接收鼠标事件
        
        # 设置组件层级，在Canvas背景之上 (Image)
        if hasattr(obj, 'set_sort'):
            obj.set_sort(1)  # 在Canvas背景之上
        
        # 加载纹理 (使用皮肤中的'blank'或外部路径)
        # 注意: LUISprite通常使用图集图像名称或纹理
        # 对于外部图像，可能需要加载纹理并使用不同的LUI元素
        # 或将其注册到图集中。LUISprite(parent, image, atlas)
        
        # 为了简单起见，我们使用默认皮肤sprite，它有'blank'
        img_name = "blank" 
        spr = LUISprite(obj, img_name, "skin")
        spr.width = width
        spr.height = height
        spr.left = 0
        spr.top = 0
        
        # 设置默认图像颜色（浅灰色，表示这是一个图像占位符）
        spr.color = (1.0, 1.0, 1.0, 1.0)
        
        comp_data = {
            'type': 'Image',
            'object': obj,
            'sprite': spr,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"Image_{len(manager.components)}",
            'texture_path': texture_path,
            'color': (1.0, 1.0, 1.0, 1.0),
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return obj

    def create_video(manager, video_path="", x=100, y=100, width=320, height=240, parent=None):
        """Create a LUI Video Player component"""
        if not manager.lui_enabled: return None
        
        # 如果没有指定parent，使用当前Canvas的panel作为parent
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        # 1. 创建容器对象
        obj = LUIObject(parent=parent, x=x, y=y)
        obj.width = width
        obj.height = height
        obj.solid = True
        
        # 设置层级
        if hasattr(obj, 'set_sort'):
            obj.set_sort(1)
            
        # 2. 准备视频纹理
        video_texture = None
        if video_path:
            try:
                from panda3d.core import Filename, MovieTexture, MovieVideo, loadPrcFileData
                # Trim whitespace which could cause "file not found" errors
                video_path = video_path.strip()
                
                # Allow network protocols
                loadPrcFileData("", "ffmpeg-protocol-whitelist http,https,tcp,tls,file")
                
                if "://" in video_path:
                    # 尝试更稳定的加载序列
                    try:
                        video_src = MovieVideo.get(Filename(video_path))
                        video_texture = MovieTexture("RemoteVideo")
                        if not video_texture.load(video_src):
                            video_texture = manager.world.loader.loadTexture(video_path)
                    except:
                        video_texture = manager.world.loader.loadTexture(video_path)
                else:
                    video_texture = manager.world.loader.loadTexture(Filename.from_os_specific(video_path))
                
                if video_texture:
                    print(f"✓ 视频加载成功: {video_path}")
                    # 尝试同步视频尺寸比例
                    orig_w = video_texture.getOrigFileXSize()
                    orig_h = video_texture.getOrigFileYSize()
                    if orig_w > 0 and orig_h > 0:
                        ratio = orig_w / orig_h
                        # 保持宽度，调整高度
                        new_height = width / ratio
                        obj.height = new_height
                        height = new_height # Update local var
            except Exception as e:
                print(f"⚠ 加载视频失败: {e}")




        # 2.1 ???????????????????????????????????????

        audio_sound = None
        audio_path = ""
        audio_from_video = False
        if video_path and "://" not in video_path:
            try:
                audio_path = video_path
                audio_sound = manager.world.loader.loadSfx(p3d.Filename.from_os_specific(video_path))
                if audio_sound:
                    audio_from_video = True
                    if hasattr(audio_sound, 'setLoop'):
                        audio_sound.setLoop(True)
                    if hasattr(audio_sound, 'setVolume'):
                        audio_sound.setVolume(1.0)
            except Exception:
                audio_sound = None
                audio_path = ""

        # 3. 创建Sprite
        if video_texture:
            # 直接使用纹理创建，不使用Atlas
            spr = LUISprite(obj, video_texture)
            spr.color = (1, 1, 1, 1) # Video needs white to show original colors
        else:
            # 使用默认黑色背景
            spr = LUISprite(obj, "blank", "skin")
            spr.color = (0.0, 0.0, 0.0, 1.0) 

        spr.width = width
        spr.height = height

        if video_texture:
            obj.video_texture = video_texture # Prevent GC
            
            # Ensure full UVs
            if hasattr(spr, 'set_uv_range'):
                spr.set_uv_range(0, 0, 1, 1)
            
            # 如果支持，自动播放
            if hasattr(video_texture, 'play'):
                # Stop first to reset state, critical for some video formats
                if hasattr(video_texture, 'stop'):
                     video_texture.stop()
                video_texture.play()
            if hasattr(video_texture, 'setLoop'):
                video_texture.setLoop(True)
        if audio_sound:
            try:
                if hasattr(audio_sound, 'stop'):
                    audio_sound.stop()
                if hasattr(audio_sound, 'play'):
                    audio_sound.play()
            except Exception:
                pass
        
        # 4. 创建简单的控制栏 (可选，这里先做个占位符)
        # TODO: Play/Pause controls
        
        comp_data = {
            'type': 'Video',
            'object': obj,
            'sprite': spr,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"Video_{len(manager.components)}",
            'video_path': video_path,
            'texture': video_texture,
            'audio': audio_sound,
            'audio_path': audio_path,
            'audio_from_video': audio_from_video,
            'volume': 1.0,
            'is_playing': True,
            'loop': True,
            'sort': 1
        }
        
        # 设置拖拽功能
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        
        # 添加到层级树
        manager._add_to_scene_tree(comp_data)
        
        manager.components.append(comp_data)
        return obj

    def create_progressbar(manager, value=50, x=100, y=100, width=200, parent=None):
        """Create a LUI Progressbar"""
        if not manager.lui_enabled: return None
        
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        pg = LUIProgressbar(parent=parent, width=width, value=value)
        pg.left = x
        pg.top = y
        pg.solid = True # Ensure it receives input
        
        if hasattr(pg, 'set_sort'):
            pg.set_sort(1)

        comp_data = {
            'type': 'Progressbar',
            'object': pg,
            'left': x,
            'top': y,
            'width': width,
            'height': 30, # default height
            'canvas_index': manager.current_canvas_index,
            'name': f"Progressbar_{len(manager.components)}",
            'value': value,
            'sort': 1
        }
        
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        manager._add_to_scene_tree(comp_data)
        manager.components.append(comp_data)
        return pg

    def create_selectbox(manager, options=None, x=100, y=100, width=200, parent=None):
        """Create a LUI Selectbox"""
        if not manager.lui_enabled: return None
        
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        if options is None:
            options = [(0, "Option 1"), (1, "Option 2"), (2, "Option 3")]
            
        sb = LUISelectbox(width=width, options=options, selected_option=0)
        sb.parent = parent
        sb.left = x
        sb.top = y
        sb.solid = True # Ensure it receives input
        
        if hasattr(sb, 'set_sort'):
            sb.set_sort(1)

        comp_data = {
            'type': 'Selectbox',
            'object': sb,
            'left': x,
            'top': y,
            'width': width,
            'height': 30, # default height
            'canvas_index': manager.current_canvas_index,
            'name': f"Selectbox_{len(manager.components)}",
            'options': options,
            'sort': 1
        }
        
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        manager._add_to_scene_tree(comp_data)
        manager.components.append(comp_data)
        return sb
        
    def create_scrollable_region(manager, x=100, y=100, width=200, height=200, parent=None):
        """Create a LUI Scrollable Region"""
        if not manager.lui_enabled: return None
        
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        sr = LUIScrollableRegion(parent=parent, width=width, height=height)
        sr.left = x
        sr.top = y
        sr.solid = True # Ensure it receives input
        
        if hasattr(sr, 'set_sort'):
            sr.set_sort(1)

        comp_data = {
            'type': 'ScrollableRegion',
            'object': sr,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"ScrollableRegion_{len(manager.components)}",
            'sort': 1
        }
        
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        manager._add_to_scene_tree(comp_data)
        manager.components.append(comp_data)
        return sr
        
    def create_tabbed_frame(manager, x=100, y=100, width=300, height=200, parent=None):
        """Create a LUI Tabbed Frame"""
        if not manager.lui_enabled: return None
        
        if parent is None and manager.current_canvas_index >= 0:
            parent = manager.canvases[manager.current_canvas_index]['panel']
        elif parent is None:
            parent = manager.overlay_root
            
        tf = LUITabbedFrame(parent=parent, width=width, height=height)
        tf.left = x
        tf.top = y
        tf.solid = True # Ensure it receives input
        
        # Add default tabs
        f1 = LUIFrame(width=width, height=height)
        tf.add("Tab 1", f1)
        f2 = LUIFrame(width=width, height=height)
        tf.add("Tab 2", f2)
        
        if hasattr(tf, 'set_sort'):
            tf.set_sort(1)

        comp_data = {
            'type': 'TabbedFrame',
            'object': tf,
            'left': x,
            'top': y,
            'width': width,
            'height': height,
            'canvas_index': manager.current_canvas_index,
            'name': f"TabbedFrame_{len(manager.components)}",
            'sort': 1
        }
        
        comp_index = len(manager.components)
        manager._setup_component_drag(comp_data, comp_index)
        manager._add_to_scene_tree(comp_data)
        manager.components.append(comp_data)
        return tf

    @staticmethod
    def delete_component(manager, index):
        """Delete a LUI component and update all references"""
        if index < 0 or index >= len(manager.components):
            return

        # 1. Identify all components to delete (recursive)
        to_delete = {index}
        queue = list(manager.components[index].get('children_indices', []))
        while queue:
            curr = queue.pop(0)
            if curr not in to_delete:
                to_delete.add(curr)
                if curr < len(manager.components):
                    queue.extend(manager.components[curr].get('children_indices', []))

        # Cleanup any cached outlines for deleted components
        if hasattr(manager, 'debug_outlines') and isinstance(manager.debug_outlines, dict):
            for del_idx in list(to_delete):
                borders = manager.debug_outlines.pop(del_idx, None)
                if borders:
                    for b in borders:
                        try:
                            if hasattr(b, 'remove'):
                                b.remove()
                            else:
                                b.visible = False
                        except Exception:
                            pass
        
        # Sort indices descending to delete from end first
        sorted_indices = sorted(list(to_delete), reverse=True)
        
        # Clear selection if affected
        if manager.selected_index in to_delete:
            manager.selected_index = -1
            if hasattr(manager, '_hide_resize_handles'):
                manager._hide_resize_handles()
        
        # Process deletions
        for i in sorted_indices:
            if i >= len(manager.components): continue
            
            deleted_comp = manager.components.pop(i)
            
            # Cleanup physical objects
            if 'object' in deleted_comp:
                obj = deleted_comp['object']
                if hasattr(obj, 'parent') and obj.parent:
                    if hasattr(obj.parent, 'remove_child'):
                        obj.parent.remove_child(obj)
            # Cleanup audio if any
            audio = deleted_comp.get("audio")
            if audio:
                try:
                    if hasattr(audio, "stop"): audio.stop()
                except Exception:
                    pass
            
            # Cleanup associated node handles if any
            if hasattr(manager, 'resize_handles') and manager.resizing_handle and getattr(manager.resizing_handle, '_resize_index', -1) == i:
                 manager.resizing_handle = None
            
            print(f"✓ Deleted LUI component: {deleted_comp.get('name', 'Unknown')}")
            
            # Shift indices > i down by 1
            if manager.selected_index > i:
                 manager.selected_index -= 1
                 
            for c in manager.components:
                # Update parent_index
                p_idx = c.get('parent_index')
                if p_idx is not None and p_idx > i:
                    c['parent_index'] = p_idx - 1
                elif p_idx == i:
                    c['parent_index'] = -1
                    
                # Update children_indices
                if 'children_indices' in c:
                    new_children = []
                    for child_idx in c['children_indices']:
                        if child_idx == i: continue
                        elif child_idx > i: new_children.append(child_idx - 1)
                        else: new_children.append(child_idx)
                    c['children_indices'] = new_children

        # Reindex remaining outlines after component indices shift
        if hasattr(manager, 'debug_outlines') and isinstance(manager.debug_outlines, dict) and manager.debug_outlines:
            deleted_sorted = sorted(to_delete)
            new_outlines = {}
            for old_idx, borders in manager.debug_outlines.items():
                shift = 0
                for d in deleted_sorted:
                    if d < old_idx:
                        shift += 1
                    else:
                        break
                new_outlines[old_idx - shift] = borders
            manager.debug_outlines = new_outlines

    def _load_ui_texture(manager, file_path, name_hint="", max_size=1024):
        """Load image as a standalone Panda3D Texture (avoid LUI atlas)."""
        try:
            panda_path = p3d.Filename.from_os_specific(file_path)
            pnm = p3d.PNMImage()
            if not pnm.read(panda_path):
                print(f"⚠ Failed to read image: {file_path}")
                return None

            # Ensure alpha exists to avoid unexpected black/transparent results.
            if not pnm.hasAlpha():
                pnm.addAlpha()
                pnm.alphaFill(1.0)

            orig_x = pnm.getXSize()
            orig_y = pnm.getYSize()

            # Power-of-two resize for better compatibility and to avoid atlas issues.
            def next_pow2(v):
                v = max(1, int(v))
                return 1 << (v - 1).bit_length()

            pot_x = min(next_pow2(orig_x), max_size)
            pot_y = min(next_pow2(orig_y), max_size)

            if pot_x != orig_x or pot_y != orig_y:
                pnm_scaled = p3d.PNMImage(pot_x, pot_y, pnm.getNumChannels())
                pnm_scaled.quickFilterFrom(pnm)
                pnm = pnm_scaled
                print(f"⚠ Resized to POT: {orig_x}x{orig_y} -> {pot_x}x{pot_y}")

            tex = p3d.Texture()
            safe_name = f"{name_hint}_{os.path.basename(file_path)}" if name_hint else os.path.basename(file_path)
            tex.setName(safe_name)
            tex.load(pnm)
            tex.setKeepRamImage(True)
            tex.setMinfilter(p3d.Texture.FT_linear)
            tex.setMagfilter(p3d.Texture.FT_linear)
            tex.setWrapU(p3d.Texture.WM_clamp)
            tex.setWrapV(p3d.Texture.WM_clamp)
            tex.setCompression(p3d.Texture.CM_off)
            tex.setQualityLevel(p3d.Texture.QL_best)

            return tex
        except Exception as e:
            print(f"⚠ Texture load failed: {e}")
            try:
                panda_path = p3d.Filename.from_os_specific(file_path)
                return manager.world.loader.loadTexture(panda_path)
            except Exception as e2:
                print(f"⚠ Texture fallback load failed: {e2}")
                return None

    def _get_image_atlas_page(manager, size=2048):
        """Get or create an atlas page for UI images."""
        if not hasattr(manager, "_image_atlas_pages"):
            manager._image_atlas_pages = []

        # Try existing pages first
        for page in manager._image_atlas_pages:
            if page.get("size") == size and not page.get("full", False):
                return page

        # Create a new page
        pnm = p3d.PNMImage(size, size, 4)
        pnm.fill(0, 0, 0)
        pnm.alphaFill(0.0)

        tex = p3d.Texture()
        tex.setName(f"ui_atlas_{len(manager._image_atlas_pages)}_{size}")
        tex.load(pnm)
        tex.setKeepRamImage(True)
        tex.setMinfilter(p3d.Texture.FT_linear)
        tex.setMagfilter(p3d.Texture.FT_linear)
        tex.setWrapU(p3d.Texture.WM_clamp)
        tex.setWrapV(p3d.Texture.WM_clamp)
        tex.setCompression(p3d.Texture.CM_off)
        tex.setQualityLevel(p3d.Texture.QL_best)

        page = {
            "size": size,
            "pnm": pnm,
            "tex": tex,
            "cursor_x": 0,
            "cursor_y": 0,
            "row_h": 0,
            "full": False,
        }
        manager._image_atlas_pages.append(page)
        return page

    def _add_image_to_atlas(manager, file_path, atlas_size=2048):
        """Add image to atlas and return (tex, u0, v0, u1, v1, w, h, orig_w, orig_h)."""
        try:
            panda_path = p3d.Filename.from_os_specific(file_path)
            src = p3d.PNMImage()
            if not src.read(panda_path):
                print(f"⚠ Failed to read image: {file_path}")
                return None

            if not src.hasAlpha():
                src.addAlpha()
                src.alphaFill(1.0)

            w = src.getXSize()
            h = src.getYSize()

            orig_w = w
            orig_h = h

            # Downscale if too large for atlas
            if w > atlas_size or h > atlas_size:
                scale = min(atlas_size / max(1, w), atlas_size / max(1, h))
                new_w = max(1, int(w * scale))
                new_h = max(1, int(h * scale))
                scaled = p3d.PNMImage(new_w, new_h, src.getNumChannels())
                scaled.quickFilterFrom(src)
                src = scaled
                w, h = new_w, new_h
                print(f"⚠ Downscaled for atlas: {file_path} -> {w}x{h}")

            page = manager.luiFunction._get_image_atlas_page(manager, size=atlas_size)

            # Shelf packing
            if page["cursor_x"] + w > atlas_size:
                page["cursor_x"] = 0
                page["cursor_y"] += page["row_h"]
                page["row_h"] = 0

            if page["cursor_y"] + h > atlas_size:
                page["full"] = True
                # Create a new page and try again
                page = manager.luiFunction._get_image_atlas_page(manager, size=atlas_size)
                if page["cursor_x"] + w > atlas_size or page["cursor_y"] + h > atlas_size:
                    print(f"⚠ Atlas full for image: {file_path}")
                    return None

            x = page["cursor_x"]
            y = page["cursor_y"]

            page["pnm"].copySubImage(src, x, y, 0, 0, w, h)
            page["tex"].load(page["pnm"])

            page["cursor_x"] += w
            page["row_h"] = max(page["row_h"], h)

            size = float(atlas_size)
            u0 = x / size
            v0 = y / size
            u1 = (x + w) / size
            v1 = (y + h) / size

            return (page["tex"], u0, v0, u1, v1, w, h, orig_w, orig_h)
        except Exception as e:
            print(f"⚠ Atlas insert failed: {e}")
            return None

    def _draw_component_properties(manager, index):
        """绘制组件属性编辑面板"""
        if index < 0 or index >= len(manager.components):
            return
            
        comp_data = manager.components[index]
        comp_obj = comp_data['object']
        comp_type = comp_data['type']
        widget = comp_data.get('widget',comp_obj)
        
        # 文本属性
        # Text content
        # Text content
        if comp_type in ['Button', 'Text', 'Label']:
            imgui.text("Text Content")
            imgui.separator()
            if 'text' in comp_data:
                imgui.push_item_width(-1)
                changed, new_text = imgui.input_text("##text_input", comp_data['text'], 256)
                imgui.pop_item_width()
                if changed:
                    comp_data['text'] = new_text
                    comp_obj.text = new_text
                    print(f"Text updated: {new_text}")

            if comp_type in ['Text', 'Label']:
                imgui.spacing()
                imgui.text("Font Size")
                font_size = int(comp_data.get('font_size', 14))
                changed, new_size = imgui.slider_int("##font_size", font_size, 8, 96)
                if changed:
                    comp_data['font_size'] = int(new_size)
                    try:
                        if hasattr(comp_obj, '_text') and hasattr(comp_obj._text, 'font_size'):
                            comp_obj._text.font_size = int(new_size)
                        if hasattr(comp_obj, '_shadow_text') and hasattr(comp_obj._shadow_text, 'font_size'):
                            comp_obj._shadow_text.font_size = int(new_size)
                        if hasattr(comp_obj, 'text_handle') and hasattr(comp_obj.text_handle, 'font_size'):
                            comp_obj.text_handle.font_size = int(new_size)
                    except Exception as e:
                        print(f"Font size update failed: {e}")

            imgui.spacing()
            imgui.text("Color")
            color = comp_data.get('color', (1.0, 1.0, 1.0, 1.0))
            color = list(color) if isinstance(color, tuple) else color
            changed, new_color = imgui.color_edit4("##color_edit", color)
            if changed:
                comp_data['color'] = tuple(new_color)
                if comp_type == 'Button':
                    if hasattr(comp_obj, '_layout'):
                        try:
                            if hasattr(comp_obj._layout, '_sprite_left'):
                                comp_obj._layout._sprite_left.color = tuple(new_color)
                            if hasattr(comp_obj._layout, '_sprite_mid'):
                                comp_obj._layout._sprite_mid.color = tuple(new_color)
                            if hasattr(comp_obj._layout, '_sprite_right'):
                                comp_obj._layout._sprite_right.color = tuple(new_color)
                        except Exception as e:
                            print(f"Button color set failed: {e}")
                else:
                    if hasattr(comp_obj, '_text'):
                        comp_obj._text.color = tuple(new_color)
            imgui.separator()

            if comp_type == 'Button':
                if hasattr(comp_obj, '_layout'):
                    try:
                        if hasattr(comp_obj._layout, '_sprite_left'):
                            comp_obj._layout._sprite_left.color = tuple(new_color)
                        if hasattr(comp_obj._layout, '_sprite_mid'):
                            comp_obj._layout._sprite_mid.color = tuple(new_color)
                        if hasattr(comp_obj._layout, '_sprite_right'):
                            comp_obj._layout._sprite_right.color = tuple(new_color)
                    except Exception as e:
                        print(f"\u8bbe\u7f6e\u6309\u94ae\u989c\u8272\u5931\u8d25: {e}")
            else:
                if hasattr(comp_obj, '_text'):
                    comp_obj._text.color = tuple(new_color)
            imgui.separator()
            imgui.spacing()
            imgui.text("\u989c\u8272")
            color = comp_data.get('color', (1.0, 1.0, 1.0, 1.0))


        if comp_type == 'Button':
            imgui.text("\u6309\u94ae\u56fe\u7247")

            def _apply_button_textures():
                normal_tex = comp_data.get('button_texture_ref_normal') or comp_data.get('button_texture_ref')
                normal_uv = comp_data.get('button_atlas_uv_normal') or comp_data.get('button_atlas_uv')
                hover_tex = comp_data.get('button_texture_ref_hover')
                hover_uv = comp_data.get('button_atlas_uv_hover')
                pressed_tex = comp_data.get('button_texture_ref_pressed')
                pressed_uv = comp_data.get('button_atlas_uv_pressed')
                if hasattr(comp_obj, 'set_custom_textures') and normal_tex is not None:
                    comp_obj.set_custom_textures(normal_tex, normal_uv, hover_tex, hover_uv, pressed_tex, pressed_uv)
                elif hasattr(comp_obj, 'set_custom_texture') and normal_tex is not None:
                    comp_obj.set_custom_texture(normal_tex, normal_uv)

            if imgui.button("\u66f4\u6539\u9ed8\u8ba4\u56fe##button_img_norm", (160, 20)):
                selected_path = manager._change_image_texture(
                    title = "\u9009\u62e9\u56fe\u7247\u6587\u4ef6",
                    filetypes = [
                        ("\u56fe\u7247\u6587\u4ef6", "*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tga"),
                        ("PNG", "*.png"),
                        ("JPEG", "*.jpg;*.jpeg"),
                        ("\u6240\u6709\u6587\u4ef6", "*.*")
                    ]
                )
                if selected_path:
                    try:
                        atlas_result = manager.luiFunction._add_image_to_atlas(manager, selected_path, atlas_size=2048)
                        if atlas_result:
                            if len(atlas_result) >= 7:
                                tex, u0, v0, u1, v1, w, h = atlas_result[:7]
                            else:
                                tex, u0, v0, u1, v1 = atlas_result
                                w = int(max(1, round((u1 - u0) * tex.getXSize())))
                                h = int(max(1, round((v1 - v0) * tex.getYSize())))
                            comp_data['button_texture_path'] = selected_path
                            comp_data['button_texture_path_normal'] = selected_path
                            comp_data['button_atlas_uv'] = (u0, v0, u1, v1)
                            comp_data['button_atlas_uv_normal'] = (u0, v0, u1, v1)
                            comp_data['button_atlas_tex'] = tex
                            comp_data['button_texture_ref'] = tex
                            comp_data['button_texture_ref_normal'] = tex
                            comp_data['button_image_size'] = (int(w), int(h))
                            comp_data['button_image_size_normal'] = (int(w), int(h))
                            _apply_button_textures()
                    except Exception as e:
                        print(f"Button texture set failed: {e}")

            if imgui.button("\u66f4\u6539\u60ac\u505c\u56fe##button_img_hover", (160, 20)):
                selected_path = manager._change_image_texture(
                    title = "\u9009\u62e9\u56fe\u7247\u6587\u4ef6",
                    filetypes = [
                        ("\u56fe\u7247\u6587\u4ef6", "*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tga"),
                        ("PNG", "*.png"),
                        ("JPEG", "*.jpg;*.jpeg"),
                        ("\u6240\u6709\u6587\u4ef6", "*.*")
                    ]
                )
                if selected_path:
                    try:
                        atlas_result = manager.luiFunction._add_image_to_atlas(manager, selected_path, atlas_size=2048)
                        if atlas_result:
                            if len(atlas_result) >= 7:
                                tex, u0, v0, u1, v1, w, h = atlas_result[:7]
                            else:
                                tex, u0, v0, u1, v1 = atlas_result
                                w = int(max(1, round((u1 - u0) * tex.getXSize())))
                                h = int(max(1, round((v1 - v0) * tex.getYSize())))
                            comp_data['button_texture_path_hover'] = selected_path
                            comp_data['button_atlas_uv_hover'] = (u0, v0, u1, v1)
                            comp_data['button_texture_ref_hover'] = tex
                            comp_data['button_image_size_hover'] = (int(w), int(h))
                            _apply_button_textures()
                    except Exception as e:
                        print(f"Button hover texture set failed: {e}")

            if imgui.button("\u66f4\u6539\u6309\u4e0b\u56fe##button_img_pressed", (160, 20)):
                selected_path = manager._change_image_texture(
                    title = "\u9009\u62e9\u56fe\u7247\u6587\u4ef6",
                    filetypes = [
                        ("\u56fe\u7247\u6587\u4ef6", "*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tga"),
                        ("PNG", "*.png"),
                        ("JPEG", "*.jpg;*.jpeg"),
                        ("\u6240\u6709\u6587\u4ef6", "*.*")
                    ]
                )
                if selected_path:
                    try:
                        atlas_result = manager.luiFunction._add_image_to_atlas(manager, selected_path, atlas_size=2048)
                        if atlas_result:
                            if len(atlas_result) >= 7:
                                tex, u0, v0, u1, v1, w, h = atlas_result[:7]
                            else:
                                tex, u0, v0, u1, v1 = atlas_result
                                w = int(max(1, round((u1 - u0) * tex.getXSize())))
                                h = int(max(1, round((v1 - v0) * tex.getYSize())))
                            comp_data['button_texture_path_pressed'] = selected_path
                            comp_data['button_atlas_uv_pressed'] = (u0, v0, u1, v1)
                            comp_data['button_texture_ref_pressed'] = tex
                            comp_data['button_image_size_pressed'] = (int(w), int(h))
                            _apply_button_textures()
                    except Exception as e:
                        print(f"Button pressed texture set failed: {e}")

            if imgui.button("\u4f7f\u7528\u9ed8\u8ba4\u56fe\u5c3a\u5bf8##button_fit", (160, 20)):
                w = h = None
                if 'button_image_size_normal' in comp_data and comp_data['button_image_size_normal']:
                    try:
                        w, h = comp_data['button_image_size_normal']
                    except Exception:
                        w = h = None
                elif 'button_image_size' in comp_data and comp_data['button_image_size']:
                    try:
                        w, h = comp_data['button_image_size']
                    except Exception:
                        w = h = None
                if w is not None and h is not None and w > 0 and h > 0:
                    comp_data['width'] = float(w)
                    comp_data['height'] = float(h)
                    comp_obj.width = float(w)
                    comp_obj.height = float(h)
                    if hasattr(comp_obj, '_apply_stretch_sizes'):
                        comp_obj._apply_stretch_sizes()

            if imgui.button("\u6062\u590d\u9ed8\u8ba4\u6309\u94ae##button_default", (160, 20)):
                if hasattr(comp_obj, 'clear_custom_texture'):
                    comp_obj.clear_custom_texture()
                for key in [
                    'button_texture_path', 'button_texture_path_normal', 'button_texture_path_hover',
                    'button_texture_path_pressed', 'button_texture_ref', 'button_texture_ref_normal',
                    'button_texture_ref_hover', 'button_texture_ref_pressed', 'button_atlas_uv',
                    'button_atlas_uv_normal', 'button_atlas_uv_hover', 'button_atlas_uv_pressed',
                    'button_image_size', 'button_image_size_normal', 'button_image_size_hover', 'button_image_size_pressed'
                ]:
                    if key in comp_data:
                        comp_data[key] = None

            if comp_data.get('button_texture_path_normal'):
                imgui.text(f"\u9ed8\u8ba4\u56fe: {comp_data['button_texture_path_normal']}")
            if comp_data.get('button_texture_path_hover'):
                imgui.text(f"\u60ac\u505c\u56fe: {comp_data['button_texture_path_hover']}")
            if comp_data.get('button_texture_path_pressed'):
                imgui.text(f"\u6309\u4e0b\u56fe: {comp_data['button_texture_path_pressed']}")

        # 布局模式
        layout_mode = comp_data.get('layout_mode', 'manual')
        is_fill = (layout_mode == 'fill')
        changed, new_fill = imgui.checkbox("填充父级", is_fill)
        if changed:
            comp_data['layout_mode'] = 'fill' if new_fill else 'manual'
            if new_fill:
                # Fill overrides anchor positioning
                comp_data['anchored_to_parent'] = False
                comp_data.setdefault('fill_margin_left', 0.0)
                comp_data.setdefault('fill_margin_right', 0.0)
                comp_data.setdefault('fill_margin_top', 0.0)
                comp_data.setdefault('fill_margin_bottom', 0.0)
                if hasattr(manager, '_apply_fill_layout'):
                    manager._apply_fill_layout(index)
        is_fill = (comp_data.get('layout_mode') == 'fill')
        if is_fill:
            imgui.text_disabled("填充模式下，位置/尺寸由父级决定")
            imgui.text("边距")
            m_left = float(comp_data.get('fill_margin_left', 0.0))
            m_right = float(comp_data.get('fill_margin_right', 0.0))
            m_top = float(comp_data.get('fill_margin_top', 0.0))
            m_bottom = float(comp_data.get('fill_margin_bottom', 0.0))
            changed_l, new_l = imgui.input_float("左", m_left, 1.0, 10.0, "%.1f")
            changed_r, new_r = imgui.input_float("右", m_right, 1.0, 10.0, "%.1f")
            changed_t, new_t = imgui.input_float("上", m_top, 1.0, 10.0, "%.1f")
            changed_b, new_b = imgui.input_float("下", m_bottom, 1.0, 10.0, "%.1f")
            if changed_l or changed_r or changed_t or changed_b:
                comp_data['fill_margin_left'] = new_l
                comp_data['fill_margin_right'] = new_r
                comp_data['fill_margin_top'] = new_t
                comp_data['fill_margin_bottom'] = new_b
                if hasattr(manager, '_apply_fill_layout'):
                    manager._apply_fill_layout(index)
        imgui.separator()

        # 位置属性
        imgui.text("位置")
        if is_fill:
            imgui.text(f"Left: {comp_data.get('left', 0.0):.1f}")
            imgui.text(f"Top: {comp_data.get('top', 0.0):.1f}")
        else:
            if 'left' in comp_data:
                changed, new_left = imgui.input_float("Left", comp_data['left'], 1.0, 10.0, "%.1f")
                if changed:
                    comp_data['left'] = new_left
                    comp_obj.left = new_left

            if 'top' in comp_data:
                changed, new_top = imgui.input_float("Top", comp_data['top'], 1.0, 10.0, "%.1f")
                if changed:
                    comp_data['top'] = new_top
                    comp_obj.top = new_top

        # 尺寸属性
        imgui.spacing()
        imgui.text("尺寸")
        if is_fill:
            imgui.text(f"Width: {comp_data.get('width', 0.0):.1f}")
            imgui.text(f"Height: {comp_data.get('height', 0.0):.1f}")
        else:
            width_changed = False
            height_changed = False
            if 'width' in comp_data:
                changed, new_width = imgui.input_float("Width", comp_data['width'], 1.0, 10.0, "%.1f")
                if changed and new_width > 0:
                    comp_data['width'] = new_width
                    width_changed = True
                    if hasattr(comp_obj, 'width'):
                        comp_obj.width = new_width
                    # 同步更新内部 Sprite (针对 Image/Plane/Video)
                    if 'sprite' in comp_data:
                        comp_data['sprite'].width = new_width
                    if comp_type == 'Button' and hasattr(comp_obj, '_apply_stretch_sizes'):
                        comp_obj._apply_stretch_sizes()
                    if comp_type == 'Slider' and hasattr(comp_obj, 'set_height'):
                        comp_obj.set_height(new_height)
                    if comp_type == 'InputField' and hasattr(comp_obj, 'set_height'):
                        comp_obj.set_height(new_height)
                    if comp_type == 'Slider' and hasattr(comp_obj, 'set_width'):
                        comp_obj.set_width(new_width)
                    if comp_type == 'InputField' and hasattr(comp_obj, 'set_width'):
                        comp_obj.set_width(new_width)

            if 'height' in comp_data:
                changed, new_height = imgui.input_float("Height", comp_data['height'], 1.0, 10.0, "%.1f")
                if changed and new_height > 0:
                    comp_data['height'] = new_height
                    height_changed = True
                    if hasattr(comp_obj, 'height'):
                        comp_obj.height = new_height
                    # 同步更新内部 Sprite (针对 Image/Plane/Video)
                    if 'sprite' in comp_data:
                        comp_data['sprite'].height = new_height
                    if comp_type == 'Button' and hasattr(comp_obj, '_apply_stretch_sizes'):
                        comp_obj._apply_stretch_sizes()
                    if comp_type == 'Slider' and hasattr(comp_obj, 'set_width'):
                        comp_obj.set_width(new_width)
                    if comp_type == 'InputField' and hasattr(comp_obj, 'set_width'):
                        comp_obj.set_width(new_width)

            if (width_changed or height_changed) and hasattr(manager, '_update_anchored_children'):
                manager._update_anchored_children(index)


            if comp_type in ['VerticalLayout', 'HorizontalLayout'] and hasattr(manager, '_update_layout_inner'):
                manager._update_layout_inner(index)

        # Layout group properties
        if comp_type in ['VerticalLayout', 'HorizontalLayout']:
            imgui.spacing()
            imgui.text("Layout Group")

            spacing = float(comp_data.get('layout_spacing', 0.0))
            changed, new_spacing = imgui.input_float("Spacing", spacing, 1.0, 10.0, "%.1f")
            if changed:
                comp_data['layout_spacing'] = new_spacing
                if hasattr(manager, '_update_layout_inner'):
                    manager._update_layout_inner(index)

            imgui.text("Padding")
            pad_left = float(comp_data.get('layout_padding_left', 0.0))
            pad_right = float(comp_data.get('layout_padding_right', 0.0))
            pad_top = float(comp_data.get('layout_padding_top', 0.0))
            pad_bottom = float(comp_data.get('layout_padding_bottom', 0.0))

            changed_l, new_l = imgui.input_float("Pad Left", pad_left, 1.0, 10.0, "%.1f")
            changed_r, new_r = imgui.input_float("Pad Right", pad_right, 1.0, 10.0, "%.1f")
            changed_t, new_t = imgui.input_float("Pad Top", pad_top, 1.0, 10.0, "%.1f")
            changed_b, new_b = imgui.input_float("Pad Bottom", pad_bottom, 1.0, 10.0, "%.1f")
            if changed_l or changed_r or changed_t or changed_b:
                comp_data['layout_padding_left'] = new_l
                comp_data['layout_padding_right'] = new_r
                comp_data['layout_padding_top'] = new_t
                comp_data['layout_padding_bottom'] = new_b
                if hasattr(manager, '_update_layout_inner'):
                    manager._update_layout_inner(index)

            align_options = ["start", "center", "end", "stretch"]

            if comp_type in ['HorizontalLayout', 'VerticalLayout']:
                wrap_enabled = bool(comp_data.get('layout_wrap', True))
                changed, new_wrap = imgui.checkbox("Wrap", wrap_enabled)
                if changed:
                    comp_data['layout_wrap'] = new_wrap
                    if hasattr(manager, '_update_layout_inner'):
                        manager._update_layout_inner(index)

                line_spacing = float(comp_data.get('layout_line_spacing', 0.0))
                changed, new_line_spacing = imgui.input_float("Line Spacing (Wrap)", line_spacing, 1.0, 10.0, "%.1f")
                if changed:
                    comp_data['layout_line_spacing'] = new_line_spacing
                    if hasattr(manager, '_update_layout_inner'):
                        manager._update_layout_inner(index)

            current_align = comp_data.get('layout_align', 'start')
            if imgui.begin_combo("Align", current_align):
                for opt in align_options:
                    if imgui.selectable(opt, current_align == opt)[0]:
                        comp_data['layout_align'] = opt
                        if hasattr(manager, '_update_layout_inner'):
                            manager._update_layout_inner(index)
                imgui.end_combo()

        # --- 层级与显示顺序 (LUI 中组件层级由 Z-Offset 控制) ---
        imgui.spacing()
        imgui.text("层级与显示顺序")
        
        # 获取当前深度并确保其在 comp_data 中
        current_z = comp_data.get('z_offset')
        if current_z is None:
            # 兼容旧组件的 sort 属性，如果存在则作为初始 z_offset
            current_z = comp_data.get('sort', 0.0)
            if current_z == 0 and hasattr(comp_obj, 'z_offset'):
                 current_z = comp_obj.z_offset
            comp_data['z_offset'] = float(current_z)
            
        # 1. 整数层级设置 (方便快速调整)
        layer_val = int(current_z)
        changed_layer, new_layer = imgui.input_int("渲染层级 (Layer)", layer_val)
        if changed_layer:
            comp_data['z_offset'] = float(new_layer)
            if hasattr(comp_obj, 'set_z_offset'):
                comp_obj.set_z_offset(float(new_layer))
            print(f"✓ 组件 #{index} 层级已映射到 Z-Offset: {new_layer}")

        # 2. 深度微调 (Z-Offset 浮点数)
        changed_z, new_z = imgui.input_float("深度微调 (Z-Offset)", comp_data['z_offset'], 0.1, 1.0, "%.2f")
        if changed_z:
            comp_data['z_offset'] = new_z
            if hasattr(comp_obj, 'set_z_offset'):
                comp_obj.set_z_offset(new_z)
            elif hasattr(comp_obj, 'z_offset'):
                comp_obj.z_offset = new_z
            print(f"✓ 组件 #{index} Z-Offset 已微调为: {new_z}")
            
        imgui.text_disabled("(注: LUI 内部组件通过 Z-Offset 决定遮挡关系)")
        
        # 特定类型的属性
        if comp_type == 'InputField':
            imgui.spacing()
            imgui.text("输入框属性")

            imgui.text("输入框图片")
            if imgui.button("更改图片##input_img", (120, 20)):
                selected_path = manager._change_image_texture(
                    title = "选择图片文件",
                    filetypes = [
                        ("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tga"),
                        ("PNG", "*.png"),
                        ("JPEG", "*.jpg;*.jpeg"),
                        ("所有文件", "*.*")
                    ]
                )
                if selected_path:
                    try:
                        atlas_result = manager.luiFunction._add_image_to_atlas(manager, selected_path, atlas_size=2048)
                        if atlas_result:
                            if len(atlas_result) >= 7:
                                tex, u0, v0, u1, v1, w, h = atlas_result[:7]
                            else:
                                tex, u0, v0, u1, v1 = atlas_result
                                w = int(max(1, round((u1 - u0) * tex.getXSize())))
                                h = int(max(1, round((v1 - v0) * tex.getYSize())))
                            comp_data['input_texture_path'] = selected_path
                            comp_data['input_atlas_uv'] = (u0, v0, u1, v1)
                            comp_data['input_texture_ref'] = tex
                            comp_data['input_image_size'] = (int(w), int(h))
                            layout = getattr(comp_obj, '_layout', None)
                            if layout is not None:
                                for attr in ('_sprite_left', '_sprite_mid', '_sprite_right'):
                                    spr = getattr(layout, attr, None)
                                    if spr is not None and hasattr(spr, 'set_texture'):
                                        spr.set_texture(tex, resize=False)
                                        if hasattr(spr, 'set_uv_range'):
                                            spr.set_uv_range(u0, v0, u1, v1)
                    except Exception as e:
                        print(f"InputField texture set failed: {e}")

            if imgui.button("使用图片尺寸##input_fit", (120, 20)):
                w = h = None
                if 'input_image_size' in comp_data and comp_data['input_image_size']:
                    try:
                        w, h = comp_data['input_image_size']
                    except Exception:
                        w = h = None
                elif 'input_atlas_uv' in comp_data and 'input_texture_ref' in comp_data and comp_data['input_texture_ref']:
                    try:
                        u0, v0, u1, v1 = comp_data['input_atlas_uv']
                        tex = comp_data['input_texture_ref']
                        tw = tex.getXSize() if hasattr(tex, 'getXSize') else 0
                        th = tex.getYSize() if hasattr(tex, 'getYSize') else 0
                        w = int(max(1, round((u1 - u0) * tw)))
                        h = int(max(1, round((v1 - v0) * th)))
                    except Exception:
                        w = h = None
                if w is not None and h is not None and w > 0 and h > 0:
                    comp_data['width'] = float(w)
                    comp_data['height'] = float(h)
                    comp_obj.width = float(w)
                    comp_obj.height = float(h)
                    if hasattr(comp_obj, 'set_width'):
                        comp_obj.set_width(float(w))
                    if hasattr(comp_obj, 'set_height'):
                        comp_obj.set_height(float(h))
                    layout = getattr(comp_obj, '_layout', None)
                    if layout is not None:
                        if hasattr(layout, 'width'):
                            layout.width = "100%"
                        if hasattr(layout, 'height'):
                            layout.height = "100%"

            imgui.text("输入框颜色")
            in_color = comp_data.get('input_color', (1.0, 1.0, 1.0, 1.0))
            in_color = list(in_color) if isinstance(in_color, tuple) else in_color
            changed, new_in_color = imgui.color_edit4("##input_color", in_color)
            if changed:
                comp_data['input_color'] = tuple(new_in_color)
                layout = getattr(comp_obj, '_layout', None)
                if layout is not None:
                    for attr in ('_sprite_left', '_sprite_mid', '_sprite_right'):
                        spr = getattr(layout, attr, None)
                        if spr is not None:
                            spr.color = tuple(new_in_color)

            if 'value' in comp_data:
                changed, new_value = imgui.input_text("当前值", comp_data['value'], 256)
                if changed:
                    comp_data['value'] = new_value
                    comp_obj.value = new_value

        elif comp_type == 'Slider':
            imgui.spacing()
            imgui.text("滑块属性")
            if 'value' in comp_data:
                min_val = comp_data.get('min_value', 0.0)
                max_val = comp_data.get('max_value', 100.0)
                changed, new_value = imgui.slider_float("当前值", comp_data['value'], min_val, max_val, "%.1f")
                if changed:
                    comp_data['value'] = new_value
                    if hasattr(comp_obj, 'set_value'):
                        comp_obj.set_value(new_value)
        
        elif comp_type == 'Checkbox':
            imgui.spacing()
            imgui.text("复选框属性")
            if 'text' in comp_data:
                changed, new_text = imgui.input_text("标签文本", comp_data['text'], 256)
                if changed:
                    comp_data['text'] = new_text
                    # Checkbox uses a child label for text
                    if hasattr(comp_obj, 'label') and hasattr(comp_obj.label, 'text'):
                        comp_obj.label.text = new_text
                    elif hasattr(comp_obj, 'text'):
                        comp_obj.text = new_text
            
            if 'checked' in comp_data:
                changed, new_checked = imgui.checkbox("选中状态", comp_data['checked'])
                if changed:
                    comp_data['checked'] = new_checked
                    if hasattr(comp_obj, 'checked'):
                        comp_obj.checked = new_checked
        
        elif comp_type in ['Plane', 'Image']:
            imgui.spacing()
            imgui.text(f"{comp_type}属性")
            
            # 颜色属性
            if 'color' in comp_data:
                color = comp_data.get('color', (1.0, 1.0, 1.0, 1.0))
                color = list(color) if isinstance(color, tuple) else color
                changed, new_color = imgui.color_edit4("颜色", color)
                if changed:
                    comp_data['color'] = tuple(new_color)
                    # 更新sprite颜色
                    if 'sprite' in comp_data:
                        comp_data['sprite'].color = tuple(new_color)
            
            # 如果是Image，显示纹理路径
            if comp_type == 'Image' and 'texture_path' in comp_data:
                # Fit size to image texture
                if imgui.button("使用图片尺寸##fit_image"):
                    tex = None
                    # Prefer direct texture reference if available
                    if 'texture_ref' in comp_data and comp_data['texture_ref']:
                        tex = comp_data['texture_ref']
                    elif 'texture' in comp_data and comp_data['texture']:
                        tex = comp_data['texture']
                    else:
                        # Try to read from sprite python tag if present
                        try:
                            target = comp_data.get('sprite', comp_obj)
                            if target is not None and hasattr(target, 'get_python_tag'):
                                tex = target.get_python_tag('texture_ref')
                        except Exception:
                            tex = None

                    w = h = None
                    if 'image_size' in comp_data and comp_data['image_size']:
                        try:
                            w, h = comp_data['image_size']
                        except Exception:
                            w = h = None
                    elif 'atlas_uv' in comp_data and 'atlas_tex' in comp_data and comp_data['atlas_tex']:
                        try:
                            u0, v0, u1, v1 = comp_data['atlas_uv']
                            atlas_tex = comp_data['atlas_tex']
                            atlas_w = atlas_tex.getXSize() if hasattr(atlas_tex, 'getXSize') else 0
                            atlas_h = atlas_tex.getYSize() if hasattr(atlas_tex, 'getYSize') else 0
                            w = int(max(1, round((u1 - u0) * atlas_w)))
                            h = int(max(1, round((v1 - v0) * atlas_h)))
                        except Exception:
                            w = h = None
                    elif tex is not None:
                        if hasattr(tex, 'getOrigFileXSize') and hasattr(tex, 'getOrigFileYSize'):
                            ow = tex.getOrigFileXSize()
                            oh = tex.getOrigFileYSize()
                            if ow and oh:
                                w, h = ow, oh
                        if w is None or h is None:
                            if hasattr(tex, 'getXSize') and hasattr(tex, 'getYSize'):
                                w = tex.getXSize()
                                h = tex.getYSize()

                    if w is not None and h is not None and w > 0 and h > 0:
                        comp_data['width'] = float(w)
                        comp_data['height'] = float(h)
                        comp_obj.width = float(w)
                        comp_obj.height = float(h)
                        # Sync sprite size (Image/Plane/Video)
                        if 'sprite' in comp_data and comp_data['sprite']:
                            spr = comp_data['sprite']
                            if hasattr(spr, 'set_size'):
                                spr.set_size(float(w), float(h))
                            else:
                                spr.width = float(w)
                                spr.height = float(h)
                        if hasattr(manager, '_hide_resize_handles'):
                            manager._hide_resize_handles()
                    else:
                        print("Image size not available: missing texture")

                imgui.text(f"纹理路径: {comp_data['texture_path']}")
                if imgui.button("更改纹理", (100, 20)):
                    # 实现纹理更改功能
                    selected_path = manager._change_image_texture(
                        title = "选择图片文件",
                        filetypes = [
                            ("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif;*.tga"),
                            ("PNG", "*.png"),
                            ("JPEG", "*.jpg;*.jpeg"),
                            ("所有文件", "*.*")
                        ]
                    )
                    if selected_path:
                        new_path = selected_path
                        comp_data['texture_path'] = new_path
                        try:
                            atlas_result = manager.luiFunction._add_image_to_atlas(manager, new_path, atlas_size=2048)
                            if atlas_result:
                                if len(atlas_result) >= 7:
                                    tex, u0, v0, u1, v1, w, h = atlas_result[:7]
                                else:
                                    tex, u0, v0, u1, v1 = atlas_result
                                    w = int(max(1, round((u1 - u0) * tex.getXSize())))
                                    h = int(max(1, round((v1 - v0) * tex.getYSize())))
                                print(f"? Texture loaded: {tex.getName()}, Size: {tex.getXSize()}x{tex.getYSize()}")
                                comp_data["current_texture"] = os.path.basename(new_path)
                                comp_data["atlas_uv"] = (u0, v0, u1, v1)
                                comp_data["atlas_tex"] = tex
                                comp_data["image_size"] = (int(w), int(h))
                                old_spr = comp_data.get("sprite")
                                if old_spr:
                                    try:
                                        old_spr.parent = None
                                        if hasattr(old_spr, "set_texture"): old_spr.set_texture(None)
                                        if hasattr(old_spr, "destroy"): old_spr.destroy()
                                    except:
                                        pass
                                try:
                                    new_spr = LUISprite(comp_obj, tex)
                                    if hasattr(new_spr, "set_texture"):
                                        new_spr.set_texture(tex, resize=False)
                                    if hasattr(new_spr, "set_uv_range"):
                                        new_spr.set_uv_range(u0, v0, u1, v1)
                                    new_spr.width = comp_data.get("width", 100)
                                    new_spr.height = comp_data.get("height", 100)
                                    new_spr.left = 0
                                    new_spr.top = 0
                                    new_spr.z_offset = comp_data.get("z_offset", 0)
                                    new_spr.color = comp_data.get("color", (1,1,1,1))
                                    if hasattr(new_spr, "set_uv_range"):
                                        new_spr.set_uv_range(u0, v0, u1, v1)
                                    comp_data["sprite"] = new_spr
                                    comp_data["texture_ref"] = tex
                                    if not hasattr(manager, "texture_refs"):
                                        manager.texture_refs = []
                                    manager.texture_refs.append(tex)
                                    if hasattr(new_spr, "set_python_tag"):
                                        new_spr.set_python_tag("texture_ref", tex)
                                    if hasattr(comp_obj, "set_python_tag"):
                                        comp_obj.set_python_tag("texture_ref", tex)
                                    print(f"? Replaced sprite with atlas texture image: {tex.getName()}, Size: {tex.getXSize()}x{tex.getYSize()}")
                                except Exception as ex:
                                    print(f"? Failed to create new sprite: {ex}")
                                target = comp_data.get("widget", comp_data.get("object"))
                                if hasattr(target, "set_texture"):
                                    target.set_texture(tex)
                            else:
                                print(f"? Texture loading returned None for: {new_path}")
                        except Exception as e:
                            print(f"Image texture update failed: {e}")
                if 'sprite' in comp_data:
                    sprite = comp_data['sprite']
                    imgui.text(f"当前纹理: {comp_data.get('current_texture', 'blank')}")
        
        elif comp_type == 'Frame':
            imgui.spacing()
            imgui.text("框架属性")
            
            # 框架特有的颜色属性
            if 'color' in comp_data:
                color = comp_data.get('color', (0.7, 0.7, 0.7, 0.8))
                color = list(color) if isinstance(color, tuple) else color
                changed, new_color = imgui.color_edit4("背景颜色", color)
                if changed:
                    comp_data['color'] = tuple(new_color)
                    # 更新Frame颜色
                    if hasattr(comp_obj, 'set_color'):
                        comp_obj.set_color(tuple(new_color))
        
        elif comp_type == 'Selectbox':
            imgui.spacing()
            imgui.text("Selectbox Options")
            imgui.separator()

            options = comp_data.get('options', []) or []
            # normalize to list of (id,label)
            normalized = []
            for i, opt in enumerate(options):
                try:
                    opt_id, opt_label = opt
                except Exception:
                    opt_id, opt_label = i, str(opt)
                normalized.append((opt_id, str(opt_label)))
            options = normalized
            comp_data['options'] = options

            current_selected = comp_data.get('selected_option_id')
            if hasattr(comp_obj, 'get_selected_option'):
                try:
                    current_selected = comp_obj.get_selected_option()
                except Exception:
                    pass

            opt_labels = [label for _, label in options] if options else ["(empty)"]
            opt_ids = [oid for oid, _ in options] if options else [None]
            try:
                current_index = opt_ids.index(current_selected)
            except Exception:
                current_index = 0

            changed_sel, new_index = imgui.combo("Selected", current_index, opt_labels)
            if changed_sel and options:
                sel_id = opt_ids[new_index]
                comp_data['selected_option_id'] = sel_id
                try:
                    if hasattr(comp_obj, '_select_option'):
                        comp_obj._select_option(sel_id)
                except Exception:
                    pass

            imgui.spacing()
            imgui.text("Edit Options")
            imgui.separator()

            new_labels = []
            dirty = False
            for i, (opt_id, opt_label) in enumerate(options):
                imgui.push_item_width(-40)
                changed, new_label = imgui.input_text(f"##opt_label_{i}", opt_label, 128)
                imgui.pop_item_width()
                if changed:
                    opt_label = new_label
                    dirty = True
                imgui.same_line()
                if imgui.button(f"Delete##opt_del_{i}", (60, 20)):
                    dirty = True
                    continue
                new_labels.append(opt_label)

            if imgui.button("Add Option", (100, 24)):
                new_labels.append(f"Option {len(new_labels)+1}")
                dirty = True

            if dirty:
                # rebuild options with sequential ids to avoid duplicates
                options = [(i, label) for i, label in enumerate(new_labels)]
                comp_data['options'] = options
                # keep selection if possible
                if options:
                    if current_selected not in [oid for oid, _ in options]:
                        current_selected = options[0][0]
                    comp_data['selected_option_id'] = current_selected
                else:
                    comp_data['selected_option_id'] = None
                try:
                    if hasattr(comp_obj, 'set_options'):
                        comp_obj.set_options(options)
                    elif hasattr(comp_obj, 'options'):
                        comp_obj.options = options
                    if options and hasattr(comp_obj, '_select_option'):
                        comp_obj._select_option(comp_data['selected_option_id'])
                except Exception as e:
                    print(f"Selectbox options update failed: {e}")

        elif comp_type == 'Video':
            imgui.spacing()
            imgui.text("视频属性")
            imgui.separator()
            # Audio
            imgui.spacing()
            imgui.text("Audio")
            imgui.separator()
            current_audio = comp_data.get('audio_path', '')
            audio_display = os.path.basename(current_audio) if current_audio else ''
            imgui.text(f"Current Audio: {audio_display if audio_display else 'None'}")
            if imgui.button("Select Audio", (110, 24)):
                selected_audio = manager._change_image_texture(
                    title = "Select Audio File",
                    filetypes = [("Audio", "*.mp3;*.wav;*.ogg;*.flac;*.m4a"), ("All Files", "*.*")]
                )
                if selected_audio:
                    try:
                        audio_sound = manager.world.loader.loadSfx(p3d.Filename.from_os_specific(selected_audio))
                        if audio_sound:
                            comp_data["audio"] = audio_sound
                            comp_data["audio_path"] = selected_audio
                            comp_data["audio_from_video"] = False
                            if hasattr(audio_sound, "setLoop"):
                                audio_sound.setLoop(comp_data.get("loop", True))
                            if hasattr(audio_sound, "setVolume"):
                                audio_sound.setVolume(comp_data.get("volume", 1.0))
                            if comp_data.get("is_playing") and hasattr(audio_sound, "play"):
                                audio_sound.play()
                    except Exception as e:
                        print(f"Audio load failed: {e}")
            # Volume
            vol = comp_data.get("volume", 1.0)
            changed, new_vol = imgui.slider_float("Volume", vol, 0.0, 1.0, "%.2f")
            if changed:
                comp_data["volume"] = new_vol
                audio_sound = comp_data.get("audio")
                if audio_sound and hasattr(audio_sound, "setVolume"):
                    audio_sound.setVolume(new_vol)

            
            # 视频源显示与更改 (支持本地文件和 URL 流)
            if 'video_path' in comp_data:
                current_source = comp_data.get('video_path', '')
                is_url = '://' in current_source
                
                # 顶部状态显示
                source_display = os.path.basename(current_source) if (current_source and not is_url) else current_source
                imgui.text(f"当前源: {source_display if source_display else '未设置'}")
                
                # -- 方式 1: 本地文件 --
                if imgui.button("选择本地视频", (110, 24)):
                    selected_path = manager._change_image_texture(
                        title = "选择视频文件",
                        filetypes = [
                            ("视频文件", "*.mp4;*.avi;*.mov;*.mkv;*.webm;*.ogv"),
                            ("所有文件", "*.*")
                        ]
                    )
                    if selected_path:
                        comp_data['_pending_video_source'] = selected_path
                
                # imgui.spacing()
                # imgui.text("视频URL")
                # imgui.separator()
                
                # # -- 方式 2: URL / Stream --
                # # 使用缓存的临时输入，避免每帧刷新导致光标丢失
                # url_input_key = f"video_url_input_{index}"
                # if url_input_key not in comp_data:
                #     comp_data[url_input_key] = current_source if is_url else ""
                
                # changed, new_url = imgui.input_text("##vurl", comp_data[url_input_key], 1024)
                # if changed:
                #     comp_data[url_input_key] = new_url
                
                # imgui.same_line()
                # if imgui.button("加载 URL", (80, 24)):
                #     comp_data['_pending_video_source'] = comp_data[url_input_key]
                
                # 统一执行加载逻辑
                if '_pending_video_source' in comp_data:
                    selected_path = comp_data.pop('_pending_video_source')
                    if selected_path:
                        comp_data['video_path'] = selected_path
                        # 重置时长以强制新视频重新探测
                        comp_data['duration'] = 0.0
                        print(f"✓ 尝试加载视频源: {selected_path}")
                        
                        try:
                            # 重新加载视频纹理
                            from panda3d.core import Filename, MovieTexture, MovieVideo, loadPrcFileData
                            
                            # Trim trailing spaces which are visible in error logs
                            selected_path = selected_path.strip()
                            
                            # Whitelist protocols for FFmpeg
                            loadPrcFileData("", "ffmpeg-protocol-whitelist http,https,tcp,tls,file")
                            loadPrcFileData("", "ffmpeg-show-error #t")
                            
                            if "://" in selected_path:
                                print(f"🔍 尝试加载 URL: [{selected_path}]")
                                try:
                                    # Use MovieVideo for better protocol handling
                                    video_src = MovieVideo.get(Filename(selected_path))
                                    video_texture = MovieTexture("RemoteVideo")
                                    if not video_texture.load(video_src):
                                        print("⚠ MovieVideo 加载失败，回退到 Texture.read...")
                                        if not video_texture.read(Filename(selected_path)):
                                             raise Exception("MovieTexture 无法读取 URL")
                                except Exception as e:
                                    print(f"⚠ 显式流加载失败: {e}，尝试使用 Loader...")
                                    video_texture = manager.world.loader.loadTexture(selected_path)
                            else:
                                video_texture = manager.world.loader.loadTexture(Filename.from_os_specific(selected_path))
                            if video_texture:
                                # 更新AspectRatio
                                orig_w = video_texture.getOrigFileXSize()
                                orig_h = video_texture.getOrigFileYSize()
                                if orig_w > 0 and orig_h > 0:
                                    ratio = orig_w / orig_h
                                    ratio = orig_w / orig_h
                                    width = comp_data.get('width', 320)
                                    new_height = width / ratio
                                    
                                    print(f"✓Loaded Video Texture: {orig_w}x{orig_h}")
                                    
                                    # Debug: check threading support
                                    from panda3d.core import ConfigVariableBool
                                    if not ConfigVariableBool("support-threads").getValue():
                                        print("⚠ 警告: support-threads 为 False, 视频播放可能失败!")
                                    
                                    if 'sprite' in comp_data:
                                        spr = comp_data['sprite']
                                        # Ensure full UV coordinate usage
                                        if hasattr(spr, 'set_uv_range'):
                                            spr.set_uv_range(0, 0, 1, 1)
                                    
                                    # 更新组件高度
                                    comp_data['height'] = new_height
                                    comp_obj.height = new_height
                                    if 'sprite' in comp_data:
                                        comp_data['sprite'].height = new_height
                                
                                    # Re-create sprite to ensure no atlas conflict and clean up old one
                                    if 'sprite' in comp_data:
                                        # Detach old sprite to prevent "ghost" rectangles
                                        old_spr = comp_data['sprite']
                                        if old_spr:
                                            try:
                                                # Try standard LUI detachment
                                                old_spr.parent = None
                                                if hasattr(old_spr, 'hide'):
                                                    old_spr.hide()
                                            except Exception as ex:
                                                print(f"⚠ Failed to detach old sprite: {ex}")

                                    # Create new sprite with direct texture
                                    # Note: LUISprite(parent, texture) is valid if texture is a Texture object
                                    new_spr = LUISprite(comp_obj, video_texture)
                                    # Force set texture again with resize=False to ensure binding
                                    if hasattr(new_spr, 'set_texture'):
                                         new_spr.set_texture(video_texture, resize=False)
                                         
                                    new_spr.width = width
                                    new_spr.height = new_height
                                    # Video needs white color to modulate correctly with texture
                                    new_spr.color = (1, 1, 1, 1) 
                                    new_spr.z_offset = 0
                                    
                                    # Ensure UVs are full frame
                                    if hasattr(new_spr, 'set_uv_range'):
                                        new_spr.set_uv_range(0, 0, 1, 1)
                                    
                                    comp_data['sprite'] = new_spr
                                    
                                    # WORKAROUND: Force texture update by adding a dummy node to scene graph
                                    # LUI alone might not trigger MovieTexture updates in some pipeline configs
                                    if 'keep_alive' in comp_data and comp_data['keep_alive']:
                                        try:
                                            comp_data['keep_alive'].destroy()
                                        except:
                                            pass
                                    
                                    try:
                                        from direct.gui.OnscreenImage import OnscreenImage
                                        from panda3d.core import TransparencyAttrib
                                        # Create a nearly invisible card (alpha=0.01) to force update
                                        # We position it at (0,0,0) with a small but visible scale to prevent culling
                                        dummy = OnscreenImage(image=video_texture, parent=manager.world.render2d)
                                        dummy.setScale(0.1) 
                                        dummy.setPos(0, 0, 0) 
                                        dummy.setTransparency(TransparencyAttrib.MAlpha)
                                        dummy.setColorScale(1, 1, 1, 0.01) # Nearly invisible
                                        # Ensure it's behind other UI if possible, though in render2d everything is overlay
                                        dummy.setBin("background", -10)
                                        
                                        comp_data['keep_alive'] = dummy
                                        print("✓ Created keep-alive node for video")
                                    except Exception as e:
                                        print(f"⚠ Keep-alive creation failed: {e}")
                                
                                # 更新引用
                                comp_data['texture'] = video_texture
                                comp_obj.video_texture = video_texture # Prevent GC
                                if comp_data.get("audio_from_video"):
                                    old_audio = comp_data.get("audio")
                                    if old_audio and hasattr(old_audio, "stop"):
                                        old_audio.stop()
                                    try:
                                        audio_sound = manager.world.loader.loadSfx(p3d.Filename.from_os_specific(selected_path))
                                        if audio_sound:
                                            comp_data["audio"] = audio_sound
                                            comp_data["audio_path"] = selected_path
                                            if hasattr(audio_sound, "setLoop"):
                                                audio_sound.setLoop(comp_data.get("loop", True))
                                            if hasattr(audio_sound, "setVolume"):
                                                audio_sound.setVolume(comp_data.get("volume", 1.0))
                                            if comp_data.get("is_playing") and hasattr(audio_sound, "play"):
                                                audio_sound.play()
                                    except Exception as e:
                                        print(f"Audio load failed: {e}")
                                
                                if hasattr(video_texture, 'play'):
                                    if hasattr(video_texture, 'stop'):
                                        video_texture.stop() # Reset
                                    video_texture.play()
                                if hasattr(video_texture, 'setLoop'):
                                    video_texture.setLoop(comp_data.get("loop", True))
                                
                                comp_data['is_playing'] = True
                                
                        except Exception as e:
                            print(f"⚠ 加载视频失败: {e}")
            
            # 播放控制
            imgui.spacing()


            imgui.text("播放控制")
            
            video_tex = comp_data.get('texture')
            audio_sound = comp_data.get('audio')
            if video_tex:
                # Play/Pause Button
                is_playing = comp_data.get('is_playing', False)
                if imgui.button("暂停" if is_playing else "播放", (60, 24)):
                    if is_playing:
                         if hasattr(video_tex, 'stop'): video_tex.stop()
                         if audio_sound and hasattr(audio_sound, 'stop'): audio_sound.stop()
                         comp_data['is_playing'] = False
                    else:
                         if hasattr(video_tex, 'play'): video_tex.play()
                         if audio_sound and hasattr(audio_sound, 'play'): audio_sound.play()
                         comp_data['is_playing'] = True

                imgui.same_line()
                if imgui.button("重播", (60, 24)):
                    if hasattr(video_tex, 'stop'): video_tex.stop()
                    if audio_sound and hasattr(audio_sound, 'stop'): audio_sound.stop()
                    if hasattr(video_tex, 'play'): video_tex.play()
                    if audio_sound and hasattr(audio_sound, 'play'): audio_sound.play()
                    comp_data['is_playing'] = True
                
                # Loop Checkbox
                loop = comp_data.get('loop', True)
                changed, new_loop = imgui.checkbox("循环播放", loop)
                if changed:
                    comp_data['loop'] = new_loop
                    if hasattr(video_tex, 'setLoop'):
                        video_tex.setLoop(new_loop)
                    if audio_sound and hasattr(audio_sound, 'setLoop'): audio_sound.setLoop(new_loop)
                        
                # Time Display
                if hasattr(video_tex, 'getTime'):
                    t = video_tex.getTime()
                    
                    # 检查是否需要循环播放
                    duration = comp_data.get('duration', 0.0)
                    if duration > 0 and t >= duration:
                        # 播放时间超过总时长，重置到开头
                        if hasattr(video_tex, 'setTime'):
                            video_tex.setTime(0.0)
                            t = 0.0
                            print(f"✓ 视频播放完毕，重新开始循环播放")
                    
                    imgui.text(f"当前时间: {t:.2f}s")
                    
                    # Force refresh if stuck (hack)
                    # if t == 0.0 and comp_data.get('is_playing'):
                    #     video_tex.play()

                # imgui.separator()
                # imgui.text("调试工具")
                # if imgui.button("在Render2D中测试播放"):
                #     # Create a standard OnscreenImage to test playback capability
                #     from direct.gui.OnscreenImage import OnscreenImage
                #     from panda3d.core import TransparencyAttrib
                    
                #     # Remove previous test if any
                #     if hasattr(manager, '_debug_video_node') and manager._debug_video_node:
                #         manager._debug_video_node.destroy()
                        
                #     manager._debug_video_node = OnscreenImage(image=video_tex, pos=(0, 0, 0), scale=0.5, parent=manager.world.render2d)
                #     manager._debug_video_node.setTransparency(TransparencyAttrib.MAlpha)
                #     print(f"Debug: Created OnscreenImage with texture {video_tex}")

                # if imgui.button("清除Render2D测试"):
                #     if hasattr(manager, '_debug_video_node') and manager._debug_video_node:
                #         manager._debug_video_node.destroy()
                #         manager._debug_video_node = None
                #         print("Debug: Cleared OnscreenImage")
                
                # Time Display and Control
                if hasattr(video_tex, 'getTime'):
                    current_time = video_tex.getTime()
                    
                    if 'duration' not in comp_data or comp_data['duration'] <= 0.0:
                         # Attempt to detect duration automatically
                         detected_duration = 0.0
                         # Method 1: standard MovieTexture interface (if available)
                         if hasattr(video_tex, 'getVideoLength'): 
                             detected_duration = video_tex.getVideoLength()
                         # Method 2: check if it has a 'length' property
                         elif hasattr(video_tex, 'length'):
                             detected_duration = video_tex.length
                             
                         if detected_duration > 0:
                             comp_data['duration'] = detected_duration
                             print(f"✓ Automatically detected video duration: {detected_duration}s")
                             
                    # Manual Duration Input (Essential if auto-detection fails)
                    # We only show this if we really don't know the duration
                    current_dur = comp_data.get('duration', 0.0)
                    if current_dur <= 0.1:
                        imgui.text_colored((1, 1, 0, 1), "⚠未知时长，请手动设置:")
                        changed, new_dur = imgui.input_float("总时长(s)", current_dur, 1.0, 10.0, "%.1f")
                        if changed:
                             comp_data['duration'] = new_dur
                    
                    duration = comp_data.get('duration', 0.0)
                    
                    # 检查是否需要循环播放
                    if duration > 0 and current_time >= duration:
                        # 播放时间超过总时长，重置到开头
                        if hasattr(video_tex, 'setTime'):
                            video_tex.setTime(0.0)
                            current_time = 0.0
                            print(f"✓ 视频播放完毕，重新开始循环播放")
                    
                    # If we still don't have a valid duration, use a fallback for display but warn user
                    # REVERT: Do not auto-extend duration, it causes confusion. Trust detection or user.
                    
                    display_max = duration if duration > 0 else max(current_time + 10.0, 60.0)

                    # Format function for time
                    def format_time(seconds):
                         m = int(seconds // 60)
                         s = int(seconds % 60)
                         return f"{m:02d}:{s:02d}"

                    # Progress Slider
                    imgui.text(f"进度: {format_time(current_time)} / {format_time(display_max)}")
                    imgui.same_line()
                    
                    # Use push_item_width to make it fit
                    imgui.push_item_width(-1)
                    # Use a custom format for the slider to show seconds, but maybe we can show MM:SS in the text
                    changed, seek_time = imgui.slider_float("##seek_slider", current_time, 0.0, display_max, "%.2fs")
                    imgui.pop_item_width()
                    
                    if changed:
                         if hasattr(video_tex, 'setTime'):
                             video_tex.setTime(seek_time)
                             if audio_sound and hasattr(audio_sound, 'setTime'): audio_sound.setTime(seek_time)
                             # If paused, we might need to show the frame. 
                             # Usually setTime works.
                             print(f"Seek to {seek_time}")
                    
                    # Update cached duration if we find a way, or if user inputs it?
                    # For now just show time
                    # imgui.text(f"时间: {current_time:.2f}s") # Already shown in slider
            else:
                imgui.text_colored((1, 0, 0, 1), "无有效视频纹理")
        
        # 锚点设置
        imgui.spacing()
        imgui.separator()
        imgui.text("锚点设置")
        
        parent_index = comp_data.get('parent_index')
        has_parent = (parent_index is not None and parent_index >= 0 and parent_index < len(manager.components))
        
        # 允许对父组件或Canvas进行锚点设置
        if has_parent or manager.current_canvas_index >= 0:
            is_anchored = comp_data.get('anchored_to_parent', False)
            anchor_pos = comp_data.get('anchor_position', '未设置')
            
            if has_parent:
                parent_comp = manager.components[parent_index]
                imgui.text_colored((0.0, 1.0, 0.0, 1.0), f"父组件: {parent_comp['type']}")
            else:
                imgui.text_colored((0.0, 1.0, 0.0, 1.0), "父容器: Canvas")
            
            if is_anchored:
                imgui.text_colored((0.0, 1.0, 0.0, 1.0), f"当前锚点: {anchor_pos}")
                
                # 锚点位置选择器 - 9宫格布局
                imgui.spacing()
                imgui.text("锚点位置:")
                
                # 定义9个锚点位置
                anchor_positions = [
                    ['top-left', 'top-center', 'top-right'],
                    ['middle-left', 'center', 'middle-right'],
                    ['bottom-left', 'bottom-center', 'bottom-right']
                ]
                
                # 中文显示名称
                anchor_names = {
                    'top-left': '左上', 'top-center': '中上', 'top-right': '右上',
                    'middle-left': '左中', 'center': '中心', 'middle-right': '右中',
                    'bottom-left': '左下', 'bottom-center': '中下', 'bottom-right': '右下'
                }
                
                # 绘制3x3网格按钮
                for row_idx, row in enumerate(anchor_positions):
                    for col_idx, pos in enumerate(row):
                        # 检查是否是当前选中的锚点
                        is_current = (anchor_pos == pos)
                        
                        # 如果是当前锚点，使用不同颜色
                        if is_current:
                            imgui.push_style_color(imgui.Col_.button, (0.2, 0.7, 0.2, 1.0))
                            imgui.push_style_color(imgui.Col_.button_hovered, (0.3, 0.8, 0.3, 1.0))
                            imgui.push_style_color(imgui.Col_.button_active, (0.1, 0.6, 0.1, 1.0))
                        
                        # 绘制按钮
                        button_size = (50, 25)
                        if imgui.button(f"{anchor_names[pos]}##anchor_{pos}", button_size):
                            # 更新锚点位置
                            manager._update_component_anchor_position(index, pos)
                        
                        if is_current:
                            imgui.pop_style_color(3)
                        
                        # 同一行的按钮在同一行显示
                        if col_idx < len(row) - 1:
                            imgui.same_line()
                
                imgui.spacing()
                
                # 锚点偏移微调
                imgui.text("位置微调:")
                anchor_offset_x = comp_data.get('anchor_manual_offset_x', 0.0)
                anchor_offset_y = comp_data.get('anchor_manual_offset_y', 0.0)
                
                changed_x, new_offset_x = imgui.input_float("X偏移", anchor_offset_x, 1.0, 10.0, "%.1f")
                if changed_x:
                    comp_data['anchor_manual_offset_x'] = new_offset_x
                    manager._update_component_anchor_position(index, anchor_pos, manual_offset=(new_offset_x, anchor_offset_y))
                
                changed_y, new_offset_y = imgui.input_float("Y偏移", anchor_offset_y, 1.0, 10.0, "%.1f")
                if changed_y:
                    comp_data['anchor_manual_offset_y'] = new_offset_y
                    manager._update_component_anchor_position(index, anchor_pos, manual_offset=(anchor_offset_x, new_offset_y))
                
                imgui.spacing()
                
                # 取消锚点按钮
                if imgui.button("取消锚点", (100, 20)):
                    comp_data['anchored_to_parent'] = False
                    comp_data['anchor_manual_offset_x'] = 0.0
                    comp_data['anchor_manual_offset_y'] = 0.0
                    print("✓ 已取消组件的锚点关系")
            else:
                imgui.text_colored((1.0, 0.0, 0.0, 1.0), "未使用锚点定位")
                if imgui.button("设置锚点", (100, 20)):
                    imgui.open_popup("选择锚点位置")
                    manager._temp_selected_index_for_anchor = index
        else:
            imgui.text_disabled("无法设置锚点 (无父组件且无有效Canvas)")
            
        # Hierarchy & Parent Management
        imgui.spacing()
        imgui.text("层级与父级管理")
        imgui.separator()
        
        current_parent_index = comp_data.get('parent_index')
        parent_text = "ROOT (无父组件)" if current_parent_index is None or current_parent_index < 0 else f"{manager.components[current_parent_index]['type']} #{current_parent_index}"
        imgui.text(f"当前父级: {parent_text}")

        # Create list of potential parents (exclude self and any children to avoid circular loops)
        def get_all_descendants(idx):
             desc = set()
             comp = manager.components[idx]
             for c_idx in comp.get('children_indices', []):
                 desc.add(c_idx)
                 desc.update(get_all_descendants(c_idx))
             return desc
        
        descendants = get_all_descendants(index)
        parent_options = ["(无 / ROOT)"]
        parent_indices = [-1]
        
        for i, comp in enumerate(manager.components):
            if i != index and i not in descendants:
                parent_options.append(f"{comp['type']} #{i}: {comp.get('name', '')}")
                parent_indices.append(i)
        
        # Current selection in combo
        try:
             current_p_select = parent_indices.index(current_parent_index if current_parent_index is not None else -1)
        except:
             current_p_select = 0
             
        changed, new_p_select = imgui.combo("更改父级", current_p_select, parent_options)
        if changed:
            new_parent_idx = parent_indices[new_p_select]
            if new_parent_idx == -1:
                # Unparent to Root
                if current_parent_index is not None and current_parent_index >= 0:
                     # Remove from old parent
                     old_p = manager.components[current_parent_index]
                     if index in old_p.get('children_indices', []):
                          old_p['children_indices'].remove(index)
                     
                     comp_data['parent_index'] = -1
                     # Physical reparent to Canvas or Root
                     if manager.current_canvas_index >= 0:
                          canvas_panel = manager.canvases[manager.current_canvas_index]['panel']
                          comp_obj.reparent_to(canvas_panel)
                     else:
                          # Fallback to overlay root
                          comp_obj.reparent_to(manager.overlay_root)
                     print(f"✓ 已将组件 #{index} 设为根节点")
            else:
                # Set New Parent
                manager._set_parent_child_relationship(index, new_parent_idx, keep_world=True)
                print(f"✓ 已更改父级: 组件 #{index} -> 父级 #{new_parent_idx}")
        
        # 删除按钮
        imgui.spacing()
        imgui.separator()
        imgui.push_style_color(imgui.Col_.button, (0.8, 0.2, 0.2, 1.0))
        imgui.push_style_color(imgui.Col_.button_hovered, (0.9, 0.3, 0.3, 1.0))
        imgui.push_style_color(imgui.Col_.button_active, (0.7, 0.1, 0.1, 1.0))
        if imgui.button("删除组件", (-1, 30)):
            manager.luiFunction.delete_component(manager,index)
        imgui.pop_style_color(3)
        
        # Draw the anchor popup if requested
        manager._handle_anchor_popup()
