245 lines
7.2 KiB
Lua
245 lines
7.2 KiB
Lua
--
|
|
-- fs51 - Compatibility layer for Minetest formspecs
|
|
--
|
|
-- Copyright © 2019-2021 by luk3yx.
|
|
--
|
|
|
|
fs51 = {}
|
|
local fs51 = fs51
|
|
|
|
local padding, spacing_x, spacing_y = 3/8, 5/4, 15/13
|
|
|
|
-- Random offsets
|
|
local random_offsets = {
|
|
-- box = {{0, 0}, {0.2, 0.125}},
|
|
label = {{0, 0.3}},
|
|
field = {{-padding, -0.33}, {-0.25, -0.2}},
|
|
pwdfield = {{-padding, -0.33}, {-0.25, 0}},
|
|
-- textarea = {{-0.3, -0.33}, {-0.2, 0}},
|
|
textarea = {{-padding, 0}, {-0.25, -padding}},
|
|
dropdown = {{0, 0}, {-0.25, 0}},
|
|
checkbox = {{0, 0.5}},
|
|
background = {{(1 - spacing_x) / 2, (1 - spacing_y) / 2}},
|
|
tabheader = {{-padding, -padding}},
|
|
}
|
|
|
|
local fixers = {}
|
|
|
|
local function fix_pos(elem, random_offset)
|
|
if type(elem.x) == 'number' and type(elem.y) == 'number' then
|
|
if random_offset then
|
|
elem.x = elem.x - random_offset[1][1]
|
|
elem.y = elem.y - random_offset[1][2]
|
|
end
|
|
|
|
elem.x = (elem.x - padding) / spacing_x
|
|
elem.y = (elem.y - padding) / spacing_y
|
|
end
|
|
end
|
|
|
|
local function default_fixer(elem)
|
|
local random_offset = random_offsets[elem.type]
|
|
fix_pos(elem, random_offset)
|
|
|
|
if type(elem.w) == 'number' then
|
|
if random_offset and random_offset[2] then
|
|
elem.w = elem.w - random_offset[2][1]
|
|
end
|
|
elem.w = elem.w / spacing_x
|
|
end
|
|
|
|
if type(elem.h) == 'number' then
|
|
if random_offset and random_offset[2] then
|
|
elem.h = elem.h - random_offset[2][2]
|
|
end
|
|
elem.h = elem.h / spacing_y
|
|
end
|
|
end
|
|
|
|
-- Other fixers
|
|
function fixers.image_button(elem)
|
|
fix_pos(elem, random_offsets[elem.type])
|
|
elem.w = elem.w * 0.8 + 0.205
|
|
elem.h = elem.h * 0.866 + 0.134
|
|
end
|
|
fixers.item_image_button = fixers.image_button
|
|
fixers.image_button_exit = fixers.image_button
|
|
|
|
function fixers.textarea(elem)
|
|
local h = elem.h
|
|
default_fixer(elem)
|
|
elem.h = h + 0.15
|
|
end
|
|
|
|
fixers.image = fix_pos
|
|
fixers.item_image = fixers.image
|
|
|
|
function fixers.button(elem)
|
|
elem.type = 'image_' .. elem.type
|
|
elem.texture_name = 'blank.png'
|
|
return fixers.image_button(elem)
|
|
end
|
|
fixers.button_exit = fixers.button
|
|
|
|
function fixers.size(elem)
|
|
elem.w = elem.w / spacing_x - padding * 2 + 0.36
|
|
elem.h = elem.h / spacing_y - padding * 2
|
|
end
|
|
|
|
-- Lists are a special case because they return a container which needs to be
|
|
-- processed and flattened.
|
|
local function fix_list(elem)
|
|
fix_pos(elem)
|
|
|
|
-- Split the list[] into multiple list[]s.
|
|
local start = math.max(elem.starting_item_index or 0, 0)
|
|
for row = 1, elem.h do
|
|
local r = row - 1
|
|
elem[row] = {
|
|
type = 'list',
|
|
inventory_location = elem.inventory_location,
|
|
list_name = elem.list_name,
|
|
x = 0,
|
|
y = (r * 1.25) / spacing_y,
|
|
w = elem.w,
|
|
h = 1,
|
|
starting_item_index = start + (elem.w * r),
|
|
}
|
|
end
|
|
end
|
|
|
|
-- Remove the "height" attribute on dropdowns.
|
|
function fixers.dropdown(elem)
|
|
fix_pos(elem)
|
|
elem.w = elem.w / spacing_y
|
|
elem.h = nil
|
|
|
|
-- Make index_event nil if it's set to false
|
|
elem.index_event = elem.index_event or nil
|
|
end
|
|
|
|
-- Use a hack to make "neither" work properly. Not much can be done about
|
|
-- "both" unfortunately.
|
|
function fixers.bgcolor(elem)
|
|
if elem.fullscreen == 'neither' then
|
|
elem.bgcolor = '#0000'
|
|
elem.fullscreen = false
|
|
end
|
|
elem.fbgcolor = nil
|
|
end
|
|
|
|
--
|
|
local pre_types = {size = true, position = true, anchor = true,
|
|
no_prepend = true}
|
|
local xywh = {'x', 'y', 'w', 'h'}
|
|
function fs51.backport(tree)
|
|
-- Flatten the tree (this will also copy it).
|
|
tree = formspec_ast.flatten(tree)
|
|
local real_coordinates = type(tree.formspec_version) == 'number' and
|
|
tree.formspec_version >= 2
|
|
tree.formspec_version = 1
|
|
|
|
-- Check for an initial real_coordinates[].
|
|
if not real_coordinates then
|
|
for _, elem in ipairs(tree) do
|
|
if elem.type == 'real_coordinates' then
|
|
real_coordinates = elem.bool
|
|
break
|
|
elseif not pre_types[elem.type] then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Allow deletion of real_coordinates[]
|
|
local list1, list2
|
|
local i = 1
|
|
while tree[i] ~= nil do
|
|
local elem = tree[i]
|
|
if elem.type == 'real_coordinates' then
|
|
real_coordinates = elem.bool
|
|
table.remove(tree, i)
|
|
i = i - 1
|
|
elseif elem.type == 'list' then
|
|
-- There's no need to store every single list
|
|
list1, list2 = list2, elem
|
|
|
|
if real_coordinates then
|
|
fix_list(elem)
|
|
formspec_ast.apply_offset(elem, elem.x, elem.y)
|
|
|
|
-- Remove the container from the tree and append its contents.
|
|
tree[i] = elem[1]
|
|
for j = 2, #elem do
|
|
i = i + 1
|
|
table.insert(tree, i, elem[j])
|
|
end
|
|
end
|
|
elseif elem.type == 'listring' and not elem.inventory_location and
|
|
list1 then
|
|
-- This is required because lists are split into multiple elements
|
|
elem.inventory_location = list1.inventory_location
|
|
elem.list_name = list1.list_name
|
|
|
|
i = i + 1
|
|
table.insert(tree, i, {
|
|
type = 'listring',
|
|
inventory_location = list2.inventory_location,
|
|
list_name = list2.list_name,
|
|
})
|
|
elseif elem.type == 'label' and real_coordinates then
|
|
-- This workaround is probably too specific and targets
|
|
-- unified_inventory.
|
|
default_fixer(elem)
|
|
elem.x = math.floor(elem.x * 1000) / 1000
|
|
elem.y = math.floor(elem.y * 1000) / 1000
|
|
|
|
-- Move labels before buttons if they don't clip
|
|
local j = i
|
|
while j > 1 do
|
|
j = j - 1
|
|
local elem2 = tree[j]
|
|
if elem2.type:sub(-6) == 'button' then
|
|
if elem2.x + elem2.w > elem.x and
|
|
elem2.y + elem2.h > elem.y + 0.225 then
|
|
break
|
|
end
|
|
elseif elem2.type ~= 'tooltip' then
|
|
break
|
|
end
|
|
end
|
|
table.remove(tree, i)
|
|
table.insert(tree, j + 1, elem)
|
|
elseif real_coordinates then
|
|
(fixers[elem.type] or default_fixer)(elem)
|
|
for _, n in ipairs(xywh) do
|
|
if elem[n] then
|
|
elem[n] = math.floor(elem[n] * 1000) / 1000
|
|
end
|
|
end
|
|
end
|
|
|
|
i = i + 1
|
|
end
|
|
|
|
return tree
|
|
end
|
|
|
|
local minetest_log = rawget(_G, 'minetest') and minetest.log or print
|
|
function fs51.backport_string(formspec)
|
|
local fs, err = formspec_ast.parse(formspec)
|
|
if not fs then
|
|
minetest_log('warning', '[fs51] Error parsing formspec: ' ..
|
|
tostring(err))
|
|
return nil, err
|
|
end
|
|
return formspec_ast.unparse(fs51.backport(fs))
|
|
end
|
|
|
|
-- Monkey patch Minetest's code
|
|
if rawget(_G, 'minetest') and minetest.register_on_player_receive_fields and
|
|
not minetest.settings:get_bool('fs51.disable_monkey_patching') then
|
|
local fn = minetest.get_modpath('fs51') .. '/monkey_patching.lua'
|
|
assert(loadfile(fn))(fixers)
|
|
end
|