diff --git a/builtin/fstk/tabview.lua b/builtin/fstk/tabview.lua index 424d329fb..63c205d6e 100644 --- a/builtin/fstk/tabview.lua +++ b/builtin/fstk/tabview.lua @@ -42,6 +42,7 @@ local function add_tab(self,tab) event_handler = tab.cbf_events, get_formspec = tab.cbf_formspec, tabsize = tab.tabsize, + formspec_version = tab.formspec_version, on_change = tab.on_change, tabdata = {}, } @@ -69,6 +70,10 @@ local function get_formspec(self) local tsize = tab.tabsize or {width=self.width, height=self.height} prepend = string.format("size[%f,%f,%s]", tsize.width, tsize.height, dump(self.fixed_size)) + + if tab.formspec_version then + prepend = ("formspec_version[%d]"):format(tab.formspec_version) .. prepend + end end local formspec = (prepend or "") .. self:tab_header() .. content diff --git a/builtin/mainmenu/dlg_settings_advanced.lua b/builtin/mainmenu/dlg_settings_advanced.lua deleted file mode 100644 index 32c051c26..000000000 --- a/builtin/mainmenu/dlg_settings_advanced.lua +++ /dev/null @@ -1,1132 +0,0 @@ ---Minetest ---Copyright (C) 2015 PilzAdam --- ---This program is free software; you can redistribute it and/or modify ---it under the terms of the GNU Lesser General Public License as published by ---the Free Software Foundation; either version 2.1 of the License, or ---(at your option) any later version. --- ---This program is distributed in the hope that it will be useful, ---but WITHOUT ANY WARRANTY; without even the implied warranty of ---MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ---GNU Lesser General Public License for more details. --- ---You should have received a copy of the GNU Lesser General Public License along ---with this program; if not, write to the Free Software Foundation, Inc., ---51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -local FILENAME = "settingtypes.txt" - -local CHAR_CLASSES = { - SPACE = "[%s]", - VARIABLE = "[%w_%-%.]", - INTEGER = "[+-]?[%d]", - FLOAT = "[+-]?[%d%.]", - FLAGS = "[%w_%-%.,]", -} - -local function flags_to_table(flags) - return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split -end - --- returns error message, or nil -local function parse_setting_line(settings, line, read_all, base_level, allow_secure) - - -- strip carriage returns (CR, /r) - line = line:gsub("\r", "") - - -- comment - local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") - if comment then - if settings.current_comment == "" then - settings.current_comment = comment - else - settings.current_comment = settings.current_comment .. "\n" .. comment - end - return - end - - -- clear current_comment so only comments directly above a setting are bound to it - -- but keep a local reference to it for variables in the current line - local current_comment = settings.current_comment - settings.current_comment = "" - - -- empty lines - if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then - return - end - - -- category - local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") - if category then - table.insert(settings, { - name = category, - level = stars:len() + base_level, - type = "category", - }) - return - end - - -- settings - local first_part, name, readable_name, setting_type = line:match("^" - -- this first capture group matches the whole first part, - -- so we can later strip it from the rest of the line - .. "(" - .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name - .. CHAR_CLASSES.SPACE .. "*" - .. "%(([^%)]*)%)" -- readable name - .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type - .. CHAR_CLASSES.SPACE .. "*" - .. ")") - - if not first_part then - return "Invalid line" - end - - if name:match("secure%.[.]*") and not allow_secure then - return "Tried to add \"secure.\" setting" - end - - if readable_name == "" then - readable_name = nil - end - local remaining_line = line:sub(first_part:len() + 1) - - if setting_type == "int" then - local default, min, max = remaining_line:match("^" - -- first int is required, the last 2 are optional - .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.INTEGER .. "*)" - .. "$") - - if not default or not tonumber(default) then - return "Invalid integer setting" - end - - min = tonumber(min) - max = tonumber(max) - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "int", - default = default, - min = min, - max = max, - comment = current_comment, - }) - return - end - - if setting_type == "string" - or setting_type == "key" or setting_type == "v3f" then - local default = remaining_line:match("^(.*)$") - - if not default then - return "Invalid string setting" - end - if setting_type == "key" and not read_all then - -- ignore key type if read_all is false - return - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = setting_type, - default = default, - comment = current_comment, - }) - return - end - - if setting_type == "noise_params_2d" - or setting_type == "noise_params_3d" then - local default = remaining_line:match("^(.*)$") - - if not default then - return "Invalid string setting" - end - - local values = {} - local ti = 1 - local index = 1 - for match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters - index = default:find("[+-]?[%d.-e]+", index) + match:len() - table.insert(values, match) - ti = ti + 1 - if ti > 9 then - break - end - end - index = default:find("[^, ]", index) - local flags = "" - if index then - flags = default:sub(index) - default = default:sub(1, index - 3) -- Make sure no flags in single-line format - end - table.insert(values, flags) - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = setting_type, - default = default, - default_table = { - offset = values[1], - scale = values[2], - spread = { - x = values[3], - y = values[4], - z = values[5] - }, - seed = values[6], - octaves = values[7], - persistence = values[8], - lacunarity = values[9], - flags = values[10] - }, - values = values, - comment = current_comment, - noise_params = true, - flags = flags_to_table("defaults,eased,absvalue") - }) - return - end - - if setting_type == "bool" then - if remaining_line ~= "false" and remaining_line ~= "true" then - return "Invalid boolean setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "bool", - default = remaining_line, - comment = current_comment, - }) - return - end - - if setting_type == "float" then - local default, min, max = remaining_line:match("^" - -- first float is required, the last 2 are optional - .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLOAT .. "*)" - .."$") - - if not default or not tonumber(default) then - return "Invalid float setting" - end - - min = tonumber(min) - max = tonumber(max) - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "float", - default = default, - min = min, - max = max, - comment = current_comment, - }) - return - end - - if setting_type == "enum" then - local default, values = remaining_line:match("^" - -- first value (default) may be empty (i.e. is optional) - .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLAGS .. "+)" - .. "$") - - if not default or values == "" then - return "Invalid enum setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "enum", - default = default, - values = values:split(",", true), - comment = current_comment, - }) - return - end - - if setting_type == "path" or setting_type == "filepath" then - local default = remaining_line:match("^(.*)$") - - if not default then - return "Invalid path setting" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = setting_type, - default = default, - comment = current_comment, - }) - return - end - - if setting_type == "flags" then - local default, possible = remaining_line:match("^" - -- first value (default) may be empty (i.e. is optional) - -- this is implemented by making the last value optional, and - -- swapping them around if it turns out empty. - .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*" - .. "(" .. CHAR_CLASSES.FLAGS .. "*)" - .. "$") - - if not default or not possible then - return "Invalid flags setting" - end - - if possible == "" then - possible = default - default = "" - end - - table.insert(settings, { - name = name, - readable_name = readable_name, - type = "flags", - default = default, - possible = flags_to_table(possible), - comment = current_comment, - }) - return - end - - return "Invalid setting type \"" .. setting_type .. "\"" -end - -local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) - -- store this helper variable in the table so it's easier to pass to parse_setting_line() - result.current_comment = "" - - local line = file:read("*line") - while line do - local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) - if error_msg then - core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") - end - line = file:read("*line") - end - - result.current_comment = nil -end - --- read_all: whether to ignore certain setting types for GUI or not --- parse_mods: whether to parse settingtypes.txt in mods and games -local function parse_config_file(read_all, parse_mods) - local settings = {} - - do - local builtin_path = core.get_builtin_path() .. FILENAME - local file = io.open(builtin_path, "r") - if not file then - core.log("error", "Can't load " .. FILENAME) - return settings - end - - parse_single_file(file, builtin_path, read_all, settings, 0, true) - - file:close() - end - - if parse_mods then - -- Parse games - local games_category_initialized = false - for _, game in ipairs(pkgmgr.games) do - local path = game.path .. DIR_DELIM .. FILENAME - local file = io.open(path, "r") - if file then - if not games_category_initialized then - fgettext_ne("Content: Games") -- not used, but needed for xgettext - table.insert(settings, { - name = "Content: Games", - level = 0, - type = "category", - }) - games_category_initialized = true - end - - table.insert(settings, { - name = game.name, - level = 1, - type = "category", - }) - - parse_single_file(file, path, read_all, settings, 2, false) - - file:close() - end - end - - -- Parse mods - local mods_category_initialized = false - local mods = {} - pkgmgr.get_mods(core.get_modpath(), "mods", mods) - for _, mod in ipairs(mods) do - local path = mod.path .. DIR_DELIM .. FILENAME - local file = io.open(path, "r") - if file then - if not mods_category_initialized then - fgettext_ne("Content: Mods") -- not used, but needed for xgettext - table.insert(settings, { - name = "Content: Mods", - level = 0, - type = "category", - }) - mods_category_initialized = true - end - - table.insert(settings, { - name = mod.name, - readable_name = mod.title, - level = 1, - type = "category", - }) - - parse_single_file(file, path, read_all, settings, 2, false) - - file:close() - end - end - - -- Parse client mods - local clientmods_category_initialized = false - local clientmods = {} - pkgmgr.get_mods(core.get_clientmodpath(), "clientmods", clientmods) - for _, mod in ipairs(clientmods) do - local path = mod.path .. DIR_DELIM .. FILENAME - local file = io.open(path, "r") - if file then - if not clientmods_category_initialized then - fgettext_ne("Client Mods") -- not used, but needed for xgettext - table.insert(settings, { - name = "Client Mods", - level = 0, - type = "category", - }) - clientmods_category_initialized = true - end - - table.insert(settings, { - name = mod.name, - level = 1, - type = "category", - }) - - parse_single_file(file, path, read_all, settings, 2, false) - - file:close() - end - end - end - - return settings -end - -local function filter_settings(settings, searchstring) - if not searchstring or searchstring == "" then - return settings, -1 - end - - -- Setup the keyword list - local keywords = {} - for word in searchstring:lower():gmatch("%S+") do - table.insert(keywords, word) - end - - local result = {} - local category_stack = {} - local current_level = 0 - local best_setting = nil - for _, entry in pairs(settings) do - if entry.type == "category" then - -- Remove all settingless categories - while #category_stack > 0 and entry.level <= current_level do - table.remove(category_stack, #category_stack) - if #category_stack > 0 then - current_level = category_stack[#category_stack].level - else - current_level = 0 - end - end - - -- Push category onto stack - category_stack[#category_stack + 1] = entry - current_level = entry.level - else - -- See if setting matches keywords - local setting_score = 0 - for k = 1, #keywords do - local keyword = keywords[k] - - if string.find(entry.name:lower(), keyword, 1, true) then - setting_score = setting_score + 1 - end - - if entry.readable_name and - string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then - setting_score = setting_score + 1 - end - - if entry.comment and - string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then - setting_score = setting_score + 1 - end - end - - -- Add setting to results if match - if setting_score > 0 then - -- Add parent categories - for _, category in pairs(category_stack) do - result[#result + 1] = category - end - category_stack = {} - - -- Add setting - result[#result + 1] = entry - entry.score = setting_score - - if not best_setting or - setting_score > result[best_setting].score then - best_setting = #result - end - end - end - end - return result, best_setting or -1 -end - -local full_settings = parse_config_file(false, true) -local search_string = "" -local settings = full_settings -local selected_setting = 1 - -local function get_current_value(setting) - local value = core.settings:get(setting.name) - if value == nil then - value = setting.default - end - return value -end - -local function get_current_np_group(setting) - local value = core.settings:get_np_group(setting.name) - if value == nil then - return setting.values - end - local p = "%g" - return { - p:format(value.offset), - p:format(value.scale), - p:format(value.spread.x), - p:format(value.spread.y), - p:format(value.spread.z), - p:format(value.seed), - p:format(value.octaves), - p:format(value.persistence), - p:format(value.lacunarity), - value.flags - } -end - -local function get_current_np_group_as_string(setting) - local value = core.settings:get_np_group(setting.name) - if value == nil then - return setting.default - end - return ("%g, %g, (%g, %g, %g), %g, %g, %g, %g"):format( - value.offset, - value.scale, - value.spread.x, - value.spread.y, - value.spread.z, - value.seed, - value.octaves, - value.persistence, - value.lacunarity - ) .. (value.flags ~= "" and (", " .. value.flags) or "") -end - -local checkboxes = {} -- handle checkboxes events - -local function create_change_setting_formspec(dialogdata) - local setting = settings[selected_setting] - -- Final formspec will be created at the end of this function - -- Default values below, may be changed depending on setting type - local width = 10 - local height = 3.5 - local description_height = 3 - local formspec = "" - - -- Setting-specific formspec elements - if setting.type == "bool" then - local selected_index = 1 - if core.is_yes(get_current_value(setting)) then - selected_index = 2 - end - formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;" - .. fgettext("Disabled") .. "," .. fgettext("Enabled") .. ";" - .. selected_index .. "]" - height = height + 1.25 - - elseif setting.type == "enum" then - local selected_index = 0 - formspec = "dropdown[3," .. height .. ";4,1;dd_setting_value;" - for index, value in ipairs(setting.values) do - -- translating value is not possible, since it's the value - -- that we set the setting to - formspec = formspec .. core.formspec_escape(value) .. "," - if get_current_value(setting) == value then - selected_index = index - end - end - if #setting.values > 0 then - formspec = formspec:sub(1, -2) -- remove trailing comma - end - formspec = formspec .. ";" .. selected_index .. "]" - height = height + 1.25 - - elseif setting.type == "path" or setting.type == "filepath" then - local current_value = dialogdata.selected_path - if not current_value then - current_value = get_current_value(setting) - end - formspec = "field[0.28," .. height + 0.15 .. ";8,1;te_setting_value;;" - .. core.formspec_escape(current_value) .. "]" - .. "button[8," .. height - 0.15 .. ";2,1;btn_browser_" - .. setting.type .. ";" .. fgettext("Browse") .. "]" - height = height + 1.15 - - elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then - local t = get_current_np_group(setting) - local dimension = 3 - if setting.type == "noise_params_2d" then - dimension = 2 - end - - -- More space for 3x3 fields - description_height = description_height - 1.5 - height = height - 1.5 - - local fields = {} - local function add_field(x, name, label, value) - fields[#fields + 1] = ("field[%f,%f;3.3,1;%s;%s;%s]"):format( - x, height, name, label, core.formspec_escape(value or "") - ) - end - -- First row - height = height + 0.3 - add_field(0.3, "te_offset", fgettext("Offset"), t[1]) - add_field(3.6, "te_scale", fgettext("Scale"), t[2]) - add_field(6.9, "te_seed", fgettext("Seed"), t[6]) - height = height + 1.1 - - -- Second row - add_field(0.3, "te_spreadx", fgettext("X spread"), t[3]) - if dimension == 3 then - add_field(3.6, "te_spready", fgettext("Y spread"), t[4]) - else - fields[#fields + 1] = "label[4," .. height - 0.2 .. ";" .. - fgettext("2D Noise") .. "]" - end - add_field(6.9, "te_spreadz", fgettext("Z spread"), t[5]) - height = height + 1.1 - - -- Third row - add_field(0.3, "te_octaves", fgettext("Octaves"), t[7]) - add_field(3.6, "te_persist", fgettext("Persistence"), t[8]) - add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9]) - height = height + 1.1 - - - local enabled_flags = flags_to_table(t[10]) - local flags = {} - for _, name in ipairs(enabled_flags) do - -- Index by name, to avoid iterating over all enabled_flags for every possible flag. - flags[name] = true - end - for _, name in ipairs(setting.flags) do - local checkbox_name = "cb_" .. name - local is_enabled = flags[name] == true -- to get false if nil - checkboxes[checkbox_name] = is_enabled - end - -- Flags - formspec = table.concat(fields) - .. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;" - --[[~ "defaults" is a noise parameter flag. - It describes the default processing options - for noise settings in main menu -> "All Settings". ]] - .. fgettext("defaults") .. ";" -- defaults - .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil - .. "checkbox[5," .. height - 0.6 .. ";cb_eased;" - --[[~ "eased" is a noise parameter flag. - It is used to make the map smoother and - can be enabled in noise settings in - main menu -> "All Settings". ]] - .. fgettext("eased") .. ";" -- eased - .. tostring(flags["eased"] == true) .. "]" - .. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;" - --[[~ "absvalue" is a noise parameter flag. - It is short for "absolute value". - It can be enabled in noise settings in - main menu -> "All Settings". ]] - .. fgettext("absvalue") .. ";" -- absvalue - .. tostring(flags["absvalue"] == true) .. "]" - height = height + 1 - - elseif setting.type == "v3f" then - local val = get_current_value(setting) - local v3f = {} - for line in val:gmatch("[+-]?[%d.+-eE]+") do -- All numeric characters - table.insert(v3f, line) - end - - height = height + 0.3 - formspec = formspec - .. "field[0.3," .. height .. ";3.3,1;te_x;" - .. fgettext("X") .. ";" -- X - .. core.formspec_escape(v3f[1] or "") .. "]" - .. "field[3.6," .. height .. ";3.3,1;te_y;" - .. fgettext("Y") .. ";" -- Y - .. core.formspec_escape(v3f[2] or "") .. "]" - .. "field[6.9," .. height .. ";3.3,1;te_z;" - .. fgettext("Z") .. ";" -- Z - .. core.formspec_escape(v3f[3] or "") .. "]" - height = height + 1.1 - - elseif setting.type == "flags" then - local current_flags = flags_to_table(get_current_value(setting)) - local flags = {} - for _, name in ipairs(current_flags) do - -- Index by name, to avoid iterating over all enabled_flags for every possible flag. - if name:sub(1, 2) == "no" then - flags[name:sub(3)] = false - else - flags[name] = true - end - end - local flags_count = #setting.possible / 2 - local max_height = math.ceil(flags_count / 2) / 2 - - -- More space for flags - description_height = description_height - 1 - height = height - 1 - - local fields = {} -- To build formspec - local j = 1 - for _, name in ipairs(setting.possible) do - if name:sub(1, 2) ~= "no" then - local x = 0.5 - local y = height + j / 2 - 0.75 - if j - 1 >= flags_count / 2 then -- 2nd column - x = 5 - y = y - max_height - end - j = j + 1; - local checkbox_name = "cb_" .. name - local is_enabled = flags[name] == true -- to get false if nil - checkboxes[checkbox_name] = is_enabled - - fields[#fields + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format( - x, y, checkbox_name, name, tostring(is_enabled) - ) - end - end - formspec = table.concat(fields) - height = height + max_height + 0.25 - - else - -- TODO: fancy input for float, int - local text = get_current_value(setting) - if dialogdata.error_message and dialogdata.entered_text then - text = dialogdata.entered_text - end - formspec = "field[0.28," .. height + 0.15 .. ";" .. width .. ",1;te_setting_value;;" - .. core.formspec_escape(text) .. "]" - height = height + 1.15 - end - - -- Box good, textarea bad. Calculate textarea size from box. - local function create_textfield(size, label, text, bg_color) - local textarea = { - x = size.x + 0.3, - y = size.y, - w = size.w + 0.25, - h = size.h * 1.16 + 0.12 - } - return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format( - size.x, size.y, size.w, size.h, bg_color or "#000", - textarea.x, textarea.y, textarea.w, textarea.h, - core.formspec_escape(label), core.formspec_escape(text) - ) - - end - - -- When there's an error: Shrink description textarea and add error below - if dialogdata.error_message then - local error_box = { - x = 0, - y = description_height - 0.4, - w = width - 0.25, - h = 0.5 - } - formspec = formspec .. - create_textfield(error_box, "", dialogdata.error_message, "#600") - description_height = description_height - 0.75 - end - - -- Get description field - local description_box = { - x = 0, - y = 0.2, - w = width - 0.25, - h = description_height - } - - local setting_name = setting.name - if setting.readable_name then - setting_name = fgettext_ne(setting.readable_name) .. - " (" .. setting.name .. ")" - end - - local comment_text - if setting.comment == "" then - comment_text = fgettext_ne("(No description of setting given)") - else - comment_text = fgettext_ne(setting.comment) - end - - return ( - "size[" .. width .. "," .. height + 0.25 .. ",true]" .. - create_textfield(description_box, setting_name, comment_text) .. - formspec .. - "button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" .. - fgettext("Save") .. "]" .. - "button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" .. - fgettext("Cancel") .. "]" - ) -end - -local function handle_change_setting_buttons(this, fields) - local setting = settings[selected_setting] - if fields["btn_done"] or fields["key_enter"] then - if setting.type == "bool" then - local new_value = fields["dd_setting_value"] - -- Note: new_value is the actual (translated) value shown in the dropdown - core.settings:set_bool(setting.name, new_value == fgettext("Enabled")) - - elseif setting.type == "enum" then - local new_value = fields["dd_setting_value"] - core.settings:set(setting.name, new_value) - - elseif setting.type == "int" then - local new_value = tonumber(fields["te_setting_value"]) - if not new_value or math.floor(new_value) ~= new_value then - this.data.error_message = fgettext_ne("Please enter a valid integer.") - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.min and new_value < setting.min then - this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.max and new_value > setting.max then - this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - core.settings:set(setting.name, new_value) - - elseif setting.type == "float" then - local new_value = tonumber(fields["te_setting_value"]) - if not new_value then - this.data.error_message = fgettext_ne("Please enter a valid number.") - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.min and new_value < setting.min then - this.data.error_message = fgettext_ne("The value must be at least $1.", setting.min) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - if setting.max and new_value > setting.max then - this.data.error_message = fgettext_ne("The value must not be larger than $1.", setting.max) - this.data.entered_text = fields["te_setting_value"] - core.update_formspec(this:get_formspec()) - return true - end - core.settings:set(setting.name, new_value) - - elseif setting.type == "flags" then - local values = {} - for _, name in ipairs(setting.possible) do - if name:sub(1, 2) ~= "no" then - if checkboxes["cb_" .. name] then - table.insert(values, name) - else - table.insert(values, "no" .. name) - end - end - end - - checkboxes = {} - - local new_value = table.concat(values, ", ") - core.settings:set(setting.name, new_value) - - elseif setting.type == "noise_params_2d" or setting.type == "noise_params_3d" then - local np_flags = {} - for _, name in ipairs(setting.flags) do - if checkboxes["cb_" .. name] then - table.insert(np_flags, name) - end - end - - checkboxes = {} - - if setting.type == "noise_params_2d" then - fields["te_spready"] = fields["te_spreadz"] - end - local new_value = { - offset = fields["te_offset"], - scale = fields["te_scale"], - spread = { - x = fields["te_spreadx"], - y = fields["te_spready"], - z = fields["te_spreadz"] - }, - seed = fields["te_seed"], - octaves = fields["te_octaves"], - persistence = fields["te_persist"], - lacunarity = fields["te_lacun"], - flags = table.concat(np_flags, ", ") - } - core.settings:set_np_group(setting.name, new_value) - - elseif setting.type == "v3f" then - local new_value = "(" - .. fields["te_x"] .. ", " - .. fields["te_y"] .. ", " - .. fields["te_z"] .. ")" - core.settings:set(setting.name, new_value) - - else - local new_value = fields["te_setting_value"] - core.settings:set(setting.name, new_value) - end - core.settings:write() - this:delete() - return true - end - - if fields["btn_cancel"] then - this:delete() - return true - end - - if fields["btn_browser_path"] then - core.show_path_select_dialog("dlg_browse_path", - fgettext_ne("Select directory"), false) - end - - if fields["btn_browser_filepath"] then - core.show_path_select_dialog("dlg_browse_path", - fgettext_ne("Select file"), true) - end - - if fields["dlg_browse_path_accepted"] then - this.data.selected_path = fields["dlg_browse_path_accepted"] - core.update_formspec(this:get_formspec()) - end - - if setting.type == "flags" - or setting.type == "noise_params_2d" - or setting.type == "noise_params_3d" then - for name, value in pairs(fields) do - if name:sub(1, 3) == "cb_" then - checkboxes[name] = value == "true" - end - end - end - - return false -end - -local function create_settings_formspec(tabview, _, tabdata) - local formspec = "size[12,5.4;true]" .. - "tablecolumns[color;tree;text,width=28;text]" .. - "tableoptions[background=#00000000;border=false]" .. - "field[0.3,0.1;10.2,1;search_string;;" .. core.formspec_escape(search_string) .. "]" .. - "field_close_on_enter[search_string;false]" .. - "button[10.2,-0.2;2,1;search;" .. fgettext("Search") .. "]" .. - "table[0,0.8;12,3.5;list_settings;" - - local current_level = 0 - for _, entry in ipairs(settings) do - local name - if not core.settings:get_bool("show_technical_names") and entry.readable_name then - name = fgettext_ne(entry.readable_name) - else - name = entry.name - end - - if entry.type == "category" then - current_level = entry.level - formspec = formspec .. "#FFFF00," .. current_level .. "," .. fgettext(name) .. ",," - - elseif entry.type == "bool" then - local value = get_current_value(entry) - if core.is_yes(value) then - value = fgettext("Enabled") - else - value = fgettext("Disabled") - end - formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," - .. value .. "," - - elseif entry.type == "key" then --luacheck: ignore - -- ignore key settings, since we have a special dialog for them - - elseif entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then - formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," - .. core.formspec_escape(get_current_np_group_as_string(entry)) .. "," - - else - formspec = formspec .. "," .. (current_level + 1) .. "," .. core.formspec_escape(name) .. "," - .. core.formspec_escape(get_current_value(entry)) .. "," - end - end - - if #settings > 0 then - formspec = formspec:sub(1, -2) -- remove trailing comma - end - formspec = formspec .. ";" .. selected_setting .. "]" .. - "button[0,4.9;4,1;btn_back;".. fgettext("< Back to Settings page") .. "]" .. - "button[10,4.9;2,1;btn_edit;" .. fgettext("Edit") .. "]" .. - "button[7,4.9;3,1;btn_restore;" .. fgettext("Restore Default") .. "]" .. - "checkbox[0,4.3;cb_tech_settings;" .. fgettext("Show technical names") .. ";" - .. dump(core.settings:get_bool("show_technical_names")) .. "]" - - return formspec -end - -local function handle_settings_buttons(this, fields, tabname, tabdata) - local list_enter = false - if fields["list_settings"] then - selected_setting = core.get_table_index("list_settings") - if core.explode_table_event(fields["list_settings"]).type == "DCL" then - -- Directly toggle booleans - local setting = settings[selected_setting] - if setting and setting.type == "bool" then - local current_value = get_current_value(setting) - core.settings:set_bool(setting.name, not core.is_yes(current_value)) - core.settings:write() - return true - else - list_enter = true - end - else - return true - end - end - - if fields.search or fields.key_enter_field == "search_string" then - if search_string == fields.search_string then - if selected_setting > 0 then - -- Go to next result on enter press - local i = selected_setting + 1 - local looped = false - while i > #settings or settings[i].type == "category" do - i = i + 1 - if i > #settings then - -- Stop infinte looping - if looped then - return false - end - i = 1 - looped = true - end - end - selected_setting = i - core.update_formspec(this:get_formspec()) - return true - end - else - -- Search for setting - search_string = fields.search_string - settings, selected_setting = filter_settings(full_settings, search_string) - core.update_formspec(this:get_formspec()) - end - return true - end - - if fields["btn_edit"] or list_enter then - local setting = settings[selected_setting] - if setting and setting.type ~= "category" then - local edit_dialog = dialog_create("change_setting", - create_change_setting_formspec, handle_change_setting_buttons) - edit_dialog:set_parent(this) - this:hide() - edit_dialog:show() - end - return true - end - - if fields["btn_restore"] then - local setting = settings[selected_setting] - if setting and setting.type ~= "category" then - core.settings:remove(setting.name) - core.settings:write() - core.update_formspec(this:get_formspec()) - end - return true - end - - if fields["btn_back"] then - this:delete() - return true - end - - if fields["cb_tech_settings"] then - core.settings:set("show_technical_names", fields["cb_tech_settings"]) - core.settings:write() - core.update_formspec(this:get_formspec()) - return true - end - - return false -end - -function create_adv_settings_dlg() - local dlg = dialog_create("settings_advanced", - create_settings_formspec, - handle_settings_buttons, - nil) - - return dlg -end - --- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'. --- For RUN_IN_PLACE the generated files may appear in the 'bin' folder. --- See comment and alternative line at the end of 'generate_from_settingtypes.lua'. - ---assert(loadfile(core.get_builtin_path().."mainmenu"..DIR_DELIM.. --- "generate_from_settingtypes.lua"))(parse_config_file(true, false)) diff --git a/builtin/mainmenu/init.lua b/builtin/mainmenu/init.lua index e674ec915..add94e420 100644 --- a/builtin/mainmenu/init.lua +++ b/builtin/mainmenu/init.lua @@ -40,7 +40,7 @@ dofile(menupath .. DIR_DELIM .. "serverlistmgr.lua") dofile(menupath .. DIR_DELIM .. "game_theme.lua") dofile(menupath .. DIR_DELIM .. "dlg_config_world.lua") -dofile(menupath .. DIR_DELIM .. "dlg_settings_advanced.lua") +dofile(menupath .. DIR_DELIM .. "settings" .. DIR_DELIM .. "init.lua") dofile(menupath .. DIR_DELIM .. "dlg_contentstore.lua") dofile(menupath .. DIR_DELIM .. "dlg_create_world.lua") dofile(menupath .. DIR_DELIM .. "dlg_delete_content.lua") @@ -51,7 +51,23 @@ dofile(menupath .. DIR_DELIM .. "dlg_version_info.lua") local tabs = {} -tabs.settings = dofile(menupath .. DIR_DELIM .. "tab_settings.lua") +tabs.settings = { + name = "settings", + caption = fgettext("Settings"), + cbf_formspec = function() + return "button[0.1,0.1;3,0.8;open_settings;" .. fgettext("Open Settings") .. "]" + end, + cbf_button_handler = function(tabview, fields) + if fields.open_settings then + local dlg = create_settings_dlg() + dlg:set_parent(tabview) + tabview:hide() + dlg:show() + return true + end + end, +} + tabs.content = dofile(menupath .. DIR_DELIM .. "tab_content.lua") tabs.about = dofile(menupath .. DIR_DELIM .. "tab_about.lua") tabs.local_game = dofile(menupath .. DIR_DELIM .. "tab_local.lua") @@ -103,7 +119,6 @@ local function init_globals() tv_main:set_autosave_tab(true) tv_main:add(tabs.local_game) tv_main:add(tabs.play_online) - tv_main:add(tabs.content) tv_main:add(tabs.settings) tv_main:add(tabs.about) diff --git a/builtin/mainmenu/settings/components.lua b/builtin/mainmenu/settings/components.lua new file mode 100644 index 000000000..c50bb53e3 --- /dev/null +++ b/builtin/mainmenu/settings/components.lua @@ -0,0 +1,384 @@ +--Minetest +--Copyright (C) 2022 rubenwardy +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local make = {} + + +-- This file defines various component constructors, of the form: +-- +-- make.component(setting) +-- +-- `setting` is a table representing the settingtype. +-- +-- A component is a table with the following: +-- +-- * `full_width`: (Optional) true if the component shouldn't reserve space for info / reset. +-- * `info_text`: (Optional) string, informational text shown in an info icon. +-- * `setting`: (Optional) the setting. +-- * `max_w`: (Optional) maximum width, `avail_w` will never exceed this. +-- * `changed`: (Optional) true if the setting has changed from its default value. +-- * `get_formspec = function(self, avail_w)`: +-- * `avail_w` is the available width for the component. +-- * Returns `fs, used_height`. +-- * `fs` is a string for the formspec. +-- Components should be relative to `0,0`, and not exceed `avail_w` or the returned `used_height`. +-- * `used_height` is the space used by components in `fs`. +-- * `on_submit = function(self, fields, parent)`: +-- * `fields`: submitted formspec fields +-- * `parent`: the fstk element for the settings UI, use to show dialogs +-- * Return true if the event was handled, to prevent future components receiving it. + + +local function get_label(setting) + local show_technical_names = core.settings:get_bool("show_technical_names") + if not show_technical_names and setting.readable_name then + return fgettext(setting.readable_name) + end + return setting.name +end + + +local function is_valid_number(value) + return type(value) == "number" and not (value ~= value or value >= math.huge or value <= -math.huge) +end + + +function make.heading(text) + return { + full_width = true, + get_formspec = function(self, avail_w) + return ("label[0,0.6;%s]box[0,0.9;%f,0.05;#ccc6]"):format(core.formspec_escape(text), avail_w), 1.2 + end, + } +end + + +--- Used for string and numeric style fields +--- +--- @param converter Function to coerce values +--- @param validator Validator function, optional. Returns true when valid. +local function make_field(converter, validator) + return function(setting) + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local value = core.settings:get(setting.name) or setting.default + self.changed = converter(value) ~= converter(setting.default) + + local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( + avail_w - 1.5, setting.name, get_label(setting), core.formspec_escape(value)) + fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) + + return fs, 1.1 + end, + + on_submit = function(self, fields) + if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then + local value = converter(fields[setting.name]) + if value == nil or (validator and not validator(value)) then + return true + end + + if setting.min then + value = math.max(value, setting.min) + end + if setting.max then + value = math.min(value, setting.max) + end + core.settings:set(setting.name, tostring(value)) + return true + end + end, + } + end +end + + +make.float = make_field(tonumber, is_valid_number) +make.int = make_field(function(x) + local value = tonumber(x) + return value and math.floor(value) +end, is_valid_number) +make.string = make_field(tostring, nil) + + +function make.bool(setting) + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local value = core.settings:get_bool(setting.name, core.is_yes(setting.default)) + self.changed = tostring(value) ~= setting.default + + local fs = ("checkbox[0,0.25;%s;%s;%s]"):format( + setting.name, get_label(setting), tostring(value)) + return fs, 0.5 + end, + + on_submit = function(self, fields) + if fields[setting.name] == nil then + return false + end + + core.settings:set_bool(setting.name, core.is_yes(fields[setting.name])) + return true + end, + } +end + + +function make.enum(setting) + return { + info_text = setting.comment, + setting = setting, + max_w = 4.5, + + get_formspec = function(self, avail_w) + local value = core.settings:get(setting.name) or setting.default + self.changed = value ~= setting.default + + local items = {} + for i, option in ipairs(setting.values) do + items[i] = core.formspec_escape(option) + end + + local selected_idx = table.indexof(setting.values, value) + local fs = "label[0,0.1;" .. get_label(setting) .. "]" + + fs = fs .. ("dropdown[0,0.3;%f,0.8;%s;%s;%d]"):format( + avail_w, setting.name, table.concat(items, ","), selected_idx, value) + + return fs, 1.1 + end, + + on_submit = function(self, fields) + local old_value = core.settings:get(setting.name) or setting.default + local value = fields[setting.name] + if value == nil or value == old_value then + return false + end + + core.settings:set(setting.name, value) + return true + end, + } +end + + +function make.path(setting) + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local value = core.settings:get(setting.name) or setting.default + self.changed = value ~= setting.default + + local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format( + avail_w - 3, setting.name, get_label(setting), value) + fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 3, "pick_" .. setting.name, fgettext("Browse")) + fs = fs .. ("button[%f,0.3;1.5,0.8;%s;%s]"):format(avail_w - 1.5, "set_" .. setting.name, fgettext("Set")) + + return fs, 1.1 + end, + + on_submit = function(self, fields) + local dialog_name = "dlg_path_" .. setting.name + if fields["pick_" .. setting.name] then + local is_file = setting.type ~= "path" + core.show_path_select_dialog(dialog_name, + is_file and fgettext_ne("Select file") or fgettext_ne("Select directory"), is_file) + return true + end + if fields[dialog_name .. "_accepted"] then + local value = fields[dialog_name .. "_accepted"] + if value ~= nil then + core.settings:set(setting.name, value) + end + return true + end + if fields["set_" .. setting.name] or fields.key_enter_field == setting.name then + local value = fields[setting.name] + if value ~= nil then + core.settings:set(setting.name, value) + end + return true + end + end, + } +end + + +function make.v3f(setting) + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local value = vector.from_string(core.settings:get(setting.name) or setting.default) + self.changed = value ~= vector.from_string(setting.default) + + -- Allocate space for "Set" button + avail_w = avail_w - 1 + + local fs = "label[0,0.1;" .. get_label(setting) .. "]" + + local field_width = (avail_w - 3*0.25) / 3 + + fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format( + 0, field_width, setting.name .. "_x", "X", value.x) + fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format( + field_width + 0.25, field_width, setting.name .. "_y", "Y", value.y) + fs = fs .. ("field[%f,0.6;%f,0.8;%s;%s;%s]"):format( + 2 * (field_width + 0.25), field_width, setting.name .. "_z", "Z", value.z) + + fs = fs .. ("button[%f,0.6;1,0.8;%s;%s]"):format(avail_w, "set_" .. setting.name, fgettext("Set")) + + return fs, 1.4 + end, + + on_submit = function(self, fields) + if fields["set_" .. setting.name] or + fields.key_enter_field == setting.name .. "_x" or + fields.key_enter_field == setting.name .. "_y" or + fields.key_enter_field == setting.name .. "_z" then + local x = tonumber(fields[setting.name .. "_x"]) + local y = tonumber(fields[setting.name .. "_y"]) + local z = tonumber(fields[setting.name .. "_z"]) + if is_valid_number(x) and is_valid_number(y) and is_valid_number(z) then + core.settings:set(setting.name, vector.new(x, y, z):to_string()) + else + core.log("error", "Invalid vector: " .. dump({x, y, z})) + end + return true + end + end, + } +end + + +function make.flags(setting) + local checkboxes = {} + + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local fs = { + "label[0,0.1;" .. get_label(setting) .. "]", + } + + local value = core.settings:get(setting.name) or setting.default + self.changed = value:gsub(" ", "") ~= setting.default:gsub(" ", "") + + checkboxes = {} + for _, name in ipairs(value:split(",")) do + name = name:trim() + if name:sub(1, 2) == "no" then + checkboxes[name:sub(3)] = false + elseif name ~= "" then + checkboxes[name] = true + end + end + + local columns = math.max(math.floor(avail_w / 2.5), 1) + local column_width = avail_w / columns + local x = 0 + local y = 0.55 + + for _, possible in ipairs(setting.possible) do + if possible:sub(1, 2) ~= "no" then + if x >= avail_w then + x = 0 + y = y + 0.5 + end + + local is_checked = checkboxes[possible] + fs[#fs + 1] = ("checkbox[%f,%f;%s;%s;%s]"):format( + x, y, setting.name .. "_" .. possible, + core.formspec_escape(possible), tostring(is_checked)) + x = x + column_width + end + end + + return table.concat(fs, ""), y + 0.25 + end, + + on_submit = function(self, fields) + local changed = false + for name, _ in pairs(checkboxes) do + local value = fields[setting.name .. "_" .. name] + if value ~= nil then + checkboxes[name] = core.is_yes(value) + changed = true + end + end + + if changed then + local values = {} + for _, name in ipairs(setting.possible) do + if name:sub(1, 2) ~= "no" then + if checkboxes[name] then + table.insert(values, name) + else + table.insert(values, "no" .. name) + end + end + end + + core.settings:set(setting.name, table.concat(values, ",")) + end + return changed + end + } +end + + +local function noise_params(setting) + return { + info_text = setting.comment, + setting = setting, + + get_formspec = function(self, avail_w) + local fs = "label[0,0.4;" .. get_label(setting) .. "]" .. + ("button[%f,0;2.5,0.8;%s;%s]"):format(avail_w - 2.5, "edit_" .. setting.name, fgettext("Edit")) + return fs, 0.8 + end, + + on_submit = function(self, fields, tabview) + if fields["edit_" .. setting.name] then + local dlg = create_change_mapgen_flags_dlg(setting) + dlg:set_parent(tabview) + tabview:hide() + dlg:show() + + return true + end + end, + } +end + + +make.filepath = make.path +make.noise_params_2d = noise_params +make.noise_params_3d = noise_params + +return make diff --git a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua b/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua new file mode 100644 index 000000000..3b5ef26f4 --- /dev/null +++ b/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua @@ -0,0 +1,249 @@ +--Minetest +--Copyright (C) 2015 PilzAdam +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local checkboxes = {} + +local function flags_to_table(flags) + return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split +end + +local function get_current_np_group(setting) + local value = core.settings:get_np_group(setting.name) + if value == nil then + return setting.values + end + local p = "%g" + return { + p:format(value.offset), + p:format(value.scale), + p:format(value.spread.x), + p:format(value.spread.y), + p:format(value.spread.z), + p:format(value.seed), + p:format(value.octaves), + p:format(value.persistence), + p:format(value.lacunarity), + value.flags + } +end + + +local function get_formspec(dialogdata) + local setting = dialogdata.setting + + -- Final formspec will be created at the end of this function + -- Default values below, may be changed depending on setting type + local width = 10 + local height = 3.5 + local description_height = 3 + + local t = get_current_np_group(setting) + local dimension = 3 + if setting.type == "noise_params_2d" then + dimension = 2 + end + + -- More space for 3x3 fields + description_height = description_height - 1.5 + height = height - 1.5 + + local fields = {} + local function add_field(x, name, label, value) + fields[#fields + 1] = ("field[%f,%f;3.3,1;%s;%s;%s]"):format( + x, height, name, label, core.formspec_escape(value or "") + ) + end + -- First row + height = height + 0.3 + add_field(0.3, "te_offset", fgettext("Offset"), t[1]) + add_field(3.6, "te_scale", fgettext("Scale"), t[2]) + add_field(6.9, "te_seed", fgettext("Seed"), t[6]) + height = height + 1.1 + + -- Second row + add_field(0.3, "te_spreadx", fgettext("X spread"), t[3]) + if dimension == 3 then + add_field(3.6, "te_spready", fgettext("Y spread"), t[4]) + else + fields[#fields + 1] = "label[4," .. height - 0.2 .. ";" .. + fgettext("2D Noise") .. "]" + end + add_field(6.9, "te_spreadz", fgettext("Z spread"), t[5]) + height = height + 1.1 + + -- Third row + add_field(0.3, "te_octaves", fgettext("Octaves"), t[7]) + add_field(3.6, "te_persist", fgettext("Persistence"), t[8]) + add_field(6.9, "te_lacun", fgettext("Lacunarity"), t[9]) + height = height + 1.1 + + + local enabled_flags = flags_to_table(t[10]) + local flags = {} + for _, name in ipairs(enabled_flags) do + -- Index by name, to avoid iterating over all enabled_flags for every possible flag. + flags[name] = true + end + for _, name in ipairs(setting.flags) do + local checkbox_name = "cb_" .. name + local is_enabled = flags[name] == true -- to get false if nil + checkboxes[checkbox_name] = is_enabled + end + + local formspec = table.concat(fields) + .. "checkbox[0.5," .. height - 0.6 .. ";cb_defaults;" + --[[~ "defaults" is a noise parameter flag. + It describes the default processing options + for noise settings in main menu -> "All Settings". ]] + .. fgettext("defaults") .. ";" -- defaults + .. tostring(flags["defaults"] == true) .. "]" -- to get false if nil + .. "checkbox[5," .. height - 0.6 .. ";cb_eased;" + --[[~ "eased" is a noise parameter flag. + It is used to make the map smoother and + can be enabled in noise settings in + main menu -> "All Settings". ]] + .. fgettext("eased") .. ";" -- eased + .. tostring(flags["eased"] == true) .. "]" + .. "checkbox[5," .. height - 0.15 .. ";cb_absvalue;" + --[[~ "absvalue" is a noise parameter flag. + It is short for "absolute value". + It can be enabled in noise settings in + main menu -> "All Settings". ]] + .. fgettext("absvalue") .. ";" -- absvalue + .. tostring(flags["absvalue"] == true) .. "]" + + height = height + 1 + + -- Box good, textarea bad. Calculate textarea size from box. + local function create_textfield(size, label, text, bg_color) + local textarea = { + x = size.x + 0.3, + y = size.y, + w = size.w + 0.25, + h = size.h * 1.16 + 0.12 + } + return ("box[%f,%f;%f,%f;%s]textarea[%f,%f;%f,%f;;%s;%s]"):format( + size.x, size.y, size.w, size.h, bg_color or "#000", + textarea.x, textarea.y, textarea.w, textarea.h, + core.formspec_escape(label), core.formspec_escape(text) + ) + + end + + -- When there's an error: Shrink description textarea and add error below + if dialogdata.error_message then + local error_box = { + x = 0, + y = description_height - 0.4, + w = width - 0.25, + h = 0.5 + } + formspec = formspec .. + create_textfield(error_box, "", dialogdata.error_message, "#600") + description_height = description_height - 0.75 + end + + -- Get description field + local description_box = { + x = 0, + y = 0.2, + w = width - 0.25, + h = description_height + } + + local setting_name = setting.name + if setting.readable_name then + setting_name = fgettext_ne(setting.readable_name) .. + " (" .. setting.name .. ")" + end + + local comment_text + if setting.comment == "" then + comment_text = fgettext_ne("(No description of setting given)") + else + comment_text = fgettext_ne(setting.comment) + end + + return ( + "size[" .. width .. "," .. height + 0.25 .. ",true]" .. + create_textfield(description_box, setting_name, comment_text) .. + formspec .. + "button[" .. width / 2 - 2.5 .. "," .. height - 0.4 .. ";2.5,1;btn_done;" .. + fgettext("Save") .. "]" .. + "button[" .. width / 2 .. "," .. height - 0.4 .. ";2.5,1;btn_cancel;" .. + fgettext("Cancel") .. "]" + ) +end + + +local function buttonhandler(this, fields) + local setting = this.data.setting + if fields["btn_done"] or fields["key_enter"] then + local np_flags = {} + for _, name in ipairs(setting.flags) do + if checkboxes["cb_" .. name] then + table.insert(np_flags, name) + end + end + + checkboxes = {} + + if setting.type == "noise_params_2d" then + fields["te_spready"] = fields["te_spreadz"] + end + local new_value = { + offset = fields["te_offset"], + scale = fields["te_scale"], + spread = { + x = fields["te_spreadx"], + y = fields["te_spready"], + z = fields["te_spreadz"] + }, + seed = fields["te_seed"], + octaves = fields["te_octaves"], + persistence = fields["te_persist"], + lacunarity = fields["te_lacun"], + flags = table.concat(np_flags, ", ") + } + core.settings:set_np_group(setting.name, new_value) + + core.settings:write() + this:delete() + return true + end + + if fields["btn_cancel"] then + this:delete() + return true + end + + return false +end + + +function create_change_mapgen_flags_dlg(setting) + assert(type(setting) == "table") + + local retval = dialog_create("dlg_change_mapgen_flags", + get_formspec, + buttonhandler, + nil) + + retval.data.setting = setting + return retval +end diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua new file mode 100644 index 000000000..fdf324452 --- /dev/null +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -0,0 +1,520 @@ +--Minetest +--Copyright (C) 2022 rubenwardy +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM .. + "settings" .. DIR_DELIM .. "components.lua") + +local quick_shader_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. + "settings" .. DIR_DELIM .. "shader_component.lua") + + +local full_settings = settingtypes.parse_config_file(false, true) +local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") +local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") + +local gettext = fgettext_ne +local all_pages = {} +local page_by_id = {} +local filtered_pages = all_pages +local filtered_page_by_id = page_by_id + +local function add_page(page) + assert(type(page.id) == "string") + assert(type(page.title) == "string") + assert(page.section == nil or type(page.section) == "string") + assert(type(page.content) == "table") + + assert(not page_by_id[page.id], "Page " .. page.id .. " already registered") + + all_pages[#all_pages + 1] = page + page_by_id[page.id] = page + return page +end + + +local change_keys = { + query_text = "Change keys", + get_formspec = function(self, avail_w) + local btn_w = math.min(avail_w, 3) + return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Change keys")), 0.8 + end, + on_submit = function(self, fields) + if fields.btn_change_keys then + core.show_keys_menu() + end + end, +} + + +add_page({ + id = "most_used", + title = gettext("Most Used"), + content = { + change_keys, + "language", + "fullscreen", + PLATFORM ~= "Android" and "autosave_screensize" or false, + "touchscreen_threshold", + { heading = gettext("Scaling") }, + "gui_scaling", + "hud_scaling", + { heading = gettext("Graphics / Performance") }, + "smooth_lighting", + "enable_particles", + "enable_3d_clouds", + "opaque_water", + "connected_glass", + "node_highlighting", + "leaves_style", + { heading = gettext("Shaders") }, + quick_shader_component, + }, +}) + +add_page({ + id = "accessibility", + title = gettext("Accessibility"), + content = { + "font_size", + "chat_font_size", + "gui_scaling", + "hud_scaling", + "show_nametag_backgrounds", + { heading = gettext("Chat") }, + "console_height", + "console_alpha", + "console_color", + { heading = gettext("Controls") }, + "autojump", + "safe_dig_and_place", + { heading = gettext("Movement") }, + "arm_inertia", + "view_bobbing_amount", + "fall_bobbing_amount", + }, +}) + + +local tabsize = { + width = 15.5, + height= 12, +} + + +local function load_settingtypes() + local page = nil + local section = nil + local function ensure_page_started() + if not page then + page = add_page({ + id = (section or "general"):lower():gsub(" ", "_"), + title = section or gettext("General"), + section = section, + content = {}, + }) + end + end + + for _, entry in ipairs(full_settings) do + if entry.type == "category" then + if entry.level == 0 then + section = entry.name + page = nil + elseif entry.level == 1 then + page = { + id = ((section and section .. "_" or "") .. entry.name):lower():gsub(" ", "_"), + title = entry.readable_name or entry.name, + section = section, + content = {}, + } + + if page.title:sub(1, 5) ~= "Hide:" then + page = add_page(page) + end + elseif entry.level == 2 then + ensure_page_started() + page.content[#page.content + 1] = { + heading = gettext(entry.readable_name or entry.name), + } + end + else + ensure_page_started() + page.content[#page.content + 1] = entry.name + end + end +end +load_settingtypes() + +table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) + + +local function get_setting_info(name) + for _, entry in ipairs(full_settings) do + if entry.type ~= "category" and entry.name == name then + return entry + end + end + + return nil +end + + +-- See if setting matches keywords +local function get_setting_match_weight(entry, query_keywords) + local setting_score = 0 + for _, keyword in ipairs(query_keywords) do + if string.find(entry.name:lower(), keyword, 1, true) then + setting_score = setting_score + 1 + end + + if entry.readable_name and + string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then + setting_score = setting_score + 1 + end + + if entry.comment and + string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then + setting_score = setting_score + 1 + end + end + + return setting_score +end + + +local function filter_page_content(page, query_keywords) + if #query_keywords == 0 then + return page.content + end + + local retval = {} + local i = 1 + local max_weight = 0 + for _, content in ipairs(page.content) do + if type(content) == "string" then + local setting = get_setting_info(content) + assert(setting, "Unknown setting: " .. content) + + local weight = get_setting_match_weight(setting, query_keywords) + if weight > 0 then + max_weight = math.max(max_weight, weight) + retval[i] = content + i = i + 1 + end + elseif type(content) == "table" and content.query_text then + for _, keyword in ipairs(query_keywords) do + if string.find(fgettext(content.query_text), keyword, 1, true) then + max_weight = math.max(max_weight, 1) + retval[i] = content + i = i + 1 + break + end + end + end + end + return retval, max_weight +end + + +local function update_filtered_pages(query) + filtered_pages = {} + filtered_page_by_id = {} + + if query == "" or query == nil then + filtered_pages = all_pages + filtered_page_by_id = page_by_id + return filtered_pages[1].id + end + + local query_keywords = {} + for word in query:lower():gmatch("%S+") do + table.insert(query_keywords, word) + end + + local best_page = nil + local best_page_weight = -1 + + for _, page in ipairs(all_pages) do + local content, page_weight = filter_page_content(page, query_keywords) + if #content > 0 then + local new_page = table.copy(page) + new_page.content = content + + filtered_pages[#filtered_pages + 1] = new_page + filtered_page_by_id[new_page.id] = new_page + + if page_weight > best_page_weight then + best_page = new_page + best_page_weight = page_weight + end + end + end + + return best_page and best_page.id or nil +end + + +local function build_page_components(page) + local retval = {} + local j = 1 + for i, content in ipairs(page.content) do + if content == false then + -- false is used to disable components conditionally (ie: Android specific) + j = j - 1 + elseif type(content) == "string" then + local setting = get_setting_info(content) + assert(setting, "Unknown setting: " .. content) + + local component_func = component_funcs[setting.type] + assert(component_func, "Unknown setting type: " .. setting.type) + retval[j] = component_func(setting) + elseif content.get_formspec then + retval[j] = content + elseif content.heading then + retval[j] = component_funcs.heading(content.heading) + else + error("Unknown content in page: " .. dump(content)) + end + j = j + 1 + end + return retval +end + + +--- Creates a scrollbaroptions for a scroll_container +-- +-- @param visible_l the length of the scroll_container and scrollbar +-- @param total_l length of the scrollable area +-- @param scroll_factor as passed to scroll_container +local function make_scrollbaroptions_for_scroll_container(visible_l, total_l, scroll_factor) + assert(total_l >= visible_l) + local max = total_l - visible_l + local thumb_size = (visible_l / total_l) * max + return ("scrollbaroptions[min=0;max=%f;thumbsize=%f]"):format(max / scroll_factor, thumb_size / scroll_factor) +end + + +local formspec_show_hack = false + + +local function get_formspec(dialogdata) + local page_id = dialogdata.page_id or "most_used" + local page = filtered_page_by_id[page_id] + + local scrollbar_w = 0.4 + if PLATFORM == "Android" then + scrollbar_w = 0.6 + end + + local left_pane_width = 4.25 + local search_width = left_pane_width + scrollbar_w - (0.75 * 2) + + local show_technical_names = core.settings:get_bool("show_technical_names") + + formspec_show_hack = not formspec_show_hack + + local fs = { + "formspec_version[6]", + "size[", tostring(tabsize.width), ",", tostring(tabsize.height + 1), "]", + "bgcolor[#0000]", + + -- HACK: this is needed to allow resubmitting the same formspec + formspec_show_hack and " " or "", + + "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]", + + "button[0,", tostring(tabsize.height + 0.2), ";3,0.8;back;", fgettext("Back"), "]", + + ("box[%f,%f;5,0.8;#0000008C]"):format(tabsize.width - 5, tabsize.height + 0.2), + "checkbox[", tostring(tabsize.width - 4.75), ",", tostring(tabsize.height + 0.6), ";show_technical_names;", + fgettext("Show technical names"), ";", tostring(show_technical_names), "]", + + "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;", + core.formspec_escape(dialogdata.query or ""), "]", + "container[", tostring(search_width + 0.25), ", 0.25]", + "image_button[0,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]", + "image_button[0.75,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";search_clear;]", + "tooltip[search;", fgettext("Search"), "]", + "tooltip[search_clear;", fgettext("Clear"), "]", + "container_end[]", + "scroll_container[0.25,1.25;", tostring(left_pane_width), ",", + tostring(tabsize.height - 1.5), ";leftscroll;vertical;0.1]", + "style_type[button;border=false;bgcolor=#3333]", + "style_type[button:hover;border=false;bgcolor=#6663]", + } + + local y = 0 + local last_section = nil + for _, other_page in ipairs(filtered_pages) do + if other_page.section ~= last_section then + fs[#fs + 1] = ("label[0.1,%f;%s]"):format(y + 0.41, core.colorize("#ff0", fgettext(other_page.section))) + last_section = other_page.section + y = y + 0.82 + end + fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format( + y, left_pane_width, other_page.id == page_id and "#467832FF" or "#3339") + fs[#fs + 1] = ("button[0,%f;%f,0.8;page_%s;%s]") + :format(y, left_pane_width, other_page.id, fgettext(other_page.title)) + y = y + 0.82 + end + + if #filtered_pages == 0 then + fs[#fs + 1] = "label[0.1,0.41;" + fs[#fs + 1] = fgettext("No results") + fs[#fs + 1] = "]" + end + + fs[#fs + 1] = "scroll_container_end[]" + + if y >= tabsize.height - 1.25 then + fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height - 1.5, y, 0.1) + fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format( + left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0) + end + + fs[#fs + 1] = "style_type[button;border=;bgcolor=]" + + if not dialogdata.components then + dialogdata.components = page and build_page_components(page) or {} + end + + local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25 + fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1]"):format( + tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height) + + y = 0.25 + for i, comp in ipairs(dialogdata.components) do + fs[#fs + 1] = ("container[0,%f]"):format(y) + + local avail_w = right_pane_width - 0.25 + if not comp.full_width then + avail_w = avail_w - 1.4 + end + if comp.max_w then + avail_w = math.min(avail_w, comp.max_w) + end + + local comp_fs, used_h = comp:get_formspec(avail_w) + fs[#fs + 1] = comp_fs + + fs[#fs + 1] = "style_type[image_button;border=false;padding=]" + + local show_reset = comp.changed and comp.setting and comp.setting.default + local show_info = comp.info_text and comp.info_text ~= "" + if show_reset or show_info then + -- ensure there's enough space for reset/info + used_h = math.max(used_h, 0.5) + end + local info_reset_y = used_h / 2 - 0.25 + + if show_reset then + fs[#fs + 1] = ("image_button[%f,%f;0.5,0.5;%s;%s;]"):format( + right_pane_width - 1.4, info_reset_y, reset_icon_path, "reset_" .. i) + fs[#fs + 1] = ("tooltip[%s;%s]"):format( + "reset_" .. i, fgettext("Reset setting to default ($1)", tostring(comp.setting.default))) + end + + if show_info then + local info_x = right_pane_width - 0.75 + fs[#fs + 1] = ("image[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, info_icon_path) + fs[#fs + 1] = ("tooltip[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, fgettext(comp.info_text)) + end + + fs[#fs + 1] = "style_type[image_button;border=;padding=]" + + fs[#fs + 1] = "container_end[]" + + if used_h > 0 then + y = y + used_h + 0.25 + end + end + + fs[#fs + 1] = "scroll_container_end[]" + + if y >= tabsize.height then + fs[#fs + 1] = make_scrollbaroptions_for_scroll_container(tabsize.height, y + 0.375, 0.1) + fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format( + tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0) + end + + return table.concat(fs, "") +end + + +local function buttonhandler(this, fields) + local dialogdata = this.data + dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll + dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll + dialogdata.query = fields.search_query + + if fields.back then + this:delete() + return true + end + + if fields.show_technical_names ~= nil then + local value = core.is_yes(fields.show_technical_names) + core.settings:set_bool("show_technical_names", value) + return true + end + + if fields.search or fields.key_enter_field == "search_query" then + dialogdata.components = nil + dialogdata.leftscroll = 0 + dialogdata.rightscroll = 0 + + dialogdata.page_id = update_filtered_pages(dialogdata.query) + + return true + end + if fields.search_clear then + dialogdata.query = "" + dialogdata.components = nil + dialogdata.leftscroll = 0 + dialogdata.rightscroll = 0 + + dialogdata.page_id = update_filtered_pages("") + return true + end + + for _, page in ipairs(all_pages) do + if fields["page_" .. page.id] then + dialogdata.page_id = page.id + dialogdata.components = nil + dialogdata.rightscroll = 0 + return true + end + end + + for i, comp in ipairs(dialogdata.components) do + if comp.on_submit and comp:on_submit(fields, this) then + return true + end + if comp.setting and fields["reset_" .. i] then + core.settings:set(comp.setting.name, comp.setting.default) + return true + end + end + + return false +end + + +function create_settings_dlg() + return dialog_create("dlg_settings", get_formspec, buttonhandler, nil) +end diff --git a/builtin/mainmenu/generate_from_settingtypes.lua b/builtin/mainmenu/settings/generate_from_settingtypes.lua similarity index 100% rename from builtin/mainmenu/generate_from_settingtypes.lua rename to builtin/mainmenu/settings/generate_from_settingtypes.lua diff --git a/builtin/mainmenu/settings/init.lua b/builtin/mainmenu/settings/init.lua new file mode 100644 index 000000000..09ea8b9c1 --- /dev/null +++ b/builtin/mainmenu/settings/init.lua @@ -0,0 +1,28 @@ +--Minetest +--Copyright (C) 2022 rubenwardy +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +local path = core.get_mainmenu_path() .. DIR_DELIM .. "settings" + +dofile(path .. DIR_DELIM .. "settingtypes.lua") +dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") +dofile(path .. DIR_DELIM .. "dlg_settings.lua") + +-- Uncomment to generate 'minetest.conf.example' and 'settings_translation_file.cpp'. +-- For RUN_IN_PLACE the generated files may appear in the 'bin' folder. +-- See comment and alternative line at the end of 'generate_from_settingtypes.lua'. + +--assert(loadfile(path .. DIR_DELIM .. "generate_from_settingtypes.lua"))(parse_config_file(true, false)) diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/mainmenu/settings/settingtypes.lua new file mode 100644 index 000000000..8660e64b7 --- /dev/null +++ b/builtin/mainmenu/settings/settingtypes.lua @@ -0,0 +1,456 @@ +--Minetest +--Copyright (C) 2015 PilzAdam +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +settingtypes = {} + +-- A Setting type is a table with the following keys: +-- +-- name: Identifier +-- readable_name: Readable title +-- type: Category +-- +-- name = mod.name, +-- readable_name = mod.title, +-- level = 1, +-- type = "category", "int", "string", "" +-- } + + +local FILENAME = "settingtypes.txt" + +local CHAR_CLASSES = { + SPACE = "[%s]", + VARIABLE = "[%w_%-%.]", + INTEGER = "[+-]?[%d]", + FLOAT = "[+-]?[%d%.]", + FLAGS = "[%w_%-%.,]", +} + +local function flags_to_table(flags) + return flags:gsub("%s+", ""):split(",", true) -- Remove all spaces and split +end + +-- returns error message, or nil +local function parse_setting_line(settings, line, read_all, base_level, allow_secure) + + -- strip carriage returns (CR, /r) + line = line:gsub("\r", "") + + -- comment + local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") + if comment then + if settings.current_comment == "" then + settings.current_comment = comment + else + settings.current_comment = settings.current_comment .. "\n" .. comment + end + return + end + + -- clear current_comment so only comments directly above a setting are bound to it + -- but keep a local reference to it for variables in the current line + local current_comment = settings.current_comment + settings.current_comment = "" + + -- empty lines + if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then + return + end + + -- category + local stars, category = line:match("^%[([%*]*)([^%]]+)%]$") + if category then + table.insert(settings, { + name = category, + level = stars:len() + base_level, + type = "category", + }) + return + end + + -- settings + local first_part, name, readable_name, setting_type = line:match("^" + -- this first capture group matches the whole first part, + -- so we can later strip it from the rest of the line + .. "(" + .. "([" .. CHAR_CLASSES.VARIABLE .. "+)" -- variable name + .. CHAR_CLASSES.SPACE .. "*" + .. "%(([^%)]*)%)" -- readable name + .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.VARIABLE .. "+)" -- type + .. CHAR_CLASSES.SPACE .. "*" + .. ")") + + if not first_part then + return "Invalid line" + end + + if name:match("secure%.[.]*") and not allow_secure then + return "Tried to add \"secure.\" setting" + end + + if readable_name == "" then + readable_name = nil + end + local remaining_line = line:sub(first_part:len() + 1) + + if setting_type == "int" then + local default, min, max = remaining_line:match("^" + -- first int is required, the last 2 are optional + .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.INTEGER .. "*)" + .. "$") + + if not default or not tonumber(default) then + return "Invalid integer setting" + end + + min = tonumber(min) + max = tonumber(max) + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "int", + default = default, + min = min, + max = max, + comment = current_comment, + }) + return + end + + if setting_type == "string" + or setting_type == "key" or setting_type == "v3f" then + local default = remaining_line:match("^(.*)$") + + if not default then + return "Invalid string setting" + end + if setting_type == "key" and not read_all then + -- ignore key type if read_all is false + return + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = setting_type, + default = default, + comment = current_comment, + }) + return + end + + if setting_type == "noise_params_2d" + or setting_type == "noise_params_3d" then + local default = remaining_line:match("^(.*)$") + + if not default then + return "Invalid string setting" + end + + local values = {} + local ti = 1 + local index = 1 + for match in default:gmatch("[+-]?[%d.-e]+") do -- All numeric characters + index = default:find("[+-]?[%d.-e]+", index) + match:len() + table.insert(values, match) + ti = ti + 1 + if ti > 9 then + break + end + end + index = default:find("[^, ]", index) + local flags = "" + if index then + flags = default:sub(index) + default = default:sub(1, index - 3) -- Make sure no flags in single-line format + end + table.insert(values, flags) + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = setting_type, + default = default, + default_table = { + offset = values[1], + scale = values[2], + spread = { + x = values[3], + y = values[4], + z = values[5] + }, + seed = values[6], + octaves = values[7], + persistence = values[8], + lacunarity = values[9], + flags = values[10] + }, + values = values, + comment = current_comment, + noise_params = true, + flags = flags_to_table("defaults,eased,absvalue") + }) + return + end + + if setting_type == "bool" then + if remaining_line ~= "false" and remaining_line ~= "true" then + return "Invalid boolean setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "bool", + default = remaining_line, + comment = current_comment, + }) + return + end + + if setting_type == "float" then + local default, min, max = remaining_line:match("^" + -- first float is required, the last 2 are optional + .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLOAT .. "*)" + .."$") + + if not default or not tonumber(default) then + return "Invalid float setting" + end + + min = tonumber(min) + max = tonumber(max) + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "float", + default = default, + min = min, + max = max, + comment = current_comment, + }) + return + end + + if setting_type == "enum" then + local default, values = remaining_line:match("^" + -- first value (default) may be empty (i.e. is optional) + .. "(" .. CHAR_CLASSES.VARIABLE .. "*)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLAGS .. "+)" + .. "$") + + if not default or values == "" then + return "Invalid enum setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "enum", + default = default, + values = values:split(",", true), + comment = current_comment, + }) + return + end + + if setting_type == "path" or setting_type == "filepath" then + local default = remaining_line:match("^(.*)$") + + if not default then + return "Invalid path setting" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = setting_type, + default = default, + comment = current_comment, + }) + return + end + + if setting_type == "flags" then + local default, possible = remaining_line:match("^" + -- first value (default) may be empty (i.e. is optional) + -- this is implemented by making the last value optional, and + -- swapping them around if it turns out empty. + .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. "*" + .. "(" .. CHAR_CLASSES.FLAGS .. "*)" + .. "$") + + if not default or not possible then + return "Invalid flags setting" + end + + if possible == "" then + possible = default + default = "" + end + + table.insert(settings, { + name = name, + readable_name = readable_name, + type = "flags", + default = default, + possible = flags_to_table(possible), + comment = current_comment, + }) + return + end + + return "Invalid setting type \"" .. setting_type .. "\"" +end + +local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure) + -- store this helper variable in the table so it's easier to pass to parse_setting_line() + result.current_comment = "" + + local line = file:read("*line") + while line do + local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure) + if error_msg then + core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"") + end + line = file:read("*line") + end + + result.current_comment = nil +end + + +--- Returns table of setting types +-- +-- @param read_all Whether to ignore certain setting types for GUI or not +-- @parse_mods Whether to parse settingtypes.txt in mods and games +function settingtypes.parse_config_file(read_all, parse_mods) + local settings = {} + + do + local builtin_path = core.get_builtin_path() .. FILENAME + local file = io.open(builtin_path, "r") + if not file then + core.log("error", "Can't load " .. FILENAME) + return settings + end + + parse_single_file(file, builtin_path, read_all, settings, 0, true) + + file:close() + end + + if parse_mods then + -- Parse games + local games_category_initialized = false + for _, game in ipairs(pkgmgr.games) do + local path = game.path .. DIR_DELIM .. FILENAME + local file = io.open(path, "r") + if file then + if not games_category_initialized then + fgettext_ne("Content: Games") -- not used, but needed for xgettext + table.insert(settings, { + name = "Content: Games", + level = 0, + type = "category", + }) + games_category_initialized = true + end + + table.insert(settings, { + name = game.name, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + end + + -- Parse mods + local mods_category_initialized = false + local mods = {} + pkgmgr.get_mods(core.get_modpath(), "mods", mods) + table.sort(mods, function(a, b) return a.name < b.name end) + + for _, mod in ipairs(mods) do + local path = mod.path .. DIR_DELIM .. FILENAME + local file = io.open(path, "r") + if file then + if not mods_category_initialized then + fgettext_ne("Content: Mods") -- not used, but needed for xgettext + table.insert(settings, { + name = "Content: Mods", + level = 0, + type = "category", + }) + mods_category_initialized = true + end + + table.insert(settings, { + name = mod.name, + readable_name = mod.title, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + end + + -- Parse client mods + local clientmods_category_initialized = false + local clientmods = {} + pkgmgr.get_mods(core.get_clientmodpath(), "clientmods", clientmods) + for _, mod in ipairs(clientmods) do + local path = mod.path .. DIR_DELIM .. FILENAME + local file = io.open(path, "r") + if file then + if not clientmods_category_initialized then + fgettext_ne("Client Mods") -- not used, but needed for xgettext + table.insert(settings, { + name = "Client Mods", + level = 0, + type = "category", + }) + clientmods_category_initialized = true + end + + table.insert(settings, { + name = mod.name, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + end + end + + return settings +end diff --git a/builtin/mainmenu/settings/shader_component.lua b/builtin/mainmenu/settings/shader_component.lua new file mode 100644 index 000000000..a1a48b03b --- /dev/null +++ b/builtin/mainmenu/settings/shader_component.lua @@ -0,0 +1,159 @@ +--Minetest +--Copyright (C) 2022 rubenwardy +-- +--This program is free software; you can redistribute it and/or modify +--it under the terms of the GNU Lesser General Public License as published by +--the Free Software Foundation; either version 2.1 of the License, or +--(at your option) any later version. +-- +--This program is distributed in the hope that it will be useful, +--but WITHOUT ANY WARRANTY; without even the implied warranty of +--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +--GNU Lesser General Public License for more details. +-- +--You should have received a copy of the GNU Lesser General Public License along +--with this program; if not, write to the Free Software Foundation, Inc., +--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +local shadow_levels_labels = { + fgettext("Disabled"), + fgettext("Very Low"), + fgettext("Low"), + fgettext("Medium"), + fgettext("High"), + fgettext("Very High") +} + + +local shadow_levels_options = { + table.concat(shadow_levels_labels, ","), + { "0", "1", "2", "3", "4", "5" } +} + + +local function get_shadow_mapping_id() + local shadow_setting = core.settings:get("shadow_levels") + for i = 1, #shadow_levels_options[2] do + if shadow_setting == shadow_levels_options[2][i] then + return i + end + end + return 1 +end + + +return { + query_text = "Shaders", + get_formspec = function(self, avail_w) + local fs = "" + + local video_driver = core.get_active_driver() + local shaders_enabled = core.settings:get_bool("enable_shaders") + if video_driver == "opengl" then + fs = fs .. + "checkbox[0,0.25;cb_shaders;" .. fgettext("Shaders") .. ";" + .. tostring(shaders_enabled) .. "]" + elseif video_driver == "ogles2" then + fs = fs .. + "checkbox[0,0.25;cb_shaders;" .. fgettext("Shaders (experimental)") .. ";" + .. tostring(shaders_enabled) .. "]" + else + core.settings:set_bool("enable_shaders", false) + shaders_enabled = false + fs = fs .. + "label[0.13,0.25;" .. core.colorize("#888888", + fgettext("Shaders (unavailable)")) .. "]" + end + + if shaders_enabled then + fs = fs .. + "container[0,0.75]" .. + "checkbox[0,0;cb_tonemapping;" .. fgettext("Tone mapping") .. ";" + .. tostring(core.settings:get_bool("tone_mapping")) .. "]" .. + "checkbox[0,0.5;cb_waving_water;" .. fgettext("Waving liquids") .. ";" + .. tostring(core.settings:get_bool("enable_waving_water")) .. "]" .. + "checkbox[0,1;cb_waving_leaves;" .. fgettext("Waving leaves") .. ";" + .. tostring(core.settings:get_bool("enable_waving_leaves")) .. "]" .. + "checkbox[0,1.5;cb_waving_plants;" .. fgettext("Waving plants") .. ";" + .. tostring(core.settings:get_bool("enable_waving_plants")) .. "]" + + if video_driver == "opengl" then + fs = fs .. + "label[0,2.2;" .. fgettext("Dynamic shadows") .. "]" .. + "dropdown[0,2.4;3,0.8;dd_shadows;" .. shadow_levels_options[1] .. ";" .. get_shadow_mapping_id() .. "]" .. + "label[0,3.5;" .. core.colorize("#bbb", fgettext("(The game will need to enable shadows as well)")) .. "]" + else + fs = fs .. + "label[0,2.2;" .. core.colorize("#888888", fgettext("Dynamic shadows")) .. "]" + end + + fs = fs .. "container_end[]" + else + fs = fs .. + "container[0.35,0.75]" .. + "label[0,0;" .. core.colorize("#888888", + fgettext("Tone mapping")) .. "]" .. + "label[0,0.5;" .. core.colorize("#888888", + fgettext("Waving liquids")) .. "]" .. + "label[0,1;" .. core.colorize("#888888", + fgettext("Waving leaves")) .. "]" .. + "label[0,1.5;" .. core.colorize("#888888", + fgettext("Waving plants")) .. "]".. + "label[0,2;" .. core.colorize("#888888", + fgettext("Dynamic shadows")) .. "]" .. + "container_end[]" + end + return fs, 4.5 + end, + + on_submit = function(self, fields) + if fields.cb_shaders then + core.settings:set("enable_shaders", fields.cb_shaders) + return true + end + if fields.cb_tonemapping then + core.settings:set("tone_mapping", fields.cb_tonemapping) + return true + end + if fields.cb_waving_water then + core.settings:set("enable_waving_water", fields.cb_waving_water) + return true + end + if fields.cb_waving_leaves then + core.settings:set("enable_waving_leaves", fields.cb_waving_leaves) + return true + end + if fields.cb_waving_plants then + core.settings:set("enable_waving_plants", fields.cb_waving_plants) + return true + end + + for i = 1, #shadow_levels_labels do + if fields.dd_shadows == shadow_levels_labels[i] then + core.settings:set("shadow_levels", shadow_levels_options[2][i]) + end + end + + if fields.dd_shadows == shadow_levels_labels[1] then + core.settings:set("enable_dynamic_shadows", "false") + else + local shadow_presets = { + [2] = { 62, 512, "true", 0, "false" }, + [3] = { 93, 1024, "true", 0, "false" }, + [4] = { 140, 2048, "true", 1, "false" }, + [5] = { 210, 4096, "true", 2, "true" }, + [6] = { 300, 8192, "true", 2, "true" }, + } + local s = shadow_presets[table.indexof(shadow_levels_labels, fields.dd_shadows)] + if s then + core.settings:set("enable_dynamic_shadows", "true") + core.settings:set("shadow_map_max_distance", s[1]) + core.settings:set("shadow_map_texture_size", s[2]) + core.settings:set("shadow_map_texture_32bit", s[3]) + core.settings:set("shadow_filters", s[4]) + core.settings:set("shadow_map_color", s[5]) + end + end + end, +} diff --git a/builtin/mainmenu/tab_settings.lua b/builtin/mainmenu/tab_settings.lua deleted file mode 100644 index 404286acf..000000000 --- a/builtin/mainmenu/tab_settings.lua +++ /dev/null @@ -1,399 +0,0 @@ ---Minetest ---Copyright (C) 2013 sapier --- ---This program is free software; you can redistribute it and/or modify ---it under the terms of the GNU Lesser General Public License as published by ---the Free Software Foundation; either version 2.1 of the License, or ---(at your option) any later version. --- ---This program is distributed in the hope that it will be useful, ---but WITHOUT ANY WARRANTY; without even the implied warranty of ---MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ---GNU Lesser General Public License for more details. --- ---You should have received a copy of the GNU Lesser General Public License along ---with this program; if not, write to the Free Software Foundation, Inc., ---51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - --------------------------------------------------------------------------------- - -local labels = { - leaves = { - fgettext("Opaque Leaves"), - fgettext("Simple Leaves"), - fgettext("Fancy Leaves") - }, - node_highlighting = { - fgettext("Node Outlining"), - fgettext("Node Highlighting"), - fgettext("None") - }, - filters = { - fgettext("No Filter"), - fgettext("Bilinear Filter"), - fgettext("Trilinear Filter") - }, - mipmap = { - fgettext("No Mipmap"), - fgettext("Mipmap"), - fgettext("Mipmap + Aniso. Filter") - }, - antialiasing = { - fgettext("None"), - fgettext("2x"), - fgettext("4x"), - fgettext("8x") - }, - shadow_levels = { - fgettext("Disabled"), - fgettext("Very Low"), - fgettext("Low"), - fgettext("Medium"), - fgettext("High"), - fgettext("Very High") - } -} - -local dd_options = { - leaves = { - table.concat(labels.leaves, ","), - {"opaque", "simple", "fancy"} - }, - node_highlighting = { - table.concat(labels.node_highlighting, ","), - {"box", "halo", "none"} - }, - filters = { - table.concat(labels.filters, ","), - {"", "bilinear_filter", "trilinear_filter"} - }, - mipmap = { - table.concat(labels.mipmap, ","), - {"", "mip_map", "anisotropic_filter"} - }, - antialiasing = { - table.concat(labels.antialiasing, ","), - {"0", "2", "4", "8"} - }, - shadow_levels = { - table.concat(labels.shadow_levels, ","), - { "0", "1", "2", "3", "4", "5" } - } -} - -local getSettingIndex = { - Leaves = function() - local style = core.settings:get("leaves_style") - for idx, name in pairs(dd_options.leaves[2]) do - if style == name then return idx end - end - return 1 - end, - NodeHighlighting = function() - local style = core.settings:get("node_highlighting") - for idx, name in pairs(dd_options.node_highlighting[2]) do - if style == name then return idx end - end - return 1 - end, - Filter = function() - if core.settings:get(dd_options.filters[2][3]) == "true" then - return 3 - elseif core.settings:get(dd_options.filters[2][3]) == "false" and - core.settings:get(dd_options.filters[2][2]) == "true" then - return 2 - end - return 1 - end, - Mipmap = function() - if core.settings:get(dd_options.mipmap[2][3]) == "true" then - return 3 - elseif core.settings:get(dd_options.mipmap[2][3]) == "false" and - core.settings:get(dd_options.mipmap[2][2]) == "true" then - return 2 - end - return 1 - end, - Antialiasing = function() - local antialiasing_setting = core.settings:get("fsaa") - for i = 1, #dd_options.antialiasing[2] do - if antialiasing_setting == dd_options.antialiasing[2][i] then - return i - end - end - return 1 - end, - ShadowMapping = function() - local shadow_setting = core.settings:get("shadow_levels") - for i = 1, #dd_options.shadow_levels[2] do - if shadow_setting == dd_options.shadow_levels[2][i] then - return i - end - end - return 1 - end -} - -local function antialiasing_fname_to_name(fname) - for i = 1, #labels.antialiasing do - if fname == labels.antialiasing[i] then - return dd_options.antialiasing[2][i] - end - end - return 0 -end - -local function formspec(tabview, name, tabdata) - local tab_string = - "box[0,0;3.75,4.5;#999999]" .. - "checkbox[0.25,0;cb_smooth_lighting;" .. fgettext("Smooth Lighting") .. ";" - .. dump(core.settings:get_bool("smooth_lighting")) .. "]" .. - "checkbox[0.25,0.5;cb_particles;" .. fgettext("Particles") .. ";" - .. dump(core.settings:get_bool("enable_particles")) .. "]" .. - "checkbox[0.25,1;cb_3d_clouds;" .. fgettext("3D Clouds") .. ";" - .. dump(core.settings:get_bool("enable_3d_clouds")) .. "]" .. - "checkbox[0.25,1.5;cb_opaque_water;" .. fgettext("Opaque Water") .. ";" - .. dump(core.settings:get_bool("opaque_water")) .. "]" .. - "checkbox[0.25,2.0;cb_connected_glass;" .. fgettext("Connected Glass") .. ";" - .. dump(core.settings:get_bool("connected_glass")) .. "]" .. - "dropdown[0.25,2.8;3.5;dd_node_highlighting;" .. dd_options.node_highlighting[1] .. ";" - .. getSettingIndex.NodeHighlighting() .. "]" .. - "dropdown[0.25,3.6;3.5;dd_leaves_style;" .. dd_options.leaves[1] .. ";" - .. getSettingIndex.Leaves() .. "]" .. - "box[4,0;3.75,4.9;#999999]" .. - "label[4.25,0.1;" .. fgettext("Texturing:") .. "]" .. - "dropdown[4.25,0.55;3.5;dd_filters;" .. dd_options.filters[1] .. ";" - .. getSettingIndex.Filter() .. "]" .. - "dropdown[4.25,1.35;3.5;dd_mipmap;" .. dd_options.mipmap[1] .. ";" - .. getSettingIndex.Mipmap() .. "]" .. - "label[4.25,2.15;" .. fgettext("Antialiasing:") .. "]" .. - "dropdown[4.25,2.6;3.5;dd_antialiasing;" .. dd_options.antialiasing[1] .. ";" - .. getSettingIndex.Antialiasing() .. "]" .. - "box[8,0;3.75,4.5;#999999]" - - local video_driver = core.get_active_driver() - local shaders_enabled = core.settings:get_bool("enable_shaders") - if video_driver == "opengl" then - tab_string = tab_string .. - "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders") .. ";" - .. tostring(shaders_enabled) .. "]" - elseif video_driver == "ogles2" then - tab_string = tab_string .. - "checkbox[8.25,0;cb_shaders;" .. fgettext("Shaders (experimental)") .. ";" - .. tostring(shaders_enabled) .. "]" - else - core.settings:set_bool("enable_shaders", false) - shaders_enabled = false - tab_string = tab_string .. - "label[8.38,0.2;" .. core.colorize("#888888", - fgettext("Shaders (unavailable)")) .. "]" - end - - tab_string = tab_string .. - "button[8,4.75;3.95,1;btn_change_keys;" - .. fgettext("Change Keys") .. "]" - - tab_string = tab_string .. - "button[0,4.75;3.95,1;btn_advanced_settings;" - .. fgettext("All Settings") .. "]" - - - if core.settings:get("touchscreen_threshold") ~= nil then - tab_string = tab_string .. - "label[4.25,3.5;" .. fgettext("Touch threshold (px):") .. "]" .. - "dropdown[4.25,3.95;3.5;dd_touchthreshold;0,10,20,30,40,50;" .. - ((tonumber(core.settings:get("touchscreen_threshold")) / 10) + 1) .. - "]" - else - tab_string = tab_string .. - "label[4.25,3.65;" .. fgettext("Screen:") .. "]" .. - "checkbox[4.25,3.9;cb_autosave_screensize;" .. fgettext("Autosave Screen Size") .. ";" - .. dump(core.settings:get_bool("autosave_screensize")) .. "]" - end - - if shaders_enabled then - tab_string = tab_string .. - "checkbox[8.25,0.5;cb_tonemapping;" .. fgettext("Tone Mapping") .. ";" - .. dump(core.settings:get_bool("tone_mapping")) .. "]" .. - "checkbox[8.25,1;cb_waving_water;" .. fgettext("Waving Liquids") .. ";" - .. dump(core.settings:get_bool("enable_waving_water")) .. "]" .. - "checkbox[8.25,1.5;cb_waving_leaves;" .. fgettext("Waving Leaves") .. ";" - .. dump(core.settings:get_bool("enable_waving_leaves")) .. "]" .. - "checkbox[8.25,2;cb_waving_plants;" .. fgettext("Waving Plants") .. ";" - .. dump(core.settings:get_bool("enable_waving_plants")) .. "]" - - if video_driver == "opengl" then - tab_string = tab_string .. - "label[8.25,2.8;" .. fgettext("Dynamic shadows:") .. "]" .. - "label[8.25,3.2;" .. fgettext("(game support required)") .. "]" .. - "dropdown[8.25,3.7;3.5;dd_shadows;" .. dd_options.shadow_levels[1] .. ";" - .. getSettingIndex.ShadowMapping() .. "]" - else - tab_string = tab_string .. - "label[8.38,2.7;" .. core.colorize("#888888", - fgettext("Dynamic shadows")) .. "]" - end - else - tab_string = tab_string .. - "label[8.38,0.7;" .. core.colorize("#888888", - fgettext("Tone Mapping")) .. "]" .. - "label[8.38,1.2;" .. core.colorize("#888888", - fgettext("Waving Liquids")) .. "]" .. - "label[8.38,1.7;" .. core.colorize("#888888", - fgettext("Waving Leaves")) .. "]" .. - "label[8.38,2.2;" .. core.colorize("#888888", - fgettext("Waving Plants")) .. "]".. - "label[8.38,2.7;" .. core.colorize("#888888", - fgettext("Dynamic shadows")) .. "]" - end - - return tab_string -end - --------------------------------------------------------------------------------- -local function handle_settings_buttons(this, fields, tabname, tabdata) - - if fields["btn_advanced_settings"] ~= nil then - local adv_settings_dlg = create_adv_settings_dlg() - adv_settings_dlg:set_parent(this) - this:hide() - adv_settings_dlg:show() - --mm_game_theme.update("singleplayer", current_game()) - return true - end - if fields["cb_smooth_lighting"] then - core.settings:set("smooth_lighting", fields["cb_smooth_lighting"]) - return true - end - if fields["cb_particles"] then - core.settings:set("enable_particles", fields["cb_particles"]) - return true - end - if fields["cb_3d_clouds"] then - core.settings:set("enable_3d_clouds", fields["cb_3d_clouds"]) - return true - end - if fields["cb_opaque_water"] then - core.settings:set("opaque_water", fields["cb_opaque_water"]) - return true - end - if fields["cb_connected_glass"] then - core.settings:set("connected_glass", fields["cb_connected_glass"]) - return true - end - if fields["cb_autosave_screensize"] then - core.settings:set("autosave_screensize", fields["cb_autosave_screensize"]) - return true - end - if fields["cb_shaders"] then - core.settings:set("enable_shaders", fields["cb_shaders"]) - return true - end - if fields["cb_tonemapping"] then - core.settings:set("tone_mapping", fields["cb_tonemapping"]) - return true - end - if fields["cb_waving_water"] then - core.settings:set("enable_waving_water", fields["cb_waving_water"]) - return true - end - if fields["cb_waving_leaves"] then - core.settings:set("enable_waving_leaves", fields["cb_waving_leaves"]) - end - if fields["cb_waving_plants"] then - core.settings:set("enable_waving_plants", fields["cb_waving_plants"]) - return true - end - if fields["btn_change_keys"] then - core.show_keys_menu() - return true - end - - --Note dropdowns have to be handled LAST! - local ddhandled = false - - for i = 1, #labels.leaves do - if fields["dd_leaves_style"] == labels.leaves[i] then - core.settings:set("leaves_style", dd_options.leaves[2][i]) - ddhandled = true - end - end - for i = 1, #labels.node_highlighting do - if fields["dd_node_highlighting"] == labels.node_highlighting[i] then - core.settings:set("node_highlighting", dd_options.node_highlighting[2][i]) - ddhandled = true - end - end - if fields["dd_filters"] == labels.filters[1] then - core.settings:set("bilinear_filter", "false") - core.settings:set("trilinear_filter", "false") - ddhandled = true - elseif fields["dd_filters"] == labels.filters[2] then - core.settings:set("bilinear_filter", "true") - core.settings:set("trilinear_filter", "false") - ddhandled = true - elseif fields["dd_filters"] == labels.filters[3] then - core.settings:set("bilinear_filter", "false") - core.settings:set("trilinear_filter", "true") - ddhandled = true - end - if fields["dd_mipmap"] == labels.mipmap[1] then - core.settings:set("mip_map", "false") - core.settings:set("anisotropic_filter", "false") - ddhandled = true - elseif fields["dd_mipmap"] == labels.mipmap[2] then - core.settings:set("mip_map", "true") - core.settings:set("anisotropic_filter", "false") - ddhandled = true - elseif fields["dd_mipmap"] == labels.mipmap[3] then - core.settings:set("mip_map", "true") - core.settings:set("anisotropic_filter", "true") - ddhandled = true - end - if fields["dd_antialiasing"] then - core.settings:set("fsaa", - antialiasing_fname_to_name(fields["dd_antialiasing"])) - ddhandled = true - end - if fields["dd_touchthreshold"] then - core.settings:set("touchscreen_threshold", fields["dd_touchthreshold"]) - ddhandled = true - end - - for i = 1, #labels.shadow_levels do - if fields["dd_shadows"] == labels.shadow_levels[i] then - core.settings:set("shadow_levels", dd_options.shadow_levels[2][i]) - ddhandled = true - end - end - - if fields["dd_shadows"] == labels.shadow_levels[1] then - core.settings:set("enable_dynamic_shadows", "false") - else - local shadow_presets = { - [2] = { 62, 512, "true", 0, "false" }, - [3] = { 93, 1024, "true", 0, "false" }, - [4] = { 140, 2048, "true", 1, "false" }, - [5] = { 210, 4096, "true", 2, "true" }, - [6] = { 300, 8192, "true", 2, "true" }, - } - local s = shadow_presets[table.indexof(labels.shadow_levels, fields["dd_shadows"])] - if s then - core.settings:set("enable_dynamic_shadows", "true") - core.settings:set("shadow_map_max_distance", s[1]) - core.settings:set("shadow_map_texture_size", s[2]) - core.settings:set("shadow_map_texture_32bit", s[3]) - core.settings:set("shadow_filters", s[4]) - core.settings:set("shadow_map_color", s[5]) - end - end - - return ddhandled -end - -return { - name = "settings", - caption = fgettext("Settings"), - cbf_formspec = formspec, - cbf_button_handler = handle_settings_buttons -} diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 786157c65..3dd9767a1 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -2051,7 +2051,7 @@ joystick_deadzone (Joystick dead zone) int 2048 0 65535 joystick_frustum_sensitivity (Joystick frustum sensitivity) float 170.0 0.001 -[*Temporary Settings] +[*Hide: Temporary Settings] # Path to texture directory. All textures are first searched from here. texture_path (Texture path) path