#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { [[nodiscard]] bool AnyKeyReleased() { for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; ++key) { if (ImGui::IsKeyReleased(static_cast(key))) { return true; } } return false; } } Editor::Editor(App& app, EditorSettings settings) : m_App(app), m_Settings(std::move(settings)), m_PlayMode(app), m_ScenePath(m_Settings.scenePath) { } void Editor::onAttach() { auto& assetFs = AssetFS::GetInstance(); #ifdef DESTRUM_ENGINE_ASSETS_SRC_DIR assetFs.SetSourceRoot("engine", DESTRUM_ENGINE_ASSETS_SRC_DIR); #endif if (!m_Settings.gameAssetsSource.empty()) { assetFs.SetSourceRoot("game", m_Settings.gameAssetsSource); } AssetDatabase::GetInstance().Refresh(); #if defined(DESTRUM_ENGINE_ASSETS_SRC_DIR) && defined(DESTRUM_THECHEF_PATH) m_ShaderHotReload.Configure(DESTRUM_THECHEF_PATH, DESTRUM_ENGINE_ASSETS_SRC_DIR, assetFs.GetFullPath("engine://shaders").parent_path()); #endif if (m_Settings.startInEditMode) { m_PlayMode.EnterEditMode(); } if (Scene* scene = CurrentScene()) { m_UndoStack.Reset(*scene); } } Scene* Editor::CurrentScene() const { auto& sceneManager = SceneManager::GetInstance(); return sceneManager.GetActiveScenes().empty() ? nullptr : &sceneManager.GetCurrentScene(); } bool Editor::UsesEditorCamera() { return m_PlayMode.IsEditing() || m_PlayMode.IsPaused() || m_App.gameCamera() == nullptr; } Camera* Editor::viewCamera() { return UsesEditorCamera() ? &m_Camera.GetCamera() : nullptr; } AppLayer::InputFocus Editor::gameInputFocus() const { return { .mouse = m_Viewport.hovered && !m_Viewport.mouseClaimed, .keyboard = m_Viewport.focused || m_Camera.IsLooking(), }; } void Editor::onBeginFrame() { ApplyViewportResize(); } void Editor::onImGuiBegin(float dt) { DrawDockSpace(); ApplyPick(); HandleShortcuts(); if (UsesEditorCamera()) { m_Camera.Update(dt, m_Viewport.hovered || m_Viewport.focused || m_Camera.IsLooking()); } if (m_ShaderHotReload.Poll(dt)) { const bool reloaded = m_App.getRenderer().reloadShaders(m_App.getGfxDevice()); SetStatus(reloaded ? "Reloaded shaders" : "Shader reload failed, the old shaders stay active"); } } void Editor::onImGui(float) { DrawMainMenuBar(); DrawSceneDialogs(); DrawViewport(); Scene* scene = CurrentScene(); if (scene != nullptr && m_Panels.inspector) { ObjectId selected = scene->GetSelectedObjectId(); m_Inspector.Draw(*scene, selected, [scene] { scene->DrawSettingsImGui(); }, &m_Panels.inspector); scene->SetSelectedObject(selected); } if (m_Panels.assets) { if (const auto opened = m_AssetBrowser.Draw(&m_Panels.assets)) { OnAssetDropped(*opened); } } if (m_Panels.stats) { DrawStatsPanel(); } if (m_Panels.physics) { DrawPhysicsPanel(); } if (m_Panels.imageCache) { m_ResourcesInspector.drawImageCacheWindow(&m_Panels.imageCache, m_App.getResources(), m_App.getImguiPass()); } if (scene != nullptr && m_View.gizmos && UsesEditorCamera()) { GameObject* selected = scene->GetSelectedObject(); if (selected != nullptr && !RectGizmo::CanManipulate(*selected)) { m_TransformGizmo.DrawToolbar(); } } } void Editor::onEndFrame() { if (m_PlayMode.ApplyPendingToggle()) { AfterSceneReplaced(false); } const bool itemActive = ImGui::IsAnyItemActive() || ImGuizmo::IsUsing(); const bool gestureEnded = (m_ItemWasActive && !itemActive) || ImGui::IsMouseReleased(ImGuiMouseButton_Left) || AnyKeyReleased() || std::exchange(m_EditPending, false); m_ItemWasActive = itemActive; if (gestureEnded && !itemActive && m_PlayMode.IsEditing()) { RecordEdits(); } } void Editor::onRender(GameRenderer& renderer) { const bool picking = m_PlayMode.IsEditing() || m_PlayMode.IsPaused(); renderer.setSelectionPassEnabled(picking); renderer.setHoverPixel(picking ? m_Viewport.hoverPixel : std::nullopt); std::vector highlighted; Scene* scene = CurrentScene(); if (scene == nullptr) { renderer.setHighlightedObjects(highlighted); return; } if (GameObject* selected = scene->GetSelectedObject(); selected != nullptr && picking) { std::vector pending{selected}; while (!pending.empty()) { GameObject* object = pending.back(); pending.pop_back(); highlighted.push_back(static_cast(object->GetId())); for (const Transform* child : object->GetTransform().GetChildren()) { if (child != nullptr && child->GetOwner() != nullptr) { pending.push_back(child->GetOwner()); } } } } renderer.setHighlightedObjects(highlighted); if (m_View.colliders) { SubmitColliderLines(*scene); } } void Editor::HandleShortcuts() { const ImGuiIO& io = ImGui::GetIO(); if (io.WantTextInput) { return; } if (ImGui::IsKeyPressed(ImGuiKey_F5, false)) { m_PlayMode.RequestToggle(); } if (ImGui::IsKeyPressed(ImGuiKey_F7, false) && m_PlayMode.IsPlaying()) { m_PlayMode.SetPaused(!m_PlayMode.IsPaused()); } if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S, false)) { SaveScene(); } if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Z)) { io.KeyShift ? Redo() : Undo(); } if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Y)) { Redo(); } if (!m_Viewport.focused && !m_Viewport.hovered) { return; } if (UsesEditorCamera() && !m_Camera.IsLooking()) { m_TransformGizmo.HandleShortcuts(); } if (ImGui::IsKeyPressed(ImGuiKey_F, false) && !io.KeyCtrl) { FocusSelection(); } if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) { DeleteSelection(); } if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_D, false)) { DuplicateSelection(); } } void Editor::Undo() { Scene* scene = CurrentScene(); if (scene == nullptr || !m_PlayMode.IsEditing()) { return; } if (m_UndoStack.Undo(*scene)) { m_App.collectUnusedResources(); m_App.onPlayModeChanged(false); } } void Editor::Redo() { Scene* scene = CurrentScene(); if (scene == nullptr || !m_PlayMode.IsEditing()) { return; } if (m_UndoStack.Redo(*scene)) { m_App.collectUnusedResources(); m_App.onPlayModeChanged(false); } } void Editor::RecordEdits() { if (Scene* scene = CurrentScene()) { m_UndoStack.Record(*scene); } } void Editor::AfterSceneReplaced(bool resetHistory) { m_App.collectUnusedResources(); Scene* scene = CurrentScene(); if (scene == nullptr) { return; } if (resetHistory) { m_UndoStack.Reset(*scene); } else { m_UndoStack.Rebase(*scene); } } void Editor::DeleteSelection() { Scene* scene = CurrentScene(); GameObject* selected = scene != nullptr ? scene->GetSelectedObject() : nullptr; if (selected == nullptr) { return; } selected->Destroy(); scene->SetSelectedObject(InvalidObjectId); m_EditPending = true; } void Editor::DuplicateSelection() { Scene* scene = CurrentScene(); GameObject* selected = scene != nullptr ? scene->GetSelectedObject() : nullptr; if (selected == nullptr) { return; } if (const GameObject* copy = PrefabSerializer::Duplicate(*selected)) { scene->SetSelectedObject(copy->GetId()); m_EditPending = true; } } void Editor::FocusSelection() { Scene* scene = CurrentScene(); if (GameObject* selected = scene != nullptr ? scene->GetSelectedObject() : nullptr) { m_Camera.Focus(ComputeWorldBounds(*selected, m_App.getResources())); } } void Editor::SetStatus(std::string status) { spdlog::info("{}", status); m_Status = std::move(status); } bool Editor::WriteScene(Scene& scene, std::string_view path) { const AssetDatabase::MetaFileScope createMetaFiles; const auto root = SceneSerializer::Capture(scene); if (!root || !AssetFS::GetInstance().WriteFile(path, root->dump(4))) { SetStatus("Could not save the scene to " + std::string{path}); return false; } SetStatus("Saved " + std::string{path}); return true; } bool Editor::SaveScene() { if (!m_PlayMode.IsEditing()) { SetStatus("Stop play mode before saving, the scene is the play copy right now"); return false; } if (m_ScenePath.empty()) { m_SaveAsDialog = true; return false; } Scene* scene = CurrentScene(); return scene != nullptr && WriteScene(*scene, m_ScenePath); } bool Editor::SaveSceneAs(std::string_view path) { std::string target{path}; if (!AssetTypes::IsScene(target)) { target += AssetTypes::SceneExtension; } m_ScenePath = target; return SaveScene(); } bool Editor::OpenScene(std::string_view path) { Scene* scene = CurrentScene(); if (scene == nullptr) { return false; } if (!m_PlayMode.IsEditing()) { SetStatus("Stop play mode before opening another scene"); return false; } if (!SceneSerializer::Load(*scene, AssetFS::GetInstance().GetFullPath(path))) { SetStatus("Could not open " + std::string{path}); return false; } m_ScenePath = path; m_PlayMode.EnterEditMode(); AfterSceneReplaced(true); SetStatus("Opened " + std::string{path}); return true; } void Editor::NewScene() { Scene* scene = CurrentScene(); if (scene == nullptr || !m_PlayMode.IsEditing()) { return; } const nlohmann::json empty{{"name", "Untitled"}, {"objects", nlohmann::json::array()}}; if (SceneSerializer::LoadFromJson(*scene, empty)) { m_ScenePath.clear(); m_PlayMode.EnterEditMode(); AfterSceneReplaced(true); } }