Index: binaries/data/config/default.cfg =================================================================== --- binaries/data/config/default.cfg +++ binaries/data/config/default.cfg @@ -324,6 +324,10 @@ fastforward = Space ; If timewarp mode enabled, speed up the game rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game +[hotkey.gamesetup] +nexttab = "Alt+S" ; Show next option tab +prevtab = "Alt+W" ; Show previous option tab + [hotkey.text] ; > GUI TEXTBOX HOTKEYS delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor delete.right = "Ctrl+Del" ; Delete word to the right of cursor Index: binaries/data/mods/mod/gui/common/modern/sprites.xml =================================================================== --- binaries/data/mods/mod/gui/common/modern/sprites.xml +++ binaries/data/mods/mod/gui/common/modern/sprites.xml @@ -483,6 +483,26 @@ - Misc. - ========================================== --> + + + + + + + + + + + !g_IsNetworked, + "hidden": () => { + if (!g_IsNetworked) + return true; + + let size = Engine.GetGUIObjectByName("chatPanel").getComputedSize(); + return size.right - size.left < g_MinChatWidth; + }, }, "chatInput": { "tooltip": () => colorizeAutocompleteHotkey(translate("Press %(hotkey)s to autocomplete playernames or settings.")), @@ -912,14 +986,7 @@ }, "teamResetButton": { "hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController, - }, - // Display these after having hidden every GUI object in the "More Options" dialog - "moreOptionsLabel": { - "hidden": () => false, - }, - "hideMoreOptions": { - "hidden": () => false, - }, + } }; /** @@ -1064,7 +1131,8 @@ for (let dropdown in g_PlayerDropdowns) initPlayerDropdowns(dropdown); - resizeMoreOptionsWindow(); + placeOptionTabButtons(); + displayOptions(); initSPTips(); @@ -1083,6 +1151,119 @@ hideLoadingWindow(); } +function placeOptionTabButtons() +{ + let frame = Engine.GetGUIObjectByName("optionTabButtons"); + let frameSize = frame.size; + let frameBottom = frameSize.top + g_OptionOrderGUI.length * (g_TabButtonHeight + g_TabButtonDist); + frameSize.bottom = frameBottom; + frame.size = frameSize; + + let gameDescription = Engine.GetGUIObjectByName("mapInfoDescriptionFrame"); + let gameDescriptionSize = gameDescription.size; + gameDescriptionSize.top = frameBottom + 3; + gameDescription.size = gameDescriptionSize; + for (let category in g_OptionOrderGUI) + { + let button = Engine.GetGUIObjectByName("optionTabButton[" + category + "]"); + if (!button) + { + warn("Too few tab-buttons!"); + break; + } + + button.hidden = false; + + let size = button.size; + size.top = category * (g_TabButtonHeight + g_TabButtonDist) + g_TabButtonDist / 2; + size.bottom = size.top + g_TabButtonHeight; + button.size = size; + button.tooltip = g_OptionOrderGUI[category].tooltip || ""; + + button.onPress = (category => function() { + if (category == g_SelectedCategory) + g_SelectedCategory = undefined; + else + g_SelectedCategory = category; + displayOptions(); + })(category); + + Engine.GetGUIObjectByName("tabButtonText[" + category + "]").caption = g_OptionOrderGUI[category].label; + } +} + +function displayOptions() +{ + // Highlight the selected tab + Engine.GetGUIObjectByName("optionTabButtons").children.forEach((button, i) => { + button.sprite = i == g_SelectedCategory ? "ModernTabVerticalForeground" : "ModernTabVerticalBackground"; + }); + + updateGUIObjects(); + Engine.GetGUIObjectByName("options").hidden = false; +} + +/** + * Perform hotkey action. + */ +function nextOptionTab(backward) +{ + let panels = g_OptionOrderGUI.length; + if (g_SelectedCategory != undefined) + { + g_SelectedCategory += backward ? -1 : 1; + g_SelectedCategory = g_SelectedCategory < 0 ? panels - 1 : g_SelectedCategory >= panels ? 0 : g_SelectedCategory; + } + else + g_SelectedCategory = backward ? panels - 1 : 0; + + displayOptions(); +} + +/** + * Slide options panel. + */ +function updateOptionsPanelPosition(dt) +{ + let panel = Engine.GetGUIObjectByName("options"); + + let border = Engine.GetGUIObjectByName("optionTabButtons").size.left; + let offset = 0; + if (g_SelectedCategory == undefined) + { + let maxOffset = border - panel.size.left; + if (maxOffset > 0) + offset = Math.min(g_SlideSpeed * dt, maxOffset); + } + else if (border > panel.size.right) + offset = Math.min(g_SlideSpeed * dt, border - panel.size.right); + else + { + let maxOffset = panel.size.right - border; + if (maxOffset > 0) + offset = -Math.min(g_SlideSpeed * dt, maxOffset); + } + + let size = panel.size; + size.left += offset; + size.right += offset; + panel.size = size; + + let optionBackground = Engine.GetGUIObjectByName("optionBackground"); + let backgroundSize = optionBackground.size; + backgroundSize.left = size.left; + optionBackground.size = backgroundSize; + if (g_IsNetworked) + { + let chatPanel = Engine.GetGUIObjectByName("chatPanel"); + let chatSize = chatPanel.size; + + chatSize.right += offset; + chatPanel.size = chatSize; + chatPanel.hidden = g_MiscControls.chatPanel.hidden(); + } +} + function hideLoadingWindow() { let loadingWindow = Engine.GetGUIObjectByName("loadingWindow"); @@ -1095,20 +1276,22 @@ } /** - * Options in the "More Options" or "Map" panel use a generic name. + * Options under the option tabs use a generic name. * Player settings use custom names. */ function getGUIObjectNameFromSetting(name) { - for (let panel in g_OptionOrderGUI) + let idxOffset = 0; + for (let category of g_OptionOrderGUI) { - let idx = g_OptionOrderGUI[panel].indexOf(name); + let idx = category.options.indexOf(name); if (idx != -1) return [ - panel + "Option", + "option", g_Dropdowns[name] ? "Dropdown" : "Checkbox", - "[" + idx + "]" + "[" + (idx + idxOffset) + "]" ]; + idxOffset += category.options.length; } // Assume there is a GUI object with exactly that setting name @@ -1194,45 +1377,117 @@ Engine.ConfigDB_WriteValueToFile("user", "gui.gamesetup.enabletips", enabled, "config/user.cfg"); } -function verticallyDistributeGUIObjects(parent, objectHeight, ignore) +/** + * Distribute the currently visible options over the option panel. + * First calculate the number of columns required, then place the objects. + */ +function distributeOptions() { - let yPos; - - let parentObject = Engine.GetGUIObjectByName(parent); + let parentObject = Engine.GetGUIObjectByName("options"); + let actualParentSize = parentObject.getComputedSize(); + let oldColumns = Math.round((actualParentSize.right - actualParentSize.left) / g_ColumnWidth) - 1; + + let maxPerColumn = Math.floor((actualParentSize.bottom - actualParentSize.top) / g_OptionHeight); + let childs = 0; + let checkboxLeft = true; for (let child of parentObject.children) { - if (ignore.indexOf(child.name) != -1) + if (child.hidden) continue; - let childSize = child.size; - yPos = yPos || childSize.top; + for (let grandChild of child.children) + { + if (grandChild.hidden && grandChild.enabled) + continue; + // Two consecutive checkboxes can be alligned horizontally + if (grandChild.name.substr(0, 14) == "optionCheckbox" && !checkboxLeft) + checkboxLeft = true; + else if (grandChild.name.substr(0, 14) == "optionCheckbox" && checkboxLeft) + { + checkboxLeft = false; + ++childs; + } + else if (grandChild.name.substr(0, 14) == "optionDropdown") + { + checkboxLeft = true; + ++childs; + } + } + } + + let perColumn = childs / Math.ceil(childs / maxPerColumn); + + let yPos = g_OptionDist; + checkboxLeft = true; + let column = 0; + let thisColumn = 0; + let parentSize = parentObject.size; + + for (let child of parentObject.children) + { if (child.hidden) continue; - childSize.top = yPos; - childSize.bottom = yPos + objectHeight - 2; - child.size = childSize; - - yPos += objectHeight; - } - return yPos; -} + let childSize = child.size; + for (let grandChild of child.children) + { + if (grandChild.hidden && grandChild.enabled) + continue; -/** - * Remove empty space in case of hidden options (like cheats, rating or victory duration) - */ -function resizeMoreOptionsWindow() -{ - verticallyDistributeGUIObjects("mapOptions", 32, []); + // Place two consecutive checkboxes horizontally alligned + if (grandChild.name.substr(0, 14) == "optionCheckbox" && !checkboxLeft) + { + childSize = new GUISize( + column * g_ColumnWidth + g_ColumnWidth / 2, + yPos - g_OptionHeight, + column * g_ColumnWidth + 3/2 * g_ColumnWidth - 15, + yPos - g_OptionDist); + checkboxLeft = true; + } + else if (grandChild.name.substr(0, 14) == "optionCheckbox" && checkboxLeft) + { + if (thisColumn >= perColumn) + { + yPos = g_OptionDist; + ++column; + thisColumn = 0; + } + + childSize = new GUISize( + column * g_ColumnWidth, + yPos, + column * g_ColumnWidth + g_ColumnWidth - 10, + yPos + g_OptionHeight - g_OptionDist); + yPos += g_OptionHeight; + checkboxLeft = false; + ++thisColumn; + } + else if (grandChild.name.substr(0, 14) == "optionDropdown") + { + if (thisColumn >= perColumn) + { + yPos = g_OptionDist; + ++column; + thisColumn = 0; + } + + childSize = new GUISize( + column * g_ColumnWidth, + yPos, + column * g_ColumnWidth + g_ColumnWidth - 5, + yPos + g_OptionHeight - g_OptionDist); + yPos += g_OptionHeight; + checkboxLeft = true; + ++thisColumn; + } - let yPos = verticallyDistributeGUIObjects("moreOptions", 32, ["moreOptionsLabel"]); + child.size = childSize; + } + } - // Resize the vertically centered window containing the options - let moreOptions = Engine.GetGUIObjectByName("moreOptions"); - let mSize = moreOptions.size; - mSize.bottom = mSize.top + yPos + 20; - moreOptions.size = mSize; + parentSize.right += (column - oldColumns) * g_ColumnWidth; + parentObject.size = parentSize; } /** @@ -1519,6 +1774,8 @@ })))); initDropdown("biome"); + updateGUIDropdown("biome"); + distributeOptions(); } function loadMapData(name) @@ -1674,6 +1931,20 @@ handleNetMessages(); updateTimers(); + + let now = Date.now(); + let tickLength = now - g_LastTickTime; + g_LastTickTime = now; + + updateOptionsPanelPosition(tickLength); +} + +/** + * Keep everything nicely fitted in there box when resizing. + */ +function onWindowResized() +{ + distributeOptions(); } /** @@ -1782,10 +2053,11 @@ let indexHidden = isControlArrayElementHidden(playerIdx); let obj = (playerIdx === undefined ? g_Dropdowns : g_PlayerDropdowns)[name]; - let selected = indexHidden ? -1 : dropdown.list_data.indexOf(String(obj.get(playerIdx))); - let enabled = !indexHidden && (!obj.enabled || obj.enabled(playerIdx)); let hidden = indexHidden || obj.hidden && obj.hidden(playerIdx); + let selected = hidden ? -1 : dropdown.list_data.indexOf(String(obj.get(playerIdx))); + let enabled = !indexHidden && (!obj.enabled || obj.enabled(playerIdx)); + dropdown.enabled = g_IsController && enabled; dropdown.hidden = !g_IsController || !enabled || hidden; dropdown.selected = selected; dropdown.tooltip = !indexHidden && obj.tooltip ? obj.tooltip(-1, playerIdx) : ""; @@ -1825,7 +2097,7 @@ Engine.GetGUIObjectByName(guiName + "Dropdown" + guiIdx).hidden = true; checkbox.checked = checked; - checkbox.enabled = enabled; + checkbox.enabled = g_IsController && enabled; checkbox.hidden = hidden || !g_IsController; checkbox.tooltip = obj.tooltip ? obj.tooltip() : ""; @@ -1990,16 +2262,20 @@ updatePlayerAssignmentChoices(); // Hide exceeding dropdowns and checkboxes - for (let panel in g_OptionOrderGUI) - for (let child of Engine.GetGUIObjectByName(panel + "Options").children) - child.hidden = true; + for (let child of Engine.GetGUIObjectByName("options").children) + child.hidden = true; // Show the relevant ones - for (let name in g_Dropdowns) - updateGUIDropdown(name); - - for (let name in g_Checkboxes) - updateGUICheckbox(name); + if (g_SelectedCategory != undefined) + { + for (let name in g_Dropdowns) + if (g_OptionOrderGUI[g_SelectedCategory].options.indexOf(name) != -1) + updateGUIDropdown(name); + + for (let name in g_Checkboxes) + if (g_OptionOrderGUI[g_SelectedCategory].options.indexOf(name) != -1) + updateGUICheckbox(name); + } for (let i = 0; i < g_MaxPlayers; ++i) { @@ -2014,7 +2290,7 @@ updateGUIMiscControl(name); updateGameDescription(); - resizeMoreOptionsWindow(); + distributeOptions(); rightAlignCancelButton(); updateAutocompleteEntries(); @@ -2264,12 +2540,6 @@ Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n"); } -function showMoreOptions(show) -{ - Engine.GetGUIObjectByName("moreOptionsFade").hidden = !show; - Engine.GetGUIObjectByName("moreOptions").hidden = !show; -} - function resetCivilizations() { for (let i in g_GameAttributes.settings.PlayerData) Index: binaries/data/mods/public/gui/gamesetup/gamesetup.xml =================================================================== --- binaries/data/mods/public/gui/gamesetup/gamesetup.xml +++ binaries/data/mods/public/gui/gamesetup/gamesetup.xml @@ -14,6 +14,13 @@