commit 1828235c
Backported editor from saland. It can be started with "--editor". Currently it is just a browser but I have already observed an issue with the current "bomb" sprite
Changed files
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 <iostream>
+#include "imgui.h"
+#include "backends/imgui_impl_sdl2.h"
+#include "backends/imgui_impl_sdlrenderer2.h"
+#include <SDL_image.h>
+#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<std::string>& list, const std::string& filter = "") {
+ std::vector<std::string> 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<std::string> populateTree(const std::string& filter = "") {
+ std::vector<std::string> 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 <vector>
+#include <string>
+#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<std::string> textures;
+ std::map<std::string,SagoSprite> 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 <boost/algorithm/string/predicate.hpp>
+#include <iostream>
+
+
+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<std::string, SagoSprite>& sprites) {
+ nlohmann::json j = nlohmann::json::parse(json_content);
+ //std::map<std::string, SagoSprite> 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<std::string, SagoSprite> LoadSprites() {
+ std::map<std::string, SagoSprite> sprites;
+ std::vector<std::string> 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 <string>
+#include "nlohmann/json.hpp"
+#include <vector>
+#include <map>
+
+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 = "<memory>";
+ std::string meta_modified = "false";
+};
+
+struct SagoSpriteFile {
+ std::map<std::string, SagoSprite> 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<std::string, SagoSprite>& sprites);
+
+std::map<std::string, SagoSprite> 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<sago::SagoSpriteHolder> spriteHolder;
std::vector<std::string> 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<std::string> 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<std::string>(), "Sets the default puzzle file to load")
("puzzle-single-level", boost::program_options::value<int>(), "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;