diff --git a/Basalt/objects/Flexbox.lua b/Basalt/objects/Flexbox.lua index 8411266..9915570 100644 --- a/Basalt/objects/Flexbox.lua +++ b/Basalt/objects/Flexbox.lua @@ -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 \ No newline at end of file +end +