File size: 12,293 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
if not Platform.developer then
	EditedMapVariation = false
	return
end

----- Notes about the map variation editing internal state
--
-- 1. The *loaded* map variation is stored in CurrentMapVariation; see MapVariationPreset
-- 2. The *edited* map variation is in EditedMapVariation (either false or equal to CurrentMapVariation)
-- 3. The user can hide the *edited map variation*; in this state:
--    a) the user is editing the *base map*, using a different undo queue
--    b) EditedMapVariation becomes false; and HiddenMapVariationUndoIndex stores the last index in the
--       "map variation undo queue"; we rewind to this index when we go back to the *edited map variation*
--    c) the "save map" action saves the *base map*

local function reset_edited_variation()
	EditedMapVariation = false
	EditedMapVariationUndoQueue = false
	OriginalEditorUndoQueue = false
	HiddenMapVariationUndoIndex = false
end

if FirstLoad then
	reset_edited_variation()
end
OnMsg.ChangeMap = reset_edited_variation

local function start_editing_loaded_variation()
	assert(CurrentMapVariation and not EditedMapVariation)
	EditedMapVariationUndoQueue = XEditorUndo
	OriginalEditorUndoQueue = XEditorUndoQueue:new()
	HiddenMapVariationUndoIndex = false
	
	EditedMapVariation = CurrentMapVariation
	XEditorUpdateStatusText()
end

function OnMsg.GameEnterEditor()
	-- initiate editing of the current map variation
	if CurrentMapVariation and not EditedMapVariation and not HiddenMapVariationUndoIndex then
		start_editing_loaded_variation()
	end
end

local function drop_edited_variation_changes()
	assert(CurrentMapVariation and EditedMapVariation and not HiddenMapVariationUndoIndex)
	
	-- reverse everything from the currently edited variation
	HiddenMapVariationUndoIndex = XEditorUndo.undo_index
	while XEditorUndo.undo_index ~= 0 do
		XEditorUndo:UndoRedo("undo")
	end
	EditedMapVariation = false
	EditorMapDirty = false
	XEditorUpdateStatusText()
	
	-- switch back to the "base map editing" undo queue
	XEditorUndo = OriginalEditorUndoQueue
	XEditorUpdateToolbars()
end

local function restore_edited_variation_changes()
	assert(CurrentMapVariation and not EditedMapVariation and HiddenMapVariationUndoIndex)
	
	-- switch to the "map variation editing" undo queue
	XEditorUndo = EditedMapVariationUndoQueue
	XEditorUpdateToolbars()
	
	-- restore map variation changes
	while XEditorUndo.undo_index ~= HiddenMapVariationUndoIndex do
		XEditorUndo:UndoRedo("redo")
	end
	HiddenMapVariationUndoIndex = false
	EditedMapVariation = CurrentMapVariation
	EditorMapDirty = false
	XEditorUpdateStatusText()
end

local function ask_save_and_drop_variation()
	assert(CurrentMapVariation)
	local save_map =
		WaitQuestion(nil, Untranslated("Save changes?"),
			Untranslated(string.format("This will remove any changes made by the current map variation '%s'.\nSave it?", MapVariationNameText(CurrentMapVariation))),
			Untranslated("Yes"), Untranslated("No")) == "ok"
	if HiddenMapVariationUndoIndex then
		restore_edited_variation_changes()
	end
	if save_map then
		SaveMap()
	end
	drop_edited_variation_changes()
end

local function ensure_map_saved(drop_variation)
	if drop_variation and CurrentMapVariation then
		ask_save_and_drop_variation()
	elseif EditorMapDirty then
		if WaitQuestion(nil, Untranslated("Map Not Saved"), Untranslated("The map must be saved before creating/editing a variation.")) ~= "ok" then
			return false
		end
		SaveMap()
	end
	return true
end

function IsMapVariationEdited(preset)
	return EditedMapVariation == preset or CurrentMapVariation == preset and HiddenMapVariationUndoIndex
end

function StopEditingCurrentMapVariation(keep_changes)
	if not keep_changes and not HiddenMapVariationUndoIndex then
		drop_edited_variation_changes()
	end
	CurrentMapVariation = false
	EditedMapVariation = false
	EditedMapVariationUndoQueue = false
	HiddenMapVariationUndoIndex = false
	XEditorUndo = OriginalEditorUndoQueue
	OriginalEditorUndoQueue = false
	XEditorUpdateStatusText()
end


----- UI actions

