diff --git a/CMakeLists.txt b/CMakeLists.txt index 95d9be6..eb8db63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ if (NOT WIN32 AND NOT STANDALONE) endif() endif() -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wsuggest-override -std=c++11 -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wsuggest-override -std=c++17 -g") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -g") if (WIN32) diff --git a/src/editor/SagoTextureSelector.cpp b/src/editor/SagoTextureSelector.cpp new file mode 100644 index 0000000..4d2c69d --- /dev/null +++ b/src/editor/SagoTextureSelector.cpp @@ -0,0 +1,203 @@ +/* +=========================================================================== + * Sago Multi Scrambler Puzzle +Copyright (C) 2022-2024 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#include "SagoTextureSelector.hpp" +#include +#include "imgui.h" +#include "backends/imgui_impl_sdl2.h" +#include "backends/imgui_impl_sdlrenderer2.h" +#include +#include "../../sago/SagoMisc.hpp" +#include "../global.hpp" + + +static void addLinesToCanvas(SDL_Renderer* renderer, SDL_Texture* texture, int xstep = 32, int ystep = 32, int xoffset = 0, int yoffset = 0) { + int width, height; + SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); + + SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE); + if (xstep > 0) { + for (int i = 0; i < width+1; i += xstep) { + ImGui::GetWindowDrawList()->AddLine(ImVec2(i+xoffset, yoffset), ImVec2(i+xoffset, height+yoffset), IM_COL32(255, 0, 0, 255)); + } + } + if (ystep > 0) { + for (int i = 0; i < height+1; i += ystep) { + ImGui::GetWindowDrawList()->AddLine(ImVec2(xoffset, i+yoffset), ImVec2(width+xoffset, i+yoffset), IM_COL32(255, 0, 0, 255)); + } + } + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); +} + +static void addRectableToCanvas(SDL_Renderer* renderer, int topx = 0, int topy = 0, int height = 100, int width = 100, int xoffset = 0, int yoffset = 0) { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE); + topx += xoffset; + topy += yoffset; + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx, topy), ImVec2(topx+width, topy), IM_COL32(255, 0, 0, 255)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx+width, topy), ImVec2(topx+width, topy+height), IM_COL32(255, 0, 0, 255)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx+width, topy+height), ImVec2(topx, topy+height), IM_COL32(255, 0, 0, 255)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx, topy+height), ImVec2(topx, topy), IM_COL32(255, 0, 0, 255)); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); +} + +static void addFolderToList(const std::string& folder, std::vector& list, const std::string& filter = "") { + std::vector textures = sago::GetFileList(folder.c_str()); + for (const auto& texture : textures) { + std::cout << "Texture: " << texture << "\n"; + if (texture.find(".png") == std::string::npos) { + continue; + } + if (filter.empty() || texture.find(filter) != std::string::npos) { + list.push_back(texture); + } + } +} + +static std::vector populateTree(const std::string& filter = "") { + std::vector textures; + addFolderToList("textures", textures, filter); + return textures; +} + +std::string remove_file_extension(const std::string& filename) { + size_t lastindex = filename.find_last_of("."); + return filename.substr(0, lastindex); +} + +void SagoTextureSelector::runSpriteSelectorFrame(SDL_Renderer* target) { + ImGui::Begin("SpriteList", nullptr, ImGuiWindowFlags_NoCollapse); + static char filter[256] = ""; + ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter)); + ImGui::Separator(); + for (const auto& sprite : sprites) { + std::string sprite_name = sprite.first; + if (filter[0] == '\0' || sprite_name.find(filter) != std::string::npos) { + if (ImGui::Selectable(sprite_name.c_str(), selected_sprite == sprite_name)) { + selected_sprite = sprite_name; + } + } + } + + ImGui::End(); + + ImGui::Begin("SpriteViewer"); + if (selected_sprite.length()) { + int tex_w, tex_h; + const SagoSprite& current_sprite = sprites[selected_sprite]; + SDL_Texture* current_texture = globalData.dataHolder->getTexturePtr(current_sprite.texture); + SDL_QueryTexture(current_texture, nullptr, nullptr, &tex_w, &tex_h); + float sprite_w = current_sprite.width; + float sprite_h = current_sprite.height; + float topx = current_sprite.topx; + float topy = current_sprite.topy; + ImGui::Text("Size: %d x %d", tex_w, tex_h); + ImGui::BeginChild("Test"); + ImVec2 p = ImGui::GetCursorScreenPos(); + + // Normalized coordinates of pixel (10,10) in a 256x256 texture. + ImVec2 uv0 = ImVec2(topx / tex_h, topy / tex_w); + + // Normalized coordinates of pixel (110,210) in a 256x256 texture. + ImVec2 uv1 = ImVec2( (topx+sprite_h) / tex_h, (topy+sprite_w) / tex_w); + + + ImGui::Image((ImTextureID)(intptr_t)current_texture, ImVec2((float)sprite_w, (float)sprite_h), uv0, uv1); + ImGui::EndChild(); + } + ImGui::End(); + + ImGui::Begin("SpriteTexture"); + if (selected_sprite.length() && sprites[selected_sprite].texture.length()) { + int tex_w, tex_h; + const SagoSprite& current_sprite = sprites[selected_sprite]; + SDL_Texture* current_texture = globalData.dataHolder->getTexturePtr(current_sprite.texture); + SDL_QueryTexture(current_texture, nullptr, nullptr, &tex_w, &tex_h); + ImGui::Text("Size: %d x %d", tex_w, tex_h); + ImGui::BeginChild("Test"); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::Image((ImTextureID)(intptr_t)current_texture, ImVec2((float)tex_w, (float)tex_h)); + addRectableToCanvas(target, current_sprite.topx, current_sprite.topy, current_sprite.height, current_sprite.width, p.x, p.y); + ImGui::EndChild(); + } + ImGui::End(); +} + +void SagoTextureSelector::runTextureSelectorFrame(SDL_Renderer* target) { + ImGui::Begin("TextureList", nullptr, ImGuiWindowFlags_NoCollapse); + static char filter[256] = ""; + ImGui::InputText("Filter", filter, IM_ARRAYSIZE(filter)); + ImGui::Separator(); + for (const auto& texture : textures) { + if (filter[0] == '\0' || texture.find(filter) != std::string::npos) { + if (ImGui::Selectable(texture.c_str(), selected_texture == texture)) { + selected_texture = texture; + } + } + } + + ImGui::End(); + + ImGui::Begin("TextureViewer"); + if (selected_texture.length()) { + int tex_w, tex_h; + SDL_Texture* current_texture = globalData.dataHolder->getTexturePtr(remove_file_extension(selected_texture)); + SDL_QueryTexture(current_texture, nullptr, nullptr, &tex_w, &tex_h); + ImGui::Text("Size: %d x %d", tex_w, tex_h); + ImGui::BeginChild("Test"); + ImVec2 p = ImGui::GetCursorScreenPos(); + ImGui::Image((ImTextureID)(intptr_t)current_texture, ImVec2((float)tex_w, (float)tex_h)); + addLinesToCanvas(target, current_texture, 32, 32, p.x, p.y); + ImGui::EndChild(); + } + ImGui::End(); + +} + + + +SagoTextureSelector::SagoTextureSelector() { +} + +SagoTextureSelector::~SagoTextureSelector() { +} + +bool SagoTextureSelector::IsActive() { + return isActive; +} + +void SagoTextureSelector::ProcessInput(const SDL_Event& event, bool &processed) { + ImGui_ImplSDL2_ProcessEvent(&event); +} + +void SagoTextureSelector::Draw(SDL_Renderer* target) { + runTextureSelectorFrame(target); + runSpriteSelectorFrame(target); +} + +void SagoTextureSelector::Update() { +} + +void SagoTextureSelector::Init() { + textures = populateTree(); + sprites = LoadSprites(); +} \ No newline at end of file diff --git a/src/editor/SagoTextureSelector.hpp b/src/editor/SagoTextureSelector.hpp new file mode 100644 index 0000000..dbc3a82 --- /dev/null +++ b/src/editor/SagoTextureSelector.hpp @@ -0,0 +1,55 @@ +/* +=========================================================================== + * Sago Multi Scrambler Puzzle +Copyright (C) 2022 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#include "../../sago/GameStateInterface.hpp" +#include +#include +#include "editor_loader.hpp" + + + + +#pragma once + +class SagoTextureSelector : public sago::GameStateInterface { +public: + SagoTextureSelector(); + SagoTextureSelector(const SagoTextureSelector& orig) = delete; + virtual ~SagoTextureSelector(); + + bool IsActive() override; + void ProcessInput(const SDL_Event& event, bool &processed) override; + void Draw(SDL_Renderer* target) override; + void Update() override; + void Init(); + + void runTextureSelectorFrame(SDL_Renderer* target); + void runSpriteSelectorFrame(SDL_Renderer* target); + +private: + bool isActive = true; + std::vector textures; + std::map sprites; + std::string selected_texture; + std::string selected_sprite; +}; \ No newline at end of file diff --git a/src/editor/editor_loader.cpp b/src/editor/editor_loader.cpp new file mode 100644 index 0000000..643c38f --- /dev/null +++ b/src/editor/editor_loader.cpp @@ -0,0 +1,99 @@ +/* +=========================================================================== + * Sago Multi Scrambler Puzzle +Copyright (C) 2022-2024 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#include "editor_loader.hpp" +#include "../sago/SagoMisc.hpp" +#include +#include + + +void to_json(nlohmann::json& j, const SagoSprite& p) { + j = nlohmann::json{{"texture", p.texture}, {"topx", p.topx}, {"topy", p.topy}, {"height", p.height}, {"width", p.width}, {"number_of_frames", p.number_of_frames}, {"frame_time", p.frame_time}, {"originx", p.originx}, {"originy", p.originy}}; +} + +void from_json(const nlohmann::json& j, SagoSprite& p) { + j.at("texture").get_to(p.texture); + j.at("topx").get_to(p.topx); + j.at("topy").get_to(p.topy); + j.at("height").get_to(p.height); + j.at("width").get_to(p.width); + if (j.contains("number_of_frames")) { + j.at("number_of_frames").get_to(p.number_of_frames); + } + if (j.contains("frame_time")) { + j.at("frame_time").get_to(p.frame_time); + } + if(j.contains("originx")) { + j.at("originx").get_to(p.originx); + } + if(j.contains("originy")) { + j.at("originy").get_to(p.originy); + } +} + +void from_json(const nlohmann::json& j, SagoSpriteFile& p) { + std::cout << "from_json\n"; + for (auto& [key, value] : j.items()) { + SagoSprite s; + if (!value.is_object()) { + continue; + } + from_json(value, s); + p.sprites[key] = s; + } +} + +void AppendFromFile(const std::string& json_content, const std::string& filename, std::map& sprites) { + nlohmann::json j = nlohmann::json::parse(json_content); + //std::map file; + for (auto& sprite : j.items()) { + std::cout << sprite.key() << "\n"; + if (!sprite.value().is_object()) { + std::cout << "Not an object\n"; + continue; + } + std::string name = sprite.key(); + SagoSprite s = sprite.value(); + s.meta_from_file = filename; + s.meta_name = name; + sprites[name] = s; + } +} + +std::map LoadSprites() { + std::map sprites; + std::vector spritefiles = sago::GetFileList("sprites"); + for (std::string& item : spritefiles ) { + if (boost::algorithm::ends_with(item,".sprite")) { + std::string filename = "sprites/"+item; + std::string content = sago::GetFileContent(filename.c_str()); + if (content.empty()) { + std::cout << "File \"" << filename << "\" does not exit!\n"; + return sprites; + } + std::cout << "Reading " << filename << "\n"; + AppendFromFile(content, item, sprites); + } + } + return sprites; +} \ No newline at end of file diff --git a/src/editor/editor_loader.hpp b/src/editor/editor_loader.hpp new file mode 100644 index 0000000..f96fe85 --- /dev/null +++ b/src/editor/editor_loader.hpp @@ -0,0 +1,57 @@ +/* +=========================================================================== + * Sago Multi Scrambler Puzzle +Copyright (C) 2022-2024 Poul Sander + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/ + +Source information and contacts persons can be found at +https://github.com/sago007/saland +=========================================================================== +*/ + +#include +#include "nlohmann/json.hpp" +#include +#include + +struct SagoSprite { + std::string texture; + int topx = 0; + int topy = 0; + int height = 1; + int width = 1; + int number_of_frames = 1; + int frame_time = 1; + int originx = 0; + int originy = 0; + + std::string meta_name = ""; + std::string meta_from_file = ""; + std::string meta_modified = "false"; +}; + +struct SagoSpriteFile { + std::map sprites; +}; + +void to_json(nlohmann::json& j, const SagoSprite& p); + +void from_json(const nlohmann::json& j, SagoSprite& p); + +void from_json(const nlohmann::json& j, SagoSpriteFile& p); + +void AppendFromJson(const std::string& json_content, const std::string& filename, std::map& sprites); + +std::map LoadSprites(); \ No newline at end of file diff --git a/src/sago/SagoDataHolder.cpp b/src/sago/SagoDataHolder.cpp index cb2ce7f..47ef639 100644 --- a/src/sago/SagoDataHolder.cpp +++ b/src/sago/SagoDataHolder.cpp @@ -114,7 +114,8 @@ SDL_Texture* SagoDataHolder::getTexturePtr(const std::string& textureName) const printFileWeLoad(path); } if (!PHYSFS_exists(path.c_str())) { - sago::SagoFatalErrorF("getTextureFailed - Texture does not exist: %s", path.c_str()); + std::cerr << "getTextureFailed - Texture does not exist: " << path << "\n"; + return getTexturePtr("fallback"); } unsigned int m_size = 0; std::unique_ptr m_data; diff --git a/src/saland.cpp b/src/saland.cpp index 354879e..a9c23b6 100644 --- a/src/saland.cpp +++ b/src/saland.cpp @@ -24,6 +24,7 @@ https://github.com/salandgame/saland #include #include #include +#include "editor/SagoTextureSelector.hpp" #include "sago/SagoDataHolder.hpp" #include "sago/SagoSpriteHolder.hpp" #include "sago/GameStateInterface.hpp" @@ -282,6 +283,12 @@ void runStartGame() { RunGameState(g); } +void runEditor() { + SagoTextureSelector sts; + sts.Init(); + RunGameState(sts); +} + class ButtonConfigValue : public Button { private: std::string config_key; @@ -444,6 +451,11 @@ void runGame() { globalData.dataHolder = &holder; ResetFullscreen(); + if (globalData.editor) { + runEditor(); + globalData.isShuttingDown = true; + } + TitleScreen ts; RunGameState(ts); @@ -491,6 +503,7 @@ int main(int argc, char* argv[]) { desc.add_options() ("version", "Print version information and quit") ("help,h", "Print basic usage information to stdout and quit") + ("editor", "Start the editor") ("fullscreen", "Run in fullscreen") ("no-fullscreen", "Run in window") ("software-renderer", boost::program_options::value(), "Asks SDL2 to use software renderer") @@ -506,6 +519,9 @@ int main(int argc, char* argv[]) { std::cout << "saland " << VERSION_NUMBER << "\n"; return 0; } + if (vm.count("editor")) { + globalData.editor = true; + } if (vm.count("fullscreen")) { Config::getInstance()->setInt("fullscreen", 1); } diff --git a/src/saland/globals.hpp b/src/saland/globals.hpp index 840a50c..0d4f27c 100644 --- a/src/saland/globals.hpp +++ b/src/saland/globals.hpp @@ -57,6 +57,8 @@ struct GlobalData { bool debugDrawCollision = false; bool debugDrawProtectedAreas = false; + + bool editor = false; }; extern GlobalData globalData;