Reworked Flexbox

- Improved the current flexbox implementation

setJustifyContent: flex-start, flex-end, center, space-between, space-around, space-evenly
setDirection: row, column
setFlexWrap: nowrap, wrap

Child object new methods:
setFlexGrow, setFlexShrink, setFlexBasis
This commit is contained in:
Robert Jelic
2023-05-19 10:42:44 +02:00
parent b6114ca8da
commit b6ff31d77c

View File

@@ -1,90 +1,331 @@
local function flexObjectPlugin(base, basalt)
local flexGrow = 0
local flexShrink = 0
local flexBasis = 6
local object = {
getFlexGrow = function(self)
return flexGrow
end,
setFlexGrow = function(self, value)
flexGrow = value
return self
end,
getFlexShrink = function(self)
return flexShrink
end,
setFlexShrink = function(self, value)
flexShrink = value
return self
end,
getFlexBasis = function(self)
return flexBasis
end,
setFlexBasis = function(self, value)
flexBasis = value
return self
end
}
for k,v in pairs(object)do
base[k] = v
end
return base
end
return function(name, basalt)
local base = basalt.getObject("Frame")(name, basalt)
local base = basalt.getObject("ScrollableFrame")(name, basalt)
local objectType = "Flexbox"
local flexDirection = "row" -- "row" or "column"
local justifyContent = "flex-start" -- "flex-start", "flex-end", "center", "space-between", "space-around"
local alignItems = "flex-start" -- "flex-start", "flex-end", "center", "space-between", "space-around"
local direction = "row"
local spacing = 1
local justifyContent = "flex-start"
local wrap = "nowrap"
local children = {}
local sortedChildren = {}
local updateLayout = false
local lineBreakFakeObject = flexObjectPlugin({
getHeight = function(self) return 0 end,
getWidth = function(self) return 0 end,
getPosition = function(self) return 0, 0 end,
getSize = function(self) return 0, 0 end,
isType = function(self) return false end,
getType = function(self) return "lineBreakFakeObject" end,
setPosition = function(self) end,
setSize = function(self) end,
})
local function getObjectOffAxisOffset(self, obj)
local width, height = self:getSize()
local objWidth, objHeight = obj.element:getSize()
local availableSpace = flexDirection == "row" and height - objHeight or width - objWidth
local offset = 1
if alignItems == "center" then
offset = 1 + availableSpace / 2
elseif alignItems == "flex-end" then
offset = 1 + availableSpace
local function sortChildren(self)
if(wrap=="nowrap")then
sortedChildren = {}
local index = 1
local lineSize = 1
local lineOffset = 1
for _,v in pairs(children)do
if(sortedChildren[index]==nil)then sortedChildren[index]={offset=1} end
local childHeight = direction == "row" and v:getHeight() or v:getWidth()
if childHeight > lineSize then
lineSize = childHeight
end
if(v == lineBreakFakeObject)then
lineOffset = lineOffset + lineSize + spacing
lineSize = 1
index = index + 1
sortedChildren[index] = {offset=lineOffset, v}
else
table.insert(sortedChildren[index], v)
end
end
elseif(wrap=="wrap")then
sortedChildren = {}
local lineSize = 1
local lineOffset = 1
local maxSize = direction == "row" and self:getWidth() or self:getHeight()
local usedSize = 0
local index = 1
for _,v in pairs(children)do
if(sortedChildren[index]==nil)then sortedChildren[index]={offset=1} end
local childHeight = direction == "row" and v:getHeight() or v:getWidth()
if childHeight > lineSize then
lineSize = childHeight
end
if v == lineBreakFakeObject then
lineOffset = lineOffset + lineSize + spacing
lineSize = 1
index = index + 1
sortedChildren[index] = {offset=lineOffset, v}
else
local objSize = direction == "row" and v:getWidth() or v:getHeight()
if(objSize+usedSize>maxSize)then
lineOffset = lineOffset + lineSize + spacing
lineSize = direction == "row" and v:getHeight() or v:getWidth()
index = index + 1
usedSize = 0
sortedChildren[index] = {offset=lineOffset, v}
else
usedSize = usedSize + objSize
table.insert(sortedChildren[index], v)
end
end
end
end
end
local function calculateRow(self, children)
local containerWidth, containerHeight = self:getSize()
local totalFlexGrow = 0
local totalFlexShrink = 0
local totalFlexBasis = 0
for _, child in ipairs(children) do
totalFlexGrow = totalFlexGrow + child:getFlexGrow()
totalFlexShrink = totalFlexShrink + child:getFlexShrink()
totalFlexBasis = totalFlexBasis + child:getFlexBasis()
end
local remainingSpace = containerWidth - totalFlexBasis - (spacing * (#children - 1))
local currentX = 1
for _, child in ipairs(children) do
if(child~=lineBreakFakeObject)then
local childWidth
local flexGrow = child:getFlexGrow()
local flexShrink = child:getFlexShrink()
if totalFlexGrow > 0 then
childWidth = child:getFlexBasis() + flexGrow / totalFlexGrow * remainingSpace
else
childWidth = child:getFlexBasis()
end
if remainingSpace < 0 and totalFlexShrink > 0 then
childWidth = child:getFlexBasis() + flexShrink / totalFlexShrink * remainingSpace
end
child:setPosition(currentX, children.offset or 1)
child:setSize(childWidth, child:getHeight())
basalt.log(children.offset)
currentX = currentX + childWidth + spacing
end
end
if justifyContent == "flex-end" then
local totalWidth = currentX - spacing
local offset = containerWidth - totalWidth + 1
for _, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x + offset, y)
end
elseif justifyContent == "center" then
local totalWidth = currentX - spacing
local offset = (containerWidth - totalWidth) / 2 + 1
for _, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x + offset, y)
end
elseif justifyContent == "space-between" then
local totalWidth = currentX - spacing
local offset = (containerWidth - totalWidth) / (#children - 1) + 1
for i, child in ipairs(children) do
if i > 1 then
local x, y = child:getPosition()
child:setPosition(x + offset * (i - 1), y)
end
end
elseif justifyContent == "space-around" then
local totalWidth = currentX - spacing
local offset = (containerWidth - totalWidth) / #children
for i, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x + offset * i - offset / 2, y)
end
elseif justifyContent == "space-evenly" then
local numSpaces = #children + 1
local totalChildWidth = 0
for _, child in ipairs(children) do
totalChildWidth = totalChildWidth + child:getWidth()
end
local totalSpace = containerWidth - totalChildWidth
local offset = math.floor(totalSpace / numSpaces)
local remaining = totalSpace - offset * numSpaces
currentX = offset + (remaining > 0 and 1 or 0)
remaining = remaining > 0 and remaining - 1 or 0
for _, child in ipairs(children) do
child:setPosition(currentX, 1)
currentX = currentX + child:getWidth() + offset + (remaining > 0 and 1 or 0)
remaining = remaining > 0 and remaining - 1 or 0
end
end
end
local function calculateColumn(self, children)
local containerWidth, containerHeight = self:getSize()
local totalFlexGrow = 0
local totalFlexShrink = 0
local totalFlexBasis = 0
for _, child in ipairs(children) do
totalFlexGrow = totalFlexGrow + child:getFlexGrow()
totalFlexShrink = totalFlexShrink + child:getFlexShrink()
totalFlexBasis = totalFlexBasis + child:getFlexBasis()
end
local remainingSpace = containerHeight - totalFlexBasis - (spacing * (#children - 1))
local currentY = 1
for _, child in ipairs(children) do
if(child~=lineBreakFakeObject)then
local childHeight
local flexGrow = child:getFlexGrow()
local flexShrink = child:getFlexShrink()
if totalFlexGrow > 0 then
childHeight = child:getFlexBasis() + flexGrow / totalFlexGrow * remainingSpace
else
childHeight = child:getFlexBasis()
end
if remainingSpace < 0 and totalFlexShrink > 0 then
childHeight = child:getFlexBasis() + flexShrink / totalFlexShrink * remainingSpace
end
child:setPosition(children.offset, currentY)
child:setSize(child:getWidth(), childHeight)
currentY = currentY + childHeight + spacing
end
end
if justifyContent == "flex-end" then
local totalHeight = currentY - spacing
local offset = containerHeight - totalHeight + 1
for _, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x, y + offset)
end
elseif justifyContent == "center" then
local totalHeight = currentY - spacing
local offset = (containerHeight - totalHeight) / 2
for _, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x, y + offset)
end
elseif justifyContent == "space-between" then
local totalHeight = currentY - spacing
local offset = (containerHeight - totalHeight) / (#children - 1) + 1
for i, child in ipairs(children) do
if i > 1 then
local x, y = child:getPosition()
child:setPosition(x, y + offset * (i - 1))
end
end
elseif justifyContent == "space-around" then
local totalHeight = currentY - spacing
local offset = (containerHeight - totalHeight) / #children
for i, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x, y + offset * i - offset / 2)
end
elseif justifyContent == "space-evenly" then
local numSpaces = #children + 1
local totalChildHeight = 0
for _, child in ipairs(children) do
totalChildHeight = totalChildHeight + child:getHeight()
end
local totalSpace = containerHeight - totalChildHeight
local offset = math.floor(totalSpace / numSpaces)
local remaining = totalSpace - offset * numSpaces
currentY = offset + (remaining > 0 and 1 or 0)
remaining = remaining > 0 and remaining - 1 or 0
for _, child in ipairs(children) do
local x, y = child:getPosition()
child:setPosition(x, currentY)
currentY = currentY + child:getHeight() + offset + (remaining > 0 and 1 or 0)
remaining = remaining > 0 and remaining - 1 or 0
end
end
return offset
end
local function applyLayout(self)
local children = self:getChildren()
local totalChildren = #children
local width, height = self:getSize()
local mainAxisTotalChildSize = 0
for _, obj in ipairs(children) do
local objWidth, objHeight = obj.element:getSize()
if flexDirection == "row" then
mainAxisTotalChildSize = mainAxisTotalChildSize + objWidth
else
mainAxisTotalChildSize = mainAxisTotalChildSize + objHeight
end
end
local mainAxisAvailableSpace = (flexDirection == "row" and width or height) - mainAxisTotalChildSize - (spacing * (totalChildren - 1))
local justifyContentOffset = 1
if justifyContent == "center" then
justifyContentOffset = 1 + mainAxisAvailableSpace / 2
elseif justifyContent == "flex-end" then
justifyContentOffset = 1 + mainAxisAvailableSpace
end
for _, obj in ipairs(children) do
local alignItemsOffset = getObjectOffAxisOffset(self, obj)
if flexDirection == "row" then
obj.element:setPosition(justifyContentOffset, alignItemsOffset)
local objWidth, _ = obj.element:getSize()
justifyContentOffset = justifyContentOffset + objWidth + spacing
else
obj.element:setPosition(alignItemsOffset, math.floor(justifyContentOffset+0.5))
local _, objHeight = obj.element:getSize()
justifyContentOffset = justifyContentOffset + objHeight + spacing
sortChildren(self)
if direction == "row" then
for _,v in pairs(sortedChildren)do
calculateRow(self, v)
end
else
for _,v in pairs(sortedChildren)do
calculateColumn(self, v)
end
end
updateLayout = false
end
local object = {
getType = function()
return objectType
end,
isType = function(self, t)
return objectType == t or base.getBase(self).isType(t) or false
return objectType == t or base.isType ~= nil and base.isType(t) or false
end,
setSpacing = function(self, newSpacing)
spacing = newSpacing
applyLayout(self)
return self
end,
getSpacing = function(self)
return spacing
end,
getFlexDirection = function(self)
return flexDirection
end,
setFlexDirection = function(self, direction)
if direction == "row" or direction == "column" then
flexDirection = direction
applyLayout(self)
end
setJustifyContent = function(self, value)
justifyContent = value
updateLayout = true
return self
end,
@@ -92,35 +333,62 @@ return function(name, basalt)
return justifyContent
end,
setJustifyContent = function(self, alignment)
if alignment == "flex-start" or alignment == "flex-end" or alignment == "center" or alignment == "space-between" or alignment == "space-around" then
justifyContent = alignment
applyLayout(self)
end
setDirection = function(self, value)
direction = value
updateLayout = true
return self
end,
getAlignItems = function(self)
return alignItems
getDirection = function(self)
return direction
end,
setAlignItems = function(self, alignment)
if alignment == "flex-start" or alignment == "flex-end" or alignment == "center" or alignment == "space-between" or alignment == "space-around" then
alignItems = alignment
applyLayout(self)
end
setSpacing = function(self, value)
spacing = value
updateLayout = true
return self
end,
getSpacing = function(self)
return spacing
end,
setFlexWrap = function(self, value)
wrap = value
updateLayout = true
return self
end,
updateLayout = function(self)
updateLayout = true
end,
addBreak = function(self)
table.insert(children, lineBreakFakeObject)
updateLayout = true
return self
end,
draw = function(self)
base.draw(self)
self:addDraw("flexboxDraw", function()
if updateLayout then
applyLayout(self)
end
end, 1)
end
}
for objectName, _ in pairs(basalt.getObjects()) do
object["add" .. objectName] = function(self, id)
local obj = base["add" .. objectName](self, id)
applyLayout(base)
return obj
for k, _ in pairs(basalt.getObjects()) do
object["add" .. k] = function(self, name)
local child = flexObjectPlugin(base["add" .. k](self, name), basalt)
table.insert(children, child)
updateLayout = true
return child
end
end
object.__index = object
return setmetatable(object, base)
end
end