minetest-flow/expand.lua
2025-03-03 14:35:18 +13:00

263 lines
8.8 KiB
Lua

--
-- Flow: Layout expansion/stretching pass
--
-- Copyright © 2022-2025 by luk3yx
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Lesser General Public License as published by
-- the Free Software Foundation, either version 2.1 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Lesser General Public License for more details.
--
-- You should have received a copy of the GNU Lesser General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
--
local DEFAULT_SPACING, LABEL_HEIGHT, get_and_fill_in_sizes,
invisible_elems = ...
local align_types = {}
function align_types.fill(node, x, w, extra_space)
-- Special cases
if node.type == "list" or node.type == "checkbox" or node._label_hack then
return align_types.centre(node, x, w, extra_space)
elseif node.type == "label" then
if x == "y" then
node.y = node.y + extra_space / 2
return
end
-- Hack
node.type = "container"
-- Reset bgimg, some games apply styling to all image_buttons inside
-- the formspec prepend
node[1] = {
type = "style",
-- MT 5.1.0 only supports one style selector
selectors = {"_#"},
-- bgimg_pressed is included for 5.1.0 support
-- bgimg_hovered is unnecessary as it was added in 5.2.0 (which
-- also adds support for :hovered and :pressed)
props = {bgimg = "", bgimg_pressed = ""},
}
-- Use the newer pressed selector as well in case the deprecated one is
-- removed
node[2] = {
type = "style",
selectors = {"_#:hovered", "_#:pressed"},
props = {bgimg = ""},
}
node[3] = {
type = "image_button",
texture_name = "blank.png",
drawborder = false,
x = 0, y = 0,
w = node.w + extra_space, h = node.h,
name = "_#", label = node.label,
style = node.style,
}
-- Overlay button to prevent clicks from doing anything
node[4] = {
type = "image_button",
texture_name = "blank.png",
drawborder = false,
x = 0, y = 0,
w = node.w + extra_space, h = node.h,
name = "_#", label = "",
}
node.y = node.y - (node._flow_font_height or LABEL_HEIGHT) / 2
node.label = nil
node.style = nil
node._label_hack = true
assert(#node == 4)
end
if node[w] then
node[w] = node[w] + extra_space
else
core.log("warning", "[flow] Unknown element: \"" ..
tostring(node.type) .. "\". Please make sure that flow is " ..
"up-to-date and the element has a size set (if required).")
node[w] = extra_space
end
end
function align_types.start()
-- No alterations required
end
-- "end" is a Lua keyword
align_types["end"] = function(node, x, _, extra_space)
node[x] = node[x] + extra_space
end
-- Aliases for convenience
align_types.top, align_types.bottom = align_types.start, align_types["end"]
align_types.left, align_types.right = align_types.start, align_types["end"]
function align_types.centre(node, x, w, extra_space)
if node.type == "label" then
return align_types.fill(node, x, w, extra_space)
elseif node.type == "checkbox" and x == "y" then
node.y = (node.h + extra_space) / 2
return
end
node[x] = node[x] + extra_space / 2
end
align_types.center = align_types.centre
-- Try to guess at what the best expansion setting is
local auto_align_centre = {
image = true, animated_image = true, model = true, item_image_button = true
}
function align_types.auto(node, x, w, extra_space, cross)
if auto_align_centre[node.type] then
return align_types.centre(node, x, w, extra_space)
end
if x == "y" or (node.type ~= "label" and node.type ~= "checkbox") or
(node.expand and not cross) then
return align_types.fill(node, x, w, extra_space)
end
end
local expand_child_boxes
local function expand(box)
local x, w, align_h, y, h, align_v
local box_type = box.type
if box_type == "hbox" then
x, w, align_h, y, h, align_v = "x", "w", "align_h", "y", "h", "align_v"
elseif box_type == "vbox" then
x, w, align_h, y, h, align_v = "y", "h", "align_v", "x", "w", "align_h"
elseif box_type == "stack" or
(box_type == "padding" and box[1].expand) then
box.type = "container"
box._enable_bgimg_hack = true
for _, node in ipairs(box) do
if not invisible_elems[node.type] then
local width, height = node.w or 0, node.h or 0
if node.type == "list" then
width, height = get_and_fill_in_sizes(node)
end
local padding_x2 = (node.padding or 0) * 2
align_types[node.align_h or "auto"](node, "x", "w", box.w -
width - padding_x2)
align_types[node.align_v or "auto"](node, "y", "h", box.h -
height - padding_x2 - (node._padding_top or 0))
end
end
return expand_child_boxes(box)
elseif box_type == "container" or box_type == "scroll_container" then
for _, node in ipairs(box) do
if node.x == 0 and node.expand and box.w then
node.w = box.w
end
expand(node)
end
return
elseif box_type == "padding" then
box.type = "container"
return expand_child_boxes(box)
else
return
end
box.type = "container"
-- Calculate the amount of free space and put expand nodes into a table
local box_h = box[h]
local free_space = box[w]
local expandable = {}
local expand_count = 0
local first = true
for i, node in ipairs(box) do
local width, height = node[w] or 0, node[h] or 0
if not invisible_elems[node.type] then
if first then
first = false
else
free_space = free_space - (box.spacing or DEFAULT_SPACING)
end
if node.type == "list" then
width, height = get_and_fill_in_sizes(node)
if y == "x" then
width, height = height, width
end
end
free_space = free_space - width - (node.padding or 0) * 2 -
(y == "x" and node._padding_top or 0)
if node.expand then
expandable[node] = i
expand_count = expand_count + 1
elseif node.type == "label" and align_h == "align_h" then
-- Use the image_button hack even if the label isn't expanded
align_types[node.align_h or "auto"](node, "x", "w", 0)
end
-- Nodes are expanded in the other direction no matter what their
-- expand setting is
if box_h > height or (node.type == "label" and
align_v == "align_h") then
align_types[node[align_v] or "auto"](node, y, h,
box_h - height - (node.padding or 0) * 2 -
(y == "y" and node._padding_top or 0), true)
end
end
end
-- If there's any free space then expand the nodes to fit
if free_space > 0 then
local extra_space = free_space / expand_count
for node, node_idx in pairs(expandable) do
align_types[node[align_h] or "auto"](node, x, w, extra_space)
-- Shift other elements along
for j = node_idx + 1, #box do
if box[j][x] then
box[j][x] = box[j][x] + extra_space
end
end
end
elseif align_h == "align_h" then
-- Use the image_button hack on labels regardless of the amount of free
-- space if this is in a horizontal box.
for node in pairs(expandable) do
if node.type == "label" then
align_types[node.align_h or "auto"](node, "x", "w", 0)
end
end
end
expand_child_boxes(box)
end
function expand_child_boxes(box)
-- Recursively expand and remove any invisible nodes
for i = #box, 1, -1 do
local node = box[i]
-- node.visible ~= nil and not node.visible
if node.visible == false then
-- There's no need to try and expand anything inside invisible
-- nodes since it won't affect the overall size.
table.remove(box, i)
else
expand(node)
end
end
end
return expand