diff --git a/source/code/editor/SagoTextureSelector.cpp b/source/code/editor/SagoTextureSelector.cpp new file mode 100644 index 0000000..91fef74 --- /dev/null +++ b/source/code/editor/SagoTextureSelector.cpp @@ -0,0 +1,244 @@ +/* +=========================================================================== + * 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" + + +class ChangeSDLColor +{ +public: + ChangeSDLColor(SDL_Renderer* renderer, Uint8 r, Uint8 g, Uint8 b, Uint8 a) : renderer(renderer) { + SDL_GetRenderDrawColor(renderer, &oldr, &oldg, &oldb, &olda); + SDL_SetRenderDrawColor(renderer, r, g, b, a); + } + + ChangeSDLColor(SDL_Renderer* renderer) : renderer(renderer) { + SDL_GetRenderDrawColor(renderer, &oldr, &oldg, &oldb, &olda); + } + + void setRed() { + SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE); + } + + void setYellow() { + SDL_SetRenderDrawColor(renderer, 255, 255, 0, SDL_ALPHA_OPAQUE); + } + + void reset() { + SDL_SetRenderDrawColor(renderer, oldr, oldg, oldb, olda); + } + + ~ChangeSDLColor() { + SDL_SetRenderDrawColor(renderer, oldr, oldg, oldb, olda); + } + +private: + SDL_Renderer* renderer; + Uint8 oldr, oldg, oldb, olda; +}; + + +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); + Uint8 r, g, b, a; + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + + 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(r, g, b, a)); + } + } + 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(r, g, b, a)); + } + } +} + +static void addRectableToCanvas(SDL_Renderer* renderer, int topx = 0, int topy = 0, int height = 100, int width = 100, int xoffset = 0, int yoffset = 0) { + Uint8 r, g, b, a; + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + topx += xoffset; + topy += yoffset; + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx, topy), ImVec2(topx+width, topy), IM_COL32(r, g, b, a)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx+width, topy), ImVec2(topx+width, topy+height), IM_COL32(r, g, b, a)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx+width, topy+height), ImVec2(topx, topy+height), IM_COL32(r, g, b, a)); + ImGui::GetWindowDrawList()->AddLine(ImVec2(topx, topy+height), ImVec2(topx, topy), IM_COL32(r, g, b, a)); +} + +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); +} + + +static void ImGuiWritePartOfImage(SDL_Texture* texture, int topx, int topy, int w, int h) { + int tex_w, tex_h; + SDL_QueryTexture(texture, nullptr, nullptr, &tex_w, &tex_h); + float sprite_w = w; + float sprite_h = h; + float topxf = topx; + float topyf = topy; + ImVec2 uv0 = ImVec2(topxf / tex_w, topyf / tex_h); + ImVec2 uv1 = ImVec2((topxf + sprite_w) / tex_w, (topyf + sprite_h) / tex_h); + ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2((float)w, (float)h), uv0, uv1); +} + +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()) { + const SagoSprite& current_sprite = sprites[selected_sprite]; + SDL_Texture* current_texture = globalData.dataHolder->getTexturePtr(current_sprite.texture); + ImGui::Text("Size: %d x %d", current_sprite.width, current_sprite.height); + ImGui::BeginChild("Test"); + int offset = current_sprite.width * (SDL_GetTicks()/current_sprite.frame_time % current_sprite.number_of_frames); + ImGuiWritePartOfImage(current_texture, current_sprite.topx+offset, current_sprite.topy, current_sprite.width, current_sprite.height); + 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)); + ChangeSDLColor color(target); + color.setYellow(); + for (int i = 1; i < current_sprite.number_of_frames; i++) { + addRectableToCanvas(target, current_sprite.topx+i*current_sprite.width, current_sprite.topy, current_sprite.height, current_sprite.width, p.x, p.y); + } + color.setRed(); + 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)); + ChangeSDLColor color(target); + color.setRed(); + 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/source/code/editor/SagoTextureSelector.hpp b/source/code/editor/SagoTextureSelector.hpp new file mode 100644 index 0000000..b822d8a --- /dev/null +++ b/source/code/editor/SagoTextureSelector.hpp @@ -0,0 +1,52 @@ +/* +=========================================================================== + * 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/source/code/editor/editor_loader.cpp b/source/code/editor/editor_loader.cpp new file mode 100644 index 0000000..643c38f --- /dev/null +++ b/source/code/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/source/code/editor/editor_loader.hpp b/source/code/editor/editor_loader.hpp new file mode 100644 index 0000000..f96fe85 --- /dev/null +++ b/source/code/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/source/code/global.hpp b/source/code/global.hpp index cfe4ede..c795eb0 100644 --- a/source/code/global.hpp +++ b/source/code/global.hpp @@ -104,6 +104,7 @@ struct GlobalData { bool NoSound = false; bool alwaysSixteenNine = false; int verboseLevel = 0; + sago::SagoDataHolder* dataHolder = nullptr; std::unique_ptr spriteHolder; std::vector modList; ModInfo modinfo; diff --git a/source/code/main.cpp b/source/code/main.cpp index 7790645..f8c4312 100644 --- a/source/code/main.cpp +++ b/source/code/main.cpp @@ -68,6 +68,7 @@ https://blockattack.net #include "ExplosionManager.hpp" +#include "editor/SagoTextureSelector.hpp" #include "puzzle_editor/PuzzleEditorState.hpp" #include "SagoImGui.hpp" @@ -265,6 +266,7 @@ void ResetFullscreen() { globalData.logicalResize = sago::SagoLogicalResize(globalData.xsize, globalData.ysize); dataHolder.invalidateAll(globalData.screen); globalData.spriteHolder.reset(new sago::SagoSpriteHolder( dataHolder ) ); + globalData.dataHolder = &dataHolder; globalData.spriteHolder->ReadSprites(globalData.modinfo.getModSpriteFiles()); if (sago::FileExists("sprites/custom_backgrounds.sprite")) { std::vector custom_backgrounds = { "custom_backgrounds" }; @@ -925,6 +927,7 @@ static void ParseArguments(int argc, char* argv[], globalConfig& conf) { ("print-search-path", "Prints the search path and quits") ("no-auto-scale", "Do not automatically auto scale") ("always-sixteen-nine", "Use 16:9 format even in Window mode") + ("editor", "Start the sprite editor/browser") ("puzzle-editor", "Start the build in puzzle editor") ("puzzle-level-file", boost::program_options::value(), "Sets the default puzzle file to load") ("puzzle-single-level", boost::program_options::value(), "Start the specific puzzle level directly") @@ -1021,6 +1024,9 @@ static void ParseArguments(int argc, char* argv[], globalConfig& conf) { if (vm.count("always-sixteen-nine")) { globalData.alwaysSixteenNine = true; } + if (vm.count("editor")) { + editor = true; + } if (vm.count("puzzle-editor")) { puzzleEditor = true; } @@ -1306,6 +1312,17 @@ int main(int argc, char* argv[]) { if (singlePuzzle) { runGame(Gametype::Puzzle, singlePuzzleNr); } + else if (editor) { + InitImGui(sdlWindow, renderer, globalData.xsize, globalData.ysize); + ImGuiIO& io = ImGui::GetIO(); + io.IniFilename = nullptr; + std::string imgui_inifile = getPathToSaveFiles() + "/imgui.ini"; + ImGui::LoadIniSettingsFromDisk(imgui_inifile.c_str()); + SagoTextureSelector sts; + sts.Init(); + RunImGuiGameState(sts); + ImGui::SaveIniSettingsToDisk(imgui_inifile.c_str()); + } else if (puzzleEditor) { InitImGui(sdlWindow, renderer, globalData.xsize, globalData.ysize); ImGuiIO& io = ImGui::GetIO(); diff --git a/source/code/mainVars.inc b/source/code/mainVars.inc index 66221e4..f7c48a2 100644 --- a/source/code/mainVars.inc +++ b/source/code/mainVars.inc @@ -89,7 +89,8 @@ bool twoPlayers; //True if two playerImGui_ImplSDL2_ProcessEvent(&event);s ar //Allows starting the game with just a single puzzle. Mainly to be able to open a puzzle directly from the level editor -static bool singlePuzzle = false; +static bool singlePuzzle = false; +static bool editor = false; static bool puzzleEditor = false; static int singlePuzzleNr = 0; static std::string singlePuzzleFile;