#include "PrefabEditorWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Nuake; PrefabEditorWindow::PrefabEditorWindow(Ref inPrefab) : prefab(inPrefab) { const Vector2 defaultSize = Vector2{ 640, 360 }; viewportFramebuffer = CreateRef(true, defaultSize); viewportFramebuffer->SetTexture(CreateRef(defaultSize, GL_RGB, GL_RGB16F, GL_FLOAT)); virtualScene = CreateRef(); virtualScene->GetEnvironment()->CurrentSkyType = SkyType::ProceduralSky; virtualScene->GetEnvironment()->ProceduralSkybox->SunDirection = { 0.58f, 0.34f, -0.74f }; prefab = Prefab::InstanceInScene(inPrefab->Path, virtualScene.get()); prefab->Path = inPrefab->Path; Ref outputTexture = CreateRef(defaultSize, GL_RGB); outputTexture->SetParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); outputTexture->SetParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); auto& previewLight = virtualScene->CreateEntity("_directionalLight").AddComponent(); previewLight.Type = LightType::Directional; previewLight.SetCastShadows(true); previewLight.SyncDirectionWithSky = true; previewLight.Strength = 5.5f; } void PrefabEditorWindow::Update(float ts) { } void PrefabEditorWindow::Draw() { RenderScene(); ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; ImGuiID dockspace_id = ImGui::GetID(prefab->Path.c_str()); ImVec2 dockspace_size; if (!isInitialized) { ImGui::SetNextWindowSize(ImVec2(1280, 720)); } if(ImGui::Begin(prefab->Path.c_str(), 0, window_flags)) { dockspace_size = ImGui::GetContentRegionAvail(); ImGui::DockSpace(dockspace_id, dockspace_size, dockspace_flags); if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("File")) { if(ImGui::MenuItem("Save", "Ctrl+S")) { Save(); } ImGui::EndMenu(); } ImGui::EndMenuBar(); } } ImGui::End(); // Programmatically dock windows using DockBuilder API if (!isInitialized) { isInitialized = true; // Create dock layout for the embedded dockspace ImGui::DockBuilderRemoveNode(dockspace_id); // Clear any existing layout ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace); ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size); // Split the dockspace into two areas: left and right ImGuiID dock_left_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Left, 0.25f, nullptr, &dockspace_id); ImGuiID dock_r_id = ImGui::DockBuilderSplitNode(dockspace_id, ImGuiDir_Right, 0.66f, nullptr, &dockspace_id); // Dock windows into the created nodes ImGui::DockBuilderDockWindow(std::string("Prefab Hierarchy## " + prefab->Path).c_str(), dock_left_id); ImGui::DockBuilderDockWindow(std::string("Viewport##" + prefab->Path).c_str(), dockspace_id); ImGui::DockBuilderDockWindow(std::string("Properties##" + prefab->Path).c_str(), dock_r_id); // Commit the dock layout ImGui::DockBuilderFinish(dockspace_id); } if (ImGui::Begin(std::string("Prefab Hierarchy## " + prefab->Path).c_str())) { Ref scene = virtualScene; std::string searchQuery = ""; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 8, 8 }); ImGui::InputTextWithHint("##search", "Search entity", &searchQuery, 0, 0, 0); ImGui::PopStyleVar(); ImGui::SameLine(); if (UI::PrimaryButton("Add Entity", { ImGui::GetContentRegionAvail().x, 0 })) { ImGui::OpenPopup("create_entity_popup"); } if (ImGui::BeginPopup("create_entity_popup")) { Nuake::Entity entity; if (ImGui::MenuItem("Empty")) { entity = scene->CreateEntity("Empty"); } if (ImGui::BeginMenu("3D")) { if (ImGui::MenuItem("Camera")) { entity = scene->CreateEntity("Camera"); entity.AddComponent(); } if (ImGui::MenuItem("Model")) { entity = scene->CreateEntity("Model"); entity.AddComponent(); } if (ImGui::MenuItem("Skinned Model")) { entity = scene->CreateEntity("Skinned Model"); entity.AddComponent(); } if (ImGui::MenuItem("Sprite")) { entity = scene->CreateEntity("Sprite"); entity.AddComponent(); } if (ImGui::MenuItem("Particle Emitter")) { entity = scene->CreateEntity("Particle Emitter"); entity.AddComponent(); } if (ImGui::MenuItem("Light")) { entity = scene->CreateEntity("Light"); entity.AddComponent(); } if (ImGui::MenuItem("Quake Map")) { entity = scene->CreateEntity("Quake Map"); entity.AddComponent(); } if (ImGui::MenuItem("NavMesh Volume")) { entity = scene->CreateEntity("NavMesh Volume"); entity.AddComponent(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Physics")) { if (ImGui::MenuItem("Character Controller")) { entity = scene->CreateEntity("Character Controller"); entity.AddComponent(); } if (ImGui::MenuItem("Rigid Body")) { entity = scene->CreateEntity("Rigid Body"); entity.AddComponent(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Colliders")) { if (ImGui::MenuItem("Box Collider")) { entity = scene->CreateEntity("Box Collider"); entity.AddComponent(); } if (ImGui::MenuItem("Sphere Collider")) { entity = scene->CreateEntity("Sphere Collider"); entity.AddComponent(); } if (ImGui::MenuItem("Capsule Collider")) { entity = scene->CreateEntity("Capsule Collider"); entity.AddComponent(); } if (ImGui::MenuItem("Cylinder Collider")) { entity = scene->CreateEntity("Cylinder Collider"); entity.AddComponent(); } if (ImGui::MenuItem("Mesh Collider")) { entity = scene->CreateEntity("Mesh Collider"); entity.AddComponent(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Audio")) { if (ImGui::MenuItem("Audio Emitter")) { entity = scene->CreateEntity("Audio Emitter"); entity.AddComponent(); } ImGui::EndMenu(); } if (entity.IsValid()) { if (Selection.Type == EditorSelectionType::Entity && Selection.Entity.IsValid()) { Selection.Entity.AddChild(entity); } else { auto& camera = virtualScene->m_EditorCamera; Vector3 newEntityPos = camera->Translation + camera->Direction; entity.GetComponent().SetLocalPosition(newEntityPos); } Selection = EditorSelection(entity); } ImGui::EndPopup(); } // Draw a tree of entities. ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(26.f / 255.0f, 26.f / 255.0f, 26.f / 255.0f, 1)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4, 4 }); if (ImGui::BeginChild("Scene tree", ImGui::GetContentRegionAvail(), false)) { if (ImGui::BeginTable("entity_table", 4, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn(" Label", ImGuiTableColumnFlags_IndentEnable | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Script", ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Visibility ", ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); std::vector entities = scene->GetAllEntities(); ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanFullWidth; std::string name = prefab->Root.GetComponent().Name; // If selected add selected flag. if (Selection.Type == EditorSelectionType::Entity && Selection.Entity == prefab->Root) base_flags |= ImGuiTreeNodeFlags_Selected; DrawEntityTree(prefab->Root); ImGui::PopStyleVar(); } ImGui::EndTable(); if (QueueDeletion.GetHandle() != -1) { virtualScene->DestroyEntity(QueueDeletion); // Clear Selection Selection = EditorSelection(); QueueDeletion = Nuake::Entity{ (entt::entity)-1, scene.get() }; } } ImGui::EndChild(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); } ImGui::End(); SelectionPanel.Draw(Selection, prefab->Path); DrawViewportWindow(); } void PrefabEditorWindow::DrawViewportWindow() { //viewportFramebuffer->Bind(); ////RenderCommand::SetClearColor(Color(1, 0, 0, 1)); //viewportFramebuffer->Clear(); //viewportFramebuffer->Unbind(); //RenderScene(); if (ImGui::Begin(std::string("Viewport##" + prefab->Path).c_str())) { DrawOverlay(); ImGuizmo::BeginFrame(); ImGuizmo::SetOrthographic(false); ImVec2 regionAvail = ImGui::GetContentRegionAvail(); Vector2 viewportPanelSize = glm::vec2(regionAvail.x, regionAvail.y); if (viewportFramebuffer->GetSize() != viewportPanelSize) viewportFramebuffer->QueueResize(viewportPanelSize); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));\ viewportFramebuffer->QueueResize(viewportPanelSize); ImGui::Image((void*)viewportFramebuffer->GetTexture(GL_COLOR_ATTACHMENT0)->GetID(), regionAvail, ImVec2(0, 1), ImVec2(1, 0)); ImGui::PopStyleVar(); const Vector2& mousePos = Input::GetMousePosition(); ImVec2 imagePos = ImGui::GetWindowPos() + ImGui::GetCursorPos(); const ImVec2& windowPos = ImGui::GetWindowPos(); const auto windowPosNuake = Vector2(windowPos.x, windowPos.y); const ImVec2& windowSize = ImGui::GetWindowSize(); const bool isInsideWidth = mousePos.x > windowPos.x && mousePos.x < windowPos.x + windowSize.x; const bool isInsideHeight = mousePos.y > windowPos.y && mousePos.y < windowPos.y + windowSize.y; isHoveringViewport = isInsideWidth && isInsideHeight; ImGuizmo::SetDrawlist(); ImGuizmo::AllowAxisFlip(true); ImGuizmo::SetRect(imagePos.x, imagePos.y + 0.0f, viewportPanelSize.x, viewportPanelSize.y); auto& editorCam = virtualScene->m_EditorCamera; isControllingCamera = editorCam->Update(Engine::GetTimestep(), isHoveringViewport); if (Selection.Type == EditorSelectionType::Entity && !Engine::IsPlayMode()) { if (!Selection.Entity.IsValid()) { Selection = EditorSelection(); } else { TransformComponent& tc = Selection.Entity.GetComponent(); Matrix4 transform = tc.GetGlobalTransform(); const auto& editorCam = virtualScene->GetCurrentCamera(); Matrix4 cameraView = editorCam->GetTransform(); Matrix4 cameraProjection = editorCam->GetPerspective(); static Vector3 camPreviousPos = virtualScene->m_EditorCamera->Translation; static Vector3 camNewPos = Vector3(0, 0, 0); Vector3 camDelta = camNewPos - camPreviousPos; Vector3 previousGlobalPos = transform[3]; // Imguizmo calculates the delta from the gizmo, ImGuizmo::Manipulate( glm::value_ptr(cameraView), glm::value_ptr(cameraProjection), CurrentOperation, CurrentMode, glm::value_ptr(transform), NULL, UseSnapping ? &CurrentSnapping.x : NULL ); if (ImGuizmo::IsUsing()) { // Since imguizmo returns a transform in global space and we want the local transform, // we need to multiply by the inverse of the parent's global transform in order to revert // the changes from the parent transform. Matrix4 localTransform = Matrix4(transform); Vector3 newGlobalPos = transform[3]; if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { Vector3 positionDelta = newGlobalPos - previousGlobalPos; virtualScene->m_EditorCamera->Translation += positionDelta; camNewPos = virtualScene->m_EditorCamera->Translation; } ParentComponent& parent = Selection.Entity.GetComponent(); if (parent.HasParent) { const auto& parentTransformComponent = parent.Parent.GetComponent(); const Matrix4& parentTransform = parentTransformComponent.GetGlobalTransform(); localTransform = glm::inverse(parentTransform) * localTransform; } // Decompose local transform float decomposedPosition[3]; float decomposedEuler[3]; float decomposedScale[3]; ImGuizmo::DecomposeMatrixToComponents(glm::value_ptr(localTransform), decomposedPosition, decomposedEuler, decomposedScale); const auto& localPosition = Vector3(decomposedPosition[0], decomposedPosition[1], decomposedPosition[2]); const auto& localScale = Vector3(decomposedScale[0], decomposedScale[1], decomposedScale[2]); localTransform[0] /= localScale.x; localTransform[1] /= localScale.y; localTransform[2] /= localScale.z; const auto& rotationMatrix = Matrix3(localTransform); const Quat& localRotation = glm::normalize(Quat(rotationMatrix)); const Matrix4& rotationMatrix4 = glm::mat4_cast(localRotation); const Matrix4& scaleMatrix = glm::scale(Matrix4(1.0f), localScale); const Matrix4& translationMatrix = glm::translate(Matrix4(1.0f), localPosition); const Matrix4& newLocalTransform = translationMatrix * rotationMatrix4 * scaleMatrix; tc.Translation = localPosition; if (CurrentOperation != ImGuizmo::SCALE) { tc.Rotation = localRotation; } tc.Scale = localScale; tc.LocalTransform = newLocalTransform; tc.Dirty = true; } } } if (ImGui::IsWindowHovered() && isHoveringViewport && !isViewportFocused && ImGui::GetIO().WantCaptureMouse) { ImGui::FocusWindow(ImGui::GetCurrentWindow()); } isViewportFocused = ImGui::IsWindowFocused(); if (ImGui::GetIO().WantCaptureMouse && isHoveringViewport && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && isViewportFocused) { auto& gbuffer = virtualScene->m_SceneRenderer->GetGBuffer(); auto pixelPos = (Input::GetMousePosition() - windowPosNuake); pixelPos.y = gbuffer.GetSize().y - pixelPos.y; // imgui coords are inverted on the Y axi gbuffer.Bind(); if (const int result = gbuffer.ReadPixel(3, pixelPos); result > 0) { auto ent = Nuake::Entity{ (entt::entity)(result - 1), virtualScene.get() }; if (ent.IsValid()) { Selection = EditorSelection(ent); virtualScene->m_SceneRenderer->mOutlineEntityID = (uint32_t)ent.GetHandle(); } } else { Selection = EditorSelection(); // None } gbuffer.Unbind(); } } ImGui::End(); } void PrefabEditorWindow::DrawOverlay() { if (Engine::GetGameState() == GameState::Playing) { return; } // FIXME-VIEWPORT: Select a default viewport const float DISTANCE = 10.0f; int corner = 0; ImGuiIO& io = ImGui::GetIO(); ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav; window_flags |= ImGuiWindowFlags_NoMove; ImGuiViewport* viewport = ImGui::GetWindowViewport(); ImVec2 work_area_pos = ImGui::GetCurrentWindow()->Pos; // Instead of using viewport->Pos we use GetWorkPos() to avoid menu bars, if any! ImVec2 work_area_size = ImGui::GetCurrentWindow()->Size; ImVec2 window_pos = ImVec2((corner & 1) ? (work_area_pos.x + work_area_size.x - DISTANCE) : (work_area_pos.x + DISTANCE), (corner & 2) ? (work_area_pos.y + work_area_size.y - DISTANCE) : (work_area_pos.y + DISTANCE)); ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); ImGui::SetNextWindowViewport(viewport->ID); ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 32.0f); if (ImGui::Begin("ActionBar2", 0, window_flags)) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(2, 2)); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32(0, 0, 0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 100); bool selectedMode = CurrentOperation == ImGuizmo::OPERATION::TRANSLATE; if (selectedMode) { Color color = Engine::GetProject()->Settings.PrimaryColor; ImGui::PushStyleColor(ImGuiCol_Button, { color.r, color.g, color.b, 1.0f }); } if (ImGui::Button(ICON_FA_ARROWS_ALT , ImVec2(30, 28)) || (ImGui::Shortcut(ImGuiKey_W, 0, ImGuiInputFlags_RouteGlobalLow) && !ImGui::IsAnyItemActive() && !isControllingCamera)) { CurrentOperation = ImGuizmo::OPERATION::TRANSLATE; } UI::Tooltip("Translate"); if (selectedMode) { ImGui::PopStyleColor(); } ImGui::SameLine(); selectedMode = CurrentOperation == ImGuizmo::OPERATION::ROTATE; if (selectedMode) { Color color = Engine::GetProject()->Settings.PrimaryColor; ImGui::PushStyleColor(ImGuiCol_Button, { color.r, color.g, color.b, 1.0f }); } if (ImGui::Button(ICON_FA_SYNC_ALT, ImVec2(30, 28)) || (ImGui::Shortcut(ImGuiKey_E, 0, ImGuiInputFlags_RouteGlobalLow) && !ImGui::IsAnyItemActive() && !isControllingCamera)) { CurrentOperation = ImGuizmo::OPERATION::ROTATE; } UI::Tooltip("Rotate"); if (selectedMode) { ImGui::PopStyleColor(); } ImGui::SameLine(); selectedMode = CurrentOperation == ImGuizmo::OPERATION::SCALE; if (selectedMode) { Color color = Engine::GetProject()->Settings.PrimaryColor; ImGui::PushStyleColor(ImGuiCol_Button, { color.r, color.g, color.b, 1.0f }); } if (ImGui::Button(ICON_FA_EXPAND_ALT, ImVec2(30, 28)) || (ImGui::Shortcut(ImGuiKey_R, 0, ImGuiInputFlags_RouteGlobalLow) && !ImGui::IsAnyItemActive() && !isControllingCamera)) { CurrentOperation = ImGuizmo::OPERATION::SCALE; } UI::Tooltip("Scale"); if (selectedMode) { ImGui::PopStyleColor(); } ImGui::SameLine(); selectedMode = CurrentMode == ImGuizmo::MODE::WORLD; if (selectedMode) { Color color = Engine::GetProject()->Settings.PrimaryColor; ImGui::PushStyleColor(ImGuiCol_Button, { color.r, color.g, color.b, 1.0f }); } if (ImGui::Button(ICON_FA_GLOBE, ImVec2(30, 28))) { CurrentMode = ImGuizmo::MODE::WORLD; } UI::Tooltip("Global Transformation"); if (selectedMode) { ImGui::PopStyleColor(); } ImGui::SameLine(); selectedMode = CurrentMode == ImGuizmo::MODE::LOCAL; if (selectedMode) { Color color = Engine::GetProject()->Settings.PrimaryColor; ImGui::PushStyleColor(ImGuiCol_Button, { color.r, color.g, color.b, 1.0f }); } if (ImGui::Button(ICON_FA_CUBE, ImVec2(30, 28))) { CurrentMode = ImGuizmo::MODE::LOCAL; } UI::Tooltip("Local Transformation"); if (selectedMode) { ImGui::PopStyleColor(); } ImGui::SameLine(); ImGui::SameLine(); ImGui::PushItemWidth(75); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 6, 6 }); ImGui::DragFloat("##snapping", &CurrentSnapping.x, 0.01f, 0.0f, 100.0f); CurrentSnapping = { CurrentSnapping.x, CurrentSnapping.x, CurrentSnapping.z }; ImGui::PopStyleVar(); ImGui::PopItemWidth(); UI::Tooltip("Snapping"); ImGui::PopStyleVar(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); } ImGui::PopStyleVar(); ImGui::End(); int corner2 = 1; work_area_pos = ImGui::GetCurrentWindow()->Pos; // Instead of using viewport->Pos we use GetWorkPos() to avoid menu bars, if any! work_area_size = ImGui::GetCurrentWindow()->Size; window_pos = ImVec2((corner2 & 1) ? (work_area_pos.x + work_area_size.x - DISTANCE) : (work_area_pos.x + DISTANCE), (corner2 & 2) ? (work_area_pos.y + work_area_size.y - DISTANCE) : (work_area_pos.y + DISTANCE)); window_pos_pivot = ImVec2((corner2 & 1) ? 1.0f : 0.0f, (corner2 & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos + ImVec2(0, 40), ImGuiCond_Always, window_pos_pivot); ImGui::SetNextWindowViewport(viewport->ID); ImGui::SetNextWindowBgAlpha(0.35f); // Transparent background ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 32.0f); ImGui::SetNextWindowSize(ImVec2(16, ImGui::GetContentRegionAvail().y - DISTANCE * 2.0 - 40.0)); if (ImGui::Begin("Controls2", 0, window_flags)) { const auto& editorCam = virtualScene->m_EditorCamera; const float camSpeed = editorCam->Speed; const float maxSpeed = 50.0f; const float minSpeed = 0.05f; const float normalizedSpeed = glm::clamp((camSpeed / maxSpeed), 0.0f, 1.0f); ImVec2 start = ImGui::GetWindowPos() - ImVec2(0.0, 4.0); ImVec2 end = start + ImGui::GetWindowSize() - ImVec2(0, 16.0); ImVec2 startOffset = ImVec2(start.x, end.y - (normalizedSpeed * (ImGui::GetWindowHeight() - 20.0))); ImGui::GetWindowDrawList()->AddRectFilled(startOffset + ImVec2(0, 10.0), end + ImVec2(0.0, 20.0), IM_COL32(255, 255, 255, 180), 8.0f, ImDrawFlags_RoundCornersAll); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 100); ImGui::PopStyleVar(); } ImGui::PopStyleVar(); ImGui::End(); } void PrefabEditorWindow::RenderScene() { virtualScene->Update(Engine::GetTimestep()); Ref sceneRenderer = virtualScene->m_SceneRenderer; Ref editorCam = virtualScene->m_EditorCamera; editorCam->OnWindowResize(viewportFramebuffer->GetSize().x, viewportFramebuffer->GetSize().y); virtualScene->Draw(*viewportFramebuffer.get()); sceneRenderer->BeginRenderScene(editorCam->GetPerspective(), editorCam->GetTransform(), editorCam->Translation); sceneRenderer->RenderScene(*virtualScene, *viewportFramebuffer.get(), true); } void PrefabEditorWindow::DrawEntityTree(Nuake::Entity e) { ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{ 0.0f, 0.0f }); ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAllColumns; NameComponent& nameComponent = e.GetComponent(); std::string name = nameComponent.Name; ParentComponent& parent = e.GetComponent(); if (Selection.Type == EditorSelectionType::Entity && Selection.Entity == e) base_flags |= ImGuiTreeNodeFlags_Selected; ImGui::TableNextColumn(); // Write in normal font. // ImGui::PushFont(normalFont); // If has no childrens draw tree node leaf if (parent.Children.size() <= 0) { base_flags |= ImGuiTreeNodeFlags_Leaf; } if (nameComponent.IsPrefab && e.HasComponent()) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 255, 0, 255)); } else if (e.HasComponent()) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 120)); } //if (!m_IsRenaming && m_ShouldUnfoldEntityTree && Selection.Type == EditorSelectionType::Entity && e.GetScene()->EntityIsParent(Selection.Entity, e)) //{ // ImGui::SetNextItemOpen(true); //} auto cursorPos = ImGui::GetCursorPos(); ImGui::SetNextItemAllowOverlap(); bool open = ImGui::TreeNodeEx(name.c_str(), base_flags); if (isRenaming) { if (Selection.Type == EditorSelectionType::Entity && Selection.Entity == e) { ImGui::SetCursorPosY(cursorPos.y); ImGui::Indent(); ImGui::InputText("##renamingEntity", &name); ImGui::Unindent(); if (Input::IsKeyDown(Key::ENTER)) { nameComponent.Name = name; isRenaming = false; } } } bool isDragging = false; if (nameComponent.IsPrefab && e.HasComponent() || e.HasComponent()) { ImGui::PopStyleColor(); } else if (!isRenaming && ImGui::BeginDragDropSource()) { ImGui::SetDragDropPayload("ENTITYPrefab", (void*)&e, sizeof(Nuake::Entity)); ImGui::Text(name.c_str()); ImGui::EndDragDropSource(); } if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITYPrefab")) { Nuake::Entity payload_entity = *(const Nuake::Entity*)payload->Data; // Check if entity is already parent. ParentComponent& parentPayload = payload_entity.GetComponent(); if (!payload_entity.EntityContainsItself(payload_entity, e) && parentPayload.Parent != e && std::count(parent.Children.begin(), parent.Children.end(), payload_entity) == 0) { if (parentPayload.HasParent) { // Erase remove idiom. ParentComponent& childOfParent = parentPayload.Parent.GetComponent(); childOfParent.Children.erase(std::remove(childOfParent.Children.begin(), childOfParent.Children.end(), payload_entity), childOfParent.Children.end()); } parentPayload.Parent = e; parentPayload.HasParent = true; parent.Children.push_back(payload_entity); } } else if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("_NetScript")) { char* file = (char*)payload->Data; std::string fullPath = std::string(file, 512); std::string path = Nuake::FileSystem::AbsoluteToRelative(std::move(fullPath)); if (e.HasComponent()) { e.GetComponent().ScriptPath = path; } else { e.AddComponent().ScriptPath = path; } } ImGui::EndDragDropTarget(); } if (!isDragging && ImGui::IsItemHovered() && ImGui::IsMouseReleased(0)) { // We selected another another that we werent renaming if (Selection.Entity != e) { isRenaming = false; } Selection = EditorSelection(e); } if (!isDragging && (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) || Input::IsKeyPressed(Key::F2)) { isRenaming = true; } if (!isRenaming && Selection.Type == EditorSelectionType::Entity && Input::IsKeyPressed(Key::DELETE_KEY)) { QueueDeletion = Selection.Entity; } if (ImGui::BeginPopupContextItem()) { Selection = EditorSelection(e); Nuake::Entity entity = Selection.Entity; if (entity.HasComponent()) { // Moves the editor camera to the camera position and direction. if (ImGui::Selectable("Focus camera")) { Ref editorCam = virtualScene->m_EditorCamera; Vector3 camDirection = entity.GetComponent().CameraInstance->GetDirection(); camDirection.z *= -1.0f; editorCam->SetTransform(glm::inverse(entity.GetComponent().GetGlobalTransform())); } ImGui::Separator(); } if (ImGui::Selectable("Remove")) { QueueDeletion = e; } if (entity.GetComponent().HasParent && ImGui::Selectable("Move to root")) { auto& parentComp = Selection.Entity.GetComponent(); if (parentComp.HasParent) { auto& parentParentComp = parentComp.Parent.GetComponent(); parentParentComp.RemoveChildren(entity); parentComp.HasParent = false; } } if (ImGui::Selectable("Save entity as a new prefab")) { Ref newPrefab = Prefab::CreatePrefabFromEntity(Selection.Entity); std::string savePath = Nuake::FileDialog::SaveFile("*.prefab"); if (!String::EndsWith(savePath, ".prefab")) { savePath += ".prefab"; } if (!savePath.empty()) { newPrefab->SaveAs(savePath); Selection.Entity.AddComponent().PrefabInstance = newPrefab; } } ImGui::EndPopup(); } ImGui::TableNextColumn(); ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1.0), ""); ImGui::TableNextColumn(); { bool hasScript = e.HasComponent(); if (hasScript) { std::string scrollIcon = std::string(ICON_FA_SCROLL) + "##" + name; ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); if (ImGui::Button(scrollIcon.c_str(), { 40, 0 })) { auto& scriptComponent = e.GetComponent(); if (!scriptComponent.ScriptPath.empty() && FileSystem::FileExists(scriptComponent.ScriptPath)) { OS::OpenIn(FileSystem::RelativeToAbsolute(scriptComponent.ScriptPath)); } } ImGui::PopStyleColor(); } } ImGui::TableNextColumn(); { bool& isVisible = e.GetComponent().Visible; std::string visibilityIcon = isVisible ? ICON_FA_EYE : ICON_FA_EYE_SLASH; ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); if (ImGui::Button(visibilityIcon.c_str(), { 40, 0 })) { isVisible = !isVisible; } ImGui::PopStyleColor(); } if (open) { // Caching list to prevent deletion while iterating. std::vector childrens = parent.Children; for (auto& c : childrens) DrawEntityTree(c); ImGui::TreePop(); } ImGui::PopStyleVar(); //ImGui::PopFont(); } void PrefabEditorWindow::Save() { Ref newPrefab = Prefab::CreatePrefabFromEntity(prefab->Root); newPrefab->Path = prefab->Path; newPrefab->SaveAs(newPrefab->Path); }