Fix models and buttons, and let containers handle inventories properly.

This commit is contained in:
Signal 2026-01-26 15:28:26 -05:00
parent 1040034efa
commit 7ccec27e0a
2 changed files with 66 additions and 28 deletions

View file

@ -278,9 +278,9 @@ Creates a model element.
Methods: Methods:
* `:style([state, ]props)`: Applies the styling properties `props` to the model when in the `state` state. If omitted, `state` is `"default"`. * `:style([state, ]props)`: Applies the styling properties `props` to the model when in the `state` state. If omitted, `state` is `"default"`.
* `:rotation(rotation[, continuous])`: Set the rotation of the model in the view, and optionally whether it is continuous. * `:rotation(rotation_x[, rotation_y[, continuous]])`: Set the X and Y rotation of the model in the view, and optionally whether it is continuous.
* `:mouse_control(state)`: Passing `false` will prevent the user from changing the model's rotation by clicking and dragging. * `:mouse_control(state)`: Passing `false` will prevent the user from changing the model's rotation by clicking and dragging.
* `:animated(frames, speed)`: Sets an animation on the model defined by the given frame range and speed. * `:animated(start, end_, speed)`: Sets an animation on the model defined by the given frame range and speed.
### `imfs.button(x, y, width, height, label)` ### `imfs.button(x, y, width, height, label)`
@ -295,7 +295,7 @@ Methods:
### `imfs.list(x, y, width, height, location = "current_player", list = "main"[, start])` ### `imfs.list(x, y, width, height, location = "current_player", list = "main"[, start])`
Creates an inventory element. Creates an inventory element. Note that `width` and `height` are in units of inventory slots; when set to flex ratios, however, they will resolve to the maximum number of slots that can be displayed in their assigned width.
`imfs.inventory` is an alias for this element. `imfs.inventory` is an alias for this element.

View file

