Implement new API for adding custom footer-buttons

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.
This commit is contained in:
Jaidyn Ann 2024-01-11 22:21:41 -06:00
parent 74f8fe64a8
commit 623b14bfe8
4 changed files with 292 additions and 187 deletions

View File

@ -20,7 +20,7 @@ local function lf(path)
end end
i3 = { i3 = {
version = 1161, version = 1162,
data = core.deserialize(storage:get_string"data") or {}, data = core.deserialize(storage:get_string"data") or {},
settings = { settings = {
@ -102,6 +102,7 @@ i3 = {
plants = {}, plants = {},
modules = {}, modules = {},
minitabs = {}, minitabs = {},
footer_buttons = {},
craft_types = {}, craft_types = {},
recipe_filters = {}, recipe_filters = {},

View File

@ -1,5 +1,6 @@
local http = ... 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("sorter", "sort_inventory", "play_sound")
IMPORT("sort", "concat", "copy", "insert", "remove") IMPORT("sort", "concat", "copy", "insert", "remove")
@ -257,6 +258,88 @@ function i3.override_tab(name, newdef)
end end
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", { i3.register_craft_type("digging", {
description = S"Digging", description = S"Digging",
icon = "i3_steelpick.png", icon = "i3_steelpick.png",

View File

@ -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("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") 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 function inv_fields(player, data, fields)
local name = data.player_name local name = data.player_name
local inv = player:get_inventory() 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+$") data.font_size = tonumber(fields.sb_font_size:match"-?%d+$")
end 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 for field in pairs(fields) do
if sub(field, 1, 4) == "btn_" then if sub(field, 1, 4) == "btn_" then
data.subcat = indexof(i3.categories, sub(field, 5)) data.subcat = indexof(i3.categories, sub(field, 5))
@ -93,8 +101,7 @@ local function inv_fields(player, data, fields)
end end
if fields.quit then if fields.quit then
data.confirm_trash = nil data.footer_button = nil
data.show_settings = nil
data.waypoint_see = nil data.waypoint_see = nil
data.bag_rename = nil data.bag_rename = nil
data.goto_page = nil data.goto_page = nil
@ -103,58 +110,9 @@ local function inv_fields(player, data, fields)
data.enable_search = nil data.enable_search = nil
end 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 elseif fields.close_preview then
data.waypoint_see = nil 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 elseif fields.bag_rename then
data.bag_rename = true data.bag_rename = true
@ -429,6 +387,55 @@ local function rcp_fields(player, data, fields)
end end
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_games 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 i3s 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) core.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name() 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)) data.itab = tonumber(f:sub(-1))
sort_by_category(data) sort_by_category(data)
break 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
end end
@ -484,3 +499,5 @@ core.register_on_player_receive_fields(function(player, formname, fields)
return true, set_fs(player) return true, set_fs(player)
end) end)
return home_footer_fields, confirm_trash_footer_fields, settings_footer_fields

View File

@ -569,14 +569,16 @@ local function get_container(fs, data, player, yoffset, ctn_len, award_list, awa
end end
end end
local function show_settings(fs, data, player) -- Get the formspec of the footer “Confirm trash” dialogue.
if data.confirm_trash then local function confirm_trash_footer_fs(player, data, fs)
image(2.8, 10.65, 4.6, 0.7, PNG.bg_goto) image(2.8, 10.65, 4.6, 0.7, PNG.bg_goto)
label(3.02, 11, "Confirm trash?") label(3.02, 11, "Confirm trash?")
image_button(5.07, 10.75, 1, 0.5, "", "confirm_trash_yes", "Yes") 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") image_button(6.17, 10.75, 1, 0.5, "", "confirm_trash_no", "No")
end
elseif data.show_settings then -- Get the formspec of the footer settings-dialogue.
local function settings_footer_fs(player, data, fs)
fs"container[-0.06,0]" fs"container[-0.06,0]"
image(2.2, 9, 6.1, 2.35, PNG.bg_content) image(2.2, 9, 6.1, 2.35, PNG.bg_content)
@ -687,26 +689,28 @@ local function show_settings(fs, data, player)
end end
fs"container_end[]" fs"container_end[]"
end
end end
-- Get the inventory-footers formspec.
local function get_footer(fs, data, player) local function get_footer(fs, data, player)
local btn = { -- Render individual footer buttons.
{"trash", ES"Clear inventory"}, local starting_x = 3.43 - (.3 * (#i3.footer_buttons - 4)) -- Center the icons
{"sort", ES"Sort inventory"}, for i, btn in ipairs(i3.footer_buttons) do
{"settings", ES"Settings"}, local btn_name = "footer_" .. btn.name
{"home", ES"Go home"}, 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, "")
for i, v in ipairs(btn) do fs("tooltip[%s;%s;#32333899;#fff]", btn_name, btn.description)
local btn_name, tooltip = unpack(v) end
fs("style[%s;fgimg=%s;fgimg_hovered=%s;content_offset=0]",
btn_name, PNG[btn_name], PNG[fmt("%s_hover", btn_name)]) -- Render the current-selected buttons formspec, if one is active.
image_button(i + 3.43 - (i * 0.4), 11.43, 0.35, 0.35, "", btn_name, "") footer = i3.footer_buttons[data.footer_button]
fs("tooltip[%s;%s;#32333899;#fff]", btn_name, tooltip) if footer then
if footer.formspec then
footer.formspec(player, data, fs)
end
end end
show_settings(fs, data, player)
end end
local function get_slots(fs, data, player) local function get_slots(fs, data, player)
@ -1821,4 +1825,4 @@ local function make_fs(player, data)
return fs return fs
end end
return make_fs, get_inventory_fs return make_fs, get_inventory_fs, confirm_trash_footer_fs, settings_footer_fs