#include "editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "imgui-SFML.h" #include "imgui.h" #include "level.hpp" #include "nfd.h" #include "nfd.hpp" #include "package.hpp" #include "sets/tileset.hpp" namespace silly::editor { void Editor::update(const sf::Event &event, sf::RenderWindow &window) { // tile rotation if (const auto *e = event.getIf()) { if (e->code == sf::Keyboard::Key::R) { this->rotation += 90.0f; if (this->rotation >= 360.0f) { this->rotation = 0.0f; } } } // world movement if (const auto *e = event.getIf()) { if (e->button == sf::Mouse::Button::Middle) { isDragging = true; lastMousePosition = window.mapPixelToCoords(sf::Mouse::getPosition(window)); } } if (const auto *e = event.getIf()) { if (e->button == sf::Mouse::Button::Middle) { isDragging = false; lastMousePosition = {}; } } // world zoom if (const auto *e = event.getIf()) { sf::FloatRect visibleArea({0.f, 0.f}, sf::Vector2f(window.getSize())); sf::View view(visibleArea); this->zoom -= e->delta; this->zoom = (this->zoom * 10.0f) / 10.0f; if (this->zoom >= 1.0f) { this->zoom = 1.0f; } else if (this->zoom <= 0.3f) { this->zoom = 0.3f; } view.zoom(this->zoom); view.setCenter(window.getView().getCenter()); window.setView(view); } } void Editor::update(sf::RenderWindow &window) { // tile placement if ((sf::Mouse::isButtonPressed(sf::Mouse::Button::Left) || sf::Mouse::isButtonPressed(sf::Mouse::Button::Right)) && !this->package.get_levels().empty() && !this->package.get_current_level().get_floors().empty()) { sf::Vector2i pos = sf::Mouse::getPosition(window); sf::Vector2f mousePosition = window.mapPixelToCoords(pos); TileLevel &level = this->package.get_current_level(); TileFloor &floor = level.get_current_floor(); for (int x = 0; x < floor.get_width(); x++) { for (int y = 0; y < floor.get_height(); y++) { int rx = x * TILE_WIDTH, ry = y * TILE_HEIGHT; if ((rx < mousePosition.x && mousePosition.x < rx + TILE_WIDTH) && (ry < mousePosition.y && mousePosition.y < ry + TILE_HEIGHT) && // editor related pos.x < window.getSize().x - 400.0f && !this->newTileState.has_value()) { sf::Vector2i pos(x, y); if (this->selectedTile.has_value()) { auto tile = this->selectedTile.value(); if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Left)) { floor.place_tile(tile, pos, this->rotation); } else if (sf::Mouse::isButtonPressed(sf::Mouse::Button::Right)) { floor.remove_tile(tile->type, pos); } } } } } } // world movement if (isDragging) { sf::Vector2f mousePosition = window.mapPixelToCoords(sf::Mouse::getPosition(window)); sf::Vector2f deltaPosition = lastMousePosition - mousePosition; sf::View view = window.getView(); view.move(deltaPosition); window.setView(view); } } void Editor::createSavePackageWindow(const sf::RenderWindow &window) { sf::Vector2u windowSize = window.getSize(); SavePackageState &state = this->savePackageState.value(); ImGui::SetNextWindowPos( ImVec2(windowSize.x / 2.0f - 160, windowSize.y / 2.0f - 70)); ImGui::SetNextWindowSize(ImVec2(320, 140)); ImGui::Begin("Export package...", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); std::string text_format, text_description; if (state.format == PACKAGE_TXT) { text_format = "TXT"; text_description = "Represent level package as a .txt file\n" "- File can be read by a human\n" "- File can be imported to other editor\n" "- Tileset is NOT embedded\n"; } else { text_format = "WTF"; text_description = "uhhhh please make an issue about this on github"; } if (ImGui::BeginCombo("Format", text_format.c_str())) { for (int i = 0; i < 1; i++) { bool is_selected = state.format == (LevelPackageFormat)i; std::string text_format; if (state.format == PACKAGE_TXT) { text_format = "TXT"; } else { text_format = "WTF"; } if (ImGui::Selectable(text_format.c_str(), is_selected)) { state.format = (LevelPackageFormat)i; } if (is_selected) ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); } ImGui::Text(text_description.c_str()); if (ImGui::Button("Export")) { NFD::UniquePath outPath; nfdresult_t result; if (state.format == PACKAGE_TXT) { result = NFD::PickFolder(outPath); } else { result = NFD::SaveDialog(outPath); } if (result == NFD_OKAY) { state.path = outPath.get(); this->package.save(state.format, state.path); ImGui::SetWindowCollapsed(true); } } if (ImGui::IsWindowCollapsed()) { ImGui::SetWindowCollapsed(false); this->savePackageState = std::nullopt; } ImGui::End(); } void Editor::createNewFloor(const sf::RenderWindow &window) { if (!this->newFloorState.has_value()) { this->newFloorState = std::make_optional((NewFloorState){}); } sf::Vector2u windowSize = window.getSize(); NewFloorState &state = this->newFloorState.value(); ImGui::SetNextWindowPos( ImVec2(windowSize.x / 2.0f - 100, windowSize.y / 2.0f - 50)); ImGui::SetNextWindowSize(ImVec2(200, 100)); ImGui::Begin("New floor", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); ImGui::InputInt("Width", &state.width); ImGui::InputInt("Height", &state.height); state.width = std::min(std::max(state.width, 1), 100); state.height = std::min(std::max(state.height, 1), 100); if (ImGui::Button("Create")) { TileFloor floor(state.width, state.height); this->package.get_current_level().add_floor(floor); this->package.get_current_level().move_to_floor( this->package.get_current_level().get_floors().size() - 1); ImGui::SetWindowCollapsed(true); } if (ImGui::IsWindowCollapsed()) { ImGui::SetWindowCollapsed(false); this->newFloorState = std::nullopt; } ImGui::End(); } void Editor::createNewLevel(const sf::RenderWindow &window) { if (!this->newLevelState.has_value()) { this->newLevelState = std::make_optional((NewLevelState){}); } sf::Vector2u windowSize = window.getSize(); NewLevelState &state = this->newLevelState.value(); ImGui::SetNextWindowPos( ImVec2(windowSize.x / 2.0f - 100, windowSize.y / 2.0f - 40)); ImGui::SetNextWindowSize(ImVec2(200, 80)); ImGui::Begin("New level", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); ImGui::InputText("Name", state.name, sizeof(state.name)); if (ImGui::Button("Create")) { TileLevel level(state.name); this->package.add_level(level); this->package.move_to_level_index(this->package.get_levels().size() - 1); ImGui::SetWindowCollapsed(true); } if (ImGui::IsWindowCollapsed()) { ImGui::SetWindowCollapsed(false); this->newLevelState = std::nullopt; } ImGui::End(); } void Editor::drawHeaderBar(sf::RenderWindow &window) { LevelPackage &pkg = this->package; if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("File")) { if (!pkg.get_levels().empty() && !pkg.get_current_level().get_floors().empty()) { if (ImGui::MenuItem("Export")) { this->savePackageState = std::make_optional((SavePackageState){}); } ImGui::Separator(); } if (ImGui::MenuItem("Quit")) { window.close(); } ImGui::EndMenu(); } if (!pkg.get_levels().empty() && !pkg.get_current_level().get_floors().empty()) { if (ImGui::BeginMenu("Edit")) { if (ImGui::MenuItem("New level")) { createNewLevel(window); } if (ImGui::MenuItem("New floor")) { createNewFloor(window); } ImGui::EndMenu(); } if (ImGui::BeginMenu("Levels")) { if (ImGui::BeginMenu("Move to level...")) { for (int i = 0; i < this->package.get_levels().size(); i++) { std::ostringstream ss; ss << this->package.get_levels().at(i).get_name(); ss << " (Level "; if (i < 10) { ss << "0"; } ss << i; ss << ")"; if (ImGui::MenuItem(ss.str().c_str())) { this->package.move_to_level_index(i); } } ImGui::EndMenu(); } if (ImGui::BeginMenu("Move to floor...")) { TileLevel &level = this->package.get_current_level(); for (int i = 0; i < level.get_floors().size(); i++) { std::ostringstream ss; ss << "Floor "; if (i < 10) { ss << "0"; } ss << i; if (ImGui::MenuItem(ss.str().c_str())) { level.move_to_floor(i); } } ImGui::EndMenu(); } ImGui::EndMenu(); } } ImGui::SameLine(ImGui::GetContentRegionAvail().x - 200); // Current level and floor info if (!pkg.get_levels().empty() && !pkg.get_current_level().get_floors().empty()) { TileLevel &level = this->package.get_current_level(); TileFloor &floor = level.get_current_floor(); ImGui::Text("Level %s%d | Floor %s%d | Tiles: %d", (this->package.get_current_level_index() < 10 ? "0" : ""), this->package.get_current_level_index(), (level.get_current_floor_index() < 10 ? "0" : ""), level.get_current_floor_index(), floor.get_tile_count()); } ImGui::EndMainMenuBar(); } } void Editor::render(sf::RenderWindow &window) { sf::Vector2u windowSize = window.getSize(); int width = 400; int height = windowSize.y - 20.0f; int x = windowSize.x - width; int y = windowSize.y - height; // --- HEADER BAR --- this->drawHeaderBar(window); // --- EDITOR WINDOW --- ImGui::SetNextWindowPos(ImVec2(x, y)); ImGui::SetNextWindowSize(ImVec2(width, height)); ImGui::Begin("Editor", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar); // --- SELECTED TILE --- ImGui::BeginChild("SelectedTileRegion", ImVec2(200, 100)); if (this->selectedTile.has_value()) { TilesetTile *t = this->selectedTile->get(); if (t->type == TILE_FLOOR) { ImGui::Text("FLOOR"); } else if (t->type == TILE_WALL) { ImGui::Text("WALL"); } else { ImGui::Text("UNKNOWN"); } ImGui::Image(t->texture, sf::Vector2f(64, 64)); } else { ImGui::Text("NO TILE SELECTED!"); ImGui::Dummy(ImVec2(64, 64)); } ImGui::EndChild(); ImGui::SameLine(); // --- EDITOR SETTINGS --- ImGui::BeginChild("EditorSettingsRegion", ImVec2(170, 100)); ImGui::Text("Rotation is %.0f degrees", this->rotation); ImGui::EndChild(); ImGui::BeginDisabled(this->newLevelState.has_value() || this->newFloorState.has_value() || this->savePackageState.has_value()); // --- TILE SELECTION --- ImGui::BeginChild("TileSelectionRegion", ImVec2(0, 400), ImGuiChildFlags_Border, ImGuiWindowFlags_HorizontalScrollbar); float padding = 10.0f; float imageSize = TILE_WIDTH * 2.5f; int columns = ImGui::GetContentRegionAvail().x / (imageSize + padding); int count = 0; for (auto it = this->package.get_tileset().get_entries().begin(); it != this->package.get_tileset().get_entries().end(); ++it) { if (ImGui::ImageButton(std::to_string(it->get()->id).c_str(), it->get()->texture, sf::Vector2f(imageSize, imageSize))) { this->selectedTile = std::make_optional(*it); } count++; if (count % columns != 0) { ImGui::SameLine(); } } ImGui::EndChild(); // --- "CREATING A NEW TILE" WINDOW --- if (ImGui::Button("Add tile") && !this->newTileState.has_value()) { this->newTileState = std::make_optional((NewTileState){"", {}, TILE_FLOOR}); } ImGui::EndDisabled(); if (this->newTileState.has_value()) { NewTileState &state = this->newTileState.value(); ImGui::SetNextWindowPos( ImVec2(windowSize.x / 2.0f - 200, windowSize.y / 2.0f - 250)); ImGui::SetNextWindowSize(ImVec2(400, 500)); ImGui::Begin("Creating a new tile", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize); // -- Tile type -- ImGui::Text("Tile type"); if (ImGui::RadioButton("Floor", state.type == TILE_FLOOR)) { state.type = TILE_FLOOR; } ImGui::SameLine(); if (ImGui::RadioButton("Wall", state.type == TILE_WALL)) { state.type = TILE_WALL; } // -- Texture loading -- ImGui::Text("2D"); if (!state.path.empty()) { ImGui::Image(state.texture); ImGui::SameLine(); } if (ImGui::Button("Load texture file")) { NFD::UniquePath outPath; nfdfilteritem_t filterItem[1] = {{"Images", "png"}}; nfdresult_t result = NFD::OpenDialog(outPath, filterItem, 1); if (result == NFD_OKAY) { state.path = outPath.get(); if (!state.texture.loadFromFile(state.path)) { state.path = ""; } } } // -- Tile preview -- if (!state.path.empty()) { for (int y = 0; y <= 5; y++) { for (int x = 0; x <= 10; x++) { ImGui::Image(state.texture); if (x < 10) { ImGui::SameLine(0, 0); } } } } // -- Tile creation -- if (ImGui::Button("Create a new tile")) { this->package.get_tileset().add_entry(state.path, state.type); ImGui::SetWindowCollapsed(true); } if (ImGui::IsWindowCollapsed()) { ImGui::SetWindowCollapsed(false); this->newTileState = std::nullopt; } ImGui::End(); } ImGui::End(); if (package.get_levels().empty() || this->newLevelState.has_value()) { this->createNewLevel(window); return; } if (package.get_current_level().get_floors().empty() || this->newFloorState.has_value()) { this->createNewFloor(window); return; } if (this->savePackageState.has_value()) { this->createSavePackageWindow(window); return; } } const float Editor::get_zoom() const { return this->zoom; } }