@ -77,6 +77,10 @@ local function new_id()
return "_"..minetest.get_us_time().."_"..math.random(1, 100000) return "_"..minetest.get_us_time().."_"..math.random(1, 100000)
end end
local function string_or(a, b)
return (a ~= "" and a) or b
end
-- MARK: Data structures -- MARK: Data structures
local observers = {} local observers = {}
@ -163,9 +167,18 @@ local function get(e, x)
local item = e[x] local item = e[x]
local mt = getmetatable(item) local mt = getmetatable(item)
if mt == state or mt == DerivedState then if mt == state or mt == DerivedState then
return fe(item()) return fe(tostring(item() or ""))
end end
return fe(item) return fe(tostring(item or ""))
end
local function get_raw(e, x)
local item = e[x]
local mt = getmetatable(item)
if mt == state or mt == DerivedState then
return item()
end
return item
end end
-- MARK: Elements -- MARK: Elements
@ -226,8 +239,8 @@ local function fs_named_tooltip(name, text, bgcolor, txtcolor)
end end
local fs_label = { local fs_label = {
render = function(e) render = function(e, x, y)
return string.format("label[%f,%f;%s]", get(e, "x"), get(e, "y"), get(e, "txt")) return string.format("label[%f,%f;%s]", x or get(e, "x"), y or get(e, "y"), get(e, "txt"))
end end
} }
fs_label.__index = fs_label fs_label.__index = fs_label
@ -241,11 +254,16 @@ setmetatable(fs_label, {
}) })
local fs_arealabel = { local fs_arealabel = {
render = function(e) 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")
if e._scrollable then if e._scrollable then
return string.format("textarea[%f,%f;%f,%f;;;%s]", get(e, "x"), get(e, "y"), get(e, "w"), get(e, "h"), get(e, "txt")) return string.format("textarea[%f,%f;%f,%f;;;%s]", x, y, w, h, get(e, "txt"))
else else
return string.format("label[%f,%f;%f,%f;%s]", get(e, "x"), get(e, "y"), get(e, "w"), get(e, "h"), get(e, "txt")) return string.format("label[%f,%f;%f,%f;%s]", x, y, w, h, get(e, "txt"))
end end
end, end,
scrollable = function(e) scrollable = function(e)
@ -265,8 +283,8 @@ setmetatable(fs_arealabel, {
}) })
local fs_hypertext = { local fs_hypertext = {
render = function(e) render = function(e, x, y, w, h)
return string.format("hypertext[%f,%f;%f,%f;%s]", get(e, "x"), get(e, "y"), get(e, "w"), get(e, "h"), hte(get(e, "txt"))) 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")))
end, end,
onaction = function(e, fn) onaction = function(e, fn)
ctx._events.on_click[e.__id] = fn ctx._events.on_click[e.__id] = fn
@ -361,28 +379,46 @@ local fs_model = {
for _, x in pairs(e._styles) do for _, x in pairs(e._styles) do
out[#out +1] = x:render() out[#out +1] = x:render()
end end
local textures = get_raw(e, "textures")
if type(textures) ~= "string" then
textures = table.concat(textures, ",")
end
local as = get(e, "_animation_start")
local ae = get(e, "_animation_end")
local animation = ""
if as and ae then
animation = string.format("%s,%s", fe(as), fe(ae))
end
out[#out +1] = string.format( out[#out +1] = string.format(
"model[%f,%f;%f,%f;%s;%s;%s;%f;%s;%s;%s;%f]", "model[%f,%f;%f,%f;%s;%s;%s;%s,%s;%s;%s;%s;%s]",
x or get(e, "x"), y or get(e, "y"), x or get(e, "x"), y or get(e, "y"),
w or get(e, "w"), h or get(e, "h"), w or get(e, "w"), h or get(e, "h"),
e.__id, e.__id,
get(e, "mesh"), get(e, "textures"), get(e, "mesh"), textures,
get(e, "_rotation") or "", get(e, "_continuous") or "", get(e, "_rotation_x"), get(e, "_rotation_y"), get(e, "_continuous"),
get(e, "_mouse_control"), get(e, "_mouse_control"),
get(e, "_animation") or "", get(e, "_animation_speed") animation, string_or(get(e, "_animation_speed"), "1")
) )
return table.concat(out) return table.concat(out)
end, end,
rotation = function(e, rot, continuous) rotation = function(e, x, y, continuous)
e._rotation = rot e._rotation_x = x
e._rotation_y = y or 0
e._continuous = continuous e._continuous = continuous
return e
end, end,
mouse_control = function(e, mouse_control) mouse_control = function(e, mouse_control)
e._mouse_control = mouse_control ~= false and true or false e._mouse_control = mouse_control ~= false and true or false
return e
end, end,
animated = function(frames, speed) animated = function(e, start, end_, speed)
e._animation = frames e._animation_start = start
e._animation_end = end_
e._animation_speed = speed or 1 e._animation_speed = speed or 1
return e
end, end,
style = add_elem_style, style = add_elem_style,
tooltip = add_elem_tooltip, tooltip = add_elem_tooltip,
@ -390,7 +426,7 @@ local fs_model = {
fs_model.__index = fs_model fs_model.__index = fs_model
setmetatable(fs_model, { setmetatable(fs_model, {
__call = function(_, x, y, w, h, mesh, textures) __call = function(_, x, y, w, h, mesh, textures)
local e = {x = x, y = y, w = w, h = h, mesh = mesh, textures = textures, mouse_control = true} local e = {x = x, y = y, w = w, h = h, mesh = mesh, textures = textures, _mouse_control = true, _styles = {}}
e.__id = new_id() e.__id = new_id()
setmetatable(e, fs_model) setmetatable(e, fs_model)
table.insert(ctx, e) table.insert(ctx, e)
@ -421,12 +457,15 @@ local fs_button = {
image = function(e, img, pressed_img) image = function(e, img, pressed_img)
e._image = img e._image = img
e._image_pressed = pressed_img e._image_pressed = pressed_img
return e
end, end,
item_image = function(e, item) item_image = function(e, item)
e._item = item e._item = item
return e
end, end,
exit = function(e) exit = function(e)
e._exit = true e._exit = true
return e
end, end,
onclick = function(e, fn) onclick = function(e, fn)
ctx._events.on_click[e.__id] = fn ctx._events.on_click[e.__id] = fn
@ -472,17 +511,13 @@ local fs_list = {
w = w or get(e, "w") w = w or get(e, "w")
h = h or get(e, "h") h = h or get(e, "h")
-- These lines will cause `w` and `h` to be interpreted as formspec coordinates rather than numbers of slots.
-- w = w -((w -1) /4)
-- h = h -((h -1) /4)
return string.format("list[%s;%s;%f,%f;%d,%d;%s]", get(e, "location"), get(e, "list"), x or get(e, "x"), y or get(e, "y"), w, h, get(e, "start")) return string.format("list[%s;%s;%f,%f;%d,%d;%s]", get(e, "location"), get(e, "list"), x or get(e, "x"), y or get(e, "y"), w, h, get(e, "start"))
end end
} }
fs_list.__index = fs_list fs_list.__index = fs_list
setmetatable(fs_list, { setmetatable(fs_list, {
__call = function(_, x, y, w, h, location, list, start) __call = function(_, x, y, w, h, location, list, start)
local e = {x = x, y = y, w = w, h = h, location = location or "current_player", list = list or "main", start = start or ""} local e = {x = x, y = y, w = type(w == "string") and w or w +((w -1) /4), h = type(h) == "string" and h or h +((h -1) /4), location = location or "current_player", list = list or "main", start = start or ""}
e.__id = new_id() e.__id = new_id()
setmetatable(e, fs_list) setmetatable(e, fs_list)
table.insert(ctx, e) table.insert(ctx, e)
@ -891,8 +926,11 @@ local Window = {
for i = 1, #e do for i = 1, #e do
local c = e[i] local c = e[i]
local cx = resolve_layout_units(get(c, "x"), e.width) local cx, cy
local cy = resolve_layout_units(get(c, "y"), e.height) if c.x then
cx = resolve_layout_units(get(c, "x"), e.width)
cy = resolve_layout_units(get(c, "y"), e.height)
end
local cw, ch local cw, ch
if c.w then if c.w then