2
0

dlg_settings.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. --Luanti
  2. --Copyright (C) 2022 rubenwardy
  3. --
  4. --This program is free software; you can redistribute it and/or modify
  5. --it under the terms of the GNU Lesser General Public License as published by
  6. --the Free Software Foundation; either version 2.1 of the License, or
  7. --(at your option) any later version.
  8. --
  9. --This program is distributed in the hope that it will be useful,
  10. --but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. --GNU Lesser General Public License for more details.
  13. --
  14. --You should have received a copy of the GNU Lesser General Public License along
  15. --with this program; if not, write to the Free Software Foundation, Inc.,
  16. --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. local component_funcs = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
  18. "settings" .. DIR_DELIM .. "components.lua")
  19. local shadows_component = dofile(core.get_mainmenu_path() .. DIR_DELIM ..
  20. "settings" .. DIR_DELIM .. "shadows_component.lua")
  21. local loaded = false
  22. local full_settings
  23. local info_icon_path = core.formspec_escape(defaulttexturedir .. "settings_info.png")
  24. local reset_icon_path = core.formspec_escape(defaulttexturedir .. "settings_reset.png")
  25. local all_pages = {}
  26. local page_by_id = {}
  27. local filtered_pages = all_pages
  28. local filtered_page_by_id = page_by_id
  29. local function get_setting_info(name)
  30. for _, entry in ipairs(full_settings) do
  31. if entry.type ~= "category" and entry.name == name then
  32. return entry
  33. end
  34. end
  35. return nil
  36. end
  37. local function add_page(page)
  38. assert(type(page.id) == "string")
  39. assert(type(page.title) == "string")
  40. assert(page.section == nil or type(page.section) == "string")
  41. assert(type(page.content) == "table")
  42. assert(not page_by_id[page.id], "Page " .. page.id .. " already registered")
  43. all_pages[#all_pages + 1] = page
  44. page_by_id[page.id] = page
  45. return page
  46. end
  47. local function load_settingtypes()
  48. local page = nil
  49. local section = nil
  50. local function ensure_page_started()
  51. if not page then
  52. page = add_page({
  53. id = (section or "general"):lower():gsub(" ", "_"),
  54. title = section or fgettext_ne("General"),
  55. section = section,
  56. content = {},
  57. })
  58. end
  59. end
  60. for _, entry in ipairs(full_settings) do
  61. if entry.type == "category" then
  62. if entry.level == 0 then
  63. section = entry.name
  64. page = nil
  65. elseif entry.level == 1 then
  66. page = {
  67. id = ((section and section .. "_" or "") .. entry.name):lower():gsub(" ", "_"),
  68. title = entry.readable_name or entry.name,
  69. section = section,
  70. content = {},
  71. }
  72. page = add_page(page)
  73. elseif entry.level == 2 then
  74. ensure_page_started()
  75. page.content[#page.content + 1] = {
  76. heading = fgettext_ne(entry.readable_name or entry.name),
  77. }
  78. end
  79. else
  80. ensure_page_started()
  81. page.content[#page.content + 1] = entry.name
  82. end
  83. end
  84. end
  85. local function load()
  86. if loaded then
  87. return
  88. end
  89. loaded = true
  90. full_settings = settingtypes.parse_config_file(false, true)
  91. local change_keys = {
  92. query_text = "Controls",
  93. requires = {
  94. touch_controls = false,
  95. },
  96. get_formspec = function(self, avail_w)
  97. local btn_w = math.min(avail_w, 3)
  98. return ("button[0,0;%f,0.8;btn_change_keys;%s]"):format(btn_w, fgettext("Controls")), 0.8
  99. end,
  100. on_submit = function(self, fields)
  101. if fields.btn_change_keys then
  102. core.show_keys_menu()
  103. end
  104. end,
  105. }
  106. local touchscreen_layout = {
  107. query_text = "Touchscreen layout",
  108. requires = {
  109. touchscreen = true,
  110. },
  111. get_formspec = function(self, avail_w)
  112. local btn_w = math.min(avail_w, 6)
  113. return ("button[0,0;%f,0.8;btn_touch_layout;%s]"):format(btn_w, fgettext("Touchscreen layout")), 0.8
  114. end,
  115. on_submit = function(self, fields)
  116. if fields.btn_touch_layout then
  117. core.show_touchscreen_layout()
  118. end
  119. end,
  120. }
  121. add_page({
  122. id = "accessibility",
  123. title = fgettext_ne("Accessibility"),
  124. content = {
  125. "language",
  126. { heading = fgettext_ne("General") },
  127. "font_size",
  128. "chat_font_size",
  129. "gui_scaling",
  130. "hud_scaling",
  131. "show_nametag_backgrounds",
  132. { heading = fgettext_ne("Chat") },
  133. "console_height",
  134. "console_alpha",
  135. "console_color",
  136. { heading = fgettext_ne("Controls") },
  137. "autojump",
  138. "safe_dig_and_place",
  139. { heading = fgettext_ne("Movement") },
  140. "arm_inertia",
  141. "view_bobbing_amount",
  142. "fall_bobbing_amount",
  143. },
  144. })
  145. load_settingtypes()
  146. table.insert(page_by_id.controls_keyboard_and_mouse.content, 1, change_keys)
  147. -- insert after "touch_controls"
  148. table.insert(page_by_id.controls_touchscreen.content, 2, touchscreen_layout)
  149. do
  150. local content = page_by_id.graphics_and_audio_effects.content
  151. local idx = table.indexof(content, "enable_dynamic_shadows")
  152. table.insert(content, idx, shadows_component)
  153. idx = table.indexof(content, "enable_auto_exposure") + 1
  154. local note = component_funcs.note(fgettext_ne("(The game will need to enable automatic exposure as well)"))
  155. note.requires = get_setting_info("enable_auto_exposure").requires
  156. table.insert(content, idx, note)
  157. idx = table.indexof(content, "enable_bloom") + 1
  158. note = component_funcs.note(fgettext_ne("(The game will need to enable bloom as well)"))
  159. note.requires = get_setting_info("enable_bloom").requires
  160. table.insert(content, idx, note)
  161. idx = table.indexof(content, "enable_volumetric_lighting") + 1
  162. note = component_funcs.note(fgettext_ne("(The game will need to enable volumetric lighting as well)"))
  163. note.requires = get_setting_info("enable_volumetric_lighting").requires
  164. table.insert(content, idx, note)
  165. end
  166. -- These must not be translated, as they need to show in the local
  167. -- language no matter the user's current language.
  168. -- This list must be kept in sync with src/unsupported_language_list.txt.
  169. get_setting_info("language").option_labels = {
  170. [""] = fgettext_ne("(Use system language)"),
  171. --ar = " [ar]", blacklisted
  172. be = "Беларуская [be]",
  173. bg = "Български [bg]",
  174. ca = "Català [ca]",
  175. cs = "Česky [cs]",
  176. cy = "Cymraeg [cy]",
  177. da = "Dansk [da]",
  178. de = "Deutsch [de]",
  179. --dv = " [dv]", blacklisted
  180. el = "Ελληνικά [el]",
  181. en = "English [en]",
  182. eo = "Esperanto [eo]",
  183. es = "Español [es]",
  184. et = "Eesti [et]",
  185. eu = "Euskara [eu]",
  186. fi = "Suomi [fi]",
  187. fil = "Wikang Filipino [fil]",
  188. fr = "Français [fr]",
  189. gd = "Gàidhlig [gd]",
  190. gl = "Galego [gl]",
  191. --he = " [he]", blacklisted
  192. --hi = " [hi]", blacklisted
  193. hu = "Magyar [hu]",
  194. id = "Bahasa Indonesia [id]",
  195. it = "Italiano [it]",
  196. ja = "日本語 [ja]",
  197. jbo = "Lojban [jbo]",
  198. kk = "Қазақша [kk]",
  199. --kn = " [kn]", blacklisted
  200. ko = "한국어 [ko]",
  201. ky = "Kırgızca / Кыргызча [ky]",
  202. lt = "Lietuvių [lt]",
  203. lv = "Latviešu [lv]",
  204. mn = "Монгол [mn]",
  205. mr = "मराठी [mr]",
  206. ms = "Bahasa Melayu [ms]",
  207. --ms_Arab = " [ms_Arab]", blacklisted
  208. nb = "Norsk Bokmål [nb]",
  209. nl = "Nederlands [nl]",
  210. nn = "Norsk Nynorsk [nn]",
  211. oc = "Occitan [oc]",
  212. pl = "Polski [pl]",
  213. pt = "Português [pt]",
  214. pt_BR = "Português do Brasil [pt_BR]",
  215. ro = "Română [ro]",
  216. ru = "Русский [ru]",
  217. sk = "Slovenčina [sk]",
  218. sl = "Slovenščina [sl]",
  219. sr_Cyrl = "Српски [sr_Cyrl]",
  220. sr_Latn = "Srpski (Latinica) [sr_Latn]",
  221. sv = "Svenska [sv]",
  222. sw = "Kiswahili [sw]",
  223. --th = " [th]", blacklisted
  224. tr = "Türkçe [tr]",
  225. tt = "Tatarça [tt]",
  226. uk = "Українська [uk]",
  227. vi = "Tiếng Việt [vi]",
  228. zh_CN = "中文 (简体) [zh_CN]",
  229. zh_TW = "正體中文 (繁體) [zh_TW]",
  230. }
  231. get_setting_info("touch_controls").option_labels = {
  232. ["auto"] = fgettext_ne("Auto"),
  233. ["true"] = fgettext_ne("Enabled"),
  234. ["false"] = fgettext_ne("Disabled"),
  235. }
  236. end
  237. -- See if setting matches keywords
  238. local function get_setting_match_weight(entry, query_keywords)
  239. local setting_score = 0
  240. for _, keyword in ipairs(query_keywords) do
  241. if string.find(entry.name:lower(), keyword, 1, true) then
  242. setting_score = setting_score + 1
  243. end
  244. if entry.readable_name and
  245. string.find(fgettext(entry.readable_name):lower(), keyword, 1, true) then
  246. setting_score = setting_score + 1
  247. end
  248. if entry.comment and
  249. string.find(fgettext_ne(entry.comment):lower(), keyword, 1, true) then
  250. setting_score = setting_score + 1
  251. end
  252. end
  253. return setting_score
  254. end
  255. local function filter_page_content(page, query_keywords)
  256. if #query_keywords == 0 then
  257. return page.content, 0
  258. end
  259. local retval = {}
  260. local i = 1
  261. local max_weight = 0
  262. for _, content in ipairs(page.content) do
  263. if type(content) == "string" then
  264. local setting = get_setting_info(content)
  265. assert(setting, "Unknown setting: " .. content)
  266. local weight = get_setting_match_weight(setting, query_keywords)
  267. if weight > 0 then
  268. max_weight = math.max(max_weight, weight)
  269. retval[i] = content
  270. i = i + 1
  271. end
  272. elseif type(content) == "table" and content.query_text then
  273. for _, keyword in ipairs(query_keywords) do
  274. if string.find(fgettext(content.query_text), keyword, 1, true) then
  275. max_weight = math.max(max_weight, 1)
  276. retval[i] = content
  277. i = i + 1
  278. break
  279. end
  280. end
  281. end
  282. end
  283. return retval, max_weight
  284. end
  285. local function update_filtered_pages(query)
  286. filtered_pages = {}
  287. filtered_page_by_id = {}
  288. local query_keywords = {}
  289. for word in query:lower():gmatch("%S+") do
  290. table.insert(query_keywords, word)
  291. end
  292. local best_page = nil
  293. local best_page_weight = -1
  294. for _, page in ipairs(all_pages) do
  295. local content, page_weight = filter_page_content(page, query_keywords)
  296. if page_has_contents(page, content) then
  297. local new_page = table.copy(page)
  298. new_page.content = content
  299. filtered_pages[#filtered_pages + 1] = new_page
  300. filtered_page_by_id[new_page.id] = new_page
  301. if page_weight > best_page_weight then
  302. best_page = new_page
  303. best_page_weight = page_weight
  304. end
  305. end
  306. end
  307. return best_page and best_page.id or nil
  308. end
  309. local function check_requirements(name, requires)
  310. if requires == nil then
  311. return true
  312. end
  313. local video_driver = core.get_active_driver()
  314. local touch_support = core.irrlicht_device_supports_touch()
  315. local touch_controls = core.settings:get("touch_controls")
  316. local special = {
  317. android = PLATFORM == "Android",
  318. desktop = PLATFORM ~= "Android",
  319. touch_support = touch_support,
  320. -- When touch_controls is "auto", we don't know which input method will
  321. -- be used, so we show settings for both.
  322. touchscreen = touch_support and (touch_controls == "auto" or core.is_yes(touch_controls)),
  323. keyboard_mouse = not touch_support or (touch_controls == "auto" or not core.is_yes(touch_controls)),
  324. opengl = (video_driver == "opengl" or video_driver == "opengl3"),
  325. gles = video_driver:sub(1, 5) == "ogles",
  326. }
  327. for req_key, req_value in pairs(requires) do
  328. if special[req_key] == nil then
  329. local required_setting = get_setting_info(req_key)
  330. if required_setting == nil then
  331. core.log("warning", "Unknown setting " .. req_key .. " required by " .. (name or "???"))
  332. end
  333. local actual_value = core.settings:get_bool(req_key,
  334. required_setting and core.is_yes(required_setting.default))
  335. if actual_value ~= req_value then
  336. return false
  337. end
  338. elseif special[req_key] ~= req_value then
  339. return false
  340. end
  341. end
  342. return true
  343. end
  344. function page_has_contents(page, actual_content)
  345. local is_advanced =
  346. page.id:sub(1, #"client_and_server") == "client_and_server" or
  347. page.id:sub(1, #"mapgen") == "mapgen" or
  348. page.id:sub(1, #"advanced") == "advanced"
  349. local show_advanced = core.settings:get_bool("show_advanced")
  350. if is_advanced and not show_advanced then
  351. return false
  352. end
  353. for _, item in ipairs(actual_content) do
  354. if item == false or item.heading then --luacheck: ignore
  355. -- skip
  356. elseif type(item) == "string" then
  357. local setting = get_setting_info(item)
  358. assert(setting, "Unknown setting: " .. item)
  359. if check_requirements(setting.name, setting.requires) then
  360. return true
  361. end
  362. elseif item.get_formspec then
  363. if check_requirements(item.id, item.requires) then
  364. return true
  365. end
  366. else
  367. error("Unknown content in page: " .. dump(item))
  368. end
  369. end
  370. return false
  371. end
  372. local function build_page_components(page)
  373. -- Filter settings based on requirements
  374. local content = {}
  375. local last_heading
  376. for _, item in ipairs(page.content) do
  377. if item == false then --luacheck: ignore
  378. -- skip
  379. elseif item.heading then
  380. last_heading = item
  381. else
  382. local name, requires
  383. if type(item) == "string" then
  384. local setting = get_setting_info(item)
  385. assert(setting, "Unknown setting: " .. item)
  386. name = setting.name
  387. requires = setting.requires
  388. elseif item.get_formspec then
  389. name = item.id
  390. requires = item.requires
  391. else
  392. error("Unknown content in page: " .. dump(item))
  393. end
  394. if check_requirements(name, requires) then
  395. if last_heading then
  396. content[#content + 1] = last_heading
  397. last_heading = nil
  398. end
  399. content[#content + 1] = item
  400. end
  401. end
  402. end
  403. -- Create components
  404. local retval = {}
  405. for i, item in ipairs(content) do
  406. if type(item) == "string" then
  407. local setting = get_setting_info(item)
  408. local component_func = component_funcs[setting.type]
  409. assert(component_func, "Unknown setting type: " .. setting.type)
  410. retval[i] = component_func(setting)
  411. elseif item.get_formspec then
  412. retval[i] = item
  413. elseif item.heading then
  414. retval[i] = component_funcs.heading(item.heading)
  415. end
  416. end
  417. return retval
  418. end
  419. local formspec_show_hack = false
  420. local function get_formspec(dialogdata)
  421. local page_id = dialogdata.page_id or "accessibility"
  422. local page = filtered_page_by_id[page_id]
  423. local extra_h = 1 -- not included in tabsize.height
  424. local tabsize = {
  425. width = core.settings:get_bool("touch_gui") and 16.5 or 15.5,
  426. height = core.settings:get_bool("touch_gui") and (10 - extra_h) or 12,
  427. }
  428. local scrollbar_w = core.settings:get_bool("touch_gui") and 0.6 or 0.4
  429. local left_pane_width = core.settings:get_bool("touch_gui") and 4.5 or 4.25
  430. local left_pane_padding = 0.25
  431. local search_width = left_pane_width + scrollbar_w - (0.75 * 2)
  432. local back_w = 3
  433. local checkbox_w = (tabsize.width - back_w - 2*0.2) / 2
  434. local show_technical_names = core.settings:get_bool("show_technical_names")
  435. local show_advanced = core.settings:get_bool("show_advanced")
  436. formspec_show_hack = not formspec_show_hack
  437. local fs = {
  438. "formspec_version[6]",
  439. "size[", tostring(tabsize.width), ",", tostring(tabsize.height + extra_h), "]",
  440. core.settings:get_bool("touch_gui") and "padding[0.01,0.01]" or "",
  441. "bgcolor[#0000]",
  442. -- HACK: this is needed to allow resubmitting the same formspec
  443. formspec_show_hack and " " or "",
  444. "box[0,0;", tostring(tabsize.width), ",", tostring(tabsize.height), ";#0000008C]",
  445. ("button[0,%f;%f,0.8;back;%s]"):format(
  446. tabsize.height + 0.2, back_w, fgettext("Back")),
  447. ("box[%f,%f;%f,0.8;#0000008C]"):format(
  448. back_w + 0.2, tabsize.height + 0.2, checkbox_w),
  449. ("checkbox[%f,%f;show_technical_names;%s;%s]"):format(
  450. back_w + 2*0.2, tabsize.height + 0.6,
  451. fgettext("Show technical names"), tostring(show_technical_names)),
  452. ("box[%f,%f;%f,0.8;#0000008C]"):format(
  453. back_w + 2*0.2 + checkbox_w, tabsize.height + 0.2, checkbox_w),
  454. ("checkbox[%f,%f;show_advanced;%s;%s]"):format(
  455. back_w + 3*0.2 + checkbox_w, tabsize.height + 0.6,
  456. fgettext("Show advanced settings"), tostring(show_advanced)),
  457. "field[0.25,0.25;", tostring(search_width), ",0.75;search_query;;",
  458. core.formspec_escape(dialogdata.query or ""), "]",
  459. "field_enter_after_edit[search_query;true]",
  460. "container[", tostring(search_width + 0.25), ", 0.25]",
  461. "image_button[0,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "search.png"), ";search;]",
  462. "image_button[0.75,0;0.75,0.75;", core.formspec_escape(defaulttexturedir .. "clear.png"), ";search_clear;]",
  463. "tooltip[search;", fgettext("Search"), "]",
  464. "tooltip[search_clear;", fgettext("Clear"), "]",
  465. "container_end[]",
  466. ("scroll_container[0.25,1.25;%f,%f;leftscroll;vertical;0.1;0]"):format(
  467. left_pane_width, tabsize.height - 1.5),
  468. "style_type[button;border=false;bgcolor=#3333]",
  469. "style_type[button:hover;border=false;bgcolor=#6663]",
  470. }
  471. local y = 0
  472. local last_section = nil
  473. for _, other_page in ipairs(filtered_pages) do
  474. if other_page.section ~= last_section then
  475. fs[#fs + 1] = ("label[0.1,%f;%s]"):format(
  476. y + 0.41, core.colorize("#ff0", fgettext(other_page.section)))
  477. last_section = other_page.section
  478. y = y + 0.82
  479. end
  480. fs[#fs + 1] = ("box[0,%f;%f,0.8;%s]"):format(
  481. y, left_pane_width-left_pane_padding, other_page.id == page_id and "#467832FF" or "#3339")
  482. fs[#fs + 1] = ("button[0,%f;%f,0.8;page_%s;%s]")
  483. :format(y, left_pane_width-left_pane_padding, other_page.id, fgettext(other_page.title))
  484. y = y + 0.82
  485. end
  486. if #filtered_pages == 0 then
  487. fs[#fs + 1] = "label[0.1,0.41;"
  488. fs[#fs + 1] = fgettext("No results")
  489. fs[#fs + 1] = "]"
  490. end
  491. fs[#fs + 1] = "scroll_container_end[]"
  492. if y >= tabsize.height - 1.25 then
  493. fs[#fs + 1] = ("scrollbar[%f,1.25;%f,%f;vertical;leftscroll;%f]"):format(
  494. left_pane_width + 0.25, scrollbar_w, tabsize.height - 1.5, dialogdata.leftscroll or 0)
  495. end
  496. fs[#fs + 1] = "style_type[button;border=;bgcolor=]"
  497. if not dialogdata.components then
  498. dialogdata.components = page and build_page_components(page) or {}
  499. end
  500. local right_pane_width = tabsize.width - left_pane_width - 0.375 - 2*scrollbar_w - 0.25
  501. fs[#fs + 1] = ("scroll_container[%f,0;%f,%f;rightscroll;vertical;0.1;0.25]"):format(
  502. tabsize.width - right_pane_width - scrollbar_w, right_pane_width, tabsize.height)
  503. y = 0.25
  504. for i, comp in ipairs(dialogdata.components) do
  505. fs[#fs + 1] = ("container[0,%f]"):format(y)
  506. local avail_w = right_pane_width - 0.25
  507. if not comp.full_width then
  508. avail_w = avail_w - 1.4
  509. end
  510. if comp.max_w then
  511. avail_w = math.min(avail_w, comp.max_w)
  512. end
  513. local comp_fs, used_h = comp:get_formspec(avail_w)
  514. fs[#fs + 1] = comp_fs
  515. fs[#fs + 1] = "style_type[image_button;border=false;padding=]"
  516. local show_reset = comp.resettable and comp.setting
  517. local show_info = comp.info_text and comp.info_text ~= ""
  518. if show_reset or show_info then
  519. -- ensure there's enough space for reset/info
  520. used_h = math.max(used_h, 0.5)
  521. end
  522. local info_reset_y = used_h / 2 - 0.25
  523. if show_reset then
  524. local default = comp.setting.default
  525. local reset_tooltip = default and
  526. fgettext("Reset setting to default ($1)", tostring(default)) or
  527. fgettext("Reset setting to default")
  528. fs[#fs + 1] = ("image_button[%f,%f;0.5,0.5;%s;%s;]"):format(
  529. right_pane_width - 1.4, info_reset_y, reset_icon_path, "reset_" .. i)
  530. fs[#fs + 1] = ("tooltip[%s;%s]"):format("reset_" .. i, reset_tooltip)
  531. end
  532. if show_info then
  533. local info_x = right_pane_width - 0.75
  534. fs[#fs + 1] = ("image[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, info_icon_path)
  535. fs[#fs + 1] = ("tooltip[%f,%f;0.5,0.5;%s]"):format(info_x, info_reset_y, fgettext(comp.info_text))
  536. end
  537. fs[#fs + 1] = "style_type[image_button;border=;padding=]"
  538. fs[#fs + 1] = "container_end[]"
  539. if used_h > 0 then
  540. y = y + used_h + 0.25
  541. end
  542. end
  543. fs[#fs + 1] = "scroll_container_end[]"
  544. if y >= tabsize.height then
  545. fs[#fs + 1] = ("scrollbar[%f,0;%f,%f;vertical;rightscroll;%f]"):format(
  546. tabsize.width - scrollbar_w, scrollbar_w, tabsize.height, dialogdata.rightscroll or 0)
  547. end
  548. return table.concat(fs, "")
  549. end
  550. -- On Android, closing the app via the "Recents screen" won't result in a clean
  551. -- exit, discarding any setting changes made by the user.
  552. -- To avoid that, we write the settings file in more cases on Android.
  553. function write_settings_early()
  554. if PLATFORM == "Android" then
  555. core.settings:write()
  556. end
  557. end
  558. local function regenerate_page_list(dialogdata)
  559. local suggested_page_id = update_filtered_pages(dialogdata.query)
  560. dialogdata.components = nil
  561. if not filtered_page_by_id[dialogdata.page_id] then
  562. dialogdata.leftscroll = 0
  563. dialogdata.rightscroll = 0
  564. dialogdata.page_id = suggested_page_id
  565. end
  566. end
  567. local function buttonhandler(this, fields)
  568. local dialogdata = this.data
  569. dialogdata.leftscroll = core.explode_scrollbar_event(fields.leftscroll).value or dialogdata.leftscroll
  570. dialogdata.rightscroll = core.explode_scrollbar_event(fields.rightscroll).value or dialogdata.rightscroll
  571. dialogdata.query = fields.search_query
  572. if fields.back then
  573. this:delete()
  574. return true
  575. end
  576. if fields.show_technical_names ~= nil then
  577. local value = core.is_yes(fields.show_technical_names)
  578. core.settings:set_bool("show_technical_names", value)
  579. write_settings_early()
  580. return true
  581. end
  582. if fields.show_advanced ~= nil then
  583. local value = core.is_yes(fields.show_advanced)
  584. core.settings:set_bool("show_advanced", value)
  585. write_settings_early()
  586. regenerate_page_list(dialogdata)
  587. return true
  588. end
  589. if fields.search or fields.key_enter_field == "search_query" then
  590. dialogdata.components = nil
  591. dialogdata.leftscroll = 0
  592. dialogdata.rightscroll = 0
  593. dialogdata.page_id = update_filtered_pages(dialogdata.query)
  594. return true
  595. end
  596. if fields.search_clear then
  597. dialogdata.query = ""
  598. dialogdata.components = nil
  599. dialogdata.leftscroll = 0
  600. dialogdata.rightscroll = 0
  601. dialogdata.page_id = update_filtered_pages("")
  602. return true
  603. end
  604. for _, page in ipairs(all_pages) do
  605. if fields["page_" .. page.id] then
  606. dialogdata.page_id = page.id
  607. dialogdata.components = nil
  608. dialogdata.rightscroll = 0
  609. return true
  610. end
  611. end
  612. local function after_setting_change(comp)
  613. write_settings_early()
  614. if comp.setting and comp.setting.name == "touch_controls" then
  615. -- Changing the "touch_controls" setting may result in a different
  616. -- page list.
  617. regenerate_page_list(dialogdata)
  618. else
  619. -- Clear components so they regenerate
  620. dialogdata.components = nil
  621. end
  622. end
  623. for i, comp in ipairs(dialogdata.components) do
  624. if comp.on_submit and comp:on_submit(fields, this) then
  625. after_setting_change(comp)
  626. return true
  627. end
  628. if comp.setting and fields["reset_" .. i] then
  629. core.settings:remove(comp.setting.name)
  630. after_setting_change(comp)
  631. return true
  632. end
  633. end
  634. return false
  635. end
  636. local function eventhandler(event)
  637. if event == "DialogShow" then
  638. -- Don't show the header image behind the dialog.
  639. mm_game_theme.set_engine(true)
  640. return true
  641. end
  642. if event == "FullscreenChange" then
  643. -- Refresh the formspec to keep the fullscreen checkbox up to date.
  644. ui.update()
  645. return true
  646. end
  647. return false
  648. end
  649. function create_settings_dlg()
  650. load()
  651. local dlg = dialog_create("dlg_settings", get_formspec, buttonhandler, eventhandler)
  652. dlg.data.page_id = update_filtered_pages("")
  653. return dlg
  654. end