#include "EditorCore.hpp" #include "App.hpp" #include "AppConfig.hpp" #include "CpuMesh.hpp" #include "EditorAccessories.hpp" #include "EditorAttachmentImpl.hpp" #include "EditorNotification.hpp" #include "EditorUtils.hpp" #include "GameObjectTags.hpp" #include "Level.hpp" #include "Mesh.hpp" #include "Player.hpp" #define GLFW_INCLUDE_NONE #include #include #include #include #include #include #include namespace ProjectBrussel_UNITY_ID { void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { app->PushKeyCaptureCallback([=](int key, int action) { // Allow the user to cancel by pressing Esc if (key == GLFW_KEY_ESCAPE) { return true; } if (action == GLFW_PRESS) { *writeKey = key; *writeKeyStatus = writeKeyStatus; return true; } return false; }); } void ShowShaderName(const Shader* shader) { if (shader) { auto& name = shader->GetName(); bool isAnnoymous = name.empty(); if (isAnnoymous) { ImGui::Text("Shader ", (void*)shader); } else { ImGui::Text("Shader: %s", name.c_str()); } } else { ImGui::TextUnformatted("Shader: "); } } void ShowMaterialName(const Material* material) { if (material) { auto& name = material->GetName(); bool isAnnoymous = name.empty(); if (isAnnoymous) { ImGui::Text("Material: ", (void*)material); } else { ImGui::Text("Material: %s", name.c_str()); } } else { ImGui::TextUnformatted("Material: "); } } } // namespace ProjectBrussel_UNITY_ID EditorInstance::EditorInstance(App* app, GameWorld* world) : mApp{ app } , mWorld{ world } , mEdContentBrowser(this) {} EditorInstance::~EditorInstance() { } void EditorInstance::Show() { if (!mWorld) return; auto& io = ImGui::GetIO(); if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) { mEdContentBrowserVisible = !mEdContentBrowserVisible; } ImGui::Begin("World properties"); ShowWorldProperties(); ImGui::End(); ImGui::Begin("World structure"); ShowGameObjectInTree(&mWorld->GetRoot()); ImGui::End(); ImGui::Begin("Inspector"); switch (mSelectedItt) { case ITT_GameObject: ShowInspector(static_cast(mSelectedItPtr)); break; case ITT_Shader: ShowInspector(static_cast(mSelectedItPtr)); break; case ITT_Material: ShowInspector(static_cast(mSelectedItPtr)); break; case ITT_Ires: ShowInspector("", static_cast(mSelectedItPtr)); break; // TODO case ITT_None: break; } ImGui::End(); if (mEdContentBrowserVisible) { mEdContentBrowser.Show(&mEdContentBrowserVisible); } ShowSpriteViewer(); } void EditorInstance::SelectIt(void* ptr, InspectorTargetType itt) { mSelectedItPtr = ptr; mSelectedItt = itt; } void EditorInstance::ShowWorldProperties() { } // TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism void EditorInstance::ShowInspector(Shader* shader) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; EaShader* attachment; if (auto ea = shader->GetEditorAttachment()) { attachment = static_cast(ea); } else { attachment = new EaShader(); attachment->shader = shader; shader->SetEditorAttachment(attachment); } auto& info = shader->GetInfo(); auto& name = shader->GetName(); bool isAnnoymous = name.empty(); ShowShaderName(shader); if (ImGui::Button("Reload metadata", isAnnoymous)) { shader->LoadMetadataFromFile(shader->GetDesignatedMetadataPath()); } ImGui::SameLine(); if (ImGui::Button("Save metadata", isAnnoymous)) { shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath()); } ImGui::SameLine(); if (ImGui::Button("Gather info")) { shader->GatherInfoShaderIntrospection(); } if (ImGui::CollapsingHeader("Inputs")) { for (auto& input : info.inputs) { input.ShowInfo(); } } if (ImGui::CollapsingHeader("Outputs")) { for (auto& output : info.outputs) { output.ShowInfo(); } } if (ImGui::CollapsingHeader("Uniforms")) { for (auto& uniform : info.uniforms) { uniform->ShowInfo(); } } } void EditorInstance::ShowInspector(Material* material) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; EaMaterial* attachment; if (auto ea = material->GetEditorAttachment()) { attachment = static_cast(ea); } else { attachment = new EaMaterial(); material->SetEditorAttachment(attachment); } auto& name = material->GetName(); bool isAnnoymous = name.empty(); if (isAnnoymous) { ImGui::Text("", (void*)(&material)); } else { if (attachment->isEditingName) { bool save = false; save |= ImGui::InputText("##", &attachment->editingScratch, ImGuiInputTextFlags_EnterReturnsTrue); ImGui::SameLine(); save |= ImGui::Button("Save"); if (save) { bool success = MaterialManager::instance->RenameMaterial(material, attachment->editingScratch); if (success) { attachment->isEditingName = false; } } ImGui::SameLine(); if (ImGui::Button("Cancel")) { attachment->isEditingName = false; } } else { // NOTE: ReadOnly shouldn't write any data into the buffer ImGui::InputText("##", material->mName.data(), name.size() + 1, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (ImGui::Button("Edit")) { attachment->editingScratch = name; // Copy attachment->isEditingName = true; } } } auto shader = material->GetShader(); ShowShaderName(shader); if (ImGui::BeginDragDropTarget()) { if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_DRAG_DROP_SHADER)) { auto shader = *static_cast(payload->Data); material->SetShader(shader); } ImGui::EndDragDropTarget(); } ImGui::SameLine(); if (ImGui::Button("GoTo", shader == nullptr)) { mSelectedItt = ITT_Shader; mSelectedItPtr = shader; } if (!shader) return; auto& info = shader->GetInfo(); if (ImGui::Button("Reload", isAnnoymous)) { material->LoadFromFile(material->GetDesignatedPath()); } ImGui::SameLine(); if (ImGui::Button("Save", isAnnoymous)) { material->SaveToFile(material->GetDesignatedPath()); } for (auto& field : material->mBoundScalars) { auto& decl = static_cast(*info.uniforms[field.infoUniformIndex]); decl.ShowInfo(); ImGui::Indent(); switch (decl.scalarType) { case GL_FLOAT: ImGui::InputFloat("##", &field.floatValue); break; case GL_INT: ImGui::InputInt("##", &field.intValue); break; // TODO proper uint edit? case GL_UNSIGNED_INT: ImGui::InputInt("##", (int32_t*)(&field.uintValue), 0, std::numeric_limits::max()); break; default: ImGui::TextUnformatted("Unsupported scalar type"); break; } ImGui::Unindent(); } for (auto& field : material->mBoundVectors) { auto& decl = static_cast(*info.uniforms[field.infoUniformIndex]); decl.ShowInfo(); ImGui::Indent(); switch (decl.semantic) { case VES_Color1: case VES_Color2: { ImGui::ColorEdit4("##", field.value); } break; default: { ImGui::InputFloat4("##", field.value); } break; } ImGui::Unindent(); } for (auto& field : material->mBoundMatrices) { auto& decl = static_cast(*info.uniforms[field.infoUniformIndex]); decl.ShowInfo(); // TODO } for (auto& field : material->mBoundTextures) { auto& decl = static_cast(*info.uniforms[field.infoUniformIndex]); decl.ShowInfo(); // TODO } } void EditorInstance::ShowInspector(const std::string& path, IresObject* genericIres) { ImGui::Text("%s", path.c_str()); switch (genericIres->GetKind()) { case IresObject::KD_Texture: { ImGui::TextUnformatted("Texture"); ImGui::Separator(); // TODO ImGui::TextUnformatted("Unimplemented"); } break; case IresObject::KD_SpriteFiles: { ImGui::TextUnformatted("Sprite Files"); ImGui::Separator(); // TODO ImGui::TextUnformatted("Unimplemented"); } break; case IresObject::KD_Spritesheet: { ImGui::TextUnformatted("Spritesheet"); ImGui::Separator(); auto& ires = *static_cast(genericIres); auto instance = ires.GetInstance(); // NOTE: may be null if (ImGui::Button("View Sprite", instance == nullptr)) { OpenSpriteViewer(instance); } bool doInvalidateInstance = false; if (ImGui::InputText("Spritesheet", &ires.spritesheetFile)) { doInvalidateInstance = true; } if (ImGui::InputInt("Horizontal Split", &ires.sheetWSplit)) { if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit); } if (ImGui::InputInt("Vertical Split", &ires.sheetHSplit)) { if (instance) IresSpritesheet::ResplitSpritesheet(instance, ires.sheetWSplit, ires.sheetHSplit); } if (instance) { auto atlas = instance->GetAtlas(); auto aspectRatio = (float)atlas->GetInfo().size.y / atlas->GetInfo().size.x; ImVec2 size; size.x = ImGui::GetContentRegionAvail().x; size.y = aspectRatio * size.x; ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), size); } else { ImGui::TextUnformatted("Sprite configuration invalid"); } if (doInvalidateInstance) { ires.InvalidateInstance(); } } break; case IresObject::KD_COUNT: break; } } void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; auto type = object->GetTypeTag(); switch (type) { case Tags::GOT_Player: { ShowGameObjecetFields(object); ImGui::Separator(); auto player = static_cast(object); auto& kb = player->keybinds; ImGui::Text("Player #%d", player->GetId()); if (ImGui::Button("Load config")) { bool success = player->LoadFromFile(); if (success) { ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully loaded player config")); } } ImGui::SameLine(); if (ImGui::Button("Save config")) { bool success = player->SaveToFile(); if (success) { ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully saved player config")); } } ImGui::Text("Move left (%s)", ImGui::GetKeyNameGlfw(kb.keyLeft)); ImGui::SameLine(); if (ImGui::Button("Change##Move left")) { PushKeyCodeRecorder(mApp, &kb.keyLeft, &kb.pressedLeft); } ImGui::Text("Move right (%s)", ImGui::GetKeyNameGlfw(kb.keyRight)); ImGui::SameLine(); if (ImGui::Button("Change##Move right")) { PushKeyCodeRecorder(mApp, &kb.keyRight, &kb.pressedRight); } ImGui::Text("Jump (%s)", ImGui::GetKeyNameGlfw(kb.keyJump)); ImGui::SameLine(); if (ImGui::Button("Change##Jump")) { PushKeyCodeRecorder(mApp, &kb.keyJump, &kb.pressedJump); } ImGui::Text("Attack (%s)", ImGui::GetKeyNameGlfw(kb.keyAttack)); ImGui::SameLine(); if (ImGui::Button("Change##Attack")) { PushKeyCodeRecorder(mApp, &kb.keyAttack, &kb.pressedAttack); } } break; case Tags::GOT_LevelWrapper: { ShowGameObjecetFields(object); ImGui::Separator(); auto lwo = static_cast(object); // TODO } break; default: ShowGameObjecetFields(object); break; } } void EditorInstance::ShowGameObjecetFields(GameObject* object) { auto pos = object->GetPos(); if (ImGui::InputFloat3("Position", &pos.x)) { object->SetPos(pos); } auto quat = object->GetRotation(); if (ImGui::InputFloat4("Rotation", &quat.x)) { object->SetRotation(quat); } } void EditorInstance::ShowGameObjectInTree(GameObject* object) { auto attachment = object->GetEditorAttachment(); if (!attachment) { attachment = EaGameObject::Create(object).release(); object->SetEditorAttachment(attachment); // NOTE: takes ownership } ImGuiTreeNodeFlags flags = 0; flags |= ImGuiTreeNodeFlags_DefaultOpen; flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick; flags |= ImGuiTreeNodeFlags_OpenOnArrow; flags |= ImGuiTreeNodeFlags_SpanAvailWidth; if (mSelectedItPtr == object) { flags |= ImGuiTreeNodeFlags_Selected; } if (ImGui::TreeNodeEx(attachment->name.c_str(), flags)) { if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { mSelectedItPtr = object; mSelectedItt = ITT_GameObject; } for (auto& child : object->GetChildren()) { ShowGameObjectInTree(child); } ImGui::TreePop(); } } void EditorInstance::OpenSpriteViewer(Sprite* sprite) { mSpriteView_Instance.Attach(sprite); ImGui::OpenPopup("Sprite Viewer"); } void EditorInstance::ShowSpriteViewer() { if (ImGui::BeginPopup("Sprite Viewer")) { // TODO ImGui::EndPopup(); } }