local elementManager = require("elementManager") local Container = elementManager.getElement("Container") ---@class Flexbox : Container local Flexbox = setmetatable({}, Container) Flexbox.__index = Flexbox Flexbox.defineProperty(Flexbox, "flexDirection", {default = "row", type = "string"}) Flexbox.defineProperty(Flexbox, "flexSpacing", {default = 1, type = "number"}) Flexbox.defineProperty(Flexbox, "flexJustifyContent", { default = "flex-start", type = "string", setter = function(self, value) if not value:match("^flex%-") then value = "flex-" .. value end return value end }) Flexbox.defineProperty(Flexbox, "flexWrap", {default = false, type = "boolean"}) Flexbox.defineProperty(Flexbox, "flexUpdateLayout", {default = false, type = "boolean"}) local lineBreakElement = { getHeight = function(self) return 0 end, getWidth = function(self) return 0 end, getZ = function(self) return 1 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 "lineBreak" end, getName = function(self) return "lineBreak" end, setPosition = function(self) end, setParent = function(self) end, setSize = function(self) end, getFlexGrow = function(self) return 0 end, getFlexShrink = function(self) return 0 end, getFlexBasis = function(self) return 0 end, init = function(self) end, getVisible = function(self) return true end, } local function sortElements(self, direction, spacing, wrap) local elements = self.get("children") local sortedElements = {} if not(wrap)then local index = 1 local lineSize = 1 local lineOffset = 1 for _,v in pairs(elements)do if(sortedElements[index]==nil)then sortedElements[index]={offset=1} end local childHeight = direction == "row" and v.get("height") or v.get("width") if childHeight > lineSize then lineSize = childHeight end if(v == lineBreakElement)then lineOffset = lineOffset + lineSize + spacing lineSize = 1 index = index + 1 sortedElements[index] = {offset=lineOffset} else table.insert(sortedElements[index], v) end end elseif(wrap)then local lineSize = 1 local lineOffset = 1 local maxSize = direction == "row" and self.get("width") or self.get("height") local usedSize = 0 local index = 1 for _,v in pairs(elements) do if(sortedElements[index]==nil) then sortedElements[index]={offset=1} end if v:getType() == "lineBreak" then lineOffset = lineOffset + lineSize + spacing usedSize = 0 lineSize = 1 index = index + 1 sortedElements[index] = {offset=lineOffset} else local objSize = direction == "row" and v.get("width") or v.get("height") if(objSize+usedSize<=maxSize) then table.insert(sortedElements[index], v) usedSize = usedSize + objSize + spacing else lineOffset = lineOffset + lineSize + spacing lineSize = direction == "row" and v.get("height") or v.get("width") index = index + 1 usedSize = objSize + spacing sortedElements[index] = {offset=lineOffset, v} end local childHeight = direction == "row" and v.get("height") or v.get("width") if childHeight > lineSize then lineSize = childHeight end end end end return sortedElements end local function calculateRow(self, children, spacing, justifyContent) local containerWidth = self.get("width") local usedSpace = spacing * (#children - 1) local totalFlexGrow = 0 for _, child in ipairs(children) do if child ~= lineBreakElement then usedSpace = usedSpace + child.get("width") totalFlexGrow = totalFlexGrow + child.get("flexGrow") end end local remainingSpace = containerWidth - usedSpace local extraSpacePerUnit = totalFlexGrow > 0 and (remainingSpace / totalFlexGrow) or 0 local distributedSpace = 0 local currentX = 1 for i, child in ipairs(children) do if child ~= lineBreakElement then local childWidth = child.get("width") if child.get("flexGrow") > 0 then if i == #children then local extraSpace = remainingSpace - distributedSpace childWidth = childWidth + extraSpace else local extraSpace = math.floor(extraSpacePerUnit * child.get("flexGrow")) childWidth = childWidth + extraSpace distributedSpace = distributedSpace + extraSpace end end child.set("x", currentX) child.set("y", children.offset or 1) child.set("width", childWidth) currentX = currentX + childWidth + spacing end end if justifyContent == "flex-end" then local offset = containerWidth - (currentX - spacing - 1) for _, child in ipairs(children) do child.set("x", child.get("x") + offset) end elseif justifyContent == "flex-center" or justifyContent == "center" then -- Akzeptiere beide Formate local offset = math.floor((containerWidth - (currentX - spacing - 1)) / 2) for _, child in ipairs(children) do child.set("x", child.get("x") + offset) end end end local function calculateColumn(self, children, spacing, justifyContent) local containerHeight = self.get("height") local usedSpace = spacing * (#children - 1) local totalFlexGrow = 0 for _, child in ipairs(children) do if child ~= lineBreakElement then usedSpace = usedSpace + child.get("height") totalFlexGrow = totalFlexGrow + child.get("flexGrow") end end local remainingSpace = containerHeight - usedSpace local extraSpacePerUnit = totalFlexGrow > 0 and (remainingSpace / totalFlexGrow) or 0 local distributedSpace = 0 local currentY = 1 for i, child in ipairs(children) do if child ~= lineBreakElement then local childHeight = child.get("height") if child.get("flexGrow") > 0 then if i == #children then local extraSpace = remainingSpace - distributedSpace childHeight = childHeight + extraSpace else local extraSpace = math.floor(extraSpacePerUnit * child.get("flexGrow")) childHeight = childHeight + extraSpace distributedSpace = distributedSpace + extraSpace end end child.set("x", children.offset or 1) child.set("y", currentY) child.set("height", childHeight) currentY = currentY + childHeight + spacing end end if justifyContent == "flex-end" then local offset = containerHeight - (currentY - spacing - 1) for _, child in ipairs(children) do child.set("y", child.get("y") + offset) end elseif justifyContent == "flex-center" or justifyContent == "center" then -- Akzeptiere beide Formate local offset = math.floor((containerHeight - (currentY - spacing - 1)) / 2) for _, child in ipairs(children) do child.set("y", child.get("y") + offset) end end end local function updateLayout(self, direction, spacing, justifyContent, wrap) local elements = sortElements(self, direction, spacing, wrap) if direction == "row" then for _,v in pairs(elements)do calculateRow(self, v, spacing, justifyContent) end else for _,v in pairs(elements)do calculateColumn(self, v, spacing, justifyContent) end end self.set("flexUpdateLayout", false) end --- Creates a new Flexbox instance --- @return Flexbox object The newly created Flexbox instance --- @usage local element = Flexbox.new("myId", basalt) function Flexbox.new() local self = setmetatable({}, Flexbox):__init() self.set("width", 12) self.set("height", 6) self.set("background", colors.blue) self.set("z", 10) self:observe("width", function() self.set("flexUpdateLayout", true) end) self:observe("height", function() self.set("flexUpdateLayout", true) end) return self end function Flexbox:init(props, basalt) Container.init(self, props, basalt) self.set("type", "Flexbox") end function Flexbox:addChild(element) Container.addChild(self, element) if(element~=lineBreakElement)then element:instanceProperty("flexGrow", {default = 0, type = "number"}) element:instanceProperty("flexShrink", {default = 0, type = "number"}) element:instanceProperty("flexBasis", {default = 0, type = "number"}) end self.set("flexUpdateLayout", true) return self end function Flexbox:removeChild(element) Container.removeChild(self, element) if(element~=lineBreakElement)then element.setFlexGrow = nil element.setFlexShrink = nil element.setFlexBasis = nil element.getFlexGrow = nil element.getFlexShrink = nil element.getFlexBasis = nil element.set("flexGrow", nil) element.set("flexShrink", nil) element.set("flexBasis", nil) end self.set("flexUpdateLayout", true) return self end --- Adds a new line break to the flexbox. ---@param self Flexbox The element itself ---@return Flexbox function Flexbox:addLineBreak() self:addChild(lineBreakElement) return self end function Flexbox:render() if(self.get("flexUpdateLayout"))then updateLayout(self, self.get("flexDirection"), self.get("flexSpacing"), self.get("flexJustifyContent"), self.get("flexWrap")) end Container.render(self) end return Flexbox