From 1f7c4b5cda4e1309caad894fe645faf507f6bc74 Mon Sep 17 00:00:00 2001 From: ayuan9957 <107920784+ayuan9957@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:43:38 +0800 Subject: [PATCH] Refine editor panels and viewport controls --- .../Private/MetaCoreBuiltinEditorModule.cpp | 200 +++++++++++++----- .../Private/MetaCoreEditorApp.cpp | 37 +++- .../MetaCoreSceneInteractionService.cpp | 15 +- 3 files changed, 190 insertions(+), 62 deletions(-) diff --git a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp index 6369f59..b1c51ec 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreBuiltinEditorModule.cpp @@ -325,7 +325,11 @@ public: BeginRename(editorContext, requestedRenameId); } + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 8)); + ImGui::TextColored(ImVec4(0.4F, 0.6F, 1.0F, 1.0F), " (S) "); + ImGui::SameLine(); ImGui::TextUnformatted("SampleScene"); + ImGui::PopStyleVar(); ImGui::Separator(); for (MetaCoreId rootId : editorContext.GetScene().GetRootObjectIds()) { @@ -548,19 +552,32 @@ public: bool IsOpenByDefault() const override { return true; } void DrawPanel(MetaCoreEditorContext&) override { + ImGui::Columns(2, "ProjectColumns", true); + if (ImGui::IsWindowAppearing()) { + ImGui::SetColumnWidth(0, 150.0F); + } + if (ImGui::TreeNodeEx("Assets", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::BulletText("Scenes"); - ImGui::BulletText("Materials"); + ImGui::Selectable("Scenes"); + ImGui::Selectable("Materials"); ImGui::TreePop(); } if (ImGui::TreeNodeEx("Packages", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextDisabled("当前阶段未接入真实包管理。"); + ImGui::TextDisabled("Built-in"); ImGui::TreePop(); } + ImGui::NextColumn(); + + ImGui::TextDisabled("Assets > Scenes"); ImGui::Separator(); - ImGui::TextDisabled("当前无真实资源导入流水线。"); + // Placeholder for icons + ImGui::Button("Scene01", ImVec2(64, 64)); + ImGui::SameLine(); + ImGui::Button("MaterialX", ImVec2(64, 64)); + + ImGui::Columns(1); } }; @@ -573,12 +590,15 @@ public: void DrawPanel(MetaCoreEditorContext& editorContext) override { for (const MetaCoreLogEntry& entry : editorContext.GetLogService().GetEntries()) { ImVec4 textColor = ImVec4(0.85F, 0.85F, 0.88F, 1.0F); + const char* icon = "(i)"; if (entry.Level == MetaCoreLogLevel::Warning) { textColor = ImVec4(0.95F, 0.75F, 0.25F, 1.0F); + icon = "(!)"; } else if (entry.Level == MetaCoreLogLevel::Error) { textColor = ImVec4(0.95F, 0.35F, 0.30F, 1.0F); + icon = "(X)"; } - ImGui::TextColored(textColor, "[%s] %s", entry.Category.c_str(), entry.Message.c_str()); + ImGui::TextColored(textColor, "%s [%s] %s", icon, entry.Category.c_str(), entry.Message.c_str()); } } }; @@ -589,19 +609,77 @@ public: bool Supports(const MetaCoreGameObject&) const override { return true; } void DrawInspector(MetaCoreEditorContext& editorContext, MetaCoreGameObject& gameObject) override { + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 3)); if (ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::DragFloat3("Position", glm::value_ptr(gameObject.Transform.Position), 0.05F); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); - - ImGui::DragFloat3("Rotation", glm::value_ptr(gameObject.Transform.RotationEulerDegrees), 0.5F); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); - - ImGui::DragFloat3("Scale", glm::value_ptr(gameObject.Transform.Scale), 0.05F, 0.05F, 100.0F); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + ImGui::PopStyleVar(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 2)); + DrawVec3Control("位置", gameObject.Transform.Position, 0.0F, 0.05F, editorContext); + DrawVec3Control("旋转", gameObject.Transform.RotationEulerDegrees, 0.0F, 0.5F, editorContext); + DrawVec3Control("缩放", gameObject.Transform.Scale, 1.0F, 0.05F, editorContext, true); + ImGui::PopStyleVar(); + } else { + ImGui::PopStyleVar(); } } + +private: + static bool GScaleLocked; + + void DrawVec3Control(const std::string& label, glm::vec3& values, float resetValue, float speed, MetaCoreEditorContext& editorContext, bool isScale = false) { + ImGui::PushID(label.c_str()); + + ImGui::Columns(2); + ImGui::SetColumnWidth(0, 80.0F); + + // Scale Lock Icon (Unity Style) + if (isScale) { + ImGui::PushStyleColor(ImGuiCol_Text, GScaleLocked ? ImVec4(1, 1, 1, 1) : ImVec4(0.5F, 0.5F, 0.5F, 1)); + if (ImGui::Selectable(GScaleLocked ? " (L) " : " (U) ", &GScaleLocked, 0, ImVec2(24, 0))) { + // Toggle locked state + } + ImGui::PopStyleColor(); + ImGui::SameLine(); + } + + ImGui::Text("%s", label.c_str()); + ImGui::NextColumn(); + + const float labelWidth = 14.0F; // Smaller labels + const float itemWidth = (ImGui::GetContentRegionAvail().x - labelWidth * 3.0F - 12.0F) / 3.0F; + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 4)); + + auto drawComponent = [&](const char* compLabel, float& val, const ImVec4& color) { + ImGui::TextColored(color, "%s", compLabel); + ImGui::SameLine(); + ImGui::SetNextItemWidth(itemWidth); + float oldVal = val; + if (ImGui::DragFloat(("##" + std::string(compLabel)).c_str(), &val, speed)) { + if (isScale && GScaleLocked && oldVal != 0.0f) { + float ratio = val / oldVal; + if (compLabel[0] == 'X') { values.y *= ratio; values.z *= ratio; } + else if (compLabel[0] == 'Y') { values.x *= ratio; values.z *= ratio; } + else if (compLabel[0] == 'Z') { values.x *= ratio; values.y *= ratio; } + } + MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + } + }; + + drawComponent("X", values.x, ImVec4(0.9F, 0.3F, 0.3F, 1.0F)); + ImGui::SameLine(); + drawComponent("Y", values.y, ImVec4(0.3F, 0.9F, 0.3F, 1.0F)); + ImGui::SameLine(); + drawComponent("Z", values.z, ImVec4(0.3F, 0.4F, 1.0F, 1.0F)); + + ImGui::PopStyleVar(); + ImGui::Columns(1); + ImGui::PopID(); + } }; +bool MetaCoreTransformInspectorDrawer::GScaleLocked = true; + class MetaCoreInspectorPanelProvider final : public MetaCoreIEditorPanelProvider { public: std::string GetPanelId() const override { return "Inspector"; } @@ -627,34 +705,48 @@ public: std::snprintf(NameBuffer_.data(), NameBuffer_.size(), "%s", selectedObject->Name.c_str()); } - if (selectedCount > 1) { - ImGui::Text("已选中 %zu 个对象(当前编辑 Active 对象)", selectedCount); - ImGui::Separator(); - } - - ImGui::Text("对象 ID: %llu", static_cast(selectedObject->Id)); - bool renameSubmitted = ImGui::InputText( - "名称", - NameBuffer_.data(), - NameBuffer_.size(), - ImGuiInputTextFlags_EnterReturnsTrue - ); - if (ImGui::IsItemDeactivatedAfterEdit()) { - renameSubmitted = true; - } - if (renameSubmitted) { - if (!MetaCoreRenameObject(editorContext, selectedObject->Id, NameBuffer_.data())) { - NameBuffer_.fill('\0'); - std::snprintf(NameBuffer_.data(), NameBuffer_.size(), "%s", selectedObject->Name.c_str()); - } - } - - if (ImGui::Button("聚焦对象")) { - editorContext.GetCameraController().FocusGameObject(*selectedObject); + // GameObject Header (Unity Style) + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 4)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); + + // Active Checkbox + Cube Icon + Name + static bool active = true; + ImGui::Checkbox("##Active", &active); + ImGui::SameLine(); + + // Cube icon (small) + ImGui::TextColored(ImVec4(0.4F, 0.7F, 1.0F, 1.0F), " (B) "); + ImGui::SameLine(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 110.0F); + if (ImGui::InputText("##Name", NameBuffer_.data(), NameBuffer_.size(), ImGuiInputTextFlags_EnterReturnsTrue)) { + (void)MetaCoreRenameObject(editorContext, selectedObject->Id, NameBuffer_.data()); } + ImGui::SameLine(); + static bool isStatic = false; + ImGui::TextDisabled("Static"); ImGui::SameLine(); + ImGui::Checkbox("##Static", &isStatic); + // Tag and Layer + float colWidth = ImGui::GetContentRegionAvail().x * 0.5f; + ImGui::Columns(2, "TagLayerCols", false); + ImGui::SetColumnWidth(0, colWidth); + + ImGui::Text("Tag"); ImGui::SameLine(); + ImGui::SetNextItemWidth(-1); + if (ImGui::Button("Untagged##Tag", ImVec2(-1, 0))) { /* Menu */ } + + ImGui::NextColumn(); + ImGui::Text("Layer"); ImGui::SameLine(); + ImGui::SetNextItemWidth(-1); + if (ImGui::Button("Default##Layer", ImVec2(-1, 0))) { /* Menu */ } + + ImGui::Columns(1); + ImGui::PopStyleVar(2); ImGui::Separator(); + // Components + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 4)); for (const auto& drawer : editorContext.GetModuleRegistry().GetInspectorDrawers()) { if (drawer->Supports(*selectedObject)) { drawer->DrawInspector(editorContext, *selectedObject); @@ -662,26 +754,34 @@ public: } if (selectedObject->Camera.has_value()) { - ImGui::Separator(); - ImGui::TextUnformatted("Camera"); - ImGui::Text("FOV: %.1f", selectedObject->Camera->FieldOfViewDegrees); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::CollapsingHeader("Camera", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text("FOV: %.1f", selectedObject->Camera->FieldOfViewDegrees); + } } if (selectedObject->Light.has_value()) { - ImGui::Separator(); - ImGui::TextUnformatted("Light"); - ImGui::DragFloat("Intensity", &selectedObject->Light->Intensity, 0.05F, 0.0F, 8.0F); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::CollapsingHeader("Light", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::DragFloat("Intensity", &selectedObject->Light->Intensity, 0.05F, 0.0F, 8.0F); + MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + } } if (selectedObject->MeshRenderer.has_value()) { - ImGui::Separator(); - ImGui::TextUnformatted("MeshRenderer"); - ImGui::ColorEdit3("BaseColor", glm::value_ptr(selectedObject->MeshRenderer->BaseColor)); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); - ImGui::Checkbox("Visible", &selectedObject->MeshRenderer->Visible); - MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", false); + ImGui::SetNextItemOpen(true, ImGuiCond_Once); + if (ImGui::CollapsingHeader("Mesh Renderer", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::ColorEdit3("BaseColor", glm::value_ptr(selectedObject->MeshRenderer->BaseColor)); + MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", true); + ImGui::Checkbox("Visible", &selectedObject->MeshRenderer->Visible); + MetaCoreTrackInspectorEdit(editorContext, "修改检查器属性", false); + } } + ImGui::PopStyleVar(); + + ImGui::Spacing(); + ImGui::Separator(); + if (ImGui::Button("添加组件", ImVec2(-1, 26))) { /* Add Component */ } } private: diff --git a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp index 6319021..237efb3 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreEditorApp.cpp @@ -333,6 +333,20 @@ void MetaCoreEditorApp::DrawEditorFrame() { for (const auto& menuProvider : ModuleRegistry_.GetMenuProviders()) { menuProvider->DrawMenuBar(*EditorContext_); } + + // Center Play/Pause/Stop buttons + const float buttonWidth = 32.0F; + const float totalWidth = buttonWidth * 3 + ImGui::GetStyle().ItemSpacing.x * 2; + ImGui::SetCursorPosX((ImGui::GetWindowWidth() - totalWidth) * 0.5F); + + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2F, 0.2F, 0.2F, 0.0F)); + if (ImGui::Button(" > ", ImVec2(buttonWidth, 0))) { /* Play */ } + ImGui::SameLine(); + if (ImGui::Button(" ||", ImVec2(buttonWidth, 0))) { /* Pause */ } + ImGui::SameLine(); + if (ImGui::Button(" >>", ImVec2(buttonWidth, 0))) { /* Step */ } + ImGui::PopStyleColor(); + ImGui::EndMenuBar(); } @@ -383,16 +397,29 @@ void MetaCoreEditorApp::DrawEditorFrame() { if (ModuleRegistry_.IsPanelOpen("Scene")) { ImGuiDockNode* centralNode = ImGui::DockBuilderGetCentralNode(dockSpaceId); if (centralNode != nullptr) { + // Viewport Tabs (Scene / Game) + ImGui::SetNextWindowPos(centralNode->Pos); + ImGui::SetNextWindowSize(ImVec2(centralNode->Size.x, 30.0F)); + ImGui::Begin("ViewportTabs", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); + + static int activeTab = 0; // 0: Scene, 1: Game + if (ImGui::Selectable(" 场景 ", activeTab == 0, 0, ImVec2(60, 0))) activeTab = 0; + ImGui::SameLine(); + if (ImGui::Selectable(" 游戏 ", activeTab == 1, 0, ImVec2(60, 0))) activeTab = 1; + + ImGui::End(); + constexpr float sceneToolbarHeight = 34.0F; + constexpr float tabHeaderHeight = 30.0F; MetaCoreSceneViewportState& viewportState = EditorContext_->GetSceneViewportState(); const ImVec2 mainViewportPosition = ImGui::GetMainViewport()->Pos; viewportState.Left = centralNode->Pos.x - mainViewportPosition.x; - viewportState.Top = centralNode->Pos.y - mainViewportPosition.y + sceneToolbarHeight; + viewportState.Top = centralNode->Pos.y - mainViewportPosition.y + sceneToolbarHeight + tabHeaderHeight; viewportState.Width = centralNode->Size.x; if (viewportState.Width < 1.0F) { viewportState.Width = 1.0F; } - viewportState.Height = centralNode->Size.y - sceneToolbarHeight; + viewportState.Height = centralNode->Size.y - sceneToolbarHeight - tabHeaderHeight; if (viewportState.Height < 1.0F) { viewportState.Height = 1.0F; } @@ -401,7 +428,7 @@ void MetaCoreEditorApp::DrawEditorFrame() { viewportState.Hovered = mousePosition.x >= centralNode->Pos.x && mousePosition.x <= centralNode->Pos.x + centralNode->Size.x && - mousePosition.y >= centralNode->Pos.y + sceneToolbarHeight && + mousePosition.y >= centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight && mousePosition.y <= centralNode->Pos.y + centralNode->Size.y; // 1. Process Input and Viewport State viewportState.Focused = viewportState.Hovered && !ImGui::GetIO().WantCaptureMouse; @@ -432,7 +459,7 @@ void MetaCoreEditorApp::DrawEditorFrame() { bool gizmoUsing = false; if (GMetaCoreEnableImGuizmo) { - ImGui::SetNextWindowPos(ImVec2(centralNode->Pos.x, centralNode->Pos.y + sceneToolbarHeight)); + ImGui::SetNextWindowPos(ImVec2(centralNode->Pos.x, centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight)); ImGui::SetNextWindowSize(ImVec2(viewportState.Width, viewportState.Height)); ImGui::SetNextWindowBgAlpha(0.0F); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F); @@ -456,7 +483,7 @@ void MetaCoreEditorApp::DrawEditorFrame() { const float cameraDistance = glm::distance(sceneView.CameraPosition, sceneView.CameraTarget); const ImVec2 viewCubePos = ImVec2( centralNode->Pos.x + viewportState.Width - 120.0F, - centralNode->Pos.y + sceneToolbarHeight + 20.0F + centralNode->Pos.y + sceneToolbarHeight + tabHeaderHeight + 20.0F ); const ImVec2 viewCubeSize = ImVec2(100.0F, 100.0F); diff --git a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp index 632f2e7..7089d86 100644 --- a/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp +++ b/Source/MetaCoreEditor/Private/MetaCoreSceneInteractionService.cpp @@ -273,7 +273,9 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont ImGuizmo::SetOrthographic(false); ImGuizmo::Enable(true); ImGuizmo::AllowAxisFlip(false); - ImGuizmo::SetGizmoSizeClipSpace(0.30F); + const MetaCoreGizmoOperation currentOp = editorContext.GetGizmoOperation(); + const float gizmoSize = (currentOp == MetaCoreGizmoOperation::Rotate) ? 0.20F : 0.30F; + ImGuizmo::SetGizmoSizeClipSpace(gizmoSize); ImGuizmo::SetDrawlist(ImGui::GetWindowDrawList()); ImGuizmo::SetRect( ImGui::GetWindowPos().x, @@ -283,7 +285,6 @@ void MetaCoreSceneInteractionService::HandleGizmoManipulation(MetaCoreEditorCont ); // 4. Determine Gizmo Mode (force Local for Scale). - const MetaCoreGizmoOperation currentOp = editorContext.GetGizmoOperation(); const MetaCoreGizmoMode mcMode = editorContext.GetGizmoMode(); ImGuizmo::MODE effectiveMode = (mcMode == MetaCoreGizmoMode::Global) ? ImGuizmo::WORLD : ImGuizmo::LOCAL; @@ -375,20 +376,20 @@ void MetaCoreSceneInteractionService::DrawViewportToolbar(MetaCoreEditorContext& return clicked; }; - // Tools - if (drawToolbarToggle("Q", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None)) { + // Tools (Unity-Style Icons/Labels) + if (drawToolbarToggle(" V ", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::None)) { editorContext.SetGizmoOperation(MetaCoreGizmoOperation::None); } ImGui::SameLine(); - if (drawToolbarToggle("W", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) { + if (drawToolbarToggle(" M ", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Translate)) { editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Translate); } ImGui::SameLine(); - if (drawToolbarToggle("E", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) { + if (drawToolbarToggle(" R ", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Rotate)) { editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Rotate); } ImGui::SameLine(); - if (drawToolbarToggle("R", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) { + if (drawToolbarToggle(" S ", editorContext.GetGizmoOperation() == MetaCoreGizmoOperation::Scale)) { editorContext.SetGizmoOperation(MetaCoreGizmoOperation::Scale); }