/
dieoska
/
ddnet
Обзор
Документация
Войти
/
dieoska
/
ddnet
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/game/editor/popups.cpp
3 202 строки
114 KB
Robert Müller
Add `CEditor::Map` getter, make `CEditor::m_Map` private
20 дек 2025, 14:25
20 дек 2025, 14:25
6eee3c6
Код
Авторство
О чём код?
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include "editor.h" #include "editor_actions.h" #include <base/color.h> #include <engine/graphics.h> #include <engine/input.h> #include <engine/keys.h> #include <engine/shared/config.h> #include <engine/storage.h> #include <engine/textrender.h> #include <game/client/gameclient.h> #include <game/client/ui_scrollregion.h> #include <game/editor/mapitems/image.h> #include <game/editor/mapitems/sound.h> #include <limits> using namespace FontIcons; CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); static int s_NewMapButton = 0; static int s_SaveButton = 0; static int s_SaveAsButton = 0; static int s_SaveCopyButton = 0; static int s_OpenButton = 0; static int s_OpenCurrentMapButton = 0; static int s_AppendButton = 0; static int s_TestMapLocallyButton = 0; static int s_ExitButton = 0; CUIRect Slot; View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_NewMapButton, "New", 0, &Slot, BUTTONFLAG_LEFT, "[Ctrl+N] Create a new map.")) { if(pEditor->HasUnsavedData()) { pEditor->m_PopupEventType = POPEVENT_NEW; pEditor->m_PopupEventActivated = true; } else { pEditor->Reset(); } return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(10.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_OpenButton, "Load", 0, &Slot, BUTTONFLAG_LEFT, "[Ctrl+L] Open a map for editing.")) { if(pEditor->HasUnsavedData()) { pEditor->m_PopupEventType = POPEVENT_LOAD; pEditor->m_PopupEventActivated = true; } else pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::MAP, "Load map", "Load", "maps", "", CallbackOpenMap, pEditor); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_OpenCurrentMapButton, pEditor->m_QuickActionLoadCurrentMap.Label(), pEditor->m_QuickActionLoadCurrentMap.Disabled() ? -1 : 0, &Slot, BUTTONFLAG_LEFT, pEditor->m_QuickActionLoadCurrentMap.Description())) { pEditor->m_QuickActionLoadCurrentMap.Call(); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(10.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_AppendButton, "Append", 0, &Slot, BUTTONFLAG_LEFT, "[Ctrl+A] Open a map and add everything from that map to the current one.")) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::MAP, "Append map", "Append", "maps", "", CallbackAppendMap, pEditor); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(10.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_SaveButton, "Save", 0, &Slot, BUTTONFLAG_LEFT, "[Ctrl+S] Save the current map.")) { if(pEditor->Map()->m_aFilename[0] != '\0' && pEditor->Map()->m_ValidSaveFilename) { CallbackSaveMap(pEditor->Map()->m_aFilename, IStorage::TYPE_SAVE, pEditor); } else { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_SAVE, CFileBrowser::EFileType::MAP, "Save map", "Save", "maps", "", CallbackSaveMap, pEditor); } return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_SaveAsButton, pEditor->m_QuickActionSaveAs.Label(), 0, &Slot, BUTTONFLAG_LEFT, pEditor->m_QuickActionSaveAs.Description())) { pEditor->m_QuickActionSaveAs.Call(); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_SaveCopyButton, "Save copy", 0, &Slot, BUTTONFLAG_LEFT, "[Ctrl+Shift+Alt+S] Save a copy of the current map under a new name.")) { char aDefaultName[IO_MAX_PATH_LENGTH]; fs_split_file_extension(fs_filename(pEditor->Map()->m_aFilename), aDefaultName, sizeof(aDefaultName)); pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_SAVE, CFileBrowser::EFileType::MAP, "Save map", "Save copy", "maps", aDefaultName, CallbackSaveCopyMap, pEditor); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(10.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&pEditor->m_QuickActionMapDetails, pEditor->m_QuickActionMapDetails.Label(), 0, &Slot, BUTTONFLAG_LEFT, pEditor->m_QuickActionMapDetails.Description())) { pEditor->m_QuickActionMapDetails.Call(); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_TestMapLocallyButton, pEditor->m_QuickActionTestMapLocally.Label(), 0, &Slot, BUTTONFLAG_LEFT, pEditor->m_QuickActionTestMapLocally.Description())) { pEditor->m_QuickActionTestMapLocally.Call(); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(10.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ExitButton, "Exit", 0, &Slot, BUTTONFLAG_LEFT, "[Escape] Exit from the editor.")) { if(pEditor->HasUnsavedData()) { pEditor->m_PopupEventType = POPEVENT_EXIT; pEditor->m_PopupEventActivated = true; } else { pEditor->OnClose(); g_Config.m_ClEditor = 0; } return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupMenuTools(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CUIRect Slot; View.HSplitTop(12.0f, &Slot, &View); static int s_RemoveUnusedEnvelopesButton = 0; static CUi::SConfirmPopupContext s_ConfirmPopupContext; if(pEditor->DoButton_MenuItem(&s_RemoveUnusedEnvelopesButton, "Remove unused envelopes", 0, &Slot, BUTTONFLAG_LEFT, "Remove all unused envelopes from the map.")) { s_ConfirmPopupContext.Reset(); s_ConfirmPopupContext.YesNoButtons(); str_copy(s_ConfirmPopupContext.m_aMessage, "Are you sure that you want to remove all unused envelopes from this map?"); pEditor->Ui()->ShowPopupConfirm(Slot.x + Slot.w, Slot.y, &s_ConfirmPopupContext); } if(s_ConfirmPopupContext.m_Result == CUi::SConfirmPopupContext::CONFIRMED) pEditor->RemoveUnusedEnvelopes(); if(s_ConfirmPopupContext.m_Result != CUi::SConfirmPopupContext::UNSET) { s_ConfirmPopupContext.Reset(); return CUi::POPUP_CLOSE_CURRENT; } static int s_BorderButton = 0; View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_BorderButton, "Place border", 0, &Slot, BUTTONFLAG_LEFT, "Place tiles in a 2-tile wide border at the edges of the selected tile layer.")) { std::shared_ptr<CLayerTiles> pT = std::static_pointer_cast<CLayerTiles>(pEditor->GetSelectedLayerType(0, LAYERTYPE_TILES)); if(pT && !pT->m_HasTele && !pT->m_HasSpeedup && !pT->m_HasSwitch && !pT->m_HasFront && !pT->m_HasTune) { pEditor->m_PopupEventType = POPEVENT_PLACE_BORDER_TILES; pEditor->m_PopupEventActivated = true; } else { pEditor->ShowFileDialogError("No tile layer selected"); } } static int s_GotoButton = 0; View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_GotoButton, "Goto position", 0, &Slot, BUTTONFLAG_LEFT, "Go to a specified coordinate point on the map.")) { static SPopupMenuId s_PopupGotoId; pEditor->Ui()->DoPopupMenu(&s_PopupGotoId, Slot.x, Slot.y + Slot.h, 120, 52, pEditor, PopupGoto); } static int s_TileartButton = 0; View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_TileartButton, "Add tileart", 0, &Slot, BUTTONFLAG_LEFT, "Generate tileart from image.")) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::IMAGE, "Add tileart", "Open", "mapres", "", CallbackAddTileart, pEditor); return CUi::POPUP_CLOSE_CURRENT; } static int s_QuadArtButton = 0; View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_QuadArtButton, "Add quadart", 0, &Slot, BUTTONFLAG_LEFT, "Generate quadart from image.")) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::IMAGE, "Add quadart", "Open", "mapres", "", CallbackAddQuadArt, pEditor); return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } static int EntitiesListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser) { CEditor *pEditor = (CEditor *)pUser; if(!IsDir && str_endswith(pName, ".png")) { std::string Name = pName; pEditor->m_vSelectEntitiesFiles.push_back(Name.substr(0, Name.length() - 4)); } return 0; } CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CUIRect Slot; View.HSplitTop(12.0f, &Slot, &View); static int s_EntitiesButtonId = 0; char aButtonText[64]; str_format(aButtonText, sizeof(aButtonText), "Entities: %s", pEditor->m_SelectEntitiesImage.c_str()); if(pEditor->DoButton_MenuItem(&s_EntitiesButtonId, aButtonText, 0, &Slot, BUTTONFLAG_LEFT, "Choose game layer entities image for different gametypes.")) { pEditor->m_vSelectEntitiesFiles.clear(); pEditor->Storage()->ListDirectory(IStorage::TYPE_ALL, "editor/entities", EntitiesListdirCallback, pEditor); std::sort(pEditor->m_vSelectEntitiesFiles.begin(), pEditor->m_vSelectEntitiesFiles.end()); pEditor->m_vSelectEntitiesFiles.emplace_back("Custom…"); static SPopupMenuId s_PopupEntitiesId; pEditor->Ui()->DoPopupMenu(&s_PopupEntitiesId, Slot.x, Slot.y + Slot.h, 250, pEditor->m_vSelectEntitiesFiles.size() * 14.0f + 10.0f, pEditor, PopupEntities); } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Brush coloring", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !pEditor->m_BrushColorEnabled, &No, BUTTONFLAG_LEFT, "Disable brush coloring.", IGraphics::CORNER_L)) { pEditor->m_BrushColorEnabled = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", pEditor->m_BrushColorEnabled, &Yes, BUTTONFLAG_LEFT, "Enable brush coloring.", IGraphics::CORNER_R)) { pEditor->m_BrushColorEnabled = true; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Allow unused", 10.0f, TEXTALIGN_ML); if(pEditor->m_AllowPlaceUnusedTiles != EUnusedEntities::ALLOWED_IMPLICIT) { static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", pEditor->m_AllowPlaceUnusedTiles == EUnusedEntities::NOT_ALLOWED, &No, BUTTONFLAG_LEFT, "[Ctrl+U] Disallow placing unused tiles.", IGraphics::CORNER_L)) { pEditor->m_AllowPlaceUnusedTiles = EUnusedEntities::NOT_ALLOWED; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", pEditor->m_AllowPlaceUnusedTiles == EUnusedEntities::ALLOWED_EXPLICIT, &Yes, BUTTONFLAG_LEFT, "[Ctrl+U] Allow placing unused tiles.", IGraphics::CORNER_R)) { pEditor->m_AllowPlaceUnusedTiles = EUnusedEntities::ALLOWED_EXPLICIT; } } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect Off, Dec, Hex; Selector.VSplitLeft(Selector.w / 3.0f, &Off, &Selector); Selector.VSplitMid(&Dec, &Hex); pEditor->Ui()->DoLabel(&Label, "Show info", 10.0f, TEXTALIGN_ML); static int s_ButtonOff = 0; static int s_ButtonDec = 0; static int s_ButtonHex = 0; CQuickAction *pAction = &pEditor->m_QuickActionShowInfoOff; if(pEditor->DoButton_Ex(&s_ButtonOff, pAction->LabelShort(), pAction->Active(), &Off, BUTTONFLAG_LEFT, pAction->Description(), IGraphics::CORNER_L)) { pAction->Call(); } pAction = &pEditor->m_QuickActionShowInfoDec; if(pEditor->DoButton_Ex(&s_ButtonDec, pAction->LabelShort(), pAction->Active(), &Dec, BUTTONFLAG_LEFT, pAction->Description(), IGraphics::CORNER_NONE)) { pAction->Call(); } pAction = &pEditor->m_QuickActionShowInfoHex; if(pEditor->DoButton_Ex(&s_ButtonHex, pAction->LabelShort(), pAction->Active(), &Hex, BUTTONFLAG_LEFT, pAction->Description(), IGraphics::CORNER_R)) { pAction->Call(); } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Preview quad envelopes", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !pEditor->m_ShowEnvelopePreview, &No, BUTTONFLAG_LEFT, "Do not preview the paths of quads with a position envelope when a quad layer is selected.", IGraphics::CORNER_L)) { pEditor->m_ShowEnvelopePreview = false; pEditor->m_ActiveEnvelopePreview = EEnvelopePreview::NONE; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", pEditor->m_ShowEnvelopePreview, &Yes, BUTTONFLAG_LEFT, "Preview the paths of quads with a position envelope when a quad layer is selected.", IGraphics::CORNER_R)) { pEditor->m_ShowEnvelopePreview = true; pEditor->m_ActiveEnvelopePreview = EEnvelopePreview::NONE; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Align quads", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdAlignQuads, &No, BUTTONFLAG_LEFT, "Do not perform quad alignment to other quads/points when moving quads.", IGraphics::CORNER_L)) { g_Config.m_EdAlignQuads = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdAlignQuads, &Yes, BUTTONFLAG_LEFT, "Allow quad alignment to other quads/points when moving quads.", IGraphics::CORNER_R)) { g_Config.m_EdAlignQuads = true; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Show quads bounds", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdShowQuadsRect, &No, BUTTONFLAG_LEFT, "Do not show quad bounds when moving quads.", IGraphics::CORNER_L)) { g_Config.m_EdShowQuadsRect = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdShowQuadsRect, &Yes, BUTTONFLAG_LEFT, "Show quad bounds when moving quads.", IGraphics::CORNER_R)) { g_Config.m_EdShowQuadsRect = true; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Auto map reload", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdAutoMapReload, &No, BUTTONFLAG_LEFT, "Do not run 'hot_reload' on the local server while rcon authed on map save.", IGraphics::CORNER_L)) { g_Config.m_EdAutoMapReload = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdAutoMapReload, &Yes, BUTTONFLAG_LEFT, "Run 'hot_reload' on the local server while rcon authed on map save.", IGraphics::CORNER_R)) { g_Config.m_EdAutoMapReload = true; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Select layers by tile", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdLayerSelector, &No, BUTTONFLAG_LEFT, "Do not select layers when ctrl+right clicking on a tile.", IGraphics::CORNER_L)) { g_Config.m_EdLayerSelector = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdLayerSelector, &Yes, BUTTONFLAG_LEFT, "Select layers when ctrl+right clicking on a tile.", IGraphics::CORNER_R)) { g_Config.m_EdLayerSelector = true; } } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Slot, &View); { Slot.VMargin(5.0f, &Slot); CUIRect Label, Selector; Slot.VSplitMid(&Label, &Selector); CUIRect No, Yes; Selector.VSplitMid(&No, &Yes); pEditor->Ui()->DoLabel(&Label, "Show ingame entities", 10.0f, TEXTALIGN_ML); static int s_ButtonNo = 0; static int s_ButtonYes = 0; if(pEditor->DoButton_Ex(&s_ButtonNo, "No", !g_Config.m_EdShowIngameEntities, &No, BUTTONFLAG_LEFT, "Do not show how weapons, shields, hearts and flags appear ingame.", IGraphics::CORNER_L)) { g_Config.m_EdShowIngameEntities = false; } if(pEditor->DoButton_Ex(&s_ButtonYes, "Yes", g_Config.m_EdShowIngameEntities, &Yes, BUTTONFLAG_LEFT, "Show how weapons, shields, hearts and flags appear ingame.", IGraphics::CORNER_R)) { g_Config.m_EdShowIngameEntities = true; } } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); // remove group button CUIRect Button; View.HSplitBottom(12.0f, &View, &Button); static int s_DeleteButton = 0; // don't allow deletion of game group if(pEditor->Map()->m_pGameGroup != pEditor->GetSelectedGroup()) { if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete group", 0, &Button, BUTTONFLAG_LEFT, "Delete the group.")) { pEditor->Map()->m_EditorHistory.RecordAction(std::make_shared<CEditorActionGroup>(pEditor->Map(), pEditor->m_SelectedGroup, true)); pEditor->Map()->DeleteGroup(pEditor->m_SelectedGroup); pEditor->m_SelectedGroup = maximum(0, pEditor->m_SelectedGroup - 1); return CUi::POPUP_CLOSE_CURRENT; } } else { if(pEditor->DoButton_Editor(&s_DeleteButton, "Clean up game tiles", 0, &Button, BUTTONFLAG_LEFT, "Remove game tiles that aren't based on a layer.")) { // gather all tile layers std::vector<std::shared_ptr<CLayerTiles>> vpLayers; int GameLayerIndex = -1; for(int LayerIndex = 0; LayerIndex < (int)pEditor->Map()->m_pGameGroup->m_vpLayers.size(); LayerIndex++) { auto &pLayer = pEditor->Map()->m_pGameGroup->m_vpLayers.at(LayerIndex); if(pLayer != pEditor->Map()->m_pGameLayer && pLayer->m_Type == LAYERTYPE_TILES) vpLayers.push_back(std::static_pointer_cast<CLayerTiles>(pLayer)); else if(pLayer == pEditor->Map()->m_pGameLayer) GameLayerIndex = LayerIndex; } // search for unneeded game tiles std::shared_ptr<CLayerTiles> pGameLayer = pEditor->Map()->m_pGameLayer; for(int y = 0; y < pGameLayer->m_Height; ++y) { for(int x = 0; x < pGameLayer->m_Width; ++x) { if(pGameLayer->m_pTiles[y * pGameLayer->m_Width + x].m_Index > static_cast<unsigned char>(TILE_NOHOOK)) continue; bool Found = false; for(const auto &pLayer : vpLayers) { if(x < pLayer->m_Width && y < pLayer->m_Height && pLayer->m_pTiles[y * pLayer->m_Width + x].m_Index) { Found = true; break; } } CTile Tile = pGameLayer->GetTile(x, y); if(!Found && Tile.m_Index != TILE_AIR) { Tile.m_Index = TILE_AIR; pGameLayer->SetTile(x, y, Tile); pEditor->Map()->OnModify(); } } } if(!pGameLayer->m_TilesHistory.empty()) { if(GameLayerIndex == -1) { dbg_msg("editor", "failed to record action (GameLayerIndex not found)"); } else { // record undo pEditor->Map()->m_EditorHistory.RecordAction(std::make_shared<CEditorActionTileChanges>(pEditor->Map(), pEditor->m_SelectedGroup, GameLayerIndex, "Clean up game tiles", pGameLayer->m_TilesHistory)); } pGameLayer->ClearHistory(); } return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->Map()->m_pTeleLayer) { // new tele layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddTeleLayer, pEditor->m_QuickActionAddTeleLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddTeleLayer.Description())) { pEditor->m_QuickActionAddTeleLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->Map()->m_pSpeedupLayer) { // new speedup layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddSpeedupLayer, pEditor->m_QuickActionAddSpeedupLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddSpeedupLayer.Description())) { pEditor->m_QuickActionAddSpeedupLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->Map()->m_pTuneLayer) { // new tune layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddTuneLayer, pEditor->m_QuickActionAddTuneLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddTuneLayer.Description())) { pEditor->m_QuickActionAddTuneLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->Map()->m_pFrontLayer) { // new front layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddFrontLayer, pEditor->m_QuickActionAddFrontLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddFrontLayer.Description())) { pEditor->m_QuickActionAddFrontLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->Map()->m_pSwitchLayer) { // new Switch layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddSwitchLayer, pEditor->m_QuickActionAddSwitchLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddSwitchLayer.Description())) { pEditor->m_QuickActionAddSwitchLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } // new quad layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddQuadsLayer, pEditor->m_QuickActionAddQuadsLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddQuadsLayer.Description())) { pEditor->m_QuickActionAddQuadsLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } // new tile layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddTileLayer, pEditor->m_QuickActionAddTileLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddTileLayer.Description())) { pEditor->m_QuickActionAddTileLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } // new sound layer View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionAddSoundLayer, pEditor->m_QuickActionAddSoundLayer.Label(), 0, &Button, BUTTONFLAG_LEFT, pEditor->m_QuickActionAddSoundLayer.Description())) { pEditor->m_QuickActionAddSoundLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } // group name if(!pEditor->GetSelectedGroup()->m_GameGroup) { View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); pEditor->Ui()->DoLabel(&Button, "Name:", 10.0f, TEXTALIGN_ML); Button.VSplitLeft(40.0f, nullptr, &Button); static CLineInput s_NameInput; s_NameInput.SetBuffer(pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_aName, sizeof(pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_aName)); if(pEditor->DoEditBox(&s_NameInput, &Button, 10.0f)) pEditor->Map()->OnModify(); } CProperty aProps[] = { {"Order", pEditor->m_SelectedGroup, PROPTYPE_INT, 0, (int)pEditor->Map()->m_vpGroups.size() - 1}, {"Pos X", -pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, PROPTYPE_INT, -1000000, 1000000}, {"Pos Y", -pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY, PROPTYPE_INT, -1000000, 1000000}, {"Para X", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX, PROPTYPE_INT, -1000000, 1000000}, {"Para Y", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY, PROPTYPE_INT, -1000000, 1000000}, {"Use Clipping", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping, PROPTYPE_BOOL, 0, 1}, {"Clip X", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX, PROPTYPE_INT, -1000000, 1000000}, {"Clip Y", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY, PROPTYPE_INT, -1000000, 1000000}, {"Clip W", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW, PROPTYPE_INT, 0, 1000000}, {"Clip H", pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH, PROPTYPE_INT, 0, 1000000}, {nullptr}, }; // cut the properties that aren't needed if(pEditor->GetSelectedGroup()->m_GameGroup) aProps[(int)EGroupProp::PROP_POS_X].m_pName = nullptr; static int s_aIds[(int)EGroupProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState<EGroupProp>(&View, aProps, s_aIds, &NewVal); if(Prop != EGroupProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) { pEditor->Map()->OnModify(); } pEditor->Map()->m_LayerGroupPropTracker.Begin(pEditor->GetSelectedGroup().get(), Prop, State); if(Prop == EGroupProp::PROP_ORDER) { pEditor->m_SelectedGroup = pEditor->Map()->MoveGroup(pEditor->m_SelectedGroup, NewVal); } // these can not be changed on the game group if(!pEditor->GetSelectedGroup()->m_GameGroup) { if(Prop == EGroupProp::PROP_PARA_X) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal; } else if(Prop == EGroupProp::PROP_PARA_Y) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal; } else if(Prop == EGroupProp::PROP_POS_X) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal; } else if(Prop == EGroupProp::PROP_POS_Y) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal; } else if(Prop == EGroupProp::PROP_USE_CLIPPING) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal; } else if(Prop == EGroupProp::PROP_CLIP_X) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal; } else if(Prop == EGroupProp::PROP_CLIP_Y) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal; } else if(Prop == EGroupProp::PROP_CLIP_W) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal; } else if(Prop == EGroupProp::PROP_CLIP_H) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal; } } pEditor->Map()->m_LayerGroupPropTracker.End(Prop, State); return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, bool Active) { SLayerPopupContext *pPopup = (SLayerPopupContext *)pContext; CEditor *pEditor = pPopup->m_pEditor; std::shared_ptr<CLayerGroup> pCurrentGroup = pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]; std::shared_ptr<CLayer> pCurrentLayer = pEditor->GetSelectedLayer(0); if(!pCurrentLayer || !pCurrentGroup) return CUi::POPUP_CLOSE_CURRENT; if(pPopup->m_vpLayers.size() > 1) { return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor->Map(), &View, pPopup->m_vpLayers, pPopup->m_vLayerIndices); } const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer(); // delete button if(pEditor->Map()->m_pGameLayer != pCurrentLayer) // entities layers except the game layer can be deleted { CUIRect DeleteButton; View.HSplitBottom(12.0f, &View, &DeleteButton); if(pEditor->DoButton_Editor(&pEditor->m_QuickActionDeleteLayer, pEditor->m_QuickActionDeleteLayer.Label(), 0, &DeleteButton, BUTTONFLAG_LEFT, pEditor->m_QuickActionDeleteLayer.Description())) { pEditor->m_QuickActionDeleteLayer.Call(); return CUi::POPUP_CLOSE_CURRENT; } } // duplicate button if(!EntitiesLayer) // entities layers cannot be duplicated { CUIRect DuplicateButton; View.HSplitBottom(4.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &DuplicateButton); static int s_DuplicationButton = 0; if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DuplicateButton, BUTTONFLAG_LEFT, "Create an identical copy of the selected layer.")) { pEditor->Map()->m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]); pEditor->Map()->m_EditorHistory.RecordAction(std::make_shared<CEditorActionAddLayer>(pEditor->Map(), pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0] + 1, true)); return CUi::POPUP_CLOSE_CURRENT; } } // layer name if(!EntitiesLayer) // name cannot be changed for entities layers { CUIRect Label, EditBox; View.HSplitBottom(5.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Label); Label.VSplitLeft(40.0f, &Label, &EditBox); pEditor->Ui()->DoLabel(&Label, "Name:", 10.0f, TEXTALIGN_ML); static CLineInput s_NameInput; s_NameInput.SetBuffer(pCurrentLayer->m_aName, sizeof(pCurrentLayer->m_aName)); if(pEditor->DoEditBox(&s_NameInput, &EditBox, 10.0f)) pEditor->Map()->OnModify(); } // spacing if any button was rendered if(!EntitiesLayer || pEditor->Map()->m_pGameLayer != pCurrentLayer) View.HSplitBottom(10.0f, &View, nullptr); CProperty aProps[] = { {"Group", pEditor->m_SelectedGroup, PROPTYPE_INT, 0, (int)pEditor->Map()->m_vpGroups.size() - 1}, {"Order", pEditor->m_vSelectedLayers[0], PROPTYPE_INT, 0, (int)pCurrentGroup->m_vpLayers.size() - 1}, {"Detail", pCurrentLayer->m_Flags & LAYERFLAG_DETAIL, PROPTYPE_BOOL, 0, 1}, {nullptr}, }; // don't use Group and Detail from the selection if this is an entities layer if(EntitiesLayer) { aProps[0].m_Type = PROPTYPE_NULL; aProps[2].m_Type = PROPTYPE_NULL; } static int s_aIds[(int)ELayerProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState<ELayerProp>(&View, aProps, s_aIds, &NewVal); if(Prop != ELayerProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) { pEditor->Map()->OnModify(); } pEditor->Map()->m_LayerPropTracker.Begin(pCurrentLayer.get(), Prop, State); if(Prop == ELayerProp::PROP_ORDER) { pEditor->SelectLayer(pCurrentGroup->MoveLayer(pEditor->m_vSelectedLayers[0], NewVal)); } else if(Prop == ELayerProp::PROP_GROUP) { if(NewVal >= 0 && (size_t)NewVal < pEditor->Map()->m_vpGroups.size() && NewVal != pEditor->m_SelectedGroup) { auto Position = std::find(pCurrentGroup->m_vpLayers.begin(), pCurrentGroup->m_vpLayers.end(), pCurrentLayer); if(Position != pCurrentGroup->m_vpLayers.end()) pCurrentGroup->m_vpLayers.erase(Position); pEditor->Map()->m_vpGroups[NewVal]->m_vpLayers.push_back(pCurrentLayer); pEditor->m_SelectedGroup = NewVal; pEditor->SelectLayer(pEditor->Map()->m_vpGroups[NewVal]->m_vpLayers.size() - 1); } } else if(Prop == ELayerProp::PROP_HQ) { pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL; if(NewVal) pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL; } pEditor->Map()->m_LayerPropTracker.End(Prop, State); return pCurrentLayer->RenderProperties(&View); } CUi::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, bool Active) { CQuadPopupContext *pQuadPopupContext = static_cast<CQuadPopupContext *>(pContext); CEditor *pEditor = pQuadPopupContext->m_pEditor; std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads(); if(!in_range<int>(pQuadPopupContext->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) { return CUi::POPUP_CLOSE_CURRENT; } CQuad *pCurrentQuad = vpQuads[pQuadPopupContext->m_SelectedQuadIndex]; std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS)); CUIRect Button; // delete button View.HSplitBottom(12.0f, &View, &Button); static int s_DeleteButton = 0; if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, BUTTONFLAG_LEFT, "Delete the current quad.")) { if(pLayer) { pEditor->Map()->OnModify(); pEditor->DeleteSelectedQuads(); } return CUi::POPUP_CLOSE_CURRENT; } // aspect ratio button View.HSplitBottom(10.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); if(pLayer && pLayer->m_Image >= 0 && (size_t)pLayer->m_Image < pEditor->Map()->m_vpImages.size()) { static int s_AspectRatioButton = 0; if(pEditor->DoButton_Editor(&s_AspectRatioButton, "Aspect ratio", 0, &Button, BUTTONFLAG_LEFT, "Resize the current quad based on the aspect ratio of its image.")) { pEditor->Map()->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { int Top = pQuad->m_aPoints[0].y; int Left = pQuad->m_aPoints[0].x; int Right = pQuad->m_aPoints[0].x; for(int k = 1; k < 4; k++) { if(pQuad->m_aPoints[k].y < Top) Top = pQuad->m_aPoints[k].y; if(pQuad->m_aPoints[k].x < Left) Left = pQuad->m_aPoints[k].x; if(pQuad->m_aPoints[k].x > Right) Right = pQuad->m_aPoints[k].x; } const int Height = (Right - Left) * pEditor->Map()->m_vpImages[pLayer->m_Image]->m_Height / pEditor->Map()->m_vpImages[pLayer->m_Image]->m_Width; pQuad->m_aPoints[0].x = Left; pQuad->m_aPoints[0].y = Top; pQuad->m_aPoints[1].x = Right; pQuad->m_aPoints[1].y = Top; pQuad->m_aPoints[2].x = Left; pQuad->m_aPoints[2].y = Top + Height; pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Top + Height; pEditor->Map()->OnModify(); } pEditor->Map()->m_QuadTracker.EndQuadTrack(); return CUi::POPUP_CLOSE_CURRENT; } } // center pivot button View.HSplitBottom(6.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); static int s_CenterButton = 0; if(pEditor->DoButton_Editor(&s_CenterButton, "Center pivot", 0, &Button, BUTTONFLAG_LEFT, "Center the pivot of the current quad.")) { pEditor->Map()->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); int Top = pCurrentQuad->m_aPoints[0].y; int Left = pCurrentQuad->m_aPoints[0].x; int Bottom = pCurrentQuad->m_aPoints[0].y; int Right = pCurrentQuad->m_aPoints[0].x; for(int k = 1; k < 4; k++) { if(pCurrentQuad->m_aPoints[k].y < Top) Top = pCurrentQuad->m_aPoints[k].y; if(pCurrentQuad->m_aPoints[k].x < Left) Left = pCurrentQuad->m_aPoints[k].x; if(pCurrentQuad->m_aPoints[k].y > Bottom) Bottom = pCurrentQuad->m_aPoints[k].y; if(pCurrentQuad->m_aPoints[k].x > Right) Right = pCurrentQuad->m_aPoints[k].x; } pCurrentQuad->m_aPoints[4].x = Left + (Right - Left) / 2; pCurrentQuad->m_aPoints[4].y = Top + (Bottom - Top) / 2; pEditor->Map()->m_QuadTracker.EndQuadTrack(); pEditor->Map()->OnModify(); return CUi::POPUP_CLOSE_CURRENT; } // align button View.HSplitBottom(6.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); static int s_AlignButton = 0; if(pEditor->DoButton_Editor(&s_AlignButton, "Align", 0, &Button, BUTTONFLAG_LEFT, "Align coordinates of the quad points.")) { pEditor->Map()->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { for(int k = 1; k < 4; k++) { pQuad->m_aPoints[k].x = 1000.0f * (pQuad->m_aPoints[k].x / 1000); pQuad->m_aPoints[k].y = 1000.0f * (pQuad->m_aPoints[k].y / 1000); } pEditor->Map()->OnModify(); } pEditor->Map()->m_QuadTracker.EndQuadTrack(); return CUi::POPUP_CLOSE_CURRENT; } // square button View.HSplitBottom(6.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); static int s_Button = 0; if(pEditor->DoButton_Editor(&s_Button, "Square", 0, &Button, BUTTONFLAG_LEFT, "Square the current quad.")) { pEditor->Map()->m_QuadTracker.BeginQuadTrack(pLayer, pEditor->m_vSelectedQuads); for(auto &pQuad : vpQuads) { int Top = pQuad->m_aPoints[0].y; int Left = pQuad->m_aPoints[0].x; int Bottom = pQuad->m_aPoints[0].y; int Right = pQuad->m_aPoints[0].x; for(int k = 1; k < 4; k++) { if(pQuad->m_aPoints[k].y < Top) Top = pQuad->m_aPoints[k].y; if(pQuad->m_aPoints[k].x < Left) Left = pQuad->m_aPoints[k].x; if(pQuad->m_aPoints[k].y > Bottom) Bottom = pQuad->m_aPoints[k].y; if(pQuad->m_aPoints[k].x > Right) Right = pQuad->m_aPoints[k].x; } pQuad->m_aPoints[0].x = Left; pQuad->m_aPoints[0].y = Top; pQuad->m_aPoints[1].x = Right; pQuad->m_aPoints[1].y = Top; pQuad->m_aPoints[2].x = Left; pQuad->m_aPoints[2].y = Bottom; pQuad->m_aPoints[3].x = Right; pQuad->m_aPoints[3].y = Bottom; pEditor->Map()->OnModify(); } pEditor->Map()->m_QuadTracker.EndQuadTrack(); return CUi::POPUP_CLOSE_CURRENT; } // slice button View.HSplitBottom(6.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &Button); static int s_SliceButton = 0; if(pEditor->DoButton_Editor(&s_SliceButton, "Slice", 0, &Button, BUTTONFLAG_LEFT, "Enable quad knife mode.")) { pEditor->Map()->m_QuadKnife.m_Active = true; pEditor->Map()->m_QuadKnife.m_Count = 0; pEditor->Map()->m_QuadKnife.m_SelectedQuadIndex = pQuadPopupContext->m_SelectedQuadIndex; return CUi::POPUP_CLOSE_CURRENT; } const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0; CProperty aProps[] = { {"Order", pEditor->m_vSelectedQuads[pQuadPopupContext->m_SelectedQuadIndex], PROPTYPE_INT, 0, NumQuads}, {"Pos X", fx2i(pCurrentQuad->m_aPoints[4].x), PROPTYPE_INT, -1000000, 1000000}, {"Pos Y", fx2i(pCurrentQuad->m_aPoints[4].y), PROPTYPE_INT, -1000000, 1000000}, {"Pos. Env", pCurrentQuad->m_PosEnv + 1, PROPTYPE_ENVELOPE, 0, 0}, {"Pos. TO", pCurrentQuad->m_PosEnvOffset, PROPTYPE_INT, -1000000, 1000000}, {"Color", pQuadPopupContext->m_Color, PROPTYPE_COLOR, 0, 0}, {"Color Env", pCurrentQuad->m_ColorEnv + 1, PROPTYPE_ENVELOPE, 0, 0}, {"Color TO", pCurrentQuad->m_ColorEnvOffset, PROPTYPE_INT, -1000000, 1000000}, {nullptr}, }; static int s_aIds[(int)EQuadProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState<EQuadProp>(&View, aProps, s_aIds, &NewVal); if(Prop != EQuadProp::PROP_NONE && (State == EEditState::START || State == EEditState::ONE_GO)) { pEditor->Map()->m_QuadTracker.BeginQuadPropTrack(pLayer, pEditor->m_vSelectedQuads, Prop); } const float OffsetX = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].x; const float OffsetY = i2fx(NewVal) - pCurrentQuad->m_aPoints[4].y; if(Prop == EQuadProp::PROP_ORDER && pLayer) { const int QuadIndex = pLayer->SwapQuads(pEditor->m_vSelectedQuads[pQuadPopupContext->m_SelectedQuadIndex], NewVal); pEditor->m_vSelectedQuads[pQuadPopupContext->m_SelectedQuadIndex] = QuadIndex; } for(auto &pQuad : vpQuads) { if(Prop == EQuadProp::PROP_POS_X) { for(auto &Point : pQuad->m_aPoints) Point.x += OffsetX; } else if(Prop == EQuadProp::PROP_POS_Y) { for(auto &Point : pQuad->m_aPoints) Point.y += OffsetY; } else if(Prop == EQuadProp::PROP_POS_ENV) { int Index = std::clamp(NewVal - 1, -1, (int)pEditor->Map()->m_vpEnvelopes.size() - 1); int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1; if(StepDirection != 0) { for(; Index >= -1 && Index < (int)pEditor->Map()->m_vpEnvelopes.size(); Index += StepDirection) { if(Index == -1 || pEditor->Map()->m_vpEnvelopes[Index]->GetChannels() == 3) { pQuad->m_PosEnv = Index; break; } } } } else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET) { pQuad->m_PosEnvOffset = NewVal; } else if(Prop == EQuadProp::PROP_COLOR) { pQuadPopupContext->m_Color = NewVal; std::fill(std::begin(pQuad->m_aColors), std::end(pQuad->m_aColors), UnpackColor(NewVal)); } else if(Prop == EQuadProp::PROP_COLOR_ENV) { int Index = std::clamp(NewVal - 1, -1, (int)pEditor->Map()->m_vpEnvelopes.size() - 1); int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1; if(StepDirection != 0) { for(; Index >= -1 && Index < (int)pEditor->Map()->m_vpEnvelopes.size(); Index += StepDirection) { if(Index == -1 || pEditor->Map()->m_vpEnvelopes[Index]->GetChannels() == 4) { pQuad->m_ColorEnv = Index; break; } } } } else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET) { pQuad->m_ColorEnvOffset = NewVal; } } if(Prop != EQuadProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) { pEditor->Map()->m_QuadTracker.EndQuadPropTrack(Prop); pEditor->Map()->OnModify(); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CSoundSource *pSource = pEditor->GetSelectedSource(); if(!pSource) return CUi::POPUP_CLOSE_CURRENT; CUIRect Button; // delete button View.HSplitBottom(12.0f, &View, &Button); static int s_DeleteButton = 0; if(pEditor->DoButton_Editor(&s_DeleteButton, "Delete", 0, &Button, BUTTONFLAG_LEFT, "Delete the current sound source.")) { std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(pEditor->GetSelectedLayerType(0, LAYERTYPE_SOUNDS)); if(pLayer) { pEditor->Map()->m_EditorHistory.Execute(std::make_shared<CEditorActionDeleteSoundSource>(pEditor->Map(), pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource)); } return CUi::POPUP_CLOSE_CURRENT; } // Sound shape button CUIRect ShapeButton; View.HSplitBottom(3.0f, &View, nullptr); View.HSplitBottom(12.0f, &View, &ShapeButton); static const char *s_apShapeNames[CSoundShape::NUM_SHAPES] = { "Rectangle", "Circle"}; pSource->m_Shape.m_Type = pSource->m_Shape.m_Type % CSoundShape::NUM_SHAPES; // prevent out of array errors static int s_ShapeTypeButton = 0; if(pEditor->DoButton_Editor(&s_ShapeTypeButton, s_apShapeNames[pSource->m_Shape.m_Type], 0, &ShapeButton, BUTTONFLAG_LEFT, "Change sound source shape.")) { pEditor->Map()->m_EditorHistory.Execute(std::make_shared<CEditorActionEditSoundSourceShape>(pEditor->Map(), pEditor->m_SelectedGroup, pEditor->m_vSelectedLayers[0], pEditor->m_SelectedSource, (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES)); } CProperty aProps[] = { {"Pos X", pSource->m_Position.x / 1000, PROPTYPE_INT, -1000000, 1000000}, {"Pos Y", pSource->m_Position.y / 1000, PROPTYPE_INT, -1000000, 1000000}, {"Loop", pSource->m_Loop, PROPTYPE_BOOL, 0, 1}, {"Pan", pSource->m_Pan, PROPTYPE_BOOL, 0, 1}, {"Delay", pSource->m_TimeDelay, PROPTYPE_INT, 0, 1000000}, {"Falloff", pSource->m_Falloff, PROPTYPE_INT, 0, 255}, {"Pos. Env", pSource->m_PosEnv + 1, PROPTYPE_ENVELOPE, 0, 0}, {"Pos. TO", pSource->m_PosEnvOffset, PROPTYPE_INT, -1000000, 1000000}, {"Sound Env", pSource->m_SoundEnv + 1, PROPTYPE_ENVELOPE, 0, 0}, {"Sound. TO", pSource->m_SoundEnvOffset, PROPTYPE_INT, -1000000, 1000000}, {nullptr}, }; static int s_aIds[(int)ESoundProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState<ESoundProp>(&View, aProps, s_aIds, &NewVal); if(Prop != ESoundProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) { pEditor->Map()->OnModify(); } pEditor->Map()->m_SoundSourcePropTracker.Begin(pSource, Prop, State); if(Prop == ESoundProp::PROP_POS_X) { pSource->m_Position.x = NewVal * 1000; } else if(Prop == ESoundProp::PROP_POS_Y) { pSource->m_Position.y = NewVal * 1000; } else if(Prop == ESoundProp::PROP_LOOP) { pSource->m_Loop = NewVal; } else if(Prop == ESoundProp::PROP_PAN) { pSource->m_Pan = NewVal; } else if(Prop == ESoundProp::PROP_TIME_DELAY) { pSource->m_TimeDelay = NewVal; } else if(Prop == ESoundProp::PROP_FALLOFF) { pSource->m_Falloff = NewVal; } else if(Prop == ESoundProp::PROP_POS_ENV) { int Index = std::clamp(NewVal - 1, -1, (int)pEditor->Map()->m_vpEnvelopes.size() - 1); const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1; for(; Index >= -1 && Index < (int)pEditor->Map()->m_vpEnvelopes.size(); Index += StepDirection) { if(Index == -1 || pEditor->Map()->m_vpEnvelopes[Index]->GetChannels() == 3) { pSource->m_PosEnv = Index; break; } } } else if(Prop == ESoundProp::PROP_POS_ENV_OFFSET) { pSource->m_PosEnvOffset = NewVal; } else if(Prop == ESoundProp::PROP_SOUND_ENV) { int Index = std::clamp(NewVal - 1, -1, (int)pEditor->Map()->m_vpEnvelopes.size() - 1); const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1; for(; Index >= -1 && Index < (int)pEditor->Map()->m_vpEnvelopes.size(); Index += StepDirection) { if(Index == -1 || pEditor->Map()->m_vpEnvelopes[Index]->GetChannels() == 1) { pSource->m_SoundEnv = Index; break; } } } else if(Prop == ESoundProp::PROP_SOUND_ENV_OFFSET) { pSource->m_SoundEnvOffset = NewVal; } pEditor->Map()->m_SoundSourcePropTracker.End(Prop, State); // source shape properties switch(pSource->m_Shape.m_Type) { case CSoundShape::SHAPE_CIRCLE: { CProperty aCircleProps[] = { {"Radius", pSource->m_Shape.m_Circle.m_Radius, PROPTYPE_INT, 0, 1000000}, {nullptr}, }; static int s_aCircleIds[(int)ECircleShapeProp::NUM_PROPS] = {0}; NewVal = 0; auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ECircleShapeProp>(&View, aCircleProps, s_aCircleIds, &NewVal); if(LocalProp != ECircleShapeProp::PROP_NONE && (LocalState == EEditState::END || LocalState == EEditState::ONE_GO)) { pEditor->Map()->OnModify(); } pEditor->Map()->m_SoundSourceCircleShapePropTracker.Begin(pSource, LocalProp, LocalState); if(LocalProp == ECircleShapeProp::PROP_CIRCLE_RADIUS) { pSource->m_Shape.m_Circle.m_Radius = NewVal; } pEditor->Map()->m_SoundSourceCircleShapePropTracker.End(LocalProp, LocalState); break; } case CSoundShape::SHAPE_RECTANGLE: { CProperty aRectangleProps[] = { {"Width", pSource->m_Shape.m_Rectangle.m_Width / 1024, PROPTYPE_INT, 0, 1000000}, {"Height", pSource->m_Shape.m_Rectangle.m_Height / 1024, PROPTYPE_INT, 0, 1000000}, {nullptr}, }; static int s_aRectangleIds[(int)ERectangleShapeProp::NUM_PROPS] = {0}; NewVal = 0; auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ERectangleShapeProp>(&View, aRectangleProps, s_aRectangleIds, &NewVal); if(LocalProp != ERectangleShapeProp::PROP_NONE && (LocalState == EEditState::END || LocalState == EEditState::ONE_GO)) { pEditor->Map()->OnModify(); } pEditor->Map()->m_SoundSourceRectShapePropTracker.Begin(pSource, LocalProp, LocalState); if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_WIDTH) { pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024; } else if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT) { pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024; } pEditor->Map()->m_SoundSourceRectShapePropTracker.End(LocalProp, LocalState); break; } } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, bool Active) { CPointPopupContext *pPointPopupContext = static_cast<CPointPopupContext *>(pContext); CEditor *pEditor = pPointPopupContext->m_pEditor; std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads(); if(!in_range<int>(pPointPopupContext->m_SelectedQuadIndex, 0, vpQuads.size() - 1)) { return CUi::POPUP_CLOSE_CURRENT; } CQuad *pCurrentQuad = vpQuads[pPointPopupContext->m_SelectedQuadIndex]; std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(pEditor->GetSelectedLayerType(0, LAYERTYPE_QUADS)); const int X = fx2i(pCurrentQuad->m_aPoints[pPointPopupContext->m_SelectedQuadPoint].x); const int Y = fx2i(pCurrentQuad->m_aPoints[pPointPopupContext->m_SelectedQuadPoint].y); const int TextureU = fx2f(pCurrentQuad->m_aTexcoords[pPointPopupContext->m_SelectedQuadPoint].x) * 1024; const int TextureV = fx2f(pCurrentQuad->m_aTexcoords[pPointPopupContext->m_SelectedQuadPoint].y) * 1024; CProperty aProps[] = { {"Pos X", X, PROPTYPE_INT, -1000000, 1000000}, {"Pos Y", Y, PROPTYPE_INT, -1000000, 1000000}, {"Color", PackColor(pCurrentQuad->m_aColors[pPointPopupContext->m_SelectedQuadPoint]), PROPTYPE_COLOR, 0, 0}, {"Tex U", TextureU, PROPTYPE_INT, -1000000, 1000000}, {"Tex V", TextureV, PROPTYPE_INT, -1000000, 1000000}, {nullptr}, }; static int s_aIds[(int)EQuadPointProp::NUM_PROPS] = {0}; int NewVal = 0; auto [State, Prop] = pEditor->DoPropertiesWithState<EQuadPointProp>(&View, aProps, s_aIds, &NewVal); if(Prop != EQuadPointProp::PROP_NONE && (State == EEditState::START || State == EEditState::ONE_GO)) { pEditor->Map()->m_QuadTracker.BeginQuadPointPropTrack(pLayer, pEditor->m_vSelectedQuads, pEditor->m_SelectedQuadPoints); pEditor->Map()->m_QuadTracker.AddQuadPointPropTrack(Prop); } for(CQuad *pQuad : vpQuads) { if(Prop == EQuadPointProp::PROP_POS_X) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aPoints[v].x = i2fx(fx2i(pQuad->m_aPoints[v].x) + NewVal - X); } else if(Prop == EQuadPointProp::PROP_POS_Y) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aPoints[v].y = i2fx(fx2i(pQuad->m_aPoints[v].y) + NewVal - Y); } else if(Prop == EQuadPointProp::PROP_COLOR) { for(int v = 0; v < 4; v++) { if(pEditor->IsQuadCornerSelected(v)) { pQuad->m_aColors[v] = UnpackColor(NewVal); } } } else if(Prop == EQuadPointProp::PROP_TEX_U) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aTexcoords[v].x = f2fx(fx2f(pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f); } else if(Prop == EQuadPointProp::PROP_TEX_V) { for(int v = 0; v < 4; v++) if(pEditor->IsQuadCornerSelected(v)) pQuad->m_aTexcoords[v].y = f2fx(fx2f(pQuad->m_aTexcoords[v].y) + (NewVal - TextureV) / 1024.0f); } } if(Prop != EQuadPointProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO)) { pEditor->Map()->m_QuadTracker.EndQuadPointPropTrack(Prop); pEditor->Map()->OnModify(); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->Map()->m_vpEnvelopes.size()) return CUi::POPUP_CLOSE_CURRENT; const float RowHeight = 12.0f; CUIRect Row, Label, EditBox; pEditor->m_ActiveEnvelopePreview = EEnvelopePreview::SELECTED; std::shared_ptr<CEnvelope> pEnvelope = pEditor->Map()->m_vpEnvelopes[pEditor->m_SelectedEnvelope]; if(pEnvelope->GetChannels() == 4 && !pEditor->IsTangentSelected()) { View.HSplitTop(RowHeight, &Row, &View); View.HSplitTop(4.0f, nullptr, &View); Row.VSplitLeft(60.0f, &Label, &Row); Row.VSplitLeft(10.0f, nullptr, &EditBox); pEditor->Ui()->DoLabel(&Label, "Color:", RowHeight - 2.0f, TEXTALIGN_ML); const auto SelectedPoint = pEditor->m_vSelectedEnvelopePoints.front(); const int SelectedIndex = SelectedPoint.first; auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues; const ColorRGBA Color = pEnvelope->m_vPoints[SelectedIndex].ColorValue(); const auto &&SetColor = [&](ColorRGBA NewColor) { if(Color == NewColor && pEditor->m_ColorPickerPopupContext.m_State == EEditState::EDITING) return; static int s_Values[4]; if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::START || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO) { for(int Channel = 0; Channel < 4; ++Channel) s_Values[Channel] = pValues[Channel]; } pEnvelope->m_vPoints[SelectedIndex].SetColorValue(NewColor); if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::END || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO) { std::vector<std::shared_ptr<IEditorAction>> vpActions(4); for(int Channel = 0; Channel < 4; ++Channel) { vpActions[Channel] = std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, Channel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, s_Values[Channel], f2fx(NewColor[Channel])); } char aDisplay[256]; str_format(aDisplay, sizeof(aDisplay), "Edit color of point %d of envelope %d", SelectedIndex, pEditor->m_SelectedEnvelope); pEditor->Map()->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(pEditor->Map(), vpActions, aDisplay)); } pEditor->m_UpdateEnvPointInfo = true; pEditor->Map()->OnModify(); }; static char s_ColorPickerButton; pEditor->DoColorPickerButton(&s_ColorPickerButton, &EditBox, Color, SetColor); } static CLineInputNumber s_CurValueInput; static CLineInputNumber s_CurTimeInput; static float s_CurrentTime = 0; static float s_CurrentValue = 0; if(pEditor->m_UpdateEnvPointInfo) { pEditor->m_UpdateEnvPointInfo = false; const auto &[CurrentTime, CurrentValue] = pEditor->EnvGetSelectedTimeAndValue(); // update displayed text s_CurValueInput.SetFloat(fx2f(CurrentValue)); s_CurTimeInput.SetFloat(CurrentTime.AsSeconds()); s_CurrentTime = s_CurTimeInput.GetFloat(); s_CurrentValue = s_CurValueInput.GetFloat(); } View.HSplitTop(RowHeight, &Row, &View); Row.VSplitLeft(60.0f, &Label, &Row); Row.VSplitLeft(10.0f, nullptr, &EditBox); pEditor->Ui()->DoLabel(&Label, "Value:", RowHeight - 2.0f, TEXTALIGN_ML); pEditor->DoEditBox(&s_CurValueInput, &EditBox, RowHeight - 2.0f, IGraphics::CORNER_ALL, "The value of the selected envelope point."); View.HSplitTop(4.0f, nullptr, &View); View.HSplitTop(RowHeight, &Row, &View); Row.VSplitLeft(60.0f, &Label, &Row); Row.VSplitLeft(10.0f, nullptr, &EditBox); pEditor->Ui()->DoLabel(&Label, "Time (in s):", RowHeight - 2.0f, TEXTALIGN_ML); pEditor->DoEditBox(&s_CurTimeInput, &EditBox, RowHeight - 2.0f, IGraphics::CORNER_ALL, "The time of the selected envelope point."); if(pEditor->Input()->KeyIsPressed(KEY_RETURN) || pEditor->Input()->KeyIsPressed(KEY_KP_ENTER)) { float CurrentTime = s_CurTimeInput.GetFloat(); float CurrentValue = s_CurValueInput.GetFloat(); if(!(absolute(CurrentTime - s_CurrentTime) < 0.0001f && absolute(CurrentValue - s_CurrentValue) < 0.0001f)) { const auto &[OldTime, OldValue] = pEditor->EnvGetSelectedTimeAndValue(); if(pEditor->IsTangentInSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_IN, OldTime, OldValue, CFixedTime::FromSeconds(CurrentTime), f2fx(CurrentValue))); CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]).AsSeconds(); } else if(pEditor->IsTangentOutSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::TANGENT_OUT, OldTime, OldValue, CFixedTime::FromSeconds(CurrentTime), f2fx(CurrentValue))); CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]).AsSeconds(); } else { auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionEditEnvelopePointValue>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEditEnvelopePointValue::EType::POINT, OldTime, OldValue, CFixedTime::FromSeconds(CurrentTime), f2fx(CurrentValue))); if(SelectedIndex != 0) { CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time.AsSeconds(); } else { CurrentTime = 0.0f; pEnvelope->m_vPoints[SelectedIndex].m_Time = CFixedTime(0); } } s_CurTimeInput.SetFloat(CFixedTime::FromSeconds(CurrentTime).AsSeconds()); s_CurValueInput.SetFloat(fx2f(f2fx(CurrentValue))); s_CurrentTime = s_CurTimeInput.GetFloat(); s_CurrentValue = s_CurValueInput.GetFloat(); } } View.HSplitTop(6.0f, nullptr, &View); View.HSplitTop(RowHeight, &Row, &View); static int s_DeleteButtonId = 0; const char *pButtonText = pEditor->IsTangentSelected() ? "Reset" : "Delete"; const char *pTooltip = pEditor->IsTangentSelected() ? "Reset tangent point to default value." : "Delete current envelope point in all channels."; if(pEditor->DoButton_Editor(&s_DeleteButtonId, pButtonText, 0, &Row, BUTTONFLAG_LEFT, pTooltip)) { if(pEditor->IsTangentInSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint; pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionResetEnvelopePointTangent>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, true)); } else if(pEditor->IsTangentOutSelected()) { auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint; pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionResetEnvelopePointTangent>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, false)); } else { auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front(); pEditor->Map()->m_EnvelopeEditorHistory.Execute(std::make_shared<CEditorActionDeleteEnvelopePoint>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex)); } return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointMulti(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); const float RowHeight = 12.0f; static int s_CurveButtonId = 0; CUIRect CurveButton; View.HSplitTop(RowHeight, &CurveButton, &View); if(pEditor->DoButton_Editor(&s_CurveButtonId, "Project onto", 0, &CurveButton, BUTTONFLAG_LEFT, "Project all selected envelopes onto the curve between the first and last selected envelope.")) { static SPopupMenuId s_PopupCurveTypeId; pEditor->Ui()->DoPopupMenu(&s_PopupCurveTypeId, pEditor->Ui()->MouseX(), pEditor->Ui()->MouseY(), 80, 80, pEditor, PopupEnvPointCurveType); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); const float RowHeight = 14.0f; int CurveType = -1; static int s_ButtonLinearId; CUIRect ButtonLinear; View.HSplitTop(RowHeight, &ButtonLinear, &View); if(pEditor->DoButton_MenuItem(&s_ButtonLinearId, "Linear", 0, &ButtonLinear)) CurveType = CURVETYPE_LINEAR; static int s_ButtonSlowId; CUIRect ButtonSlow; View.HSplitTop(RowHeight, &ButtonSlow, &View); if(pEditor->DoButton_MenuItem(&s_ButtonSlowId, "Slow", 0, &ButtonSlow)) CurveType = CURVETYPE_SLOW; static int s_ButtonFastId; CUIRect ButtonFast; View.HSplitTop(RowHeight, &ButtonFast, &View); if(pEditor->DoButton_MenuItem(&s_ButtonFastId, "Fast", 0, &ButtonFast)) CurveType = CURVETYPE_FAST; static int s_ButtonStepId; CUIRect ButtonStep; View.HSplitTop(RowHeight, &ButtonStep, &View); if(pEditor->DoButton_MenuItem(&s_ButtonStepId, "Step", 0, &ButtonStep)) CurveType = CURVETYPE_STEP; static int s_ButtonSmoothId; CUIRect ButtonSmooth; View.HSplitTop(RowHeight, &ButtonSmooth, &View); if(pEditor->DoButton_MenuItem(&s_ButtonSmoothId, "Smooth", 0, &ButtonSmooth)) CurveType = CURVETYPE_SMOOTH; std::vector<std::shared_ptr<IEditorAction>> vpActions; if(CurveType >= 0) { std::shared_ptr<CEnvelope> pEnvelope = pEditor->Map()->m_vpEnvelopes.at(pEditor->m_SelectedEnvelope); for(int c = 0; c < pEnvelope->GetChannels(); c++) { int FirstSelectedIndex = pEnvelope->m_vPoints.size(); int LastSelectedIndex = -1; for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints) { if(SelectedChannel == c) { FirstSelectedIndex = minimum(FirstSelectedIndex, SelectedIndex); LastSelectedIndex = maximum(LastSelectedIndex, SelectedIndex); } } if(FirstSelectedIndex < (int)pEnvelope->m_vPoints.size() && LastSelectedIndex >= 0 && FirstSelectedIndex != LastSelectedIndex) { CEnvPoint FirstPoint = pEnvelope->m_vPoints[FirstSelectedIndex]; CEnvPoint LastPoint = pEnvelope->m_vPoints[LastSelectedIndex]; CEnvelope HelperEnvelope(1); HelperEnvelope.AddPoint(FirstPoint.m_Time, {FirstPoint.m_aValues[c], 0, 0, 0}); HelperEnvelope.AddPoint(LastPoint.m_Time, {LastPoint.m_aValues[c], 0, 0, 0}); HelperEnvelope.m_vPoints[0].m_Curvetype = CurveType; for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints) { if(SelectedChannel == c) { if(SelectedIndex != FirstSelectedIndex && SelectedIndex != LastSelectedIndex) { CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex]; ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f); HelperEnvelope.Eval(CurrentPoint.m_Time.AsSeconds(), Channels, 1); int PrevValue = CurrentPoint.m_aValues[c]; CurrentPoint.m_aValues[c] = f2fx(Channels.r); vpActions.push_back(std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor->Map(), pEditor->m_SelectedEnvelope, SelectedIndex, SelectedChannel, CEditorActionEnvelopeEditPoint::EEditType::VALUE, PrevValue, CurrentPoint.m_aValues[c])); } } } } } if(!vpActions.empty()) { pEditor->Map()->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionBulk>(pEditor->Map(), vpActions, "Project points")); } pEditor->Map()->OnModify(); return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } static const auto &&gs_ModifyIndexDeleted = [](int DeletedIndex) { return [DeletedIndex](int *pIndex) { if(*pIndex == DeletedIndex) *pIndex = -1; else if(*pIndex > DeletedIndex) *pIndex = *pIndex - 1; }; }; CUi::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); static int s_ExternalButton = 0; static int s_ReimportButton = 0; static int s_ReplaceButton = 0; static int s_RemoveButton = 0; static int s_ExportButton = 0; const float RowHeight = 12.0f; CUIRect Slot; View.HSplitTop(RowHeight, &Slot, &View); std::shared_ptr<CEditorImage> pImg = pEditor->Map()->SelectedImage(); if(!pImg->m_External) { CUIRect Label, EditBox; static CLineInput s_RenameInput; Slot.VMargin(5.0f, &Slot); Slot.VSplitLeft(35.0f, &Label, &Slot); Slot.VSplitLeft(RowHeight - 2.0f, nullptr, &EditBox); pEditor->Ui()->DoLabel(&Label, "Name:", RowHeight - 2.0f, TEXTALIGN_ML); s_RenameInput.SetBuffer(pImg->m_aName, sizeof(pImg->m_aName)); if(pEditor->DoEditBox(&s_RenameInput, &EditBox, RowHeight - 2.0f)) pEditor->Map()->OnModify(); View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); } if(pImg->m_External) { if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Embed", 0, &Slot, BUTTONFLAG_LEFT, "Embed the image into the map file.")) { if(pImg->m_pData == nullptr) { pEditor->ShowFileDialogError("Embedding is not possible because the image could not be loaded."); return CUi::POPUP_KEEP_OPEN; } pImg->m_External = 0; return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); } else if(CEditor::IsVanillaImage(pImg->m_aName)) { if(pEditor->DoButton_MenuItem(&s_ExternalButton, "Make external", 0, &Slot, BUTTONFLAG_LEFT, "Remove the image from the map file.")) { pImg->m_External = 1; return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); } static CUi::SSelectionPopupContext s_SelectionPopupContext; static CScrollRegion s_SelectionPopupScrollRegion; s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion; if(pEditor->DoButton_MenuItem(&s_ReimportButton, "Re-import", 0, &Slot, BUTTONFLAG_LEFT, "Re-import the image from the mapres folder.")) { char aFilename[IO_MAX_PATH_LENGTH]; str_format(aFilename, sizeof(aFilename), "%s.png", pImg->m_aName); s_SelectionPopupContext.Reset(); std::set<std::string> EntriesSet; pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet); for(const auto &Entry : EntriesSet) s_SelectionPopupContext.m_vEntries.push_back(Entry); if(s_SelectionPopupContext.m_vEntries.empty()) { pEditor->ShowFileDialogError("Error: could not find image '%s' in the mapres folder.", aFilename); } else if(s_SelectionPopupContext.m_vEntries.size() == 1) { s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front(); } else { str_copy(s_SelectionPopupContext.m_aMessage, "Select the wanted image:"); pEditor->Ui()->ShowPopupSelection(pEditor->Ui()->MouseX(), pEditor->Ui()->MouseY(), &s_SelectionPopupContext); } } if(s_SelectionPopupContext.m_pSelection != nullptr) { const bool WasExternal = pImg->m_External; const bool Result = pEditor->ReplaceImage(s_SelectionPopupContext.m_pSelection->c_str(), IStorage::TYPE_ALL, false); pImg->m_External = WasExternal; s_SelectionPopupContext.Reset(); return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, BUTTONFLAG_LEFT, "Replace the image with a new one.")) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::IMAGE, "Replace image", "Replace", "mapres", "", ReplaceImageCallback, pEditor); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, BUTTONFLAG_LEFT, "Remove the image from the map.")) { if(pEditor->Map()->IsImageUsed(pEditor->Map()->m_SelectedImage)) { pEditor->m_PopupEventType = POPEVENT_REMOVE_USED_IMAGE; pEditor->m_PopupEventActivated = true; } else { pEditor->Map()->m_vpImages.erase(pEditor->Map()->m_vpImages.begin() + pEditor->Map()->m_SelectedImage); pEditor->Map()->ModifyImageIndex(gs_ModifyIndexDeleted(pEditor->Map()->m_SelectedImage)); } return CUi::POPUP_CLOSE_CURRENT; } if(!pImg->m_External) { View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ExportButton, "Export", 0, &Slot, BUTTONFLAG_LEFT, "Export the image to a separate file.")) { if(pImg->m_pData == nullptr) { pEditor->ShowFileDialogError("Exporting is not possible because the image could not be loaded."); return CUi::POPUP_KEEP_OPEN; } pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_SAVE, CFileBrowser::EFileType::IMAGE, "Save image", "Save", "mapres", pImg->m_aName, CallbackSaveImage, pEditor); return CUi::POPUP_CLOSE_CURRENT; } } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); static int s_ReimportButton = 0; static int s_ReplaceButton = 0; static int s_RemoveButton = 0; static int s_ExportButton = 0; const float RowHeight = 12.0f; CUIRect Slot; View.HSplitTop(RowHeight, &Slot, &View); std::shared_ptr<CEditorSound> pSound = pEditor->Map()->SelectedSound(); static CUi::SSelectionPopupContext s_SelectionPopupContext; static CScrollRegion s_SelectionPopupScrollRegion; s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion; CUIRect Label, EditBox; static CLineInput s_RenameInput; Slot.VMargin(5.0f, &Slot); Slot.VSplitLeft(35.0f, &Label, &Slot); Slot.VSplitLeft(RowHeight - 2.0f, nullptr, &EditBox); pEditor->Ui()->DoLabel(&Label, "Name:", RowHeight - 2.0f, TEXTALIGN_ML); s_RenameInput.SetBuffer(pSound->m_aName, sizeof(pSound->m_aName)); if(pEditor->DoEditBox(&s_RenameInput, &EditBox, RowHeight - 2.0f)) pEditor->Map()->OnModify(); View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ReimportButton, "Re-import", 0, &Slot, BUTTONFLAG_LEFT, "Re-import the sound from the mapres folder.")) { char aFilename[IO_MAX_PATH_LENGTH]; str_format(aFilename, sizeof(aFilename), "%s.opus", pSound->m_aName); s_SelectionPopupContext.Reset(); std::set<std::string> EntriesSet; pEditor->Storage()->FindFiles(aFilename, "mapres", IStorage::TYPE_ALL, &EntriesSet); for(const auto &Entry : EntriesSet) s_SelectionPopupContext.m_vEntries.push_back(Entry); if(s_SelectionPopupContext.m_vEntries.empty()) { pEditor->ShowFileDialogError("Error: could not find sound '%s' in the mapres folder.", aFilename); } else if(s_SelectionPopupContext.m_vEntries.size() == 1) { s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front(); } else { str_copy(s_SelectionPopupContext.m_aMessage, "Select the wanted sound:"); pEditor->Ui()->ShowPopupSelection(pEditor->Ui()->MouseX(), pEditor->Ui()->MouseY(), &s_SelectionPopupContext); } } if(s_SelectionPopupContext.m_pSelection != nullptr) { const bool Result = pEditor->ReplaceSound(s_SelectionPopupContext.m_pSelection->c_str(), IStorage::TYPE_ALL, false); s_SelectionPopupContext.Reset(); return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ReplaceButton, "Replace", 0, &Slot, BUTTONFLAG_LEFT, "Replace the sound with a new one.")) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::SOUND, "Replace sound", "Replace", "mapres", "", ReplaceSoundCallback, pEditor); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_RemoveButton, "Remove", 0, &Slot, BUTTONFLAG_LEFT, "Remove the sound from the map.")) { if(pEditor->Map()->IsSoundUsed(pEditor->Map()->m_SelectedSound)) { pEditor->m_PopupEventType = POPEVENT_REMOVE_USED_SOUND; pEditor->m_PopupEventActivated = true; } else { pEditor->Map()->m_vpSounds.erase(pEditor->Map()->m_vpSounds.begin() + pEditor->Map()->m_SelectedSound); pEditor->Map()->ModifySoundIndex(gs_ModifyIndexDeleted(pEditor->Map()->m_SelectedSound)); pEditor->m_ToolbarPreviewSound = -1; } return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(5.0f, nullptr, &View); View.HSplitTop(RowHeight, &Slot, &View); if(pEditor->DoButton_MenuItem(&s_ExportButton, "Export", 0, &Slot, BUTTONFLAG_LEFT, "Export the sound to a separate file.")) { if(pSound->m_pData == nullptr) { pEditor->ShowFileDialogError("Exporting is not possible because the sound could not be loaded."); return CUi::POPUP_KEEP_OPEN; } pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_SAVE, CFileBrowser::EFileType::SOUND, "Save sound", "Save", "mapres", pSound->m_aName, CallbackSaveSound, pEditor); return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CUIRect Label, ButtonBar, Button; View.Margin(10.0f, &View); View.HSplitBottom(20.0f, &View, &ButtonBar); // title View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "Map details", 20.0f, TEXTALIGN_MC); View.HSplitTop(10.0f, nullptr, &View); // author box View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "Author:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); static CLineInput s_AuthorInput; s_AuthorInput.SetBuffer(pEditor->Map()->m_MapInfoTmp.m_aAuthor, sizeof(pEditor->Map()->m_MapInfoTmp.m_aAuthor)); pEditor->DoEditBox(&s_AuthorInput, &Button, 10.0f); // version box View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "Version:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); static CLineInput s_VersionInput; s_VersionInput.SetBuffer(pEditor->Map()->m_MapInfoTmp.m_aVersion, sizeof(pEditor->Map()->m_MapInfoTmp.m_aVersion)); pEditor->DoEditBox(&s_VersionInput, &Button, 10.0f); // credits box View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "Credits:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); static CLineInput s_CreditsInput; s_CreditsInput.SetBuffer(pEditor->Map()->m_MapInfoTmp.m_aCredits, sizeof(pEditor->Map()->m_MapInfoTmp.m_aCredits)); pEditor->DoEditBox(&s_CreditsInput, &Button, 10.0f); // license box View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "License:", 10.0f, TEXTALIGN_ML); Label.VSplitLeft(60.0f, nullptr, &Button); Button.HMargin(3.0f, &Button); static CLineInput s_LicenseInput; s_LicenseInput.SetBuffer(pEditor->Map()->m_MapInfoTmp.m_aLicense, sizeof(pEditor->Map()->m_MapInfoTmp.m_aLicense)); pEditor->DoEditBox(&s_LicenseInput, &Button, 10.0f); // button bar ButtonBar.VSplitLeft(110.0f, &Label, &ButtonBar); static int s_CancelButton = 0; if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Label, BUTTONFLAG_LEFT, nullptr)) return CUi::POPUP_CLOSE_CURRENT; ButtonBar.VSplitRight(110.0f, &ButtonBar, &Label); static int s_ConfirmButton = 0; if(pEditor->DoButton_Editor(&s_ConfirmButton, "Confirm", 0, &Label, BUTTONFLAG_LEFT, nullptr) || (Active && pEditor->Ui()->ConsumeHotkey(CUi::HOTKEY_ENTER))) { bool AuthorDifferent = str_comp(pEditor->Map()->m_MapInfoTmp.m_aAuthor, pEditor->Map()->m_MapInfo.m_aAuthor) != 0; bool VersionDifferent = str_comp(pEditor->Map()->m_MapInfoTmp.m_aVersion, pEditor->Map()->m_MapInfo.m_aVersion) != 0; bool CreditsDifferent = str_comp(pEditor->Map()->m_MapInfoTmp.m_aCredits, pEditor->Map()->m_MapInfo.m_aCredits) != 0; bool LicenseDifferent = str_comp(pEditor->Map()->m_MapInfoTmp.m_aLicense, pEditor->Map()->m_MapInfo.m_aLicense) != 0; if(AuthorDifferent || VersionDifferent || CreditsDifferent || LicenseDifferent) pEditor->Map()->OnModify(); pEditor->Map()->m_MapInfo.Copy(pEditor->Map()->m_MapInfoTmp); return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); const char *pTitle; const char *pMessage; char aMessageBuf[128]; if(pEditor->m_PopupEventType == POPEVENT_EXIT) { pTitle = "Exit the editor"; pMessage = "The map contains unsaved data, you might want to save it before you exit the editor.\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT || pEditor->m_PopupEventType == POPEVENT_LOADDROP) { pTitle = "Load map"; pMessage = "The map contains unsaved data, you might want to save it before you load a new map.\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_NEW) { pTitle = "New map"; pMessage = "The map contains unsaved data, you might want to save it before you create a new map.\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_LARGELAYER) { pTitle = "Large layer"; pMessage = "You are trying to set the height or width of a layer to more than 1000 tiles. This is actually possible, but only rarely necessary. It may cause the editor to work slower and will result in a larger file size as well as higher memory usage for client and server."; } else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES) { pTitle = "Unused tiles disabled"; pMessage = "Unused tiles can't be placed by default because they could get a use later and then destroy your map.\n\nActivate the 'Allow unused' setting to be able to place every tile."; } else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16) { pTitle = "Image width/height"; pMessage = "The width or height of this image is not divisible by 16. This is required for images used in tile layers."; } else if(pEditor->m_PopupEventType == POPEVENT_IMAGE_MAX) { pTitle = "Max images"; str_format(aMessageBuf, sizeof(aMessageBuf), "The client only allows a maximum of %" PRIzu " images.", MAX_MAPIMAGES); pMessage = aMessageBuf; } else if(pEditor->m_PopupEventType == POPEVENT_SOUND_MAX) { pTitle = "Max sounds"; str_format(aMessageBuf, sizeof(aMessageBuf), "The client only allows a maximum of %" PRIzu " sounds.", MAX_MAPSOUNDS); pMessage = aMessageBuf; } else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES) { pTitle = "Place border tiles"; pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?"; } else if(pEditor->m_PopupEventType == POPEVENT_TILEART_BIG_IMAGE) { pTitle = "Big image"; pMessage = "The selected image is big. Converting it to tileart may take some time.\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_TILEART_MANY_COLORS) { pTitle = "Many colors"; pMessage = "The selected image contains many colors, which will lead to a big mapfile. You may want to consider reducing the number of colors.\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_TILEART_TOO_MANY_COLORS) { pTitle = "Too many colors"; pMessage = "The client only supports 64 images but more would be needed to add the selected image as tileart."; } else if(pEditor->m_PopupEventType == POPEVENT_QUADART_BIG_IMAGE) { pTitle = "Big image"; pMessage = "The selected image is really big. Expect performance issues!\n\nContinue anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_REMOVE_USED_IMAGE) { pTitle = "Remove image"; pMessage = "This image is used in the map. Removing it will reset all layers that use this image to their default.\n\nRemove anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_REMOVE_USED_SOUND) { pTitle = "Remove sound"; pMessage = "This sound is used in the map. Removing it will reset all layers that use this sound to their default.\n\nRemove anyway?"; } else if(pEditor->m_PopupEventType == POPEVENT_RESTART_SERVER) { pTitle = "Restart server"; pMessage = "You have a local server running, but you are not authorized or connected.\n\nDo you want to restart the server and reconnect?"; } else if(pEditor->m_PopupEventType == POPEVENT_RESTARTING_SERVER) { pTitle = "Restarting server"; pMessage = "Local server is restarting. Please wait…"; CGameClient *pGameClient = (CGameClient *)pEditor->Kernel()->RequestInterface<IGameClient>(); if(!pGameClient->m_LocalServer.IsServerRunning()) { pEditor->TestMapLocally(); return CUi::POPUP_CLOSE_CURRENT; } } else { dbg_assert_failed("m_PopupEventType invalid"); } CUIRect Label, ButtonBar, Button; View.Margin(10.0f, &View); View.HSplitBottom(20.0f, &View, &ButtonBar); // title View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, pTitle, 20.0f, TEXTALIGN_MC); // message SLabelProperties Props; Props.m_MaxWidth = View.w; pEditor->Ui()->DoLabel(&View, pMessage, 10.0f, TEXTALIGN_ML, Props); // button bar ButtonBar.VSplitLeft(110.0f, &Button, &ButtonBar); if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER && pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES && pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 && pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX && pEditor->m_PopupEventType != POPEVENT_SOUND_MAX && pEditor->m_PopupEventType != POPEVENT_TILEART_TOO_MANY_COLORS) { static int s_CancelButton = 0; if(pEditor->DoButton_Editor(&s_CancelButton, "Cancel", 0, &Button, BUTTONFLAG_LEFT, nullptr)) { if(pEditor->m_PopupEventType == POPEVENT_LOADDROP) pEditor->m_aFilenamePendingLoad[0] = 0; else if(pEditor->m_PopupEventType == POPEVENT_TILEART_BIG_IMAGE || pEditor->m_PopupEventType == POPEVENT_TILEART_MANY_COLORS) pEditor->m_TileartImageInfo.Free(); else if(pEditor->m_PopupEventType == POPEVENT_QUADART_BIG_IMAGE) pEditor->m_QuadArtImageInfo.Free(); pEditor->m_PopupEventWasActivated = false; return CUi::POPUP_CLOSE_CURRENT; } } if(pEditor->m_PopupEventType == POPEVENT_RESTARTING_SERVER) return CUi::POPUP_KEEP_OPEN; ButtonBar.VSplitRight(110.0f, &ButtonBar, &Button); static int s_ConfirmButton = 0; if(pEditor->DoButton_Editor(&s_ConfirmButton, "Confirm", 0, &Button, BUTTONFLAG_LEFT, nullptr) || (Active && pEditor->Ui()->ConsumeHotkey(CUi::HOTKEY_ENTER))) { if(pEditor->m_PopupEventType == POPEVENT_EXIT) { pEditor->OnClose(); g_Config.m_ClEditor = 0; } else if(pEditor->m_PopupEventType == POPEVENT_LOAD) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::MAP, "Load map", "Load", "maps", "", CallbackOpenMap, pEditor); } else if(pEditor->m_PopupEventType == POPEVENT_LOADCURRENT) { pEditor->LoadCurrentMap(); } else if(pEditor->m_PopupEventType == POPEVENT_LOADDROP) { int Result = pEditor->Load(pEditor->m_aFilenamePendingLoad, IStorage::TYPE_ALL_OR_ABSOLUTE); if(!Result) dbg_msg("editor", "editing passed map file '%s' failed", pEditor->m_aFilenamePendingLoad); pEditor->m_aFilenamePendingLoad[0] = 0; } else if(pEditor->m_PopupEventType == POPEVENT_NEW) { pEditor->Reset(); } else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES) { pEditor->PlaceBorderTiles(); } else if(pEditor->m_PopupEventType == POPEVENT_TILEART_BIG_IMAGE) { pEditor->TileartCheckColors(); } else if(pEditor->m_PopupEventType == POPEVENT_TILEART_MANY_COLORS) { pEditor->AddTileart(); } else if(pEditor->m_PopupEventType == POPEVENT_QUADART_BIG_IMAGE) { pEditor->AddQuadArt(); } else if(pEditor->m_PopupEventType == POPEVENT_REMOVE_USED_IMAGE) { pEditor->Map()->m_vpImages.erase(pEditor->Map()->m_vpImages.begin() + pEditor->Map()->m_SelectedImage); pEditor->Map()->ModifyImageIndex(gs_ModifyIndexDeleted(pEditor->Map()->m_SelectedImage)); } else if(pEditor->m_PopupEventType == POPEVENT_REMOVE_USED_SOUND) { pEditor->Map()->m_vpSounds.erase(pEditor->Map()->m_vpSounds.begin() + pEditor->Map()->m_SelectedSound); pEditor->Map()->ModifySoundIndex(gs_ModifyIndexDeleted(pEditor->Map()->m_SelectedSound)); pEditor->m_ToolbarPreviewSound = -1; } else if(pEditor->m_PopupEventType == POPEVENT_RESTART_SERVER) { CGameClient *pGameClient = (CGameClient *)pEditor->Kernel()->RequestInterface<IGameClient>(); pGameClient->m_LocalServer.KillServer(); pEditor->m_PopupEventType = CEditor::POPEVENT_RESTARTING_SERVER; pEditor->m_PopupEventActivated = true; } pEditor->m_PopupEventWasActivated = false; return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } static int g_SelectImageSelected = -100; static int g_SelectImageCurrent = -100; CUi::EPopupMenuFunctionResult CEditor::PopupSelectImage(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CUIRect ButtonBar, ImageView; View.VSplitLeft(150.0f, &ButtonBar, &View); View.Margin(10.0f, &ImageView); int ShowImage = g_SelectImageCurrent; const float ButtonHeight = 12.0f; const float ButtonMargin = 2.0f; static CListBox s_ListBox; s_ListBox.DoStart(ButtonHeight, pEditor->Map()->m_vpImages.size() + 1, 1, 4, g_SelectImageCurrent + 1, &ButtonBar, false); s_ListBox.DoAutoSpacing(ButtonMargin); for(int i = 0; i <= (int)pEditor->Map()->m_vpImages.size(); i++) { static int s_NoneButton = 0; CListboxItem Item = s_ListBox.DoNextItem(i == 0 ? (void *)&s_NoneButton : &pEditor->Map()->m_vpImages[i - 1], (i - 1) == g_SelectImageCurrent, 3.0f); if(!Item.m_Visible) continue; if(pEditor->Ui()->MouseInside(&Item.m_Rect)) ShowImage = i - 1; CUIRect Label; Item.m_Rect.VMargin(5.0f, &Label); SLabelProperties Props; Props.m_MaxWidth = Label.w; Props.m_EllipsisAtEnd = true; pEditor->Ui()->DoLabel(&Label, i == 0 ? "None" : pEditor->Map()->m_vpImages[i - 1]->m_aName, EditorFontSizes::MENU, TEXTALIGN_ML, Props); } int NewSelected = s_ListBox.DoEnd() - 1; if(NewSelected != g_SelectImageCurrent) g_SelectImageSelected = NewSelected; if(ShowImage >= 0 && (size_t)ShowImage < pEditor->Map()->m_vpImages.size()) { if(ImageView.h < ImageView.w) ImageView.w = ImageView.h; else ImageView.h = ImageView.w; float Max = (float)(maximum(pEditor->Map()->m_vpImages[ShowImage]->m_Width, pEditor->Map()->m_vpImages[ShowImage]->m_Height)); ImageView.w *= pEditor->Map()->m_vpImages[ShowImage]->m_Width / Max; ImageView.h *= pEditor->Map()->m_vpImages[ShowImage]->m_Height / Max; pEditor->Graphics()->TextureSet(pEditor->Map()->m_vpImages[ShowImage]->m_Texture); pEditor->Graphics()->BlendNormal(); pEditor->Graphics()->WrapClamp(); pEditor->Graphics()->QuadsBegin(); IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h); pEditor->Graphics()->QuadsDrawTL(&QuadItem, 1); pEditor->Graphics()->QuadsEnd(); pEditor->Graphics()->WrapNormal(); } return CUi::POPUP_KEEP_OPEN; } void CEditor::PopupSelectImageInvoke(int Current, float x, float y) { static SPopupMenuId s_PopupSelectImageId; g_SelectImageSelected = -100; g_SelectImageCurrent = Current; Ui()->DoPopupMenu(&s_PopupSelectImageId, x, y, 450, 300, this, PopupSelectImage); } int CEditor::PopupSelectImageResult() { if(g_SelectImageSelected == -100) return -100; g_SelectImageCurrent = g_SelectImageSelected; g_SelectImageSelected = -100; return g_SelectImageCurrent; } static int g_SelectSoundSelected = -100; static int g_SelectSoundCurrent = -100; CUi::EPopupMenuFunctionResult CEditor::PopupSelectSound(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); const float ButtonHeight = 12.0f; const float ButtonMargin = 2.0f; static CListBox s_ListBox; s_ListBox.DoStart(ButtonHeight, pEditor->Map()->m_vpSounds.size() + 1, 1, 4, g_SelectSoundCurrent + 1, &View, false); s_ListBox.DoAutoSpacing(ButtonMargin); for(int i = 0; i <= (int)pEditor->Map()->m_vpSounds.size(); i++) { static int s_NoneButton = 0; CListboxItem Item = s_ListBox.DoNextItem(i == 0 ? (void *)&s_NoneButton : &pEditor->Map()->m_vpSounds[i - 1], (i - 1) == g_SelectSoundCurrent, 3.0f); if(!Item.m_Visible) continue; CUIRect Label; Item.m_Rect.VMargin(5.0f, &Label); SLabelProperties Props; Props.m_MaxWidth = Label.w; Props.m_EllipsisAtEnd = true; pEditor->Ui()->DoLabel(&Label, i == 0 ? "None" : pEditor->Map()->m_vpSounds[i - 1]->m_aName, EditorFontSizes::MENU, TEXTALIGN_ML, Props); } int NewSelected = s_ListBox.DoEnd() - 1; if(NewSelected != g_SelectSoundCurrent) g_SelectSoundSelected = NewSelected; return CUi::POPUP_KEEP_OPEN; } void CEditor::PopupSelectSoundInvoke(int Current, float x, float y) { static SPopupMenuId s_PopupSelectSoundId; g_SelectSoundSelected = -100; g_SelectSoundCurrent = Current; Ui()->DoPopupMenu(&s_PopupSelectSoundId, x, y, 150, 300, this, PopupSelectSound); } int CEditor::PopupSelectSoundResult() { if(g_SelectSoundSelected == -100) return -100; g_SelectSoundCurrent = g_SelectSoundSelected; g_SelectSoundSelected = -100; return g_SelectSoundCurrent; } static int s_GametileOpSelected = -1; static const char *s_apGametileOpButtonNames[] = { "Air", "Hookable", "Death", "Unhookable", "Hookthrough", "Freeze", "Unfreeze", "Deep Freeze", "Deep Unfreeze", "Blue Check-Tele", "Red Check-Tele", "Live Freeze", "Live Unfreeze", }; CUi::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); const int PreviousSelected = s_GametileOpSelected; CUIRect Button; for(size_t i = 0; i < std::size(s_apGametileOpButtonNames); ++i) { View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Button, &View); if(pEditor->DoButton_Editor(&s_apGametileOpButtonNames[i], s_apGametileOpButtonNames[i], 0, &Button, BUTTONFLAG_LEFT, nullptr)) s_GametileOpSelected = i; } return s_GametileOpSelected == PreviousSelected ? CUi::POPUP_KEEP_OPEN : CUi::POPUP_CLOSE_CURRENT; } void CEditor::PopupSelectGametileOpInvoke(float x, float y) { static SPopupMenuId s_PopupSelectGametileOpId; s_GametileOpSelected = -1; Ui()->DoPopupMenu(&s_PopupSelectGametileOpId, x, y, 120.0f, std::size(s_apGametileOpButtonNames) * 14.0f + 10.0f, this, PopupSelectGametileOp); } int CEditor::PopupSelectGameTileOpResult() { if(s_GametileOpSelected < 0) return -1; int Result = s_GametileOpSelected; s_GametileOpSelected = -1; return Result; } static int s_AutoMapConfigSelected = -100; static int s_AutoMapConfigCurrent = -100; CUi::EPopupMenuFunctionResult CEditor::PopupSelectConfigAutoMap(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(pEditor->GetSelectedLayer(0)); CAutoMapper *pAutoMapper = &pEditor->Map()->m_vpImages[pLayer->m_Image]->m_AutoMapper; const float ButtonHeight = 12.0f; const float ButtonMargin = 2.0f; static CListBox s_ListBox; s_ListBox.DoStart(ButtonHeight, pAutoMapper->ConfigNamesNum() + 1, 1, 4, s_AutoMapConfigCurrent + 1, &View, false); s_ListBox.SetScrollbarWidth(15.0f); s_ListBox.DoAutoSpacing(ButtonMargin); for(int i = 0; i < pAutoMapper->ConfigNamesNum() + 1; i++) { static int s_NoneButton = 0; CListboxItem Item = s_ListBox.DoNextItem(i == 0 ? (void *)&s_NoneButton : pAutoMapper->GetConfigName(i - 1), (i - 1) == s_AutoMapConfigCurrent, 3.0f); if(!Item.m_Visible) continue; CUIRect Label; Item.m_Rect.VMargin(5.0f, &Label); SLabelProperties Props; Props.m_MaxWidth = Label.w; Props.m_EllipsisAtEnd = true; pEditor->Ui()->DoLabel(&Label, i == 0 ? "None" : pAutoMapper->GetConfigName(i - 1), EditorFontSizes::MENU, TEXTALIGN_ML, Props); } int NewSelected = s_ListBox.DoEnd() - 1; if(NewSelected != s_AutoMapConfigCurrent) s_AutoMapConfigSelected = NewSelected; return CUi::POPUP_KEEP_OPEN; } void CEditor::PopupSelectConfigAutoMapInvoke(int Current, float x, float y) { static SPopupMenuId s_PopupSelectConfigAutoMapId; s_AutoMapConfigSelected = -100; s_AutoMapConfigCurrent = Current; std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayer(0)); const int ItemCount = minimum(Map()->m_vpImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum() + 1, 10); // +1 for None-entry // Width for buttons is 120, 15 is the scrollbar width, 2 is the margin between both. Ui()->DoPopupMenu(&s_PopupSelectConfigAutoMapId, x, y, 120.0f + 15.0f + 2.0f, 10.0f + 12.0f * ItemCount + 2.0f * (ItemCount - 1) + CScrollRegion::HEIGHT_MAGIC_FIX, this, PopupSelectConfigAutoMap); } int CEditor::PopupSelectConfigAutoMapResult() { if(s_AutoMapConfigSelected == -100) return -100; s_AutoMapConfigCurrent = s_AutoMapConfigSelected; s_AutoMapConfigSelected = -100; return s_AutoMapConfigCurrent; } static int s_AutoMapReferenceSelected = -100; static int s_AutoMapReferenceCurrent = -100; CUi::EPopupMenuFunctionResult CEditor::PopupSelectAutoMapReference(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(pEditor->GetSelectedLayer(0)); const float ButtonHeight = 12.0f; const float ButtonMargin = 2.0f; static CListBox s_ListBox; s_ListBox.DoStart(ButtonHeight, std::size(AUTOMAP_REFERENCE_NAMES) + 1, 1, 4, s_AutoMapReferenceCurrent + 1, &View, false); s_ListBox.DoAutoSpacing(ButtonMargin); for(int i = 0; i < static_cast<int>(std::size(AUTOMAP_REFERENCE_NAMES)) + 1; i++) { static int s_NoneButton = 0; CListboxItem Item = s_ListBox.DoNextItem(i == 0 ? (void *)&s_NoneButton : AUTOMAP_REFERENCE_NAMES[i - 1], (i - 1) == s_AutoMapReferenceCurrent, 3.0f); if(!Item.m_Visible) continue; CUIRect Label; Item.m_Rect.VMargin(5.0f, &Label); SLabelProperties Props; Props.m_MaxWidth = Label.w; Props.m_EllipsisAtEnd = true; pEditor->Ui()->DoLabel(&Label, i == 0 ? "None" : AUTOMAP_REFERENCE_NAMES[i - 1], EditorFontSizes::MENU, TEXTALIGN_ML, Props); } int NewSelected = s_ListBox.DoEnd() - 1; if(NewSelected != s_AutoMapReferenceCurrent) s_AutoMapReferenceSelected = NewSelected; return CUi::POPUP_KEEP_OPEN; } void CEditor::PopupSelectAutoMapReferenceInvoke(int Current, float x, float y) { static SPopupMenuId s_PopupSelectAutoMapReferenceId; s_AutoMapReferenceSelected = -100; s_AutoMapReferenceCurrent = Current; std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(GetSelectedLayer(0)); // Width for buttons is 120, 15 is the scrollbar width, 2 is the margin between both. Ui()->DoPopupMenu(&s_PopupSelectAutoMapReferenceId, x, y, 120.0f + 15.0f + 2.0f, 26.0f + 14.0f * std::size(AUTOMAP_REFERENCE_NAMES) + 1, this, PopupSelectAutoMapReference); } int CEditor::PopupSelectAutoMapReferenceResult() { if(s_AutoMapReferenceSelected == -100) return -100; s_AutoMapReferenceCurrent = s_AutoMapReferenceSelected; s_AutoMapReferenceSelected = -100; return s_AutoMapReferenceCurrent; } // DDRace CUi::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); if(!pEditor->Map()->m_pTeleLayer) return CUi::POPUP_CLOSE_CURRENT; static int s_PreviousTeleNumber; static int s_PreviousCheckpointNumber; static int s_PreviousViewTeleNumber; CUIRect NumberPicker; CUIRect FindEmptySlot; CUIRect FindFreeTeleSlot, FindFreeCheckpointSlot, FindFreeViewSlot; View.VSplitRight(15.f, &NumberPicker, &FindEmptySlot); NumberPicker.VSplitRight(2.f, &NumberPicker, nullptr); FindEmptySlot.HSplitTop(13.0f, &FindFreeTeleSlot, &FindEmptySlot); FindEmptySlot.HSplitTop(13.0f, &FindFreeCheckpointSlot, &FindEmptySlot); FindEmptySlot.HSplitTop(13.0f, &FindFreeViewSlot, &FindEmptySlot); FindFreeTeleSlot.HMargin(1.0f, &FindFreeTeleSlot); FindFreeCheckpointSlot.HMargin(1.0f, &FindFreeCheckpointSlot); FindFreeViewSlot.HMargin(1.0f, &FindFreeViewSlot); auto ViewTele = [](CEditor *pEd) -> bool { if(!pEd->m_ViewTeleNumber) return false; int TeleX, TeleY; pEd->Map()->m_pTeleLayer->GetPos(pEd->m_ViewTeleNumber, -1, TeleX, TeleY); if(TeleX != -1 && TeleY != -1) { pEd->MapView()->SetWorldOffset({32.0f * TeleX + 0.5f, 32.0f * TeleY + 0.5f}); return true; } return false; }; static std::vector<ColorRGBA> s_vColors = { ColorRGBA(0.5f, 1, 0.5f, 0.5f), ColorRGBA(0.5f, 1, 0.5f, 0.5f), ColorRGBA(1, 0.5f, 0.5f, 0.5f), }; enum { PROP_TELE = 0, PROP_TELE_CP, PROP_TELE_VIEW, NUM_PROPS, }; // find next free numbers buttons { // Pressing ctrl+f will find next free numbers for both tele and checkpoints static int s_NextFreeTelePid = 0; if(pEditor->DoButton_Editor(&s_NextFreeTelePid, "F", 0, &FindFreeTeleSlot, BUTTONFLAG_LEFT, "[Ctrl+F] Find next free tele number.") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { int TeleNumber = pEditor->Map()->m_pTeleLayer->FindNextFreeNumber(false); if(TeleNumber != -1) { pEditor->m_TeleNumber = TeleNumber; pEditor->AdjustBrushSpecialTiles(false); } } static int s_NextFreeCheckpointPid = 0; if(pEditor->DoButton_Editor(&s_NextFreeCheckpointPid, "F", 0, &FindFreeCheckpointSlot, BUTTONFLAG_LEFT, "[Ctrl+F] Find next free checkpoint number.") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { int CheckpointNumber = pEditor->Map()->m_pTeleLayer->FindNextFreeNumber(true); if(CheckpointNumber != -1) { pEditor->m_TeleCheckpointNumber = CheckpointNumber; pEditor->AdjustBrushSpecialTiles(false); } } static int s_NextFreeViewPid = 0; if(pEditor->DoButton_Editor(&s_NextFreeViewPid, "N", 0, &FindFreeViewSlot, BUTTONFLAG_LEFT, "[N] Show next tele with this number.") || (Active && pEditor->Input()->KeyPress(KEY_N))) { s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } } // number picker { CProperty aProps[] = { {"Number", pEditor->m_TeleNumber, PROPTYPE_INT, 1, 255}, {"Checkpoint", pEditor->m_TeleCheckpointNumber, PROPTYPE_INT, 1, 255}, {"View", pEditor->m_ViewTeleNumber, PROPTYPE_INT, 1, 255}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_vColors); if(Prop == PROP_TELE) { pEditor->m_TeleNumber = (NewVal - 1 + 255) % 255 + 1; pEditor->AdjustBrushSpecialTiles(false); } else if(Prop == PROP_TELE_CP) { pEditor->m_TeleCheckpointNumber = (NewVal - 1 + 255) % 255 + 1; pEditor->AdjustBrushSpecialTiles(false); } else if(Prop == PROP_TELE_VIEW) pEditor->m_ViewTeleNumber = (NewVal - 1 + 255) % 255 + 1; if(s_PreviousTeleNumber == 1 || s_PreviousTeleNumber != pEditor->m_TeleNumber) s_vColors[PROP_TELE] = pEditor->Map()->m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleNumber, false) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); if(s_PreviousCheckpointNumber == 1 || s_PreviousCheckpointNumber != pEditor->m_TeleCheckpointNumber) s_vColors[PROP_TELE_CP] = pEditor->Map()->m_pTeleLayer->ContainsElementWithId(pEditor->m_TeleCheckpointNumber, true) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); if(s_PreviousViewTeleNumber != pEditor->m_ViewTeleNumber) s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } s_PreviousTeleNumber = pEditor->m_TeleNumber; s_PreviousCheckpointNumber = pEditor->m_TeleCheckpointNumber; s_PreviousViewTeleNumber = pEditor->m_ViewTeleNumber; return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupSpeedup(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); enum { PROP_FORCE = 0, PROP_MAXSPEED, PROP_ANGLE, NUM_PROPS }; CProperty aProps[] = { {"Force", pEditor->m_SpeedupForce, PROPTYPE_INT, 1, 255}, {"Max Speed", pEditor->m_SpeedupMaxSpeed, PROPTYPE_INT, 0, 255}, {"Angle", pEditor->m_SpeedupAngle, PROPTYPE_ANGLE_SCROLL, 0, 359}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop == PROP_FORCE) { pEditor->m_SpeedupForce = std::clamp(NewVal, 1, 255); } else if(Prop == PROP_MAXSPEED) { pEditor->m_SpeedupMaxSpeed = std::clamp(NewVal, 0, 255); } else if(Prop == PROP_ANGLE) { pEditor->m_SpeedupAngle = std::clamp(NewVal, 0, 359); pEditor->AdjustBrushSpecialTiles(false); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); if(!pEditor->Map()->m_pSwitchLayer) return CUi::POPUP_CLOSE_CURRENT; CUIRect NumberPicker, FindEmptySlot, ViewEmptySlot; View.VSplitRight(15.0f, &NumberPicker, &FindEmptySlot); NumberPicker.VSplitRight(2.0f, &NumberPicker, nullptr); FindEmptySlot.HSplitTop(13.0f, &FindEmptySlot, &ViewEmptySlot); ViewEmptySlot.HSplitTop(13.0f, nullptr, &ViewEmptySlot); FindEmptySlot.HMargin(1.0f, &FindEmptySlot); ViewEmptySlot.HMargin(1.0f, &ViewEmptySlot); auto ViewSwitch = [pEditor]() -> bool { if(!pEditor->m_ViewSwitch) return false; ivec2 SwitchPos; pEditor->Map()->m_pSwitchLayer->GetPos(pEditor->m_ViewSwitch, -1, SwitchPos); if(SwitchPos != ivec2(-1, -1)) { pEditor->MapView()->SetWorldOffset({32.0f * SwitchPos.x + 0.5f, 32.0f * SwitchPos.y + 0.5f}); return true; } return false; }; static std::vector<ColorRGBA> s_vColors = { ColorRGBA(1, 1, 1, 0.5f), ColorRGBA(1, 1, 1, 0.5f), ColorRGBA(1, 1, 1, 0.5f), }; enum { PROP_SWITCH_NUMBER = 0, PROP_SWITCH_DELAY, PROP_SWITCH_VIEW, NUM_PROPS, }; // find empty number button { static int s_EmptySlotPid = 0; if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, BUTTONFLAG_LEFT, "[Ctrl+F] Find empty slot.") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { int Number = pEditor->Map()->m_pSwitchLayer->FindNextFreeNumber(); if(Number != -1) pEditor->m_SwitchNumber = Number; } static int s_NextViewPid = 0; if(pEditor->DoButton_Editor(&s_NextViewPid, "N", 0, &ViewEmptySlot, BUTTONFLAG_LEFT, "[N] Show next switcher with this number.") || (Active && pEditor->Input()->KeyPress(KEY_N))) { s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } } // number picker static int s_PreviousNumber = -1; static int s_PreviousView = -1; { CProperty aProps[] = { {"Number", pEditor->m_SwitchNumber, PROPTYPE_INT, 0, 255}, {"Delay", pEditor->m_SwitchDelay, PROPTYPE_INT, 0, 255}, {"View", pEditor->m_ViewSwitch, PROPTYPE_INT, 0, 255}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_vColors); if(Prop == PROP_SWITCH_NUMBER) { pEditor->m_SwitchNumber = (NewVal + 256) % 256; } else if(Prop == PROP_SWITCH_DELAY) { pEditor->m_SwitchDelay = (NewVal + 256) % 256; } else if(Prop == PROP_SWITCH_VIEW) { pEditor->m_ViewSwitch = (NewVal + 256) % 256; } if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_SwitchNumber) s_vColors[PROP_SWITCH_NUMBER] = pEditor->Map()->m_pSwitchLayer->ContainsElementWithId(pEditor->m_SwitchNumber) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); if(s_PreviousView != pEditor->m_ViewSwitch) s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } s_PreviousNumber = pEditor->m_SwitchNumber; s_PreviousView = pEditor->m_ViewSwitch; return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupTune(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); if(!pEditor->Map()->m_pTuneLayer) return CUi::POPUP_CLOSE_CURRENT; CUIRect NumberPicker, FindEmptySlot, ViewEmptySlot; View.VSplitRight(15.0f, &NumberPicker, &FindEmptySlot); NumberPicker.VSplitRight(2.0f, &NumberPicker, nullptr); FindEmptySlot.HSplitTop(13.0f, &FindEmptySlot, &ViewEmptySlot); FindEmptySlot.HMargin(1.0f, &FindEmptySlot); ViewEmptySlot.HMargin(1.0f, &ViewEmptySlot); auto ViewTune = [pEditor]() -> bool { if(!pEditor->m_ViewTuning) return false; ivec2 TunePos; pEditor->Map()->m_pTuneLayer->GetPos(pEditor->m_ViewTuning, -1, TunePos); if(TunePos != ivec2(-1, -1)) { pEditor->MapView()->SetWorldOffset({32.0f * TunePos.x + 0.5f, 32.0f * TunePos.y + 0.5f}); return true; } return false; }; static std::vector<ColorRGBA> s_vColors = { ColorRGBA(1, 1, 1, 0.5f), ColorRGBA(1, 0.5f, 0.5f, 0.5f), }; enum { PROP_TUNE_NUMBER = 0, PROP_TUNE_VIEW, NUM_PROPS, }; // find empty number button { static int s_EmptySlotPid = 0; if(pEditor->DoButton_Editor(&s_EmptySlotPid, "F", 0, &FindEmptySlot, BUTTONFLAG_LEFT, "[Ctrl+F] Find unused zone.") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(KEY_F))) { int Number = pEditor->Map()->m_pTuneLayer->FindNextFreeNumber(); if(Number != -1) pEditor->m_TuningNumber = Number; } static int s_NextViewPid = 0; if(pEditor->DoButton_Editor(&s_NextViewPid, "N", 0, &ViewEmptySlot, BUTTONFLAG_LEFT, "[N] Show next tune tile with this number.") || (Active && pEditor->Input()->KeyPress(KEY_N))) { s_vColors[PROP_TUNE_VIEW] = ViewTune() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } } // number picker static int s_PreviousNumber = -1; static int s_PreviousView = -1; { CProperty aProps[] = { {"Zone", pEditor->m_TuningNumber, PROPTYPE_INT, 1, 255}, {"View", pEditor->m_ViewTuning, PROPTYPE_INT, 1, 255}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; int Prop = pEditor->DoProperties(&NumberPicker, aProps, s_aIds, &NewVal, s_vColors); if(Prop == PROP_TUNE_NUMBER) { pEditor->m_TuningNumber = (NewVal - 1 + 255) % 255 + 1; } else if(Prop == PROP_TUNE_VIEW) { pEditor->m_ViewTuning = (NewVal - 1 + 255) % 255 + 1; } if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_TuningNumber) s_vColors[PROP_TUNE_NUMBER] = pEditor->Map()->m_pTuneLayer->ContainsElementWithId(pEditor->m_TuningNumber) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f); if(s_PreviousView != pEditor->m_ViewTuning) s_vColors[PROP_TUNE_VIEW] = ViewTune() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f); } s_PreviousNumber = pEditor->m_TuningNumber; s_PreviousView = pEditor->m_ViewTuning; return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupGoto(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); enum { PROP_COORD_X = 0, PROP_COORD_Y, NUM_PROPS, }; static ivec2 s_GotoPos(0, 0); CProperty aProps[] = { {"X", s_GotoPos.x, PROPTYPE_INT, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()}, {"Y", s_GotoPos.y, PROPTYPE_INT, std::numeric_limits<int>::min(), std::numeric_limits<int>::max()}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop == PROP_COORD_X) { s_GotoPos.x = NewVal; } else if(Prop == PROP_COORD_Y) { s_GotoPos.y = NewVal; } CUIRect Button; View.HSplitBottom(12.0f, &View, &Button); static int s_Button; if(pEditor->DoButton_Editor(&s_Button, "Go", 0, &Button, BUTTONFLAG_LEFT, nullptr)) { pEditor->MapView()->SetWorldOffset({32.0f * s_GotoPos.x + 0.5f, 32.0f * s_GotoPos.y + 0.5f}); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEntities(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); for(size_t i = 0; i < pEditor->m_vSelectEntitiesFiles.size(); i++) { CUIRect Button; View.HSplitTop(14.0f, &Button, &View); const char *pName = pEditor->m_vSelectEntitiesFiles[i].c_str(); if(pEditor->DoButton_MenuItem(pName, pName, pEditor->m_vSelectEntitiesFiles[i] == pEditor->m_SelectEntitiesImage, &Button)) { if(pEditor->m_vSelectEntitiesFiles[i] != pEditor->m_SelectEntitiesImage) { if(i == pEditor->m_vSelectEntitiesFiles.size() - 1) { pEditor->m_FileBrowser.ShowFileDialog(IStorage::TYPE_ALL, CFileBrowser::EFileType::IMAGE, "Load custom entities", "Load", "assets/entities", "", CallbackCustomEntities, pEditor); return CUi::POPUP_CLOSE_CURRENT; } pEditor->m_SelectEntitiesImage = pEditor->m_vSelectEntitiesFiles[i]; pEditor->m_AllowPlaceUnusedTiles = pEditor->m_SelectEntitiesImage == "DDNet" ? EUnusedEntities::NOT_ALLOWED : EUnusedEntities::ALLOWED_IMPLICIT; pEditor->m_PreventUnusedTilesWasWarned = false; pEditor->Graphics()->UnloadTexture(&pEditor->m_EntitiesTexture); char aBuf[IO_MAX_PATH_LENGTH]; str_format(aBuf, sizeof(aBuf), "editor/entities/%s.png", pName); pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(aBuf, IStorage::TYPE_ALL, pEditor->GetTextureUsageFlag()); return CUi::POPUP_CLOSE_CURRENT; } } } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupProofMode(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); CUIRect Button; View.HSplitTop(12.0f, &Button, &View); static int s_ButtonIngame; if(pEditor->DoButton_MenuItem(&s_ButtonIngame, "Ingame", pEditor->MapView()->ProofMode()->IsModeIngame(), &Button, BUTTONFLAG_LEFT, "These borders represent what a player maximum can see.")) { pEditor->MapView()->ProofMode()->SetModeIngame(); return CUi::POPUP_CLOSE_CURRENT; } View.HSplitTop(2.0f, nullptr, &View); View.HSplitTop(12.0f, &Button, &View); static int s_ButtonMenu; if(pEditor->DoButton_MenuItem(&s_ButtonMenu, "Menu", pEditor->MapView()->ProofMode()->IsModeMenu(), &Button, BUTTONFLAG_LEFT, "These borders represent what will be shown in the menu.")) { pEditor->MapView()->ProofMode()->SetModeMenu(); return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupAnimateSettings(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); static constexpr float MIN_ANIM_SPEED = 0.001f; static constexpr float MAX_ANIM_SPEED = 1000000.0f; CUIRect Row, Label, ButtonDecrease, EditBox, ButtonIncrease, ButtonReset; View.HSplitTop(13.0f, &Row, &View); Row.VSplitMid(&Label, &Row); Row.HMargin(1.0f, &Row); Row.VSplitLeft(10.0f, &ButtonDecrease, &Row); Row.VSplitRight(10.0f, &EditBox, &ButtonIncrease); View.HSplitBottom(12.0f, &View, &ButtonReset); pEditor->Ui()->DoLabel(&Label, "Speed", 10.0f, TEXTALIGN_ML); static char s_DecreaseButton; if(pEditor->DoButton_FontIcon(&s_DecreaseButton, FONT_ICON_MINUS, 0, &ButtonDecrease, BUTTONFLAG_LEFT, "Decrease animation speed.", IGraphics::CORNER_L, 7.0f)) { pEditor->m_AnimateSpeed -= pEditor->m_AnimateSpeed <= 1.0f ? 0.1f : 0.5f; pEditor->m_AnimateSpeed = maximum(pEditor->m_AnimateSpeed, MIN_ANIM_SPEED); pEditor->m_AnimateUpdatePopup = true; } static char s_IncreaseButton; if(pEditor->DoButton_FontIcon(&s_IncreaseButton, FONT_ICON_PLUS, 0, &ButtonIncrease, BUTTONFLAG_LEFT, "Increase animation speed.", IGraphics::CORNER_R, 7.0f)) { if(pEditor->m_AnimateSpeed < 0.1f) pEditor->m_AnimateSpeed = 0.1f; else pEditor->m_AnimateSpeed += pEditor->m_AnimateSpeed < 1.0f ? 0.1f : 0.5f; pEditor->m_AnimateSpeed = minimum(pEditor->m_AnimateSpeed, MAX_ANIM_SPEED); pEditor->m_AnimateUpdatePopup = true; } static char s_DefaultButton; if(pEditor->DoButton_Ex(&s_DefaultButton, "Default", 0, &ButtonReset, BUTTONFLAG_LEFT, "Reset to normal animation speed.", IGraphics::CORNER_ALL)) { pEditor->m_AnimateSpeed = 1.0f; pEditor->m_AnimateUpdatePopup = true; } static CLineInputNumber s_SpeedInput; if(pEditor->m_AnimateUpdatePopup) { s_SpeedInput.SetFloat(pEditor->m_AnimateSpeed); pEditor->m_AnimateUpdatePopup = false; } if(pEditor->DoEditBox(&s_SpeedInput, &EditBox, 10.0f, IGraphics::CORNER_NONE, "The animation speed.")) { pEditor->m_AnimateSpeed = std::clamp(s_SpeedInput.GetFloat(), MIN_ANIM_SPEED, MAX_ANIM_SPEED); } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupEnvelopeCurvetype(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->Map()->m_vpEnvelopes.size()) { return CUi::POPUP_CLOSE_CURRENT; } std::shared_ptr<CEnvelope> pEnvelope = pEditor->Map()->m_vpEnvelopes[pEditor->m_SelectedEnvelope]; if(pEditor->m_PopupEnvelopeSelectedPoint < 0 || pEditor->m_PopupEnvelopeSelectedPoint >= (int)pEnvelope->m_vPoints.size()) { return CUi::POPUP_CLOSE_CURRENT; } CEnvPoint_runtime &SelectedPoint = pEnvelope->m_vPoints[pEditor->m_PopupEnvelopeSelectedPoint]; static const char *const TYPE_NAMES[NUM_CURVETYPES] = {"Step", "Linear", "Slow", "Fast", "Smooth", "Bezier"}; static char s_aButtonIds[NUM_CURVETYPES] = {0}; for(int Type = 0; Type < NUM_CURVETYPES; Type++) { CUIRect Button; View.HSplitTop(14.0f, &Button, &View); if(pEditor->DoButton_MenuItem(&s_aButtonIds[Type], TYPE_NAMES[Type], Type == SelectedPoint.m_Curvetype, &Button)) { const int PrevCurve = SelectedPoint.m_Curvetype; if(PrevCurve != Type) { SelectedPoint.m_Curvetype = Type; pEditor->Map()->m_EnvelopeEditorHistory.RecordAction(std::make_shared<CEditorActionEnvelopeEditPoint>(pEditor->Map(), pEditor->m_SelectedEnvelope, pEditor->m_PopupEnvelopeSelectedPoint, 0, CEditorActionEnvelopeEditPoint::EEditType::CURVE_TYPE, PrevCurve, SelectedPoint.m_Curvetype)); pEditor->Map()->OnModify(); return CUi::POPUP_CLOSE_CURRENT; } } } return CUi::POPUP_KEEP_OPEN; } CUi::EPopupMenuFunctionResult CEditor::PopupQuadArt(void *pContext, CUIRect View, bool Active) { CEditor *pEditor = static_cast<CEditor *>(pContext); enum { PROP_IMAGE_PIXELSIZE = 0, PROP_QUAD_PIXELSIZE, PROP_OPTIMIZE, PROP_CENTRALIZE, NUM_PROPS, }; CProperty aProps[] = { {"Image pixelsize", pEditor->m_QuadArtParameters.m_ImagePixelSize, PROPTYPE_INT, 1, 1024}, {"Quad pixelsize", pEditor->m_QuadArtParameters.m_QuadPixelSize, PROPTYPE_INT, 1, 1024}, {"Optimize", pEditor->m_QuadArtParameters.m_Optimize, PROPTYPE_BOOL, false, true}, {"Centralize", pEditor->m_QuadArtParameters.m_Centralize, PROPTYPE_BOOL, false, true}, {nullptr}, }; static int s_aIds[NUM_PROPS] = {0}; int NewVal = 0; // Title CUIRect Label; View.HSplitTop(20.0f, &Label, &View); pEditor->Ui()->DoLabel(&Label, "Configure Quadart", 20.0f, TEXTALIGN_MC); View.HSplitTop(10.0f, nullptr, &View); // Properties int Prop = pEditor->DoProperties(&View, aProps, s_aIds, &NewVal); if(Prop == PROP_IMAGE_PIXELSIZE) { pEditor->m_QuadArtParameters.m_ImagePixelSize = NewVal; } else if(Prop == PROP_QUAD_PIXELSIZE) { pEditor->m_QuadArtParameters.m_QuadPixelSize = NewVal; } else if(Prop == PROP_OPTIMIZE) { pEditor->m_QuadArtParameters.m_Optimize = (bool)NewVal; } else if(Prop == PROP_CENTRALIZE) { pEditor->m_QuadArtParameters.m_Centralize = (bool)NewVal; } // Buttons CUIRect BottomBar, Left, Right; View.HSplitBottom(20.f, &View, &BottomBar); BottomBar.VSplitLeft(110.f, &Left, &BottomBar); static int s_Cancel; if(pEditor->DoButton_Editor(&s_Cancel, "Cancel", 0, &Left, BUTTONFLAG_LEFT, nullptr)) { pEditor->m_QuadArtImageInfo.Free(); return CUi::POPUP_CLOSE_CURRENT; } BottomBar.VSplitRight(110.f, &BottomBar, &Right); static int s_Confirm; constexpr int MaximumQuadThreshold = 100'000; if(pEditor->DoButton_Editor(&s_Confirm, "Confirm", 0, &Right, BUTTONFLAG_LEFT, nullptr)) { size_t MaximumQuadNumber = (pEditor->m_QuadArtImageInfo.m_Width / pEditor->m_QuadArtParameters.m_ImagePixelSize) * (pEditor->m_QuadArtImageInfo.m_Height / pEditor->m_QuadArtParameters.m_ImagePixelSize); if(MaximumQuadNumber > MaximumQuadThreshold) { pEditor->m_PopupEventType = CEditor::POPEVENT_QUADART_BIG_IMAGE; pEditor->m_PopupEventActivated = true; } else { pEditor->AddQuadArt(); } return CUi::POPUP_CLOSE_CURRENT; } return CUi::POPUP_KEEP_OPEN; }