Files
Basalt2/docs/references/elements/Flexbox.md
2025-02-16 14:12:49 +00:00

9.9 KiB

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