Files
Destrum/editor/src/Editor.cpp
T

388 lines
10 KiB
C++

#include <editor/Editor.h>
#include <utility>
#include <imgui.h>
#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <destrum/App.h>
#include <destrum/Assets/AssetDatabase.h>
#include <destrum/Assets/AssetTypes.h>
#include <destrum/FS/AssetFS.h>
#include <destrum/Input/InputManager.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Scene/SceneManager.h>
#include <destrum/Serialization/PrefabSerializer.h>
#include <destrum/Serialization/SceneSerializer.h>
#include <ImGuizmo.h>
namespace {
[[nodiscard]] bool AnyKeyReleased()
{
for (int key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; ++key) {
if (ImGui::IsKeyReleased(static_cast<ImGuiKey>(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<std::uint32_t> highlighted;
Scene* scene = CurrentScene();
if (scene == nullptr) {
renderer.setHighlightedObjects(highlighted);
return;
}
if (GameObject* selected = scene->GetSelectedObject(); selected != nullptr && picking) {
std::vector<GameObject*> pending{selected};
while (!pending.empty()) {
GameObject* object = pending.back();
pending.pop_back();
highlighted.push_back(static_cast<std::uint32_t>(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);
}
}