diff options
author | hnOsmium0001 <[email protected]> | 2022-04-18 17:54:29 -0700 |
---|---|---|
committer | hnOsmium0001 <[email protected]> | 2022-04-18 17:54:29 -0700 |
commit | 4b57fe1fb1401bab9439a639bd842ca61386fe22 (patch) | |
tree | ce06c1fc38b65e8f74acf36d1e3ecfa7e56b367a | |
parent | d43508ba4843801cbbf1f42a27af260d4eef5701 (diff) |
Implement IresSpritesheet
-rw-r--r-- | source/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/EditorCore.cpp | 445 | ||||
-rw-r--r-- | source/EditorCore.hpp | 64 | ||||
-rw-r--r-- | source/EditorResources.cpp | 175 | ||||
-rw-r--r-- | source/EditorResources.hpp | 36 | ||||
-rw-r--r-- | source/EditorUtils.cpp | 17 | ||||
-rw-r--r-- | source/EditorUtils.hpp | 15 | ||||
-rw-r--r-- | source/Ires.cpp | 103 | ||||
-rw-r--r-- | source/Ires.hpp | 15 | ||||
-rw-r--r-- | source/Macros.hpp | 6 | ||||
-rw-r--r-- | source/Material.hpp | 21 | ||||
-rw-r--r-- | source/Sprite.cpp | 137 | ||||
-rw-r--r-- | source/Sprite.hpp | 13 |
13 files changed, 689 insertions, 359 deletions
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index bc4bfdb..ea0c4c4 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -6,7 +6,6 @@ PRIVATE EditorAttachmentImpl.cpp EditorCore.cpp EditorNotification.cpp - EditorResources.cpp EditorUtils.cpp GameObject.cpp GraphicsTags.cpp diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp index 0c2e6a2..ec500e0 100644 --- a/source/EditorCore.cpp +++ b/source/EditorCore.cpp @@ -9,8 +9,10 @@ #include "EditorUtils.hpp" #include "GameObjectTags.hpp" #include "Level.hpp" +#include "Macros.hpp" #include "Mesh.hpp" #include "Player.hpp" +#include "ScopeGuard.hpp" #define GLFW_INCLUDE_NONE #include <GLFW/glfw3.h> @@ -18,10 +20,267 @@ #include <ImGuizmo.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("Shaders", mPane == P_Shader)) { + mPane = P_Shader; + } + if (ImGui::Selectable("Materials", mPane == P_Material)) { + mPane = P_Material; + } + 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_Shader: { + if (ImGui::Button("Refresh")) { + // TODO reload shaders while keeping existing references working + } + ImGui::SameLine(); + if (ImGui::Button("Save all")) { + auto& shaders = ShaderManager::instance->GetShaders(); + for (auto&& [DISCARD, shader] : shaders) { + shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath()); + } + } + + auto& shaders = ShaderManager::instance->GetShaders(); + for (auto it = shaders.begin(); it != shaders.end(); ++it) { + auto shader = it->second.Get(); + auto& name = shader->GetName(); + + bool selected = mInspector->selectedItPtr == shader; + if (ImGui::Selectable(name.c_str(), selected)) { + mInspector->SelectTarget(EditorInspector::ITT_Shader, shader); + } + + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as Fpayload + ImGui::SetDragDropPayload(BRUSSEL_TAG_Shader, &shader, sizeof(shader)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("Shader '%s'", name.c_str()); + ImGui::EndDragDropSource(); + } + } + } break; + + case P_Material: { + if (ImGui::Button("New")) { + int n = std::rand(); + auto mat = new Material("Unnamed Material " + std::to_string(n)); + auto guard = GuardDeletion(mat); + auto [DISCARD, inserted] = MaterialManager::instance->SaveMaterial(mat); + if (inserted) { + guard.Dismiss(); + } else { + ImGui::AddNotification(ImGuiToast(ImGuiToastType_Error, "Failed to create material.")); + } + } + ImGui::SameLine(); + if (ImGui::Button("Refresh")) { + // TODO + } + ImGui::SameLine(); + if (ImGui::Button("Save all")) { + auto& mats = MaterialManager::instance->GetMaterials(); + for (auto&& [DISCARD, mat] : mats) { + mat->SaveToFile(mat->GetDesignatedPath()); + } + } + + auto& mats = MaterialManager::instance->GetMaterials(); + for (auto it = mats.begin(); it != mats.end(); ++it) { + auto mat = it->second.Get(); + auto& name = mat->GetName(); + + bool selected = mInspector->selectedItPtr == mat; + if (ImGui::Selectable(name.c_str(), selected)) { + mInspector->SelectTarget(EditorInspector::ITT_Material, mat); + } + + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(BRUSSEL_TAG_Material, &mat, sizeof(mat)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text("Material '%s'", name.c_str()); + ImGui::EndDragDropSource(); + } + } + } 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_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 { void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) { app->PushKeyCaptureCallback([=](int key, int action) { @@ -71,7 +330,7 @@ void ShowMaterialName(const Material* material) { EditorInstance::EditorInstance(App* app, GameWorld* world) : mApp{ app } , mWorld{ world } - , mEdContentBrowser(this) {} + , mEdContentBrowser(&mEdInspector) {} EditorInstance::~EditorInstance() { } @@ -93,12 +352,25 @@ void EditorInstance::Show() { ImGui::End(); ImGui::Begin("Inspector"); - switch (mSelectedItt) { - case ITT_GameObject: ShowInspector(static_cast<GameObject*>(mSelectedItPtr)); break; - case ITT_Shader: ShowInspector(static_cast<Shader*>(mSelectedItPtr)); break; - case ITT_Material: ShowInspector(static_cast<Material*>(mSelectedItPtr)); break; - case ITT_Ires: ShowInspector("", static_cast<IresObject*>(mSelectedItPtr)); break; // TODO - case ITT_None: break; + switch (mEdInspector.selectedItt) { + case EditorInspector::ITT_GameObject: { + ShowInspector(static_cast<GameObject*>(mEdInspector.selectedItPtr)); + } break; + + case EditorInspector::ITT_Shader: { + ShowInspector(static_cast<Shader*>(mEdInspector.selectedItPtr)); + } break; + + case EditorInspector::ITT_Material: { + ShowInspector(static_cast<Material*>(mEdInspector.selectedItPtr)); + } break; + + case EditorInspector::ITT_Ires: { + auto ires = static_cast<IresObject*>(mEdInspector.selectedItPtr); + ShowInspector(ires); + } break; + + case EditorInspector::ITT_None: break; } ImGui::End(); @@ -109,11 +381,6 @@ void EditorInstance::Show() { ShowSpriteViewer(); } -void EditorInstance::SelectIt(void* ptr, InspectorTargetType itt) { - mSelectedItPtr = ptr; - mSelectedItt = itt; -} - void EditorInstance::ShowWorldProperties() { } @@ -212,7 +479,7 @@ void EditorInstance::ShowInspector(Material* material) { auto shader = material->GetShader(); ShowShaderName(shader); if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_DRAG_DROP_SHADER)) { + if (auto payload = ImGui::AcceptDragDropPayload(BRUSSEL_TAG_Shader)) { auto shader = *static_cast<Shader* const*>(payload->Data); material->SetShader(shader); } @@ -220,8 +487,7 @@ void EditorInstance::ShowInspector(Material* material) { } ImGui::SameLine(); if (ImGui::Button("GoTo", shader == nullptr)) { - mSelectedItt = ITT_Shader; - mSelectedItPtr = shader; + mEdInspector.SelectTarget(EditorInspector::ITT_Shader, shader); } if (!shader) return; @@ -280,76 +546,30 @@ void EditorInstance::ShowInspector(Material* material) { } } -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<IresSpritesheet*>(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(IresObject* ires) { + ires->ShowEditor(*this); } void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; + auto ShowFields = [&]() { + 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); + } + }; + auto type = object->GetTypeTag(); switch (type) { case Tags::GOT_Player: { - ShowGameObjecetFields(object); + ShowFields(); ImGui::Separator(); auto player = static_cast<Player*>(object); @@ -397,28 +617,16 @@ void EditorInstance::ShowInspector(GameObject* object) { } break; case Tags::GOT_LevelWrapper: { - ShowGameObjecetFields(object); + ShowFields(); ImGui::Separator(); auto lwo = static_cast<LevelWrapperObject*>(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); + default: { + ShowFields(); + } break; } } @@ -434,14 +642,13 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) { flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick; flags |= ImGuiTreeNodeFlags_OpenOnArrow; flags |= ImGuiTreeNodeFlags_SpanAvailWidth; - if (mSelectedItPtr == object) { + if (mEdInspector.selectedItPtr == object) { flags |= ImGuiTreeNodeFlags_Selected; } if (ImGui::TreeNodeEx(attachment->name.c_str(), flags)) { if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { - mSelectedItPtr = object; - mSelectedItt = ITT_GameObject; + mEdInspector.SelectTarget(EditorInspector::ITT_GameObject, object); } for (auto& child : object->GetChildren()) { @@ -453,12 +660,54 @@ void EditorInstance::ShowGameObjectInTree(GameObject* object) { void EditorInstance::OpenSpriteViewer(Sprite* sprite) { mSpriteView_Instance.Attach(sprite); - ImGui::OpenPopup("Sprite Viewer"); + mSpriteView_Frame = 0; + mSpriteView_OpenNextFrame = true; } void EditorInstance::ShowSpriteViewer() { - if (ImGui::BeginPopup("Sprite Viewer")) { - // TODO + 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(); } } diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index c604f36..2216de5 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -1,7 +1,6 @@ #pragma once #include "EditorAttachment.hpp" -#include "EditorResources.hpp" #include "GameObject.hpp" #include "Ires.hpp" #include "RcPtr.hpp" @@ -11,10 +10,8 @@ #include <memory> #include <string> -class App; -class EditorInstance { -public: - enum InspectorTargetType { +struct EditorInspector { + enum TargetType { ITT_GameObject, ITT_Shader, ITT_Material, @@ -22,14 +19,54 @@ public: ITT_None, }; + std::string renamingScratchBuffer; + void* selectedItPtr = nullptr; + TargetType selectedItt = ITT_None; + bool renaming = false; + + void SelectTarget(TargetType type, void* object); +}; + +class EditorContentBrowser { +private: + enum Pane { + P_Settings, + P_Shader, + P_Material, + P_Ires, + }; + + static constexpr float kSplitterThickness = 3.0f; + static constexpr float kPadding = 4.0f; + + // <root> + static constexpr float kLeftPaneMinWidth = 200.0f; + static constexpr float kRightPaneMinWidth = 200.0f; + + EditorInspector* mInspector; + Pane mPane = P_Settings; + float mBrowserHeight = 0.5f; + float mSplitterLeft = kLeftPaneMinWidth; + float mSplitterRight = 0.0f; + bool mDocked = true; + +public: + EditorContentBrowser(EditorInspector* inspector); + ~EditorContentBrowser(); + + void Show(bool* open = nullptr); +}; + +class App; +class EditorInstance { private: App* mApp; GameWorld* mWorld; - // TODO store more fields for ITT - void* mSelectedItPtr = nullptr; RcPtr<Sprite> mSpriteView_Instance; + EditorInspector mEdInspector; EditorContentBrowser mEdContentBrowser; - InspectorTargetType mSelectedItt = ITT_None; + int mSpriteView_Frame; + bool mSpriteView_OpenNextFrame = false; bool mEdContentBrowserVisible = false; public: @@ -38,21 +75,20 @@ public: void Show(); - void* GetSelectedItPtr() const { return mSelectedItPtr; } - InspectorTargetType GetSelectedItt() const { return mSelectedItt; } - void SelectIt(void* ptr, InspectorTargetType itt); + EditorInspector& GetInspector() { return mEdInspector; } + EditorContentBrowser& GetContentBrowser() { return mEdContentBrowser; } + + void OpenSpriteViewer(Sprite* sprite); private: void ShowWorldProperties(); void ShowInspector(Shader* shader); void ShowInspector(Material* material); - void ShowInspector(const std::string& path, IresObject* ires); + void ShowInspector(IresObject* ires); void ShowInspector(GameObject* object); - void ShowGameObjecetFields(GameObject* object); void ShowGameObjectInTree(GameObject* object); - void OpenSpriteViewer(Sprite* sprite); void ShowSpriteViewer(); }; diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp deleted file mode 100644 index 4f1a7c4..0000000 --- a/source/EditorResources.cpp +++ /dev/null @@ -1,175 +0,0 @@ -#include "EditorResources.hpp" - -#include "EditorCore.hpp" -#include "EditorNotification.hpp" -#include "EditorUtils.hpp" -#include "Macros.hpp" -#include "ScopeGuard.hpp" -#include "Shader.hpp" - -#include <imgui.h> -#include <misc/cpp/imgui_stdlib.h> -#include <cstdlib> -#include <limits> -#include <string> -#include <string_view> - -using namespace std::literals; - -EditorContentBrowser::EditorContentBrowser(EditorInstance* editor) - : mEditor{ editor } { -} - -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("Shaders", mPane == P_Shader)) { - mPane = P_Shader; - } - if (ImGui::Selectable("Materials", mPane == P_Material)) { - mPane = P_Material; - } - 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_Shader: { - if (ImGui::Button("Refresh")) { - // TODO reload shaders while keeping existing references working - } - ImGui::SameLine(); - if (ImGui::Button("Save all")) { - auto& shaders = ShaderManager::instance->GetShaders(); - for (auto&& [DISCARD, shader] : shaders) { - shader->SaveMetadataToFile(shader->GetDesignatedMetadataPath()); - } - } - - auto& shaders = ShaderManager::instance->GetShaders(); - for (auto it = shaders.begin(); it != shaders.end(); ++it) { - auto shader = it->second.Get(); - auto& name = shader->GetName(); - - bool selected = mEditor->GetSelectedItPtr() == shader; - if (ImGui::Selectable(name.c_str(), selected)) { - mEditor->SelectIt(shader, EditorInstance::ITT_Shader); - } - - if (ImGui::BeginDragDropSource()) { - // Reason: intentionally using pointer as Fpayload - ImGui::SetDragDropPayload(BRUSSEL_DRAG_DROP_SHADER, &shader, sizeof(shader)); // NOLINT(bugprone-sizeof-expression) - ImGui::Text("Shader '%s'", name.c_str()); - ImGui::EndDragDropSource(); - } - } - } break; - - case P_Material: { - if (ImGui::Button("New")) { - int n = std::rand(); - auto mat = new Material("Unnamed Material " + std::to_string(n)); - auto guard = GuardDeletion(mat); - auto [DISCARD, inserted] = MaterialManager::instance->SaveMaterial(mat); - if (inserted) { - guard.Dismiss(); - } else { - ImGui::AddNotification(ImGuiToast(ImGuiToastType_Error, "Failed to create material.")); - } - } - ImGui::SameLine(); - if (ImGui::Button("Refresh")) { - // TODO - } - ImGui::SameLine(); - if (ImGui::Button("Save all")) { - auto& mats = MaterialManager::instance->GetMaterials(); - for (auto&& [DISCARD, mat] : mats) { - mat->SaveToFile(mat->GetDesignatedPath()); - } - } - - auto& mats = MaterialManager::instance->GetMaterials(); - for (auto it = mats.begin(); it != mats.end(); ++it) { - auto mat = it->second.Get(); - auto& name = mat->GetName(); - - bool selected = mEditor->GetSelectedItPtr() == mat; - if (ImGui::Selectable(name.c_str(), selected)) { - mEditor->SelectIt(mat, EditorInstance::ITT_Material); - } - - if (ImGui::BeginDragDropSource()) { - // Reason: intentionally using pointer as payload - ImGui::SetDragDropPayload(BRUSSEL_DRAG_DROP_MATERIAL, &mat, sizeof(mat)); // NOLINT(bugprone-sizeof-expression) - ImGui::Text("Material '%s'", name.c_str()); - ImGui::EndDragDropSource(); - } - } - } break; - - case P_Ires: { - if (ImGui::Button("New")) { - ImGui::OpenPopup("NewIresMenu"); - } - if (ImGui::BeginPopup("NewIresMenu")) { - 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(); - } - - 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 = mEditor->GetSelectedItPtr() == ires; - if (ImGui::Selectable(name.c_str(), selected)) { - mEditor->SelectIt(ires, EditorInstance::ITT_Ires); - } - } - } break; - } - } - ImGui::EndChild(); - - ImGui::End(); -} diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp deleted file mode 100644 index 65969f0..0000000 --- a/source/EditorResources.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include "Shader.hpp" - -#include <string> - -class EditorInstance; -class EditorContentBrowser { -private: - enum Pane { - P_Settings, - P_Shader, - P_Material, - P_Ires, - }; - - static constexpr float kSplitterThickness = 3.0f; - static constexpr float kPadding = 4.0f; - - // <root> - static constexpr float kLeftPaneMinWidth = 200.0f; - static constexpr float kRightPaneMinWidth = 200.0f; - - EditorInstance* mEditor; - Pane mPane = P_Settings; - float mBrowserHeight = 0.5f; - float mSplitterLeft = kLeftPaneMinWidth; - float mSplitterRight = 0.0f; - bool mDocked = true; - -public: - EditorContentBrowser(EditorInstance* editor); - ~EditorContentBrowser(); - - void Show(bool* open = nullptr); -}; diff --git a/source/EditorUtils.cpp b/source/EditorUtils.cpp index a0d2fc7..4863301 100644 --- a/source/EditorUtils.cpp +++ b/source/EditorUtils.cpp @@ -160,3 +160,20 @@ bool ImGui::Splitter(bool splitVertically, float thickness, float* size1, float* return held; } + +float Utils::CalcImageHeight(glm::vec2 original, int targetWidth) { + // Xorig / Yorig = Xnew / Ynew + // Ynew = Xnew * Yorig / Xorig + return targetWidth * original.y / original.x; +} + +float Utils::CalcImageWidth(glm::vec2 original, float targetHeight) { + // Xorig / Yorig = Xnew / Ynew + // Xnew = Xorig / Yorig * Ynew + return original.x / original.y * targetHeight; +} + +ImVec2 Utils::FitImage(glm::vec2 original) { + float newWidth = ImGui::GetContentRegionAvail().x; + return ImVec2(newWidth, CalcImageHeight(original, newWidth)); +} diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp index 090f7f6..d6483da 100644 --- a/source/EditorUtils.hpp +++ b/source/EditorUtils.hpp @@ -5,8 +5,11 @@ #include <imgui.h> #include <string> -#define BRUSSEL_DRAG_DROP_SHADER "Shader" -#define BRUSSEL_DRAG_DROP_MATERIAL "Mat" +#define BRUSSEL_TAG_Shader "Shader" +#define BRUSSEL_TAG_Material "Mat" +// To check whether a payload is of this type, use starts_with() +#define BRUSSEL_TAG_PREFIX_GameObject "GameObject" +#define BRUSSEL_TAG_PREFIX_Ires "Ires" namespace ImGui { @@ -29,3 +32,11 @@ bool ColorPicker4(const char* label, RgbaColor* color, ImGuiColorEditFlags flags bool Splitter(bool splitVertically, float thickness, float* size1, float* size2, float minSize1, float minSize2, float splitterLongAxisSize = -1.0f); } // namespace ImGui + +namespace Utils { + +float CalcImageHeight(glm::vec2 original, int targetWidth); +float CalcImageWidth(glm::vec2 original, float targetHeight); +ImVec2 FitImage(glm::vec2 original); + +} // namespace Utils diff --git a/source/Ires.cpp b/source/Ires.cpp index f700ed6..255c221 100644 --- a/source/Ires.cpp +++ b/source/Ires.cpp @@ -1,12 +1,15 @@ #include "Ires.hpp" #include "AppConfig.hpp" +#include "EditorUtils.hpp" #include "RapidJsonHelper.hpp" #include "ScopeGuard.hpp" #include "Sprite.hpp" #include "Texture.hpp" #include "Utils.hpp" +#include <imgui.h> +#include <misc/cpp/imgui_stdlib.h> #include <rapidjson/document.h> #include <rapidjson/filereadstream.h> #include <rapidjson/filewritestream.h> @@ -22,18 +25,18 @@ IresObject::IresObject(Kind kind) std::string_view IresObject::ToString(Kind kind) { switch (kind) { - case KD_Texture: return "Texture"sv; - case KD_SpriteFiles: return "SpriteFiles"sv; - case KD_Spritesheet: return "Spritesheet"sv; + case KD_Texture: return BRUSSEL_TAG_PREFIX_Ires "Texture"sv; + case KD_SpriteFiles: return BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv; + case KD_Spritesheet: return BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv; case KD_COUNT: break; } return std::string_view(); } IresObject::Kind IresObject::FromString(std::string_view name) { - if (name == "Texture"sv) return KD_Texture; - if (name == "SpriteFiles"sv) return KD_SpriteFiles; - if (name == "Spritesheet"sv) return KD_Spritesheet; + if (name == BRUSSEL_TAG_PREFIX_Ires "Texture"sv) return KD_Texture; + if (name == BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv) return KD_SpriteFiles; + if (name == BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv) return KD_Spritesheet; return KD_COUNT; } @@ -51,14 +54,29 @@ bool IresObject::IsAnnoymous() const { return mName.empty(); } -rapidjson::Value IresObject::WriteFull(const IresObject& ires, rapidjson::Document& root) { - rapidjson::Value rvIres; - ires.Write(rvIres, root); +void IresObject::SetName(std::string name) { + if (mMan) { + mMan->Rename(this, std::move(name)); + } else { + mName = std::move(name); + } +} - rapidjson::Value result; - result.AddMember("Type", rapidjson::StringRef(ToString(ires.GetKind())), root.GetAllocator()); - result.AddMember("Value", rvIres, root.GetAllocator()); - return result; +void IresObject::ShowEditor(EditorInstance& editor) { + bool isAnnoymous = mName.empty(); + if (isAnnoymous) { + ImGui::Text("<Annoymous Ires at %p>", (void*)this); + } else { + ImGui::Text("%s", mName.c_str()); + } +} + +void IresObject::WriteFull(IresObject* ires, rapidjson::Value& value, rapidjson::Document& root) { + rapidjson::Value rvIres(rapidjson::kObjectType); + ires->Write(rvIres, root); + + value.AddMember("Type", rapidjson::StringRef(ToString(ires->GetKind())), root.GetAllocator()); + value.AddMember("Value", rvIres, root.GetAllocator()); } std::unique_ptr<IresObject> IresObject::ReadFull(const rapidjson::Value& value) { @@ -68,13 +86,20 @@ std::unique_ptr<IresObject> IresObject::ReadFull(const rapidjson::Value& value) auto ires = Create(kind); if (!ires) return nullptr; - auto rvValue = rapidjson::GetProperty(value, "Value"sv); - if (!rvValue) return nullptr; - ires->Read(*rvValue); + if (!ReadPartial(ires.get(), value)) { + return nullptr; + } return ires; } +bool IresObject::ReadPartial(IresObject* ires, const rapidjson::Value& value) { + auto rvValue = rapidjson::GetProperty(value, "Value"sv); + if (!rvValue) return false; + ires->Read(*rvValue); + return true; +} + void IresManager::DiscoverFilesDesignatedLocation() { auto path = AppConfig::assetDirPath / "Ires"; DiscoverFiles(path); @@ -121,7 +146,9 @@ void IresManager::DiscoverFiles(const fs::path& dir) { idenView = std::string_view(idenView.data(), idenView.size() - 1); } #endif - mObjects.try_emplace(std::move(iden), ires.release()); + ires->mName = std::move(iden); + std::string_view key(ires->mName); + mObjects.try_emplace(key, ires.release()); } } @@ -166,6 +193,48 @@ bool IresManager::Rename(IresObject* ires, std::string newName) { return true; } +IresObject* IresManager::Load(const fs::path& path) { + // TODO + return nullptr; +} + +static fs::path GetDesignatedPath(IresObject* ires) { + return AppConfig::assetDirPath / "Ires" / fs::path(ires->GetName()).replace_extension(".json"); +} + +void IresManager::Reload(IresObject* ires) { + auto file = Utils::OpenCstdioFile(GetDesignatedPath(ires), Utils::Read); + if (!file) return; + DEFER { fclose(file); }; + + char readerBuffer[65536]; + rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); + + rapidjson::Document root; + root.ParseStream(stream); + + IresObject::ReadPartial(ires, root); +} + +void IresManager::Save(IresObject* ires) { + Save(ires, GetDesignatedPath(ires)); +} + +void IresManager::Save(IresObject* ires, const fs::path& filePath) { + rapidjson::Document root(rapidjson::kObjectType); + + IresObject::WriteFull(ires, root, root); + + auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate); + if (!file) return; + DEFER { fclose(file); }; + + char writerBuffer[65536]; + rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); + rapidjson::Writer<rapidjson::FileWriteStream> writer(stream); + root.Accept(writer); +} + IresObject* IresManager::FindIres(std::string_view path) { auto iter = mObjects.find(path); if (iter != mObjects.end()) { diff --git a/source/Ires.hpp b/source/Ires.hpp index 66a931e..9b055af 100644 --- a/source/Ires.hpp +++ b/source/Ires.hpp @@ -11,7 +11,7 @@ #include <string_view> // Forward declarations -class IresObject; +class EditorInstance; class IresManager; class IresObject : public RefCounted { @@ -43,12 +43,16 @@ public: IresManager* GetAssociatedManager() const { return mMan; } bool IsAnnoymous() const; const std::string& GetName() const { return mName; } + void SetName(std::string name); + + virtual void ShowEditor(EditorInstance& editor); EditorAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); } void SetEditorAttachment(EditorAttachment* attachment) { mEditorAttachment.reset(attachment); } - static rapidjson::Value WriteFull(const IresObject& ires, rapidjson::Document& root); + static void WriteFull(IresObject* ires, rapidjson::Value& value, rapidjson::Document& root); static std::unique_ptr<IresObject> ReadFull(const rapidjson::Value& value); + static bool ReadPartial(IresObject* ires, const rapidjson::Value& value); virtual void Write(rapidjson::Value& value, rapidjson::Document& root) const = 0; virtual void Read(const rapidjson::Value& value) = 0; }; @@ -58,7 +62,7 @@ public: static inline IresManager* instance = nullptr; private: - robin_hood::unordered_map<std::string_view, RcPtr<IresObject>, StringHash, StringEqual> mObjects; + robin_hood::unordered_map<std::string_view, RcPtr<IresObject>> mObjects; public: void DiscoverFilesDesignatedLocation(); @@ -68,6 +72,11 @@ public: void Delete(IresObject* ires); bool Rename(IresObject* ires, std::string newName); + IresObject* Load(const std::filesystem::path& path); + void Reload(IresObject* ires); + void Save(IresObject* ires); + void Save(IresObject* ires, const std::filesystem::path& filePath); + const auto& GetObjects() const { return mObjects; } IresObject* FindIres(std::string_view path); }; diff --git a/source/Macros.hpp b/source/Macros.hpp index 71a438a..9f25cc4 100644 --- a/source/Macros.hpp +++ b/source/Macros.hpp @@ -21,9 +21,3 @@ #else # define UNREACHABLE #endif - -#if defined(DOCTEST_CONFIG_DISABLE) -# define TESTED_MEMEBERS_VISBILITY private -#else -# define TESTED_MEMEBERS_VISBILITY public -#endif diff --git a/source/Material.hpp b/source/Material.hpp index bf1c988..469cb7b 100644 --- a/source/Material.hpp +++ b/source/Material.hpp @@ -4,6 +4,7 @@ #include "RcPtr.hpp" #include "Shader.hpp" #include "Texture.hpp" +// #include "Ires.hpp" #include <glad/glad.h> #include <robin_hood.h> @@ -113,6 +114,26 @@ public: bool LoadFromFile(const std::filesystem::path& filePath); }; +// class IresMaterial : public IresObject { +// public: +// RcPtr<Material> mInstance; + +// public: +// IresMaterial() +// : IresObject(KD_Spritesheet) {} + +// bool IsValid() const; + +// Material* CreateInstance() const; +// Material* GetInstance(); +// void InvalidateInstance(); + +// void ShowEditor() override; + +// void Write(rapidjson::Value& value, rapidjson::Document& root) const override; +// void Read(const rapidjson::Value& value) override; +// }; + class MaterialManager { public: static inline MaterialManager* instance = nullptr; diff --git a/source/Sprite.cpp b/source/Sprite.cpp index cb8d327..6cd575e 100644 --- a/source/Sprite.cpp +++ b/source/Sprite.cpp @@ -1,8 +1,13 @@ #include "Sprite.hpp" +#include "AppConfig.hpp" +#include "EditorCore.hpp" +#include "EditorUtils.hpp" #include "Image.hpp" #include "RapidJsonHelper.hpp" +#include <imgui.h> +#include <misc/cpp/imgui_stdlib.h> #include <rapidjson/document.h> #include <memory> @@ -69,6 +74,8 @@ void IresSpriteFiles::Write(rapidjson::Value& value, rapidjson::Document& root) } void IresSpriteFiles::Read(const rapidjson::Value& value) { + InvalidateInstance(); + auto rvFileList = rapidjson::GetProperty(value, rapidjson::kArrayType, "Sprites"sv); if (!rvFileList) return; spriteFiles.clear(); @@ -81,7 +88,11 @@ bool IresSpritesheet::IsValid() const { sheetHSplit != 0; } -void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit) { +void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, const IresSpritesheet* conf) { + ResplitSpritesheet(sprite, conf->sheetWSplit, conf->sheetHSplit, conf->frameCountOverride); +} + +void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit, int frameCount) { auto atlas = sprite->GetAtlas(); auto size = atlas->GetInfo().size; int frameWidth = size.x / wSplit; @@ -94,6 +105,10 @@ void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit) // Width and height in UV coordinates for each frame float deltaU = 1.0f / wSplit; float deltaV = 1.0f / hSplit; + int i = 0; + if (frameCount < 0) { + frameCount = std::numeric_limits<int>::max(); + } for (int y = 0; y < hSplit; ++y) { for (int x = 0; x < wSplit; ++x) { auto& subregion = sprite->mFrames.emplace_back(); @@ -102,7 +117,13 @@ void IresSpritesheet::ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit) subregion.v0 = deltaV * y; // Bottom right subregion.u1 = subregion.u0 + deltaU; - subregion.u1 = subregion.v0 + deltaV; + subregion.v1 = subregion.v0 + deltaV; + + if ((i + 1) >= frameCount) { + return; + } + + ++i; } } } @@ -112,14 +133,17 @@ Sprite* IresSpritesheet::CreateInstance() const { return nullptr; } + char path[256]; + snprintf(path, sizeof(path), "%s/Ires/%s", AppConfig::assetDir.c_str(), spritesheetFile.c_str()); + auto atlas = std::make_unique<Texture>(); - if (!atlas->InitFromFile(spritesheetFile.c_str())) { + if (atlas->InitFromFile(path) != Texture::EC_Success) { return nullptr; } auto sprite = std::make_unique<Sprite>(); sprite->mAtlas.Attach(atlas.release()); - ResplitSpritesheet(sprite.get(), sheetWSplit, sheetHSplit); + ResplitSpritesheet(sprite.get(), this); return sprite.release(); } @@ -134,16 +158,121 @@ void IresSpritesheet::InvalidateInstance() { mInstance.Attach(nullptr); } +bool IresSpritesheet::IsFrameCountOverriden() const { + return frameCountOverride > 0; +} + +int IresSpritesheet::GetFrameCount() const { + if (IsFrameCountOverriden()) { + return frameCountOverride; + } else { + return sheetWSplit * sheetHSplit; + } +} + +void IresSpritesheet::ShowEditor(EditorInstance& editor) { + IresObject::ShowEditor(editor); + + bool doInvalidateInstance = false; + auto instance = GetInstance(); // NOTE: may be null + + if (ImGui::Button("View Sprite", instance == nullptr)) { + editor.OpenSpriteViewer(instance); + } + + if (ImGui::InputText("Spritesheet", &spritesheetFile)) { + doInvalidateInstance = true; + } + + if (ImGui::InputInt("Horizontal split", &sheetWSplit)) { + sheetWSplit = std::max(sheetWSplit, 1); + if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); + } + + if (ImGui::InputInt("Vertical split", &sheetHSplit)) { + sheetHSplit = std::max(sheetHSplit, 1); + if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); + } + + bool frameCountOverriden = frameCountOverride > 0; + if (ImGui::Checkbox("##", &frameCountOverriden)) { + if (frameCountOverriden) { + frameCountOverride = sheetWSplit * sheetHSplit; + } else { + frameCountOverride = 0; + } + } + ImGui::SameLine(); + if (frameCountOverriden) { + if (ImGui::InputInt("Frame count", &frameCountOverride)) { + frameCountOverride = std::max(frameCountOverride, 1); + if (instance) IresSpritesheet::ResplitSpritesheet(instance, this); + } + } else { + int dummy = sheetWSplit * sheetHSplit; + ImGui::PushDisabled(); + ImGui::InputInt("Frame count", &dummy, ImGuiInputTextFlags_ReadOnly); + ImGui::PopDisabled(); + } + + if (instance) { + auto atlas = instance->GetAtlas(); + auto imageSize = Utils::FitImage(atlas->GetInfo().size); + auto imagePos = ImGui::GetCursorScreenPos(); + ImGui::Image((ImTextureID)(uintptr_t)atlas->GetHandle(), imageSize); + + auto drawlist = ImGui::GetWindowDrawList(); + float deltaX = imageSize.x / sheetWSplit; + for (int ix = 0; ix < sheetWSplit + 1; ++ix) { + float x = ix * deltaX; + ImVec2 start{ imagePos.x + x, imagePos.y }; + ImVec2 end{ imagePos.x + x, imagePos.y + imageSize.y }; + drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); + } + float deltaY = imageSize.y / sheetHSplit; + for (int iy = 0; iy < sheetHSplit + 1; ++iy) { + float y = iy * deltaY; + ImVec2 start{ imagePos.x, imagePos.y + y }; + ImVec2 end{ imagePos.x + imageSize.x, imagePos.y + y }; + drawlist->AddLine(start, end, IM_COL32(255, 255, 0, 255)); + } + + int i = sheetWSplit * sheetHSplit; + int frameCount = GetFrameCount(); + for (int y = sheetHSplit - 1; y >= 0; --y) { + for (int x = sheetWSplit - 1; x >= 0; --x) { + if (i > frameCount) { + ImVec2 tl{ imagePos.x + x * deltaX + 1.0f, imagePos.y + y * deltaY + 1.0f }; + ImVec2 br{ imagePos.x + (x + 1) * deltaX, imagePos.y + (y + 1) * deltaY }; + drawlist->AddRectFilled(tl, br, IM_COL32(255, 0, 0, 100)); + } + --i; + } + } + } else { + ImGui::TextUnformatted("Sprite configuration invalid"); + } + + if (doInvalidateInstance) { + InvalidateInstance(); + } +} + void IresSpritesheet::Write(rapidjson::Value& value, rapidjson::Document& root) const { value.AddMember("SpriteSheet", spritesheetFile, root.GetAllocator()); value.AddMember("WSplit", sheetWSplit, root.GetAllocator()); value.AddMember("HSplit", sheetHSplit, root.GetAllocator()); + if (frameCountOverride > 0) { + value.AddMember("FrameCount", frameCountOverride, root.GetAllocator()); + } } void IresSpritesheet::Read(const rapidjson::Value& value) { + InvalidateInstance(); BRUSSEL_JSON_GET(value, "SpriteSheet", std::string, spritesheetFile, return ); BRUSSEL_JSON_GET(value, "WSplit", int, sheetWSplit, return ); BRUSSEL_JSON_GET(value, "HSplit", int, sheetHSplit, return ); + BRUSSEL_JSON_GET_DEFAULT(value, "FrameCount", int, frameCountOverride, 0); } SpriteMesh::SpriteMesh(Sprite* sprite) diff --git a/source/Sprite.hpp b/source/Sprite.hpp index ec25fbd..d9fb612 100644 --- a/source/Sprite.hpp +++ b/source/Sprite.hpp @@ -53,8 +53,9 @@ class IresSpritesheet : public IresObject { public: RcPtr<Sprite> mInstance; std::string spritesheetFile; - int sheetWSplit; - int sheetHSplit; + int sheetWSplit = 1; + int sheetHSplit = 1; + int frameCountOverride = 0; public: IresSpritesheet() @@ -62,12 +63,18 @@ public: bool IsValid() const; - static void ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit); + static void ResplitSpritesheet(Sprite* sprite, const IresSpritesheet* conf); + static void ResplitSpritesheet(Sprite* sprite, int wSplit, int hSplit, int frameCountOverride = -1); Sprite* CreateInstance() const; Sprite* GetInstance(); void InvalidateInstance(); + bool IsFrameCountOverriden() const; + int GetFrameCount() const; + + void ShowEditor(EditorInstance& editor) override; + void Write(rapidjson::Value& value, rapidjson::Document& root) const override; void Read(const rapidjson::Value& value) override; }; |