commit 5b3c9c09
Add basic editor
Changed files
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 <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"
+
+
+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<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);
+}
+
+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 <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/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 <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/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 <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/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<char[]> 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 <iostream>
#include <boost/program_options.hpp>
#include <SDL_mixer.h>
+#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<int>(), "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;