refactor(lui): complete batch split for interaction/editor/property flows

This commit is contained in:
ayuan9957 2026-03-01 19:39:13 +08:00
parent a1f6fe6097
commit c8eb4cef4d
5 changed files with 1976 additions and 1509 deletions

View File

@ -62,13 +62,91 @@
- `ui/panels/editor_panels_left.py::_draw_resource_manager` 保留布局版拆分已完成:
- 实测指标:`310 -> 23` 行(第一轮 `310 -> 163`,第二轮 `163 -> 23`
- 目录/文件项渲染、展开内容、右键菜单流程已拆到独立 helper
- `ui/LUI/lui_function_properties.py::_draw_component_properties` 第一轮编排拆分已完成:
- 实测指标:`1441 -> 3` 行(入口)
- 新增编排/子流程 helper
- `_draw_component_properties_core``34`
- `_draw_text_and_button_properties``216`
- `_draw_layout_transform_properties``200`
- `_draw_type_specific_properties``835`
- `_draw_anchor_and_hierarchy_properties``168`
- `ui/LUI/lui_manager_interaction.py::_handle_resize_drag` 第二轮拆分已完成:
- 实测指标:`257 -> 3` 行(入口)
- 新增编排/子流程 helper
- `_handle_resize_drag_core``44`
- `_finish_resize_drag_if_released``19`
- `_compute_resize_drag_bounds``109`
- `_apply_resize_drag_updates``110`
- `ui/LUI/lui_manager_interaction.py::_compute_resize_drag_bounds` / `_apply_resize_drag_updates` 第三轮细拆已完成:
- 实测指标:`109 -> 27` / `110 -> 21`
- 新增细粒度 helper边界计算与组件应用分层
- `_get_resize_start_values``7`
- `_apply_resize_handle_delta``36`
- `_apply_resize_min_size_clamp``15`
- `_apply_shift_keep_ratio_resize``17`
- `_apply_alt_center_resize``22`
- `_resolve_resize_parent_context``13`
- `_apply_resize_component_size_by_type``24`
- `_sync_input_field_layout_stretch``22`
- `ui/LUI/lui_function_properties.py::_draw_type_specific_properties` 第二轮拆分已完成:
- 实测指标:`835 -> 25` 行(类型分发入口)
- 新增类型 helper
- `_draw_type_props_input_field``91`
- `_draw_type_props_slider``11`
- `_draw_type_props_checkbox``19`
- `_draw_type_props_plane_image``152`
- `_draw_type_props_frame``14`
- `_draw_type_props_selectbox``84`
- `_draw_type_props_http_text``72`
- `_draw_type_props_video``10`
- `_draw_video_source_and_audio_controls``4`,第三轮后为编排入口)
- `_draw_video_playback_controls``147`
- `ui/LUI/lui_function_properties.py::_draw_video_source_and_audio_controls` 第三轮拆分已完成:
- 实测指标:`231 -> 4` 行(入口)
- 新增子流程 helper
- `_draw_video_audio_controls``26`
- `_draw_video_source_controls``19`
- `_process_pending_video_source``22`
- `_load_video_texture_from_source``21`
- `_apply_loaded_video_texture``14`
- `_sync_video_size_and_sprite``28`
- `_replace_video_sprite``23`
- `_refresh_video_keep_alive_node``22`
- `_load_audio_for_component``22`
- `_start_video_texture_playback``7`
- `ui/LUI/lui_function_properties.py::_draw_text_and_button_properties` 第四轮拆分已完成:
- 实测指标:`216 -> 14` 行(入口)
- 新增子流程 helper文本编辑/颜色同步/按钮贴图):
- `_draw_text_content_editor``13`
- `_draw_text_font_size_editor``18`
- `_draw_text_color_editor``11`
- `_sync_text_or_button_color``10`
- `_draw_button_texture_controls``19`
- `_apply_button_texture_variant``28`
- `ui/LUI/lui_manager_editor.py::_set_parent_child_relationship` 第四轮拆分已完成:
- 实测指标:`234 -> 43` 行(入口)
- 新增子流程 helper关系校验/索引同步/可见层级):
- `_is_valid_relationship_index``5`
- `_prepare_parent_child_link``8`
- `_remove_child_from_old_parent``8`
- `_attach_child_to_new_parent``6`
- `_compute_new_child_local_position``7`
- `_ensure_child_visibility_and_layer``19`
- `ui/LUI/lui_function_properties.py::_draw_layout_transform_properties` 第四轮拆分已完成:
- 实测指标:`200 -> 10` 行(入口)
- 新增子流程 helper填充模式/尺寸联动/Layout Group/Z-Offset
- `_draw_fill_layout_controls``37`
- `_draw_position_controls``18`
- `_draw_size_controls``17`
- `_draw_layout_group_controls``53`
- `_draw_z_offset_controls``27`
## 1. 总体画像
- Python 文件: `147`
- 代码总行数: `58,341`
- `except Exception` / `except:` 总计: `949`
- 裸 `except:` 总计: `62`
- 代码总行数: `58,479`
- `except Exception` / `except:` 总计: `993`
- 裸 `except:` 总计: `52`
- 旧上下文关键词引用总量:
- `interface_manager`: `20`
- `treeWidget`: `2`
@ -85,24 +163,27 @@
### 2.1 超大文件 Top非 VR
1. `core/selection.py` (`2941` 行)
2. `core/InfoPanelManager.py` (`1725` 行)
3. `ui/LUI/lui_manager_editor.py` (`1724` 行)
4. `ui/panels/property_helpers.py` (`1711` 行)
5. `ui/LUI/lui_function_properties.py` (`1707` 行)
6. `TransformGizmo/rotate_gizmo.py` (`1587` 行)
7. `ui/panels/animation_tools.py` (`1579` 行)
8. `templates/main_template.py` (`1571` 行)
1. `core/selection.py` (`2933` 行)
2. `ui/LUI/lui_function_properties.py` (`1731` 行)
3. `core/InfoPanelManager.py` (`1726` 行)
4. `ui/LUI/lui_manager_editor.py` (`1725` 行)
5. `ui/panels/property_helpers.py` (`1712` 行)
6. `TransformGizmo/rotate_gizmo.py` (`1588` 行)
7. `templates/main_template.py` (`1572` 行)
8. `ui/panels/animation_tools.py` (`1563` 行)
### 2.2 长函数 Top优先拆分非 VR 当前状态)
1. `ui/LUI/lui_function_properties.py::_draw_component_properties` (`1441` 行)
2. `ui/LUI/lui_manager_interaction.py::_handle_resize_drag` (`257` 行)
3. `ui/LUI/lui_manager_editor.py::_set_parent_child_relationship` (`234` 行)
4. `ui/panels/editor_panels_top.py::draw_menu_bar` (`227` 行)
5. `scene/scene_manager_io_mixin.py::saveScene` (`222` 行)
6. `TransformGizmo/move_gizmo.py::_on_mouse_down` (`222` 行)
7. `ui/LUI/lui_manager_editor.py::draw_editor` (`217` 行)
1. `ui/panels/editor_panels_top.py::draw_menu_bar` (`227` 行)
2. `scene/scene_manager_io_mixin.py::saveScene` (`222` 行)
3. `TransformGizmo/move_gizmo.py::_on_mouse_down` (`222` 行)
4. `ui/LUI/lui_manager_editor.py::draw_editor` (`217` 行)
5. `ui/LUI/lui_manager_editor.py::draw_component_tree` (`217` 行)
6. `core/InfoPanelManager.py::add_methods_to_property_panel` (`209` 行)
7. `ui/panels/editor_panels_right.py::_draw_animation_properties` (`201` 行)
8. `ui/panels/editor_panels_right.py::_draw_gui_properties` (`191` 行)
9. `core/event_handler.py::mousePressEventLeft` (`186` 行)
10. `scene/scene_manager_model_mixin.py::importModel` (`177` 行)
### 2.3 异常处理密度高(可观测性风险)
@ -161,9 +242,9 @@
建议拆分顺序(非 VR 当前阶段):
1. `ui/LUI/lui_function_properties.py::_draw_component_properties`(可按“变换/布局/视觉/交互/脚本”分区)
2. `ui/LUI/lui_manager_interaction.py::_handle_resize_drag`
3. `ui/panels/editor_panels_top.py::draw_menu_bar`
1. `ui/panels/editor_panels_top.py::draw_menu_bar`
2. `scene/scene_manager_io_mixin.py::saveScene`
3. `core/InfoPanelManager.py::add_methods_to_property_panel`
预期收益:
@ -249,12 +330,65 @@
- 主函数长度降到 `<= 140` 行(实际 `28` 行)
- 拖拽与缩放交互行为保持一致
### Task H推荐下一步2-3 天
### Task H已完成,第一轮
- 拆分 `ui/LUI/lui_function_properties.py::_draw_component_properties`
- 验收:
- 主函数长度降到 `<= 260`
- 按“变换/布局/视觉/交互/脚本”子区块拆分并保持渲染逻辑一致
- 主函数长度降到 `<= 260` 行(实际 `3` 行,编排核心 `34` 行)
- 已完成第一轮分区拆分并保持渲染逻辑一致
### Task I已完成第二轮
- 拆分 `ui/LUI/lui_manager_interaction.py::_handle_resize_drag`
- 验收:
- 入口流程收敛到 `<= 120` 行(实际 `3` 行,编排核心 `44` 行)
- 缩放手柄行为与边界语义保持一致
- 第三轮细拆补充(已完成):
- `_compute_resize_drag_bounds`: `109 -> 27`
- `_apply_resize_drag_updates`: `110 -> 21`
- 继续保持缩放语义一致
### Task J已完成第二轮
- 继续拆分 `ui/LUI/lui_function_properties.py::_draw_type_specific_properties`
- 验收:
- 单函数长度降到 `<= 320` 行(实际 `25` 行)
- 按组件类型拆分为独立 helper 并保持行为一致
### Task K已完成第三轮
- 继续拆分 `ui/LUI/lui_function_properties.py::_draw_video_source_and_audio_controls`(已完成)
- 验收:
- 单函数长度降到 `<= 160` 行(实际 `4` 行)
- 行为保持一致(本地视频/URL加载/音频联动)
### Task L已完成第四轮
- 继续拆分 `ui/LUI/lui_function_properties.py::_draw_text_and_button_properties`
- 验收:
- 单函数长度降到 `<= 160` 行(实际 `14` 行)
- 文本/按钮属性编辑行为保持一致
### Task M已完成第四轮
- 继续拆分 `ui/LUI/lui_manager_editor.py::_set_parent_child_relationship`
- 验收:
- 单函数长度降到 `<= 180` 行(实际 `43` 行)
- 父子关系编辑行为保持一致
### Task N推荐下一步1-2 天)
- 拆分 `ui/panels/editor_panels_top.py::draw_menu_bar`
- 验收:
- 单函数长度降到 `<= 170`
- 菜单项顺序与行为保持一致
### Task O已完成第四轮
- 继续拆分 `ui/LUI/lui_function_properties.py::_draw_layout_transform_properties`
- 验收:
- 单函数长度降到 `<= 160` 行(实际 `10` 行)
- 布局与尺寸联动行为保持一致
## 4.1 本轮深入分析(非 VRP1 准备)
@ -281,7 +415,7 @@
- 失败重试逻辑保持语义一致。
- 当前状态2026-03-01:
- 第二轮已完成(`loadScene` 已达验收目标:`74` 行)。
- 后续建议转入回归观察,优先推进 `ui/LUI/lui_function_properties.py::_draw_component_properties`。
- 后续建议转入回归观察,优先推进 `ui/panels/editor_panels_top.py::draw_menu_bar`。
### B) `main.py::__init__`(第二优先,已完成第一轮)
@ -345,8 +479,8 @@
1. `loadScene` 已完成两轮拆分(当前建议冻结并转入回归观察)。
2. `main.__init__` 已完成第一轮拆分(建议转入回归观察)。
3. `_update_drag` 已完成第一轮拆分,建议转入回归观察。
4. 下一步处理 `_draw_component_properties`(属性面板分区拆分)
5. 然后处理 `_handle_resize_drag`(拖拽缩放路径补充拆分)
4. `_draw_component_properties` 第一轮已完成,`_draw_type_specific_properties` 二轮与视频段三轮已完成
5. `_handle_resize_drag` 第三轮细拆已完成,`_draw_text_and_button_properties`、`_set_parent_child_relationship`、`_draw_layout_transform_properties` 第四轮已完成
## 5. 与现有文档关系
@ -356,4 +490,4 @@
---
如果按此路线继续,建议下一轮直接从 **Task H** 开始,先落地 `_draw_component_properties` 的分区拆分并保持行为不变。
如果按此路线继续,建议下一轮直接从 **Task N** 开始,优先落地 `draw_menu_bar` 的流程拆分并保持行为不变。

