diff --git a/README.md b/README.md index 89510e4..fdd17fe 100644 --- a/README.md +++ b/README.md @@ -399,3 +399,38 @@ end) ``` ![Screenshot](https://user-images.githubusercontent.com/3182651/212222545-baee3669-15cd-410d-a638-c63b65a8811b.png) + +### Using a form as an inventory + +A form can be set as the player inventory. Flow internally generates the +formspec and passes it to `player:set_inventory_formspec()`. This will +completely replace your inventory and isn't compatible with inventory mods like +sfinv. + +```lua +local example_inventory = flow.make_gui(function (player, context) + return gui.Label{ label = "Inventory goes here!" } +end) +minetest.register_on_joinplayer(function(player) + example_inventory:set_as_inventory_for(player) +end) +``` + +Like with the `show_hud` function, `update*` functions don't do anything, so to +update it, call `set_as_inventory_for` again with the new context. If the +context is not provided, it will reuse the existing context. + +```lua +example_inventory:set_as_inventory_for(player, new_context) +``` + +While the form will of course be cleared when the player leaves, if you'd like +to unset the inventory manually, call `:unset_as_inventory_for(player)`, +analogue to `close_hud`: + +```lua +example_inventory:unset_as_inventory_for(player) +``` + +This will set the inventory formspec string to `""` and stop flow from +processing inventory formspec input. diff --git a/init.lua b/init.lua index 8802e23..4300503 100644 --- a/init.lua +++ b/init.lua @@ -720,10 +720,12 @@ function Form:_render(player, ctx, formspec_version, id1) "Changing the value of ctx.form is not supported!") ctx.form = orig_form + -- The numbering of automatically named elements is continued from previous + -- iterations of the form to work around race conditions + if not id1 or id1 > 1e6 then id1 = 0 end + local tree = render_ast(box) - local callbacks, saved_fields, id2 = parse_callbacks( - tree, orig_form, id1 or 0 - ) + local callbacks, saved_fields, id2 = parse_callbacks(tree, orig_form, id1) local redraw_if_changed = {} for var in pairs(used_ctx_vars) do @@ -744,8 +746,7 @@ function Form:_render(player, ctx, formspec_version, id1) } end -local open_formspecs = {} -local function show_form(self, player, formname, ctx, auto_name_id) +local function prepare_form(self, player, formname, ctx, auto_name_id) local name = player:get_player_name() -- local t = DEBUG_MODE and minetest.get_us_time() local info = minetest.get_player_information(name) @@ -757,10 +758,19 @@ local function show_form(self, player, formname, ctx, auto_name_id) -- local t3 = DEBUG_MODE and minetest.get_us_time() form_info.formname = formname - open_formspecs[name] = form_info -- if DEBUG_MODE then -- print(t3 - t, t2 - t, t3 - t2) -- end + return fs, form_info +end + +local open_formspecs = {} +local function show_form(self, player, formname, ctx, auto_name_id) + local name = player:get_player_name() + local fs, form_info = prepare_form(self, player, formname, ctx, + auto_name_id) + + open_formspecs[name] = form_info minetest.show_formspec(name, formname, fs) end @@ -786,6 +796,22 @@ function Form:show_hud(player, ctx) hud_fs.show_hud(player, self, tree) end +local open_inv_formspecs = {} +function Form:set_as_inventory_for(player, ctx) + local name = player:get_player_name() + local old_form_info = open_inv_formspecs[name] + if not ctx and old_form_info and old_form_info.self == self then + ctx = old_form_info.ctx + end + + -- Formname of "" is inventory + local fs, form_info = prepare_form(self, player, "", ctx or {}, + old_form_info and old_form_info.auto_name_id) + + open_inv_formspecs[name] = form_info + player:set_inventory_formspec(fs) +end + function Form:close(player) local name = player:get_player_name() local form_info = open_formspecs[name] @@ -799,15 +825,20 @@ function Form:close_hud(player) hud_fs.close_hud(player, self) end -local function update_form(self, player, form_info) - -- The numbering of automatically named elements is continued from previous - -- iterations of the form to work around race conditions - local auto_name_id - if form_info.auto_name_id < 1e6 then - auto_name_id = form_info.auto_name_id +function Form:unset_as_inventory_for(player) + local name = player:get_player_name() + local form_info = open_inv_formspecs[name] + if form_info and form_info.self == self then + open_inv_formspecs[name] = nil + player:set_inventory_formspec("") end +end - show_form(self, player, form_info.formname, form_info.ctx, auto_name_id) +-- This function may eventually call minetest.update_formspec if/when it gets +-- added (https://github.com/minetest/minetest/issues/13142) +local function update_form(self, player, form_info) + show_form(self, player, form_info.formname, form_info.ctx, + form_info.auto_name_id) end function Form:update(player) @@ -835,7 +866,8 @@ end local function on_fs_input(player, formname, fields) local name = player:get_player_name() - local form_info = open_formspecs[name] + local form_infos = formname == "" and open_inv_formspecs or open_formspecs + local form_info = form_infos[name] if not form_info or formname ~= form_info.formname then return end local callbacks = form_info.callbacks @@ -848,12 +880,13 @@ local function on_fs_input(player, formname, fields) for field, transformer in pairs(form_info.saved_fields) do if fields[field] then local new_value = transformer(fields[field]) - if redraw_if_changed[field] and ctx_form[field] ~= new_value then - if DEBUG_MODE then - print('Modified:', dump(field), dump(ctx_form[field]), - '->', dump(new_value)) + if ctx_form[field] ~= new_value then + if redraw_if_changed[field] then + redraw_fs = true + elseif formname == "" then + -- Update the inventory when the player closes it next + form_info.ctx_form_modified = true end - redraw_fs = true end ctx_form[field] = new_value end @@ -866,9 +899,14 @@ local function on_fs_input(player, formname, fields) end end - if open_formspecs[name] ~= form_info then return true end + if form_infos[name] ~= form_info then return true end - if fields.quit then + if formname == "" then + -- Special case for inventory forms + if redraw_fs or (fields.quit and form_info.ctx_form_modified) then + form_info.self:set_as_inventory_for(player) + end + elseif fields.quit then open_formspecs[name] = nil elseif redraw_fs then update_form(form_info.self, player, form_info) @@ -877,7 +915,9 @@ local function on_fs_input(player, formname, fields) end local function on_leaveplayer(player) - open_formspecs[player:get_player_name()] = nil + local name = player:get_player_name() + open_formspecs[name] = nil + open_inv_formspecs[name] = nil end if DEBUG_MODE then diff --git a/test.lua b/test.lua index 9f49c1f..134c81b 100644 --- a/test.lua +++ b/test.lua @@ -22,6 +22,27 @@ local function dummy() end minetest.register_on_leaveplayer = dummy minetest.get_modpath = dummy minetest.is_singleplayer = dummy +minetest.get_player_information = dummy +minetest.show_formspec = dummy + +-- Stub minetest player api +local function stub_player(name) + assert(type(name) == "string") + local self = {} + function self:get_player_name() + return name + end + function self:get_inventory_formspec() + return "" + end + function self:set_inventory_formspec(formspec) + assert(formspec ~= nil) + function self:get_inventory_formspec() + return formspec + end + end + return self +end table.indexof = table.indexof or function(list, value) for i, item in ipairs(list) do @@ -258,4 +279,39 @@ describe("Flow", function() style[test;prop=value] ]]) end) + + it("registers inventory formspecs", function () + local stupid_simple_inv_expected = + "formspec_version[5]" .. + "size[10.35,5.35]" .. + "list[current_player;main;0.3,0.3;8,4]" + local stupid_simple_inv = flow.make_gui(function (p, c) + return gui.List{ + inventory_location = "current_player", + list_name = "main", + w = 8, + h = 4, + } + end) + local player = stub_player("test_player") + assert(player:get_inventory_formspec() == "") + stupid_simple_inv:set_as_inventory_for(player) + assert(player:get_inventory_formspec() == stupid_simple_inv_expected) + end) + + it("can still show a form when an inventory formspec is shown", function () + local expected_one = "formspec_version[5]size[1.6,1.6]box[0.3,0.3;1,1;]" + local one = flow.make_gui(function (p, c) + return gui.Box{ w = 1, h = 1 } + end) + local blue = flow.make_gui(function (p, c) + return gui.Box{ w = 1, h = 4, color = "blue" } + end) + local player = stub_player("test_player") + assert(player:get_inventory_formspec() == "") + one:set_as_inventory_for(player) + assert(player:get_inventory_formspec() == expected_one) + blue:show(player) + assert(player:get_inventory_formspec() == expected_one) + end) end)