// // Created by sfd on 25-5-25. // #include "EditorLayer.h" #include #include #include #include #include #include #include #include "glm/gtx/matrix_decompose.hpp" namespace Hazel { extern const std::filesystem::path g_AssetPath; EditorLayer::EditorLayer() : Layer("HazelEditor"), m_CameraController((float)Application::Get().GetWindow().GetWidth() / (float)Application::Get().GetWindow().GetHeight()) { } void EditorLayer::OnAttach() { FrameBufferSpecification spec; spec.Width = Application::Get().GetWindow().GetWidth(); spec.Height = Application::Get().GetWindow().GetHeight(); spec.Attachments = { FrameBufferTextureFormat::RGBA8, FrameBufferTextureFormat::RED_INTEGER,FrameBufferTextureFormat::DEPTH }; m_FrameBuffer = FrameBuffer::Create(spec); m_ViewPortSize = { spec.Width, spec.Height }; m_LogoTexture = Texture2D::Create("assets/textures/iceLogo.png"); m_CheckerBoardTexture = Texture2D::Create("assets/textures/Checkerboard.png"); m_IconPlay = Texture2D::Create("Resources/Icons/PlayButton.png"); m_IconStop = Texture2D::Create("Resources/Icons/PauseButton.png"); m_IconSimulate = Texture2D::Create("Resources/Icons/SimulateButton.png"); m_EditorScene = CreateRef(); m_EditorScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); m_ActiveScene = m_EditorScene; m_EditorCamera = EditorCamera(45.0f, 1.667f, 0.1f, 1000.0f); m_SceneHierachyPanel.SetContext(m_ActiveScene); m_GizmoType = ImGuizmo::OPERATION::TRANSLATE; } void EditorLayer::OnDetech() { } void EditorLayer::OnUpdate(TimeStep& ts) { // reset Renderer Draw Stats Renderer2D::ResetStats(); if (const auto& spec = m_FrameBuffer->GetSpecification(); spec.Width != m_ViewPortSize.x || spec.Height != m_ViewPortSize.y) { m_FrameBuffer->Resize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); m_CameraController.OnResize(m_ViewPortSize.x, m_ViewPortSize.y); m_EditorCamera.SetViewPortSize(m_ViewPortSize.x, m_ViewPortSize.y); m_ActiveScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); } // update camera // update Renderer m_FrameBuffer->Bind(); RendererCommand::SetClearColor(m_BackgroundColor); RendererCommand::Clear(); m_FrameBuffer->ClearAttachment(1, -1); switch (m_SceneState) { case SceneState::Play: m_ActiveScene->OnUpdateRuntime(ts); break; case SceneState::Edit: if (m_ViewportFocused) m_CameraController.OnUpdate(ts); m_EditorCamera.OnUpdate(ts); m_ActiveScene->OnUpdateEditor(ts, m_EditorCamera); break; case SceneState::Simulate: m_EditorCamera.OnUpdate(ts); m_ActiveScene->OnUpdateSimulation(ts, m_EditorCamera); break; } auto [mx, my] = ImGui::GetMousePos(); mx -= m_ViewPortBounds[0].x; my -= m_ViewPortBounds[0].y; const glm::vec2 viewPortSize = m_ViewPortBounds[1] - m_ViewPortBounds[0]; my = viewPortSize.y - my; const int mouseX = (int)mx; const int mouseY = (int)my; if (mouseX >= 0 && mouseY >= 0 && mouseX < m_ViewPortSize.x && mouseY < m_ViewPortSize.y) { int pixelData = m_FrameBuffer->ReadPixel(1, mouseX, mouseY); m_HoveredEntity = pixelData == -1 ? Entity{} : Entity{ (entt::entity)pixelData, m_ActiveScene.get()}; } // Renderer2D::DrawQuad({0, 0.5f}, {1.0f, 1.0f}, {0.2f, 0.3f, 0.8f, 1.0f}); // Renderer2D::DrawQuad({0, -0.5f}, {1.0f, 1.0f}, {0.8f, 0.2f, 0.2f, 1.0f}); // Renderer2D::DrawQuad({-1, 0}, {1, 1}, m_LogoTexture); // Renderer2D::DrawQuad({1, 0}, {1, 1}, m_CheckerBoardTexture); OnOverLayRender(); // Renderer2D::EndScene(); m_FrameBuffer->UnBind(); } void EditorLayer::OnOverLayRender() { if (m_SceneState == SceneState::Play) { Entity camera = m_ActiveScene->GetPrimaryCameraEntity(); if (camera) Renderer2D::BeginScene(camera.GetComponent().Camera, camera.GetComponent().GetTransform()); else Renderer2D::BeginScene(m_EditorCamera); }else { Renderer2D::BeginScene(m_EditorCamera); } if (m_ShowPhysicsColliders) { // box collider auto bcview = m_ActiveScene->GetAllEntitiesWith(); for (auto entity : bcview) { auto [tc, bc2D] = bcview.get(entity); glm::mat4 model = glm::translate(glm::mat4(1.0f), tc.Translation) * glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), tc.Scale); glm::vec4 worldOffset = model * glm::vec4(bc2D.Offset.x, bc2D.Offset.y, 0.0f, 1.0f); // glm::vec3 translation = glm::vec3(worldOffset) + glm::vec3(0.0f, 0.0f, 0.001f); glm::vec3 scale = tc.Scale * glm::vec3(bc2D.Size * 2.0f, 1.0f); glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(worldOffset)) * glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), scale); Renderer2D::DrawRect(transform, glm::vec4(0.2f, 1.0f, 0.2f, 1.0f)); } auto ccview = m_ActiveScene->GetAllEntitiesWith(); for (auto entity : ccview) { auto [tc, cc2D] = ccview.get(entity); glm::mat4 model = glm::translate(glm::mat4(1.0f), tc.Translation) * glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), tc.Scale); glm::vec4 worldOffset = model * glm::vec4(cc2D.Offset.x, cc2D.Offset.y, 0.0f, 1.0f); // glm::vec3 translation = glm::vec3(worldOffset) + glm::vec3(0.0f, 0.0f, 0.001f); glm::vec3 scale = tc.Scale * glm::vec3(cc2D.Radius * 2.05f); glm::mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3(worldOffset)) * glm::rotate(glm::mat4(1.0f), tc.Rotation.z, glm::vec3(0.0f, 0.0f, 1.0f)) * glm::scale(glm::mat4(1.0f), scale); Renderer2D::DrawCircle(transform, glm::vec4(0.2, 1.0f, 0.2f, 1.0f), Renderer2D::GetLineWidth() * 0.015f); } } Renderer2D::EndScene(); } void EditorLayer::OnImGuiRender() { static bool showDockspace = true; if (showDockspace) { static bool dockspaceOpen = true; static bool opt_fullscreen = true; static bool opt_padding = false; static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None; // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, // because it would be confusing to have two docking targets within each others. ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; if (opt_fullscreen) { const ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->WorkPos); ImGui::SetNextWindowSize(viewport->WorkSize); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; } else { dockspace_flags &= ~ImGuiDockNodeFlags_PassthruCentralNode; } // When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background // and handle the pass-thru hole, so we ask Begin() to not render a background. if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) window_flags |= ImGuiWindowFlags_NoBackground; // Important: note that we proceed even if Begin() returns false (aka window is collapsed). // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, // all active windows docked into it will lose their parent and become undocked. // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. if (!opt_padding) ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::Begin("DockSpace", &dockspaceOpen, window_flags); if (!opt_padding) ImGui::PopStyleVar(); if (opt_fullscreen) ImGui::PopStyleVar(2); // Submit the DockSpace ImGuiIO& io = ImGui::GetIO(); ImGuiStyle& style = ImGui::GetStyle(); style.WindowMinSize.x = 364.0f; if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) { ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags); } if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu("Options")) { // Disabling fullscreen would allow the window to be moved to the front of other windows, // which we can't undo at the moment without finer window depth/z control. // ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen); // ImGui::MenuItem("Padding", NULL, &opt_padding); // ImGui::Separator(); if (ImGui::MenuItem("New", "Ctrl+N")) NewScene(); if (ImGui::MenuItem("Open...", "Ctrl+O")) OpenScene(); if (ImGui::MenuItem("Save", "Ctrl+S", false, m_ActiveScene ? true : false)) SaveScene(); if (ImGui::MenuItem("Save As...", "Ctrl+Shift+S")) SaveSceneAs(); ImGui::Separator(); if (ImGui::MenuItem("Exit")) { Hazel::Application::Get().Close(); } ImGui::EndMenu(); } ImGui::EndMenuBar(); } // Scene Hierachy Panel m_SceneHierachyPanel.OnImGuiRender(); m_ContentBroswerPanel.OnImGuiRender(); // Render Status { ImGui::Begin("Render Status"); auto stats = Renderer2D::GetStats(); ImGui::Text("Renderer BatchInfo: "); ImGui::Text("Draw Calls: %d", stats.DrawCalls); ImGui::Text("Quads: %d", stats.QuadCount); ImGui::Text("Vertices: %d", stats.GetTotalVertexCount()); ImGui::Text("Indices: %d", stats.GetTotalIndexCount()); ImGui::Separator(); ImGui::Text("viewPortSize: (%.2f, %.2f)", m_ViewPortSize.x, m_ViewPortSize.y); ImGui::Separator(); auto editorCameraPos = m_EditorCamera.GetPosition(); ImGui::Text("Editor Camera(%.3f, %.3f, %.3f)", editorCameraPos.x, editorCameraPos.y, editorCameraPos.z); ImGui::Text("Editor Camera Distance: %f", m_EditorCamera.GetDistance()); float lineWidth = Renderer2D::GetLineWidth(); if (ImGui::DragFloat("LineWidth", &lineWidth, 0.01f, 0.0f, 10.0f)) { Renderer2D::SetLineWidth(lineWidth); } // std::string name = "none"; // if (m_HoveredEntity) // name = m_HoveredEntity.GetComponent().Tag; // ImGui::Text("Hovered Entity: %s", name.c_str()); ImGui::Separator(); ImGui::Checkbox("Show Colliders", &m_ShowPhysicsColliders); ImGui::End(); } // ViewPort { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0.0f, 0.0f}); ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoCollapse); auto viewportOffet = ImGui::GetCursorPos(); m_ViewportFocused = ImGui::IsWindowFocused(); m_ViewportHovered = ImGui::IsWindowHovered(); ImVec2 viewPortPanelSize = ImGui::GetContentRegionAvail(); if (m_ViewPortSize != *reinterpret_cast(&viewPortPanelSize)) { m_ViewPortSize = {viewPortPanelSize.x, viewPortPanelSize.y}; } // ImGui::Text("Viewport: (%.2f, %.2f)", viewPortPanelSize.x, viewPortPanelSize.y); ImGui::Image(m_FrameBuffer->GetColorAttachmentID(), {m_ViewPortSize.x, m_ViewPortSize.y}, {0, 1}, {1, 0}); const ImVec2 &viewPortMin = ImGui::GetItemRectMin(); // left-top pos const ImVec2 &viewPortMax = ImGui::GetItemRectMax(); // right-buttom pos if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("CONTENT_BROSWER_ITEM")) { const wchar_t* path = (const wchar_t*)payload->Data; OpenScene(std::filesystem::path(g_AssetPath) / path); } ImGui::EndDragDropTarget(); } auto windowsSize = ImGui::GetWindowSize(); ImVec2 minBound = ImGui::GetWindowPos(); minBound.x += viewportOffet.x; minBound.y += viewportOffet.y + m_ViewPortSize.y - windowsSize.y; ImVec2 maxBound = {minBound.x + windowsSize.x, minBound.y + windowsSize.y}; m_ViewPortBounds[0] = {minBound.x, minBound.y}; m_ViewPortBounds[1] = {maxBound.x, maxBound.y}; // ImGuizmo Entity selectedEntity = m_SceneHierachyPanel.GetSelectedEntity(); if (selectedEntity) { ImGuizmo::SetOrthographic(false); ImGuizmo::SetDrawlist(); ImGuizmo::SetRect(viewPortMin.x, viewPortMin.y, viewPortMax.x - viewPortMin.x, viewPortMax.y - viewPortMin.y); if (m_GizmoType != -1) { glm::mat4 cameraProjection; glm::mat4 cameraView; if (m_SceneState == SceneState::Play) { auto cameraEntity = m_ActiveScene->GetPrimaryCameraEntity(); const auto& camera = cameraEntity.GetComponent().Camera; cameraProjection = camera.GetProjection(); cameraView = glm::inverse(cameraEntity.GetComponent().GetTransform()); }else { cameraProjection = m_EditorCamera.GetProjection(); cameraView = m_EditorCamera.GetViewMatrix(); } auto& tc = selectedEntity.GetComponent(); glm::mat4 transform = tc.GetTransform(); bool snap = SDL_GetModState() & SDL_KMOD_CTRL; float snapValue = 0.5f; if (m_GizmoType == ImGuizmo::OPERATION::TRANSLATE) snapValue = 0.25f; else if (m_GizmoType == ImGuizmo::OPERATION::ROTATE) snapValue = 15.0f; else if (m_GizmoType == ImGuizmo::OPERATION::SCALE) snapValue = 0.25f; float snapValues[3] = {snapValue, snapValue, snapValue}; if (ImGuizmo::Manipulate(glm::value_ptr(cameraView), glm::value_ptr(cameraProjection), static_cast(m_GizmoType), ImGuizmo::WORLD, glm::value_ptr(transform), nullptr, snap ? snapValues : nullptr) && !Input::IsKeyPressed(SDL_SCANCODE_LALT)) { if (ImGuizmo::IsUsing()) { glm::vec3 translation, rotation, scale; Hazel::Math::DecomposeTransform(transform, translation, rotation, scale); glm::vec3 deltaRotation = rotation - tc.Rotation; tc.Translation = translation; tc.Rotation += deltaRotation; tc.Scale = scale; } } } } ImGui::End(); ImGui::PopStyleVar(); } UI_ToolBar(); ImGui::End(); } } void EditorLayer::SaveSceneAs() const { std::string filepath = FileDiaglogs::SaveFile("Hazel Scene (*.scene,*.yaml)\0*.scene;*.yaml\0*\0*.*\0\0"); if (!filepath.empty()) { SerializeScene(m_ActiveScene, filepath); } } void EditorLayer::OpenScene() { std::string filepath = FileDiaglogs::OpenFile("Scene(*.scene, *.yaml)\0*.scene;*.yaml\0All files\0*.*\0\0"); if (!filepath.empty()) OpenScene(filepath); } void EditorLayer::OpenScene(const std::filesystem::path& scenePath) { if (m_SceneState != SceneState::Edit) OnSceneStop(); Ref newScene = CreateRef(); SceneSerializer serializer(newScene); if (serializer.Deserialize(scenePath.string())) { m_EditorScene = newScene; m_EditorScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); m_ActiveScene = m_EditorScene; m_SceneHierachyPanel.SetContext(m_ActiveScene); m_EditorScenePath = scenePath; } } void EditorLayer::NewScene() { m_ActiveScene = CreateRef(); m_ActiveScene->OnViewportResize((uint32_t)m_ViewPortSize.x, (uint32_t)m_ViewPortSize.y); m_SceneHierachyPanel.SetContext(m_ActiveScene); m_EditorScenePath = std::filesystem::path(); } void EditorLayer::SaveScene() const { if (!m_EditorScenePath.empty()) { SerializeScene(m_ActiveScene, m_EditorScenePath); } } void EditorLayer::SerializeScene(Ref scene, const std::filesystem::path& scenePath) const { const SceneSerializer serializer(scene); serializer.Serialize(scenePath.string()); } void EditorLayer::OnDuplicateEntity() { if (m_SceneState != SceneState::Edit) return; Entity selectedEntity = m_SceneHierachyPanel.GetSelectedEntity(); if (selectedEntity) m_EditorScene->DuplicateEntity(selectedEntity); } void EditorLayer::OnScenePlay() { if (m_SceneState == SceneState::Simulate) OnSceneStop(); // SaveScene(); m_SceneState = SceneState::Play; m_ActiveScene = Scene::Copy(m_EditorScene); m_ActiveScene->OnRuntimeStart(); m_SceneHierachyPanel.SetContext(m_ActiveScene); } void EditorLayer::OnSceneStop() { if (m_SceneState != SceneState::Play && m_SceneState != SceneState::Simulate) { HZ_CORE_WARN("Scene is not playing or simulating!"); return; } if (m_SceneState == SceneState::Play) m_ActiveScene->OnRuntimeStop(); else if (m_SceneState == SceneState::Simulate) m_ActiveScene->OnSimulationStop(); m_SceneState = SceneState::Edit; m_ActiveScene = m_EditorScene; m_SceneHierachyPanel.SetContext(m_ActiveScene); } void EditorLayer::OnSceneSimulation() { if (m_SceneState == SceneState::Simulate) OnSceneStop(); // SaveScene(); m_SceneState = SceneState::Simulate; m_ActiveScene = Scene::Copy(m_EditorScene); m_ActiveScene->OnSimulationStart(); m_SceneHierachyPanel.SetContext(m_ActiveScene); } void EditorLayer::ChangeOptMode(unsigned int mode) { if (m_ViewportHovered) m_GizmoType = mode; } void EditorLayer::OnEvent(SDL_Event& e) { if (m_ViewportFocused) { m_CameraController.OnEvent(e); m_EditorCamera.OnEvent(e); } if (e.button.clicks && m_ViewportHovered && !ImGuizmo::IsOver() && Input::IsMouseButtonPressed(SDL_BUTTON_LEFT) && !Input::IsKeyPressed(SDL_SCANCODE_LALT)) m_SceneHierachyPanel.SetSelectedEntity(m_HoveredEntity); #define SHORTCUT_EXIT (SDL_KMOD_CTRL + SDLK_W) #define SHORTCUT_NEW (SDL_KMOD_CTRL + SDLK_N) #define SHORTCUT_OPEN (SDL_KMOD_CTRL + SDLK_O) #define SHORTCUT_SAVE (SDL_KMOD_CTRL + SDLK_S) #define SHORTCUT_SAVE_AS (SDL_KMOD_CTRL + SDL_KMOD_SHIFT + SDLK_S) #define SHORTCUT_CTRL_D (SDL_KMOD_CTRL + SDLK_D) if (e.key.down && e.key.repeat == 0) { const auto mod = SDL_GetModState(); const auto ctrl = (mod & SDL_KMOD_CTRL) ? SDL_KMOD_CTRL : 0; const auto shift = (mod & SDL_KMOD_SHIFT) ? SDL_KMOD_SHIFT : 0; switch (ctrl + shift + e.key.key) { case SHORTCUT_EXIT: Application::Get().Close(); break; case SHORTCUT_NEW: NewScene(); break; case SHORTCUT_OPEN: OpenScene(); break; case SHORTCUT_SAVE: SaveScene(); break; case SHORTCUT_SAVE_AS: SaveSceneAs(); break; case SHORTCUT_CTRL_D: OnDuplicateEntity(); break; // GIZMO case SDLK_Q: ChangeOptMode(-1); break; case SDLK_W: ChangeOptMode(ImGuizmo::OPERATION::TRANSLATE); break; case SDLK_E: ChangeOptMode(ImGuizmo::OPERATION::SCALE); break; case SDLK_R: ChangeOptMode(ImGuizmo::OPERATION::ROTATE); break; default: break; } } } void EditorLayer::UI_ToolBar() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 2)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(0, 0)); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); auto& colors = ImGui::GetStyle().Colors; auto& buttonHovered = colors[ImGuiCol_ButtonHovered]; ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(buttonHovered.x, buttonHovered.y, buttonHovered.z, 0.5f)); auto& buttonActive = colors[ImGuiCol_ButtonActive]; ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(buttonActive.x, buttonActive.y, buttonActive.z, 0.5f)); ImGui::Begin("##ToolBar", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); // ImGui::Begin("##ToolBar", nullptr); float size = ImGui::GetWindowHeight() - 10.0f; ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - size * 0.5f); ImGui::SetCursorPosX(ImGui::GetWindowContentRegionMax().x * 0.5f - (size * 0.5f)); Ref icon = (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Simulate)? m_IconPlay : m_IconStop; if (ImGui::ImageButton("toolbar-play-edit", icon->GetRendererID(), ImVec2{size, size}, ImVec2{0, 0}, ImVec2{1, 1})) { if (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Simulate) OnScenePlay(); else if (m_SceneState == SceneState::Play) OnSceneStop(); } ImGui::SameLine(); { Ref icon = (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Play)? m_IconSimulate : m_IconStop; if (ImGui::ImageButton("toolbar-simulate", icon->GetRendererID(), ImVec2{size, size}, ImVec2{0, 0}, ImVec2{1, 1})) { if (m_SceneState == SceneState::Edit || m_SceneState == SceneState::Play) OnSceneSimulation(); else if (m_SceneState == SceneState::Simulate) OnSceneStop(); } } ImGui::PopStyleVar(2); ImGui::PopStyleColor(3); ImGui::End(); } }