EG/ui/panels/editor_panels_top.py
2026-04-23 14:23:28 +08:00

559 lines
29 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
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)
lui_available = bool(
getattr(getattr(self.app, "lui_manager", None), "lui_enabled", False)
)
changed, visible = imgui.menu_item(
"LUI编辑器",
"",
self.app.is_panel_visible("lui_editor"),
lui_available,
)
if changed:
self.app.set_panel_visible("lui_editor", visible)
if not lui_available and imgui.is_item_hovered():
imgui.set_tooltip("当前环境未启用LUI缺少 lui 模块)")
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)