formspec_ast/helpers.lua

207 lines
6.2 KiB
Lua

--
-- formspec_ast: An abstract system tree for formspecs.
--
-- This does not actually depend on Minetest and could probably run in
-- standalone Lua.
--
-- The MIT License (MIT)
--
-- Copyright © 2019 by luk3yx.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to
-- deal in the Software without restriction, including without limitation the
-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-- sell copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-- IN THE SOFTWARE.
--
local formspec_ast, minetest = formspec_ast, formspec_ast.minetest
-- Parses and unparses plain formspecs and just unparses AST trees.
function formspec_ast.interpret(spec, custom_handlers)
local ast = spec
if type(spec) == 'string' then
local err
ast, err = formspec_ast.parse(spec, custom_handlers)
if not ast then
return nil, err
end
end
return formspec_ast.unparse(ast)
end
-- Returns an iterator over all nodes in a formspec AST, including ones in
-- containers.
function formspec_ast.walk(tree)
local parents = {}
local i = 1
return function()
local res = tree[i]
while not res do
local n = table.remove(parents)
if not n then
return
end
tree, i = n[1], n[2]
res = tree[i]
end
i = i + 1
if res.type == 'container' or res.type == 'scroll_container' then
table.insert(parents, {tree, i})
tree = res
i = 1
end
return res
end
end
-- Similar to formspec_ast.walk(), however only returns nodes which have a type
-- of `node_type`.
function formspec_ast.find(tree, node_type)
local walk = formspec_ast.walk(tree)
return function()
local node
repeat
node = walk()
until node == nil or node.type == node_type
return node
end
end
-- Returns the first element in the AST tree that has the given name.
function formspec_ast.get_element_by_name(tree, name)
for elem in formspec_ast.walk(tree) do
if elem.name == name then
return elem
end
end
end
-- Returns a table/list/array of all elements in the AST tree that have the
-- given name.
function formspec_ast.get_elements_by_name(tree, name)
local res = {}
for elem in formspec_ast.walk(tree) do
if elem.name == name then
table.insert(res, elem)
end
end
return res
end
-- Offsets all elements in an element list.
function formspec_ast.apply_offset(elems, x, y)
x, y = x or 0, y or 0
for _, elem in ipairs(elems) do
if type(elem.x) == 'number' and type(elem.y) == 'number' then
elem.x = elem.x + x
elem.y = elem.y + y
end
end
end
-- Removes container elements and fixes nodes inside containers.
function formspec_ast.flatten(tree)
local res = {formspec_version=tree.formspec_version}
for elem in formspec_ast.walk(table.copy(tree)) do
if elem.type == 'container' then
formspec_ast.apply_offset(elem, elem.x, elem.y)
else
table.insert(res, elem)
end
end
return res
end
-- Similar to minetest.show_formspec, however is passed through
-- formspec_ast.interpret first and will return an error message if the
-- formspec could not be parsed.
function formspec_ast.show_formspec(player, formname, formspec)
if minetest.is_player(player) then
player = player:get_player_name()
end
if type(player) ~= 'string' or player == '' then
return 'No such player!'
end
local new_fs, err = formspec_ast.interpret(formspec)
if new_fs then
minetest.show_formspec(player, formname, new_fs)
else
minetest.log('warning', 'formspec_ast.show_formspec(): ' ..
tostring(err))
return err
end
end
-- Centered labels
-- Credit to https://github.com/v-rob/minetest_formspec_game for the click
-- animation workaround.
-- size[5,2]formspec_ast:centered_label[0,0;5,1;Centered label]
formspec_ast.register_element('formspec_ast:centered_label', function(raw,
parse)
-- Create a container
return {
type = 'container',
x = parse.number(raw[1][1]),
y = parse.number(raw[1][2]),
-- Add a background-less image button with the text.
{
type = 'image_button',
x = 0,
y = 0,
w = parse.number(raw[2][1]),
h = parse.number(raw[2][2]),
texture_name = 'blank.png',
name = '',
label = parse.string(raw[3]),
noclip = true,
drawborder = false,
pressed_texture_name = '',
},
-- Add another background-less image button to hack around the click
-- animation.
{
type = 'image_button',
x = 0,
y = 0,
w = parse.number(raw[2][1]),
h = parse.number(raw[2][2]),
texture_name = '',
name = '',
label = '',
noclip = true,
drawborder = false,
pressed_texture_name = '',
},
}
end)
-- Add a formspec element to crash clients
formspec_ast.register_element('formspec_ast:crash', function(_, _)
return {
type = 'list',
inventory_location = '___die',
list_name = 'crash',
x = 0,
y = 0,
w = 0,
h = 0,
}
end)