View File

@ -15,6 +15,12 @@
- 项目已进入“非 VR 结构优化阶段”。
- `_getActor`、`updateGizmoDrag`、`_update_drag` 第一轮拆分已完成。
- `资源管理器面板` 已完成“保留原布局/样式/交互”的第二轮拆分收敛。
- `_draw_component_properties` 已完成第一轮“编排入口 + 子流程”拆分(入口 `1441 -> 3` 行)。
- `_handle_resize_drag` 已完成第三轮“边界计算 / 更新应用”细拆(核心 `44` 行)。
- `_draw_video_source_and_audio_controls` 已完成第三轮“音频/视频源/加载应用”拆分(`231 -> 4` 行)。
- `_draw_text_and_button_properties` 已完成第四轮“文本/颜色/按钮贴图”拆分(`216 -> 14` 行)。
- `_set_parent_child_relationship` 已完成第四轮“关系编排 + 可见层级”拆分(`234 -> 43` 行)。
- `_draw_layout_transform_properties` 已完成第四轮“布局/尺寸/Z-Offset”拆分`200 -> 10` 行)。
## 已完成Done
@ -89,23 +95,173 @@
- 新增 helper`_update_resource_manager_window_rect`、`_draw_resource_directory_entries`、`_draw_resource_directory_entry`、`_draw_resource_directory_children`、`_draw_resource_subdir_entry`、`_draw_resource_subfile_entry`、`_draw_resource_file_entries`、`_draw_resource_file_entry`
- 保持目标:不改视觉布局,不改交互语义,仅做结构分解
### H. LUI 属性面板拆分完成(第一轮编排)
- [x] `ui/LUI/lui_function_properties.py::_draw_component_properties` 第一轮拆分完成:
- 入口函数行数:`1441 -> 3`
- 编排主流程函数:`_draw_component_properties_core = 34` 行
- 新增 helper
- `_draw_text_and_button_properties``216` 行)
- `_draw_layout_transform_properties``200` 行)
- `_draw_type_specific_properties``835` 行)
- `_draw_anchor_and_hierarchy_properties``168` 行)
- 保持目标:属性面板行为不变,先完成入口和流程层收敛
### I. LUI 缩放链路拆分完成(第二轮)
- [x] `ui/LUI/lui_manager_interaction.py::_handle_resize_drag` 第二轮拆分完成:
- 入口函数行数:`257 -> 3`
- 编排主流程函数:`_handle_resize_drag_core = 44` 行
- 新增 helper
- `_finish_resize_drag_if_released``19` 行)
- `_compute_resize_drag_bounds``109` 行)
- `_apply_resize_drag_updates``110` 行)
- 保持目标:缩放手柄行为与边界语义保持一致
### J. LUI 属性类型分支拆分完成(第二轮)
- [x] `ui/LUI/lui_function_properties.py::_draw_type_specific_properties` 第二轮拆分完成:
- 主函数行数:`835 -> 25`(类型分发入口)
- 新增类型 helper
- `_draw_type_props_input_field``91` 行)
- `_draw_type_props_slider``11` 行)
- `_draw_type_props_checkbox``19` 行)
- `_draw_type_props_plane_image``152` 行)
- `_draw_type_props_frame``14` 行)
- `_draw_type_props_selectbox``84` 行)
- `_draw_type_props_http_text``72` 行)
- `_draw_type_props_video``10` 行)
- `_draw_video_source_and_audio_controls``231` 行)
- `_draw_video_playback_controls``147` 行)
- 保持目标:各组件类型交互语义保持一致,先完成类型分发层收敛
### K. LUI 视频属性段拆分完成(第三轮)
- [x] `ui/LUI/lui_function_properties.py::_draw_video_source_and_audio_controls` 第三轮拆分完成:
- 主函数行数:`231 -> 4`(编排入口)
- 新增 helper
- `_draw_video_audio_controls``26` 行)
- `_draw_video_source_controls``19` 行)
- `_process_pending_video_source``22` 行)
- `_load_video_texture_from_source``21` 行)
- `_apply_loaded_video_texture``14` 行)
- `_sync_video_size_and_sprite``28` 行)
- `_replace_video_sprite``23` 行)
- `_refresh_video_keep_alive_node``22` 行)
- `_load_audio_for_component``22` 行)
- `_start_video_texture_playback``7` 行)
- 保持目标:本地视频加载/URL加载回退/音频联动语义保持一致
### L. LUI 缩放链路细拆完成(第三轮)
- [x] `ui/LUI/lui_manager_interaction.py::_compute_resize_drag_bounds` / `_apply_resize_drag_updates` 第三轮细拆完成:
- 主函数行数:`109 -> 27` / `110 -> 21`
- 新增 helper
- `_get_resize_start_values``7` 行)
- `_apply_resize_handle_delta``36` 行)
- `_apply_resize_min_size_clamp``15` 行)
- `_apply_shift_keep_ratio_resize``17` 行)
- `_apply_alt_center_resize``22` 行)
- `_cache_resize_canvas_bounds``8` 行)
- `_sanitize_resize_dimensions``6` 行)
- `_resolve_resize_parent_context``13` 行)
- `_write_resize_component_data``5` 行)
- `_apply_resize_component_position``9` 行)
- `_apply_resize_component_size_by_type``24` 行)
- `_apply_button_resize_size``7` 行)
- `_apply_input_field_resize_size``10` 行)
- `_sync_input_field_layout_stretch``22` 行)
- `_post_resize_component_sync``7` 行)
- `_get_resize_modifier_info``7` 行)
- 保持目标:缩放手柄行为与边界语义保持一致
### M. LUI 文本按钮属性段拆分完成(第四轮)
- [x] `ui/LUI/lui_function_properties.py::_draw_text_and_button_properties` 第四轮拆分完成:
- 主函数行数:`216 -> 14`
- 新增 helper
- `_draw_text_content_editor``13` 行)
- `_draw_text_font_size_editor``18` 行)
- `_draw_text_color_editor``11` 行)
- `_sync_text_or_button_color``10` 行)
- `_apply_button_layout_color``12` 行)
- `_apply_text_color``3` 行)
- `_draw_button_texture_controls``19` 行)
- `_draw_button_texture_pick_button``22` 行)
- `_apply_button_texture_variant``28` 行)
- `_unpack_button_atlas_result``9` 行)
- `_apply_button_custom_textures``11` 行)
- `_fit_button_to_default_texture_size``22` 行)
- `_clear_button_custom_textures``13` 行)
- `_draw_button_texture_path_labels``7` 行)
- 保持目标:文本/按钮编辑行为与贴图切换语义保持一致
### N. LUI 层级编辑父子关系拆分完成(第四轮)
- [x] `ui/LUI/lui_manager_editor.py::_set_parent_child_relationship` 第四轮拆分完成:
- 主函数行数:`234 -> 43`
- 新增 helper
- `_is_valid_relationship_index``5` 行)
- `_prepare_parent_child_link``8` 行)
- `_remove_child_from_old_parent``8` 行)
- `_attach_child_to_new_parent``6` 行)
- `_compute_new_child_local_position``7` 行)
- `_resolve_parent_visual_object``10` 行)
- `_set_child_visual_parent_policy``12` 行)
- `_clamp_child_local_within_parent``27` 行)
- `_sync_child_canvas_index_with_parent``7` 行)
- `_apply_child_absolute_position``10` 行)
- `_ensure_child_visibility_and_layer``19` 行)
- `_ensure_button_child_visibility``15` 行)
- `_ensure_frame_child_visibility``9` 行)
- `_ensure_media_child_visibility``13` 行)
- `_ensure_child_sprite_visibility_and_layer``24` 行)
- `_finalize_parent_child_link``9` 行)
- 保持目标:逻辑父子关系/可见层级/画布索引同步语义保持一致
### O. LUI 布局变换属性段拆分完成(第四轮)
- [x] `ui/LUI/lui_function_properties.py::_draw_layout_transform_properties` 第四轮拆分完成:
- 主函数行数:`200 -> 10`
- 新增 helper
- `_draw_fill_layout_controls``37` 行)
- `_draw_position_controls``18` 行)
- `_draw_size_controls``17` 行)
- `_draw_width_control``23` 行)
- `_draw_height_control``19` 行)
- `_post_size_control_sync``9` 行)
- `_draw_layout_group_controls``53` 行)
- `_draw_z_offset_controls``27` 行)
- 保持目标:填充模式/尺寸联动/Layout Group/Z-Offset 行为一致
## 下一步Next
### N1 `属性面板超大函数` 拆分(最高优先级,非 VR
### N1 `顶部菜单栏` 拆分(最高优先级,非 VR
- 目标文件:`ui/LUI/lui_function_properties.py::_draw_component_properties`
- 当前规模:`1441` 行
- 目标文件:`ui/panels/editor_panels_top.py::draw_menu_bar`
- 当前规模:`227` 行
- 验收目标:
- 按“变换/布局/视觉/交互/脚本”拆分为多个子绘制函数
- 主流程压缩并保持属性面板行为一致
- 拆分为“菜单组渲染 / 行为分发 / 状态同步”子流程
- 单函数规模收敛到 `<= 170`
- 保持现有菜单项顺序与功能一致
### N2 `LUI 缩放链路` 拆分(高优先级,非 VR
### N2 `场景保存链路` 拆分(高优先级,非 VR
- 目标文件:`ui/LUI/lui_manager_interaction.py::_handle_resize_drag`
- 当前规模:`257` 行
- 目标文件:`scene/scene_manager_io_mixin.py::saveScene`
- 当前规模:`222` 行
- 验收目标:
- 入口流程收敛到 `<= 120`
- 保持缩放手柄行为与边界语义一致
- 拆分为“保存前校验 / 节点序列化 / 后处理”子流程
- 单函数规模收敛到 `<= 170`
- 保持保存行为与输出格式一致
### N3 `属性面板方法注入` 拆分(中优先级,非 VR
- 目标文件:`core/InfoPanelManager.py::add_methods_to_property_panel`
- 当前规模:`209` 行
- 验收目标:
- 拆分为“能力绑定 / 兼容兜底 / 日志分层”子流程
- 单函数规模收敛到 `<= 170`
- 保持属性面板行为一致
## 验收标准(阶段)

