回补G任务:拆分LUI _update_drag

This commit is contained in:
ayuan9957 2026-03-01 13:20:51 +08:00
parent c006b69613
commit 22a9b5b64a

View File

@ -310,258 +310,242 @@ class LUIManagerInteractionMixin:
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
def _clear_pending_drag_state(self):
"""清空 pending 拖拽状态。"""
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
# 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
def _has_mouse_for_drag(self):
"""检查是否存在可用鼠标输入。"""
return hasattr(self.world, 'mouseWatcherNode') and self.world.mouseWatcherNode.hasMouse()
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
# 检查鼠标左键是否释放
def _is_left_mouse_down(self):
"""检查鼠标左键是否按下。"""
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
return self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one())
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
# 获取鼠标位置
def _get_mouse_pixel_data(self):
"""获取鼠标像素坐标及窗口尺寸。"""
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信息
return pixel_x, pixel_y, win_x, win_y
def _find_canvas_data_by_panel(self, canvas_panel):
"""按 panel 对象查找 canvas 配置。"""
for c in self.canvases:
if c['panel'] == canvas_panel:
return c
return None
def _process_canvas_drag(self):
"""处理 Canvas 拖动,返回是否已处理该帧。"""
if not (hasattr(self, 'dragging_canvas') and self.dragging_canvas):
return False
canvas_data = self._find_canvas_data_by_panel(self.dragging_canvas)
if canvas_data and canvas_data.get('fixed', False):
return True
if not self._has_mouse_for_drag():
return True
if not self._is_left_mouse_down():
self.dragging_canvas = None
return True
pixel_x, pixel_y, _, _ = self._get_mouse_pixel_data()
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 True
def _try_start_pending_component_drag(self):
"""处理 pending 拖拽转正,返回是否需要本帧提前结束。"""
if self.dragging_comp is not None:
return False
if self.pending_drag_comp is None:
return True
if not self._has_mouse_for_drag():
return True
if not self._is_left_mouse_down():
# click without drag
self._clear_pending_drag_state()
return True
pixel_x, pixel_y, _, _ = self._get_mouse_pixel_data()
if self.pending_drag_start_mouse is None:
self.pending_drag_start_mouse = (pixel_x, pixel_y)
return True
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 True
# 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 Exception:
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)
self._clear_pending_drag_state()
return False
def _finish_component_drag_if_released(self):
"""在鼠标释放时完成拖拽收尾,返回是否已处理该帧。"""
if not self._is_left_mouse_down():
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 True
return False
def _resolve_canvas_metrics_for_drag(self, win_x, win_y):
"""获取当前 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:
except Exception:
# 如果获取失败,使用默认值
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'):
@ -572,7 +556,18 @@ class LUIManagerInteractionMixin:
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)
return canvas_abs_left, canvas_abs_top, canvas_width, canvas_height
def _update_active_component_drag(self, pixel_x, pixel_y, win_x, win_y):
"""更新已开始拖拽的组件位置。"""
# 计算新的绝对位置(减去偏移量)
new_abs_left = pixel_x - self.drag_offset[0]
new_abs_top = pixel_y - self.drag_offset[1]
# 获取Canvas信息
canvas_abs_left, canvas_abs_top, canvas_width, canvas_height = self._resolve_canvas_metrics_for_drag(win_x, win_y)
# 转换为Canvas相对坐标 (Global Canvas Pos)
canvas_relative_left = new_abs_left - canvas_abs_left
canvas_relative_top = new_abs_top - canvas_abs_top
@ -584,7 +579,7 @@ class LUIManagerInteractionMixin:
# 获取组件宽高 (用于边界限制)
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
@ -595,35 +590,33 @@ class LUIManagerInteractionMixin:
# 确保左边界不小于0
if canvas_relative_left < 0:
canvas_relative_left = 0
# 确保上边界不小于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'):
@ -648,7 +641,7 @@ class LUIManagerInteractionMixin:
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
@ -656,7 +649,27 @@ class LUIManagerInteractionMixin:
# 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)
def _update_drag(self, task):
"""每帧更新拖动状态 - 支持Canvas边界约束和局部坐标"""
if getattr(self, 'play_mode', False):
# Disable editor drag logic while in play mode
return task.cont
if self._process_canvas_drag():
return task.cont
if self._try_start_pending_component_drag():
return task.cont
if not self._has_mouse_for_drag():
return task.cont
if self._finish_component_drag_if_released():
return task.cont
pixel_x, pixel_y, win_x, win_y = self._get_mouse_pixel_data()
self._update_active_component_drag(pixel_x, pixel_y, win_x, win_y)
return task.cont
def _create_resize_handles(self):