function XEditorCreateNewVariation()
	if not ensure_map_saved("drop_variation") then
		return
	end
	
	local name = WaitInputText(nil, "Map Variation", "Enter variation name...")
	if not name then return end
	
	local save_in = WaitListChoice(nil, DlcComboItems(), "Save In")
	if not save_in then return end
	save_in = save_in.value
	
	if FindMapVariation(name, save_in) then
		local message = string.format("The map variation '%s' already exists%s.\n\nOverwrite?",
			name, save_in =="" and "" or string.format(" in DLC '%s'", save_in), save_in)
		if WaitQuestion(nil, Untranslated("Error"), Untranslated(message)) ~= "ok" then
			return
		end
	end
	
	XEditorUndo = XEditorUndoQueue:new() -- start on an empty undo queue
	CreateMapVariation(name, save_in)
	start_editing_loaded_variation()
end

function XEditorEditVariation(variation)
	assert(variation and CurrentMapVariation ~= variation)
	if CurrentMapVariation == variation then return end
	
	if not ensure_map_saved("drop_variation") then
		return
	end
	
	-- replace the undo queue with one for editing the map variation
	OriginalEditorUndoQueue = XEditorUndo
	EditedMapVariationUndoQueue = XEditorUndoQueue:new()
	HiddenMapVariationUndoIndex = false
	XEditorUndo = EditedMapVariationUndoQueue
	XEditorUpdateToolbars() -- update undo queue combo
	
	-- apply the patch in this new undo queue for editing
	XEditorApplyMapPatch(variation:GetMapPatchPath())
	CurrentMapVariation = variation
	EditedMapVariation = variation
	XEditorUpdateStatusText() -- update edited map variation, variations button
end

function XEditorHideShowVariation(variation)
	assert(variation == CurrentMapVariation)
	if HiddenMapVariationUndoIndex then
		if not ensure_map_saved() then
			return
		end
		restore_edited_variation_changes()
	else
		drop_edited_variation_changes()
	end
end

function XEditorDeleteVariation(variation)
	if WaitQuestion(nil, Untranslated("Confirmation"),
		Untranslated(string.format("Delete map variation %s?", MapVariationNameText(variation)))) == "ok"
	then
		if variation == CurrentMapVariation then
			StopEditingCurrentMapVariation()
		end
		variation:OnEditorDelete()
		variation:delete()
	end
end

function XEditorMergeVariation(variation)
	assert(variation == CurrentMapVariation)
	if WaitQuestion(nil, Untranslated("Confirmation"),
		Untranslated(string.format("Merge map variation %s into the original map and delete the variation?", MapVariationNameText(variation)))) == "ok"
	then
		if HiddenMapVariationUndoIndex then
			restore_edited_variation_changes()
		end
		StopEditingCurrentMapVariation("keep_changes")
		variation:OnEditorDelete()
		variation:delete()
		XEditorSaveMap()
	end
end


----- Map variations popup UI

DefineClass("XDarkModeAwarePopupList", "XPopupList", "XDarkModeAwareDialog")

function XDarkModeAwarePopupList:Open(...)
	XPopupList.Open(self, ...)
	self:SetDarkMode(GetDarkModeSetting())
end

function XDarkModeAwarePopupList:OnShortcut(shortcut, source, ...)
	if shortcut == "Escape" or shortcut == "ButtonB" then
		self:Close()
		return "break"
	end
end