File diff suppressed because it is too large Load Diff

View File

@ -28,231 +28,245 @@ class LUIManagerEditorMixin:
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)):
if not self._is_valid_relationship_index(child_index, parent_index):
print("Error: invalid component index")
return
child_data = self.components[child_index]
parent_data = self.components[parent_index]
if not self._prepare_parent_child_link(child_index, parent_index, child_data, parent_data):
return
# 编排入口:仅更新逻辑父子关系,不执行真实 reparent避免破坏复杂组件内部结构。
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})")
self._remove_child_from_old_parent(child_data, child_index)
self._attach_child_to_new_parent(child_data, parent_data, child_index, parent_index)
new_local_left, new_local_top = self._compute_new_child_local_position(
child_index, parent_index, current_abs_left, current_abs_top
)
child_data['left'] = new_local_left
child_data['top'] = new_local_top
child_obj = child_data['object']
parent_obj = self._resolve_parent_visual_object(parent_data)
try:
self._set_child_visual_parent_policy(child_data, parent_data)
if not keep_world:
new_local_left, new_local_top = self._clamp_child_local_within_parent(
child_data, parent_data, child_obj, parent_obj, new_local_left, new_local_top
)
child_data['left'] = new_local_left
child_data['top'] = new_local_top
self._sync_child_canvas_index_with_parent(child_data, parent_data)
self._apply_child_absolute_position(child_obj, current_abs_left, current_abs_top, keep_world)
self._ensure_child_visibility_and_layer(child_data, parent_data, child_obj, parent_obj)
except Exception as e:
print(f"Reparenting error: {e}")
self._finalize_parent_child_link(child_index, parent_index, parent_data)
def _is_valid_relationship_index(self, child_index, parent_index):
return (
0 <= child_index < len(self.components)
and 0 <= parent_index < len(self.components)
)
def _prepare_parent_child_link(self, child_index, parent_index, child_data, parent_data):
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 False
return True
def _remove_child_from_old_parent(self, child_data, child_index):
old_parent_index = child_data.get('parent_index')
if old_parent_index is None or old_parent_index < 0:
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)
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)
def _attach_child_to_new_parent(self, child_data, parent_data, child_index, parent_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)
def _compute_new_child_local_position(self, child_index, parent_index, current_abs_left, current_abs_top):
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})")
return new_local_left, new_local_top
# 在数据结构中存储局部坐标(相对于父组件)
child_data['left'] = new_local_left
child_data['top'] = new_local_top
child_obj = child_data['object']
def _resolve_parent_visual_object(self, parent_data):
parent_obj = parent_data['object']
parent_type = parent_data.get('type')
if parent_type == 'ScrollableRegion':
return parent_data['object'].content_node
if parent_type in ['VerticalLayout', 'HorizontalLayout'] and parent_data.get('layout_obj'):
if parent_data.get('layout_wrap', True):
return parent_data.get('object')
return parent_data.get('layout_obj').cell()
return parent_obj
def _set_child_visual_parent_policy(self, child_data, parent_data):
visual_container_types = [
'Frame', 'Plane', 'Video', 'HttpText',
'ScrollableRegion', 'TabbedFrame',
'VerticalLayout', 'HorizontalLayout'
'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()
if parent_data.get('type') in visual_container_types:
child_data['visual_parent_canvas'] = False
print("[_set_parent_child_relationship] Setting parent relationship (data only, no reparenting)")
else:
# 非视觉容器保持画布父级显示,避免被父组件裁剪。
child_data['visual_parent_canvas'] = True
def _clamp_child_local_within_parent(
self, child_data, parent_data, child_obj, parent_obj, new_local_left, new_local_top
):
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
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
# 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
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
# 关键修改由于我们不实际重新父化对象所有组件都保持相对于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})")
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))
except Exception:
pass
# 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
return new_local_left, new_local_top
# 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', 'HttpText']:
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}")
def _sync_child_canvas_index_with_parent(self, child_data, parent_data):
if not child_data.get('visual_parent_canvas'):
return
try:
child_data['canvas_index'] = parent_data.get('canvas_index', self.current_canvas_index)
except Exception:
pass
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'):
def _apply_child_absolute_position(self, child_obj, current_abs_left, current_abs_top, keep_world):
if hasattr(child_obj, 'left'):
child_obj.left = current_abs_left
if hasattr(child_obj, 'top'):
child_obj.top = current_abs_top
if keep_world:
print(f"[_set_parent_child_relationship] Child position kept at absolute: ({current_abs_left:.1f}, {current_abs_top:.1f})")
else:
print(f"[_set_parent_child_relationship] Child position set to absolute: ({current_abs_left:.1f}, {current_abs_top:.1f})")
def _ensure_child_visibility_and_layer(self, child_data, parent_data, child_obj, parent_obj):
if hasattr(child_obj, 'z_offset'):
try:
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
child_obj.z_offset = float(parent_z) + 2.0
except Exception:
pass
comp_type = child_data.get('type')
if comp_type == 'Button':
self._ensure_button_child_visibility(child_obj)
elif comp_type == 'Frame':
self._ensure_frame_child_visibility(child_obj)
elif comp_type in ['Plane', 'Image', 'Video', 'HttpText']:
self._ensure_media_child_visibility(child_data)
self._ensure_child_sprite_visibility_and_layer(child_data, parent_data, child_obj)
if hasattr(child_obj, 'show'):
child_obj.show()
def _ensure_button_child_visibility(self, child_obj):
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
if lbl is not None and hasattr(lbl, 'show'):
lbl.show()
except Exception:
pass
def _ensure_frame_child_visibility(self, child_obj):
try:
if hasattr(child_obj, 'show'):
child_obj.show()
if hasattr(child_obj, 'visible'):
child_obj.visible = True
print("[_set_parent_child_relationship] Frame visibility ensured")
except Exception as e:
print(f"Reparenting error: {e}")
print(f"[_set_parent_child_relationship] Error ensuring Frame visibility: {e}")
# Ensure canvas_index follows parent canvas (visibility)
def _ensure_media_child_visibility(self, child_data):
try:
spr = child_data.get('sprite')
if spr is not None:
if hasattr(spr, 'show'):
spr.show()
if hasattr(spr, 'visible'):
spr.visible = True
if child_data.get('type') == 'Plane' and hasattr(spr, 'color'):
spr.color = child_data.get('color', (1, 1, 1, 1))
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}")
def _ensure_child_sprite_visibility_and_layer(self, child_data, parent_data, child_obj):
child_sprite = child_data.get('sprite')
if child_sprite is None:
return
try:
if hasattr(child_sprite, 'show'):
child_sprite.show()
if hasattr(child_sprite, 'parent') and child_sprite.parent != child_obj:
child_sprite.parent = child_obj
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_z = 0.0
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)
child_sprite.z_offset = max(
parent_sprite_z + 1.0,
float(getattr(child_obj, 'z_offset', 0)) + 1.0,
)
except Exception:
pass
def _finalize_parent_child_link(self, child_index, parent_index, parent_data):
parent_canvas = parent_data.get('canvas_index')
if parent_canvas is not None:
self._update_canvas_index_recursive(child_index, parent_canvas)

