From dc5b58d7d39b081377d9596ab49fd29e897986f2b6491ce38a8739de02ddab18 Mon Sep 17 00:00:00 2001 From: Mexpert_PRO Date: Sun, 18 Jan 2026 14:59:44 +0100 Subject: [PATCH] added editor WIP --- TSE_Base/src/Debug.cpp | 19 + TSE_Base/src/Debug.hpp | 9 + TSE_Core/src/BehaviourScripts/Camera.cpp | 4 +- TSE_Editor/CMakeLists.txt | 63 ++ TSE_Editor/src/BehaviourScriptRegistry.hpp | 53 + TSE_Editor/src/EditorSubsystem.cpp | 40 + TSE_Editor/src/EditorSubsystem.hpp | 29 + TSE_Editor/src/UI/ElementDrawer.cpp | 981 ++++++++++++++++++ TSE_Editor/src/UI/ElementDrawer.hpp | 89 ++ TSE_Editor/src/UI/ViewportController.cpp | 213 ++++ TSE_Editor/src/UI/ViewportController.hpp | 41 + TSE_Editor/src/UI/base/GuiWindow.cpp | 35 + TSE_Editor/src/UI/base/GuiWindow.h | 27 + TSE_Editor/src/UI/base/IGUIElement.hpp | 16 + TSE_Editor/src/UI/windows/CameraView.cpp | 29 + TSE_Editor/src/UI/windows/CameraView.hpp | 18 + TSE_Editor/src/UI/windows/ConsolView.cpp | 117 +++ TSE_Editor/src/UI/windows/ConsolView.hpp | 65 ++ TSE_Editor/src/UI/windows/DebugView.cpp | 24 + TSE_Editor/src/UI/windows/DebugView.hpp | 23 + TSE_Editor/src/UI/windows/HirearchieView.cpp | 392 +++++++ TSE_Editor/src/UI/windows/HirearchieView.hpp | 47 + TSE_Editor/src/UI/windows/PropertiesView.cpp | 48 + TSE_Editor/src/UI/windows/PropertiesView.hpp | 24 + TSE_Editor/src/UI/windows/SceneView.cpp | 21 + TSE_Editor/src/UI/windows/SceneView.hpp | 16 + TSE_GlfwOpenGlImpl/src/buffer/FrameBuffer.cpp | 6 + 27 files changed, 2448 insertions(+), 1 deletion(-) create mode 100644 TSE_Editor/CMakeLists.txt create mode 100644 TSE_Editor/src/BehaviourScriptRegistry.hpp create mode 100644 TSE_Editor/src/EditorSubsystem.cpp create mode 100644 TSE_Editor/src/EditorSubsystem.hpp create mode 100644 TSE_Editor/src/UI/ElementDrawer.cpp create mode 100644 TSE_Editor/src/UI/ElementDrawer.hpp create mode 100644 TSE_Editor/src/UI/ViewportController.cpp create mode 100644 TSE_Editor/src/UI/ViewportController.hpp create mode 100644 TSE_Editor/src/UI/base/GuiWindow.cpp create mode 100644 TSE_Editor/src/UI/base/GuiWindow.h create mode 100644 TSE_Editor/src/UI/base/IGUIElement.hpp create mode 100644 TSE_Editor/src/UI/windows/CameraView.cpp create mode 100644 TSE_Editor/src/UI/windows/CameraView.hpp create mode 100644 TSE_Editor/src/UI/windows/ConsolView.cpp create mode 100644 TSE_Editor/src/UI/windows/ConsolView.hpp create mode 100644 TSE_Editor/src/UI/windows/DebugView.cpp create mode 100644 TSE_Editor/src/UI/windows/DebugView.hpp create mode 100644 TSE_Editor/src/UI/windows/HirearchieView.cpp create mode 100644 TSE_Editor/src/UI/windows/HirearchieView.hpp create mode 100644 TSE_Editor/src/UI/windows/PropertiesView.cpp create mode 100644 TSE_Editor/src/UI/windows/PropertiesView.hpp create mode 100644 TSE_Editor/src/UI/windows/SceneView.cpp create mode 100644 TSE_Editor/src/UI/windows/SceneView.hpp diff --git a/TSE_Base/src/Debug.cpp b/TSE_Base/src/Debug.cpp index 7ff2db3..cb9b3bb 100644 --- a/TSE_Base/src/Debug.cpp +++ b/TSE_Base/src/Debug.cpp @@ -7,6 +7,8 @@ std::ofstream TSE::Debug::logFile; const std::string fileName = "log.txt"; const std::string fileNameOld = "lastlog.txt"; +std::vector TSE::Debug::onLogCallbacks = std::vector(); + int luaPrintRedirect(lua_State* L) { int n = lua_gettop(L); @@ -61,18 +63,30 @@ void TSE::Debug::Log(string msg) { std::cout << msg << std::endl; logFile << msg << std::endl; + for(auto fnc : onLogCallbacks) + { + fnc(msg, LogEntryType::Log); + } } void TSE::Debug::Error(string msg) { std::cerr << "[Error] " << msg << std::endl; logFile << "[Error] " << msg << std::endl; + for(auto fnc : onLogCallbacks) + { + fnc("[Error] " + msg, LogEntryType::Error); + } } void TSE::Debug::Warning(string msg) { std::cout << "[Warning] " << msg << std::endl; logFile << "[Warning] " << msg << std::endl; + for(auto fnc : onLogCallbacks) + { + fnc("[Warning] " + msg, LogEntryType::Warning); + } } void TSE::Debug::Close() @@ -80,3 +94,8 @@ void TSE::Debug::Close() logFile.flush(); logFile.close(); } + +void TSE::Debug::AddCallback(void (*func)(const std::string &, const LogEntryType &)) +{ + onLogCallbacks.push_back(func); +} diff --git a/TSE_Base/src/Debug.hpp b/TSE_Base/src/Debug.hpp index f88cd59..bc9a401 100644 --- a/TSE_Base/src/Debug.hpp +++ b/TSE_Base/src/Debug.hpp @@ -9,6 +9,13 @@ namespace TSE { + enum LogEntryType + { + Log, + Error, + Warning, + }; + class Debug { public: @@ -25,9 +32,11 @@ namespace TSE static void Warning(string msg); /// @brief closes the log file static void Close(); + static void AddCallback(void(*func)(const std::string&, const LogEntryType&)); private: static std::ofstream logFile; + static std::vector onLogCallbacks; }; } // namespace TSE diff --git a/TSE_Core/src/BehaviourScripts/Camera.cpp b/TSE_Core/src/BehaviourScripts/Camera.cpp index b3db71c..d7e93c8 100644 --- a/TSE_Core/src/BehaviourScripts/Camera.cpp +++ b/TSE_Core/src/BehaviourScripts/Camera.cpp @@ -132,7 +132,9 @@ void TSE::Camera::OnUpdate() { mainCamera = this; } - IRenderer::camerasToRenderWith.push_back(this); + + if(rt != nullptr) + IRenderer::camerasToRenderWith.push_back(this); } void TSE::Camera::Start() diff --git a/TSE_Editor/CMakeLists.txt b/TSE_Editor/CMakeLists.txt new file mode 100644 index 0000000..7e9b77e --- /dev/null +++ b/TSE_Editor/CMakeLists.txt @@ -0,0 +1,63 @@ +#cmake version +cmake_minimum_required(VERSION 3.31) + +#project name +project(TSE_Editor) + +#cpp settings +find_program(CLANG_C NAMES clang) +find_program(CLANG_CXX NAMES clang++) + +if(CLANG_C AND CLANG_CXX) + message(STATUS "foung Clang, using as Compiler") + set(CMAKE_C_COMPILER ${CLANG_C} CACHE STRING "C Compiler" FORCE) + set(CMAKE_CXX_COMPILER ${CLANG_CXX} CACHE STRING "C++ Compiler" FORCE) +else() + message(STATUS "Clang not found, using Standard-Compiler") +endif() +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +#project output settings +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib/Debug") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib/Release") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/lib") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${PROJECT_SOURCE_DIR}/lib/Debug") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${PROJECT_SOURCE_DIR}/lib/Release") + +#source files +file(GLOB CPP_SOURCE_TSE + "${PROJECT_SOURCE_DIR}/src/*.cpp" + "${PROJECT_SOURCE_DIR}/src/*/*.cpp" + "${PROJECT_SOURCE_DIR}/src/*/*/*.cpp" + "${PROJECT_SOURCE_DIR}/src/*/*/*/*.cpp" + "${PROJECT_SOURCE_DIR}/src/*.c" + "${PROJECT_SOURCE_DIR}/src/*/*.c" + "${PROJECT_SOURCE_DIR}/src/*/*/*.c" + "${PROJECT_SOURCE_DIR}/src/*/*/*/*.c" +) + +#includes +include_directories(${PROJECT_SOURCE_DIR}/src) +include_directories(${PROJECT_SOURCE_DIR}/include) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_Base/src) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_Base/include) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_Math/src) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_Core/src) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_Core/include) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_GlfwImpl/src) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_GlfwImpl/include) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_GlfwOpenGlImpl/src) +include_directories(${PROJECT_SOURCE_DIR}/../TSE_GlfwOpenGlImpl/include) + +#project def +if(Lib) + add_library(TSE_Editor SHARED ${CPP_SOURCE_TSE}) +else() + add_library(TSE_Editor STATIC ${CPP_SOURCE_TSE}) +endif() + +#flags +target_compile_options(TSE_Editor PRIVATE -march=native) + diff --git a/TSE_Editor/src/BehaviourScriptRegistry.hpp b/TSE_Editor/src/BehaviourScriptRegistry.hpp new file mode 100644 index 0000000..509144d --- /dev/null +++ b/TSE_Editor/src/BehaviourScriptRegistry.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include "Types.hpp" +#include +#include "elements/BehaviourScript.hpp" + +namespace TSE::EDITOR +{ + class BehaviourScriptRegistry + { + private: + using BehaviourScriptCtor = BehaviourScript* (*)(); + + inline static std::unordered_map registeredScripts = {}; + + public: + inline static void RegisterBehaviourScript(string name, BehaviourScriptCtor ctor ) + { + registeredScripts[name] = ctor; + } + inline static BehaviourScript* CreateBehaviourScript(string name) + { + auto it = registeredScripts.find(name); + if (it != registeredScripts.end()) + return it->second(); + return nullptr; + } + inline static void UnregisterBehaviourScript(string name) + { + registeredScripts.erase(name); + } + inline static int GetScriptCount() + { + return registeredScripts.size(); + } + inline static string GetScriptNameAt(int i) + { + if (i < 0 || i >= static_cast(registeredScripts.size())) + return ""; + + auto it = registeredScripts.begin(); + std::advance(it, i); + return it->first; + } + + static const auto& GetRegistry() + { + return registeredScripts; + } + }; +} // namespace TSE::EDITOR + diff --git a/TSE_Editor/src/EditorSubsystem.cpp b/TSE_Editor/src/EditorSubsystem.cpp new file mode 100644 index 0000000..b243f7f --- /dev/null +++ b/TSE_Editor/src/EditorSubsystem.cpp @@ -0,0 +1,40 @@ +#include "EditorSubsystem.hpp" + +#include "BehaviourScriptRegistry.hpp" + +TSE::EDITOR::EditorSubsystem::EditorSubsystem() : sv(nullptr), rt({10,10}), editorLayer("") +{ + rt = TSE::GLFW::RenderTexture({100,100}); + sv = SceneView(&rt); + + controller.AddGuiElement("Scene", &sv); + controller.AddGuiElement("Consol", &cv); + controller.AddGuiElement("Hirearchie", &hv); + controller.AddGuiElement("Properties", &pv); + controller.AddGuiElement("Debug", &dv); + controller.AddGuiElement("Camera", &camv); + + BehaviourScriptRegistry::RegisterBehaviourScript("Image", []() -> BehaviourScript* {return new Image();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Image Animation", []() -> BehaviourScript* {return new ImageAnimation();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Mesh Container", []() -> BehaviourScript* {return new MeshContainer();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Rect Base", []() -> BehaviourScript* {return new RectBase();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Renderable", []() -> BehaviourScript* {return new Renderable();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Particle System", []() -> BehaviourScript* {return new ParticleSystem();}); + BehaviourScriptRegistry::RegisterBehaviourScript("Camera", []() -> BehaviourScript* {return new Camera();}); + + #pragma region camerastuff + + Transformable* editorCamera = new Transformable(".EditorCamera"); + + Camera* editorCam = new Camera(); + editorCam->SetRenderTarget(&rt); + editorCamera->AddBehaviourScript(editorCam); + + // basicCameraControls controls = basicCameraControls(); + // editorCamera->AddBehaviourScript(&controls); + + editorLayer = Layer(".editor"); + editorLayer.AddTransformable(editorCamera); + + #pragma endregion +} \ No newline at end of file diff --git a/TSE_Editor/src/EditorSubsystem.hpp b/TSE_Editor/src/EditorSubsystem.hpp new file mode 100644 index 0000000..5a8e204 --- /dev/null +++ b/TSE_Editor/src/EditorSubsystem.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "UI/ViewportController.hpp" +#include "UI/windows/CameraView.hpp" +#include "UI/windows/ConsolView.hpp" +#include "UI/windows/DebugView.hpp" +#include "UI/windows/HirearchieView.hpp" +#include "UI/windows/PropertiesView.hpp" +#include "UI/windows/SceneView.hpp" +#include "RenderTexture.hpp" + +namespace TSE::EDITOR +{ + class EditorSubsystem + { + public: + ViewportController controller; + ConsolView cv; + DebugView dv; + HirearchieView hv = HirearchieView(nullptr); + PropertiesView pv; + CameraView camv; + TSE::GLFW::RenderTexture rt; + SceneView sv; + Layer editorLayer; + + EditorSubsystem(); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/ElementDrawer.cpp b/TSE_Editor/src/UI/ElementDrawer.cpp new file mode 100644 index 0000000..32bf2fb --- /dev/null +++ b/TSE_Editor/src/UI/ElementDrawer.cpp @@ -0,0 +1,981 @@ +#include "ElementDrawer.hpp" +#include "BehaviourScriptRegistry.hpp" +#include "elements/ShaderRegistry.hpp" +#include "BehaviourScripts/Camera.hpp" +#include + +namespace TSE::EDITOR +{ + #pragma region helper + bool InputText(const char* label, std::string& str, size_t bufferSize = 256) { + std::vector buffer(bufferSize); + strncpy(buffer.data(), str.c_str(), bufferSize); + buffer[bufferSize - 1] = '\0'; + + bool changed = ImGui::InputText(label, buffer.data(), bufferSize); + if (changed) { + str = std::string(buffer.data()); + } + return changed; + } + void BeginList(std::string listName) + { + ImGui::BeginChild(listName.c_str(), {0,150}, ImGuiChildFlags_Borders); + } + bool ListAddBtn(std::string label = "Add") + { + float available_width = ImGui::GetContentRegionAvail().x; + return ImGui::Button(label.c_str(),{available_width, 0}); + } + void BeginListItem(std::string name, float customHeight = 20) + { + float available_width = ImGui::GetContentRegionAvail().x; + ImGui::BeginChild(name.c_str(),{available_width - 30, customHeight}); + } + void EndListItem() + { + ImGui::EndChild(); + } + bool ListItemXBotton(std::string id) + { + ImGui::SameLine(); + return ImGui::Button(id.c_str(), {20,0}); + } + void EndList() + { + ImGui::EndChild(); + } + #pragma endregion + + void ElementDrawer::DrawAddDropdown(const std::vector& options, string& selectedOption) + { + ImGuiStyle& style = ImGui::GetStyle(); + float availableWidth = ImGui::GetContentRegionAvail().x; + + // Add Button + if (ImGui::Button("Add", ImVec2(availableWidth, 0))) { + addDropdownOpen = !addDropdownOpen; + } + + if (addDropdownOpen) + { + // Begin Popup + ImGui::BeginChild("##AddDropdownChild", ImVec2(availableWidth, 300), true); + + // Suchfeld mit X Button + ImGui::PushItemWidth(-style.FramePadding.x * 4 - 24); // Platz für Clear Button + ImGui::InputTextWithHint("##SearchField", "Suchfeld", searchBuffer, IM_ARRAYSIZE(searchBuffer)); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + if (ImGui::Button("X")) { + searchBuffer[0] = '\0'; + } + + ImGui::Separator(); + + // Scrollbare Liste mit Filter + ImGui::BeginChild("##OptionsList", ImVec2(0, 250), false, ImGuiWindowFlags_AlwaysVerticalScrollbar); + + int count = 0; + for (const auto& option : options) + { + if (strlen(searchBuffer) == 0 || option.find(searchBuffer) != std::string::npos) + { + if (ImGui::Selectable(option.c_str())) + { + selectedOption = option; + addDropdownOpen = false; + break; + } + count++; + if (count >= 50) break; // Begrenze maximal gezeichnete Elemente zur Performance + } + } + + ImGui::EndChild(); + ImGui::EndChild(); + } + } + void ElementDrawer::Draw(Transformable* element,const bool& debug) { + ImGui::SeparatorText("Transform"); + ImGui::Checkbox("##checkbox", &element->_enabled); + ImGui::SameLine(); + InputText("<-- Name##", element->name); + ImGui::DragFloat3("<-- Position", &element->position.x, 0.1f); + ImGui::DragFloat3("<--Scale", &element->scale.x, 0.1f); + Vector3 euler = element->GetEuler(); + ImGui::DragFloat3("<-- Rotation", &(euler.x), 0.1f); + element->SetEuler(euler); + ImGui::Spacing(); + if(debug) + { + + ImGui::BeginDisabled(); + ImGui::Text(("ID: " + to_string(element->id)).c_str()); + ImGui::Text(("Child Count: " + std::to_string(element->GetChildren().size())).c_str()); + ImGui::Text(("Component Count: " + std::to_string(element->GetComponentCount())).c_str()); + ImGui::EndDisabled(); + ImGui::Spacing(); + } + if(element->GetComponentCount() > 0) + ImGui::SeparatorText("Components"); + for (int i = 0; i < element->GetComponentCount(); i++) + { + Draw(element->GetBehaviourScriptAt(i), debug, i); + } + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + + std::vector behaviourScripts; + for (const auto& [name, _] : BehaviourScriptRegistry::GetRegistry()) + { + behaviourScripts.push_back(name); + } + + std::string selectedScript = ""; + + DrawAddDropdown(behaviourScripts, selectedScript); + + if (!selectedScript.empty()) + { + // Erzeuge das Script: + BehaviourScript* script = BehaviourScriptRegistry::CreateBehaviourScript(selectedScript); + if (script) + { + if(element->HasBehaviourScript(typeid(*script).name())) + { + delete script; + } + else + element->AddBehaviourScript(script); + } + selectedScript = ""; // Reset nach Hinzufügen + } + + } + void ElementDrawer::Draw(Scene* element,const bool& debug) { + ImGui::Text("Scene Name: %s", element->GetName().c_str()); + ImGui::Text("Layer Count: %d", element->GetLayerCount()); + } + void ElementDrawer::Draw(Layer* element,const bool& debug) { + ImGui::Text("Layer Name: %s", element->GetName().c_str()); + } + void ElementDrawer::Draw(BehaviourScript *element, const bool &debug, const int& id) + { + ImGui::PushID(("Script##" + std::to_string(id)).c_str()); + std::string name = element->GetName(); + if(ImGui::CollapsingHeader(name.c_str())) + { + bool enabled = element->IsEnabled(); + ImGui::Checkbox("enabled", &enabled); + element->SetEnabled(enabled); + if(name == "Renderable") + { + Draw((Renderable*)element, debug); + } + else if (name == "Mesh Container") + { + Draw((MeshContainer*)element, debug); + } + else if (name == "Image") + { + Draw((Image*)element, debug); + } + else if (name == "Image Animation") + { + Draw((ImageAnimation*)element, debug); + } + else if (name == "Rect Base") + { + Draw((RectBase*)element, debug); + } + else if (name == "Camera") + { + Draw((Camera*)element, debug); + } + else if (name == "Particle System") + { + Draw((ParticleSystem*)element, debug); + } + else + { + element->CustomDraw(debug); + } + } + ImGui::PopID(); + } + void ElementDrawer::Draw(Renderable *element, const bool &debug) + { + ImGui::SeparatorText("Material"); + int height = 100; + if(element->GetMaterial() == nullptr) + { + height = 35; + } + ImVec2 size(0, height); + ImGui::BeginChild("MaterialViewer", size, ImGuiChildFlags_Borders); + Draw(element->GetMaterial(), debug); + ImGui::EndChild(); + } + void ElementDrawer::Draw(MeshContainer *element, const bool &debug) + { + int height = ImGui::GetContentRegionAvail().x; + if(element->GetMesh() == nullptr) + { + height = 35; + } + ImVec2 size(0, height); + Draw(element->GetMesh(), debug, "Mesh", true); + } + void ElementDrawer::Draw(Image *element, const bool &debug) + { + int height = ImGui::GetContentRegionAvail().x + 50; + if(element->GetSpritePtr() == nullptr) + { + height = 35; + } + ImVec2 size(0, height); + ImGui::SeparatorText("Sprite"); + Draw(element->GetSpritePtr(), debug, "Sprite", true); + } + void ElementDrawer::Draw(ImageAnimation *element, const bool &debug) + { + int currentSelection = -1; + int itemCount = element->GetImageAnimationSetCount(); + std::vector items; + for (int i = 0; i < itemCount; i++) + { + if(element->GetImageAnimationAt(i) == nullptr) continue; + items.push_back(element->GetImageAnimationAt(i)->Name); + if(element->GetImageAnimationAt(i)->Name == element->GetCurrentAnimation()) + { + currentSelection = i; + } + } + auto getter = [](void* vec, int idx, const char** out_text) { + auto& vector = *static_cast*>(vec); + if (idx < 0 || idx >= static_cast(vector.size())) return false; + *out_text = vector.at(idx).c_str(); + return true; + }; + if(ImGui::Combo("Active animation", ¤tSelection, getter, static_cast(&items), items.size())) + { + element->StartAnimation(items[currentSelection]); + } + items.clear(); + for (int i = 0; i < itemCount; i++) + { + if(element->GetImageAnimationAt(i) == nullptr) + { + items.push_back("NULL"); + continue; + } + items.push_back(element->GetImageAnimationAt(i)->Name); + } + ImGui::Text(("Frame: " + std::to_string(element->GetCurrentFrame())).c_str()); + if(debug) + { + ImGui::TextDisabled(("DeltaTime: " + std::to_string(element->GetDeltaTime())).c_str()); + } + ImGui::SeparatorText("Animation Sets"); + BeginList("ImageAnimationSetList"); + if(ListAddBtn() && (itemCount == 0 || items[itemCount - 1] != "NULL")) + { + element->SetAnimationSet("NULL", nullptr); + itemCount++; + items.push_back("NULL"); + } + for (int i = 0; i < itemCount; i++) + { + BeginListItem("subcomponent##" + std::to_string(i)); + Draw(element->GetImageAnimationAt(i),debug, items[i], true); + EndListItem(); + if(ListItemXBotton("x##" + std::to_string(i))) + { + element->RemoveAnimationSet(items[i]); + itemCount--; + i--; + } + } + EndList(); + } + void ElementDrawer::Draw(RectBase *element, const bool &debug) + { + ImGui::DragFloat2("Size", &element->size.x); + ImGui::Separator(); + + if (ImGui::TreeNode("Anchors")) + { + ImGui::Text("Min"); + ImGui::DragFloat("X##anchor_min_x", &element->anchors.p1.x,0.2f); + ImGui::DragFloat("Y##anchor_min_y", &element->anchors.p1.y,0.2f); + + ImGui::Text("Max"); + ImGui::DragFloat("X##anchor_max_x", &element->anchors.p2.x,0.2f); + ImGui::DragFloat("Y##anchor_max_y", &element->anchors.p2.y,0.2f); + + ImGui::TreePop(); + } + + ImGui::Text("Pivot"); + ImGui::DragFloat("X##pivot_x", &element->pivit.x,0.2f); + ImGui::DragFloat("Y##pivot_y", &element->pivit.y,0.2f); + + } + void ElementDrawer::Draw(Material *element, const bool &debug) + { + if(element == nullptr) + { + ImGui::TextDisabled("No material"); + return; + } + else + { + InputText("Name", element->GetName()); + + int currentSelection = -1; + int itemCount = ShaderRegistry::GetShaderCount(); + std::vector items; + for (int i = 0; i < itemCount; i++) + { + if(ShaderRegistry::GetShaderAt(i) == nullptr) continue; + items.push_back(ShaderRegistry::GetNameAt(i)); + if(ShaderRegistry::GetShaderAt(i) == element->GetShader()) + { + currentSelection = i; + } + } + auto getter = [](void* vec, int idx, const char** out_text) { + auto& vector = *static_cast*>(vec); + if (idx < 0 || idx >= static_cast(vector.size())) return false; + *out_text = vector.at(idx).c_str(); + return true; + }; + if(ImGui::Combo("Shader", ¤tSelection, getter, static_cast(&items), items.size())) + { + element->SetShader(ShaderRegistry::GetShader(items[currentSelection])); + } + if(debug) + ImGui::TextDisabled(to_string(element->GetID()).c_str()); + ImGui::Separator(); + int count = element->GetValueCount(); + for(int i = 0; i < count; i++) + { + std::tuple tupel = element->GetValueAt(i); + auto[ptr, type, name] = tupel; + if (type == typeid(int).name()) + { + int value = element->GetValue(name); + if(ImGui::InputInt(name.c_str(), &value)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(float).name()) + { + float value = element->GetValue(name); + if(ImGui::InputFloat(name.c_str(), &value)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(Vector2).name()) + { + Vector2 value = element->GetValue(name); + if(ImGui::InputFloat2(name.c_str(), &value.x)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(Vector3).name()) + { + Vector3 value = element->GetValue(name); + if(ImGui::InputFloat3(name.c_str(), &value.x)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(Vector4).name()) + { + Vector4 value = element->GetValue(name); + if(ImGui::InputFloat4(name.c_str(), &value.x)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(Matrix4x4).name()) + { + Matrix4x4 value = element->GetValue(name); + //TODO: need to implement; + } + else if (type == typeid(Color).name()) + { + Color value = element->GetValue(name); + if(ImGui::ColorEdit4(name.c_str(), &value.r)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(std::string).name()) + { + Vector4 value = element->GetValue(name); + if(ImGui::InputFloat4(name.c_str(), &value.x)) + { + element->SetValue(name, value); + } + } + else if (type == typeid(Texture*).name()) + { + Texture* value = element->GetValue(name); + Draw(value, debug, name , true); + } + else + { + ImGui::TextDisabled(("Not Implemented: " + type).c_str()); + } + } + } + } + void ElementDrawer::Draw(Texture *element, const bool &debug, const std::string& label, const bool small) + { + if(element == nullptr) + { + ImGui::Text("No Texture Set"); + return; + } + if(small) DrawTextureCompact(element, debug, label); + else DrawTextureNormal(element, debug, label); + } + void ElementDrawer::Draw(Sprite *element, const bool &debug, const std::string &label, const bool small) + { + if(element == nullptr) + { + ImGui::Text("No Sprite Set"); + return; + } + if(small) DrawSpriteCompact(element, debug, label); + else DrawSpriteNormal(element, debug, label); + } + void ElementDrawer::Draw(Mesh *element, const bool &debug, const std::string &label, const bool small) + { + if(element == nullptr) + { + ImGui::Text("No Mesh Set"); + return; + } + if(small) DrawMeshCompact(element, debug, label); + else DrawMeshNormal(element, debug, label); + } + void ElementDrawer::Draw(ImageAnimationSet *element, const bool &debug, const std::string &label, const bool small) + { + if(element == nullptr) + { + ImGui::Text("No Animation Set Asigned"); + return; + } + if(small) DrawImageAnimationSetCompact(element, debug, label); + else DrawImageAnimationSetNormal(element, debug, label); + } + void ElementDrawer::Draw(Camera *element, const bool &debug) + { + const bool isMain = (Camera::mainCamera == element); + if (!isMain && element->baseObject->name != ".EditorCamera") { + if (ImGui::Button("Make Main Camera")) { + Camera::mainCamera = element; + } + } else if(element->baseObject->name != ".EditorCamera") { + ImGui::Text("This is the Main Camera"); + } + else + { + ImGui::Text("Editor Camera can't be main camera"); + } + + ImGui::Separator(); + // Render Scale + float renderScale = element->GetRenderScale(); + if (ImGui::DragFloat("Render Scale", &renderScale, 0.1f)) + element->SetRenderScale(renderScale); + + ImGui::Separator(); + + // Projection + int projIndex = (element->GetProjection() == ProjectionType::Orthographic) ? 0 : 1; + const char* projItems[] = { "Orthographic", "Perspective" }; + if (ImGui::Combo("Projection", &projIndex, projItems, IM_ARRAYSIZE(projItems))) + element->SetProjection(projIndex == 0 ? ProjectionType::Orthographic + : ProjectionType::Perspective); + + // Clipping Planes + float nearCP = element->GetNearClippingPlane(); + if (ImGui::DragFloat("Near Clipping Plane", &nearCP, 0.1f)) + element->SetNearClippingPlane(nearCP); + + float farCP = element->GetFarClippingPlane(); + if (ImGui::DragFloat("Far Clipping Plane", &farCP, 0.1f)) + element->SetFarClippingPlane(farCP); + + // Field of View (only relevant for Perspective) + const bool isPerspective = (element->GetProjection() == ProjectionType::Perspective); + if (!isPerspective) ImGui::BeginDisabled(); + float fov = element->GetFov(); + if (ImGui::DragFloat("Field of View (deg)", &fov, 0.1f)) + element->SetFov(fov); + if (!isPerspective) ImGui::EndDisabled(); + } + void ElementDrawer::Draw(ParticleSystem *element, const bool &debug) + { + float indent = 15.0f; + auto OpeningAngleDeg = [](float conePlane) -> float { + float cosA = std::clamp(conePlane * 2.0f - 1.0f, -1.0f, 1.0f); + return std::acos(cosA) * (180.0f / TSE_PI); + }; + auto ClampPositive = [](float& v, float minv = 0.0f) { if (v < minv) v = minv; }; + + // ========================= + // MISC + // ========================= + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + indent); + if (ImGui::CollapsingHeader("Misc", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(20.0f); + + int pc = static_cast(element->particleCount); + if (ImGui::DragInt("Max Particles", &pc, 1.0f, 0, 100000)) { + if (pc < 0) pc = 0; + element->particleCount = static_cast(pc); + } + + ImGui::Checkbox("Start with Simulated Particles", &element->startWithSimulatedParicles); + if (ImGui::IsItemHovered()) ImGui::SetTooltip("Wenn aktiv, wird beim Start bereits ein gefüllter Partikelpuffer simuliert."); + + ImGui::Unindent(20.0f); + ImGui::Separator(); + } + + // ========================= + // START CONDITIONS + // ========================= + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + indent); + if (ImGui::CollapsingHeader("Start Conditions", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(20.0f); + + if (ImGui::TreeNodeEx("Rotation (Start)", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Random Rotation Direction", &element->startWithRandomRotationDirection); + if (!element->startWithRandomRotationDirection) { + int dirIdx = (element->rotationDirection >= 0.0f) ? 1 : 0; + const char* dirItems[] = { "-1 (CCW)", "+1 (CW)" }; + if (ImGui::Combo("Rotation Direction", &dirIdx, dirItems, IM_ARRAYSIZE(dirItems))) { + element->rotationDirection = (dirIdx == 1) ? 1.0f : -1.0f; + } + } else { + ImGui::BeginDisabled(); + float dummy = element->rotationDirection; + ImGui::InputFloat("Rotation Direction", &dummy); + ImGui::EndDisabled(); + } + + ImGui::Checkbox("Random Start Rotation", &element->startWithRandomRotation); + if (element->startWithRandomRotation) { + ImGui::DragFloat("Min Rotation (rad)", &element->minRotationRad, 0.01f); + ImGui::DragFloat("Max Rotation (rad)", &element->maxrotationRad, 0.01f); + if (element->maxrotationRad < element->minRotationRad) std::swap(element->minRotationRad, element->maxrotationRad); + } else { + ImGui::DragFloat("Start Rotation (rad)", &element->maxrotationRad, 0.01f); + ImGui::BeginDisabled(); ImGui::DragFloat("Min Rotation (rad)", &element->minRotationRad, 0.01f); ImGui::EndDisabled(); + } + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Size (Start)")) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Random Start Size", &element->startWithRandomSize); + if (element->startWithRandomSize) { + if (ImGui::DragFloat("Min Size", &element->minSize, 0.01f)) { ClampPositive(element->minSize, 0.0f); } + if (ImGui::DragFloat("Max Size", &element->maxSize, 0.01f)) { ClampPositive(element->maxSize, 0.0f); } + if (element->maxSize < element->minSize) std::swap(element->minSize, element->maxSize); + } else { + if (ImGui::DragFloat("Start Size", &element->maxSize, 0.01f)) { ClampPositive(element->maxSize, 0.0f); } + ImGui::BeginDisabled(); ImGui::DragFloat("Min Size", &element->minSize, 0.01f); ImGui::EndDisabled(); + } + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Lifetime (Start)")) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Random Start Lifetime", &element->startWithRandomLifetime); + if (element->startWithRandomLifetime) { + if (ImGui::DragFloat("Min Lifetime (s)", &element->minLifetime, 0.01f)) { ClampPositive(element->minLifetime, 0.0f); } + if (ImGui::DragFloat("Max Lifetime (s)", &element->maxLifetime, 0.01f)) { ClampPositive(element->maxLifetime, 0.0f); } + if (element->maxLifetime < element->minLifetime) std::swap(element->minLifetime, element->maxLifetime); + } else { + if (ImGui::DragFloat("Lifetime (s)", &element->maxLifetime, 0.01f)) { ClampPositive(element->maxLifetime, 0.0f); } + ImGui::BeginDisabled(); ImGui::DragFloat("Min Lifetime (s)", &element->minLifetime, 0.01f); ImGui::EndDisabled(); + } + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + ImGui::Unindent(20.0f); + ImGui::Separator(); + } + + // ========================= + // LIFETIME CONDITIONS + // ========================= + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + indent); + if (ImGui::CollapsingHeader("Lifetime Conditions", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(20.0f); + + if (ImGui::TreeNode("Color over Lifetime")) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Enable Color over Lifetime", &element->changeColorOverLifetime); + // Start immer editierbar + ImGui::ColorEdit4("Start Color", &element->startColor.r, ImGuiColorEditFlags_Float); + // End nur wenn aktiviert + if (!element->changeColorOverLifetime) ImGui::BeginDisabled(); + ImGui::ColorEdit4("End Color", &element->endColor.r, ImGuiColorEditFlags_Float); + if (!element->changeColorOverLifetime) ImGui::EndDisabled(); + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Speed over Lifetime")) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Enable Speed over Lifetime", &element->changeSpeedOverLifetime); + // Start immer frei + ImGui::DragFloat("Start Speed", &element->startSpeed, 0.01f); + // End nur bei aktiv + if (!element->changeSpeedOverLifetime) ImGui::BeginDisabled(); + ImGui::DragFloat("End Speed", &element->endSpeed, 0.01f); + if (!element->changeSpeedOverLifetime) ImGui::EndDisabled(); + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Rotation Speed over Lifetime")) + { + ImGui::Indent(16.0f); + + ImGui::Checkbox("Enable RotationSpeed over Lifetime", &element->changeRotationspeedOverLifetime); + // Start immer frei + ImGui::DragFloat("Start RotSpeed", &element->startRotationSpeed, 0.01f); + // End nur bei aktiv + if (!element->changeRotationspeedOverLifetime) ImGui::BeginDisabled(); + ImGui::DragFloat("End RotSpeed", &element->endRotationSpeed, 0.01f); + if (!element->changeRotationspeedOverLifetime) ImGui::EndDisabled(); + + ImGui::Unindent(16.0f); + ImGui::TreePop(); + } + + ImGui::Unindent(20.0f); + ImGui::Separator(); + } + + // ========================= + // SPAWN AREA + // ========================= + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + indent); + if (ImGui::CollapsingHeader("Spawn Area", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Indent(20.0f); + + ImGui::DragFloat3("Offset (world)", &element->offset.x, 0.01f); + + ImGui::DragFloat3("Forward", &element->forward.x, 0.01f); + ImGui::SameLine(); + if (ImGui::SmallButton("Normalize##fwd")) { + element->forward.NormalizeSafe(Vector3::forward); + } + + if (ImGui::DragFloat("Radius", &element->radius, 0.01f)) { + if (element->radius < 0.0f) element->radius = 0.0f; + } + + if (ImGui::SliderFloat("Cone Plane", &element->conePlane, 0.0f, 1.0f, "%.3f")) { + element->conePlane = std::clamp(element->conePlane, 0.0f, 1.0f); + } + + float angle = OpeningAngleDeg(element->conePlane); + ImGui::Text("Opening Angle: %.2f deg", angle); + + const char* mode = "Forward Cone"; + if (element->conePlane == 1.0f) mode = "Full Sphere"; + else if (element->conePlane <= 0.5f) mode = "Forward Cone"; + else mode = "Inverse Cone"; + ImGui::Text("Mode (derived): %s", mode); + + if (debug) { + ImGui::Separator(); + ImGui::TextDisabled("Debug:"); + ImGui::BulletText("Forward len^2: %.6f", element->forward.LengthSqr()); + } + + ImGui::Unindent(20.0f); + } + } + void ElementDrawer::DrawImageAnimationSetCompact(ImageAnimationSet *element, const bool &debug, const std::string &label) + { + float item_spacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); + + float available_width = ImGui::GetContentRegionAvail().x; + float field_width = available_width - label_size.x - item_spacing; + + ImVec2 field_size = ImVec2(field_width, label_size.y + 4); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg)); // gleiche Farbe wie InputText + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ImGui::GetStyle().FrameRounding); + + ImGui::BeginChild("##FakeInput", field_size, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::SetCursorPos(ImVec2(2, 2)); + ImGui::TextUnformatted(element->Name.c_str()); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImVec2 cursorCurrent = ImGui::GetCursorPos(); + cursorCurrent.y += 2; + ImGui::SetCursorPos(cursorCurrent); + ImGui::TextUnformatted(label.c_str()); + } + void ElementDrawer::DrawImageAnimationSetNormal(ImageAnimationSet *element, const bool &debug, const std::string &label) + { + InputText("Name", element->Name); + ImGui::InputFloat("Frame Time", &element->frameTime); + ImGui::SeparatorText("Sprites"); + BeginList("SpriteList"); + if(ListAddBtn()) + { + element->Sprites.push_back(nullptr); + } + auto it = element->Sprites.begin(); + for (int i = 0; i < element->Sprites.size(); i++) + { + BeginListItem("subcomponent##" + std::to_string(i),65); + Draw(element->Sprites[i],debug, std::to_string(i), true); + EndListItem(); + if(ListItemXBotton("x##" + std::to_string(i))) + { + auto it2 = it; + it--; + element->Sprites.erase(it2); + i--; + } + it++; + } + EndList(); + } + void ElementDrawer::DrawMeshCompact(Mesh *element, const bool &debug, const std::string &label) + { + float item_spacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); + + float available_width = ImGui::GetContentRegionAvail().x; + float field_width = available_width - label_size.x - item_spacing; + + ImVec2 field_size = ImVec2(field_width, label_size.y + 4); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg)); // gleiche Farbe wie InputText + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ImGui::GetStyle().FrameRounding); + + ImGui::BeginChild("##FakeInput", field_size, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::SetCursorPos(ImVec2(2, 2)); + ImGui::TextUnformatted(element->name.c_str()); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImVec2 cursorCurrent = ImGui::GetCursorPos(); + cursorCurrent.y += 2; + ImGui::SetCursorPos(cursorCurrent); + ImGui::TextUnformatted(label.c_str()); + } + void ElementDrawer::DrawMeshNormal(Mesh *element, const bool &debug, const std::string &label) + { + ImGui::Text(("Name: " + element->name).c_str()); + if(debug) + { + //ImGui::TextDisabled(("ID: " + to_string(element->id)).c_str()); + } + ImGui::Separator(); + ImGui::Text(("Vectex Count: " + std::to_string(element->vertecies.size())).c_str()); + ImGui::Text(("Index Count: " + std::to_string(element->indecies.size())).c_str()); + ImGui::Text(("UV Count: " + std::to_string(element->uvs.size())).c_str()); + ImGui::Indent(20.0f); + if(ImGui::CollapsingHeader("Vertecies")) + { + ImGui::PushID("Verts"); + ImGui::BeginDisabled(); + for (int i = 0; i < element->vertecies.size(); i++) + { + ImGui::InputFloat3(std::to_string(i).c_str(), &element->vertecies[i].x); + } + ImGui::EndDisabled(); + ImGui::PopID(); + } + if(ImGui::CollapsingHeader("Indecies")) + { + ImGui::PushID("Inds"); + ImGui::BeginDisabled(); + for (int i = 0; i < element->indecies.size(); i++) + { + int val = element->indecies[i]; + ImGui::InputInt(std::to_string(i).c_str(), &val); + } + ImGui::EndDisabled(); + ImGui::PopID(); + } + if(ImGui::CollapsingHeader("UVs")) + { + ImGui::PushID("Uvs"); + ImGui::BeginDisabled(); + for (int i = 0; i < element->uvs.size(); i++) + { + ImGui::InputFloat2(std::to_string(i).c_str(), &element->uvs[i].x); + } + ImGui::EndDisabled(); + ImGui::PopID(); + } + ImGui::Unindent(20.0f); + + } + void ElementDrawer::DrawSpriteCompact(Sprite *element, const bool &debug, const std::string &label) + { + float item_spacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); + + float available_width = ImGui::GetContentRegionAvail().x; + float field_width = available_width - label_size.x - item_spacing; + + ImVec2 field_size = ImVec2(field_width, 60); + + Rect r = element->GetUVRect(); + float ymultiplyer = (element->GetTexture()->height() * r.height()) / (element->GetTexture()->width() * r.width()); + + ImVec2 texSize (field_size.y - 2, (field_size.y - 2) * ymultiplyer); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg)); // gleiche Farbe wie InputText + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ImGui::GetStyle().FrameRounding); + + ImGui::BeginChild("##FakeInput", field_size, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::SetCursorPos(ImVec2(1,(60-texSize.y) / 2)); + ImGui::Image(element->GetTexture()->GetTextureId(), texSize, {r.p1.x,r.p2.y}, {r.p2.x,r.p1.y}); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImVec2 cursorCurrent = ImGui::GetCursorPos(); + cursorCurrent.y += (60-label_size.y) / 2; + ImGui::SetCursorPos(cursorCurrent); + ImGui::TextUnformatted(label.c_str()); + } + void ElementDrawer::DrawSpriteNormal(Sprite *element, const bool &debug, const std::string &label) + { + Rect& r = element->GetUVRect(); + ImGui::InputFloat2("UV1", &r.p1.x); + ImGui::InputFloat2("UV2", &r.p2.x); + if(debug) + { + Draw(element->GetTexture(), debug, "Texture", true); + } + ImGui::Separator(); + float available_width = ImGui::GetContentRegionAvail().x; + float ymultiplyer = (element->GetTexture()->height() * r.height()) / (element->GetTexture()->width() * r.width()); + + ImVec2 texSize (available_width, (available_width) * ymultiplyer); + ImGui::Image(element->GetTexture()->GetTextureId(), texSize, {r.p1.x,r.p2.y}, {r.p2.x,r.p1.y}); + } + void ElementDrawer::DrawTextureCompact(Texture *element, const bool &debug, const std::string &label) + { + float item_spacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); + + float available_width = ImGui::GetContentRegionAvail().x; + float field_width = available_width - label_size.x - item_spacing; + + ImVec2 field_size = ImVec2(field_width, 60); + + float ymultiplyer = element->height() / element->width(); + + ImVec2 texSize (field_size.y - 2, (field_size.y - 2) * ymultiplyer); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_FrameBg)); // gleiche Farbe wie InputText + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ImGui::GetStyle().FrameRounding); + + ImGui::BeginChild("##FakeInput", field_size, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::SetCursorPos(ImVec2(1,(60-texSize.y) / 2)); + ImGui::Image(element->GetTextureId(), texSize, {0,1}, {1,0}); + ImGui::SameLine(); + ImGui::SetCursorPos(ImVec2(field_size.y + 1,(60-label_size.y) / 2)); + ImGui::TextUnformatted(element->name.c_str()); + + ImGui::EndChild(); + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImVec2 cursorCurrent = ImGui::GetCursorPos(); + cursorCurrent.y += (60-label_size.y) / 2; + ImGui::SetCursorPos(cursorCurrent); + ImGui::TextUnformatted(label.c_str()); + } + void ElementDrawer::DrawTextureNormal(Texture *element, const bool &debug, const std::string& label) + { + ImGui::TextUnformatted(element->name.c_str()); + if(debug) + { + ImGui::Separator(); + ImGui::TextDisabled(("Width: " + std::to_string(element->width())).c_str()); + ImGui::TextDisabled(("Height: " + std::to_string(element->height())).c_str()); + ImGui::TextDisabled(("BPP: " + std::to_string(element->bpp())).c_str()); + ImGui::TextDisabled(("Chanels: " + std::to_string(element->Chanels())).c_str()); + ImGui::Separator(); + ImGui::TextDisabled(("TextureID: " + std::to_string(element->GetTextureId())).c_str()); + } + ImGui::Separator(); + float available_width = ImGui::GetContentRegionAvail().x; + float ymultiplyer = element->height() / element->width(); + + ImVec2 texSize (available_width, (available_width) * ymultiplyer); + ImGui::Image(element->GetTextureId(), texSize, {0,1}, {1,0}); + } + +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/ElementDrawer.hpp b/TSE_Editor/src/UI/ElementDrawer.hpp new file mode 100644 index 0000000..52774e4 --- /dev/null +++ b/TSE_Editor/src/UI/ElementDrawer.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include "imgui/imgui.h" +#include "Types.hpp" +#include "elements/Transformable.hpp" +#include "elements/Scene.hpp" +#include "elements/Layer.hpp" +#include "Debug.hpp" +#include "BehaviourScripts/Renderable.hpp" +#include "BehaviourScripts/MeshContainer.hpp" +#include "BehaviourScripts/ImageAnimation.hpp" +#include "BehaviourScripts/RectBase.hpp" +#include "BehaviourScripts/ParticleSystem.hpp" + +namespace TSE::EDITOR +{ + enum class InspectableType + { + None, + Transformable, + Scene, + Layer + }; + + struct Inspectable + { + InspectableType type = InspectableType::None; + void* ptr = nullptr; + + Inspectable() = default; + Inspectable(InspectableType t, void* p) : type(t), ptr(p) {} + }; + + class ElementDrawer + { + private: + inline static bool addDropdownOpen = false; + inline static char searchBuffer[128] = ""; + inline static ImGuiTextFilter filter; + + public: + static void Draw(const Inspectable& element,const bool& debug) { + switch (element.type) { + case InspectableType::Transformable: + Draw(static_cast(element.ptr), debug); + break; + case InspectableType::Scene: + Draw(static_cast(element.ptr), debug); + break; + case InspectableType::Layer: + Draw(static_cast(element.ptr), debug); + break; + default: + TSE_WARNING("Draw not implemented for this type."); + break; + } + } + + private: + + static void DrawAddDropdown(const std::vector& options, std::string& selectedOption); + static void Draw(Transformable* element,const bool& debug); + static void Draw(Scene* element,const bool& debug); + static void Draw(Layer* element,const bool& debug); + static void Draw(BehaviourScript* element,const bool& debug, const int& id = 0); + static void Draw(Renderable* element, const bool& debug); + static void Draw(MeshContainer* element, const bool& debug); + static void Draw(Image* element, const bool& debug); + static void Draw(ImageAnimation* element, const bool& debug); + static void Draw(RectBase* element, const bool& debug); + static void Draw(Material* element, const bool& debug); + static void Draw(Texture* element, const bool& debug, const std::string& label = "", const bool small = false); + static void Draw(Sprite* element, const bool& debug, const std::string& label = "", const bool small = false); + static void Draw(Mesh* element, const bool& debug, const std::string& label = "", const bool small = false); + static void Draw(ImageAnimationSet* element, const bool& debug, const std::string& label = "", const bool small = false); + static void Draw(Camera* element, const bool& debug); + static void Draw(ParticleSystem* element, const bool& debug); + + + static void DrawImageAnimationSetCompact(ImageAnimationSet* element, const bool& debug, const std::string& label); + static void DrawImageAnimationSetNormal(ImageAnimationSet* element, const bool& debug, const std::string& label); + static void DrawMeshCompact(Mesh* element, const bool& debug, const std::string& label); + static void DrawMeshNormal(Mesh* element, const bool& debug, const std::string& label); + static void DrawSpriteCompact(Sprite* element, const bool& debug, const std::string& label); + static void DrawSpriteNormal(Sprite* element, const bool& debug, const std::string& label); + static void DrawTextureCompact(Texture* element, const bool& debug, const std::string& label); + static void DrawTextureNormal(Texture* element, const bool& debug, const std::string& label); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/ViewportController.cpp b/TSE_Editor/src/UI/ViewportController.cpp new file mode 100644 index 0000000..b8ec4f2 --- /dev/null +++ b/TSE_Editor/src/UI/ViewportController.cpp @@ -0,0 +1,213 @@ +#include "ViewportController.hpp" +#include "imgui/imgui.h" + + +extern "C" { + #include "tinyfiledialogs.h" +} + +TSE::EDITOR::ViewportController::ViewportController() +{ + ImGuiIO& io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable | ImGuiConfigFlags_DockingEnable; +} + +bool wantOpenCreate = false; + +void TSE::EDITOR::ViewportController::ShowFileMenu() +{ + // if(ImGui::MenuItem("New Project")) + // { + // wantOpenCreate = true; + // gCreateState.confirmed = false; + // gCreateState.nameBuf[0] = '\0'; + // gCreateState.pathBuf[0] = '\0'; + // } + // if(ImGui::MenuItem("Load Project")) + // { + + // } + // if(ImGui::MenuItem("Save Project")) + // { + + // } + // ImGui::Separator(); + // if(ImGui::MenuItem("Build")) + // { + + // } + // ImGui::Separator(); + if(ImGui::MenuItem("Exit")) + { + exit(0); + } +} + +static std::optional PickFolderBlocking(const char* startPath) +{ + const char* chosen = tinyfd_selectFolderDialog("Ordner wählen", startPath); + if (chosen && chosen[0]) return std::string(chosen); + return std::nullopt; +} + + +void TSE::EDITOR::ViewportController::NewProjectPopup() +{ + if (wantOpenCreate) { + ImGui::OpenPopup("Neues Element##CreateItemModal"); + wantOpenCreate = false; + } + + // WICHTIG: Diese SetNext*-Aufrufe OHNE IF davor! + ImGui::SetNextWindowSize(ImVec2(520, 0), ImGuiCond_Appearing); + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), + ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSizeConstraints(ImVec2(480, 0), ImVec2(620, FLT_MAX)); + + // --- POPUP --- + if (ImGui::BeginPopupModal("Neues Element##CreateItemModal", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::TextUnformatted("Name"); + ImGui::SetNextItemWidth(-1); + ImGui::InputText("##name", gCreateState.nameBuf, IM_ARRAYSIZE(gCreateState.nameBuf)); + + ImGui::Dummy(ImVec2(0, 8)); + + ImGui::TextUnformatted("Ordner"); + ImGui::PushItemWidth(-40); + ImGui::InputText("##path", gCreateState.pathBuf, IM_ARRAYSIZE(gCreateState.pathBuf)); + ImGui::PopItemWidth(); + ImGui::SameLine(); + if (!gCreateState.pickingFolder) { + if (ImGui::Button("...", ImVec2(28, 0))) { + const char* startPath = (gCreateState.pathBuf[0] ? gCreateState.pathBuf : nullptr); + gCreateState.pickingFolder = true; + // Erzwinge neuen Thread: std::launch::async + gCreateState.pickFuture = std::async(std::launch::async, [startPath]{ + return PickFolderBlocking(startPath); + }); + } + } else { + // Während der Auswahl: Button disabled + kleines Status-Label + ImGui::BeginDisabled(); + ImGui::Button("...", ImVec2(28, 0)); + ImGui::EndDisabled(); + ImGui::SameLine(); + ImGui::TextUnformatted("Öffne Ordner-Dialog..."); + + // Poll das Future: sobald fertig, Ergebnis übernehmen + using namespace std::chrono_literals; + if (gCreateState.pickFuture.valid() && + gCreateState.pickFuture.wait_for(0ms) == std::future_status::ready) + { + if (auto res = gCreateState.pickFuture.get(); res && !res->empty()) { + std::snprintf(gCreateState.pathBuf, IM_ARRAYSIZE(gCreateState.pathBuf), "%s", res->c_str()); + } + gCreateState.pickingFolder = false; + } + } + + ImGui::Dummy(ImVec2(0, 10)); + ImGui::Separator(); + ImGui::Dummy(ImVec2(0, 2)); + + // Buttons rechts + float btnW = 80.0f; + float totalW = btnW*2 + ImGui::GetStyle().ItemSpacing.x; + float avail = ImGui::GetContentRegionAvail().x; + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + avail - totalW); + + if (ImGui::Button("OK", ImVec2(btnW, 0))) { + bool valid = gCreateState.nameBuf[0] && gCreateState.pathBuf[0]; + if (valid) { + gCreateState.confirmed = true; + ImGui::CloseCurrentPopup(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(btnW, 0))) { + gCreateState.confirmed = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + + if(gCreateState.confirmed) + { + gCreateState.confirmed = false; + //ProjectManager::NewProject(gCreateState.pathBuf, gCreateState.nameBuf); + } +} + +void TSE::EDITOR::ViewportController::ShowViewsMenu() +{ + for(auto ui : uiElements) + { + if(ImGui::MenuItem(ui.first.c_str())) + { + ui.second->SetEnabled(true); + } + } +} + +void TSE::EDITOR::ViewportController::MenuBar() +{ + ImGui::BeginMainMenuBar(); + + if(ImGui::BeginMenu("File")) + { + ShowFileMenu(); + ImGui::EndMenu(); + } + if(ImGui::BeginMenu("Views")) + { + ShowViewsMenu(); + ImGui::EndMenu(); + } + + + + ImGui::EndMainMenuBar(); +} + +void TSE::EDITOR::ViewportController::Docking() +{ + ImGuiWindowFlags wndFlags = ImGuiWindowFlags_NoDocking; + 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); + // wndFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + // wndFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + // ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {0,0}); + // ImGui::Begin("DockSpace", &dockSpaceOpen, wndFlags); + // ImGui::PopStyleVar(3); + uint dockspaceID = ImGui::GetID("MyDockSpace"); + ImGui::DockSpaceOverViewport(dockspaceID, viewport); + // ImGui::DockSpace(dockspaceID, {0,0}); + // ImGui::End(); +} + +void TSE::EDITOR::ViewportController::Update() +{ + MenuBar(); + NewProjectPopup(); + Docking(); + for(auto ui : uiElements) + { + ui.second->Render(); + } +} + +void TSE::EDITOR::ViewportController::AddGuiElement(const std::string &name, IGUIElement *element) +{ + uiElements[name] = element; +} + +TSE::EDITOR::IGUIElement *TSE::EDITOR::ViewportController::GetGuiElement(const std::string &name) +{ + return uiElements.at(name); +} diff --git a/TSE_Editor/src/UI/ViewportController.hpp b/TSE_Editor/src/UI/ViewportController.hpp new file mode 100644 index 0000000..5cbc60b --- /dev/null +++ b/TSE_Editor/src/UI/ViewportController.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "UI/base/IGUIElement.hpp" +#include +#include "Types.hpp" +#include +#include +#include + +namespace TSE::EDITOR +{ + struct CreateItemState + { + bool confirmed = false; + char nameBuf[256] = ""; + char pathBuf[1024] = ""; + + std::atomic pickingFolder{false}; + std::future> pickFuture; + }; + + class ViewportController + { + private: + bool dockSpaceOpen = true; + std::unordered_map uiElements; + CreateItemState gCreateState; + + public: + ViewportController(); + + void ShowFileMenu(); + void NewProjectPopup(); + void ShowViewsMenu(); + void MenuBar(); + void Docking(); + void Update(); + void AddGuiElement(const std::string& name, IGUIElement* element); + IGUIElement* GetGuiElement(const std::string& name); + }; +} // namespace TSE::Editor diff --git a/TSE_Editor/src/UI/base/GuiWindow.cpp b/TSE_Editor/src/UI/base/GuiWindow.cpp new file mode 100644 index 0000000..9854093 --- /dev/null +++ b/TSE_Editor/src/UI/base/GuiWindow.cpp @@ -0,0 +1,35 @@ +#include "GuiWindow.h" + +TSE::EDITOR::GuiWindow::GuiWindow(const string &title, const ImGuiWindowFlags &flags) +{ + this->title = title; + this->flags = flags; +} + +TSE::EDITOR::GuiWindow::GuiWindow(const string &title, const Vector2 pos, const Vector2 size, const ImGuiWindowFlags &flags) +{ + this->title = title; + this->pos = pos; + this->size = size; + this->flags = flags; +} + +void TSE::EDITOR::GuiWindow::Render() +{ + if(enabled) + { + Begin(); + Define(); + End(); + } +} + +void TSE::EDITOR::GuiWindow::Begin() +{ + ImGui::Begin(title.c_str(), &enabled, flags); +} + +void TSE::EDITOR::GuiWindow::End() +{ + ImGui::End(); +} diff --git a/TSE_Editor/src/UI/base/GuiWindow.h b/TSE_Editor/src/UI/base/GuiWindow.h new file mode 100644 index 0000000..37e5f66 --- /dev/null +++ b/TSE_Editor/src/UI/base/GuiWindow.h @@ -0,0 +1,27 @@ +#pragma once + +#include "IGUIElement.hpp" +#include "Types.hpp" +#include "Vector2.hpp" +#include "imgui/imgui.h" + +namespace TSE::EDITOR +{ + class GuiWindow : public IGUIElement + { + public: + Vector2 pos = Vector2::zero; + Vector2 size = Vector2(100,100); + ImGuiWindowFlags flags = ImGuiWindowFlags_None; + string title = "Title"; + + GuiWindow(const string& title, const ImGuiWindowFlags& flags = ImGuiWindowFlags_None); + GuiWindow(const string& title, const Vector2 pos, const Vector2 size, const ImGuiWindowFlags& flags = ImGuiWindowFlags_None); + void Render() override; + virtual void Define() = 0; + + private: + void Begin(); + void End(); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/base/IGUIElement.hpp b/TSE_Editor/src/UI/base/IGUIElement.hpp new file mode 100644 index 0000000..2e14f4a --- /dev/null +++ b/TSE_Editor/src/UI/base/IGUIElement.hpp @@ -0,0 +1,16 @@ +#pragma once + +namespace TSE::EDITOR +{ + class IGUIElement + { + protected: + bool enabled = true; + public: + virtual void Render() = 0; + inline void SetEnabled(bool state) + { + enabled = state; + }; + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/CameraView.cpp b/TSE_Editor/src/UI/windows/CameraView.cpp new file mode 100644 index 0000000..0731902 --- /dev/null +++ b/TSE_Editor/src/UI/windows/CameraView.cpp @@ -0,0 +1,29 @@ +#include "CameraView.hpp" + +TSE::EDITOR::CameraView::CameraView() : GuiWindow("Camera", ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar) +, fb({100,100}) { } + +void TSE::EDITOR::CameraView::Define() +{ + if(currentCamera != Camera::mainCamera) + { + if(currentCamera != nullptr) + currentCamera->SetRenderTarget(nullptr); + currentCamera = Camera::mainCamera; + if(currentCamera != nullptr) + currentCamera->SetRenderTarget(&fb); + } + + ImGuiWindowFlags flags2 = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar; + if(ImGui::BeginChild("##SceneChild", {0,0}, ImGuiChildFlags_None, flags2)) + { + float w = fb.width(); + ImGui::Image(fb.GetTextureId(), {fb.width(), fb.height()},{0,1}, {1,0}); + auto vec2 = ImGui::GetWindowSize(); + if(fb.width() != vec2.x || fb.height() != vec2.y) + { + fb.SetSize({vec2.x, vec2.y}); + } + } + ImGui::EndChild(); +} diff --git a/TSE_Editor/src/UI/windows/CameraView.hpp b/TSE_Editor/src/UI/windows/CameraView.hpp new file mode 100644 index 0000000..fa20b1b --- /dev/null +++ b/TSE_Editor/src/UI/windows/CameraView.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include "RenderTexture.hpp" +#include "BehaviourScripts/Camera.hpp" + +namespace TSE::EDITOR +{ + class CameraView : public GuiWindow + { + private: + Camera* currentCamera = nullptr; + TSE::GLFW::RenderTexture fb; + public: + CameraView(); + void Define() override; + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/ConsolView.cpp b/TSE_Editor/src/UI/windows/ConsolView.cpp new file mode 100644 index 0000000..d966768 --- /dev/null +++ b/TSE_Editor/src/UI/windows/ConsolView.cpp @@ -0,0 +1,117 @@ +#include "ConsolView.hpp" + +TSE::EDITOR::ConsolView::ConsolView() : GuiWindow("Console", ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoCollapse) +{ + Debug::AddCallback(CreateLogEntry); + textbuffer[0] = '\0'; +} + +TSE::EDITOR::ConsolView::~ConsolView() +{ + delete[] textbuffer; +} + +void TSE::EDITOR::ConsolView::MenuBar() +{ + if(ImGui::BeginMenuBar()) + { + if(ImGui::MenuItem("Clear")) + { + entries.clear(); + } + bool style = logs; + if(style) ImGui::PushStyleColor(ImGuiCol_Text, {0,1,0,1}); + if(ImGui::MenuItem("Logs")) logs = !logs; + if(style) ImGui::PopStyleColor(); + style = warnings; + if(style) ImGui::PushStyleColor(ImGuiCol_Text, {0,1,0,1}); + if(ImGui::MenuItem("Warning")) warnings = !warnings; + if(style) ImGui::PopStyleColor(); + style = errors; + if(style) ImGui::PushStyleColor(ImGuiCol_Text, {0,1,0,1}); + if(ImGui::MenuItem("Errors")) errors = !errors; + if(style) ImGui::PopStyleColor(); + style = autoScroll; + if(style) ImGui::PushStyleColor(ImGuiCol_Text, {0,1,0,1}); + if(ImGui::MenuItem("Auto Scroll")) autoScroll = !autoScroll; + if(style) ImGui::PopStyleColor(); + } + ImGui::EndMenuBar(); +} + +void TSE::EDITOR::ConsolView::LogList() +{ + float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + if(ImGui::BeginChild("ScrollingRegion", {0, -footer_height_to_reserve}, ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_HorizontalScrollbar)) + { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {4, 1}); + for(auto entry : entries) + { + switch (entry.type) + { + case LogEntryType::Warning: + if (warnings) continue; + ImGui::PushStyleColor(ImGuiCol_Text, {1,1,0,1}); + ImGui::TextUnformatted(entry.CreateTimedMSG().c_str()); + ImGui::PopStyleColor(); + break; + case LogEntryType::Error: + if (errors) continue; + ImGui::PushStyleColor(ImGuiCol_Text, {1,0,0,1}); + ImGui::TextUnformatted(entry.CreateTimedMSG().c_str()); + ImGui::PopStyleColor(); + break; + case LogEntryType::Log: + if (logs) continue; + ImGui::TextUnformatted(entry.CreateTimedMSG().c_str()); + break; + } + } + if (autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + + ImGui::PopStyleVar(); + + } + ImGui::EndChild(); + ImGui::Separator(); +} + +int Callback(ImGuiInputTextCallbackData* data) +{ + return 0; +} + +void TSE::EDITOR::ConsolView::Input() +{ + ImGuiInputTextFlags _flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory; + + if(ImGui::InputText("<-- Command", textbuffer, 512, _flags, Callback)) + { + HandleInput(std::string(textbuffer)); + textbuffer[0] = '\0'; + } +} + +void TSE::EDITOR::ConsolView::Define() +{ + MenuBar(); + LogList(); + Input(); +} + +void TSE::EDITOR::ConsolView::HandleInput(const string &input) +{ + LuaStateHandler::RunLuaString(input); +} + +void TSE::EDITOR::ConsolView::CreateLogEntry(const string &msg, const LogEntryType &t) +{ + if(entries.size() > 0 && entries[entries.size() - 1].msg == msg) + { + entries[entries.size() - 1].count += 1; + return; + } + LogEntry entry(msg, t); + entries.push_back(entry); +} diff --git a/TSE_Editor/src/UI/windows/ConsolView.hpp b/TSE_Editor/src/UI/windows/ConsolView.hpp new file mode 100644 index 0000000..5cff5e7 --- /dev/null +++ b/TSE_Editor/src/UI/windows/ConsolView.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include +#include +#include "Types.hpp" +#include +#include "Debug.hpp" + +namespace TSE::EDITOR +{ + struct LogEntry + { + public: + LogEntryType type; + string msg; + std::chrono::hh_mm_ss time; + int count = 1; + + inline LogEntry(const string& message, const LogEntryType& t) + { + msg = message; + type = t; + + auto now = std::chrono::system_clock::now(); + + auto today = std::chrono::floor(now); + auto time_since_midnight = now - today; + + time = std::chrono::hh_mm_ss(std::chrono::floor(time_since_midnight)); + + }; + + inline string CreateTimedMSG() + { + string res = std::to_string(time.hours().count()) + ":" + std::to_string(time.minutes().count()) + ":" + std::to_string(time.seconds().count()); + res += " -> " + msg; + if(count > 1) + { + res += " (" + std::to_string(count) + ")"; + } + return res; + } + }; + + class ConsolView : public GuiWindow + { + private: + static inline std::vector entries = {}; + + bool logs = false, warnings = false, errors = false, autoScroll = true; + char* textbuffer = new char[512]; + public: + static std::vector arguments; + + ConsolView(); + ~ConsolView(); + void MenuBar(); + void LogList(); + void Input(); + void Define() override; + void HandleInput(const string& input); + static void CreateLogEntry(const string& msg, const LogEntryType& t); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/DebugView.cpp b/TSE_Editor/src/UI/windows/DebugView.cpp new file mode 100644 index 0000000..a3a327a --- /dev/null +++ b/TSE_Editor/src/UI/windows/DebugView.cpp @@ -0,0 +1,24 @@ +#include "DebugView.hpp" +#include "utils/Time.hpp" +#include "elements/Transformable.hpp" + +TSE::EDITOR::DebugView::DebugView() : GuiWindow("Debug", ImGuiWindowFlags_NoCollapse) { } + +void TSE::EDITOR::DebugView::Define() +{ + lasttime += Time::deltaTime(); + if(lasttime >= 1) + { + lasttime = 0; + debugFps = std::to_string(frameCount) + " FPS"; + + frameCount = 0; + } + else + { + frameCount++; + } + + ImGui::Text(debugFps.c_str()); + ImGui::Text(("Object count: " + std::to_string(Transformable::GetTansformableCount())).c_str()); +} diff --git a/TSE_Editor/src/UI/windows/DebugView.hpp b/TSE_Editor/src/UI/windows/DebugView.hpp new file mode 100644 index 0000000..21c9970 --- /dev/null +++ b/TSE_Editor/src/UI/windows/DebugView.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include "Types.hpp" + +namespace TSE::EDITOR +{ + class DebugView : public GuiWindow + { + private: + string debugFps = "0 FPS"; + float lasttime = 0; + int frameCount = 0; + + public: + + DebugView(); + + ~DebugView() = default; + + void Define() override; + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/HirearchieView.cpp b/TSE_Editor/src/UI/windows/HirearchieView.cpp new file mode 100644 index 0000000..c898665 --- /dev/null +++ b/TSE_Editor/src/UI/windows/HirearchieView.cpp @@ -0,0 +1,392 @@ +#include "HirearchieView.hpp" +#include "PropertiesView.hpp" + +TSE::EDITOR::HirearchieView::HirearchieView(Scene *s) : GuiWindow("Hirearchie", ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoCollapse) +{ + SetScene(s); +} + +void TSE::EDITOR::HirearchieView::SetScene(Scene *s) +{ + currentScene = s; +} + +void TSE::EDITOR::HirearchieView::Define() +{ + if(currentScene == nullptr) return; + MenuBar(); + + bool collapseOpen = ImGui::CollapsingHeader(currentScene->GetName().c_str(), ImGuiTreeNodeFlags_DefaultOpen); + if(ImGui::BeginPopupContextItem()) + { + if(ImGui::MenuItem("Rename")) + { + strcpy(inputBuffer, currentScene->GetName().c_str()); + activatePopUpNamingLayer = true; + inputPurpose = RenamingScene; + } + ImGui::Separator(); + AddMenu(nullptr, nullptr); + ImGui::EndPopup(); + } + if(collapseOpen) + { + int layerCount = currentScene->GetLayerCount(); + for (int i = 0; i < layerCount; i++) + { + auto layer = currentScene->GetLayerAt(i); + //if(layer->GetName() != ".editor") + DisplayLayer(layer); + } + + } + + if(activatePopUpNamingLayer) + { + activatePopUpNamingLayer = false; + openPopUpNamingLayer = true; + ImGui::OpenPopup("Enter Text"); + } + + if (openPopUpNamingLayer) { + if (ImGui::BeginPopupModal("Enter Text", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Text("Please enter a Name:"); + + ImGui::InputText("##input", inputBuffer, IM_ARRAYSIZE(inputBuffer)); + + bool inputValid = strlen(inputBuffer) > 0; + + if (!inputValid) { + ImGui::TextColored(ImVec4(1,0,0,1), "Not Valid."); + } + + if (ImGui::Button("OK", ImVec2(120, 0))) { + if (inputValid) { + inputConfirmed = true; + openPopUpNamingLayer = false; + ImGui::CloseCurrentPopup(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(120, 0))) { + openPopUpNamingLayer = false; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + } + if (inputConfirmed) { + switch (inputPurpose) + { + case LayerCreation: + CreateLayer(); + break; + case RenamingLayer: + currentScene->RenameLayer(tmpHolder2->GetName(), inputBuffer); + tmpHolder2 = nullptr; + break; + case RenamingScene: + currentScene->SetName(inputBuffer); + break; + case RenamingTransformable: + tmpHolder1->name = inputBuffer; + tmpHolder1 = nullptr; + break; + + } + inputConfirmed = false; + inputPurpose = Nothing; + strcpy(inputBuffer, ""); // Reset + } +} + +void TSE::EDITOR::HirearchieView::MenuBar() +{ + if(ImGui::BeginMenuBar()) + { + if(ImGui::BeginMenu("Add")) + { + if(ImGui::MenuItem("Empty")) + { + CreateEmpty(currentScene->GetLayerAt(0)); + } + ImGui::Separator(); + if(ImGui::MenuItem("Layer")) + { + activatePopUpNamingLayer = true; + inputPurpose = LayerCreation; + } + ImGui::EndMenu(); + } + } + ImGui::EndMenuBar(); +} + +void TSE::EDITOR::HirearchieView::DisplayLayer(Layer *l) +{ + ImGui::Indent(20.0f); + bool collapseOpen = ImGui::CollapsingHeader(l->GetName().c_str(), ImGuiTreeNodeFlags_DefaultOpen); + if(ImGui::BeginPopupContextItem()) + { + bool disabled = currentScene->GetLayerCount() <= 1; + if (disabled) + { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + if(ImGui::MenuItem("Move Up") && !disabled) + { + MoveUpLayer(l); + } + if(ImGui::MenuItem("Move Down") && !disabled) + { + MoveDownLayer(l); + } + if (disabled) + { + ImGui::PopStyleVar(); + } + if(ImGui::MenuItem("Rename")) + { + strcpy(inputBuffer, l->GetName().c_str()); + activatePopUpNamingLayer = true; + inputPurpose = RenamingLayer; + tmpHolder2 = l; + } + ImGui::Separator(); + AddMenu(nullptr, l); + ImGui::Separator(); + if(ImGui::MenuItem("Delete")) + { + currentScene->RemoveLayer(l->GetName()); + //property display remove if present + + //exit early: + ImGui::EndPopup(); + ImGui::Unindent(20.0f); + return; + } + ImGui::EndPopup(); + } + if(ImGui::BeginDragDropTarget()) + { + auto payload = ImGui::AcceptDragDropPayload("OBJ_TRANS_PTR"); + if(payload != nullptr) + { + auto t2 = *(Transformable**)payload->Data; + HandleDropping(l, t2); + } + ImGui::EndDragDropTarget(); + } + if(collapseOpen) + { + for(int i = 0; i < l->GetAllObjects().size(); i++) + { + DisplayObj(l->GetAllObjects()[i], l); + } + } + ImGui::Unindent(20.0f); +} + +void TSE::EDITOR::HirearchieView::DisplayObj(Transformable *t, Layer *l) +{ + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanFullWidth; + if(t->GetChildren().size() == 0) + { + flags |= ImGuiTreeNodeFlags_Leaf; + } + if(selected == t->id) + { + flags |= ImGuiTreeNodeFlags_Selected; + } + bool open = ImGui::TreeNodeEx((t->GetName() + "##" + to_string(t->id)).c_str(), flags); + if(ImGui::BeginPopupContextItem()) + { + bool disabled = false; + if(t->GetParent() != nullptr) + disabled = t->GetParent()->GetChildren().size() < 1; + else + disabled = l->GetAllObjects().size() <= 1; + if (disabled) + { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + if(ImGui::MenuItem("Move Up") && !disabled) + { + t->MoveUp(l); + } + if(ImGui::MenuItem("Move Down") && !disabled) + { + t->MoveDown(l); + } + if (disabled) + { + ImGui::PopStyleVar(); + } + if(ImGui::MenuItem("Rename")) + { + strcpy(inputBuffer, t->name.c_str()); + activatePopUpNamingLayer = true; + inputPurpose = RenamingTransformable; + tmpHolder1 = t; + } + ImGui::Separator(); + AddMenu(t, nullptr); + ImGui::Separator(); + if(ImGui::MenuItem("Delete")) + { + t->SetParent(nullptr); + int layerCount = currentScene->GetLayerCount(); + for (int i = 0; i < layerCount; i++) + { + auto layer = currentScene->GetLayerAt(i); + if(layer->HasTransformable(t)) + { + layer->RemoveTransformable(t); + break; + } + } + Transformable::HardDelete(t->id); + PropertiesView::ForceClearInspectorElement(); + + //exit early: + ImGui::EndPopup(); + if(open) + { + ImGui::TreePop(); + } + return; + } + ImGui::EndPopup(); + } + if(ImGui::BeginDragDropSource()) + { + auto ptr = &t; + ImGui::SetDragDropPayload("OBJ_TRANS_PTR", ptr, sizeof(ptr)); + std::string name = t->GetName(); + ImGui::Text((std::string("Move \"") + t->GetName() + "\"").c_str()); + ImGui::EndDragDropSource(); + } + if(ImGui::BeginDragDropTarget()) + { + auto payload = ImGui::AcceptDragDropPayload("OBJ_TRANS_PTR"); + if(payload != nullptr) + { + auto t2 = *(Transformable**)payload->Data; + HandleDropping(t, t2); + } + ImGui::EndDragDropTarget(); + } + if(ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) + { + selected = t->id; + PropertiesView::SetInspectorElement(InspectableType::Transformable, t); + } + if(open) + { + for(int i = 0; i < t->GetChildren().size(); i++) + { + DisplayObj(t->GetChildren()[i], nullptr); + } + ImGui::TreePop(); + } +} + +void TSE::EDITOR::HirearchieView::HandleDropping(Layer *target, Transformable *payload) +{ + payload->SetParent(nullptr); + int layerCount = currentScene->GetLayerCount(); + for (int i = 0; i < layerCount; i++) + { + auto layer = currentScene->GetLayerAt(i); + if(layer == target && layer->HasTransformable(payload)) return; + if(layer->HasTransformable(payload)) + { + layer->RemoveTransformable(payload); + break; + } + } + target->AddTransformable(payload); +} + +void TSE::EDITOR::HirearchieView::HandleDropping(Transformable *target, Transformable *payload) +{ + if(target->id == payload->id) return; + if(payload->IsMyChild(target)) return; + int layerCount = currentScene->GetLayerCount(); + for (int i = 0; i < layerCount; i++) + { + auto layer = currentScene->GetLayerAt(i); + if(layer->HasTransformable(payload)) + { + layer->RemoveTransformable(payload); + break; + } + } + payload->SetParent(target); +} + +void TSE::EDITOR::HirearchieView::AddMenu(Transformable *t, Layer *l) +{ + if(t== nullptr && l == nullptr) //Scene + { + if(ImGui::MenuItem("Add Layer")) + { + activatePopUpNamingLayer = true; + inputPurpose = LayerCreation; + } + } + else //Layer and Transformable + { + if(ImGui::MenuItem("Add Empty")) + { + if(t == nullptr) + CreateEmpty(l); + else + CreateEmpty(t); + } + + } +} + +void TSE::EDITOR::HirearchieView::MoveUpLayer(Layer *t) +{ + if(currentScene->GetLayerAt(0) == t) return; + currentScene->MoveLayerUp(t); +} + +void TSE::EDITOR::HirearchieView::MoveDownLayer(Layer *t) +{ + int lc = currentScene->GetLayerCount(); + if(currentScene->GetLayerAt(lc - 1) == t) return; + currentScene->MoveLayerDown(t); +} + +void TSE::EDITOR::HirearchieView::CreateEmpty(Transformable *parent) +{ + Transformable* t = new Transformable("Unnamed Object"); + if(parent != nullptr) + { + t->SetParent(parent); + } + // else + // { + // // TODO: need to chose a layer to put it in. I would suggest the first one? + // currentScene->GetLayerAt(0)->AddTransformable(t); + // } +} + +void TSE::EDITOR::HirearchieView::CreateEmpty(Layer *parent) +{ + Transformable* t = new Transformable("Unnamed Object"); + if(parent != nullptr) + { + parent->AddTransformable(t); + } +} + +void TSE::EDITOR::HirearchieView::CreateLayer() +{ + Layer* l = new Layer(inputBuffer); + currentScene->AddLayer(l); +} diff --git a/TSE_Editor/src/UI/windows/HirearchieView.hpp b/TSE_Editor/src/UI/windows/HirearchieView.hpp new file mode 100644 index 0000000..7add1a7 --- /dev/null +++ b/TSE_Editor/src/UI/windows/HirearchieView.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include "uuid.h" +#include "elements/Scene.hpp" + +namespace TSE::EDITOR +{ + class HirearchieView : public GuiWindow + { + private: + enum NamingPopUpPorPuse + { + Nothing = 0, + LayerCreation = 1, + RenamingLayer = 2, + RenamingTransformable = 3, + RenamingScene = 4 + }; + Scene* currentScene; + uuids::uuid selected = uuids::uuid(); + + bool openPopUpNamingLayer = false; + bool activatePopUpNamingLayer = false; + char inputBuffer[128] = ""; + bool inputConfirmed = false; + NamingPopUpPorPuse inputPurpose = Nothing; + Transformable* tmpHolder1 = nullptr; + Layer* tmpHolder2 = nullptr; + + public: + HirearchieView(Scene* s); + void SetScene(Scene* s); + void Define() override; + void MenuBar(); + void DisplayLayer(Layer* l); + void DisplayObj(Transformable* t, Layer* l); + void HandleDropping(Layer* target, Transformable* payload); + void HandleDropping(Transformable* target, Transformable* payload); + void AddMenu(Transformable* t, Layer* l); + void MoveUpLayer(Layer* t); + void MoveDownLayer(Layer* t); + void CreateEmpty(Transformable* parent); + void CreateEmpty(Layer* parent); + void CreateLayer(); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/PropertiesView.cpp b/TSE_Editor/src/UI/windows/PropertiesView.cpp new file mode 100644 index 0000000..041ca57 --- /dev/null +++ b/TSE_Editor/src/UI/windows/PropertiesView.cpp @@ -0,0 +1,48 @@ +#include "PropertiesView.hpp" + +TSE::EDITOR::PropertiesView::PropertiesView() : GuiWindow("Properies", ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoCollapse) { } + +void TSE::EDITOR::PropertiesView::Define() +{ + MenuBar(); + if(currentlyInspecting.type == InspectableType::None) return; + + ElementDrawer::Draw(currentlyInspecting, debugMode); +} + +void TSE::EDITOR::PropertiesView::MenuBar() +{ + if(ImGui::BeginMenuBar()) + { + if(ImGui::BeginMenu("...")) + { + if(ImGui::MenuItem("Debug")) + { + debugMode = !debugMode; + } + ImGui::EndMenu(); + } + bool style = locked; + if(style) ImGui::PushStyleColor(ImGuiCol_Text, {0,1,0,1}); + if(ImGui::MenuItem("Lock")) locked = !locked; + if(style) ImGui::PopStyleColor(); + } + ImGui::EndMenuBar(); +} + +void TSE::EDITOR::PropertiesView::SetInspectorElement(InspectableType type, void *element) +{ + if(!locked) + currentlyInspecting = Inspectable(type, element); +} + +void TSE::EDITOR::PropertiesView::ClearInspectorElement() +{ + if(!locked) + currentlyInspecting = Inspectable(InspectableType::None, nullptr); +} + +void TSE::EDITOR::PropertiesView::ForceClearInspectorElement() +{ + currentlyInspecting = Inspectable(InspectableType::None, nullptr); +} diff --git a/TSE_Editor/src/UI/windows/PropertiesView.hpp b/TSE_Editor/src/UI/windows/PropertiesView.hpp new file mode 100644 index 0000000..372d5b2 --- /dev/null +++ b/TSE_Editor/src/UI/windows/PropertiesView.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include "UI/ElementDrawer.hpp" + +namespace TSE::EDITOR +{ + class PropertiesView : public GuiWindow + { + private: + inline static Inspectable currentlyInspecting = {}; + bool debugMode = false; + inline static bool locked = false; + + public: + PropertiesView(); + + void Define() override; + void MenuBar(); + static void SetInspectorElement(InspectableType type, void* element); + static void ClearInspectorElement(); + static void ForceClearInspectorElement(); + }; +} // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/SceneView.cpp b/TSE_Editor/src/UI/windows/SceneView.cpp new file mode 100644 index 0000000..596c0b6 --- /dev/null +++ b/TSE_Editor/src/UI/windows/SceneView.cpp @@ -0,0 +1,21 @@ +#include "SceneView.hpp" + +TSE::EDITOR::SceneView::SceneView(TSE::GLFW::RenderTexture *frameBuffer) : GuiWindow("Scene", ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar) +{ + fb = frameBuffer; +} + +void TSE::EDITOR::SceneView::Define() +{ + ImGuiWindowFlags flags2 = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar; + if(ImGui::BeginChild("##SceneChild", {0,0}, ImGuiChildFlags_None, flags2)) + { + ImGui::Image(fb->GetTextureId(), {fb->width(), fb->height()},{0,1}, {1,0}); + auto vec2 = ImGui::GetWindowSize(); + if(fb->width() != vec2.x || fb->height() != vec2.y) + { + fb->SetSize({vec2.x, vec2.y}); + } + } + ImGui::EndChild(); +} diff --git a/TSE_Editor/src/UI/windows/SceneView.hpp b/TSE_Editor/src/UI/windows/SceneView.hpp new file mode 100644 index 0000000..aa249af --- /dev/null +++ b/TSE_Editor/src/UI/windows/SceneView.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "UI/base/GuiWindow.h" +#include "RenderTexture.hpp" + +namespace TSE::EDITOR +{ + class SceneView : public GuiWindow + { + private: + TSE::GLFW::RenderTexture* fb; + public: + SceneView(TSE::GLFW::RenderTexture* frameBuffer); + void Define() override; + }; +} // namespace TSE::EDITOR diff --git a/TSE_GlfwOpenGlImpl/src/buffer/FrameBuffer.cpp b/TSE_GlfwOpenGlImpl/src/buffer/FrameBuffer.cpp index 25ca93d..59fe2bf 100644 --- a/TSE_GlfwOpenGlImpl/src/buffer/FrameBuffer.cpp +++ b/TSE_GlfwOpenGlImpl/src/buffer/FrameBuffer.cpp @@ -14,12 +14,16 @@ TSE::GLFW::FrameBuffer::FrameBuffer(const Vector2 &size) void TSE::GLFW::FrameBuffer::Bind() { + glBindRenderbuffer(GL_RENDERBUFFER, depthRboID); + glBindTexture(GL_TEXTURE_2D, textureID); glBindFramebuffer(GL_FRAMEBUFFER, bufferID); } void TSE::GLFW::FrameBuffer::Unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); } TSE::GLFW::FrameBuffer::~FrameBuffer() @@ -69,6 +73,8 @@ void TSE::GLFW::FrameBuffer::Initialize() TSE_LOG(std::to_string(glGetError())); } Unbind(); + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); } void TSE::GLFW::FrameBuffer::LoadFBTexture()