Removed all goto statements

Fixed the parameter order in observe
Used the correct internal property approach
Did not use the built-in visibleChildren, as we need to handle elements that are obscured or completely removed
This commit is contained in:
megaSukura
2025-04-20 05:40:24 +08:00
parent 7df5380d18
commit 2fc72a6a13

View File

@@ -68,17 +68,15 @@ local lineBreakElement = {
getVisible = function(self) return true end,
}
local function sortElements(self, direction, spacing, wrap)
-- Pre-allocate tables to reduce dynamic expansion
local elements = self.get("children")
local sortedElements = {}
local visibleElements = {}
local childCount = 0
-- First gather all visible elements
for _, elem in pairs(elements) do
if elem:getVisible() then
-- We can't use self.get("visibleChildren") here
--because it would exclude elements that are obscured
for _, elem in pairs(self.get("children")) do
if elem.get("visible") then
table.insert(visibleElements, elem)
if elem ~= lineBreakElement then
childCount = childCount + 1
@@ -86,19 +84,16 @@ local function sortElements(self, direction, spacing, wrap)
end
end
-- No visible elements, nothing to layout
if childCount == 0 then
return sortedElements
end
-- Use known size to pre-allocate array
if not wrap then
-- No-wrap mode, all elements in one row/column
sortedElements[1] = {offset=1}
for _, elem in ipairs(visibleElements) do
if elem == lineBreakElement then
-- Create new line
local nextIndex = #sortedElements + 1
if sortedElements[nextIndex] == nil then
sortedElements[nextIndex] = {offset=1}
@@ -108,41 +103,32 @@ local function sortElements(self, direction, spacing, wrap)
end
end
else
-- Wrap mode, need to calculate rows/columns more optimally
local containerSize = direction == "row" and self.get("width") or self.get("height")
-- First split elements by line breaks
local segments = {{}}
local currentSegment = 1
for _, elem in ipairs(visibleElements) do
if elem == lineBreakElement then
-- Start a new segment
currentSegment = currentSegment + 1
segments[currentSegment] = {}
else
-- Add to current segment
table.insert(segments[currentSegment], elem)
end
end
-- Now process each segment optimally
for segmentIndex, segment in ipairs(segments) do
if #segment == 0 then
-- Empty segment (consecutive line breaks)
sortedElements[#sortedElements + 1] = {offset=1}
else
-- Try to pack elements optimally within this segment
local rows = {}
local currentRow = {}
local currentWidth = 0
for _, elem in ipairs(segment) do
-- Get intrinsic size if available, otherwise use current size
local intrinsicSize = 0
local currentSize = direction == "row" and elem.get("width") or elem.get("height")
-- Try to get intrinsic size, safely
local hasIntrinsic = false
if direction == "row" then
local ok, intrinsicWidth = pcall(function() return elem.get("intrinsicWidth") end)
@@ -158,35 +144,28 @@ local function sortElements(self, direction, spacing, wrap)
end
end
-- Fall back to current size if no intrinsic size
local elemSize = hasIntrinsic and intrinsicSize or currentSize
local spaceNeeded = elemSize
-- Add spacing if not first element in row
if #currentRow > 0 then
spaceNeeded = spaceNeeded + spacing
end
-- Check if element fits in current row
if currentWidth + spaceNeeded <= containerSize or #currentRow == 0 then
-- Element fits or it's first element (must place even if too large)
table.insert(currentRow, elem)
currentWidth = currentWidth + spaceNeeded
else
-- Element doesn't fit, start new row
table.insert(rows, currentRow)
currentRow = {elem}
currentWidth = elemSize
end
end
-- Don't forget the last row
if #currentRow > 0 then
table.insert(rows, currentRow)
end
-- Add rows to sorted elements
for _, row in ipairs(rows) do
sortedElements[#sortedElements + 1] = {offset=1}
for _, elem in ipairs(row) do
@@ -197,7 +176,6 @@ local function sortElements(self, direction, spacing, wrap)
end
end
-- Filter out empty rows/columns
local filteredElements = {}
for i, rowOrColumn in ipairs(sortedElements) do
if #rowOrColumn > 0 then
@@ -640,44 +618,27 @@ end
-- Optimize updateLayout function
local function updateLayout(self, direction, spacing, justifyContent, wrap)
-- Check essential properties for layout
if self.get("width") <= 0 or self.get("height") <= 0 then
return
end
-- Force direction to be valid
direction = (direction == "row" or direction == "column") and direction or "row"
-- Check if container size has changed since last layout
local currentWidth, currentHeight = self.get("width"), self.get("height")
local lastWidth = self.get("_lastLayoutWidth") or 0
local lastHeight = self.get("_lastLayoutHeight") or 0
local sizeChanged = currentWidth ~= lastWidth or currentHeight ~= lastHeight
local sizeChanged = currentWidth ~= self._lastLayoutWidth or currentHeight ~= self._lastLayoutHeight
-- Store current size for next comparison
self.set("_lastLayoutWidth", currentWidth)
self.set("_lastLayoutHeight", currentHeight)
self._lastLayoutWidth = currentWidth
self._lastLayoutHeight = currentHeight
-- If container size increased, we might need to reset flexGrow items to recalculate
if wrap and sizeChanged and (currentWidth > lastWidth or currentHeight > lastHeight) then
-- Get reference to all children
local allChildren = self.get("children")
-- Reset flex items to intrinsic size temporarily to allow reflow
for _, child in pairs(allChildren) do
if wrap and sizeChanged and (currentWidth > self._lastLayoutWidth or currentHeight > self._lastLayoutHeight) then
for _, child in pairs(self.get("children")) do
if child ~= lineBreakElement and child:getVisible() and child.get("flexGrow") and child.get("flexGrow") > 0 then
if direction == "row" then
-- Store the actual width temporarily
local actualWidth = child.get("width")
-- Reset to intrinsic width for layout calculation
local ok, value = pcall(function() return child.get("intrinsicWidth") end)
if ok and value then
child.set("width", value)
end
else
-- Store the actual height temporarily
local actualHeight = child.get("height")
-- Reset to intrinsic height for layout calculation
local ok, value = pcall(function() return child.get("intrinsicHeight") end)
if ok and value then
child.set("height", value)
@@ -687,94 +648,70 @@ local function updateLayout(self, direction, spacing, justifyContent, wrap)
end
end
-- Get all elements that need layout
local elements = sortElements(self, direction, spacing, wrap)
if #elements == 0 then return end
-- Debug: Check what elements were found
if #elements == 0 then
return -- No elements to layout
end
-- Based on direction, select layout function, avoid checking every iteration
local layoutFunction = direction == "row" and calculateRow or calculateColumn
-- Apply layout calculation with vertical offset
if direction == "row" and wrap then
-- In row direction with wrap, we need to offset each row vertically
local currentY = 1
for i, rowOrColumn in ipairs(elements) do
-- Skip empty rows
if #rowOrColumn == 0 then goto continue end
-- First, set the vertical offset for this row
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
element.set("y", currentY)
if #rowOrColumn > 0 then
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
element.set("y", currentY)
end
end
layoutFunction(self, rowOrColumn, spacing, justifyContent)
local rowHeight = 0
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
rowHeight = math.max(rowHeight, element.get("height"))
end
end
if i < #elements then
currentY = currentY + rowHeight + spacing
else
currentY = currentY + rowHeight
end
end
-- Apply the row layout
layoutFunction(self, rowOrColumn, spacing, justifyContent)
-- Calculate height for this row (maximum element height)
local rowHeight = 0
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
rowHeight = math.max(rowHeight, element.get("height"))
end
end
-- Move to next row (add spacing only if not the last row)
if i < #elements then
currentY = currentY + rowHeight + spacing
else
currentY = currentY + rowHeight
end
::continue::
end
elseif direction == "column" and wrap then
-- In column direction with wrap, we need to offset each column horizontally
local currentX = 1
for i, rowOrColumn in ipairs(elements) do
-- Skip empty columns
if #rowOrColumn == 0 then goto continue end
-- First, set the horizontal offset for this column
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
element.set("x", currentX)
if #rowOrColumn > 0 then
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
element.set("x", currentX)
end
end
layoutFunction(self, rowOrColumn, spacing, justifyContent)
local columnWidth = 0
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
columnWidth = math.max(columnWidth, element.get("width"))
end
end
if i < #elements then
currentX = currentX + columnWidth + spacing
else
currentX = currentX + columnWidth
end
end
-- Apply the column layout
layoutFunction(self, rowOrColumn, spacing, justifyContent)
-- Calculate width for this column (maximum element width)
local columnWidth = 0
for _, element in ipairs(rowOrColumn) do
if element ~= lineBreakElement then
columnWidth = math.max(columnWidth, element.get("width"))
end
end
-- Move to next column (add spacing only if not the last column)
if i < #elements then
currentX = currentX + columnWidth + spacing
else
currentX = currentX + columnWidth
end
::continue::
end
else
-- Simple case: no wrapping
for i, rowOrColumn in ipairs(elements) do
for _, rowOrColumn in ipairs(elements) do
layoutFunction(self, rowOrColumn, spacing, justifyContent)
end
end
-- Reset layout update flag
self:sortChildren()
self.set("childrenEventsSorted", false)
self.set("flexUpdateLayout", false)
end
@@ -789,11 +726,9 @@ function Flexbox.new()
self.set("background", colors.blue)
self.set("z", 10)
-- Add instance properties for layout tracking
self:instanceProperty("_lastLayoutWidth", {default = 0, type = "number"})
self:instanceProperty("_lastLayoutHeight", {default = 0, type = "number"})
self._lastLayoutWidth = 0
self._lastLayoutHeight = 0
-- Add observers for properties that affect layout
self:observe("width", function() self.set("flexUpdateLayout", true) end)
self:observe("height", function() self.set("flexUpdateLayout", true) end)
self:observe("flexDirection", function() self.set("flexUpdateLayout", true) end)
@@ -831,20 +766,18 @@ function Flexbox:addChild(element)
element:instanceProperty("intrinsicWidth", {default = element.get("width"), type = "number"})
element:instanceProperty("intrinsicHeight", {default = element.get("height"), type = "number"})
-- Add observer to child element's flexGrow and flexShrink properties
element:observe("flexGrow", function() self.set("flexUpdateLayout", true) end)
element:observe("flexShrink", function() self.set("flexUpdateLayout", true) end)
-- Add observer for size changes to track intrinsic size
element:observe("width", function(_, oldW, newW)
element:observe("width", function(_, newValue, oldValue)
if element.get("flexGrow") == 0 then
element.set("intrinsicWidth", newW)
element.set("intrinsicWidth", newValue)
end
self.set("flexUpdateLayout", true)
end)
element:observe("height", function(_, oldH, newH)
element:observe("height", function(_, newValue, oldValue)
if element.get("flexGrow") == 0 then
element.set("intrinsicHeight", newH)
element.set("intrinsicHeight", newValue)
end
self.set("flexUpdateLayout", true)
end)