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