1 | /**
|
---|
2 | * Translated JSON file contents.
|
---|
3 | */
|
---|
4 | var g_Options;
|
---|
5 |
|
---|
6 | /**
|
---|
7 | * Names of config keys that have changed, value returned when closing the page.
|
---|
8 | */
|
---|
9 | var g_ChangedKeys;
|
---|
10 |
|
---|
11 | /**
|
---|
12 | * Vertical size of a tab button.
|
---|
13 | */
|
---|
14 | var g_TabButtonHeight = 30;
|
---|
15 |
|
---|
16 | /**
|
---|
17 | * Vertical space between two tab buttons.
|
---|
18 | */
|
---|
19 | var g_TabButtonDist = 5;
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * Vertical distance between the top of the page and the first option.
|
---|
23 | */
|
---|
24 | var g_OptionControlOffset = 5;
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * Vertical size of each option control.
|
---|
28 | */
|
---|
29 | var g_OptionControlHeight = 26;
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * Vertical distance between two consecutive options.
|
---|
33 | */
|
---|
34 | var g_OptionControlDist = 2;
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Horizontal indentation to distinguish options that depend on another option.
|
---|
38 | */
|
---|
39 | var g_DependentLabelIndentation = 25;
|
---|
40 |
|
---|
41 | /**
|
---|
42 | * Color used to indicate that the string entered by the player isn't a sane color.
|
---|
43 | */
|
---|
44 | var g_InsaneColor = "255 0 255";
|
---|
45 |
|
---|
46 | /**
|
---|
47 | * Defines the parsing of config strings and GUI control interaction for the different option types.
|
---|
48 | *
|
---|
49 | * @property configToValue - parses a string from the user config to a value of the declared type.
|
---|
50 | * @property valueToGui - sets the GUI control to display the given value.
|
---|
51 | * @property guiToValue - returns the value of the GUI control.
|
---|
52 | * @property guiSetter - event name that should be considered a value change of the GUI control.
|
---|
53 | * @property initGUI - sets properties of the GUI control that are independent of the current value.
|
---|
54 | * @property sanitizeValue - Displays a visual clue if the entered value is invalid and returns a sane value.
|
---|
55 | * @property tooltip - appends a custom tooltip to the given option description depending on the current value.
|
---|
56 | */
|
---|
57 | var g_OptionType = {
|
---|
58 | "boolean":
|
---|
59 | {
|
---|
60 | "configToValue": config => config == "true",
|
---|
61 | "valueToGui": (value, control) => {
|
---|
62 | control.checked = value;
|
---|
63 | },
|
---|
64 | "guiToValue": control => control.checked,
|
---|
65 | "guiSetter": "onPress"
|
---|
66 | },
|
---|
67 | "string":
|
---|
68 | {
|
---|
69 | "configToValue": value => value,
|
---|
70 | "valueToGui": (value, control) => {
|
---|
71 | control.caption = value;
|
---|
72 | },
|
---|
73 | "guiToValue": control => control.caption,
|
---|
74 | "guiSetter": "onTextEdit"
|
---|
75 | },
|
---|
76 | "color":
|
---|
77 | {
|
---|
78 | "configToValue": value => value,
|
---|
79 | "valueToGui": (value, control) => {
|
---|
80 | control.caption = value;
|
---|
81 | },
|
---|
82 | "guiToValue": control => control.caption,
|
---|
83 | "guiSetter": "onTextEdit",
|
---|
84 | "sanitizeValue": (value, control, option) => {
|
---|
85 | let color = guiToRgbColor(value);
|
---|
86 | let sanitized = rgbToGuiColor(color);
|
---|
87 | if (control)
|
---|
88 | {
|
---|
89 | control.sprite = sanitized == value ? "ModernDarkBoxWhite" : "ModernDarkBoxWhiteInvalid";
|
---|
90 | control.children[1].sprite = sanitized == value ? "color:" + value : "color:" + g_InsaneColor;
|
---|
91 | }
|
---|
92 | return sanitized;
|
---|
93 | },
|
---|
94 | "tooltip": (value, option) =>
|
---|
95 | sprintf(translate("Default: %(value)s"), {
|
---|
96 | "value": Engine.ConfigDB_GetValue("default", option.config)
|
---|
97 | })
|
---|
98 | },
|
---|
99 | "number":
|
---|
100 | {
|
---|
101 | "configToValue": value => value,
|
---|
102 | "valueToGui": (value, control) => {
|
---|
103 | control.caption = value;
|
---|
104 | },
|
---|
105 | "guiToValue": control => control.caption,
|
---|
106 | "guiSetter": "onTextEdit",
|
---|
107 | "sanitizeValue": (value, control, option) => {
|
---|
108 | let sanitized =
|
---|
109 | Math.min(option.max !== undefined ? option.max : +Infinity,
|
---|
110 | Math.max(option.min !== undefined ? option.min : -Infinity,
|
---|
111 | isNaN(+value) ? 0 : value));
|
---|
112 |
|
---|
113 | if (control)
|
---|
114 | control.sprite = sanitized == value ? "ModernDarkBoxWhite" : "ModernDarkBoxWhiteInvalid";
|
---|
115 |
|
---|
116 | return sanitized;
|
---|
117 | },
|
---|
118 | "tooltip": (value, option) =>
|
---|
119 | sprintf(
|
---|
120 | option.min !== undefined && option.max !== undefined ?
|
---|
121 | translateWithContext("option number", "Min: %(min)s, Max: %(max)s") :
|
---|
122 | option.min !== undefined && option.max === undefined ?
|
---|
123 | translateWithContext("option number", "Min: %(min)s") :
|
---|
124 | option.min === undefined && option.max !== undefined ?
|
---|
125 | translateWithContext("option number", "Max: %(max)s") :
|
---|
126 | "",
|
---|
127 | {
|
---|
128 | "min": option.min,
|
---|
129 | "max": option.max
|
---|
130 | })
|
---|
131 | },
|
---|
132 | "dropdown":
|
---|
133 | {
|
---|
134 | "configToValue": value => value,
|
---|
135 | "valueToGui": (value, control) => {
|
---|
136 | control.selected = control.list_data.indexOf(value);
|
---|
137 | },
|
---|
138 | "guiToValue": control => control.list_data[control.selected],
|
---|
139 | "guiSetter": "onSelectionChange",
|
---|
140 | "initGUI": (option, control) => {
|
---|
141 | control.list = option.list.map(e => e.label);
|
---|
142 | control.list_data = option.list.map(e => e.value);
|
---|
143 | control.onHoverChange = () => {
|
---|
144 | let item = option.list[control.hovered];
|
---|
145 | control.tooltip = item && item.tooltip || option.tooltip;
|
---|
146 | };
|
---|
147 | }
|
---|
148 | },
|
---|
149 | "dropdownNumber":
|
---|
150 | {
|
---|
151 | "configToValue": value => +value,
|
---|
152 | "valueToGui": (value, control) => {
|
---|
153 | control.selected = control.list_data.indexOf("" + value);
|
---|
154 | },
|
---|
155 | "guiToValue": control => +control.list_data[control.selected],
|
---|
156 | "guiSetter": "onSelectionChange",
|
---|
157 | "initGUI": (option, control) => {
|
---|
158 | control.list = option.list.map(e => e.label);
|
---|
159 | control.list_data = option.list.map(e => e.value);
|
---|
160 | control.onHoverChange = () => {
|
---|
161 | const item = option.list[control.hovered];
|
---|
162 | control.tooltip = item && item.tooltip || option.tooltip;
|
---|
163 | };
|
---|
164 | },
|
---|
165 | "timeout": (option, oldValue, hasChanges, newValue) => {
|
---|
166 | if (!option.timeout)
|
---|
167 | return;
|
---|
168 | timedConfirmation(
|
---|
169 | 500, 200,
|
---|
170 | translate("Do you want to keep changes?"),
|
---|
171 | 500,
|
---|
172 | translate("Warning"),
|
---|
173 | [translate("No"), translate("Yes")],
|
---|
174 | [() => {this.revertChange(option, +oldValue, hasChanges);}, null]
|
---|
175 | );
|
---|
176 | }
|
---|
177 | },
|
---|
178 | "slider":
|
---|
179 | {
|
---|
180 | "configToValue": value => +value,
|
---|
181 | "valueToGui": (value, control) => {
|
---|
182 | control.value = +value;
|
---|
183 | },
|
---|
184 | "guiToValue": control => control.value,
|
---|
185 | "guiSetter": "onValueChange",
|
---|
186 | "initGUI": (option, control) => {
|
---|
187 | control.max_value = option.max;
|
---|
188 | control.min_value = option.min;
|
---|
189 | },
|
---|
190 | "tooltip": (value, option) =>
|
---|
191 | sprintf(translateWithContext("slider number", "Value: %(val)s (min: %(min)s, max: %(max)s)"), {
|
---|
192 | "val": value.toFixed(2),
|
---|
193 | "min": option.min.toFixed(2),
|
---|
194 | "max": option.max.toFixed(2)
|
---|
195 | })
|
---|
196 | }
|
---|
197 | };
|
---|
198 |
|
---|
199 | function init(data, hotloadData)
|
---|
200 | {
|
---|
201 | g_ChangedKeys = hotloadData ? hotloadData.changedKeys : new Set();
|
---|
202 | g_TabCategorySelected = hotloadData ? hotloadData.tabCategorySelected : 0;
|
---|
203 |
|
---|
204 | g_Options = Engine.ReadJSONFile("gui/options/options.json");
|
---|
205 | translateObjectKeys(g_Options, ["label", "tooltip"]);
|
---|
206 | deepfreeze(g_Options);
|
---|
207 |
|
---|
208 | placeTabButtons(
|
---|
209 | g_Options,
|
---|
210 | false,
|
---|
211 | g_TabButtonHeight,
|
---|
212 | g_TabButtonDist,
|
---|
213 | selectPanel,
|
---|
214 | displayOptions);
|
---|
215 | }
|
---|
216 |
|
---|
217 | function getHotloadData()
|
---|
218 | {
|
---|
219 | return {
|
---|
220 | "tabCategorySelected": g_TabCategorySelected,
|
---|
221 | "changedKeys": g_ChangedKeys
|
---|
222 | };
|
---|
223 | }
|
---|
224 |
|
---|
225 | /**
|
---|
226 | * Sets up labels and controls of all options of the currently selected category.
|
---|
227 | */
|
---|
228 | function displayOptions()
|
---|
229 | {
|
---|
230 | // Hide all controls
|
---|
231 | for (let body of Engine.GetGUIObjectByName("option_controls").children)
|
---|
232 | {
|
---|
233 | body.hidden = true;
|
---|
234 | for (let control of body.children)
|
---|
235 | control.hidden = true;
|
---|
236 | }
|
---|
237 |
|
---|
238 | // Initialize label and control of each option for this category
|
---|
239 | for (let i = 0; i < g_Options[g_TabCategorySelected].options.length; ++i)
|
---|
240 | {
|
---|
241 | // Position vertically
|
---|
242 | let body = Engine.GetGUIObjectByName("option_control[" + i + "]");
|
---|
243 | let bodySize = body.size;
|
---|
244 | bodySize.top = g_OptionControlOffset + i * (g_OptionControlHeight + g_OptionControlDist);
|
---|
245 | bodySize.bottom = bodySize.top + g_OptionControlHeight;
|
---|
246 | body.size = bodySize;
|
---|
247 | body.hidden = false;
|
---|
248 |
|
---|
249 | // Load option data
|
---|
250 | let option = g_Options[g_TabCategorySelected].options[i];
|
---|
251 | let optionType = g_OptionType[option.type];
|
---|
252 | let value = optionType.configToValue(Engine.ConfigDB_GetValue("user", option.config));
|
---|
253 |
|
---|
254 | // Setup control
|
---|
255 | let control = Engine.GetGUIObjectByName("option_control_" + option.type + "[" + i + "]");
|
---|
256 | control.tooltip = option.tooltip + (optionType.tooltip ? "\n" + optionType.tooltip(value, option) : "");
|
---|
257 | control.hidden = false;
|
---|
258 |
|
---|
259 | if (optionType.initGUI)
|
---|
260 | optionType.initGUI(option, control);
|
---|
261 |
|
---|
262 | control[optionType.guiSetter] = function() {};
|
---|
263 | optionType.valueToGui(value, control);
|
---|
264 | if (optionType.sanitizeValue)
|
---|
265 | optionType.sanitizeValue(value, control, option);
|
---|
266 |
|
---|
267 | control[optionType.guiSetter] = function() {
|
---|
268 |
|
---|
269 | let value = optionType.guiToValue(control);
|
---|
270 |
|
---|
271 | if (optionType.sanitizeValue)
|
---|
272 | optionType.sanitizeValue(value, control, option);
|
---|
273 |
|
---|
274 | const oldValue = optionType.configToValue(Engine.ConfigDB_GetValue("user", option.config));
|
---|
275 |
|
---|
276 | control.tooltip = option.tooltip + (optionType.tooltip ? "\n" + optionType.tooltip(value, option) : "");
|
---|
277 |
|
---|
278 | const hasChanges = Engine.ConfigDB_HasChanges("user");
|
---|
279 | Engine.ConfigDB_CreateValue("user", option.config, String(value));
|
---|
280 | Engine.ConfigDB_SetChanges("user", true);
|
---|
281 |
|
---|
282 | g_ChangedKeys.add(option.config);
|
---|
283 | fireConfigChangeHandlers(new Set([option.config]));
|
---|
284 |
|
---|
285 | if (option.timeout)
|
---|
286 | optionType.timeout(option, oldValue, hasChanges, value);
|
---|
287 |
|
---|
288 | if (option.function)
|
---|
289 | Engine[option.function](value);
|
---|
290 |
|
---|
291 | enableButtons();
|
---|
292 | };
|
---|
293 |
|
---|
294 | // Setup label
|
---|
295 | let label = Engine.GetGUIObjectByName("option_label[" + i + "]");
|
---|
296 | label.caption = option.label;
|
---|
297 | label.tooltip = option.tooltip;
|
---|
298 | label.hidden = false;
|
---|
299 |
|
---|
300 | let labelSize = label.size;
|
---|
301 | labelSize.left = option.dependencies ? g_DependentLabelIndentation : 0;
|
---|
302 | labelSize.rright = control.size.rleft;
|
---|
303 | label.size = labelSize;
|
---|
304 | }
|
---|
305 |
|
---|
306 | enableButtons();
|
---|
307 | }
|
---|
308 |
|
---|
309 | /**
|
---|
310 | * Enable exactly the buttons whose dependencies are met.
|
---|
311 | */
|
---|
312 | function enableButtons()
|
---|
313 | {
|
---|
314 | g_Options[g_TabCategorySelected].options.forEach((option, i) => {
|
---|
315 |
|
---|
316 | let enabled =
|
---|
317 | !option.dependencies ||
|
---|
318 | option.dependencies.every(config => Engine.ConfigDB_GetValue("user", config) == "true");
|
---|
319 |
|
---|
320 | Engine.GetGUIObjectByName("option_label[" + i + "]").enabled = enabled;
|
---|
321 | Engine.GetGUIObjectByName("option_control_" + option.type + "[" + i + "]").enabled = enabled;
|
---|
322 | });
|
---|
323 |
|
---|
324 | let hasChanges = Engine.ConfigDB_HasChanges("user");
|
---|
325 | Engine.GetGUIObjectByName("revertChanges").enabled = hasChanges;
|
---|
326 | Engine.GetGUIObjectByName("saveChanges").enabled = hasChanges;
|
---|
327 | }
|
---|
328 |
|
---|
329 | function setDefaults()
|
---|
330 | {
|
---|
331 | messageBox(
|
---|
332 | 500, 200,
|
---|
333 | translate("Resetting the options will erase your saved settings. Do you want to continue?"),
|
---|
334 | translate("Warning"),
|
---|
335 | [translate("No"), translate("Yes")],
|
---|
336 | [null, reallySetDefaults]
|
---|
337 | );
|
---|
338 | }
|
---|
339 |
|
---|
340 | function reallySetDefaults()
|
---|
341 | {
|
---|
342 | for (let category in g_Options)
|
---|
343 | for (let option of g_Options[category].options)
|
---|
344 | {
|
---|
345 | Engine.ConfigDB_RemoveValue("user", option.config);
|
---|
346 | g_ChangedKeys.add(option.config);
|
---|
347 | }
|
---|
348 |
|
---|
349 | Engine.ConfigDB_WriteFile("user", "config/user.cfg");
|
---|
350 | revertChanges();
|
---|
351 | }
|
---|
352 |
|
---|
353 | function revertChange(option, oldValue, hadChanges)
|
---|
354 | {
|
---|
355 | if (!hadChanges)
|
---|
356 | Engine.ConfigDB_SetChanges("user", false);
|
---|
357 |
|
---|
358 | Engine.ConfigDB_CreateValue("user", option.config, String(oldValue));
|
---|
359 | if (option.function)
|
---|
360 | Engine[option.function](oldValue);
|
---|
361 |
|
---|
362 | displayOptions();
|
---|
363 | }
|
---|
364 |
|
---|
365 | function revertChanges()
|
---|
366 | {
|
---|
367 | Engine.ConfigDB_Reload("user");
|
---|
368 | Engine.ConfigDB_SetChanges("user", false);
|
---|
369 |
|
---|
370 | for (let category in g_Options)
|
---|
371 | for (let option of g_Options[category].options)
|
---|
372 | if (option.function)
|
---|
373 | Engine[option.function](
|
---|
374 | g_OptionType[option.type].configToValue(
|
---|
375 | Engine.ConfigDB_GetValue("user", option.config)));
|
---|
376 |
|
---|
377 | displayOptions();
|
---|
378 | }
|
---|
379 |
|
---|
380 | function saveChanges()
|
---|
381 | {
|
---|
382 | for (let category in g_Options)
|
---|
383 | for (let i = 0; i < g_Options[category].options.length; ++i)
|
---|
384 | {
|
---|
385 | let option = g_Options[category].options[i];
|
---|
386 | let optionType = g_OptionType[option.type];
|
---|
387 | if (!optionType.sanitizeValue)
|
---|
388 | continue;
|
---|
389 |
|
---|
390 | let value = optionType.configToValue(Engine.ConfigDB_GetValue("user", option.config));
|
---|
391 | if (value == optionType.sanitizeValue(value, undefined, option))
|
---|
392 | continue;
|
---|
393 |
|
---|
394 | selectPanel(category);
|
---|
395 |
|
---|
396 | messageBox(
|
---|
397 | 500, 200,
|
---|
398 | translate("Some setting values are invalid! Are you sure you want to save them?"),
|
---|
399 | translate("Warning"),
|
---|
400 | [translate("No"), translate("Yes")],
|
---|
401 | [null, reallySaveChanges]
|
---|
402 | );
|
---|
403 | return;
|
---|
404 | }
|
---|
405 |
|
---|
406 | reallySaveChanges();
|
---|
407 | }
|
---|
408 |
|
---|
409 | function reallySaveChanges()
|
---|
410 | {
|
---|
411 | Engine.ConfigDB_WriteFile("user", "config/user.cfg");
|
---|
412 | Engine.ConfigDB_SetChanges("user", false);
|
---|
413 | enableButtons();
|
---|
414 | }
|
---|
415 |
|
---|
416 | /**
|
---|
417 | * Close GUI page and inform the parent GUI page which options changed.
|
---|
418 | **/
|
---|
419 | function closePage()
|
---|
420 | {
|
---|
421 | if (Engine.ConfigDB_HasChanges("user"))
|
---|
422 | messageBox(
|
---|
423 | 500, 200,
|
---|
424 | translate("You have unsaved changes, do you want to close this window?"),
|
---|
425 | translate("Warning"),
|
---|
426 | [translate("No"), translate("Yes")],
|
---|
427 | [null, closePageWithoutConfirmation]);
|
---|
428 | else
|
---|
429 | closePageWithoutConfirmation();
|
---|
430 | }
|
---|
431 |
|
---|
432 | function closePageWithoutConfirmation()
|
---|
433 | {
|
---|
434 | Engine.PopGuiPage(g_ChangedKeys);
|
---|
435 | }
|
---|