EG/ui/panels/editor_panels_left.py

601 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
self._update_resource_manager_window_rect(rm)
imgui.text("文件浏览器")
imgui.separator()
self._draw_resource_toolbar_and_filters(rm)
imgui.separator()
rm.refresh_if_needed()
dirs, files = rm.get_directory_contents(rm.current_path)
self._draw_resource_directory_entries(rm, dirs)
self._draw_resource_file_entries(rm, files)
self._draw_resource_context_menu(rm)
def _update_resource_manager_window_rect(self, rm):
"""更新资源管理器窗口矩形与根拖拽目标。"""
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),
))
def _draw_resource_directory_entries(self, rm, dirs):
"""绘制当前目录下的文件夹列表。"""
for dir_path in dirs:
if not rm.should_show_file(dir_path):
continue
self._draw_resource_directory_entry(rm, dir_path)
def _draw_resource_directory_entry(self, rm, dir_path: Path):
"""绘制单个文件夹节点(含一层展开内容)。"""
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 = self._load_resource_icon(icon_name)
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():
self._handle_resource_item_selection(rm, dir_path, is_selected)
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):
self._open_resource_item_context_menu(rm, dir_path)
if node_open:
self._draw_resource_directory_children(rm, dir_path)
imgui.tree_pop()
def _draw_resource_directory_children(self, rm, parent_dir: Path):
"""绘制文件夹展开后的子目录与子文件(保持原有顺序)。"""
subdirs, subfiles = rm.get_directory_contents(parent_dir)
for subdir in subdirs:
if not rm.should_show_file(subdir):
continue
self._draw_resource_subdir_entry(rm, subdir)
for subfile in subfiles:
if not rm.should_show_file(subfile):
continue
self._draw_resource_subfile_entry(rm, subfile)
def _draw_resource_subdir_entry(self, rm, subdir: Path):
"""绘制一级子目录节点。"""
subicon_name = rm.get_file_icon(subdir.name, is_folder=True)
sub_is_selected = subdir in rm.selected_files
subicon_texture = self._load_resource_icon(subicon_name)
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():
self._handle_resource_item_selection(rm, subdir, sub_is_selected)
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):
self._open_resource_item_context_menu(rm, subdir)
if sub_node_open:
imgui.tree_pop()
def _draw_resource_subfile_entry(self, rm, subfile: Path):
"""绘制一级子文件项。"""
subicon_name = rm.get_file_icon(subfile.name)
sub_is_selected = subfile in rm.selected_files
subicon_texture = self._load_resource_icon(subicon_name)
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:
self._handle_resource_item_selection(rm, subfile, sub_is_selected)
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
self._handle_resource_file_double_click(rm, subfile)
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
self._open_resource_item_context_menu(rm, subfile)
def _draw_resource_file_entries(self, rm, files):
"""绘制当前目录下的文件列表。"""
for file_path in files:
if not rm.should_show_file(file_path):
continue
self._draw_resource_file_entry(rm, file_path)
def _draw_resource_file_entry(self, rm, file_path: Path):
"""绘制顶层文件项。"""
icon_name = rm.get_file_icon(file_path.name)
is_selected = file_path in rm.selected_files
icon_texture = self._load_resource_icon(icon_name)
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:
self._handle_resource_item_selection(rm, file_path, is_selected)
if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0):
self._handle_resource_file_double_click(rm, file_path)
if imgui.is_item_hovered() and imgui.is_mouse_clicked(1):
self._open_resource_item_context_menu(rm, file_path)
def _draw_resource_toolbar_and_filters(self, rm):
"""绘制资源管理器顶部工具条与筛选输入。保持原有按钮顺序与文案。"""
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 Exception:
pass
# 搜索框
changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256)
def _load_resource_icon(self, icon_name: str):
"""加载资源图标;失败时返回 None。"""
try:
return self.app.style_manager.load_icon(f"file_types/{icon_name}")
except Exception:
return None
def _handle_resource_item_selection(self, rm, target_path: Path, is_selected: bool):
"""处理资源项选中逻辑(支持 Ctrl 多选)。"""
if imgui.get_io().key_ctrl:
if is_selected:
rm.selected_files.discard(target_path)
else:
rm.selected_files.add(target_path)
else:
rm.selected_files.clear()
rm.selected_files.add(target_path)
rm.focused_file = target_path
def _open_resource_item_context_menu(self, rm, target_path: Path):
"""打开资源项右键菜单。"""
rm.show_context_menu = True
rm.context_menu_file = target_path
rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y)
imgui.open_popup("resource_context_menu")
def _handle_resource_file_double_click(self, rm, file_path: Path):
"""处理文件双击:模型导入,其他文件打开。"""
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)
def _draw_resource_context_menu(self, rm):
"""绘制资源管理器右键菜单。保持原有菜单项与顺序。"""
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