function XEditorOpenMapVariationsPopup()
	local popup = XDarkModeAwarePopupList:new({
		Id = "idMapVariationsPopup",
		Margins = box(0, 2, 0, 2),
		Padding = box(3, 0, 3, 5),
		MinWidth = 360,
		LayoutMethod = "VList",
		DrawOnTop = true,
		HandleMouse = true,
		OnMouseButtonUp = function(self, pt, button)
			if button == "L" then
				if not self:MouseInWindow(pt) then
					self:Close()
				end
				return "break"
			elseif button == "R" then
				self:Close()
				return "break"
			end
		end,
	}, terminal.desktop)
	
	-- header
	local title = XText:new({ TextStyle = "GedTitle", TextHAlign = "center", }, popup)
	title:SetText("Map Variations")
	XWindow:new({ Background = RGB(0, 0, 0), Margins = box(10, 1, 10, 1), MinHeight = 1 }, popup) -- separator
	
	-- footer
	local button_holder = XWindow:new({ Dock = "bottom", HAlign = "center", LayoutMethod = "HList" }, popup)
	XTextButton:new({
		BorderWidth = 1,
		Margins = box(2, 2, 2, 2),
		VAlign = "center",
		Text = "Create new variation",
		FocusedBorderColor = RGB(128, 128, 128),
		DisabledBorderColor = RGB(128, 128, 128),
		RolloverBorderColor = RGB(128, 128, 128),
		PressedBorderColor = RGB(128, 128, 128),
		OnPress = function(button) XEditorCloseVariationsPopup() CreateRealTimeThread(XEditorCreateNewVariation) end,
	}, button_holder)
	if Presets.MapVariationPreset then
		XTextButton:new({
			BorderWidth = 1,
			Margins = box(2, 2, 2, 2),
			VAlign = "center",
			Text = "Manage",
			FocusedBorderColor = RGB(128, 128, 128),
			DisabledBorderColor = RGB(128, 128, 128),
			RolloverBorderColor = RGB(128, 128, 128),
			PressedBorderColor = RGB(128, 128, 128),
			OnPress = function(button)
				if CurrentMapVariation then
					CurrentMapVariation:OpenEditor()
				else
					OpenPresetEditor("MapVariationPreset")
				end
			end,
		}, button_holder)
	end
	local help_text = XText:new({ Dock = "bottom", TextHAlign = "center" }, popup)
	help_text:SetText("<color 128 128 128>Map variations are saved as patches over the base map.\nThis allows editing the base map and the variation changes separately.")
	
	for idx, item in ipairs(MapVariationItems(CurrentMap)) do
		local entry = XContextWindow:new({
			IdNode = true,
			Margins = box(3, 2, 3, 2),
			OnSetRollover = function(self, rollover)
				self:SetBackground(rollover and RGBA(128, 128, 128, 40) or 0)
				self.idHintText:SetVisible(rollover and CurrentMapVariation ~= self.context.value)
			end
		}, popup, item)
		
		-- delete button
		XTextButton:new({
			Dock = "left",
			VAlign = "center",
			Text = "x",
			MaxWidth = 20,
			MaxHeight = 16,
			LayoutHSpacing = 0,
			Padding = box(1, 1, 0, 1),
			Background = RGBA(0, 0, 0, 0),
			RolloverBackground = RGB(204, 232, 255),
			PressedBackground = RGB(121, 189, 241),
			OnPress = function(button) XEditorCloseVariationsPopup() CreateRealTimeThread(XEditorDeleteVariation, item.value) end,
		}, entry)
		
		-- show/hide button
		local showhide_button = XTextButton:new({
			Id = "idShowHideButton",
			Dock = "right",
			FoldWhenHidden = true,
			Margins = box(0, 0, 3, 0),
			BorderWidth = 1,
			VAlign = "center",
			Text = HiddenMapVariationUndoIndex and "Show" or "Hide",
			FocusedBorderColor = RGB(128, 128, 128),
			DisabledBorderColor = RGB(128, 128, 128),
			RolloverBorderColor = RGB(128, 128, 128),
			PressedBorderColor = RGB(128, 128, 128),
			OnPress = function(button) XEditorCloseVariationsPopup() CreateRealTimeThread(XEditorHideShowVariation, item.value) end,
		}, entry)
		showhide_button:SetVisible(CurrentMapVariation == item.value)
		
		-- merge button
		local merge_button = XTextButton:new({
			Id = "idMergeButton",
			Dock = "right",
			FoldWhenHidden = true,
			Margins = box(5, 0, 5, 0),
			BorderWidth = 1,
			VAlign = "center",
			Text = "Merge",
			FocusedBorderColor = RGB(128, 128, 128),
			DisabledBorderColor = RGB(128, 128, 128),
			RolloverBorderColor = RGB(128, 128, 128),
			PressedBorderColor = RGB(128, 128, 128),
			OnPress = function(button) XEditorCloseVariationsPopup() CreateRealTimeThread(XEditorMergeVariation, item.value) end,
		}, entry)
		merge_button:SetVisible(CurrentMapVariation == item.value)
		
		-- edit hint
		local hint_text = XText:new({ Id = "idHintText", Dock = "right", HandleMouse = false, }, entry)
		hint_text:SetText("(click to edit)")
		hint_text:SetVisible(false)
		
		-- variation name
		local name = XText:new({ Dock = "box", Padding = box(0, 2, 2, 2), }, entry)
		name:SetText(item.text)
		name:SetTextStyle(CurrentMapVariation == item.value and "GedHighlight" or "GedDefault")
		name.OnMouseButtonDown = function(win, pt, button)
			XEditorCloseVariationsPopup()
			if CurrentMapVariation ~= item.value then
				CreateRealTimeThread(XEditorEditVariation, item.value)
			end
			return "break"
		end
	end
	
	popup.idContainer:SetBackground(RGB(255, 255, 255))
	popup:SetAnchor(XEditorGetMapButton("idMapVariationsButton").parent.box)
	popup:SetAnchorType("top")
	popup:Open()
	popup:SetModal()
	popup:SetFocus()
	popup.popup_parent = GetDialog("XEditor")
	Msg("XWindowRecreated", popup)
end

function XEditorCloseVariationsPopup()
	local popup = rawget(terminal.desktop, "idMapVariationsPopup")
	if popup then
		popup:Close()
	end
end

OnMsg.GameExitEditor = XEditorCloseVariationsPopup