////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor 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 3 of the License, or // (at your option) any later version. // // Remere's Map Editor 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 . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "gui.h" #include "application.h" #include "client_assets.h" #include "main_menubar.h" #include "editor.h" #include "brush.h" #include "map.h" #include "sprites.h" #include "materials.h" #include "doodad_brush.h" #include "spawn_monster_brush.h" #include "object_pool.h" #include "common_windows.h" #include "result_window.h" #include "minimap_window.h" #include "palette_window.h" #include "map_display.h" #include "application.h" #include "welcome_dialog.h" #include "spawn_npc_brush.h" #include "actions_history_window.h" #include "lua/lua_scripts_window.h" #include "sprite_appearances.h" #include "preferences.h" #include "live_client.h" #include "live_tab.h" #include "live_server.h" #include namespace InternalGUI { void logErrorAndSetMessage(const std::string &message, wxString &error) { spdlog::error(message); error = message + ". Error:" + error; g_gui.DestroyLoadBar(); g_gui.unloadMapWindow(); } } // namespace (internal use only) const wxEventType EVT_UPDATE_MENUS = wxNewEventType(); const wxEventType EVT_UPDATE_ACTIONS = wxNewEventType(); // Global GUI instance GUI g_gui; // GUI class implementation GUI::GUI() : aui_manager(nullptr), root(nullptr), minimap(nullptr), gem(nullptr), search_result_window(nullptr), actions_history_window(nullptr), script_manager_window(nullptr), secondary_map(nullptr), doodad_buffer_map(nullptr), house_brush(nullptr), house_exit_brush(nullptr), waypoint_brush(nullptr), optional_brush(nullptr), eraser(nullptr), normal_door_brush(nullptr), locked_door_brush(nullptr), magic_door_brush(nullptr), quest_door_brush(nullptr), hatch_door_brush(nullptr), window_door_brush(nullptr), OGLContext(nullptr), mode(SELECTION_MODE), pasting(false), hotkeys_enabled(true), current_brush(nullptr), previous_brush(nullptr), brush_shape(BRUSHSHAPE_SQUARE), brush_size(0), brush_variation(0), monster_spawntime(0), npc_spawntime(0), use_custom_thickness(false), custom_thickness_mod(0.0), progressBar(nullptr), disabled_counter(0) { doodad_buffer_map = newd BaseMap(); } GUI::~GUI() { JoinAsyncSqliteBootstrapThread(); delete doodad_buffer_map; delete g_gui.aui_manager; delete OGLContext; } wxGLContext* GUI::GetGLContext(wxGLCanvas* win) { if (OGLContext == nullptr) { OGLContext = newd wxGLContext(win); } return OGLContext; } wxString GUI::GetDataDirectory() { std::string cfg_str = g_settings.getString(Config::DATA_DIRECTORY); if (!cfg_str.empty()) { FileName dir; dir.Assign(wxstr(cfg_str)); wxString path; if (dir.DirExists()) { path = dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); return path; } } // Silently reset directory FileName exec_directory; try { exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath(); } catch (const std::bad_cast &) { throw; // Crash application (this should never happend anyways...) } exec_directory.AppendDir("data"); return exec_directory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); } wxString GUI::GetExecDirectory() { // Silently reset directory FileName exec_directory; try { exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath(); } catch (const std::bad_cast &) { wxLogError("Could not fetch executable directory."); } return exec_directory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); } wxString GUI::GetLocalDataDirectory() { if (g_settings.getInteger(Config::INDIRECTORY_INSTALLATION)) { FileName dir = GetDataDirectory(); dir.AppendDir("user"); dir.AppendDir("data"); dir.Mkdir(0755, wxPATH_MKDIR_FULL); return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); ; } else { FileName dir = dynamic_cast(wxStandardPaths::Get()).GetUserDataDir(); #ifdef __WINDOWS__ dir.AppendDir("Remere's Map Editor"); #else dir.AppendDir(".rme"); #endif dir.AppendDir("data"); dir.Mkdir(0755, wxPATH_MKDIR_FULL); return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); } } wxString GUI::GetLocalDirectory() { if (g_settings.getInteger(Config::INDIRECTORY_INSTALLATION)) { FileName dir = GetDataDirectory(); dir.AppendDir("user"); dir.Mkdir(0755, wxPATH_MKDIR_FULL); return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); ; } else { FileName dir = dynamic_cast(wxStandardPaths::Get()).GetUserDataDir(); #ifdef __WINDOWS__ dir.AppendDir("Remere's Map Editor"); #else dir.AppendDir(".rme"); #endif dir.Mkdir(0755, wxPATH_MKDIR_FULL); return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR); } } void GUI::discoverDataDirectory(const wxString &existentFile) { wxString currentDir = wxGetCwd(); wxString execDir = GetExecDirectory(); wxString possiblePaths[] = { execDir, currentDir + "/", // these are used usually when running from build directories execDir + "/../", execDir + "/../../", execDir + "/../../../", currentDir + "/../", }; bool found = false; for (const wxString &path : possiblePaths) { if (wxFileName(path + "data/" + existentFile).FileExists()) { m_dataDirectory = path + "data/"; found = true; break; } } if (!found) { wxLogError(wxString() + "Could not find data directory.\n"); } } bool GUI::loadMapWindow(wxString &error, wxArrayString &warnings, bool force /* = false*/) { if (!force && ClientAssets::isLoaded()) { return true; } // There is another version loaded right now, save window layout g_gui.SavePerspective(); // Disable all rendering so the data is not accessed while reloading UnnamedRenderingLock(); DestroyPalettes(); DestroyMinimap(); g_spriteAppearances.terminate(); // Destroy the previous window unloadMapWindow(); bool ret = LoadDataFiles(error, warnings); if (ret) { g_gui.LoadPerspective(); } return ret; } void GUI::EnableHotkeys() { hotkeys_enabled = true; } void GUI::DisableHotkeys() { hotkeys_enabled = false; } bool GUI::AreHotkeysEnabled() const { return hotkeys_enabled; } void GUI::CycleTab(bool forward) { tabbook->CycleTab(forward); } bool GUI::LoadDataFiles(wxString &error, wxArrayString &warnings) { FileName data_path = GetDataDirectory(); FileName exec_directory; try { exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath(); } catch (std::bad_cast &) { error = "Couldn't establish working directory..."; return false; } g_spriteAppearances.init(); g_gui.CreateLoadBar("Loading assets files"); g_gui.SetLoadDone(0, "Loading assets file"); spdlog::info("Loading assets"); g_gui.SetLoadDone(20, "Loading client assets..."); spdlog::info("Loading appearances"); if (!ClientAssets::loadAppearanceProtobuf(error, warnings)) { InternalGUI::logErrorAndSetMessage("Couldn't load catalog-content.json", error); return false; } g_gui.SetLoadDone(30, "Loading items.xml ..."); spdlog::info("Loading items"); FileName itemsPath(exec_directory); itemsPath.AppendDir("data"); itemsPath.AppendDir("items"); itemsPath.SetFullName("items.xml"); if (!g_items.loadFromGameXml(itemsPath, error, warnings)) { warnings.push_back("Couldn't load items.xml: " + error); spdlog::warn("[GUI::LoadDataFiles] {}: {}", itemsPath.GetFullPath().ToStdString(), error.ToStdString()); } { std::string monstersLuaDir = g_settings.getString(Config::MONSTERS_LUA_DIRECTORY); if (monstersLuaDir.empty()) { warnings.push_back("Monsters Lua Directory is not configured. Set it in Edit > Preferences."); spdlog::warn("[GUI::LoadDataFiles] Monsters Lua Directory is not configured."); } else { g_gui.SetLoadDone(47, "Loading Canary monster Lua files..."); wxString luaErr; wxArrayString luaWarn; if (!g_monsters.loadFromLuaDir(wxString(monstersLuaDir), luaErr, luaWarn)) { warnings.push_back("Error loading Canary monster Lua files: " + luaErr); } for (const auto &w : luaWarn) { warnings.push_back(w); } } } { std::string npcsLuaDir = g_settings.getString(Config::NPCS_LUA_DIRECTORY); if (npcsLuaDir.empty()) { warnings.push_back("NPCs Lua Directory is not configured. Set it in Edit > Preferences."); spdlog::warn("[GUI::LoadDataFiles] NPCs Lua Directory is not configured."); } else { g_gui.SetLoadDone(48, "Loading Canary NPC Lua files..."); wxString luaErr; wxArrayString luaWarn; if (!g_npcs.loadFromLuaDir(wxString(npcsLuaDir), luaErr, luaWarn)) { warnings.push_back("Error loading Canary NPC Lua files: " + luaErr); } for (const auto &w : luaWarn) { warnings.push_back(w); } } } g_gui.SetLoadDone(50, "Loading materials.xml ..."); spdlog::info("Loading materials"); g_materials.initializeBrushDatabase(warnings); auto materialsPath = wxString(data_path.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + "materials/materials.xml"); bool startAsyncSqliteBootstrap = false; if (g_settings.getBoolean(Config::USE_SQLITE_MATERIALS)) { bool skipSqliteImport = false; wxString sqliteImportStatus; if (!g_materials.shouldSkipSqliteBootstrapImports(skipSqliteImport, sqliteImportStatus)) { warnings.push_back("SQLite import bootstrap check failed: " + sqliteImportStatus); spdlog::warn("[GUI::LoadDataFiles] SQLite import bootstrap check failed: {}", sqliteImportStatus.ToStdString()); } else if (skipSqliteImport) { spdlog::info("{}", sqliteImportStatus.ToStdString()); } else { spdlog::info("{}", sqliteImportStatus.ToStdString()); startAsyncSqliteBootstrap = true; } } if (!g_materials.loadMaterials(materialsPath, error, warnings)) { warnings.push_back("Couldn't load materials.xml: " + error); spdlog::warn("[GUI::LoadDataFiles] {}: {}", materialsPath.ToStdString(), error.ToStdString()); } g_gui.SetLoadDone(70, "Finishing..."); spdlog::info("Finishing load map..."); g_brushes.init(); g_materials.createOtherTileset(); g_materials.createNpcTileset(); g_gui.DestroyLoadBar(); spdlog::info("Assets loaded"); if (startAsyncSqliteBootstrap) { StartAsyncSqliteBootstrapImport(); } return true; } void GUI::unloadMapWindow() { UnnamedRenderingLock(); gfx.clear(); current_brush = nullptr; previous_brush = nullptr; house_brush = nullptr; house_exit_brush = nullptr; waypoint_brush = nullptr; optional_brush = nullptr; eraser = nullptr; normal_door_brush = nullptr; locked_door_brush = nullptr; magic_door_brush = nullptr; quest_door_brush = nullptr; hatch_door_brush = nullptr; window_door_brush = nullptr; // Reload/unload tears down the in-memory materials graph, so wait for the // background SQLite bootstrap to stop reading shared brush/items state first. JoinAsyncSqliteBootstrapThread(); g_materials.clear(); g_brushes.clear(); g_items.clear(); gfx.clear(); FileName cdb = ClientAssets::getLocalPath(); cdb.SetFullName("monsters.xml"); g_monsters.saveToXML(cdb); cdb.SetFullName("npcs.xml"); g_monsters.saveToXML(cdb); g_monsters.clear(); g_npcs.clear(); } void GUI::SaveCurrentMap(FileName filename, bool showdialog) { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { Editor* editor = mapTab->GetEditor(); if (editor) { editor->saveMap(filename, showdialog); const std::string &filename = editor->getMap().getFilename(); const Position &position = mapTab->GetScreenCenterPosition(); std::ostringstream stream; stream << position; g_settings.setString(Config::RECENT_EDITED_MAP_PATH, filename); g_settings.setString(Config::RECENT_EDITED_MAP_POSITION, stream.str()); } } UpdateTitle(); root->UpdateMenubar(); root->Refresh(); } bool GUI::IsEditorOpen() const { return tabbook != nullptr && GetCurrentMapTab(); } double GUI::GetCurrentZoom() { MapTab* tab = GetCurrentMapTab(); if (tab) { return tab->GetCanvas()->GetZoom(); } return 1.0; } void GUI::SetCurrentZoom(double zoom) { MapTab* tab = GetCurrentMapTab(); if (tab) { tab->GetCanvas()->SetZoom(zoom); } } void GUI::FitViewToMap() { for (int index = 0; index < tabbook->GetTabCount(); ++index) { if (auto* tab = dynamic_cast(tabbook->GetTab(index))) { tab->GetView()->FitToMap(); } } } void GUI::FitViewToMap(MapTab* mt) { for (int index = 0; index < tabbook->GetTabCount(); ++index) { if (auto* tab = dynamic_cast(tabbook->GetTab(index))) { if (tab->HasSameReference(mt)) { tab->GetView()->FitToMap(); } } } } bool GUI::NewMap() { FinishWelcomeDialog(); Editor* editor; try { editor = newd Editor(copybuffer); } catch (std::runtime_error &e) { PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK); return false; } auto* mapTab = newd MapTab(tabbook, editor); mapTab->OnSwitchEditorMode(mode); editor->clearChanges(); SetStatusText("Created new map"); UpdateTitle(); RefreshPalettes(); root->UpdateMenubar(); root->Refresh(); return true; } void GUI::OpenMap() { wxString wildcard = MAP_LOAD_FILE_WILDCARD; wxFileDialog dialog(root, "Open map file", wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE); if (dialog.ShowModal() == wxID_OK) { wxArrayString paths; dialog.GetPaths(paths); for (uint32_t i = 0; i < paths.GetCount(); ++i) { LoadMap(FileName(paths[i])); } } } void GUI::SaveMap() { if (!IsEditorOpen()) { return; } if (GetCurrentMap().hasFile()) { SaveCurrentMap(true); } else { wxString wildcard = MAP_SAVE_FILE_WILDCARD; wxFileDialog dialog(root, "Save...", wxEmptyString, wxEmptyString, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dialog.ShowModal() == wxID_OK) { SaveCurrentMap(dialog.GetPath(), true); } } } void GUI::SaveMapAs() { if (!IsEditorOpen()) { return; } wxString wildcard = MAP_SAVE_FILE_WILDCARD; wxFileDialog dialog(root, "Save As...", "", "", wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dialog.ShowModal() == wxID_OK) { SaveCurrentMap(dialog.GetPath(), true); UpdateTitle(); root->menu_bar->AddRecentFile(dialog.GetPath()); root->UpdateMenubar(); } } bool GUI::LoadMap(const FileName &fileName) { rme::bindPooledObjectOwnerThread(); FinishWelcomeDialog(); if (GetCurrentEditor() && !GetCurrentMap().hasChanged() && !GetCurrentMap().hasFile()) { g_gui.CloseCurrentEditor(); } rme::resetPooledObjectStats(); Editor* editor; try { editor = newd Editor(copybuffer, fileName); } catch (std::runtime_error &e) { rme::dumpPooledObjectStats(); PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK); return false; } rme::dumpPooledObjectStats(); auto* mapTab = newd MapTab(tabbook, editor); mapTab->OnSwitchEditorMode(mode); root->AddRecentFile(fileName); mapTab->GetView()->FitToMap(); UpdateTitle(); ListDialog("Map loader errors", mapTab->GetMap()->getWarnings()); // Npc and monsters root->DoQueryImportCreatures(); FitViewToMap(mapTab); root->UpdateMenubar(); std::string path = g_settings.getString(Config::RECENT_EDITED_MAP_PATH); if (!path.empty()) { FileName file(path); if (file == fileName) { std::istringstream stream(g_settings.getString(Config::RECENT_EDITED_MAP_POSITION)); Position position; stream >> position; mapTab->SetScreenCenterPosition(position); } } for (const auto &palette : palettes) { palette->OnUpdate(mapTab->GetMap()); } return true; } Editor* GUI::GetCurrentEditor() { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { return mapTab->GetEditor(); } return nullptr; } EditorTab* GUI::GetTab(int idx) { return tabbook->GetTab(idx); } int GUI::GetTabCount() const { return tabbook->GetTabCount(); } EditorTab* GUI::GetCurrentTab() { return tabbook->GetCurrentTab(); } MapTab* GUI::GetCurrentMapTab() const { if (tabbook && tabbook->GetTabCount() > 0) { EditorTab* editorTab = tabbook->GetCurrentTab(); auto* mapTab = dynamic_cast(editorTab); return mapTab; } return nullptr; } Map &GUI::GetCurrentMap() { Editor* editor = GetCurrentEditor(); ASSERT(editor); return editor->getMap(); } int GUI::GetOpenMapCount() { std::set open_maps; for (int i = 0; i < tabbook->GetTabCount(); ++i) { auto* tab = dynamic_cast(tabbook->GetTab(i)); if (tab) { open_maps.insert(open_maps.begin(), tab->GetMap()); } } return static_cast(open_maps.size()); } bool GUI::ShouldSave() { Editor* editor = GetCurrentEditor(); ASSERT(editor); return editor->hasChanges(); } void GUI::AddPendingCanvasEvent(wxEvent &event) { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { mapTab->GetCanvas()->GetEventHandler()->AddPendingEvent(event); } } void GUI::CloseCurrentEditor() { RefreshPalettes(); tabbook->DeleteTab(tabbook->GetSelection()); root->UpdateMenubar(); } bool GUI::CloseLiveEditors(LiveSocket* sock) { for (int i = 0; i < tabbook->GetTabCount(); ++i) { auto* mapTab = dynamic_cast(tabbook->GetTab(i)); if (mapTab) { Editor* editor = mapTab->GetEditor(); if (editor->GetLiveClient() == sock) { tabbook->DeleteTab(i--); } } auto* liveLogTab = dynamic_cast(tabbook->GetTab(i)); if (liveLogTab) { if (liveLogTab->GetSocket() == sock) { liveLogTab->Disconnect(); tabbook->DeleteTab(i--); } } } root->UpdateMenubar(); return true; } bool GUI::CloseAllEditors() { for (int i = 0; i < tabbook->GetTabCount(); ++i) { auto* mapTab = dynamic_cast(tabbook->GetTab(i)); if (mapTab) { if (mapTab->IsUniqueReference() && mapTab->GetMap() && mapTab->GetMap()->hasChanged()) { tabbook->SetFocusedTab(i); if (!root->DoQuerySave(false)) { return false; } else { RefreshPalettes(); tabbook->DeleteTab(i--); } } else { tabbook->DeleteTab(i--); } } } if (root) { root->UpdateMenubar(); } return true; } void GUI::NewMapView() { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { auto* newMapTab = newd MapTab(mapTab); newMapTab->OnSwitchEditorMode(mode); SetStatusText("Created new view"); UpdateTitle(); RefreshPalettes(); root->UpdateMenubar(); root->Refresh(); } } void GUI::LoadPerspective() { if (!ClientAssets::isLoaded()) { if (g_settings.getInteger(Config::WINDOW_MAXIMIZED)) { root->Maximize(); } else { root->SetSize(wxSize( g_settings.getInteger(Config::WINDOW_WIDTH), g_settings.getInteger(Config::WINDOW_HEIGHT) )); } } else { std::string tmp; std::string layout = g_settings.getString(Config::PALETTE_LAYOUT); std::vector palette_list; for (char c : layout) { if (c == '|') { palette_list.push_back(tmp); tmp.clear(); } else { tmp.push_back(c); } } if (!tmp.empty()) { palette_list.push_back(tmp); } for (const std::string &name : palette_list) { PaletteWindow* palette = CreatePalette(); wxAuiPaneInfo &info = aui_manager->GetPane(palette); aui_manager->LoadPaneInfo(wxstr(name), info); if (info.IsFloatable()) { bool offscreen = true; for (uint32_t index = 0; index < wxDisplay::GetCount(); ++index) { wxDisplay display(index); wxRect rect = display.GetClientArea(); if (rect.Contains(info.floating_pos)) { offscreen = false; break; } } if (offscreen) { info.Dock(); } } } if (g_settings.getInteger(Config::MINIMAP_VISIBLE)) { if (!minimap) { wxAuiPaneInfo info; const wxString &data = wxstr(g_settings.getString(Config::MINIMAP_LAYOUT)); aui_manager->LoadPaneInfo(data, info); minimap = newd MinimapWindow(root); aui_manager->AddPane(minimap, info); } else { wxAuiPaneInfo &info = aui_manager->GetPane(minimap); const wxString &data = wxstr(g_settings.getString(Config::MINIMAP_LAYOUT)); aui_manager->LoadPaneInfo(data, info); } wxAuiPaneInfo &info = aui_manager->GetPane(minimap); if (info.IsFloatable()) { bool offscreen = true; for (uint32_t index = 0; index < wxDisplay::GetCount(); ++index) { wxDisplay display(index); wxRect rect = display.GetClientArea(); if (rect.Contains(info.floating_pos)) { offscreen = false; break; } } if (offscreen) { info.Dock(); } } } if (g_settings.getInteger(Config::ACTIONS_HISTORY_VISIBLE)) { if (!actions_history_window) { wxAuiPaneInfo info; const wxString &data = wxstr(g_settings.getString(Config::ACTIONS_HISTORY_LAYOUT)); aui_manager->LoadPaneInfo(data, info); actions_history_window = new ActionsHistoryWindow(root); aui_manager->AddPane(actions_history_window, info); } else { wxAuiPaneInfo &info = aui_manager->GetPane(actions_history_window); const wxString &data = wxstr(g_settings.getString(Config::ACTIONS_HISTORY_LAYOUT)); aui_manager->LoadPaneInfo(data, info); } wxAuiPaneInfo &info = aui_manager->GetPane(actions_history_window); if (info.IsFloatable()) { bool offscreen = true; for (uint32_t index = 0; index < wxDisplay::GetCount(); ++index) { wxDisplay display(index); wxRect rect = display.GetClientArea(); if (rect.Contains(info.floating_pos)) { offscreen = false; break; } } if (offscreen) { info.Dock(); } } } aui_manager->Update(); root->UpdateMenubar(); } root->GetAuiToolBar()->LoadPerspective(); } void GUI::SavePerspective() { g_settings.setInteger(Config::WINDOW_MAXIMIZED, root->IsMaximized()); g_settings.setInteger(Config::WINDOW_WIDTH, root->GetSize().GetWidth()); g_settings.setInteger(Config::WINDOW_HEIGHT, root->GetSize().GetHeight()); g_settings.setInteger(Config::MINIMAP_VISIBLE, minimap ? 1 : 0); g_settings.setInteger(Config::ACTIONS_HISTORY_VISIBLE, actions_history_window ? 1 : 0); wxString pinfo; for (auto &palette : palettes) { if (aui_manager->GetPane(palette).IsShown()) { pinfo << aui_manager->SavePaneInfo(aui_manager->GetPane(palette)) << "|"; } } g_settings.setString(Config::PALETTE_LAYOUT, nstr(pinfo)); if (minimap) { wxString s = aui_manager->SavePaneInfo(aui_manager->GetPane(minimap)); g_settings.setString(Config::MINIMAP_LAYOUT, nstr(s)); } if (actions_history_window) { wxString info = aui_manager->SavePaneInfo(aui_manager->GetPane(actions_history_window)); g_settings.setString(Config::ACTIONS_HISTORY_LAYOUT, nstr(info)); } root->GetAuiToolBar()->SavePerspective(); } void GUI::HideSearchWindow() { if (search_result_window) { aui_manager->GetPane(search_result_window).Show(false); aui_manager->Update(); } } SearchResultWindow* GUI::ShowSearchWindow() { if (search_result_window == nullptr) { search_result_window = newd SearchResultWindow(root); aui_manager->AddPane(search_result_window, wxAuiPaneInfo().Caption("Search Results")); } else { aui_manager->GetPane(search_result_window).Show(); } aui_manager->Update(); return search_result_window; } ActionsHistoryWindow* GUI::ShowActionsWindow() { if (!actions_history_window) { actions_history_window = new ActionsHistoryWindow(root); aui_manager->AddPane(actions_history_window, wxAuiPaneInfo().Caption("Actions History")); } else { aui_manager->GetPane(actions_history_window).Show(); } aui_manager->Update(); actions_history_window->RefreshActions(); return actions_history_window; } void GUI::HideActionsWindow() { if (actions_history_window) { aui_manager->GetPane(actions_history_window).Show(false); aui_manager->Update(); } } LuaScriptsWindow* GUI::ShowScriptManagerWindow() { if (!script_manager_window) { script_manager_window = new LuaScriptsWindow(root); LuaScriptsWindow::SetInstance(script_manager_window); aui_manager->AddPane(script_manager_window, wxAuiPaneInfo().Caption("Script Manager").Right().Layer(1).CloseButton(true).MinSize(300, 200).BestSize(400, 300)); } else { aui_manager->GetPane(script_manager_window).Show(); } aui_manager->Update(); script_manager_window->RefreshScriptList(); return script_manager_window; } //============================================================================= // Palette Window Interface implementation PaletteWindow* GUI::GetPalette() { if (palettes.empty()) { return nullptr; } return palettes.front(); } PaletteWindow* GUI::NewPalette() { return CreatePalette(); } void GUI::RefreshPalettes(Map* m, bool usedefault) { for (auto &palette : palettes) { const auto currentMap = IsEditorOpen() ? &GetCurrentMap() : nullptr; const auto defaultMap = usedefault ? currentMap : nullptr; palette->OnUpdate(m ? m : defaultMap); } SelectBrush(); RefreshActions(); } void GUI::RefreshOtherPalettes(PaletteWindow* p) { for (auto &palette : palettes) { if (palette != p) { palette->OnUpdate(IsEditorOpen() ? &GetCurrentMap() : nullptr); } } SelectBrush(); } PaletteWindow* GUI::CreatePalette() { if (!ClientAssets::isLoaded()) { return nullptr; } auto* palette = newd PaletteWindow(root, g_materials.tilesets); aui_manager->AddPane(palette, wxAuiPaneInfo().Caption("Palette").TopDockable(false).BottomDockable(false)); palette->OnUpdate(GetCurrentMapTab()->GetMap()); aui_manager->Update(); // Make us the active palette palettes.push_front(palette); // Select brush from this palette SelectBrushInternal(palette->GetSelectedBrush()); RefreshPalettes(); return palette; } void GUI::ActivatePalette(PaletteWindow* p) { palettes.erase(std::find(palettes.begin(), palettes.end(), p)); palettes.push_front(p); } void GUI::DestroyPalettes() { for (auto palette : palettes) { aui_manager->DetachPane(palette); palette->Destroy(); palette = nullptr; } palettes.clear(); aui_manager->Update(); } void GUI::RebuildPalettes() { // Palette lits might be modified due to active palette changes // Use a temporary list for iterating PaletteList tmp = palettes; for (auto &piter : tmp) { piter->ReloadSettings(IsEditorOpen() ? &GetCurrentMap() : nullptr); } aui_manager->Update(); } void GUI::ShowPalette() { if (palettes.empty()) { return; } for (auto &palette : palettes) { if (aui_manager->GetPane(palette).IsShown()) { return; } } aui_manager->GetPane(palettes.front()).Show(true); aui_manager->Update(); } void GUI::SelectPalettePage(PaletteType pt) { if (palettes.empty()) { CreatePalette(); } PaletteWindow* p = GetPalette(); if (!p) { return; } ShowPalette(); p->SelectPage(pt); aui_manager->Update(); SelectBrushInternal(p->GetSelectedBrush()); } //============================================================================= // Minimap Window Interface Implementation void GUI::CreateMinimap() { if (!ClientAssets::isLoaded()) { return; } if (minimap) { aui_manager->GetPane(minimap).Show(true); } else { minimap = newd MinimapWindow(root); minimap->Show(true); aui_manager->AddPane(minimap, wxAuiPaneInfo().Caption("Minimap")); } aui_manager->Update(); } void GUI::HideMinimap() { if (minimap) { aui_manager->GetPane(minimap).Show(false); aui_manager->Update(); } } void GUI::DestroyMinimap() { if (minimap) { aui_manager->DetachPane(minimap); aui_manager->Update(); minimap->Destroy(); minimap = nullptr; } } void GUI::UpdateMinimap(bool immediate) { if (IsMinimapVisible()) { if (immediate) { minimap->Refresh(); } else { minimap->DelayedUpdate(); } } } bool GUI::IsMinimapVisible() const { if (minimap) { const wxAuiPaneInfo &pi = aui_manager->GetPane(minimap); if (pi.IsShown()) { return true; } } return false; } //============================================================================= void GUI::RefreshView() { EditorTab* editorTab = GetCurrentTab(); if (!editorTab) { return; } if (!dynamic_cast(editorTab)) { editorTab->GetWindow()->Refresh(); return; } std::vector editorTabs; for (int32_t index = 0; index < tabbook->GetTabCount(); ++index) { auto* mapTab = dynamic_cast(tabbook->GetTab(index)); if (mapTab) { editorTabs.push_back(mapTab); } } for (EditorTab* editorTab : editorTabs) { auto* mapTab = static_cast(editorTab); mapTab->GetCanvas()->Refresh(); // MapCanvas::Refresh() → markDirty() + wxGLCanvas::Refresh() editorTab->GetWindow()->Update(); } } void GUI::CreateLoadBar(wxString message, bool canCancel /* = false */) { CreateLoadBar(message, canCancel, true); } void GUI::CreateLoadBar(wxString message, bool canCancel, bool appModal) { progressText = message; progressFrom = 0; progressTo = 100; currentProgress = -1; const long style = (appModal ? wxPD_APP_MODAL : 0) | wxPD_SMOOTH | (canCancel ? wxPD_CAN_ABORT : 0); progressBar = newd wxGenericProgressDialog("Loading", progressText + " (0%)", 100, root, style); progressBar->SetSize(280, -1); progressBar->Show(true); for (int idx = 0; idx < tabbook->GetTabCount(); ++idx) { auto* mt = dynamic_cast(tabbook->GetTab(idx)); if (mt && mt->GetEditor()->IsLiveServer()) { mt->GetEditor()->GetLiveServer()->startOperation(progressText); } } progressBar->Update(0); } void GUI::SetLoadScale(int32_t from, int32_t to) { progressFrom = from; progressTo = to; } bool GUI::SetLoadDone(int32_t done, const wxString &newMessage) { if (done == 100) { DestroyLoadBar(); return true; } int32_t newProgress = progressFrom + static_cast((done / 100.f) * (progressTo - progressFrom)); newProgress = std::max(0, std::min(100, newProgress)); bool messageChanged = !newMessage.empty() && newMessage != progressText; if (newProgress == currentProgress && !messageChanged) { return true; } if (!newMessage.empty()) { progressText = newMessage; } bool skip = false; bool continueProcessing = true; if (progressBar) { continueProcessing = progressBar->Update( newProgress, wxString::Format("%s (%d%%)", progressText.c_str(), newProgress), &skip ); currentProgress = newProgress; } for (int32_t index = 0; index < tabbook->GetTabCount(); ++index) { auto* mapTab = dynamic_cast(tabbook->GetTab(index)); if (mapTab && mapTab->GetEditor()) { LiveServer* server = mapTab->GetEditor()->GetLiveServer(); if (server) { server->updateOperation(newProgress); } } } return continueProcessing && !skip; } void GUI::DestroyLoadBar() { if (progressBar) { progressBar->Show(false); currentProgress = -1; progressBar->Destroy(); progressBar = nullptr; if (root->IsActive()) { root->Raise(); } else { root->RequestUserAttention(); } } } void GUI::ShowWelcomeDialog(const wxBitmap &icon) { std::vector recent_files = root->GetRecentFiles(); welcomeDialog = newd WelcomeDialog(__W_RME_APPLICATION_NAME__, "Version " + __W_RME_VERSION__, FROM_DIP(root, wxSize(800, 480)), icon, recent_files); welcomeDialog->Bind(wxEVT_CLOSE_WINDOW, &GUI::OnWelcomeDialogClosed, this); welcomeDialog->Bind(WELCOME_DIALOG_ACTION, &GUI::OnWelcomeDialogAction, this); welcomeDialog->Show(); UpdateMenubar(); } void GUI::FinishWelcomeDialog() { if (welcomeDialog != nullptr) { welcomeDialog->Hide(); root->Show(); welcomeDialog->Destroy(); welcomeDialog = nullptr; } } bool GUI::IsWelcomeDialogShown() { return welcomeDialog != nullptr && welcomeDialog->IsShown(); } void GUI::OnWelcomeDialogClosed(wxCloseEvent &event) { welcomeDialog->Destroy(); root->Close(); } void GUI::OnWelcomeDialogAction(wxCommandEvent &event) { if (event.GetId() == wxID_NEW) { NewMap(); } else if (event.GetId() == wxID_OPEN) { LoadMap(FileName(event.GetString())); } } void GUI::UpdateMenubar() { if (root == nullptr) { return; } root->UpdateMenubar(); } void GUI::SetScreenCenterPosition(const Position &position, bool showIndicator) { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { mapTab->SetScreenCenterPosition(position, showIndicator); } } void GUI::DoCut() { if (!IsSelectionMode()) { return; } Editor* editor = GetCurrentEditor(); if (!editor) { return; } editor->copybuffer.cut(*editor, GetCurrentFloor()); RefreshView(); root->UpdateMenubar(); } void GUI::DoCopy() { if (!IsSelectionMode()) { return; } Editor* editor = GetCurrentEditor(); if (!editor) { return; } editor->copybuffer.copy(*editor, GetCurrentFloor()); RefreshView(); root->UpdateMenubar(); } void GUI::DoPaste() { MapTab* mapTab = GetCurrentMapTab(); if (mapTab) { copybuffer.paste(*mapTab->GetEditor(), mapTab->GetCanvas()->GetCursorPosition()); } } void GUI::PreparePaste() { Editor* editor = GetCurrentEditor(); if (editor) { SetSelectionMode(); Selection &selection = editor->getSelection(); selection.start(); selection.clear(); selection.finish(); StartPasting(); RefreshView(); } } void GUI::StartPasting() { if (GetCurrentEditor()) { pasting = true; secondary_map = ©buffer.getBufferMap(); } } void GUI::EndPasting() { if (pasting) { pasting = false; secondary_map = nullptr; } } bool GUI::CanUndo() { Editor* editor = GetCurrentEditor(); return (editor && editor->canUndo()); } bool GUI::CanRedo() { Editor* editor = GetCurrentEditor(); return (editor && editor->canRedo()); } bool GUI::DoUndo() { Editor* editor = GetCurrentEditor(); if (editor && editor->canUndo()) { editor->undo(); if (editor->hasSelection()) { SetSelectionMode(); } SetStatusText("Undo action"); UpdateMinimap(); root->UpdateMenubar(); RefreshView(); return true; } return false; } bool GUI::DoRedo() { Editor* editor = GetCurrentEditor(); if (editor && editor->canRedo()) { editor->redo(); if (editor->hasSelection()) { SetSelectionMode(); } SetStatusText("Redo action"); UpdateMinimap(); root->UpdateMenubar(); RefreshView(); return true; } return false; } int GUI::GetCurrentFloor() { MapTab* tab = GetCurrentMapTab(); ASSERT(tab); return tab->GetCanvas()->GetFloor(); } void GUI::ChangeFloor(int new_floor) { MapTab* tab = GetCurrentMapTab(); if (tab) { int old_floor = GetCurrentFloor(); if (new_floor < rme::MapMinLayer || new_floor > rme::MapMaxLayer) { return; } if (old_floor != new_floor) { tab->GetCanvas()->ChangeFloor(new_floor); } } } void GUI::SetStatusText(wxString text) { if (g_gui.root == nullptr) { return; } g_gui.root->SetStatusText(text, 0); } bool GUI::IsAsyncSqliteBootstrapRunning() const { return sqlite_bootstrap_running_.load(); } void GUI::JoinAsyncSqliteBootstrapThread() { if (!sqlite_bootstrap_thread_.joinable()) { return; } sqlite_bootstrap_thread_.join(); sqlite_bootstrap_running_.store(false); } void GUI::RunAsyncSqliteBootstrapImport() { wxString sqliteImportError; wxArrayString sqliteWarnings; const bool success = g_materials.bootstrapSqliteDatabase(sqliteImportError, sqliteWarnings); for (const wxString &warning : sqliteWarnings) { spdlog::warn("[SQLiteBootstrap] {}", warning.ToStdString()); } if (!success && !sqliteImportError.IsEmpty()) { spdlog::error("[SQLiteBootstrap] {}", sqliteImportError.ToStdString()); } sqlite_bootstrap_running_.store(false); if (wxTheApp) { wxTheApp->CallAfter([this, success, sqliteImportError, sqliteWarnings]() { HandleAsyncSqliteBootstrapResult(success, sqliteImportError, sqliteWarnings); }); } } void GUI::HandleAsyncSqliteBootstrapResult(bool success, const wxString &sqliteImportError, const wxArrayString &sqliteWarnings) { g_gui.UpdateMenubar(); if (success) { if (sqliteWarnings.IsEmpty()) { g_gui.SetStatusText("SQLite materials database built in background."); } else { g_gui.SetStatusText("SQLite materials database built in background with warnings."); } return; } g_gui.SetStatusText("SQLite materials background build failed."); if (g_gui.root) { g_gui.PopupDialog(g_gui.root, "SQLite Materials Import Failed", sqliteImportError, wxOK | wxICON_WARNING); } } void GUI::StartAsyncSqliteBootstrapImport() { if (sqlite_bootstrap_running_.exchange(true)) { return; } JoinAsyncSqliteBootstrapThread(); SetStatusText("Building SQLite materials database in background..."); UpdateMenubar(); sqlite_bootstrap_thread_ = std::thread(&GUI::RunAsyncSqliteBootstrapImport, this); } void GUI::SetTitle(wxString title) { if (g_gui.root == nullptr) { return; } #ifdef NIGHTLY_BUILD #ifdef SVN_BUILD #define TITLE_APPEND (wxString(" (Nightly Build #") << i2ws(SVN_BUILD) << ")") #else #define TITLE_APPEND (wxString(" (Nightly Build)")) #endif #else #ifdef SVN_BUILD #define TITLE_APPEND (wxString(" (Build #") << i2ws(SVN_BUILD) << ")") #else #define TITLE_APPEND (wxString("")) #endif #endif #ifdef __EXPERIMENTAL__ if (title != "") { g_gui.root->SetTitle(title << " - Canary's Map Editor BETA" << TITLE_APPEND); } else { g_gui.root->SetTitle(wxString("Canary's Map Editor BETA") << TITLE_APPEND); } #elif __SNAPSHOT__ if (title != "") { g_gui.root->SetTitle(title << " - Canary's Map Editor - SNAPSHOT" << TITLE_APPEND); } else { g_gui.root->SetTitle(wxString("Canary's Map Editor - SNAPSHOT") << TITLE_APPEND); } #else if (!title.empty()) { g_gui.root->SetTitle(title << " - Canary's Map Editor" << TITLE_APPEND); } else { g_gui.root->SetTitle(wxString("Canary's Map Editor") << TITLE_APPEND); } #endif } void GUI::UpdateTitle() { if (tabbook->GetTabCount() > 0) { SetTitle(tabbook->GetCurrentTab()->GetTitle()); for (int idx = 0; idx < tabbook->GetTabCount(); ++idx) { if (tabbook->GetTab(idx)) { tabbook->SetTabLabel(idx, tabbook->GetTab(idx)->GetTitle()); } } } else { SetTitle(""); } } void GUI::UpdateMenus() { wxCommandEvent evt(EVT_UPDATE_MENUS); g_gui.root->AddPendingEvent(evt); } void GUI::UpdateActions() { wxCommandEvent evt(EVT_UPDATE_ACTIONS); g_gui.root->AddPendingEvent(evt); } void GUI::RefreshActions() { if (actions_history_window) { actions_history_window->RefreshActions(); } } void GUI::ShowToolbar(ToolBarID id, bool show) { if (root && root->GetAuiToolBar()) { root->GetAuiToolBar()->Show(id, show); } } void GUI::SwitchMode() { if (mode == DRAWING_MODE) { SetSelectionMode(); } else { SetDrawingMode(); } } void GUI::SetSelectionMode() { if (mode == SELECTION_MODE) { return; } if (current_brush && current_brush->isDoodad()) { secondary_map = nullptr; } tabbook->OnSwitchEditorMode(SELECTION_MODE); mode = SELECTION_MODE; } void GUI::SetDrawingMode() { if (mode == DRAWING_MODE) { return; } std::set al; for (int idx = 0; idx < tabbook->GetTabCount(); ++idx) { EditorTab* editorTab = tabbook->GetTab(idx); if (MapTab* mapTab = dynamic_cast(editorTab)) { if (al.find(mapTab) != al.end()) { continue; } Editor* editor = mapTab->GetEditor(); Selection &selection = editor->getSelection(); selection.start(Selection::NONE, ACTION_UNSELECT); selection.clear(); selection.finish(); selection.updateSelectionCount(); al.insert(mapTab); } } if (current_brush && current_brush->isDoodad()) { secondary_map = doodad_buffer_map; } else { secondary_map = nullptr; } tabbook->OnSwitchEditorMode(DRAWING_MODE); mode = DRAWING_MODE; } void GUI::SetBrushSizeInternal(int nz) { if (nz != brush_size && current_brush && current_brush->isDoodad() && !current_brush->oneSizeFitsAll()) { brush_size = nz; FillDoodadPreviewBuffer(); secondary_map = doodad_buffer_map; } else { brush_size = nz; } } void GUI::SetBrushSize(int nz) { SetBrushSizeInternal(nz); for (auto &palette : palettes) { palette->OnUpdateBrushSize(brush_shape, brush_size); } root->GetAuiToolBar()->UpdateBrushSize(brush_shape, brush_size); } void GUI::SetBrushVariation(int nz) { if (nz != brush_variation && current_brush && current_brush->isDoodad()) { // Monkey! brush_variation = nz; FillDoodadPreviewBuffer(); secondary_map = doodad_buffer_map; } } void GUI::SetBrushShape(BrushShape bs) { if (bs != brush_shape && current_brush && current_brush->isDoodad() && !current_brush->oneSizeFitsAll()) { // Donkey! brush_shape = bs; FillDoodadPreviewBuffer(); secondary_map = doodad_buffer_map; } brush_shape = bs; for (auto &palette : palettes) { palette->OnUpdateBrushSize(brush_shape, brush_size); } root->GetAuiToolBar()->UpdateBrushSize(brush_shape, brush_size); } void GUI::SetBrushThickness(bool on, int x, int y) { use_custom_thickness = on; if (x != -1 || y != -1) { custom_thickness_mod = std::max(x, 1.f) / std::max(y, 1.f); } if (current_brush && current_brush->isDoodad()) { FillDoodadPreviewBuffer(); } RefreshView(); } void GUI::SetBrushThickness(int low, int ceil) { custom_thickness_mod = std::max(low, 1.f) / std::max(ceil, 1.f); if (use_custom_thickness && current_brush && current_brush->isDoodad()) { FillDoodadPreviewBuffer(); } RefreshView(); } void GUI::DecreaseBrushSize(bool wrap) { switch (brush_size) { case 0: { if (wrap) { SetBrushSize(11); } break; } case 1: { SetBrushSize(0); break; } case 2: case 3: { SetBrushSize(1); break; } case 4: case 5: { SetBrushSize(2); break; } case 6: case 7: { SetBrushSize(4); break; } case 8: case 9: case 10: { SetBrushSize(6); break; } case 11: default: { SetBrushSize(8); break; } } } void GUI::IncreaseBrushSize(bool wrap) { switch (brush_size) { case 0: { SetBrushSize(1); break; } case 1: { SetBrushSize(2); break; } case 2: case 3: { SetBrushSize(4); break; } case 4: case 5: { SetBrushSize(6); break; } case 6: case 7: { SetBrushSize(8); break; } case 8: case 9: case 10: { SetBrushSize(11); break; } case 11: default: { if (wrap) { SetBrushSize(0); } break; } } } Brush* GUI::GetCurrentBrush() const { return current_brush; } BrushShape GUI::GetBrushShape() const { if (current_brush == spawn_brush) { return BRUSHSHAPE_SQUARE; } if (current_brush == spawn_npc_brush) { return BRUSHSHAPE_SQUARE; } return brush_shape; } int GUI::GetBrushSize() const { return brush_size; } int GUI::GetBrushVariation() const { return brush_variation; } int GUI::GetSpawnMonsterTime() const { return monster_spawntime; } int GUI::GetSpawnNpcTime() const { return npc_spawntime; } void GUI::SelectBrush() { if (palettes.empty()) { return; } SelectBrushInternal(palettes.front()->GetSelectedBrush()); RefreshView(); } bool GUI::SelectBrush(const Brush* whatbrush, PaletteType primary) { if (palettes.empty()) { if (!CreatePalette()) { return false; } } if (!palettes.front()->OnSelectBrush(whatbrush, primary)) { return false; } SelectBrushInternal(const_cast(whatbrush)); root->GetAuiToolBar()->UpdateBrushButtons(); return true; } void GUI::SelectBrushInternal(Brush* brush) { // Fear no evil don't you say no evil if (current_brush != brush && brush) { previous_brush = current_brush; } current_brush = brush; if (!current_brush) { return; } brush_variation = std::min(brush_variation, brush->getMaxVariation()); FillDoodadPreviewBuffer(); if (brush->isDoodad()) { secondary_map = doodad_buffer_map; } SetDrawingMode(); RefreshView(); } void GUI::SelectPreviousBrush() { if (previous_brush) { SelectBrush(previous_brush); } } void GUI::FillDoodadPreviewBuffer() { if (!current_brush || !current_brush->isDoodad()) { return; } doodad_buffer_map->clear(); DoodadBrush* brush = current_brush->asDoodad(); if (brush->isEmpty(GetBrushVariation())) { return; } int object_count = 0; int area; if (GetBrushShape() == BRUSHSHAPE_SQUARE) { area = 2 * GetBrushSize(); area = area * area + 1; } else { if (GetBrushSize() == 1) { // There is a huge deviation here with the other formula. area = 5; } else { area = int(0.5 + GetBrushSize() * GetBrushSize() * rme::PI); } } const int object_range = (use_custom_thickness ? int(area * custom_thickness_mod) : brush->getThickness() * area / std::max(1, brush->getThicknessCeiling())); const int final_object_count = std::max(1, object_range + random(object_range)); Position center_pos(0x8000, 0x8000, 0x8); if (brush_size > 0 && !brush->oneSizeFitsAll()) { while (object_count < final_object_count) { int retries = 0; bool exit = false; // Try to place objects 5 times while (retries < 5 && !exit) { int pos_retries = 0; int xpos = 0, ypos = 0; bool found_pos = false; if (GetBrushShape() == BRUSHSHAPE_CIRCLE) { while (pos_retries < 5 && !found_pos) { xpos = random(-brush_size, brush_size); ypos = random(-brush_size, brush_size); float distance = sqrt(float(xpos * xpos) + float(ypos * ypos)); if (distance < g_gui.GetBrushSize() + 0.005) { found_pos = true; } else { ++pos_retries; } } } else { found_pos = true; xpos = random(-brush_size, brush_size); ypos = random(-brush_size, brush_size); } if (!found_pos) { ++retries; continue; } // Decide whether the zone should have a composite or several single objects. bool fail = false; if (random(brush->getTotalChance(GetBrushVariation())) <= brush->getCompositeChance(GetBrushVariation())) { // Composite const CompositeTileList &composites = brush->getComposite(GetBrushVariation()); // Figure out if the placement is valid for (const auto &composite : composites) { Position pos = center_pos + composite.first + Position(xpos, ypos, 0); if (Tile* tile = doodad_buffer_map->getTile(pos)) { if (!tile->empty()) { fail = true; break; } } } if (fail) { ++retries; break; } // Transfer items to the stack for (const auto &composite : composites) { Position pos = center_pos + composite.first + Position(xpos, ypos, 0); const ItemVector &items = composite.second; Tile* tile = doodad_buffer_map->getTile(pos); if (!tile) { tile = doodad_buffer_map->allocator(doodad_buffer_map->createTileL(pos)); } for (auto item : items) { tile->addItem(item->deepCopy()); } doodad_buffer_map->setTile(tile->getPosition(), tile); } exit = true; } else if (brush->hasSingleObjects(GetBrushVariation())) { Position pos = center_pos + Position(xpos, ypos, 0); Tile* tile = doodad_buffer_map->getTile(pos); if (tile) { if (!tile->empty()) { fail = true; break; } } else { tile = doodad_buffer_map->allocator(doodad_buffer_map->createTileL(pos)); } int variation = GetBrushVariation(); brush->draw(doodad_buffer_map, tile, &variation); // std::cout << "\tpos: " << tile->getPosition() << std::endl; doodad_buffer_map->setTile(tile->getPosition(), tile); exit = true; } if (fail) { ++retries; break; } } ++object_count; } } else { if (brush->hasCompositeObjects(GetBrushVariation()) && random(brush->getTotalChance(GetBrushVariation())) <= brush->getCompositeChance(GetBrushVariation())) { // Composite const CompositeTileList &composites = brush->getComposite(GetBrushVariation()); // All placement is valid... // Transfer items to the buffer for (const auto &composite : composites) { Position pos = center_pos + composite.first; const ItemVector &items = composite.second; Tile* tile = doodad_buffer_map->allocator(doodad_buffer_map->createTileL(pos)); // std::cout << pos << " = " << center_pos << " + " << buffer_tile->getPosition() << std::endl; for (auto item : items) { tile->addItem(item->deepCopy()); } doodad_buffer_map->setTile(tile->getPosition(), tile); } } else if (brush->hasSingleObjects(GetBrushVariation())) { Tile* tile = doodad_buffer_map->allocator(doodad_buffer_map->createTileL(center_pos)); int variation = GetBrushVariation(); brush->draw(doodad_buffer_map, tile, &variation); doodad_buffer_map->setTile(center_pos, tile); } } } long GUI::PopupDialog(wxWindow* parent, wxString title, wxString text, long style, wxString confisavename, uint32_t configsavevalue) { if (text.empty()) { return wxID_ANY; } wxMessageDialog dlg(parent, text, title, style); return dlg.ShowModal(); } long GUI::PopupDialog(wxString title, wxString text, long style, wxString configsavename, uint32_t configsavevalue) { return g_gui.PopupDialog(g_gui.root, title, text, style, configsavename, configsavevalue); } void GUI::ListDialog(wxWindow* parent, wxString title, const wxArrayString ¶m_items) { if (param_items.empty()) { return; } wxArrayString list_items(param_items); // Create the window wxDialog* dlg = newd wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX); wxSizer* sizer = newd wxBoxSizer(wxVERTICAL); wxListBox* item_list = newd wxListBox(dlg, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE); item_list->SetMinSize(wxSize(500, 300)); for (size_t i = 0; i != list_items.GetCount();) { wxString str = list_items[i]; size_t pos = str.find("\n"); if (pos != wxString::npos) { // Split string! item_list->Append(str.substr(0, pos)); list_items[i] = str.substr(pos + 1); continue; } item_list->Append(list_items[i]); ++i; } sizer->Add(item_list, 1, wxEXPAND); wxSizer* stdsizer = newd wxBoxSizer(wxHORIZONTAL); stdsizer->Add(newd wxButton(dlg, wxID_OK, "OK"), wxSizerFlags(1).Center()); sizer->Add(stdsizer, wxSizerFlags(0).Center()); dlg->SetSizerAndFit(sizer); // Show the window dlg->ShowModal(); delete dlg; } void GUI::ShowTextBox(wxWindow* parent, wxString title, wxString content) { wxDialog* dlg = newd wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxRESIZE_BORDER | wxCAPTION | wxCLOSE_BOX); wxSizer* topsizer = newd wxBoxSizer(wxVERTICAL); wxTextCtrl* text_field = newd wxTextCtrl(dlg, wxID_ANY, content, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); text_field->SetMinSize(wxSize(400, 550)); topsizer->Add(text_field, wxSizerFlags(5).Expand()); wxSizer* choicesizer = newd wxBoxSizer(wxHORIZONTAL); choicesizer->Add(newd wxButton(dlg, wxID_CANCEL, "OK"), wxSizerFlags(1).Center()); topsizer->Add(choicesizer, wxSizerFlags(0).Center()); dlg->SetSizerAndFit(topsizer); dlg->ShowModal(); } void GUI::SetHotkey(int index, Hotkey &hotkey) { ASSERT(index >= 0 && index <= 9); hotkeys[index] = hotkey; SetStatusText("Set hotkey " + i2ws(index) + "."); } const Hotkey &GUI::GetHotkey(int index) const { ASSERT(index >= 0 && index <= 9); return hotkeys[index]; } void GUI::SaveHotkeys() const { std::ostringstream os; for (const auto &hotkey : hotkeys) { os << hotkey << '\n'; } g_settings.setString(Config::NUMERICAL_HOTKEYS, os.str()); } void GUI::LoadHotkeys() { std::istringstream is; is.str(g_settings.getString(Config::NUMERICAL_HOTKEYS)); std::string line; int index = 0; while (getline(is, line)) { std::istringstream line_is; line_is.str(line); line_is >> hotkeys[index]; ++index; } } Hotkey::Hotkey() : type(NONE) { //// } Hotkey::Hotkey(Position _pos) : type(POSITION), pos(_pos) { //// } Hotkey::Hotkey(Brush* brush) : type(BRUSH), brushname(brush->getName()) { //// } Hotkey::Hotkey(std::string _name) : type(BRUSH), brushname(_name) { //// } Hotkey::~Hotkey() { //// } std::ostream &operator<<(std::ostream &os, const Hotkey &hotkey) { switch (hotkey.type) { case Hotkey::POSITION: { os << "pos:{" << hotkey.pos << "}"; } break; case Hotkey::BRUSH: { if (hotkey.brushname.find('{') != std::string::npos || hotkey.brushname.find('}') != std::string::npos) { break; } os << "brush:{" << hotkey.brushname << "}"; } break; default: { os << "none:{}"; } break; } return os; } std::istream &operator>>(std::istream &is, Hotkey &hotkey) { std::string type; getline(is, type, ':'); if (type == "none") { is.ignore(2); // ignore "{}" } else if (type == "pos") { is.ignore(1); // ignore "{" Position pos; is >> pos; hotkey = Hotkey(pos); is.ignore(1); // ignore "}" } else if (type == "brush") { is.ignore(1); // ignore "{" std::string brushname; getline(is, brushname, '}'); hotkey = Hotkey(brushname); } else { // Do nothing... } return is; } void SetWindowToolTip(wxWindow* a, const wxString &tip) { a->SetToolTip(tip); } void SetWindowToolTip(wxWindow* a, wxWindow* b, const wxString &tip) { a->SetToolTip(tip); b->SetToolTip(tip); }