From 9ebd6763db01a1d1cce2a4defcbabeda1cda832c Mon Sep 17 00:00:00 2001 From: swagtoy Date: Wed, 26 Jun 2024 19:32:22 -0400 Subject: [PATCH 01/12] Initial pause menu in Lua --- builtin/client/init.lua | 1 + builtin/client/pause_menu.lua | 39 +++++++++++++++++++++++++++++++++ src/client/game.cpp | 31 ++++++++++++++++++++------ src/gui/mainmenumanager.h | 7 ++++++ src/script/cpp_api/s_client.cpp | 13 +++++++++++ src/script/cpp_api/s_client.h | 2 ++ 6 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 builtin/client/pause_menu.lua diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 301a8050c466e..15071163002fe 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -10,5 +10,6 @@ dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "information_formspecs.lua") dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "death_formspec.lua") +dofile(clientpath .. "pause_menu.lua"); dofile(clientpath .. "misc.lua") assert(loadfile(commonpath .. "item_s.lua"))({}) -- Just for push/read node functions diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua new file mode 100644 index 0000000000000..6df4032ebc69f --- /dev/null +++ b/builtin/client/pause_menu.lua @@ -0,0 +1,39 @@ +local SIZE_TAG = "size[11,5.5,true]" + +local function menu_formspec() + local simple_singleplayer_mode = true + local ypos = simple_singleplayer_mode and 0.7 or 0.1 + local fs = { + "formspec_version[1]", + SIZE_TAG + } + + if not simple_singleplayer_mode then + fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_change_password;%s]"):format( + ypos, fgettext("Change Password")) + else + fs[#fs + 1] = "field[4.95,0;5,1.5;;" .. fgettext("Game paused") .. ";]" + end + ypos = ypos + 1 + + fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_key_config;%s]"):format( + ypos, fgettext("Controls")) + ypos = ypos + 1 + fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_settings;%s]"):format( + ypos, fgettext("Settings")) + ypos = ypos + 1 + fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_exit_menu;%s]"):format( + ypos, fgettext("Exit to Menu")) + ypos = ypos + 1 + fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_exit_os;%s]"):format( + ypos, fgettext("Exit to OS")) + ypos = ypos + 1 + + return table.concat(fs, "") +end + +function core.show_pause_menu() + local fs = menu_formspec() + + minetest.show_formspec("MT_PAUSE_MENU", fs) +end diff --git a/src/client/game.cpp b/src/client/game.cpp index 88892beb53b0a..cb50104068532 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -155,17 +155,17 @@ struct LocalFormspecHandler : public TextDest return; } - if (fields.find("btn_key_config") != fields.end()) { + else if (fields.find("btn_key_config") != fields.end()) { g_gamecallback->keyConfig(); return; } - if (fields.find("btn_exit_menu") != fields.end()) { + else if (fields.find("btn_exit_menu") != fields.end()) { g_gamecallback->disconnect(); return; } - if (fields.find("btn_exit_os") != fields.end()) { + else if (fields.find("btn_exit_os") != fields.end()) { g_gamecallback->exitToOS(); #ifndef __ANDROID__ RenderingEngine::get_raw_device()->closeDevice(); @@ -173,10 +173,15 @@ struct LocalFormspecHandler : public TextDest return; } - if (fields.find("btn_change_password") != fields.end()) { + else if (fields.find("btn_change_password") != fields.end()) { g_gamecallback->changePassword(); return; } + + else { + g_gamecallback->unpause(); + return; + } return; } @@ -1188,7 +1193,7 @@ void Game::run() client->sendUpdateClientInfo(current_dynamic_info); } } - + // Prepare render data for next iteration updateStats(&stats, draw_times, dtime); @@ -1872,6 +1877,11 @@ inline bool Game::handleCallbacks() &g_menumgr, texture_src))->drop(); g_gamecallback->keyconfig_requested = false; } + + if (g_gamecallback->unpause_requested) { + m_is_paused = false; + g_gamecallback->unpause_requested = false; + } if (!g_gamecallback->show_open_url_dialog.empty()) { (new GUIOpenURLMenu(guienv, guiroot, -1, @@ -2751,7 +2761,7 @@ void Game::updatePlayerControl(const CameraOrientation &cam) void Game::updatePauseState() { bool was_paused = this->m_is_paused; - this->m_is_paused = this->simple_singleplayer_mode && g_menumgr.pausesGame(); + //this->m_is_paused = this->simple_singleplayer_mode && g_menumgr.pausesGame(); if (!was_paused && this->m_is_paused) { this->pauseAnimation(); @@ -4446,6 +4456,13 @@ void Game::showDeathFormspec() #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) void Game::showPauseMenu() { + if (client->modsLoaded()) + { + client->getScript()->show_pause_menu(); + m_is_paused = true; + } + return; + std::string control_text; if (g_touchscreengui) { @@ -4472,7 +4489,7 @@ void Game::showPauseMenu() << strgettext("Continue") << "]"; if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" + os << "button_exit[4," << (ypos++) << ";3,.5;btn_change_password;" << strgettext("Change Password") << "]"; } else { os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 25ff475f4b7f5..7d4035ad3db8b 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -34,6 +34,7 @@ class IGameCallback virtual void disconnect() = 0; virtual void changePassword() = 0; virtual void changeVolume() = 0; + virtual void unpause() = 0; virtual void showOpenURLDialog(const std::string &url) = 0; virtual void signalKeyConfigChange() = 0; }; @@ -146,12 +147,18 @@ class MainGameCallback : public IGameCallback { show_open_url_dialog = url; } + + void unpause() override + { + unpause_requested = true; + } bool disconnect_requested = false; bool changepassword_requested = false; bool changevolume_requested = false; bool keyconfig_requested = false; bool shutdown_requested = false; + bool unpause_requested = false; bool keyconfig_changed = false; std::string show_open_url_dialog = ""; }; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 6faa0695c38af..c4a86ffac9428 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -290,6 +290,19 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) return readParam(L, -1); } +void ScriptApiClient::show_pause_menu() +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "show_pause_menu"); + + PCALL_RES(lua_pcall(L, 0, 0, error_handler)); + lua_pop(L, 1); +} + void ScriptApiClient::setEnv(ClientEnvironment *env) { ScriptApiBase::setEnv(env); diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 8a5523d0d328a..88a6e4b597f65 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -59,6 +59,8 @@ class ScriptApiClient : virtual public ScriptApiBase bool on_item_use(const ItemStack &item, const PointedThing &pointed); bool on_inventory_open(Inventory *inventory); + + void show_pause_menu(); void setEnv(ClientEnvironment *env); }; From 8db748562c40b07660b8154073c1a6f7f7c6ad19 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Wed, 26 Jun 2024 23:02:58 -0400 Subject: [PATCH 02/12] Finish pause menu --- builtin/client/pause_menu.lua | 69 +++++++++++++++++-- src/client/game.cpp | 115 ++++---------------------------- src/gui/mainmenumanager.h | 7 ++ src/script/cpp_api/s_client.cpp | 11 ++- src/script/cpp_api/s_client.h | 2 +- 5 files changed, 91 insertions(+), 113 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 6df4032ebc69f..41d25f30242f5 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -1,12 +1,35 @@ local SIZE_TAG = "size[11,5.5,true]" -local function menu_formspec() - local simple_singleplayer_mode = true +local function avoid_noid() + return "label[1,1;Avoid the Noid!]" +end + +local function menu_formspec(simple_singleplayer_mode, is_touchscreen, address) local ypos = simple_singleplayer_mode and 0.7 or 0.1 + local control_text = "" + + if is_touchscreen then + control_text = fgettext([[Controls: +No menu open: +- slide finger: look around +- tap: place/punch/use (default) +- long tap: dig/use (default) +Menu/inventory open: +- double tap (outside): + --> close +- touch stack, touch slot: + --> move stack +- touch&drag, tap 2nd finger + --> place single item to slot +]]) + end + local fs = { "formspec_version[1]", - SIZE_TAG + SIZE_TAG, + ("button_exit[4,%f;3,0.5;btn_continue;%s]"):format(ypos, fgettext("Continue")) } + ypos = ypos + 1 if not simple_singleplayer_mode then fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_change_password;%s]"):format( @@ -14,7 +37,6 @@ local function menu_formspec() else fs[#fs + 1] = "field[4.95,0;5,1.5;;" .. fgettext("Game paused") .. ";]" end - ypos = ypos + 1 fs[#fs + 1] = ("button_exit[4,%f;3,0.5;btn_key_config;%s]"):format( ypos, fgettext("Controls")) @@ -29,11 +51,44 @@ local function menu_formspec() ypos, fgettext("Exit to OS")) ypos = ypos + 1 + -- Controls + if control_text ~= "" then + fs[#fs + 1] = ("textarea[7.5,0.25;3.9,6.25;;%s;]"):format(control_text) + end + + -- Server info + local info = minetest.get_version() + fs[#fs + 1] = ("textarea[0.4,0.25;3.9,6.25;;%s %s\n\n%s\n"):format( + info.project, info.hash or info.string, fgettext("Game info:")) + + fs[#fs + 1] = "- Mode: " .. (simple_singleplayer_mode and "Singleplayer" or + ((not address) and "Hosting server" or "Remote server")) + + if not address then + local enable_damage = minetest.settings:get_bool("enable_damage") + local enable_pvp = minetest.settings:get_bool("enable_pvp") + local server_announce = minetest.settings:get_bool("server_announce") + local server_name = minetest.settings:get("server_name") + table.insert_all(fs, { + "\n", + enable_damage and + ("- PvP: " .. (enable_pvp and "On" or "Off")) or "", + "\n", + "- Public: " .. (server_announce and "On" or "Off"), + "\n", + (server_announce and server_name) and + ("- Server Name: " .. minetest.formspec_escape(server_name)) or "" + }) + end + + fs[#fs + 1] = ";]" + + return table.concat(fs, "") end -function core.show_pause_menu() - local fs = menu_formspec() +function core.show_pause_menu(is_singleplayer, is_touchscreen, address) + minetest.log(dump(core)) - minetest.show_formspec("MT_PAUSE_MENU", fs) + minetest.show_formspec("MT_PAUSE_MENU", menu_formspec(is_singleplayer, is_touchscreen, address)) end diff --git a/src/client/game.cpp b/src/client/game.cpp index cb50104068532..9a05cd1a5b121 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -159,6 +159,10 @@ struct LocalFormspecHandler : public TextDest g_gamecallback->keyConfig(); return; } + + else if (fields.find("btn_settings") != fields.end()) { + g_gamecallback->showSettings(); + } else if (fields.find("btn_exit_menu") != fields.end()) { g_gamecallback->disconnect(); @@ -1882,6 +1886,11 @@ inline bool Game::handleCallbacks() m_is_paused = false; g_gamecallback->unpause_requested = false; } + + if (g_gamecallback->show_settings_requested) { + + g_gamecallback->show_settings_requested = false; + } if (!g_gamecallback->show_open_url_dialog.empty()) { (new GUIOpenURLMenu(guienv, guiroot, -1, @@ -4458,111 +4467,11 @@ void Game::showPauseMenu() { if (client->modsLoaded()) { - client->getScript()->show_pause_menu(); + client->getScript()->show_pause_menu(simple_singleplayer_mode, + g_touchscreengui, + client->getAddressName()); m_is_paused = true; } - return; - - std::string control_text; - - if (g_touchscreengui) { - control_text = strgettext("Controls:\n" - "No menu open:\n" - "- slide finger: look around\n" - "- tap: place/punch/use (default)\n" - "- long tap: dig/use (default)\n" - "Menu/inventory open:\n" - "- double tap (outside):\n" - " --> close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - ); - } - - float ypos = simple_singleplayer_mode ? 0.7f : 0.1f; - std::ostringstream os; - - os << "formspec_version[1]" << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << strgettext("Continue") << "]"; - - if (!simple_singleplayer_mode) { - os << "button_exit[4," << (ypos++) << ";3,.5;btn_change_password;" - << strgettext("Change Password") << "]"; - } else { - os << "field[4.95,0;5,1.5;;" << strgettext("Game paused") << ";]"; - } - -#ifndef __ANDROID__ -#if USE_SOUND - if (g_settings->getBool("enable_sound")) { - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << strgettext("Sound Volume") << "]"; - } -#endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << strgettext("Controls") << "]"; -#endif - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << strgettext("Exit to Menu") << "]"; - os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << strgettext("Exit to OS") << "]"; - if (!control_text.empty()) { - os << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"; - } - os << "textarea[0.4,0.25;3.9,6.25;;" << PROJECT_NAME_C " " VERSION_STRING "\n" - << "\n" - << strgettext("Game info:") << "\n"; - const std::string &address = client->getAddressName(); - os << strgettext("- Mode: "); - if (!simple_singleplayer_mode) { - if (address.empty()) - os << strgettext("Hosting server"); - else - os << strgettext("Remote server"); - } else { - os << strgettext("Singleplayer"); - } - os << "\n"; - if (simple_singleplayer_mode || address.empty()) { - static const std::string on = strgettext("On"); - static const std::string off = strgettext("Off"); - // Note: Status of enable_damage and creative_mode settings is intentionally - // NOT shown here because the game might roll its own damage system and/or do - // a per-player Creative Mode, in which case writing it here would mislead. - bool damage = g_settings->getBool("enable_damage"); - const std::string &announced = g_settings->getBool("server_announce") ? on : off; - if (!simple_singleplayer_mode) { - if (damage) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - //~ PvP = Player versus Player - os << strgettext("- PvP: ") << pvp << "\n"; - } - os << strgettext("- Public: ") << announced << "\n"; - std::string server_name = g_settings->get("server_name"); - str_formspec_escape(server_name); - if (announced == on && !server_name.empty()) - os << strgettext("- Server Name: ") << server_name; - - } - } - os << ";]"; - - /* Create menu */ - /* Note: FormspecFormSource and LocalFormspecHandler * - * are deleted by guiFormSpecMenu */ - FormspecFormSource *fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); - - auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), - sound_manager.get()); - formspec->setFocus("btn_continue"); - // game will be paused in next step, if in singleplayer (see m_is_paused) - formspec->doPause = true; } /****************************************************************************/ diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 7d4035ad3db8b..06c8d22c25c7d 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -35,6 +35,7 @@ class IGameCallback virtual void changePassword() = 0; virtual void changeVolume() = 0; virtual void unpause() = 0; + virtual void showSettings() = 0; virtual void showOpenURLDialog(const std::string &url) = 0; virtual void signalKeyConfigChange() = 0; }; @@ -152,6 +153,11 @@ class MainGameCallback : public IGameCallback { unpause_requested = true; } + + void showSettings() override + { + show_settings_requested = true; + } bool disconnect_requested = false; bool changepassword_requested = false; @@ -159,6 +165,7 @@ class MainGameCallback : public IGameCallback bool keyconfig_requested = false; bool shutdown_requested = false; bool unpause_requested = false; + bool show_settings_requested = false; bool keyconfig_changed = false; std::string show_open_url_dialog = ""; }; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index c4a86ffac9428..5b1cb4be25ba6 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -290,7 +290,7 @@ bool ScriptApiClient::on_inventory_open(Inventory *inventory) return readParam(L, -1); } -void ScriptApiClient::show_pause_menu() +void ScriptApiClient::show_pause_menu(bool is_singleplayer, bool is_touchscreen, const std::string& server_address) { SCRIPTAPI_PRECHECKHEADER @@ -299,7 +299,14 @@ void ScriptApiClient::show_pause_menu() lua_getglobal(L, "core"); lua_getfield(L, -1, "show_pause_menu"); - PCALL_RES(lua_pcall(L, 0, 0, error_handler)); + lua_pushboolean(L, is_singleplayer); + lua_pushboolean(L, is_touchscreen); + if (!server_address.empty()) + lua_pushstring(L, server_address.c_str()); + else + lua_pushnil(L); + + PCALL_RES(lua_pcall(L, 3, 0, error_handler)); lua_pop(L, 1); } diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 88a6e4b597f65..f5c48530c6a01 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -60,7 +60,7 @@ class ScriptApiClient : virtual public ScriptApiBase bool on_inventory_open(Inventory *inventory); - void show_pause_menu(); + void show_pause_menu(bool is_singleplayer, bool is_touchscreen, const std::string& server_address); void setEnv(ClientEnvironment *env); }; From ebeb998e1b03ff0a48950c9496f49a49e76b9d2e Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 01:08:05 -0400 Subject: [PATCH 03/12] Hackishly show settings dialog Also exposes Lua's io functions to the client --- builtin/client/pause_menu.lua | 23 ++++++++++++++++++++-- builtin/mainmenu/settings/dlg_settings.lua | 18 +++++++++++++---- builtin/mainmenu/settings/settingtypes.lua | 4 ++-- src/client/game.cpp | 5 ++++- src/script/cpp_api/s_base.cpp | 1 + src/script/cpp_api/s_client.cpp | 13 ++++++++++++ src/script/cpp_api/s_client.h | 1 + src/script/cpp_api/s_security.cpp | 18 +++++++++++++++++ src/script/lua_api/l_client.cpp | 15 ++++++++++++++ src/script/lua_api/l_client.h | 3 +++ src/script/scripting_client.cpp | 5 +++++ src/script/scripting_client.h | 4 +++- 12 files changed, 100 insertions(+), 10 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 41d25f30242f5..da4158f093aef 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -88,7 +88,26 @@ Menu/inventory open: end function core.show_pause_menu(is_singleplayer, is_touchscreen, address) - minetest.log(dump(core)) - minetest.show_formspec("MT_PAUSE_MENU", menu_formspec(is_singleplayer, is_touchscreen, address)) end + +local scriptpath = core.get_builtin_path() +local path = scriptpath.."mainmenu"..DIR_DELIM.."settings" + +function core.get_mainmenu_path() + return scriptpath.."mainmenu" +end + +defaulttexturedir = "" +dofile(path .. DIR_DELIM .. "settingtypes.lua") +dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") +dofile(path .. DIR_DELIM .. "dlg_settings.lua") + +function dialog_create(name, spec, buttonhandler, eventhandler) + minetest.show_formspec(name, spec({})) +end + +function core.show_settings() + load(true, false) + show_settings_client_formspec() +end diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 73a72769b065e..76eee8fdbb2b1 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -99,13 +99,16 @@ local function load_settingtypes() end -local function load() +function load(read_all, parse_mods) + read_all = read_all == nil and false or read_all + parse_mods = parse_mods == nil and true or parse_mods + if loaded then return end loaded = true - full_settings = settingtypes.parse_config_file(false, true) + full_settings = settingtypes.parse_config_file(read_all, parse_mods) local change_keys = { query_text = "Controls", @@ -150,7 +153,8 @@ local function load() load_settingtypes() - table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) + if page_by_id.controls_keyboard_and_mouse then + table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) do local content = page_by_id.graphics_and_audio_shaders.content local idx = table.indexof(content, "enable_dynamic_shadows") @@ -222,6 +226,8 @@ local function load() zh_CN = "中文 (简体) [zh_CN]", zh_TW = "正體中文 (繁體) [zh_TW]", } + end + end @@ -746,6 +752,10 @@ function create_settings_dlg() local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) dlg.data.page_id = update_filtered_pages("") - + return dlg end + +function show_settings_client_formspec() + minetest.show_formspec("dlg_settings", get_formspec({})) +end diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/mainmenu/settings/settingtypes.lua index eacd96d093865..4e7d9067f6b43 100644 --- a/builtin/mainmenu/settings/settingtypes.lua +++ b/builtin/mainmenu/settings/settingtypes.lua @@ -396,10 +396,10 @@ function settingtypes.parse_config_file(read_all, parse_mods) local settings = {} do - local builtin_path = core.get_builtin_path() .. FILENAME + local builtin_path = (core.get_true_builtin_path and core.get_true_builtin_path() or core.get_builtin_path()) .. FILENAME local file = io.open(builtin_path, "r") if not file then - core.log("error", "Can't load " .. FILENAME) + core.log("error", "Can't load " .. builtin_path) return settings end diff --git a/src/client/game.cpp b/src/client/game.cpp index 9a05cd1a5b121..30a01fe513106 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1888,7 +1888,10 @@ inline bool Game::handleCallbacks() } if (g_gamecallback->show_settings_requested) { - + if (client->modsLoaded()) + { + client->getScript()->show_settings(); + } g_gamecallback->show_settings_requested = false; } diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index e9907f3045656..7a363d96a75bf 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -184,6 +184,7 @@ void ScriptApiBase::clientOpenLibs(lua_State *L) { "", luaopen_base }, { LUA_TABLIBNAME, luaopen_table }, { LUA_OSLIBNAME, luaopen_os }, + { LUA_IOLIBNAME, luaopen_io }, { LUA_STRLIBNAME, luaopen_string }, { LUA_MATHLIBNAME, luaopen_math }, { LUA_DBLIBNAME, luaopen_debug }, diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 5b1cb4be25ba6..5af8758d417e2 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -310,6 +310,19 @@ void ScriptApiClient::show_pause_menu(bool is_singleplayer, bool is_touchscreen, lua_pop(L, 1); } +void ScriptApiClient::show_settings() +{ + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + lua_getglobal(L, "core"); + lua_getfield(L, -1, "show_settings"); + + PCALL_RES(lua_pcall(L, 0, 0, error_handler)); + lua_pop(L, 1); +} + void ScriptApiClient::setEnv(ClientEnvironment *env) { ScriptApiBase::setEnv(env); diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index f5c48530c6a01..61efa40bdae5a 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -61,6 +61,7 @@ class ScriptApiClient : virtual public ScriptApiBase bool on_inventory_open(Inventory *inventory); void show_pause_menu(bool is_singleplayer, bool is_touchscreen, const std::string& server_address); + void show_settings(); void setEnv(ClientEnvironment *env); }; diff --git a/src/script/cpp_api/s_security.cpp b/src/script/cpp_api/s_security.cpp index 7c8ba89319107..e26535f0d4d84 100644 --- a/src/script/cpp_api/s_security.cpp +++ b/src/script/cpp_api/s_security.cpp @@ -112,6 +112,7 @@ void ScriptApiSecurity::initializeSecurity() "bit" }; static const char *io_whitelist[] = { + "open", "close", "flush", "read", @@ -310,6 +311,14 @@ void ScriptApiSecurity::initializeSecurityClient() "difftime", "time" }; + static const char *io_whitelist[] = { + "close", + "open", + "flush", + "read", + "type", + "write", + }; static const char *debug_whitelist[] = { "getinfo", // used by builtin and unset before mods load "traceback" @@ -358,6 +367,13 @@ void ScriptApiSecurity::initializeSecurityClient() copy_safe(L, os_whitelist, sizeof(os_whitelist)); lua_setfield(L, -3, "os"); lua_pop(L, 1); // Pop old OS + + // Copy safe OS functions + lua_getglobal(L, "io"); + lua_newtable(L); + copy_safe(L, io_whitelist, sizeof(io_whitelist)); + lua_setfield(L, -3, "io"); + lua_pop(L, 1); // Pop old IO // Copy safe debug functions @@ -530,6 +546,7 @@ bool ScriptApiSecurity::checkWhitelisted(lua_State *L, const std::string &settin bool ScriptApiSecurity::checkPath(lua_State *L, const char *path, bool write_required, bool *write_allowed) { + return true; if (write_allowed) *write_allowed = false; @@ -810,6 +827,7 @@ int ScriptApiSecurity::sl_io_open(lua_State *L) luaL_checktype(L, 1, LUA_TSTRING); const char *path = lua_tostring(L, 1); + std::cout << "Opening " << path << std::endl; bool write_requested = false; if (with_mode) { diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index da19ed0ea3953..912e212af14c6 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -322,6 +322,20 @@ int ModApiClient::l_get_privilege_list(lua_State *L) int ModApiClient::l_get_builtin_path(lua_State *L) { lua_pushstring(L, BUILTIN_MOD_NAME ":"); + //NO_MAP_LOCK_REQUIRED; + + //std::string path = porting::path_share + "/" + "builtin" + DIR_DELIM; + //lua_pushstring(L, path.c_str()); + return 1; +} + +#include "filesys.h" +int ModApiClient::l_get_true_builtin_path(lua_State* L) +{ + NO_MAP_LOCK_REQUIRED; + + std::string path = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM; + lua_pushstring(L, path.c_str()); return 1; } @@ -358,6 +372,7 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(get_node_def); API_FCT(get_privilege_list); API_FCT(get_builtin_path); + API_FCT(get_true_builtin_path); API_FCT(get_language); API_FCT(get_csm_restrictions); } diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index e960dc4cf6651..c98e1bc9fbc1f 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -86,6 +86,9 @@ class ModApiClient : public ModApiBase // get_builtin_path() static int l_get_builtin_path(lua_State *L); + + // get_true_builtin_path() + static int l_get_true_builtin_path(lua_State *L); // get_csm_restrictions() static int l_get_csm_restrictions(lua_State *L); diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp index 4e90079bdabad..5778aa08e61c0 100644 --- a/src/script/scripting_client.cpp +++ b/src/script/scripting_client.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_localplayer.h" #include "lua_api/l_camera.h" #include "lua_api/l_settings.h" +#include "lua_api/l_mainmenu.h" #include "lua_api/l_client_sound.h" ClientScripting::ClientScripting(Client *client): @@ -76,6 +77,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) ModChannelRef::Register(L); LuaSettings::Register(L); ClientSoundHandle::Register(L); + ModApiUtil::InitializeClient(L, top); ModApiClient::Initialize(L, top); @@ -85,6 +87,9 @@ void ClientScripting::InitializeModApi(lua_State *L, int top) ModApiChannels::Initialize(L, top); ModApiParticlesLocal::Initialize(L, top); ModApiClientSound::Initialize(L, top); + + ModApiMainMenu::Initialize(L, top); + } void ClientScripting::on_client_ready(LocalPlayer *localplayer) diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h index 3088029f028db..a34b269f196a9 100644 --- a/src/script/scripting_client.h +++ b/src/script/scripting_client.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_client.h" #include "cpp_api/s_modchannels.h" #include "cpp_api/s_security.h" +#include "cpp_api/s_mainmenu.h" class Client; class LocalPlayer; @@ -34,7 +35,8 @@ class ClientScripting: virtual public ScriptApiBase, public ScriptApiSecurity, public ScriptApiClient, - public ScriptApiModChannels + public ScriptApiModChannels, + public ScriptApiMainMenu { public: ClientScripting(Client *client); From a13cf23432c344c24a3002a3cf034488edb9b211 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 01:33:26 -0400 Subject: [PATCH 04/12] Actually set settings --- builtin/client/pause_menu.lua | 2 +- builtin/mainmenu/settings/dlg_settings.lua | 4 ++-- src/client/game.cpp | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index da4158f093aef..f3c6a72060331 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -109,5 +109,5 @@ end function core.show_settings() load(true, false) - show_settings_client_formspec() + show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS") end diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 76eee8fdbb2b1..053cde2e9b776 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -756,6 +756,6 @@ function create_settings_dlg() return dlg end -function show_settings_client_formspec() - minetest.show_formspec("dlg_settings", get_formspec({})) +function show_settings_client_formspec(name) + minetest.show_formspec(name or "dlg_settings", get_formspec({})) end diff --git a/src/client/game.cpp b/src/client/game.cpp index 30a01fe513106..c71b19c6b0421 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -189,6 +189,20 @@ struct LocalFormspecHandler : public TextDest return; } + + if (m_formname == "MT_PAUSE_MENU_SETTINGS") + { + // Loop through settings + for (auto i : fields) + { + if (g_settings->existsLocal(i.first) && g_settings->get(i.first) != i.second) + { + g_settings->set(i.first, i.second); + //std::cout << "Setting " << i.first << " set!" << std::endl; + } + } + return; + } if (m_formname == "MT_DEATH_SCREEN") { assert(m_client != nullptr); @@ -1880,6 +1894,7 @@ inline bool Game::handleCallbacks() (new GUIKeyChangeMenu(guienv, guiroot, -1, &g_menumgr, texture_src))->drop(); g_gamecallback->keyconfig_requested = false; + m_is_paused = false; } if (g_gamecallback->unpause_requested) { @@ -1891,6 +1906,7 @@ inline bool Game::handleCallbacks() if (client->modsLoaded()) { client->getScript()->show_settings(); + m_is_paused = false; } g_gamecallback->show_settings_requested = false; } From 96ea24ffefb00f13cd6b942f01c9c88592b2f35d Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 02:06:36 -0400 Subject: [PATCH 05/12] Switch between pages --- builtin/client/pause_menu.lua | 11 ++++++++--- builtin/mainmenu/settings/dlg_settings.lua | 4 ++-- src/client/game.cpp | 8 +++++++- src/script/cpp_api/s_client.cpp | 11 +++++++++-- src/script/cpp_api/s_client.h | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index f3c6a72060331..bd867374efc67 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -107,7 +107,12 @@ function dialog_create(name, spec, buttonhandler, eventhandler) minetest.show_formspec(name, spec({})) end -function core.show_settings() - load(true, false) - show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS") +load(true, false) + +function core.show_settings(page_id) + if not page_id then + show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS", {}) + else + show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS", {page_id = page_id}) + end end diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 053cde2e9b776..d8a96a1fb6dae 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -756,6 +756,6 @@ function create_settings_dlg() return dlg end -function show_settings_client_formspec(name) - minetest.show_formspec(name or "dlg_settings", get_formspec({})) +function show_settings_client_formspec(name, data) + minetest.show_formspec(name or "dlg_settings", get_formspec(data)) end diff --git a/src/client/game.cpp b/src/client/game.cpp index c71b19c6b0421..62949b489ca0d 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -198,8 +198,14 @@ struct LocalFormspecHandler : public TextDest if (g_settings->existsLocal(i.first) && g_settings->get(i.first) != i.second) { g_settings->set(i.first, i.second); - //std::cout << "Setting " << i.first << " set!" << std::endl; } + if (i.first.rfind("page_", 0) == 0) + { + m_client->getScript()->show_settings(i.first.substr(5)); + std::cout << "Found ..." << m_client << std::endl; + } + //std::cout << "Setting " << i.first << " set!" << std::endl; + } return; } diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 5af8758d417e2..32db7a528c63f 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -310,7 +310,7 @@ void ScriptApiClient::show_pause_menu(bool is_singleplayer, bool is_touchscreen, lua_pop(L, 1); } -void ScriptApiClient::show_settings() +void ScriptApiClient::show_settings(const std::string& page) { SCRIPTAPI_PRECHECKHEADER @@ -319,7 +319,14 @@ void ScriptApiClient::show_settings() lua_getglobal(L, "core"); lua_getfield(L, -1, "show_settings"); - PCALL_RES(lua_pcall(L, 0, 0, error_handler)); + if (page != "") + { + lua_pushstring(L, page.c_str()); + } + else + lua_pushnil(L); + + PCALL_RES(lua_pcall(L, 1, 0, error_handler)); lua_pop(L, 1); } diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index 61efa40bdae5a..c0f8a4196e812 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -61,7 +61,7 @@ class ScriptApiClient : virtual public ScriptApiBase bool on_inventory_open(Inventory *inventory); void show_pause_menu(bool is_singleplayer, bool is_touchscreen, const std::string& server_address); - void show_settings(); + void show_settings(const std::string& page = ""); void setEnv(ClientEnvironment *env); }; From 1f52edb14ce7cbc468dfd51c4f52976cbacdbd2f Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 02:41:15 -0400 Subject: [PATCH 06/12] Live FOV changes Probably should add a method for changing the FOV but I am tired. This also works with /set fov ###, so not sure why something like this isn't done already. --- src/client/camera.h | 2 ++ src/client/game.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/client/camera.h b/src/client/camera.h index 88533181b6769..0c432ac8da21a 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -228,7 +228,9 @@ class Camera Client *m_client; // Default Client FOV (as defined by the "fov" setting) +public: // TODO make a setter f32 m_cache_fov; +private: // Absolute camera position v3f m_camera_position; diff --git a/src/client/game.cpp b/src/client/game.cpp index 62949b489ca0d..68ce6067d7d53 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -202,7 +202,6 @@ struct LocalFormspecHandler : public TextDest if (i.first.rfind("page_", 0) == 0) { m_client->getScript()->show_settings(i.first.substr(5)); - std::cout << "Found ..." << m_client << std::endl; } //std::cout << "Setting " << i.first << " set!" << std::endl; @@ -1041,6 +1040,8 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("pause_on_lost_focus", &settingChangedCallback, this); + g_settings->registerChangedCallback("fov", + &settingChangedCallback, this); readSettings(); } @@ -4441,13 +4442,17 @@ void Game::readSettings() m_cache_enable_noclip = g_settings->getBool("noclip"); m_cache_enable_free_move = g_settings->getBool("free_move"); + m_cache_cam_smoothing = 0; if (g_settings->getBool("cinematic")) m_cache_cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing"); else m_cache_cam_smoothing = 1 - g_settings->getFloat("camera_smoothing"); - + + if (camera) + camera->m_cache_fov = g_settings->getFloat("fov"); + m_cache_cam_smoothing = rangelim(m_cache_cam_smoothing, 0.01f, 1.0f); m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0); From 4b74e8d07134abfb9d8279f690a0c70547571191 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 10:58:14 -0400 Subject: [PATCH 07/12] Move pause formspec events to Lua --- builtin/client/pause_menu.lua | 27 ++++++++++++- builtin/mainmenu/settings/dlg_settings.lua | 2 +- src/client/game.cpp | 45 +--------------------- src/script/cpp_api/s_client.cpp | 11 +----- src/script/cpp_api/s_client.h | 2 +- src/script/lua_api/l_client.cpp | 27 +++++++++++++ src/script/lua_api/l_client.h | 9 +++++ 7 files changed, 68 insertions(+), 55 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index bd867374efc67..87358843790aa 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -88,9 +88,27 @@ Menu/inventory open: end function core.show_pause_menu(is_singleplayer, is_touchscreen, address) - minetest.show_formspec("MT_PAUSE_MENU", menu_formspec(is_singleplayer, is_touchscreen, address)) + minetest.show_formspec("builtin:MT_PAUSE_MENU", menu_formspec(is_singleplayer, is_touchscreen, address)) end +core.register_on_formspec_input(function(formname, fields) + if formname ~= "builtin:MT_PAUSE_MENU" then return end + + if fields.btn_continue then + core.unpause() + elseif fields.btn_key_config then + core.key_config() -- Don't want this + elseif fields.btn_change_password then + core.change_password() + elseif fields.btn_exit_menu then + core.disconnect() + elseif fields.btn_exit_os then + core.exit_to_os() + end + + minetest.log(dump(fields)) +end) + local scriptpath = core.get_builtin_path() local path = scriptpath.."mainmenu"..DIR_DELIM.."settings" @@ -109,6 +127,13 @@ end load(true, false) +local data = {data = {}} +core.register_on_formspec_input(function(formname, fields) + local this = data + --buttonhandler(this, fields) +end) + + function core.show_settings(page_id) if not page_id then show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS", {}) diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index d8a96a1fb6dae..71483d9151161 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -633,7 +633,7 @@ function write_settings_early() end -local function buttonhandler(this, fields) +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 diff --git a/src/client/game.cpp b/src/client/game.cpp index 68ce6067d7d53..2fa1e2fb3743a 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -149,48 +149,7 @@ struct LocalFormspecHandler : public TextDest void gotText(const StringMap &fields) { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } - - else if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - else if (fields.find("btn_settings") != fields.end()) { - g_gamecallback->showSettings(); - } - - else if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } - - else if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); -#ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); -#endif - return; - } - - else if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - - else { - g_gamecallback->unpause(); - return; - } - - return; - } - - if (m_formname == "MT_PAUSE_MENU_SETTINGS") + if (m_formname == "MT_PAUSE_MENU_SETTINGS" && false) { // Loop through settings for (auto i : fields) @@ -201,7 +160,7 @@ struct LocalFormspecHandler : public TextDest } if (i.first.rfind("page_", 0) == 0) { - m_client->getScript()->show_settings(i.first.substr(5)); + //m_client->getScript()->show_settings(i.first.substr(5)); } //std::cout << "Setting " << i.first << " set!" << std::endl; diff --git a/src/script/cpp_api/s_client.cpp b/src/script/cpp_api/s_client.cpp index 32db7a528c63f..5af8758d417e2 100644 --- a/src/script/cpp_api/s_client.cpp +++ b/src/script/cpp_api/s_client.cpp @@ -310,7 +310,7 @@ void ScriptApiClient::show_pause_menu(bool is_singleplayer, bool is_touchscreen, lua_pop(L, 1); } -void ScriptApiClient::show_settings(const std::string& page) +void ScriptApiClient::show_settings() { SCRIPTAPI_PRECHECKHEADER @@ -319,14 +319,7 @@ void ScriptApiClient::show_settings(const std::string& page) lua_getglobal(L, "core"); lua_getfield(L, -1, "show_settings"); - if (page != "") - { - lua_pushstring(L, page.c_str()); - } - else - lua_pushnil(L); - - PCALL_RES(lua_pcall(L, 1, 0, error_handler)); + PCALL_RES(lua_pcall(L, 0, 0, error_handler)); lua_pop(L, 1); } diff --git a/src/script/cpp_api/s_client.h b/src/script/cpp_api/s_client.h index c0f8a4196e812..61efa40bdae5a 100644 --- a/src/script/cpp_api/s_client.h +++ b/src/script/cpp_api/s_client.h @@ -61,7 +61,7 @@ class ScriptApiClient : virtual public ScriptApiBase bool on_inventory_open(Inventory *inventory); void show_pause_menu(bool is_singleplayer, bool is_touchscreen, const std::string& server_address); - void show_settings(const std::string& page = ""); + void show_settings(); void setEnv(ClientEnvironment *env); }; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 912e212af14c6..9d6cdf3830536 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/client.h" #include "client/clientevent.h" #include "client/sound.h" +#include "client/renderingengine.h" #include "client/clientenvironment.h" #include "common/c_content.h" #include "common/c_converter.h" @@ -178,6 +179,29 @@ int ModApiClient::l_disconnect(lua_State *L) return 1; } +// unpause() +int ModApiClient::l_unpause(lua_State *L) +{ + g_gamecallback->unpause(); + //lua_pushboolean(L, true); + return 1; +} + +int ModApiClient::l_exit_to_os(lua_State *L) +{ + g_gamecallback->exitToOS(); +#ifndef __ANDROID__ + RenderingEngine::get_raw_device()->closeDevice(); +#endif + return 1; +} + +int ModApiClient::l_key_config(lua_State *L) +{ + g_gamecallback->keyConfig(); + return 1; +} + // gettext(text) int ModApiClient::l_gettext(lua_State *L) { @@ -366,6 +390,9 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(gettext); API_FCT(get_node_or_nil); API_FCT(disconnect); + API_FCT(unpause); + API_FCT(exit_to_os); + API_FCT(key_config); API_FCT(get_meta); API_FCT(get_server_info); API_FCT(get_item_def); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index c98e1bc9fbc1f..7f93d66ce9f5a 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -50,6 +50,15 @@ class ModApiClient : public ModApiBase // show_formspec(name, formspec) static int l_show_formspec(lua_State *L); + + // unpause() + static int l_unpause(lua_State *L); + + // exit_to_os() + static int l_exit_to_os(lua_State *L); + + // key_config() + static int l_key_config(lua_State *L); // send_respawn() static int l_send_respawn(lua_State *L); From b8959a0bac410cfe116ea13442248d470fac9b90 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Thu, 27 Jun 2024 12:27:58 -0400 Subject: [PATCH 08/12] Settings formspec demonstration This is really hacky, but it demonstrates that everything works (kinda) --- builtin/client/pause_menu.lua | 139 +++++++++++++++++++-- builtin/mainmenu/settings/dlg_settings.lua | 16 ++- 2 files changed, 138 insertions(+), 17 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 87358843790aa..8908954f89a25 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -100,13 +100,15 @@ core.register_on_formspec_input(function(formname, fields) core.key_config() -- Don't want this elseif fields.btn_change_password then core.change_password() + elseif fields.btn_settings then + core.show_settings() elseif fields.btn_exit_menu then core.disconnect() elseif fields.btn_exit_os then core.exit_to_os() end - minetest.log(dump(fields)) + return end) local scriptpath = core.get_builtin_path() @@ -125,19 +127,134 @@ function dialog_create(name, spec, buttonhandler, eventhandler) minetest.show_formspec(name, spec({})) end -load(true, false) -local data = {data = {}} +local settings_data = {} +settings_data.data = { + leftscroll = 0, + query = "", + rightscroll = 0, + components = {}, + page_id = "accessibility" +} + core.register_on_formspec_input(function(formname, fields) - local this = data - --buttonhandler(this, fields) -end) + if formname ~= "builtin:MT_PAUSE_MENU_SETTINGS" then return true end + --local this = data + --buttonhandler(settings_data, fields) + local dialogdata = settings_data.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 + local update = false + if fields.back then + this:delete() + return true + end -function core.show_settings(page_id) - if not page_id then - show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS", {}) - else - show_settings_client_formspec("MT_PAUSE_MENU_SETTINGS", {page_id = page_id}) + if fields.show_technical_names ~= nil then + local value = core.is_yes(fields.show_technical_names) + core.settings:set_bool("show_technical_names", value) + write_settings_early() + update = true + --return true + end + + if fields.show_advanced ~= nil then + local value = core.is_yes(fields.show_advanced) + core.settings:set_bool("show_advanced", value) + write_settings_early() + core.show_settings() + update = true + end + + -- enable_touch is a checkbox in a setting component. We handle this + -- setting differently so we can hide/show pages using the next if-statement + if fields.enable_touch ~= nil then + local value = core.is_yes(fields.enable_touch) + core.settings:set_bool("enable_touch", value) + write_settings_early() + core.show_settings() + update = true end + + if fields.show_advanced ~= nil or fields.enable_touch ~= nil then + local suggested_page_id = update_filtered_pages(dialogdata.query) + + dialogdata.components = nil + + if not filtered_page_by_id[dialogdata.page_id] then + dialogdata.leftscroll = 0 + dialogdata.rightscroll = 0 + + dialogdata.page_id = suggested_page_id + end + + 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 + core.show_settings() + return true + end + end + + if dialogdata.components then + for i, comp in ipairs(dialogdata.components) do + if comp.on_submit and comp:on_submit(fields, this) then + write_settings_early() + core.show_settings() + + -- Clear components so they regenerate + --dialogdata.components = nil + return true + end + if comp.setting and fields["reset_" .. i] then + core.settings:remove(comp.setting.name) + write_settings_early() + core.show_settings() + + -- Clear components so they regenerate + --dialogdata.components = nil + return true + end + end + end + + if update then + core.show_settings() + end + + return false +end) + +load(true, false) +--settings_data.data.page_id = update_filtered_pages("") + +function core.show_settings() + show_settings_client_formspec("builtin:MT_PAUSE_MENU_SETTINGS", settings_data.data) + core.unpause() end diff --git a/builtin/mainmenu/settings/dlg_settings.lua b/builtin/mainmenu/settings/dlg_settings.lua index 71483d9151161..d9691d13bd8c2 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -23,13 +23,13 @@ local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. "settings" .. DIR_DELIM .. "shadows_component.lua") local loaded = false -local full_settings + full_settings = {} local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") -local all_pages = {} -local page_by_id = {} -local filtered_pages = all_pages -local filtered_page_by_id = page_by_id + all_pages = {} + page_by_id = {} + filtered_pages = all_pages + filtered_page_by_id = page_by_id local function get_setting_info(name) @@ -288,7 +288,7 @@ local function filter_page_content(page, query_keywords) end -local function update_filtered_pages(query) +function update_filtered_pages(query) filtered_pages = {} filtered_page_by_id = {} @@ -638,6 +638,8 @@ function buttonhandler(this, fields) 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 + + minetest.log(dump(fields)) if fields.back then this:delete() @@ -709,6 +711,7 @@ function buttonhandler(this, fields) end end + if dialogdata.components then for i, comp in ipairs(dialogdata.components) do if comp.on_submit and comp:on_submit(fields, this) then write_settings_early() @@ -726,6 +729,7 @@ function buttonhandler(this, fields) return true end end + end return false end From b2792e59f735376889de310d844efeac49a7649a Mon Sep 17 00:00:00 2001 From: swagtoy Date: Fri, 28 Jun 2024 01:31:37 -0400 Subject: [PATCH 09/12] Reload graphics & dynamic shadows You can force reload graphics by showing the verbose debug page (press F5 a few times). It might be better to bind it to a specific keybind, but this was easier to implement and didn't require taking up another keybind. --- builtin/client/pause_menu.lua | 1 + src/client/clientenvironment.cpp | 6 ++++++ src/client/clientenvironment.h | 6 ++++-- src/client/clientobject.h | 1 + src/client/content_cao.cpp | 13 +++++++++++++ src/client/content_cao.h | 1 + src/client/game.cpp | 18 ++++++++++++++++++ src/client/render/factory.cpp | 2 +- src/gui/mainmenumanager.h | 7 +++++++ src/script/lua_api/l_client.cpp | 7 +++++++ src/script/lua_api/l_client.h | 3 +++ 11 files changed, 62 insertions(+), 3 deletions(-) diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 8908954f89a25..7d2481deda368 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -218,6 +218,7 @@ core.register_on_formspec_input(function(formname, fields) dialogdata.components = nil dialogdata.rightscroll = 0 core.show_settings() + core.reload_graphics() return true end end diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 7e1676ffe3ad6..7d38b538cf843 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -273,6 +273,9 @@ void ClientEnvironment::step(float dtime) auto cb_state = [this, dtime, update_lighting, day_night_ratio] (ClientActiveObject *cao) { // Step object cao->step(dtime, this); + + if (m_update_shadows) + cao->updateSceneShadows(); if (update_lighting) cao->updateLight(day_night_ratio); @@ -296,6 +299,9 @@ void ClientEnvironment::step(float dtime) ++i; } } + + if (m_update_shadows) + m_update_shadows = false; } void ClientEnvironment::addSimpleObject(ClientSimpleObject *simple) diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index bdb8b97260d6c..8f6e4350b2203 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -142,13 +142,14 @@ class ClientEnvironment : public Environment const std::set &getPlayerNames() { return m_player_names; } void addPlayerName(const std::string &name) { m_player_names.insert(name); } void removePlayerName(const std::string &name) { m_player_names.erase(name); } - void updateCameraOffset(const v3s16 &camera_offset) - { m_camera_offset = camera_offset; } + void updateCameraOffset(const v3s16 &camera_offset) { m_camera_offset = camera_offset; } + void requestUpdateShadows() { m_update_shadows = true; } v3s16 getCameraOffset() const { return m_camera_offset; } void updateFrameTime(bool is_paused); u64 getFrameTime() const { return m_frame_time; } u64 getFrameTimeDelta() const { return m_frame_dtime; } + private: ClientMap *m_map; @@ -162,6 +163,7 @@ class ClientEnvironment : public Environment IntervalLimiter m_active_object_light_update_interval; std::set m_player_names; v3s16 m_camera_offset; + bool m_update_shadows = false; u64 m_frame_time = 0; u64 m_frame_dtime = 0; u64 m_frame_time_pause_accumulator = 0; diff --git a/src/client/clientobject.h b/src/client/clientobject.h index f636813137629..83ef1e3461d0c 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -62,6 +62,7 @@ class ClientActiveObject : public ActiveObject virtual void updateAttachments() {}; virtual bool doShowSelectionBox() { return true; } + virtual void updateSceneShadows() {} // Step object in time virtual void step(float dtime, ClientEnvironment *env) {} diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 0044cc16e966c..e0207719a4f79 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -604,6 +604,17 @@ void GenericCAO::removeFromScene(bool permanent) m_client->getMinimap()->removeMarker(&m_marker); } +void GenericCAO::updateSceneShadows() +{ + if (scene::ISceneNode *node = getSceneNode()) { + if (m_matrixnode) + node->setParent(m_matrixnode); + + if (auto shadow = RenderingEngine::get_shadow_renderer()) + shadow->addNodeToShadowList(node); + } +} + void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) { m_smgr = smgr; @@ -833,6 +844,8 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) if (m_reset_textures_timer < 0) updateTextures(m_current_texture_modifier); + + if (scene::ISceneNode *node = getSceneNode()) { if (m_matrixnode) node->setParent(m_matrixnode); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 7fdcb73da2ce1..8e3a507bbadbf 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -249,6 +249,7 @@ class GenericCAO : public ClientActiveObject } void updateLight(u32 day_night_ratio) override; + void updateSceneShadows() override; void setNodeLight(const video::SColor &light); diff --git a/src/client/game.cpp b/src/client/game.cpp index 2fa1e2fb3743a..8cbfc59e22e13 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -739,6 +739,7 @@ class Game { void updateCameraOrientation(CameraOrientation *cam, float dtime); void updatePlayerControl(const CameraOrientation &cam); void updatePauseState(); + void reloadGraphics(); void step(f32 dtime); void processClientEvents(CameraOrientation *cam); void updateCamera(f32 dtime); @@ -1126,6 +1127,12 @@ bool Game::startup(bool *kill, return true; } +inline void Game::reloadGraphics() +{ + m_rendering_engine->initialize(client, hud); + client->getEnv().requestUpdateShadows(); + +} void Game::run() { @@ -1802,6 +1809,8 @@ bool Game::getServerContent(bool *aborted) } + + /****************************************************************************/ /**************************************************************************** Run @@ -1865,9 +1874,15 @@ inline bool Game::handleCallbacks() if (g_gamecallback->unpause_requested) { m_is_paused = false; + m_rendering_engine->initialize(client, hud); g_gamecallback->unpause_requested = false; } + if (g_gamecallback->reload_graphics_requested) { + reloadGraphics(); + g_gamecallback->reload_graphics_requested = false; + } + if (g_gamecallback->show_settings_requested) { if (client->modsLoaded()) { @@ -1911,7 +1926,9 @@ void Game::updateDebugState() m_game_ui->m_flags.show_basic_debug = false; } else if (m_game_ui->m_flags.show_minimal_debug) { if (has_basic_debug) + { m_game_ui->m_flags.show_basic_debug = true; + } } if (!has_basic_debug) hud->disableBlockBounds(); @@ -2523,6 +2540,7 @@ void Game::toggleDebug() } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { if (has_basic_debug) m_game_ui->m_flags.show_basic_debug = true; + reloadGraphics(); m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { diff --git a/src/client/render/factory.cpp b/src/client/render/factory.cpp index 2e9d5eb5fa2bd..dbb33e9f3a385 100644 --- a/src/client/render/factory.cpp +++ b/src/client/render/factory.cpp @@ -82,4 +82,4 @@ void createPipeline(const std::string &stereo_mode, IrrlichtDevice *device, Clie // fallback to plain renderer errorstream << "Invalid rendering mode: " << stereo_mode << std::endl; populatePlainPipeline(result.pipeline, client); -} \ No newline at end of file +} diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h index 06c8d22c25c7d..08c5a9242ae9e 100644 --- a/src/gui/mainmenumanager.h +++ b/src/gui/mainmenumanager.h @@ -35,6 +35,7 @@ class IGameCallback virtual void changePassword() = 0; virtual void changeVolume() = 0; virtual void unpause() = 0; + virtual void reloadGraphics() = 0; virtual void showSettings() = 0; virtual void showOpenURLDialog(const std::string &url) = 0; virtual void signalKeyConfigChange() = 0; @@ -154,6 +155,11 @@ class MainGameCallback : public IGameCallback unpause_requested = true; } + void reloadGraphics() override + { + reload_graphics_requested = true; + } + void showSettings() override { show_settings_requested = true; @@ -165,6 +171,7 @@ class MainGameCallback : public IGameCallback bool keyconfig_requested = false; bool shutdown_requested = false; bool unpause_requested = false; + bool reload_graphics_requested = false; bool show_settings_requested = false; bool keyconfig_changed = false; std::string show_open_url_dialog = ""; diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 9d6cdf3830536..110426ad6fbea 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -196,6 +196,12 @@ int ModApiClient::l_exit_to_os(lua_State *L) return 1; } +int ModApiClient::l_reload_graphics(lua_State *L) +{ + g_gamecallback->reloadGraphics(); + return 1; +} + int ModApiClient::l_key_config(lua_State *L) { g_gamecallback->keyConfig(); @@ -392,6 +398,7 @@ void ModApiClient::Initialize(lua_State *L, int top) API_FCT(disconnect); API_FCT(unpause); API_FCT(exit_to_os); + API_FCT(reload_graphics); API_FCT(key_config); API_FCT(get_meta); API_FCT(get_server_info); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 7f93d66ce9f5a..b26faa5a47fb9 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -57,6 +57,9 @@ class ModApiClient : public ModApiBase // exit_to_os() static int l_exit_to_os(lua_State *L); + // reload_graphics() + static int l_reload_graphics(lua_State *L); + // key_config() static int l_key_config(lua_State *L); From 5de3ebf9e3fb9b57c08693fa43696fdc0a1fd994 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Fri, 28 Jun 2024 16:22:20 -0400 Subject: [PATCH 10/12] Refactor settings to not depend on FSTK, move reloadGraphics call to wireframe debug instead Still need to nuke the old FSTK settings pages and fix the main menu --- builtin/client/pause_menu.lua | 149 +--- builtin/settings/components.lua | 407 +++++++++ .../settings/generate_from_settingtypes.lua | 139 ++++ builtin/settings/gui_change_mapgen_flags.lua | 252 ++++++ builtin/settings/gui_settings.lua | 773 ++++++++++++++++++ builtin/settings/init.lua | 28 + builtin/settings/settingtypes.lua | 507 ++++++++++++ builtin/settings/shadows_component.lua | 121 +++ src/client/game.cpp | 2 +- 9 files changed, 2247 insertions(+), 131 deletions(-) create mode 100644 builtin/settings/components.lua create mode 100644 builtin/settings/generate_from_settingtypes.lua create mode 100644 builtin/settings/gui_change_mapgen_flags.lua create mode 100644 builtin/settings/gui_settings.lua create mode 100644 builtin/settings/init.lua create mode 100644 builtin/settings/settingtypes.lua create mode 100644 builtin/settings/shadows_component.lua diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 7d2481deda368..18d0d064fee72 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -94,9 +94,7 @@ end core.register_on_formspec_input(function(formname, fields) if formname ~= "builtin:MT_PAUSE_MENU" then return end - if fields.btn_continue then - core.unpause() - elseif fields.btn_key_config then + if fields.btn_key_config then core.key_config() -- Don't want this elseif fields.btn_change_password then core.change_password() @@ -106,156 +104,47 @@ core.register_on_formspec_input(function(formname, fields) core.disconnect() elseif fields.btn_exit_os then core.exit_to_os() + elseif fields.quit or fields.btn_continue then + core.unpause() end return end) -local scriptpath = core.get_builtin_path() -local path = scriptpath.."mainmenu"..DIR_DELIM.."settings" +local settingspath = core.get_builtin_path().."settings" function core.get_mainmenu_path() - return scriptpath.."mainmenu" + return settingspath end -defaulttexturedir = "" -dofile(path .. DIR_DELIM .. "settingtypes.lua") -dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") -dofile(path .. DIR_DELIM .. "dlg_settings.lua") +defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM +dofile(settingspath .. DIR_DELIM .. "settingtypes.lua") +dofile(settingspath .. DIR_DELIM .. "gui_change_mapgen_flags.lua") +dofile(settingspath .. DIR_DELIM .. "gui_settings.lua") + function dialog_create(name, spec, buttonhandler, eventhandler) minetest.show_formspec(name, spec({})) end -local settings_data = {} -settings_data.data = { - leftscroll = 0, - query = "", - rightscroll = 0, - components = {}, - page_id = "accessibility" -} - core.register_on_formspec_input(function(formname, fields) if formname ~= "builtin:MT_PAUSE_MENU_SETTINGS" then return true end - --local this = data - --buttonhandler(settings_data, fields) - local dialogdata = settings_data.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 - local update = false - - 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) - write_settings_early() - update = true - --return true - end - - if fields.show_advanced ~= nil then - local value = core.is_yes(fields.show_advanced) - core.settings:set_bool("show_advanced", value) - write_settings_early() - core.show_settings() - update = true - end - - -- enable_touch is a checkbox in a setting component. We handle this - -- setting differently so we can hide/show pages using the next if-statement - if fields.enable_touch ~= nil then - local value = core.is_yes(fields.enable_touch) - core.settings:set_bool("enable_touch", value) - write_settings_early() - core.show_settings() - update = true - end - - if fields.show_advanced ~= nil or fields.enable_touch ~= nil then - local suggested_page_id = update_filtered_pages(dialogdata.query) - - dialogdata.components = nil - - if not filtered_page_by_id[dialogdata.page_id] then - dialogdata.leftscroll = 0 - dialogdata.rightscroll = 0 - - dialogdata.page_id = suggested_page_id - end - - 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 - core.show_settings() - core.reload_graphics() - return true - end - end - - if dialogdata.components then - for i, comp in ipairs(dialogdata.components) do - if comp.on_submit and comp:on_submit(fields, this) then - write_settings_early() - core.show_settings() - - -- Clear components so they regenerate - --dialogdata.components = nil - return true - end - if comp.setting and fields["reset_" .. i] then - core.settings:remove(comp.setting.name) - write_settings_early() - core.show_settings() - - -- Clear components so they regenerate - --dialogdata.components = nil - return true - end - end - end - if update then + settings:buttonhandler(fields) + if settings.refresh_page then + --minetest.show_formspec("", "") core.show_settings() + core.reload_graphics() + settings.refresh_page = false end - - return false + return true end) -load(true, false) ---settings_data.data.page_id = update_filtered_pages("") +settings.load(true, false) function core.show_settings() - show_settings_client_formspec("builtin:MT_PAUSE_MENU_SETTINGS", settings_data.data) + settings.show_client_formspec("builtin:MT_PAUSE_MENU_SETTINGS", settings) core.unpause() end diff --git a/builtin/settings/components.lua b/builtin/settings/components.lua new file mode 100644 index 0000000000000..51cc0c95bb489 --- /dev/null +++ b/builtin/settings/components.lua @@ -0,0 +1,407 @@ +--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. +-- * `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 + + +--- 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) .. "]", + } + + local value = core.settings:get(setting.name) or setting.default + self.resettable = core.settings:has(setting.name) + + 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 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 diff --git a/builtin/settings/generate_from_settingtypes.lua b/builtin/settings/generate_from_settingtypes.lua new file mode 100644 index 0000000000000..e18b2c2d6077a --- /dev/null +++ b/builtin/settings/generate_from_settingtypes.lua @@ -0,0 +1,139 @@ +local concat = table.concat +local insert = table.insert +local sprintf = string.format +local rep = string.rep + +local minetest_example_header = [[ +# This file contains a list of all available settings and their default value for minetest.conf + +# By default, all the settings are commented and not functional. +# Uncomment settings by removing the preceding #. + +# minetest.conf is read by default from: +# ../minetest.conf +# ../../minetest.conf +# Any other path can be chosen by passing the path as a parameter +# to the program, eg. "minetest.exe --config ../minetest.conf.example". + +# Further documentation: +# https://wiki.minetest.net/ + +]] + +local group_format_template = [[ +# %s = { +# offset = %s, +# scale = %s, +# spread = (%s, %s, %s), +# seed = %s, +# octaves = %s, +# persistence = %s, +# lacunarity = %s, +# flags =%s +# } + +]] + +local function create_minetest_conf_example(settings) + local result = { minetest_example_header } + for _, entry in ipairs(settings) do + if entry.type == "category" then + if entry.level == 0 then + insert(result, "#\n# " .. entry.name .. "\n#\n\n") + else + insert(result, rep("#", entry.level)) + insert(result, "# " .. entry.name .. "\n\n") + end + else + local group_format = false + if entry.noise_params and entry.values then + if entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then + group_format = true + end + end + if entry.comment ~= "" then + for _, comment_line in ipairs(entry.comment:split("\n", true)) do + if comment_line == "" then + insert(result, "#\n") + else + insert(result, "# " .. comment_line .. "\n") + end + end + end + if entry.type == "key" then + local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h" + insert(result, "# " .. line .. "\n") + end + insert(result, "# type: " .. entry.type) + if entry.min then + insert(result, " min: " .. entry.min) + end + if entry.max then + insert(result, " max: " .. entry.max) + end + if entry.values and entry.noise_params == nil then + insert(result, " values: " .. concat(entry.values, ", ")) + end + if entry.possible then + insert(result, " possible values: " .. concat(entry.possible, ", ")) + end + insert(result, "\n") + if group_format == true then + local flags = entry.values[10] + if flags ~= "" then + flags = " "..flags + end + insert(result, sprintf(group_format_template, entry.name, entry.values[1], + entry.values[2], entry.values[3], entry.values[4], entry.values[5], + entry.values[6], entry.values[7], entry.values[8], entry.values[9], + flags)) + else + local append + if entry.default ~= "" then + append = " " .. entry.default + end + insert(result, sprintf("# %s =%s\n\n", entry.name, append or "")) + end + end + end + return concat(result) +end + +local translation_file_header = [[ +// This file is automatically generated +// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files +// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua + +fake_function() {]] + +local function create_translation_file(settings) + local result = { translation_file_header } + for _, entry in ipairs(settings) do + if entry.type == "category" then + insert(result, sprintf("\tgettext(%q);", entry.name)) + else + if entry.readable_name then + insert(result, sprintf("\tgettext(%q);", entry.readable_name)) + end + if entry.comment ~= "" then + local comment_escaped = entry.comment:gsub("\n", "\\n") + comment_escaped = comment_escaped:gsub("\"", "\\\"") + insert(result, "\tgettext(\"" .. comment_escaped .. "\");") + end + end + end + insert(result, "}\n") + return concat(result, "\n") +end + +local file = assert(io.open("minetest.conf.example", "w")) +file:write(create_minetest_conf_example(settingtypes.parse_config_file(true, false))) +file:close() + +file = assert(io.open("src/settings_translation_file.cpp", "w")) +-- If 'minetest.conf.example' appears in the 'bin' folder, the line below may have to be +-- used instead. The file will also appear in the 'bin' folder. +--file = assert(io.open("settings_translation_file.cpp", "w")) +-- We don't want hidden settings to be translated, so we set read_all to false. +file:write(create_translation_file(settingtypes.parse_config_file(false, false))) +file:close() diff --git a/builtin/settings/gui_change_mapgen_flags.lua b/builtin/settings/gui_change_mapgen_flags.lua new file mode 100644 index 0000000000000..570d184da63d2 --- /dev/null +++ b/builtin/settings/gui_change_mapgen_flags.lua @@ -0,0 +1,252 @@ +--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 = 2 + local description_height = 1.5 + + local t = get_current_np_group(setting) + local dimension = 3 + if setting.type == "noise_params_2d" then + dimension = 2 + end + + 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 the settings menu. ]] + .. 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 + the settings menu. ]] + .. 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 + the settings menu. ]] + .. 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 + + for name, value in pairs(fields) do + if name:sub(1, 3) == "cb_" then + checkboxes[name] = core.is_yes(value) + return false -- Don't update the formspec! + end + 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/settings/gui_settings.lua b/builtin/settings/gui_settings.lua new file mode 100644 index 0000000000000..d32ef53a9a60a --- /dev/null +++ b/builtin/settings/gui_settings.lua @@ -0,0 +1,773 @@ +--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. + + +settings = {} + +settings.loaded = false +settings.full_settings = {} +settings.all_pages = {} +settings.page_by_id = {} +settings.filtered_pages = settings.all_pages +settings.filtered_pages_by_id = settings.page_by_id + +local component_funcs = dofile(core.get_builtin_path() .. DIR_DELIM .. + "settings" .. DIR_DELIM .. "components.lua") + +local shadows_component = dofile(core.get_builtin_path() .. DIR_DELIM .. + "settings" .. DIR_DELIM .. "shadows_component.lua") + +local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") +local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") + + +local function get_setting_info(name) + for _, entry in ipairs(settings.full_settings) do + if entry.type ~= "category" and entry.name == name then + return entry + end + end + + return nil +end + + +function settings.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 settings.page_by_id[page.id], "Page " .. page.id .. " already registered") + + settings.all_pages[#settings.all_pages + 1] = page + settings.page_by_id[page.id] = page + return page +end + + +local function load_settingtypes() + local page = nil + local section = nil + local function ensure_page_started() + if not page then + page = settings.add_page({ + id = (section or "general"):lower():gsub(" ", "_"), + title = section or fgettext_ne("General"), + section = section, + content = {}, + }) + end + end + + for _, entry in ipairs(settings.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 = {}, + } + + page = settings.add_page(page) + elseif entry.level == 2 then + ensure_page_started() + page.content[#page.content + 1] = { + heading = fgettext_ne(entry.readable_name or entry.name), + } + end + else + ensure_page_started() + page.content[#page.content + 1] = entry.name + end + end +end + + +function settings.load(read_all, parse_mods) + read_all = read_all == nil and false or read_all + parse_mods = parse_mods == nil and true or parse_mods + + if settings.loaded then + return + end + settings.loaded = true + + settings.full_settings = settingtypes.parse_config_file(read_all, parse_mods) + + local change_keys = { + query_text = "Controls", + requires = { + keyboard_mouse = true, + }, + 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("Controls")), 0.8 + end, + on_submit = function(self, fields) + if fields.btn_change_keys then + core.show_keys_menu() + end + end, + } + + settings.add_page({ + id = "accessibility", + title = fgettext_ne("Accessibility"), + content = { + "language", + { heading = fgettext_ne("General") }, + "font_size", + "chat_font_size", + "gui_scaling", + "hud_scaling", + "show_nametag_backgrounds", + { heading = fgettext_ne("Chat") }, + "console_height", + "console_alpha", + "console_color", + { heading = fgettext_ne("Controls") }, + "autojump", + "safe_dig_and_place", + { heading = fgettext_ne("Movement") }, + "arm_inertia", + "view_bobbing_amount", + "fall_bobbing_amount", + }, + }) + + load_settingtypes() + + if settings.page_by_id.controls_keyboard_and_mouse then + table.insert(settings.page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) + do + local content = settings.page_by_id.graphics_and_audio_shaders.content + local idx = table.indexof(content, "enable_dynamic_shadows") + table.insert(content, idx, shadows_component) + end + + -- These must not be translated, as they need to show in the local + -- language no matter the user's current language. + -- This list must be kept in sync with src/unsupported_language_list.txt. + get_setting_info("language").option_labels = { + [""] = fgettext_ne("(Use system language)"), + --ar = " [ar]", blacklisted + be = "Беларуская [be]", + bg = "Български [bg]", + ca = "Català [ca]", + cs = "Česky [cs]", + cy = "Cymraeg [cy]", + da = "Dansk [da]", + de = "Deutsch [de]", + --dv = " [dv]", blacklisted + el = "Ελληνικά [el]", + en = "English [en]", + eo = "Esperanto [eo]", + es = "Español [es]", + et = "Eesti [et]", + eu = "Euskara [eu]", + fi = "Suomi [fi]", + fil = "Wikang Filipino [fil]", + fr = "Français [fr]", + gd = "Gàidhlig [gd]", + gl = "Galego [gl]", + --he = " [he]", blacklisted + --hi = " [hi]", blacklisted + hu = "Magyar [hu]", + id = "Bahasa Indonesia [id]", + it = "Italiano [it]", + ja = "日本語 [ja]", + jbo = "Lojban [jbo]", + kk = "Қазақша [kk]", + --kn = " [kn]", blacklisted + ko = "한국어 [ko]", + ky = "Kırgızca / Кыргызча [ky]", + lt = "Lietuvių [lt]", + lv = "Latviešu [lv]", + mn = "Монгол [mn]", + mr = "मराठी [mr]", + ms = "Bahasa Melayu [ms]", + --ms_Arab = " [ms_Arab]", blacklisted + nb = "Norsk Bokmål [nb]", + nl = "Nederlands [nl]", + nn = "Norsk Nynorsk [nn]", + oc = "Occitan [oc]", + pl = "Polski [pl]", + pt = "Português [pt]", + pt_BR = "Português do Brasil [pt_BR]", + ro = "Română [ro]", + ru = "Русский [ru]", + sk = "Slovenčina [sk]", + sl = "Slovenščina [sl]", + sr_Cyrl = "Српски [sr_Cyrl]", + sr_Latn = "Srpski (Latinica) [sr_Latn]", + sv = "Svenska [sv]", + sw = "Kiswahili [sw]", + --th = " [th]", blacklisted + tr = "Türkçe [tr]", + tt = "Tatarça [tt]", + uk = "Українська [uk]", + vi = "Tiếng Việt [vi]", + zh_CN = "中文 (简体) [zh_CN]", + zh_TW = "正體中文 (繁體) [zh_TW]", + } + end + +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, 0 + 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 + + +function settings.update_filtered_pages(query) + settings.filtered_pages = {} + settings.filtered_pages_by_id = {} + + 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(settings.all_pages) do + local content, page_weight = filter_page_content(page, query_keywords) + if settings.page_has_contents(page, content) then + local new_page = table.copy(page) + new_page.content = content + + settings.filtered_pages[#settings.filtered_pages + 1] = new_page + settings.filtered_pages_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 check_requirements(name, requires) + if requires == nil then + return true + end + + local video_driver = core.get_active_driver() + local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2" + local special = { + android = PLATFORM == "Android", + desktop = PLATFORM ~= "Android", + touchscreen_gui = core.settings:get_bool("enable_touch"), + keyboard_mouse = not core.settings:get_bool("enable_touch"), + shaders_support = shaders_support, + shaders = core.settings:get_bool("enable_shaders") and shaders_support, + opengl = video_driver == "opengl", + gles = video_driver:sub(1, 5) == "ogles", + } + + for req_key, req_value in pairs(requires) do + if special[req_key] == nil then + local required_setting = get_setting_info(req_key) + if required_setting == nil then + core.log("warning", "Unknown setting " .. req_key .. " required by " .. name) + end + local actual_value = core.settings:get_bool(req_key, + required_setting and core.is_yes(required_setting.default)) + if actual_value ~= req_value then + return false + end + elseif special[req_key] ~= req_value then + return false + end + end + + return true +end + + +function settings.page_has_contents(page, actual_content) + local is_advanced = + page.id:sub(1, #"client_and_server") == "client_and_server" or + page.id:sub(1, #"mapgen") == "mapgen" or + page.id:sub(1, #"advanced") == "advanced" + local show_advanced = core.settings:get_bool("show_advanced") + if is_advanced and not show_advanced then + return false + end + + for _, item in ipairs(actual_content) do + if item == false or item.heading then --luacheck: ignore + -- skip + elseif type(item) == "string" then + local setting = get_setting_info(item) + assert(setting, "Unknown setting: " .. item) + if check_requirements(setting.name, setting.requires) then + return true + end + elseif item.get_formspec then + if check_requirements(item.id, item.requires) then + return true + end + else + error("Unknown content in page: " .. dump(item)) + end + end + + return false +end + + +local function build_page_components(page) + -- Filter settings based on requirements + local content = {} + local last_heading + for _, item in ipairs(page.content) do + if item == false then --luacheck: ignore + -- skip + elseif item.heading then + last_heading = item + else + local name, requires + if type(item) == "string" then + local setting = get_setting_info(item) + assert(setting, "Unknown setting: " .. item) + name = setting.name + requires = setting.requires + elseif item.get_formspec then + name = item.id + requires = item.requires + else + error("Unknown content in page: " .. dump(item)) + end + + if check_requirements(name, requires) then + if last_heading then + content[#content + 1] = last_heading + last_heading = nil + end + content[#content + 1] = item + end + end + end + + -- Create components + local retval = {} + for i, item in ipairs(content) do + if type(item) == "string" then + local setting = get_setting_info(item) + local component_func = component_funcs[setting.type] + assert(component_func, "Unknown setting type: " .. setting.type) + retval[i] = component_func(setting) + elseif item.get_formspec then + retval[i] = item + elseif item.heading then + retval[i] = component_funcs.heading(item.heading) + end + 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 + + +function settings.get_formspec() + local page_id = settings.page_id or "accessibility" + local page = settings.filtered_pages_by_id[page_id] + + local extra_h = 1 -- not included in tabsize.height + local tabsize = { + width = core.settings:get_bool("enable_touch") and 16.5 or 15.5, + height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12, + } + + local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4 + + local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25 + local left_pane_padding = 0.25 + local search_width = left_pane_width + scrollbar_w - (0.75 * 2) + + local back_w = 3 + local checkbox_w = (tabsize.width - back_w - 2*0.2) / 2 + local show_technical_names = core.settings:get_bool("show_technical_names") + local show_advanced = core.settings:get_bool("show_advanced") + + formspec_show_hack = not formspec_show_hack + + local fs = { + "formspec_version[6]", + "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", + core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", + "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,%f;%f,0.8;back;%s]"):format( + tabsize.height + 0.2, back_w, fgettext("Back")), + + ("box[%f,%f;%f,0.8;#0000008C]"):format( + back_w + 0.2, tabsize.height + 0.2, checkbox_w), + ("checkbox[%f,%f;show_technical_names;%s;%s]"):format( + back_w + 2*0.2, tabsize.height + 0.6, + fgettext("Show technical names"), tostring(show_technical_names)), + + ("box[%f,%f;%f,0.8;#0000008C]"):format( + back_w + 2*0.2 + checkbox_w, tabsize.height + 0.2, checkbox_w), + ("checkbox[%f,%f;show_advanced;%s;%s]"):format( + back_w + 3*0.2 + checkbox_w, tabsize.height + 0.6, + fgettext("Show advanced settings"), tostring(show_advanced)), + + "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;", + core.formspec_escape(settings.query or ""), "]", + "field_enter_after_edit[search_query;true]", + "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(settings.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-left_pane_padding, 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-left_pane_padding, other_page.id, fgettext(other_page.title)) + y = y + 0.82 + end + + if #settings.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, settings.leftscroll or 0) + end + + fs[#fs + 1] = "style_type[button;border=;bgcolor=]" + + if not settings.components then + settings.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(settings.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.resettable and comp.setting + 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 + local default = comp.setting.default + local reset_tooltip = default and + fgettext("Reset setting to default ($1)", tostring(default)) or + fgettext("Reset setting to default") + 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, reset_tooltip) + 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, settings.rightscroll or 0) + end + + return table.concat(fs, "") +end + + +-- On Android, closing the app via the "Recents screen" won't result in a clean +-- exit, discarding any setting changes made by the user. +-- To avoid that, we write the settings file in more cases on Android. +function write_settings_early() + if PLATFORM == "Android" then + core.settings:write() + end +end + + +function settings.buttonhandler(this, fields) + this.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or this.leftscroll + this.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or this.rightscroll + this.query = fields.search_query + + if fields.back and this.is_dlg 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) + write_settings_early() + this.refresh_page = true + return true + end + + if fields.show_advanced ~= nil then + local value = core.is_yes(fields.show_advanced) + core.settings:set_bool("show_advanced", value) + write_settings_early() + this.refresh_page = true + end + + -- enable_touch is a checkbox in a setting component. We handle this + -- setting differently so we can hide/show pages using the next if-statement + if fields.enable_touch ~= nil then + local value = core.is_yes(fields.enable_touch) + core.settings:set_bool("enable_touch", value) + write_settings_early() + this.refresh_page = true + end + + if fields.show_advanced ~= nil or fields.enable_touch ~= nil then + local suggested_page_id = settings.update_filtered_pages(this.query) + + this.components = nil + + if not settings.filtered_pages_by_id[this.page_id] then + this.leftscroll = 0 + this.rightscroll = 0 + + this.page_id = suggested_page_id + this.refresh_page = true + end + + return true + end + + if fields.search or fields.key_enter_field == "search_query" then + this.components = nil + this.leftscroll = 0 + this.rightscroll = 0 + + this.page_id = settings.update_filtered_pages(this.query) + this.refresh_page = true + + return true + end + if fields.search_clear then + this.query = "" + this.components = nil + this.leftscroll = 0 + this.rightscroll = 0 + + this.page_id = settings.update_filtered_pages("") + this.refresh_page = true + return true + end + + for _, page in ipairs(settings.all_pages) do + if fields["page_" .. page.id] then + this.page_id = page.id + this.components = nil + this.rightscroll = 0 + this.refresh_page = true + return true + end + end + + if this.components then + for i, comp in ipairs(this.components) do + if comp.on_submit and comp:on_submit(fields, this) then + write_settings_early() + + -- Clear components so they regenerate + this.components = nil + this.refresh_page = true + return true + end + if comp.setting and fields["reset_" .. i] then + core.settings:remove(comp.setting.name) + write_settings_early() + + -- Clear components so they regenerate + this.components = nil + this.refresh_page = true + return true + end + end + end + + return false +end + + +local function eventhandler(event) + if event == "DialogShow" then + -- Don't show the "MINETEST" header behind the dialog. + mm_game_theme.set_engine(true) + return true + end + if event == "FullscreenChange" then + -- Refresh the formspec to keep the fullscreen checkbox up to date. + ui.update() + return true + end + + return false +end + + +function create_settings_dlg() + settings.load() + local dlg = dialog_create("dlg_settings", settings.get_formspec, settings.buttonhandler, eventhandler) + + dlg.data.page_id = settings.update_filtered_pages("") + + return dlg +end + +function settings.show_client_formspec(name, data) + minetest.show_formspec(name, settings.get_formspec(data)) +end diff --git a/builtin/settings/init.lua b/builtin/settings/init.lua new file mode 100644 index 0000000000000..63f7f7673b5a5 --- /dev/null +++ b/builtin/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_builtin_path() .. DIR_DELIM .. "settings" + +dofile(path .. DIR_DELIM .. "settingtypes.lua") +dofile(path .. DIR_DELIM .. "gui_change_mapgen_flags.lua") +dofile(path .. DIR_DELIM .. "gui_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'. + +-- dofile(path .. DIR_DELIM .. "generate_from_settingtypes.lua") diff --git a/builtin/settings/settingtypes.lua b/builtin/settings/settingtypes.lua new file mode 100644 index 0000000000000..4e7d9067f6b43 --- /dev/null +++ b/builtin/settings/settingtypes.lua @@ -0,0 +1,507 @@ +--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_match = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") + if comment_match then + settings.current_comment[#settings.current_comment + 1] = comment_match + 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 + local category_level = stars:len() + base_level + + if settings.current_hide_level then + if settings.current_hide_level < category_level then + -- Skip this category, it's inside a hidden category. + return + else + -- The start of this category marks the end of a hidden category. + settings.current_hide_level = nil + end + end + + if not read_all and category:sub(1, 5) == "Hide:" then + -- This category is hidden. + settings.current_hide_level = category_level + return + end + + table.insert(settings, { + name = category, + level = category_level, + type = "category", + }) + return + end + + if settings.current_hide_level then + -- Ignore this line, we're inside a hidden 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 + + local requires = {} + local last_line = #current_comment > 0 and current_comment[#current_comment]:trim() + if last_line and last_line:lower():sub(1, 9) == "requires:" then + local parts = last_line:sub(10):split(",") + current_comment[#current_comment] = nil + + for _, part in ipairs(parts) do + part = part:trim() + + local value = true + if part:sub(1, 1) == "!" then + value = false + part = part:sub(2):trim() + end + + requires[part] = value + end + end + + if readable_name == "" then + readable_name = nil + end + local remaining_line = line:sub(first_part:len() + 1) + + local comment = table.concat(current_comment, "\n"):trim() + + 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, + requires = requires, + comment = 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, + requires = requires, + comment = 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) + 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, + requires = requires, + comment = 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, + requires = requires, + comment = 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, + requires = requires, + comment = 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), + requires = requires, + comment = 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, + requires = requires, + comment = 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), + requires = requires, + comment = 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 = {} + result.current_hide_level = nil + + 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 + result.current_hide_level = 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_true_builtin_path and core.get_true_builtin_path() or core.get_builtin_path()) .. FILENAME + local file = io.open(builtin_path, "r") + if not file then + core.log("error", "Can't load " .. builtin_path) + 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.path, + readable_name = game.title, + level = 1, + type = "category", + }) + + parse_single_file(file, path, read_all, settings, 2, false) + + file:close() + end + end + + -- Parse mods + pkgmgr.load_all() + local mods_category_initialized = false + local mods = pkgmgr.global_mods:get_list() + 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.path, + readable_name = mod.title or mod.name, + 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.path, + readable_name = mod.title or 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/settings/shadows_component.lua b/builtin/settings/shadows_component.lua new file mode 100644 index 0000000000000..93c071bf78e5a --- /dev/null +++ b/builtin/settings/shadows_component.lua @@ -0,0 +1,121 @@ +--Minetest +--Copyright (C) 2021-2 x2048 +--Copyright (C) 2022-3 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"), + fgettext("Custom"), +} +local PRESET_DISABLED = 1 +local PRESET_CUSTOM = #shadow_levels_labels + + +-- max distance, texture size, texture_32bit, filters, map color +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 function detect_mapping_idx() + if not core.settings:get_bool("enable_dynamic_shadows", false) then + return PRESET_DISABLED + end + + local shadow_map_max_distance = tonumber(core.settings:get("shadow_map_max_distance")) + local shadow_map_texture_size = tonumber(core.settings:get("shadow_map_texture_size")) + local shadow_map_texture_32bit = core.settings:get_bool("shadow_map_texture_32bit", false) + local shadow_filters = tonumber(core.settings:get("shadow_filters")) + local shadow_map_color = core.settings:get_bool("shadow_map_color", false) + + for i = 2, 6 do + local preset = shadow_presets[i] + if preset[1] == shadow_map_max_distance and + preset[2] == shadow_map_texture_size and + preset[3] == shadow_map_texture_32bit and + preset[4] == shadow_filters and + preset[5] == shadow_map_color then + return i + end + end + return PRESET_CUSTOM +end + + +local function apply_preset(preset) + if preset then + core.settings:set_bool("enable_dynamic_shadows", true) + core.settings:set("shadow_map_max_distance", preset[1]) + core.settings:set("shadow_map_texture_size", preset[2]) + core.settings:set_bool("shadow_map_texture_32bit", preset[3]) + core.settings:set("shadow_filters", preset[4]) + core.settings:set_bool("shadow_map_color", preset[5]) + else + core.settings:set_bool("enable_dynamic_shadows", false) + end +end + + +return { + query_text = "Shadows", + requires = { + shaders = true, + opengl = true, + }, + get_formspec = function(self, avail_w) + local labels = table.copy(shadow_levels_labels) + local idx = detect_mapping_idx() + + -- Remove "custom" if not already selected + if idx ~= PRESET_CUSTOM then + table.remove(labels, PRESET_CUSTOM) + end + + local fs = + "label[0,0.2;" .. fgettext("Dynamic shadows") .. "]" .. + "dropdown[0,0.4;3,0.8;dd_shadows;" .. table.concat(labels, ",") .. ";" .. idx .. ";true]" .. + "label[0,1.5;" .. core.colorize("#bbb", fgettext("(The game will need to enable shadows as well)")) .. "]" + return fs, 1.8 + end, + on_submit = function(self, fields) + if fields.dd_shadows then + local old_shadow_level_idx = detect_mapping_idx() + local shadow_level_idx = tonumber(fields.dd_shadows) + if shadow_level_idx == nil or shadow_level_idx == old_shadow_level_idx then + return false + end + + if shadow_level_idx == PRESET_CUSTOM then + core.settings:set_bool("enable_dynamic_shadows", true) + return true + end + + local preset = shadow_presets[shadow_level_idx] + apply_preset(preset) + return true + end + end, +} diff --git a/src/client/game.cpp b/src/client/game.cpp index 8cbfc59e22e13..0d3022bcf4711 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2540,7 +2540,6 @@ void Game::toggleDebug() } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { if (has_basic_debug) m_game_ui->m_flags.show_basic_debug = true; - reloadGraphics(); m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { @@ -2549,6 +2548,7 @@ void Game::toggleDebug() m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = true; m_game_ui->showTranslatedStatusText("Wireframe shown"); + reloadGraphics(); } else { m_game_ui->m_flags.show_minimal_debug = false; m_game_ui->m_flags.show_basic_debug = false; From 1c3afe6b4c7ae2be4be1704036924f2e4d554f18 Mon Sep 17 00:00:00 2001 From: swagtoy Date: Fri, 28 Jun 2024 16:36:51 -0400 Subject: [PATCH 11/12] Get rid of old formspecs --- builtin/client/pause_menu.lua | 32 +- builtin/mainmenu/settings/components.lua | 407 ---------- .../settings/dlg_change_mapgen_flags.lua | 252 ------ builtin/mainmenu/settings/dlg_settings.lua | 768 +----------------- .../settings/generate_from_settingtypes.lua | 139 ---- builtin/mainmenu/settings/init.lua | 5 +- builtin/mainmenu/settings/settingtypes.lua | 507 ------------ .../mainmenu/settings/shadows_component.lua | 121 --- builtin/settings/gui_settings.lua | 80 +- 9 files changed, 70 insertions(+), 2241 deletions(-) delete mode 100644 builtin/mainmenu/settings/components.lua delete mode 100644 builtin/mainmenu/settings/dlg_change_mapgen_flags.lua delete mode 100644 builtin/mainmenu/settings/generate_from_settingtypes.lua delete mode 100644 builtin/mainmenu/settings/settingtypes.lua delete mode 100644 builtin/mainmenu/settings/shadows_component.lua diff --git a/builtin/client/pause_menu.lua b/builtin/client/pause_menu.lua index 18d0d064fee72..db49d5a1af2bc 100644 --- a/builtin/client/pause_menu.lua +++ b/builtin/client/pause_menu.lua @@ -1,5 +1,17 @@ local SIZE_TAG = "size[11,5.5,true]" +local settingspath = core.get_builtin_path().."settings" + +function core.get_mainmenu_path() + return settingspath +end + +defaulttexturedir = "" +dofile(settingspath .. DIR_DELIM .. "settingtypes.lua") +dofile(settingspath .. DIR_DELIM .. "gui_change_mapgen_flags.lua") +dofile(settingspath .. DIR_DELIM .. "gui_settings.lua") + + local function avoid_noid() return "label[1,1;Avoid the Noid!]" end @@ -111,30 +123,16 @@ core.register_on_formspec_input(function(formname, fields) return end) -local settingspath = core.get_builtin_path().."settings" - -function core.get_mainmenu_path() - return settingspath -end - -defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. - DIR_DELIM .. "pack" .. DIR_DELIM -dofile(settingspath .. DIR_DELIM .. "settingtypes.lua") -dofile(settingspath .. DIR_DELIM .. "gui_change_mapgen_flags.lua") -dofile(settingspath .. DIR_DELIM .. "gui_settings.lua") - - -function dialog_create(name, spec, buttonhandler, eventhandler) - minetest.show_formspec(name, spec({})) +-- Override a dlg function to just exit +function settings.delete() + core.unpause() end - core.register_on_formspec_input(function(formname, fields) if formname ~= "builtin:MT_PAUSE_MENU_SETTINGS" then return true end settings:buttonhandler(fields) if settings.refresh_page then - --minetest.show_formspec("", "") core.show_settings() core.reload_graphics() settings.refresh_page = false diff --git a/builtin/mainmenu/settings/components.lua b/builtin/mainmenu/settings/components.lua deleted file mode 100644 index 51cc0c95bb489..0000000000000 --- a/builtin/mainmenu/settings/components.lua +++ /dev/null @@ -1,407 +0,0 @@ ---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. --- * `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 - - ---- 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) .. "]", - } - - local value = core.settings:get(setting.name) or setting.default - self.resettable = core.settings:has(setting.name) - - 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 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 diff --git a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua b/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua deleted file mode 100644 index 570d184da63d2..0000000000000 --- a/builtin/mainmenu/settings/dlg_change_mapgen_flags.lua +++ /dev/null @@ -1,252 +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 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 = 2 - local description_height = 1.5 - - local t = get_current_np_group(setting) - local dimension = 3 - if setting.type == "noise_params_2d" then - dimension = 2 - end - - 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 the settings menu. ]] - .. 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 - the settings menu. ]] - .. 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 - the settings menu. ]] - .. 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 - - for name, value in pairs(fields) do - if name:sub(1, 3) == "cb_" then - checkboxes[name] = core.is_yes(value) - return false -- Don't update the formspec! - end - 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 index d9691d13bd8c2..f656409724fc1 100644 --- a/builtin/mainmenu/settings/dlg_settings.lua +++ b/builtin/mainmenu/settings/dlg_settings.lua @@ -1,765 +1,21 @@ ---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 settingspath = core.get_builtin_path().."settings" - -local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "components.lua") - -local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM .. - "settings" .. DIR_DELIM .. "shadows_component.lua") - -local loaded = false - full_settings = {} -local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png") -local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png") - all_pages = {} - page_by_id = {} - filtered_pages = all_pages - filtered_page_by_id = page_by_id - - -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 - - -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 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 fgettext_ne("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 = {}, - } - - page = add_page(page) - elseif entry.level == 2 then - ensure_page_started() - page.content[#page.content + 1] = { - heading = fgettext_ne(entry.readable_name or entry.name), - } - end - else - ensure_page_started() - page.content[#page.content + 1] = entry.name - end - end -end - - -function load(read_all, parse_mods) - read_all = read_all == nil and false or read_all - parse_mods = parse_mods == nil and true or parse_mods - - if loaded then - return - end - loaded = true - - full_settings = settingtypes.parse_config_file(read_all, parse_mods) - - local change_keys = { - query_text = "Controls", - requires = { - keyboard_mouse = true, - }, - 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("Controls")), 0.8 - end, - on_submit = function(self, fields) - if fields.btn_change_keys then - core.show_keys_menu() - end - end, - } - - add_page({ - id = "accessibility", - title = fgettext_ne("Accessibility"), - content = { - "language", - { heading = fgettext_ne("General") }, - "font_size", - "chat_font_size", - "gui_scaling", - "hud_scaling", - "show_nametag_backgrounds", - { heading = fgettext_ne("Chat") }, - "console_height", - "console_alpha", - "console_color", - { heading = fgettext_ne("Controls") }, - "autojump", - "safe_dig_and_place", - { heading = fgettext_ne("Movement") }, - "arm_inertia", - "view_bobbing_amount", - "fall_bobbing_amount", - }, - }) - - load_settingtypes() - - if page_by_id.controls_keyboard_and_mouse then - table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys) - do - local content = page_by_id.graphics_and_audio_shaders.content - local idx = table.indexof(content, "enable_dynamic_shadows") - table.insert(content, idx, shadows_component) - end - - -- These must not be translated, as they need to show in the local - -- language no matter the user's current language. - -- This list must be kept in sync with src/unsupported_language_list.txt. - get_setting_info("language").option_labels = { - [""] = fgettext_ne("(Use system language)"), - --ar = " [ar]", blacklisted - be = "Беларуская [be]", - bg = "Български [bg]", - ca = "Català [ca]", - cs = "Česky [cs]", - cy = "Cymraeg [cy]", - da = "Dansk [da]", - de = "Deutsch [de]", - --dv = " [dv]", blacklisted - el = "Ελληνικά [el]", - en = "English [en]", - eo = "Esperanto [eo]", - es = "Español [es]", - et = "Eesti [et]", - eu = "Euskara [eu]", - fi = "Suomi [fi]", - fil = "Wikang Filipino [fil]", - fr = "Français [fr]", - gd = "Gàidhlig [gd]", - gl = "Galego [gl]", - --he = " [he]", blacklisted - --hi = " [hi]", blacklisted - hu = "Magyar [hu]", - id = "Bahasa Indonesia [id]", - it = "Italiano [it]", - ja = "日本語 [ja]", - jbo = "Lojban [jbo]", - kk = "Қазақша [kk]", - --kn = " [kn]", blacklisted - ko = "한국어 [ko]", - ky = "Kırgızca / Кыргызча [ky]", - lt = "Lietuvių [lt]", - lv = "Latviešu [lv]", - mn = "Монгол [mn]", - mr = "मराठी [mr]", - ms = "Bahasa Melayu [ms]", - --ms_Arab = " [ms_Arab]", blacklisted - nb = "Norsk Bokmål [nb]", - nl = "Nederlands [nl]", - nn = "Norsk Nynorsk [nn]", - oc = "Occitan [oc]", - pl = "Polski [pl]", - pt = "Português [pt]", - pt_BR = "Português do Brasil [pt_BR]", - ro = "Română [ro]", - ru = "Русский [ru]", - sk = "Slovenčina [sk]", - sl = "Slovenščina [sl]", - sr_Cyrl = "Српски [sr_Cyrl]", - sr_Latn = "Srpski (Latinica) [sr_Latn]", - sv = "Svenska [sv]", - sw = "Kiswahili [sw]", - --th = " [th]", blacklisted - tr = "Türkçe [tr]", - tt = "Tatarça [tt]", - uk = "Українська [uk]", - vi = "Tiếng Việt [vi]", - zh_CN = "中文 (简体) [zh_CN]", - zh_TW = "正體中文 (繁體) [zh_TW]", - } - end - -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, 0 - 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 - - -function update_filtered_pages(query) - filtered_pages = {} - filtered_page_by_id = {} - - 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 page_has_contents(page, content) 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 check_requirements(name, requires) - if requires == nil then - return true - end - - local video_driver = core.get_active_driver() - local shaders_support = video_driver == "opengl" or video_driver == "opengl3" or video_driver == "ogles2" - local special = { - android = PLATFORM == "Android", - desktop = PLATFORM ~= "Android", - touchscreen_gui = core.settings:get_bool("enable_touch"), - keyboard_mouse = not core.settings:get_bool("enable_touch"), - shaders_support = shaders_support, - shaders = core.settings:get_bool("enable_shaders") and shaders_support, - opengl = video_driver == "opengl", - gles = video_driver:sub(1, 5) == "ogles", - } - - for req_key, req_value in pairs(requires) do - if special[req_key] == nil then - local required_setting = get_setting_info(req_key) - if required_setting == nil then - core.log("warning", "Unknown setting " .. req_key .. " required by " .. name) - end - local actual_value = core.settings:get_bool(req_key, - required_setting and core.is_yes(required_setting.default)) - if actual_value ~= req_value then - return false - end - elseif special[req_key] ~= req_value then - return false - end - end - - return true +function core.get_mainmenu_path() + return settingspath end - -function page_has_contents(page, actual_content) - local is_advanced = - page.id:sub(1, #"client_and_server") == "client_and_server" or - page.id:sub(1, #"mapgen") == "mapgen" or - page.id:sub(1, #"advanced") == "advanced" - local show_advanced = core.settings:get_bool("show_advanced") - if is_advanced and not show_advanced then - return false - end - - for _, item in ipairs(actual_content) do - if item == false or item.heading then --luacheck: ignore - -- skip - elseif type(item) == "string" then - local setting = get_setting_info(item) - assert(setting, "Unknown setting: " .. item) - if check_requirements(setting.name, setting.requires) then - return true - end - elseif item.get_formspec then - if check_requirements(item.id, item.requires) then - return true - end - else - error("Unknown content in page: " .. dump(item)) - end - end - - return false -end - - -local function build_page_components(page) - -- Filter settings based on requirements - local content = {} - local last_heading - for _, item in ipairs(page.content) do - if item == false then --luacheck: ignore - -- skip - elseif item.heading then - last_heading = item - else - local name, requires - if type(item) == "string" then - local setting = get_setting_info(item) - assert(setting, "Unknown setting: " .. item) - name = setting.name - requires = setting.requires - elseif item.get_formspec then - name = item.id - requires = item.requires - else - error("Unknown content in page: " .. dump(item)) - end - - if check_requirements(name, requires) then - if last_heading then - content[#content + 1] = last_heading - last_heading = nil - end - content[#content + 1] = item - end - end - end - - -- Create components - local retval = {} - for i, item in ipairs(content) do - if type(item) == "string" then - local setting = get_setting_info(item) - local component_func = component_funcs[setting.type] - assert(component_func, "Unknown setting type: " .. setting.type) - retval[i] = component_func(setting) - elseif item.get_formspec then - retval[i] = item - elseif item.heading then - retval[i] = component_funcs.heading(item.heading) - end - 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 "accessibility" - local page = filtered_page_by_id[page_id] - - local extra_h = 1 -- not included in tabsize.height - local tabsize = { - width = core.settings:get_bool("enable_touch") and 16.5 or 15.5, - height = core.settings:get_bool("enable_touch") and (10 - extra_h) or 12, - } - - local scrollbar_w = core.settings:get_bool("enable_touch") and 0.6 or 0.4 - - local left_pane_width = core.settings:get_bool("enable_touch") and 4.5 or 4.25 - local left_pane_padding = 0.25 - local search_width = left_pane_width + scrollbar_w - (0.75 * 2) - - local back_w = 3 - local checkbox_w = (tabsize.width - back_w - 2*0.2) / 2 - local show_technical_names = core.settings:get_bool("show_technical_names") - local show_advanced = core.settings:get_bool("show_advanced") - - formspec_show_hack = not formspec_show_hack - - local fs = { - "formspec_version[6]", - "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", - core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", - "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,%f;%f,0.8;back;%s]"):format( - tabsize.height + 0.2, back_w, fgettext("Back")), - - ("box[%f,%f;%f,0.8;#0000008C]"):format( - back_w + 0.2, tabsize.height + 0.2, checkbox_w), - ("checkbox[%f,%f;show_technical_names;%s;%s]"):format( - back_w + 2*0.2, tabsize.height + 0.6, - fgettext("Show technical names"), tostring(show_technical_names)), - - ("box[%f,%f;%f,0.8;#0000008C]"):format( - back_w + 2*0.2 + checkbox_w, tabsize.height + 0.2, checkbox_w), - ("checkbox[%f,%f;show_advanced;%s;%s]"):format( - back_w + 3*0.2 + checkbox_w, tabsize.height + 0.6, - fgettext("Show advanced settings"), tostring(show_advanced)), - - "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;", - core.formspec_escape(dialogdata.query or ""), "]", - "field_enter_after_edit[search_query;true]", - "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-left_pane_padding, 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-left_pane_padding, 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.resettable and comp.setting - 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 - local default = comp.setting.default - local reset_tooltip = default and - fgettext("Reset setting to default ($1)", tostring(default)) or - fgettext("Reset setting to default") - 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, reset_tooltip) - 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 - - --- On Android, closing the app via the "Recents screen" won't result in a clean --- exit, discarding any setting changes made by the user. --- To avoid that, we write the settings file in more cases on Android. -function write_settings_early() - if PLATFORM == "Android" then - core.settings:write() - end -end - - -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 - - minetest.log(dump(fields)) - - 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) - write_settings_early() - - return true - end - - if fields.show_advanced ~= nil then - local value = core.is_yes(fields.show_advanced) - core.settings:set_bool("show_advanced", value) - write_settings_early() - end - - -- enable_touch is a checkbox in a setting component. We handle this - -- setting differently so we can hide/show pages using the next if-statement - if fields.enable_touch ~= nil then - local value = core.is_yes(fields.enable_touch) - core.settings:set_bool("enable_touch", value) - write_settings_early() - end - - if fields.show_advanced ~= nil or fields.enable_touch ~= nil then - local suggested_page_id = update_filtered_pages(dialogdata.query) - - dialogdata.components = nil - - if not filtered_page_by_id[dialogdata.page_id] then - dialogdata.leftscroll = 0 - dialogdata.rightscroll = 0 - - dialogdata.page_id = suggested_page_id - end - - 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 - - if dialogdata.components then - for i, comp in ipairs(dialogdata.components) do - if comp.on_submit and comp:on_submit(fields, this) then - write_settings_early() - - -- Clear components so they regenerate - dialogdata.components = nil - return true - end - if comp.setting and fields["reset_" .. i] then - core.settings:remove(comp.setting.name) - write_settings_early() - - -- Clear components so they regenerate - dialogdata.components = nil - return true - end - end - end - - return false -end - - -local function eventhandler(event) - if event == "DialogShow" then - -- Don't show the "MINETEST" header behind the dialog. - mm_game_theme.set_engine(true) - return true - end - if event == "FullscreenChange" then - -- Refresh the formspec to keep the fullscreen checkbox up to date. - ui.update() - return true - end - - return false -end +defaulttexturedir = core.get_texturepath_share() .. DIR_DELIM .. "base" .. + DIR_DELIM .. "pack" .. DIR_DELIM +dofile(settingspath .. DIR_DELIM .. "settingtypes.lua") +dofile(settingspath .. DIR_DELIM .. "gui_change_mapgen_flags.lua") +dofile(settingspath .. DIR_DELIM .. "gui_settings.lua") function create_settings_dlg() - load() - local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler) - - dlg.data.page_id = update_filtered_pages("") + settings.load() + local dlg = dialog_create("dlg_settings", settings.get_formspec, settings.buttonhandler, settings.eventhandler) + settings.is_dlg = true + settings.page_id = settings.update_filtered_pages("") return dlg end - -function show_settings_client_formspec(name, data) - minetest.show_formspec(name or "dlg_settings", get_formspec(data)) -end diff --git a/builtin/mainmenu/settings/generate_from_settingtypes.lua b/builtin/mainmenu/settings/generate_from_settingtypes.lua deleted file mode 100644 index e18b2c2d6077a..0000000000000 --- a/builtin/mainmenu/settings/generate_from_settingtypes.lua +++ /dev/null @@ -1,139 +0,0 @@ -local concat = table.concat -local insert = table.insert -local sprintf = string.format -local rep = string.rep - -local minetest_example_header = [[ -# This file contains a list of all available settings and their default value for minetest.conf - -# By default, all the settings are commented and not functional. -# Uncomment settings by removing the preceding #. - -# minetest.conf is read by default from: -# ../minetest.conf -# ../../minetest.conf -# Any other path can be chosen by passing the path as a parameter -# to the program, eg. "minetest.exe --config ../minetest.conf.example". - -# Further documentation: -# https://wiki.minetest.net/ - -]] - -local group_format_template = [[ -# %s = { -# offset = %s, -# scale = %s, -# spread = (%s, %s, %s), -# seed = %s, -# octaves = %s, -# persistence = %s, -# lacunarity = %s, -# flags =%s -# } - -]] - -local function create_minetest_conf_example(settings) - local result = { minetest_example_header } - for _, entry in ipairs(settings) do - if entry.type == "category" then - if entry.level == 0 then - insert(result, "#\n# " .. entry.name .. "\n#\n\n") - else - insert(result, rep("#", entry.level)) - insert(result, "# " .. entry.name .. "\n\n") - end - else - local group_format = false - if entry.noise_params and entry.values then - if entry.type == "noise_params_2d" or entry.type == "noise_params_3d" then - group_format = true - end - end - if entry.comment ~= "" then - for _, comment_line in ipairs(entry.comment:split("\n", true)) do - if comment_line == "" then - insert(result, "#\n") - else - insert(result, "# " .. comment_line .. "\n") - end - end - end - if entry.type == "key" then - local line = "See https://github.com/minetest/irrlicht/blob/master/include/Keycodes.h" - insert(result, "# " .. line .. "\n") - end - insert(result, "# type: " .. entry.type) - if entry.min then - insert(result, " min: " .. entry.min) - end - if entry.max then - insert(result, " max: " .. entry.max) - end - if entry.values and entry.noise_params == nil then - insert(result, " values: " .. concat(entry.values, ", ")) - end - if entry.possible then - insert(result, " possible values: " .. concat(entry.possible, ", ")) - end - insert(result, "\n") - if group_format == true then - local flags = entry.values[10] - if flags ~= "" then - flags = " "..flags - end - insert(result, sprintf(group_format_template, entry.name, entry.values[1], - entry.values[2], entry.values[3], entry.values[4], entry.values[5], - entry.values[6], entry.values[7], entry.values[8], entry.values[9], - flags)) - else - local append - if entry.default ~= "" then - append = " " .. entry.default - end - insert(result, sprintf("# %s =%s\n\n", entry.name, append or "")) - end - end - end - return concat(result) -end - -local translation_file_header = [[ -// This file is automatically generated -// It contains a bunch of fake gettext calls, to tell xgettext about the strings in config files -// To update it, refer to the bottom of builtin/mainmenu/dlg_settings_advanced.lua - -fake_function() {]] - -local function create_translation_file(settings) - local result = { translation_file_header } - for _, entry in ipairs(settings) do - if entry.type == "category" then - insert(result, sprintf("\tgettext(%q);", entry.name)) - else - if entry.readable_name then - insert(result, sprintf("\tgettext(%q);", entry.readable_name)) - end - if entry.comment ~= "" then - local comment_escaped = entry.comment:gsub("\n", "\\n") - comment_escaped = comment_escaped:gsub("\"", "\\\"") - insert(result, "\tgettext(\"" .. comment_escaped .. "\");") - end - end - end - insert(result, "}\n") - return concat(result, "\n") -end - -local file = assert(io.open("minetest.conf.example", "w")) -file:write(create_minetest_conf_example(settingtypes.parse_config_file(true, false))) -file:close() - -file = assert(io.open("src/settings_translation_file.cpp", "w")) --- If 'minetest.conf.example' appears in the 'bin' folder, the line below may have to be --- used instead. The file will also appear in the 'bin' folder. ---file = assert(io.open("settings_translation_file.cpp", "w")) --- We don't want hidden settings to be translated, so we set read_all to false. -file:write(create_translation_file(settingtypes.parse_config_file(false, false))) -file:close() diff --git a/builtin/mainmenu/settings/init.lua b/builtin/mainmenu/settings/init.lua index 4541468c1d9cc..57950f322e132 100644 --- a/builtin/mainmenu/settings/init.lua +++ b/builtin/mainmenu/settings/init.lua @@ -16,10 +16,11 @@ --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. local path = core.get_mainmenu_path() .. DIR_DELIM .. "settings" +local settingspath = core.get_builtin_path().."settings" + -dofile(path .. DIR_DELIM .. "settingtypes.lua") -dofile(path .. DIR_DELIM .. "dlg_change_mapgen_flags.lua") dofile(path .. DIR_DELIM .. "dlg_settings.lua") +dofile(settingspath .. DIR_DELIM .. "gui_change_mapgen_flags.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. diff --git a/builtin/mainmenu/settings/settingtypes.lua b/builtin/mainmenu/settings/settingtypes.lua deleted file mode 100644 index 4e7d9067f6b43..0000000000000 --- a/builtin/mainmenu/settings/settingtypes.lua +++ /dev/null @@ -1,507 +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. - -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_match = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$") - if comment_match then - settings.current_comment[#settings.current_comment + 1] = comment_match - 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 - local category_level = stars:len() + base_level - - if settings.current_hide_level then - if settings.current_hide_level < category_level then - -- Skip this category, it's inside a hidden category. - return - else - -- The start of this category marks the end of a hidden category. - settings.current_hide_level = nil - end - end - - if not read_all and category:sub(1, 5) == "Hide:" then - -- This category is hidden. - settings.current_hide_level = category_level - return - end - - table.insert(settings, { - name = category, - level = category_level, - type = "category", - }) - return - end - - if settings.current_hide_level then - -- Ignore this line, we're inside a hidden 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 - - local requires = {} - local last_line = #current_comment > 0 and current_comment[#current_comment]:trim() - if last_line and last_line:lower():sub(1, 9) == "requires:" then - local parts = last_line:sub(10):split(",") - current_comment[#current_comment] = nil - - for _, part in ipairs(parts) do - part = part:trim() - - local value = true - if part:sub(1, 1) == "!" then - value = false - part = part:sub(2):trim() - end - - requires[part] = value - end - end - - if readable_name == "" then - readable_name = nil - end - local remaining_line = line:sub(first_part:len() + 1) - - local comment = table.concat(current_comment, "\n"):trim() - - 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, - requires = requires, - comment = 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, - requires = requires, - comment = 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) - 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, - requires = requires, - comment = 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, - requires = requires, - comment = 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, - requires = requires, - comment = 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), - requires = requires, - comment = 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, - requires = requires, - comment = 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), - requires = requires, - comment = 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 = {} - result.current_hide_level = nil - - 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 - result.current_hide_level = 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_true_builtin_path and core.get_true_builtin_path() or core.get_builtin_path()) .. FILENAME - local file = io.open(builtin_path, "r") - if not file then - core.log("error", "Can't load " .. builtin_path) - 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.path, - readable_name = game.title, - level = 1, - type = "category", - }) - - parse_single_file(file, path, read_all, settings, 2, false) - - file:close() - end - end - - -- Parse mods - pkgmgr.load_all() - local mods_category_initialized = false - local mods = pkgmgr.global_mods:get_list() - 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.path, - readable_name = mod.title or mod.name, - 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.path, - readable_name = mod.title or 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/shadows_component.lua b/builtin/mainmenu/settings/shadows_component.lua deleted file mode 100644 index 93c071bf78e5a..0000000000000 --- a/builtin/mainmenu/settings/shadows_component.lua +++ /dev/null @@ -1,121 +0,0 @@ ---Minetest ---Copyright (C) 2021-2 x2048 ---Copyright (C) 2022-3 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"), - fgettext("Custom"), -} -local PRESET_DISABLED = 1 -local PRESET_CUSTOM = #shadow_levels_labels - - --- max distance, texture size, texture_32bit, filters, map color -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 function detect_mapping_idx() - if not core.settings:get_bool("enable_dynamic_shadows", false) then - return PRESET_DISABLED - end - - local shadow_map_max_distance = tonumber(core.settings:get("shadow_map_max_distance")) - local shadow_map_texture_size = tonumber(core.settings:get("shadow_map_texture_size")) - local shadow_map_texture_32bit = core.settings:get_bool("shadow_map_texture_32bit", false) - local shadow_filters = tonumber(core.settings:get("shadow_filters")) - local shadow_map_color = core.settings:get_bool("shadow_map_color", false) - - for i = 2, 6 do - local preset = shadow_presets[i] - if preset[1] == shadow_map_max_distance and - preset[2] == shadow_map_texture_size and - preset[3] == shadow_map_texture_32bit and - preset[4] == shadow_filters and - preset[5] == shadow_map_color then - return i - end - end - return PRESET_CUSTOM -end - - -local function apply_preset(preset) - if preset then - core.settings:set_bool("enable_dynamic_shadows", true) - core.settings:set("shadow_map_max_distance", preset[1]) - core.settings:set("shadow_map_texture_size", preset[2]) - core.settings:set_bool("shadow_map_texture_32bit", preset[3]) - core.settings:set("shadow_filters", preset[4]) - core.settings:set_bool("shadow_map_color", preset[5]) - else - core.settings:set_bool("enable_dynamic_shadows", false) - end -end - - -return { - query_text = "Shadows", - requires = { - shaders = true, - opengl = true, - }, - get_formspec = function(self, avail_w) - local labels = table.copy(shadow_levels_labels) - local idx = detect_mapping_idx() - - -- Remove "custom" if not already selected - if idx ~= PRESET_CUSTOM then - table.remove(labels, PRESET_CUSTOM) - end - - local fs = - "label[0,0.2;" .. fgettext("Dynamic shadows") .. "]" .. - "dropdown[0,0.4;3,0.8;dd_shadows;" .. table.concat(labels, ",") .. ";" .. idx .. ";true]" .. - "label[0,1.5;" .. core.colorize("#bbb", fgettext("(The game will need to enable shadows as well)")) .. "]" - return fs, 1.8 - end, - on_submit = function(self, fields) - if fields.dd_shadows then - local old_shadow_level_idx = detect_mapping_idx() - local shadow_level_idx = tonumber(fields.dd_shadows) - if shadow_level_idx == nil or shadow_level_idx == old_shadow_level_idx then - return false - end - - if shadow_level_idx == PRESET_CUSTOM then - core.settings:set_bool("enable_dynamic_shadows", true) - return true - end - - local preset = shadow_presets[shadow_level_idx] - apply_preset(preset) - return true - end - end, -} diff --git a/builtin/settings/gui_settings.lua b/builtin/settings/gui_settings.lua index d32ef53a9a60a..f7709d46da3c8 100644 --- a/builtin/settings/gui_settings.lua +++ b/builtin/settings/gui_settings.lua @@ -123,7 +123,7 @@ function settings.load(read_all, parse_mods) return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8 end, on_submit = function(self, fields) - if fields.btn_change_keys then + if fields.btn_change_keys and settings.is_dlg then core.show_keys_menu() end end, @@ -494,8 +494,8 @@ function settings.get_formspec() "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]", - ("button[0,%f;%f,0.8;back;%s]"):format( - tabsize.height + 0.2, back_w, fgettext("Back")), + settings.is_dlg and ("button[0,%f;%f,0.8;back;%s]"):format( + tabsize.height + 0.2, back_w, fgettext("Back")) or "", ("box[%f,%f;%f,0.8;#0000008C]"):format( back_w + 0.2, tabsize.height + 0.2, checkbox_w), @@ -637,11 +637,11 @@ end function settings.buttonhandler(this, fields) - this.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or this.leftscroll - this.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or this.rightscroll - this.query = fields.search_query + settings.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or settings.leftscroll + settings.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or settings.rightscroll + settings.query = fields.search_query - if fields.back and this.is_dlg then + if fields.back then this:delete() return true end @@ -650,7 +650,7 @@ function settings.buttonhandler(this, fields) local value = core.is_yes(fields.show_technical_names) core.settings:set_bool("show_technical_names", value) write_settings_early() - this.refresh_page = true + settings.refresh_page = true return true end @@ -658,73 +658,73 @@ function settings.buttonhandler(this, fields) local value = core.is_yes(fields.show_advanced) core.settings:set_bool("show_advanced", value) write_settings_early() - this.refresh_page = true + settings.refresh_page = true end - -- enable_touch is a checkbox in a setting component. We handle this + -- enable_touch is a checkbox in a setting component. We handle settings -- setting differently so we can hide/show pages using the next if-statement if fields.enable_touch ~= nil then local value = core.is_yes(fields.enable_touch) core.settings:set_bool("enable_touch", value) write_settings_early() - this.refresh_page = true + settings.refresh_page = true end if fields.show_advanced ~= nil or fields.enable_touch ~= nil then - local suggested_page_id = settings.update_filtered_pages(this.query) + local suggested_page_id = settings.update_filtered_pages(settings.query) - this.components = nil + settings.components = nil - if not settings.filtered_pages_by_id[this.page_id] then - this.leftscroll = 0 - this.rightscroll = 0 + if not settings.filtered_pages_by_id[settings.page_id] then + settings.leftscroll = 0 + settings.rightscroll = 0 - this.page_id = suggested_page_id - this.refresh_page = true + settings.page_id = suggested_page_id + settings.refresh_page = true end return true end if fields.search or fields.key_enter_field == "search_query" then - this.components = nil - this.leftscroll = 0 - this.rightscroll = 0 + settings.components = nil + settings.leftscroll = 0 + settings.rightscroll = 0 - this.page_id = settings.update_filtered_pages(this.query) - this.refresh_page = true + settings.page_id = settings.update_filtered_pages(settings.query) + settings.refresh_page = true return true end if fields.search_clear then - this.query = "" - this.components = nil - this.leftscroll = 0 - this.rightscroll = 0 + settings.query = "" + settings.components = nil + settings.leftscroll = 0 + settings.rightscroll = 0 - this.page_id = settings.update_filtered_pages("") - this.refresh_page = true + settings.page_id = settings.update_filtered_pages("") + settings.refresh_page = true return true end for _, page in ipairs(settings.all_pages) do if fields["page_" .. page.id] then - this.page_id = page.id - this.components = nil - this.rightscroll = 0 - this.refresh_page = true + settings.page_id = page.id + settings.components = nil + settings.rightscroll = 0 + settings.refresh_page = true return true end end - if this.components then - for i, comp in ipairs(this.components) do - if comp.on_submit and comp:on_submit(fields, this) then + if settings.components then + for i, comp in ipairs(settings.components) do + if comp.on_submit and comp:on_submit(fields, settings) then write_settings_early() -- Clear components so they regenerate - this.components = nil - this.refresh_page = true + settings.components = nil + settings.refresh_page = true return true end if comp.setting and fields["reset_" .. i] then @@ -732,8 +732,8 @@ function settings.buttonhandler(this, fields) write_settings_early() -- Clear components so they regenerate - this.components = nil - this.refresh_page = true + settings.components = nil + settings.refresh_page = true return true end end From 7fca09fd5b51b7e419cda15bb8994482683e5d7c Mon Sep 17 00:00:00 2001 From: swagtoy Date: Fri, 28 Jun 2024 16:46:04 -0400 Subject: [PATCH 12/12] Fix ugly background --- builtin/settings/gui_settings.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builtin/settings/gui_settings.lua b/builtin/settings/gui_settings.lua index f7709d46da3c8..56fba08d39a67 100644 --- a/builtin/settings/gui_settings.lua +++ b/builtin/settings/gui_settings.lua @@ -486,13 +486,14 @@ function settings.get_formspec() local fs = { "formspec_version[6]", "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]", - core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", + "no_prepend[]", "bgcolor[#0000]", + core.settings:get_bool("enable_touch") and "padding[0.01,0.01]" or "", -- 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]", + "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#000000A3]", settings.is_dlg and ("button[0,%f;%f,0.8;back;%s]"):format( tabsize.height + 0.2, back_w, fgettext("Back")) or "",