"""LUIManager interaction and resize mixin."""

import struct
from pathlib import Path

import panda3d.core as p3d
from imgui_bundle import imgui, imgui_ctx


class LUIManagerInteractionMixin:
    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', 'HttpText']:
            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 _update_http_components_task(self, task):
        """Drive async updates for HttpText components."""
        if not self.lui_enabled:
            return task.cont
        try:
            self.luiFunction.update_http_components(self)
        except Exception:
            pass
        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', 'HttpText']:
                sprite = comp_data.get('sprite')
                if sprite is not None:
                    sprite.width = new_width
                    sprite.height = new_height
                if comp_type == 'HttpText':
                    self.luiFunction.sync_http_text_layout(self, comp_data)
            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}")