View File

@ -1213,68 +1213,140 @@ class LUIManagerInteractionMixin:
print("✓ 重新创建了手柄,确保在最前面")
def _handle_resize_drag(self):
"""处理 resize 拖拽(编排入口)。"""
self._handle_resize_drag_core()
def _handle_resize_drag_core(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
if self._finish_resize_drag_if_released(p3d):
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相对坐标
new_left, new_top, new_width, new_height = self._compute_resize_drag_bounds(
delta_x, delta_y, shift_held, alt_held
)
if self.selected_index < 0:
return
comp_data = self.components[self.selected_index]
self._apply_resize_drag_updates(
comp_data, comp_obj, comp_type,
new_left, new_top, new_width, new_height,
shift_held, alt_held,
)
def _finish_resize_drag_if_released(self, p3d):
"""鼠标释放时结束缩放并执行收尾同步。"""
if self.world.mouseWatcherNode.is_button_down(p3d.MouseButton.one()):
return False
if self.resizing_handle and self.selected_index >= 0 and self.selected_index < len(self.components):
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 True
def _compute_resize_drag_bounds(self, delta_x, delta_y, shift_held, alt_held):
"""根据当前手柄/修饰键计算新的 left/top/width/height。"""
start_left, start_top, start_width, start_height, aspect_ratio = self._get_resize_start_values()
handle_pos = self.resizing_handle._resize_position
new_left, new_top, new_width, new_height = self._apply_resize_handle_delta(
handle_pos, start_left, start_top, start_width, start_height, delta_x, delta_y
)
new_left, new_top, new_width, new_height = self._apply_resize_min_size_clamp(
handle_pos, start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height,
)
if shift_held:
new_left, new_top, new_width, new_height = self._apply_shift_keep_ratio_resize(
handle_pos, start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height, aspect_ratio,
)
if alt_held:
new_left, new_top, new_width, new_height = self._apply_alt_center_resize(
start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height,
)
self._cache_resize_canvas_bounds()
return self._sanitize_resize_dimensions(new_left, new_top, new_width, new_height)
def _apply_resize_drag_updates(
self, comp_data, comp_obj, comp_type,
new_left, new_top, new_width, new_height,
shift_held, alt_held,
):
"""将缩放结果写回组件数据与渲染对象。"""
parent_index, parent_offset_x, parent_offset_y, is_scene_parented = self._resolve_resize_parent_context(
comp_data, comp_obj
)
_ = parent_index
self._write_resize_component_data(comp_data, new_left, new_top, new_width, new_height)
self._apply_resize_component_position(
comp_obj, new_left, new_top, parent_offset_x, parent_offset_y, is_scene_parented
)
try:
self._apply_resize_component_size_by_type(comp_data, comp_obj, comp_type, new_width, new_height)
self._post_resize_component_sync(comp_type, shift_held, alt_held)
except Exception as e:
print(f"⚠ 设置组件尺寸失败 ({comp_type}): {e}")
def _get_resize_start_values(self):
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
return start_left, start_top, start_width, start_height, aspect_ratio
def _apply_resize_handle_delta(
self, handle_pos, start_left, start_top, start_width, start_height, delta_x, delta_y
):
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
@ -1301,171 +1373,191 @@ class LUIManagerInteractionMixin:
elif handle_pos == 'left':
new_left = start_left + delta_x
new_width = start_width - delta_x
# 应用最小尺寸限制
return new_left, new_top, new_width, new_height
def _apply_resize_min_size_clamp(
self, handle_pos, start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height,
):
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
return new_left, new_top, new_width, new_height
def _apply_shift_keep_ratio_resize(
self, handle_pos, start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height, aspect_ratio,
):
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
return new_left, new_top, new_width, new_height
def _apply_alt_center_resize(
self, start_left, start_top, start_width, start_height,
new_left, new_top, new_width, new_height,
):
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
# 再次应用最小尺寸限制
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高度
return new_left, new_top, new_width, new_height
def _cache_resize_canvas_bounds(self):
canvas_width = 800
canvas_height = 600
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
_ = (canvas_width, canvas_height)
comp_data = self.components[self.selected_index]
def _sanitize_resize_dimensions(self, new_left, new_top, new_width, new_height):
if new_width < 1.0:
new_width = 1.0
if new_height < 1.0:
new_height = 1.0
return new_left, new_top, new_width, new_height
def _resolve_resize_parent_context(self, comp_data, comp_obj):
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)
parent_offset_x, parent_offset_y = self._get_component_accumulated_pos(parent_index)
# Update component data
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')
is_scene_parented = (child_parent is not None and child_parent == parent_obj)
return parent_index, parent_offset_x, parent_offset_y, is_scene_parented
def _write_resize_component_data(self, comp_data, new_left, new_top, new_width, new_height):
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)
def _apply_resize_component_position(
self, comp_obj, new_left, new_top, parent_offset_x, parent_offset_y, is_scene_parented
):
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
def _apply_resize_component_size_by_type(self, comp_data, comp_obj, comp_type, new_width, new_height):
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':
self._apply_button_resize_size(comp_obj, new_width, new_height)
elif comp_type == 'InputField':
self._apply_input_field_resize_size(comp_obj, new_width, new_height)
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)
def _apply_button_resize_size(self, comp_obj, new_width, new_height):
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()
def _apply_input_field_resize_size(self, comp_obj, new_width, new_height):
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
self._sync_input_field_layout_stretch(comp_obj)
def _sync_input_field_layout_stretch(self, comp_obj):
try:
if hasattr(comp_obj, 'width'):
comp_obj.width = new_width
if hasattr(comp_obj, 'height'):
comp_obj.height = new_height
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
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)
def _post_resize_component_sync(self, comp_type, shift_held, alt_held):
if comp_type in ['VerticalLayout', 'HorizontalLayout']:
self._update_layout_inner(self.selected_index)
if comp_type in ['VerticalLayout', 'HorizontalLayout']:
self._update_layout_inner(self.selected_index)
self._update_anchored_children(self.selected_index)
modifier_str = self._get_resize_modifier_info(shift_held, alt_held)
_ = modifier_str
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 _get_resize_modifier_info(self, shift_held, alt_held):
modifier_info = []
if shift_held:
modifier_info.append('Shift(keep ratio)')
if alt_held:
modifier_info.append('Alt(center scale)')
return ' + '.join(modifier_info) if modifier_info else 'Normal'