继续优化:拆分editor_panels并收敛旧接口
This commit is contained in:
parent
e9784b3400
commit
3a13025009
62
IMGUI_MODULE_ANALYSIS.md
Normal file
62
IMGUI_MODULE_ANALYSIS.md
Normal file
@ -0,0 +1,62 @@
|
||||
# EG 本地项目模块分析(Qt 清理后)
|
||||
|
||||
更新时间:2026-02-28
|
||||
分析范围:`d:\IMGUI\EG` 本地文件,不包含任何远程仓库信息。
|
||||
|
||||
## 1. 当前结论
|
||||
|
||||
- 主编辑器路径已切换为 ImGui 方案,Qt/PySide 在项目代码主路径中已清理完成。
|
||||
- 本地扫描结果(排除 `RenderPipelineFile` 第三方目录):
|
||||
- `PyQt/PySide/Qt` 关键引用:`0`
|
||||
- Python 脚本数:`385`
|
||||
- 当前主要技术债已从“Qt 运行时依赖”转为“旧接口命名和耦合残留”。
|
||||
- 已完成基础稳定性优化:入口保护、兼容字段初始化、脚本恢复拼写错误修复。
|
||||
- 已完成第一轮接口收敛:`event_handler/selection/runtime_actions/InfoPanelManager` 已改为 helper 访问模式。
|
||||
|
||||
## 2. 本轮 Qt 清理落点
|
||||
|
||||
已完成清理的关键文件:
|
||||
|
||||
- [core/InfoPanelManager.py](d:/IMGUI/EG/core/InfoPanelManager.py)
|
||||
- [core/selection.py](d:/IMGUI/EG/core/selection.py)
|
||||
- [scene/scene_manager_convert_tiles_mixin.py](d:/IMGUI/EG/scene/scene_manager_convert_tiles_mixin.py)
|
||||
- [ui/widgets.py](d:/IMGUI/EG/ui/widgets.py)
|
||||
- [ui/icon_manager.py](d:/IMGUI/EG/ui/icon_manager.py)
|
||||
- [core/world.py](d:/IMGUI/EG/core/world.py)
|
||||
- [core/vr_manager.py](d:/IMGUI/EG/core/vr_manager.py)
|
||||
- [core/vr/testing/test_mode.py](d:/IMGUI/EG/core/vr/testing/test_mode.py)
|
||||
- [scene/tree_roles.py](d:/IMGUI/EG/scene/tree_roles.py)
|
||||
- [TransformGizmo/move_gizmo.py](d:/IMGUI/EG/TransformGizmo/move_gizmo.py)
|
||||
- [TransformGizmo/rotate_gizmo.py](d:/IMGUI/EG/TransformGizmo/rotate_gizmo.py)
|
||||
- [TransformGizmo/scale_gizmo.py](d:/IMGUI/EG/TransformGizmo/scale_gizmo.py)
|
||||
- [requirements/requirements.txt](d:/IMGUI/EG/requirements/requirements.txt)
|
||||
- [requirements/clean-requirements.txt](d:/IMGUI/EG/requirements/clean-requirements.txt)
|
||||
- [requirements/environment.yml](d:/IMGUI/EG/requirements/environment.yml)
|
||||
- [requirements/DEPLOYMENT_README.md](d:/IMGUI/EG/requirements/DEPLOYMENT_README.md)
|
||||
|
||||
## 3. 仍需优化的结构点(非 Qt 依赖问题)
|
||||
|
||||
- 旧接口命名残留仍较多:`interface_manager/treeWidget/gui_manager` 引用约 `92` 处。
|
||||
- `main.py` 仍是脚本式入口(模块导入副作用风险仍在)。
|
||||
- 场景树与 GUI 元素生命周期在多个模块分散维护,后续建议继续做单一上下文收敛。
|
||||
|
||||
## 4. 风险分级(Qt 清理后)
|
||||
|
||||
- 高风险:
|
||||
- 多处旧接口命名仍在调用链中,后续重构若不做适配层可能引发行为回归。
|
||||
- 中风险:
|
||||
- 场景保存/加载与树状态同步仍跨模块分散,维护成本高。
|
||||
- 低风险:
|
||||
- 文档和模板文件中仍有历史术语,易造成新成员理解偏差。
|
||||
|
||||
## 5. 后续建议顺序
|
||||
|
||||
1. 建立统一 EditorContext,收敛 `interface_manager/gui_manager` 访问入口。
|
||||
2. 将场景树操作抽象为 ImGui 专用接口,逐步移除 `treeWidget` 语义。
|
||||
3. 统一入口规范(`if __name__ == "__main__":`)并补最小回归脚本。
|
||||
|
||||
## 6. 全量目录与脚本清单
|
||||
|
||||
为后续优化分析,完整目录树与全部脚本行数/用途清单见:
|
||||
|
||||
- [PROJECT_FULL_CATALOG.md](d:/IMGUI/EG/PROJECT_FULL_CATALOG.md)
|
||||
2465
PROJECT_FULL_CATALOG.md
Normal file
2465
PROJECT_FULL_CATALOG.md
Normal file
File diff suppressed because it is too large
Load Diff
62
QT_TO_IMGUI_MIGRATION_CHECKLIST.md
Normal file
62
QT_TO_IMGUI_MIGRATION_CHECKLIST.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Qt -> ImGui 迁移清单(Qt 清理后状态)
|
||||
|
||||
更新时间:2026-02-28
|
||||
适用范围:`d:\IMGUI\EG` 本地工作区
|
||||
|
||||
## 目标
|
||||
|
||||
- 编辑器主路径稳定运行在 ImGui + Panda3D。
|
||||
- 项目主代码不再依赖 PyQt/PySide 运行。
|
||||
- 后续把历史命名与耦合(`interface_manager/treeWidget/gui_manager`)继续收敛。
|
||||
|
||||
## 当前状态总览
|
||||
|
||||
- `PyQt/PySide/Qt` 在项目代码主路径(排除 `RenderPipelineFile`)扫描结果:`0`。
|
||||
- 依赖清单已从 Qt 运行依赖切到 ImGui 运行依赖。
|
||||
- `ui/widgets.py` 已改为 legacy 占位模块,避免误用 Qt 路径。
|
||||
|
||||
## 已完成(Done)
|
||||
|
||||
- [x] `core/InfoPanelManager.py`:去除 Qt 直接导入。
|
||||
- [x] `core/selection.py`:光标逻辑改为 Panda3D 路径。
|
||||
- [x] `scene/scene_manager_convert_tiles_mixin.py`:去掉 `QProgressDialog`,改为非 Qt 进度反馈。
|
||||
- [x] `ui/widgets.py`:替换为 ImGui 迁移提示占位模块。
|
||||
- [x] `ui/icon_manager.py`:替换为无 Qt 的兼容层实现。
|
||||
- [x] `core/world.py` / `core/vr_manager.py` / `core/vr/testing/test_mode.py`:去除 `qtWidget` 语义依赖。
|
||||
- [x] `requirements/*`:清理 Qt/PySide 依赖项并同步部署文档。
|
||||
- [x] `main.py`:补齐兼容字段初始化(`gui_elements/gui_manager/interface_manager/guiEditMode/currentGUITool`)。
|
||||
- [x] `scene/scene_manager_io_mixin.py`:修复 `_find_scrip_in_directory` 拼写错误。
|
||||
- [x] `main.py`:入口改为 `if __name__ == "__main__":`,消除导入即运行副作用。
|
||||
- [x] `core/event_handler.py`:新增场景树访问 helper,替换 `interface_manager.treeWidget` 直连。
|
||||
- [x] `core/selection.py`:统一树控件清空逻辑(`_get_tree_widget/_clear_tree_selection`)。
|
||||
- [x] `ui/panels/runtime_actions.py`:统一 `gui_manager` 访问与 `gui_elements` 追加入口。
|
||||
- [x] `core/InfoPanelManager.py`:统一场景树控件访问 helper。
|
||||
- [x] `ui/panels/editor_panels.py`:按布局拆分为 4 个 mixin(`top/left/right/center`),保留门面类组合。
|
||||
|
||||
## 待完成(Next)
|
||||
|
||||
### N1 场景树接口收敛(高优先级)
|
||||
|
||||
- 目标:移除 `treeWidget` 语义,统一走 ImGui 场景树接口。
|
||||
- 影响路径:`core/event_handler.py`、`scene/scene_manager_*_mixin.py`、`core/terrain_manager.py` 等。
|
||||
|
||||
### N2 GUI 管理上下文收敛(高优先级)
|
||||
|
||||
- 目标:将 `interface_manager/gui_manager/gui_elements` 统一到单一上下文对象。
|
||||
- 影响路径:`project/project_manager.py`、`scene/scene_manager_io_mixin.py`、`ui/panels/*`。
|
||||
|
||||
### N3 入口规范化与回归脚本(中优先级)
|
||||
|
||||
- 目标:减少脚本式副作用,建立最小回归检查。
|
||||
- 建议覆盖:启动、导入模型、选择变换、保存加载、脚本挂载。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 不安装 PyQt/PySide 时,`python main.py` 能进入编辑器主界面。
|
||||
- 场景导入/保存/加载流程不触发 Qt 相关异常。
|
||||
- 关键交互(选择、变换、场景树)行为与清理前一致。
|
||||
|
||||
## 关联文档
|
||||
|
||||
- 模块分析: [IMGUI_MODULE_ANALYSIS.md](d:/IMGUI/EG/IMGUI_MODULE_ANALYSIS.md)
|
||||
- 全量目录与脚本清单: [PROJECT_FULL_CATALOG.md](d:/IMGUI/EG/PROJECT_FULL_CATALOG.md)
|
||||
@ -14,6 +14,43 @@ class EventHandler:
|
||||
self.showRay = False # 是否显示射线(默认关闭)
|
||||
self.rayNode = None # 当前显示的射线节点
|
||||
self.rayLifetime = 2.0 # 射线显示时间(秒)
|
||||
|
||||
def _get_interface_manager(self):
|
||||
"""获取兼容层中的场景树管理器(若存在)。"""
|
||||
return getattr(self.world, "interface_manager", None)
|
||||
|
||||
def _get_tree_widget(self):
|
||||
"""安全获取场景树控件。"""
|
||||
interface_manager = self._get_interface_manager()
|
||||
if not interface_manager:
|
||||
return None
|
||||
return getattr(interface_manager, "treeWidget", None)
|
||||
|
||||
def _sync_tree_selection(self, selected_model):
|
||||
"""将当前选中模型同步到场景树。"""
|
||||
interface_manager = self._get_interface_manager()
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not interface_manager or not tree_widget:
|
||||
return False
|
||||
|
||||
root = tree_widget.invisibleRootItem()
|
||||
found_item = None
|
||||
|
||||
for i in range(root.childCount()):
|
||||
scene_item = root.child(i)
|
||||
if scene_item.text(0) != "场景":
|
||||
continue
|
||||
found_item = interface_manager.findTreeItem(selected_model, scene_item)
|
||||
if found_item:
|
||||
try:
|
||||
tree_widget.itemClicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
tree_widget.setCurrentItem(found_item)
|
||||
tree_widget.itemClicked.connect(interface_manager.onTreeItemClicked)
|
||||
return True
|
||||
break
|
||||
return False
|
||||
|
||||
def showClickRay(self, nearPoint, farPoint, hitPos=None):
|
||||
"""显示鼠标点击的射线"""
|
||||
@ -239,17 +276,22 @@ class EventHandler:
|
||||
|
||||
# 处理GUI编辑模式
|
||||
if self.world.guiEditMode:
|
||||
gui_manager = getattr(self.world, "gui_manager", None)
|
||||
if not gui_manager:
|
||||
print("GUI编辑模式已开启,但 gui_manager 不可用")
|
||||
pickerNP.removeNode()
|
||||
return
|
||||
#print("处理GUI编辑模式点击")
|
||||
# 检查是否点击了GUI元素
|
||||
clickedGUI = self.world.gui_manager.findClickedGUI(hitNode)
|
||||
clickedGUI = gui_manager.findClickedGUI(hitNode)
|
||||
if clickedGUI:
|
||||
# 选中GUI元素
|
||||
self.world.selection.updateSelection(clickedGUI)
|
||||
self.world.gui_manager.selectGUIInTree(clickedGUI)
|
||||
gui_manager.selectGUIInTree(clickedGUI)
|
||||
print(f"选中GUI元素: {clickedGUI.getTag('gui_text')}")
|
||||
elif hasattr(self.world, 'currentGUITool') and self.world.currentGUITool:
|
||||
# 在点击位置创建新GUI元素
|
||||
self.world.gui_manager.createGUIAtPosition(hitPos, self.world.currentGUITool)
|
||||
gui_manager.createGUIAtPosition(hitPos, self.world.currentGUITool)
|
||||
pickerNP.removeNode()
|
||||
return
|
||||
|
||||
@ -280,7 +322,9 @@ class EventHandler:
|
||||
default_height = 0.0
|
||||
world_pos = Point3(mx * 10, 0, my * 10) # 简单的屏幕到世界坐标转换
|
||||
world_pos.setZ(default_height)
|
||||
self.world.gui_manager.createGUIAtPosition(world_pos, self.world.currentGUITool)
|
||||
gui_manager = getattr(self.world, "gui_manager", None)
|
||||
if gui_manager:
|
||||
gui_manager.createGUIAtPosition(world_pos, self.world.currentGUITool)
|
||||
|
||||
# 确保总是清理碰撞检测节点
|
||||
try:
|
||||
@ -488,35 +532,12 @@ class EventHandler:
|
||||
self.world.selection.updateSelection(selectedModel)
|
||||
|
||||
# 在树形控件中查找并选中对应的项
|
||||
if hasattr(self.world, 'interface_manager') and self.world.interface_manager and hasattr(self.world.interface_manager, 'treeWidget') and self.world.interface_manager.treeWidget:
|
||||
#print("查找树形控件中的对应项...")
|
||||
root = self.world.interface_manager.treeWidget.invisibleRootItem()
|
||||
foundItem = None
|
||||
|
||||
for i in range(root.childCount()):
|
||||
sceneItem = root.child(i)
|
||||
if sceneItem.text(0) == "场景":
|
||||
#print(f"在场景节点下查找...")
|
||||
foundItem = self.world.interface_manager.findTreeItem(selectedModel, sceneItem)
|
||||
if foundItem:
|
||||
print(f"✓ 在树形控件中找到对应项: {foundItem.text(0)}")
|
||||
try:
|
||||
self.world.interface_manager.treeWidget.itemClicked.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(foundItem)
|
||||
|
||||
self.world.interface_manager.treeWidget.itemClicked.connect(
|
||||
self.world.interface_manager.onTreeItemClicked)
|
||||
else:
|
||||
print("× 在树形控件中没有找到对应项")
|
||||
break
|
||||
|
||||
if not foundItem:
|
||||
print("× 没有找到场景节点或对应的树形项")
|
||||
if self._sync_tree_selection(selectedModel):
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget and tree_widget.currentItem():
|
||||
print(f"✓ 在树形控件中找到对应项: {tree_widget.currentItem().text(0)}")
|
||||
else:
|
||||
print("× 树形控件不存在")
|
||||
print("× 场景树未同步(接口不可用或未找到对应项)")
|
||||
else:
|
||||
print("× 没有找到可选择的模型节点")
|
||||
self.world.selection.updateSelection(None)
|
||||
@ -534,11 +555,12 @@ class EventHandler:
|
||||
super(type(self.world), self.world).wheelForward(data)
|
||||
|
||||
# 更新属性面板
|
||||
if (self.world.interface_manager.treeWidget and
|
||||
self.world.interface_manager.treeWidget.currentItem() and
|
||||
self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"):
|
||||
self.world.property_panel.updatePropertyPanel(
|
||||
self.world.interface_manager.treeWidget.currentItem())
|
||||
tree_widget = self._get_tree_widget()
|
||||
current_item = tree_widget.currentItem() if tree_widget else None
|
||||
if (current_item and
|
||||
current_item.text(0) == "相机" and
|
||||
hasattr(self.world, "property_panel")):
|
||||
self.world.property_panel.updatePropertyPanel(current_item)
|
||||
|
||||
def wheelBackward(self, data=None):
|
||||
"""处理滚轮向后滚动(后退)"""
|
||||
@ -546,11 +568,12 @@ class EventHandler:
|
||||
super(type(self.world), self.world).wheelBackward(data)
|
||||
|
||||
# 更新属性面板
|
||||
if (self.world.interface_manager.treeWidget and
|
||||
self.world.interface_manager.treeWidget.currentItem() and
|
||||
self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"):
|
||||
self.world.property_panel.updatePropertyPanel(
|
||||
self.world.interface_manager.treeWidget.currentItem())
|
||||
tree_widget = self._get_tree_widget()
|
||||
current_item = tree_widget.currentItem() if tree_widget else None
|
||||
if (current_item and
|
||||
current_item.text(0) == "相机" and
|
||||
hasattr(self.world, "property_panel")):
|
||||
self.world.property_panel.updatePropertyPanel(current_item)
|
||||
|
||||
def mousePressEventMiddle(self, evt):
|
||||
"""处理鼠标中键按下事件"""
|
||||
|
||||
@ -90,6 +90,22 @@ class SelectionSystem:
|
||||
self._double_click_task = None
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
|
||||
def _get_tree_widget(self):
|
||||
"""统一获取场景树控件。"""
|
||||
interface_manager = getattr(self.world, "interface_manager", None)
|
||||
if not interface_manager:
|
||||
return None
|
||||
return getattr(interface_manager, "treeWidget", None)
|
||||
|
||||
def _clear_tree_selection(self):
|
||||
"""清空场景树选中项。"""
|
||||
tree_widget = self._get_tree_widget()
|
||||
if not tree_widget:
|
||||
return False
|
||||
tree_widget.setCurrentItem(None)
|
||||
return True
|
||||
|
||||
# ==================== 光标设置 ====================
|
||||
def _setCursor(self,cursor_type):
|
||||
try:
|
||||
@ -2103,10 +2119,7 @@ class SelectionSystem:
|
||||
print("✓ 取消选择")
|
||||
|
||||
#当取消选择时,同步清空树形控件的选中状态
|
||||
if (hasattr(self.world,'interface_manager')and
|
||||
self.world.interface_manager and
|
||||
self.world.interface_manager.treeWidget):
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(None)
|
||||
if self._clear_tree_selection():
|
||||
print("✓ 树形控件选中状态已清空")
|
||||
|
||||
#print("=== 选择状态更新完成 ===\n")
|
||||
@ -2955,11 +2968,7 @@ class SelectionSystem:
|
||||
self.clearGizmo()
|
||||
|
||||
# 清除树形控件中的选择
|
||||
if (hasattr(self.world, 'interface_manager') and
|
||||
self.world.interface_manager and
|
||||
hasattr(self.world.interface_manager, 'treeWidget') and
|
||||
self.world.interface_manager.treeWidget):
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(None)
|
||||
self._clear_tree_selection()
|
||||
|
||||
print("已清除选择")
|
||||
except Exception as e:
|
||||
|
||||
12
main.py
12
main.py
@ -68,6 +68,13 @@ class MyWorld(PanelDelegates, CoreWorld):
|
||||
#props.set_maximized(True)
|
||||
self.win.request_properties(props)
|
||||
print("✓ 窗口已设置为最大化模式")
|
||||
|
||||
# Legacy compatibility fields used by scene/project modules.
|
||||
self.gui_elements = []
|
||||
self.gui_manager = None
|
||||
self.interface_manager = None
|
||||
self.guiEditMode = False
|
||||
self.currentGUITool = None
|
||||
|
||||
# 初始化选择和变换系统
|
||||
self.selection = SelectionSystem(self)
|
||||
@ -817,5 +824,6 @@ class MyWorld(PanelDelegates, CoreWorld):
|
||||
except Exception as e:
|
||||
print(f"绑定 TransformGizmo 灯光同步事件失败: {e}")
|
||||
|
||||
demo = MyWorld()
|
||||
demo.run()
|
||||
if __name__ == "__main__":
|
||||
demo = MyWorld()
|
||||
demo.run()
|
||||
|
||||
@ -811,7 +811,7 @@ class SceneManagerIOMixin:
|
||||
loaded_class = script_manager.load_script_from_file(script_file)
|
||||
if loaded_class is None:
|
||||
print(f"从文件加载脚本失败{script_file}")
|
||||
script_path = self._find_scrip_in_directory(script_name)
|
||||
script_path = self._find_script_in_directory(script_name)
|
||||
if script_path:
|
||||
print(f"从目录找到脚本并加载{script_path}")
|
||||
script_manager.load_script_from_file(script_path)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
222
ui/panels/editor_panels_center.py
Normal file
222
ui/panels/editor_panels_center.py
Normal file
@ -0,0 +1,222 @@
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
import io
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from panda3d.core import Filename
|
||||
try:
|
||||
from PIL import Image
|
||||
except Exception: # pragma: no cover - pillow may be missing in minimal env
|
||||
Image = None
|
||||
|
||||
class EditorPanelsCenterMixin:
|
||||
"""Auto-split mixin from editor_panels.py."""
|
||||
|
||||
def _on_create_web_panel(self):
|
||||
"""创建或激活 ImGui Web 面板。"""
|
||||
self._ensure_web_panel_state()
|
||||
self.app.showWebPanel = True
|
||||
|
||||
webview = getattr(self.app, "_imgui_webview", None)
|
||||
if webview and getattr(webview, "_running", False):
|
||||
return True
|
||||
|
||||
return self._start_imgui_webview(self.app.web_panel_url_input)
|
||||
|
||||
def _ensure_web_panel_state(self):
|
||||
if not hasattr(self.app, "web_panel_url_input") or not self.app.web_panel_url_input:
|
||||
self.app.web_panel_url_input = "https://www.baidu.com"
|
||||
if not hasattr(self.app, "_imgui_webview"):
|
||||
self.app._imgui_webview = None
|
||||
if not hasattr(self.app, "_imgui_webview_tex_id"):
|
||||
self.app._imgui_webview_tex_id = None
|
||||
if not hasattr(self.app, "_imgui_webview_texture_path"):
|
||||
self.app._imgui_webview_texture_path = None
|
||||
|
||||
def _start_imgui_webview(self, url):
|
||||
self._ensure_web_panel_state()
|
||||
self._stop_imgui_webview()
|
||||
try:
|
||||
target_url = (url or "").strip()
|
||||
if not target_url:
|
||||
target_url = "https://www.example.com"
|
||||
if not target_url.startswith(("http://", "https://", "file://")):
|
||||
target_url = "https://" + target_url
|
||||
|
||||
from core.imgui_webview import ImGuiWebView
|
||||
webview = ImGuiWebView(width=1280, height=720)
|
||||
webview.start(target_url)
|
||||
self.app._imgui_webview = webview
|
||||
return True
|
||||
except Exception as e:
|
||||
self.app.add_error_message(f"启动Web视图失败: {e}")
|
||||
return False
|
||||
|
||||
def _stop_imgui_webview(self):
|
||||
webview = getattr(self.app, "_imgui_webview", None)
|
||||
if webview:
|
||||
try:
|
||||
webview.stop()
|
||||
except Exception:
|
||||
pass
|
||||
self.app._imgui_webview = None
|
||||
|
||||
tex_id = getattr(self.app, "_imgui_webview_tex_id", None)
|
||||
if tex_id:
|
||||
try:
|
||||
self.app.imgui.removeTexture(tex_id)
|
||||
except Exception:
|
||||
pass
|
||||
self.app._imgui_webview_tex_id = None
|
||||
texture_path = getattr(self.app, "_imgui_webview_texture_path", None)
|
||||
if texture_path:
|
||||
try:
|
||||
Path(texture_path).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
self.app._imgui_webview_texture_path = None
|
||||
|
||||
def _navigate_web_panel(self):
|
||||
self._ensure_web_panel_state()
|
||||
url = (self.app.web_panel_url_input or "").strip()
|
||||
if not url:
|
||||
return
|
||||
|
||||
webview = getattr(self.app, "_imgui_webview", None)
|
||||
if not webview or not getattr(webview, "_running", False):
|
||||
self._start_imgui_webview(url)
|
||||
return
|
||||
|
||||
webview.navigate(url)
|
||||
|
||||
def _update_web_panel_texture(self):
|
||||
webview = getattr(self.app, "_imgui_webview", None)
|
||||
if not webview:
|
||||
return
|
||||
if not webview.tex_dirty:
|
||||
return
|
||||
|
||||
jpeg_bytes = webview.get_screenshot_bytes()
|
||||
if not jpeg_bytes:
|
||||
return
|
||||
|
||||
try:
|
||||
if Image is None:
|
||||
self.app.add_warning_message("缺少 Pillow,无法更新Web纹理")
|
||||
return
|
||||
img = Image.open(io.BytesIO(jpeg_bytes)).convert("RGBA")
|
||||
# p3dimgui 纹理坐标系与网页截图存在Y轴方向差异,先在像素层修正
|
||||
img = img.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
temp_dir = Path(tempfile.gettempdir()) / "eg_imgui_webpanel"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
temp_path = temp_dir / f"webview_{time.time_ns()}.png"
|
||||
img.save(temp_path, format="PNG")
|
||||
|
||||
panda_path = Filename.fromOsSpecific(str(temp_path)).getFullpath()
|
||||
new_tex_id = self.app.imgui.loadTexture(panda_path)
|
||||
old_tex_id = getattr(self.app, "_imgui_webview_tex_id", None)
|
||||
if old_tex_id:
|
||||
try:
|
||||
self.app.imgui.removeTexture(old_tex_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
old_texture_path = getattr(self.app, "_imgui_webview_texture_path", None)
|
||||
if old_texture_path:
|
||||
try:
|
||||
Path(old_texture_path).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.app._imgui_webview_tex_id = new_tex_id
|
||||
self.app._imgui_webview_texture_path = str(temp_path)
|
||||
except Exception as e:
|
||||
self.app.add_warning_message(f"Web纹理更新失败: {e}")
|
||||
finally:
|
||||
webview.tex_dirty = False
|
||||
|
||||
@staticmethod
|
||||
def _clamp01(value):
|
||||
return 0.0 if value < 0.0 else 1.0 if value > 1.0 else value
|
||||
|
||||
def _draw_web_panel(self):
|
||||
"""绘制 Web 面板(ImGui + 后台浏览器截图)。"""
|
||||
self._ensure_web_panel_state()
|
||||
if not self.app.showWebPanel:
|
||||
self._stop_imgui_webview()
|
||||
return
|
||||
|
||||
flags = self.app.style_manager.get_window_flags("panel")
|
||||
with self.app.style_manager.begin_styled_window("Web面板", self.app.showWebPanel, flags) as (_, opened):
|
||||
if not opened:
|
||||
self.app.showWebPanel = False
|
||||
self._stop_imgui_webview()
|
||||
return
|
||||
|
||||
self.app.showWebPanel = True
|
||||
|
||||
changed, self.app.web_panel_url_input = imgui.input_text(
|
||||
"URL", self.app.web_panel_url_input, 1024
|
||||
)
|
||||
if changed:
|
||||
self.app.web_panel_url_input = self.app.web_panel_url_input.strip()
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("访问"):
|
||||
self._navigate_web_panel()
|
||||
|
||||
webview = getattr(self.app, "_imgui_webview", None)
|
||||
if not webview:
|
||||
if not self._start_imgui_webview(self.app.web_panel_url_input):
|
||||
imgui.text_colored((1.0, 0.4, 0.4, 1.0), "Web视图启动失败")
|
||||
return
|
||||
webview = self.app._imgui_webview
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("后退"):
|
||||
webview.go_back()
|
||||
imgui.same_line()
|
||||
if imgui.button("前进"):
|
||||
webview.go_forward()
|
||||
imgui.same_line()
|
||||
if imgui.button("刷新"):
|
||||
webview.reload()
|
||||
|
||||
current_url = webview.current_url or self.app.web_panel_url_input
|
||||
if current_url:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), current_url)
|
||||
if webview.error:
|
||||
imgui.text_colored((1.0, 0.4, 0.4, 1.0), webview.error)
|
||||
|
||||
imgui.separator()
|
||||
|
||||
self._update_web_panel_texture()
|
||||
tex_id = getattr(self.app, "_imgui_webview_tex_id", None)
|
||||
available = imgui.get_content_region_avail()
|
||||
display_w = max(float(available.x), 64.0)
|
||||
display_h = max(float(available.y), 64.0)
|
||||
|
||||
if tex_id:
|
||||
imgui.image(tex_id, (display_w, display_h))
|
||||
|
||||
if imgui.is_item_hovered():
|
||||
mouse_wheel = imgui.get_io().mouse_wheel
|
||||
if abs(mouse_wheel) > 1e-4:
|
||||
webview.scroll(-mouse_wheel * 120.0)
|
||||
|
||||
if imgui.is_mouse_clicked(0):
|
||||
item_min = imgui.get_item_rect_min()
|
||||
item_max = imgui.get_item_rect_max()
|
||||
item_w = max(float(item_max.x - item_min.x), 1.0)
|
||||
item_h = max(float(item_max.y - item_min.y), 1.0)
|
||||
mouse_pos = imgui.get_mouse_pos()
|
||||
|
||||
x_ratio = self._clamp01((float(mouse_pos.x) - float(item_min.x)) / item_w)
|
||||
y_ratio = self._clamp01((float(mouse_pos.y) - float(item_min.y)) / item_h)
|
||||
webview.click(x_ratio, y_ratio)
|
||||
elif webview.is_loading:
|
||||
imgui.text("网页加载中...")
|
||||
else:
|
||||
imgui.text("正在初始化Web视图...")
|
||||
|
||||
615
ui/panels/editor_panels_left.py
Normal file
615
ui/panels/editor_panels_left.py
Normal file
@ -0,0 +1,615 @@
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
from pathlib import Path
|
||||
|
||||
class EditorPanelsLeftMixin:
|
||||
"""Auto-split mixin from editor_panels.py."""
|
||||
|
||||
def _draw_scene_tree(self):
|
||||
"""绘制场景树面板"""
|
||||
# 使用更少的限制性标志,允许docking
|
||||
flags = (imgui.WindowFlags_.no_collapse)
|
||||
|
||||
with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags):
|
||||
self.app.showSceneTree = True # 确保窗口保持打开
|
||||
window_pos = imgui.get_window_pos()
|
||||
window_size = imgui.get_window_size()
|
||||
self.app._scene_tree_window_rect = (
|
||||
float(window_pos.x),
|
||||
float(window_pos.y),
|
||||
float(window_size.x),
|
||||
float(window_size.y),
|
||||
)
|
||||
|
||||
imgui.text("场景层级")
|
||||
imgui.separator()
|
||||
|
||||
# 构建动态场景树
|
||||
self._build_scene_tree()
|
||||
|
||||
def _build_scene_tree(self):
|
||||
"""构建动态场景树"""
|
||||
# 渲染节点
|
||||
if imgui.tree_node("渲染"):
|
||||
# 环境光
|
||||
if hasattr(self.app, 'ambient_light') and self.app.ambient_light:
|
||||
self._draw_scene_node(self.app.ambient_light, "环境光", "light")
|
||||
|
||||
# 聚光灯
|
||||
if hasattr(self.app, 'scene_manager') and self.app.scene_manager:
|
||||
if hasattr(self.app.scene_manager, 'Spotlight') and self.app.scene_manager.Spotlight:
|
||||
for i, spotlight in enumerate(self.app.scene_manager.Spotlight):
|
||||
self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light")
|
||||
if hasattr(self.app.scene_manager, 'Pointlight') and self.app.scene_manager.Pointlight:
|
||||
for i, pointlight in enumerate(self.app.scene_manager.Pointlight):
|
||||
self._draw_scene_node(pointlight, f"点光源_{i+1}", "light")
|
||||
|
||||
# 地板
|
||||
if hasattr(self.app, 'ground') and self.app.ground:
|
||||
self._draw_scene_node(self.app.ground, "地板", "geometry")
|
||||
|
||||
imgui.tree_pop()
|
||||
|
||||
# 相机节点
|
||||
if imgui.tree_node("相机"):
|
||||
if hasattr(self.app, 'camera') and self.app.camera:
|
||||
self._draw_scene_node(self.app.camera, "主相机", "camera")
|
||||
imgui.tree_pop()
|
||||
|
||||
# 3D模型节点
|
||||
if imgui.tree_node("模型"):
|
||||
models = []
|
||||
if hasattr(self.app, 'scene_manager') and self.app.scene_manager and hasattr(self.app.scene_manager, 'models'):
|
||||
models.extend([m for m in self.app.scene_manager.models if m and not m.isEmpty()])
|
||||
|
||||
# SSBO模式下,模型可能不在 scene_manager.models 中,补充显示 ssbo_editor.model
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent() and ssbo_model not in models:
|
||||
models.append(ssbo_model)
|
||||
|
||||
if models:
|
||||
for i, model in enumerate(models):
|
||||
self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model")
|
||||
else:
|
||||
imgui.text("(空)")
|
||||
imgui.tree_pop()
|
||||
|
||||
# if imgui.tree_node("GUI元素"):
|
||||
# if hasattr(self,'gui_manager') and self.app.gui_manager and hasattr(self.app.gui_manager,'gui_elements'):
|
||||
# if self.app.gui_manager.gui_elements:
|
||||
# for gui_element in self.app.gui_manager.gui_elements:
|
||||
# if gui_element and hasattr(gui_element,'node'):
|
||||
# gui_type = getattr(gui_element,'gui_type','GUI_UNKNOWN')
|
||||
# display_name = getattr(gui_element,'name',gui_type)
|
||||
# self._draw_scene_node(gui_element.node,display_name,"gui",gui_type)
|
||||
# else:
|
||||
# imgui.text("(空)")
|
||||
# else:
|
||||
# imgui.text("(空)")
|
||||
# imgui.tree_pop()
|
||||
|
||||
# LUI元素节点
|
||||
if imgui.tree_node("GUI元素"):
|
||||
if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled:
|
||||
self.app.lui_manager.draw_component_tree()
|
||||
imgui.tree_pop()
|
||||
# if imgui.tree_node("LUI元素"):
|
||||
# if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled:
|
||||
# if self.app.lui_manager.components:
|
||||
# for comp in self.app.lui_manager.components:
|
||||
# if 'node' in comp:
|
||||
# self._draw_scene_node(comp['node'], comp['name'], "ui")
|
||||
|
||||
# if self.app.lui_manager.canvases:
|
||||
# for canvas in self.app.lui_manager.canvases:
|
||||
# if imgui.tree_node(f"Canvas: {canvas['name']}"):
|
||||
# # 实际上组件已经在 node 下了,可以通过 _draw_scene_node 递归显示
|
||||
# # 但为了清晰,我们可以手动列出或者依赖递归
|
||||
# self._draw_scene_node(canvas['node'], canvas['name'], "geometry")
|
||||
# imgui.tree_pop()
|
||||
# else:
|
||||
# imgui.text("(空)")
|
||||
# else:
|
||||
# imgui.text("(空)")
|
||||
# imgui.tree_pop()
|
||||
|
||||
def _draw_scene_node(self, node, name, node_type, gui_subtype=None):
|
||||
"""绘制单个场景节点"""
|
||||
if not node or node.isEmpty():
|
||||
return
|
||||
|
||||
# 检查是否被选中
|
||||
is_selected = (hasattr(self.app, 'selection') and self.app.selection and
|
||||
hasattr(self.app.selection, 'selectedNode') and
|
||||
self.app.selection.selectedNode == node)
|
||||
|
||||
# 节点可见性
|
||||
is_visible = node.is_hidden() == False
|
||||
|
||||
# 设置选择颜色
|
||||
if is_selected:
|
||||
imgui.push_style_color(imgui.Col_.text, (0.2, 0.6, 1.0, 1.0))
|
||||
|
||||
node_open = False
|
||||
try:
|
||||
# 显示节点
|
||||
node_open = imgui.tree_node(name)
|
||||
|
||||
# 处理节点选择
|
||||
if imgui.is_item_clicked():
|
||||
# In SSBO mode, clicking model root should finally bind gizmo to
|
||||
# SSBO group proxy (not legacy model root). So run legacy
|
||||
# selection first, then force SSBO root selection at the end.
|
||||
force_ssbo_root_key = None
|
||||
ssbo_editor_ref = None
|
||||
try:
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None
|
||||
ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None
|
||||
root_key = getattr(controller, "tree_root_key", None) if controller else None
|
||||
if (
|
||||
ssbo_editor and controller and ssbo_model and node == ssbo_model
|
||||
and root_key and root_key in controller.tree_nodes
|
||||
):
|
||||
force_ssbo_root_key = root_key
|
||||
ssbo_editor_ref = ssbo_editor
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self.app, 'selection') and self.app.selection:
|
||||
self.app.selection.updateSelection(node)
|
||||
|
||||
if force_ssbo_root_key and ssbo_editor_ref:
|
||||
try:
|
||||
ssbo_editor_ref.select_node(force_ssbo_root_key)
|
||||
except Exception:
|
||||
pass
|
||||
# Clear LUI selection when a scene node is selected
|
||||
if hasattr(self.app, 'lui_manager'):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
|
||||
if self.app.is_dragging and imgui.is_item_hovered():
|
||||
self.app._drag_scene_tree_hover_node = node
|
||||
|
||||
# 右键菜单
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
self._show_node_context_menu(node, name, node_type)
|
||||
|
||||
# 显示节点属性
|
||||
imgui.same_line()
|
||||
if is_visible:
|
||||
imgui.text_colored((0.5, 1.0, 0.5, 1.0), "可见")
|
||||
else:
|
||||
imgui.text_colored((0.5, 0.5, 0.5, 1.0), "隐藏")
|
||||
|
||||
if node_open:
|
||||
# SSBO模型使用虚拟层级显示(避免 flatten 后真实子级丢失)
|
||||
if self._draw_ssbo_virtual_children(node):
|
||||
pass
|
||||
elif node.getNumChildren() > 0:
|
||||
for i in range(node.getNumChildren()):
|
||||
child = node.getChild(i)
|
||||
if not child or child.isEmpty():
|
||||
continue
|
||||
child_name = child.getName() or f"child_{i+1}"
|
||||
# 过滤碰撞辅助节点,避免污染场景树
|
||||
if child_name.startswith("modelCollision_"):
|
||||
continue
|
||||
self._draw_scene_node(child, child_name, node_type)
|
||||
# tree_pop moved to finally
|
||||
except Exception as e:
|
||||
print(f"绘制场景节点时出错: {e}")
|
||||
finally:
|
||||
if node_open:
|
||||
imgui.tree_pop()
|
||||
# Ensure style stack is balanced.
|
||||
if is_selected:
|
||||
imgui.pop_style_color()
|
||||
|
||||
def _draw_ssbo_virtual_children(self, node):
|
||||
"""Draw SSBO controller tree_nodes as virtual children for scene tree."""
|
||||
ssbo_editor = getattr(self.app, "ssbo_editor", None)
|
||||
if not ssbo_editor:
|
||||
return False
|
||||
model = getattr(ssbo_editor, "model", None)
|
||||
controller = getattr(ssbo_editor, "controller", None)
|
||||
if not model or model != node or not controller:
|
||||
return False
|
||||
|
||||
root_key = getattr(controller, "tree_root_key", None)
|
||||
if not root_key or root_key not in controller.tree_nodes:
|
||||
imgui.text_disabled("(无可用子节点)")
|
||||
return True
|
||||
|
||||
root_node = controller.tree_nodes[root_key]
|
||||
if not root_node["children"]:
|
||||
imgui.text_disabled("(无可用子节点)")
|
||||
return True
|
||||
|
||||
for child_key in root_node["children"]:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
return True
|
||||
|
||||
def _draw_ssbo_virtual_tree_node(self, ssbo_editor, controller, key):
|
||||
"""Recursively draw SSBO tree_nodes hierarchy in scene tree."""
|
||||
node_data = controller.tree_nodes.get(key)
|
||||
if not node_data:
|
||||
return
|
||||
|
||||
# Skip redundant wrapper nodes (e.g. ROOT), show their children instead.
|
||||
if controller.should_hide_tree_node(key):
|
||||
for child_key in node_data["children"]:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
return
|
||||
|
||||
display = controller.display_names.get(key, key)
|
||||
obj_count = len(controller.name_to_ids.get(key, []))
|
||||
children = node_data["children"]
|
||||
is_selected = (getattr(ssbo_editor, "selected_name", None) == key)
|
||||
|
||||
if not children:
|
||||
# Leaf node: selectable
|
||||
label = f"{display} ({obj_count})##{key}"
|
||||
if imgui.selectable(label, is_selected)[0]:
|
||||
ssbo_editor.select_node(key)
|
||||
if hasattr(self.app, "lui_manager"):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
else:
|
||||
# Branch node: tree node
|
||||
flags = imgui.TreeNodeFlags_.open_on_arrow
|
||||
if is_selected:
|
||||
flags |= imgui.TreeNodeFlags_.selected
|
||||
label = f"{display} ({obj_count})##{key}"
|
||||
opened = imgui.tree_node_ex(label, flags)
|
||||
if imgui.is_item_clicked(0):
|
||||
ssbo_editor.select_node(key)
|
||||
if hasattr(self.app, "lui_manager"):
|
||||
self.app.lui_manager.selected_index = -1
|
||||
if opened:
|
||||
for child_key in children:
|
||||
self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key)
|
||||
imgui.tree_pop()
|
||||
|
||||
def _show_node_context_menu(self, node, name, node_type):
|
||||
"""显示节点右键菜单"""
|
||||
self.app._context_menu_node = True
|
||||
self.app._context_menu_target = node
|
||||
|
||||
def _draw_resource_manager(self):
|
||||
"""绘制资源管理器面板"""
|
||||
flags = self.app.style_manager.get_window_flags("panel")
|
||||
|
||||
with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags):
|
||||
self.app.showResourceManager = True
|
||||
rm = self.app.resource_manager
|
||||
|
||||
window_pos = imgui.get_window_pos()
|
||||
window_size = imgui.get_window_size()
|
||||
self.app._resource_manager_window_rect = (
|
||||
float(window_pos.x),
|
||||
float(window_pos.y),
|
||||
float(window_size.x),
|
||||
float(window_size.y),
|
||||
)
|
||||
self.app._resource_drop_targets.append((
|
||||
float(window_pos.x),
|
||||
float(window_pos.y),
|
||||
float(window_size.x),
|
||||
float(window_size.y),
|
||||
str(rm.current_path),
|
||||
))
|
||||
|
||||
imgui.text("文件浏览器")
|
||||
imgui.separator()
|
||||
|
||||
if imgui.button("◀"):
|
||||
rm.navigate_back()
|
||||
imgui.same_line()
|
||||
if imgui.button("▶"):
|
||||
rm.navigate_forward()
|
||||
imgui.same_line()
|
||||
if imgui.button("▲"):
|
||||
rm.navigate_up()
|
||||
imgui.same_line()
|
||||
if imgui.button("主页"):
|
||||
rm.navigate_to(rm.project_root / "Resources")
|
||||
imgui.same_line()
|
||||
if imgui.button("刷新"):
|
||||
rm.force_refresh()
|
||||
|
||||
# 自动刷新开关
|
||||
imgui.same_line()
|
||||
changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled)
|
||||
if changed:
|
||||
rm.set_auto_refresh(rm.auto_refresh_enabled)
|
||||
|
||||
imgui.same_line()
|
||||
imgui.text(" ")
|
||||
imgui.same_line()
|
||||
|
||||
# 路径输入框
|
||||
changed, new_path = imgui.input_text("路径", str(rm.current_path), 256)
|
||||
if changed:
|
||||
try:
|
||||
rm.navigate_to(Path(new_path))
|
||||
except:
|
||||
pass
|
||||
|
||||
# 搜索框
|
||||
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if rm.refresh_if_needed():
|
||||
pass
|
||||
|
||||
dirs, files = rm.get_directory_contents(rm.current_path)
|
||||
|
||||
for dir_path in dirs:
|
||||
if not rm.should_show_file(dir_path):
|
||||
continue
|
||||
|
||||
icon_name = rm.get_file_icon(dir_path.name, is_folder=True)
|
||||
is_selected = dir_path in rm.selected_files
|
||||
|
||||
if is_selected:
|
||||
imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0))
|
||||
|
||||
icon_texture = None
|
||||
try:
|
||||
icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if icon_texture:
|
||||
imgui.image(icon_texture, (16, 16))
|
||||
imgui.same_line()
|
||||
node_open = imgui.tree_node(f"{dir_path.name}##dir_{dir_path}")
|
||||
else:
|
||||
node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}##dir_{dir_path}")
|
||||
|
||||
if is_selected:
|
||||
imgui.pop_style_color()
|
||||
|
||||
self._append_drop_target_from_last_item(dir_path)
|
||||
self._start_resource_drag_if_needed(rm, dir_path)
|
||||
|
||||
if imgui.is_item_clicked():
|
||||
if imgui.get_io().key_ctrl:
|
||||
if is_selected:
|
||||
rm.selected_files.discard(dir_path)
|
||||
else:
|
||||
rm.selected_files.add(dir_path)
|
||||
else:
|
||||
rm.selected_files.clear()
|
||||
rm.selected_files.add(dir_path)
|
||||
rm.focused_file = dir_path
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
rm.navigate_to(dir_path)
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
rm.show_context_menu = True
|
||||
rm.context_menu_file = dir_path
|
||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||
imgui.open_popup("resource_context_menu")
|
||||
|
||||
if node_open:
|
||||
subdirs, subfiles = rm.get_directory_contents(dir_path)
|
||||
|
||||
for subdir in subdirs:
|
||||
if not rm.should_show_file(subdir):
|
||||
continue
|
||||
|
||||
subicon_name = rm.get_file_icon(subdir.name, is_folder=True)
|
||||
sub_is_selected = subdir in rm.selected_files
|
||||
|
||||
subicon_texture = None
|
||||
try:
|
||||
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if subicon_texture:
|
||||
imgui.image(subicon_texture, (16, 16))
|
||||
imgui.same_line()
|
||||
sub_node_open = imgui.tree_node(f" {subdir.name}##dir_{subdir}")
|
||||
else:
|
||||
sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}##dir_{subdir}")
|
||||
|
||||
self._append_drop_target_from_last_item(subdir)
|
||||
self._start_resource_drag_if_needed(rm, subdir)
|
||||
|
||||
if imgui.is_item_clicked():
|
||||
if imgui.get_io().key_ctrl:
|
||||
if sub_is_selected:
|
||||
rm.selected_files.discard(subdir)
|
||||
else:
|
||||
rm.selected_files.add(subdir)
|
||||
else:
|
||||
rm.selected_files.clear()
|
||||
rm.selected_files.add(subdir)
|
||||
rm.focused_file = subdir
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
rm.navigate_to(subdir)
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
rm.show_context_menu = True
|
||||
rm.context_menu_file = subdir
|
||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||
imgui.open_popup("resource_context_menu")
|
||||
|
||||
if sub_node_open:
|
||||
imgui.tree_pop()
|
||||
|
||||
for subfile in subfiles:
|
||||
if not rm.should_show_file(subfile):
|
||||
continue
|
||||
|
||||
subicon_name = rm.get_file_icon(subfile.name)
|
||||
sub_is_selected = subfile in rm.selected_files
|
||||
|
||||
subicon_texture = None
|
||||
try:
|
||||
subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if subicon_texture:
|
||||
imgui.image(subicon_texture, (16, 16))
|
||||
imgui.same_line()
|
||||
selected = imgui.selectable(f" {subfile.name}##file_{subfile}", sub_is_selected)
|
||||
else:
|
||||
selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}##file_{subfile}", sub_is_selected)
|
||||
|
||||
selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected)
|
||||
self._start_resource_drag_if_needed(rm, subfile)
|
||||
|
||||
if selected_clicked:
|
||||
if imgui.get_io().key_ctrl:
|
||||
if sub_is_selected:
|
||||
rm.selected_files.discard(subfile)
|
||||
else:
|
||||
rm.selected_files.add(subfile)
|
||||
else:
|
||||
rm.selected_files.clear()
|
||||
rm.selected_files.add(subfile)
|
||||
rm.focused_file = subfile
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
if self._is_model_file(subfile):
|
||||
self.app._import_model_for_runtime(str(subfile))
|
||||
self.app.add_info_message(f"正在导入模型: {subfile.name}")
|
||||
else:
|
||||
rm.open_file(subfile)
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
rm.show_context_menu = True
|
||||
rm.context_menu_file = subfile
|
||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||
imgui.open_popup("resource_context_menu")
|
||||
|
||||
imgui.tree_pop()
|
||||
|
||||
for file_path in files:
|
||||
if not rm.should_show_file(file_path):
|
||||
continue
|
||||
|
||||
icon_name = rm.get_file_icon(file_path.name)
|
||||
is_selected = file_path in rm.selected_files
|
||||
|
||||
icon_texture = None
|
||||
try:
|
||||
icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if icon_texture:
|
||||
imgui.image(icon_texture, (16, 16))
|
||||
imgui.same_line()
|
||||
selected = imgui.selectable(f"{file_path.name}##file_{file_path}", is_selected)
|
||||
else:
|
||||
selected = imgui.selectable(f"[{icon_name.upper()}] {file_path.name}##file_{file_path}", is_selected)
|
||||
|
||||
selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected)
|
||||
self._start_resource_drag_if_needed(rm, file_path)
|
||||
|
||||
if selected_clicked:
|
||||
if imgui.get_io().key_ctrl:
|
||||
if is_selected:
|
||||
rm.selected_files.discard(file_path)
|
||||
else:
|
||||
rm.selected_files.add(file_path)
|
||||
else:
|
||||
rm.selected_files.clear()
|
||||
rm.selected_files.add(file_path)
|
||||
rm.focused_file = file_path
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
|
||||
if self._is_model_file(file_path):
|
||||
self.app.add_info_message(f"正在导入模型: {file_path.name}")
|
||||
self.app._import_model_for_runtime(str(file_path))
|
||||
else:
|
||||
rm.open_file(file_path)
|
||||
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
|
||||
rm.show_context_menu = True
|
||||
rm.context_menu_file = file_path
|
||||
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
|
||||
imgui.open_popup("resource_context_menu")
|
||||
|
||||
if rm.context_menu_file:
|
||||
imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1]))
|
||||
|
||||
if imgui.begin_popup("resource_context_menu"):
|
||||
if rm.context_menu_file and rm.context_menu_file.is_dir():
|
||||
if imgui.menu_item("打开")[1]:
|
||||
rm.navigate_to(rm.context_menu_file)
|
||||
imgui.separator()
|
||||
if imgui.menu_item("重命名")[1]:
|
||||
print(f"重命名文件夹: {rm.context_menu_file.name}")
|
||||
if imgui.menu_item("删除")[1]:
|
||||
print(f"删除文件夹: {rm.context_menu_file.name}")
|
||||
elif rm.context_menu_file:
|
||||
if imgui.menu_item("打开")[1]:
|
||||
rm.open_file(rm.context_menu_file)
|
||||
imgui.separator()
|
||||
if imgui.menu_item("导入到场景")[1]:
|
||||
if self._is_model_file(rm.context_menu_file):
|
||||
self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}")
|
||||
self.app._import_model_for_runtime(str(rm.context_menu_file))
|
||||
if imgui.menu_item("重命名")[1]:
|
||||
print(f"重命名文件: {rm.context_menu_file.name}")
|
||||
if imgui.menu_item("删除")[1]:
|
||||
print(f"删除文件: {rm.context_menu_file.name}")
|
||||
|
||||
if rm.context_menu_file:
|
||||
imgui.separator()
|
||||
if imgui.menu_item("复制路径")[1]:
|
||||
imgui.set_clipboard_text(str(rm.context_menu_file))
|
||||
self.app.add_info_message("路径已复制到剪贴板")
|
||||
if imgui.menu_item("在文件管理器中显示")[1]:
|
||||
import platform
|
||||
import subprocess
|
||||
if platform.system() == "Windows":
|
||||
subprocess.run(["explorer", "/select,", str(rm.context_menu_file)])
|
||||
elif platform.system() == "Darwin":
|
||||
subprocess.run(["open", "-R", str(rm.context_menu_file)])
|
||||
else:
|
||||
subprocess.run(["xdg-open", str(rm.context_menu_file.parent)])
|
||||
|
||||
if imgui.is_mouse_clicked(0) and not imgui.is_window_hovered():
|
||||
rm.context_menu_file = None
|
||||
rm.show_context_menu = False
|
||||
imgui.close_current_popup()
|
||||
|
||||
imgui.end_popup()
|
||||
|
||||
@staticmethod
|
||||
def _is_model_file(path: Path) -> bool:
|
||||
return path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj']
|
||||
|
||||
def _append_drop_target_from_last_item(self, target_path: Path):
|
||||
item_min = imgui.get_item_rect_min()
|
||||
item_max = imgui.get_item_rect_max()
|
||||
width = float(item_max.x - item_min.x)
|
||||
height = float(item_max.y - item_min.y)
|
||||
if width <= 0 or height <= 0:
|
||||
return
|
||||
self.app._resource_drop_targets.append((
|
||||
float(item_min.x),
|
||||
float(item_min.y),
|
||||
width,
|
||||
height,
|
||||
str(target_path),
|
||||
))
|
||||
|
||||
def _start_resource_drag_if_needed(self, rm, fallback_path: Path):
|
||||
if not imgui.is_item_active() or not imgui.is_mouse_dragging(0):
|
||||
return
|
||||
drag_files = list(rm.selected_files) if rm.selected_files else [fallback_path]
|
||||
rm.start_drag(drag_files)
|
||||
self.app.is_dragging = True
|
||||
self.app.show_drag_overlay = True
|
||||
|
||||
1279
ui/panels/editor_panels_right.py
Normal file
1279
ui/panels/editor_panels_right.py
Normal file
File diff suppressed because it is too large
Load Diff
323
ui/panels/editor_panels_top.py
Normal file
323
ui/panels/editor_panels_top.py
Normal file
@ -0,0 +1,323 @@
|
||||
from imgui_bundle import imgui, imgui_ctx
|
||||
|
||||
class EditorPanelsTopMixin:
|
||||
"""Auto-split mixin from editor_panels.py."""
|
||||
|
||||
def draw_menu_bar(self):
|
||||
"""绘制菜单栏"""
|
||||
with imgui_ctx.begin_main_menu_bar() as main_menu:
|
||||
if main_menu:
|
||||
# 文件菜单
|
||||
with imgui_ctx.begin_menu("文件") as file_menu:
|
||||
if file_menu:
|
||||
if imgui.menu_item("新建项目", "Ctrl+N", False, True)[1]:
|
||||
self.app._on_new_project()
|
||||
if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]:
|
||||
self.app._on_open_project()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("保存", "Ctrl+S", False, True)[1]:
|
||||
self.app._on_save_project()
|
||||
if imgui.menu_item("另存为", "", False, True)[1]:
|
||||
self.app._on_save_as_project()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("退出", "Alt+F4", False, True)[1]:
|
||||
self.app._on_exit()
|
||||
|
||||
# 编辑菜单
|
||||
with imgui_ctx.begin_menu("编辑") as edit_menu:
|
||||
if edit_menu:
|
||||
if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]:
|
||||
self.app._on_undo()
|
||||
if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]:
|
||||
self.app._on_redo()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]:
|
||||
self.app._on_cut()
|
||||
if imgui.menu_item("复制", "Ctrl+C", False, True)[1]:
|
||||
self.app._on_copy()
|
||||
if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]:
|
||||
self.app._on_paste()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("删除", "Del", False, True)[1]:
|
||||
self.app._on_delete()
|
||||
|
||||
# 创建菜单
|
||||
with imgui_ctx.begin_menu("创建") as create_menu:
|
||||
if create_menu:
|
||||
if imgui.menu_item("导入模型", "", False, True)[1]:
|
||||
self.app._on_import_model()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.menu_item("空对象", "", False, True)[1]:
|
||||
self.app._on_create_empty_object()
|
||||
|
||||
# 3D对象子菜单
|
||||
with imgui_ctx.begin_menu("3D对象") as three_d_menu:
|
||||
if three_d_menu:
|
||||
if imgui.menu_item("立方体", "", False, True)[1]:
|
||||
self.app._on_create_cube()
|
||||
if imgui.menu_item("球体", "", False, True)[1]:
|
||||
self.app._on_create_sphere()
|
||||
if imgui.menu_item("圆柱体", "", False, True)[1]:
|
||||
self.app._on_create_cylinder()
|
||||
if imgui.menu_item("平面", "", False, True)[1]:
|
||||
self.app._on_create_plane()
|
||||
|
||||
# 3D GUI子菜单
|
||||
# with imgui_ctx.begin_menu("3D GUI") as three_d_gui_menu:
|
||||
# if three_d_gui_menu:
|
||||
# if imgui.menu_item("3D文本", "", False, True)[1]:
|
||||
# self.app._on_create_3d_text()
|
||||
# if imgui.menu_item("3D图片", "", False, True)[1]:
|
||||
# self.app._on_create_3d_image()
|
||||
|
||||
# # GUI子菜单
|
||||
# with imgui_ctx.begin_menu("GUI") as gui_menu:
|
||||
# if gui_menu:
|
||||
# if imgui.menu_item("创建按钮", "", False, True)[1]:
|
||||
# self.app._on_create_gui_button()
|
||||
# if imgui.menu_item("创建标签", "", False, True)[1]:
|
||||
# self.app._on_create_gui_label()
|
||||
# if imgui.menu_item("创建输入框", "", False, True)[1]:
|
||||
# self.app._on_create_gui_entry()
|
||||
# if imgui.menu_item("创建图片", "", False, True)[1]:
|
||||
# self.app._on_create_gui_image()
|
||||
# imgui.separator()
|
||||
# if imgui.menu_item("创建视频屏幕", "", False, True)[1]:
|
||||
# self.app._on_create_video_screen()
|
||||
# if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]:
|
||||
# self.app._on_create_2d_video_screen()
|
||||
# if imgui.menu_item("创建球形视频", "", False, True)[1]:
|
||||
# self.app._on_create_spherical_video()
|
||||
# if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]:
|
||||
# self.app._on_create_virtual_screen()
|
||||
|
||||
# 光源子菜单
|
||||
with imgui_ctx.begin_menu("光源") as light_menu:
|
||||
if light_menu:
|
||||
if imgui.menu_item("聚光灯", "", False, True)[1]:
|
||||
self.app._on_create_spot_light()
|
||||
if imgui.menu_item("点光源", "", False, True)[1]:
|
||||
self.app._on_create_point_light()
|
||||
|
||||
# 地形子菜单
|
||||
with imgui_ctx.begin_menu("地形") as terrain_menu:
|
||||
if terrain_menu:
|
||||
if imgui.menu_item("创建平面地形", "", False, True)[1]:
|
||||
self.app._on_create_flat_terrain()
|
||||
if imgui.menu_item("从高度图创建地形", "", False, True)[1]:
|
||||
self.app._on_create_heightmap_terrain()
|
||||
|
||||
# 脚本子菜单
|
||||
with imgui_ctx.begin_menu("脚本") as script_menu:
|
||||
if script_menu:
|
||||
if imgui.menu_item("创建脚本...", "", False, True)[1]:
|
||||
self.app._on_create_script()
|
||||
if imgui.menu_item("加载脚本文件...", "", False, True)[1]:
|
||||
self.app._on_load_script()
|
||||
imgui.separator()
|
||||
if imgui.menu_item("重载所有脚本", "", False, True)[1]:
|
||||
self.app._on_reload_all_scripts()
|
||||
_, self.app.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.app.hotReloadEnabled, True)
|
||||
if imgui.menu_item("脚本管理器", "", False, True)[1]:
|
||||
self.app._on_open_scripts_manager()
|
||||
|
||||
# 信息面板子菜单
|
||||
with imgui_ctx.begin_menu("信息面板") as info_panel_menu:
|
||||
if info_panel_menu:
|
||||
# if imgui.menu_item("创建2D示例面板", "", False, True)[1]:
|
||||
# self.app._on_create_2d_sample_panel()
|
||||
# if imgui.menu_item("创建3D实例面板", "", False, True)[1]:
|
||||
# self.app._on_create_3d_sample_panel()
|
||||
if imgui.menu_item("Web面板", "", False, True)[1]:
|
||||
self.app._on_create_web_panel()
|
||||
|
||||
# 视图菜单
|
||||
with imgui_ctx.begin_menu("视图") as view_menu:
|
||||
if view_menu:
|
||||
_, self.app.showToolbar = imgui.menu_item("工具栏", "", self.app.showToolbar, True)
|
||||
_, self.app.showSceneTree = imgui.menu_item("场景树", "", self.app.showSceneTree, True)
|
||||
_, self.app.showResourceManager = imgui.menu_item("资源管理器", "", self.app.showResourceManager, True)
|
||||
_, self.app.showPropertyPanel = imgui.menu_item("属性面板", "", self.app.showPropertyPanel, True)
|
||||
_, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True)
|
||||
_, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True)
|
||||
_, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True)
|
||||
prev_show_web_panel = self.app.showWebPanel
|
||||
_, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True)
|
||||
if prev_show_web_panel and not self.app.showWebPanel:
|
||||
self._stop_imgui_webview()
|
||||
elif (not prev_show_web_panel) and self.app.showWebPanel:
|
||||
self._on_create_web_panel()
|
||||
|
||||
# 工具菜单
|
||||
with imgui_ctx.begin_menu("工具") as tools_menu:
|
||||
if tools_menu:
|
||||
# 工具切换选项
|
||||
if imgui.menu_item("选择工具", "", False, True)[1]:
|
||||
self.app.tool_manager.setCurrentTool("选择")
|
||||
if imgui.menu_item("移动工具", "", False, True)[1]:
|
||||
self.app.tool_manager.setCurrentTool("移动")
|
||||
if imgui.menu_item("旋转工具", "", False, True)[1]:
|
||||
self.app.tool_manager.setCurrentTool("旋转")
|
||||
if imgui.menu_item("缩放工具", "", False, True)[1]:
|
||||
self.app.tool_manager.setCurrentTool("缩放")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 编辑工具
|
||||
# if imgui.menu_item("光照编辑", "", False, True)[1]:
|
||||
# self.app.tool_manager.setCurrentTool("光照编辑")
|
||||
# if imgui.menu_item("图形编辑", "", False, True)[1]:
|
||||
# self.app.tool_manager.setCurrentTool("图形编辑")
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# VR子菜单
|
||||
with imgui_ctx.begin_menu("VR") as vr_menu:
|
||||
if vr_menu:
|
||||
if imgui.menu_item("进入VR模式", "", False, True)[1]:
|
||||
self.app._toggle_vr_mode()
|
||||
if imgui.menu_item("退出VR模式", "", False, True)[1]:
|
||||
self.app._exit_vr_mode()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
if imgui.menu_item("VR状态", "", False, True)[1]:
|
||||
self.app._show_vr_status()
|
||||
if imgui.menu_item("VR设置", "", False, True)[1]:
|
||||
self.app._show_vr_settings()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# VR调试子菜单
|
||||
with imgui_ctx.begin_menu("VR调试") as vr_debug_menu:
|
||||
if vr_debug_menu:
|
||||
_, self.app.vr_debug_enabled = imgui.menu_item("启用调试输出", "", self.app.vr_debug_enabled, True)
|
||||
|
||||
if imgui.menu_item("立即显示性能报告", "", False, True)[1]:
|
||||
self.app._show_vr_performance_report()
|
||||
|
||||
imgui.separator()
|
||||
|
||||
# 输出模式
|
||||
with imgui_ctx.begin_menu("输出模式") as output_menu:
|
||||
if output_menu:
|
||||
if imgui.menu_item("简短模式", "", not self.app.vr_detailed_mode, True)[1]:
|
||||
self.app.vr_detailed_mode = False
|
||||
if imgui.menu_item("详细模式", "", self.app.vr_detailed_mode, True)[1]:
|
||||
self.app.vr_detailed_mode = True
|
||||
|
||||
imgui.separator()
|
||||
|
||||
_, self.app.vr_performance_monitor = imgui.menu_item("启用性能监控", "", self.app.vr_performance_monitor, True)
|
||||
|
||||
# 窗口菜单 - 已隐藏
|
||||
# with imgui_ctx.begin_menu("窗口") as window_menu:
|
||||
# if window_menu:
|
||||
# _, self.app.showDemoWindow = imgui.menu_item("ImGui演示", "", self.app.showDemoWindow, True)
|
||||
# if self.app.testTexture:
|
||||
# imgui.menu_item("关闭纹理测试", "", False, True)
|
||||
# else:
|
||||
# imgui.menu_item("显示纹理测试", "", False, True)
|
||||
|
||||
# 帮助菜单
|
||||
with imgui_ctx.begin_menu("帮助") as help_menu:
|
||||
if help_menu:
|
||||
imgui.menu_item("关于", "", False, True)
|
||||
imgui.menu_item("文档", "", False, True)
|
||||
|
||||
# 右侧显示FPS
|
||||
imgui.set_cursor_pos_x(imgui.get_window_size().x - 140)
|
||||
imgui.text("%.2f FPS (%.2f ms)" % (imgui.get_io().framerate, 1000.0 / imgui.get_io().framerate))
|
||||
|
||||
def draw_toolbar(self):
|
||||
"""绘制工具栏"""
|
||||
# 工具栏可以保持无标题栏,但允许移动和调整大小
|
||||
flags = self.app.style_manager.get_window_flags("toolbar")
|
||||
|
||||
with self.app.style_manager.begin_styled_window("工具栏", self.app.showToolbar, flags):
|
||||
self.app.showToolbar = True # 确保窗口保持打开
|
||||
|
||||
# 选择工具按钮
|
||||
select_active = self.app.tool_manager.isSelectionTool()
|
||||
if self.app.icons.get('select'):
|
||||
tint_col = (1.0, 1.0, 0.0, 1.0) if select_active else (1.0, 1.0, 1.0, 1.0)
|
||||
if self.app.style_manager.image_button(self.app.icons['select'], (24, 24), tint_col=tint_col):
|
||||
self.app.tool_manager.setCurrentTool("选择")
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("选择工具 (Q)")
|
||||
imgui.same_line()
|
||||
else:
|
||||
if imgui.button("选择##select_tool"):
|
||||
self.app.tool_manager.setCurrentTool("选择")
|
||||
if select_active:
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
button_min = imgui.get_item_rect_min()
|
||||
button_max = imgui.get_item_rect_max()
|
||||
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
|
||||
imgui.same_line()
|
||||
|
||||
# 移动工具按钮
|
||||
move_active = self.app.tool_manager.isMoveTool()
|
||||
if self.app.icons.get('move'):
|
||||
# 使用不同颜色表示活动状态
|
||||
tint_col = (1.0, 1.0, 0.0, 1.0) if move_active else (1.0, 1.0, 1.0, 1.0) # 活动时显示黄色
|
||||
if self.app.style_manager.image_button(self.app.icons['move'], (24, 24), tint_col=tint_col):
|
||||
self.app.tool_manager.setCurrentTool("移动")
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("移动工具 (W)")
|
||||
imgui.same_line()
|
||||
else:
|
||||
if imgui.button("移动##move_tool"):
|
||||
self.app.tool_manager.setCurrentTool("移动")
|
||||
if move_active:
|
||||
# 为活动按钮添加背景色
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
button_min = imgui.get_item_rect_min()
|
||||
button_max = imgui.get_item_rect_max()
|
||||
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
|
||||
imgui.same_line()
|
||||
|
||||
# 旋转工具按钮
|
||||
rotate_active = self.app.tool_manager.isRotateTool()
|
||||
if self.app.icons.get('rotate'):
|
||||
tint_col = (1.0, 1.0, 0.0, 1.0) if rotate_active else (1.0, 1.0, 1.0, 1.0)
|
||||
if self.app.style_manager.image_button(self.app.icons['rotate'], (24, 24), tint_col=tint_col):
|
||||
self.app.tool_manager.setCurrentTool("旋转")
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("旋转工具 (E)")
|
||||
imgui.same_line()
|
||||
else:
|
||||
if imgui.button("旋转##rotate_tool"):
|
||||
self.app.tool_manager.setCurrentTool("旋转")
|
||||
if rotate_active:
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
button_min = imgui.get_item_rect_min()
|
||||
button_max = imgui.get_item_rect_max()
|
||||
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
|
||||
imgui.same_line()
|
||||
|
||||
# 缩放工具按钮
|
||||
scale_active = self.app.tool_manager.isScaleTool()
|
||||
if self.app.icons.get('scale'):
|
||||
tint_col = (1.0, 1.0, 0.0, 1.0) if scale_active else (1.0, 1.0, 1.0, 1.0)
|
||||
if self.app.style_manager.image_button(self.app.icons['scale'], (24, 24), tint_col=tint_col):
|
||||
self.app.tool_manager.setCurrentTool("缩放")
|
||||
if imgui.is_item_hovered():
|
||||
imgui.set_tooltip("缩放工具 (R)")
|
||||
else:
|
||||
if imgui.button("缩放##scale_tool"):
|
||||
self.app.tool_manager.setCurrentTool("缩放")
|
||||
if scale_active:
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
button_min = imgui.get_item_rect_min()
|
||||
button_max = imgui.get_item_rect_max()
|
||||
draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3)))
|
||||
|
||||
imgui.same_line()
|
||||
imgui.separator()
|
||||
imgui.same_line()
|
||||
|
||||
# 工具按钮已移除(导入、保存、播放)
|
||||
|
||||
@ -17,10 +17,24 @@ class RuntimeActions:
|
||||
else:
|
||||
setattr(self.app, name, value)
|
||||
|
||||
def _get_gui_manager(self):
|
||||
"""统一获取 GUI 管理器,避免散落的 hasattr 判空。"""
|
||||
return getattr(self.app, "gui_manager", None)
|
||||
|
||||
def _append_gui_element(self, element_wrapper):
|
||||
"""统一维护 gui_elements 列表。"""
|
||||
gui_manager = self._get_gui_manager()
|
||||
if not gui_manager:
|
||||
return False
|
||||
if not hasattr(gui_manager, "gui_elements") or gui_manager.gui_elements is None:
|
||||
gui_manager.gui_elements = []
|
||||
gui_manager.gui_elements.append(element_wrapper)
|
||||
return True
|
||||
|
||||
def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1):
|
||||
"""创建2D GUI按钮"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
if self._get_gui_manager():
|
||||
return self._create_simple_gui_button(pos, text, size)
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -55,7 +69,8 @@ class RuntimeActions:
|
||||
button_wrapper.position = pos
|
||||
button_wrapper.size = size
|
||||
|
||||
self.gui_manager.gui_elements.append(button_wrapper)
|
||||
if not self._append_gui_element(button_wrapper):
|
||||
return None
|
||||
|
||||
print(f"✓ GUI按钮创建成功: {text}")
|
||||
return button_wrapper
|
||||
@ -148,7 +163,7 @@ class RuntimeActions:
|
||||
def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08):
|
||||
"""创建2D GUI标签"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
if self._get_gui_manager():
|
||||
return self._create_simple_gui_label(pos, text, size)
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -183,7 +198,8 @@ class RuntimeActions:
|
||||
label_wrapper.position = pos
|
||||
label_wrapper.size = size
|
||||
|
||||
self.gui_manager.gui_elements.append(label_wrapper)
|
||||
if not self._append_gui_element(label_wrapper):
|
||||
return None
|
||||
|
||||
print(f"✓ GUI标签创建成功: {text}")
|
||||
return label_wrapper
|
||||
@ -194,7 +210,7 @@ class RuntimeActions:
|
||||
def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
|
||||
"""创建2D GUI文本输入框"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
if self._get_gui_manager():
|
||||
return self._create_simple_gui_entry(pos, placeholder, size)
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -231,7 +247,8 @@ class RuntimeActions:
|
||||
entry_wrapper.position = pos
|
||||
entry_wrapper.size = size
|
||||
|
||||
self.gui_manager.gui_elements.append(entry_wrapper)
|
||||
if not self._append_gui_element(entry_wrapper):
|
||||
return None
|
||||
|
||||
print(f"✓ GUI输入框创建成功: {placeholder}")
|
||||
return entry_wrapper
|
||||
@ -242,7 +259,7 @@ class RuntimeActions:
|
||||
def createGUIImage(self, pos=(0, 0, 0), image_path=None, size=2):
|
||||
"""创建2D GUI图片"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
if self._get_gui_manager():
|
||||
return self._create_simple_gui_image(pos, image_path, size)
|
||||
return None
|
||||
except Exception as e:
|
||||
@ -286,7 +303,8 @@ class RuntimeActions:
|
||||
image_wrapper.size = size
|
||||
image_wrapper.image_path = image_path
|
||||
|
||||
self.gui_manager.gui_elements.append(image_wrapper)
|
||||
if not self._append_gui_element(image_wrapper):
|
||||
return None
|
||||
|
||||
print(f"✓ GUI图片创建成功: {image_name}")
|
||||
return image_wrapper
|
||||
@ -297,8 +315,9 @@ class RuntimeActions:
|
||||
def createVideoScreen(self, pos=(0, 0, 0), size=1, video_path=None):
|
||||
"""创建视频屏幕"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
return self.gui_manager.createVideoScreen(pos, size, video_path)
|
||||
gui_manager = self._get_gui_manager()
|
||||
if gui_manager:
|
||||
return gui_manager.createVideoScreen(pos, size, video_path)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"创建视频屏幕失败: {e}")
|
||||
@ -307,8 +326,9 @@ class RuntimeActions:
|
||||
def create2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None):
|
||||
"""创建2D视频屏幕"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
return self.gui_manager.createGUI2DVideoScreen(pos, size, video_path)
|
||||
gui_manager = self._get_gui_manager()
|
||||
if gui_manager:
|
||||
return gui_manager.createGUI2DVideoScreen(pos, size, video_path)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"创建2D视频屏幕失败: {e}")
|
||||
@ -317,8 +337,9 @@ class RuntimeActions:
|
||||
def createSphericalVideo(self, pos=(0, 0, 0), radius=5.0, video_path=None):
|
||||
"""创建360度视频"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
return self.gui_manager.createSphericalVideo(pos, radius, video_path)
|
||||
gui_manager = self._get_gui_manager()
|
||||
if gui_manager:
|
||||
return gui_manager.createSphericalVideo(pos, radius, video_path)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"创建球形视频失败: {e}")
|
||||
@ -327,8 +348,9 @@ class RuntimeActions:
|
||||
def createVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"):
|
||||
"""创建虚拟屏幕"""
|
||||
try:
|
||||
if hasattr(self, "gui_manager") and self.gui_manager:
|
||||
return self.gui_manager.createGUIVirtualScreen(pos, size, text)
|
||||
gui_manager = self._get_gui_manager()
|
||||
if gui_manager:
|
||||
return gui_manager.createGUIVirtualScreen(pos, size, text)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"创建虚拟屏幕失败: {e}")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user