1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <base/color.h>
5
6#include <engine/console.h>
7#include <engine/graphics.h>
8#include <engine/input.h>
9#include <engine/keys.h>
10#include <engine/shared/config.h>
11#include <engine/storage.h>
12#include <engine/textrender.h>
13#include <limits>
14
15#include <game/client/ui_scrollregion.h>
16#include <game/editor/mapitems/image.h>
17#include <game/editor/mapitems/sound.h>
18
19#include "editor.h"
20#include "editor_actions.h"
21
22using namespace FontIcons;
23
24CUi::EPopupMenuFunctionResult CEditor::PopupMenuFile(void *pContext, CUIRect View, bool Active)
25{
26 CEditor *pEditor = static_cast<CEditor *>(pContext);
27
28 static int s_NewMapButton = 0;
29 static int s_SaveButton = 0;
30 static int s_SaveAsButton = 0;
31 static int s_SaveCopyButton = 0;
32 static int s_OpenButton = 0;
33 static int s_OpenCurrentMapButton = 0;
34 static int s_AppendButton = 0;
35 static int s_MapInfoButton = 0;
36 static int s_ExitButton = 0;
37
38 CUIRect Slot;
39 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
40 if(pEditor->DoButton_MenuItem(pId: &s_NewMapButton, pText: "New", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Creates a new map (ctrl+n)"))
41 {
42 if(pEditor->HasUnsavedData())
43 {
44 pEditor->m_PopupEventType = POPEVENT_NEW;
45 pEditor->m_PopupEventActivated = true;
46 }
47 else
48 {
49 pEditor->Reset();
50 pEditor->m_aFileName[0] = 0;
51 }
52 return CUi::POPUP_CLOSE_CURRENT;
53 }
54
55 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
56 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
57 if(pEditor->DoButton_MenuItem(pId: &s_OpenButton, pText: "Load", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Opens a map for editing (ctrl+l)"))
58 {
59 if(pEditor->HasUnsavedData())
60 {
61 pEditor->m_PopupEventType = POPEVENT_LOAD;
62 pEditor->m_PopupEventActivated = true;
63 }
64 else
65 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_MAP, pTitle: "Load map", pButtonText: "Load", pBasepath: "maps", FilenameAsDefault: false, pfnFunc: CEditor::CallbackOpenMap, pUser: pEditor);
66 return CUi::POPUP_CLOSE_CURRENT;
67 }
68
69 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
70 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
71 if(pEditor->DoButton_MenuItem(pId: &s_OpenCurrentMapButton, pText: "Load Current Map", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Opens the current in game map for editing (ctrl+alt+l)"))
72 {
73 if(pEditor->HasUnsavedData())
74 {
75 pEditor->m_PopupEventType = POPEVENT_LOADCURRENT;
76 pEditor->m_PopupEventActivated = true;
77 }
78 else
79 {
80 pEditor->LoadCurrentMap();
81 }
82 return CUi::POPUP_CLOSE_CURRENT;
83 }
84
85 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
86 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
87 if(pEditor->DoButton_MenuItem(pId: &s_AppendButton, pText: "Append", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Opens a map and adds everything from that map to the current one (ctrl+a)"))
88 {
89 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_MAP, pTitle: "Append map", pButtonText: "Append", pBasepath: "maps", FilenameAsDefault: false, pfnFunc: CEditor::CallbackAppendMap, pUser: pEditor);
90 return CUi::POPUP_CLOSE_CURRENT;
91 }
92
93 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
94 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
95 if(pEditor->DoButton_MenuItem(pId: &s_SaveButton, pText: "Save", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Saves the current map (ctrl+s)"))
96 {
97 if(pEditor->m_aFileName[0] && pEditor->m_ValidSaveFilename)
98 {
99 str_copy(dst&: pEditor->m_aFileSaveName, src: pEditor->m_aFileName);
100 pEditor->m_PopupEventType = POPEVENT_SAVE;
101 pEditor->m_PopupEventActivated = true;
102 }
103 else
104 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_MAP, pTitle: "Save map", pButtonText: "Save", pBasepath: "maps", FilenameAsDefault: false, pfnFunc: CEditor::CallbackSaveMap, pUser: pEditor);
105 return CUi::POPUP_CLOSE_CURRENT;
106 }
107
108 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
109 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
110 if(pEditor->DoButton_MenuItem(pId: &s_SaveAsButton, pText: "Save As", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Saves the current map under a new name (ctrl+shift+s)"))
111 {
112 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_MAP, pTitle: "Save map", pButtonText: "Save", pBasepath: "maps", FilenameAsDefault: true, pfnFunc: CEditor::CallbackSaveMap, pUser: pEditor);
113 return CUi::POPUP_CLOSE_CURRENT;
114 }
115
116 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
117 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
118 if(pEditor->DoButton_MenuItem(pId: &s_SaveCopyButton, pText: "Save Copy", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Saves a copy of the current map under a new name (ctrl+shift+alt+s)"))
119 {
120 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_MAP, pTitle: "Save map", pButtonText: "Save", pBasepath: "maps", FilenameAsDefault: true, pfnFunc: CEditor::CallbackSaveCopyMap, pUser: pEditor);
121 return CUi::POPUP_CLOSE_CURRENT;
122 }
123
124 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
125 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
126 if(pEditor->DoButton_MenuItem(pId: &s_MapInfoButton, pText: "Map details", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Adjust the map details of the current map"))
127 {
128 const CUIRect *pScreen = pEditor->Ui()->Screen();
129 pEditor->m_Map.m_MapInfoTmp.Copy(Source: pEditor->m_Map.m_MapInfo);
130 static SPopupMenuId s_PopupMapInfoId;
131 constexpr float PopupWidth = 400.0f;
132 constexpr float PopupHeight = 170.0f;
133 pEditor->Ui()->DoPopupMenu(pId: &s_PopupMapInfoId, X: pScreen->w / 2.0f - PopupWidth / 2.0f, Y: pScreen->h / 2.0f - PopupHeight / 2.0f, Width: PopupWidth, Height: PopupHeight, pContext: pEditor, pfnFunc: PopupMapInfo);
134 pEditor->Ui()->SetActiveItem(nullptr);
135 return CUi::POPUP_CLOSE_CURRENT;
136 }
137
138 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
139 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
140 if(pEditor->DoButton_MenuItem(pId: &s_ExitButton, pText: "Exit", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Exits from the editor"))
141 {
142 if(pEditor->HasUnsavedData())
143 {
144 pEditor->m_PopupEventType = POPEVENT_EXIT;
145 pEditor->m_PopupEventActivated = true;
146 }
147 else
148 {
149 pEditor->OnClose();
150 g_Config.m_ClEditor = 0;
151 }
152 return CUi::POPUP_CLOSE_CURRENT;
153 }
154
155 return CUi::POPUP_KEEP_OPEN;
156}
157
158CUi::EPopupMenuFunctionResult CEditor::PopupMenuTools(void *pContext, CUIRect View, bool Active)
159{
160 CEditor *pEditor = static_cast<CEditor *>(pContext);
161
162 CUIRect Slot;
163 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
164 static int s_RemoveUnusedEnvelopesButton = 0;
165 static CUi::SConfirmPopupContext s_ConfirmPopupContext;
166 if(pEditor->DoButton_MenuItem(pId: &s_RemoveUnusedEnvelopesButton, pText: "Remove unused envelopes", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes all unused envelopes from the map"))
167 {
168 s_ConfirmPopupContext.Reset();
169 s_ConfirmPopupContext.YesNoButtons();
170 str_copy(dst&: s_ConfirmPopupContext.m_aMessage, src: "Are you sure that you want to remove all unused envelopes from this map?");
171 pEditor->Ui()->ShowPopupConfirm(X: Slot.x + Slot.w, Y: Slot.y, pContext: &s_ConfirmPopupContext);
172 }
173 if(s_ConfirmPopupContext.m_Result == CUi::SConfirmPopupContext::CONFIRMED)
174 pEditor->RemoveUnusedEnvelopes();
175 if(s_ConfirmPopupContext.m_Result != CUi::SConfirmPopupContext::UNSET)
176 {
177 s_ConfirmPopupContext.Reset();
178 return CUi::POPUP_CLOSE_CURRENT;
179 }
180
181 static int s_BorderButton = 0;
182 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
183 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
184 if(pEditor->DoButton_MenuItem(pId: &s_BorderButton, pText: "Place Border", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Place tiles in a 2-tile wide border at the edges of the layer"))
185 {
186 std::shared_ptr<CLayerTiles> pT = std::static_pointer_cast<CLayerTiles>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
187 if(pT && !pT->m_Tele && !pT->m_Speedup && !pT->m_Switch && !pT->m_Front && !pT->m_Tune)
188 {
189 pEditor->m_PopupEventType = POPEVENT_PLACE_BORDER_TILES;
190 pEditor->m_PopupEventActivated = true;
191 }
192 else
193 {
194 static CUi::SMessagePopupContext s_MessagePopupContext;
195 s_MessagePopupContext.DefaultColor(pTextRender: pEditor->m_pTextRender);
196 str_copy(dst&: s_MessagePopupContext.m_aMessage, src: "No tile layer selected");
197 pEditor->Ui()->ShowPopupMessage(X: Slot.x, Y: Slot.y + Slot.h, pContext: &s_MessagePopupContext);
198 }
199 }
200
201 static int s_GotoButton = 0;
202 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
203 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
204 if(pEditor->DoButton_MenuItem(pId: &s_GotoButton, pText: "Goto XY", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Go to a specified coordinate point on the map"))
205 {
206 static SPopupMenuId s_PopupGotoId;
207 pEditor->Ui()->DoPopupMenu(pId: &s_PopupGotoId, X: Slot.x, Y: Slot.y + Slot.h, Width: 120, Height: 52, pContext: pEditor, pfnFunc: PopupGoto);
208 }
209
210 static int s_TileartButton = 0;
211 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
212 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
213 if(pEditor->DoButton_MenuItem(pId: &s_TileartButton, pText: "Add tileart", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Generate tileart from image"))
214 {
215 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_IMG, pTitle: "Add tileart", pButtonText: "Open", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: CallbackAddTileart, pUser: pEditor);
216 return CUi::POPUP_CLOSE_CURRENT;
217 }
218
219 return CUi::POPUP_KEEP_OPEN;
220}
221
222static int EntitiesListdirCallback(const char *pName, int IsDir, int StorageType, void *pUser)
223{
224 CEditor *pEditor = (CEditor *)pUser;
225 if(!IsDir && str_endswith(str: pName, suffix: ".png"))
226 {
227 std::string Name = pName;
228 pEditor->m_vSelectEntitiesFiles.push_back(x: Name.substr(pos: 0, n: Name.length() - 4));
229 }
230
231 return 0;
232}
233
234CUi::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect View, bool Active)
235{
236 CEditor *pEditor = static_cast<CEditor *>(pContext);
237
238 CUIRect Slot;
239 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
240 static int s_EntitiesButtonId = 0;
241 char aButtonText[64];
242 str_format(buffer: aButtonText, buffer_size: sizeof(aButtonText), format: "Entities: %s", pEditor->m_SelectEntitiesImage.c_str());
243 if(pEditor->DoButton_MenuItem(pId: &s_EntitiesButtonId, pText: aButtonText, Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Choose game layer entities image for different gametypes"))
244 {
245 pEditor->m_vSelectEntitiesFiles.clear();
246 pEditor->Storage()->ListDirectory(Type: IStorage::TYPE_ALL, pPath: "editor/entities", pfnCallback: EntitiesListdirCallback, pUser: pEditor);
247 std::sort(first: pEditor->m_vSelectEntitiesFiles.begin(), last: pEditor->m_vSelectEntitiesFiles.end());
248
249 static SPopupMenuId s_PopupEntitiesId;
250 pEditor->Ui()->DoPopupMenu(pId: &s_PopupEntitiesId, X: Slot.x, Y: Slot.y + Slot.h, Width: 250, Height: pEditor->m_vSelectEntitiesFiles.size() * 14.0f + 10.0f, pContext: pEditor, pfnFunc: PopupEntities);
251 }
252
253 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
254 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
255 {
256 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
257
258 CUIRect Label, Selector;
259 Slot.VSplitMid(pLeft: &Label, pRight: &Selector);
260 CUIRect No, Yes;
261 Selector.VSplitMid(pLeft: &No, pRight: &Yes);
262
263 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Brush coloring", Size: 10.0f, Align: TEXTALIGN_ML);
264 static int s_ButtonNo = 0;
265 static int s_ButtonYes = 0;
266 if(pEditor->DoButton_Ex(pId: &s_ButtonNo, pText: "No", Checked: !pEditor->m_BrushColorEnabled, pRect: &No, Flags: 0, pToolTip: "Disable brush coloring", Corners: IGraphics::CORNER_L))
267 {
268 pEditor->m_BrushColorEnabled = false;
269 }
270 if(pEditor->DoButton_Ex(pId: &s_ButtonYes, pText: "Yes", Checked: pEditor->m_BrushColorEnabled, pRect: &Yes, Flags: 0, pToolTip: "Enable brush coloring", Corners: IGraphics::CORNER_R))
271 {
272 pEditor->m_BrushColorEnabled = true;
273 }
274 }
275
276 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
277 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
278 {
279 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
280
281 CUIRect Label, Selector;
282 Slot.VSplitMid(pLeft: &Label, pRight: &Selector);
283 CUIRect No, Yes;
284 Selector.VSplitMid(pLeft: &No, pRight: &Yes);
285
286 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Allow unused", Size: 10.0f, Align: TEXTALIGN_ML);
287 if(pEditor->m_AllowPlaceUnusedTiles != -1)
288 {
289 static int s_ButtonNo = 0;
290 static int s_ButtonYes = 0;
291 if(pEditor->DoButton_Ex(pId: &s_ButtonNo, pText: "No", Checked: !pEditor->m_AllowPlaceUnusedTiles, pRect: &No, Flags: 0, pToolTip: "[ctrl+u] Disallow placing unused tiles", Corners: IGraphics::CORNER_L))
292 {
293 pEditor->m_AllowPlaceUnusedTiles = false;
294 }
295 if(pEditor->DoButton_Ex(pId: &s_ButtonYes, pText: "Yes", Checked: pEditor->m_AllowPlaceUnusedTiles, pRect: &Yes, Flags: 0, pToolTip: "[ctrl+u] Allow placing unused tiles", Corners: IGraphics::CORNER_R))
296 {
297 pEditor->m_AllowPlaceUnusedTiles = true;
298 }
299 }
300 }
301
302 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
303 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
304 {
305 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
306
307 CUIRect Label, Selector;
308 Slot.VSplitMid(pLeft: &Label, pRight: &Selector);
309 CUIRect Off, Dec, Hex;
310 Selector.VSplitLeft(Cut: Selector.w / 3.0f, pLeft: &Off, pRight: &Selector);
311 Selector.VSplitMid(pLeft: &Dec, pRight: &Hex);
312
313 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Show Info", Size: 10.0f, Align: TEXTALIGN_ML);
314 static int s_ButtonOff = 0;
315 static int s_ButtonDec = 0;
316 static int s_ButtonHex = 0;
317 if(pEditor->DoButton_Ex(pId: &s_ButtonOff, pText: "Off", Checked: pEditor->m_ShowTileInfo == SHOW_TILE_OFF, pRect: &Off, Flags: 0, pToolTip: "Do not show tile information", Corners: IGraphics::CORNER_L))
318 {
319 pEditor->m_ShowTileInfo = SHOW_TILE_OFF;
320 pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
321 }
322 if(pEditor->DoButton_Ex(pId: &s_ButtonDec, pText: "Dec", Checked: pEditor->m_ShowTileInfo == SHOW_TILE_DECIMAL, pRect: &Dec, Flags: 0, pToolTip: "[ctrl+i] Show tile information", Corners: IGraphics::CORNER_NONE))
323 {
324 pEditor->m_ShowTileInfo = SHOW_TILE_DECIMAL;
325 pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
326 }
327 if(pEditor->DoButton_Ex(pId: &s_ButtonHex, pText: "Hex", Checked: pEditor->m_ShowTileInfo == SHOW_TILE_HEXADECIMAL, pRect: &Hex, Flags: 0, pToolTip: "[ctrl+shift+i] Show tile information in hexadecimal", Corners: IGraphics::CORNER_R))
328 {
329 pEditor->m_ShowTileInfo = SHOW_TILE_HEXADECIMAL;
330 pEditor->m_ShowEnvelopePreview = SHOWENV_NONE;
331 }
332 }
333
334 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
335 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
336 {
337 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
338
339 CUIRect Label, Selector;
340 Slot.VSplitMid(pLeft: &Label, pRight: &Selector);
341 CUIRect No, Yes;
342 Selector.VSplitMid(pLeft: &No, pRight: &Yes);
343
344 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Align quads", Size: 10.0f, Align: TEXTALIGN_ML);
345
346 static int s_ButtonNo = 0;
347 static int s_ButtonYes = 0;
348 if(pEditor->DoButton_Ex(pId: &s_ButtonNo, pText: "No", Checked: !g_Config.m_EdAlignQuads, pRect: &No, Flags: 0, pToolTip: "Do not perform quad alignment to other quads/points when moving quads", Corners: IGraphics::CORNER_L))
349 {
350 g_Config.m_EdAlignQuads = false;
351 }
352 if(pEditor->DoButton_Ex(pId: &s_ButtonYes, pText: "Yes", Checked: g_Config.m_EdAlignQuads, pRect: &Yes, Flags: 0, pToolTip: "Allow quad alignment to other quads/points when moving quads", Corners: IGraphics::CORNER_R))
353 {
354 g_Config.m_EdAlignQuads = true;
355 }
356 }
357
358 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
359 View.HSplitTop(Cut: 12.0f, pTop: &Slot, pBottom: &View);
360 {
361 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
362
363 CUIRect Label, Selector;
364 Slot.VSplitMid(pLeft: &Label, pRight: &Selector);
365 CUIRect No, Yes;
366 Selector.VSplitMid(pLeft: &No, pRight: &Yes);
367
368 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Show quads bounds", Size: 10.0f, Align: TEXTALIGN_ML);
369
370 static int s_ButtonNo = 0;
371 static int s_ButtonYes = 0;
372 if(pEditor->DoButton_Ex(pId: &s_ButtonNo, pText: "No", Checked: !g_Config.m_EdShowQuadsRect, pRect: &No, Flags: 0, pToolTip: "Do not show quad bounds when moving quads", Corners: IGraphics::CORNER_L))
373 {
374 g_Config.m_EdShowQuadsRect = false;
375 }
376 if(pEditor->DoButton_Ex(pId: &s_ButtonYes, pText: "Yes", Checked: g_Config.m_EdShowQuadsRect, pRect: &Yes, Flags: 0, pToolTip: "Show quad bounds when moving quads", Corners: IGraphics::CORNER_R))
377 {
378 g_Config.m_EdShowQuadsRect = true;
379 }
380 }
381
382 return CUi::POPUP_KEEP_OPEN;
383}
384
385CUi::EPopupMenuFunctionResult CEditor::PopupGroup(void *pContext, CUIRect View, bool Active)
386{
387 CEditor *pEditor = static_cast<CEditor *>(pContext);
388
389 // remove group button
390 CUIRect Button;
391 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
392 static int s_DeleteButton = 0;
393
394 // don't allow deletion of game group
395 if(pEditor->m_Map.m_pGameGroup != pEditor->GetSelectedGroup())
396 {
397 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Delete group", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Delete group"))
398 {
399 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionGroup>(args&: pEditor, args&: pEditor->m_SelectedGroup, args: true));
400 pEditor->m_Map.DeleteGroup(Index: pEditor->m_SelectedGroup);
401 pEditor->m_SelectedGroup = maximum(a: 0, b: pEditor->m_SelectedGroup - 1);
402 return CUi::POPUP_CLOSE_CURRENT;
403 }
404 }
405 else
406 {
407 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Clean up game tiles", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Removes game tiles that aren't based on a layer"))
408 {
409 // gather all tile layers
410 std::vector<std::shared_ptr<CLayerTiles>> vpLayers;
411 int GameLayerIndex = -1;
412 for(int LayerIndex = 0; LayerIndex < (int)pEditor->m_Map.m_pGameGroup->m_vpLayers.size(); LayerIndex++)
413 {
414 auto &pLayer = pEditor->m_Map.m_pGameGroup->m_vpLayers.at(n: LayerIndex);
415 if(pLayer != pEditor->m_Map.m_pGameLayer && pLayer->m_Type == LAYERTYPE_TILES)
416 vpLayers.push_back(x: std::static_pointer_cast<CLayerTiles>(r: pLayer));
417 else if(pLayer == pEditor->m_Map.m_pGameLayer)
418 GameLayerIndex = LayerIndex;
419 }
420
421 // search for unneeded game tiles
422 std::shared_ptr<CLayerTiles> pGameLayer = pEditor->m_Map.m_pGameLayer;
423 for(int y = 0; y < pGameLayer->m_Height; ++y)
424 {
425 for(int x = 0; x < pGameLayer->m_Width; ++x)
426 {
427 if(pGameLayer->m_pTiles[y * pGameLayer->m_Width + x].m_Index > static_cast<unsigned char>(TILE_NOHOOK))
428 continue;
429
430 bool Found = false;
431 for(const auto &pLayer : vpLayers)
432 {
433 if(x < pLayer->m_Width && y < pLayer->m_Height && pLayer->m_pTiles[y * pLayer->m_Width + x].m_Index)
434 {
435 Found = true;
436 break;
437 }
438 }
439
440 CTile Tile = pGameLayer->GetTile(x, y);
441 if(!Found && Tile.m_Index != TILE_AIR)
442 {
443 Tile.m_Index = TILE_AIR;
444 pGameLayer->SetTile(x, y, Tile);
445 pEditor->m_Map.OnModify();
446 }
447 }
448 }
449
450 if(!pGameLayer->m_TilesHistory.empty())
451 {
452 if(GameLayerIndex == -1)
453 {
454 dbg_msg(sys: "editor", fmt: "failed to record action (GameLayerIndex not found)");
455 }
456 else
457 {
458 // record undo
459 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionTileChanges>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: GameLayerIndex, args: "Clean up game tiles", args&: pGameLayer->m_TilesHistory));
460 }
461 pGameLayer->ClearHistory();
462 }
463
464 return CUi::POPUP_CLOSE_CURRENT;
465 }
466 }
467
468 if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTeleLayer)
469 {
470 // new tele layer
471 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
472 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
473 static int s_NewTeleLayerButton = 0;
474 if(pEditor->DoButton_Editor(pId: &s_NewTeleLayerButton, pText: "Add tele layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new tele layer"))
475 {
476 std::shared_ptr<CLayer> pTeleLayer = std::make_shared<CLayerTele>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
477 pEditor->m_Map.MakeTeleLayer(pLayer: pTeleLayer);
478 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pTeleLayer);
479 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
480 pEditor->SelectLayer(LayerIndex);
481 pEditor->m_pBrush->Clear();
482 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
483 return CUi::POPUP_CLOSE_CURRENT;
484 }
485 }
486
487 if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSpeedupLayer)
488 {
489 // new speedup layer
490 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
491 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
492 static int s_NewSpeedupLayerButton = 0;
493 if(pEditor->DoButton_Editor(pId: &s_NewSpeedupLayerButton, pText: "Add speedup layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new speedup layer"))
494 {
495 std::shared_ptr<CLayer> pSpeedupLayer = std::make_shared<CLayerSpeedup>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
496 pEditor->m_Map.MakeSpeedupLayer(pLayer: pSpeedupLayer);
497 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pSpeedupLayer);
498 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
499 pEditor->SelectLayer(LayerIndex);
500 pEditor->m_pBrush->Clear();
501 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
502 return CUi::POPUP_CLOSE_CURRENT;
503 }
504 }
505
506 if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pTuneLayer)
507 {
508 // new tune layer
509 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
510 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
511 static int s_NewTuneLayerButton = 0;
512 if(pEditor->DoButton_Editor(pId: &s_NewTuneLayerButton, pText: "Add tune layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new tuning layer"))
513 {
514 std::shared_ptr<CLayer> pTuneLayer = std::make_shared<CLayerTune>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
515 pEditor->m_Map.MakeTuneLayer(pLayer: pTuneLayer);
516 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pTuneLayer);
517 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
518 pEditor->SelectLayer(LayerIndex);
519 pEditor->m_pBrush->Clear();
520 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
521 return CUi::POPUP_CLOSE_CURRENT;
522 }
523 }
524
525 if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pFrontLayer)
526 {
527 // new front layer
528 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
529 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
530 static int s_NewFrontLayerButton = 0;
531 if(pEditor->DoButton_Editor(pId: &s_NewFrontLayerButton, pText: "Add front layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new item layer"))
532 {
533 std::shared_ptr<CLayer> pFrontLayer = std::make_shared<CLayerFront>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
534 pEditor->m_Map.MakeFrontLayer(pLayer: pFrontLayer);
535 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pFrontLayer);
536 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
537 pEditor->SelectLayer(LayerIndex);
538 pEditor->m_pBrush->Clear();
539 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
540 return CUi::POPUP_CLOSE_CURRENT;
541 }
542 }
543
544 if(pEditor->GetSelectedGroup()->m_GameGroup && !pEditor->m_Map.m_pSwitchLayer)
545 {
546 // new Switch layer
547 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
548 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
549 static int s_NewSwitchLayerButton = 0;
550 if(pEditor->DoButton_Editor(pId: &s_NewSwitchLayerButton, pText: "Add switch layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new switch layer"))
551 {
552 std::shared_ptr<CLayer> pSwitchLayer = std::make_shared<CLayerSwitch>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
553 pEditor->m_Map.MakeSwitchLayer(pLayer: pSwitchLayer);
554 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pSwitchLayer);
555 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
556 pEditor->SelectLayer(LayerIndex);
557 pEditor->m_pBrush->Clear();
558 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
559 return CUi::POPUP_CLOSE_CURRENT;
560 }
561 }
562
563 // new quad layer
564 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
565 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
566 static int s_NewQuadLayerButton = 0;
567 if(pEditor->DoButton_Editor(pId: &s_NewQuadLayerButton, pText: "Add quads layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new quad layer"))
568 {
569 std::shared_ptr<CLayer> pQuadLayer = std::make_shared<CLayerQuads>(args&: pEditor);
570 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pQuadLayer);
571 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
572 pEditor->SelectLayer(LayerIndex);
573 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
574 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
575 return CUi::POPUP_CLOSE_CURRENT;
576 }
577
578 // new tile layer
579 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
580 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
581 static int s_NewTileLayerButton = 0;
582 if(pEditor->DoButton_Editor(pId: &s_NewTileLayerButton, pText: "Add tile layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new tile layer"))
583 {
584 std::shared_ptr<CLayer> pTileLayer = std::make_shared<CLayerTiles>(args&: pEditor, args&: pEditor->m_Map.m_pGameLayer->m_Width, args&: pEditor->m_Map.m_pGameLayer->m_Height);
585 pTileLayer->m_pEditor = pEditor;
586 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pTileLayer);
587 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
588 pEditor->SelectLayer(LayerIndex);
589 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
590 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
591 return CUi::POPUP_CLOSE_CURRENT;
592 }
593
594 // new sound layer
595 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
596 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
597 static int s_NewSoundLayerButton = 0;
598 if(pEditor->DoButton_Editor(pId: &s_NewSoundLayerButton, pText: "Add sound layer", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Creates a new sound layer"))
599 {
600 std::shared_ptr<CLayer> pSoundLayer = std::make_shared<CLayerSounds>(args&: pEditor);
601 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->AddLayer(pLayer: pSoundLayer);
602 int LayerIndex = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_vpLayers.size() - 1;
603 pEditor->SelectLayer(LayerIndex);
604 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_Collapse = false;
605 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: LayerIndex));
606 return CUi::POPUP_CLOSE_CURRENT;
607 }
608
609 // group name
610 if(!pEditor->GetSelectedGroup()->m_GameGroup)
611 {
612 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
613 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
614 pEditor->Ui()->DoLabel(pRect: &Button, pText: "Name:", Size: 10.0f, Align: TEXTALIGN_ML);
615 Button.VSplitLeft(Cut: 40.0f, pLeft: nullptr, pRight: &Button);
616 static CLineInput s_NameInput;
617 s_NameInput.SetBuffer(pStr: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName, MaxSize: sizeof(pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_aName));
618 if(pEditor->DoEditBox(pLineInput: &s_NameInput, pRect: &Button, FontSize: 10.0f))
619 pEditor->m_Map.OnModify();
620 }
621
622 CProperty aProps[] = {
623 {.m_pName: "Order", .m_Value: pEditor->m_SelectedGroup, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: (int)pEditor->m_Map.m_vpGroups.size() - 1},
624 {.m_pName: "Pos X", .m_Value: -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
625 {.m_pName: "Pos Y", .m_Value: -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
626 {.m_pName: "Para X", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
627 {.m_pName: "Para Y", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
628 {.m_pName: "Use Clipping", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
629 {.m_pName: "Clip X", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
630 {.m_pName: "Clip Y", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
631 {.m_pName: "Clip W", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
632 {.m_pName: "Clip H", .m_Value: pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
633 {.m_pName: nullptr},
634 };
635
636 // cut the properties that aren't needed
637 if(pEditor->GetSelectedGroup()->m_GameGroup)
638 aProps[(int)EGroupProp::PROP_POS_X].m_pName = nullptr;
639
640 static int s_aIds[(int)EGroupProp::NUM_PROPS] = {0};
641 int NewVal = 0;
642 auto [State, Prop] = pEditor->DoPropertiesWithState<EGroupProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
643 if(Prop != EGroupProp::PROP_NONE)
644 {
645 pEditor->m_Map.OnModify();
646 }
647
648 static CLayerGroupPropTracker s_Tracker(pEditor);
649 s_Tracker.Begin(pObject: pEditor->GetSelectedGroup().get(), Prop, State);
650
651 if(Prop == EGroupProp::PROP_ORDER)
652 {
653 pEditor->m_SelectedGroup = pEditor->m_Map.SwapGroups(Index0: pEditor->m_SelectedGroup, Index1: NewVal);
654 }
655
656 // these can not be changed on the game group
657 if(!pEditor->GetSelectedGroup()->m_GameGroup)
658 {
659 if(Prop == EGroupProp::PROP_PARA_X)
660 {
661 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal;
662 }
663 else if(Prop == EGroupProp::PROP_PARA_Y)
664 {
665 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal;
666 }
667 else if(Prop == EGroupProp::PROP_POS_X)
668 {
669 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal;
670 }
671 else if(Prop == EGroupProp::PROP_POS_Y)
672 {
673 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY = -NewVal;
674 }
675 else if(Prop == EGroupProp::PROP_USE_CLIPPING)
676 {
677 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping = NewVal;
678 }
679 else if(Prop == EGroupProp::PROP_CLIP_X)
680 {
681 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX = NewVal;
682 }
683 else if(Prop == EGroupProp::PROP_CLIP_Y)
684 {
685 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipY = NewVal;
686 }
687 else if(Prop == EGroupProp::PROP_CLIP_W)
688 {
689 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipW = NewVal;
690 }
691 else if(Prop == EGroupProp::PROP_CLIP_H)
692 {
693 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipH = NewVal;
694 }
695 }
696
697 s_Tracker.End(Prop, State);
698
699 return CUi::POPUP_KEEP_OPEN;
700}
701
702CUi::EPopupMenuFunctionResult CEditor::PopupLayer(void *pContext, CUIRect View, bool Active)
703{
704 SLayerPopupContext *pPopup = (SLayerPopupContext *)pContext;
705 CEditor *pEditor = pPopup->m_pEditor;
706
707 std::shared_ptr<CLayerGroup> pCurrentGroup = pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup];
708 std::shared_ptr<CLayer> pCurrentLayer = pEditor->GetSelectedLayer(Index: 0);
709
710 if(pPopup->m_vpLayers.size() > 1)
711 {
712 return CLayerTiles::RenderCommonProperties(State&: pPopup->m_CommonPropState, pEditor, pToolbox: &View, vpLayers&: pPopup->m_vpLayers, vLayerIndices&: pPopup->m_vLayerIndices);
713 }
714
715 const bool EntitiesLayer = pCurrentLayer->IsEntitiesLayer();
716
717 // delete button
718 if(pEditor->m_Map.m_pGameLayer != pCurrentLayer) // entities layers except the game layer can be deleted
719 {
720 CUIRect DeleteButton;
721 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &DeleteButton);
722 static int s_DeleteButton = 0;
723 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Delete layer", Checked: 0, pRect: &DeleteButton, Flags: 0, pToolTip: "Deletes the layer"))
724 {
725 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionDeleteLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: pEditor->m_vSelectedLayers[0]));
726
727 if(pCurrentLayer == pEditor->m_Map.m_pFrontLayer)
728 pEditor->m_Map.m_pFrontLayer = nullptr;
729 if(pCurrentLayer == pEditor->m_Map.m_pTeleLayer)
730 pEditor->m_Map.m_pTeleLayer = nullptr;
731 if(pCurrentLayer == pEditor->m_Map.m_pSpeedupLayer)
732 pEditor->m_Map.m_pSpeedupLayer = nullptr;
733 if(pCurrentLayer == pEditor->m_Map.m_pSwitchLayer)
734 pEditor->m_Map.m_pSwitchLayer = nullptr;
735 if(pCurrentLayer == pEditor->m_Map.m_pTuneLayer)
736 pEditor->m_Map.m_pTuneLayer = nullptr;
737 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DeleteLayer(Index: pEditor->m_vSelectedLayers[0]);
738
739 return CUi::POPUP_CLOSE_CURRENT;
740 }
741 }
742
743 // duplicate button
744 if(!EntitiesLayer) // entities layers cannot be duplicated
745 {
746 CUIRect DuplicateButton;
747 View.HSplitBottom(Cut: 4.0f, pTop: &View, pBottom: nullptr);
748 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &DuplicateButton);
749 static int s_DuplicationButton = 0;
750 if(pEditor->DoButton_Editor(pId: &s_DuplicationButton, pText: "Duplicate layer", Checked: 0, pRect: &DuplicateButton, Flags: 0, pToolTip: "Duplicates the layer"))
751 {
752 pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(Index: pEditor->m_vSelectedLayers[0]);
753 pEditor->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorActionAddLayer>(args&: pEditor, args&: pEditor->m_SelectedGroup, args: pEditor->m_vSelectedLayers[0] + 1, args: true));
754 return CUi::POPUP_CLOSE_CURRENT;
755 }
756 }
757
758 // layer name
759 if(!EntitiesLayer) // name cannot be changed for entities layers
760 {
761 CUIRect Label, EditBox;
762 View.HSplitBottom(Cut: 5.0f, pTop: &View, pBottom: nullptr);
763 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Label);
764 Label.VSplitLeft(Cut: 40.0f, pLeft: &Label, pRight: &EditBox);
765 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: 10.0f, Align: TEXTALIGN_ML);
766 static CLineInput s_NameInput;
767 s_NameInput.SetBuffer(pStr: pCurrentLayer->m_aName, MaxSize: sizeof(pCurrentLayer->m_aName));
768 if(pEditor->DoEditBox(pLineInput: &s_NameInput, pRect: &EditBox, FontSize: 10.0f))
769 pEditor->m_Map.OnModify();
770 }
771
772 // spacing if any button was rendered
773 if(!EntitiesLayer || pEditor->m_Map.m_pGameLayer != pCurrentLayer)
774 View.HSplitBottom(Cut: 10.0f, pTop: &View, pBottom: nullptr);
775
776 CProperty aProps[] = {
777 {.m_pName: "Group", .m_Value: pEditor->m_SelectedGroup, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: (int)pEditor->m_Map.m_vpGroups.size() - 1},
778 {.m_pName: "Order", .m_Value: pEditor->m_vSelectedLayers[0], .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: (int)pCurrentGroup->m_vpLayers.size() - 1},
779 {.m_pName: "Detail", .m_Value: pCurrentLayer->m_Flags & LAYERFLAG_DETAIL, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
780 {.m_pName: nullptr},
781 };
782
783 // don't use Group and Detail from the selection if this is an entities layer
784 if(EntitiesLayer)
785 {
786 aProps[0].m_Type = PROPTYPE_NULL;
787 aProps[2].m_Type = PROPTYPE_NULL;
788 }
789
790 static int s_aIds[(int)ELayerProp::NUM_PROPS] = {0};
791 int NewVal = 0;
792 auto [State, Prop] = pEditor->DoPropertiesWithState<ELayerProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
793 if(Prop != ELayerProp::PROP_NONE)
794 {
795 pEditor->m_Map.OnModify();
796 }
797
798 static CLayerPropTracker s_Tracker(pEditor);
799 s_Tracker.Begin(pObject: pCurrentLayer.get(), Prop, State);
800
801 if(Prop == ELayerProp::PROP_ORDER)
802 {
803 int NewIndex = pCurrentGroup->SwapLayers(Index0: pEditor->m_vSelectedLayers[0], Index1: NewVal);
804 pEditor->SelectLayer(LayerIndex: NewIndex);
805 }
806 else if(Prop == ELayerProp::PROP_GROUP)
807 {
808 if(NewVal >= 0 && (size_t)NewVal < pEditor->m_Map.m_vpGroups.size() && NewVal != pEditor->m_SelectedGroup)
809 {
810 auto Position = std::find(first: pCurrentGroup->m_vpLayers.begin(), last: pCurrentGroup->m_vpLayers.end(), val: pCurrentLayer);
811 if(Position != pCurrentGroup->m_vpLayers.end())
812 pCurrentGroup->m_vpLayers.erase(position: Position);
813 pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.push_back(x: pCurrentLayer);
814 pEditor->m_SelectedGroup = NewVal;
815 pEditor->SelectLayer(LayerIndex: pEditor->m_Map.m_vpGroups[NewVal]->m_vpLayers.size() - 1);
816 }
817 }
818 else if(Prop == ELayerProp::PROP_HQ)
819 {
820 pCurrentLayer->m_Flags &= ~LAYERFLAG_DETAIL;
821 if(NewVal)
822 pCurrentLayer->m_Flags |= LAYERFLAG_DETAIL;
823 }
824
825 s_Tracker.End(Prop, State);
826
827 return pCurrentLayer->RenderProperties(pToolbox: &View);
828}
829
830CUi::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, bool Active)
831{
832 CEditor *pEditor = static_cast<CEditor *>(pContext);
833 std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads();
834 if(pEditor->m_SelectedQuadIndex < 0 || pEditor->m_SelectedQuadIndex >= (int)vpQuads.size())
835 return CUi::POPUP_CLOSE_CURRENT;
836 CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
837 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
838
839 CUIRect Button;
840
841 // delete button
842 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
843 static int s_DeleteButton = 0;
844 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Delete", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Deletes the current quad"))
845 {
846 if(pLayer)
847 {
848 pEditor->m_Map.OnModify();
849 pEditor->DeleteSelectedQuads();
850 }
851 return CUi::POPUP_CLOSE_CURRENT;
852 }
853
854 // aspect ratio button
855 View.HSplitBottom(Cut: 10.0f, pTop: &View, pBottom: nullptr);
856 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
857 if(pLayer && pLayer->m_Image >= 0 && (size_t)pLayer->m_Image < pEditor->m_Map.m_vpImages.size())
858 {
859 static int s_AspectRatioButton = 0;
860 if(pEditor->DoButton_Editor(pId: &s_AspectRatioButton, pText: "Aspect ratio", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Resizes the current Quad based on the aspect ratio of the image"))
861 {
862 pEditor->m_QuadTracker.BeginQuadTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads);
863 for(auto &pQuad : vpQuads)
864 {
865 int Top = pQuad->m_aPoints[0].y;
866 int Left = pQuad->m_aPoints[0].x;
867 int Right = pQuad->m_aPoints[0].x;
868
869 for(int k = 1; k < 4; k++)
870 {
871 if(pQuad->m_aPoints[k].y < Top)
872 Top = pQuad->m_aPoints[k].y;
873 if(pQuad->m_aPoints[k].x < Left)
874 Left = pQuad->m_aPoints[k].x;
875 if(pQuad->m_aPoints[k].x > Right)
876 Right = pQuad->m_aPoints[k].x;
877 }
878
879 const int Height = (Right - Left) * pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_Height / pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_Width;
880
881 pQuad->m_aPoints[0].x = Left;
882 pQuad->m_aPoints[0].y = Top;
883 pQuad->m_aPoints[1].x = Right;
884 pQuad->m_aPoints[1].y = Top;
885 pQuad->m_aPoints[2].x = Left;
886 pQuad->m_aPoints[2].y = Top + Height;
887 pQuad->m_aPoints[3].x = Right;
888 pQuad->m_aPoints[3].y = Top + Height;
889 pEditor->m_Map.OnModify();
890 }
891 pEditor->m_QuadTracker.EndQuadTrack();
892
893 return CUi::POPUP_CLOSE_CURRENT;
894 }
895 }
896
897 // align button
898 View.HSplitBottom(Cut: 6.0f, pTop: &View, pBottom: nullptr);
899 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
900 static int s_AlignButton = 0;
901 if(pEditor->DoButton_Editor(pId: &s_AlignButton, pText: "Align", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Aligns coordinates of the quad points"))
902 {
903 pEditor->m_QuadTracker.BeginQuadTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads);
904 for(auto &pQuad : vpQuads)
905 {
906 for(int k = 1; k < 4; k++)
907 {
908 pQuad->m_aPoints[k].x = 1000.0f * (pQuad->m_aPoints[k].x / 1000);
909 pQuad->m_aPoints[k].y = 1000.0f * (pQuad->m_aPoints[k].y / 1000);
910 }
911 pEditor->m_Map.OnModify();
912 }
913 pEditor->m_QuadTracker.EndQuadTrack();
914 return CUi::POPUP_CLOSE_CURRENT;
915 }
916
917 // square button
918 View.HSplitBottom(Cut: 6.0f, pTop: &View, pBottom: nullptr);
919 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
920 static int s_Button = 0;
921 if(pEditor->DoButton_Editor(pId: &s_Button, pText: "Square", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Squares the current quad"))
922 {
923 pEditor->m_QuadTracker.BeginQuadTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads);
924 for(auto &pQuad : vpQuads)
925 {
926 int Top = pQuad->m_aPoints[0].y;
927 int Left = pQuad->m_aPoints[0].x;
928 int Bottom = pQuad->m_aPoints[0].y;
929 int Right = pQuad->m_aPoints[0].x;
930
931 for(int k = 1; k < 4; k++)
932 {
933 if(pQuad->m_aPoints[k].y < Top)
934 Top = pQuad->m_aPoints[k].y;
935 if(pQuad->m_aPoints[k].x < Left)
936 Left = pQuad->m_aPoints[k].x;
937 if(pQuad->m_aPoints[k].y > Bottom)
938 Bottom = pQuad->m_aPoints[k].y;
939 if(pQuad->m_aPoints[k].x > Right)
940 Right = pQuad->m_aPoints[k].x;
941 }
942
943 pQuad->m_aPoints[0].x = Left;
944 pQuad->m_aPoints[0].y = Top;
945 pQuad->m_aPoints[1].x = Right;
946 pQuad->m_aPoints[1].y = Top;
947 pQuad->m_aPoints[2].x = Left;
948 pQuad->m_aPoints[2].y = Bottom;
949 pQuad->m_aPoints[3].x = Right;
950 pQuad->m_aPoints[3].y = Bottom;
951 pEditor->m_Map.OnModify();
952 }
953 pEditor->m_QuadTracker.EndQuadTrack();
954 return CUi::POPUP_CLOSE_CURRENT;
955 }
956
957 // slice button
958 View.HSplitBottom(Cut: 6.0f, pTop: &View, pBottom: nullptr);
959 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
960 static int s_SliceButton = 0;
961 if(pEditor->DoButton_Editor(pId: &s_SliceButton, pText: "Slice", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Enables quad knife mode"))
962 {
963 pEditor->m_QuadKnifeCount = 0;
964 pEditor->m_QuadKnifeActive = true;
965 return CUi::POPUP_CLOSE_CURRENT;
966 }
967
968 const int NumQuads = pLayer ? (int)pLayer->m_vQuads.size() : 0;
969 CProperty aProps[] = {
970 {.m_pName: "Order", .m_Value: pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: NumQuads},
971 {.m_pName: "Pos X", .m_Value: fx2i(v: pCurrentQuad->m_aPoints[4].x), .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
972 {.m_pName: "Pos Y", .m_Value: fx2i(v: pCurrentQuad->m_aPoints[4].y), .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
973 {.m_pName: "Pos. Env", .m_Value: pCurrentQuad->m_PosEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
974 {.m_pName: "Pos. TO", .m_Value: pCurrentQuad->m_PosEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
975 {.m_pName: "Color Env", .m_Value: pCurrentQuad->m_ColorEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
976 {.m_pName: "Color TO", .m_Value: pCurrentQuad->m_ColorEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
977 {.m_pName: nullptr},
978 };
979
980 static int s_aIds[(int)EQuadProp::NUM_PROPS] = {0};
981 int NewVal = 0;
982 auto PropRes = pEditor->DoPropertiesWithState<EQuadProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
983 EQuadProp Prop = PropRes.m_Value;
984 if(Prop != EQuadProp::PROP_NONE)
985 {
986 pEditor->m_Map.OnModify();
987 if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO)
988 {
989 pEditor->m_QuadTracker.BeginQuadPropTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads, Prop);
990 }
991 }
992
993 const float OffsetX = i2fx(v: NewVal) - pCurrentQuad->m_aPoints[4].x;
994 const float OffsetY = i2fx(v: NewVal) - pCurrentQuad->m_aPoints[4].y;
995
996 if(Prop == EQuadProp::PROP_ORDER && pLayer)
997 {
998 const int QuadIndex = pLayer->SwapQuads(Index0: pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], Index1: NewVal);
999 pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex;
1000 }
1001
1002 for(auto &pQuad : vpQuads)
1003 {
1004 if(Prop == EQuadProp::PROP_POS_X)
1005 {
1006 for(auto &Point : pQuad->m_aPoints)
1007 Point.x += OffsetX;
1008 }
1009 else if(Prop == EQuadProp::PROP_POS_Y)
1010 {
1011 for(auto &Point : pQuad->m_aPoints)
1012 Point.y += OffsetY;
1013 }
1014 else if(Prop == EQuadProp::PROP_POS_ENV)
1015 {
1016 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1017 int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1;
1018 if(StepDirection != 0)
1019 {
1020 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1021 {
1022 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
1023 {
1024 pQuad->m_PosEnv = Index;
1025 break;
1026 }
1027 }
1028 }
1029 }
1030 else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET)
1031 {
1032 pQuad->m_PosEnvOffset = NewVal;
1033 }
1034 else if(Prop == EQuadProp::PROP_COLOR_ENV)
1035 {
1036 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1037 int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1;
1038 if(StepDirection != 0)
1039 {
1040 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1041 {
1042 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 4)
1043 {
1044 pQuad->m_ColorEnv = Index;
1045 break;
1046 }
1047 }
1048 }
1049 }
1050 else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
1051 {
1052 pQuad->m_ColorEnvOffset = NewVal;
1053 }
1054 }
1055
1056 if(Prop != EQuadProp::PROP_NONE)
1057 {
1058 if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO)
1059 {
1060 pEditor->m_QuadTracker.EndQuadPropTrack(Prop);
1061 }
1062 }
1063
1064 return CUi::POPUP_KEEP_OPEN;
1065}
1066
1067CUi::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, bool Active)
1068{
1069 CEditor *pEditor = static_cast<CEditor *>(pContext);
1070 CSoundSource *pSource = pEditor->GetSelectedSource();
1071 if(!pSource)
1072 return CUi::POPUP_CLOSE_CURRENT;
1073
1074 CUIRect Button;
1075
1076 // delete button
1077 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
1078 static int s_DeleteButton = 0;
1079 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Delete", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Deletes the current source"))
1080 {
1081 std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_SOUNDS));
1082 if(pLayer)
1083 {
1084 pEditor->m_EditorHistory.Execute(pAction: std::make_shared<CEditorActionDeleteSoundSource>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: pEditor->m_vSelectedLayers[0], args&: pEditor->m_SelectedSource));
1085 }
1086 return CUi::POPUP_CLOSE_CURRENT;
1087 }
1088
1089 // Sound shape button
1090 CUIRect ShapeButton;
1091 View.HSplitBottom(Cut: 3.0f, pTop: &View, pBottom: nullptr);
1092 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &ShapeButton);
1093
1094 static const char *s_apShapeNames[CSoundShape::NUM_SHAPES] = {
1095 "Rectangle",
1096 "Circle"};
1097
1098 pSource->m_Shape.m_Type = pSource->m_Shape.m_Type % CSoundShape::NUM_SHAPES; // prevent out of array errors
1099
1100 static int s_ShapeTypeButton = 0;
1101 if(pEditor->DoButton_Editor(pId: &s_ShapeTypeButton, pText: s_apShapeNames[pSource->m_Shape.m_Type], Checked: 0, pRect: &ShapeButton, Flags: 0, pToolTip: "Change shape"))
1102 {
1103 pEditor->m_EditorHistory.Execute(pAction: std::make_shared<CEditorActionEditSoundSource>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: pEditor->m_vSelectedLayers[0], args&: pEditor->m_SelectedSource, args: CEditorActionEditSoundSource::EEditType::SHAPE, args: (pSource->m_Shape.m_Type + 1) % CSoundShape::NUM_SHAPES));
1104 }
1105
1106 CProperty aProps[] = {
1107 {.m_pName: "Pos X", .m_Value: pSource->m_Position.x / 1000, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1108 {.m_pName: "Pos Y", .m_Value: pSource->m_Position.y / 1000, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1109 {.m_pName: "Loop", .m_Value: pSource->m_Loop, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
1110 {.m_pName: "Pan", .m_Value: pSource->m_Pan, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
1111 {.m_pName: "Delay", .m_Value: pSource->m_TimeDelay, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1112 {.m_pName: "Falloff", .m_Value: pSource->m_Falloff, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
1113 {.m_pName: "Pos. Env", .m_Value: pSource->m_PosEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
1114 {.m_pName: "Pos. TO", .m_Value: pSource->m_PosEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1115 {.m_pName: "Sound Env", .m_Value: pSource->m_SoundEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
1116 {.m_pName: "Sound. TO", .m_Value: pSource->m_SoundEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1117 {.m_pName: nullptr},
1118 };
1119
1120 static int s_aIds[(int)ESoundProp::NUM_PROPS] = {0};
1121 int NewVal = 0;
1122 auto [State, Prop] = pEditor->DoPropertiesWithState<ESoundProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1123 if(Prop != ESoundProp::PROP_NONE)
1124 {
1125 pEditor->m_Map.OnModify();
1126 }
1127
1128 static CSoundSourcePropTracker s_Tracker(pEditor);
1129 s_Tracker.Begin(pObject: pSource, Prop, State);
1130
1131 if(Prop == ESoundProp::PROP_POS_X)
1132 {
1133 pSource->m_Position.x = NewVal * 1000;
1134 }
1135 else if(Prop == ESoundProp::PROP_POS_Y)
1136 {
1137 pSource->m_Position.y = NewVal * 1000;
1138 }
1139 else if(Prop == ESoundProp::PROP_LOOP)
1140 {
1141 pSource->m_Loop = NewVal;
1142 }
1143 else if(Prop == ESoundProp::PROP_PAN)
1144 {
1145 pSource->m_Pan = NewVal;
1146 }
1147 else if(Prop == ESoundProp::PROP_TIME_DELAY)
1148 {
1149 pSource->m_TimeDelay = NewVal;
1150 }
1151 else if(Prop == ESoundProp::PROP_FALLOFF)
1152 {
1153 pSource->m_Falloff = NewVal;
1154 }
1155 else if(Prop == ESoundProp::PROP_POS_ENV)
1156 {
1157 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1158 const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
1159 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1160 {
1161 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
1162 {
1163 pSource->m_PosEnv = Index;
1164 break;
1165 }
1166 }
1167 }
1168 else if(Prop == ESoundProp::PROP_POS_ENV_OFFSET)
1169 {
1170 pSource->m_PosEnvOffset = NewVal;
1171 }
1172 else if(Prop == ESoundProp::PROP_SOUND_ENV)
1173 {
1174 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1175 const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
1176 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1177 {
1178 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 1)
1179 {
1180 pSource->m_SoundEnv = Index;
1181 break;
1182 }
1183 }
1184 }
1185 else if(Prop == ESoundProp::PROP_SOUND_ENV_OFFSET)
1186 {
1187 pSource->m_SoundEnvOffset = NewVal;
1188 }
1189
1190 s_Tracker.End(Prop, State);
1191
1192 // source shape properties
1193 switch(pSource->m_Shape.m_Type)
1194 {
1195 case CSoundShape::SHAPE_CIRCLE:
1196 {
1197 CProperty aCircleProps[] = {
1198 {.m_pName: "Radius", .m_Value: pSource->m_Shape.m_Circle.m_Radius, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1199 {.m_pName: nullptr},
1200 };
1201
1202 static int s_aCircleIds[(int)ECircleShapeProp::NUM_CIRCLE_PROPS] = {0};
1203 NewVal = 0;
1204 auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ECircleShapeProp>(pToolbox: &View, pProps: aCircleProps, pIds: s_aCircleIds, pNewVal: &NewVal);
1205 if(LocalProp != ECircleShapeProp::PROP_NONE)
1206 {
1207 pEditor->m_Map.OnModify();
1208 }
1209
1210 static CSoundSourceCircleShapePropTracker s_ShapeTracker(pEditor);
1211 s_ShapeTracker.Begin(pObject: pSource, Prop: LocalProp, State: LocalState);
1212
1213 if(LocalProp == ECircleShapeProp::PROP_CIRCLE_RADIUS)
1214 {
1215 pSource->m_Shape.m_Circle.m_Radius = NewVal;
1216 }
1217
1218 s_ShapeTracker.End(Prop: LocalProp, State: LocalState);
1219 break;
1220 }
1221
1222 case CSoundShape::SHAPE_RECTANGLE:
1223 {
1224 CProperty aRectangleProps[] = {
1225 {.m_pName: "Width", .m_Value: pSource->m_Shape.m_Rectangle.m_Width / 1024, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1226 {.m_pName: "Height", .m_Value: pSource->m_Shape.m_Rectangle.m_Height / 1024, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1227 {.m_pName: nullptr},
1228 };
1229
1230 static int s_aRectangleIds[(int)ERectangleShapeProp::NUM_RECTANGLE_PROPS] = {0};
1231 NewVal = 0;
1232 auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ERectangleShapeProp>(pToolbox: &View, pProps: aRectangleProps, pIds: s_aRectangleIds, pNewVal: &NewVal);
1233 if(LocalProp != ERectangleShapeProp::PROP_NONE)
1234 {
1235 pEditor->m_Map.OnModify();
1236 }
1237
1238 static CSoundSourceRectShapePropTracker s_ShapeTracker(pEditor);
1239 s_ShapeTracker.Begin(pObject: pSource, Prop: LocalProp, State: LocalState);
1240
1241 if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_WIDTH)
1242 {
1243 pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024;
1244 }
1245 else if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT)
1246 {
1247 pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024;
1248 }
1249
1250 s_ShapeTracker.End(Prop: LocalProp, State: LocalState);
1251 break;
1252 }
1253 }
1254
1255 return CUi::POPUP_KEEP_OPEN;
1256}
1257
1258CUi::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, bool Active)
1259{
1260 CEditor *pEditor = static_cast<CEditor *>(pContext);
1261 std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads();
1262 if(!in_range<int>(a: pEditor->m_SelectedQuadIndex, lower: 0, upper: vpQuads.size() - 1))
1263 return CUi::POPUP_CLOSE_CURRENT;
1264 CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
1265 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
1266
1267 int Color = PackColor(Color: pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint]);
1268
1269 const int X = fx2i(v: pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x);
1270 const int Y = fx2i(v: pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y);
1271 const int TextureU = fx2f(v: pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].x) * 1024;
1272 const int TextureV = fx2f(v: pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].y) * 1024;
1273
1274 CProperty aProps[] = {
1275 {.m_pName: "Pos X", .m_Value: X, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1276 {.m_pName: "Pos Y", .m_Value: Y, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1277 {.m_pName: "Color", .m_Value: Color, .m_Type: PROPTYPE_COLOR, .m_Min: 0, .m_Max: 0},
1278 {.m_pName: "Tex U", .m_Value: TextureU, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1279 {.m_pName: "Tex V", .m_Value: TextureV, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1280 {.m_pName: nullptr},
1281 };
1282
1283 static int s_aIds[(int)EQuadPointProp::NUM_PROPS] = {0};
1284 int NewVal = 0;
1285 auto PropRes = pEditor->DoPropertiesWithState<EQuadPointProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1286 EQuadPointProp Prop = PropRes.m_Value;
1287 if(Prop != EQuadPointProp::PROP_NONE)
1288 {
1289 pEditor->m_Map.OnModify();
1290 if(PropRes.m_State == EEditState::START || PropRes.m_State == EEditState::ONE_GO)
1291 {
1292 pEditor->m_QuadTracker.BeginQuadPointPropTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads, SelectedQuadPoints: pEditor->m_SelectedQuadPoints);
1293 pEditor->m_QuadTracker.AddQuadPointPropTrack(Prop);
1294 }
1295 }
1296
1297 for(CQuad *pQuad : vpQuads)
1298 {
1299 if(Prop == EQuadPointProp::PROP_POS_X)
1300 {
1301 for(int v = 0; v < 4; v++)
1302 if(pEditor->IsQuadCornerSelected(Index: v))
1303 pQuad->m_aPoints[v].x = i2fx(v: fx2i(v: pQuad->m_aPoints[v].x) + NewVal - X);
1304 }
1305 else if(Prop == EQuadPointProp::PROP_POS_Y)
1306 {
1307 for(int v = 0; v < 4; v++)
1308 if(pEditor->IsQuadCornerSelected(Index: v))
1309 pQuad->m_aPoints[v].y = i2fx(v: fx2i(v: pQuad->m_aPoints[v].y) + NewVal - Y);
1310 }
1311 else if(Prop == EQuadPointProp::PROP_COLOR)
1312 {
1313 for(int v = 0; v < 4; v++)
1314 {
1315 if(pEditor->IsQuadCornerSelected(Index: v))
1316 {
1317 pQuad->m_aColors[v].r = (NewVal >> 24) & 0xff;
1318 pQuad->m_aColors[v].g = (NewVal >> 16) & 0xff;
1319 pQuad->m_aColors[v].b = (NewVal >> 8) & 0xff;
1320 pQuad->m_aColors[v].a = NewVal & 0xff;
1321 }
1322 }
1323 }
1324 else if(Prop == EQuadPointProp::PROP_TEX_U)
1325 {
1326 for(int v = 0; v < 4; v++)
1327 if(pEditor->IsQuadCornerSelected(Index: v))
1328 pQuad->m_aTexcoords[v].x = f2fx(v: fx2f(v: pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f);
1329 }
1330 else if(Prop == EQuadPointProp::PROP_TEX_V)
1331 {
1332 for(int v = 0; v < 4; v++)
1333 if(pEditor->IsQuadCornerSelected(Index: v))
1334 pQuad->m_aTexcoords[v].y = f2fx(v: fx2f(v: pQuad->m_aTexcoords[v].y) + (NewVal - TextureV) / 1024.0f);
1335 }
1336 }
1337
1338 if(Prop != EQuadPointProp::PROP_NONE)
1339 {
1340 pEditor->m_Map.OnModify();
1341 if(PropRes.m_State == EEditState::END || PropRes.m_State == EEditState::ONE_GO)
1342 {
1343 pEditor->m_QuadTracker.EndQuadPointPropTrack(Prop);
1344 }
1345 }
1346
1347 return CUi::POPUP_KEEP_OPEN;
1348}
1349
1350CUi::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active)
1351{
1352 CEditor *pEditor = static_cast<CEditor *>(pContext);
1353 if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->m_Map.m_vpEnvelopes.size())
1354 return CUi::POPUP_CLOSE_CURRENT;
1355
1356 const float RowHeight = 12.0f;
1357 CUIRect Row, Label, EditBox;
1358
1359 pEditor->m_ShowEnvelopePreview = SHOWENV_SELECTED;
1360
1361 std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes[pEditor->m_SelectedEnvelope];
1362
1363 if(pEnvelope->GetChannels() == 4 && !pEditor->IsTangentSelected())
1364 {
1365 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1366 View.HSplitTop(Cut: 4.0f, pTop: nullptr, pBottom: &View);
1367 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1368 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1369 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Color:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1370
1371 const auto SelectedPoint = pEditor->m_vSelectedEnvelopePoints.front();
1372 const int SelectedIndex = SelectedPoint.first;
1373 auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues;
1374 const ColorRGBA Color = ColorRGBA(fx2f(v: pValues[0]), fx2f(v: pValues[1]), fx2f(v: pValues[2]), fx2f(v: pValues[3]));
1375 const auto &&SetColor = [&](ColorRGBA NewColor) {
1376 if(Color == NewColor && pEditor->m_ColorPickerPopupContext.m_State == EEditState::EDITING)
1377 return;
1378
1379 static int s_Values[4];
1380
1381 if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::START || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
1382 {
1383 for(int Channel = 0; Channel < 4; ++Channel)
1384 s_Values[Channel] = pValues[Channel];
1385 }
1386
1387 for(int Channel = 0; Channel < 4; ++Channel)
1388 {
1389 pValues[Channel] = f2fx(v: NewColor[Channel]);
1390 }
1391
1392 if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::END || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
1393 {
1394 std::vector<std::shared_ptr<IEditorAction>> vpActions(4);
1395
1396 for(int Channel = 0; Channel < 4; ++Channel)
1397 {
1398 vpActions[Channel] = std::make_shared<CEditorActionEnvelopeEditPoint>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args: SelectedIndex, args&: Channel, args: CEditorActionEnvelopeEditPoint::EEditType::VALUE, args&: s_Values[Channel], args: f2fx(v: NewColor[Channel]));
1399 }
1400
1401 char aDisplay[256];
1402 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Edit color of point %d of envelope %d", SelectedIndex, pEditor->m_SelectedEnvelope);
1403 pEditor->m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args&: pEditor, args&: vpActions, args&: aDisplay));
1404 }
1405
1406 pEditor->m_UpdateEnvPointInfo = true;
1407 pEditor->m_Map.OnModify();
1408 };
1409 static char s_ColorPickerButton;
1410 pEditor->DoColorPickerButton(pId: &s_ColorPickerButton, pRect: &EditBox, Color, SetColor);
1411 }
1412
1413 static CLineInputNumber s_CurValueInput;
1414 static CLineInputNumber s_CurTimeInput;
1415
1416 static float s_CurrentTime = 0;
1417 static float s_CurrentValue = 0;
1418
1419 if(pEditor->m_UpdateEnvPointInfo)
1420 {
1421 pEditor->m_UpdateEnvPointInfo = false;
1422
1423 auto TimeAndValue = pEditor->EnvGetSelectedTimeAndValue();
1424 int CurrentTime = TimeAndValue.first;
1425 int CurrentValue = TimeAndValue.second;
1426
1427 // update displayed text
1428 s_CurValueInput.SetFloat(fx2f(v: CurrentValue));
1429 s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
1430
1431 s_CurrentTime = s_CurTimeInput.GetFloat();
1432 s_CurrentValue = s_CurValueInput.GetFloat();
1433 }
1434
1435 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1436 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1437 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1438 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Value:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1439 pEditor->DoEditBox(pLineInput: &s_CurValueInput, pRect: &EditBox, FontSize: RowHeight - 2.0f, Corners: IGraphics::CORNER_ALL, pToolTip: "The value of the selected envelope point");
1440
1441 View.HSplitTop(Cut: 4.0f, pTop: nullptr, pBottom: &View);
1442 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1443 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1444 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1445 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Time (in s):", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1446 pEditor->DoEditBox(pLineInput: &s_CurTimeInput, pRect: &EditBox, FontSize: RowHeight - 2.0f, Corners: IGraphics::CORNER_ALL, pToolTip: "The time of the selected envelope point");
1447
1448 if(pEditor->Input()->KeyIsPressed(Key: KEY_RETURN) || pEditor->Input()->KeyIsPressed(Key: KEY_KP_ENTER))
1449 {
1450 float CurrentTime = s_CurTimeInput.GetFloat();
1451 float CurrentValue = s_CurValueInput.GetFloat();
1452 if(!(absolute(a: CurrentTime - s_CurrentTime) < 0.0001f && absolute(a: CurrentValue - s_CurrentValue) < 0.0001f))
1453 {
1454 auto [OldTime, OldValue] = pEditor->EnvGetSelectedTimeAndValue();
1455
1456 if(pEditor->IsTangentInSelected())
1457 {
1458 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
1459
1460 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionEditEnvelopePointValue>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: CEditorActionEditEnvelopePointValue::EType::TANGENT_IN, args: static_cast<int>(OldTime * 1000.0f), args: f2fx(v: OldValue), args: static_cast<int>(CurrentTime * 1000.0f), args: f2fx(v: CurrentValue)));
1461 CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f;
1462 }
1463 else if(pEditor->IsTangentOutSelected())
1464 {
1465 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
1466
1467 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionEditEnvelopePointValue>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: CEditorActionEditEnvelopePointValue::EType::TANGENT_OUT, args: static_cast<int>(OldTime * 1000.0f), args: f2fx(v: OldValue), args: static_cast<int>(CurrentTime * 1000.0f), args: f2fx(v: CurrentValue)));
1468 CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f;
1469 }
1470 else
1471 {
1472 auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
1473 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionEditEnvelopePointValue>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: CEditorActionEditEnvelopePointValue::EType::POINT, args: static_cast<int>(OldTime * 1000.0f), args: f2fx(v: OldValue), args: static_cast<int>(CurrentTime * 1000.0f), args: f2fx(v: CurrentValue)));
1474
1475 if(SelectedIndex != 0)
1476 {
1477 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f;
1478 }
1479 else
1480 {
1481 CurrentTime = 0.0f;
1482 pEnvelope->m_vPoints[SelectedIndex].m_Time = 0.0f;
1483 }
1484 }
1485
1486 s_CurTimeInput.SetFloat(static_cast<int>(CurrentTime * 1000.0f) / 1000.0f);
1487 s_CurValueInput.SetFloat(fx2f(v: f2fx(v: CurrentValue)));
1488
1489 s_CurrentTime = s_CurTimeInput.GetFloat();
1490 s_CurrentValue = s_CurValueInput.GetFloat();
1491 }
1492 }
1493
1494 View.HSplitTop(Cut: 6.0f, pTop: nullptr, pBottom: &View);
1495 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1496 static int s_DeleteButtonId = 0;
1497 const char *pButtonText = pEditor->IsTangentSelected() ? "Reset" : "Delete";
1498 const char *pTooltip = pEditor->IsTangentSelected() ? "Reset tangent point to default value." : "Delete current envelope point in all channels.";
1499 if(pEditor->DoButton_Editor(pId: &s_DeleteButtonId, pText: pButtonText, Checked: 0, pRect: &Row, Flags: 0, pToolTip: pTooltip))
1500 {
1501 if(pEditor->IsTangentInSelected())
1502 {
1503 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
1504 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionResetEnvelopePointTangent>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: true));
1505 }
1506 else if(pEditor->IsTangentOutSelected())
1507 {
1508 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
1509 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionResetEnvelopePointTangent>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: false));
1510 }
1511 else
1512 {
1513 auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
1514 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionDeleteEnvelopePoint>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex));
1515 }
1516
1517 return CUi::POPUP_CLOSE_CURRENT;
1518 }
1519
1520 return CUi::POPUP_KEEP_OPEN;
1521}
1522
1523CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointMulti(void *pContext, CUIRect View, bool Active)
1524{
1525 CEditor *pEditor = static_cast<CEditor *>(pContext);
1526 const float RowHeight = 12.0f;
1527
1528 static int s_CurveButtonId = 0;
1529 CUIRect CurveButton;
1530 View.HSplitTop(Cut: RowHeight, pTop: &CurveButton, pBottom: &View);
1531 if(pEditor->DoButton_Editor(pId: &s_CurveButtonId, pText: "Project onto", Checked: 0, pRect: &CurveButton, Flags: 0, pToolTip: "Project all selected envelopes onto the curve between the first and last selected envelope."))
1532 {
1533 static SPopupMenuId s_PopupCurveTypeId;
1534 pEditor->Ui()->DoPopupMenu(pId: &s_PopupCurveTypeId, X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), Width: 80, Height: 80, pContext: pEditor, pfnFunc: PopupEnvPointCurveType);
1535 }
1536
1537 return CUi::POPUP_KEEP_OPEN;
1538}
1539
1540CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CUIRect View, bool Active)
1541{
1542 CEditor *pEditor = static_cast<CEditor *>(pContext);
1543 const float RowHeight = 14.0f;
1544
1545 int CurveType = -1;
1546
1547 static int s_ButtonLinearId;
1548 CUIRect ButtonLinear;
1549 View.HSplitTop(Cut: RowHeight, pTop: &ButtonLinear, pBottom: &View);
1550 if(pEditor->DoButton_MenuItem(pId: &s_ButtonLinearId, pText: "Linear", Checked: 0, pRect: &ButtonLinear))
1551 CurveType = CURVETYPE_LINEAR;
1552
1553 static int s_ButtonSlowId;
1554 CUIRect ButtonSlow;
1555 View.HSplitTop(Cut: RowHeight, pTop: &ButtonSlow, pBottom: &View);
1556 if(pEditor->DoButton_MenuItem(pId: &s_ButtonSlowId, pText: "Slow", Checked: 0, pRect: &ButtonSlow))
1557 CurveType = CURVETYPE_SLOW;
1558
1559 static int s_ButtonFastId;
1560 CUIRect ButtonFast;
1561 View.HSplitTop(Cut: RowHeight, pTop: &ButtonFast, pBottom: &View);
1562 if(pEditor->DoButton_MenuItem(pId: &s_ButtonFastId, pText: "Fast", Checked: 0, pRect: &ButtonFast))
1563 CurveType = CURVETYPE_FAST;
1564
1565 static int s_ButtonStepId;
1566 CUIRect ButtonStep;
1567 View.HSplitTop(Cut: RowHeight, pTop: &ButtonStep, pBottom: &View);
1568 if(pEditor->DoButton_MenuItem(pId: &s_ButtonStepId, pText: "Step", Checked: 0, pRect: &ButtonStep))
1569 CurveType = CURVETYPE_STEP;
1570
1571 static int s_ButtonSmoothId;
1572 CUIRect ButtonSmooth;
1573 View.HSplitTop(Cut: RowHeight, pTop: &ButtonSmooth, pBottom: &View);
1574 if(pEditor->DoButton_MenuItem(pId: &s_ButtonSmoothId, pText: "Smooth", Checked: 0, pRect: &ButtonSmooth))
1575 CurveType = CURVETYPE_SMOOTH;
1576
1577 std::vector<std::shared_ptr<IEditorAction>> vpActions;
1578
1579 if(CurveType >= 0)
1580 {
1581 std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes.at(n: pEditor->m_SelectedEnvelope);
1582
1583 for(int c = 0; c < pEnvelope->GetChannels(); c++)
1584 {
1585 int FirstSelectedIndex = pEnvelope->m_vPoints.size();
1586 int LastSelectedIndex = -1;
1587 for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints)
1588 {
1589 if(SelectedChannel == c)
1590 {
1591 FirstSelectedIndex = minimum(a: FirstSelectedIndex, b: SelectedIndex);
1592 LastSelectedIndex = maximum(a: LastSelectedIndex, b: SelectedIndex);
1593 }
1594 }
1595
1596 if(FirstSelectedIndex < (int)pEnvelope->m_vPoints.size() && LastSelectedIndex >= 0 && FirstSelectedIndex != LastSelectedIndex)
1597 {
1598 CEnvPoint FirstPoint = pEnvelope->m_vPoints[FirstSelectedIndex];
1599 CEnvPoint LastPoint = pEnvelope->m_vPoints[LastSelectedIndex];
1600
1601 CEnvelope HelperEnvelope(1);
1602 HelperEnvelope.AddPoint(Time: FirstPoint.m_Time, v0: FirstPoint.m_aValues[c]);
1603 HelperEnvelope.AddPoint(Time: LastPoint.m_Time, v0: LastPoint.m_aValues[c]);
1604 HelperEnvelope.m_vPoints[0].m_Curvetype = CurveType;
1605
1606 for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints)
1607 {
1608 if(SelectedChannel == c)
1609 {
1610 if(SelectedIndex != FirstSelectedIndex && SelectedIndex != LastSelectedIndex)
1611 {
1612 CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex];
1613 ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
1614 HelperEnvelope.Eval(Time: CurrentPoint.m_Time / 1000.0f, Result&: Channels, Channels: 1);
1615 int PrevValue = CurrentPoint.m_aValues[c];
1616 CurrentPoint.m_aValues[c] = f2fx(v: Channels.r);
1617 vpActions.push_back(x: std::make_shared<CEditorActionEnvelopeEditPoint>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: CEditorActionEnvelopeEditPoint::EEditType::VALUE, args&: PrevValue, args&: CurrentPoint.m_aValues[c]));
1618 }
1619 }
1620 }
1621 }
1622 }
1623
1624 if(!vpActions.empty())
1625 {
1626 pEditor->m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args&: pEditor, args&: vpActions, args: "Project points"));
1627 }
1628
1629 pEditor->m_Map.OnModify();
1630 return CUi::POPUP_CLOSE_CURRENT;
1631 }
1632
1633 return CUi::POPUP_KEEP_OPEN;
1634}
1635
1636static const auto &&gs_ModifyIndexDeleted = [](int DeletedIndex) {
1637 return [DeletedIndex](int *pIndex) {
1638 if(*pIndex == DeletedIndex)
1639 *pIndex = -1;
1640 else if(*pIndex > DeletedIndex)
1641 *pIndex = *pIndex - 1;
1642 };
1643};
1644
1645CUi::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, bool Active)
1646{
1647 CEditor *pEditor = static_cast<CEditor *>(pContext);
1648
1649 static int s_ExternalButton = 0;
1650 static int s_ReaddButton = 0;
1651 static int s_ReplaceButton = 0;
1652 static int s_RemoveButton = 0;
1653 static int s_ExportButton = 0;
1654
1655 const float RowHeight = 12.0f;
1656
1657 CUIRect Slot;
1658 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1659 std::shared_ptr<CEditorImage> pImg = pEditor->m_Map.m_vpImages[pEditor->m_SelectedImage];
1660
1661 if(!pImg->m_External)
1662 {
1663 CUIRect Label, EditBox;
1664
1665 static CLineInput s_RenameInput;
1666
1667 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
1668 Slot.VSplitLeft(Cut: 35.0f, pLeft: &Label, pRight: &Slot);
1669 Slot.VSplitLeft(Cut: RowHeight - 2.0f, pLeft: nullptr, pRight: &EditBox);
1670 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1671
1672 s_RenameInput.SetBuffer(pStr: pImg->m_aName, MaxSize: sizeof(pImg->m_aName));
1673 if(pEditor->DoEditBox(pLineInput: &s_RenameInput, pRect: &EditBox, FontSize: RowHeight - 2.0f))
1674 pEditor->m_Map.OnModify();
1675
1676 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1677 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1678 }
1679
1680 if(pImg->m_External)
1681 {
1682 if(pEditor->DoButton_MenuItem(pId: &s_ExternalButton, pText: "Embed", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Embeds the image into the map file."))
1683 {
1684 pImg->m_External = 0;
1685 return CUi::POPUP_CLOSE_CURRENT;
1686 }
1687 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1688 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1689 }
1690 else if(CEditor::IsVanillaImage(pImage: pImg->m_aName))
1691 {
1692 if(pEditor->DoButton_MenuItem(pId: &s_ExternalButton, pText: "Make external", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the image from the map file."))
1693 {
1694 pImg->m_External = 1;
1695 return CUi::POPUP_CLOSE_CURRENT;
1696 }
1697 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1698 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1699 }
1700
1701 static CUi::SSelectionPopupContext s_SelectionPopupContext;
1702 static CScrollRegion s_SelectionPopupScrollRegion;
1703 s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
1704 if(pEditor->DoButton_MenuItem(pId: &s_ReaddButton, pText: "Readd", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Reloads the image from the mapres folder"))
1705 {
1706 char aFilename[IO_MAX_PATH_LENGTH];
1707 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s.png", pImg->m_aName);
1708 s_SelectionPopupContext.Reset();
1709 std::set<std::string> EntriesSet;
1710 pEditor->Storage()->FindFiles(pFilename: aFilename, pPath: "mapres", Type: IStorage::TYPE_ALL, pEntries: &EntriesSet);
1711 for(const auto &Entry : EntriesSet)
1712 s_SelectionPopupContext.m_vEntries.push_back(x: Entry);
1713 if(s_SelectionPopupContext.m_vEntries.empty())
1714 {
1715 pEditor->ShowFileDialogError(pFormat: "Error: could not find image '%s' in the mapres folder.", aFilename);
1716 }
1717 else if(s_SelectionPopupContext.m_vEntries.size() == 1)
1718 {
1719 s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
1720 }
1721 else
1722 {
1723 str_copy(dst&: s_SelectionPopupContext.m_aMessage, src: "Select the wanted image:");
1724 pEditor->Ui()->ShowPopupSelection(X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), pContext: &s_SelectionPopupContext);
1725 }
1726 }
1727 if(s_SelectionPopupContext.m_pSelection != nullptr)
1728 {
1729 const bool WasExternal = pImg->m_External;
1730 const bool Result = pEditor->ReplaceImage(pFilename: s_SelectionPopupContext.m_pSelection->c_str(), StorageType: IStorage::TYPE_ALL, CheckDuplicate: false);
1731 pImg->m_External = WasExternal;
1732 s_SelectionPopupContext.Reset();
1733 return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN;
1734 }
1735
1736 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1737 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1738 if(pEditor->DoButton_MenuItem(pId: &s_ReplaceButton, pText: "Replace", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Replaces the image with a new one"))
1739 {
1740 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_IMG, pTitle: "Replace Image", pButtonText: "Replace", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: ReplaceImageCallback, pUser: pEditor);
1741 return CUi::POPUP_CLOSE_CURRENT;
1742 }
1743
1744 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1745 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1746 if(pEditor->DoButton_MenuItem(pId: &s_RemoveButton, pText: "Remove", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the image from the map"))
1747 {
1748 pEditor->m_Map.m_vpImages.erase(position: pEditor->m_Map.m_vpImages.begin() + pEditor->m_SelectedImage);
1749 pEditor->m_Map.ModifyImageIndex(pfnFunc: gs_ModifyIndexDeleted(pEditor->m_SelectedImage));
1750 return CUi::POPUP_CLOSE_CURRENT;
1751 }
1752
1753 if(!pImg->m_External)
1754 {
1755 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1756 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1757 if(pEditor->DoButton_MenuItem(pId: &s_ExportButton, pText: "Export", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Export the image"))
1758 {
1759 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_IMG, pTitle: "Save image", pButtonText: "Save", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: CallbackSaveImage, pUser: pEditor);
1760 pEditor->m_FileDialogFileNameInput.Set(pImg->m_aName);
1761 return CUi::POPUP_CLOSE_CURRENT;
1762 }
1763 }
1764
1765 return CUi::POPUP_KEEP_OPEN;
1766}
1767
1768CUi::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View, bool Active)
1769{
1770 CEditor *pEditor = static_cast<CEditor *>(pContext);
1771
1772 static int s_ReaddButton = 0;
1773 static int s_ReplaceButton = 0;
1774 static int s_RemoveButton = 0;
1775 static int s_ExportButton = 0;
1776
1777 const float RowHeight = 12.0f;
1778
1779 CUIRect Slot;
1780 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1781 std::shared_ptr<CEditorSound> pSound = pEditor->m_Map.m_vpSounds[pEditor->m_SelectedSound];
1782
1783 static CUi::SSelectionPopupContext s_SelectionPopupContext;
1784 static CScrollRegion s_SelectionPopupScrollRegion;
1785 s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
1786
1787 CUIRect Label, EditBox;
1788
1789 static CLineInput s_RenameInput;
1790
1791 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
1792 Slot.VSplitLeft(Cut: 35.0f, pLeft: &Label, pRight: &Slot);
1793 Slot.VSplitLeft(Cut: RowHeight - 2.0f, pLeft: nullptr, pRight: &EditBox);
1794 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1795
1796 s_RenameInput.SetBuffer(pStr: pSound->m_aName, MaxSize: sizeof(pSound->m_aName));
1797 if(pEditor->DoEditBox(pLineInput: &s_RenameInput, pRect: &EditBox, FontSize: RowHeight - 2.0f))
1798 pEditor->m_Map.OnModify();
1799
1800 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1801 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1802
1803 if(pEditor->DoButton_MenuItem(pId: &s_ReaddButton, pText: "Readd", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Reloads the sound from the mapres folder"))
1804 {
1805 char aFilename[IO_MAX_PATH_LENGTH];
1806 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s.opus", pSound->m_aName);
1807 s_SelectionPopupContext.Reset();
1808 std::set<std::string> EntriesSet;
1809 pEditor->Storage()->FindFiles(pFilename: aFilename, pPath: "mapres", Type: IStorage::TYPE_ALL, pEntries: &EntriesSet);
1810 for(const auto &Entry : EntriesSet)
1811 s_SelectionPopupContext.m_vEntries.push_back(x: Entry);
1812 if(s_SelectionPopupContext.m_vEntries.empty())
1813 {
1814 pEditor->ShowFileDialogError(pFormat: "Error: could not find sound '%s' in the mapres folder.", aFilename);
1815 }
1816 else if(s_SelectionPopupContext.m_vEntries.size() == 1)
1817 {
1818 s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
1819 }
1820 else
1821 {
1822 str_copy(dst&: s_SelectionPopupContext.m_aMessage, src: "Select the wanted sound:");
1823 pEditor->Ui()->ShowPopupSelection(X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), pContext: &s_SelectionPopupContext);
1824 }
1825 }
1826 if(s_SelectionPopupContext.m_pSelection != nullptr)
1827 {
1828 const bool Result = pEditor->ReplaceSound(pFileName: s_SelectionPopupContext.m_pSelection->c_str(), StorageType: IStorage::TYPE_ALL, CheckDuplicate: false);
1829 s_SelectionPopupContext.Reset();
1830 return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN;
1831 }
1832
1833 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1834 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1835 if(pEditor->DoButton_MenuItem(pId: &s_ReplaceButton, pText: "Replace", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Replaces the sound with a new one"))
1836 {
1837 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_SOUND, pTitle: "Replace sound", pButtonText: "Replace", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: ReplaceSoundCallback, pUser: pEditor);
1838 return CUi::POPUP_CLOSE_CURRENT;
1839 }
1840
1841 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1842 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1843 if(pEditor->DoButton_MenuItem(pId: &s_RemoveButton, pText: "Remove", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the sound from the map"))
1844 {
1845 pEditor->m_Map.m_vpSounds.erase(position: pEditor->m_Map.m_vpSounds.begin() + pEditor->m_SelectedSound);
1846 pEditor->m_Map.ModifySoundIndex(pfnFunc: gs_ModifyIndexDeleted(pEditor->m_SelectedSound));
1847 return CUi::POPUP_CLOSE_CURRENT;
1848 }
1849
1850 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1851 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1852 if(pEditor->DoButton_MenuItem(pId: &s_ExportButton, pText: "Export", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Export sound"))
1853 {
1854 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_SOUND, pTitle: "Save sound", pButtonText: "Save", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: CallbackSaveSound, pUser: pEditor);
1855 pEditor->m_FileDialogFileNameInput.Set(pSound->m_aName);
1856 return CUi::POPUP_CLOSE_CURRENT;
1857 }
1858
1859 return CUi::POPUP_KEEP_OPEN;
1860}
1861
1862CUi::EPopupMenuFunctionResult CEditor::PopupNewFolder(void *pContext, CUIRect View, bool Active)
1863{
1864 CEditor *pEditor = static_cast<CEditor *>(pContext);
1865
1866 CUIRect Label, ButtonBar, Button;
1867
1868 View.Margin(Cut: 10.0f, pOtherRect: &View);
1869 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
1870
1871 // title
1872 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1873 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Create new folder", Size: 20.0f, Align: TEXTALIGN_MC);
1874 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
1875
1876 // folder name
1877 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1878 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: 10.0f, Align: TEXTALIGN_ML);
1879 Label.VSplitLeft(Cut: 50.0f, pLeft: nullptr, pRight: &Button);
1880 Button.HMargin(Cut: 2.0f, pOtherRect: &Button);
1881 pEditor->DoEditBox(pLineInput: &pEditor->m_FileDialogNewFolderNameInput, pRect: &Button, FontSize: 12.0f);
1882
1883 // button bar
1884 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Button, pRight: &ButtonBar);
1885 static int s_CancelButton = 0;
1886 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
1887 return CUi::POPUP_CLOSE_CURRENT;
1888
1889 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Button);
1890 static int s_CreateButton = 0;
1891 if(pEditor->DoButton_Editor(pId: &s_CreateButton, pText: "Create", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr) || (Active && pEditor->Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER)))
1892 {
1893 // create the folder
1894 if(!pEditor->m_FileDialogNewFolderNameInput.IsEmpty())
1895 {
1896 char aBuf[IO_MAX_PATH_LENGTH];
1897 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_FileDialogNewFolderNameInput.GetString());
1898 if(pEditor->Storage()->CreateFolder(pFoldername: aBuf, Type: IStorage::TYPE_SAVE))
1899 {
1900 pEditor->FilelistPopulate(StorageType: IStorage::TYPE_SAVE);
1901 return CUi::POPUP_CLOSE_CURRENT;
1902 }
1903 else
1904 {
1905 pEditor->ShowFileDialogError(pFormat: "Failed to create the folder '%s'.", aBuf);
1906 }
1907 }
1908 }
1909
1910 return CUi::POPUP_KEEP_OPEN;
1911}
1912
1913CUi::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View, bool Active)
1914{
1915 CEditor *pEditor = static_cast<CEditor *>(pContext);
1916
1917 CUIRect Label, ButtonBar, Button;
1918
1919 View.Margin(Cut: 10.0f, pOtherRect: &View);
1920 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
1921
1922 // title
1923 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1924 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Map details", Size: 20.0f, Align: TEXTALIGN_MC);
1925 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
1926
1927 // author box
1928 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1929 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Author:", Size: 10.0f, Align: TEXTALIGN_ML);
1930 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1931 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1932 static CLineInput s_AuthorInput;
1933 s_AuthorInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aAuthor, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aAuthor));
1934 pEditor->DoEditBox(pLineInput: &s_AuthorInput, pRect: &Button, FontSize: 10.0f);
1935
1936 // version box
1937 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1938 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Version:", Size: 10.0f, Align: TEXTALIGN_ML);
1939 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1940 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1941 static CLineInput s_VersionInput;
1942 s_VersionInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aVersion, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aVersion));
1943 pEditor->DoEditBox(pLineInput: &s_VersionInput, pRect: &Button, FontSize: 10.0f);
1944
1945 // credits box
1946 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1947 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Credits:", Size: 10.0f, Align: TEXTALIGN_ML);
1948 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1949 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1950 static CLineInput s_CreditsInput;
1951 s_CreditsInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aCredits, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aCredits));
1952 pEditor->DoEditBox(pLineInput: &s_CreditsInput, pRect: &Button, FontSize: 10.0f);
1953
1954 // license box
1955 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1956 pEditor->Ui()->DoLabel(pRect: &Label, pText: "License:", Size: 10.0f, Align: TEXTALIGN_ML);
1957 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1958 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1959 static CLineInput s_LicenseInput;
1960 s_LicenseInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aLicense, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aLicense));
1961 pEditor->DoEditBox(pLineInput: &s_LicenseInput, pRect: &Button, FontSize: 10.0f);
1962
1963 // button bar
1964 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Label, pRight: &ButtonBar);
1965 static int s_CancelButton = 0;
1966 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Label, Flags: 0, pToolTip: nullptr))
1967 return CUi::POPUP_CLOSE_CURRENT;
1968
1969 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Label);
1970 static int s_ConfirmButton = 0;
1971 if(pEditor->DoButton_Editor(pId: &s_ConfirmButton, pText: "Confirm", Checked: 0, pRect: &Label, Flags: 0, pToolTip: nullptr) || (Active && pEditor->Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER)))
1972 {
1973 bool AuthorDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aAuthor, b: pEditor->m_Map.m_MapInfo.m_aAuthor) != 0;
1974 bool VersionDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aVersion, b: pEditor->m_Map.m_MapInfo.m_aVersion) != 0;
1975 bool CreditsDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aCredits, b: pEditor->m_Map.m_MapInfo.m_aCredits) != 0;
1976 bool LicenseDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aLicense, b: pEditor->m_Map.m_MapInfo.m_aLicense) != 0;
1977
1978 if(AuthorDifferent || VersionDifferent || CreditsDifferent || LicenseDifferent)
1979 pEditor->m_Map.OnModify();
1980
1981 pEditor->m_Map.m_MapInfo.Copy(Source: pEditor->m_Map.m_MapInfoTmp);
1982 return CUi::POPUP_CLOSE_CURRENT;
1983 }
1984
1985 return CUi::POPUP_KEEP_OPEN;
1986}
1987
1988CUi::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, bool Active)
1989{
1990 CEditor *pEditor = static_cast<CEditor *>(pContext);
1991
1992 const char *pTitle;
1993 const char *pMessage;
1994 char aMessageBuf[128];
1995 if(pEditor->m_PopupEventType == POPEVENT_EXIT)
1996 {
1997 pTitle = "Exit the editor";
1998 pMessage = "The map contains unsaved data, you might want to save it before you exit the editor.\n\nContinue anyway?";
1999 }
2000 else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT || pEditor->m_PopupEventType == POPEVENT_LOADDROP)
2001 {
2002 pTitle = "Load map";
2003 pMessage = "The map contains unsaved data, you might want to save it before you load a new map.\n\nContinue anyway?";
2004 }
2005 else if(pEditor->m_PopupEventType == POPEVENT_NEW)
2006 {
2007 pTitle = "New map";
2008 pMessage = "The map contains unsaved data, you might want to save it before you create a new map.\n\nContinue anyway?";
2009 }
2010 else if(pEditor->m_PopupEventType == POPEVENT_SAVE || pEditor->m_PopupEventType == POPEVENT_SAVE_COPY)
2011 {
2012 pTitle = "Save map";
2013 pMessage = "The file already exists.\n\nDo you want to overwrite the map?";
2014 }
2015 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_IMG)
2016 {
2017 pTitle = "Save image";
2018 pMessage = "The file already exists.\n\nDo you want to overwrite the image?";
2019 }
2020 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_SOUND)
2021 {
2022 pTitle = "Save sound";
2023 pMessage = "The file already exists.\n\nDo you want to overwrite the sound?";
2024 }
2025 else if(pEditor->m_PopupEventType == POPEVENT_LARGELAYER)
2026 {
2027 pTitle = "Large layer";
2028 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.";
2029 }
2030 else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES)
2031 {
2032 pTitle = "Unused tiles disabled";
2033 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.";
2034 }
2035 else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16)
2036 {
2037 pTitle = "Image width/height";
2038 pMessage = "The width or height of this image is not divisible by 16. This is required for images used in tile layers.";
2039 }
2040 else if(pEditor->m_PopupEventType == POPEVENT_IMAGE_MAX)
2041 {
2042 pTitle = "Max images";
2043 str_format(buffer: aMessageBuf, buffer_size: sizeof(aMessageBuf), format: "The client only allows a maximum of %" PRIzu " images.", MAX_MAPIMAGES);
2044 pMessage = aMessageBuf;
2045 }
2046 else if(pEditor->m_PopupEventType == POPEVENT_SOUND_MAX)
2047 {
2048 pTitle = "Max sounds";
2049 str_format(buffer: aMessageBuf, buffer_size: sizeof(aMessageBuf), format: "The client only allows a maximum of %" PRIzu " sounds.", MAX_MAPSOUNDS);
2050 pMessage = aMessageBuf;
2051 }
2052 else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
2053 {
2054 pTitle = "Place border tiles";
2055 pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?";
2056 }
2057 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
2058 {
2059 pTitle = "Big image";
2060 pMessage = "The selected image is big. Converting it to tileart may take some time.\n\nContinue anyway?";
2061 }
2062 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2063 {
2064 pTitle = "Many colors";
2065 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?";
2066 }
2067 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_TOO_MANY_COLORS)
2068 {
2069 pTitle = "Too many colors";
2070 pMessage = "The client only supports 64 images but more would be needed to add the selected image as tileart.";
2071 }
2072 else
2073 {
2074 dbg_assert(false, "m_PopupEventType invalid");
2075 return CUi::POPUP_CLOSE_CURRENT;
2076 }
2077
2078 CUIRect Label, ButtonBar, Button;
2079
2080 View.Margin(Cut: 10.0f, pOtherRect: &View);
2081 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
2082
2083 // title
2084 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
2085 pEditor->Ui()->DoLabel(pRect: &Label, pText: pTitle, Size: 20.0f, Align: TEXTALIGN_MC);
2086
2087 // message
2088 SLabelProperties Props;
2089 Props.m_MaxWidth = View.w;
2090 pEditor->Ui()->DoLabel(pRect: &View, pText: pMessage, Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: Props);
2091
2092 // button bar
2093 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Button, pRight: &ButtonBar);
2094 if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER &&
2095 pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES &&
2096 pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 &&
2097 pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX &&
2098 pEditor->m_PopupEventType != POPEVENT_SOUND_MAX &&
2099 pEditor->m_PopupEventType != POPEVENT_PIXELART_TOO_MANY_COLORS)
2100 {
2101 static int s_CancelButton = 0;
2102 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2103 {
2104 if(pEditor->m_PopupEventType == POPEVENT_LOADDROP)
2105 pEditor->m_aFileNamePending[0] = 0;
2106 pEditor->m_PopupEventWasActivated = false;
2107
2108 if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE || pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2109 {
2110 pEditor->m_TileartImageInfo.Free();
2111 }
2112
2113 return CUi::POPUP_CLOSE_CURRENT;
2114 }
2115 }
2116
2117 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Button);
2118 static int s_ConfirmButton = 0;
2119 if(pEditor->DoButton_Editor(pId: &s_ConfirmButton, pText: "Confirm", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr) || (Active && pEditor->Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ENTER)))
2120 {
2121 if(pEditor->m_PopupEventType == POPEVENT_EXIT)
2122 {
2123 pEditor->OnClose();
2124 g_Config.m_ClEditor = 0;
2125 }
2126 else if(pEditor->m_PopupEventType == POPEVENT_LOAD)
2127 {
2128 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_MAP, pTitle: "Load map", pButtonText: "Load", pBasepath: "maps", FilenameAsDefault: false, pfnFunc: CEditor::CallbackOpenMap, pUser: pEditor);
2129 }
2130 else if(pEditor->m_PopupEventType == POPEVENT_LOADCURRENT)
2131 {
2132 pEditor->LoadCurrentMap();
2133 }
2134 else if(pEditor->m_PopupEventType == POPEVENT_LOADDROP)
2135 {
2136 int Result = pEditor->Load(pFilename: pEditor->m_aFileNamePending, StorageType: IStorage::TYPE_ALL_OR_ABSOLUTE);
2137 if(!Result)
2138 dbg_msg(sys: "editor", fmt: "editing passed map file '%s' failed", pEditor->m_aFileNamePending);
2139 pEditor->m_aFileNamePending[0] = 0;
2140 }
2141 else if(pEditor->m_PopupEventType == POPEVENT_NEW)
2142 {
2143 pEditor->Reset();
2144 pEditor->m_aFileName[0] = 0;
2145 }
2146 else if(pEditor->m_PopupEventType == POPEVENT_SAVE)
2147 {
2148 CallbackSaveMap(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2149 return CUi::POPUP_CLOSE_CURRENT;
2150 }
2151 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_COPY)
2152 {
2153 CallbackSaveCopyMap(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2154 return CUi::POPUP_CLOSE_CURRENT;
2155 }
2156 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_IMG)
2157 {
2158 CallbackSaveImage(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2159 return CUi::POPUP_CLOSE_CURRENT;
2160 }
2161 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_SOUND)
2162 {
2163 CallbackSaveSound(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2164 return CUi::POPUP_CLOSE_CURRENT;
2165 }
2166 else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
2167 {
2168 pEditor->PlaceBorderTiles();
2169 }
2170 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
2171 {
2172 pEditor->TileartCheckColors();
2173 }
2174 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2175 {
2176 pEditor->AddTileart();
2177 }
2178 pEditor->m_PopupEventWasActivated = false;
2179 return CUi::POPUP_CLOSE_CURRENT;
2180 }
2181
2182 return CUi::POPUP_KEEP_OPEN;
2183}
2184
2185static int g_SelectImageSelected = -100;
2186static int g_SelectImageCurrent = -100;
2187
2188CUi::EPopupMenuFunctionResult CEditor::PopupSelectImage(void *pContext, CUIRect View, bool Active)
2189{
2190 CEditor *pEditor = static_cast<CEditor *>(pContext);
2191
2192 CUIRect ButtonBar, ImageView;
2193 View.VSplitLeft(Cut: 150.0f, pLeft: &ButtonBar, pRight: &View);
2194 View.Margin(Cut: 10.0f, pOtherRect: &ImageView);
2195
2196 int ShowImage = g_SelectImageCurrent;
2197
2198 const float ButtonHeight = 12.0f;
2199 const float ButtonMargin = 2.0f;
2200
2201 static CListBox s_ListBox;
2202 s_ListBox.DoStart(RowHeight: ButtonHeight, NumItems: pEditor->m_Map.m_vpImages.size() + 1, ItemsPerRow: 1, RowsPerScroll: 4, SelectedIndex: g_SelectImageCurrent + 1, pRect: &ButtonBar, Background: false);
2203 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2204
2205 for(int i = 0; i <= (int)pEditor->m_Map.m_vpImages.size(); i++)
2206 {
2207 static int s_NoneButton = 0;
2208 CListboxItem Item = s_ListBox.DoNextItem(pId: i == 0 ? (void *)&s_NoneButton : &pEditor->m_Map.m_vpImages[i - 1], Selected: (i - 1) == g_SelectImageCurrent, CornerRadius: 3.0f);
2209 if(!Item.m_Visible)
2210 continue;
2211
2212 if(pEditor->Ui()->MouseInside(pRect: &Item.m_Rect))
2213 ShowImage = i - 1;
2214
2215 CUIRect Label;
2216 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2217
2218 SLabelProperties Props;
2219 Props.m_MaxWidth = Label.w;
2220 Props.m_EllipsisAtEnd = true;
2221 pEditor->Ui()->DoLabel(pRect: &Label, pText: i == 0 ? "None" : pEditor->m_Map.m_vpImages[i - 1]->m_aName, Size: EditorFontSizes::MENU, Align: TEXTALIGN_ML, LabelProps: Props);
2222 }
2223
2224 int NewSelected = s_ListBox.DoEnd() - 1;
2225 if(NewSelected != g_SelectImageCurrent)
2226 g_SelectImageSelected = NewSelected;
2227
2228 if(ShowImage >= 0 && (size_t)ShowImage < pEditor->m_Map.m_vpImages.size())
2229 {
2230 if(ImageView.h < ImageView.w)
2231 ImageView.w = ImageView.h;
2232 else
2233 ImageView.h = ImageView.w;
2234 float Max = (float)(maximum(a: pEditor->m_Map.m_vpImages[ShowImage]->m_Width, b: pEditor->m_Map.m_vpImages[ShowImage]->m_Height));
2235 ImageView.w *= pEditor->m_Map.m_vpImages[ShowImage]->m_Width / Max;
2236 ImageView.h *= pEditor->m_Map.m_vpImages[ShowImage]->m_Height / Max;
2237 pEditor->Graphics()->TextureSet(Texture: pEditor->m_Map.m_vpImages[ShowImage]->m_Texture);
2238 pEditor->Graphics()->BlendNormal();
2239 pEditor->Graphics()->WrapClamp();
2240 pEditor->Graphics()->QuadsBegin();
2241 IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h);
2242 pEditor->Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
2243 pEditor->Graphics()->QuadsEnd();
2244 pEditor->Graphics()->WrapNormal();
2245 }
2246
2247 return CUi::POPUP_KEEP_OPEN;
2248}
2249
2250void CEditor::PopupSelectImageInvoke(int Current, float x, float y)
2251{
2252 static SPopupMenuId s_PopupSelectImageId;
2253 g_SelectImageSelected = -100;
2254 g_SelectImageCurrent = Current;
2255 Ui()->DoPopupMenu(pId: &s_PopupSelectImageId, X: x, Y: y, Width: 450, Height: 300, pContext: this, pfnFunc: PopupSelectImage);
2256}
2257
2258int CEditor::PopupSelectImageResult()
2259{
2260 if(g_SelectImageSelected == -100)
2261 return -100;
2262
2263 g_SelectImageCurrent = g_SelectImageSelected;
2264 g_SelectImageSelected = -100;
2265 return g_SelectImageCurrent;
2266}
2267
2268static int g_SelectSoundSelected = -100;
2269static int g_SelectSoundCurrent = -100;
2270
2271CUi::EPopupMenuFunctionResult CEditor::PopupSelectSound(void *pContext, CUIRect View, bool Active)
2272{
2273 CEditor *pEditor = static_cast<CEditor *>(pContext);
2274
2275 const float ButtonHeight = 12.0f;
2276 const float ButtonMargin = 2.0f;
2277
2278 static CListBox s_ListBox;
2279 s_ListBox.DoStart(RowHeight: ButtonHeight, NumItems: pEditor->m_Map.m_vpSounds.size() + 1, ItemsPerRow: 1, RowsPerScroll: 4, SelectedIndex: g_SelectSoundCurrent + 1, pRect: &View, Background: false);
2280 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2281
2282 for(int i = 0; i <= (int)pEditor->m_Map.m_vpSounds.size(); i++)
2283 {
2284 static int s_NoneButton = 0;
2285 CListboxItem Item = s_ListBox.DoNextItem(pId: i == 0 ? (void *)&s_NoneButton : &pEditor->m_Map.m_vpSounds[i - 1], Selected: (i - 1) == g_SelectSoundCurrent, CornerRadius: 3.0f);
2286 if(!Item.m_Visible)
2287 continue;
2288
2289 CUIRect Label;
2290 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2291
2292 SLabelProperties Props;
2293 Props.m_MaxWidth = Label.w;
2294 Props.m_EllipsisAtEnd = true;
2295 pEditor->Ui()->DoLabel(pRect: &Label, pText: i == 0 ? "None" : pEditor->m_Map.m_vpSounds[i - 1]->m_aName, Size: EditorFontSizes::MENU, Align: TEXTALIGN_ML, LabelProps: Props);
2296 }
2297
2298 int NewSelected = s_ListBox.DoEnd() - 1;
2299 if(NewSelected != g_SelectSoundCurrent)
2300 g_SelectSoundSelected = NewSelected;
2301
2302 return CUi::POPUP_KEEP_OPEN;
2303}
2304
2305void CEditor::PopupSelectSoundInvoke(int Current, float x, float y)
2306{
2307 static SPopupMenuId s_PopupSelectSoundId;
2308 g_SelectSoundSelected = -100;
2309 g_SelectSoundCurrent = Current;
2310 Ui()->DoPopupMenu(pId: &s_PopupSelectSoundId, X: x, Y: y, Width: 150, Height: 300, pContext: this, pfnFunc: PopupSelectSound);
2311}
2312
2313int CEditor::PopupSelectSoundResult()
2314{
2315 if(g_SelectSoundSelected == -100)
2316 return -100;
2317
2318 g_SelectSoundCurrent = g_SelectSoundSelected;
2319 g_SelectSoundSelected = -100;
2320 return g_SelectSoundCurrent;
2321}
2322
2323static int s_GametileOpSelected = -1;
2324
2325static const char *s_apGametileOpButtonNames[] = {
2326 "Air",
2327 "Hookable",
2328 "Death",
2329 "Unhookable",
2330 "Hookthrough",
2331 "Freeze",
2332 "Unfreeze",
2333 "Deep Freeze",
2334 "Deep Unfreeze",
2335 "Blue Check-Tele",
2336 "Red Check-Tele",
2337 "Live Freeze",
2338 "Live Unfreeze",
2339};
2340
2341CUi::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUIRect View, bool Active)
2342{
2343 CEditor *pEditor = static_cast<CEditor *>(pContext);
2344
2345 const int PreviousSelected = s_GametileOpSelected;
2346
2347 CUIRect Button;
2348 for(size_t i = 0; i < std::size(s_apGametileOpButtonNames); ++i)
2349 {
2350 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
2351 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2352 if(pEditor->DoButton_Editor(pId: &s_apGametileOpButtonNames[i], pText: s_apGametileOpButtonNames[i], Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2353 s_GametileOpSelected = i;
2354 }
2355
2356 return s_GametileOpSelected == PreviousSelected ? CUi::POPUP_KEEP_OPEN : CUi::POPUP_CLOSE_CURRENT;
2357}
2358
2359void CEditor::PopupSelectGametileOpInvoke(float x, float y)
2360{
2361 static SPopupMenuId s_PopupSelectGametileOpId;
2362 s_GametileOpSelected = -1;
2363 Ui()->DoPopupMenu(pId: &s_PopupSelectGametileOpId, X: x, Y: y, Width: 120.0f, Height: std::size(s_apGametileOpButtonNames) * 14.0f + 10.0f, pContext: this, pfnFunc: PopupSelectGametileOp);
2364}
2365
2366int CEditor::PopupSelectGameTileOpResult()
2367{
2368 if(s_GametileOpSelected < 0)
2369 return -1;
2370
2371 int Result = s_GametileOpSelected;
2372 s_GametileOpSelected = -1;
2373 return Result;
2374}
2375
2376static int s_AutoMapConfigSelected = -100;
2377static int s_AutoMapConfigCurrent = -100;
2378
2379CUi::EPopupMenuFunctionResult CEditor::PopupSelectConfigAutoMap(void *pContext, CUIRect View, bool Active)
2380{
2381 CEditor *pEditor = static_cast<CEditor *>(pContext);
2382 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: pEditor->GetSelectedLayer(Index: 0));
2383 CAutoMapper *pAutoMapper = &pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper;
2384
2385 const float ButtonHeight = 12.0f;
2386 const float ButtonMargin = 2.0f;
2387
2388 static CListBox s_ListBox;
2389 s_ListBox.DoStart(RowHeight: ButtonHeight, NumItems: pAutoMapper->ConfigNamesNum() + 1, ItemsPerRow: 1, RowsPerScroll: 4, SelectedIndex: s_AutoMapConfigCurrent + 1, pRect: &View, Background: false);
2390 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2391
2392 for(int i = 0; i < pAutoMapper->ConfigNamesNum() + 1; i++)
2393 {
2394 static int s_NoneButton = 0;
2395 CListboxItem Item = s_ListBox.DoNextItem(pId: i == 0 ? (void *)&s_NoneButton : pAutoMapper->GetConfigName(Index: i - 1), Selected: (i - 1) == s_AutoMapConfigCurrent, CornerRadius: 3.0f);
2396 if(!Item.m_Visible)
2397 continue;
2398
2399 CUIRect Label;
2400 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2401
2402 SLabelProperties Props;
2403 Props.m_MaxWidth = Label.w;
2404 Props.m_EllipsisAtEnd = true;
2405 pEditor->Ui()->DoLabel(pRect: &Label, pText: i == 0 ? "None" : pAutoMapper->GetConfigName(Index: i - 1), Size: EditorFontSizes::MENU, Align: TEXTALIGN_ML, LabelProps: Props);
2406 }
2407
2408 int NewSelected = s_ListBox.DoEnd() - 1;
2409 if(NewSelected != s_AutoMapConfigCurrent)
2410 s_AutoMapConfigSelected = NewSelected;
2411
2412 return CUi::POPUP_KEEP_OPEN;
2413}
2414
2415void CEditor::PopupSelectConfigAutoMapInvoke(int Current, float x, float y)
2416{
2417 static SPopupMenuId s_PopupSelectConfigAutoMapId;
2418 s_AutoMapConfigSelected = -100;
2419 s_AutoMapConfigCurrent = Current;
2420 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: GetSelectedLayer(Index: 0));
2421 const int ItemCount = minimum(a: m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), b: 10);
2422 // Width for buttons is 120, 15 is the scrollbar width, 2 is the margin between both.
2423 Ui()->DoPopupMenu(pId: &s_PopupSelectConfigAutoMapId, X: x, Y: y, Width: 120.0f + 15.0f + 2.0f, Height: 26.0f + 14.0f * ItemCount, pContext: this, pfnFunc: PopupSelectConfigAutoMap);
2424}
2425
2426int CEditor::PopupSelectConfigAutoMapResult()
2427{
2428 if(s_AutoMapConfigSelected == -100)
2429 return -100;
2430
2431 s_AutoMapConfigCurrent = s_AutoMapConfigSelected;
2432 s_AutoMapConfigSelected = -100;
2433 return s_AutoMapConfigCurrent;
2434}
2435
2436// DDRace
2437
2438CUi::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, bool Active)
2439{
2440 CEditor *pEditor = static_cast<CEditor *>(pContext);
2441
2442 static int s_PreviousTeleNumber;
2443 static int s_PreviousCheckpointNumber;
2444 static int s_PreviousViewTeleNumber;
2445
2446 CUIRect NumberPicker;
2447 CUIRect FindEmptySlot;
2448 CUIRect FindFreeTeleSlot, FindFreeCheckpointSlot, FindFreeViewSlot;
2449
2450 View.VSplitRight(Cut: 15.f, pLeft: &NumberPicker, pRight: &FindEmptySlot);
2451 NumberPicker.VSplitRight(Cut: 2.f, pLeft: &NumberPicker, pRight: nullptr);
2452
2453 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeTeleSlot, pBottom: &FindEmptySlot);
2454 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeCheckpointSlot, pBottom: &FindEmptySlot);
2455 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeViewSlot, pBottom: &FindEmptySlot);
2456
2457 FindFreeTeleSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeTeleSlot);
2458 FindFreeCheckpointSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeCheckpointSlot);
2459 FindFreeViewSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeViewSlot);
2460
2461 auto ViewTele = [](CEditor *pEd) -> bool {
2462 if(!pEd->m_ViewTeleNumber)
2463 return false;
2464 int TeleX, TeleY;
2465 pEd->m_Map.m_pTeleLayer->GetPos(Number: pEd->m_ViewTeleNumber, Offset: -1, TeleX, TeleY);
2466 if(TeleX != -1 && TeleY != -1)
2467 {
2468 pEd->MapView()->SetWorldOffset({32.0f * TeleX + 0.5f, 32.0f * TeleY + 0.5f});
2469 return true;
2470 }
2471 return false;
2472 };
2473
2474 static std::vector<ColorRGBA> s_vColors = {
2475 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2476 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2477 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2478 };
2479 enum
2480 {
2481 PROP_TELE = 0,
2482 PROP_TELE_CP,
2483 PROP_TELE_VIEW,
2484 NUM_PROPS,
2485 };
2486
2487 // find next free numbers buttons
2488 {
2489 // Pressing ctrl+f will find next free numbers for both tele and checkpoints
2490
2491 static int s_NextFreeTelePid = 0;
2492 if(pEditor->DoButton_Editor(pId: &s_NextFreeTelePid, pText: "F", Checked: 0, pRect: &FindFreeTeleSlot, Flags: 0, pToolTip: "[ctrl+f] Find next free tele number") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(Key: KEY_F)))
2493 {
2494 int TeleNumber = pEditor->FindNextFreeTeleNumber();
2495
2496 if(TeleNumber != -1)
2497 pEditor->m_TeleNumber = TeleNumber;
2498 }
2499
2500 static int s_NextFreeCheckpointPid = 0;
2501 if(pEditor->DoButton_Editor(pId: &s_NextFreeCheckpointPid, pText: "F", Checked: 0, pRect: &FindFreeCheckpointSlot, Flags: 0, pToolTip: "[ctrl+f] Find next free checkpoint number") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(Key: KEY_F)))
2502 {
2503 int CPNumber = pEditor->FindNextFreeTeleNumber(IsCheckpoint: true);
2504
2505 if(CPNumber != -1)
2506 pEditor->m_TeleCheckpointNumber = CPNumber;
2507 }
2508
2509 static int s_NextFreeViewPid = 0;
2510 int btn = pEditor->DoButton_Editor(pId: &s_NextFreeViewPid, pText: "N", Checked: 0, pRect: &FindFreeViewSlot, Flags: 0, pToolTip: "[n] Show next tele with this number");
2511 if(btn || (Active && pEditor->Input()->KeyPress(Key: KEY_N)))
2512 s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2513 }
2514
2515 // number picker
2516 {
2517 CProperty aProps[] = {
2518 {.m_pName: "Number", .m_Value: pEditor->m_TeleNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2519 {.m_pName: "Checkpoint", .m_Value: pEditor->m_TeleCheckpointNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2520 {.m_pName: "View", .m_Value: pEditor->m_ViewTeleNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2521 {.m_pName: nullptr},
2522 };
2523
2524 static int s_aIds[NUM_PROPS] = {0};
2525
2526 static int NewVal = 0;
2527 int Prop = pEditor->DoProperties(pToolbox: &NumberPicker, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal, vColors: s_vColors);
2528 if(Prop == PROP_TELE)
2529 pEditor->m_TeleNumber = (NewVal - 1 + 255) % 255 + 1;
2530 else if(Prop == PROP_TELE_CP)
2531 pEditor->m_TeleCheckpointNumber = (NewVal - 1 + 255) % 255 + 1;
2532 else if(Prop == PROP_TELE_VIEW)
2533 pEditor->m_ViewTeleNumber = (NewVal - 1 + 255) % 255 + 1;
2534
2535 if(s_PreviousTeleNumber == 1 || s_PreviousTeleNumber != pEditor->m_TeleNumber)
2536 s_vColors[PROP_TELE] = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(Id: pEditor->m_TeleNumber, Checkpoint: false) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2537
2538 if(s_PreviousCheckpointNumber == 1 || s_PreviousCheckpointNumber != pEditor->m_TeleCheckpointNumber)
2539 s_vColors[PROP_TELE_CP] = pEditor->m_Map.m_pTeleLayer->ContainsElementWithId(Id: pEditor->m_TeleCheckpointNumber, Checkpoint: true) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2540
2541 if(s_PreviousViewTeleNumber != pEditor->m_ViewTeleNumber)
2542 s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2543 }
2544
2545 s_PreviousTeleNumber = pEditor->m_TeleNumber;
2546 s_PreviousCheckpointNumber = pEditor->m_TeleCheckpointNumber;
2547 s_PreviousViewTeleNumber = pEditor->m_ViewTeleNumber;
2548
2549 return CUi::POPUP_KEEP_OPEN;
2550}
2551
2552CUi::EPopupMenuFunctionResult CEditor::PopupSpeedup(void *pContext, CUIRect View, bool Active)
2553{
2554 CEditor *pEditor = static_cast<CEditor *>(pContext);
2555
2556 enum
2557 {
2558 PROP_FORCE = 0,
2559 PROP_MAXSPEED,
2560 PROP_ANGLE,
2561 NUM_PROPS
2562 };
2563
2564 CProperty aProps[] = {
2565 {.m_pName: "Force", .m_Value: pEditor->m_SpeedupForce, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2566 {.m_pName: "Max Speed", .m_Value: pEditor->m_SpeedupMaxSpeed, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2567 {.m_pName: "Angle", .m_Value: pEditor->m_SpeedupAngle, .m_Type: PROPTYPE_ANGLE_SCROLL, .m_Min: 0, .m_Max: 359},
2568 {.m_pName: nullptr},
2569 };
2570
2571 static int s_aIds[NUM_PROPS] = {0};
2572 int NewVal = 0;
2573 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2574
2575 if(Prop == PROP_FORCE)
2576 {
2577 pEditor->m_SpeedupForce = clamp(val: NewVal, lo: 1, hi: 255);
2578 }
2579 else if(Prop == PROP_MAXSPEED)
2580 {
2581 pEditor->m_SpeedupMaxSpeed = clamp(val: NewVal, lo: 0, hi: 255);
2582 }
2583 else if(Prop == PROP_ANGLE)
2584 {
2585 pEditor->m_SpeedupAngle = clamp(val: NewVal, lo: 0, hi: 359);
2586 }
2587
2588 return CUi::POPUP_KEEP_OPEN;
2589}
2590
2591CUi::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, bool Active)
2592{
2593 CEditor *pEditor = static_cast<CEditor *>(pContext);
2594
2595 CUIRect NumberPicker, FindEmptySlot, ViewEmptySlot;
2596
2597 View.VSplitRight(Cut: 15.0f, pLeft: &NumberPicker, pRight: &FindEmptySlot);
2598 NumberPicker.VSplitRight(Cut: 2.0f, pLeft: &NumberPicker, pRight: nullptr);
2599
2600 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindEmptySlot, pBottom: &ViewEmptySlot);
2601 ViewEmptySlot.HSplitTop(Cut: 13.0f, pTop: nullptr, pBottom: &ViewEmptySlot);
2602
2603 FindEmptySlot.HMargin(Cut: 1.0f, pOtherRect: &FindEmptySlot);
2604 ViewEmptySlot.HMargin(Cut: 1.0f, pOtherRect: &ViewEmptySlot);
2605
2606 auto ViewSwitch = [pEditor]() -> bool {
2607 if(!pEditor->m_ViewSwitch)
2608 return false;
2609 ivec2 SwitchPos;
2610 pEditor->m_Map.m_pSwitchLayer->GetPos(Number: pEditor->m_ViewSwitch, Offset: -1, SwitchPos);
2611 if(SwitchPos != ivec2(-1, -1))
2612 {
2613 pEditor->MapView()->SetWorldOffset({32.0f * SwitchPos.x + 0.5f, 32.0f * SwitchPos.y + 0.5f});
2614 return true;
2615 }
2616 return false;
2617 };
2618
2619 static std::vector<ColorRGBA> s_vColors = {
2620 ColorRGBA(1, 1, 1, 0.5f),
2621 ColorRGBA(1, 1, 1, 0.5f),
2622 ColorRGBA(1, 1, 1, 0.5f),
2623 };
2624
2625 enum
2626 {
2627 PROP_SWITCH_NUMBER = 0,
2628 PROP_SWITCH_DELAY,
2629 PROP_SWITCH_VIEW,
2630 NUM_PROPS,
2631 };
2632
2633 // find empty number button
2634 {
2635 static int s_EmptySlotPid = 0;
2636 if(pEditor->DoButton_Editor(pId: &s_EmptySlotPid, pText: "F", Checked: 0, pRect: &FindEmptySlot, Flags: 0, pToolTip: "[ctrl+f] Find empty slot") || (Active && pEditor->Input()->ModifierIsPressed() && pEditor->Input()->KeyPress(Key: KEY_F)))
2637 {
2638 int Number = pEditor->FindNextFreeSwitchNumber();
2639
2640 if(Number != -1)
2641 pEditor->m_SwitchNum = Number;
2642 }
2643
2644 static int s_NextViewPid = 0;
2645 int ButtonResult = pEditor->DoButton_Editor(pId: &s_NextViewPid, pText: "N", Checked: 0, pRect: &ViewEmptySlot, Flags: 0, pToolTip: "[n] Show next switcher with this number");
2646 if(ButtonResult || (Active && pEditor->Input()->KeyPress(Key: KEY_N)))
2647 s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2648 }
2649
2650 // number picker
2651 static int s_PreviousNumber = -1;
2652 static int s_PreviousView = -1;
2653 {
2654 CProperty aProps[] = {
2655 {.m_pName: "Number", .m_Value: pEditor->m_SwitchNum, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2656 {.m_pName: "Delay", .m_Value: pEditor->m_SwitchDelay, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2657 {.m_pName: "View", .m_Value: pEditor->m_ViewSwitch, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2658 {.m_pName: nullptr},
2659 };
2660
2661 static int s_aIds[NUM_PROPS] = {0};
2662 int NewVal = 0;
2663 int Prop = pEditor->DoProperties(pToolbox: &NumberPicker, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal, vColors: s_vColors);
2664
2665 if(Prop == PROP_SWITCH_NUMBER)
2666 {
2667 pEditor->m_SwitchNum = (NewVal + 256) % 256;
2668 }
2669 else if(Prop == PROP_SWITCH_DELAY)
2670 {
2671 pEditor->m_SwitchDelay = (NewVal + 256) % 256;
2672 }
2673 else if(Prop == PROP_SWITCH_VIEW)
2674 {
2675 pEditor->m_ViewSwitch = (NewVal + 256) % 256;
2676 }
2677
2678 if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_SwitchNum)
2679 s_vColors[PROP_SWITCH_NUMBER] = pEditor->m_Map.m_pSwitchLayer->ContainsElementWithId(Id: pEditor->m_SwitchNum) ? ColorRGBA(1, 0.5f, 0.5f, 0.5f) : ColorRGBA(0.5f, 1, 0.5f, 0.5f);
2680 if(s_PreviousView != pEditor->m_ViewSwitch)
2681 s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2682 }
2683
2684 s_PreviousNumber = pEditor->m_SwitchNum;
2685 s_PreviousView = pEditor->m_ViewSwitch;
2686 return CUi::POPUP_KEEP_OPEN;
2687}
2688
2689CUi::EPopupMenuFunctionResult CEditor::PopupTune(void *pContext, CUIRect View, bool Active)
2690{
2691 CEditor *pEditor = static_cast<CEditor *>(pContext);
2692
2693 enum
2694 {
2695 PROP_TUNE = 0,
2696 NUM_PROPS,
2697 };
2698
2699 CProperty aProps[] = {
2700 {.m_pName: "Zone", .m_Value: pEditor->m_TuningNum, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2701 {.m_pName: nullptr},
2702 };
2703
2704 static int s_aIds[NUM_PROPS] = {0};
2705 int NewVal = 0;
2706 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2707
2708 if(Prop == PROP_TUNE)
2709 {
2710 pEditor->m_TuningNum = (NewVal - 1 + 255) % 255 + 1;
2711 }
2712
2713 return CUi::POPUP_KEEP_OPEN;
2714}
2715
2716CUi::EPopupMenuFunctionResult CEditor::PopupGoto(void *pContext, CUIRect View, bool Active)
2717{
2718 CEditor *pEditor = static_cast<CEditor *>(pContext);
2719
2720 enum
2721 {
2722 PROP_COORD_X = 0,
2723 PROP_COORD_Y,
2724 NUM_PROPS,
2725 };
2726
2727 static ivec2 s_GotoPos(0, 0);
2728
2729 CProperty aProps[] = {
2730 {.m_pName: "X", .m_Value: s_GotoPos.x, .m_Type: PROPTYPE_INT, .m_Min: std::numeric_limits<int>::min(), .m_Max: std::numeric_limits<int>::max()},
2731 {.m_pName: "Y", .m_Value: s_GotoPos.y, .m_Type: PROPTYPE_INT, .m_Min: std::numeric_limits<int>::min(), .m_Max: std::numeric_limits<int>::max()},
2732 {.m_pName: nullptr},
2733 };
2734
2735 static int s_aIds[NUM_PROPS] = {0};
2736 int NewVal = 0;
2737 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2738
2739 if(Prop == PROP_COORD_X)
2740 {
2741 s_GotoPos.x = NewVal;
2742 }
2743 else if(Prop == PROP_COORD_Y)
2744 {
2745 s_GotoPos.y = NewVal;
2746 }
2747
2748 CUIRect Button;
2749 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
2750
2751 static int s_Button;
2752 if(pEditor->DoButton_Editor(pId: &s_Button, pText: "Go", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2753 {
2754 pEditor->MapView()->SetWorldOffset({32.0f * s_GotoPos.x + 0.5f, 32.0f * s_GotoPos.y + 0.5f});
2755 }
2756
2757 return CUi::POPUP_KEEP_OPEN;
2758}
2759
2760CUi::EPopupMenuFunctionResult CEditor::PopupEntities(void *pContext, CUIRect View, bool Active)
2761{
2762 CEditor *pEditor = static_cast<CEditor *>(pContext);
2763
2764 for(size_t i = 0; i < pEditor->m_vSelectEntitiesFiles.size(); i++)
2765 {
2766 CUIRect Button;
2767 View.HSplitTop(Cut: 14.0f, pTop: &Button, pBottom: &View);
2768
2769 const char *pName = pEditor->m_vSelectEntitiesFiles[i].c_str();
2770 if(pEditor->DoButton_MenuItem(pId: pName, pText: pName, Checked: pEditor->m_vSelectEntitiesFiles[i] == pEditor->m_SelectEntitiesImage, pRect: &Button))
2771 {
2772 if(pEditor->m_vSelectEntitiesFiles[i] != pEditor->m_SelectEntitiesImage)
2773 {
2774 pEditor->m_SelectEntitiesImage = pEditor->m_vSelectEntitiesFiles[i];
2775 pEditor->m_AllowPlaceUnusedTiles = pEditor->m_SelectEntitiesImage == "DDNet" ? 0 : -1;
2776 pEditor->m_PreventUnusedTilesWasWarned = false;
2777
2778 if(pEditor->m_EntitiesTexture.IsValid())
2779 pEditor->Graphics()->UnloadTexture(pIndex: &pEditor->m_EntitiesTexture);
2780
2781 char aBuf[IO_MAX_PATH_LENGTH];
2782 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "editor/entities/%s.png", pName);
2783 pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(pFilename: aBuf, StorageType: IStorage::TYPE_ALL, Flags: pEditor->GetTextureUsageFlag());
2784 return CUi::POPUP_CLOSE_CURRENT;
2785 }
2786 }
2787 }
2788
2789 return CUi::POPUP_KEEP_OPEN;
2790}
2791
2792CUi::EPopupMenuFunctionResult CEditor::PopupProofMode(void *pContext, CUIRect View, bool Active)
2793{
2794 CEditor *pEditor = static_cast<CEditor *>(pContext);
2795
2796 CUIRect Button;
2797 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2798 static int s_ButtonIngame;
2799 if(pEditor->DoButton_MenuItem(pId: &s_ButtonIngame, pText: "Ingame", Checked: pEditor->MapView()->ProofMode()->IsModeIngame(), pRect: &Button, Flags: 0, pToolTip: "These borders represent what a player maximum can see."))
2800 {
2801 pEditor->MapView()->ProofMode()->SetModeIngame();
2802 return CUi::POPUP_CLOSE_CURRENT;
2803 }
2804
2805 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
2806 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2807 static int s_ButtonMenu;
2808 if(pEditor->DoButton_MenuItem(pId: &s_ButtonMenu, pText: "Menu", Checked: pEditor->MapView()->ProofMode()->IsModeMenu(), pRect: &Button, Flags: 0, pToolTip: "These borders represent what will be shown in the menu."))
2809 {
2810 pEditor->MapView()->ProofMode()->SetModeMenu();
2811 return CUi::POPUP_CLOSE_CURRENT;
2812 }
2813
2814 return CUi::POPUP_KEEP_OPEN;
2815}
2816
2817CUi::EPopupMenuFunctionResult CEditor::PopupAnimateSettings(void *pContext, CUIRect View, bool Active)
2818{
2819 CEditor *pEditor = static_cast<CEditor *>(pContext);
2820
2821 constexpr float MIN_ANIM_SPEED = 0.001f;
2822 constexpr float MAX_ANIM_SPEED = 1000000.0f;
2823
2824 CUIRect Row, Label, ButtonDecrease, EditBox, ButtonIncrease, ButtonReset;
2825 View.HSplitTop(Cut: 13.0f, pTop: &Row, pBottom: &View);
2826 Row.VSplitMid(pLeft: &Label, pRight: &Row);
2827 Row.HMargin(Cut: 1.0f, pOtherRect: &Row);
2828 Row.VSplitLeft(Cut: 10.0f, pLeft: &ButtonDecrease, pRight: &Row);
2829 Row.VSplitRight(Cut: 10.0f, pLeft: &EditBox, pRight: &ButtonIncrease);
2830 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &ButtonReset);
2831 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Speed", Size: 10.0f, Align: TEXTALIGN_ML);
2832
2833 static char s_DecreaseButton;
2834 if(pEditor->DoButton_FontIcon(pId: &s_DecreaseButton, pText: FONT_ICON_MINUS, Checked: 0, pRect: &ButtonDecrease, Flags: 0, pToolTip: "Decrease animation speed", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
2835 {
2836 pEditor->m_AnimateSpeed -= pEditor->m_AnimateSpeed <= 1.0f ? 0.1f : 0.5f;
2837 pEditor->m_AnimateSpeed = maximum(a: pEditor->m_AnimateSpeed, b: MIN_ANIM_SPEED);
2838 pEditor->m_AnimateUpdatePopup = true;
2839 }
2840
2841 static char s_IncreaseButton;
2842 if(pEditor->DoButton_FontIcon(pId: &s_IncreaseButton, pText: FONT_ICON_PLUS, Checked: 0, pRect: &ButtonIncrease, Flags: 0, pToolTip: "Increase animation speed", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
2843 {
2844 if(pEditor->m_AnimateSpeed < 0.1f)
2845 pEditor->m_AnimateSpeed = 0.1f;
2846 else
2847 pEditor->m_AnimateSpeed += pEditor->m_AnimateSpeed < 1.0f ? 0.1f : 0.5f;
2848 pEditor->m_AnimateSpeed = minimum(a: pEditor->m_AnimateSpeed, b: MAX_ANIM_SPEED);
2849 pEditor->m_AnimateUpdatePopup = true;
2850 }
2851
2852 static char s_DefaultButton;
2853 if(pEditor->DoButton_Ex(pId: &s_DefaultButton, pText: "Default", Checked: 0, pRect: &ButtonReset, Flags: 0, pToolTip: "Normal animation speed", Corners: IGraphics::CORNER_ALL))
2854 {
2855 pEditor->m_AnimateSpeed = 1.0f;
2856 pEditor->m_AnimateUpdatePopup = true;
2857 }
2858
2859 static CLineInputNumber s_SpeedInput;
2860 if(pEditor->m_AnimateUpdatePopup)
2861 {
2862 s_SpeedInput.SetFloat(pEditor->m_AnimateSpeed);
2863 pEditor->m_AnimateUpdatePopup = false;
2864 }
2865
2866 if(pEditor->DoEditBox(pLineInput: &s_SpeedInput, pRect: &EditBox, FontSize: 10.0f, Corners: IGraphics::CORNER_NONE, pToolTip: "The animation speed"))
2867 {
2868 pEditor->m_AnimateSpeed = clamp(val: s_SpeedInput.GetFloat(), lo: MIN_ANIM_SPEED, hi: MAX_ANIM_SPEED);
2869 }
2870
2871 return CUi::POPUP_KEEP_OPEN;
2872}
2873