From cde94efdd44553f3f6575ce84b44c6799e1a1425 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Fri, 6 May 2022 19:52:12 -0700 Subject: Changeset: 22 Improved camera and various cleanups --- source/App.cpp | 44 +++---- source/App.hpp | 13 ++- source/Camera.hpp | 2 + source/EditorCore.cpp | 181 +++++++++++++++++++---------- source/EditorCore.hpp | 12 +- source/EditorNotification.hpp | 2 +- source/EditorUtils.cpp | 261 +++++++++++++++++++++++++++++++++++++++++- source/EditorUtils.hpp | 18 +++ source/GameObject.cpp | 6 + source/GameObject.hpp | 1 + source/Ires.cpp | 6 +- source/Player.cpp | 2 +- source/Sprite.cpp | 2 +- 13 files changed, 457 insertions(+), 93 deletions(-) (limited to 'source') diff --git a/source/App.cpp b/source/App.cpp index 9c18fea..421adb5 100644 --- a/source/App.cpp +++ b/source/App.cpp @@ -1,8 +1,12 @@ #include "App.hpp" +#include #include -App::App() { +using namespace std::literals; + +App::App() + : mActiveCamera{ &mMainCamera } { auto& worldRoot = mWorld.GetRoot(); constexpr int kPlayerCount = 2; @@ -19,18 +23,27 @@ App::App() { SetGameRunning(true); #endif - // TODO this renders nothing, fix - mGameCamera.Move(glm::vec3(0, 0, 1)); - mGameCamera.LookAt(glm::vec3(0, 0, 0)); - mGameCamera.SetHasPerspective(false); - mEditorCamera.Move(glm::vec3(0, 0, 1)); - mEditorCamera.LookAt(glm::vec3(0, 0, 0)); - mEditorCamera.SetHasPerspective(false); + mMainCamera.name = "Main Camera"s; + mMainCamera.Move(glm::vec3(0, 0, 1)); + mMainCamera.LookAt(glm::vec3(0, 0, 0)); + mMainCamera.SetHasPerspective(false); } App::~App() { } +Camera* App::GetActiveCamera() const { + return mActiveCamera; +} + +void App::BindActiveCamera(Camera* camera) { + mActiveCamera = camera; +} + +void App::UnbindActiveCamera() { + mActiveCamera = &mMainCamera; +} + bool App::IsGameRunning() const { return mGameRunning; } @@ -70,18 +83,11 @@ void App::Show() { void App::Update() { if (IsGameRunning()) { mWorld.Update(); - mEditor->OnUpdate(); } } void App::Draw(float currentTime, float deltaTime) { - Camera* camera; - if (IsGameRunning()) { - camera = &mGameCamera; - } else { - camera = &mEditorCamera; - } - mRenderer.BeginFrame(*camera, currentTime, deltaTime); + mWorldRenderer.BeginFrame(*mActiveCamera, currentTime, deltaTime); PodVector stack; stack.push_back(&mWorld.GetRoot()); @@ -95,12 +101,10 @@ void App::Draw(float currentTime, float deltaTime) { } auto renderObjects = obj->GetRenderObjects(); - mRenderer.Draw(renderObjects.data(), renderObjects.size()); + mWorldRenderer.Draw(renderObjects.data(), renderObjects.size()); } - mRenderer.EndFrame(); - // TODO pass camera info to editor without strong coupling to Renderer - mEditor->OnDraw(mRenderer.GetLastFrameInfo()); + mWorldRenderer.EndFrame(); } void App::HandleMouse(int button, int action) { diff --git a/source/App.hpp b/source/App.hpp index 26857db..54e1fb5 100644 --- a/source/App.hpp +++ b/source/App.hpp @@ -17,15 +17,18 @@ using KeyCaptureCallback = std::function; +// TODO how should we split responsibilities between App and Editor? class App { + friend class EditorInstance; + private: std::deque mKeyCaptureCallbacks; PodVector mPlayers; std::unique_ptr mEditor; GameWorld mWorld; - Camera mGameCamera; - Camera mEditorCamera; - Renderer mRenderer; + Renderer mWorldRenderer; + Camera mMainCamera; + Camera* mActiveCamera; // NOTE: should only be true when mEditor != nullptr bool mEditorVisible = false; bool mGameRunning = false; @@ -37,6 +40,10 @@ public: EditorInstance* GetEditor() { return mEditor.get(); } GameWorld* GetWorld() { return &mWorld; } + Camera* GetActiveCamera() const; + void BindActiveCamera(Camera* camera); + void UnbindActiveCamera(); + bool IsGameRunning() const; void SetGameRunning(bool running); diff --git a/source/Camera.hpp b/source/Camera.hpp index d203622..f70d402 100644 --- a/source/Camera.hpp +++ b/source/Camera.hpp @@ -1,9 +1,11 @@ #pragma once #include +#include class Camera { public: + std::string name; glm::vec3 pos; glm::vec3 lookAt; bool perspective = false; diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp index bd5d78d..1841dfb 100644 --- a/source/EditorCore.cpp +++ b/source/EditorCore.cpp @@ -203,21 +203,22 @@ void EditorContentBrowser::Show(bool* open) { ImGui::End(); } -void EditorTransformEdit::ShowWorld(float* cameraView, float* cameraProjection) { - glm::mat4 identityMatrix(1.00f); +void EditorTransformEdit::ShowWorld(Camera* camera) { + // TODO + // glm::mat4 identityMatrix(1.00f); - ImGuiIO& io = ImGui::GetIO(); - float viewManipulateRight = io.DisplaySize.x; - float viewManipulateTop = 0; - static ImGuiWindowFlags gizmoWindowFlags = 0; + // ImGuiIO& io = ImGui::GetIO(); + // float viewManipulateRight = io.DisplaySize.x; + // float viewManipulateTop = 0; + // static ImGuiWindowFlags gizmoWindowFlags = 0; - ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + // ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); - if (showGrid) { - ImGuizmo::DrawGrid(cameraView, cameraProjection, &identityMatrix[0][0], 100.f); - } + // if (showGrid) { + // ImGuizmo::DrawGrid(cameraView, cameraProjection, &identityMatrix[0][0], 100.f); + // } - ImGuizmo::ViewManipulate(cameraView, camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010); + // ImGuizmo::ViewManipulate(cameraView, camDistance, ImVec2(viewManipulateRight - 128, viewManipulateTop), ImVec2(128, 128), 0x10101010); } bool EditorTransformEdit::ShowObjectInspector(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale) { @@ -412,23 +413,22 @@ struct CreatableGameObject { EditorInstance::EditorInstance(App* app) : mApp{ app } - , mFallbackFrameInfo{ - .camera = nullptr, - .matrixCombined = {}, - .time = 0.0f, - .deltaTime = 0.0f, - } - , mCurrentFrameInfo{ &mFallbackFrameInfo } - , mEdContentBrowser(&mEdInspector) {} - -EditorInstance::~EditorInstance() { + , mEdContentBrowser(&mEdInspector) { + mEditorCamera.name = "Editor Camera"s; + mEditorCamera.Move(glm::vec3(0, 0, 1)); + mEditorCamera.LookAt(glm::vec3(0, 0, 0)); + mEditorCamera.SetHasPerspective(false); } -void EditorInstance::OnUpdate() { +EditorInstance::~EditorInstance() { } -void EditorInstance::OnDraw(const RendererFrameInfo& frameInfo) { - mCurrentFrameInfo = &frameInfo; +void EditorInstance::OnGameStateChanged(bool running) { + if (running) { + mApp->UnbindActiveCamera(); + } else { + mApp->BindActiveCamera(&mEditorCamera); + } } void EditorInstance::Show() { @@ -468,17 +468,56 @@ void EditorInstance::Show() { mEdContentBrowser.Show(&mWindowVisible_ContentBrowser); } - auto cameraViewMat = mCurrentFrameInfo->matrixView; - auto cameraView = &cameraViewMat[0][0]; - auto cameraProjMat = mCurrentFrameInfo->matrixProj; - auto cameraProj = &cameraProjMat[0][0]; - // auto cameraView = &mCurrentFrameInfo->matrixView[0][0]; - // auto cameraProj = &mCurrentFrameInfo->matrixProj[0][0]; - // TODO moving camera - // mEdTransformEdit.ShowWorld(cameraView, cameraProj); + auto& camera = *mApp->GetActiveCamera(); ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); ImGuizmo::SetDrawlist(ImGui::GetForegroundDrawList()); + auto cameraPos = camera.pos; + { + mEdTransformEdit.ShowWorld(&camera); + 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; + } + + 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; + } + } + // 2D camera: always look straight ahead + camera.pos = cameraPos; + camera.lookAt = cameraPos; + + // 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]; if (mWindowVisible_Inspector) { ImGui::Begin("Inspector"); switch (mEdInspector.selectedItt) { @@ -516,46 +555,48 @@ void EditorInstance::Show() { if (mWindowVisible_WorldStructure) { ImGui::Begin("World structure"); - { - GobjTreeNodeShowInfo showInfo{ - .in_editor = this, - }; - GobjTreeNode(showInfo, &world->GetRoot()); + GobjTreeNodeShowInfo showInfo{ + .in_editor = this, + }; + GobjTreeNode(showInfo, &world->GetRoot()); - if (showInfo.out_openPopup) { - mPopupCurrent_GameObject = showInfo.out_openPopup; + if (showInfo.out_openPopup) { + mPopupCurrent_GameObject = showInfo.out_openPopup; - ImGui::OpenPopup("GameObject Popup"); - ImGui::SetNextWindowPos(ImGui::GetMousePos()); + 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::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); - } + 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")) { - // TODO } - ImGui::EndPopup(); + 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(); } @@ -575,6 +616,28 @@ void EditorInstance::ShowWorldProperties() { 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.pos = {}; + camera.lookAt = {}; + } + 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::Unindent(); } // TOOD move resource-specific and gameobject-specific inspector code into attachments mechanism diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index 785fc6b..8b70ee0 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -71,23 +71,25 @@ private: bool showGrid = false; public: - void ShowWorld(float* cameraView, float* cameraProjection); + void ShowWorld(Camera* camera); bool ShowObjectInspector(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale); bool ShowObject(float* cameraView, float* cameraProjection, glm::vec3* pos, glm::quat* rotation, glm::vec3* scale); }; +// TODO editor undo stack class App; class EditorInstance { private: App* mApp; - RendererFrameInfo mFallbackFrameInfo; - const RendererFrameInfo* mCurrentFrameInfo; GameObject* mPopupCurrent_GameObject = nullptr; + Camera mEditorCamera; RcPtr mSpriteView_Instance; EditorCommandPalette mEdCommandPalette; EditorInspector mEdInspector; EditorContentBrowser mEdContentBrowser; EditorTransformEdit mEdTransformEdit; + glm::vec3 mDragCam_CamInitial; + ImVec2 mDragCam_CursorInitial; int mSpriteView_Frame; bool mSpriteView_OpenNextFrame = false; bool mWindowVisible_ImGuiDemo = false; @@ -96,13 +98,13 @@ private: bool mWindowVisible_ContentBrowser = true; bool mWindowVisible_WorldStructure = true; bool mWindowVisible_WorldProperties = true; + bool mDragCam_Happening = false; public: EditorInstance(App* app); ~EditorInstance(); - void OnUpdate(); - void OnDraw(const RendererFrameInfo& frameInfo); + void OnGameStateChanged(bool running); void Show(); EditorInspector& GetInspector() { return mEdInspector; } diff --git a/source/EditorNotification.hpp b/source/EditorNotification.hpp index 7bb6dab..3af8c2d 100644 --- a/source/EditorNotification.hpp +++ b/source/EditorNotification.hpp @@ -64,7 +64,7 @@ public: ImVec4 GetColor(); const char* GetIcon(); const char* GetContent(); - ; + uint64_t GetElapsedTime(); ImGuiToastPhase GetPhase(); float GetFadePercent(); diff --git a/source/EditorUtils.cpp b/source/EditorUtils.cpp index 39d66fb..20caef7 100644 --- a/source/EditorUtils.cpp +++ b/source/EditorUtils.cpp @@ -4,8 +4,10 @@ #include #include +#include #include #include +#include const char* ImGui::GetKeyNameGlfw(int key) { return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key)); @@ -163,13 +165,270 @@ bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* return held; } -void ImGui ::AddUnderLine(ImColor col) { +void ImGui::AddUnderLine(ImColor col) { auto min = ImGui::GetItemRectMin(); auto max = ImGui::GetItemRectMax(); min.y = max.y; ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0f); } +void ImGui::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor) { + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/drawing.cpp + // ax::NodeEditor::DrawIcon + + auto rect = ImRect(a, b); + auto rect_x = rect.Min.x; + auto rect_y = rect.Min.y; + auto rect_w = rect.Max.x - rect.Min.x; + auto rect_h = rect.Max.y - rect.Min.y; + auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f; + auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f; + auto rect_center = ImVec2(rect_center_x, rect_center_y); + const auto outline_scale = rect_w / 24.0f; + const auto extra_segments = static_cast(2 * outline_scale); // for full circle + + if (type == IconType::Flow) { + const auto origin_scale = rect_w / 24.0f; + + const auto offset_x = 1.0f * origin_scale; + const auto offset_y = 0.0f * origin_scale; + const auto margin = 2.0f * origin_scale; + const auto rounding = 0.1f * origin_scale; + const auto tip_round = 0.7f; // percentage of triangle edge (for tip) + // const auto edge_round = 0.7f; // percentage of triangle edge (for corner) + const auto canvas = ImRect( + rect.Min.x + margin + offset_x, + rect.Min.y + margin + offset_y, + rect.Max.x - margin + offset_x, + rect.Max.y - margin + offset_y); + const auto canvas_x = canvas.Min.x; + const auto canvas_y = canvas.Min.y; + const auto canvas_w = canvas.Max.x - canvas.Min.x; + const auto canvas_h = canvas.Max.y - canvas.Min.y; + + const auto left = canvas_x + canvas_w * 0.5f * 0.3f; + const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f; + const auto top = canvas_y + canvas_h * 0.5f * 0.2f; + const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f; + const auto center_y = (top + bottom) * 0.5f; + // const auto angle = AX_PI * 0.5f * 0.5f * 0.5f; + + const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top); + const auto tip_right = ImVec2(right, center_y); + const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom); + + drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, top), + ImVec2(left, top), + ImVec2(left, top) + ImVec2(rounding, 0)); + drawList->PathLineTo(tip_top); + drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round); + drawList->PathBezierCubicCurveTo( + tip_right, + tip_right, + tip_bottom + (tip_right - tip_bottom) * tip_round); + drawList->PathLineTo(tip_bottom); + drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0)); + drawList->PathBezierCubicCurveTo( + ImVec2(left, bottom), + ImVec2(left, bottom), + ImVec2(left, bottom) - ImVec2(0, rounding)); + + if (!filled) { + if (innerColor & 0xFF000000) { + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + } + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } else { + drawList->PathFillConvex(color); + } + } else { + auto triangleStart = rect_center_x + 0.32f * rect_w; + + auto rect_offset = -static_cast(rect_w * 0.25f * 0.25f); + + rect.Min.x += rect_offset; + rect.Max.x += rect_offset; + rect_x += rect_offset; + rect_center_x += rect_offset * 0.5f; + rect_center.x += rect_offset * 0.5f; + + if (type == IconType::Circle) { + const auto c = rect_center; + + if (!filled) { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + + if (innerColor & 0xFF000000) + drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments); + drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale); + } else { + drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments); + } + } + + if (type == IconType::Square) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, 0, 15 + extra_segments); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, 0, 15 + extra_segments); + + drawList->AddRect(p0, p1, color, 0, 15 + extra_segments, 2.0f * outline_scale); + } + } + + if (type == IconType::Grid) { + const auto r = 0.5f * rect_w / 2.0f; + const auto w = ceilf(r / 3.0f); + + const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f)); + const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w)); + + auto tl = baseTl; + auto br = baseBr; + for (int i = 0; i < 3; ++i) { + tl.x = baseTl.x; + br.x = baseBr.x; + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + if (i != 1 || filled) + drawList->AddRectFilled(tl, br, color); + tl.x += w * 2; + br.x += w * 2; + drawList->AddRectFilled(tl, br, color); + + tl.y += w * 2; + br.y += w * 2; + } + + triangleStart = br.x + w + 1.0f / 24.0f * rect_w; + } + + if (type == IconType::RoundSquare) { + if (filled) { + const auto r = 0.5f * rect_w / 2.0f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + drawList->AddRectFilled(p0, p1, color, cr, 15); + } else { + const auto r = 0.5f * rect_w / 2.0f - 0.5f; + const auto cr = r * 0.5f; + const auto p0 = rect_center - ImVec2(r, r); + const auto p1 = rect_center + ImVec2(r, r); + + if (innerColor & 0xFF000000) + drawList->AddRectFilled(p0, p1, innerColor, cr, 15); + + drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale); + } + } else if (type == IconType::Diamond) { + if (filled) { + const auto r = 0.607f * rect_w / 2.0f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + drawList->PathFillConvex(color); + } else { + const auto r = 0.607f * rect_w / 2.0f - 0.5f; + const auto c = rect_center; + + drawList->PathLineTo(c + ImVec2(0, -r)); + drawList->PathLineTo(c + ImVec2(r, 0)); + drawList->PathLineTo(c + ImVec2(0, r)); + drawList->PathLineTo(c + ImVec2(-r, 0)); + + if (innerColor & 0xFF000000) + drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor); + + drawList->PathStroke(color, true, 2.0f * outline_scale); + } + } else { + const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f); + + drawList->AddTriangleFilled( + ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f), + ImVec2(triangleStart, rect_center_y + 0.15f * rect_h), + ImVec2(triangleStart, rect_center_y - 0.15f * rect_h), + color); + } + } +} + +void ImGui::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color, const ImVec4& innerColor) { + // Taken from https://github.com/thedmd/imgui-node-editor/blob/master/examples/blueprints-example/utilities/widgets.cpp + // ax::NodeEditor::Icon + + if (ImGui::IsRectVisible(size)) { + auto cursorPos = ImGui::GetCursorScreenPos(); + auto drawList = ImGui::GetWindowDrawList(); + DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor)); + } + + ImGui::Dummy(size); +} + +void ImGui::DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness) { + // Adapted from https://stackoverflow.com/a/6333775 + + using namespace std::numbers; + constexpr float kHeadLength = 10; + + auto angle = std::atan2(to.y - from.y, to.x - from.x); + drawList->AddLine(from, to, color, lineThickness); + drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle - pi / 6), to.y - kHeadLength * std::sin(angle - pi / 6)), color, lineThickness); + drawList->AddLine(to, ImVec2(to.x - kHeadLength * std::cos(angle + pi / 6), to.y - kHeadLength * std::sin(angle + pi / 6)), color, lineThickness); +} + +struct DialogObject { + std::string message; + std::function callback; +}; + +static DialogObject gConfirmationDialog{}; + +void ImGui::DialogConfirmation(std::string message, std::function callback) { + gConfirmationDialog.message = std::move(message); + // TODO is converting void(bool) to void(int) fine? + gConfirmationDialog.callback = std::move(callback); +} + +void ImGui::ShowDialogs() { + if (gConfirmationDialog.callback) { + if (ImGui::BeginPopupModal("Confirmation")) { + ImGui::Text("%s", gConfirmationDialog.message.c_str()); + if (ImGui::Button("Cancel")) { + gConfirmationDialog.callback(false); + gConfirmationDialog.callback = {}; + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Confirm")) { + gConfirmationDialog.callback(true); + gConfirmationDialog.callback = {}; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } +} + float Utils::CalcImageHeight(glm::vec2 original, int targetWidth) { // Xorig / Yorig = Xnew / Ynew // Ynew = Xnew * Yorig / Xorig diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp index b079f13..d832447 100644 --- a/source/EditorUtils.hpp +++ b/source/EditorUtils.hpp @@ -33,6 +33,24 @@ bool Splitter(bool splitVertically, float thickness, float* size1, float* size2, void AddUnderLine(ImColor col); +enum class IconType { + Flow, + Circle, + Square, + Grid, + RoundSquare, + Diamond, +}; + +void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor); +void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0)); + +void DrawArrow(ImDrawList* drawList, ImVec2 from, ImVec2 to, ImU32 color, float lineThickness = 1.0f); + +// NOTE: string is copied into an internal storage +void DialogConfirmation(std::string message, std::function callback); +void ShowDialogs(); + } // namespace ImGui namespace Utils { diff --git a/source/GameObject.cpp b/source/GameObject.cpp index bb86acc..0dccf38 100644 --- a/source/GameObject.cpp +++ b/source/GameObject.cpp @@ -124,6 +124,12 @@ GameObject* GameObject::RemoveChild(GameObject* child) { return nullptr; } +void GameObject::RemoveSelfFromParent() { + if (mParent) { + mParent->RemoveChild(this); + } +} + PodVector GameObject::RemoveAllChildren() { for (auto& child : mChildren) { child->SetParent(nullptr); diff --git a/source/GameObject.hpp b/source/GameObject.hpp index d0f94a5..c30b91b 100644 --- a/source/GameObject.hpp +++ b/source/GameObject.hpp @@ -58,6 +58,7 @@ public: void AddChild(GameObject* child); GameObject* RemoveChild(int index); GameObject* RemoveChild(GameObject* child); + void RemoveSelfFromParent(); PodVector RemoveAllChildren(); EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } diff --git a/source/Ires.cpp b/source/Ires.cpp index 346e6e6..0421a54 100644 --- a/source/Ires.cpp +++ b/source/Ires.cpp @@ -303,10 +303,12 @@ std::pair IresManager::Add(IresObject* ires) { auto& name = ires->mName; if (name.empty()) { int n = std::rand(); +#define IRES_NAME_ERR_MESSAGE "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n // NOTE: does not include null-terminator - int size = snprintf(nullptr, 0, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); + int size = snprintf(nullptr, 0, IRES_NAME_ERR_MESSAGE); name.resize(size); // std::string::resize handles storage for null-terminator alreaedy - snprintf(name.data(), size, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); + snprintf(name.data(), size, IRES_NAME_ERR_MESSAGE); +#undef IRES_NAME_ERR_MESSAGE } auto& uid = ires->mUid; diff --git a/source/Player.cpp b/source/Player.cpp index b37a232..34c4549 100644 --- a/source/Player.cpp +++ b/source/Player.cpp @@ -110,7 +110,7 @@ void Player::HandleKeyInput(int key, int action) { function(file, "attack=%d\n", fieldPrefix keybinds.keyAttack); static FILE* OpenPlayerConfigFile(Player* player, Utils::IoMode mode) { - char path[512]; + char path[2048]; snprintf(path, sizeof(path), "%s/player%d.txt", AppConfig::dataDir.c_str(), player->GetId()); return Utils::OpenCstdioFile(path, mode); diff --git a/source/Sprite.cpp b/source/Sprite.cpp index f855999..0f1a3bb 100644 --- a/source/Sprite.cpp +++ b/source/Sprite.cpp @@ -138,7 +138,7 @@ SpriteDefinition* IresSpritesheet::CreateInstance() const { return nullptr; } - char path[256]; + char path[2048]; snprintf(path, sizeof(path), "%s/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str()); auto atlas = std::make_unique(); -- cgit v1.2.3-70-g09d2