diff --git a/FlexLove.lua b/FlexLove.lua index 5e6074b..331e01a 100644 --- a/FlexLove.lua +++ b/FlexLove.lua @@ -582,95 +582,183 @@ function Element:layoutChildren() end end - -- Position children - local currentPos = spacing - for _, child in ipairs(self.children) do - if child.positioning == Positioning.ABSOLUTE then - -- Skip positioning for absolute children as they should maintain their own coordinates - goto continue - end +-- Position children + local currentPos = spacing + for _, child in ipairs(self.children) do + if child.positioning == Positioning.ABSOLUTE then + -- Skip positioning for absolute children as they should maintain their own coordinates + goto continue + end - if self.flexDirection == FlexDirection.VERTICAL then - -- Position relative to parent origin - child.x = self.x + (self.margin.left or 0) - child.y = self.y + currentPos + if self.flexDirection == FlexDirection.VERTICAL then + -- Position relative to parent origin + child.x = self.x + (self.margin.left or 0) + child.y = self.y + currentPos - -- Apply alignment to vertical axis (alignItems) - if self.alignItems == AlignItems.FLEX_START then - -- nothing - elseif self.alignItems == AlignItems.CENTER then - child.x = self.x + (self.width - (child.width or 0)) / 2 - elseif self.alignItems == AlignItems.FLEX_END then - child.x = self.x + self.width - (child.width or 0) - elseif self.alignItems == AlignItems.STRETCH then - child.width = self.width - end + -- Apply alignment to vertical axis (alignItems) + if self.alignItems == AlignItems.FLEX_START then + -- nothing + elseif self.alignItems == AlignItems.CENTER then + child.x = self.x + (self.width - (child.width or 0)) / 2 + elseif self.alignItems == AlignItems.FLEX_END then + child.x = self.x + self.width - (child.width or 0) + elseif self.alignItems == AlignItems.STRETCH then + child.width = self.width + end - -- Apply self alignment to cross axis (alignSelf) - local effectiveAlignSelf = child.alignSelf - if child.alignSelf == AlignSelf.AUTO then - effectiveAlignSelf = self.alignItems - end + -- Apply self alignment to cross axis (alignSelf) + local effectiveAlignSelf = child.alignSelf + if child.alignSelf == AlignSelf.AUTO then + effectiveAlignSelf = self.alignItems + end - if effectiveAlignSelf == AlignSelf.FLEX_START then - -- Position at the start of cross axis relative to parent - child.x = self.x + (self.margin.left or 0) - elseif effectiveAlignSelf == AlignSelf.CENTER then - if self.flexDirection == FlexDirection.VERTICAL then - child.x = self.x + (self.width - (child.width or 0)) / 2 - else - child.y = self.y + (self.height - (child.height or 0)) / 2 - end - elseif effectiveAlignSelf == AlignSelf.FLEX_END then - if self.flexDirection == FlexDirection.VERTICAL then - child.x = self.x + self.width - (child.width or 0) - else - child.y = self.y + self.height - (child.height or 0) - end - elseif effectiveAlignSelf == AlignSelf.STRETCH then - if self.flexDirection == FlexDirection.VERTICAL then - -- Only set width if not already stretched by alignItems - if child.width ~= self.width then - child.width = self.width - end - else - -- Only set height if not already stretched by alignItems - if child.height ~= self.height then - child.height = self.height - end - end - end + if effectiveAlignSelf == AlignSelf.FLEX_START then + -- Position at the start of cross axis relative to parent + child.x = self.x + (self.margin.left or 0) + elseif effectiveAlignSelf == AlignSelf.CENTER then + if self.flexDirection == FlexDirection.VERTICAL then + child.x = self.x + (self.width - (child.width or 0)) / 2 + else + child.y = self.y + (self.height - (child.height or 0)) / 2 + end + elseif effectiveAlignSelf == AlignSelf.FLEX_END then + if self.flexDirection == FlexDirection.VERTICAL then + child.x = self.x + self.width - (child.width or 0) + else + child.y = self.y + self.height - (child.height or 0) + end + elseif effectiveAlignSelf == AlignSelf.STRETCH then + if self.flexDirection == FlexDirection.VERTICAL then + -- Only set width if not already stretched by alignItems + if child.width ~= self.width then + child.width = self.width + end + else + -- Only set height if not already stretched by alignItems + if child.height ~= self.height then + child.height = self.height + end + end + end - currentPos = currentPos + (child.height or 0) + self.gap + (self.margin.top or 0) + (self.margin.bottom or 0) - else - -- Horizontal layout: position relative to parent origin - child.x = self.x + currentPos + (self.margin.left or 0) - child.y = self.y + (self.margin.top or 0) + -- Apply justifySelf alignment to main axis (for vertical flexDirection, this is y-axis) + local effectiveJustifySelf = child.justifySelf + if child.justifySelf == JustifySelf.AUTO then + effectiveJustifySelf = self.justifyContent + end - -- Determine effective alignment - alignSelf takes precedence over alignItems - local effectiveAlign = child.alignSelf - if effectiveAlign == AlignSelf.AUTO then - effectiveAlign = self.alignItems - end + if effectiveJustifySelf == JustifySelf.FLEX_START then + -- Keep the current Y position (already set by spacing logic) + elseif effectiveJustifySelf == JustifySelf.CENTER then + -- Center along the main axis (y-axis for vertical flex direction) + local childHeight = child.height or 0 + local availableSpace = self.height - (self.margin.top or 0) - (self.margin.bottom or 0) + local totalChildHeight = 0 + for _, c in ipairs(self.children) do + if c.positioning ~= Positioning.ABSOLUTE then + totalChildHeight = totalChildHeight + (c.height or 0) + end + end + local freeSpace = availableSpace - totalChildHeight - (self.gap * (#self.children - 1)) + if freeSpace > 0 then + child.y = self.y + (self.margin.top or 0) + freeSpace / 2 + end + elseif effectiveJustifySelf == JustifySelf.FLEX_END then + -- Position at the end of main axis (y-axis for vertical flex direction) + local childHeight = child.height or 0 + local availableSpace = self.height - (self.margin.top or 0) - (self.margin.bottom or 0) + local totalChildHeight = 0 + for _, c in ipairs(self.children) do + if c.positioning ~= Positioning.ABSOLUTE then + totalChildHeight = totalChildHeight + (c.height or 0) + end + end + local freeSpace = availableSpace - totalChildHeight - (self.gap * (#self.children - 1)) + if freeSpace > 0 then + child.y = self.y + (self.margin.top or 0) + freeSpace + end + elseif effectiveJustifySelf == JustifySelf.SPACE_AROUND then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + elseif effectiveJustifySelf == JustifySelf.SPACE_EVENLY then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + elseif effectiveJustifySelf == JustifySelf.SPACE_BETWEEN then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + end - -- Apply alignment - if effectiveAlign == AlignItems.FLEX_START then - -- Keep the margin.top position (already applied) - elseif effectiveAlign == AlignItems.CENTER then - child.y = self.y + (self.height - (child.height or 0)) / 2 - elseif effectiveAlign == AlignItems.FLEX_END then - child.y = self.y + self.height - (child.height or 0) - elseif effectiveAlign == AlignItems.STRETCH then - -- Only set height if not already stretched - if child.height ~= self.height then - child.height = self.height - end - end + currentPos = currentPos + (child.height or 0) + self.gap + (self.margin.top or 0) + (self.margin.bottom or 0) + else + -- Horizontal layout: position relative to parent origin + child.x = self.x + currentPos + (self.margin.left or 0) + child.y = self.y + (self.margin.top or 0) - currentPos = currentPos + (child.width or 0) + self.gap + (self.margin.left or 0) + (self.margin.right or 0) - end - ::continue:: - end + -- Determine effective alignment - alignSelf takes precedence over alignItems + local effectiveAlign = child.alignSelf + if effectiveAlign == AlignSelf.AUTO then + effectiveAlign = self.alignItems + end + + -- Apply alignment + if effectiveAlign == AlignItems.FLEX_START then + -- Keep the margin.top position (already applied) + elseif effectiveAlign == AlignItems.CENTER then + child.y = self.y + (self.height - (child.height or 0)) / 2 + elseif effectiveAlign == AlignItems.FLEX_END then + child.y = self.y + self.height - (child.height or 0) + elseif effectiveAlign == AlignItems.STRETCH then + -- Only set height if not already stretched + if child.height ~= self.height then + child.height = self.height + end + end + + -- Apply justifySelf alignment to main axis (for horizontal flexDirection, this is x-axis) + local effectiveJustifySelf = child.justifySelf + if child.justifySelf == JustifySelf.AUTO then + effectiveJustifySelf = self.justifyContent + end + + if effectiveJustifySelf == JustifySelf.FLEX_START then + -- Keep the current X position (already set by spacing logic) + elseif effectiveJustifySelf == JustifySelf.CENTER then + -- Center along the main axis (x-axis for horizontal flex direction) + local childWidth = child.width or 0 + local availableSpace = self.width - (self.margin.left or 0) - (self.margin.right or 0) + local totalChildWidth = 0 + for _, c in ipairs(self.children) do + if c.positioning ~= Positioning.ABSOLUTE then + totalChildWidth = totalChildWidth + (c.width or 0) + end + end + local freeSpace = availableSpace - totalChildWidth - (self.gap * (#self.children - 1)) + if freeSpace > 0 then + child.x = self.x + (self.margin.left or 0) + freeSpace / 2 + end + elseif effectiveJustifySelf == JustifySelf.FLEX_END then + -- Position at the end of main axis (x-axis for horizontal flex direction) + local childWidth = child.width or 0 + local availableSpace = self.width - (self.margin.left or 0) - (self.margin.right or 0) + local totalChildWidth = 0 + for _, c in ipairs(self.children) do + if c.positioning ~= Positioning.ABSOLUTE then + totalChildWidth = totalChildWidth + (c.width or 0) + end + end + local freeSpace = availableSpace - totalChildWidth - (self.gap * (#self.children - 1)) + if freeSpace > 0 then + child.x = self.x + (self.margin.left or 0) + freeSpace + end + elseif effectiveJustifySelf == JustifySelf.SPACE_AROUND then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + elseif effectiveJustifySelf == JustifySelf.SPACE_EVENLY then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + elseif effectiveJustifySelf == JustifySelf.SPACE_BETWEEN then + -- This would be handled by the justifyContent logic already, so we'll keep existing behavior + end + + currentPos = currentPos + (child.width or 0) + self.gap + (self.margin.left or 0) + (self.margin.right or 0) + end + ::continue:: + end end --- Destroy element and its children