453 lines
12 KiB
Lua
453 lines
12 KiB
Lua
|
|
local currentPath = (...):match('(.-)[^%./]+$')
|
||
|
|
|
||
|
|
--- @class IUILib
|
||
|
|
local iui = require(currentPath .. "iui")
|
||
|
|
|
||
|
|
--- @alias IUILayoutRowKind "fixed" | "dynamic" | "intrinsic" | "mixed"
|
||
|
|
|
||
|
|
--- @class (exact) IUILayoutPanel
|
||
|
|
--- @field x number
|
||
|
|
--- @field y number
|
||
|
|
--- @field w number
|
||
|
|
--- @field h number
|
||
|
|
--- @field margin number
|
||
|
|
--- @field rowHeight number
|
||
|
|
--- @field rowKind IUILayoutRowKind
|
||
|
|
--- @field rowData any
|
||
|
|
--- @field intrinsicDefault? number
|
||
|
|
--- @field intrinsicLimit? number
|
||
|
|
--- @field rowY number
|
||
|
|
--- @field columnX number
|
||
|
|
--- @field wantsIntrinsicWidth boolean
|
||
|
|
--- @field wantsIntrinsicHeight boolean
|
||
|
|
--- @field intrinsicWidth? number
|
||
|
|
--- @field intrinsicHeight? number
|
||
|
|
--- @field columnIndex number
|
||
|
|
--- @field columnCountCache? number
|
||
|
|
--- @field columnBoundsCache? IUIColumnXBounds[]
|
||
|
|
--- @field zStackCount? number
|
||
|
|
--- @field contentWidth number
|
||
|
|
--- @field contentHeight number
|
||
|
|
|
||
|
|
--- @class (exact) IUIColumnXBounds
|
||
|
|
--- @field x number
|
||
|
|
--- @field w number
|
||
|
|
|
||
|
|
--- @class (exact) IUILayoutColumn
|
||
|
|
--- @field kind "fixed" | "dynamic"
|
||
|
|
--- @field size number
|
||
|
|
|
||
|
|
--- @class IUILayout
|
||
|
|
--- @field windowWidth number
|
||
|
|
--- @field windowHeight number
|
||
|
|
local layout = {}
|
||
|
|
|
||
|
|
--- @type IUILayoutPanel[]
|
||
|
|
local panels = {}
|
||
|
|
|
||
|
|
function layout.beginFrame()
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
function layout.endFrame()
|
||
|
|
if #panels ~= 0 then
|
||
|
|
error("Unbalanced panel stack: expected 0, got " .. #panels)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @return number rowHeight
|
||
|
|
function layout.getDefaultRowHeight()
|
||
|
|
local padding = iui.style["padding"]
|
||
|
|
local fontHeight = math.floor(iui.style["font"]:getHeight())
|
||
|
|
local rowHeight = fontHeight + padding * 2
|
||
|
|
|
||
|
|
return rowHeight
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param panel IUILayoutPanel
|
||
|
|
local function resetColumnBoundsCache(panel)
|
||
|
|
if panel.columnBoundsCache ~= nil then
|
||
|
|
iui.pool.put(panel.columnBoundsCache)
|
||
|
|
panel.columnBoundsCache = nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param x number
|
||
|
|
--- @param y number
|
||
|
|
--- @param w number
|
||
|
|
--- @param h number
|
||
|
|
--- @param margin? number
|
||
|
|
--- @return IUILayoutPanel
|
||
|
|
function layout.beginPanel(x, y, w, h, margin)
|
||
|
|
margin = margin or iui.style["margin"]
|
||
|
|
|
||
|
|
local rowHeight = layout.getDefaultRowHeight()
|
||
|
|
|
||
|
|
--- @type IUILayoutPanel
|
||
|
|
local panel = iui.pool.get("layout_panel")
|
||
|
|
|
||
|
|
panel.x = x
|
||
|
|
panel.y = y
|
||
|
|
panel.w = w
|
||
|
|
panel.h = h
|
||
|
|
panel.margin = margin
|
||
|
|
panel.rowHeight = rowHeight
|
||
|
|
panel.rowY = margin
|
||
|
|
panel.columnX = margin
|
||
|
|
panel.wantsIntrinsicWidth = false
|
||
|
|
panel.wantsIntrinsicHeight = false
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.rowKind = "dynamic"
|
||
|
|
panel.rowData = 1
|
||
|
|
panel.intrinsicDefault = nil
|
||
|
|
panel.intrinsicLimit = nil
|
||
|
|
panel.contentWidth = 0
|
||
|
|
panel.contentHeight = 0
|
||
|
|
|
||
|
|
panel.intrinsicWidth = nil
|
||
|
|
panel.intrinsicHeight = nil
|
||
|
|
panel.columnCountCache = nil
|
||
|
|
panel.zStackCount = nil
|
||
|
|
|
||
|
|
resetColumnBoundsCache(panel)
|
||
|
|
|
||
|
|
table.insert(panels, panel)
|
||
|
|
|
||
|
|
return panel
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param advance? boolean
|
||
|
|
function layout.endPanel(advance)
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
if panel.zStackCount ~= nil and panel.zStackCount ~= 0 then
|
||
|
|
error(
|
||
|
|
"Unbalanced panel zstack count: expected 0, got " ..
|
||
|
|
panel.zStackCount
|
||
|
|
)
|
||
|
|
end
|
||
|
|
|
||
|
|
table.remove(panels)
|
||
|
|
|
||
|
|
iui.pool.put(panel)
|
||
|
|
|
||
|
|
if advance == true or (#panels > 0 and advance ~= false) then
|
||
|
|
iui.layout.advance()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param index? number
|
||
|
|
function layout.getPanel(index)
|
||
|
|
return panels[index or #panels]
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @return number w, number h
|
||
|
|
function layout.getContentSize()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
return panel.contentWidth, panel.contentHeight
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @return boolean wantsIntrinsicWidth, boolean wantsIntrinsicHeight
|
||
|
|
function layout.getWantsIntrinsic()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
return panel.wantsIntrinsicWidth, panel.wantsIntrinsicHeight
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param w number
|
||
|
|
function layout.setIntrinsicWidth(w)
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
panel.intrinsicWidth = w
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param h number
|
||
|
|
function layout.setIntrinsicHeight(h)
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
panel.intrinsicHeight = h
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param rowHeight? number
|
||
|
|
--- @return IUILayoutPanel
|
||
|
|
local function beginRowCommon(rowHeight)
|
||
|
|
if rowHeight == nil then
|
||
|
|
rowHeight = layout.getDefaultRowHeight()
|
||
|
|
end
|
||
|
|
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
|
||
|
|
if panel.columnIndex ~= 1 then
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.rowY = panel.rowY + panel.rowHeight + iui.style["spacing"]
|
||
|
|
end
|
||
|
|
|
||
|
|
panel.wantsIntrinsicHeight = false
|
||
|
|
panel.wantsIntrinsicWidth = false
|
||
|
|
panel.intrinsicWidth = nil
|
||
|
|
panel.intrinsicHeight = nil
|
||
|
|
panel.rowHeight = rowHeight
|
||
|
|
panel.columnCountCache = nil
|
||
|
|
resetColumnBoundsCache(panel)
|
||
|
|
|
||
|
|
return panel
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param count? number
|
||
|
|
--- @param rowHeight? number
|
||
|
|
function layout.beginDynamicRow(count, rowHeight)
|
||
|
|
local panel = beginRowCommon(rowHeight)
|
||
|
|
panel.rowKind = "dynamic"
|
||
|
|
panel.rowData = count or 1
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param size number
|
||
|
|
--- @param rowHeight? number
|
||
|
|
function layout.beginFixedRow(size, rowHeight)
|
||
|
|
local panel = beginRowCommon(rowHeight)
|
||
|
|
panel.rowKind = "fixed"
|
||
|
|
panel.rowData = size
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param default? number
|
||
|
|
--- @param limit? number
|
||
|
|
--- @param rowHeight? number
|
||
|
|
function layout.beginIntrinsicRow(default, limit, rowHeight)
|
||
|
|
local panel = beginRowCommon(rowHeight)
|
||
|
|
panel.rowKind = "intrinsic"
|
||
|
|
panel.wantsIntrinsicWidth = true
|
||
|
|
panel.intrinsicDefault = default
|
||
|
|
panel.intrinsicLimit = limit
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param columns IUILayoutColumn[]
|
||
|
|
--- @param rowHeight? number
|
||
|
|
function layout.beginMixedRow(columns, rowHeight)
|
||
|
|
local panel = beginRowCommon(rowHeight)
|
||
|
|
panel.rowKind = "mixed"
|
||
|
|
panel.rowData = columns
|
||
|
|
end
|
||
|
|
|
||
|
|
--- Begins a row that causes the next widget to fill the remainder of the
|
||
|
|
--- current panel.
|
||
|
|
function layout.fillPanel()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
|
||
|
|
if panel.columnIndex ~= 1 then
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.rowY = panel.rowY + panel.rowHeight + iui.style["spacing"]
|
||
|
|
end
|
||
|
|
|
||
|
|
local h = panel.h - (panel.rowY + panel.margin)
|
||
|
|
layout.beginDynamicRow(1, h)
|
||
|
|
end
|
||
|
|
|
||
|
|
function layout.beginZStack()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
panel.zStackCount = (panel.zStackCount or 0) + 1
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param advance? boolean
|
||
|
|
function layout.endZStack(advance)
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
if panel.zStackCount == nil or panel.zStackCount == 0 then
|
||
|
|
error("Attempt to end ZStack without one on panel")
|
||
|
|
end
|
||
|
|
panel.zStackCount = panel.zStackCount - 1
|
||
|
|
|
||
|
|
if advance then
|
||
|
|
iui.layout.advance()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @return number x, number y, number w, number h
|
||
|
|
function layout.getBounds()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
local y = panel.rowY
|
||
|
|
local h = panel.rowHeight
|
||
|
|
|
||
|
|
local margin = panel.margin
|
||
|
|
local spacing = iui.style["spacing"]
|
||
|
|
|
||
|
|
local x, w = 0, 0
|
||
|
|
local rowKind = panel.rowKind
|
||
|
|
if rowKind == "fixed" then
|
||
|
|
w = panel.rowData
|
||
|
|
x = margin + (panel.columnIndex - 1) * (w + spacing)
|
||
|
|
elseif rowKind == "dynamic" then
|
||
|
|
local count = panel.rowData
|
||
|
|
w = (panel.w - (2 * margin + (count - 1) * spacing)) / count
|
||
|
|
x = margin + (panel.columnIndex - 1) * (w + spacing)
|
||
|
|
|
||
|
|
local maxX = iui.utils.round(x + w)
|
||
|
|
x = iui.utils.round(x)
|
||
|
|
w = maxX - x
|
||
|
|
elseif rowKind == "intrinsic" then
|
||
|
|
local edge = panel.w - panel.margin
|
||
|
|
x = panel.columnX
|
||
|
|
w = panel.intrinsicWidth or panel.intrinsicDefault or (edge - x)
|
||
|
|
if x + w > edge and panel.columnIndex ~= 1 then
|
||
|
|
x = panel.margin
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.rowY = panel.rowY + panel.rowHeight + spacing
|
||
|
|
y = panel.rowY
|
||
|
|
end
|
||
|
|
panel.intrinsicWidth = w
|
||
|
|
elseif rowKind == "mixed" then
|
||
|
|
if panel.columnBoundsCache == nil then
|
||
|
|
--- The sum of dynamic column `size` values.
|
||
|
|
local dynamicSum = 0
|
||
|
|
|
||
|
|
--- The amount of space available to dynamic columns. This is
|
||
|
|
--- whatever's left over after the margin, spacing, and fixed
|
||
|
|
--- columns are accounted for.
|
||
|
|
local dynamicSpace = panel.w - margin * 2
|
||
|
|
|
||
|
|
--- @type IUILayoutColumn[]
|
||
|
|
local columns = panel.rowData
|
||
|
|
|
||
|
|
-- Do a first pass through the columns, calculating the dynamic sum
|
||
|
|
-- and space values.
|
||
|
|
for idx = 1, #columns do
|
||
|
|
if idx > 1 then
|
||
|
|
dynamicSpace = dynamicSpace - spacing
|
||
|
|
end
|
||
|
|
|
||
|
|
local column = columns[idx]
|
||
|
|
if column.kind == "dynamic" then
|
||
|
|
dynamicSum = dynamicSum + column.size
|
||
|
|
elseif column.kind == "fixed" then
|
||
|
|
dynamicSpace = dynamicSpace - column.size
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @type IUIColumnXBounds[]
|
||
|
|
local bounds = iui.pool.get("layout_column_bounds_cache")
|
||
|
|
for index, entry in ipairs(bounds) do
|
||
|
|
iui.pool.put(entry)
|
||
|
|
bounds[index] = nil
|
||
|
|
end
|
||
|
|
|
||
|
|
local head = margin
|
||
|
|
for idx = 1, #columns do
|
||
|
|
local column = columns[idx]
|
||
|
|
local colX = head
|
||
|
|
local colW = 0
|
||
|
|
if column.kind == "fixed" then
|
||
|
|
colW = column.size
|
||
|
|
elseif column.kind == "dynamic" then
|
||
|
|
colW = dynamicSpace * (column.size / dynamicSum)
|
||
|
|
end
|
||
|
|
head = head + colW + spacing
|
||
|
|
local maxX = colX + colW
|
||
|
|
colX = iui.utils.round(colX)
|
||
|
|
maxX = iui.utils.round(maxX)
|
||
|
|
colW = maxX - colX
|
||
|
|
--- @type IUIColumnXBounds
|
||
|
|
local value = iui.pool.get("layout_column_bounds_entry")
|
||
|
|
value.x = colX
|
||
|
|
value.w = colW
|
||
|
|
|
||
|
|
table.insert(bounds, value)
|
||
|
|
end
|
||
|
|
|
||
|
|
panel.columnBoundsCache = bounds
|
||
|
|
end
|
||
|
|
|
||
|
|
local bounds = panel.columnBoundsCache[panel.columnIndex]
|
||
|
|
|
||
|
|
x = bounds.x
|
||
|
|
w = bounds.w
|
||
|
|
end
|
||
|
|
|
||
|
|
x = panel.x + x
|
||
|
|
y = panel.y + y
|
||
|
|
|
||
|
|
panel.contentWidth = math.max(panel.contentWidth, x + w + panel.margin - panel.x)
|
||
|
|
panel.contentHeight = math.max(panel.contentHeight, y + h + panel.margin - panel.y)
|
||
|
|
|
||
|
|
return x, y, w, h
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param index? number
|
||
|
|
--- @return number x, number y, number w, number h
|
||
|
|
function layout.getPanelBounds(index)
|
||
|
|
local panel = layout.getPanel(index)
|
||
|
|
|
||
|
|
return panel.x, panel.y, panel.w, panel.h
|
||
|
|
end
|
||
|
|
|
||
|
|
--- @param x? number
|
||
|
|
--- @param y? number
|
||
|
|
--- @return boolean
|
||
|
|
function layout.containsPoint(x, y)
|
||
|
|
x = x or iui.input.mouse.x
|
||
|
|
y = y or iui.input.mouse.y
|
||
|
|
|
||
|
|
local bx, by, bw, bh = layout.getBounds()
|
||
|
|
if not iui.utils.rectContains(bx, by, bw, bh, x, y) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
|
||
|
|
local cx, cy, cw, ch = iui.draw.getClipBounds()
|
||
|
|
if cx then
|
||
|
|
if not iui.utils.rectContains(
|
||
|
|
cx, cy --[[@as any]], cw --[[@as any]], ch --[[@as any]], x, y
|
||
|
|
) then
|
||
|
|
return false
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
return true
|
||
|
|
end
|
||
|
|
|
||
|
|
function layout.spacer()
|
||
|
|
layout.advance()
|
||
|
|
end
|
||
|
|
|
||
|
|
function layout.advance()
|
||
|
|
local panel = layout.getPanel()
|
||
|
|
|
||
|
|
if panel.zStackCount ~= nil and panel.zStackCount ~= 0 then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local spacing = iui.style["spacing"]
|
||
|
|
|
||
|
|
local rowKind = panel.rowKind
|
||
|
|
local count = panel.columnCountCache
|
||
|
|
|
||
|
|
if count == nil then
|
||
|
|
if rowKind == "fixed" then
|
||
|
|
local num = panel.w + spacing - panel.margin * 2
|
||
|
|
local den = panel.rowData + spacing
|
||
|
|
count = math.floor(num / den)
|
||
|
|
if count < 1 then
|
||
|
|
count = 1
|
||
|
|
end
|
||
|
|
elseif rowKind == "dynamic" then
|
||
|
|
count = panel.rowData
|
||
|
|
elseif rowKind == "mixed" then
|
||
|
|
count = #panel.rowData
|
||
|
|
end
|
||
|
|
|
||
|
|
panel.columnCountCache = count
|
||
|
|
end
|
||
|
|
|
||
|
|
if rowKind == "intrinsic" then
|
||
|
|
panel.columnIndex = panel.columnIndex + 1
|
||
|
|
panel.columnX = panel.columnX + spacing + (panel.intrinsicWidth or 0)
|
||
|
|
panel.intrinsicWidth = nil
|
||
|
|
panel.intrinsicHeight = nil
|
||
|
|
|
||
|
|
if panel.intrinsicLimit and panel.columnIndex >= panel.intrinsicLimit then
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.columnX = panel.margin
|
||
|
|
panel.rowY = panel.rowY + panel.rowHeight + spacing
|
||
|
|
end
|
||
|
|
elseif panel.columnIndex == count then
|
||
|
|
panel.columnIndex = 1
|
||
|
|
panel.rowY = panel.rowY + panel.rowHeight + spacing
|
||
|
|
else
|
||
|
|
panel.columnIndex = panel.columnIndex + 1
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
iui.layout = layout
|