import os
import sys
import struct
from pathlib import Path
import panda3d.core as p3d
from panda3d.core import NodePath, CardMaker
from ui.lui_function import luiFunction

# 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))

# Handle DLL loading for lui.pyd
import panda3d
panda_dir = os.path.dirname(panda3d.__file__)
if hasattr(os, "add_dll_directory"):
    try:
        os.add_dll_directory(panda_dir)
        os.add_dll_directory(str(UI_DIR))
    except Exception as e:
        print(f"Warning: Failed to add DLL directory: {e}")

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
except ImportError as e:
    print(f"Error: Failed to import LUI: {e}")
    lui = None

from imgui_bundle import imgui, imgui_ctx

class LUIManager:
    """Manager for LUI system integration"""
    def __init__(self, world):
        self.world = world
        self.lui_enabled = (lui is not None)
        # Always initialize selection fields, even when LUI is unavailable.
        self.selected_index = -1
        self._last_selected_index = -1

        self.luiFunction = luiFunction
        
        # Editor State
        self.show_editor = True
        self.play_mode = False
        self._selected_type_index = 0
        
        if not self.lui_enabled:
            print("LUI is not available, LUIManager will be disabled.")
            return

        # 1. 优先注册中文字体 (必须在皮肤加载前)
        self._register_chinese_font()

        # 2. 初始化核心 LUI 区域
        self._init_ui_regions()
        
        # 3. 加载皮肤资源
        self.lui_skin = LUIDefaultSkin()
        self.lui_skin.load()
        
        # 4. 状态管理 - 与engine保持一致
        self.canvases = []
        self.current_canvas_index = -1
        self.canvas_counter = 0
        self.components = []
        self.root_order = []
        self._tree_drag_src = None
        self.selected_index = -1
        self._last_selected_index = -1
        
        # 拖拽和选择状态
        self.dragging_comp = None
        self.pending_drag_comp = None
        self.pending_drag_index = -1
        self.pending_drag_start_mouse = None
        self.pending_drag_start_abs = None
        self.pending_drag_start_parent = None
        self.dragging_index = -1
        self.drag_start_threshold = 6.0
        self.drag_offset = (0, 0)
        
        # 锚点功能相关变量
        self.anchor_popup_open = False
        self.pending_child_creation = False
        self.pending_parent_index = -1
        
        # 子父对齐功能
        self.child_parent_alignment_enabled = True
        
        # Resize handles 相关变量
        self.resize_handles = []
        self.selection_box = []
        self.debug_outlines = {}  # comp_index -> [border sprites]
        self.resizing_handle = None
        self.resize_start_pos = (0, 0)
        self.resize_start_bounds = {}
        self.min_size = 20
        
        # 使用外部字典管理状态，避免修改 C++ 对象属性导致 AttributeError
        self.drag_states = {} 
        self._last_interaction_frame = -1 # 记录最后一次交互的帧数，用于防止事件穿透 
        self._input_enabled = True
        
        # 创建resize handles和selection box（延迟创建，确保在Canvas之后）
        if hasattr(self.world, 'taskMgr'):
            self.world.taskMgr.doMethodLater(0.1, self._delayed_create_resize_handles, "delayed_create_resize_handles")
        else:
            self._create_resize_handles()
        
        # 添加任务来处理拖动和resize handles更新
        if hasattr(self.world, 'taskMgr'):
            self.world.taskMgr.add(self._update_drag, "update_lui_drag")
            self.world.taskMgr.add(self._update_resize_handles, "update_resize_handles")
            self.world.taskMgr.add(self._update_component_outlines, "update_lui_outlines")
        
        print("✓ LUIManager initialized with complete functionality")

    def set_input_enabled(self, enabled):
        """Enable/disable LUI input handling (used to avoid ImGui click-through)."""
        if not self.lui_enabled:
            return

        if enabled == self._input_enabled:
            return

        self._input_enabled = enabled

        try:
            if enabled:
                if hasattr(self, "overlay_handler_node") and self.overlay_handler_node:
                    self.overlay_handler_node.reparent_to(self.world.mouseWatcher)
                if hasattr(self, "overlay_region") and self.overlay_region:
                    self.overlay_region.set_input_handler(self.overlay_handler)
            else:
                if hasattr(self, "overlay_region") and self.overlay_region:
                    self.overlay_region.set_input_handler(None)
                if hasattr(self, "overlay_handler_node") and self.overlay_handler_node:
                    self.overlay_handler_node.detach_node()
        except Exception as e:
            print(f"Warning: Failed to toggle LUI input state: {e}")

    def _register_chinese_font(self):
        """注册支持中文的字体"""
        from panda3d.lui import LUIFontPool
        import os
        
        # 尝试常见的系统路径，确保使用完整的 OS 路径
        candidate_fonts = [
            "C:/Windows/Fonts/msyh.ttc",
            "C:/Windows/Fonts/simhei.ttf",
            "C:/Windows/Fonts/arial.ttf"
        ]
        
        font_registered = False
        for fpath in candidate_fonts:
            if os.path.exists(fpath):
                try:
                    # 在 LUI 中，建议直接用 loader.loadFont 载入后获取动态库指针
                    # 或者直接传递路径字符串
                    font = self.world.loader.loadFont(fpath)
                    if font:
                        LUIFontPool.get_global_ptr().register_font("default", font)
                        LUIFontPool.get_global_ptr().register_font("label", font)
                        print(f"✓ LUI 成功注册字体: {fpath}")
                        font_registered = True
                        break
                except Exception as e:
                    print(f"LUI 字体注册失败 {fpath}: {e}")
        
        if not font_registered:
            # 如果没有系统字体，至少注册一个默认的避免崩溃
            print("⚠️ 警告: 未能找到合适的 LUI 中文字体")

    def _init_ui_regions(self):
        """初始化 LUI 区域 (Overlay, Camera, WorldSpace)"""
        # 渲染层级说明（参考engine/main.py的设置）：
        # - 3D场景（render）默认sort=0
        # - LUI组件设置sort=5，在3D场景之上
        # - pixel2d (ImGui) 默认sort=20，在LUI之上
        # 这样：3D场景 < LUI组件 < ImGui面板
        
        self.overlay_region = LUIRegion.make("OverlayUI", self.world.win)
        self.overlay_handler = LUIInputHandler()
        self.overlay_handler_node = self.world.mouseWatcher.attach_new_node(self.overlay_handler)
        self.overlay_region.set_input_handler(self.overlay_handler)
        self.overlay_region.set_sort(5)  # 在3D场景之上，在ImGui之下（与engine保持一致）
        self.overlay_root = self.overlay_region.root
        
        # 调试输出：确认层级设置
        actual_sort = self.overlay_region.getSort()
        print(f"✓ LUI Overlay Region 层级设置: {actual_sort}")
        
        # 显示所有DisplayRegion的层级信息
        num_regions = self.world.win.getNumDisplayRegions()
        print(f"✓ 窗口总共有 {num_regions} 个DisplayRegion:")
        for i in range(num_regions):
            dr = self.world.win.getDisplayRegion(i)
            sort_value = dr.getSort()
            camera = dr.getCamera()
            camera_name = camera.getName() if not camera.isEmpty() else "无摄像机"
            print(f"  Region {i}: sort={sort_value}, camera={camera_name}")

        # 当前操作的根节点
        self.root = self.overlay_root

    def _make_canvas_draggable(self, canvas_panel):
        """使Canvas可拖拽"""
        def on_canvas_drag_start(event):
            # 检查是否有组件或手柄在当前帧被点击
            current_frame = 0
            if hasattr(p3d, 'ClockObject'):
                current_frame = p3d.ClockObject.getGlobalClock().getFrameCount()
            else:
                current_frame = self.world.taskMgr.globalClock.getFrameCount()
                
            if self._last_interaction_frame == current_frame:
                # 当前帧已经处理了组件或手柄的点击，忽略Canvas点击事件
                return
            
            # 点击空白处：不自动取消选中，避免事件穿透导致闪退
            # self.deselect_all()
            
            # 如果启用填充模式，则自动禁用，以便用户可以自由拖动
            canvas_index = -1
            for i, c in enumerate(self.canvases):
                if c['panel'] == canvas_panel:
                    canvas_index = i
                    break
            
            if canvas_index >= 0:
                canvas_data = self.canvases[canvas_index]
                
                # 如果是固定模式，则禁止拖动初始化
                if canvas_data.get('fixed', False):
                    return

                if canvas_data.get('fill_mode', False):
                    canvas_data['fill_mode'] = False
                    print("✓ 自动禁用填充模式以允许自由拖动")

            # 记录Canvas拖拽状态
            if hasattr(self.world, 'mouseWatcherNode') and self.world.mouseWatcherNode.hasMouse():
                mouse_x = self.world.mouseWatcherNode.getMouseX()
                mouse_y = self.world.mouseWatcherNode.getMouseY()
                
                win_x = self.world.win.getXSize()
                win_y = self.world.win.getYSize()
                pixel_x = (mouse_x + 1) * win_x / 2
                pixel_y = (1 - mouse_y) * win_y / 2
                
                pos = canvas_panel.get_pos()
                self.canvas_drag_offset = (pixel_x - pos.x, pixel_y - pos.y)
                self.dragging_canvas = canvas_panel
        
        canvas_panel.bind("mousedown", on_canvas_drag_start)

    def switch_canvas(self, index):
        """切换到指定Canvas并显示/隐藏相应的UI组件"""
        if index < 0 or index >= len(self.canvases):
            return
            
        self.current_canvas_index = index
        
        # 显示选中的Canvas，隐藏其他Canvas
        for i, canvas in enumerate(self.canvases):
            if i == index:
                canvas['panel'].show()
                canvas['visible'] = True
            else:
                canvas['panel'].hide()
                canvas['visible'] = False
        
        # 显示/隐藏属于不同Canvas的组件（包括子组件）
        visible_count = 0
        hidden_count = 0
        
        for comp in self.components:
            comp_canvas_index = comp.get('canvas_index')
            comp_obj = comp['object']
            comp_name = comp.get('name', 'Unknown')
            
            should_be_visible = (comp_canvas_index == index)
            
            if should_be_visible:
                if hasattr(comp_obj, 'show'):
                    comp_obj.show()
                elif hasattr(comp_obj, 'visible'):
                    comp_obj.visible = True
                visible_count += 1
            else:
                if hasattr(comp_obj, 'hide'):
                    comp_obj.hide()
                elif hasattr(comp_obj, 'visible'):
                    comp_obj.visible = False
                hidden_count += 1
        
        # 更新当前根节点
        if index >= 0:
            self.root = self.canvases[index]['panel']
        
        # 如果选中的组件不属于当前Canvas，取消选中
        if self.selected_index >= 0 and self.selected_index < len(self.components):
            selected_comp = self.components[self.selected_index]
            if selected_comp.get('canvas_index') != index:
                if getattr(self, '_force_keep_selection_frames', 0) > 0:
                    pass
                else:
                    self.selected_index = -1
                    self._hide_selection()
        
        print(f"✓ Switched to Canvas: {self.canvases[index]['name']}")
        print(f"  显示组件: {visible_count}, 隐藏组件: {hidden_count}")

    def _update_canvas_geometry(self, canvas_data):
        """根据填充模式和边距更新Canvas几何信息"""
        if not canvas_data.get('fill_mode', False):
            return

        panel = canvas_data['panel']
        margins = canvas_data.get('margins', {'left':0,'right':0,'top':0,'bottom':0})
        
        win_width = 800
        win_height = 600
        if hasattr(self.world, 'win'):
            win_width = self.world.win.getXSize()
            win_height = self.world.win.getYSize()
            
        new_x = margins['left']
        new_y = margins['top']
        new_width = max(10, win_width - margins['left'] - margins['right'])
        new_height = max(10, win_height - margins['top'] - margins['bottom'])
        
        panel.set_pos(new_x, new_y)
        panel.width = new_width
        panel.height = new_height
        
        if 'background' in canvas_data:
            bg = canvas_data['background']
            bg.width = new_width
            bg.height = new_height

    def _setup_component_drag(self, comp_data, comp_index):
        """为组件设置拖动功能 - 支持Canvas相对坐标系"""
        comp_obj = comp_data['object']
        
        def on_mouse_down(event):
            comp_type = comp_data.get('type')
            if getattr(self, 'play_mode', False):
                # Let runtime widgets handle their own events (especially Slider)
                if comp_type == 'Slider':
                    return
                try:
                    if hasattr(comp_obj, 'on_mousedown'):
                        comp_obj.on_mousedown(event)
                except Exception:
                    pass
                return
            # Prevent re-entrant drag
            if self.dragging_comp is not None:
                return

            # Find current index
            current_index = -1
            for i, c in enumerate(self.components):
                if c is comp_data:
                    current_index = i
                    break

            if current_index == -1:
                return

            # Mark interaction frame
            if hasattr(p3d, 'ClockObject'):
                self._last_interaction_frame = p3d.ClockObject.getGlobalClock().getFrameCount()
            else:
                self._last_interaction_frame = self.world.taskMgr.globalClock.getFrameCount()

            # Select component
            self.selected_index = current_index
            self._last_selected_index = current_index
            self._force_keep_selection_frames = 3

            # Force component to current canvas to avoid immediate deselect
            try:
                comp_data['canvas_index'] = self.current_canvas_index
            except Exception:
                pass

            try:
                if comp_data.get('canvas_index') is None:
                    comp_data['canvas_index'] = self.current_canvas_index
            except Exception:
                pass
            try:
                self._show_and_update_handles(comp_data)
            except Exception:
                pass

            # Slider: allow knob/bar drag in edit mode without hijacking
            if comp_type == 'Slider':
                sender = getattr(event, 'sender', None)
                knob = getattr(comp_obj, '_knob', None)
                bg = getattr(comp_obj, '_slider_bg', None)
                if sender is not None and (sender == knob or sender == bg):
                    # Let slider handle its own drag logic
                    try:
                        if hasattr(comp_obj, '_start_drag'):
                            comp_obj._start_drag(event)
                    except Exception:
                        pass
                    return

            parent_idx = comp_data.get('parent_index')
            if parent_idx is not None and parent_idx >= 0:
                parent_type = self.components[parent_idx].get('type')
                if parent_type in ['VerticalLayout', 'HorizontalLayout']:
                    return

            if not comp_data.get('draggable', True):
                return

            self.pending_drag_comp = comp_data
            self.pending_drag_index = current_index
            self.pending_drag_start_abs = self._get_component_accumulated_pos(current_index)
            self.pending_drag_start_parent = comp_obj.parent
            self._is_drag_reparented = False

            if hasattr(self.world, 'mouseWatcherNode') and self.world.mouseWatcherNode.hasMouse():
                mouse_x = self.world.mouseWatcherNode.getMouseX()
                mouse_y = self.world.mouseWatcherNode.getMouseY()

                win_x = self.world.win.getXSize()
                win_y = self.world.win.getYSize()
                pixel_x = (mouse_x + 1) * win_x / 2
                pixel_y = (1 - mouse_y) * win_y / 2
                self.pending_drag_start_mouse = (pixel_x, pixel_y)
            else:
                self.pending_drag_start_mouse = None

        # Bind mouse event
        comp_obj.bind("mousedown", on_mouse_down)

        # Slider selection bindings: allow clicking knob/bar to select in edit mode
        try:
            if comp_data.get('type') == 'Slider':
                knob = getattr(comp_obj, '_knob', None)
                bg = getattr(comp_obj, '_slider_bg', None)
        except Exception:
            pass

        def on_mouse_up(event):
            if getattr(self, 'play_mode', False):
                try:
                    if hasattr(comp_obj, 'on_mouseup'):
                        comp_obj.on_mouseup(event)
                except Exception:
                    pass
                return

            if comp_data.get('type') == 'Slider':
                sender = getattr(event, 'sender', None)
                knob = getattr(comp_obj, '_knob', None)
                if sender is not None and sender == knob:
                    try:
                        if hasattr(comp_obj, '_stop_drag'):
                            comp_obj._stop_drag(event)
                    except Exception:
                        pass

        comp_obj.bind("mouseup", on_mouse_up)

    def _make_draggable(self, lui_obj):
        """启用 LUI 对象的鼠标拖拽功能 - 兼容旧接口"""
        # 查找对应的组件索引
        comp_index = -1
        for i, comp in enumerate(self.components):
            if comp.get('object') == lui_obj:
                comp_index = i
                break
        
        if comp_index >= 0:
            self._setup_component_drag(self.components[comp_index], comp_index)

    def _get_component_accumulated_pos(self, comp_index):
        """递归计算组件相对于Canvas的累积位置"""
        if comp_index < 0 or comp_index >= len(self.components):
            return 0, 0
            
        comp_data = self.components[comp_index]
        comp_obj = comp_data['object']
        
        # 优先使用递归计算（更可靠）
        left = comp_data.get('left', 0)
        top = comp_data.get('top', 0)
        
        parent_index = comp_data.get('parent_index')
        if parent_index is not None and parent_index >= 0:
            parent_left, parent_top = self._get_component_accumulated_pos(parent_index)
            result_x = parent_left + left
            result_y = parent_top + top
            print(f"[_get_component_accumulated_pos] Component #{comp_index} (has parent #{parent_index}): local=({left:.1f}, {top:.1f}), parent_offset=({parent_left:.1f}, {parent_top:.1f}), result=({result_x:.1f}, {result_y:.1f})")
            return result_x, result_y
        
        # 没有父组件，直接返回局部坐标（相对于Canvas）
        print(f"[_get_component_accumulated_pos] Component #{comp_index} (root): local=({left:.1f}, {top:.1f})")
        return left, top

    def _update_drag(self, task):
        """每帧更新拖动状态 - 支持Canvas边界约束和局部坐标"""
        if getattr(self, 'play_mode', False):
            # Disable editor drag logic while in play mode
            return task.cont

        # 1. 处理Canvas拖动
        if hasattr(self, 'dragging_canvas') and self.dragging_canvas:
            # 查找对应的Canvas数据
            canvas_data = None
            for c in self.canvases:
                if c['panel'] == self.dragging_canvas:
                    canvas_data = c
                    break
            
            # 检查Canvas是否锁定
            if canvas_data and canvas_data.get('fixed', False):
                return task.cont

            if not hasattr(self.world, 'mouseWatcherNode') or not self.world.mouseWatcherNode.hasMouse():
                return task.cont
                
            import panda3d.core as p3d
            if not self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one()):
                self.dragging_canvas = None
                return task.cont

            mouse_x = self.world.mouseWatcherNode.getMouseX()
            mouse_y = self.world.mouseWatcherNode.getMouseY()
            
            win_x = self.world.win.getXSize()
            win_y = self.world.win.getYSize()
            pixel_x = (mouse_x + 1) * win_x / 2
            pixel_y = (1 - mouse_y) * win_y / 2
            
            new_x = pixel_x - self.canvas_drag_offset[0]
            new_y = pixel_y - self.canvas_drag_offset[1]
            
            self.dragging_canvas.set_pos(new_x, new_y)
            return task.cont

        # 2. 处理组件拖动
        if self.dragging_comp is None:
            # Pending drag: only start after threshold
            if self.pending_drag_comp is None:
                return task.cont

            if not hasattr(self.world, 'mouseWatcherNode') or not self.world.mouseWatcherNode.hasMouse():
                return task.cont

            import panda3d.core as p3d
            if not self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one()):
                # click without drag
                self.pending_drag_comp = None
                self.pending_drag_index = -1
                self.pending_drag_start_mouse = None
                self.pending_drag_start_abs = None
                self.pending_drag_start_parent = None
                return task.cont

            mouse_x = self.world.mouseWatcherNode.getMouseX()
            mouse_y = self.world.mouseWatcherNode.getMouseY()
            win_x = self.world.win.getXSize()
            win_y = self.world.win.getYSize()
            pixel_x = (mouse_x + 1) * win_x / 2
            pixel_y = (1 - mouse_y) * win_y / 2

            if self.pending_drag_start_mouse is None:
                self.pending_drag_start_mouse = (pixel_x, pixel_y)
                return task.cont

            dx = pixel_x - self.pending_drag_start_mouse[0]
            dy = pixel_y - self.pending_drag_start_mouse[1]
            if (dx * dx + dy * dy) < (self.drag_start_threshold * self.drag_start_threshold):
                return task.cont

            # Start drag now
            comp_data = self.pending_drag_comp
            self.dragging_comp = comp_data
            self.dragging_index = self.pending_drag_index
            self._original_parent_obj = self.pending_drag_start_parent
            self._is_drag_reparented = False

            acc_left, acc_top = self.pending_drag_start_abs
            if acc_left is None or acc_top is None:
                acc_left, acc_top = self._get_component_accumulated_pos(self.pending_drag_index)

            # If child, reparent to canvas for free drag
            p_idx = comp_data.get('parent_index')
            if p_idx is not None and p_idx >= 0 and self.current_canvas_index >= 0:
                canvas_panel = self.canvases[self.current_canvas_index]['panel']
                comp_obj = comp_data['object']
                if not comp_data.get('visual_parent_canvas'):
                    try:
                        if getattr(comp_obj, 'parent', None) == canvas_panel:
                            pass
                        elif hasattr(comp_obj, 'reparent_to'):
                            comp_obj.reparent_to(canvas_panel)
                            if hasattr(comp_obj, 'set_pos'):
                                comp_obj.set_pos(acc_left, acc_top)
                            else:
                                comp_obj.left = acc_left
                                comp_obj.top = acc_top
                            self._is_drag_reparented = True
                    except Exception:
                        pass

            # Compute drag offset
            canvas_abs_left = 0
            canvas_abs_top = 0
            if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
                canvas_panel = self.canvases[self.current_canvas_index]['panel']
                canvas_abs_left = getattr(canvas_panel, 'left', 0)
                canvas_abs_top = getattr(canvas_panel, 'top', 0)
                if canvas_abs_left == 0 and canvas_abs_top == 0:
                    try:
                        canvas_pos = canvas_panel.get_pos()
                        canvas_abs_left = canvas_pos.x
                        canvas_abs_top = canvas_pos.y
                    except:
                        canvas_abs_left = 300
                        canvas_abs_top = 100

            comp_abs_left = canvas_abs_left + acc_left
            comp_abs_top = canvas_abs_top + acc_top
            self.drag_offset = (pixel_x - comp_abs_left, pixel_y - comp_abs_top)

            # Clear pending state
            self.pending_drag_comp = None
            self.pending_drag_index = -1
            self.pending_drag_start_mouse = None
            self.pending_drag_start_abs = None
            self.pending_drag_start_parent = None

            # Continue with drag in same frame

        
        # 检查鼠标状态
        if not hasattr(self.world, 'mouseWatcherNode') or not self.world.mouseWatcherNode.hasMouse():
            return task.cont
        
        # 检查鼠标左键是否释放
        import panda3d.core as p3d
        if not self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one()):
            if self.dragging_comp:
                comp_data = self.dragging_comp
                print(f"组件移动到: left={comp_data.get('left', 0):.1f}, top={comp_data.get('top', 0):.1f} (局部坐标)")
                
                # 拖动结束：检查是否需要接触父子关系 (Auto-unparent)
                should_unparent = False
                if getattr(self, '_is_drag_reparented', False):
                     comp_obj = comp_data['object']
                     
                     # Check if center of component is outside parent bounds
                     p_idx = comp_data.get('parent_index')
                     if p_idx is not None and p_idx >= 0:
                         parent_data = self.components[p_idx]
                         p_w = parent_data.get('width', 100)
                         p_h = parent_data.get('height', 100)
                         
                         c_w = comp_data.get('width', 0)
                         c_h = comp_data.get('height', 0)
                         
                         # Current comp_data['left'] is LOCAL to parent
                         local_l = comp_data.get('left', 0)
                         local_t = comp_data.get('top', 0)
                         
                         center_x = local_l + c_w / 2
                         center_y = local_t + c_h / 2
                         
                         # Check bounds: center must be in 0..p_w, 0..p_h
                         # Allow some leniency or strict? Let's say if center is outside, unparent.
                         if center_x < 0 or center_x > p_w or center_y < 0 or center_y > p_h:
                             should_unparent = True

                     if should_unparent:
                          # Unparent!
                          # The component is currently visually attached to Canvas (from drag start)
                          # We just need to update the data to reflect this permanent change.
                          
                          # Calculate the new global (canvas-relative) position
                          # current local + parent offset
                          p_off_x, p_off_y = self._get_component_accumulated_pos(p_idx)
                          new_canvas_left = local_l + p_off_x
                          new_canvas_top = local_t + p_off_y
                          
                          comp_data['left'] = new_canvas_left
                          comp_data['top'] = new_canvas_top
                          
                          # Remove parent linkage
                          if 'parent_index' in comp_data:
                              del comp_data['parent_index']
                          if 'anchored_to_parent' in comp_data:
                              del comp_data['anchored_to_parent']
                              
                          print(f"✓ Auto-unparented component from #{p_idx} (Dragged out)")
                          
                     elif hasattr(comp_obj, 'reparent_to') and hasattr(self, '_original_parent_obj'):
                          comp_obj.reparent_to(self._original_parent_obj)
                          # 重新应用计算出的局部坐标
                          comp_obj.left = comp_data.get('left', 0)
                          comp_obj.top = comp_data.get('top', 0)
                          print(f"✓ 组件归位到逻辑父级")
                
                self.dragging_comp = None
            return task.cont
        
        # 获取鼠标位置
        mouse_x = self.world.mouseWatcherNode.getMouseX()
        mouse_y = self.world.mouseWatcherNode.getMouseY()
        
        # 转换为像素坐标
        win_x = self.world.win.getXSize()
        win_y = self.world.win.getYSize()
        pixel_x = (mouse_x + 1) * win_x / 2
        pixel_y = (1 - mouse_y) * win_y / 2
        
        # 计算新的绝对位置（减去偏移量）
        new_abs_left = pixel_x - self.drag_offset[0]
        new_abs_top = pixel_y - self.drag_offset[1]
        
        # 获取Canvas信息
        canvas_abs_left = 0
        canvas_abs_top = 0
        canvas_width = 800
        canvas_height = 600
        
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas_data = self.canvases[self.current_canvas_index]
            canvas_panel = canvas_data['panel']
            
            # 获取Canvas的绝对位置
            canvas_abs_left = getattr(canvas_panel, 'left', 0)
            canvas_abs_top = getattr(canvas_panel, 'top', 0)
            
            if canvas_abs_left == 0 and canvas_abs_top == 0:
                try:
                    pos = canvas_panel.get_pos()
                    canvas_abs_left = pos.x
                    canvas_abs_top = pos.y
                except:
                    # 如果获取失败，使用默认值
                    canvas_abs_left = canvas_data.get('margins', {}).get('left', 240)
                    canvas_abs_top = canvas_data.get('margins', {}).get('top', 89)
            
            # 获取Canvas的实际尺寸
            try:
                if hasattr(canvas_panel, 'width') and canvas_panel.width is not None:
                    canvas_width = float(canvas_panel.width)
                elif hasattr(canvas_panel, 'get_width'):
                    canvas_width = float(canvas_panel.get_width())
                    
                if hasattr(canvas_panel, 'height') and canvas_panel.height is not None:
                    canvas_height = float(canvas_panel.height)
                elif hasattr(canvas_panel, 'get_height'):
                    canvas_height = float(canvas_panel.get_height())
            except Exception as e:
                print(f"Warning: Failed to get canvas size: {e}")
                # 使用窗口尺寸减去边距作为后备
                margins = canvas_data.get('margins', {'left': 240, 'right': 480, 'top': 89, 'bottom': 220})
                canvas_width = win_x - margins.get('left', 0) - margins.get('right', 0)
                canvas_height = win_y - margins.get('top', 0) - margins.get('bottom', 0)
        
        # 转换为Canvas相对坐标 (Global Canvas Pos)
        canvas_relative_left = new_abs_left - canvas_abs_left
        canvas_relative_top = new_abs_top - canvas_abs_top

        # 获取当前拖拽的组件数据
        comp_data = self.dragging_comp
        comp_obj = comp_data['object']

        # 获取组件宽高 (用于边界限制)
        comp_width = comp_data.get('width', 0)
        comp_height = comp_data.get('height', 0)
        
        # 尝试从对象获取更准确的宽高
        if hasattr(comp_obj, 'width') and comp_obj.width is not None and comp_obj.width > 0:
            comp_width = comp_obj.width
        if hasattr(comp_obj, 'height') and comp_obj.height is not None and comp_obj.height > 0:
            comp_height = comp_obj.height

        # 限制在Canvas范围内
        # 确保左边界不小于0
        if canvas_relative_left < 0:
            canvas_relative_left = 0
            
        # 确保上边界不小于0 
        if canvas_relative_top < 0:
            canvas_relative_top = 0
            
        # 确保右边界不超出Canvas宽度
        if canvas_relative_left + comp_width > canvas_width:
            canvas_relative_left = max(0, canvas_width - comp_width)
            
        # 确保下边界不超出Canvas高度
        if canvas_relative_top + comp_height > canvas_height:
            canvas_relative_top = max(0, canvas_height - comp_height)
        
        # 如果有父组件，需要转换为相对于父组件的局部坐标
        comp_data = self.dragging_comp
        parent_index = comp_data.get('parent_index')
        parent_offset_x = 0
        parent_offset_y = 0
        
        if parent_index is not None and parent_index >= 0:
            # 获取父组件相对于Canvas的累积位置
            parent_offset_x, parent_offset_y = self._get_component_accumulated_pos(parent_index)
            
        # 计算新的局部坐标
        new_local_left = canvas_relative_left - parent_offset_x
        new_local_top = canvas_relative_top - parent_offset_y
        
        # 如果物理上已脱离父级（挂在Canvas上），则物理坐标就是Canvas相对坐标
        comp_obj = comp_data['object']
        if getattr(self, '_is_drag_reparented', False):
            # 子组件被重定向到Canvas，使其可以移动到负数局部坐标（即父组件外侧）
            if hasattr(comp_obj, 'set_pos'):
                comp_obj.set_pos(canvas_relative_left, canvas_relative_top)
            else:
                comp_obj.left = canvas_relative_left
                comp_obj.top = canvas_relative_top
            # print(f"Dragging Child (Canvas Rel): {canvas_relative_left:.1f}, {canvas_relative_top:.1f} | Local: {new_local_left:.1f}, {new_local_top:.1f}")
        else:
            # If visual parent is canvas, keep world position while storing local
            if comp_data.get('visual_parent_canvas'):
                abs_left = parent_offset_x + new_local_left
                abs_top = parent_offset_y + new_local_top
                if hasattr(comp_obj, 'set_pos'):
                    comp_obj.set_pos(abs_left, abs_top)
                else:
                    comp_obj.left = abs_left
                    comp_obj.top = abs_top
            else:
                if hasattr(comp_obj, 'set_pos'):
                    comp_obj.set_pos(new_local_left, new_local_top)
                else:
                    comp_obj.left = new_local_left
                    comp_obj.top = new_local_top
            
        # 更新存储的逻辑坐标数据 (保持始终相对于逻辑父级)
        comp_data['left'] = new_local_left
        comp_data['top'] = new_local_top

        # Sync visually-canvas children for non-container parents
        if self.dragging_index is not None and self.dragging_index >= 0:
            self._sync_canvas_children(self.dragging_index)
        
        return task.cont

    def _create_resize_handles(self):
        """创建选择框和8个resize手柄"""
        from Builtin.LUISprite import LUISprite
        from Builtin.LUIObject import LUIObject
        
        print("开始创建resize handles...")
        
        # 先创建4条选择框边线（蓝色，2px宽）
        for i in range(4):
            border = LUISprite(self.overlay_root, "blank", "skin")
            border.color = (0.2, 0.5, 1.0, 1.0)  # 蓝色
            border.visible = False
            
            # 设置选择框层级，在组件之上但在LUI内部
            if hasattr(border, 'z_offset'):
                border.z_offset = 40  # 选中框在普通边框(25)之上
            elif hasattr(border, 'set_sort'):
                border.set_sort(40)
            
            # 确保边框在最后创建，这样在渲染顺序上会在前面
            self.selection_box.append(border)
        
        # 最后创建8个resize手柄（白色圆形，蓝色边框）
        handle_positions = [
            'top-left', 'top', 'top-right', 'right',
            'bottom-right', 'bottom', 'bottom-left', 'left'
        ]
        
        for i, pos in enumerate(handle_positions):
            # 创建容器对象用于接收鼠标事件
            handle_container = LUIObject(parent=self.overlay_root)
            handle_container.width = 10
            handle_container.height = 10
            handle_container.solid = True
            handle_container.visible = False
            
            # 设置手柄层级，在选择框之上但在LUI内部
            if hasattr(handle_container, 'z_offset'):
                handle_container.z_offset = 50  # 在选择框之上
            elif hasattr(handle_container, 'set_sort'):
                handle_container.set_sort(50)  # 在选择框之上
            
            # 创建蓝色边框（外圈）
            handle_border = LUISprite(handle_container, "blank", "skin")
            handle_border.width = 10
            handle_border.height = 10
            handle_border.left = 0
            handle_border.top = 0
            handle_border.color = (0.2, 0.5, 1.0, 1.0)  # 蓝色边框
            
            # 创建白色圆形背景（内圈）
            handle_bg = LUISprite(handle_container, "blank", "skin")
            handle_bg.width = 8
            handle_bg.height = 8
            handle_bg.left = 1
            handle_bg.top = 1
            handle_bg.color = (1.0, 1.0, 1.0, 1.0)  # 白色
            
            # 存储手柄位置信息
            handle_container._resize_position = pos
            handle_container._resize_index = i
            handle_container._bg = handle_bg
            handle_container._border = handle_border
            
            # 绑定鼠标事件
            handle_container.bind("mousedown", lambda event, h=handle_container: self._on_handle_mousedown(event, h))
            
            self.resize_handles.append(handle_container)
        
        print(f"✓ 创建了{len(self.selection_box)}条边框和{len(self.resize_handles)}个手柄")
        
        # 强制将手柄移到最前面（在LUI中，后创建的元素通常在前面）
        for handle in self.resize_handles:
            # 尝试重新设置父节点来强制更新渲染顺序
            if hasattr(handle, 'reparent_to'):
                handle.reparent_to(self.overlay_root)
        
        for border in self.selection_box:
            if hasattr(border, 'reparent_to'):
                border.reparent_to(self.overlay_root)

    def _delayed_create_resize_handles(self, task):
        """延迟创建resize handles，确保在Canvas之后创建"""
        self._create_resize_handles()
        return task.done

    def _on_handle_mousedown(self, event, handle):
        """手柄鼠标按下事件"""
        # 标记交互帧，防止Canvas点击事件触发取消选中
        if hasattr(p3d, 'ClockObject'):
             self._last_interaction_frame = p3d.ClockObject.getGlobalClock().getFrameCount()
        else:
             # Fallback if specific import not avail in this scope (though p3d is imported)
             self._last_interaction_frame = self.world.taskMgr.globalClock.getFrameCount()

        if self.selected_index < 0:
            return

        comp_data = self.components[self.selected_index]
        print(f"✓ 开始resize: 手柄位置={handle._resize_position}")

        # 记录开始调整大小的状态
        self.resizing_handle = handle

        # 获取鼠标位置
        if hasattr(self.world, 'mouseWatcherNode') and self.world.mouseWatcherNode.hasMouse():
            mouse_x = self.world.mouseWatcherNode.getMouseX()
            mouse_y = self.world.mouseWatcherNode.getMouseY()

            win_x = self.world.win.getXSize()
            win_y = self.world.win.getYSize()
            pixel_x = (mouse_x + 1) * win_x / 2
            pixel_y = (1 - mouse_y) * win_y / 2

            self.resize_start_pos = (pixel_x, pixel_y)

        # 记录组件的初始边界
        self.resize_start_bounds = {
            'left': comp_data.get('left', 0),
            'top': comp_data.get('top', 0),
            'width': comp_data.get('width', 100),
            'height': comp_data.get('height', 30),
        }

    def _update_resize_handles(self, task):
        # Restore selection if it was cleared immediately
        if self.selected_index < 0 and getattr(self, '_force_keep_selection_frames', 0) > 0:
            if getattr(self, '_last_selected_index', -1) >= 0:
                self.selected_index = self._last_selected_index

        if getattr(self, '_force_keep_selection_frames', 0) > 0:
            self._force_keep_selection_frames -= 1

        """更新resize handles的位置和可见性"""
        # 检查当前Canvas是否可见
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas = self.canvases[self.current_canvas_index]
            if not canvas.get('visible', True):
                 self._hide_resize_handles()
                 return task.cont

        if self.selected_index < 0 or self.selected_index >= len(self.components):
            # 隐藏所有handles
            self._hide_resize_handles()
            return task.cont
        
        comp_data = self.components[self.selected_index]
        comp_type = comp_data['type']
        
        # 只对Frame、Button、Slider、InputField、Plane、Image显示resize handles
        if comp_type not in ['Frame', 'Button', 'Slider', 'InputField', 'Plane', 'Image', 'Checkbox', 'Text', 'Label', 'Video', 'Progressbar', 'Selectbox', 'ScrollableRegion', 'TabbedFrame', 'VerticalLayout', 'HorizontalLayout']:
            self._hide_resize_handles()
            return task.cont
        
        # 显示并更新handles位置
        self._show_and_update_handles(comp_data)
        
        # 处理resize拖拽
        if self.resizing_handle is not None:
            self._handle_resize_drag()
        
        return task.cont

    def _hide_resize_handles(self):
        """隐藏所有resize handles和selection box"""
        for border in self.selection_box:
            border.visible = False
        for handle in self.resize_handles:
            handle.visible = False

    def _ensure_component_outline(self, comp_index):
        'Create a simple 4-edge outline for a component if not exists.'
        if comp_index in self.debug_outlines:
            return self.debug_outlines[comp_index]

        from Builtin.LUISprite import LUISprite

        borders = []
        for _ in range(4):
            border = LUISprite(self.overlay_root, "blank", "skin")
            border.color = (0.2, 0.7, 1.0, 1.0)
            border.visible = False
            
            # Ensure outline is above components
            if hasattr(border, 'z_offset'):
                border.z_offset = 25
            elif hasattr(border, 'set_sort'):
                border.set_sort(25)
                
            borders.append(border)

        self.debug_outlines[comp_index] = borders
        return borders

    def _update_component_outlines(self, task):
        'Show component outlines in edit mode, hide in play mode.'
        if getattr(self, 'play_mode', False) or not self.lui_enabled or not self.show_editor:
            for borders in self.debug_outlines.values():
                for b in borders:
                    b.visible = False
            return task.cont

        for idx, comp_data in enumerate(self.components):
            # If selected, let the resize handles/selection box handle the outline (Bright Blue)
            # We hide the "debug" outline for the selected item to avoid double-drawing or z-fighting
            if idx == self.selected_index:
                borders = self.debug_outlines.get(idx)
                if borders:
                    for b in borders:
                        b.visible = False
                continue

            comp_obj = comp_data.get('object')
            if comp_obj is None:
                continue

            canvas_index = comp_data.get('canvas_index', self.current_canvas_index)
            if canvas_index is None or canvas_index < 0 or canvas_index >= len(self.canvases):
                # Component not assigned to a valid canvas
                borders = self.debug_outlines.get(idx)
                if borders:
                    for b in borders: b.visible = False
                continue
                
            canvas = self.canvases[canvas_index]
            if not canvas.get('visible', True):
                # Hide outlines for components on hidden canvases
                borders = self.debug_outlines.get(idx)
                if borders:
                    for b in borders:
                        b.visible = False
                continue

            # Determine size
            width = comp_data.get('width', 0)
            height = comp_data.get('height', 0)
            try:
                if hasattr(comp_obj, 'width') and comp_obj.width is not None and comp_obj.width > 0:
                    width = comp_obj.width
                if hasattr(comp_obj, 'height') and comp_obj.height is not None and comp_obj.height > 0:
                    height = comp_obj.height
            except Exception:
                pass

            if width <= 0 or height <= 0:
                continue

            # Determine Position (Absolute)
            canvas_abs_left = 0
            canvas_abs_top = 0
            canvas_panel = self.canvases[canvas_index]['panel']
            canvas_abs_left = getattr(canvas_panel, 'left', 0)
            canvas_abs_top = getattr(canvas_panel, 'top', 0)
            if canvas_abs_left == 0 and canvas_abs_top == 0:
                try:
                    canvas_pos = canvas_panel.get_pos()
                    canvas_abs_left = canvas_pos.x
                    canvas_abs_top = canvas_pos.y
                except Exception:
                    canvas_abs_left = 300
                    canvas_abs_top = 100

            if hasattr(comp_obj, 'get_abs_pos'):
                try:
                    abs_pos = comp_obj.get_abs_pos()
                    abs_left = abs_pos.x
                    abs_top = abs_pos.y
                except Exception:
                    acc_left, acc_top = self._get_component_accumulated_pos(idx)
                    abs_left = canvas_abs_left + acc_left
                    abs_top = canvas_abs_top + acc_top
            else:
                acc_left, acc_top = self._get_component_accumulated_pos(idx)
                abs_left = canvas_abs_left + acc_left
                abs_top = canvas_abs_top + acc_top

            # Draw Outline
            borders = self._ensure_component_outline(idx)
            
            # Style for unselected components: Gray, thinner or same thickness
            for b in borders:
                b.color = (0.7, 0.7, 0.7, 0.5) # Light gray, semi-transparent
                if hasattr(b, 'z_offset'):
                    b.z_offset = 25
                elif hasattr(b, 'set_sort'):
                    b.set_sort(25)
                b.visible = True

            t = 1.0 # Thickness

            borders[0].left = abs_left
            borders[0].top = abs_top - t
            borders[0].width = width
            borders[0].height = t

            borders[1].left = abs_left
            borders[1].top = abs_top + height
            borders[1].width = width
            borders[1].height = t

            borders[2].left = abs_left - t
            borders[2].top = abs_top
            borders[2].width = t
            borders[2].height = height

            borders[3].left = abs_left + width
            borders[3].top = abs_top
            borders[3].width = t
            borders[3].height = height

        return task.cont

    def _show_and_update_handles(self, comp_data):
        """显示并更新handles位置 - 修复坐标系不一致问题"""
        # 每次显示时重新创建手柄，确保它们在最前面
        if not hasattr(self, '_handles_recreated'):
            self._recreate_handles_on_top()
            self._handles_recreated = True
        
        # 获取组件的Canvas相对位置和尺寸
        comp_obj = comp_data['object']
        
        # 获取Canvas的绝对位置用于坐标转换
        canvas_abs_left = 0
        canvas_abs_top = 0
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas_panel = self.canvases[self.current_canvas_index]['panel']
            # 使用LUI对象的left和top属性，而不是get_pos()方法
            canvas_abs_left = getattr(canvas_panel, 'left', 0)
            canvas_abs_top = getattr(canvas_panel, 'top', 0)
            
            # 如果left/top属性不存在，尝试使用get_pos()
            if canvas_abs_left == 0 and canvas_abs_top == 0:
                try:
                    canvas_pos = canvas_panel.get_pos()
                    canvas_abs_left = canvas_pos.x
                    canvas_abs_top = canvas_pos.y
                except:
                    # 如果get_pos()也失败，使用默认值
                    canvas_abs_left = 300  # 默认Canvas位置
                    canvas_abs_top = 100
        
        # 尝试直接获取组件的绝对屏幕坐标
        if hasattr(comp_obj, 'get_abs_pos'):
            try:
                abs_pos = comp_obj.get_abs_pos()
                abs_left = abs_pos.x
                abs_top = abs_pos.y
                # print(f"使用 get_abs_pos(): {abs_left}, {abs_top}")
            except Exception as e:
                print(f"get_abs_pos() 失败: {e}")
                # Fallback to manual calculation
                acc_left, acc_top = self._get_component_accumulated_pos(self.selected_index)
                abs_left = canvas_abs_left + acc_left
                abs_top = canvas_abs_top + acc_top
        else:
            # 递归计算组件的 Canvas 相对位置（累加所有父组件的相对位置）
            # 使用 self.selected_index 似乎最安全，或者反查 comp_data
            if self.selected_index >= 0 and self.components[self.selected_index] == comp_data:
                 acc_left, acc_top = self._get_component_accumulated_pos(self.selected_index)
            else:
                 acc_left = comp_data.get('left', 0)
                 acc_top = comp_data.get('top', 0)
    
            # 转换为绝对屏幕坐标（用于手柄和边框显示）
            abs_left = canvas_abs_left + acc_left
            abs_top = canvas_abs_top + acc_top
        
        # 获取组件尺寸 - 优先从LUI对象获取实际尺寸
        comp_type = comp_data['type']
        width = comp_data.get('width', 100)
        height = comp_data.get('height', 30)
        
        # 尝试从LUI对象获取实际尺寸
        try:
            if hasattr(comp_obj, 'width') and comp_obj.width is not None:
                actual_width = comp_obj.width
                if actual_width > 0:
                    width = actual_width
            if hasattr(comp_obj, 'height') and comp_obj.height is not None:
                actual_height = comp_obj.height
                if actual_height > 0:
                    height = actual_height
        except:
            pass
        
        # 确保宽高是数值类型
        try:
            width = float(width)
            height = float(height)
        except (TypeError, ValueError):
            width = 100.0
            height = 30.0
        
        # 更新comp_data中的尺寸
        comp_data['width'] = width
        comp_data['height'] = height

        # 更新4条边框（使用绝对位置，因为边框在overlay_root下）
        # 确保 selection_box 存在
        if not self.selection_box:
            self._create_resize_handles()
            if not self.selection_box: # Still empty?
                return

        # 上边框
        self.selection_box[0].left = abs_left
        self.selection_box[0].top = abs_top - 2
        self.selection_box[0].width = width
        self.selection_box[0].height = 2
        self.selection_box[0].visible = True

        # 下边框
        self.selection_box[1].left = abs_left
        self.selection_box[1].top = abs_top + height
        self.selection_box[1].width = width
        self.selection_box[1].height = 2
        self.selection_box[1].visible = True

        # 左边框
        self.selection_box[2].left = abs_left - 2
        self.selection_box[2].top = abs_top
        self.selection_box[2].width = 2
        self.selection_box[2].height = height
        self.selection_box[2].visible = True

        # 右边框
        self.selection_box[3].left = abs_left + width
        self.selection_box[3].top = abs_top
        self.selection_box[3].width = 2
        self.selection_box[3].height = height
        self.selection_box[3].visible = True

        # 更新8个手柄位置（使用绝对位置，因为手柄在overlay_root下）
        handle_positions = [
            (abs_left - 5, abs_top - 5),                      # 左上
            (abs_left + width/2 - 5, abs_top - 5),           # 上
            (abs_left + width - 5, abs_top - 5),             # 右上
            (abs_left + width - 5, abs_top + height/2 - 5),  # 右
            (abs_left + width - 5, abs_top + height - 5),    # 右下
            (abs_left + width/2 - 5, abs_top + height - 5),  # 下
            (abs_left - 5, abs_top + height - 5),            # 左下
            (abs_left - 5, abs_top + height/2 - 5),          # 左
        ]

        for i, (h_left, h_top) in enumerate(handle_positions):
            if i < len(self.resize_handles):
                handle = self.resize_handles[i]
                handle.left = h_left
                handle.top = h_top
                handle.visible = True

    def _recreate_handles_on_top(self):
        """重新创建手柄，确保它们在最前面"""
        from Builtin.LUISprite import LUISprite
        from Builtin.LUIObject import LUIObject
        
        # 清除旧的手柄
        for handle in self.resize_handles:
            if hasattr(handle, 'remove'):
                handle.remove()
        for border in self.selection_box:
            if hasattr(border, 'remove'):
                border.remove()
        
        self.resize_handles.clear()
        self.selection_box.clear()
        
        # 重新创建边框
        for i in range(4):
            border = LUISprite(self.overlay_root, "blank", "skin")
            border.color = (0.2, 0.5, 1.0, 1.0)  # 蓝色
            border.visible = False
            
            # 设置选择框层级，在组件之上但在LUI内部
            if hasattr(border, 'z_offset'):
                border.z_offset = 40
            elif hasattr(border, 'set_sort'):
                border.set_sort(40)
            
            self.selection_box.append(border)
        
        # 重新创建手柄
        handle_positions = [
            'top-left', 'top', 'top-right', 'right',
            'bottom-right', 'bottom', 'bottom-left', 'left'
        ]
        
        for i, pos in enumerate(handle_positions):
            handle_container = LUIObject(parent=self.overlay_root)
            handle_container.width = 10
            handle_container.height = 10
            handle_container.solid = True
            handle_container.visible = False
            
            # 设置手柄层级，在选择框之上但在LUI内部
            if hasattr(handle_container, 'z_offset'):
                handle_container.z_offset = 50
            elif hasattr(handle_container, 'set_sort'):
                handle_container.set_sort(50)
            
            # 创建蓝色边框（外圈）
            handle_border = LUISprite(handle_container, "blank", "skin")
            handle_border.width = 10
            handle_border.height = 10
            handle_border.left = 0
            handle_border.top = 0
            handle_border.color = (0.2, 0.5, 1.0, 1.0)  # 蓝色边框
            
            # 创建白色圆形背景（内圈）
            handle_bg = LUISprite(handle_container, "blank", "skin")
            handle_bg.width = 8
            handle_bg.height = 8
            handle_bg.left = 1
            handle_bg.top = 1
            handle_bg.color = (1.0, 1.0, 1.0, 1.0)  # 白色
            
            # 存储手柄位置信息
            handle_container._resize_position = pos
            handle_container._resize_index = i
            handle_container._bg = handle_bg
            handle_container._border = handle_border
            
            # 绑定鼠标事件
            handle_container.bind("mousedown", lambda event, h=handle_container: self._on_handle_mousedown(event, h))
            
            self.resize_handles.append(handle_container)
        
        print("✓ 重新创建了手柄，确保在最前面")

    def _handle_resize_drag(self):
        """处理resize拖拽 - 完整功能实现，支持Shift和Alt键操作"""
        if not hasattr(self.world, 'mouseWatcherNode') or not self.world.mouseWatcherNode.hasMouse():
            return
        
        # 检查鼠标左键是否释放
        import panda3d.core as p3d
        if not self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one()):
            if self.resizing_handle:
                comp_data = self.components[self.selected_index]
                width_val = comp_data.get('width', 0)
                height_val = comp_data.get('height', 0)
                print(f"✓ 组件调整完成: width={width_val:.1f}, height={height_val:.1f}")
                
                # 更新锚点位置（如果有锚点）
                if comp_data.get('anchored_to_parent'):
                    anchor_pos = comp_data.get('anchor_position')
                    if anchor_pos:
                        self._update_component_anchor_position(self.selected_index, anchor_pos)
                
                self.resizing_handle = None
            return
        
        # 获取当前鼠标位置
        mouse_x = self.world.mouseWatcherNode.getMouseX()
        mouse_y = self.world.mouseWatcherNode.getMouseY()
        
        win_x = self.world.win.getXSize()
        win_y = self.world.win.getYSize()
        pixel_x = (mouse_x + 1) * win_x / 2
        pixel_y = (1 - mouse_y) * win_y / 2
        
        # 计算鼠标移动距离
        delta_x = pixel_x - self.resize_start_pos[0]
        delta_y = pixel_y - self.resize_start_pos[1]
        
        # 检查修饰键
        shift_held = self.world.mouseWatcherNode.is_button_down(p3d.KeyboardButton.shift())
        alt_held = self.world.mouseWatcherNode.is_button_down(p3d.KeyboardButton.alt())
        
        # 获取组件数据
        comp_data = self.components[self.selected_index]
        comp_obj = comp_data['object']
        comp_type = comp_data['type']
        
        # 获取初始边界（Canvas相对坐标）
        start_left = self.resize_start_bounds['left']
        start_top = self.resize_start_bounds['top']
        start_width = self.resize_start_bounds['width']
        start_height = self.resize_start_bounds['height']
        
        # 计算初始宽高比
        aspect_ratio = start_width / start_height if start_height > 0 else 1
        
        # 根据手柄位置计算新的边界
        handle_pos = self.resizing_handle._resize_position
        
        new_left = start_left
        new_top = start_top
        new_width = start_width
        new_height = start_height
        
        # 根据手柄位置调整尺寸
        if handle_pos == 'top-left':
            new_left = start_left + delta_x
            new_top = start_top + delta_y
            new_width = start_width - delta_x
            new_height = start_height - delta_y
        elif handle_pos == 'top':
            new_top = start_top + delta_y
            new_height = start_height - delta_y
        elif handle_pos == 'top-right':
            new_top = start_top + delta_y
            new_width = start_width + delta_x
            new_height = start_height - delta_y
        elif handle_pos == 'right':
            new_width = start_width + delta_x
        elif handle_pos == 'bottom-right':
            new_width = start_width + delta_x
            new_height = start_height + delta_y
        elif handle_pos == 'bottom':
            new_height = start_height + delta_y
        elif handle_pos == 'bottom-left':
            new_left = start_left + delta_x
            new_width = start_width - delta_x
            new_height = start_height + delta_y
        elif handle_pos == 'left':
            new_left = start_left + delta_x
            new_width = start_width - delta_x
        
        # 应用最小尺寸限制
        if new_width < self.min_size:
            if handle_pos in ['top-left', 'left', 'bottom-left']:
                new_left = start_left + start_width - self.min_size
            new_width = self.min_size
            
        if new_height < self.min_size:
            if handle_pos in ['top-left', 'top', 'top-right']:
                new_top = start_top + start_height - self.min_size
            new_height = self.min_size
        
        # Shift键：保持宽高比
        if shift_held:
            width_change = abs(new_width - start_width)
            height_change = abs(new_height - start_height)
            
            if width_change > height_change:
                new_height = new_width / aspect_ratio
                if handle_pos in ['top-left', 'top', 'top-right']:
                    new_top = start_top + start_height - new_height
            else:
                new_width = new_height * aspect_ratio
                if handle_pos in ['top-left', 'left', 'bottom-left']:
                    new_left = start_left + start_width - new_width
        
        # Alt键：从中心点缩放
        if alt_held:
            center_x = start_left + start_width / 2
            center_y = start_top + start_height / 2
            
            width_diff = new_width - start_width
            height_diff = new_height - start_height
            
            new_width = start_width + width_diff * 2
            new_height = start_height + height_diff * 2
            new_left = center_x - new_width / 2
            new_top = center_y - new_height / 2
            
            # 再次应用最小尺寸限制
            if new_width < self.min_size:
                new_width = self.min_size
                new_left = center_x - new_width / 2
            if new_height < self.min_size:
                new_height = self.min_size
                new_top = center_y - new_height / 2
        
        # 获取Canvas边界约束
        canvas_width = 800  # 默认Canvas宽度
        canvas_height = 600  # 默认Canvas高度
        
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas_panel = self.canvases[self.current_canvas_index]['panel']
            canvas_width = canvas_panel.get_width()
            canvas_height = canvas_panel.get_height()
        
        
        # 最后的安全检查，防止宽高变为负数
        if new_width < 1.0: new_width = 1.0
        if new_height < 1.0: new_height = 1.0
        
        if self.selected_index < 0:
            return

        comp_data = self.components[self.selected_index]
        parent_index = comp_data.get('parent_index')
        parent_offset_x = 0
        parent_offset_y = 0
        
        if parent_index is not None and parent_index >= 0:
             parent_offset_x, parent_offset_y = self._get_component_accumulated_pos(parent_index)

        # Update component data
        comp_data['left'] = new_left
        comp_data['top'] = new_top
        comp_data['width'] = new_width
        comp_data['height'] = new_height

        # Update component position
        child_parent = getattr(comp_obj, 'parent', None)
        parent_obj = None
        if parent_index is not None and parent_index >= 0:
            parent_obj = self.components[parent_index].get('object')

        # Check if actually parented in LUI scene graph
        is_scene_parented = (child_parent is not None and child_parent == parent_obj)

        if is_scene_parented:
            # Component is physically parented to its logical parent -> Use Relative coords
            comp_obj.left = new_left
            comp_obj.top = new_top
        else:
            # Component is physically root/canvas -> Use Absolute coords (Parent Abs + Relative)
            comp_obj.left = parent_offset_x + new_left
            comp_obj.top = parent_offset_y + new_top

        # Update component size
        try:
            if hasattr(comp_obj, 'width'):
                comp_obj.width = new_width
            if hasattr(comp_obj, 'height'):
                comp_obj.height = new_height

            if comp_type == 'Frame':
                pass
            elif comp_type in ['Plane', 'Image', 'Video']:
                sprite = comp_data.get('sprite')
                if sprite is not None:
                    sprite.width = new_width
                    sprite.height = new_height
            elif comp_type == 'Button':
                if hasattr(comp_obj, 'set_width'):
                    comp_obj.set_width(new_width)
                if hasattr(comp_obj, 'set_height'):
                    comp_obj.set_height(new_height)
                if hasattr(comp_obj, '_apply_stretch_sizes'):
                    comp_obj._apply_stretch_sizes()
            elif comp_type == 'InputField':
                if hasattr(comp_obj, 'set_width'):
                    comp_obj.set_width(new_width)
                if hasattr(comp_obj, 'set_height'):
                    comp_obj.set_height(new_height)
                if hasattr(comp_obj, 'width'):
                    comp_obj.width = new_width
                if hasattr(comp_obj, 'height'):
                    comp_obj.height = new_height
                try:
                    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%'
                        inner = getattr(layout, '_layout', None)
                        if inner is not None:
                            if hasattr(inner, 'width'):
                                inner.width = '100%'
                            if hasattr(inner, 'height'):
                                inner.height = '100%'
                        for attr in ('_sprite_left', '_sprite_mid', '_sprite_right'):
                            spr = getattr(layout, attr, None)
                            if spr is not None and hasattr(spr, 'height'):
                                spr.height = '100%'
                            if spr is not None and attr == '_sprite_mid' and hasattr(spr, 'width'):
                                spr.width = '100%'
                except Exception:
                    pass
            elif comp_type == 'Slider':
                if hasattr(comp_obj, 'set_width'):
                    comp_obj.set_width(new_width)
                if hasattr(comp_obj, 'set_height'):
                    comp_obj.set_height(new_height)

            if comp_type in ['VerticalLayout', 'HorizontalLayout']:
                self._update_layout_inner(self.selected_index)

            self._update_anchored_children(self.selected_index)

            modifier_info = []
            if shift_held:
                modifier_info.append('Shift(keep ratio)')
            if alt_held:
                modifier_info.append('Alt(center scale)')
            modifier_str = ' + '.join(modifier_info) if modifier_info else 'Normal'
        except Exception as e:
            print(f"⚠ 设置组件尺寸失败 ({comp_type}): {e}")

    def _add_to_scene_tree(self, comp_data):
        """Add a virtual node to represent the LUI component in the scene tree"""
        from panda3d.core import NodePath
        node_name = f"UI_{comp_data['type']}_{len(self.components)}"
        ui_node = NodePath(node_name)
        
        # Reparent to current canvas node if available
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas_node = self.canvases[self.current_canvas_index]['node']
            ui_node.reparent_to(canvas_node)
            comp_data['canvas_index'] = self.current_canvas_index
        else:
            ui_node.reparent_to(self.world.render)
            comp_data['canvas_index'] = None
        
        comp_data['ui_node'] = ui_node
        comp_data['parent_index'] = None
        comp_data['children_indices'] = []

    def _set_parent_child_relationship(self, child_index, parent_index, anchor_position=None, keep_world=False):
        # Set parent-child relationship
        if (child_index < 0 or child_index >= len(self.components) or
            parent_index < 0 or parent_index >= len(self.components)):
            print("Error: invalid component index")
            return

        child_data = self.components[child_index]
        parent_data = self.components[parent_index]

        if parent_data.get('type') in ['VerticalLayout', 'HorizontalLayout']:
            child_data['draggable'] = False

        if child_data.get('parent_index') == parent_index:
            print(f"Component {child_index} already under {parent_index}")
            return

        current_abs_left, current_abs_top = self._get_component_accumulated_pos(child_index)
        
        print(f"[_set_parent_child_relationship] Child #{child_index} -> Parent #{parent_index}")
        print(f"  Current absolute position: ({current_abs_left:.1f}, {current_abs_top:.1f})")

        old_parent_index = child_data.get('parent_index')
        if old_parent_index is not None and old_parent_index >= 0:
            old_parent_data = self.components[old_parent_index]
            if child_index in old_parent_data.get('children_indices', []):
                old_parent_data['children_indices'].remove(child_index)

        child_data['parent_index'] = parent_index
        if 'children_indices' not in parent_data:
            parent_data['children_indices'] = []
        if child_index not in parent_data['children_indices']:
            parent_data['children_indices'].append(child_index)

        parent_abs_left, parent_abs_top = self._get_component_accumulated_pos(parent_index)
        new_local_left = current_abs_left - parent_abs_left
        new_local_top = current_abs_top - parent_abs_top
        
        print(f"  Parent absolute position: ({parent_abs_left:.1f}, {parent_abs_top:.1f})")
        print(f"  Calculated local position: ({new_local_left:.1f}, {new_local_top:.1f})")

        # 在数据结构中存储局部坐标（相对于父组件）
        child_data['left'] = new_local_left
        child_data['top'] = new_local_top

        child_obj = child_data['object']
        parent_obj = parent_data['object']
        parent_type = parent_data.get('type')
        visual_container_types = [
            'Frame', 'Plane', 'Video',
            'ScrollableRegion', 'TabbedFrame',
            'VerticalLayout', 'HorizontalLayout'
        ]
        if parent_data.get('type') == 'ScrollableRegion':
            parent_obj = parent_data['object'].content_node
        elif parent_data.get('type') in ['VerticalLayout', 'HorizontalLayout'] and parent_data.get('layout_obj'):
            if parent_data.get('layout_wrap', True):
                parent_obj = parent_data.get('object')
            else:
                parent_obj = parent_data.get('layout_obj').cell()

        try:
            if parent_type in visual_container_types:
                child_data['visual_parent_canvas'] = False
                # 关键修改：不要调用 reparent_to()，避免破坏内部结构
                # 只更新数据结构，保持原有的父对象关系
                print(f"[_set_parent_child_relationship] Setting parent relationship (data only, no reparenting)")
            else:
                # Keep visual parent on canvas to avoid clipping, but keep logical parent
                child_data['visual_parent_canvas'] = True

            # If not preserving world position, clamp into parent bounds for visibility
            if not keep_world:
                try:
                    parent_w = parent_data.get('width')
                    parent_h = parent_data.get('height')
                    if parent_w is None and hasattr(parent_obj, 'width'):
                        parent_w = parent_obj.width
                    if parent_h is None and hasattr(parent_obj, 'height'):
                        parent_h = parent_obj.height
                    child_w = child_data.get('width')
                    child_h = child_data.get('height')
                    if child_w is None and hasattr(child_obj, 'width'):
                        child_w = child_obj.width
                    if child_h is None and hasattr(child_obj, 'height'):
                        child_h = child_obj.height
                    if parent_w and parent_h and child_w and child_h:
                        max_x = max(0.0, float(parent_w) - float(child_w))
                        max_y = max(0.0, float(parent_h) - float(child_h))
                        new_local_left = max(0.0, min(float(new_local_left), max_x))
                        new_local_top = max(0.0, min(float(new_local_top), max_y))
                        child_data['left'] = new_local_left
                        child_data['top'] = new_local_top
                except Exception:
                    pass

            # 关键修改：由于我们不实际重新父化对象，所有组件都保持相对于Canvas的绝对位置
            # 只在数据结构中记录相对于父组件的局部坐标
            if child_data.get('visual_parent_canvas'):
                # Keep world position
                try:
                    child_data['canvas_index'] = parent_data.get('canvas_index', self.current_canvas_index)
                except Exception:
                    pass
            
            # 根据keep_world参数决定是否保持世界位置
            if keep_world:
                # 保持世界位置：使用绝对坐标
                if hasattr(child_obj, 'left'):
                    child_obj.left = current_abs_left
                if hasattr(child_obj, 'top'):
                    child_obj.top = current_abs_top
                print(f"[_set_parent_child_relationship] Child position kept at absolute: ({current_abs_left:.1f}, {current_abs_top:.1f})")
            else:
                # 不保持世界位置：使用局部坐标（但由于没有实际重新父化，这会导致位置跳变）
                # 为了避免跳变，我们仍然使用绝对坐标
                if hasattr(child_obj, 'left'):
                    child_obj.left = current_abs_left
                if hasattr(child_obj, 'top'):
                    child_obj.top = current_abs_top
                print(f"[_set_parent_child_relationship] Child position set to absolute: ({current_abs_left:.1f}, {current_abs_top:.1f})")

            # Ensure child visuals are visible above parent
            if hasattr(child_obj, 'z_offset'):
                try:
                    parent_z = getattr(parent_obj, 'z_offset', 0)
                    child_obj.z_offset = float(parent_z) + 2.0
                except Exception:
                    pass

            # Special handling for Button: raise internal sprites/label above parent
            if child_data.get('type') == 'Button':
                try:
                    layout = getattr(child_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, 'z_offset'):
                                spr.z_offset = float(getattr(child_obj, 'z_offset', 0)) + 1.0
                    lbl = getattr(child_obj, '_label', None)
                    if lbl is not None and hasattr(lbl, 'z_offset'):
                        lbl.z_offset = float(getattr(child_obj, 'z_offset', 0)) + 2.0
                    # Ensure label visible
                    if lbl is not None and hasattr(lbl, 'show'):
                        lbl.show()
                except Exception:
                    pass
            
            # Special handling for Frame: ensure visibility
            elif child_data.get('type') == 'Frame':
                try:
                    if hasattr(child_obj, 'show'):
                        child_obj.show()
                    if hasattr(child_obj, 'visible'):
                        child_obj.visible = True
                    print(f"[_set_parent_child_relationship] Frame visibility ensured")
                except Exception as e:
                    print(f"[_set_parent_child_relationship] Error ensuring Frame visibility: {e}")
            
            # Special handling for Plane, Image, Video: ensure sprite visibility
            elif child_data.get('type') in ['Plane', 'Image', 'Video']:
                try:
                    spr = child_data.get('sprite')
                    if spr is not None:
                        if hasattr(spr, 'show'):
                            spr.show()
                        if hasattr(spr, 'visible'):
                            spr.visible = True
                        
                        # Ensure sprite color is not transparent
                        if child_data.get('type') == 'Plane':
                            color = child_data.get('color', (1, 1, 1, 1))
                            if hasattr(spr, 'color'):
                                spr.color = color
                    
                    print(f"[_set_parent_child_relationship] {child_data.get('type')} sprite visibility ensured")
                except Exception as e:
                    print(f"[_set_parent_child_relationship] Error ensuring {child_data.get('type')} visibility: {e}")

            child_sprite = child_data.get('sprite')
            if child_sprite is not None:
                try:
                    if hasattr(child_sprite, 'show'):
                        child_sprite.show()
                    # Ensure sprite is attached to the child object
                    if hasattr(child_sprite, 'parent') and child_sprite.parent != child_obj:
                        child_sprite.parent = child_obj
                    # Restore sprite color if stored
                    if hasattr(child_sprite, 'color') and 'color' in child_data:
                        child_sprite.color = child_data.get('color', child_sprite.color)
                    if hasattr(child_sprite, 'z_offset'):
                        parent_sprite = parent_data.get('sprite')
                        parent_sprite_z = 0.0
                        if parent_sprite is not None and hasattr(parent_sprite, 'z_offset'):
                            parent_sprite_z = float(parent_sprite.z_offset)
                        child_sprite.z_offset = max(parent_sprite_z + 1.0, float(getattr(child_obj, 'z_offset', 0)) + 1.0)
                except Exception:
                    pass
            # Keep child visible above parent visuals
            if hasattr(child_obj, 'z_offset'):
                parent_z = getattr(parent_obj, 'z_offset', 0)
                try:
                    child_obj.z_offset = float(parent_z) + 2.0
                except Exception:
                    pass
            # Ensure child sprite renders above parent visuals
            child_sprite = child_data.get('sprite')
            if child_sprite is not None and hasattr(child_sprite, 'z_offset'):
                parent_sprite_z = 0
                try:
                    parent_sprite = parent_data.get('sprite')
                    if parent_sprite is not None and hasattr(parent_sprite, 'z_offset'):
                        parent_sprite_z = float(parent_sprite.z_offset)
                except Exception:
                    parent_sprite_z = 0
                try:
                    child_sprite.z_offset = max(float(parent_sprite_z) + 1.0, float(getattr(child_obj, 'z_offset', 0)) + 1.0)
                except Exception:
                    pass
            if hasattr(child_obj, 'show'):
                child_obj.show()

        except Exception as e:
            print(f"Reparenting error: {e}")

        # Ensure canvas_index follows parent canvas (visibility)
        parent_canvas = parent_data.get('canvas_index')
        if parent_canvas is not None:
            self._update_canvas_index_recursive(child_index, parent_canvas)

        if parent_data.get('type') in ['HorizontalLayout', 'VerticalLayout'] and parent_data.get('layout_wrap', True):
            self._apply_wrap_layout(parent_index)

        print(f"Set parent: child #{child_index} -> parent #{parent_index}")


    def _set_parent_root(self, child_index, keep_world=False):
        """Detach a component from its parent and move it to root (canvas)."""
        if child_index < 0 or child_index >= len(self.components):
            print("Error: invalid component index")
            return

        child_data = self.components[child_index]
        
        # 保存当前的累积位置（相对于Canvas的绝对位置）
        current_abs_left, current_abs_top = self._get_component_accumulated_pos(child_index)
        
        print(f"[_set_parent_root] Component #{child_index} current accumulated pos: ({current_abs_left:.1f}, {current_abs_top:.1f})")

        old_parent_index = child_data.get('parent_index')
        if old_parent_index is not None and old_parent_index >= 0:
            old_parent_data = self.components[old_parent_index]
            if child_index in old_parent_data.get('children_indices', []):
                old_parent_data['children_indices'].remove(child_index)

        # 更新数据结构
        child_data['parent_index'] = None
        child_data['visual_parent_canvas'] = False
        child_data['draggable'] = True

        # 获取Canvas panel
        parent_obj = None
        canvas_index = self.current_canvas_index
        if canvas_index is not None and canvas_index >= 0 and canvas_index < len(self.canvases):
            parent_obj = self.canvases[canvas_index]['panel']
            child_data['canvas_index'] = canvas_index
        else:
            parent_obj = getattr(self, 'overlay_root', None) or getattr(self.world, 'render', None)

        child_obj = child_data.get('object')
        
        # 关键修改：对于 Button 等复杂组件，不要重新父化！
        # 只更新位置和数据结构
        if child_obj is not None:
            # 更新位置数据
            if keep_world:
                child_data['left'] = current_abs_left
                child_data['top'] = current_abs_top
                
                # 直接设置对象位置，不改变父对象
                if hasattr(child_obj, 'set_pos'):
                    child_obj.set_pos(current_abs_left, current_abs_top)
                else:
                    if hasattr(child_obj, 'left'):
                        child_obj.left = current_abs_left
                    if hasattr(child_obj, 'top'):
                        child_obj.top = current_abs_top
                        
                print(f"[_set_parent_root] Set position to: ({current_abs_left:.1f}, {current_abs_top:.1f}) without reparenting")
            else:
                # 不保持世界位置：使用原有的局部坐标
                if hasattr(child_obj, 'left'):
                    child_obj.left = child_data.get('left', 0)
                if hasattr(child_obj, 'top'):
                    child_obj.top = child_data.get('top', 0)

        # 确保可见性
        if child_obj is not None:
            try:
                if hasattr(child_obj, 'show'):
                    child_obj.show()
                elif hasattr(child_obj, 'visible'):
                    child_obj.visible = True
            except Exception:
                pass

        # 对于 Button，确保内部组件可见
        comp_type = child_data.get('type')
        if comp_type == 'Button' and child_obj is not None:
            try:
                layout = getattr(child_obj, '_layout', None)
                lbl = getattr(child_obj, '_label', None)
                
                if layout is not None:
                    if hasattr(layout, 'show'):
                        layout.show()
                    if hasattr(layout, 'visible'):
                        layout.visible = True
                
                if lbl is not None:
                    if hasattr(lbl, 'show'):
                        lbl.show()
                    if hasattr(lbl, 'visible'):
                        lbl.visible = True
                    
                    # 重新设置文本
                    current_text = child_data.get('text', 'Button')
                    if hasattr(lbl, 'set_text'):
                        lbl.set_text(current_text)
                    elif hasattr(lbl, 'text'):
                        lbl.text = current_text
                
                print(f"[_set_parent_root] Button components visibility ensured")
            except Exception as e:
                print(f"[_set_parent_root] Error ensuring button visibility: {e}")
        
        # 对于 Plane, Image, Video - 确保内部 sprite 可见
        elif comp_type in ['Plane', 'Image', 'Video'] and child_obj is not None:
            try:
                spr = child_data.get('sprite')
                if spr is not None:
                    if hasattr(spr, 'show'):
                        spr.show()
                    if hasattr(spr, 'visible'):
                        spr.visible = True
                    
                    # 确保 sprite 的颜色不是透明的
                    if comp_type == 'Plane':
                        color = child_data.get('color', (1, 1, 1, 1))
                        if hasattr(spr, 'color'):
                            spr.color = color
                
                print(f"[_set_parent_root] {comp_type} sprite visibility ensured")
            except Exception as e:
                print(f"[_set_parent_root] Error ensuring {comp_type} visibility: {e}")
        
        # 对于 Frame - 直接确保可见
        elif comp_type == 'Frame' and child_obj is not None:
            try:
                if hasattr(child_obj, 'show'):
                    child_obj.show()
                if hasattr(child_obj, 'visible'):
                    child_obj.visible = True
                print(f"[_set_parent_root] Frame visibility ensured")
            except Exception as e:
                print(f"[_set_parent_root] Error ensuring Frame visibility: {e}")

        # Update canvas index recursively
        self._update_canvas_index_recursive(child_index, child_data.get('canvas_index'))
        
        # 调试输出：确认组件状态
        print(f"[_set_parent_root] Component #{child_index} ({child_data.get('name', 'Unknown')}) moved to root (data only)")
        print(f"  - Position: ({child_data.get('left', 0):.1f}, {child_data.get('top', 0):.1f})")
        print(f"  - Size: ({child_data.get('width', 0):.1f}, {child_data.get('height', 0):.1f})")
        print(f"  - Canvas index: {child_data.get('canvas_index')}")
        print(f"  - Visible: {getattr(child_obj, 'visible', 'N/A') if child_obj else 'N/A'}")
        print(f"  - Parent (unchanged): {getattr(child_obj, 'parent', 'N/A') if child_obj else 'N/A'}")
    def create_child_component(self, parent_index, comp_type, anchor_position=None):
        # Create child component under parent
        print("\n=== Create child component ===")
        print(f"Parent index: {parent_index}, Type: {comp_type}")

        if parent_index < 0 or parent_index >= len(self.components):
            print("Error: invalid parent index")
            return None

        parent_data = self.components[parent_index]
        parent_obj = parent_data['object']
        if parent_data.get('type') == 'ScrollableRegion':
            parent_obj = parent_data['object'].content_node
        elif parent_data.get('type') in ['VerticalLayout', 'HorizontalLayout'] and parent_data.get('layout_obj'):
            if parent_data.get('layout_wrap', True):
                parent_obj = parent_data.get('object')
            else:
                parent_obj = parent_data.get('layout_obj').cell()

        parent_width = parent_data.get('width', 100)
        parent_height = parent_data.get('height', 30)

        comp_sizes = {
            'Button': (100, 30),
            'Text': (80, 20),
            'Label': (80, 20),
            'CheckBox': (120, 20),
            'InputField': (200, 24),
            'Slider': (200, 16),
            'Frame': (300, 200),
            'Plane': (100, 100),
            'Image': (100, 100),
            'Video': (320, 240),
            'Progressbar': (200, 30),
            'Selectbox': (200, 30),
            'ScrollableRegion': (200, 200),
            'TabbedFrame': (300, 200),
            'VerticalLayout': (300, 200),
            'HorizontalLayout': (300, 200),
        }

        child_w, child_h = comp_sizes.get(comp_type, (80, 20))
        child_x = (parent_width - child_w) / 2
        child_y = (parent_height - child_h) / 2

        if parent_data.get('type') in ['VerticalLayout', 'HorizontalLayout']:
            child_x = 0
            child_y = 0

        child_obj = None
        try:
            if comp_type == 'Button':
                child_obj = self.luiFunction.create_button(self, text="ChildButton", x=child_x, y=child_y, parent=parent_obj)
            elif comp_type in ['Text', 'Label']:
                child_obj = self.luiFunction.create_label(self, text="ChildText", x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'CheckBox':
                child_obj = self.luiFunction.create_checkbox(self, label="ChildCheckbox", x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'InputField':
                child_obj = self.luiFunction.create_input_field(self, text="ChildInput", x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Slider':
                child_obj = self.luiFunction.create_slider(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Frame':
                child_obj = self.luiFunction.create_frame(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Plane':
                child_obj = self.luiFunction.create_plane(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Image':
                child_obj = self.luiFunction.create_image(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Video':
                child_obj = self.luiFunction.create_video(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Progressbar':
                child_obj = self.luiFunction.create_progressbar(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'Selectbox':
                child_obj = self.luiFunction.create_selectbox(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'ScrollableRegion':
                child_obj = self.luiFunction.create_scrollable_region(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'TabbedFrame':
                child_obj = self.luiFunction.create_tabbed_frame(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'VerticalLayout':
                child_obj = self.luiFunction.create_vertical_layout(self, x=child_x, y=child_y, parent=parent_obj)
            elif comp_type == 'HorizontalLayout':
                child_obj = self.luiFunction.create_horizontal_layout(self, x=child_x, y=child_y, parent=parent_obj)
            else:
                print(f"Error: unsupported component type {comp_type}")
                return None

        except Exception as e:
            print(f"Error: create component failed: {e}")
            import traceback
            traceback.print_exc()
            return None

        if child_obj:
            child_index = -1
            for i, comp in enumerate(self.components):
                if comp.get('object') == child_obj:
                    child_index = i
                    break

            if child_index >= 0:
                child_data = self.components[child_index]
                if parent_data.get('type') in ['HorizontalLayout', 'VerticalLayout'] and parent_data.get('layout_wrap', True):
                    self._apply_wrap_layout(parent_index)

                if parent_data.get('type') in ['VerticalLayout', 'HorizontalLayout']:
                    child_data['draggable'] = False

                child_data['parent_index'] = parent_index
                if 'children_indices' not in parent_data:
                    parent_data['children_indices'] = []
                if child_index not in parent_data['children_indices']:
                    parent_data['children_indices'].append(child_index)

                print(f"Linked child #{child_index} -> parent #{parent_index}")
                return child_index
        else:
            print("Child creation failed")
        return None

    def _get_descendants(self, idx):
        """Return set of all descendant indices for a component."""
        desc = set()
        if idx < 0 or idx >= len(self.components):
            return desc
        comp = self.components[idx]
        for c_idx in comp.get('children_indices', []):
            if c_idx in desc:
                continue
            desc.add(c_idx)
            desc.update(self._get_descendants(c_idx))
        return desc

    def _update_canvas_index_recursive(self, idx, canvas_index):
        """Update canvas_index for component and its descendants."""
        if idx < 0 or idx >= len(self.components):
            return
        comp = self.components[idx]
        comp['canvas_index'] = canvas_index
        for c_idx in comp.get('children_indices', []):
            self._update_canvas_index_recursive(c_idx, canvas_index)

    def _reorder_list(self, items, src, target):
        """Move src before target within items list."""
        if src == target:
            return items
        if src not in items or target not in items:
            return items
        items = list(items)
        items.remove(src)
        insert_at = items.index(target)
        items.insert(insert_at, src)
        return items

    def _sync_canvas_children(self, parent_index):
        """Update positions for children that keep visual parent on canvas."""
        if parent_index < 0 or parent_index >= len(self.components):
            return
        
        # Get parent's absolute position
        parent_abs_left, parent_abs_top = self._get_component_accumulated_pos(parent_index)
        parent_data = self.components[parent_index]
        parent_obj = parent_data.get('object')

        for child_idx in parent_data.get('children_indices', []):
            if child_idx < 0 or child_idx >= len(self.components):
                continue
            child = self.components[child_idx]
            child_obj = child.get('object')
            
            if child_obj is None:
                continue

            # Check actual parenting
            child_parent = getattr(child_obj, 'parent', None)
            is_scene_parented = (child_parent is not None and child_parent == parent_obj)

            if not is_scene_parented:
                # If child is NOT physically parented to the parent object (e.g. it is on Canvas),
                # we MUST manually update its absolute position to follow the parent.
                abs_left = parent_abs_left + float(child.get('left', 0))
                abs_top = parent_abs_top + float(child.get('top', 0))
                
                if hasattr(child_obj, 'left'):
                    child_obj.left = abs_left
                if hasattr(child_obj, 'top'):
                    child_obj.top = abs_top
                if hasattr(child_obj, 'set_pos'):
                    child_obj.set_pos(abs_left, abs_top)
            
            # Recurse for grandchildren
            self._sync_canvas_children(child_idx)





    def draw_editor(self):
        """Draw the LUI Editor Window"""
        if not self.lui_enabled or not self.show_editor:
            return

        imgui.set_next_window_size((350, 600), imgui.Cond_.first_use_ever)
        with imgui_ctx.begin("LUI编辑器", True) as (opened, show):
            self.show_editor = opened
            if not show:
                return

            # Play/Stop
            if imgui.button("Play" if not self.play_mode else "Stop", (120, 25)):
                self.play_mode = not self.play_mode
                if self.play_mode:
                    # Clear editor-only state when entering play mode
                    self.selected_index = -1
                    self.dragging_comp = None
                    self.pending_drag_comp = None
                    self.pending_drag_index = -1
                    self.pending_drag_start_mouse = None
                    self.pending_drag_start_abs = None
                    self.pending_drag_start_parent = None
                    self._hide_resize_handles()
                # Keep LUI input enabled for runtime UI interaction
                self.set_input_enabled(True)
            imgui.same_line()
            imgui.text_colored((0.2, 1.0, 0.2, 1.0) if self.play_mode else (1.0, 0.8, 0.2, 1.0),
                               "Running" if self.play_mode else "Editing")
            imgui.separator()

            # In play mode, show toolbar only
            if self.play_mode:
                return
            
            # Canvas Management
            imgui.text("Canvas 管理")
            imgui.separator()
            
            if imgui.button("创建新Canvas", (150, 25)):
                self.luiFunction.create_canvas(self)
            
            imgui.same_line()
            
            # 显示当前Canvas信息
            if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
                current_canvas_name = self.canvases[self.current_canvas_index]['name']
                imgui.text_colored((0.0, 1.0, 0.0, 1.0), f"当前: {current_canvas_name}")
            else:
                imgui.text_colored((1.0, 0.0, 0.0, 1.0), "无Canvas")
            
            if len(self.canvases) > 0:
                canvas_names = [c['name'] for c in self.canvases]
                if self.current_canvas_index >= len(canvas_names):
                    self.current_canvas_index = len(canvas_names) - 1
                
                changed, new_index = imgui.combo("选择Canvas", self.current_canvas_index, canvas_names)
                if changed:
                    self.switch_canvas(new_index)
                
                # Canvas Properties
                if self.current_canvas_index >= 0:
                    canvas = self.canvases[self.current_canvas_index]
                    panel = canvas['panel']
                    imgui.indent()
                    
                    # Visibility
                    is_visible = canvas.get('visible', True)
                    changed, visible = imgui.checkbox("显示Canvas", is_visible)
                    if changed:
                        canvas['visible'] = visible
                        if visible:
                            panel.show()
                        else:
                            panel.hide()
                            
                    
                    # Fill Mode & Margins
                    is_fill_mode = canvas.get('fill_mode', False)
                    imgui.separator()
                    changed, fill_mode = imgui.checkbox("场景填充模式", is_fill_mode)
                    if changed:
                        canvas['fill_mode'] = fill_mode
                        if fill_mode:
                            # 刚切换到填充模式时，更新一次
                            self._update_canvas_geometry(canvas)
                            
                    if fill_mode:
                        imgui.text("场景边距设置 (调整以匹配视口)")
                        margins = canvas.get('margins', {'left':0,'right':0,'top':0,'bottom':0})
                        geometry_changed = False
                        
                        changed, margins['left'] = imgui.drag_float("左边距", margins['left'], 1.0, 0, 1000)
                        if changed: geometry_changed = True
                            
                        changed, margins['right'] = imgui.drag_float("右边距", margins['right'], 1.0, 0, 1000)
                        if changed: geometry_changed = True
                            
                        changed, margins['top'] = imgui.drag_float("顶边距", margins['top'], 1.0, 0, 500)
                        if changed: geometry_changed = True
                            
                        changed, margins['bottom'] = imgui.drag_float("底边距", margins['bottom'], 1.0, 0, 500)
                        if changed: geometry_changed = True
                            
                        if geometry_changed:
                            canvas['margins'] = margins
                            self._update_canvas_geometry(canvas)
                    else:
                        # Manual Position
                        curr_pos_val = panel.get_pos()
                        curr_pos = (curr_pos_val.x, curr_pos_val.y)
                        
                        # 固定开关
                        is_fixed = canvas.get('fixed', False)
                        changed, fixed = imgui.checkbox("锁定位置 (禁止拖动)", is_fixed)
                        if changed:
                            canvas['fixed'] = fixed
                            
                        if not is_fixed:
                            changed, pos = imgui.drag_float2("Canvas位置", curr_pos)
                            if changed:
                                panel.set_pos(pos[0], pos[1])
                            
                        
                        # Manual Size
                        curr_size = (panel.get_width(), panel.get_height())
                        changed, size = imgui.drag_float2("Canvas尺寸", curr_size, 1, 100, 4000)
                        if changed:
                            panel.width = size[0]
                            panel.height = size[1]
                            # 同时更新背景尺寸
                            if 'background' in canvas:
                                bg = canvas['background']
                                bg.width = size[0]
                                bg.height = size[1]
                                
                    # Canvas Color
                    if 'background' in canvas:
                        imgui.separator()
                        bg = canvas['background']
                        # 获取当前颜色 (RGBA)
                        current_color = bg.color
                        # 转换为可编辑的格式 (list)
                        edit_color = [current_color[0], current_color[1], current_color[2], current_color[3]]
                        
                        changed, new_color = imgui.color_edit4("背景颜色/透明度", edit_color)
                        if changed:
                            bg.color = (new_color[0], new_color[1], new_color[2], new_color[3])
                                
                    # Title settings
                    if 'title_label' in canvas:
                        imgui.separator()
                        imgui.text("标题设置")
                        title = canvas['title_label']
                        
                        # Visible
                        is_title_visible = title.visible
                        changed, title_visible = imgui.checkbox("显示标题", is_title_visible)
                        if changed:
                            try:
                                if title_visible:
                                    title.show()
                                else:
                                    title.hide()
                            except:
                                title.visible = title_visible

                        # Text content
                        curr_text = title.text
                        changed, new_text = imgui.input_text("标题文本", curr_text, 128)
                        if changed:
                            title.text = new_text

                        # Position
                        curr_pos_val = title.get_pos()
                        curr_pos = (curr_pos_val.x, curr_pos_val.y)
                        changed, new_pos = imgui.drag_float2("标题位置", curr_pos)
                        if changed:
                            title.set_pos(new_pos[0], new_pos[1])
                            
                    imgui.unindent()
            else:
                imgui.text_colored((1, 1, 0, 1), "请先创建一个Canvas")
            
            imgui.spacing()
            imgui.text("创建组件")
            imgui.separator()
            
            if len(self.canvases) == 0:
                imgui.begin_disabled()
            
            # Component Type Selection
            component_types = ["按钮 (Button)", "文本 (Text)", "复选框 (CheckBox)", "输入框 (InputField)",
                               "滑块 (Slider)", "框架 (Frame)","面板 (Plane)","图片 (Image)","视频 (Video)",
                               "进度条 (Progressbar)", "下拉框 (Selectbox)", "滚动区域 (ScrollableRegion)", "标签页 (TabbedFrame)",
                               "垂直布局组 (VerticalLayout)", "水平布局组 (HorizontalLayout)"]
            
            changed, self._selected_type_index = imgui.combo("组件类型", self._selected_type_index, component_types)
            
            if imgui.button("创建组件", (120, 25)):
                self._create_component_by_type(self._selected_type_index)
            
            if len(self.canvases) == 0:
                imgui.end_disabled()
                
            imgui.spacing()
            imgui.text(f"已创建组件 ({len(self.components)})")
            imgui.separator()
            
            # Component Tree Helper
            # self.draw_component_tree()
            
            imgui.spacing()
            imgui.separator()
            
            # Anchor Popup Handling
            self._handle_anchor_popup()
            
    def draw_component_tree(self):
        """Draw the component tree (can be used in other windows)"""
        # Track tree item rects for drop hit-testing (allows drop on earlier items)
        self._tree_item_rects = {}

        def _store_item_rect(item_key):
            try:
                rmin = imgui.get_item_rect_min()
                rmax = imgui.get_item_rect_max()
                self._tree_item_rects[item_key] = (rmin, rmax)
            except Exception:
                pass

        def _hit_test_tree_item(mx, my):
            for key, (rmin, rmax) in self._tree_item_rects.items():
                try:
                    min_x = rmin.x
                    min_y = rmin.y
                    max_x = rmax.x
                    max_y = rmax.y
                except Exception:
                    min_x = rmin[0]
                    min_y = rmin[1]
                    max_x = rmax[0]
                    max_y = rmax[1]
                if min_x <= mx <= max_x and min_y <= my <= max_y:
                    return key
            return None

        # Root drop target
        if imgui.selectable("ROOT", self.selected_index < 0):
            # ????????
            pass
        _store_item_rect(-1)

        def render_component_tree(idx):
            if idx >= len(self.components): return True
            comp = self.components[idx]
            comp_type = comp.get('type', 'Unknown')
            
            label = f"{comp.get('name', 'N/A')}"
            
            # Determine flags
            flags = imgui.TreeNodeFlags_.open_on_arrow | imgui.TreeNodeFlags_.open_on_double_click | imgui.TreeNodeFlags_.default_open
            if self.selected_index == idx:
                flags |= imgui.TreeNodeFlags_.selected
            
            # Check for children
            children = list(comp.get('children_indices', []))
            if not children:
                flags |= imgui.TreeNodeFlags_.leaf
            else:
                label += f" ({len(children)})"
            
            # Draw tree node
            is_open = imgui.tree_node_ex(f"##node_{idx}", flags, label)
            _store_item_rect(idx)

            # Custom drag/drop (avoid payload capsule issues)
            if imgui.is_item_active() and imgui.is_mouse_dragging(0):
                self._tree_drag_src = idx
                imgui.begin_tooltip()
                imgui.text(f"Move: {label}")
                imgui.end_tooltip()

            # Handle selection
            if imgui.is_item_clicked():
                self.selected_index = idx
                # Clear 3D scene selection
                if hasattr(self.world, 'selection') and self.world.selection:
                    self.world.selection.selectedNode = None
                    self.world.selection.clearSelectionBox()
                    self.world.selection.clearGizmo()
            
            # Right-click menu
            if imgui.begin_popup_context_item(f"##comp_ctx_{idx}"):
                if imgui.menu_item("删除", "", False)[0]:
                    self.luiFunction.delete_component(self, idx)
                    imgui.end_popup()
                    if is_open: imgui.tree_pop()
                    return False
                
                imgui.separator()
                if imgui.begin_menu("创建子组件"):
                    child_types = ['Button', 'Text', 'CheckBox', 'InputField', 'Slider', 'Frame', 'Plane','Image', 'Video','Progressbar','Selectbox','ScrollableRegion','TabbedFrame', 'VerticalLayout', 'HorizontalLayout']
                    cn_names = ['按钮', '标签', '复选框', '输入框', '滑块', '框架', '面板', '图片', '视频','进度条','下拉框','滚动区域','标签页', '垂直布局组', '水平布局组']
                    for ct_idx, ct in enumerate(child_types):
                        if imgui.menu_item(f"{cn_names[ct_idx]}", "", False)[0]:
                            self.create_child_component(idx, ct)
                    imgui.end_menu()
                imgui.end_popup()
            
            # Draw children if open
            if is_open:
                for child_idx in children:
                    # After a deletion, the list might have shifted, 
                    # so we check bounds again or just return False to stop this frame's recursion
                    if child_idx < len(self.components):
                        if not render_component_tree(child_idx):
                            # If a child (or sub-child) was deleted, indices shifted. 
                            # Stopping current rendering frame's branch is safest.
                            imgui.tree_pop()
                            return False
                imgui.tree_pop()
            return True

        # Find root components
        roots = []
        for i, comp in enumerate(self.components):
            p_idx = comp.get('parent_index')
            if p_idx is None or p_idx < 0:
                roots.append(i)

        # Maintain root order for sorting
        if not self.root_order:
            self.root_order = list(roots)
        else:
            self.root_order = [r for r in self.root_order if r in roots]
            for r in roots:
                if r not in self.root_order:
                    self.root_order.append(r)

        # Render roots
        for root_idx in self.root_order:
            if not render_component_tree(root_idx):
                break

        # Handle tree drop after full render (allows drop on earlier items)
        if self._tree_drag_src is not None and imgui.is_mouse_released(0):
            src_idx = self._tree_drag_src
            self._tree_drag_src = None
            mx, my = imgui.get_mouse_pos()
            target_idx = _hit_test_tree_item(mx, my)
            if target_idx is None:
                target_idx = -1
            if 0 <= src_idx < len(self.components):
                if target_idx == -1:
                    self._set_parent_root(src_idx, keep_world=True)
                elif target_idx != src_idx:
                    if target_idx in self._get_descendants(src_idx):
                        print("Invalid drop: cannot parent to descendant")
                    else:
                        src_parent = self.components[src_idx].get('parent_index', -1)
                        tgt_parent = self.components[target_idx].get('parent_index', -1)
                        io = imgui.get_io()
                        want_reorder = bool(getattr(io, 'key_shift', False))
                        if want_reorder and src_parent == tgt_parent:
                            if src_parent is None or src_parent < 0:
                                if not self.root_order:
                                    self.root_order = [i for i, c in enumerate(self.components) if c.get('parent_index') is None or c.get('parent_index') < 0]
                                self.root_order = self._reorder_list(self.root_order, src_idx, target_idx)
                            else:
                                parent_data = self.components[src_parent]
                                children = parent_data.get('children_indices', [])
                                parent_data['children_indices'] = self._reorder_list(children, src_idx, target_idx)
                        else:
                            self._set_parent_child_relationship(src_idx, target_idx, keep_world=True)

        # Clear drag state if released outside any item
        if self._tree_drag_src is not None and imgui.is_mouse_released(0):
            self._tree_drag_src = None
            
            imgui.spacing()
            imgui.separator()
            
            # Property Editing
            if self.selected_index >= 0 and self.selected_index < len(self.components):
                imgui.text("属性编辑")
                imgui.separator()
                self.luiFunction._draw_component_properties(self,self.selected_index)
            else:
                imgui.text_disabled("未选中任何组件")
            
            # Anchor popup
            if self.anchor_popup_open:
                imgui.open_popup("选择锚点位置")
            
            if imgui.begin_popup("选择锚点位置"):
                imgui.text("选择锚点位置:")
                
                # 中文显示名称
                anchor_names = {
                    'top-left': '左上', 'top-center': '中上', 'top-right': '右上',
                    'middle-left': '左中', 'center': '中心', 'middle-right': '右中',
                    'bottom-left': '左下', 'bottom-center': '中下', 'bottom-right': '右下'
                }
                
                anchor_positions = [
                    ['top-left', 'top-center', 'top-right'],
                    ['middle-left', 'center', 'middle-right'],
                    ['bottom-left', 'bottom-center', 'bottom-right']
                ]
                
                for row in anchor_positions:
                    for i, pos in enumerate(row):
                        button_text = anchor_names.get(pos, pos)
                        if imgui.button(f"{button_text}##popup_{pos}", (50, 25)):
                            # 使用新的锚点更新方法
                            if hasattr(self, '_temp_selected_index_for_anchor'):
                                selected_idx = self._temp_selected_index_for_anchor
                                self._update_component_anchor_position(selected_idx, pos)
                            
                            self.anchor_popup_open = False
                            if hasattr(self, '_temp_selected_index_for_anchor'):
                                delattr(self, '_temp_selected_index_for_anchor')
                            imgui.close_current_popup()
                        if i < len(row) - 1:
                            imgui.same_line()
                
                imgui.spacing()
                if imgui.button("取消"):
                    self.anchor_popup_open = False
                    if hasattr(self, '_temp_selected_index_for_anchor'):
                        delattr(self, '_temp_selected_index_for_anchor')
                    imgui.close_current_popup()
                
                imgui.end_popup()

    def _create_component_by_type(self, type_index):
        """Create LUI component by index - 默认生成在Canvas中心"""
        
        # 1. 确定生成位置 (默认 Canvas 中心)
        canvas_relative_x = 100
        canvas_relative_y = 100
        
        if self.current_canvas_index >= 0 and self.current_canvas_index < len(self.canvases):
            canvas_panel = self.canvases[self.current_canvas_index]['panel']
            canvas_width = canvas_panel.get_width()
            canvas_height = canvas_panel.get_height()
            
            # 根据组件类型预测尺寸，以便进行中心点对齐
            # 注意：这些是 lui_function.py 中定义的默认值
            comp_sizes = {
                0: (100, 30),  # Button
                1: (80, 20),   # Text
                2: (120, 20),  # Checkbox
                3: (200, 24),  # InputField
                4: (200, 16),  # Slider
                5: (300, 200), # Frame
                6: (100, 100), # Plane
                7: (100, 100), # Image
                8: (320, 240),  # Video
                9: (200, 30),   # Progressbar
                10: (200, 30),  # Selectbox
                11: (200, 200), # ScrollableRegion
                12: (300, 200), # TabbedFrame
                13: (300, 200), # VerticalLayout
                14: (300, 200)  # HorizontalLayout
            }

            
            w, h = comp_sizes.get(type_index, (100, 100))
            
            # 计算中心位置
            canvas_relative_x = (canvas_width - w) / 2
            canvas_relative_y = (canvas_height - h) / 2
            
            # 确保不超出合理范围 (防止 Canvas 太小时位置为负)
            canvas_relative_x = max(10, canvas_relative_x)
            canvas_relative_y = max(10, canvas_relative_y)
        
        try:
            if type_index == 0:  # Button
                self.luiFunction.create_button(self, text="New Button", x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 1:  # Text
                self.luiFunction.create_label(self, text="New Text", x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 2:  # Checkbox
                self.luiFunction.create_checkbox(self, label="Check box", x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 3:  # InputField
                self.luiFunction.create_input_field(self, text="", x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 4:  # Slider
                self.luiFunction.create_slider(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 5:  # Frame
                self.luiFunction.create_frame(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 6:  # Plane
                self.luiFunction.create_plane(self, x=canvas_relative_x, y=canvas_relative_y, color=(0.8, 0.8, 0.8, 1))
            elif type_index == 7:  # Image
                self.luiFunction.create_image(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 8:  # Video
                self.luiFunction.create_video(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 9:  # Progressbar
                self.luiFunction.create_progressbar(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 10: # Selectbox
                self.luiFunction.create_selectbox(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 11: # ScrollableRegion
                self.luiFunction.create_scrollable_region(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 12: # TabbedFrame
                self.luiFunction.create_tabbed_frame(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 13: # VerticalLayout
                self.luiFunction.create_vertical_layout(self, x=canvas_relative_x, y=canvas_relative_y)
            elif type_index == 14: # HorizontalLayout
                self.luiFunction.create_horizontal_layout(self, x=canvas_relative_x, y=canvas_relative_y)
            
            print(f"✓ 创建组件成功，已重定向至 Canvas 中心: ({canvas_relative_x:.1f}, {canvas_relative_y:.1f})")
            if hasattr(self.world, 'updateSceneTree'):
                self.world.updateSceneTree()
        except Exception as e:
            print(f"创建组件失败: {str(e)}")



    def set_component_anchor(self, comp_index, anchor_position):
        """设置组件锚点 - 保持向后兼容"""
        self._update_component_anchor_position(comp_index, anchor_position)

    def _update_anchored_children(self, parent_index):
        """Recursively update anchored/fill children."""
        if parent_index < 0 or parent_index >= len(self.components):
            return

        print(f"[_update_anchored_children] Updating children of #{parent_index}")
        parent_data = self.components[parent_index]
        children = parent_data.get('children_indices', [])
        for child_idx in children:
            if child_idx < len(self.components):
                child_data = self.components[child_idx]
                # Fill layout has priority over anchored positioning
                if child_data.get('layout_mode') == 'fill':
                    print(f"  -> Child #{child_idx} is FILL mode")
                    self._apply_fill_layout(child_idx)
                elif child_data.get('anchored_to_parent'):
                    anchor_pos = child_data.get('anchor_position')
                    manual_offset = (
                        child_data.get('anchor_manual_offset_x', 0.0),
                        child_data.get('anchor_manual_offset_y', 0.0)
                    )
                    if anchor_pos:
                        self._update_component_anchor_position(child_idx, anchor_pos, manual_offset)

                # Recurse into children
                self._update_anchored_children(child_idx)

    def _apply_fill_layout(self, comp_index):
        """Apply fill layout to component: match parent/canvas size with margins."""
        if comp_index < 0 or comp_index >= len(self.components):
            return

        comp_data = self.components[comp_index]
        comp_obj = comp_data.get('object')
        comp_type = comp_data.get('type', '')

        parent_width = None
        parent_height = None

        parent_index = comp_data.get('parent_index')
        if parent_index is not None and parent_index >= 0 and parent_index < len(self.components):
            parent_data = self.components[parent_index]
            parent_width = parent_data.get('width')
            parent_height = parent_data.get('height')
            parent_obj = parent_data.get('object')
            if parent_width is None and parent_obj is not None and hasattr(parent_obj, 'width'):
                parent_width = parent_obj.width
            if parent_height is None and parent_obj is not None and hasattr(parent_obj, 'height'):
                parent_height = parent_obj.height
        else:
            canvas_index = comp_data.get('canvas_index', self.current_canvas_index)
            if canvas_index is not None and canvas_index >= 0 and canvas_index < len(self.canvases):
                canvas_panel = self.canvases[canvas_index].get('panel')
                if canvas_panel is not None:
                    if hasattr(canvas_panel, 'width'):
                        parent_width = canvas_panel.width
                    elif hasattr(canvas_panel, 'get_width'):
                        parent_width = canvas_panel.get_width()
                    if hasattr(canvas_panel, 'height'):
                        parent_height = canvas_panel.height
                    elif hasattr(canvas_panel, 'get_height'):
                        parent_height = canvas_panel.get_height()

        # DEBUG: Trace fill layout calculations
        # print(f"[_apply_fill_layout] Comp #{comp_index} Parent #{parent_index}: PW={parent_width} PH={parent_height}")
        
        if parent_width is None or parent_height is None:
            # print(f"[_apply_fill_layout] Failed to get parent dimensions for Comp #{comp_index}")
            return

        margin_left = float(comp_data.get('fill_margin_left', 0.0))
        margin_right = float(comp_data.get('fill_margin_right', 0.0))
        margin_top = float(comp_data.get('fill_margin_top', 0.0))
        margin_bottom = float(comp_data.get('fill_margin_bottom', 0.0))

        new_left = margin_left
        new_top = margin_top
        new_width = float(parent_width) - (margin_left + margin_right)
        new_height = float(parent_height) - (margin_top + margin_bottom)

        if new_width < 1.0:
            new_width = 1.0
        if new_height < 1.0:
            new_height = 1.0

        comp_data['left'] = new_left
        comp_data['top'] = new_top
        comp_data['width'] = new_width
        comp_data['height'] = new_height

        if comp_obj is not None:
            # Handle visual_parent_canvas case: convert local to absolute
            abs_left = new_left
            abs_top = new_top
            
            if parent_index is not None and parent_index >= 0 and comp_data.get('visual_parent_canvas'):
                 p_abs_x, p_abs_y = self._get_component_accumulated_pos(parent_index)
                 abs_left = p_abs_x + new_left
                 abs_top = p_abs_y + new_top
            
            if hasattr(comp_obj, 'set_pos'):
                 comp_obj.set_pos(abs_left, abs_top)
            else:
                 if hasattr(comp_obj, 'left'):
                     comp_obj.left = abs_left
                 if hasattr(comp_obj, 'top'):
                     comp_obj.top = abs_top
            if hasattr(comp_obj, 'width'):
                comp_obj.width = new_width
            if hasattr(comp_obj, 'height'):
                comp_obj.height = new_height

        if comp_type in ['Plane', 'Image', 'Video'] and 'sprite' in comp_data:
            comp_data['sprite'].width = new_width
            comp_data['sprite'].height = new_height
        elif comp_type == 'Button':
            if hasattr(comp_obj, 'set_width'):
                comp_obj.set_width(new_width)
            if hasattr(comp_obj, 'set_height'):
                comp_obj.set_height(new_height)
            
            # Force update internal layout for Button to ensure fill works
            try:
                layout = getattr(comp_obj, '_layout', None)
                if layout:
                    if hasattr(layout, 'width'): layout.width = new_width 
                    if hasattr(layout, 'height'): layout.height = new_height
                    
                    # Inner layout
                    inner = getattr(layout, '_layout', None)
                    if inner:
                        if hasattr(inner, 'width'): inner.width = new_width 
                        if hasattr(inner, 'height'): inner.height = new_height
                        
                    # Mid sprite usually stretches
                    spr_mid = getattr(layout, '_sprite_mid', None)
                    if spr_mid:
                        spr_mid.width = new_width
                        spr_mid.height = new_height
            except Exception:
                pass
        elif comp_type == 'InputField':
            if hasattr(comp_obj, 'set_width'):
                comp_obj.set_width(new_width)
        elif comp_type == 'Slider':
            if hasattr(comp_obj, 'set_width'):
                comp_obj.set_width(new_width)


    def _apply_layout_spacing(self, layout_obj, spacing):
        try:
            if hasattr(layout_obj, 'set_spacing'):
                layout_obj.set_spacing(spacing)
            elif hasattr(layout_obj, 'setSpacing'):
                layout_obj.setSpacing(spacing)
            elif hasattr(layout_obj, 'spacing'):
                layout_obj.spacing = spacing
            elif hasattr(layout_obj, '_spacing'):
                layout_obj._spacing = spacing
        except Exception:
            pass

    def _apply_layout_alignment(self, layout_obj, align):
        try:
            if hasattr(layout_obj, 'set_alignment'):
                layout_obj.set_alignment(align)
            elif hasattr(layout_obj, 'alignment'):
                layout_obj.alignment = align
            elif hasattr(layout_obj, 'align'):
                layout_obj.align = align
        except Exception:
            pass

    def _update_layout_inner(self, comp_index):
        if comp_index < 0 or comp_index >= len(self.components):
            return

        comp_data = self.components[comp_index]
        layout_obj = comp_data.get('layout_obj')
        if layout_obj is None:
            return

        width = float(comp_data.get('width', 0.0))
        height = float(comp_data.get('height', 0.0))

        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))

        inner_w = max(1.0, width - (pad_left + pad_right))
        inner_h = max(1.0, height - (pad_top + pad_bottom))

        try:
            if hasattr(layout_obj, 'left'):
                layout_obj.left = pad_left
            if hasattr(layout_obj, 'top'):
                layout_obj.top = pad_top
            if hasattr(layout_obj, 'width'):
                layout_obj.width = inner_w
            if hasattr(layout_obj, 'height'):
                layout_obj.height = inner_h
            if hasattr(layout_obj, 'set_size'):
                layout_obj.set_size(inner_w, inner_h)
        except Exception:
            pass

        spacing = float(comp_data.get('layout_spacing', 0.0))
        self._apply_layout_spacing(layout_obj, spacing)

        align = comp_data.get('layout_align', None)
        if align:
            self._apply_layout_alignment(layout_obj, align)


        if comp_data.get('type') in ['HorizontalLayout', 'VerticalLayout']:
            self._apply_wrap_layout(comp_index)

        try:
            if hasattr(layout_obj, 'update'):
                layout_obj.update()
        except Exception:
            pass


    def _reparent_scrollable_children(self, parent_index):
        if parent_index < 0 or parent_index >= len(self.components):
            return
        parent_data = self.components[parent_index]
        if parent_data.get('type') != 'ScrollableRegion':
            return
        parent_obj = parent_data['object']
        try:
            content_node = parent_obj.content_node
        except Exception:
            return

        for child_idx in parent_data.get('children_indices', []):
            if child_idx < 0 or child_idx >= len(self.components):
                continue
            child_data = self.components[child_idx]
            child_obj = child_data.get('object')
            if child_obj is None:
                continue
            try:
                if hasattr(child_obj, 'reparent_to'):
                    child_obj.reparent_to(content_node)
                elif hasattr(child_obj, 'parent'):
                    child_obj.parent = content_node
            except Exception:
                pass

    def _apply_wrap_layout(self, parent_index):
        if parent_index < 0 or parent_index >= len(self.components):
            return

        parent_data = self.components[parent_index]
        layout_type = parent_data.get('type')
        if layout_type not in ['HorizontalLayout', 'VerticalLayout']:
            return
        if not parent_data.get('layout_wrap', True):
            return

        # Get parent absolute position for coordinate conversion
        parent_abs_left, parent_abs_top = self._get_component_accumulated_pos(parent_index)

        width = float(parent_data.get('width', 0.0))
        height = float(parent_data.get('height', 0.0))

        pad_left = float(parent_data.get('layout_padding_left', 0.0))
        pad_right = float(parent_data.get('layout_padding_right', 0.0))
        pad_top = float(parent_data.get('layout_padding_top', 0.0))
        pad_bottom = float(parent_data.get('layout_padding_bottom', 0.0))
        spacing = float(parent_data.get('layout_spacing', 0.0))
        line_spacing = float(parent_data.get('layout_line_spacing', 0.0))

        inner_w = max(1.0, width - (pad_left + pad_right))
        inner_h = max(1.0, height - (pad_top + pad_bottom))

        if layout_type == 'HorizontalLayout':
            x = pad_left
            y = pad_top
            row_h = 0.0

            for child_idx in parent_data.get('children_indices', []):
                if child_idx < 0 or child_idx >= len(self.components):
                    continue
                child = self.components[child_idx]
                child_obj = child.get('object')

                cw = float(child.get('width', 0.0))
                ch = float(child.get('height', 0.0))
                # Allow layout even if size is 0, but usually skip
                # if cw <= 0 or ch <= 0: continue 

                if x > pad_left and (x + cw) > (pad_left + inner_w):
                    x = pad_left
                    y += row_h + line_spacing
                    row_h = 0.0

                # Data stores relative position (to parent)
                child['left'] = x
                child['top'] = y
                
                # Update visual object
                try:
                    child_parent = getattr(child_obj, 'parent', None)
                    parent_obj = parent_data.get('object')
                    
                    if child_parent is not None and child_parent == parent_obj:
                         # Child is parented to layout object: use relative coordinates
                         if hasattr(child_obj, 'left'):
                             child_obj.left = x
                         if hasattr(child_obj, 'top'):
                             child_obj.top = y
                         if hasattr(child_obj, 'set_pos'):
                             child_obj.set_pos(x, y)
                    else:
                        # Child is NOT parented to layout object (e.g. at root): use absolute coordinates
                        abs_x = parent_abs_left + x
                        abs_y = parent_abs_top + y
                        
                        if hasattr(child_obj, 'left'):
                            child_obj.left = abs_x
                        if hasattr(child_obj, 'top'):
                            child_obj.top = abs_y
                        if hasattr(child_obj, 'set_pos'):
                            child_obj.set_pos(abs_x, abs_y)
                except Exception:
                    pass
                
                # Recursively update child layout/visuals
                if child.get('type') in ['HorizontalLayout', 'VerticalLayout'] and child.get('layout_wrap', True):
                    self._apply_wrap_layout(child_idx)
                elif child.get('children_indices'):
                    self._sync_canvas_children(child_idx)

                row_h = max(row_h, ch)
                x += cw + spacing
        else:
            # Vertical wrap: flow top->bottom, then next column
            x = pad_left
            y = pad_top
            col_w = 0.0

            for child_idx in parent_data.get('children_indices', []):
                if child_idx < 0 or child_idx >= len(self.components):
                    continue
                child = self.components[child_idx]
                child_obj = child.get('object')

                cw = float(child.get('width', 0.0))
                ch = float(child.get('height', 0.0))
                
                if y > pad_top and (y + ch) > (pad_top + inner_h):
                    y = pad_top
                    x += col_w + line_spacing
                    col_w = 0.0

                child['left'] = x
                child['top'] = y
                
                try:
                    child_parent = getattr(child_obj, 'parent', None)
                    parent_obj = parent_data.get('object')
                    
                    if child_parent is not None and child_parent == parent_obj:
                         # Child is parented to layout object: use relative coordinates
                         if hasattr(child_obj, 'left'):
                             child_obj.left = x
                         if hasattr(child_obj, 'top'):
                             child_obj.top = y
                         if hasattr(child_obj, 'set_pos'):
                             child_obj.set_pos(x, y)
                    else:
                        # Child is NOT parented to layout object (e.g. at root): use absolute coordinates
                        abs_x = parent_abs_left + x
                        abs_y = parent_abs_top + y
                        
                        if hasattr(child_obj, 'left'):
                            child_obj.left = abs_x
                        if hasattr(child_obj, 'top'):
                            child_obj.top = abs_y
                        if hasattr(child_obj, 'set_pos'):
                            child_obj.set_pos(abs_x, abs_y)
                except Exception:
                    pass
                
                # If child is also a layout, recursively update it
                if child.get('type') in ['HorizontalLayout', 'VerticalLayout'] and child.get('layout_wrap', True):
                    self._apply_wrap_layout(child_idx)
                # If child is NOT a layout but has children (like a Frame with children), update their visual text
                elif child.get('children_indices'):
                    self._sync_canvas_children(child_idx)

                col_w = max(col_w, cw)
                y += ch + spacing

    def get_selected_component(self):
        """获取当前选中的组件"""
        if self.selected_index >= 0 and self.selected_index < len(self.components):
            return self.components[self.selected_index]
        return None

    def select_component(self, index):
        """选中指定索引的组件"""
        if 0 <= index < len(self.components):
            self.selected_index = index
            print(f"✓ 选中组件 #{index}: {self.components[index]['type']}")
        else:
            self.selected_index = -1

    def deselect_all(self):
        """取消选中所有组件"""
        if getattr(self, '_force_keep_selection_frames', 0) > 0:
            return
        self.selected_index = -1
        print("✓ 已取消选中所有组件")

    def _change_image_texture(self, title = "选择文件",filetypes=None,initialdir=None):
        """更改图像组件的纹理"""
        try:
            import tkinter as tk
            from tkinter import filedialog

            root = tk.Tk()
            root.withdraw()
            root.attributes('-topmost',True)

            if filetypes is None:
                filetypes = [("所有文件","*.*")]

            file_path = filedialog.askopenfilename(
                title = title,
                filetypes = filetypes,
                initialdir = initialdir
            )
            root.destroy()

            if file_path:
                return file_path
            return None
            
        except Exception as e:
            print(f"更改纹理失败: {e}")

    def _update_component_anchor_position(self, comp_index, anchor_position, manual_offset=(0.0, 0.0)):
        """更新组件的锚点位置"""
        if comp_index < 0 or comp_index >= len(self.components):
            return
        

        
        comp_data = self.components[comp_index]
        comp_obj = comp_data['object']
        
        # Update data
        comp_data['anchor_position'] = anchor_position
        comp_data['anchored_to_parent'] = True
        comp_data['anchor_manual_offset_x'] = manual_offset[0]
        comp_data['anchor_manual_offset_y'] = manual_offset[1]
        
        # Determine strict parent reference
        parent_index = comp_data.get('parent_index')
        parent_width = 0
        parent_height = 0
        
        if parent_index is not None and parent_index >= 0:
             parent_data = self.components[parent_index]
             parent_width = parent_data.get('width', 100)
             parent_height = parent_data.get('height', 30)
             # Try to get actual object size if available
             p_obj = parent_data.get('object')
             if p_obj:
                 if hasattr(p_obj, 'width') and p_obj.width > 0: parent_width = p_obj.width
                 if hasattr(p_obj, 'height') and p_obj.height > 0: parent_height = p_obj.height
        elif self.current_canvas_index >= 0:
             # Canvas anchor
             canvas = self.canvases[self.current_canvas_index]['panel']
             if hasattr(canvas, 'get_width'):
                 parent_width = canvas.get_width()
                 parent_height = canvas.get_height()
             else:
                 parent_width = canvas.width
                 parent_height = canvas.height
             
        # Calculate anchor point in parent local space
        anchor_ratios = {
            'top-left': (0, 0), 'top-center': (0.5, 0), 'top-right': (1, 0),
            'middle-left': (0, 0.5), 'center': (0.5, 0.5), 'middle-right': (1, 0.5),
            'bottom-left': (0, 1), 'bottom-center': (0.5, 1), 'bottom-right': (1, 1)
        }
        rx, ry = anchor_ratios.get(anchor_position, (0, 0))
        
        anchor_x = parent_width * rx
        anchor_y = parent_height * ry
        
        child_width = comp_data.get('width', 0)
        child_height = comp_data.get('height', 0)
        # Try to get actual object size
        if hasattr(comp_obj, 'width') and comp_obj.width > 0: child_width = comp_obj.width
        if hasattr(comp_obj, 'height') and comp_obj.height > 0: child_height = comp_obj.height
        
        # Center child on anchor (pivot logic)
        child_left = anchor_x - (child_width * rx) + manual_offset[0]
        child_top = anchor_y - (child_height * ry) + manual_offset[1]
        
        # Update logical position data
        comp_data['left'] = child_left
        comp_data['top'] = child_top
        
        # Update visual position
        if parent_index is not None and parent_index >= 0:
             # Check if visually parented to canvas (absolute positioning needed)
             if comp_data.get('visual_parent_canvas'):
                 p_abs_x, p_abs_y = self._get_component_accumulated_pos(parent_index)
                 abs_left = p_abs_x + child_left
                 abs_top = p_abs_y + child_top
                 
                 # Prefer left/top properties for LUI objects
                 if hasattr(comp_obj, 'left') and hasattr(comp_obj, 'top'):
                     comp_obj.left = abs_left
                     comp_obj.top = abs_top
                 elif hasattr(comp_obj, 'set_pos'):
                     try:
                         comp_obj.set_pos(abs_left, abs_top)
                     except TypeError:
                         # Fallback for NodePath-like objects requiring 3 args
                         comp_obj.set_pos(abs_left, abs_top, 0)
             else:
                 # Standard local parenting
                 if hasattr(comp_obj, 'left') and hasattr(comp_obj, 'top'):
                     comp_obj.left = child_left
                     comp_obj.top = child_top
                 elif hasattr(comp_obj, 'set_pos'):
                     try:
                         comp_obj.set_pos(child_left, child_top)
                     except TypeError:
                         comp_obj.set_pos(child_left, child_top, 0)
        else:
             # Canvas root
             if hasattr(comp_obj, 'left') and hasattr(comp_obj, 'top'):
                 comp_obj.left = child_left
                 comp_obj.top = child_top
             elif hasattr(comp_obj, 'set_pos'):
                 try:
                     comp_obj.set_pos(child_left, child_top)
                 except TypeError:
                     comp_obj.set_pos(child_left, child_top, 0)
                     
        print(f"✓ 已更新锚点位置: {anchor_position} -> ({child_left:.1f}, {child_top:.1f})")
        
        # Update children positions (for visual_parent_canvas or anchored children)
        self._sync_canvas_children(comp_index)
        self._update_anchored_children(comp_index)

    def _handle_anchor_popup(self):
        """Handle component anchor selection popup"""
        if imgui.begin_popup("选择锚点位置"):
            imgui.text("选择锚点位置:")
            
            anchor_names = {
                'top-left': '左上', 'top-center': '中上', 'top-right': '右上',
                'middle-left': '左中', 'center': '中心', 'middle-right': '右中',
                'bottom-left': '左下', 'bottom-center': '中下', 'bottom-right': '右下'
            }
            
            anchor_positions = [
                ['top-left', 'top-center', 'top-right'],
                ['middle-left', 'center', 'middle-right'],
                ['bottom-left', 'bottom-center', 'bottom-right']
            ]
            
            for row in anchor_positions:
                for i, pos in enumerate(row):
                    button_text = anchor_names.get(pos, pos)
                    if imgui.button(f"{button_text}##popup_{pos}", (50, 25)):
                        if hasattr(self, '_temp_selected_index_for_anchor'):
                            selected_idx = self._temp_selected_index_for_anchor
                            # Reset manual offset by passing (0,0) explicitly to match original behavior logic
                            self._update_component_anchor_position(selected_idx, pos, manual_offset=(0.0, 0.0))
                            
                            # Clean up temp attribute
                            delattr(self, '_temp_selected_index_for_anchor')
                        
                        # Reset flag if used elsewhere
                        self.anchor_popup_open = False
                        imgui.close_current_popup()
                        
                    if i < len(row) - 1:
                        imgui.same_line()
            
            imgui.spacing()
            if imgui.button("取消", (160, 25)):
                self.anchor_popup_open = False
                if hasattr(self, '_temp_selected_index_for_anchor'):
                    delattr(self, '_temp_selected_index_for_anchor')
                imgui.close_current_popup()
            
            imgui.end_popup()
