Adjust autosizing to better account for text wrapping.
This commit is contained in:
@@ -634,6 +634,55 @@ function LayoutEngine:layoutChildren()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Simulate wrapping children into lines for auto-sizing calculations
|
||||||
|
---@param children table Array of child elements
|
||||||
|
---@param availableSize number Available space in main axis
|
||||||
|
---@param isHorizontal boolean True if flex direction is horizontal
|
||||||
|
---@return table Array of lines, where each line is an array of children
|
||||||
|
function LayoutEngine:_simulateWrap(children, availableSize, isHorizontal)
|
||||||
|
local lines = {}
|
||||||
|
local currentLine = {}
|
||||||
|
local currentLineSize = 0
|
||||||
|
|
||||||
|
for _, child in ipairs(children) do
|
||||||
|
-- Calculate child size in main axis (including margins)
|
||||||
|
local childMainSize = 0
|
||||||
|
local childMainMargin = 0
|
||||||
|
if isHorizontal then
|
||||||
|
childMainSize = child:getBorderBoxWidth()
|
||||||
|
if child.margin then
|
||||||
|
childMainMargin = child.margin.left + child.margin.right
|
||||||
|
end
|
||||||
|
else
|
||||||
|
childMainSize = child:getBorderBoxHeight()
|
||||||
|
if child.margin then
|
||||||
|
childMainMargin = child.margin.top + child.margin.bottom
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local childTotalMainSize = childMainSize + childMainMargin
|
||||||
|
|
||||||
|
-- Check if adding this child would exceed the available space
|
||||||
|
local lineSpacing = #currentLine > 0 and self.gap or 0
|
||||||
|
if #currentLine > 0 and currentLineSize + lineSpacing + childTotalMainSize > availableSize then
|
||||||
|
-- Start a new line
|
||||||
|
table.insert(lines, currentLine)
|
||||||
|
currentLine = { child }
|
||||||
|
currentLineSize = childTotalMainSize
|
||||||
|
else
|
||||||
|
-- Add to current line
|
||||||
|
table.insert(currentLine, child)
|
||||||
|
currentLineSize = currentLineSize + lineSpacing + childTotalMainSize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add the last line if it has children
|
||||||
|
if #currentLine > 0 then
|
||||||
|
table.insert(lines, currentLine)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lines
|
||||||
|
end
|
||||||
|
|
||||||
--- Calculate auto width based on children
|
--- Calculate auto width based on children
|
||||||
---@return number
|
---@return number
|
||||||
function LayoutEngine:calculateAutoWidth()
|
function LayoutEngine:calculateAutoWidth()
|
||||||
@@ -647,32 +696,72 @@ function LayoutEngine:calculateAutoWidth()
|
|||||||
return contentWidth
|
return contentWidth
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For HORIZONTAL flex: sum children widths + gaps
|
-- Get flex children (children that participate in flex layout)
|
||||||
-- For VERTICAL flex: max of children widths
|
local flexChildren = {}
|
||||||
local isHorizontal = self.flexDirection == self._FlexDirection.HORIZONTAL
|
|
||||||
local totalWidth = contentWidth
|
|
||||||
local maxWidth = contentWidth
|
|
||||||
local participatingChildren = 0
|
|
||||||
|
|
||||||
for _, child in ipairs(self.element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
-- BORDER-BOX MODEL: Use border-box width for auto-sizing calculations
|
table.insert(flexChildren, child)
|
||||||
local childBorderBoxWidth = child:getBorderBoxWidth()
|
|
||||||
if isHorizontal then
|
|
||||||
totalWidth = totalWidth + childBorderBoxWidth
|
|
||||||
else
|
|
||||||
maxWidth = math.max(maxWidth, childBorderBoxWidth)
|
|
||||||
end
|
|
||||||
participatingChildren = participatingChildren + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #flexChildren == 0 then
|
||||||
|
return contentWidth
|
||||||
|
end
|
||||||
|
|
||||||
|
local isHorizontal = self.flexDirection == self._FlexDirection.HORIZONTAL
|
||||||
|
|
||||||
if isHorizontal then
|
if isHorizontal then
|
||||||
-- Add gaps between children (n-1 gaps for n children)
|
-- HORIZONTAL flex with potential wrapping
|
||||||
local gapCount = math.max(0, participatingChildren - 1)
|
if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.width and self.element.width > 0 then
|
||||||
return totalWidth + (self.gap * gapCount)
|
-- Container has explicit width and wrapping enabled - calculate based on wrapped lines
|
||||||
|
local availableWidth = self.element.width
|
||||||
|
local lines = self:_simulateWrap(flexChildren, availableWidth, true)
|
||||||
|
|
||||||
|
-- Find the widest line
|
||||||
|
local maxLineWidth = contentWidth
|
||||||
|
for _, line in ipairs(lines) do
|
||||||
|
local lineWidth = 0
|
||||||
|
for i, child in ipairs(line) do
|
||||||
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
|
local childMarginH = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginH = child.margin.left + child.margin.right
|
||||||
|
end
|
||||||
|
lineWidth = lineWidth + childBorderBoxWidth + childMarginH
|
||||||
|
if i < #line then
|
||||||
|
lineWidth = lineWidth + self.gap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
maxLineWidth = math.max(maxLineWidth, lineWidth)
|
||||||
|
end
|
||||||
|
return maxLineWidth
|
||||||
else
|
else
|
||||||
|
-- No wrapping or no explicit width - sum all children on one line
|
||||||
|
local totalWidth = contentWidth
|
||||||
|
for i, child in ipairs(flexChildren) do
|
||||||
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
|
local childMarginH = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginH = child.margin.left + child.margin.right
|
||||||
|
end
|
||||||
|
totalWidth = totalWidth + childBorderBoxWidth + childMarginH
|
||||||
|
if i < #flexChildren then
|
||||||
|
totalWidth = totalWidth + self.gap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return totalWidth
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- VERTICAL flex - return max child width (including margins)
|
||||||
|
local maxWidth = contentWidth
|
||||||
|
for _, child in ipairs(flexChildren) do
|
||||||
|
local childBorderBoxWidth = child:getBorderBoxWidth()
|
||||||
|
local childMarginH = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginH = child.margin.left + child.margin.right
|
||||||
|
end
|
||||||
|
maxWidth = math.max(maxWidth, childBorderBoxWidth + childMarginH)
|
||||||
|
end
|
||||||
return maxWidth
|
return maxWidth
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -688,34 +777,100 @@ function LayoutEngine:calculateAutoHeight()
|
|||||||
return height
|
return height
|
||||||
end
|
end
|
||||||
|
|
||||||
-- For VERTICAL flex: sum children heights + gaps
|
-- Get flex children (children that participate in flex layout)
|
||||||
-- For HORIZONTAL flex: max of children heights
|
local flexChildren = {}
|
||||||
local isVertical = self.flexDirection == self._FlexDirection.VERTICAL
|
|
||||||
local totalHeight = height
|
|
||||||
local maxHeight = height
|
|
||||||
local participatingChildren = 0
|
|
||||||
|
|
||||||
for _, child in ipairs(self.element.children) do
|
for _, child in ipairs(self.element.children) do
|
||||||
-- Skip explicitly absolute positioned children as they don't affect parent auto-sizing
|
|
||||||
if not child._explicitlyAbsolute then
|
if not child._explicitlyAbsolute then
|
||||||
-- BORDER-BOX MODEL: Use border-box height for auto-sizing calculations
|
table.insert(flexChildren, child)
|
||||||
local childBorderBoxHeight = child:getBorderBoxHeight()
|
|
||||||
if isVertical then
|
|
||||||
totalHeight = totalHeight + childBorderBoxHeight
|
|
||||||
else
|
|
||||||
maxHeight = math.max(maxHeight, childBorderBoxHeight)
|
|
||||||
end
|
|
||||||
participatingChildren = participatingChildren + 1
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #flexChildren == 0 then
|
||||||
|
return height
|
||||||
|
end
|
||||||
|
|
||||||
|
local isVertical = self.flexDirection == self._FlexDirection.VERTICAL
|
||||||
|
|
||||||
if isVertical then
|
if isVertical then
|
||||||
-- Add gaps between children (n-1 gaps for n children)
|
-- VERTICAL flex with potential wrapping
|
||||||
local gapCount = math.max(0, participatingChildren - 1)
|
if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.height and self.element.height > 0 then
|
||||||
return totalHeight + (self.gap * gapCount)
|
-- Container has explicit height and wrapping enabled - calculate based on wrapped lines
|
||||||
|
local availableHeight = self.element.height
|
||||||
|
local lines = self:_simulateWrap(flexChildren, availableHeight, false)
|
||||||
|
|
||||||
|
-- Sum all line heights
|
||||||
|
local totalLinesHeight = height
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
local lineHeight = 0
|
||||||
|
for _, child in ipairs(line) do
|
||||||
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
|
local childMarginV = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginV = child.margin.top + child.margin.bottom
|
||||||
|
end
|
||||||
|
lineHeight = math.max(lineHeight, childBorderBoxHeight + childMarginV)
|
||||||
|
end
|
||||||
|
totalLinesHeight = totalLinesHeight + lineHeight
|
||||||
|
if i < #lines then
|
||||||
|
totalLinesHeight = totalLinesHeight + self.gap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return totalLinesHeight
|
||||||
else
|
else
|
||||||
|
-- No wrapping or no explicit height - sum all children on one line
|
||||||
|
local totalHeight = height
|
||||||
|
for i, child in ipairs(flexChildren) do
|
||||||
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
|
local childMarginV = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginV = child.margin.top + child.margin.bottom
|
||||||
|
end
|
||||||
|
totalHeight = totalHeight + childBorderBoxHeight + childMarginV
|
||||||
|
if i < #flexChildren then
|
||||||
|
totalHeight = totalHeight + self.gap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return totalHeight
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- HORIZONTAL flex with potential wrapping
|
||||||
|
if self.flexWrap ~= self._FlexWrap.NOWRAP and self.element.width and self.element.width > 0 then
|
||||||
|
-- Container has explicit width and wrapping enabled - calculate based on wrapped lines
|
||||||
|
local availableWidth = self.element.width
|
||||||
|
local lines = self:_simulateWrap(flexChildren, availableWidth, true)
|
||||||
|
|
||||||
|
-- Sum all line heights (cross-axis for horizontal flex)
|
||||||
|
local totalLinesHeight = height
|
||||||
|
for i, line in ipairs(lines) do
|
||||||
|
local lineHeight = 0
|
||||||
|
for _, child in ipairs(line) do
|
||||||
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
|
local childMarginV = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginV = child.margin.top + child.margin.bottom
|
||||||
|
end
|
||||||
|
lineHeight = math.max(lineHeight, childBorderBoxHeight + childMarginV)
|
||||||
|
end
|
||||||
|
totalLinesHeight = totalLinesHeight + lineHeight
|
||||||
|
if i < #lines then
|
||||||
|
totalLinesHeight = totalLinesHeight + self.gap
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return totalLinesHeight
|
||||||
|
else
|
||||||
|
-- No wrapping or no explicit width - return max child height (including margins)
|
||||||
|
local maxHeight = height
|
||||||
|
for _, child in ipairs(flexChildren) do
|
||||||
|
local childBorderBoxHeight = child:getBorderBoxHeight()
|
||||||
|
local childMarginV = 0
|
||||||
|
if child.margin then
|
||||||
|
childMarginV = child.margin.top + child.margin.bottom
|
||||||
|
end
|
||||||
|
maxHeight = math.max(maxHeight, childBorderBoxHeight + childMarginV)
|
||||||
|
end
|
||||||
return maxHeight
|
return maxHeight
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Recalculate units based on new viewport dimensions (for vw, vh, % units)
|
--- Recalculate units based on new viewport dimensions (for vw, vh, % units)
|
||||||
|
|||||||
@@ -1737,8 +1737,12 @@ function TestElementAutoSizing:test_autosize_with_margin()
|
|||||||
parent = parent,
|
parent = parent,
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Parent should size to children (margins don't add to content size in flex layout)
|
-- Parent should size to children including margins (flexbox includes margins in sizing)
|
||||||
luaunit.assertEquals(parent.width, 200)
|
-- Child1: 100px + 20px right margin = 120px
|
||||||
|
-- Child2: 20px left margin + 100px = 120px
|
||||||
|
-- Total width: 240px
|
||||||
|
-- Max height: 100px (no vertical margins)
|
||||||
|
luaunit.assertEquals(parent.width, 240)
|
||||||
luaunit.assertEquals(parent.height, 100)
|
luaunit.assertEquals(parent.height, 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user