#include "EditorCore.hpp" #include "App.hpp" #include "AppConfig.hpp" #include "EditorAccessories.hpp" #include "EditorAttachmentImpl.hpp" #include "EditorCommandPalette.hpp" #include "EditorNotification.hpp" #include "EditorUtils.hpp" #include "GameObject.hpp" #include "Level.hpp" #include "Macros.hpp" #include "Mesh.hpp" #include "Player.hpp" #include "SceneThings.hpp" #include "ScopeGuard.hpp" #include "VertexIndex.hpp" #include "YCombinator.hpp" #define GLFW_INCLUDE_NONE #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; void EditorInspector::SelectTarget(TargetType type, void* object) { selectedItt = type; selectedItPtr = object; renaming = false; renamingScratchBuffer.clear(); } EditorContentBrowser::EditorContentBrowser(EditorInspector* inspector) : mInspector{ inspector } { } EditorContentBrowser::~EditorContentBrowser() { } void EditorContentBrowser::Show(bool* open) { ImGuiWindowFlags windowFlags; if (mDocked) { // Center window horizontally, align bottom vertically auto& viewportSize = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(ImVec2(viewportSize.x / 2, viewportSize.y), ImGuiCond_Always, ImVec2(0.5f, 1.0f)); ImGui::SetNextWindowSizeRelScreen(0.8f, mBrowserHeight); windowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; } else { windowFlags = 0; } ImGui::Begin("Content Browser", open, windowFlags); ImGui::Splitter(true, kSplitterThickness, &mSplitterLeft, &mSplitterRight, kLeftPaneMinWidth, kRightPaneMinWidth); ImGui::BeginChild("LeftPane", ImVec2(mSplitterLeft - kPadding, 0.0f)); { if (ImGui::Selectable("Settings", mPane == P_Settings)) { mPane = P_Settings; } if (ImGui::Selectable("Ires", mPane == P_Ires)) { mPane = P_Ires; } } ImGui::EndChild(); ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); ImGui::BeginChild("RightPane"); // Fill remaining space switch (mPane) { case P_Settings: { ImGui::Checkbox("Docked", &mDocked); ImGui::SliderFloat("Height", &mBrowserHeight, 0.1f, 1.0f); } break; case P_Ires: { auto itt = mInspector->selectedItt; auto itPtr = mInspector->selectedItPtr; bool isIttIres = itt == EditorInspector::ITT_Ires; if (ImGui::Button("New")) { ImGui::OpenPopup("New Ires"); } if (ImGui::BeginPopup("New Ires")) { for (int i = 0; i < IresObject::KD_COUNT; ++i) { auto kind = static_cast(i); if (ImGui::MenuItem(IresObject::ToString(kind).data())) { auto ires = IresObject::Create(kind); auto [DISCARD, success] = IresManager::instance->Add(ires.get()); if (success) { (void)ires.release(); } } } ImGui::EndPopup(); } ImGui::SameLine(); if (ImGui::Button("Refresh list") || ImGui::IsKeyPressed(ImGuiKey_F5)) { // TODO } ImGui::SameLine(); if (ImGui::Button("Save", !isIttIres)) { auto ires = static_cast(itPtr); IresManager::instance->Save(ires); } ImGui::SameLine(); if (ImGui::Button("Reload", !isIttIres)) { auto ires = static_cast(itPtr); IresManager::instance->Reload(ires); } ImGui::SameLine(); if (ImGui::Button("Rename", !isIttIres) || (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false))) { auto ires = static_cast(itPtr); mInspector->renaming = true; mInspector->renamingScratchBuffer = ires->GetName(); } ImGui::SameLine(); if (ImGui::Button("Delete", !isIttIres) || (isIttIres && ImGui::IsKeyPressed(ImGuiKey_Delete, false))) { ImGui::OpenPopup("Delete Ires"); } bool openedDummy = true; if (ImGui::BeginPopupModal("Delete Ires", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { if (ImGui::Button("Confirm")) { auto ires = static_cast(itPtr); IresManager::instance->Delete(ires); } ImGui::SameLine(); if (ImGui::Button("Cancel")) { ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } auto& objects = IresManager::instance->GetObjects(); for (auto it = objects.begin(); it != objects.end(); ++it) { auto ires = it->second.Get(); auto& name = ires->GetName(); bool selected = itPtr == ires; ImGuiSelectableFlags flags = 0; // When renaming, disable all other entries if (mInspector->renaming && !selected) { flags |= ImGuiSelectableFlags_Disabled; } if (mInspector->renaming && selected) { // State: being renamed ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); ImGui::SetKeyboardFocusHere(); if (ImGui::InputText("##Rename", &mInspector->renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { // Confirm ires->SetName(std::move(mInspector->renamingScratchBuffer)); mInspector->renaming = false; } ImGui::PopStyleVar(2); if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { // Cancel mInspector->renaming = false; } } else { // State: normal if (ImGui::Selectable(name.c_str(), selected, flags)) { mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); } if (ImGui::BeginDragDropSource()) { auto kindName = IresObject::ToString(ires->GetKind()); // Reason: intentionally using pointer as payload ImGui::SetDragDropPayload(kindName.data(), &ires, sizeof(ires)); // NOLINT(bugprone-sizeof-expression) ImGui::Text("%s '%s'", kindName.data(), name.c_str()); ImGui::EndDragDropSource(); } } } } break; } ImGui::EndChild(); ImGui::End(); } namespace ProjectBrussel_UNITY_ID { glm::quat CalcQuaternionFromDegreesEulerAngle(glm::vec3 eulerAngleDegrees) { glm::vec3 eulerAngleRadians; eulerAngleRadians.x = eulerAngleDegrees.x / 180 * M_PI; eulerAngleRadians.y = eulerAngleDegrees.y / 180 * M_PI; eulerAngleRadians.z = eulerAngleDegrees.z / 180 * M_PI; return glm::quat(eulerAngleRadians); } 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; }); } struct GobjTreeNodeShowInfo { EditorInstance* in_editor; GameObject* out_openPopup = nullptr; }; void GobjTreeNode(GobjTreeNodeShowInfo& showInfo, GameObject* object) { auto& inspector = showInfo.in_editor->GetInspector(); auto attachment = object->GetEditorAttachment(); if (!attachment) { attachment = EaGameObject::Create(object).release(); object->SetEditorAttachment(attachment); // NOTE: takes ownership } ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen; if (inspector.selectedItPtr == object) { flags |= ImGuiTreeNodeFlags_Selected; } ImGui::PushID(reinterpret_cast(object)); // BEGIN tree node bool opened = ImGui::TreeNodeEx(attachment->name.c_str(), flags); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { inspector.SelectTarget(EditorInspector::ITT_GameObject, object); } if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) { showInfo.out_openPopup = object; } if (opened) { ImGui::Indent(); for (auto& child : object->GetChildren()) { GobjTreeNode(showInfo, child); } ImGui::Unindent(); } // END tree node ImGui::PopID(); }; #define GAMEOBJECT_CONSTRUCTOR(ClassName) [](GameWorld* world) -> GameObject* { return new ClassName(world); } struct CreatableGameObject { GameObject* (*factory)(GameWorld*); const char* name; GameObject::Kind kind; } creatableGameObjects[] = { { .factory = GAMEOBJECT_CONSTRUCTOR(GameObject), .name = "GameObject", .kind = GameObject::KD_Generic, }, { .factory = GAMEOBJECT_CONSTRUCTOR(SimpleGeometryObject), .name = "Simple Geometry", .kind = GameObject::KD_SimpleGeometry, }, { .factory = GAMEOBJECT_CONSTRUCTOR(BuildingObject), .name = "Building", .kind = GameObject::KD_Building, }, { .factory = GAMEOBJECT_CONSTRUCTOR(LevelWrapperObject), .name = "Level Wrapper", .kind = GameObject::KD_LevelWrapper, }, }; #undef GAMEOBJECT_CONSTRUCTOR } // namespace ProjectBrussel_UNITY_ID EditorInstance::EditorInstance(App* app) : mApp{ app } , mEdContentBrowser(&mEdInspector) { mEditorCamera.name = "Editor Camera"s; mEditorCamera.Move(glm::vec3(0, 0, 1)); mEditorCamera.LookAtAngle(glm::vec3(0, 0, -1)); mEditorCamera.SetHasPerspective(false); } EditorInstance::~EditorInstance() { } void EditorInstance::OnGameStateChanged(bool running) { if (running) { mApp->UnbindActiveCamera(); } else { mApp->BindActiveCamera(&mEditorCamera); } } void EditorInstance::Show() { using namespace ProjectBrussel_UNITY_ID; using namespace Tags; auto world = mApp->GetWorld(); auto& io = ImGui::GetIO(); ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("View")) { ImGui::MenuItem("ImGui Demo", nullptr, &mWindowVisible_ImGuiDemo); ImGui::MenuItem("Command Palette", "Ctrl+Shift+P", &mWindowVisible_CommandPalette); ImGui::MenuItem("Inspector", nullptr, &mWindowVisible_Inspector); ImGui::MenuItem("Content Browser", "Ctrl+Space", &mWindowVisible_ContentBrowser); ImGui::MenuItem("World Structure", nullptr, &mWindowVisible_WorldStructure); ImGui::MenuItem("World Properties", nullptr, &mWindowVisible_WorldProperties); ImGui::EndMenu(); } ImGui::EndMainMenuBar(); if (mWindowVisible_ImGuiDemo) { ImGui::ShowDemoWindow(&mWindowVisible_ImGuiDemo); } if (io.KeyCtrl && io.KeyShift && ImGui::IsKeyPressed(GLFW_KEY_P, false)) { mWindowVisible_CommandPalette = !mWindowVisible_CommandPalette; } if (mWindowVisible_CommandPalette) { mEdCommandPalette.Show(&mWindowVisible_CommandPalette); } if (io.KeyCtrl && ImGui::IsKeyPressed(GLFW_KEY_SPACE, false)) { mWindowVisible_ContentBrowser = !mWindowVisible_ContentBrowser; } if (mWindowVisible_ContentBrowser) { mEdContentBrowser.Show(&mWindowVisible_ContentBrowser); } auto& camera = *mApp->GetActiveCamera(); ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList()); auto cameraPos = camera.pos; { if (ImGui::IsMouseClicked(ImGuiMouseButton_Right) && !io.WantCaptureMouse && !mDragCam_Happening) { mDragCam_CamInitial = camera.pos; mDragCam_CursorInitial = ImGui::GetMousePos(); mDragCam_Happening = true; } if (mDragCam_Happening) { auto newPos = ImGui::GetMousePos(); // NOTE: we are emulating as if the mouse is dragging the "canvas", through moving the camera in the opposite direction of the natural position delta cameraPos.x = mDragCam_CamInitial.x + mDragCam_CursorInitial.x - newPos.x; cameraPos.y = mDragCam_CamInitial.y + -(mDragCam_CursorInitial.y - newPos.y); // Invert Y delta because ImGui uses top-left origin (mouse moving down translates to positive value, but in our coordinate system down is negative) // Draw movement indicator auto drawList = ImGui::GetForegroundDrawList(); ImGui::DrawArrow(drawList, ImVec2(mDragCam_CursorInitial.x, mDragCam_CursorInitial.y), newPos, IM_COL32(0, 255, 255, 255)); } if (ImGui::IsMouseReleased(ImGuiMouseButton_Right)) { mDragCam_Happening = false; } if (mMoveCamKeyboard) { constexpr float kCameraMoveSpeed = 5.0f; if (ImGui::IsKeyDown(ImGuiKey_W)) { cameraPos.y += kCameraMoveSpeed; } if (ImGui::IsKeyDown(ImGuiKey_S)) { cameraPos.y -= kCameraMoveSpeed; } if (ImGui::IsKeyDown(ImGuiKey_A)) { cameraPos.x -= kCameraMoveSpeed; } if (ImGui::IsKeyDown(ImGuiKey_D)) { cameraPos.x += kCameraMoveSpeed; } } } camera.Move(cameraPos); if (mWindowVisible_Inspector) { ImGui::Begin("Inspector"); switch (mEdInspector.selectedItt) { case EditorInspector::ITT_GameObject: { auto object = static_cast(mEdInspector.selectedItPtr); ShowInspector(object); } break; case EditorInspector::ITT_Ires: { auto ires = static_cast(mEdInspector.selectedItPtr); ShowInspector(ires); } break; case EditorInspector::ITT_None: break; } ImGui::End(); } if (mWindowVisible_WorldProperties) { ImGui::Begin("World properties"); ShowWorldProperties(); ImGui::End(); } if (mWindowVisible_WorldStructure) { ImGui::Begin("World structure"); GobjTreeNodeShowInfo showInfo{ .in_editor = this, }; GobjTreeNode(showInfo, &world->GetRoot()); if (showInfo.out_openPopup) { mPopupCurrent_GameObject = showInfo.out_openPopup; ImGui::OpenPopup("GameObject Popup"); ImGui::SetNextWindowPos(ImGui::GetMousePos()); } if (ImGui::BeginPopup("GameObject Popup")) { // Target no longer selected during popup open if (!mPopupCurrent_GameObject) { ImGui::CloseCurrentPopup(); } if (ImGui::BeginMenu("Add child")) { for (size_t i = 0; i < std::size(creatableGameObjects); ++i) { auto& info = creatableGameObjects[i]; if (ImGui::MenuItem(info.name)) { auto object = info.factory(world); mPopupCurrent_GameObject->AddChild(object); } } ImGui::EndMenu(); } ImGui::Separator(); if (ImGui::MenuItem("Remove")) { ImGui::DialogConfirmation("Are you sure you want to delete this GameObject?", [object = mPopupCurrent_GameObject](bool yes) { object->RemoveSelfFromParent(); delete object; }); } ImGui::EndPopup(); } ImGui::End(); } ShowSpriteViewer(); ImGui::ShowDialogs(); ImGui::ShowNotifications(); } void EditorInstance::ShowWorldProperties() { if (mApp->IsGameRunning()) { if (ImGui::Button("Pause")) { mApp->SetGameRunning(false); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("The game is currently running. Click to pause."); } } else { if (ImGui::Button("Play")) { mApp->SetGameRunning(true); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("The game is currently paused. Click to run."); } } auto& camera = *mApp->GetActiveCamera(); ImGui::TextUnformatted("Active camera:"); ImGui::Indent(); #define CAMERA_POS_TEXT "at (%.3f, %.3f, %.3f)\nlooking at (%.3f, %.3f, %.3f)", camera.pos.x, camera.pos.y, camera.pos.z, camera.lookAt.x, camera.lookAt.y, camera.lookAt.z ImGui::TextUnformatted(camera.name.c_str()); ImGui::Text(CAMERA_POS_TEXT); if (ImGui::BeginPopupContextItem("##CTXMENU")) { if (ImGui::MenuItem("Reset to origin")) { camera.Move(glm::vec3(1.0f, 1.0f, 1.0f)); } if (ImGui::MenuItem("Copy")) { char buffer[2048]; int result = snprintf(buffer, sizeof(buffer), CAMERA_POS_TEXT); ImGui::SetClipboardText(buffer); ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Copied to clipboard.")); } ImGui::EndPopup(); } #undef CAMERA_POS_TEXT ImGui::Checkbox("Move camera with WASD", &mMoveCamKeyboard); ImGui::Unindent(); } // TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism void EditorInstance::ShowInspector(IresObject* ires) { ires->ShowEditor(*this); } void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; auto& io = ImGui::GetIO(); auto objectEa = static_cast(object->GetEditorAttachment()); auto ShowInspector = [&](/*array[6]*/ float* bounds = nullptr) { glm::mat4 identityMatrix(1.00f); if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_T)) mGuizmo.currOperation = ImGuizmo::TRANSLATE; if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_R)) mGuizmo.currOperation = ImGuizmo::ROTATE; if (io.KeyAlt && ImGui::IsKeyPressed(ImGuiKey_S)) mGuizmo.currOperation = ImGuizmo::SCALE; if (ImGui::RadioButton("Translate", mGuizmo.currOperation == ImGuizmo::TRANSLATE)) mGuizmo.currOperation = ImGuizmo::TRANSLATE; ImGui::SameLine(); if (ImGui::RadioButton("Rotate", mGuizmo.currOperation == ImGuizmo::ROTATE)) mGuizmo.currOperation = ImGuizmo::ROTATE; ImGui::SameLine(); if (ImGui::RadioButton("Scale", mGuizmo.currOperation == ImGuizmo::SCALE)) mGuizmo.currOperation = ImGuizmo::SCALE; ImGui::SameLine(); if (ImGui::RadioButton("Universal", mGuizmo.currOperation == ImGuizmo::UNIVERSAL)) mGuizmo.currOperation = ImGuizmo::UNIVERSAL; if (mGuizmo.currOperation != ImGuizmo::SCALE) { if (ImGui::RadioButton("Local", mGuizmo.currMode == ImGuizmo::LOCAL)) mGuizmo.currMode = ImGuizmo::LOCAL; ImGui::SameLine(); if (ImGui::RadioButton("World", mGuizmo.currMode == ImGuizmo::WORLD)) mGuizmo.currMode = ImGuizmo::WORLD; } ImGui::Checkbox("##UseSnap", &mGuizmo.useSnap); ImGui::SameLine(); switch (mGuizmo.currOperation) { case ImGuizmo::TRANSLATE: ImGui::InputFloat3("Pos Snap", &mGuizmo.snap[0]); break; case ImGuizmo::ROTATE: ImGui::InputFloat("Angle Snap", &mGuizmo.snap[0]); break; case ImGuizmo::SCALE: ImGui::InputFloat("Scale Snap", &mGuizmo.snap[0]); break; default: break; } if (bounds) { ImGui::Checkbox("Bound Sizing", &mGuizmo.boundSizing); if (mGuizmo.boundSizing) { ImGui::PushID(3); ImGui::Checkbox("##BoundSizing", &mGuizmo.boundSizingSnap); ImGui::SameLine(); ImGui::InputFloat3("Bound Snap", mGuizmo.boundsSnap); ImGui::PopID(); } } ImGui::Separator(); auto objectPos = object->GetPos(); if (ImGui::InputFloat3("Translation", &objectPos.x)) { object->SetPos(objectPos); } auto objectRot = object->GetRotation(); if (ImGui::InputFloat3("Rotation", &objectEa->eulerAnglesRotation.x)) { objectRot = CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation); object->SetRotation(objectRot); } auto objectScale = object->GetScale(); if (ImGui::InputFloat3("Scale", &objectScale.x)) { object->SetScale(objectScale); } }; auto ShowGuizmo = [&](/*array[6]*/ float* bounds = nullptr) { glm::mat4 identityMatrix(1.00f); // TODO get rid of this massive hack: how to manage const better for intuitively read-only, but write doesn't-care data? auto& lastFrameInfo = const_cast(mApp->mWorldRenderer.GetLastFrameInfo()); auto& cameraViewMat = lastFrameInfo.matrixView; float* cameraView = &cameraViewMat[0][0]; auto& cameraProjMat = lastFrameInfo.matrixProj; float* cameraProj = &cameraProjMat[0][0]; auto objectPos = object->GetPos(); auto objectScale = object->GetScale(); float matrix[16]; ImGuizmo::RecomposeMatrixFromComponents(&objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x, matrix); bool edited = ImGuizmo::Manipulate( cameraView, cameraProj, mGuizmo.currOperation, mGuizmo.currMode, matrix, nullptr, mGuizmo.useSnap ? mGuizmo.snap : nullptr, mGuizmo.boundSizing ? bounds : nullptr, (bounds && mGuizmo.boundSizingSnap) ? mGuizmo.boundsSnap : nullptr); if (edited) { ImGuizmo::DecomposeMatrixToComponents(matrix, &objectPos.x, &objectEa->eulerAnglesRotation.x, &objectScale.x); object->SetPos(objectPos); object->SetRotation(CalcQuaternionFromDegreesEulerAngle(objectEa->eulerAnglesRotation)); object->SetScale(objectScale); } }; auto type = object->GetKind(); switch (type) { case GameObject::KD_Player: { ShowInspector(); ShowGuizmo(); ImGui::Separator(); auto player = static_cast(object); auto ea = static_cast(objectEa); auto& kb = player->keybinds; ImGui::Text("Player #%d", player->GetId()); ImGui::TextUnformatted("Spritesheet: "); ImGui::SameLine(); IresObject::ShowReferenceSafe(*this, ea->confSprite.Get()); if (ImGui::BeginDragDropTarget()) { if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(IresObject::KD_Spritesheet).data())) { auto spritesheet = *static_cast(payload->Data); ea->confSprite.Attach(spritesheet); auto def = spritesheet->GetInstance(); player->sprite.SetDefinition(def); player->renderObject.autofill_TextureAtlas.Attach(def->GetAtlas()); } ImGui::EndDragDropTarget(); } ImGui::TextUnformatted("Material: "); ImGui::SameLine(); IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get()); if (ImGui::BeginDragDropTarget()) { if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(IresObject::KD_Material).data())) { auto material = *static_cast(payload->Data); ea->confMaterial.Attach(material); player->SetMaterial(material->GetInstance()); } ImGui::EndDragDropTarget(); } 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 GameObject::KD_SimpleGeometry: { auto sg = static_cast(object); float bounds[6] = { /*x1*/ -sg->GetSize().x / 2.0f, /*y1*/ -sg->GetSize().y / 2.0f, /*z1*/ 0.0f, /*x2*/ +sg->GetSize().x / 2.0f, /*y2*/ +sg->GetSize().y / 2.0f, /*z2*/ 0.0f, }; ShowInspector(bounds); ShowGuizmo(bounds); ImGui::Separator(); sg->SetSize(glm::vec2(bounds[3] - bounds[0], bounds[4] - bounds[1])); auto size = sg->GetSize(); if (ImGui::InputFloat2("Size", &size.x)) { sg->SetSize(size); } } break; case GameObject::KD_Building: { ShowInspector(); ShowGuizmo(); ImGui::Separator(); auto b = static_cast(object); // TODO } break; case GameObject::KD_LevelWrapper: { ShowInspector(); ShowGuizmo(); ImGui::Separator(); auto lwo = static_cast(object); // TODO } break; default: { ShowInspector(); ShowGuizmo(); } break; } } void EditorInstance::OpenSpriteViewer(SpriteDefinition* sprite) { mSpriteView_Instance.Attach(sprite); mSpriteView_Frame = 0; mSpriteView_OpenNextFrame = true; } void EditorInstance::ShowSpriteViewer() { if (mSpriteView_Instance == nullptr) return; if (!mSpriteView_Instance->IsValid()) return; if (mSpriteView_OpenNextFrame) { mSpriteView_OpenNextFrame = false; ImGui::OpenPopup("Sprite Viewer"); } bool windowOpen = true; if (ImGui::BeginPopupModal("Sprite Viewer", &windowOpen)) { auto atlas = mSpriteView_Instance->GetAtlas(); auto atlasSize = atlas->GetInfo().size; auto& frames = mSpriteView_Instance->GetFrames(); int frameCount = mSpriteView_Instance->GetFrames().size(); if (ImGui::Button("<")) { --mSpriteView_Frame; } ImGui::SameLine(); ImGui::Text("%d/%d", mSpriteView_Frame + 1, frameCount); ImGui::SameLine(); if (ImGui::Button(">")) { ++mSpriteView_Frame; } // Scrolling down (negative value) should advance, so invert the sign mSpriteView_Frame += -std::round(ImGui::GetIO().MouseWheel); // Clamp mSpriteView_Frame to range [0, frameCount) if (mSpriteView_Frame < 0) { mSpriteView_Frame = frameCount - 1; } else if (mSpriteView_Frame >= frameCount) { mSpriteView_Frame = 0; } auto& currFrame = frames[mSpriteView_Frame]; auto boundingBox = mSpriteView_Instance->GetBoundingBox(); ImGui::Text("Frame size: (%d, %d)", boundingBox.x, boundingBox.y); ImGui::Text("Frame location: (%f, %f) to (%f, %f)", currFrame.u0, currFrame.v0, currFrame.u1, currFrame.v1); ImGui::Image( (ImTextureID)(uintptr_t)atlas->GetHandle(), Utils::FitImage(atlasSize), ImVec2(currFrame.u0, currFrame.v0), ImVec2(currFrame.u1, currFrame.v1)); ImGui::EndPopup(); } }