EG/ui/panels/editor_panels_top.py
2026-03-25 10:45:15 +08:00

549 lines
28 KiB
Python

from imgui_bundle import imgui, imgui_ctx
class EditorPanelsTopMixin:
"""Auto-split mixin from editor_panels.py."""
@staticmethod
def _normalize_window_rect(rect):
if not rect or len(rect) != 4:
return None
x, y, w, h = [float(v) for v in rect]
if w <= 0 or h <= 0:
return None
return (x, y, w, h)
def _get_toolbar_anchor(self):
viewport = imgui.get_main_viewport()
menu_bar_height = imgui.get_frame_height()
viewport_left = float(viewport.pos.x)
viewport_top = float(viewport.pos.y) + float(menu_bar_height)
viewport_right = float(viewport.pos.x + viewport.size.x)
viewport_bottom = float(viewport.pos.y + viewport.size.y)
viewport_width = max(1.0, viewport_right - viewport_left)
viewport_height = max(1.0, viewport_bottom - viewport_top)
edge_margin = 12.0
panel_gap = 10.0
top_boundary = viewport_top
blocker_names = (
"_scene_tree_window_rect",
"_resource_manager_window_rect",
"_property_panel_window_rect",
"_script_panel_window_rect",
"_console_window_rect",
"_web_panel_window_rect",
)
blockers = []
for name in blocker_names:
rect = self._normalize_window_rect(getattr(self.app, name, None))
if rect:
blockers.append(rect)
def is_top_docked(rect):
bx, by, bw, bh = rect
touches_left = bx <= viewport_left + 6.0
touches_right = (bx + bw) >= viewport_right - 6.0
return (
by <= viewport_top + 6.0 and
bh <= viewport_height * 0.45 and
(
bw >= viewport_width * 0.80 or
touches_left or
touches_right
)
)
def is_left_docked(rect, render_top):
bx, by, bw, bh = rect
touches_top_band = by <= render_top + 6.0
reaches_bottom = (by + bh) >= viewport_bottom - 6.0
return (
bx <= viewport_left + 6.0 and
bw <= viewport_width * 0.45 and
bh >= viewport_height * 0.20 and
by <= render_top + 40.0 and
by + bh >= render_top + 40.0 and
(
touches_top_band or
reaches_bottom or
bh >= viewport_height * 0.60
)
)
for bx, by, bw, bh in blockers:
if is_top_docked((bx, by, bw, bh)):
top_boundary = max(top_boundary, by + bh)
left_boundary = viewport_left
probe_y = min(viewport_bottom - edge_margin, top_boundary + 40.0)
for bx, by, bw, bh in blockers:
if is_left_docked((bx, by, bw, bh), top_boundary):
left_boundary = max(left_boundary, bx + bw)
anchor_x = max(viewport_left + edge_margin, left_boundary + panel_gap)
anchor_y = max(viewport_top + panel_gap * 0.6, top_boundary + panel_gap * 0.6)
max_x = viewport_right - edge_margin
max_y = viewport_bottom - edge_margin
return min(anchor_x, max_x), min(anchor_y, max_y)
@staticmethod
def _draw_toolbar_icon_button(texture_id, fallback_label, active=False, tooltip=None, enabled=True, size=(26, 26)):
"""Draw a compact icon-first toolbar button for the vertical dock toolbar."""
if active:
button_color = (0.24, 0.42, 0.72, 1.0)
hovered_color = (0.28, 0.48, 0.82, 1.0)
tint_color = (1.0, 1.0, 1.0, 1.0)
border_color = (0.38, 0.62, 0.95, 1.0)
else:
button_color = (0.30, 0.31, 0.33, 1.0)
hovered_color = (0.38, 0.39, 0.42, 1.0)
tint_color = (0.9, 0.92, 0.96, 1.0)
border_color = (0.20, 0.21, 0.23, 1.0)
imgui.push_style_color(imgui.Col_.button, button_color)
imgui.push_style_color(imgui.Col_.button_hovered, hovered_color)
imgui.push_style_color(imgui.Col_.button_active, hovered_color)
imgui.push_style_color(imgui.Col_.border, border_color)
imgui.push_style_var(imgui.StyleVar_.frame_rounding, 4.0)
imgui.push_style_var(imgui.StyleVar_.frame_border_size, 1.0)
imgui.push_style_var(imgui.StyleVar_.frame_padding, (3.0, 3.0))
if not enabled:
imgui.begin_disabled()
if texture_id:
clicked = imgui.image_button(texture_id, size, (0, 1), (1, 0), (0, 0, 0, 0), tint_color)
else:
clicked = imgui.button(fallback_label, size)
if not enabled:
imgui.end_disabled()
if tooltip and imgui.is_item_hovered():
imgui.set_tooltip(tooltip)
imgui.pop_style_var(3)
imgui.pop_style_color(4)
return clicked
@staticmethod
def _draw_toolbar_separator():
imgui.dummy((1, 2))
draw_list = imgui.get_window_draw_list()
cursor_pos = imgui.get_cursor_screen_pos()
window_pos = imgui.get_window_pos()
window_size = imgui.get_window_size()
left_inset = 10.0
right_inset = 10.0
line_width = max(18.0, float(window_size.x) - left_inset - right_inset)
start_x = float(window_pos.x) + left_inset
line_y = float(cursor_pos.y)
draw_list.add_line(
(start_x, line_y),
(start_x + line_width, line_y),
imgui.color_convert_float4_to_u32((0.38, 0.39, 0.42, 1.0)),
1.0,
)
imgui.dummy((1, 6))
@staticmethod
def _truncate_toolbar_text(text, limit=20):
text = text or ""
if len(text) <= limit:
return text
return text[: max(0, limit - 1)] + ""
def _get_toolbar_project_name(self):
project_manager = getattr(self.app, "project_manager", None)
project_path = getattr(project_manager, "current_project_path", None) if project_manager else None
if not project_path:
return "未命名项目"
import os
return os.path.basename(project_path) or "未命名项目"
def _get_toolbar_selection_name(self):
selected_node = self.app._get_selection_source_node()
if selected_node and not selected_node.isEmpty():
return selected_node.getName() or "未命名对象"
ssbo_summary = self.app._get_ssbo_selection_summary()
if ssbo_summary:
return ssbo_summary.get("display_name") or "SSBO 选择"
return "未选择"
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()
if imgui.menu_item("打包项目", "", False, True)[1]:
self.app._on_build_project()
if imgui.menu_item("打包为 WebGL", "", False, True)[1]:
self.app._on_build_webgl_package()
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:
hot_reload_enabled = False
if getattr(self.app, "script_manager", None):
hot_reload_enabled = bool(self.app.script_manager.hot_reload_enabled)
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()
changed, new_hot_reload = imgui.menu_item("启用热重载", "", hot_reload_enabled, True)
if changed:
self.app._set_hot_reload_enabled(new_hot_reload)
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:
changed, visible = imgui.menu_item("工具栏", "", self.app.is_panel_visible("toolbar"), True)
if changed:
self.app.set_panel_visible("toolbar", visible)
changed, visible = imgui.menu_item("场景树", "", self.app.is_panel_visible("scene_tree"), True)
if changed:
self.app.set_panel_visible("scene_tree", visible)
changed, visible = imgui.menu_item("资源管理器", "", self.app.is_panel_visible("resources"), True)
if changed:
self.app.set_panel_visible("resources", visible)
changed, visible = imgui.menu_item("属性面板", "", self.app.is_panel_visible("property"), True)
if changed:
self.app.set_panel_visible("property", visible)
changed, visible = imgui.menu_item("控制台", "", self.app.is_panel_visible("console"), True)
if changed:
self.app.set_panel_visible("console", visible)
changed, visible = imgui.menu_item("脚本管理", "", self.app.is_panel_visible("script"), True)
if changed:
self.app.set_panel_visible("script", visible)
changed, visible = imgui.menu_item("LUI编辑器", "", self.app.is_panel_visible("lui_editor"), True)
if changed:
self.app.set_panel_visible("lui_editor", visible)
changed, visible = imgui.menu_item("Web面板", "", self.app.is_panel_visible("web"), True)
if changed:
self.app.set_panel_visible("web", visible)
imgui.separator()
if imgui.menu_item("恢复默认面板", "", False, True)[1]:
self.app.reset_panel_visibility_to_defaults()
if imgui.menu_item("重置布局", "", False, True)[1]:
self.app._reset_imgui_layout()
# 工具菜单
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:
if imgui.menu_item("关于", "", False, True)[1]:
self.app._show_about_dialog()
if imgui.menu_item("文档", "", False, True)[1]:
self.app._open_documentation()
# 右侧显示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):
"""绘制工具栏"""
anchor_x, anchor_y = self._get_toolbar_anchor()
imgui.set_next_window_pos((anchor_x, anchor_y), imgui.Cond_.always)
imgui.set_next_window_size_constraints((48.0, 170.0), (64.0, 500.0))
flags = (
imgui.WindowFlags_.no_title_bar |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.no_move |
imgui.WindowFlags_.no_scrollbar |
imgui.WindowFlags_.always_auto_resize |
imgui.WindowFlags_.no_saved_settings |
imgui.WindowFlags_.no_docking
)
with imgui_ctx.begin("工具栏", self.app.showToolbar, flags) as (_, opened):
if not opened:
self.app.showToolbar = False
self.app._toolbar_window_rect = None
return
self.app.showToolbar = opened
window_pos = imgui.get_window_pos()
window_size = imgui.get_window_size()
self.app._toolbar_window_rect = (
float(window_pos.x),
float(window_pos.y),
float(window_size.x),
float(window_size.y),
)
command_manager = getattr(self.app, "command_manager", None)
can_undo = command_manager.can_undo() if command_manager else False
can_redo = command_manager.can_redo() if command_manager else False
selection_name = self._truncate_toolbar_text(self._get_toolbar_selection_name(), 18)
icons = getattr(self.app, "icons", {}) or {}
button_width = 24.0
imgui.push_style_var(imgui.StyleVar_.item_spacing, (5.0, 5.0))
imgui.push_style_var(imgui.StyleVar_.window_padding, (7.0, 8.0))
def align_item(item_width):
window_width = float(imgui.get_window_size().x)
centered_x = max(0.0, (window_width - float(item_width)) * 0.5)
imgui.set_cursor_pos_x(centered_x)
align_item(imgui.calc_text_size("工具").x)
imgui.text_disabled("工具")
self._draw_toolbar_separator()
align_item(button_width)
if self._draw_toolbar_icon_button(
icons.get("select"),
"Q",
active=self.app.tool_manager.isSelectionTool(),
tooltip="选择工具 (Q)",
):
self.app.tool_manager.setCurrentTool("选择")
align_item(button_width)
if self._draw_toolbar_icon_button(
icons.get("move"),
"W",
active=self.app.tool_manager.isMoveTool(),
tooltip="移动工具 (W)",
):
self.app.tool_manager.setCurrentTool("移动")
align_item(button_width)
if self._draw_toolbar_icon_button(
icons.get("rotate"),
"E",
active=self.app.tool_manager.isRotateTool(),
tooltip="旋转工具 (E)",
):
self.app.tool_manager.setCurrentTool("旋转")
align_item(button_width)
if self._draw_toolbar_icon_button(
icons.get("scale"),
"R",
active=self.app.tool_manager.isScaleTool(),
tooltip="缩放工具 (R)",
):
self.app.tool_manager.setCurrentTool("缩放")
self._draw_toolbar_separator()
align_item(button_width)
if self._draw_toolbar_icon_button(None, "U", tooltip="撤销上一步 (Ctrl+Z)", enabled=can_undo):
self.app._on_undo()
align_item(button_width)
if self._draw_toolbar_icon_button(None, "Y", tooltip="重做上一步 (Ctrl+Y)", enabled=can_redo):
self.app._on_redo()
align_item(button_width)
if self._draw_toolbar_icon_button(None, "F", tooltip="聚焦当前选择 (F)", enabled=selection_name != "未选择"):
self.app.onFocusKeyPressed()
self._draw_toolbar_separator()
current_label = "当前"
align_item(imgui.calc_text_size(current_label).x)
imgui.text_disabled("当前")
align_item(imgui.calc_text_size(selection_name).x)
imgui.text(selection_name)
history_text = (
f"历史 {command_manager.get_undo_count() if command_manager else 0}/"
f"{command_manager.get_redo_count() if command_manager else 0}"
)
align_item(imgui.calc_text_size(history_text).x)
imgui.text_disabled(history_text)
window_pos = imgui.get_window_pos()
window_size = imgui.get_window_size()
self.app._toolbar_window_rect = (
float(window_pos.x),
float(window_pos.y),
float(window_size.x),
float(window_size.y),
)
imgui.pop_style_var(2)