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 && (State == EEditState::END || State == EEditState::ONE_GO))
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 && (State == EEditState::END || State == EEditState::ONE_GO))
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 [State, Prop] = pEditor->DoPropertiesWithState<EQuadProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
983 if(Prop != EQuadProp::PROP_NONE && (State == EEditState::START || State == EEditState::ONE_GO))
984 {
985 pEditor->m_QuadTracker.BeginQuadPropTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads, Prop);
986 }
987
988 const float OffsetX = i2fx(v: NewVal) - pCurrentQuad->m_aPoints[4].x;
989 const float OffsetY = i2fx(v: NewVal) - pCurrentQuad->m_aPoints[4].y;
990
991 if(Prop == EQuadProp::PROP_ORDER && pLayer)
992 {
993 const int QuadIndex = pLayer->SwapQuads(Index0: pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex], Index1: NewVal);
994 pEditor->m_vSelectedQuads[pEditor->m_SelectedQuadIndex] = QuadIndex;
995 }
996
997 for(auto &pQuad : vpQuads)
998 {
999 if(Prop == EQuadProp::PROP_POS_X)
1000 {
1001 for(auto &Point : pQuad->m_aPoints)
1002 Point.x += OffsetX;
1003 }
1004 else if(Prop == EQuadProp::PROP_POS_Y)
1005 {
1006 for(auto &Point : pQuad->m_aPoints)
1007 Point.y += OffsetY;
1008 }
1009 else if(Prop == EQuadProp::PROP_POS_ENV)
1010 {
1011 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1012 int StepDirection = Index < pQuad->m_PosEnv ? -1 : 1;
1013 if(StepDirection != 0)
1014 {
1015 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1016 {
1017 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
1018 {
1019 pQuad->m_PosEnv = Index;
1020 break;
1021 }
1022 }
1023 }
1024 }
1025 else if(Prop == EQuadProp::PROP_POS_ENV_OFFSET)
1026 {
1027 pQuad->m_PosEnvOffset = NewVal;
1028 }
1029 else if(Prop == EQuadProp::PROP_COLOR_ENV)
1030 {
1031 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1032 int StepDirection = Index < pQuad->m_ColorEnv ? -1 : 1;
1033 if(StepDirection != 0)
1034 {
1035 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1036 {
1037 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 4)
1038 {
1039 pQuad->m_ColorEnv = Index;
1040 break;
1041 }
1042 }
1043 }
1044 }
1045 else if(Prop == EQuadProp::PROP_COLOR_ENV_OFFSET)
1046 {
1047 pQuad->m_ColorEnvOffset = NewVal;
1048 }
1049 }
1050
1051 if(Prop != EQuadProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO))
1052 {
1053 pEditor->m_QuadTracker.EndQuadPropTrack(Prop);
1054 pEditor->m_Map.OnModify();
1055 }
1056
1057 return CUi::POPUP_KEEP_OPEN;
1058}
1059
1060CUi::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View, bool Active)
1061{
1062 CEditor *pEditor = static_cast<CEditor *>(pContext);
1063 CSoundSource *pSource = pEditor->GetSelectedSource();
1064 if(!pSource)
1065 return CUi::POPUP_CLOSE_CURRENT;
1066
1067 CUIRect Button;
1068
1069 // delete button
1070 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
1071 static int s_DeleteButton = 0;
1072 if(pEditor->DoButton_Editor(pId: &s_DeleteButton, pText: "Delete", Checked: 0, pRect: &Button, Flags: 0, pToolTip: "Deletes the current source"))
1073 {
1074 std::shared_ptr<CLayerSounds> pLayer = std::static_pointer_cast<CLayerSounds>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_SOUNDS));
1075 if(pLayer)
1076 {
1077 pEditor->m_EditorHistory.Execute(pAction: std::make_shared<CEditorActionDeleteSoundSource>(args&: pEditor, args&: pEditor->m_SelectedGroup, args&: pEditor->m_vSelectedLayers[0], args&: pEditor->m_SelectedSource));
1078 }
1079 return CUi::POPUP_CLOSE_CURRENT;
1080 }
1081
1082 // Sound shape button
1083 CUIRect ShapeButton;
1084 View.HSplitBottom(Cut: 3.0f, pTop: &View, pBottom: nullptr);
1085 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &ShapeButton);
1086
1087 static const char *s_apShapeNames[CSoundShape::NUM_SHAPES] = {
1088 "Rectangle",
1089 "Circle"};
1090
1091 pSource->m_Shape.m_Type = pSource->m_Shape.m_Type % CSoundShape::NUM_SHAPES; // prevent out of array errors
1092
1093 static int s_ShapeTypeButton = 0;
1094 if(pEditor->DoButton_Editor(pId: &s_ShapeTypeButton, pText: s_apShapeNames[pSource->m_Shape.m_Type], Checked: 0, pRect: &ShapeButton, Flags: 0, pToolTip: "Change shape"))
1095 {
1096 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));
1097 }
1098
1099 CProperty aProps[] = {
1100 {.m_pName: "Pos X", .m_Value: pSource->m_Position.x / 1000, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1101 {.m_pName: "Pos Y", .m_Value: pSource->m_Position.y / 1000, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1102 {.m_pName: "Loop", .m_Value: pSource->m_Loop, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
1103 {.m_pName: "Pan", .m_Value: pSource->m_Pan, .m_Type: PROPTYPE_BOOL, .m_Min: 0, .m_Max: 1},
1104 {.m_pName: "Delay", .m_Value: pSource->m_TimeDelay, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1105 {.m_pName: "Falloff", .m_Value: pSource->m_Falloff, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
1106 {.m_pName: "Pos. Env", .m_Value: pSource->m_PosEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
1107 {.m_pName: "Pos. TO", .m_Value: pSource->m_PosEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1108 {.m_pName: "Sound Env", .m_Value: pSource->m_SoundEnv + 1, .m_Type: PROPTYPE_ENVELOPE, .m_Min: 0, .m_Max: 0},
1109 {.m_pName: "Sound. TO", .m_Value: pSource->m_SoundEnvOffset, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1110 {.m_pName: nullptr},
1111 };
1112
1113 static int s_aIds[(int)ESoundProp::NUM_PROPS] = {0};
1114 int NewVal = 0;
1115 auto [State, Prop] = pEditor->DoPropertiesWithState<ESoundProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1116 if(Prop != ESoundProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO))
1117 {
1118 pEditor->m_Map.OnModify();
1119 }
1120
1121 static CSoundSourcePropTracker s_Tracker(pEditor);
1122 s_Tracker.Begin(pObject: pSource, Prop, State);
1123
1124 if(Prop == ESoundProp::PROP_POS_X)
1125 {
1126 pSource->m_Position.x = NewVal * 1000;
1127 }
1128 else if(Prop == ESoundProp::PROP_POS_Y)
1129 {
1130 pSource->m_Position.y = NewVal * 1000;
1131 }
1132 else if(Prop == ESoundProp::PROP_LOOP)
1133 {
1134 pSource->m_Loop = NewVal;
1135 }
1136 else if(Prop == ESoundProp::PROP_PAN)
1137 {
1138 pSource->m_Pan = NewVal;
1139 }
1140 else if(Prop == ESoundProp::PROP_TIME_DELAY)
1141 {
1142 pSource->m_TimeDelay = NewVal;
1143 }
1144 else if(Prop == ESoundProp::PROP_FALLOFF)
1145 {
1146 pSource->m_Falloff = NewVal;
1147 }
1148 else if(Prop == ESoundProp::PROP_POS_ENV)
1149 {
1150 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1151 const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
1152 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1153 {
1154 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
1155 {
1156 pSource->m_PosEnv = Index;
1157 break;
1158 }
1159 }
1160 }
1161 else if(Prop == ESoundProp::PROP_POS_ENV_OFFSET)
1162 {
1163 pSource->m_PosEnvOffset = NewVal;
1164 }
1165 else if(Prop == ESoundProp::PROP_SOUND_ENV)
1166 {
1167 int Index = clamp(val: NewVal - 1, lo: -1, hi: (int)pEditor->m_Map.m_vpEnvelopes.size() - 1);
1168 const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
1169 for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
1170 {
1171 if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 1)
1172 {
1173 pSource->m_SoundEnv = Index;
1174 break;
1175 }
1176 }
1177 }
1178 else if(Prop == ESoundProp::PROP_SOUND_ENV_OFFSET)
1179 {
1180 pSource->m_SoundEnvOffset = NewVal;
1181 }
1182
1183 s_Tracker.End(Prop, State);
1184
1185 // source shape properties
1186 switch(pSource->m_Shape.m_Type)
1187 {
1188 case CSoundShape::SHAPE_CIRCLE:
1189 {
1190 CProperty aCircleProps[] = {
1191 {.m_pName: "Radius", .m_Value: pSource->m_Shape.m_Circle.m_Radius, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1192 {.m_pName: nullptr},
1193 };
1194
1195 static int s_aCircleIds[(int)ECircleShapeProp::NUM_CIRCLE_PROPS] = {0};
1196 NewVal = 0;
1197 auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ECircleShapeProp>(pToolbox: &View, pProps: aCircleProps, pIds: s_aCircleIds, pNewVal: &NewVal);
1198 if(LocalProp != ECircleShapeProp::PROP_NONE && (LocalState == EEditState::END || LocalState == EEditState::ONE_GO))
1199 {
1200 pEditor->m_Map.OnModify();
1201 }
1202
1203 static CSoundSourceCircleShapePropTracker s_ShapeTracker(pEditor);
1204 s_ShapeTracker.Begin(pObject: pSource, Prop: LocalProp, State: LocalState);
1205
1206 if(LocalProp == ECircleShapeProp::PROP_CIRCLE_RADIUS)
1207 {
1208 pSource->m_Shape.m_Circle.m_Radius = NewVal;
1209 }
1210
1211 s_ShapeTracker.End(Prop: LocalProp, State: LocalState);
1212 break;
1213 }
1214
1215 case CSoundShape::SHAPE_RECTANGLE:
1216 {
1217 CProperty aRectangleProps[] = {
1218 {.m_pName: "Width", .m_Value: pSource->m_Shape.m_Rectangle.m_Width / 1024, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1219 {.m_pName: "Height", .m_Value: pSource->m_Shape.m_Rectangle.m_Height / 1024, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 1000000},
1220 {.m_pName: nullptr},
1221 };
1222
1223 static int s_aRectangleIds[(int)ERectangleShapeProp::NUM_RECTANGLE_PROPS] = {0};
1224 NewVal = 0;
1225 auto [LocalState, LocalProp] = pEditor->DoPropertiesWithState<ERectangleShapeProp>(pToolbox: &View, pProps: aRectangleProps, pIds: s_aRectangleIds, pNewVal: &NewVal);
1226 if(LocalProp != ERectangleShapeProp::PROP_NONE && (LocalState == EEditState::END || LocalState == EEditState::ONE_GO))
1227 {
1228 pEditor->m_Map.OnModify();
1229 }
1230
1231 static CSoundSourceRectShapePropTracker s_ShapeTracker(pEditor);
1232 s_ShapeTracker.Begin(pObject: pSource, Prop: LocalProp, State: LocalState);
1233
1234 if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_WIDTH)
1235 {
1236 pSource->m_Shape.m_Rectangle.m_Width = NewVal * 1024;
1237 }
1238 else if(LocalProp == ERectangleShapeProp::PROP_RECTANGLE_HEIGHT)
1239 {
1240 pSource->m_Shape.m_Rectangle.m_Height = NewVal * 1024;
1241 }
1242
1243 s_ShapeTracker.End(Prop: LocalProp, State: LocalState);
1244 break;
1245 }
1246 }
1247
1248 return CUi::POPUP_KEEP_OPEN;
1249}
1250
1251CUi::EPopupMenuFunctionResult CEditor::PopupPoint(void *pContext, CUIRect View, bool Active)
1252{
1253 CEditor *pEditor = static_cast<CEditor *>(pContext);
1254 std::vector<CQuad *> vpQuads = pEditor->GetSelectedQuads();
1255 if(!in_range<int>(a: pEditor->m_SelectedQuadIndex, lower: 0, upper: vpQuads.size() - 1))
1256 return CUi::POPUP_CLOSE_CURRENT;
1257 CQuad *pCurrentQuad = vpQuads[pEditor->m_SelectedQuadIndex];
1258 std::shared_ptr<CLayerQuads> pLayer = std::static_pointer_cast<CLayerQuads>(r: pEditor->GetSelectedLayerType(Index: 0, Type: LAYERTYPE_QUADS));
1259
1260 int Color = PackColor(Color: pCurrentQuad->m_aColors[pEditor->m_SelectedQuadPoint]);
1261
1262 const int X = fx2i(v: pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].x);
1263 const int Y = fx2i(v: pCurrentQuad->m_aPoints[pEditor->m_SelectedQuadPoint].y);
1264 const int TextureU = fx2f(v: pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].x) * 1024;
1265 const int TextureV = fx2f(v: pCurrentQuad->m_aTexcoords[pEditor->m_SelectedQuadPoint].y) * 1024;
1266
1267 CProperty aProps[] = {
1268 {.m_pName: "Pos X", .m_Value: X, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1269 {.m_pName: "Pos Y", .m_Value: Y, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1270 {.m_pName: "Color", .m_Value: Color, .m_Type: PROPTYPE_COLOR, .m_Min: 0, .m_Max: 0},
1271 {.m_pName: "Tex U", .m_Value: TextureU, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1272 {.m_pName: "Tex V", .m_Value: TextureV, .m_Type: PROPTYPE_INT, .m_Min: -1000000, .m_Max: 1000000},
1273 {.m_pName: nullptr},
1274 };
1275
1276 static int s_aIds[(int)EQuadPointProp::NUM_PROPS] = {0};
1277 int NewVal = 0;
1278 auto [State, Prop] = pEditor->DoPropertiesWithState<EQuadPointProp>(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
1279 if(Prop != EQuadPointProp::PROP_NONE && (State == EEditState::START || State == EEditState::ONE_GO))
1280 {
1281 pEditor->m_QuadTracker.BeginQuadPointPropTrack(pLayer, vSelectedQuads: pEditor->m_vSelectedQuads, SelectedQuadPoints: pEditor->m_SelectedQuadPoints);
1282 pEditor->m_QuadTracker.AddQuadPointPropTrack(Prop);
1283 }
1284
1285 for(CQuad *pQuad : vpQuads)
1286 {
1287 if(Prop == EQuadPointProp::PROP_POS_X)
1288 {
1289 for(int v = 0; v < 4; v++)
1290 if(pEditor->IsQuadCornerSelected(Index: v))
1291 pQuad->m_aPoints[v].x = i2fx(v: fx2i(v: pQuad->m_aPoints[v].x) + NewVal - X);
1292 }
1293 else if(Prop == EQuadPointProp::PROP_POS_Y)
1294 {
1295 for(int v = 0; v < 4; v++)
1296 if(pEditor->IsQuadCornerSelected(Index: v))
1297 pQuad->m_aPoints[v].y = i2fx(v: fx2i(v: pQuad->m_aPoints[v].y) + NewVal - Y);
1298 }
1299 else if(Prop == EQuadPointProp::PROP_COLOR)
1300 {
1301 for(int v = 0; v < 4; v++)
1302 {
1303 if(pEditor->IsQuadCornerSelected(Index: v))
1304 {
1305 pQuad->m_aColors[v].r = (NewVal >> 24) & 0xff;
1306 pQuad->m_aColors[v].g = (NewVal >> 16) & 0xff;
1307 pQuad->m_aColors[v].b = (NewVal >> 8) & 0xff;
1308 pQuad->m_aColors[v].a = NewVal & 0xff;
1309 }
1310 }
1311 }
1312 else if(Prop == EQuadPointProp::PROP_TEX_U)
1313 {
1314 for(int v = 0; v < 4; v++)
1315 if(pEditor->IsQuadCornerSelected(Index: v))
1316 pQuad->m_aTexcoords[v].x = f2fx(v: fx2f(v: pQuad->m_aTexcoords[v].x) + (NewVal - TextureU) / 1024.0f);
1317 }
1318 else if(Prop == EQuadPointProp::PROP_TEX_V)
1319 {
1320 for(int v = 0; v < 4; v++)
1321 if(pEditor->IsQuadCornerSelected(Index: v))
1322 pQuad->m_aTexcoords[v].y = f2fx(v: fx2f(v: pQuad->m_aTexcoords[v].y) + (NewVal - TextureV) / 1024.0f);
1323 }
1324 }
1325
1326 if(Prop != EQuadPointProp::PROP_NONE && (State == EEditState::END || State == EEditState::ONE_GO))
1327 {
1328 pEditor->m_QuadTracker.EndQuadPointPropTrack(Prop);
1329 pEditor->m_Map.OnModify();
1330 }
1331
1332 return CUi::POPUP_KEEP_OPEN;
1333}
1334
1335CUi::EPopupMenuFunctionResult CEditor::PopupEnvPoint(void *pContext, CUIRect View, bool Active)
1336{
1337 CEditor *pEditor = static_cast<CEditor *>(pContext);
1338 if(pEditor->m_SelectedEnvelope < 0 || pEditor->m_SelectedEnvelope >= (int)pEditor->m_Map.m_vpEnvelopes.size())
1339 return CUi::POPUP_CLOSE_CURRENT;
1340
1341 const float RowHeight = 12.0f;
1342 CUIRect Row, Label, EditBox;
1343
1344 pEditor->m_ShowEnvelopePreview = SHOWENV_SELECTED;
1345
1346 std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes[pEditor->m_SelectedEnvelope];
1347
1348 if(pEnvelope->GetChannels() == 4 && !pEditor->IsTangentSelected())
1349 {
1350 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1351 View.HSplitTop(Cut: 4.0f, pTop: nullptr, pBottom: &View);
1352 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1353 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1354 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Color:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1355
1356 const auto SelectedPoint = pEditor->m_vSelectedEnvelopePoints.front();
1357 const int SelectedIndex = SelectedPoint.first;
1358 auto *pValues = pEnvelope->m_vPoints[SelectedIndex].m_aValues;
1359 const ColorRGBA Color = ColorRGBA(fx2f(v: pValues[0]), fx2f(v: pValues[1]), fx2f(v: pValues[2]), fx2f(v: pValues[3]));
1360 const auto &&SetColor = [&](ColorRGBA NewColor) {
1361 if(Color == NewColor && pEditor->m_ColorPickerPopupContext.m_State == EEditState::EDITING)
1362 return;
1363
1364 static int s_Values[4];
1365
1366 if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::START || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
1367 {
1368 for(int Channel = 0; Channel < 4; ++Channel)
1369 s_Values[Channel] = pValues[Channel];
1370 }
1371
1372 for(int Channel = 0; Channel < 4; ++Channel)
1373 {
1374 pValues[Channel] = f2fx(v: NewColor[Channel]);
1375 }
1376
1377 if(pEditor->m_ColorPickerPopupContext.m_State == EEditState::END || pEditor->m_ColorPickerPopupContext.m_State == EEditState::ONE_GO)
1378 {
1379 std::vector<std::shared_ptr<IEditorAction>> vpActions(4);
1380
1381 for(int Channel = 0; Channel < 4; ++Channel)
1382 {
1383 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]));
1384 }
1385
1386 char aDisplay[256];
1387 str_format(buffer: aDisplay, buffer_size: sizeof(aDisplay), format: "Edit color of point %d of envelope %d", SelectedIndex, pEditor->m_SelectedEnvelope);
1388 pEditor->m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args&: pEditor, args&: vpActions, args&: aDisplay));
1389 }
1390
1391 pEditor->m_UpdateEnvPointInfo = true;
1392 pEditor->m_Map.OnModify();
1393 };
1394 static char s_ColorPickerButton;
1395 pEditor->DoColorPickerButton(pId: &s_ColorPickerButton, pRect: &EditBox, Color, SetColor);
1396 }
1397
1398 static CLineInputNumber s_CurValueInput;
1399 static CLineInputNumber s_CurTimeInput;
1400
1401 static float s_CurrentTime = 0;
1402 static float s_CurrentValue = 0;
1403
1404 if(pEditor->m_UpdateEnvPointInfo)
1405 {
1406 pEditor->m_UpdateEnvPointInfo = false;
1407
1408 auto TimeAndValue = pEditor->EnvGetSelectedTimeAndValue();
1409 int CurrentTime = TimeAndValue.first;
1410 int CurrentValue = TimeAndValue.second;
1411
1412 // update displayed text
1413 s_CurValueInput.SetFloat(fx2f(v: CurrentValue));
1414 s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
1415
1416 s_CurrentTime = s_CurTimeInput.GetFloat();
1417 s_CurrentValue = s_CurValueInput.GetFloat();
1418 }
1419
1420 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1421 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1422 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1423 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Value:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1424 pEditor->DoEditBox(pLineInput: &s_CurValueInput, pRect: &EditBox, FontSize: RowHeight - 2.0f, Corners: IGraphics::CORNER_ALL, pToolTip: "The value of the selected envelope point");
1425
1426 View.HSplitTop(Cut: 4.0f, pTop: nullptr, pBottom: &View);
1427 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1428 Row.VSplitLeft(Cut: 60.0f, pLeft: &Label, pRight: &Row);
1429 Row.VSplitLeft(Cut: 10.0f, pLeft: nullptr, pRight: &EditBox);
1430 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Time (in s):", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1431 pEditor->DoEditBox(pLineInput: &s_CurTimeInput, pRect: &EditBox, FontSize: RowHeight - 2.0f, Corners: IGraphics::CORNER_ALL, pToolTip: "The time of the selected envelope point");
1432
1433 if(pEditor->Input()->KeyIsPressed(Key: KEY_RETURN) || pEditor->Input()->KeyIsPressed(Key: KEY_KP_ENTER))
1434 {
1435 float CurrentTime = s_CurTimeInput.GetFloat();
1436 float CurrentValue = s_CurValueInput.GetFloat();
1437 if(!(absolute(a: CurrentTime - s_CurrentTime) < 0.0001f && absolute(a: CurrentValue - s_CurrentValue) < 0.0001f))
1438 {
1439 auto [OldTime, OldValue] = pEditor->EnvGetSelectedTimeAndValue();
1440
1441 if(pEditor->IsTangentInSelected())
1442 {
1443 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
1444
1445 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)));
1446 CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aInTangentDeltaX[SelectedChannel]) / 1000.0f;
1447 }
1448 else if(pEditor->IsTangentOutSelected())
1449 {
1450 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
1451
1452 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)));
1453 CurrentTime = (pEnvelope->m_vPoints[SelectedIndex].m_Time + pEnvelope->m_vPoints[SelectedIndex].m_Bezier.m_aOutTangentDeltaX[SelectedChannel]) / 1000.0f;
1454 }
1455 else
1456 {
1457 auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
1458 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)));
1459
1460 if(SelectedIndex != 0)
1461 {
1462 CurrentTime = pEnvelope->m_vPoints[SelectedIndex].m_Time / 1000.0f;
1463 }
1464 else
1465 {
1466 CurrentTime = 0.0f;
1467 pEnvelope->m_vPoints[SelectedIndex].m_Time = 0.0f;
1468 }
1469 }
1470
1471 s_CurTimeInput.SetFloat(static_cast<int>(CurrentTime * 1000.0f) / 1000.0f);
1472 s_CurValueInput.SetFloat(fx2f(v: f2fx(v: CurrentValue)));
1473
1474 s_CurrentTime = s_CurTimeInput.GetFloat();
1475 s_CurrentValue = s_CurValueInput.GetFloat();
1476 }
1477 }
1478
1479 View.HSplitTop(Cut: 6.0f, pTop: nullptr, pBottom: &View);
1480 View.HSplitTop(Cut: RowHeight, pTop: &Row, pBottom: &View);
1481 static int s_DeleteButtonId = 0;
1482 const char *pButtonText = pEditor->IsTangentSelected() ? "Reset" : "Delete";
1483 const char *pTooltip = pEditor->IsTangentSelected() ? "Reset tangent point to default value." : "Delete current envelope point in all channels.";
1484 if(pEditor->DoButton_Editor(pId: &s_DeleteButtonId, pText: pButtonText, Checked: 0, pRect: &Row, Flags: 0, pToolTip: pTooltip))
1485 {
1486 if(pEditor->IsTangentInSelected())
1487 {
1488 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentInPoint;
1489 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionResetEnvelopePointTangent>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: true));
1490 }
1491 else if(pEditor->IsTangentOutSelected())
1492 {
1493 auto [SelectedIndex, SelectedChannel] = pEditor->m_SelectedTangentOutPoint;
1494 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionResetEnvelopePointTangent>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex, args&: SelectedChannel, args: false));
1495 }
1496 else
1497 {
1498 auto [SelectedIndex, SelectedChannel] = pEditor->m_vSelectedEnvelopePoints.front();
1499 pEditor->m_EnvelopeEditorHistory.Execute(pAction: std::make_shared<CEditorActionDeleteEnvelopePoint>(args&: pEditor, args&: pEditor->m_SelectedEnvelope, args&: SelectedIndex));
1500 }
1501
1502 return CUi::POPUP_CLOSE_CURRENT;
1503 }
1504
1505 return CUi::POPUP_KEEP_OPEN;
1506}
1507
1508CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointMulti(void *pContext, CUIRect View, bool Active)
1509{
1510 CEditor *pEditor = static_cast<CEditor *>(pContext);
1511 const float RowHeight = 12.0f;
1512
1513 static int s_CurveButtonId = 0;
1514 CUIRect CurveButton;
1515 View.HSplitTop(Cut: RowHeight, pTop: &CurveButton, pBottom: &View);
1516 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."))
1517 {
1518 static SPopupMenuId s_PopupCurveTypeId;
1519 pEditor->Ui()->DoPopupMenu(pId: &s_PopupCurveTypeId, X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), Width: 80, Height: 80, pContext: pEditor, pfnFunc: PopupEnvPointCurveType);
1520 }
1521
1522 return CUi::POPUP_KEEP_OPEN;
1523}
1524
1525CUi::EPopupMenuFunctionResult CEditor::PopupEnvPointCurveType(void *pContext, CUIRect View, bool Active)
1526{
1527 CEditor *pEditor = static_cast<CEditor *>(pContext);
1528 const float RowHeight = 14.0f;
1529
1530 int CurveType = -1;
1531
1532 static int s_ButtonLinearId;
1533 CUIRect ButtonLinear;
1534 View.HSplitTop(Cut: RowHeight, pTop: &ButtonLinear, pBottom: &View);
1535 if(pEditor->DoButton_MenuItem(pId: &s_ButtonLinearId, pText: "Linear", Checked: 0, pRect: &ButtonLinear))
1536 CurveType = CURVETYPE_LINEAR;
1537
1538 static int s_ButtonSlowId;
1539 CUIRect ButtonSlow;
1540 View.HSplitTop(Cut: RowHeight, pTop: &ButtonSlow, pBottom: &View);
1541 if(pEditor->DoButton_MenuItem(pId: &s_ButtonSlowId, pText: "Slow", Checked: 0, pRect: &ButtonSlow))
1542 CurveType = CURVETYPE_SLOW;
1543
1544 static int s_ButtonFastId;
1545 CUIRect ButtonFast;
1546 View.HSplitTop(Cut: RowHeight, pTop: &ButtonFast, pBottom: &View);
1547 if(pEditor->DoButton_MenuItem(pId: &s_ButtonFastId, pText: "Fast", Checked: 0, pRect: &ButtonFast))
1548 CurveType = CURVETYPE_FAST;
1549
1550 static int s_ButtonStepId;
1551 CUIRect ButtonStep;
1552 View.HSplitTop(Cut: RowHeight, pTop: &ButtonStep, pBottom: &View);
1553 if(pEditor->DoButton_MenuItem(pId: &s_ButtonStepId, pText: "Step", Checked: 0, pRect: &ButtonStep))
1554 CurveType = CURVETYPE_STEP;
1555
1556 static int s_ButtonSmoothId;
1557 CUIRect ButtonSmooth;
1558 View.HSplitTop(Cut: RowHeight, pTop: &ButtonSmooth, pBottom: &View);
1559 if(pEditor->DoButton_MenuItem(pId: &s_ButtonSmoothId, pText: "Smooth", Checked: 0, pRect: &ButtonSmooth))
1560 CurveType = CURVETYPE_SMOOTH;
1561
1562 std::vector<std::shared_ptr<IEditorAction>> vpActions;
1563
1564 if(CurveType >= 0)
1565 {
1566 std::shared_ptr<CEnvelope> pEnvelope = pEditor->m_Map.m_vpEnvelopes.at(n: pEditor->m_SelectedEnvelope);
1567
1568 for(int c = 0; c < pEnvelope->GetChannels(); c++)
1569 {
1570 int FirstSelectedIndex = pEnvelope->m_vPoints.size();
1571 int LastSelectedIndex = -1;
1572 for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints)
1573 {
1574 if(SelectedChannel == c)
1575 {
1576 FirstSelectedIndex = minimum(a: FirstSelectedIndex, b: SelectedIndex);
1577 LastSelectedIndex = maximum(a: LastSelectedIndex, b: SelectedIndex);
1578 }
1579 }
1580
1581 if(FirstSelectedIndex < (int)pEnvelope->m_vPoints.size() && LastSelectedIndex >= 0 && FirstSelectedIndex != LastSelectedIndex)
1582 {
1583 CEnvPoint FirstPoint = pEnvelope->m_vPoints[FirstSelectedIndex];
1584 CEnvPoint LastPoint = pEnvelope->m_vPoints[LastSelectedIndex];
1585
1586 CEnvelope HelperEnvelope(1);
1587 HelperEnvelope.AddPoint(Time: FirstPoint.m_Time, v0: FirstPoint.m_aValues[c]);
1588 HelperEnvelope.AddPoint(Time: LastPoint.m_Time, v0: LastPoint.m_aValues[c]);
1589 HelperEnvelope.m_vPoints[0].m_Curvetype = CurveType;
1590
1591 for(auto [SelectedIndex, SelectedChannel] : pEditor->m_vSelectedEnvelopePoints)
1592 {
1593 if(SelectedChannel == c)
1594 {
1595 if(SelectedIndex != FirstSelectedIndex && SelectedIndex != LastSelectedIndex)
1596 {
1597 CEnvPoint &CurrentPoint = pEnvelope->m_vPoints[SelectedIndex];
1598 ColorRGBA Channels = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
1599 HelperEnvelope.Eval(Time: CurrentPoint.m_Time / 1000.0f, Result&: Channels, Channels: 1);
1600 int PrevValue = CurrentPoint.m_aValues[c];
1601 CurrentPoint.m_aValues[c] = f2fx(v: Channels.r);
1602 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]));
1603 }
1604 }
1605 }
1606 }
1607 }
1608
1609 if(!vpActions.empty())
1610 {
1611 pEditor->m_EnvelopeEditorHistory.RecordAction(pAction: std::make_shared<CEditorActionBulk>(args&: pEditor, args&: vpActions, args: "Project points"));
1612 }
1613
1614 pEditor->m_Map.OnModify();
1615 return CUi::POPUP_CLOSE_CURRENT;
1616 }
1617
1618 return CUi::POPUP_KEEP_OPEN;
1619}
1620
1621static const auto &&gs_ModifyIndexDeleted = [](int DeletedIndex) {
1622 return [DeletedIndex](int *pIndex) {
1623 if(*pIndex == DeletedIndex)
1624 *pIndex = -1;
1625 else if(*pIndex > DeletedIndex)
1626 *pIndex = *pIndex - 1;
1627 };
1628};
1629
1630CUi::EPopupMenuFunctionResult CEditor::PopupImage(void *pContext, CUIRect View, bool Active)
1631{
1632 CEditor *pEditor = static_cast<CEditor *>(pContext);
1633
1634 static int s_ExternalButton = 0;
1635 static int s_ReimportButton = 0;
1636 static int s_ReplaceButton = 0;
1637 static int s_RemoveButton = 0;
1638 static int s_ExportButton = 0;
1639
1640 const float RowHeight = 12.0f;
1641
1642 CUIRect Slot;
1643 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1644 std::shared_ptr<CEditorImage> pImg = pEditor->m_Map.m_vpImages[pEditor->m_SelectedImage];
1645
1646 if(!pImg->m_External)
1647 {
1648 CUIRect Label, EditBox;
1649
1650 static CLineInput s_RenameInput;
1651
1652 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
1653 Slot.VSplitLeft(Cut: 35.0f, pLeft: &Label, pRight: &Slot);
1654 Slot.VSplitLeft(Cut: RowHeight - 2.0f, pLeft: nullptr, pRight: &EditBox);
1655 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1656
1657 s_RenameInput.SetBuffer(pStr: pImg->m_aName, MaxSize: sizeof(pImg->m_aName));
1658 if(pEditor->DoEditBox(pLineInput: &s_RenameInput, pRect: &EditBox, FontSize: RowHeight - 2.0f))
1659 pEditor->m_Map.OnModify();
1660
1661 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1662 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1663 }
1664
1665 if(pImg->m_External)
1666 {
1667 if(pEditor->DoButton_MenuItem(pId: &s_ExternalButton, pText: "Embed", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Embeds the image into the map file."))
1668 {
1669 pImg->m_External = 0;
1670 return CUi::POPUP_CLOSE_CURRENT;
1671 }
1672 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1673 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1674 }
1675 else if(CEditor::IsVanillaImage(pImage: pImg->m_aName))
1676 {
1677 if(pEditor->DoButton_MenuItem(pId: &s_ExternalButton, pText: "Make external", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the image from the map file."))
1678 {
1679 pImg->m_External = 1;
1680 return CUi::POPUP_CLOSE_CURRENT;
1681 }
1682 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1683 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1684 }
1685
1686 static CUi::SSelectionPopupContext s_SelectionPopupContext;
1687 static CScrollRegion s_SelectionPopupScrollRegion;
1688 s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
1689 if(pEditor->DoButton_MenuItem(pId: &s_ReimportButton, pText: "Re-import", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Re-imports the image from the mapres folder"))
1690 {
1691 char aFilename[IO_MAX_PATH_LENGTH];
1692 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s.png", pImg->m_aName);
1693 s_SelectionPopupContext.Reset();
1694 std::set<std::string> EntriesSet;
1695 pEditor->Storage()->FindFiles(pFilename: aFilename, pPath: "mapres", Type: IStorage::TYPE_ALL, pEntries: &EntriesSet);
1696 for(const auto &Entry : EntriesSet)
1697 s_SelectionPopupContext.m_vEntries.push_back(x: Entry);
1698 if(s_SelectionPopupContext.m_vEntries.empty())
1699 {
1700 pEditor->ShowFileDialogError(pFormat: "Error: could not find image '%s' in the mapres folder.", aFilename);
1701 }
1702 else if(s_SelectionPopupContext.m_vEntries.size() == 1)
1703 {
1704 s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
1705 }
1706 else
1707 {
1708 str_copy(dst&: s_SelectionPopupContext.m_aMessage, src: "Select the wanted image:");
1709 pEditor->Ui()->ShowPopupSelection(X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), pContext: &s_SelectionPopupContext);
1710 }
1711 }
1712 if(s_SelectionPopupContext.m_pSelection != nullptr)
1713 {
1714 const bool WasExternal = pImg->m_External;
1715 const bool Result = pEditor->ReplaceImage(pFilename: s_SelectionPopupContext.m_pSelection->c_str(), StorageType: IStorage::TYPE_ALL, CheckDuplicate: false);
1716 pImg->m_External = WasExternal;
1717 s_SelectionPopupContext.Reset();
1718 return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN;
1719 }
1720
1721 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1722 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1723 if(pEditor->DoButton_MenuItem(pId: &s_ReplaceButton, pText: "Replace", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Replaces the image with a new one"))
1724 {
1725 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_IMG, pTitle: "Replace Image", pButtonText: "Replace", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: ReplaceImageCallback, pUser: pEditor);
1726 return CUi::POPUP_CLOSE_CURRENT;
1727 }
1728
1729 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1730 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1731 if(pEditor->DoButton_MenuItem(pId: &s_RemoveButton, pText: "Remove", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the image from the map"))
1732 {
1733 pEditor->m_Map.m_vpImages.erase(position: pEditor->m_Map.m_vpImages.begin() + pEditor->m_SelectedImage);
1734 pEditor->m_Map.ModifyImageIndex(pfnFunc: gs_ModifyIndexDeleted(pEditor->m_SelectedImage));
1735 return CUi::POPUP_CLOSE_CURRENT;
1736 }
1737
1738 if(!pImg->m_External)
1739 {
1740 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1741 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1742 if(pEditor->DoButton_MenuItem(pId: &s_ExportButton, pText: "Export", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Export the image"))
1743 {
1744 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_IMG, pTitle: "Save image", pButtonText: "Save", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: CallbackSaveImage, pUser: pEditor);
1745 pEditor->m_FileDialogFileNameInput.Set(pImg->m_aName);
1746 return CUi::POPUP_CLOSE_CURRENT;
1747 }
1748 }
1749
1750 return CUi::POPUP_KEEP_OPEN;
1751}
1752
1753CUi::EPopupMenuFunctionResult CEditor::PopupSound(void *pContext, CUIRect View, bool Active)
1754{
1755 CEditor *pEditor = static_cast<CEditor *>(pContext);
1756
1757 static int s_ReimportButton = 0;
1758 static int s_ReplaceButton = 0;
1759 static int s_RemoveButton = 0;
1760 static int s_ExportButton = 0;
1761
1762 const float RowHeight = 12.0f;
1763
1764 CUIRect Slot;
1765 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1766 std::shared_ptr<CEditorSound> pSound = pEditor->m_Map.m_vpSounds[pEditor->m_SelectedSound];
1767
1768 static CUi::SSelectionPopupContext s_SelectionPopupContext;
1769 static CScrollRegion s_SelectionPopupScrollRegion;
1770 s_SelectionPopupContext.m_pScrollRegion = &s_SelectionPopupScrollRegion;
1771
1772 CUIRect Label, EditBox;
1773
1774 static CLineInput s_RenameInput;
1775
1776 Slot.VMargin(Cut: 5.0f, pOtherRect: &Slot);
1777 Slot.VSplitLeft(Cut: 35.0f, pLeft: &Label, pRight: &Slot);
1778 Slot.VSplitLeft(Cut: RowHeight - 2.0f, pLeft: nullptr, pRight: &EditBox);
1779 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: RowHeight - 2.0f, Align: TEXTALIGN_ML);
1780
1781 s_RenameInput.SetBuffer(pStr: pSound->m_aName, MaxSize: sizeof(pSound->m_aName));
1782 if(pEditor->DoEditBox(pLineInput: &s_RenameInput, pRect: &EditBox, FontSize: RowHeight - 2.0f))
1783 pEditor->m_Map.OnModify();
1784
1785 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1786 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1787
1788 if(pEditor->DoButton_MenuItem(pId: &s_ReimportButton, pText: "Re-import", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Re-imports the sound from the mapres folder"))
1789 {
1790 char aFilename[IO_MAX_PATH_LENGTH];
1791 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s.opus", pSound->m_aName);
1792 s_SelectionPopupContext.Reset();
1793 std::set<std::string> EntriesSet;
1794 pEditor->Storage()->FindFiles(pFilename: aFilename, pPath: "mapres", Type: IStorage::TYPE_ALL, pEntries: &EntriesSet);
1795 for(const auto &Entry : EntriesSet)
1796 s_SelectionPopupContext.m_vEntries.push_back(x: Entry);
1797 if(s_SelectionPopupContext.m_vEntries.empty())
1798 {
1799 pEditor->ShowFileDialogError(pFormat: "Error: could not find sound '%s' in the mapres folder.", aFilename);
1800 }
1801 else if(s_SelectionPopupContext.m_vEntries.size() == 1)
1802 {
1803 s_SelectionPopupContext.m_pSelection = &s_SelectionPopupContext.m_vEntries.front();
1804 }
1805 else
1806 {
1807 str_copy(dst&: s_SelectionPopupContext.m_aMessage, src: "Select the wanted sound:");
1808 pEditor->Ui()->ShowPopupSelection(X: pEditor->Ui()->MouseX(), Y: pEditor->Ui()->MouseY(), pContext: &s_SelectionPopupContext);
1809 }
1810 }
1811 if(s_SelectionPopupContext.m_pSelection != nullptr)
1812 {
1813 const bool Result = pEditor->ReplaceSound(pFileName: s_SelectionPopupContext.m_pSelection->c_str(), StorageType: IStorage::TYPE_ALL, CheckDuplicate: false);
1814 s_SelectionPopupContext.Reset();
1815 return Result ? CUi::POPUP_CLOSE_CURRENT : CUi::POPUP_KEEP_OPEN;
1816 }
1817
1818 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1819 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1820 if(pEditor->DoButton_MenuItem(pId: &s_ReplaceButton, pText: "Replace", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Replaces the sound with a new one"))
1821 {
1822 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_SOUND, pTitle: "Replace sound", pButtonText: "Replace", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: ReplaceSoundCallback, pUser: pEditor);
1823 return CUi::POPUP_CLOSE_CURRENT;
1824 }
1825
1826 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1827 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1828 if(pEditor->DoButton_MenuItem(pId: &s_RemoveButton, pText: "Remove", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Removes the sound from the map"))
1829 {
1830 pEditor->m_Map.m_vpSounds.erase(position: pEditor->m_Map.m_vpSounds.begin() + pEditor->m_SelectedSound);
1831 pEditor->m_Map.ModifySoundIndex(pfnFunc: gs_ModifyIndexDeleted(pEditor->m_SelectedSound));
1832 return CUi::POPUP_CLOSE_CURRENT;
1833 }
1834
1835 View.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &View);
1836 View.HSplitTop(Cut: RowHeight, pTop: &Slot, pBottom: &View);
1837 if(pEditor->DoButton_MenuItem(pId: &s_ExportButton, pText: "Export", Checked: 0, pRect: &Slot, Flags: 0, pToolTip: "Export sound"))
1838 {
1839 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_SAVE, FileType: FILETYPE_SOUND, pTitle: "Save sound", pButtonText: "Save", pBasepath: "mapres", FilenameAsDefault: false, pfnFunc: CallbackSaveSound, pUser: pEditor);
1840 pEditor->m_FileDialogFileNameInput.Set(pSound->m_aName);
1841 return CUi::POPUP_CLOSE_CURRENT;
1842 }
1843
1844 return CUi::POPUP_KEEP_OPEN;
1845}
1846
1847CUi::EPopupMenuFunctionResult CEditor::PopupNewFolder(void *pContext, CUIRect View, bool Active)
1848{
1849 CEditor *pEditor = static_cast<CEditor *>(pContext);
1850
1851 CUIRect Label, ButtonBar, Button;
1852
1853 View.Margin(Cut: 10.0f, pOtherRect: &View);
1854 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
1855
1856 // title
1857 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1858 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Create new folder", Size: 20.0f, Align: TEXTALIGN_MC);
1859 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
1860
1861 // folder name
1862 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1863 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Name:", Size: 10.0f, Align: TEXTALIGN_ML);
1864 Label.VSplitLeft(Cut: 50.0f, pLeft: nullptr, pRight: &Button);
1865 Button.HMargin(Cut: 2.0f, pOtherRect: &Button);
1866 pEditor->DoEditBox(pLineInput: &pEditor->m_FileDialogNewFolderNameInput, pRect: &Button, FontSize: 12.0f);
1867
1868 // button bar
1869 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Button, pRight: &ButtonBar);
1870 static int s_CancelButton = 0;
1871 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
1872 return CUi::POPUP_CLOSE_CURRENT;
1873
1874 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Button);
1875 static int s_CreateButton = 0;
1876 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)))
1877 {
1878 // create the folder
1879 if(!pEditor->m_FileDialogNewFolderNameInput.IsEmpty())
1880 {
1881 char aBuf[IO_MAX_PATH_LENGTH];
1882 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s/%s", pEditor->m_pFileDialogPath, pEditor->m_FileDialogNewFolderNameInput.GetString());
1883 if(pEditor->Storage()->CreateFolder(pFoldername: aBuf, Type: IStorage::TYPE_SAVE))
1884 {
1885 pEditor->FilelistPopulate(StorageType: IStorage::TYPE_SAVE);
1886 return CUi::POPUP_CLOSE_CURRENT;
1887 }
1888 else
1889 {
1890 pEditor->ShowFileDialogError(pFormat: "Failed to create the folder '%s'.", aBuf);
1891 }
1892 }
1893 }
1894
1895 return CUi::POPUP_KEEP_OPEN;
1896}
1897
1898CUi::EPopupMenuFunctionResult CEditor::PopupMapInfo(void *pContext, CUIRect View, bool Active)
1899{
1900 CEditor *pEditor = static_cast<CEditor *>(pContext);
1901
1902 CUIRect Label, ButtonBar, Button;
1903
1904 View.Margin(Cut: 10.0f, pOtherRect: &View);
1905 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
1906
1907 // title
1908 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1909 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Map details", Size: 20.0f, Align: TEXTALIGN_MC);
1910 View.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &View);
1911
1912 // author box
1913 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1914 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Author:", Size: 10.0f, Align: TEXTALIGN_ML);
1915 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1916 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1917 static CLineInput s_AuthorInput;
1918 s_AuthorInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aAuthor, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aAuthor));
1919 pEditor->DoEditBox(pLineInput: &s_AuthorInput, pRect: &Button, FontSize: 10.0f);
1920
1921 // version box
1922 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1923 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Version:", Size: 10.0f, Align: TEXTALIGN_ML);
1924 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1925 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1926 static CLineInput s_VersionInput;
1927 s_VersionInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aVersion, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aVersion));
1928 pEditor->DoEditBox(pLineInput: &s_VersionInput, pRect: &Button, FontSize: 10.0f);
1929
1930 // credits box
1931 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1932 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Credits:", Size: 10.0f, Align: TEXTALIGN_ML);
1933 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1934 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1935 static CLineInput s_CreditsInput;
1936 s_CreditsInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aCredits, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aCredits));
1937 pEditor->DoEditBox(pLineInput: &s_CreditsInput, pRect: &Button, FontSize: 10.0f);
1938
1939 // license box
1940 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
1941 pEditor->Ui()->DoLabel(pRect: &Label, pText: "License:", Size: 10.0f, Align: TEXTALIGN_ML);
1942 Label.VSplitLeft(Cut: 60.0f, pLeft: nullptr, pRight: &Button);
1943 Button.HMargin(Cut: 3.0f, pOtherRect: &Button);
1944 static CLineInput s_LicenseInput;
1945 s_LicenseInput.SetBuffer(pStr: pEditor->m_Map.m_MapInfoTmp.m_aLicense, MaxSize: sizeof(pEditor->m_Map.m_MapInfoTmp.m_aLicense));
1946 pEditor->DoEditBox(pLineInput: &s_LicenseInput, pRect: &Button, FontSize: 10.0f);
1947
1948 // button bar
1949 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Label, pRight: &ButtonBar);
1950 static int s_CancelButton = 0;
1951 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Label, Flags: 0, pToolTip: nullptr))
1952 return CUi::POPUP_CLOSE_CURRENT;
1953
1954 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Label);
1955 static int s_ConfirmButton = 0;
1956 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)))
1957 {
1958 bool AuthorDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aAuthor, b: pEditor->m_Map.m_MapInfo.m_aAuthor) != 0;
1959 bool VersionDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aVersion, b: pEditor->m_Map.m_MapInfo.m_aVersion) != 0;
1960 bool CreditsDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aCredits, b: pEditor->m_Map.m_MapInfo.m_aCredits) != 0;
1961 bool LicenseDifferent = str_comp(a: pEditor->m_Map.m_MapInfoTmp.m_aLicense, b: pEditor->m_Map.m_MapInfo.m_aLicense) != 0;
1962
1963 if(AuthorDifferent || VersionDifferent || CreditsDifferent || LicenseDifferent)
1964 pEditor->m_Map.OnModify();
1965
1966 pEditor->m_Map.m_MapInfo.Copy(Source: pEditor->m_Map.m_MapInfoTmp);
1967 return CUi::POPUP_CLOSE_CURRENT;
1968 }
1969
1970 return CUi::POPUP_KEEP_OPEN;
1971}
1972
1973CUi::EPopupMenuFunctionResult CEditor::PopupEvent(void *pContext, CUIRect View, bool Active)
1974{
1975 CEditor *pEditor = static_cast<CEditor *>(pContext);
1976
1977 const char *pTitle;
1978 const char *pMessage;
1979 char aMessageBuf[128];
1980 if(pEditor->m_PopupEventType == POPEVENT_EXIT)
1981 {
1982 pTitle = "Exit the editor";
1983 pMessage = "The map contains unsaved data, you might want to save it before you exit the editor.\n\nContinue anyway?";
1984 }
1985 else if(pEditor->m_PopupEventType == POPEVENT_LOAD || pEditor->m_PopupEventType == POPEVENT_LOADCURRENT || pEditor->m_PopupEventType == POPEVENT_LOADDROP)
1986 {
1987 pTitle = "Load map";
1988 pMessage = "The map contains unsaved data, you might want to save it before you load a new map.\n\nContinue anyway?";
1989 }
1990 else if(pEditor->m_PopupEventType == POPEVENT_NEW)
1991 {
1992 pTitle = "New map";
1993 pMessage = "The map contains unsaved data, you might want to save it before you create a new map.\n\nContinue anyway?";
1994 }
1995 else if(pEditor->m_PopupEventType == POPEVENT_SAVE || pEditor->m_PopupEventType == POPEVENT_SAVE_COPY)
1996 {
1997 pTitle = "Save map";
1998 pMessage = "The file already exists.\n\nDo you want to overwrite the map?";
1999 }
2000 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_IMG)
2001 {
2002 pTitle = "Save image";
2003 pMessage = "The file already exists.\n\nDo you want to overwrite the image?";
2004 }
2005 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_SOUND)
2006 {
2007 pTitle = "Save sound";
2008 pMessage = "The file already exists.\n\nDo you want to overwrite the sound?";
2009 }
2010 else if(pEditor->m_PopupEventType == POPEVENT_LARGELAYER)
2011 {
2012 pTitle = "Large layer";
2013 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.";
2014 }
2015 else if(pEditor->m_PopupEventType == POPEVENT_PREVENTUNUSEDTILES)
2016 {
2017 pTitle = "Unused tiles disabled";
2018 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.";
2019 }
2020 else if(pEditor->m_PopupEventType == POPEVENT_IMAGEDIV16)
2021 {
2022 pTitle = "Image width/height";
2023 pMessage = "The width or height of this image is not divisible by 16. This is required for images used in tile layers.";
2024 }
2025 else if(pEditor->m_PopupEventType == POPEVENT_IMAGE_MAX)
2026 {
2027 pTitle = "Max images";
2028 str_format(buffer: aMessageBuf, buffer_size: sizeof(aMessageBuf), format: "The client only allows a maximum of %" PRIzu " images.", MAX_MAPIMAGES);
2029 pMessage = aMessageBuf;
2030 }
2031 else if(pEditor->m_PopupEventType == POPEVENT_SOUND_MAX)
2032 {
2033 pTitle = "Max sounds";
2034 str_format(buffer: aMessageBuf, buffer_size: sizeof(aMessageBuf), format: "The client only allows a maximum of %" PRIzu " sounds.", MAX_MAPSOUNDS);
2035 pMessage = aMessageBuf;
2036 }
2037 else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
2038 {
2039 pTitle = "Place border tiles";
2040 pMessage = "This is going to overwrite any existing tiles around the edges of the layer.\n\nContinue?";
2041 }
2042 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
2043 {
2044 pTitle = "Big image";
2045 pMessage = "The selected image is big. Converting it to tileart may take some time.\n\nContinue anyway?";
2046 }
2047 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2048 {
2049 pTitle = "Many colors";
2050 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?";
2051 }
2052 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_TOO_MANY_COLORS)
2053 {
2054 pTitle = "Too many colors";
2055 pMessage = "The client only supports 64 images but more would be needed to add the selected image as tileart.";
2056 }
2057 else
2058 {
2059 dbg_assert(false, "m_PopupEventType invalid");
2060 return CUi::POPUP_CLOSE_CURRENT;
2061 }
2062
2063 CUIRect Label, ButtonBar, Button;
2064
2065 View.Margin(Cut: 10.0f, pOtherRect: &View);
2066 View.HSplitBottom(Cut: 20.0f, pTop: &View, pBottom: &ButtonBar);
2067
2068 // title
2069 View.HSplitTop(Cut: 20.0f, pTop: &Label, pBottom: &View);
2070 pEditor->Ui()->DoLabel(pRect: &Label, pText: pTitle, Size: 20.0f, Align: TEXTALIGN_MC);
2071
2072 // message
2073 SLabelProperties Props;
2074 Props.m_MaxWidth = View.w;
2075 pEditor->Ui()->DoLabel(pRect: &View, pText: pMessage, Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: Props);
2076
2077 // button bar
2078 ButtonBar.VSplitLeft(Cut: 110.0f, pLeft: &Button, pRight: &ButtonBar);
2079 if(pEditor->m_PopupEventType != POPEVENT_LARGELAYER &&
2080 pEditor->m_PopupEventType != POPEVENT_PREVENTUNUSEDTILES &&
2081 pEditor->m_PopupEventType != POPEVENT_IMAGEDIV16 &&
2082 pEditor->m_PopupEventType != POPEVENT_IMAGE_MAX &&
2083 pEditor->m_PopupEventType != POPEVENT_SOUND_MAX &&
2084 pEditor->m_PopupEventType != POPEVENT_PIXELART_TOO_MANY_COLORS)
2085 {
2086 static int s_CancelButton = 0;
2087 if(pEditor->DoButton_Editor(pId: &s_CancelButton, pText: "Cancel", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2088 {
2089 if(pEditor->m_PopupEventType == POPEVENT_LOADDROP)
2090 pEditor->m_aFileNamePending[0] = 0;
2091 pEditor->m_PopupEventWasActivated = false;
2092
2093 if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE || pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2094 {
2095 pEditor->m_TileartImageInfo.Free();
2096 }
2097
2098 return CUi::POPUP_CLOSE_CURRENT;
2099 }
2100 }
2101
2102 ButtonBar.VSplitRight(Cut: 110.0f, pLeft: &ButtonBar, pRight: &Button);
2103 static int s_ConfirmButton = 0;
2104 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)))
2105 {
2106 if(pEditor->m_PopupEventType == POPEVENT_EXIT)
2107 {
2108 pEditor->OnClose();
2109 g_Config.m_ClEditor = 0;
2110 }
2111 else if(pEditor->m_PopupEventType == POPEVENT_LOAD)
2112 {
2113 pEditor->InvokeFileDialog(StorageType: IStorage::TYPE_ALL, FileType: FILETYPE_MAP, pTitle: "Load map", pButtonText: "Load", pBasepath: "maps", FilenameAsDefault: false, pfnFunc: CEditor::CallbackOpenMap, pUser: pEditor);
2114 }
2115 else if(pEditor->m_PopupEventType == POPEVENT_LOADCURRENT)
2116 {
2117 pEditor->LoadCurrentMap();
2118 }
2119 else if(pEditor->m_PopupEventType == POPEVENT_LOADDROP)
2120 {
2121 int Result = pEditor->Load(pFilename: pEditor->m_aFileNamePending, StorageType: IStorage::TYPE_ALL_OR_ABSOLUTE);
2122 if(!Result)
2123 dbg_msg(sys: "editor", fmt: "editing passed map file '%s' failed", pEditor->m_aFileNamePending);
2124 pEditor->m_aFileNamePending[0] = 0;
2125 }
2126 else if(pEditor->m_PopupEventType == POPEVENT_NEW)
2127 {
2128 pEditor->Reset();
2129 pEditor->m_aFileName[0] = 0;
2130 }
2131 else if(pEditor->m_PopupEventType == POPEVENT_SAVE)
2132 {
2133 CallbackSaveMap(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2134 return CUi::POPUP_CLOSE_CURRENT;
2135 }
2136 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_COPY)
2137 {
2138 CallbackSaveCopyMap(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2139 return CUi::POPUP_CLOSE_CURRENT;
2140 }
2141 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_IMG)
2142 {
2143 CallbackSaveImage(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2144 return CUi::POPUP_CLOSE_CURRENT;
2145 }
2146 else if(pEditor->m_PopupEventType == POPEVENT_SAVE_SOUND)
2147 {
2148 CallbackSaveSound(pFileName: pEditor->m_aFileSaveName, StorageType: IStorage::TYPE_SAVE, pUser: pEditor);
2149 return CUi::POPUP_CLOSE_CURRENT;
2150 }
2151 else if(pEditor->m_PopupEventType == POPEVENT_PLACE_BORDER_TILES)
2152 {
2153 pEditor->PlaceBorderTiles();
2154 }
2155 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_BIG_IMAGE)
2156 {
2157 pEditor->TileartCheckColors();
2158 }
2159 else if(pEditor->m_PopupEventType == POPEVENT_PIXELART_MANY_COLORS)
2160 {
2161 pEditor->AddTileart();
2162 }
2163 pEditor->m_PopupEventWasActivated = false;
2164 return CUi::POPUP_CLOSE_CURRENT;
2165 }
2166
2167 return CUi::POPUP_KEEP_OPEN;
2168}
2169
2170static int g_SelectImageSelected = -100;
2171static int g_SelectImageCurrent = -100;
2172
2173CUi::EPopupMenuFunctionResult CEditor::PopupSelectImage(void *pContext, CUIRect View, bool Active)
2174{
2175 CEditor *pEditor = static_cast<CEditor *>(pContext);
2176
2177 CUIRect ButtonBar, ImageView;
2178 View.VSplitLeft(Cut: 150.0f, pLeft: &ButtonBar, pRight: &View);
2179 View.Margin(Cut: 10.0f, pOtherRect: &ImageView);
2180
2181 int ShowImage = g_SelectImageCurrent;
2182
2183 const float ButtonHeight = 12.0f;
2184 const float ButtonMargin = 2.0f;
2185
2186 static CListBox s_ListBox;
2187 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);
2188 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2189
2190 for(int i = 0; i <= (int)pEditor->m_Map.m_vpImages.size(); i++)
2191 {
2192 static int s_NoneButton = 0;
2193 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);
2194 if(!Item.m_Visible)
2195 continue;
2196
2197 if(pEditor->Ui()->MouseInside(pRect: &Item.m_Rect))
2198 ShowImage = i - 1;
2199
2200 CUIRect Label;
2201 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2202
2203 SLabelProperties Props;
2204 Props.m_MaxWidth = Label.w;
2205 Props.m_EllipsisAtEnd = true;
2206 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);
2207 }
2208
2209 int NewSelected = s_ListBox.DoEnd() - 1;
2210 if(NewSelected != g_SelectImageCurrent)
2211 g_SelectImageSelected = NewSelected;
2212
2213 if(ShowImage >= 0 && (size_t)ShowImage < pEditor->m_Map.m_vpImages.size())
2214 {
2215 if(ImageView.h < ImageView.w)
2216 ImageView.w = ImageView.h;
2217 else
2218 ImageView.h = ImageView.w;
2219 float Max = (float)(maximum(a: pEditor->m_Map.m_vpImages[ShowImage]->m_Width, b: pEditor->m_Map.m_vpImages[ShowImage]->m_Height));
2220 ImageView.w *= pEditor->m_Map.m_vpImages[ShowImage]->m_Width / Max;
2221 ImageView.h *= pEditor->m_Map.m_vpImages[ShowImage]->m_Height / Max;
2222 pEditor->Graphics()->TextureSet(Texture: pEditor->m_Map.m_vpImages[ShowImage]->m_Texture);
2223 pEditor->Graphics()->BlendNormal();
2224 pEditor->Graphics()->WrapClamp();
2225 pEditor->Graphics()->QuadsBegin();
2226 IGraphics::CQuadItem QuadItem(ImageView.x, ImageView.y, ImageView.w, ImageView.h);
2227 pEditor->Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
2228 pEditor->Graphics()->QuadsEnd();
2229 pEditor->Graphics()->WrapNormal();
2230 }
2231
2232 return CUi::POPUP_KEEP_OPEN;
2233}
2234
2235void CEditor::PopupSelectImageInvoke(int Current, float x, float y)
2236{
2237 static SPopupMenuId s_PopupSelectImageId;
2238 g_SelectImageSelected = -100;
2239 g_SelectImageCurrent = Current;
2240 Ui()->DoPopupMenu(pId: &s_PopupSelectImageId, X: x, Y: y, Width: 450, Height: 300, pContext: this, pfnFunc: PopupSelectImage);
2241}
2242
2243int CEditor::PopupSelectImageResult()
2244{
2245 if(g_SelectImageSelected == -100)
2246 return -100;
2247
2248 g_SelectImageCurrent = g_SelectImageSelected;
2249 g_SelectImageSelected = -100;
2250 return g_SelectImageCurrent;
2251}
2252
2253static int g_SelectSoundSelected = -100;
2254static int g_SelectSoundCurrent = -100;
2255
2256CUi::EPopupMenuFunctionResult CEditor::PopupSelectSound(void *pContext, CUIRect View, bool Active)
2257{
2258 CEditor *pEditor = static_cast<CEditor *>(pContext);
2259
2260 const float ButtonHeight = 12.0f;
2261 const float ButtonMargin = 2.0f;
2262
2263 static CListBox s_ListBox;
2264 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);
2265 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2266
2267 for(int i = 0; i <= (int)pEditor->m_Map.m_vpSounds.size(); i++)
2268 {
2269 static int s_NoneButton = 0;
2270 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);
2271 if(!Item.m_Visible)
2272 continue;
2273
2274 CUIRect Label;
2275 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2276
2277 SLabelProperties Props;
2278 Props.m_MaxWidth = Label.w;
2279 Props.m_EllipsisAtEnd = true;
2280 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);
2281 }
2282
2283 int NewSelected = s_ListBox.DoEnd() - 1;
2284 if(NewSelected != g_SelectSoundCurrent)
2285 g_SelectSoundSelected = NewSelected;
2286
2287 return CUi::POPUP_KEEP_OPEN;
2288}
2289
2290void CEditor::PopupSelectSoundInvoke(int Current, float x, float y)
2291{
2292 static SPopupMenuId s_PopupSelectSoundId;
2293 g_SelectSoundSelected = -100;
2294 g_SelectSoundCurrent = Current;
2295 Ui()->DoPopupMenu(pId: &s_PopupSelectSoundId, X: x, Y: y, Width: 150, Height: 300, pContext: this, pfnFunc: PopupSelectSound);
2296}
2297
2298int CEditor::PopupSelectSoundResult()
2299{
2300 if(g_SelectSoundSelected == -100)
2301 return -100;
2302
2303 g_SelectSoundCurrent = g_SelectSoundSelected;
2304 g_SelectSoundSelected = -100;
2305 return g_SelectSoundCurrent;
2306}
2307
2308static int s_GametileOpSelected = -1;
2309
2310static const char *s_apGametileOpButtonNames[] = {
2311 "Air",
2312 "Hookable",
2313 "Death",
2314 "Unhookable",
2315 "Hookthrough",
2316 "Freeze",
2317 "Unfreeze",
2318 "Deep Freeze",
2319 "Deep Unfreeze",
2320 "Blue Check-Tele",
2321 "Red Check-Tele",
2322 "Live Freeze",
2323 "Live Unfreeze",
2324};
2325
2326CUi::EPopupMenuFunctionResult CEditor::PopupSelectGametileOp(void *pContext, CUIRect View, bool Active)
2327{
2328 CEditor *pEditor = static_cast<CEditor *>(pContext);
2329
2330 const int PreviousSelected = s_GametileOpSelected;
2331
2332 CUIRect Button;
2333 for(size_t i = 0; i < std::size(s_apGametileOpButtonNames); ++i)
2334 {
2335 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
2336 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2337 if(pEditor->DoButton_Editor(pId: &s_apGametileOpButtonNames[i], pText: s_apGametileOpButtonNames[i], Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2338 s_GametileOpSelected = i;
2339 }
2340
2341 return s_GametileOpSelected == PreviousSelected ? CUi::POPUP_KEEP_OPEN : CUi::POPUP_CLOSE_CURRENT;
2342}
2343
2344void CEditor::PopupSelectGametileOpInvoke(float x, float y)
2345{
2346 static SPopupMenuId s_PopupSelectGametileOpId;
2347 s_GametileOpSelected = -1;
2348 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);
2349}
2350
2351int CEditor::PopupSelectGameTileOpResult()
2352{
2353 if(s_GametileOpSelected < 0)
2354 return -1;
2355
2356 int Result = s_GametileOpSelected;
2357 s_GametileOpSelected = -1;
2358 return Result;
2359}
2360
2361static int s_AutoMapConfigSelected = -100;
2362static int s_AutoMapConfigCurrent = -100;
2363
2364CUi::EPopupMenuFunctionResult CEditor::PopupSelectConfigAutoMap(void *pContext, CUIRect View, bool Active)
2365{
2366 CEditor *pEditor = static_cast<CEditor *>(pContext);
2367 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: pEditor->GetSelectedLayer(Index: 0));
2368 CAutoMapper *pAutoMapper = &pEditor->m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper;
2369
2370 const float ButtonHeight = 12.0f;
2371 const float ButtonMargin = 2.0f;
2372
2373 static CListBox s_ListBox;
2374 s_ListBox.DoStart(RowHeight: ButtonHeight, NumItems: pAutoMapper->ConfigNamesNum() + 1, ItemsPerRow: 1, RowsPerScroll: 4, SelectedIndex: s_AutoMapConfigCurrent + 1, pRect: &View, Background: false);
2375 s_ListBox.DoAutoSpacing(Spacing: ButtonMargin);
2376
2377 for(int i = 0; i < pAutoMapper->ConfigNamesNum() + 1; i++)
2378 {
2379 static int s_NoneButton = 0;
2380 CListboxItem Item = s_ListBox.DoNextItem(pId: i == 0 ? (void *)&s_NoneButton : pAutoMapper->GetConfigName(Index: i - 1), Selected: (i - 1) == s_AutoMapConfigCurrent, CornerRadius: 3.0f);
2381 if(!Item.m_Visible)
2382 continue;
2383
2384 CUIRect Label;
2385 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
2386
2387 SLabelProperties Props;
2388 Props.m_MaxWidth = Label.w;
2389 Props.m_EllipsisAtEnd = true;
2390 pEditor->Ui()->DoLabel(pRect: &Label, pText: i == 0 ? "None" : pAutoMapper->GetConfigName(Index: i - 1), Size: EditorFontSizes::MENU, Align: TEXTALIGN_ML, LabelProps: Props);
2391 }
2392
2393 int NewSelected = s_ListBox.DoEnd() - 1;
2394 if(NewSelected != s_AutoMapConfigCurrent)
2395 s_AutoMapConfigSelected = NewSelected;
2396
2397 return CUi::POPUP_KEEP_OPEN;
2398}
2399
2400void CEditor::PopupSelectConfigAutoMapInvoke(int Current, float x, float y)
2401{
2402 static SPopupMenuId s_PopupSelectConfigAutoMapId;
2403 s_AutoMapConfigSelected = -100;
2404 s_AutoMapConfigCurrent = Current;
2405 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: GetSelectedLayer(Index: 0));
2406 const int ItemCount = minimum(a: m_Map.m_vpImages[pLayer->m_Image]->m_AutoMapper.ConfigNamesNum(), b: 10);
2407 // Width for buttons is 120, 15 is the scrollbar width, 2 is the margin between both.
2408 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);
2409}
2410
2411int CEditor::PopupSelectConfigAutoMapResult()
2412{
2413 if(s_AutoMapConfigSelected == -100)
2414 return -100;
2415
2416 s_AutoMapConfigCurrent = s_AutoMapConfigSelected;
2417 s_AutoMapConfigSelected = -100;
2418 return s_AutoMapConfigCurrent;
2419}
2420
2421// DDRace
2422
2423CUi::EPopupMenuFunctionResult CEditor::PopupTele(void *pContext, CUIRect View, bool Active)
2424{
2425 CEditor *pEditor = static_cast<CEditor *>(pContext);
2426
2427 static int s_PreviousTeleNumber;
2428 static int s_PreviousCheckpointNumber;
2429 static int s_PreviousViewTeleNumber;
2430
2431 CUIRect NumberPicker;
2432 CUIRect FindEmptySlot;
2433 CUIRect FindFreeTeleSlot, FindFreeCheckpointSlot, FindFreeViewSlot;
2434
2435 View.VSplitRight(Cut: 15.f, pLeft: &NumberPicker, pRight: &FindEmptySlot);
2436 NumberPicker.VSplitRight(Cut: 2.f, pLeft: &NumberPicker, pRight: nullptr);
2437
2438 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeTeleSlot, pBottom: &FindEmptySlot);
2439 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeCheckpointSlot, pBottom: &FindEmptySlot);
2440 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindFreeViewSlot, pBottom: &FindEmptySlot);
2441
2442 FindFreeTeleSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeTeleSlot);
2443 FindFreeCheckpointSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeCheckpointSlot);
2444 FindFreeViewSlot.HMargin(Cut: 1.0f, pOtherRect: &FindFreeViewSlot);
2445
2446 auto ViewTele = [](CEditor *pEd) -> bool {
2447 if(!pEd->m_ViewTeleNumber)
2448 return false;
2449 int TeleX, TeleY;
2450 pEd->m_Map.m_pTeleLayer->GetPos(Number: pEd->m_ViewTeleNumber, Offset: -1, TeleX, TeleY);
2451 if(TeleX != -1 && TeleY != -1)
2452 {
2453 pEd->MapView()->SetWorldOffset({32.0f * TeleX + 0.5f, 32.0f * TeleY + 0.5f});
2454 return true;
2455 }
2456 return false;
2457 };
2458
2459 static std::vector<ColorRGBA> s_vColors = {
2460 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2461 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2462 ColorRGBA(0.5f, 1, 0.5f, 0.5f),
2463 };
2464 enum
2465 {
2466 PROP_TELE = 0,
2467 PROP_TELE_CP,
2468 PROP_TELE_VIEW,
2469 NUM_PROPS,
2470 };
2471
2472 // find next free numbers buttons
2473 {
2474 // Pressing ctrl+f will find next free numbers for both tele and checkpoints
2475
2476 static int s_NextFreeTelePid = 0;
2477 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)))
2478 {
2479 int TeleNumber = pEditor->FindNextFreeTeleNumber();
2480
2481 if(TeleNumber != -1)
2482 pEditor->m_TeleNumber = TeleNumber;
2483 }
2484
2485 static int s_NextFreeCheckpointPid = 0;
2486 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)))
2487 {
2488 int CPNumber = pEditor->FindNextFreeTeleNumber(IsCheckpoint: true);
2489
2490 if(CPNumber != -1)
2491 pEditor->m_TeleCheckpointNumber = CPNumber;
2492 }
2493
2494 static int s_NextFreeViewPid = 0;
2495 int btn = pEditor->DoButton_Editor(pId: &s_NextFreeViewPid, pText: "N", Checked: 0, pRect: &FindFreeViewSlot, Flags: 0, pToolTip: "[n] Show next tele with this number");
2496 if(btn || (Active && pEditor->Input()->KeyPress(Key: KEY_N)))
2497 s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2498 }
2499
2500 // number picker
2501 {
2502 CProperty aProps[] = {
2503 {.m_pName: "Number", .m_Value: pEditor->m_TeleNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2504 {.m_pName: "Checkpoint", .m_Value: pEditor->m_TeleCheckpointNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2505 {.m_pName: "View", .m_Value: pEditor->m_ViewTeleNumber, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2506 {.m_pName: nullptr},
2507 };
2508
2509 static int s_aIds[NUM_PROPS] = {0};
2510
2511 static int NewVal = 0;
2512 int Prop = pEditor->DoProperties(pToolbox: &NumberPicker, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal, vColors: s_vColors);
2513 if(Prop == PROP_TELE)
2514 pEditor->m_TeleNumber = (NewVal - 1 + 255) % 255 + 1;
2515 else if(Prop == PROP_TELE_CP)
2516 pEditor->m_TeleCheckpointNumber = (NewVal - 1 + 255) % 255 + 1;
2517 else if(Prop == PROP_TELE_VIEW)
2518 pEditor->m_ViewTeleNumber = (NewVal - 1 + 255) % 255 + 1;
2519
2520 if(s_PreviousTeleNumber == 1 || s_PreviousTeleNumber != pEditor->m_TeleNumber)
2521 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);
2522
2523 if(s_PreviousCheckpointNumber == 1 || s_PreviousCheckpointNumber != pEditor->m_TeleCheckpointNumber)
2524 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);
2525
2526 if(s_PreviousViewTeleNumber != pEditor->m_ViewTeleNumber)
2527 s_vColors[PROP_TELE_VIEW] = ViewTele(pEditor) ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2528 }
2529
2530 s_PreviousTeleNumber = pEditor->m_TeleNumber;
2531 s_PreviousCheckpointNumber = pEditor->m_TeleCheckpointNumber;
2532 s_PreviousViewTeleNumber = pEditor->m_ViewTeleNumber;
2533
2534 return CUi::POPUP_KEEP_OPEN;
2535}
2536
2537CUi::EPopupMenuFunctionResult CEditor::PopupSpeedup(void *pContext, CUIRect View, bool Active)
2538{
2539 CEditor *pEditor = static_cast<CEditor *>(pContext);
2540
2541 enum
2542 {
2543 PROP_FORCE = 0,
2544 PROP_MAXSPEED,
2545 PROP_ANGLE,
2546 NUM_PROPS
2547 };
2548
2549 CProperty aProps[] = {
2550 {.m_pName: "Force", .m_Value: pEditor->m_SpeedupForce, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2551 {.m_pName: "Max Speed", .m_Value: pEditor->m_SpeedupMaxSpeed, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2552 {.m_pName: "Angle", .m_Value: pEditor->m_SpeedupAngle, .m_Type: PROPTYPE_ANGLE_SCROLL, .m_Min: 0, .m_Max: 359},
2553 {.m_pName: nullptr},
2554 };
2555
2556 static int s_aIds[NUM_PROPS] = {0};
2557 int NewVal = 0;
2558 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2559
2560 if(Prop == PROP_FORCE)
2561 {
2562 pEditor->m_SpeedupForce = clamp(val: NewVal, lo: 1, hi: 255);
2563 }
2564 else if(Prop == PROP_MAXSPEED)
2565 {
2566 pEditor->m_SpeedupMaxSpeed = clamp(val: NewVal, lo: 0, hi: 255);
2567 }
2568 else if(Prop == PROP_ANGLE)
2569 {
2570 pEditor->m_SpeedupAngle = clamp(val: NewVal, lo: 0, hi: 359);
2571 }
2572
2573 return CUi::POPUP_KEEP_OPEN;
2574}
2575
2576CUi::EPopupMenuFunctionResult CEditor::PopupSwitch(void *pContext, CUIRect View, bool Active)
2577{
2578 CEditor *pEditor = static_cast<CEditor *>(pContext);
2579
2580 CUIRect NumberPicker, FindEmptySlot, ViewEmptySlot;
2581
2582 View.VSplitRight(Cut: 15.0f, pLeft: &NumberPicker, pRight: &FindEmptySlot);
2583 NumberPicker.VSplitRight(Cut: 2.0f, pLeft: &NumberPicker, pRight: nullptr);
2584
2585 FindEmptySlot.HSplitTop(Cut: 13.0f, pTop: &FindEmptySlot, pBottom: &ViewEmptySlot);
2586 ViewEmptySlot.HSplitTop(Cut: 13.0f, pTop: nullptr, pBottom: &ViewEmptySlot);
2587
2588 FindEmptySlot.HMargin(Cut: 1.0f, pOtherRect: &FindEmptySlot);
2589 ViewEmptySlot.HMargin(Cut: 1.0f, pOtherRect: &ViewEmptySlot);
2590
2591 auto ViewSwitch = [pEditor]() -> bool {
2592 if(!pEditor->m_ViewSwitch)
2593 return false;
2594 ivec2 SwitchPos;
2595 pEditor->m_Map.m_pSwitchLayer->GetPos(Number: pEditor->m_ViewSwitch, Offset: -1, SwitchPos);
2596 if(SwitchPos != ivec2(-1, -1))
2597 {
2598 pEditor->MapView()->SetWorldOffset({32.0f * SwitchPos.x + 0.5f, 32.0f * SwitchPos.y + 0.5f});
2599 return true;
2600 }
2601 return false;
2602 };
2603
2604 static std::vector<ColorRGBA> s_vColors = {
2605 ColorRGBA(1, 1, 1, 0.5f),
2606 ColorRGBA(1, 1, 1, 0.5f),
2607 ColorRGBA(1, 1, 1, 0.5f),
2608 };
2609
2610 enum
2611 {
2612 PROP_SWITCH_NUMBER = 0,
2613 PROP_SWITCH_DELAY,
2614 PROP_SWITCH_VIEW,
2615 NUM_PROPS,
2616 };
2617
2618 // find empty number button
2619 {
2620 static int s_EmptySlotPid = 0;
2621 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)))
2622 {
2623 int Number = pEditor->FindNextFreeSwitchNumber();
2624
2625 if(Number != -1)
2626 pEditor->m_SwitchNum = Number;
2627 }
2628
2629 static int s_NextViewPid = 0;
2630 int ButtonResult = pEditor->DoButton_Editor(pId: &s_NextViewPid, pText: "N", Checked: 0, pRect: &ViewEmptySlot, Flags: 0, pToolTip: "[n] Show next switcher with this number");
2631 if(ButtonResult || (Active && pEditor->Input()->KeyPress(Key: KEY_N)))
2632 s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2633 }
2634
2635 // number picker
2636 static int s_PreviousNumber = -1;
2637 static int s_PreviousView = -1;
2638 {
2639 CProperty aProps[] = {
2640 {.m_pName: "Number", .m_Value: pEditor->m_SwitchNum, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2641 {.m_pName: "Delay", .m_Value: pEditor->m_SwitchDelay, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2642 {.m_pName: "View", .m_Value: pEditor->m_ViewSwitch, .m_Type: PROPTYPE_INT, .m_Min: 0, .m_Max: 255},
2643 {.m_pName: nullptr},
2644 };
2645
2646 static int s_aIds[NUM_PROPS] = {0};
2647 int NewVal = 0;
2648 int Prop = pEditor->DoProperties(pToolbox: &NumberPicker, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal, vColors: s_vColors);
2649
2650 if(Prop == PROP_SWITCH_NUMBER)
2651 {
2652 pEditor->m_SwitchNum = (NewVal + 256) % 256;
2653 }
2654 else if(Prop == PROP_SWITCH_DELAY)
2655 {
2656 pEditor->m_SwitchDelay = (NewVal + 256) % 256;
2657 }
2658 else if(Prop == PROP_SWITCH_VIEW)
2659 {
2660 pEditor->m_ViewSwitch = (NewVal + 256) % 256;
2661 }
2662
2663 if(s_PreviousNumber == 1 || s_PreviousNumber != pEditor->m_SwitchNum)
2664 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);
2665 if(s_PreviousView != pEditor->m_ViewSwitch)
2666 s_vColors[PROP_SWITCH_VIEW] = ViewSwitch() ? ColorRGBA(0.5f, 1, 0.5f, 0.5f) : ColorRGBA(1, 0.5f, 0.5f, 0.5f);
2667 }
2668
2669 s_PreviousNumber = pEditor->m_SwitchNum;
2670 s_PreviousView = pEditor->m_ViewSwitch;
2671 return CUi::POPUP_KEEP_OPEN;
2672}
2673
2674CUi::EPopupMenuFunctionResult CEditor::PopupTune(void *pContext, CUIRect View, bool Active)
2675{
2676 CEditor *pEditor = static_cast<CEditor *>(pContext);
2677
2678 enum
2679 {
2680 PROP_TUNE = 0,
2681 NUM_PROPS,
2682 };
2683
2684 CProperty aProps[] = {
2685 {.m_pName: "Zone", .m_Value: pEditor->m_TuningNum, .m_Type: PROPTYPE_INT, .m_Min: 1, .m_Max: 255},
2686 {.m_pName: nullptr},
2687 };
2688
2689 static int s_aIds[NUM_PROPS] = {0};
2690 int NewVal = 0;
2691 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2692
2693 if(Prop == PROP_TUNE)
2694 {
2695 pEditor->m_TuningNum = (NewVal - 1 + 255) % 255 + 1;
2696 }
2697
2698 return CUi::POPUP_KEEP_OPEN;
2699}
2700
2701CUi::EPopupMenuFunctionResult CEditor::PopupGoto(void *pContext, CUIRect View, bool Active)
2702{
2703 CEditor *pEditor = static_cast<CEditor *>(pContext);
2704
2705 enum
2706 {
2707 PROP_COORD_X = 0,
2708 PROP_COORD_Y,
2709 NUM_PROPS,
2710 };
2711
2712 static ivec2 s_GotoPos(0, 0);
2713
2714 CProperty aProps[] = {
2715 {.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()},
2716 {.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()},
2717 {.m_pName: nullptr},
2718 };
2719
2720 static int s_aIds[NUM_PROPS] = {0};
2721 int NewVal = 0;
2722 int Prop = pEditor->DoProperties(pToolbox: &View, pProps: aProps, pIds: s_aIds, pNewVal: &NewVal);
2723
2724 if(Prop == PROP_COORD_X)
2725 {
2726 s_GotoPos.x = NewVal;
2727 }
2728 else if(Prop == PROP_COORD_Y)
2729 {
2730 s_GotoPos.y = NewVal;
2731 }
2732
2733 CUIRect Button;
2734 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &Button);
2735
2736 static int s_Button;
2737 if(pEditor->DoButton_Editor(pId: &s_Button, pText: "Go", Checked: 0, pRect: &Button, Flags: 0, pToolTip: nullptr))
2738 {
2739 pEditor->MapView()->SetWorldOffset({32.0f * s_GotoPos.x + 0.5f, 32.0f * s_GotoPos.y + 0.5f});
2740 }
2741
2742 return CUi::POPUP_KEEP_OPEN;
2743}
2744
2745CUi::EPopupMenuFunctionResult CEditor::PopupEntities(void *pContext, CUIRect View, bool Active)
2746{
2747 CEditor *pEditor = static_cast<CEditor *>(pContext);
2748
2749 for(size_t i = 0; i < pEditor->m_vSelectEntitiesFiles.size(); i++)
2750 {
2751 CUIRect Button;
2752 View.HSplitTop(Cut: 14.0f, pTop: &Button, pBottom: &View);
2753
2754 const char *pName = pEditor->m_vSelectEntitiesFiles[i].c_str();
2755 if(pEditor->DoButton_MenuItem(pId: pName, pText: pName, Checked: pEditor->m_vSelectEntitiesFiles[i] == pEditor->m_SelectEntitiesImage, pRect: &Button))
2756 {
2757 if(pEditor->m_vSelectEntitiesFiles[i] != pEditor->m_SelectEntitiesImage)
2758 {
2759 pEditor->m_SelectEntitiesImage = pEditor->m_vSelectEntitiesFiles[i];
2760 pEditor->m_AllowPlaceUnusedTiles = pEditor->m_SelectEntitiesImage == "DDNet" ? 0 : -1;
2761 pEditor->m_PreventUnusedTilesWasWarned = false;
2762
2763 if(pEditor->m_EntitiesTexture.IsValid())
2764 pEditor->Graphics()->UnloadTexture(pIndex: &pEditor->m_EntitiesTexture);
2765
2766 char aBuf[IO_MAX_PATH_LENGTH];
2767 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "editor/entities/%s.png", pName);
2768 pEditor->m_EntitiesTexture = pEditor->Graphics()->LoadTexture(pFilename: aBuf, StorageType: IStorage::TYPE_ALL, Flags: pEditor->GetTextureUsageFlag());
2769 return CUi::POPUP_CLOSE_CURRENT;
2770 }
2771 }
2772 }
2773
2774 return CUi::POPUP_KEEP_OPEN;
2775}
2776
2777CUi::EPopupMenuFunctionResult CEditor::PopupProofMode(void *pContext, CUIRect View, bool Active)
2778{
2779 CEditor *pEditor = static_cast<CEditor *>(pContext);
2780
2781 CUIRect Button;
2782 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2783 static int s_ButtonIngame;
2784 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."))
2785 {
2786 pEditor->MapView()->ProofMode()->SetModeIngame();
2787 return CUi::POPUP_CLOSE_CURRENT;
2788 }
2789
2790 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &View);
2791 View.HSplitTop(Cut: 12.0f, pTop: &Button, pBottom: &View);
2792 static int s_ButtonMenu;
2793 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."))
2794 {
2795 pEditor->MapView()->ProofMode()->SetModeMenu();
2796 return CUi::POPUP_CLOSE_CURRENT;
2797 }
2798
2799 return CUi::POPUP_KEEP_OPEN;
2800}
2801
2802CUi::EPopupMenuFunctionResult CEditor::PopupAnimateSettings(void *pContext, CUIRect View, bool Active)
2803{
2804 CEditor *pEditor = static_cast<CEditor *>(pContext);
2805
2806 constexpr float MIN_ANIM_SPEED = 0.001f;
2807 constexpr float MAX_ANIM_SPEED = 1000000.0f;
2808
2809 CUIRect Row, Label, ButtonDecrease, EditBox, ButtonIncrease, ButtonReset;
2810 View.HSplitTop(Cut: 13.0f, pTop: &Row, pBottom: &View);
2811 Row.VSplitMid(pLeft: &Label, pRight: &Row);
2812 Row.HMargin(Cut: 1.0f, pOtherRect: &Row);
2813 Row.VSplitLeft(Cut: 10.0f, pLeft: &ButtonDecrease, pRight: &Row);
2814 Row.VSplitRight(Cut: 10.0f, pLeft: &EditBox, pRight: &ButtonIncrease);
2815 View.HSplitBottom(Cut: 12.0f, pTop: &View, pBottom: &ButtonReset);
2816 pEditor->Ui()->DoLabel(pRect: &Label, pText: "Speed", Size: 10.0f, Align: TEXTALIGN_ML);
2817
2818 static char s_DecreaseButton;
2819 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))
2820 {
2821 pEditor->m_AnimateSpeed -= pEditor->m_AnimateSpeed <= 1.0f ? 0.1f : 0.5f;
2822 pEditor->m_AnimateSpeed = maximum(a: pEditor->m_AnimateSpeed, b: MIN_ANIM_SPEED);
2823 pEditor->m_AnimateUpdatePopup = true;
2824 }
2825
2826 static char s_IncreaseButton;
2827 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))
2828 {
2829 if(pEditor->m_AnimateSpeed < 0.1f)
2830 pEditor->m_AnimateSpeed = 0.1f;
2831 else
2832 pEditor->m_AnimateSpeed += pEditor->m_AnimateSpeed < 1.0f ? 0.1f : 0.5f;
2833 pEditor->m_AnimateSpeed = minimum(a: pEditor->m_AnimateSpeed, b: MAX_ANIM_SPEED);
2834 pEditor->m_AnimateUpdatePopup = true;
2835 }
2836
2837 static char s_DefaultButton;
2838 if(pEditor->DoButton_Ex(pId: &s_DefaultButton, pText: "Default", Checked: 0, pRect: &ButtonReset, Flags: 0, pToolTip: "Normal animation speed", Corners: IGraphics::CORNER_ALL))
2839 {
2840 pEditor->m_AnimateSpeed = 1.0f;
2841 pEditor->m_AnimateUpdatePopup = true;
2842 }
2843
2844 static CLineInputNumber s_SpeedInput;
2845 if(pEditor->m_AnimateUpdatePopup)
2846 {
2847 s_SpeedInput.SetFloat(pEditor->m_AnimateSpeed);
2848 pEditor->m_AnimateUpdatePopup = false;
2849 }
2850
2851 if(pEditor->DoEditBox(pLineInput: &s_SpeedInput, pRect: &EditBox, FontSize: 10.0f, Corners: IGraphics::CORNER_NONE, pToolTip: "The animation speed"))
2852 {
2853 pEditor->m_AnimateSpeed = clamp(val: s_SpeedInput.GetFloat(), lo: MIN_ANIM_SPEED, hi: MAX_ANIM_SPEED);
2854 }
2855
2856 return CUi::POPUP_KEEP_OPEN;
2857}
2858