forked from your-land-mirror/minetest-flow
feat: Add render_to_formspec_string function (#5)
Co-authored-by: luk3yx <luk3yx@users.noreply.github.com>
This commit is contained in:
parent
fa13e188f9
commit
60d2674a45
41
README.md
41
README.md
@ -364,19 +364,20 @@ gui.Button{
|
||||
},
|
||||
```
|
||||
|
||||
## Experimental features
|
||||
The style elements are invisible and won't affect padding.
|
||||
|
||||
These features might be broken in the future.
|
||||
|
||||
### Hiding elements
|
||||
## Hiding elements
|
||||
|
||||
Elements inside boxes can have `visible = false` set to hide them from the
|
||||
player. Elements hidden this way will still take up space like with
|
||||
`visibility: hidden;` in CSS.
|
||||
|
||||
The style elements are invisible and won't affect padding.
|
||||
## Experimental features
|
||||
|
||||
### `no_prepend[]`
|
||||
These features might be broken in the future.
|
||||
|
||||
<details>
|
||||
<summary><b><code>no_prepend[]</code></b></summary>
|
||||
|
||||
You can set `no_prepend = true` on the "root" element to disable formspec
|
||||
prepends.
|
||||
@ -400,7 +401,8 @@ end)
|
||||
|
||||

|
||||
|
||||
### Using a form as an inventory
|
||||
</details><details>
|
||||
<summary><b>Using a form as an inventory</b></summary>
|
||||
|
||||
A form can be set as the player inventory. Flow internally generates the
|
||||
formspec and passes it to `player:set_inventory_formspec()`. This will
|
||||
@ -434,3 +436,28 @@ example_inventory:unset_as_inventory_for(player)
|
||||
|
||||
This will set the inventory formspec string to `""` and stop flow from
|
||||
processing inventory formspec input.
|
||||
|
||||
</details><details>
|
||||
<summary><b>Rendering to a formspec</b></summary>
|
||||
|
||||
This API should only be used when necessary and may have breaking changes in
|
||||
the future.
|
||||
|
||||
Some APIs in other mods, such as sfinv, expect formspec strings. You can use
|
||||
this API to embed flow forms inside them. To use flow with these mods, you can
|
||||
call `form:render_to_formspec_string(player, ctx, standalone)`.
|
||||
|
||||
- By default the the `formspec_version` and `size` elements aren't included in
|
||||
the returned formspec and are included in a third return value. Set
|
||||
`standalone` to include them in the returned formspec string. The third
|
||||
return value will not be returned.
|
||||
- Returns `formspec, process_event[, info]`
|
||||
- The `process_event(fields)` callback will return true if the formspec should
|
||||
be redrawn, where `render_to_formspec_string` should be called and the new
|
||||
`process_event` should be used in the future. This function may return true
|
||||
even if fields.quit is sent.
|
||||
|
||||
|
||||
**Do not use this API with node meta formspecs, it can and will break!**
|
||||
|
||||
</details>
|
||||
|
64
init.lua
64
init.lua
@ -842,6 +842,47 @@ function Form:set_as_inventory_for(player, ctx)
|
||||
player:set_inventory_formspec(fs)
|
||||
end
|
||||
|
||||
-- Declared here to be accessible by render_to_formspec_string
|
||||
local fs_process_events
|
||||
|
||||
-- Prevent collisions in forms, but also ensure they don't happen across
|
||||
-- mutliple embedded forms within a single parent.
|
||||
-- Unique per-user to prevent players from making the counter wrap around for
|
||||
-- other players.
|
||||
local render_to_formspec_auto_name_ids = {}
|
||||
-- If `standalone` is set, this will return a standalone formspec, otherwise it
|
||||
-- will return a formspec that can be embedded and a table with its size and
|
||||
-- target formspec version
|
||||
function Form:render_to_formspec_string(player, ctx, standalone)
|
||||
local name = player:get_player_name()
|
||||
local info = minetest.get_player_information(name)
|
||||
local tree, form_info = self:_render(player, ctx or {},
|
||||
info and info.formspec_version, render_to_formspec_auto_name_ids[name])
|
||||
local public_form_info
|
||||
if not standalone then
|
||||
local size = table.remove(tree, 1)
|
||||
public_form_info = {w = size.w, h = size.h,
|
||||
formspec_version = tree.formspec_version}
|
||||
tree.formspec_version = nil
|
||||
end
|
||||
local fs = assert(formspec_ast.unparse(tree))
|
||||
render_to_formspec_auto_name_ids[name] = form_info.auto_name_id
|
||||
local function event(fields)
|
||||
-- Just in case the player goes offline, we should not keep the player
|
||||
-- reference. Nothing prevents the user from calling this function when
|
||||
-- the player is offline, unlike the _real_ formspec submission.
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then
|
||||
minetest.log("warning", "[flow] Player " .. name ..
|
||||
" was offline when render_to_formspec_string event was" ..
|
||||
" triggered. Events were not passed through.")
|
||||
return nil
|
||||
end
|
||||
return fs_process_events(player, form_info, fields)
|
||||
end
|
||||
return fs, event, public_form_info
|
||||
end
|
||||
|
||||
function Form:close(player)
|
||||
local name = player:get_player_name()
|
||||
local form_info = open_formspecs[name]
|
||||
@ -894,12 +935,8 @@ function flow.make_gui(build_func)
|
||||
return setmetatable({_build = build_func}, form_mt)
|
||||
end
|
||||
|
||||
local function on_fs_input(player, formname, fields)
|
||||
local name = player:get_player_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
|
||||
|
||||
-- Declared locally above to be accessible to render_to_formspec_string
|
||||
function fs_process_events(player, form_info, fields)
|
||||
local callbacks = form_info.callbacks
|
||||
local ctx = form_info.ctx
|
||||
local redraw_if_changed = form_info.redraw_if_changed
|
||||
@ -915,6 +952,7 @@ local function on_fs_input(player, formname, fields)
|
||||
-- large amount of data and very long strings have the
|
||||
-- potential to break things. Please open an issue if you
|
||||
-- (somehow) need to use longer text in fields.
|
||||
local name = player:get_player_name()
|
||||
minetest.log("warning", "[flow] Player " .. name .. " tried" ..
|
||||
" submitting a large field value (>60 kB), ignoring.")
|
||||
else
|
||||
@ -922,7 +960,7 @@ local function on_fs_input(player, formname, fields)
|
||||
if ctx_form[field] ~= new_value then
|
||||
if redraw_if_changed[field] then
|
||||
redraw_fs = true
|
||||
elseif formname == "" then
|
||||
elseif form_info.formname == "" then
|
||||
-- Update the inventory when the player closes it next
|
||||
form_info.ctx_form_modified = true
|
||||
end
|
||||
@ -939,6 +977,17 @@ local function on_fs_input(player, formname, fields)
|
||||
end
|
||||
end
|
||||
|
||||
return redraw_fs
|
||||
end
|
||||
|
||||
local function on_fs_input(player, formname, fields)
|
||||
local name = player:get_player_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 redraw_fs = fs_process_events(player, form_info, fields)
|
||||
|
||||
if form_infos[name] ~= form_info then return true end
|
||||
|
||||
if formname == "" then
|
||||
@ -958,6 +1007,7 @@ local function on_leaveplayer(player)
|
||||
local name = player:get_player_name()
|
||||
open_formspecs[name] = nil
|
||||
open_inv_formspecs[name] = nil
|
||||
render_to_formspec_auto_name_ids[name] = nil
|
||||
end
|
||||
|
||||
if DEBUG_MODE then
|
||||
|
73
test.lua
73
test.lua
@ -72,7 +72,7 @@ local gui = flow.widgets
|
||||
-- values and fix weird floating point offsets
|
||||
local function normalise_tree(tree)
|
||||
tree = formspec_ast.flatten(tree)
|
||||
tree.formspec_version = 5
|
||||
tree.formspec_version = 6
|
||||
return assert(formspec_ast.parse(formspec_ast.unparse(tree)))
|
||||
end
|
||||
|
||||
@ -282,7 +282,7 @@ describe("Flow", function()
|
||||
|
||||
it("registers inventory formspecs", function ()
|
||||
local stupid_simple_inv_expected =
|
||||
"formspec_version[5]" ..
|
||||
"formspec_version[6]" ..
|
||||
"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)
|
||||
@ -300,7 +300,7 @@ describe("Flow", function()
|
||||
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 expected_one = "formspec_version[6]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)
|
||||
@ -314,4 +314,71 @@ describe("Flow", function()
|
||||
blue:show(player)
|
||||
assert(player:get_inventory_formspec() == expected_one)
|
||||
end)
|
||||
|
||||
describe("render_to_formspec_string", function ()
|
||||
it("renders the same output as manually calling _render when standalone", function()
|
||||
local build_func = function()
|
||||
return gui.VBox{
|
||||
gui.Box{w = 1, h = 1},
|
||||
gui.Label{label = "Test", align_h = "centre"},
|
||||
gui.Field{name = "4", label = "Test", align_v = "fill"}
|
||||
}
|
||||
end
|
||||
local form = flow.make_gui(build_func)
|
||||
local player = stub_player("test_player")
|
||||
local fs = form:render_to_formspec_string(player, nil, true)
|
||||
test_render(build_func, fs)
|
||||
end)
|
||||
it("renders nearly the same output as manually calling _render when not standalone", function()
|
||||
local build_func = function()
|
||||
return gui.VBox{
|
||||
gui.Box{w = 1, h = 1},
|
||||
gui.Label{label = "Test", align_h = "centre"},
|
||||
gui.Field{name = "4", label = "Test", align_v = "fill"}
|
||||
}
|
||||
end
|
||||
local form = flow.make_gui(build_func)
|
||||
local player = stub_player("test_player")
|
||||
local fs, _, info = form:render_to_formspec_string(player)
|
||||
test_render(
|
||||
build_func,
|
||||
("formspec_version[%s]size[%s,%s]"):format(
|
||||
info.formspec_version,
|
||||
info.w,
|
||||
info.h
|
||||
) .. fs
|
||||
)
|
||||
end)
|
||||
it("passes events through the callback function", function()
|
||||
local manual_spy
|
||||
local manual_spy_count = 0
|
||||
local buttonargs = {
|
||||
label = "Click me!",
|
||||
name = "btn",
|
||||
on_event = function (...)
|
||||
manual_spy = {...}
|
||||
manual_spy_count = manual_spy_count + 1
|
||||
end
|
||||
}
|
||||
local form = flow.make_gui(function()
|
||||
return gui.Button(buttonargs)
|
||||
end)
|
||||
local player = stub_player("test_player")
|
||||
function minetest.get_player_by_name(name)
|
||||
assert(name == "test_player")
|
||||
return player
|
||||
end
|
||||
local ctx = {a = 1}
|
||||
local _, trigger_event = form:render_to_formspec_string(player, ctx, true)
|
||||
|
||||
local fields = {btn = 1}
|
||||
trigger_event(fields)
|
||||
|
||||
assert.equals(manual_spy_count, 1, "event passed down only once")
|
||||
assert.equals(manual_spy[1], player, "player was first arg")
|
||||
assert.equals(manual_spy[2], ctx, "context was next")
|
||||
|
||||
minetest.get_player_by_name = nil
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user