From f39b7ae72f8aa53a7600bc29d7e677d5dfc8ce7e Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Sat, 7 Jan 2023 12:27:05 -0700 Subject: [PATCH] Many fixes for options, settings, and keybinds --- CONTRIBUTORS.MD | 1 + dmf/localization/dmf.lua | 6 + .../mods/dmf/modules/core/keybindings.lua | 153 +++++++++-- .../mods/dmf/modules/core/localization.lua | 2 +- dmf/scripts/mods/dmf/modules/dmf_options.lua | 2 +- .../mods/dmf/modules/gui/custom_views.lua | 15 +- .../modules/ui/options/dmf_options_view.lua | 2 - .../dmf_options_view_content_blueprints.lua | 12 +- .../dmf/modules/ui/options/mod_options.lua | 250 +++++++++++------- 9 files changed, 306 insertions(+), 137 deletions(-) diff --git a/CONTRIBUTORS.MD b/CONTRIBUTORS.MD index 8fdd4bc..dc7d67c 100644 --- a/CONTRIBUTORS.MD +++ b/CONTRIBUTORS.MD @@ -3,6 +3,7 @@ Contribute to DMF -- add your name here! + [Aussiemon](https://github.com/Aussiemon) + [Fracticality](https://github.com/fracticality) + [grasmann](https://github.com/grasmann) ++ [philipdestroyer](https://github.com/philippedavid) # VMF contributors (sorted alphabetically) + [Aussiemon](https://github.com/Aussiemon) diff --git a/dmf/localization/dmf.lua b/dmf/localization/dmf.lua index 7947aee..887e8c4 100644 --- a/dmf/localization/dmf.lua +++ b/dmf/localization/dmf.lua @@ -22,6 +22,12 @@ return { percent = { en = "%%", }, + toggle_mod = { + en = "Toggle Mod", + }, + toggle_mod_description = { + en = "Enable or disable the mod", + }, ui_scaling = { en = "UI Scaling for FHD+ Resolutions", es = "Reescalado de la interfaz para resoluciones Full HD+", diff --git a/dmf/scripts/mods/dmf/modules/core/keybindings.lua b/dmf/scripts/mods/dmf/modules/core/keybindings.lua index 763e808..f001b43 100644 --- a/dmf/scripts/mods/dmf/modules/core/keybindings.lua +++ b/dmf/scripts/mods/dmf/modules/core/keybindings.lua @@ -1,7 +1,9 @@ local dmf = get_mod("DMF") +local InputUtils = require("scripts/managers/input/input_utils") + local PRIMARY_BINDABLE_KEYS = { - KEYBOARD = { + ["keyboard"] = { [8] = {"Backspace", "backspace"}, [9] = {"Tab", "tab"}, [13] = {"Enter", "enter"}, @@ -116,7 +118,7 @@ local PRIMARY_BINDABLE_KEYS = { --?[226] = {"\", "oem_102 (> <)"}, [256] = {"Num Enter", "numpad enter"} }, - MOUSE = { + ["mouse"] = { [0] = {"Mouse Left", "mouse left"}, [1] = {"Mouse Right", "mouse right"}, [2] = {"Mouse Middle", "mouse middle"}, @@ -126,8 +128,9 @@ local PRIMARY_BINDABLE_KEYS = { [11] = {"Mouse Wheel Down", "mouse wheel down"}, [12] = {"Mouse Wheel Left", "mouse wheel left"}, [13] = {"Mouse Wheel Right", "mouse wheel right"} - },--[[ -- will work on this if it will be needed - GAMEPAD = { + }, + --[[ -- will work on this if it will be needed + ["gamepad"] = { [0] = {"", "d_up"}, [1] = {"", "d_down"}, [2] = {"", "d_left"}, @@ -149,31 +152,39 @@ local PRIMARY_BINDABLE_KEYS = { local OTHER_KEYS = { -- modifier keys - ["shift"] = {160, "Shift", "KEYBOARD", 161}, - ["ctrl"] = {162, "Ctrl", "KEYBOARD", 163}, - ["alt"] = {164, "Alt", "KEYBOARD", 165}, + ["left shift"] = {160, "Shift", "keyboard", 161}, + ["right shift"] = {160, "Shift", "keyboard", 161}, + ["shift"] = {160, "Shift", "keyboard", 161}, + ["left ctrl"] = {162, "Ctrl", "keyboard", 163}, + ["right ctrl"] = {162, "Ctrl", "keyboard", 163}, + ["ctrl"] = {162, "Ctrl", "keyboard", 163}, + ["left alt"] = {164, "Alt", "keyboard", 165}, + ["right alt"] = {164, "Alt", "keyboard", 165}, + ["alt"] = {164, "Alt", "keyboard", 165}, -- hack for 'dmf.build_keybind_string' function ["no_button"] = {-1, ""} } local KEYS_INFO = {} --- Populate KEYS_INFO: + +-- Populate KEYS_INFO: Index, readable name, device name for input_device_name, input_device_keys in pairs(PRIMARY_BINDABLE_KEYS) do for key_index, key_info in pairs(input_device_keys) do KEYS_INFO[key_info[2]] = {key_index, key_info[1], input_device_name} end end + for key_id, key_data in pairs(OTHER_KEYS) do KEYS_INFO[key_id] = key_data end -- Can't use 'Device.released' because it will break keybinds if button is released when game window is not active. local CHECK_INPUT_FUNCTIONS = { - KEYBOARD = { + keyboard = { PRESSED = function(key_id) return Keyboard.pressed(KEYS_INFO[key_id][1]) end, RELEASED = function(key_id) return Keyboard.button(KEYS_INFO[key_id][1]) == 0 end }, - MOUSE = { + mouse = { PRESSED = function(key_id) return Mouse.pressed(KEYS_INFO[key_id][1]) end, RELEASED = function(key_id) return Mouse.button(KEYS_INFO[key_id][1]) == 0 end } @@ -234,7 +245,7 @@ end -- Checks for pressed and released keybinds, performs keybind actions. -- * Checks for both right and left key modifiers (ctrl, alt, shift). --- * If some keybind is pressed, won't check for other keybinds until this keybing is released. +-- * If some keybind is pressed, won't check for other keybinds until this keybind is released. -- * If several mods bound the same keys, keybind action will be performed for all of them, when keybind is pressed. -- * Keybind is considered released, when its primary key is released. function dmf.check_keybinds() @@ -246,10 +257,34 @@ function dmf.check_keybinds() for primary_key, keybinds_data in pairs(_keybinds) do if keybinds_data.check_pressed(primary_key) then for _, keybind_data in ipairs(keybinds_data) do - if (not keybind_data.ctrl and not ctrl_pressed or keybind_data.ctrl and ctrl_pressed) and - (not keybind_data.alt and not alt_pressed or keybind_data.alt and alt_pressed) and - (not keybind_data.shift and not shift_pressed or keybind_data.shift and shift_pressed) + + local all_pressed = true + for enabler, _ in pairs(keybind_data.enablers) do + + -- Check that every enabler key is pressed + if OTHER_KEYS[enabler] and + ( + Keyboard.button(KEYS_INFO[enabler][1]) + + Keyboard.button(KEYS_INFO[enabler][4]) + ) <= 0 + or not (Keyboard.button(KEYS_INFO[enabler][1]) > 0) + then + all_pressed = false + break + end + end + + -- Check that no modifier keys are pressed that shouldn't be + if all_pressed and + (not keybind_data.ctrl and ctrl_pressed) and + (not keybind_data.alt and alt_pressed) and + (not keybind_data.shift and shift_pressed) then + all_pressed = false + end + + -- Peform the keybind action if everything validates + if all_pressed then if perform_keybind_action(keybind_data, true) then if keybind_data.trigger == "held" then keybind_data.release_action = true @@ -295,13 +330,14 @@ function dmf.generate_keybinds() end local keybind_data = { - mod = mod, - global = raw_keybind_data.global, - trigger = raw_keybind_data.trigger, - type = raw_keybind_data.type, - ctrl = modifier_keys["ctrl"], - alt = modifier_keys["alt"], - shift = modifier_keys["shift"], + mod = mod, + global = raw_keybind_data.global, + trigger = raw_keybind_data.trigger, + type = raw_keybind_data.type, + enablers = modifier_keys, + ctrl = modifier_keys["ctrl"] or modifier_keys["left ctrl"] or modifier_keys["right ctrl"], + alt = modifier_keys["alt"] or modifier_keys["left alt"] or modifier_keys["right alt"], + shift = modifier_keys["shift"] or modifier_keys["left shift"] or modifier_keys["right shift"], function_name = raw_keybind_data.function_name, view_name = raw_keybind_data.view_name @@ -347,7 +383,7 @@ function dmf.create_keybinds_input_service() end --- Converts key_index to readable key_id, which is used by DMF to idenify keys. +-- Converts key_index to readable key_id, which is used by DMF to identify keys. -- (Used for capturing keybinds) function dmf.get_key_id(device, key_index) local key_info = PRIMARY_BINDABLE_KEYS[device][key_index] @@ -372,6 +408,79 @@ function dmf.build_keybind_string(keys) return table.concat(readable_key_names, " + ") end + +-- Translate key watch result to mod options keybind +-- (Used in keybind widget) +function dmf.keybind_result_to_keys(keybind_result) + local keys = {} + + -- Get the local name of the main key + if keybind_result.main then + + local global_name = keybind_result.main + local device_type = InputUtils.key_device_type(global_name) + local local_name = InputUtils.local_key_name(global_name, device_type) + + -- Check for a missing or unbindable primary key name + if not local_name or not dmf.can_bind_as_primary_key(local_name) then + return keys + end + + keys[1] = local_name + end + + -- Add the enablers keys as additional keys + if keybind_result.enablers then + for _, global_name in ipairs(keybind_result.enablers) do + + local device_type = InputUtils.key_device_type(global_name) + local local_name = InputUtils.local_key_name(global_name, device_type) + + keys[#keys + 1] = local_name + end + end + + return keys +end + + +-- Translate mod options keybind to key watch result +-- (Used in keybind widget) +function dmf.keys_to_keybind_result(keys) + local keybind_result = { + enablers = {}, + disablers = {} + } + + if not keys or #keys == 0 then + return nil + end + + if keys[1] then + local local_name = keys[1] + local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) + + -- End early if our main key doesn't exist, and return an empty result + if not global_name then + return nil + end + + keybind_result.main = global_name + end + + -- Add all remaining keys to the enablers list + for i = 2, #keys do + local local_name = keys[i] + local global_name = KEYS_INFO[local_name] and InputUtils.local_to_global_name(local_name, KEYS_INFO[local_name][3]) + + if global_name then + keybind_result.enablers[#keybind_result.enablers + 1] = global_name + end + end + + return keybind_result +end + -- ##################################################################################################################### -- ##### Script ######################################################################################################## -- ##################################################################################################################### diff --git a/dmf/scripts/mods/dmf/modules/core/localization.lua b/dmf/scripts/mods/dmf/modules/core/localization.lua index 9532b7d..bae157e 100644 --- a/dmf/scripts/mods/dmf/modules/core/localization.lua +++ b/dmf/scripts/mods/dmf/modules/core/localization.lua @@ -80,7 +80,7 @@ end DMFMod.add_global_localize_strings = function (self, text_translations) - for text_id, translations in ipairs(text_translations) do + for text_id, translations in pairs(text_translations) do if not _global_localization_database[text_id] then _global_localization_database[text_id] = translations end diff --git a/dmf/scripts/mods/dmf/modules/dmf_options.lua b/dmf/scripts/mods/dmf/modules/dmf_options.lua index c9866bc..937e310 100644 --- a/dmf/scripts/mods/dmf/modules/dmf_options.lua +++ b/dmf/scripts/mods/dmf/modules/dmf_options.lua @@ -250,7 +250,7 @@ end dmf.load_developer_mode_settings = function () --@TODO: maybe move it to somewhere else? Managers.mod._settings.developer_mode = dmf:get("developer_mode") - Application.set_user_setting("mod_settings", Managers.mod._settings) + Application.set_user_setting("mod_manager_settings", Managers.mod._settings) end -- #################################################################################################################### diff --git a/dmf/scripts/mods/dmf/modules/gui/custom_views.lua b/dmf/scripts/mods/dmf/modules/gui/custom_views.lua index 8a7d908..97b866f 100644 --- a/dmf/scripts/mods/dmf/modules/gui/custom_views.lua +++ b/dmf/scripts/mods/dmf/modules/gui/custom_views.lua @@ -110,7 +110,15 @@ local function remove_injected_views(on_reload) if on_reload then for view_name, _ in pairs(_custom_views_data) do - -- Remove injected views. + + -- Close the view if active + if Managers.ui:view_active(view_name) then + + local force_close = true + Managers.ui:close_view(view_name, force_close) + end + + -- Remove the injected view _ingame_ui._view_list[view_name] = nil end end @@ -308,11 +316,6 @@ dmf:hook_safe(ViewLoader, "init", function() _custom_view_persistent_data.loader_initialized = true end) --- Track the deletion of the view loader -dmf:hook_safe(ViewLoader, "destroy", function() - _custom_view_persistent_data.loader_initialized = false -end) - -- Track the loading of views, set the loader flag if class selection is reached dmf:hook_safe(UIManager, "load_view", function(self, view_name) diff --git a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua index e4e1aff..6316923 100644 --- a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua +++ b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view.lua @@ -675,7 +675,6 @@ DMFOptionsView._setup_category_config = function (self, config) for i = 1, #config_categories do local category_config = config_categories[i] local category_display_name = category_config.display_name - local category_icon = category_config.icon local category_reset_function = category_config.reset_function local valid = self._validation_mapping[category_display_name].validation_result @@ -683,7 +682,6 @@ DMFOptionsView._setup_category_config = function (self, config) local entry = { widget_type = "settings_button", display_name = category_display_name, - icon = category_icon, can_be_reset = category_config.can_be_reset, pressed_function = function (parent, widget, entry) self._category_content_grid:select_widget(widget) diff --git a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view_content_blueprints.lua b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view_content_blueprints.lua index 715a191..136dd4a 100644 --- a/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view_content_blueprints.lua +++ b/dmf/scripts/mods/dmf/modules/ui/options/dmf_options_view_content_blueprints.lua @@ -512,20 +512,20 @@ blueprints.dropdown = { local has_options_function = entry.options_function ~= nil local has_dynamic_contents = entry.has_dynamic_contents local options = entry.options or entry.options_function and entry.options_function() - local options_by_id = {} local num_options = #options local num_visible_options = math.min(num_options, max_visible_options) content.num_visible_options = num_visible_options local optional_num_decimals = entry.optional_num_decimals local number_format = string.format("%%.%sf", optional_num_decimals or DEFAULT_NUM_DECIMALS) + local options_by_value = {} for i = 1, num_options do local option = options[i] - options_by_id[option.id] = option + options_by_value[option.value] = option end content.number_format = number_format - content.options_by_id = options_by_id + content.options_by_value = options_by_value content.options = options content.hotspot.pressed_callback = function () @@ -559,7 +559,7 @@ blueprints.dropdown = { local offset = widget.offset local style = widget.style local options = content.options - local options_by_id = content.options_by_id + local options_by_value = content.options_by_value local num_visible_options = content.num_visible_options local num_options = #options local focused = content.exclusive_focus and not is_disabled @@ -587,7 +587,7 @@ blueprints.dropdown = { value = entry.get_function and entry:get_function() or content.internal_value or "" - local preview_option = options_by_id[value] + local preview_option = options_by_value[value] local preview_option_id = preview_option and preview_option.id local preview_value = preview_option and preview_option.display_name or Localize("loc_settings_option_unavailable") @@ -677,7 +677,7 @@ blueprints.dropdown = { if not new_value and focused and not using_gamepad and option_hotspot.on_pressed then option_hotspot.on_pressed = nil - new_value = option.id + new_value = option.value content.selected_index = i end diff --git a/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua b/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua index 7cab580..f28c79b 100644 --- a/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua +++ b/dmf/scripts/mods/dmf/modules/ui/options/mod_options.lua @@ -1,15 +1,27 @@ local dmf = get_mod("DMF") local OptionsUtilities = require("scripts/utilities/ui/options") -local InputUtils = require("scripts/managers/input/input_utils") local _type_template_map = {} +local _devices = { + "keyboard", + "mouse" +} +local _cancel_keys = { + "keyboard_esc" +} +local _reserved_keys = {} + -- #################################################################################################################### -- ##### Local functions ############################################################################################## -- #################################################################################################################### --- Create value slider template +-- ##################### +-- ###### Header ####### +-- ##################### + +-- Create header template local create_header_template = function (self, params) local template = { @@ -24,11 +36,15 @@ end _type_template_map["header"] = create_header_template +-- ########################### +-- ###### Percent Slider ##### +-- ########################### + -- Create percentage slider template local create_percent_slider_template = function (self, params) params.on_value_changed_function = function(new_value) - get_mod(params.mod_name):set(params.setting_id, new_value) + get_mod(params.mod_name):set(params.setting_id, new_value, true) return true end @@ -53,11 +69,15 @@ end _type_template_map["percent_slider"] = create_percent_slider_template +-- ########################### +-- ###### Value Slider ####### +-- ########################### + -- Create value slider template local create_value_slider_template = function (self, params) params.on_value_changed_function = function(new_value) - get_mod(params.mod_name):set(params.setting_id, new_value) + get_mod(params.mod_name):set(params.setting_id, new_value, true) return true end @@ -87,6 +107,10 @@ _type_template_map["value_slider"] = create_value_slider_template _type_template_map["numeric"] = create_value_slider_template +-- ###################### +-- ###### Checkbox ###### +-- ###################### + -- Create checkbox template local create_checkbox_template = function (self, params) local template = { @@ -99,7 +123,7 @@ local create_checkbox_template = function (self, params) value_type = "boolean", } template.on_activated = function(new_value) - get_mod(params.mod_name):set(params.setting_id, new_value) + get_mod(params.mod_name):set(params.setting_id, new_value, true) return true end @@ -112,6 +136,40 @@ end _type_template_map["checkbox"] = create_checkbox_template +-- ######################## +-- ###### Mod Toggle ###### +-- ######################## + +-- Create mod toggle template +local create_mod_toggle_template = function (self, params) + local template = { + after = params.after, + category = params.category, + default_value = true, + display_name = dmf:localize("toggle_mod"), + indentation_level = 0, + tooltip_text = dmf:localize("toggle_mod_description"), + value_type = "boolean", + } + + template.on_activated = function(new_value) + dmf.mod_state_changed(params.mod_name, new_value) + + return true + end + template.get_function = function() + return get_mod(params.mod_name):is_enabled() + end + + return template +end +_type_template_map["mod_toggle"] = create_mod_toggle_template + + +-- ###################### +-- ###### Dropdown ###### +-- ###################### + -- Create dropdown template local create_dropdown_template = function (self, params) @@ -131,7 +189,7 @@ local create_dropdown_template = function (self, params) widget_type = "dropdown", } template.on_activated = function(new_value) - get_mod(params.mod_name):set(params.setting_id, new_value) + get_mod(params.mod_name):set(params.setting_id, new_value, true) return true end @@ -144,34 +202,30 @@ end _type_template_map["dropdown"] = create_dropdown_template -local set_new_keybind = function (self, keybind_widget_content) +-- ########################### +-- ######### Keybind ######### +-- ########################### + +local set_new_keybind = function (self, keybind_data) + local mod = get_mod(keybind_data.mod_name) dmf.add_mod_keybind( - get_mod(keybind_widget_content.mod_name), - keybind_widget_content.setting_id, + mod, + keybind_data.setting_id, { - global = keybind_widget_content.keybind_global, - trigger = keybind_widget_content.keybind_trigger, - type = keybind_widget_content.keybind_type, - keys = keybind_widget_content.keys, - function_name = keybind_widget_content.function_name, - view_name = keybind_widget_content.view_name, + global = keybind_data.keybind_global, + trigger = keybind_data.keybind_trigger, + type = keybind_data.keybind_type, + keys = keybind_data.keys, + function_name = keybind_data.function_name, + view_name = keybind_data.view_name, } ) + mod:set(keybind_data.setting_id, keybind_data.keys, true) end -- Create keybind template local create_keybind_template = function (self, params) - local reserved_keys = {} - local cancel_keys = { - "keyboard_esc" - } - local devices = { - "keyboard", - "mouse", - "xbox_controller", - "ps4_controller" - } local template = { widget_type = "keybind", @@ -181,63 +235,57 @@ local create_keybind_template = function (self, params) group_name = params.category, category = params.category, after = params.parent_index, - devices = devices, + devices = _devices, sort_order = params.sort_order, - cancel_keys = cancel_keys, - reserved_keys = reserved_keys, + cancel_keys = _cancel_keys, + reserved_keys = _reserved_keys, indentation_level = params.depth, mod_name = params.mod_name, setting_id = params.setting_id, + keys = dmf.keys_to_keybind_result(params.keys), on_activated = function (new_value, old_value) - for i = 1, #cancel_keys do - local cancel_key = cancel_keys[i] + for i = 1, #_cancel_keys do + local cancel_key = _cancel_keys[i] if cancel_key == new_value.main then -- Prevent unbinding the mod options menu if params.setting_id ~= "open_dmf_options" then - params.keybind_text = "" - params.keys = {} - + -- Unbind the keybind + params.keys = {} set_new_keybind(self, params) end + return true end end - for i = 1, #reserved_keys do - local reserved_key = reserved_keys[i] + for i = 1, #_reserved_keys do + local reserved_key = _reserved_keys[i] if reserved_key == new_value.main then return false end end - local device_type = InputUtils.key_device_type(new_value.main) - local key_name = InputUtils.local_key_name(new_value.main, device_type) + -- Get the new keybind + local keys = dmf.keybind_result_to_keys(new_value) - params.keybind_text = key_name - params.keys = {key_name} + -- Bind the new key and prevent unbinding the mod options menu + if keys and #keys > 0 or params.setting_id ~= "open_dmf_options" then + params.keys = keys + set_new_keybind(self, params) + end - set_new_keybind(self, params) return true end, get_function = function (template) + local keys = get_mod(template.mod_name):get(template.setting_id) + local keybind_result = dmf.keys_to_keybind_result(keys) - local setting = get_mod(template.mod_name):get(template.setting_id) - local local_name = setting and setting[1] - if not local_name then - return false - end - - local global_name = InputUtils.local_to_global_name(local_name, "keyboard") - return { - main = global_name, - disablers = {}, - enablers = {}, - } + return keybind_result end, } @@ -246,6 +294,11 @@ end _type_template_map["keybind"] = create_keybind_template +-- ########################### +-- ###### Miscellaneous ###### +-- ########################### + +-- Get the template creation function associated with a given widget data type local function widget_data_to_template(self, data) if data and data.type and type(data.type) == "string" and _type_template_map[data.type] then return _type_template_map[data.type](self, data) @@ -256,18 +309,30 @@ local function widget_data_to_template(self, data) end --- Add mod categories to options view -local create_mod_category = function (self, categories, widget_data) +-- Add a mod category to the options view categories +local function create_mod_category(self, categories, widget_data) local category = { can_be_reset = widget_data.can_be_reset or true, display_name = widget_data.readable_mod_name or widget_data.mod_name or "", - icon = widget_data.icon_material or "content/ui/materials/icons/system/settings/category_gameplay", custom = true } categories[#categories + 1] = category return category end + +-- Create an option template and handle index offsets +local function create_option_template(self, widget_data, category_name, index_offset) + local template = widget_data_to_template(self, widget_data) + if template then + template.custom = true + template.category = category_name + template.after = template.after and template.after + index_offset or nil + + return template + end +end + -- #################################################################################################################### -- ##### Hooks ######################################################################################################## -- #################################################################################################################### @@ -276,20 +341,48 @@ end -- ##### DMF internal functions and variables ######################################################################### -- #################################################################################################################### - -- Add mod settings to options view dmf.create_mod_options_settings = function (self, options_templates) local categories = options_templates.categories local settings = options_templates.settings + -- Create a category for every mod for _, mod_data in ipairs(dmf.options_widgets_data) do local category = create_mod_category(self, categories, mod_data[1]) - for _, widget_data in ipairs(mod_data) do - local template = widget_data_to_template(self, widget_data) + local index_offset = 0 + + -- Create the category header + local template = create_option_template(self, mod_data[1], category.display_name, index_offset) + if template then + settings[#settings + 1] = template + end + + -- Create a top-level toggle option if the mod is togglable + if mod_data[1].is_togglable then + local toggle_widget_data = { + mod_name = mod_data[1].mod_name, + category = category.display_name, + after = #settings, + type = "mod_toggle" + } + + local toggle_template = create_option_template(self, toggle_widget_data, category.display_name, index_offset) + if toggle_template then + settings[#settings + 1] = toggle_template + index_offset = index_offset + 1 + end + end + + -- Populate the category with options taken from the remaining options data + for i = 2, #mod_data do + local widget_data = mod_data[i] + + template = widget_data_to_template(self, widget_data) if template then template.custom = true template.category = category.display_name + template.after = template.after + index_offset settings[#settings + 1] = template end @@ -297,47 +390,6 @@ dmf.create_mod_options_settings = function (self, options_templates) end return options_templates - - --[[local settings = OptionsView._options_templates.settings - - for name, this_mod in pairs(Mods) do - -- Custom settings - if type(this_mod) == "table" and this_mod.options then - - local text = this_mod.text or name - Mods.Localization.add("loc_settings_menu_group_mods_"..name, text) - - local options_no_after = 0 - for _, option in pairs(this_mod.options) do - if not option.after then - options_no_after = options_no_after + 1 - end - end - - if options_no_after > 0 then - settings[#settings+1] = { - widget_type = "group_header", - group_name = "mods_settings", - display_name = "loc_settings_menu_group_mods_"..name, - category = "loc_settings_menu_category_mods", - custom = true, - } - end - - for _, setting in pairs(this_mod.options) do - setting.custom = true - setting.category = setting.category or "loc_settings_menu_category_mods" - setting.indentation_level = setting.after and 1 or 0 - if setting.after then - local index = self:after_index(OptionsView, setting.after) - table.insert(settings, index, setting) - else - settings[#settings+1] = setting - end - end - - end - end]] end