mirror of
https://github.com/minetest/minetest.git
synced 2024-12-23 22:52:25 +01:00
436 lines
13 KiB
Lua
436 lines
13 KiB
Lua
--Luanti
|
|
--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.
|
|
-- * `resettable`: (Optional) if this is true, a reset button is shown.
|
|
-- * `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
|
|
|
|
|
|
function make.note(text)
|
|
return {
|
|
full_width = true,
|
|
get_formspec = function(self, avail_w)
|
|
-- Assuming label height 0.4:
|
|
-- Position at y=0 to eat 0.2 of the padding above, leave 0.05.
|
|
-- The returned used_height doesn't include padding.
|
|
return ("label[0,0;%s]"):format(core.colorize("#bbb", core.formspec_escape(text))), 0.2
|
|
end,
|
|
}
|
|
end
|
|
|
|
|
|
--- Used for string and numeric style fields
|
|
---
|
|
--- @param converter Function to coerce values from strings.
|
|
--- @param validator Validator function, optional. Returns true when valid.
|
|
--- @param stringifier Function to convert values to strings, optional.
|
|
local function make_field(converter, validator, stringifier)
|
|
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.resettable = core.settings:has(setting.name)
|
|
|
|
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 .. ("field_enter_after_edit[%s;true]"):format(setting.name)
|
|
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, (stringifier or tostring)(value))
|
|
return true
|
|
end
|
|
end,
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
make.float = make_field(tonumber, is_valid_number, function(x)
|
|
local str = tostring(x)
|
|
if str:match("^[+-]?%d+$") then
|
|
str = str .. ".0"
|
|
end
|
|
return str
|
|
end)
|
|
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.resettable = core.settings:has(setting.name)
|
|
|
|
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.resettable = core.settings:has(setting.name)
|
|
|
|
local labels = setting.option_labels or {}
|
|
|
|
local items = {}
|
|
for i, option in ipairs(setting.values) do
|
|
items[i] = core.formspec_escape(labels[option] or 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;true]"):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 idx = tonumber(fields[setting.name]) or 0
|
|
local value = setting.values[idx]
|
|
if value == nil or value == old_value then
|
|
return false
|
|
end
|
|
|
|
core.settings:set(setting.name, value)
|
|
return true
|
|
end,
|
|
}
|
|
end
|
|
|
|
|
|
local 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.resettable = core.settings:has(setting.name)
|
|
|
|
local fs = ("field[0,0.3;%f,0.8;%s;%s;%s]"):format(
|
|
avail_w - 3, setting.name, get_label(setting), core.formspec_escape(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
|
|
|
|
if PLATFORM == "Android" then
|
|
-- The Irrlicht file picker doesn't work on Android.
|
|
make.path = make.string
|
|
make.filepath = make.string
|
|
else
|
|
make.path = make_path
|
|
make.filepath = make_path
|
|
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.resettable = core.settings:has(setting.name)
|
|
|
|
-- 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) .. "]",
|
|
}
|
|
|
|
self.resettable = core.settings:has(setting.name)
|
|
|
|
checkboxes = {}
|
|
for _, name in ipairs(setting.possible) do
|
|
checkboxes[name] = false
|
|
end
|
|
local function apply_flags(flag_string, what)
|
|
local prefixed_flags = {}
|
|
for _, name in ipairs(flag_string:split(",")) do
|
|
prefixed_flags[name:trim()] = true
|
|
end
|
|
for _, name in ipairs(setting.possible) do
|
|
local enabled = prefixed_flags[name]
|
|
local disabled = prefixed_flags["no" .. name]
|
|
if enabled and disabled then
|
|
core.log("warning", "Flag " .. name .. " in " .. what .. " " ..
|
|
setting.name .. " both enabled and disabled, ignoring")
|
|
elseif enabled then
|
|
checkboxes[name] = true
|
|
elseif disabled then
|
|
checkboxes[name] = false
|
|
end
|
|
end
|
|
end
|
|
-- First apply the default, which is necessary since flags
|
|
-- which are not overridden may be missing from the value.
|
|
apply_flags(setting.default, "default for setting")
|
|
local value = core.settings:get(setting.name)
|
|
if value then
|
|
apply_flags(value, "setting")
|
|
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 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
|
|
|
|
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 checkboxes[name] then
|
|
table.insert(values, name)
|
|
else
|
|
table.insert(values, "no" .. name)
|
|
end
|
|
end
|
|
|
|
core.settings:set(setting.name, table.concat(values, ","))
|
|
end
|
|
return changed
|
|
end
|
|
}
|
|
end
|
|
|
|
|
|
local function make_noise_params(setting)
|
|
return {
|
|
info_text = setting.comment,
|
|
setting = setting,
|
|
|
|
get_formspec = function(self, avail_w)
|
|
-- The "defaults" noise parameter flag doesn't reset a noise
|
|
-- setting to its default value, so we offer a regular reset button.
|
|
self.resettable = core.settings:has(setting.name)
|
|
|
|
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.noise_params_2d = make_noise_params
|
|
make.noise_params_3d = make_noise_params
|
|
|
|
|
|
return make
|