aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhnOsmium0001 <[email protected]>2022-04-18 17:54:29 -0700
committerhnOsmium0001 <[email protected]>2022-04-18 17:54:29 -0700
commit4b57fe1fb1401bab9439a639bd842ca61386fe22 (patch)
treece06c1fc38b65e8f74acf36d1e3ecfa7e56b367a
parentd43508ba4843801cbbf1f42a27af260d4eef5701 (diff)
Implement IresSpritesheet
-rw-r--r--source/CMakeLists.txt1
-rw-r--r--source/EditorCore.cpp445
-rw-r--r--source/EditorCore.hpp64
-rw-r--r--source/EditorResources.cpp175
-rw-r--r--source/EditorResources.hpp36
-rw-r--r--source/EditorUtils.cpp17
-rw-r--r--source/EditorUtils.hpp15
-rw-r--r--source/Ires.cpp103
-rw-r--r--source/Ires.hpp15
-rw-r--r--source/Macros.hpp6
-rw-r--r--source/Material.hpp21
-rw-r--r--source/Sprite.cpp137
-rw-r--r--source/Sprite.hpp13
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;
};