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