source: ps/trunk/binaries/data/mods/public/gui/options/options.js@ 25966

Last change on this file since 25966 was 25966, checked in by Silier, 3 years ago

There have been quite a bit of number of questions how to change scale of the gui, because this option is hidden from the user.

Use dropdown with values. Implement confirmation box with countdown to revert scale change because buttons can get unable to click.

Differential revision: D3037
Comments by: @vladislavbelov, @Stan, @wraitii, @pieq, @sera
Tested by: @Langbart

  • Property svn:eol-style set to native
File size: 11.9 KB
Line 
1/**
2 * Translated JSON file contents.
3 */
4var g_Options;
5
6/**
7 * Names of config keys that have changed, value returned when closing the page.
8 */
9var g_ChangedKeys;
10
11/**
12 * Vertical size of a tab button.
13 */
14var g_TabButtonHeight = 30;
15
16/**
17 * Vertical space between two tab buttons.
18 */
19var g_TabButtonDist = 5;
20
21/**
22 * Vertical distance between the top of the page and the first option.
23 */
24var g_OptionControlOffset = 5;
25
26/**
27 * Vertical size of each option control.
28 */
29var g_OptionControlHeight = 26;
30
31/**
32 * Vertical distance between two consecutive options.
33 */
34var g_OptionControlDist = 2;
35
36/**
37 * Horizontal indentation to distinguish options that depend on another option.
38 */
39var g_DependentLabelIndentation = 25;
40
41/**
42 * Color used to indicate that the string entered by the player isn't a sane color.
43 */
44var 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 */
57var 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
199function 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
217function 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 */
228function 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 */
312function 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
329function 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
340function 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
353function 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
365function 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
380function 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
409function 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 **/
419function 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
432function closePageWithoutConfirmation()
433{
434 Engine.PopGuiPage(g_ChangedKeys);
435}
Note: See TracBrowser for help on using the repository browser.