aboutsummaryrefslogtreecommitdiff
path: root/source/EditorCorePrivate.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2022-05-07 17:27:01 -0700
committerrtk0c <[email protected]>2022-05-07 17:27:01 -0700
commit9fcdfe312fd9809a1cd52c08e7d8d7bd991a9fb3 (patch)
treec9820ea29a0d38c2adb7464386233415bfcccc10 /source/EditorCorePrivate.cpp
parentf348347f205f51800d0628021f193e63f5833f8d (diff)
Changeset: 25 Reduce dependency between editor headers and main game headers
Diffstat (limited to 'source/EditorCorePrivate.cpp')
-rw-r--r--source/EditorCorePrivate.cpp827
1 files changed, 827 insertions, 0 deletions
diff --git a/source/EditorCorePrivate.cpp b/source/EditorCorePrivate.cpp
new file mode 100644
index 0000000..35378ce
--- /dev/null
+++ b/source/EditorCorePrivate.cpp
@@ -0,0 +1,827 @@
+#include "EditorCorePrivate.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 <GLFW/glfw3.h>
+
+#include <imgui.h>
+#include <misc/cpp/imgui_stdlib.h>
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+
+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<IresObject::Kind>(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<IresObject*>(itPtr);
+ IresManager::instance->Save(ires);
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Reload", !isIttIres)) {
+ auto ires = static_cast<IresObject*>(itPtr);
+ IresManager::instance->Reload(ires);
+ }
+
+ ImGui::SameLine();
+ if (ImGui::Button("Rename", !isIttIres) ||
+ (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false)))
+ {
+ auto ires = static_cast<IresObject*>(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<IresObject*>(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<uintptr_t>(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
+
+std::unique_ptr<IEditor> IEditor::CreateInstance(App* app) {
+ return std::make_unique<EditorInstance>(app);
+}
+
+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<GameObject*>(mEdInspector.selectedItPtr);
+ ShowInspector(object);
+ } break;
+
+ case EditorInspector::ITT_Ires: {
+ auto ires = static_cast<IresObject*>(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<EaGameObject*>(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<RendererFrameInfo&>(mApp->GetWorldRenderer()->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<Player*>(object);
+ auto ea = static_cast<EaPlayer*>(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<IresSpritesheet* const*>(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<IresMaterial* const*>(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<SimpleGeometryObject*>(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<BuildingObject*>(object);
+ // TODO
+ } break;
+
+ case GameObject::KD_LevelWrapper: {
+ ShowInspector();
+ ShowGuizmo();
+ ImGui::Separator();
+
+ auto lwo = static_cast<LevelWrapperObject*>(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();
+ }
+}