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