Fix a lot of broken things.
This commit is contained in:
parent
6e0f653928
commit
be73993547
2 changed files with 309 additions and 95 deletions
385
init.lua
385
init.lua
|
|
@ -18,11 +18,14 @@ local function resolve_layout_units(value, ref)
|
|||
local num = tonumber(value)
|
||||
if num then return num end
|
||||
|
||||
local percent = value:match "(%d+)%%"
|
||||
local percent = value:match "(%d*.?%d+)%%"
|
||||
if ref and percent then
|
||||
return tonumber(percent) /100 *ref
|
||||
local sign, offset = value:match "%%%s*([+-])%s*(%d*.?%d+)"
|
||||
return tonumber(percent) /100 *ref +(tonumber(offset or 0) *(sign == "-" and -1 or 1))
|
||||
end
|
||||
|
||||
print(string.match("100%", "(%d*.?%d+)%%"), percent, ref)
|
||||
|
||||
error("Malformed layout units: "..value)
|
||||
else
|
||||
return value
|
||||
|
|
@ -35,12 +38,13 @@ local function resolve_flex_layout_units(value, ref)
|
|||
local num = tonumber(value)
|
||||
if num then return num end
|
||||
|
||||
local percent = value:match "(%d+)%%"
|
||||
local percent = value:match "(%d*.?%d+)%%"
|
||||
if ref and percent then
|
||||
return tonumber(percent) /100 *ref
|
||||
local sign, offset = value:match "%%%s*([+-])%s*(%d*.?%d+)"
|
||||
return tonumber(percent) /100 *ref +(tonumber(offset or 0) *(sign == "-" and -1 or 1))
|
||||
end
|
||||
|
||||
local flex = value:match "(%d+)x"
|
||||
local flex = value:match "(%d*.?%d+)x"
|
||||
if flex then
|
||||
return tonumber(flex), "flex"
|
||||
end
|
||||
|
|
@ -63,18 +67,49 @@ local function add_elem_style(e, state, props)
|
|||
end
|
||||
|
||||
-- This should be a method on any element classes that support tooltips.
|
||||
local function add_elem_tooltip(e, text, bgcolor, txtcolor)
|
||||
local function add_elem_tooltip(e, text, enabled, bgcolor, txtcolor)
|
||||
-- Permit the user to pass a boolean to disable the tooltip (useful for modal dialogs that should occlude tooltips without more bothersome composition boilerplate than necessary).
|
||||
if enabled == false then
|
||||
return
|
||||
elseif enabled ~= true then
|
||||
txtcolor = bgcolor
|
||||
bgcolor = enabled
|
||||
end
|
||||
-- For named elements, prefer name-associated tooltips.
|
||||
if e.__id then
|
||||
imfs._named_tooltip(e.__id, text, bgcolor, txtcolor)
|
||||
-- For other elements, if they have a size, use an area tooltip instead.
|
||||
elseif e.w then
|
||||
imfs.tooltip(e.x, e.y, e.w, e.h, text, bgcolor, txtcolor)
|
||||
-- We must overload `render()` in order to ensure the tooltip's position matches the parent's exactly in all cases.
|
||||
local render = e.render
|
||||
e.render = function(e, x, y, w, h)
|
||||
local out = render(e, x, y, w, h)
|
||||
return table.concat{out, imfs.tooltip.init(x or e.x, y or e.y, w or e.w, h or e.h, text, bgcolor, txtcolor, true):render()}
|
||||
end
|
||||
end
|
||||
return e
|
||||
end
|
||||
|
||||
local name_scope = {"_"}
|
||||
local nth_id = {0}
|
||||
|
||||
local function scope(name)
|
||||
table.insert(name_scope, name)
|
||||
table.insert(nth_id, 0)
|
||||
end
|
||||
|
||||
local function scope_end()
|
||||
table.remove(name_scope)
|
||||
table.remove(nth_id)
|
||||
end
|
||||
|
||||
local function new_id()
|
||||
local depth = #nth_id
|
||||
nth_id[depth] = nth_id[depth] +1
|
||||
return table.concat(name_scope, ".").."_"..nth_id[depth]
|
||||
end
|
||||
|
||||
local function unique_id()
|
||||
return "_"..minetest.get_us_time().."_"..math.random(1, 100000)
|
||||
end
|
||||
|
||||
|
|
@ -90,7 +125,7 @@ local state = {
|
|||
get = function(e)
|
||||
local observer = observers[#observers]
|
||||
if observer then
|
||||
observer[e] = true -- Add this base state as a dep (using table for set-like uniqueness)
|
||||
observer[e] = true
|
||||
end
|
||||
return e._val
|
||||
end,
|
||||
|
|
@ -207,6 +242,12 @@ setmetatable(fs_style, {
|
|||
})
|
||||
|
||||
local fs_tooltip = {
|
||||
_no_layout = true,
|
||||
init = function(x, y, w, h, text, bgcolor, txtcolor)
|
||||
local e = {x = x, y = y, w = w, h = h, text = text, bgcolor = bgcolor or imfs.theme.tooltip_bg or "#444", txtcolor = txtcolor or imfs.theme.tooltip_text or "#aaa"}
|
||||
setmetatable(e, imfs.tooltip)
|
||||
return e
|
||||
end,
|
||||
render = function(e, x, y, w, h)
|
||||
if e._name then
|
||||
return string.format("tooltip[%s;%s;%s;%s]", e._name, get(e, "text"), get(e, "bgcolor"), get(e, "txtcolor"))
|
||||
|
|
@ -285,7 +326,7 @@ setmetatable(fs_arealabel, {
|
|||
|
||||
local fs_hypertext = {
|
||||
render = function(e, x, y, w, h)
|
||||
return string.format("hypertext[%f,%f;%f,%f;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), hte(get(e, "txt")))
|
||||
return string.format("hypertext[%f,%f;%f,%f;%s;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), e.__id, get(e, "txt"))
|
||||
end,
|
||||
onaction = function(e, fn)
|
||||
ctx._events.on_click[e.__id] = fn
|
||||
|
|
@ -335,7 +376,7 @@ local fs_image = {
|
|||
get(e, "middle")
|
||||
)
|
||||
else
|
||||
return string.format("image[%f,%f;%f,%f;%s;%s]", get(e, "x"), get(e, "y"), get(e, "w"), get(e, "h"), get(e, "texture"), get(e, "middle"))
|
||||
return string.format("image[%f,%f;%f,%f;%s;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "texture"), get(e, "middle"))
|
||||
end
|
||||
end,
|
||||
animated = function(e, frames, duration, start)
|
||||
|
|
@ -360,7 +401,7 @@ setmetatable(fs_image, {
|
|||
|
||||
local fs_item_image = {
|
||||
render = function(e, x, y, w, h)
|
||||
return string.format("item_image[%f,%f;%f,%f;%s]", get(e, "x"), get(e, "y"), get(e, "w"), get(e, "h"), get(e, "item"))
|
||||
return string.format("item_image[%f,%f;%f,%f;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "item"))
|
||||
end,
|
||||
tooltip = add_elem_tooltip,
|
||||
}
|
||||
|
|
@ -453,9 +494,9 @@ local fs_button = {
|
|||
elseif e._image then
|
||||
if e._image_pressed then
|
||||
-- We never specify noclip or border here. That's a job for styles.
|
||||
out[#out +1] = string.format("image_button[%f,%f;%f,%f;%s;%s;;;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "_image"), e.__id, get(e, "label"), get(e, "_image_pressed"))
|
||||
out[#out +1] = string.format("image_button[%f,%f;%f,%f;%s;%s;%s;;%s]", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "_image"), e.__id, get(e, "label"), get(e, "_image_pressed"))
|
||||
else
|
||||
out[#out +1] = string.format("image_button%s[%f,%f;%f,%f;%s;%s]", e._exit and "_exit" or "", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "_image"), e.__id, get(e, "label"))
|
||||
out[#out +1] = string.format("image_button%s[%f,%f;%f,%f;%s;%s;%s]", e._exit and "_exit" or "", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), get(e, "_image"), e.__id, get(e, "label"))
|
||||
end
|
||||
else
|
||||
out[#out +1] = string.format("button%s[%f,%f;%f,%f;%s;%s]", e._exit and "_exit" or "", x or get(e, "x"), y or get(e, "y"), w or get(e, "w"), h or get(e, "h"), e.__id, get(e, "label"))
|
||||
|
|
@ -608,7 +649,7 @@ local fs_field = {
|
|||
fs_field.__index = fs_field
|
||||
setmetatable(fs_field, {
|
||||
__call = function(_, x, y, w, h, label, value)
|
||||
local e = {x = x, y = y, w = w, h = h, label = label or "", value = value or label or "", _styles = {}}
|
||||
local e = {x = x, y = y, w = w, h = h, label = value and label or "", value = value or label or "", _styles = {}}
|
||||
e.__id = new_id()
|
||||
setmetatable(e, fs_field)
|
||||
table.insert(ctx, e)
|
||||
|
|
@ -627,7 +668,7 @@ local fs_scrollbar = {
|
|||
if e._options then
|
||||
out[#out +1] = "scrollbaroptions["
|
||||
local first = true
|
||||
for k, v in pairs(options) do
|
||||
for k, v in pairs(e._options) do
|
||||
if not first then
|
||||
out[#out +1] = ";"
|
||||
end
|
||||
|
|
@ -660,7 +701,7 @@ setmetatable(fs_scrollbar, {
|
|||
local e = {x = x, y = y, w = w, h = h, orientation = orientation or "vertical", value = value or "", _styles = {}}
|
||||
e.__id = "_"..minetest.get_us_time().."_"..math.random(1, 100000)
|
||||
setmetatable(e, fs_scrollbar)
|
||||
table.insert(ctx, e)
|
||||
ctx[#ctx +1] = e
|
||||
return e
|
||||
end
|
||||
})
|
||||
|
|
@ -684,8 +725,11 @@ local fs_scroll_container = {
|
|||
}
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
local cx = resolve_layout_units(get(c, "x"), w)
|
||||
local cy = resolve_layout_units(get(c, "y"), h)
|
||||
local cx, cy
|
||||
if c.x then
|
||||
cx = resolve_layout_units(get_raw(c, "x"), w)
|
||||
cy = resolve_layout_units(get_raw(c, "y"), h)
|
||||
end
|
||||
|
||||
local cw, ch
|
||||
if c.w then
|
||||
|
|
@ -698,7 +742,6 @@ local fs_scroll_container = {
|
|||
out[#out +1] = "scroll_container_end[]"
|
||||
|
||||
if e._scrollbar then
|
||||
e._scrollbar.value = e._scroll_pos or ""
|
||||
out[#out +1] = e._scrollbar:render()
|
||||
else
|
||||
local v = e.__fs._ctx.fields[e.__id]
|
||||
|
|
@ -708,14 +751,38 @@ local fs_scroll_container = {
|
|||
return table.concat(out)
|
||||
end,
|
||||
scrollbar = function(e, fn, ...)
|
||||
-- Make a fake ctx to put the scrollbar in.
|
||||
ctx = setmetatable({
|
||||
__parent = ctx,
|
||||
_events = setmetatable({}, {
|
||||
__index = ctx._events,
|
||||
__newindex = ctx._events
|
||||
}),
|
||||
}, {
|
||||
-- Assignment trap to ensure that the scrollbar gets our ID immediately on construction.
|
||||
__newindex = function(tbl, key, value)
|
||||
value.__id = e.__id
|
||||
return rawset(tbl, key, value)
|
||||
end
|
||||
})
|
||||
if type(fn) == "function" then
|
||||
e._scrollbar = fn()
|
||||
fn()
|
||||
else
|
||||
e._scrollbar = fs_scrollbar(fn, ...)
|
||||
fs_scrollbar(fn, ...)
|
||||
end
|
||||
e._scrollbar = ctx[1]
|
||||
ctx = ctx.__parent
|
||||
e._scrollbar.__id = e.__id
|
||||
return e
|
||||
end,
|
||||
onscroll = function(e, fn)
|
||||
if e._scrollbar then
|
||||
e._scrollbar:onchange(fn)
|
||||
return e
|
||||
end
|
||||
e._scrollbar = fs_scrollbar(-800, -800, 0, 0, e._orientation, e.__id):onchange(fn)
|
||||
return e
|
||||
end,
|
||||
-- A non-transient name is required in order to preserve scroll position upon a rebuild.
|
||||
named = function(e, name)
|
||||
e.__id = name
|
||||
|
|
@ -725,7 +792,22 @@ local fs_scroll_container = {
|
|||
fs_scroll_container.__index = fs_scroll_container
|
||||
setmetatable(fs_scroll_container, {
|
||||
__call = function(_, x, y, w, h, orientation, factor, padding)
|
||||
local e = {x = x, y = y, w = w, h = h, orientation = orientation or "vertical", factor = factor or "", padding = padding or "0", _styles = {}, __parent = ctx, _events = setmetatable({}, {__index = ctx._events, __newindex = ctx._events}), __fs = ctx.__parent or ctx}
|
||||
local e = {
|
||||
x = x,
|
||||
y = y,
|
||||
w = w,
|
||||
h = h,
|
||||
orientation = orientation or "vertical",
|
||||
factor = factor or "",
|
||||
padding = padding or "0",
|
||||
_styles = {},
|
||||
__parent = ctx,
|
||||
_events = setmetatable({}, {
|
||||
__index = ctx._events,
|
||||
__newindex = ctx._events
|
||||
}),
|
||||
__fs = ctx.__parent or ctx
|
||||
}
|
||||
e.__id = new_id()
|
||||
setmetatable(e, fs_scroll_container)
|
||||
table.insert(ctx, e)
|
||||
|
|
@ -747,16 +829,24 @@ end
|
|||
|
||||
local fs_group = {
|
||||
render = function(e, x, y, w, h)
|
||||
x = x or get(e, "x")
|
||||
y = y or get(e, "y")
|
||||
w = w or get(e, "w")
|
||||
h = h or get(e, "h")
|
||||
|
||||
local out = {}
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
local cx = resolve_layout_units(get(c, "x"), e.width) +(x or get(e, "x"))
|
||||
local cy = resolve_layout_units(get(c, "y"), e.height) +(y or get(e, "y"))
|
||||
local cx, cy
|
||||
if c.x then
|
||||
cx = resolve_layout_units(get_raw(c, "x"), w) +x
|
||||
cy = resolve_layout_units(get_raw(c, "y"), h) +y
|
||||
end
|
||||
|
||||
local cw, ch
|
||||
if c.w then
|
||||
cw = resolve_layout_units(get(c, "w"), e.width)
|
||||
ch = resolve_layout_units(get(c, "h"), e.height)
|
||||
cw = resolve_layout_units(get(c, "w"), w)
|
||||
ch = resolve_layout_units(get(c, "h"), h)
|
||||
end
|
||||
|
||||
out[#out +1] = c:render(cx, cy, cw, ch)
|
||||
|
|
@ -804,14 +894,16 @@ local fs_row = {
|
|||
-- Pass 1: Collect total sizing information to allow layout computation.
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
local ca, ca_flex = resolve_flex_layout_units(get(c, e._direction == "column" and "h" or "w"))
|
||||
if c.w then
|
||||
if ca_flex then
|
||||
flex_found = true
|
||||
c.__flex = ca
|
||||
total_grow = total_grow +ca
|
||||
else
|
||||
used_space = used_space +(ca or 0)
|
||||
if not c._no_layout then
|
||||
local ca, ca_flex = resolve_flex_layout_units(get(c, e._direction == "column" and "h" or "w"), axis_size)
|
||||
if c.w then
|
||||
if ca_flex then
|
||||
flex_found = true
|
||||
c.__flex = ca
|
||||
total_grow = total_grow +ca
|
||||
else
|
||||
used_space = used_space +(ca or 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -827,12 +919,14 @@ local fs_row = {
|
|||
local current = 0
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
c.__flex_offset = current
|
||||
if c.__flex then
|
||||
c.__flex = c.__flex *grow_basis
|
||||
current = current +c.__flex +e._gap
|
||||
else
|
||||
current = current +resolve_layout_units(get(c, e._direction == "column" and "h" or "w")) +e._gap
|
||||
if not c._no_layout then
|
||||
c.__flex_offset = current
|
||||
if c.__flex then
|
||||
c.__flex = c.__flex *grow_basis
|
||||
current = current +c.__flex +e._gap
|
||||
else
|
||||
current = current +resolve_layout_units(get(c, e._direction == "column" and "h" or "w"), axis_size) +e._gap
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -846,17 +940,31 @@ local fs_row = {
|
|||
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
if c.w then
|
||||
if e._direction == "column" then
|
||||
out[#out +1] = c:render(resolve_layout_units(get(c, "x")), base +c.__flex_offset, resolve_layout_units(get(c, "w")), c.__flex)
|
||||
else
|
||||
out[#out +1] = c:render(base +c.__flex_offset, resolve_layout_units(get(c, "y")), c.__flex, resolve_layout_units(get(c, "h")))
|
||||
if c._no_layout then
|
||||
local cx, cy
|
||||
if c.x then
|
||||
cx = resolve_layout_units(get_raw(c, "x"), w) +x
|
||||
cy = resolve_layout_units(get_raw(c, "y"), h) +y
|
||||
end
|
||||
else
|
||||
|
||||
local cw, ch
|
||||
if c.w then
|
||||
cw = resolve_layout_units(get(c, "w"), w)
|
||||
ch = resolve_layout_units(get(c, "h"), h)
|
||||
end
|
||||
|
||||
out[#out +1] = c:render(cx, cy, cw, ch)
|
||||
elseif c.w then
|
||||
if e._direction == "column" then
|
||||
out[#out +1] = c:render(resolve_layout_units(get(c, "x")), base +c.__flex_offset)
|
||||
out[#out +1] = c:render(resolve_layout_units(get(c, "x"), w) +x, base +c.__flex_offset, resolve_layout_units(get(c, "w"), w), c.__flex)
|
||||
else
|
||||
out[#out +1] = c:render(base +c.__flex_offset, resolve_layout_units(get(c, "y")))
|
||||
out[#out +1] = c:render(base +c.__flex_offset, resolve_layout_units(get(c, "y"), h) +y, c.__flex, resolve_layout_units(get(c, "h"), h))
|
||||
end
|
||||
elseif c.x then
|
||||
if e._direction == "column" then
|
||||
out[#out +1] = c:render(resolve_layout_units(get(c, "x"), w) +x, base +c.__flex_offset)
|
||||
else
|
||||
out[#out +1] = c:render(base +c.__flex_offset, resolve_layout_units(get(c, "y"), h) +y)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -932,6 +1040,10 @@ local Window = {
|
|||
out[#out +1] = "allow_close[false]"
|
||||
end
|
||||
|
||||
if e._fullscreen then
|
||||
out[#out +1] = string.format("bgcolor[%s;%s;%s]", get(e, "_fgcolor"), get(e, "_fullscreen"), get(e, "_bgcolor"))
|
||||
end
|
||||
|
||||
for i = 1, #e do
|
||||
local c = e[i]
|
||||
local cx, cy
|
||||
|
|
@ -973,6 +1085,24 @@ local Window = {
|
|||
onclose = function(e, fn)
|
||||
e._onclose = fn
|
||||
return e
|
||||
end,
|
||||
bgcolor = function(e, foreground, fullscreen)
|
||||
if fullscreen then
|
||||
if fullscreen == true then
|
||||
e._fullscreen = "true"
|
||||
e._bgcolor = foreground
|
||||
else
|
||||
e._fullscreen = "both"
|
||||
e._fgcolor = foreground
|
||||
e._bgcolor = fullscreen
|
||||
end
|
||||
elseif not foreground then
|
||||
e._fullscreen = "neither"
|
||||
else
|
||||
e._fullscreen = "false"
|
||||
e._fgcolor = foreground
|
||||
end
|
||||
return e
|
||||
end
|
||||
}
|
||||
Window.__index = Window
|
||||
|
|
@ -980,17 +1110,23 @@ Window.__index = Window
|
|||
local function fs_begin(w, h)
|
||||
ctx = {_events = {on_click = {}, on_change = {}, on_scrollbar_event = {}, on_enter = {}}, width = w or 12, height = h or 10}
|
||||
setmetatable(ctx, Window)
|
||||
name_scope = {"_"}
|
||||
nth_id = {0}
|
||||
return ctx
|
||||
end
|
||||
|
||||
local function fs_end()
|
||||
local _ctx = ctx
|
||||
ctx = nil
|
||||
name_scope = nil
|
||||
nth_id = nil
|
||||
return _ctx
|
||||
end
|
||||
|
||||
local Context = {
|
||||
update = function(e)
|
||||
-- This is used to prevent changes in state from inside the builder to trigger another rebuild.
|
||||
if e._ignore then return end
|
||||
-- `_inert` should be set when many state updates may take place at once, to avoid a rapid succession of rebuilds.
|
||||
-- After these updates have taken place, the user should call `rebuild()` manually if `_dirty` is true.
|
||||
if e._inert then
|
||||
|
|
@ -1001,6 +1137,7 @@ local Context = {
|
|||
end,
|
||||
rebuild = function(e)
|
||||
e._dirty = nil
|
||||
e._ignore = true
|
||||
e:clear_state_bindings()
|
||||
|
||||
local tracker = e._linked_states
|
||||
|
|
@ -1012,6 +1149,12 @@ local Context = {
|
|||
|
||||
table.remove(observers)
|
||||
|
||||
e._ignore = nil
|
||||
|
||||
for x in pairs(e._linked_states) do
|
||||
x._getters[e.id] = e
|
||||
end
|
||||
|
||||
fs._ctx = e
|
||||
|
||||
for _, el in ipairs(fs) do
|
||||
|
|
@ -1027,7 +1170,9 @@ local Context = {
|
|||
e._events = fs._events
|
||||
|
||||
local str = fs:render()
|
||||
if e._is_inventory then
|
||||
if e._mainmenu then
|
||||
minetest.update_formspec(str)
|
||||
elseif e._is_inventory then
|
||||
e._player:set_inventory_formspec(str)
|
||||
else
|
||||
minetest.show_formspec(e.target, e.id, str)
|
||||
|
|
@ -1046,6 +1191,8 @@ local Context = {
|
|||
e._window:_onclose()
|
||||
end
|
||||
e:clear_state_bindings()
|
||||
-- Kill our rebuild capability in case any callbacks fire on the way out.
|
||||
e.rebuild = function() end
|
||||
end,
|
||||
close = function(e)
|
||||
-- Inventories cannot be 'closed', only replaced.
|
||||
|
|
@ -1057,7 +1204,7 @@ local Context = {
|
|||
Context.__index = Context
|
||||
|
||||
local function fs_show(target, fs, state)
|
||||
local id = "form"..new_id()
|
||||
local id = "form"..unique_id()
|
||||
local ctx = setmetatable({
|
||||
formspec = fs,
|
||||
fields = {},
|
||||
|
|
@ -1080,7 +1227,7 @@ local function fs_show(target, fs, state)
|
|||
end
|
||||
|
||||
local function fs_set_inventory(p, fs, state)
|
||||
local id = "form"..new_id()
|
||||
local id = "form"..unique_id()
|
||||
local name = type(p) == "string" and p or p:get_player_name()
|
||||
local ctx = setmetatable({
|
||||
_is_inventory = true,
|
||||
|
|
@ -1111,53 +1258,102 @@ end
|
|||
|
||||
-- MARK: Callback handling
|
||||
|
||||
minetest.register_on_player_receive_fields(function(p, form, fields)
|
||||
local name = p:get_player_name()
|
||||
local ctx = contexts[form]
|
||||
-- If this is an event from a player inventory, check if we have a context managing that inventory.
|
||||
-- No other special handling is needed, because the context already knows that it's an inventory and will react appropriately.
|
||||
if form == "" then
|
||||
ctx = inventories[name]
|
||||
local function handler(ctx, fields)
|
||||
ctx._inert = true
|
||||
|
||||
-- We split event handling into two passes here in order to guarantee that action events take precedence over state change events.
|
||||
-- Otherwise, it would be possible for a field's state synchronization to accidentally overwrite state changed by an action, which is almost certainly not what the user intended to happen.
|
||||
|
||||
-- First pass: Stateful element updates.
|
||||
for k, v in pairs(fields) do
|
||||
if ctx._events.on_scrollbar_event[k] then
|
||||
local ev = minetest.explode_scrollbar_event(v)
|
||||
ctx._events.on_scrollbar_event[k](ev.type, ev.value)
|
||||
elseif (not ctx.fields[k] or ctx.fields[k] ~= v) and ctx._events.on_change[k] then
|
||||
ctx._events.on_change[k](v)
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle pressing Enter in a field. (This is somewhere between a state event and an action, so we process between passes.)
|
||||
if fields.key_enter_field and ctx._events.on_enter[fields.key_enter_field] then
|
||||
ctx._events.on_enter[fields.key_enter_field](fields[fields.key_enter_field])
|
||||
end
|
||||
|
||||
-- Ensure that a) players can only trigger effects of formspecs that were opened legitimately, and b) only the player who opened it may interact with a given formspec instance.
|
||||
if ctx and ctx.target == name then
|
||||
ctx._inert = true
|
||||
|
||||
if fields.key_enter_field and ctx._events.on_enter[fields.key_enter_field] then
|
||||
ctx._events.on_enter[fields.key_enter_field](fields[fields.key_enter_field])
|
||||
end
|
||||
|
||||
for k, v in pairs(fields) do
|
||||
if ctx._events.on_click[k] then
|
||||
ctx._events.on_click[k](v)
|
||||
elseif ctx._events.on_scrollbar_event[k] then
|
||||
local ev = minetest.explode_scrollbar_event(v)
|
||||
ctx._events.on_scrollbar_event[k](ev.type, ev.value)
|
||||
elseif (not ctx.fields[k] or ctx.fields[k] ~= v) and ctx._events.on_change[k] then
|
||||
ctx._events.on_change[k](v)
|
||||
end
|
||||
end
|
||||
|
||||
ctx.fields = fields
|
||||
|
||||
ctx._inert = nil
|
||||
|
||||
if fields.quit then
|
||||
ctx:deinit()
|
||||
else
|
||||
if ctx._dirty then
|
||||
ctx:rebuild()
|
||||
ctx._dirty = nil
|
||||
end
|
||||
-- Second pass: Actions.
|
||||
for k, v in pairs(fields) do
|
||||
if ctx._events.on_click[k] then
|
||||
ctx._events.on_click[k](v)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ctx.fields = fields
|
||||
|
||||
ctx._inert = nil
|
||||
|
||||
if fields.quit then
|
||||
ctx:deinit()
|
||||
else
|
||||
if ctx._dirty then
|
||||
ctx:rebuild()
|
||||
ctx._dirty = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove all contexts tied to a leaving player.
|
||||
minetest.register_on_leaveplayer(function(p)
|
||||
fs_remove_inventory(p)
|
||||
end)
|
||||
-- Compatibility with main menu usage.
|
||||
if minetest.update_formspec then
|
||||
local mainmenu_context
|
||||
function fs_show(fs, state)
|
||||
if mainmenu_context then
|
||||
mainmenu_context:deinit()
|
||||
end
|
||||
|
||||
local id = "form"..unique_id()
|
||||
local ctx = setmetatable({
|
||||
_mainmenu = true,
|
||||
formspec = fs,
|
||||
fields = {},
|
||||
target = "menu",
|
||||
id = id,
|
||||
_linked_states = {},
|
||||
state = state or {}
|
||||
}, Context)
|
||||
|
||||
mainmenu_context = ctx
|
||||
|
||||
ctx:rebuild()
|
||||
|
||||
return ctx
|
||||
end
|
||||
|
||||
fs_set_inventory = nil
|
||||
|
||||
minetest.button_handler = function(fields)
|
||||
handler(mainmenu_context, fields)
|
||||
end
|
||||
-- Normal in-game usage.
|
||||
else
|
||||
minetest.register_on_player_receive_fields(function(p, form, fields)
|
||||
local name = p:get_player_name()
|
||||
local ctx = contexts[form]
|
||||
-- If this is an event from a player inventory, check if we have a context managing that inventory.
|
||||
-- No other special handling is needed, because the context already knows that it's an inventory and will react appropriately.
|
||||
if form == "" then
|
||||
ctx = inventories[name]
|
||||
end
|
||||
|
||||
-- Ensure that a) players can only trigger effects of formspecs that were opened legitimately, and b) only the player who opened it may interact with a given formspec instance.
|
||||
if ctx and ctx.target == name then
|
||||
handler(ctx, fields)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Remove all contexts tied to a leaving player.
|
||||
minetest.register_on_leaveplayer(function(p)
|
||||
fs_remove_inventory(p)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
-- MARK: API exposure
|
||||
|
||||
|
|
@ -1170,6 +1366,8 @@ imfs = {
|
|||
get_field = get,
|
||||
begin = fs_begin,
|
||||
end_ = fs_end,
|
||||
scope = scope,
|
||||
scope_end = scope_end,
|
||||
show = fs_show,
|
||||
set_inventory = fs_set_inventory,
|
||||
remove_inventory = fs_remove_inventory,
|
||||
|
|
@ -1194,6 +1392,7 @@ imfs = {
|
|||
_named_tooltip = fs_named_tooltip,
|
||||
label = fs_label,
|
||||
arealabel = fs_arealabel,
|
||||
hypertext = fs_hypertext,
|
||||
box = fs_box,
|
||||
image = fs_image,
|
||||
item_image = fs_item_image,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue