From 623b14bfe8b788d827e5438edcab4ef302dd2103 Mon Sep 17 00:00:00 2001 From: Jaidyn Ann <10477760+JadedCtrl@users.noreply.github.com> Date: Thu, 11 Jan 2024 22:21:41 -0600 Subject: [PATCH] Implement new API for adding custom footer-buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows mods to place their own buttons alongside the trash, sort, and settings buttons in the inventory’s footer. The API mimics the custom tab API. --- init.lua | 3 +- src/api.lua | 85 +++++++++++++++- src/fields.lua | 119 ++++++++++++---------- src/gui.lua | 272 +++++++++++++++++++++++++------------------------ 4 files changed, 292 insertions(+), 187 deletions(-) diff --git a/init.lua b/init.lua index 5346889..bd34cee 100644 --- a/init.lua +++ b/init.lua @@ -20,7 +20,7 @@ local function lf(path) end i3 = { - version = 1161, + version = 1162, data = core.deserialize(storage:get_string"data") or {}, settings = { @@ -102,6 +102,7 @@ i3 = { plants = {}, modules = {}, minitabs = {}, + footer_buttons = {}, craft_types = {}, recipe_filters = {}, diff --git a/src/api.lua b/src/api.lua index 8b0c161..9c9dccc 100644 --- a/src/api.lua +++ b/src/api.lua @@ -1,5 +1,6 @@ local http = ... -local make_fs, get_inventory_fs = i3.files.gui() +local make_fs, get_inventory_fs, confirm_trash_footer_fs, settings_footer_fs = i3.files.gui() +local home_footer_fields, confirm_trash_footer_fields, settings_footer_fields = i3.files.fields() IMPORT("sorter", "sort_inventory", "play_sound") IMPORT("sort", "concat", "copy", "insert", "remove") @@ -257,6 +258,88 @@ function i3.override_tab(name, newdef) end end +function i3.new_footer_button(name, def) + if not true_str(name) then + return err "i3.new_footer_button: button name missing" + elseif not true_table(def) then + return err "i3.new_footer_button: button definition missing" + elseif not true_str(def.description) then + return err "i3.new_footer_button: description missing" + elseif not def.image then + return err "i3.new_footer_button: image missing" + elseif #i3.footer_buttons == 16 then + return err(fmt("i3.new_footer_button: cannot add '%s' button. Limit reached (16).", def.name)) + end + + def.name = name + insert(i3.footer_buttons, def) +end + +function i3.remove_footer_button(name) + if not true_str(name) then + return err "i3.remove_footer_button: tab name missing" + end + + for i = #i3.footer_buttons, 2, -1 do + local def = i3.footer_buttons[i] + if def and name == def.name then + remove(i3.footer_buttons, i) + end + end +end + +function i3.override_footer_button(name, newdef) + if not true_str(name) then + return err "i3.override_footer_button: button name missing" + elseif not true_table(newdef) then + return err "i3.override_footer_button: button definition missing" + elseif not true_str(newdef.description) then + return err "i3.override_footer_button: description missing" + elseif not true_str(newdef.image) then + return err "i3.override_footer_button: image missing" + end + + newdef.name = name + + for i, def in ipairs(i3.footer_buttons) do + if def.name == name then + i3.footer_buttons[i] = newdef + break + end + end +end + +i3.new_footer_button("trash", { + description = S"Clear inventory", + image = "i3_trash.png", + formspec = confirm_trash_footer_fs, + fields = confirm_trash_footer_fields, +}) + + +i3.new_footer_button("sort", { + description = S"Sort inventory", + image = "i3_sort.png", + fields = function(player, data, fields) + sort_inventory(player, data) + -- Immediately disable, since nothing is displayed. + return false + end, +}) + +i3.new_footer_button("settings", { + description = S"Settings", + image = "i3_settings.png", + formspec = settings_footer_fs, + fields = settings_footer_fields, +}) + +i3.new_footer_button("home", { + description = S"Go home", + image = "i3_home.png", + fields = home_footer_fields, +}) + i3.register_craft_type("digging", { description = S"Digging", icon = "i3_steelpick.png", diff --git a/src/fields.lua b/src/fields.lua index e696b6d..5f5c46c 100644 --- a/src/fields.lua +++ b/src/fields.lua @@ -9,6 +9,7 @@ IMPORT("valid_item", "get_stack", "craft_stack", "clean_name", "check_privs", "s IMPORT("msg", "is_fav", "pos_to_str", "str_to_pos", "add_hud_waypoint", "play_sound", "reset_data") IMPORT("search", "sort_inventory", "sort_by_category", "get_recipes", "get_detached_inv", "update_inv_size") +-- Fields-handler for any tab displaying the inventory & footer (that is, with def.slots true). local function inv_fields(player, data, fields) local name = data.player_name local inv = player:get_inventory() @@ -19,6 +20,13 @@ local function inv_fields(player, data, fields) data.font_size = tonumber(fields.sb_font_size:match"-?%d+$") end + local footer = data.footer_button and i3.footer_buttons[data.footer_button] + if footer and footer.fields then + if not footer.fields(player, data, fields) then + data.footer_button = nil + end + end + for field in pairs(fields) do if sub(field, 1, 4) == "btn_" then data.subcat = indexof(i3.categories, sub(field, 5)) @@ -93,8 +101,7 @@ local function inv_fields(player, data, fields) end if fields.quit then - data.confirm_trash = nil - data.show_settings = nil + data.footer_button = nil data.waypoint_see = nil data.bag_rename = nil data.goto_page = nil @@ -103,58 +110,9 @@ local function inv_fields(player, data, fields) data.enable_search = nil end - elseif fields.trash then - data.show_settings = nil - data.confirm_trash = true - - elseif fields.settings then - if not data.show_settings then - data.confirm_trash = nil - data.show_settings = true - else - data.show_settings = nil - end - - elseif fields.confirm_trash_yes or fields.confirm_trash_no then - if fields.confirm_trash_yes then - inv:set_list("main", {}) - inv:set_list("craft", {}) - end - - data.confirm_trash = nil - - elseif fields.close_settings then - data.show_settings = nil - elseif fields.close_preview then data.waypoint_see = nil - elseif fields.sort then - sort_inventory(player, data) - - elseif (fields.home or fields.set_home) and not check_privs(name, {home = true}) then - return msg(name, "'home' privilege missing") - - elseif fields.home then - if sethome then - if not sethome.go(name) then - return msg(name, "No home set") - end - elseif not data.home then - return msg(name, "No home set") - else - safe_teleport(player, str_to_pos(data.home)) - end - - msg(name, S"Welcome back home!") - - elseif fields.set_home then - if sethome then - sethome.set(name, player:get_pos()) - else - data.home = pos_to_str(player:get_pos(), 1) - end - elseif fields.bag_rename then data.bag_rename = true @@ -429,6 +387,55 @@ local function rcp_fields(player, data, fields) end end +-- Fields input-handler for the Home footer-button. +local function home_footer_fields(player, data, fields) + local name = player:get_player_name() + + -- Use minetest_game’s sethome API, if available. + if sethome then + if not sethome.go(name) then + msg(name, "No home set") + else + msg(name, S"Welcome back home!") + end + -- Otherwise, use i3’s home. + elseif not data.home then + msg(name, "No home set") + elseif name then + safe_teleport(player, str_to_pos(data.home)) + msg(name, S"Welcome back home!") + end + + -- Immediately disable this footer-dialogue, since nothing is displayed. + return false +end + +-- Fields input-handler for the “Confirm trash” footer-dialogue. +local function confirm_trash_footer_fields(player, data, fields) + local inv = player:get_inventory() + if fields.confirm_trash_yes then + inv:set_list("main", {}) + inv:set_list("craft", {}) + end + + -- In any case, disable this footer-dialogue. + return not (fields.confirm_trash_yes or fields.confirm_trash_no) +end + +-- Fields input-handler for the settings footer-dialogue. +local function settings_footer_fields(player, data, fields) + if fields.set_home and not check_privs(name, {home = true}) then + return msg(name, "'home' privilege missing") + elseif fields.set_home then + if sethome then + sethome.set(player:get_player_name(), player:get_pos()) + else + data.home = pos_to_str(player:get_pos(), 1) + end + end + return not fields.close_settings +end + core.register_on_player_receive_fields(function(player, formname, fields) local name = player:get_player_name() @@ -465,6 +472,14 @@ core.register_on_player_receive_fields(function(player, formname, fields) data.itab = tonumber(f:sub(-1)) sort_by_category(data) break + elseif sub(f, 1, 7) == "footer_" then + local footer_name = sub(f, 8) + for i, footer in ipairs(i3.footer_buttons) do + if footer.name == footer_name then + data.footer_button = i + break + end + end end end @@ -484,3 +499,5 @@ core.register_on_player_receive_fields(function(player, formname, fields) return true, set_fs(player) end) + +return home_footer_fields, confirm_trash_footer_fields, settings_footer_fields diff --git a/src/gui.lua b/src/gui.lua index c584fd3..84de221 100644 --- a/src/gui.lua +++ b/src/gui.lua @@ -569,144 +569,148 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa end end -local function show_settings(fs, data, player) - if data.confirm_trash then - image(2.8, 10.65, 4.6, 0.7, PNG.bg_goto) - label(3.02, 11, "Confirm trash?") - image_button(5.07, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes") - image_button(6.17, 10.75, 1, 0.5, "", "confirm_trash_no", "No") - - elseif data.show_settings then - fs"container[-0.06,0]" - image(2.2, 9, 6.1, 2.35, PNG.bg_content) - - local show_home = data.show_setting == "home" - local show_style = data.show_setting == "style" - local show_sorting = data.show_setting == "sorting" - - fs"style[setting_home,setting_style,setting_sorting;font=bold;font_size=16;sound=i3_click]" - fs("style[setting_home:hovered;textcolor=%s]", show_home and colors.yellow or "#fff") - fs("style[setting_style:hovered;textcolor=%s]", show_style and colors.yellow or "#fff") - fs("style[setting_sorting:hovered;textcolor=%s]", show_sorting and colors.yellow or "#fff") - - fs("style[setting_home;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", - show_home and PNG.pagenum_hover or "", PNG.pagenum_hover, - show_home and colors.yellow or "#ddd") - fs("style[setting_style;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", - show_style and PNG.pagenum_hover or "", PNG.pagenum_hover, - show_style and colors.yellow or "#ddd") - fs("style[setting_sorting;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", - show_sorting and PNG.pagenum_hover or "", PNG.pagenum_hover, - show_sorting and colors.yellow or "#ddd") - - local X = 2.5 - button(X, 9.1, 1.6, 0.55, "setting_home", "Home") - button(X + 1.7, 9.1, 1.6, 0.55, "setting_style", "Style") - button(X + 3.4, 9.1, 1.6, 0.55, "setting_sorting", "Sorting") - image_button(X + 5.12, 9.2, 0.25, 0.25, PNG.cancel_hover .. "^\\[brighten", "close_settings", "") - - if show_home then - local coords, c, str = {"X", "Y", "Z"}, 0, ES"No home set" - - local home = data.home - if sethome then - -- i3 stores home coordinates with one decimal of precision, as the blow fmt() statement - -- assumes. So we need to trim sethome’s coordinates to one decimal, likewise. - local home_pos = sethome.get(player:get_player_name()) - home = string.format("(%.1f,%.1f,%.1f)", home_pos.x, home_pos.y, home_pos.z) - end - - if home then - str = home:gsub(",", " "):sub(2,-2):gsub("%.%d", ""):gsub( - "(%-?%d+)", function(a) - c++ - return fmt("%s: ", - coords[c], colors.blue, a) - end) - end - - hypertext(2.2, 9.9, 6, 0.6, "home_pos", fmt("
%s
", str)) - fs("style[set_home;padding=20,10,-210,-10;fgimg=%s;fgimg_hovered=%s]", PNG.home_px, PNG.home_px_hover) - image_button(4.1, 10.4, 2.2, 0.7, "", "set_home", "") - label(4.9, 10.75, "Set home") - - elseif show_style then - checkbox(2.6, 9.95, "cb_hide_tabs", "Hide tabs", tostring(data.hide_tabs)) - checkbox(2.6, 10.4, "cb_legacy_inventory", "Legacy inventory", tostring(data.legacy_inventory)) - checkbox(2.6, 10.85, "cb_wielditem_hud", "HUD description", tostring(data.wielditem_hud)) - - if not recipe_filter_set() then - checkbox(5.3, 10.85, "cb_collapse", "Collapse list", tostring(data.collapse)) - end - - local sign = (data.font_size > 0 and "+") or (data.font_size > 0 and "-") or "" - label(5.3, 9.95, ES"Font size" .. fmt(": %s", sign .. data.font_size)) - - local range = 8 - fs("scrollbaroptions[min=-%u;max=%u;smallstep=1;largestep=1;thumbsize=2]", range, range) - fs("scrollbar[5.3,10.2;2.55,0.3;horizontal;sb_font_size;%d+]", data.font_size) - - fs("tooltip[cb_hide_tabs;%s;#32333899;#fff]", - ES"Enable this option to change the style of the right panel") - fs("tooltip[cb_legacy_inventory;%s;#32333899;#fff]", - ES"Enable this option to set the classic inventory size in Minetest") - fs("tooltip[cb_wielditem_hud;%s;#32333899;#fff]", - ES"Enable this option to show the wielded item description in your HUD") - fs("tooltip[cb_collapse;%s;#32333899;#fff]", - ES"Enable this option to collapse the inventory list by grouping some items") - - elseif show_sorting then - checkbox(2.6, 9.95, "cb_inv_compress", "Compression", tostring(data.inv_compress)) - checkbox(2.6, 10.4, "cb_reverse_sorting", "Reverse mode", tostring(data.reverse_sorting)) - checkbox(2.6, 10.85, "cb_ignore_hotbar", "Ignore hotbar", tostring(data.ignore_hotbar)) - checkbox(5.3, 9.95, "cb_auto_sorting", "Automation", tostring(data.auto_sorting)) - - local methods = {} - - for _, v in ipairs(i3.sorting_methods) do - local name = toupper(v.name) - insert(methods, name) - end - - label(5.3, 10.4, ES"Sorting method:") - fs("dropdown[%f,%f;2.6,0.5;dd_sorting_method;%s;%u;true]", 5.3, 10.6, concat(methods, ","), data.sort) - - local desc = i3.sorting_methods[data.sort].description - if desc then - tooltip(5.3, 10.6, 2.4, 0.5, ESC(desc)) - end - - fs("tooltip[cb_inv_compress;%s;#32333899;#fff]", - ES"Enable this option to compress your inventory") - fs("tooltip[cb_reverse_sorting;%s;#32333899;#fff]", - ES"Enable this option to sort your inventory in reverse order") - fs("tooltip[cb_ignore_hotbar;%s;#32333899;#fff]", - ES"Enable this option to sort your inventory except the hotbar slots") - fs("tooltip[cb_auto_sorting;%s;#32333899;#fff]", - ES"Enable this option to sort your inventory automatically") - end - - fs"container_end[]" - end +-- Get the formspec of the footer “Confirm trash” dialogue. +local function confirm_trash_footer_fs(player, data, fs) + image(2.8, 10.65, 4.6, 0.7, PNG.bg_goto) + label(3.02, 11, "Confirm trash?") + image_button(5.07, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes") + image_button(6.17, 10.75, 1, 0.5, "", "confirm_trash_no", "No") end -local function get_footer(fs, data, player) - local btn = { - {"trash", ES"Clear inventory"}, - {"sort", ES"Sort inventory"}, - {"settings", ES"Settings"}, - {"home", ES"Go home"}, - } +-- Get the formspec of the footer settings-dialogue. +local function settings_footer_fs(player, data, fs) + fs"container[-0.06,0]" + image(2.2, 9, 6.1, 2.35, PNG.bg_content) - for i, v in ipairs(btn) do - local btn_name, tooltip = unpack(v) - fs("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]", - btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)]) - image_button(i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "") - fs("tooltip[%s;%s;#32333899;#fff]", btn_name, tooltip) + local show_home = data.show_setting == "home" + local show_style = data.show_setting == "style" + local show_sorting = data.show_setting == "sorting" + + fs"style[setting_home,setting_style,setting_sorting;font=bold;font_size=16;sound=i3_click]" + fs("style[setting_home:hovered;textcolor=%s]", show_home and colors.yellow or "#fff") + fs("style[setting_style:hovered;textcolor=%s]", show_style and colors.yellow or "#fff") + fs("style[setting_sorting:hovered;textcolor=%s]", show_sorting and colors.yellow or "#fff") + + fs("style[setting_home;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", + show_home and PNG.pagenum_hover or "", PNG.pagenum_hover, + show_home and colors.yellow or "#ddd") + fs("style[setting_style;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", + show_style and PNG.pagenum_hover or "", PNG.pagenum_hover, + show_style and colors.yellow or "#ddd") + fs("style[setting_sorting;bgimg=%s;bgimg_hovered=%s;bgimg_middle=9;padding=-9;textcolor=%s]", + show_sorting and PNG.pagenum_hover or "", PNG.pagenum_hover, + show_sorting and colors.yellow or "#ddd") + + local X = 2.5 + button(X, 9.1, 1.6, 0.55, "setting_home", "Home") + button(X + 1.7, 9.1, 1.6, 0.55, "setting_style", "Style") + button(X + 3.4, 9.1, 1.6, 0.55, "setting_sorting", "Sorting") + image_button(X + 5.12, 9.2, 0.25, 0.25, PNG.cancel_hover .. "^\\[brighten", "close_settings", "") + + if show_home then + local coords, c, str = {"X", "Y", "Z"}, 0, ES"No home set" + + local home = data.home + if sethome then + -- i3 stores home coordinates with one decimal of precision, as the blow fmt() statement + -- assumes. So we need to trim sethome’s coordinates to one decimal, likewise. + local home_pos = sethome.get(player:get_player_name()) + home = string.format("(%.1f,%.1f,%.1f)", home_pos.x, home_pos.y, home_pos.z) + end + + if home then + str = home:gsub(",", " "):sub(2,-2):gsub("%.%d", ""):gsub( + "(%-?%d+)", function(a) + c++ + return fmt("%s: ", + coords[c], colors.blue, a) + end) + end + + hypertext(2.2, 9.9, 6, 0.6, "home_pos", fmt("
%s
", str)) + fs("style[set_home;padding=20,10,-210,-10;fgimg=%s;fgimg_hovered=%s]", PNG.home_px, PNG.home_px_hover) + image_button(4.1, 10.4, 2.2, 0.7, "", "set_home", "") + label(4.9, 10.75, "Set home") + + elseif show_style then + checkbox(2.6, 9.95, "cb_hide_tabs", "Hide tabs", tostring(data.hide_tabs)) + checkbox(2.6, 10.4, "cb_legacy_inventory", "Legacy inventory", tostring(data.legacy_inventory)) + checkbox(2.6, 10.85, "cb_wielditem_hud", "HUD description", tostring(data.wielditem_hud)) + + if not recipe_filter_set() then + checkbox(5.3, 10.85, "cb_collapse", "Collapse list", tostring(data.collapse)) + end + + local sign = (data.font_size > 0 and "+") or (data.font_size > 0 and "-") or "" + label(5.3, 9.95, ES"Font size" .. fmt(": %s", sign .. data.font_size)) + + local range = 8 + fs("scrollbaroptions[min=-%u;max=%u;smallstep=1;largestep=1;thumbsize=2]", range, range) + fs("scrollbar[5.3,10.2;2.55,0.3;horizontal;sb_font_size;%d+]", data.font_size) + + fs("tooltip[cb_hide_tabs;%s;#32333899;#fff]", + ES"Enable this option to change the style of the right panel") + fs("tooltip[cb_legacy_inventory;%s;#32333899;#fff]", + ES"Enable this option to set the classic inventory size in Minetest") + fs("tooltip[cb_wielditem_hud;%s;#32333899;#fff]", + ES"Enable this option to show the wielded item description in your HUD") + fs("tooltip[cb_collapse;%s;#32333899;#fff]", + ES"Enable this option to collapse the inventory list by grouping some items") + + elseif show_sorting then + checkbox(2.6, 9.95, "cb_inv_compress", "Compression", tostring(data.inv_compress)) + checkbox(2.6, 10.4, "cb_reverse_sorting", "Reverse mode", tostring(data.reverse_sorting)) + checkbox(2.6, 10.85, "cb_ignore_hotbar", "Ignore hotbar", tostring(data.ignore_hotbar)) + checkbox(5.3, 9.95, "cb_auto_sorting", "Automation", tostring(data.auto_sorting)) + + local methods = {} + + for _, v in ipairs(i3.sorting_methods) do + local name = toupper(v.name) + insert(methods, name) + end + + label(5.3, 10.4, ES"Sorting method:") + fs("dropdown[%f,%f;2.6,0.5;dd_sorting_method;%s;%u;true]", 5.3, 10.6, concat(methods, ","), data.sort) + + local desc = i3.sorting_methods[data.sort].description + if desc then + tooltip(5.3, 10.6, 2.4, 0.5, ESC(desc)) + end + + fs("tooltip[cb_inv_compress;%s;#32333899;#fff]", + ES"Enable this option to compress your inventory") + fs("tooltip[cb_reverse_sorting;%s;#32333899;#fff]", + ES"Enable this option to sort your inventory in reverse order") + fs("tooltip[cb_ignore_hotbar;%s;#32333899;#fff]", + ES"Enable this option to sort your inventory except the hotbar slots") + fs("tooltip[cb_auto_sorting;%s;#32333899;#fff]", + ES"Enable this option to sort your inventory automatically") + end + + fs"container_end[]" +end + +-- Get the inventory-footer’s formspec. +local function get_footer(fs, data, player) + -- Render individual footer buttons. + local starting_x = 3.43 - (.3 * (#i3.footer_buttons - 4)) -- Center the icons + for i, btn in ipairs(i3.footer_buttons) do + local btn_name = "footer_" .. btn.name + fs("style[%s;fgimg=%s;fgimg_hovered=%s^\\[brighten^\\[colorize:#fff:100;content_offset=0]", + btn_name, btn.image, btn.image) + image_button(i + starting_x - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "") + fs("tooltip[%s;%s;#32333899;#fff]", btn_name, btn.description) + end + + -- Render the current-selected button’s formspec, if one is active. + footer = i3.footer_buttons[data.footer_button] + if footer then + if footer.formspec then + footer.formspec(player, data, fs) + end end - show_settings(fs, data, player) end local function get_slots(fs, data, player) @@ -1821,4 +1825,4 @@ local function make_fs(player, data) return fs end -return make_fs, get_inventory_fs +return make_fs, get_inventory_fs, confirm_trash_footer_fs, settings_footer_fs