Index: ps/trunk/binaries/data/mods/public/gui/options/options.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 17728) +++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 17729) @@ -1,287 +1,328 @@ -var g_hasCallback = false; -var g_controls; +var g_HasCallback = false; +var g_Controls; function init(data) { if (data && data.callback) - g_hasCallback = true; + g_HasCallback = true; let revert = data && data.revert; - g_controls = []; + g_Controls = {}; var options = Engine.ReadJSONFile("gui/options/options.json"); for (let category of Object.keys(options)) { let lastSize; for (let i = 0; i < options[category].length; ++i) { let option = options[category][i]; - if (!option.label) + if (!option.label || !option.parameters || !option.parameters.config) continue; let body = Engine.GetGUIObjectByName(category + "[" + i + "]"); let label = Engine.GetGUIObjectByName(category + "Label[" + i + "]"); - setupControl(option, i, category, revert); + let config = option.parameters.config; + g_Controls[config] = { + "control": setupControl(option, i, category, revert), + "type": option.type, + "dependencies": option.dependencies || undefined + }; label.caption = translate(option.label); label.tooltip = option.tooltip ? translate(option.tooltip) : ""; // Move each element to the correct place. - if (i > 0) + if (lastSize) { let newSize = new GUISize(); newSize.left = lastSize.left; newSize.rright = lastSize.rright; newSize.top = lastSize.bottom; - newSize.bottom = newSize.top + 25; + newSize.bottom = newSize.top + 26; body.size = newSize; lastSize = newSize; } else lastSize = body.size; + // small right shift of options which depends on another one + for (let opt of options[category]) + { + if (!opt.label || !opt.parameters || !opt.parameters.config) + continue; + if (!opt.dependencies || opt.dependencies.indexOf(config) === -1) + continue; + label.caption = " " + label.caption; + break; + } // Show element. body.hidden = false; } } + updateDependencies(); if (!revert) updateStatus(); } /** * Setup the apropriate control for a given option. * * @param option Structure containing the data to setup an option. * @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i]. */ function setupControl(option, i, category, revert) { var control; var onUpdate; switch (option.type) { case "boolean": + case "invertedboolean": // More space for the label let text = Engine.GetGUIObjectByName(category + "Label[" + i + "]"); let size = text.size; size.rright = 87; text.size = size; control = Engine.GetGUIObjectByName(category + "Tickbox[" + i + "]"); var checked; var keyRenderer; var keyConfig; var functionBody; for (let param of Object.keys(option.parameters)) { switch (param) { case "config": keyConfig = option.parameters.config; if (checked === undefined || revert) checked = Engine.ConfigDB_GetValue("user", keyConfig) === "true"; else if ((Engine.ConfigDB_GetValue("user", keyConfig) === "true") !== checked) + { Engine.ConfigDB_CreateValue("user", keyConfig, String(checked)); + updateStatus(true); + } break; case "renderer": keyRenderer = option.parameters.renderer; if (!Engine["Renderer_Get" + keyRenderer + "Enabled"]) { warn(" invalid renderer key " + keyRenderer); keyRenderer = undefined; break; } if (checked === undefined) checked = Engine["Renderer_Get" + keyRenderer + "Enabled"](); else if (Engine["Renderer_Get" + keyRenderer + "Enabled"]() !== checked) Engine["Renderer_Set" + keyRenderer + "Enabled"](checked); break; default: warn("Unknown option source type '" + param + "'"); } } + // invertedboolean when we want to display the opposite of the flag value + var inverted = option.type === "invertedboolean"; + if (inverted) + checked = !checked; - onUpdate = function(keyRenderer, keyConfig) + onUpdate = function(keyRenderer, keyConfig, inverted) { return function() { + let val = inverted ? !this.checked : this.checked; if (keyRenderer) - Engine["Renderer_Set" + keyRenderer + "Enabled"](this.checked); + Engine["Renderer_Set" + keyRenderer + "Enabled"](val); if (keyConfig) - Engine.ConfigDB_CreateValue("user", keyConfig, String(this.checked)); + Engine.ConfigDB_CreateValue("user", keyConfig, String(val)); + updateDependencies(); updateStatus(true); }; - }(keyRenderer, keyConfig); + }(keyRenderer, keyConfig, inverted); // Load final data to the control element. control.checked = checked; control.onPress = onUpdate; break; case "number": // TODO: Slider case "string": control = Engine.GetGUIObjectByName(category + "Input[" + i + "]"); var caption; var key; var functionBody; var minval; var maxval; for (let param of Object.keys(option.parameters)) { switch (param) { case "config": key = option.parameters.config; caption = Engine.ConfigDB_GetValue("user", key); break; case "function": if (Engine[option.parameters.function]) functionBody = option.parameters.function; break; case "min": minval = option.parameters.min; break; case "max": maxval = option.parameters.max; break; default: - warn("Unknown option source type '" + action + "'"); + warn("Unknown option source type '" + param + "'"); } } // as the enter key is not necessarily pressed after modifying an entry, we will register the input also // - when the mouse leave the control (MouseLeave event) // - or when saving or closing the window (registerChanges function) // so we must ensure that something has indeed been modified onUpdate = function(key, functionBody, minval, maxval) { return function() { if (minval && +minval > +this.caption) this.caption = minval; if (maxval && +maxval < +this.caption) this.caption = maxval; if (Engine.ConfigDB_GetValue("user", key) === this.caption) return; Engine.ConfigDB_CreateValue("user", key, this.caption); if (functionBody) Engine[functionBody](+this.caption); + updateDependencies(); updateStatus(true); }; }(key, functionBody, minval, maxval); control.caption = caption; control.onPress = onUpdate; control.onMouseLeave = onUpdate; - g_controls.push(control); break; case "dropdown": control = Engine.GetGUIObjectByName(category + "Dropdown[" + i + "]"); var caption; var key; var functionBody; var minval; var maxval; for (let param of Object.keys(option.parameters)) { switch (param) { case "config": key = option.parameters.config; let val = +Engine.ConfigDB_GetValue("user", key); if (key === "materialmgr.quality") val = val > 5 ? 2 : val > 2 ? 1 : 0; control.selected = val; break; case "list": control.list = option.parameters.list.map(e => translate(e)); break; case "list_data": control.list_data = option.parameters.list_data; break; default: - warn("Unknown option source type '" + action + "'"); + warn("Unknown option source type '" + param + "'"); } } onUpdate = function(key) { return function() { let val = this.selected; if (key === "materialmgr.quality") val = val == 0 ? 2 : val == 1 ? 5 : 8; Engine.ConfigDB_CreateValue("user", key, val); + updateDependencies(); updateStatus(true); }; }(key); control.onSelectionChange = onUpdate; break; default: warn("Unknown option type '" + option.type + "', assuming string. Valid types are 'number', 'string', or 'bool'."); control = Engine.GetGUIObjectByName(category + "Input[" + i + "]"); break; } control.hidden = false; control.tooltip = option.tooltip ? translate(option.tooltip) : ""; return control; } function updateStatus(val) { if (typeof val == "boolean") Engine.ConfigDB_CreateValue("user", "nosave.haschanges", String(val)); else val = Engine.ConfigDB_GetValue("user", "nosave.haschanges") === "true"; Engine.GetGUIObjectByName("loadOptions").enabled = val; Engine.GetGUIObjectByName("saveOptions").enabled = val; } /** * Register changes of input (text and number) controls */ function registerChanges() { - for (let control of g_controls) - control.onPress(); + for (let item in g_Controls) + if (g_Controls[item].type === "number" || g_Controls[item].type === "string") + g_Controls[item].control.onPress(); +} + +function updateDependencies() +{ + for (let item in g_Controls) + { + let control = g_Controls[item]; + if (control.type !== "boolean" && control.type !== "invertedboolean" || !control.dependencies) + continue; + + for (let dependency of control.dependencies) + g_Controls[dependency].control.enabled = control.control.checked; + } } function revertChanges() { Engine.ConfigDB_Reload("user"); updateStatus(false); init({ "revert": true }); } function saveChanges() { registerChanges(); Engine.ConfigDB_WriteFile("user", "config/user.cfg"); updateStatus(false); } /** * Close GUI page and call callbacks if they exist. **/ function closePage() { registerChanges(); if (Engine.ConfigDB_GetValue("user", "nosave.haschanges") === "true") { let btCaptions = [translate("No"), translate("Yes")]; let btCode = [null, function(){ closePageWithoutConfirmation(); }]; messageBox(500, 200, translate("You have unsaved changes, do you want to close this window ?"), translate("Warning"), 0, btCaptions, btCode); } else closePageWithoutConfirmation(); } function closePageWithoutConfirmation() { - if (g_hasCallback) + if (g_HasCallback) Engine.PopGuiPageCB(); else Engine.PopGuiPage(); } Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 17728) +++ ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 17729) @@ -1,213 +1,214 @@ { "generalSetting": [ { "type": "boolean", "label": "Windowed Mode", "tooltip": "Start 0 A.D. in a window", "parameters": { "config": "windowed" } }, { "type": "boolean", "label": "Background Pause", "tooltip": "Pause single player games when window loses focus", "parameters": { "config": "pauseonfocusloss" } }, { "type": "boolean", "label": "Disable Welcome Screen", "tooltip": "If you disable this screen completely, you may miss important announcements.\nYou can still launch it using the main menu.", "parameters": { "config": "splashscreendisable" } }, { "type": "boolean", "label": "Detailed Tooltips", "tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.", "parameters": { "config": "showdetailedtooltips" } }, { "type": "boolean", "label": "FPS Overlay", "tooltip": "Show frames per second in top right corner.", "parameters": { "config": "overlay.fps" } }, { "type": "boolean", "label": "Realtime Overlay", "tooltip": "Show current system time in top right corner.", "parameters": { "config": "overlay.realtime" } }, { "type": "boolean", "label": "Gametime Overlay", "tooltip": "Show current simulation time in top right corner.", "parameters": { "config": "gui.session.timeelapsedcounter" } }, { "type": "boolean", "label": "Ceasefire Time Overlay", "tooltip": "Always show the remaining ceasefire time.", "parameters": { "config": "gui.session.ceasefirecounter" } }, { "type": "boolean", "label": "Persist Match Settings", "tooltip": "Save and restore match settings for quick reuse when hosting another game", "parameters": { "config": "persistmatchsettings" } } ], "graphicsSetting": [ { "type": "boolean", "label": "Prefer GLSL", "tooltip": "Use OpenGL 2.0 shaders (recommended)", "parameters": { "renderer": "PreferGLSL", "config": "preferglsl" } }, { "type": "boolean", "label": "Post Processing", "tooltip": "Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)", "parameters": { "renderer": "Postproc", "config": "postproc" } }, { - "type": "boolean", - "label": "Shadows", - "tooltip": "Enable shadows", - "parameters": { "renderer": "Shadows", "config": "shadows"} - }, - { - "type": "boolean", - "label": "Particles", - "tooltip": "Enable particles", - "parameters": { "renderer": "Particles", "config": "particles" } + "type": "dropdown", + "label": "Graphics quality", + "tooltip": "Graphics quality. REQUIRES GAME RESTART", + "parameters": { "list": [ "Low", "Medium", "High" ], "config": "materialmgr.quality" } }, { "type": "boolean", - "label": "Show Sky", - "tooltip": "Render Sky", - "parameters": { "renderer": "ShowSky", "config": "showsky" } + "label": "Shadows", + "tooltip": "Enable shadows", + "parameters": { "renderer": "Shadows", "config": "shadows"}, + "dependencies": [ "shadowpcf" ] }, { "type": "boolean", - "label": "Smooth LOS", - "tooltip": "Lift darkness and fog-of-war smoothly", - "parameters": { "renderer": "SmoothLOS", "config": "smoothlos" } + "label": "Shadow Filtering", + "tooltip": "Smooth shadows", + "parameters": { "renderer": "ShadowPCF", "config": "shadowpcf" } }, { "type": "boolean", "label": "Unit Silhouettes", "tooltip": "Show outlines of units behind buildings", "parameters": { "renderer": "Silhouettes", "config": "silhouettes" } }, { "type": "boolean", - "label": "Shadow Filtering", - "tooltip": "Smooth shadows", - "parameters": { "renderer": "ShadowPCF", "config": "shadowpcf" } + "label": "Particles", + "tooltip": "Enable particles", + "parameters": { "renderer": "Particles", "config": "particles" } }, { - "type": "boolean", - "label": "Fast & Ugly Water", - "tooltip": "Use the lowest settings possible to render water. This makes other settings irrelevant.", - "parameters": { "renderer": "WaterUgly", "config": "waterugly" } + "type": "invertedboolean", + "label": "Activate water effects", + "tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.", + "parameters": { "renderer": "WaterUgly", "config": "waterugly" }, + "dependencies": [ "waterfancyeffects", "waterrealdepth", "waterreflection", "waterrefraction", "watershadows" ] }, { "type": "boolean", "label": "HQ Water Effects", "tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.", "parameters": { "renderer": "WaterFancyEffects", "config": "waterfancyeffects" } }, { "type": "boolean", "label": "Real Water Depth", "tooltip": "Use actual water depth in rendering calculations", "parameters": { "renderer": "WaterRealDepth", "config": "waterrealdepth" } }, { "type": "boolean", "label": "Water Reflections", "tooltip": "Allow water to reflect a mirror image", "parameters": { "renderer": "WaterReflection", "config": "waterreflection" } }, { "type": "boolean", "label": "Water Refraction", "tooltip": "Use a real water refraction map and not transparency", "parameters": { "renderer": "WaterRefraction", "config": "waterrefraction" } }, { "type": "boolean", "label": "Shadows on Water", "tooltip": "Cast shadows on water", "parameters": { "renderer": "WaterShadows", "config": "watershadows" } }, { "type": "boolean", + "label": "Smooth LOS", + "tooltip": "Lift darkness and fog-of-war smoothly", + "parameters": { "renderer": "SmoothLOS", "config": "smoothlos" } + }, + { + "type": "boolean", + "label": "Show Sky", + "tooltip": "Render Sky", + "parameters": { "renderer": "ShowSky", "config": "showsky" } + }, + { + "type": "boolean", "label": "VSync", "tooltip": "Run vertical sync to fix screen tearing. REQUIRES GAME RESTART", "parameters": { "config": "vsync" } }, { "type": "boolean", "label": "Limit FPS in Menus", "tooltip": "Limit FPS to 50 in all menus, to save power.", "parameters": { "config": "gui.menu.limitfps" } - }, - { - "type": "dropdown", - "label": "Graphics quality", - "tooltip": "Graphics quality. REQUIRES GAME RESTART", - "parameters": { "list": [ "Low", "Medium", "High" ], "config": "materialmgr.quality" } - } ], "soundSetting": [ { "type": "number", "label": "Master Gain", "tooltip": "Master audio gain", "parameters": { "config": "sound.mastergain", "function": "SetMasterGain", "min": "0" } }, { "type": "number", "label": "Music Gain", "tooltip": "In game music gain", "parameters": { "config": "sound.musicgain", "function": "SetMusicGain", "min": "0" } }, { "type": "number", "label": "Ambient Gain", "tooltip": "In game ambient sound gain", "parameters": { "config": "sound.ambientgain", "function": "SetAmbientGain", "min": "0" } }, { "type": "number", "label": "Action Gain", "tooltip": "In game unit action sound gain", "parameters": { "config": "sound.actiongain", "function": "SetActionGain", "min": "0" } }, { "type": "number", "label": "UI Gain", "tooltip": "UI sound gain", "parameters": { "config": "sound.uigain", "function": "SetUIGain", "min": "0" } } ], "lobbySetting": [ { "type": "number", "label": "Chat Backlog", "tooltip": "Number of backlogged messages to load when joining the lobby", "parameters": { "config": "lobby.history", "min": "0" } }, { "type": "boolean", "label": "Chat Timestamp", "tooltip": "Show time that messages are posted in the lobby chat", "parameters": { "config": "lobby.chattimestamp" } } ] }